Esta documentación corresponde a la implementación actual de stowdb.js.
1. Descripción
stowdb.js es un SDK para consultar y modificar collections de StowDB sin escribir SQL ni construir manualmente el JSON enviado al backend.
La librería expone estas clases principales:
StowDB: configura la conexión y permite crear documentos.StowQuery: construye consultas encadenando métodos.StowCatalog: valida collections, campos, tipos, relaciones y capacidades.StowError: expone errores estructurados del SDK.
La clase StowDB queda disponible globalmente mediante:
globalThis.StowDB = StowDB;2. Instalación en una página HTML
StowDB no se distribuye mediante npm. Para producción, carga una versión fija desde el CDN oficial:
<script
src="https://stowdb.miramaxtech.com/sdk/v1/stowdb.js"
integrity="sha384-RcSZc4US9vOBliImnF3ioCIoO9a22UFflk5aLpGzSWN6TRbqATZMoyrM7nbt4PvW"
crossorigin="anonymous">
</script>También puedes descargar stowdb.js desde el portal y alojarlo junto a tu página:
<script src="./stowdb.js"></script>
<script>
const db = new StowDB(
"https://stowdb.miramaxtech.com/rest",
"vw_live_API_KEY",
"PROJECT_KEY"
);
</script>El endpoint debe escribirse sin /get, /set, /update o /delete. Una barra final es eliminada automáticamente.
3. Crear el cliente
const db = new StowDB(endpoint, apiKey, projectKey, options);Parámetros
| Parámetro | Tipo | Descripción |
|---|---|---|
endpoint | string | URL base de la API, por ejemplo https://stowdb.miramaxtech.com/rest. |
apiKey | string | API key usada como Bearer token. |
projectKey | string | Clave del proyecto enviada en X-Project-Key. |
Los tres valores son obligatorios y no pueden estar vacíos.
Opciones de conexión
const db = new StowDB(endpoint, apiKey, projectKey, {
timeout: 30000,
retries: 2,
retryDelay: 500,
retryBackoff: 2,
retryMutations: false
});| Opción | Predeterminado | Descripción |
|---|---|---|
timeout | 30000 | Tiempo máximo en milisegundos. 0 desactiva el timeout. |
retries | 2 | Reintentos para errores temporales, entre 0 y 10. |
retryDelay | 500 | Espera inicial entre reintentos, en milisegundos. |
retryBackoff | 2 | Multiplicador de la espera para cada intento. |
retryMutations | false | Permite reintentar create, update y delete. |
idempotencyKey | null | Clave enviada como Idempotency-Key para hacer una escritura repetible de forma segura. |
catalog | Sin catálogo | Definición de collections, campos, tipos, relaciones y capacidades. |
strictCatalog | false | Valida collections, campos, operadores y casts antes de enviar la petición. |
autoCast | false | Convierte automáticamente las filas según los tipos del catálogo. |
catalogCacheTtl | 300000 | Duración de la caché del catálogo en milisegundos. |
onRequest | null | Hook ejecutado antes de cada intento. |
onResponse | null | Hook ejecutado después de una respuesta correcta. |
onRetry | null | Hook ejecutado antes de reintentar. |
onError | null | Hook ejecutado cuando la petición falla definitivamente. |
Por seguridad, las escrituras no se reintentan de forma predeterminada porque una desconexión no garantiza que el servidor no haya aplicado la operación.
Ejemplo con catálogo:
const db = new StowDB(endpoint, apiKey, projectKey, {
catalog,
strictCatalog: true,
autoCast: true
});Cabeceras enviadas
Content-Type: application/json
Authorization: Bearer API_KEY
X-Project-Key: PROJECT_KEYEndpoints utilizados
| Operación | Endpoint |
|---|---|
| Consulta | POST /get |
| Creación | POST /set |
| Actualización | POST /update |
| Eliminación | POST /delete |
4. Consultar una collection
const result = await db
.from("orders")
.get();collection() es un alias de from():
const result = await db
.collection("orders")
.get();Los nombres de collections deben comenzar con letra o _ y solo pueden contener letras, números y _.
5. Seleccionar campos
select(...fields)
const result = await db
.from("orders")
.select("order_id", "status", "total")
.get();También acepta un array:
const fields = ["order_id", "status", "total"];
const result = await db
.from("orders")
.select(fields)
.get();Los campos sin collection son calificados automáticamente:
"status" // se convierte en "orders.status"También se puede proporcionar la referencia completa:
.select("orders.order_id", "customers.first_name")Alias para campos normales
const result = await db
.from("orders")
.select(
{ field: "order_id", as: "id" },
{ field: "total", as: "amount" }
)
.get();También puede usarse selectAs():
const result = await db
.from("orders")
.selectAs("order_id", "id")
.selectAs("total", "amount")
.get();Si no se llama a select(), el backend devuelve el documento completo según las reglas del parser.
6. DISTINCT
distinct(enabled = true)
const result = await db
.from("orders")
.select("status")
.distinct()
.get();Para desactivarlo explícitamente:
.distinct(false)El argumento debe ser booleano.
7. Condiciones WHERE
where(field, operator, value)
const result = await db
.from("orders")
.where("status", "=", "Paid")
.get();Varias llamadas a where() se combinan con AND:
const result = await db
.from("orders")
.where("status", "=", "Paid")
.where("total", ">", 500)
.get();Equivalente conceptual:
WHERE status = 'Paid' AND total > 500Operadores
El SDK acepta cualquier operador no vacío y lo envía en minúsculas. La disponibilidad final depende del parser del backend.
Operadores usados por StowDB:
| Operador | Ejemplo |
|---|---|
= | .where("status", "=", "Paid") |
!= o <> | .where("status", "!=", "Cancelled") |
> | .where("total", ">", 500) |
>= | .where("total", ">=", 500) |
< | .where("quantity", "<", 10) |
<= | .where("quantity", "<=", 10) |
like | .where("product", "like", "%Laptop%") |
not like | .where("product", "not like", "%Laptop%") |
in | .where("status", "in", ["Paid", "Delivered"]) |
not in | .where("status", "not in", ["Cancelled"]) |
between | Preferir whereBetween(). |
is | Preferir whereNull(). |
is not | Preferir whereNotNull(). |
No insertes valores directamente en el operador. Siempre pasa el valor en el tercer argumento.
8. Condiciones OR
orWhere(field, operator, value)
const result = await db
.from("orders")
.where("status", "=", "Paid")
.orWhere("status", "=", "Delivered")
.get();Equivalente conceptual:
WHERE status = 'Paid' OR status = 'Delivered'Se pueden encadenar condiciones OR:
const result = await db
.from("orders")
.where("order_id", "like", "%abc%")
.orWhere("customer_id", "like", "%abc%")
.orWhere("product", "like", "%abc%")
.orWhere("status", "like", "%abc%")
.get();Grupos booleanos anidados
const result = await db
.from("orders")
.whereGroup(group => {
group
.where("status", "=", "Paid")
.orWhere("status", "=", "Delivered");
})
.whereGroup(group => {
group
.where("total", ">", 500)
.andWhere("quantity", ">", 1);
})
.get();Los grupos pueden contener whereGroup() y orWhereGroup() adicionales. El parser del backend debe reconocer los objetos and y or generados.
9. IN y NOT IN
whereIn(field, values)
const result = await db
.from("orders")
.whereIn("status", ["Paid", "Delivered"])
.get();whereNotIn(field, values)
const result = await db
.from("orders")
.whereNotIn("status", ["Cancelled", "Pending"])
.get();values debe ser un array compatible con el parser del backend.
10. BETWEEN
whereBetween(field, minimum, maximum)
const result = await db
.from("orders")
.whereBetween("total", 100, 1000)
.get();Ejemplo con fechas:
const result = await db
.from("appointments")
.whereBetween("date", "2026-07-01", "2026-07-15")
.get();El tratamiento como texto, número o fecha depende del tipo reconocido por el parser.
11. Valores NULL
whereNull(field)
const result = await db
.from("orders")
.whereNull("delivery_date")
.get();whereNotNull(field)
const result = await db
.from("orders")
.whereNotNull("delivery_date")
.get();12. Ordenamiento
orderBy(field, direction = "asc", type = "text")
const result = await db
.from("orders")
.orderBy("total", "desc", "numeric")
.get();Múltiples campos:
const result = await db
.from("orders")
.orderBy("status", "asc")
.orderBy("total", "desc", "numeric")
.get();direction solo puede ser asc o desc.
El tercer argumento indica al backend cómo ordenar el valor JSON. Ejemplos habituales:
textnumericdatetimestamp
La lista exacta de tipos permitidos depende del parser del backend.
Si no se llama a orderBy(), el SDK no envía order_by.
13. Paginación
limit(value)
Define el máximo de documentos devueltos.
.limit(20)Rango permitido por el SDK: 1 a 500.
Valor predeterminado: 200.
offset(value)
Define cuántos documentos omitir antes de devolver resultados.
.offset(40)Rango permitido por el SDK: 0 a 1,000,000.
Valor predeterminado: 0.
Ejemplo de paginación
const pageSize = 20;
const page = 3;
const result = await db
.from("orders")
.select("order_id", "status", "total")
.limit(pageSize)
.offset((page - 1) * pageSize)
.get();Para detectar una página siguiente se puede solicitar un registro adicional:
const pageSize = 20;
const result = await db
.from("orders")
.limit(pageSize + 1)
.offset(0)
.get();
const rows = result.response ?? [];
const hasNextPage = rows.length > pageSize;
const visibleRows = rows.slice(0, pageSize);Paginación por cursor
paginate() usa paginación keyset: agrega una condición > o < sobre un campo estable y evita el costo creciente de OFFSET.
const firstPage = await db
.from("orders")
.select("order_id", "status", "total")
.paginate({
field: "order_id",
pageSize: 20,
direction: "asc",
type: "text"
});Respuesta:
firstPage.response;
firstPage.pagination.hasNextPage;
firstPage.pagination.nextCursor;Página siguiente:
const nextPage = await db
.from("orders")
.select("order_id", "status", "total")
.paginate({
field: "order_id",
pageSize: 20,
cursor: firstPage.pagination.nextCursor
});Reglas:
- El campo debe ser estable, único y estar incluido en el resultado.
- El cursor está vinculado al campo y dirección usados para crearlo.
- Una instancia de consulta solo puede ejecutar
paginate()una vez. - El tamaño permitido es de
1a499; el SDK solicita un registro adicional para detectar la siguiente página. - El cursor es opaco para el consumidor y no debe modificarse.
14. Ejecutar una consulta
get()
Envía la consulta a POST /get y devuelve una promesa:
try {
const result = await db
.from("orders")
.where("status", "=", "Paid")
.limit(20)
.get();
console.table(result.response);
} catch (error) {
console.error(error.message);
}15. Crear documentos
create(collection, data)
const result = await db.create("orders", {
order_id: crypto.randomUUID(),
customer_id: "3e1a3863-1d3e-4e15-b367-7cf50259cb62",
product: "Laptop",
quantity: 2,
total: 2400.50,
status: "Pending"
});El segundo argumento debe ser un objeto. No puede ser null ni un array.
Payload generado:
{
"collection": "orders",
"data": {
"order_id": "...",
"customer_id": "...",
"product": "Laptop",
"quantity": 2,
"total": 2400.5,
"status": "Pending"
}
}16. Actualizar documentos
update(values, unset = [])
Una actualización exige al menos una condición where().
const result = await db
.from("orders")
.where("order_id", "=", "910680b7-a63a-4753-93a8-e69f9c4f6402")
.limit(1)
.update({
total: 0,
status: "Cancelled"
});Para eliminar propiedades del documento JSON durante la actualización:
const result = await db
.from("orders")
.where("order_id", "=", "910680b7-a63a-4753-93a8-e69f9c4f6402")
.limit(1)
.update(
{ status: "Cancelled" },
["temporary_field", "legacy_value"]
);Reglas importantes:
valuesdebe ser un objeto.- La consulta debe contener al menos un
where(). limit()controla el máximo de documentos actualizados.- Si no se especifica
limit(), el valor heredado es200. - Para actualizar un documento único se recomienda
.limit(1)y un identificador único.
17. Eliminar documentos
delete()
La eliminación exige al menos una condición where().
const result = await db
.from("orders")
.where("order_id", "=", "9b4a7989-a8ed-4b15-88e5-be633b2bb75a")
.limit(1)
.delete();Varias condiciones:
const result = await db
.from("orders")
.where("status", "=", "Cancelled")
.where("product", "=", "Laptop")
.limit(100)
.delete();En la implementación actual del backend, delete() puede representar un borrado lógico mediante deleted_at, no necesariamente un DELETE físico.
Reglas importantes:
- No se permite eliminar sin
where(). limit()controla el máximo de documentos afectados.- Para eliminar un único documento se recomienda
.limit(1).
18. Joins
Los campos de un join deben usar el formato completo collection.field.
innerJoin(collection, leftField, rightField, alias = null)
const result = await db
.from("orders")
.select(
"orders.order_id",
"orders.status",
"customers.first_name",
"customers.last_name"
)
.innerJoin(
"customers",
"orders.customer_id",
"customers.customer_id"
)
.get();leftJoin(collection, leftField, rightField, alias = null)
const result = await db
.from("orders")
.select("orders.order_id", "products.name")
.leftJoin(
"products",
"orders.order_id",
"products.order_id"
)
.get();rightJoin(collection, leftField, rightField, alias = null)
const result = await db
.from("orders")
.rightJoin(
"products",
"orders.order_id",
"products.order_id"
)
.get();fullJoin(collection, leftField, rightField, alias = null)
const result = await db
.from("orders")
.fullJoin(
"products",
"orders.order_id",
"products.order_id"
)
.get();join(type, collection, leftField, operator, rightField, alias = null)
Método general para controlar el tipo y operador:
const result = await db
.from("orders")
.join(
"inner",
"customers",
"orders.customer_id",
"=",
"customers.customer_id"
)
.get();Tipos permitidos por el SDK:
innerleftrightfull
Alias de collection
const result = await db
.from("orders")
.select("orders.order_id", "p.name")
.leftJoin(
"products",
"orders.order_id",
"p.order_id",
"p"
)
.get();El parser del backend debe admitir el alias y la relación generada.
Varios joins
const result = await db
.from("orders")
.select(
"orders.order_id",
"customers.first_name",
"products.name"
)
.innerJoin(
"customers",
"orders.customer_id",
"customers.customer_id"
)
.leftJoin(
"products",
"orders.order_id",
"products.order_id"
)
.get();Varias condiciones ON
const result = await db
.from("orders")
.leftJoin("products", join => {
join
.on("orders.order_id", "=", "products.order_id")
.andOn("products.stock", ">", 0);
})
.get();Para combinar todas las condiciones con OR:
.leftJoin("products", join => {
join
.on("orders.order_id", "=", "products.order_id")
.orOn("orders.product", "=", "products.name");
})Grupos ON arbitrariamente anidados
.leftJoin("products", join => {
join
.on("orders.order_id", "=", "products.order_id")
.orOnGroup(group => {
group
.on("orders.product", "=", "products.name")
.andOn("products.stock", ">", { value: 0 });
});
})Están disponibles onGroup(), andOnGroup() y orOnGroup(). Los grupos pueden anidarse y el parser SELECT conserva sus paréntesis y lógica.
Relaciones automáticas con with()
Cuando hay un catálogo cargado, el SDK encuentra el camino más corto entre collections y construye los joins:
const result = await db
.from("orders")
.select(
"orders.order_id",
"customers.first_name",
"products.name"
)
.with("customers", "products")
.get();with() evita repetir collections que ya fueron incorporadas y lanza CATALOG_ERROR si no existe una relación.
19. Agrupación
groupBy(...fields)
const result = await db
.from("orders")
.select("status")
.sum("total", "total_sales")
.groupBy("status")
.get();También acepta un array:
.groupBy(["status", "product"])Los campos agrupados deben incluirse normalmente en select() cuando también se muestran en el resultado.
20. Funciones de agregación
count(field = "*", alias = "count")
const result = await db
.from("orders")
.count("*", "order_count")
.get();countDistinct(field, alias = null)
const result = await db
.from("orders")
.countDistinct("customer_id", "customer_count")
.get();sum(field, alias = null)
const result = await db
.from("orders")
.sum("total", "total_sales")
.get();avg(field, alias = null)
const result = await db
.from("orders")
.avg("total", "average_order")
.get();min(field, alias = null, type = "text")
const result = await db
.from("orders")
.min("total", "minimum_total", "numeric")
.get();max(field, alias = null, type = "text")
const result = await db
.from("orders")
.max("total", "maximum_total", "numeric")
.get();Si no se proporciona alias, el SDK lo genera con el formato:
operacion_campoEjemplo:
.sum("total") // alias: sum_totalLos alias deben comenzar con letra o _ y solo pueden contener letras, números y _.
21. HAVING
having(alias, operator, value)
having() trabaja con el alias de una agregación:
const result = await db
.from("orders")
.select("status")
.sum("total", "total_sales")
.groupBy("status")
.having("total_sales", ">", 10000)
.orderBy("total_sales", "desc")
.get();22. Consulta agregada completa
const result = await db
.from("orders")
.select("customers.city")
.innerJoin(
"customers",
"orders.customer_id",
"customers.customer_id"
)
.whereIn("orders.status", ["Paid", "Delivered"])
.countDistinct("orders.customer_id", "customer_count")
.sum("orders.total", "total_sales")
.groupBy("customers.city")
.having("customer_count", ">", 1)
.orderBy("total_sales", "desc")
.limit(50)
.offset(0)
.get();
console.table(result.response);23. Inspeccionar el payload sin ejecutar
toPayload()
const query = db
.from("orders")
.select("order_id", "status")
.where("status", "=", "Paid")
.limit(20);
console.log(query.toPayload());Resultado aproximado:
{
"from": "orders",
"distinct": false,
"limit": 20,
"offset": 0,
"select": [
"orders.order_id",
"orders.status"
],
"where": [
{
"orders.status": ["=", "Paid"]
}
]
}toUpdatePayload(values, unset = [])
const query = db
.from("orders")
.where("order_id", "=", "abc")
.limit(1);
console.log(query.toUpdatePayload({ status: "Paid" }));toDeletePayload()
const query = db
.from("orders")
.where("order_id", "=", "abc")
.limit(1);
console.log(query.toDeletePayload());24. Respuesta del backend
El SDK devuelve el objeto JSON recibido. Una respuesta habitual de consulta tiene esta forma:
{
"ok": true,
"response": [
{
"order_id": "5742459c-3b47-46df-9f88-583d17953305",
"status": "Paid",
"total": "3181.02"
}
],
"query": {
"sql": "...",
"parameters": {}
}
}Para recuperar las filas:
const rows = Array.isArray(result.response)
? result.response
: [];La forma exacta puede variar según el endpoint. Por defecto, el SDK devuelve response tal como llega; los métodos siguientes permiten transformarlo explícitamente.
Convertir tipos de las filas
const result = await db
.from("orders")
.select("order_id", "quantity", "total", "active")
.casts({
quantity: "integer",
total: "number",
active: "boolean"
})
.get();También puede declararse campo por campo:
const result = await db
.from("orders")
.cast("total", "number")
.cast("created_at", "date")
.get();Tipos disponibles:
| Tipo | Resultado |
|---|---|
string | String |
number | Número finito |
integer | Número entero |
boolean | Booleano estricto |
date | Instancia de Date |
json | Objeto o array JSON |
bigint | BigInt |
Los valores null y undefined se conservan. Los campos que no estén presentes se ignoran.
BigInt no puede serializarse directamente con JSON.stringify().
Conversión personalizada
const result = await db
.from("orders")
.cast("total", value => Number(value) * 1.07)
.get();La función recibe (value, row, index) y puede ser asíncrona.
Transformar cada fila
const result = await db
.from("orders")
.casts({
quantity: "integer",
total: "number"
})
.transform((row, index) => ({
...row,
index,
description: `${row.quantity} × ${row.product}`
}))
.get();transform() se ejecuta después de los casts y debe devolver un valor. Puede devolver una promesa.
El SDK crea nuevas filas para la respuesta transformada; no modifica directamente los objetos recibidos del backend.
25. Manejo de errores
try {
const result = await db
.from("orders")
.where("status", "=", "Paid")
.get();
console.log(result.response);
} catch (error) {
console.error(error.message);
}El SDK lanza errores cuando:
- No puede conectarse al endpoint.
- El backend devuelve JSON inválido.
- El código HTTP no es exitoso.
- La respuesta contiene
ok: false. - La respuesta contiene
success: false. - Un argumento local es inválido.
Mensajes habituales:
Unable to connect to StowDB: ...
StowDB returned invalid JSON (HTTP 500).
StowDB request error: ...El error original de conexión o JSON se conserva en error.cause cuando el navegador lo soporta.
Los errores del SDK son instancias de VanillaError:
try {
await db.from("orders").get();
} catch (error) {
console.log(error.code);
console.log(error.status);
console.log(error.details);
}| Código | Significado |
|---|---|
NETWORK_ERROR | Falló la conexión o lectura de la respuesta. |
TIMEOUT | Se agotó el tiempo configurado. |
REQUEST_ABORTED | La aplicación canceló la petición. |
HTTP_ERROR | El servidor respondió con un código HTTP fallido. |
INVALID_RESPONSE | El servidor no devolvió JSON válido. |
BACKEND_ERROR | El JSON contiene ok: false o success: false. |
TRANSFORM_ERROR | Falló un cast o la transformación de una fila. |
CATALOG_ERROR | Collection, campo, relación o tipo inválido. |
CAPABILITY_ERROR | El catálogo indica que el backend no soporta la operación. |
CURSOR_ERROR | El cursor es inválido o incompatible con la consulta. |
IDEMPOTENCY_ERROR | Una escritura reintentable no tiene una clave de idempotencia. |
REQUEST_SERIALIZATION_ERROR | El payload no puede convertirse a JSON. |
Cancelar una petición
const controller = new AbortController();
const request = db
.from("orders")
.get({ signal: controller.signal });
controller.abort();
await request;Opciones para una petición específica
const result = await db
.from("orders")
.get({
timeout: 10000,
retries: 1,
retryDelay: 250
});Las opciones específicas reemplazan únicamente los valores correspondientes de la configuración global.
Para autorizar reintentos de una escritura explícitamente:
await db.create("orders", document, {
retries: 1,
retryMutations: true
});Actualización con opciones:
await db
.from("orders")
.where("order_id", "=", orderId)
.limit(1)
.update(
{ status: "Paid" },
[],
{ timeout: 15000 }
);Idempotencia de escrituras
Una escritura solo puede reintentarse cuando incluye una clave de idempotencia:
await db.create("orders", document, {
retries: 2,
retryMutations: true,
idempotencyKey: crypto.randomUUID()
});El SDK envía:
Idempotency-Key: valor-unicoSi se solicitan reintentos de mutaciones sin la clave, se genera IDEMPOTENCY_ERROR antes de enviar la petición.
El backend incluye:
vanilla_idempotency_schema.sql: almacenamiento aislado por tenant/proyecto.IdempotencyStore.php: reserva, recuperación, finalización, liberación y purga de claves.
update_execute_endpoint.php y delete_execute_endpoint.php ya integran el almacenamiento dentro de la misma transacción. El endpoint /set debe aplicar el mismo patrón para que CREATE sea idempotente en producción.
Hooks y métricas
const db = new StowDB(endpoint, apiKey, projectKey, {
onRequest: event => console.log("request", event),
onResponse: event => console.log("response", event),
onRetry: event => console.log("retry", event),
onError: event => console.error("error", event)
});Los errores lanzados por un hook no cambian el resultado de la petición.
console.log(db.metrics());
db.resetMetrics();Las métricas contienen requests, intentos, éxitos, fallos, reintentos, duración total, promedio y desglose por operación.
26. CORS en el backend
Para usar el SDK desde un navegador, los endpoints deben aceptar las cabeceras enviadas por StowDB:
header('Access-Control-Allow-Origin: http://localhost');
header('Access-Control-Allow-Methods: POST, OPTIONS');
header(
'Access-Control-Allow-Headers: ' .
'Authorization, Content-Type, X-Project-Key, X-API-Key, Idempotency-Key'
);
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(204);
exit;
}El servidor debe responder correctamente a la petición OPTIONS previa al POST.
28. Método avanzado request()
request(operation, payload) es público y puede utilizarse directamente:
const controller = new AbortController();
const result = await db.request("select", {
from: "orders",
distinct: false,
limit: 20,
offset: 0
}, {
timeout: 10000,
signal: controller.signal
});Operaciones aceptadas:
selectcreateupdatedelete
Se recomienda usar los métodos de alto nivel porque validan argumentos y construyen el payload.
29. Referencia rápida
Cliente
| Método | Resultado |
|---|---|
new StowDB(endpoint, apiKey, projectKey, options) | Crea el cliente y configura las peticiones. |
from(collection) | Crea una consulta. |
collection(collection) | Alias de from(). |
create(collection, data) | Inserta un documento. |
request(operation, payload) | Envía una operación manual. |
useCatalog(definition) | Carga o reemplaza el catálogo. |
fetchCatalog(options) | Recupera el catálogo desde /catalog. |
clearCatalogCache() | Elimina la caché del catálogo del proyecto. |
catalogVersion() | Devuelve la versión del catálogo. |
getCatalog() | Devuelve la instancia VanillaCatalog. |
collections() | Lista las collections del catálogo. |
describe(collection) | Describe campos y tipos. |
relations() | Lista las relaciones declaradas. |
capabilities() | Devuelve operadores y casts soportados. |
metrics() | Devuelve métricas acumuladas de peticiones. |
resetMetrics() | Reinicia las métricas del cliente. |
Consulta
| Método | Descripción |
|---|---|
select(...fields) | Selecciona campos. |
selectAs(field, alias) | Selecciona un campo con alias. |
distinct(enabled) | Activa o desactiva DISTINCT. |
where(field, operator, value) | Agrega una condición AND. |
orWhere(field, operator, value) | Agrega o amplía un grupo OR. |
whereGroup(callback) | Agrega un grupo booleano AND. |
orWhereGroup(callback) | Agrega un grupo booleano OR. |
whereIn(field, values) | Condición IN. |
whereNotIn(field, values) | Condición NOT IN. |
whereBetween(field, min, max) | Condición BETWEEN. |
whereNull(field) | Condición IS NULL. |
whereNotNull(field) | Condición IS NOT NULL. |
join(...) | Join configurable. |
innerJoin(...) | INNER JOIN. |
leftJoin(...) | LEFT JOIN. |
rightJoin(...) | RIGHT JOIN. |
fullJoin(...) | FULL JOIN. |
with(...collections) | Construye joins usando el catálogo. |
groupBy(...fields) | Agrupa resultados. |
having(alias, operator, value) | Filtra agregaciones. |
count(field, alias) | COUNT. |
countDistinct(field, alias) | COUNT DISTINCT. |
sum(field, alias) | SUM. |
avg(field, alias) | AVG. |
min(field, alias, type) | MIN. |
max(field, alias, type) | MAX. |
orderBy(field, direction, type) | Ordena resultados. |
cast(field, type) | Convierte un campo de cada fila. |
casts(definitions) | Declara varias conversiones. |
transform(callback) | Transforma cada fila devuelta. |
typed(enabled) | Activa el tipado automático para la consulta. |
limit(value) | Máximo de resultados o documentos afectados. |
offset(value) | Desplazamiento de consulta. |
get() | Ejecuta SELECT. |
paginate(options, requestOptions) | Ejecuta paginación keyset por cursor. |
update(values, unset) | Actualiza documentos. |
delete() | Elimina documentos. |
toPayload() | Devuelve el payload SELECT. |
toUpdatePayload(values, unset) | Devuelve el payload UPDATE. |
toDeletePayload() | Devuelve el payload DELETE. |
30. Ejemplos rápidos
Buscar una orden
const result = await db
.from("orders")
.select("order_id", "status", "total")
.where("order_id", "=", "5742459c-3b47-46df-9f88-583d17953305")
.limit(1)
.get();Buscar órdenes pagadas
const result = await db
.from("orders")
.where("status", "=", "Paid")
.orderBy("total", "desc", "numeric")
.limit(20)
.get();Buscar texto en varios campos
const term = "Laptop";
const result = await db
.from("orders")
.where("order_id", "like", `%${term}%`)
.orWhere("product", "like", `%${term}%`)
.orWhere("status", "like", `%${term}%`)
.limit(20)
.get();Total vendido por estado
const result = await db
.from("orders")
.select("status")
.sum("total", "total_sales")
.groupBy("status")
.orderBy("total_sales", "desc")
.get();Actualizar una orden
const result = await db
.from("orders")
.where("order_id", "=", "5742459c-3b47-46df-9f88-583d17953305")
.limit(1)
.update({ status: "Delivered" });Eliminar órdenes canceladas
const result = await db
.from("orders")
.where("status", "=", "Cancelled")
.limit(100)
.delete();31. Catálogo, tipos y relaciones
El catálogo permite que el SDK conozca la estructura pública del proyecto sin conocer la tabla interna de StowDB.
const catalog = {
version: 1,
capabilities: {
operators: [
"=", "!=", ">", ">=", "<", "<=",
"like", "in", "between", "is", "is not"
],
casts: [
"text", "numeric", "integer",
"boolean", "date", "timestamp", "uuid"
]
},
collections: {
orders: {
label: "Orders",
fields: {
order_id: { type: "uuid", nullable: false },
customer_id: { type: "uuid", nullable: false },
total: { type: "numeric", nullable: false },
status: {
type: "string",
nullable: false,
enum: ["Pending", "Paid", "Delivered", "Cancelled"]
}
}
},
customers: {
fields: {
customer_id: { type: "uuid", nullable: false },
city: { type: "string", nullable: true }
}
}
},
relations: [{
name: "orders_customer",
from_collection: "orders",
from_field: "customer_id",
to_collection: "customers",
to_field: "customer_id",
join_type: "left"
}]
};Tipos de catálogo permitidos:
stringintegernumericbooleandatetimestampuuidarrayobject
Cargar el catálogo en el constructor
const db = new StowDB(endpoint, apiKey, projectKey, {
catalog,
strictCatalog: true,
autoCast: true
});Cargarlo posteriormente
db.useCatalog(catalog);Recuperarlo del backend
const catalog = await db.fetchCatalog();Esta llamada usa POST /catalog. El endpoint debe devolver la definición en catalog, response o data.
El proyecto incluye dos archivos de referencia para desplegar esta capacidad:
vanilla_catalog_schema.sql: tabla de catálogo aislada por tenant y proyecto.catalog_endpoint.php: endpoint autenticado que devuelve únicamente el catálogo del proyecto actual.
Versión y caché
db.catalogVersion();
await db.fetchCatalog({
cacheTtl: 300000,
refresh: false
});
db.clearCatalogCache();La caché está aislada por endpoint y project key. La versión positiva del documento se conserva al serializar el catálogo.
Consultar el catálogo
db.collections();
db.describe("orders");
db.relations();
db.capabilities();
db.relationPath("orders", "customers");Validación estricta
Con strictCatalog: true, el SDK valida antes del request:
- Collections conocidas.
- Campos conocidos.
- Compatibilidad del operador con el tipo.
- Operadores declarados por el backend.
- Casts declarados por el backend.
- Valores array para
INyNOT IN. - Valores
nullparaISeIS NOT.
Los errores utilizan CATALOG_ERROR o CAPABILITY_ERROR.
Tipado automático
Puede activarse globalmente con autoCast: true o por consulta:
const result = await db
.from("orders")
.select("order_id", "total")
.typed()
.get();numeric se convierte en number, integer en entero, boolean en booleano, fechas en Date, objetos y arrays en JSON.
Generar TypeScript
const declarations = db
.getCatalog()
.toTypeScript({ namespace: "VanillaSchema" });También puede usarse la herramienta de línea de comandos:
node generate_vanilla_types.mjs collection_catalog.json StowDB-schema.d.tsGenera interfaces, enums como uniones literales, nullability, CollectionName y DocumentOf<C>.
32. Uso como módulo
Navegador tradicional
<script src="https://stowdb.miramaxtech.com/sdk/v1/stowdb.js"></script>ES Modules en navegador o Node.js
import StowDB, {
VanillaError,
VanillaCatalog
} from "./StowDB.mjs";En el navegador también puede importarse directamente desde https://stowdb.miramaxtech.com/sdk/v1/stowdb.mjs.
CommonJS
const {
StowDB,
VanillaError,
VanillaCatalog
} = require("./StowDB.cjs");stowdb.js mantiene las variables globales para compatibilidad y también exporta mediante module.exports cuando se ejecuta como CommonJS.
Distribución oficial
El SDK se entrega exclusivamente mediante el portal de StowDB y el CDN oficial. El manifiesto package.json está marcado como privado para impedir su publicación accidental en npm.
URLs estables de la versión 1:
https://stowdb.miramaxtech.com/sdk/v1/StowDB-sdk-v1.0.0.ziphttps://stowdb.miramaxtech.com/sdk/v1/stowdb.jshttps://stowdb.miramaxtech.com/sdk/v1/stowdb.mjshttps://stowdb.miramaxtech.com/sdk/v1/stowdb.cjshttps://StowDB.miramaxtech.com/sdk/v1/StowDB.d.tshttps://StowDB.miramaxtech.com/sdk/v1/SHA256SUMS.txt
Usa siempre /sdk/v1/ en producción. /sdk/latest/ se reserva para pruebas porque puede cambiar sin modificar la URL.
Parsers compatibles
Las versiones incluidas de parse_select.php, parse_update.php y parse_delete.php aceptan:
- Grupos
ANDyORanidados hasta diez niveles. - Máximo de cien condiciones reales por
WHERE. IN,NOT IN,BETWEEN,IS NULL,IS NOT NULL,LIKEy comparadores.CONTAINSyNOT CONTAINSmediante JSONB@>parametrizado.- Tipado automático de números, booleanos y fechas.
parse_select.php añade aliases de salida, aliases de collections, joins INNER, LEFT, RIGHT, FULL y CROSS, múltiples joins, grupos ON arbitrariamente anidados, agregaciones, GROUP BY, HAVING, DISTINCT, ordenamiento y paginación.
33. Condiciones operativas
Las limitaciones implementables del SDK están cubiertas. Permanecen condiciones que dependen del despliegue o de la naturaleza de los datos:
POST /catalogy sus tablas deben desplegarse para que el navegador pueda enumerar collections del proyecto.- El catálogo debe reflejar las relaciones y capacidades reales del parser desplegado.
- La paginación keyset necesita un campo estable, único, seleccionable y comparable.
- La idempotencia requiere desplegar su tabla e integrar
IdempotencyStoreen los endpoints de escritura. - El portal y las rutas
/sdk/v1/y/sdk/latest/deben publicarse en IIS. - Las API keys enviadas al navegador siguen siendo visibles para el usuario de ese navegador.
34. Recomendaciones
- Usa IDs únicos en filtros de actualización y eliminación.
- Usa
.limit(1)cuando esperas afectar un solo documento. - Evita límites grandes en el frontend.
- Selecciona únicamente los campos necesarios.
- Usa tipos numéricos reales en JSON para cantidades y totales.
- Crea índices apropiados en el backend para filtros frecuentes.
- No expongas API keys con permisos administrativos en aplicaciones públicas.
- Captura siempre los errores con
try/catch.