BlitzGraph beta · puede haber interrupciones puntuales
Documentación

Construye con BQL

BlitzStore es una base de datos de grafos polimórfica. Define tus datos como , conéctalos con y consúltalo todo, incluidas las relaciones anidadas, en una sola petición JSON.

JSON de entrada, JSON de salida
Sin cadenas SQL ni cadenas de ORM
Recorrido de grafo
Expande relaciones sin N+1
Mutaciones atómicas
Operaciones por lotes, todo o nada

Conectar

Configura el acceso HTTP, elige la familia de tokens adecuada y mantén la auth de sesión de la aplicación separada de la auth del servidor de datos.

#

Autenticarse

El servidor de datos de BlitzStore solo acepta Authorization: Bearer <token>. No existe una cabecera API-Key. Cuatro familias de tokens usan ese mismo transporte:

  • bzt_*: grant tokens de operador con alcance para clientes (alcance de servidor, de base de datos o de namespace). Acúñalos desde el plano de control de BlitzGraph y úsalos en las llamadas al plano de datos (/units/query, /units/mutate, /resources/mutate).
  • bzu_*: credenciales de sujeto para los usuarios finales de un portal, ligadas a una unidad dentro de un namespace y confinadas a la superficie de aplicación para la que se acuñaron. La API en crudo las rechaza con SURFACE_CONFINED.
  • bzi_*: claves internas por instancia, sin restricciones, reservadas para una recuperación de emergencia explícita.
  • bzc_*: capacidades de 60 segundos ligadas a una instancia, a una acción HTTP y a un alcance opcional de base de datos o namespace, dentro de la política de acciones y audiencias de la clave firmante. El worker de cuarentena usa una capacidad restringida de alcance servidor ligada al cuerpo exacto de la petición. La gestión de claves internas sigue siendo exclusiva de bzi_*.
BQL · primera llamada con un grant token
curl -X POST https://api.blitzgraph.com/units/query \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer bzt_your.token-here" \
  -d '{"query":{"$kinds":"User","$fields":"*","$limit":10}}'

La rotación en caliente de las claves bzi_* ordinarias se hace por /server/mutate. La clave bzi_bootstrap.* pertenece a BLITZSTORE_SECRET_AUTH_BOOTSTRAP_KEY en el contenedor de secretos de la instancia: publica primero ese contenedor y reinicia después el servidor. Los grant tokens nuevos se proyectan por /server/mutate.

El límite de Better Auth

Los bearer access tokens de Better Auth no autentican directamente contra el servidor de datos. Autentican agentes en rutas de aplicación de BlitzGraph como POST /connect/connection, que a su vez emiten una credencial bzt_* con alcance para el Space que la persona aprobó. El servidor de datos solo llega a ver el bzt_* acuñado.

El usuario final de un portal va por otro carril: su sesión la acuña el servidor de datos como una credencial de sujeto bzu_*, confinada a la única superficie de aplicación para la que se emitió y rechazada en la API en crudo.

Vinculación del destino MCP

Ejecuta claude mcp add --transport http blitzgraph https://blitzgraph.com/mcp o codex mcp add blitzgraph --url https://blitzgraph.com/mcp. Después inicia sesión en el navegador y elige un Space. Codex requiere codex mcp login blitzgraph tras añadir el servidor.

El flujo de conexión OAuth de MCP autoriza el grant de conexión emitido contra un Space, no contra un namespace o un servidor fijos. Cada llamada resuelve en vivo la ubicación actual del Space, así que mover un Space nunca invalida un grant existente. Las herramientas heredan el destino de namespace seleccionado; usa opts.defaultSubspace cuando una llamada concreta deba apuntar a otro subspace por defecto.

Plataforma

Los alcances de almacenamiento aíslan los datos; las consultas en vivo los mantienen sincronizados; los workflows duraderos orquestan el trabajo; los límites efectivos explican la capacidad; y BlitzStudio opera todas esas superficies.

#

Namespaces y subspaces

Un es el límite de aislamiento de nivel superior dentro de una base de datos. Cada uno tiene su propio esquema, sus datos y sus índices. Un es otro límite de aislamiento dentro de ese namespace: los datos, el esquema, los portales y los índices quedan acotados a él. Los usuarios del plan gratuito tienen un namespace y pueden crear varios subspaces (por ejemplo main, archive, drafts).

BQL · apuntar a un subspace en consultas y mutaciones
// HTTP envelope default
{ "query": { "$kinds": "Note", "$fields": "*" }, "opts": { "defaultSubspace": "archive" } }

// Per-query override
{ "$kinds": "Note", "$subspace": "archive", "$fields": "*" }

// Per-mutation override
{ "$setKinds": ["Note"], "content": "temp", "$subspace": "drafts" }

// Omit $subspace → defaults to "main"
{ "$kinds": "Note", "$fields": "*" }
BQL · crear y gestionar subspaces (API de administración)
// POST /resources/mutate: create a subspace
{ "mutation": { "$type": "subspace", "$op": "create", "name": "archive", "storage": "memory" } }

// POST /resources/query: list subspaces
{ "query": { "$type": "subspace", "$op": "query" } }

Los datos están totalmente aislados entre subspaces. Una consulta sobre "main" nunca ve datos de "archive". Cada subspace puede tener su propio esquema, definido con más opts.defaultSubspace, o con ns.defaultSubspace("name").definitions.import() en el SDK.

Para transferencias autoservicio con alcance de namespace, usa operaciones runs.transfer mediante /runs/query y /runs/mutate. Sube bundles .bzg estrictos mediante /runs/artifacts; las importaciones se ejecutan en segundo plano, y el mismo bundle puede reanudar una importación interrumpida que coincida.

El $history por unidad es ilimitado por defecto. Fija las variables de entorno del servidor BLITZSTORE_HISTORY_RETENTION_DAYS o BLITZSTORE_HISTORY_UNIT_MAX_EVENTS para acotarlo; un barrido en segundo plano elimina los eventos que superan la ventana de retención o el tope, y las lecturas de $history solo ven las entradas que sobreviven.

#

Consultas en vivo

Una mantiene un resultado BQL ordenado sincronizado por SSE. El carril en crudo POST /units/live es para grants con alcance y claves internas. Las sesiones de de un portal solo se suscriben mediante una operación de consulta con nombre y validada en /_blitz/ops/query/{op}/live, así que un navegador nunca obtiene la capacidad de consulta en crudo.

BQL · suscripción con el SDK en crudo
for await (const frame of client.queryLive({
  query: { $kinds: 'Ticket', $sort: ['priority', '$iid'], $fields: '*' },
  subspace: 'main',
  resume: lastAppliedCheckpoint,
})) {
  // Persist frame.checkpoint only after the whole snapshot/patch applies.
  applyAtomically(frame)
}
BQL · hooks de React y de portal
// Scoped grant / internal-key application
const tickets = useLiveQuery({ $kinds: 'Ticket', $sort: ['$iid'], $fields: '*' })

// Portal AppUser: generated named-operation ref
const myTickets = useLiveOperation(myTicketsRef, { status: 'open' })
LiveFrame atómico
Las instantáneas, los parches, los latidos y los frames terminales comparten un único contrato generado. Un parche es todo o nada.
Reanudación opaca
Reconecta con el último checkpoint aplicado. Nunca analices, edites ni guardes un checkpoint antes de que se aplique su frame.
Autoridad que falla en modo cerrado
La caducidad, la revocación, los cambios en el nivel mínimo de autorización del sujeto y los cambios de política de la operación se revalidan antes de entregar contenido protegido.

Un LIVE_CONTINUITY_GAP reintentable significa que el servidor ha acotado un hueco y lo está reparando. LIVE_RESUME_UNAVAILABLE es estable: un operador debe configurar BLITZSTORE_SECRET_LIVE_RESUME_KEYRING antes de que la reanudación esté disponible. El store del cliente espacia las reconexiones y recurre a una instantánea autorizada nueva cuando ya no se puede usar una repetición retenida.

#

Acciones y workflows

Disponible en BlitzGraph 0.61.0. Una Action es un paso tipado y reutilizable; un es una máquina de estados duradera que orquesta Actions, BQL, esperas, señales, workflows anidados y fan-out acotado. Las definiciones pasan por el linter antes de persistirse, y cada ejecución fija su clausura ejecutable y el digest del programa.

BQL · action TypeScript en línea
{ $op: 'create', $type: 'action', name: 'scoreLead',
  type: 'pure', mode: 'inline',
  inputSchema: { type: 'object', properties: { score: { type: 'number' } } },
  outputSchema: { type: 'number' },
  $ts: 'return (input as { score: number }).score satisfies number'
}
BQL · action WASM protegida
{ $op: 'create', $type: 'action', name: 'protectedScore',
  type: 'pure', mode: 'wasm',
  module: 'scorer@sha256:<64 lowercase hex>',
  inputSchema: { type: 'object' },
  outputSchema: { type: 'number' }
}

El código en línea acepta $js, $ts o $code explícito. TypeScript se guarda tal como se escribió, pero el servidor rechaza imports, módulos, JSX, decoradores, enums, namespaces y demás sintaxis que genere runtime antes de que una definición persista. Los diagnósticos de Studio usan el mismo manifiesto generado de perfil restringido; el linter del servidor sigue siendo la autoridad.

BQL · ciclo de vida de módulos con alcance de namespace
const uploaded = await client.resources.module.upload('scorer', wasmBlob)
const pinned = uploaded.data.module // name@sha256:<digest>, abiVersion: 1

await client.resources.query({ query: { $type: 'module', $op: 'query' } })
await client.resources.query({ query: { $type: 'module', $op: 'query', name: pinned } })
const originalBytes = await client.resources.module.download(pinned)
await client.resources.mutate({ mutation: { $type: 'module', $op: 'delete', name: pinned } }) // rejected while an Action references it

WASM solo está disponible para pure×wasm. Los componentes exportan process(string) → result<string, string>, no tienen imports del host y se ejecutan con memoria, salida, errores, logs, bytes de caché y plazos acotados. El servidor fija y valida la ABI de WASM en el programa de workflow duradero; las definiciones públicas de Action no aceptan un campo abiVersion.

BQL · ejecutar una vez, inspeccionar o transmitir la tarea duradera
const run = await client.runs.mutate<{ runId: string }>({
  mutation: {
    $type: 'workflow', $op: 'execute', workflow: 'onboardCustomer',
    idempotencyKey: 'signup:customer-01J...',
    input: { customerId: 'customer-01J...' },
  },
})

const runId = run.data.runId
const status = await client.runs.query({ query: { $type: 'run', $op: 'inspect', $iid: runId } })
for await (const event of client.runs.stream(runId)) {
  console.log(event)
}
Control de flujo duradero
Las esperas, las señales, los reintentos, las llamadas anidadas, la concurrencia por clave y la compensación de saga sobreviven a los reinicios de proceso.
Map acotado
Recorre una Action o un Workflow con nombre con concurrencia acotada, resultados ordenados, reintentos y comportamiento de lanzar o recopilar.
Recuperación inspeccionable
Los eventos tipados exponen las oleadas de hijos, los joins, los reintentos, la compensación, el workflow propietario y la correlación de trazas W3C.

La recuperación tras un fallo reanuda toda la pila de llamadas y el cursor de compensación pendiente. La pertenencia de los hijos mapeados se registra antes del despacho, así que los joins y las cancelaciones no pierden hijos tras un relevo de worker. Las rutas runs.mutate y runs.query exponen el mismo ciclo de vida.

#

Límites efectivos

Un es el valor que el servidor aplica de verdad una vez resueltos los valores por defecto, la configuración, las anulaciones de entorno y el alcance de la petición. El catálogo y los detalles de rechazo son proyecciones de ese mismo objeto, así que los valores documentados y los aplicados no pueden divergir.

BQL · SDK
const { data: config } = await client.resources.query<{ limits: Array<{ limitId: string; effectiveValue: number; unit: string }> }>({
  query: { $type: "config", $op: "resolveEffectiveConfig" }
})

for (const limit of config.limits) {
  console.log(limit.limitId, limit.effectiveValue, limit.unit)
}
BQL · HTTP / MCP
POST /resources/query
{ "query": { "$type": "config", "$op": "resolveEffectiveConfig" } }
// Admission limits are returned in data.limits.

// MCP tool: no input body
limits.effective
Valor y unidad exactos
Los valores configurados, efectivos y observados usan unidades generadas e identidades de límite estables.
Acotado por autoridad
Quien llama con alcance de base de datos o de namespace solo ve su alcance efectivo exacto; el acceso de AppUser y de invitados se deniega.
Rechazo accionable
retryAfterMs junto con retryLater o afterRequestChange le indica al cliente cómo responder sin analizar mensajes.
#

BlitzStudio

BlitzStudio es la interfaz visual integrada para gestionar tus datos. Se conecta a cualquier instancia de BlitzStore y te da un espacio de trabajo completo con edición de esquema, exploración de datos y una consola de consultas en vivo.

BlitzSheet
Explorador de datos estilo hoja de cálculo. Consulta, edita, crea y borra unidades en una cuadrícula. Filtra y ordena por cualquier campo.
QueryStudio
Consola BQL en vivo. Escribe consultas y mutaciones y ve los resultados en tiempo real como un árbol JSON.
Editor de esquema
Gestión visual del esquema. Crea clases, define campos y configura roles y relaciones.
Apps (portales)
Despliega frontends propios conectados a tus datos de BlitzStore. Cada app tiene su propia ruta, servida desde tu namespace.
Transferencia de namespace
Exporta artefactos almacenados o descarga bundles .bzg, importa en un destino nuevo y consulta el estado de la transferencia sin salir de Studio.
Valores env
Gestiona las constantes del namespace y los secretos sellados de solo escritura desde la superficie tipada de administración de env.

Esquema

Define tu modelo de datos con , campos, , validaciones, campos calculados y hooks de mutación. Importa tu una vez y el mismo modelo alimenta /units/query, /units/mutate y BlitzStudio.

#

Estructura de la base de datos

BlitzStore separa los (tus unidades y conexiones) de las (el y los ).

data ── unidades, arcos, índices
definitions
schema ── kinds, dataFields, roleFields, linkFields
portals ── apps, páginas, layouts, componentes
  • POST /units/query y POST /units/mutate operan sobre los datos
  • POST /definitions/import, /definitions/query y /definitions/mutate operan sobre las definiciones (mira )
  • POST /resources/mutate usa { mutation: { $type, $op, ... } }
  • POST /units/import devuelve JSON por defecto; añade Accept: text/event-stream para recibir el progreso por SSE
#

Clases y campos

  • Un es una definición de esquema (como una clase o una tabla)
  • Una es una instancia almacenada de una o varias clases
  • Las clases tienen tres tipos de campo:
    • contiene valores, validaciones y comportamiento de cálculo
    • define los puntos de conexión (mira )
    • es un atajo para recorrer desde el otro lado
  • El comportamiento de mutación que abarca varios campos va en los a nivel de clase
BQL · definición de esquema
{
  "kinds": {
    "User": {
      "dataFields": {
        "name": { "valueType": "TEXT" },
        "email": { "valueType": "EMAIL", "unique": true },
        "age": { "valueType": "INTEGER" }
      }
    },
    "Article": {
      "dataFields": {
        "title": { "valueType": "TEXT", "required": true, "fts": true },
        "body": { "valueType": "TEXT", "fts": true },
        "status": { "valueType": "TEXT" }
      }
    }
  }
}
TEXT
Cadenas
INTEGER
Números enteros (i64)
DECIMAL
Base 10 exacta (dinero)
FLOAT
IEEE 754 f64 (ML, ciencia)
PERCENTAGE
Respaldado por decimal, 0–1
CURRENCY
Dinero: importe + ISO 4217
BOOLEAN
true / false
DATE
YYYY-MM-DD
DATETIME
ISO con zona horaria (obligatoria)
TIME
HH:MM:SS.ffffff
DURATION
Combina w/d/h/m/s/ms/u
INTERVAL
Conjunto de rangos (#intervals al escribir, intervalFormat al leer)
EMAIL
Con validación
URL
Con validación
FILE
Subida con el marcador #file
RICH_TEXT
Carga de texto enriquecido
COLOR
Valor de color
PHONE
Texto de teléfono
PASSWORD
Texto secreto
JSON
Objetos anidados
FLEX
Cualquier tipo
ID
Identificador
REF
Referencia a unidad o a esquema
Subidas de FILE

Un campo FILE almacena un blob más su metainformación. El cliente de TS detecta automáticamente los valores File / Blob en mutate() y cambia a multipart; el cuerpo JSON lleva un marcador { "#file": "<key>" } que el servidor asocia con la parte con ese nombre. Al leer, el valor se materializa como FileValue con filename / mime / size / url / thumbnailUrl.

BQL · subida con marcador multipart (forma HTTP en crudo)
// multipart body has parts: "mutation" (JSON) + named file parts
{
  "$setKinds": ["Document"],
  "title": "Annual report",
  "attachment": { "#file": "file_0" }
}
// → server stores blob, returns FileValue with signed url
Conversiones de campos

Cambia el valueType dentro de INTEGER, DECIMAL, FLOAT, PERCENTAGE, dentro de DATE, DATETIME o entre TEXT, RICH_TEXT. Una tarea en segundo plano reescribe las unidades. TEXT pasa a marcado literal escapado; RICH_TEXT pasa a texto visible. Las conversiones numéricas usan numericPolicy: rejectOnLoss (por defecto), round, truncate o allowPrecisionLoss. NaN e infinitos se rechazan. DATETIME a DATE exige medianoche UTC exacta. CURRENCY y TIME no tienen familia de conversión.

#

Polimorfismo y herencia

  • Polimorfismo. Una puede tener varias a la vez y hacerlas evolucionar con el tiempo. Un User puede ganar o perder Admin sin volver a crearse.
  • Herencia. Una clase puede declarar extends; hereda los campos y los roles de esa clase padre, y las consultas $kinds: "Parent" coinciden con todos sus descendientes por defecto.
BQL · componer y hacer evolucionar clases
// Create a unit with two kinds at once
{ "$setKinds": ["User", "Admin"], "name": "Alice" }

// Add a kind to an existing unit
{ "$id": "user_abc", "$setKinds": [{ "$op": "add", "$kinds": ["Moderator"] }] }

// Remove a kind (at least one must remain)
{ "$id": "user_abc", "$setKinds": [{ "$op": "remove", "$kinds": ["Admin"] }] }
BQL · heredar campos y roles de una clase padre
{
  "kinds": {
    "User":       { "dataFields": { "email": { "valueType": "EMAIL" } } },
    "Admin":      { "extends": "User", "dataFields": { "level": { "valueType": "INTEGER" } } },
    "SuperAdmin": { "extends": "Admin" }
  }
}

// Matches User + Admin + SuperAdmin
{ "$kinds": "User" }

// Exact match, no descendants
{ "$kinds": "User", "$descendants": false }
#

Validaciones y campos calculados

Las reglas de campo viven junto a cada . Usa los validadores integrados para comprobar tipos y rangos, validadores $js propios para las reglas de negocio y campos calculados cuando un valor deba derivarse en lugar de almacenarse. Los campos calculados también pueden leer valores meta como $id o $createdAt.

BQL · validaciones de campo integradas y propias
{
  "kinds": {
    "User": {
      "dataFields": {
        "age": {
          "valueType": "INTEGER",
          "validations": { "required": true, "min": 0, "max": 150 }
        },
        "email": { "valueType": "EMAIL" },
        "backupEmail": {
          "valueType": "TEXT",
          "validations": {
            "custom": [{
              "on": ["create", "update"],
              "$js": "$value.includes('@')",
              "message": "must be a valid email",
              "severity": "error"
            }]
          }
        }
      }
    }
  },
  "opts": { "defaultSubspace": "main" }
}
BQL · calculado al leer y valor por defecto editable
{
  "kinds": {
    "User": {
      "idField": "email",
      "dataFields": {
        "firstName": { "valueType": "TEXT" },
        "familyName": { "valueType": "TEXT" },
        "email": { "valueType": "EMAIL", "unique": true },
        "fullName": {
          "valueType": "FLEX",
          "computeType": "computed",
          "$js": "firstName + ' ' + familyName"
        },
        "displayId": {
          "valueType": "FLEX",
          "computeType": "computed",
          "$js": "$id"
        },
        "role": {
          "valueType": "TEXT",
          "computeType": "editable",
          "$js": "'user'"
        }
      }
    }
  }
}
Qué se aplica
  • Los tipos de contenido como EMAIL ya ejecutan la validación del sistema
  • Los validadores propios usan $value y pueden apuntar a create, a update o a ambos
  • severity: "error" bloquea la mutación
Comportamiento de los campos calculados
  • computeType: "computed" se ejecuta en cada consulta y rechaza las escrituras
  • computeType: "editable" + $js actúa como valor por defecto al crear, que luego se puede sobrescribir
  • Los campos calculados pueden referirse a $id, $iid, $version, $createdAt, $updatedAt y $kinds
#

Hooks

Los hooks viven en la parte alta del esquema, en un mapa schema.hooks indexado por nombre, y apuntan a Units mediante { ops, $kinds, $filter }. Cada hook declara su categoría con type (unit.validate, unit.transform o unit.effect) y se ejecuta dentro del pipeline de mutación cuando su target coincide con el borrador de la Unit. Úsalos cuando una escritura deba derivar campos, validar el borrador final combinando varios campos o lanzar efectos secundarios después del commit.

BQL · transform + validate + effect
{
  "kinds": {
    "BlogPost": {
      "dataFields": {
        "title": { "valueType": "TEXT" },
        "slug": { "valueType": "TEXT" },
        "status": { "valueType": "TEXT" },
        "word_count": { "valueType": "INTEGER" }
      }
    }
  },
  // Hooks live as a sibling of kinds at the schema level (refactored 2026-05-09).
  // Each hook declares its category via "type" and applicability via "target".
  "hooks": {
    // transform: derive or normalize fields before validation
    "BlogPost.generateSlug": {
      "type": "unit.transform",
      "target": {
        "ops": ["create", "update"],
        "$kinds": { "$any": ["BlogPost"] }
      },
      "when": { "$js": "$this.title" },
      "$js": "({ slug: $this.title.toLowerCase().replace(/\\s+/g, '-') })"
    },
    "BlogPost.defaultStatus": {
      "type": "unit.transform",
      "target": {
        "ops": ["create"],
        "$kinds": { "$any": ["BlogPost"] }
      },
      "when": { "$js": "!$this.status" },
      "$js": "({ status: 'draft' })"
    },
    // validate: reject an invalid final draft state
    "BlogPost.requireTitle": {
      "type": "unit.validate",
      "target": {
        "ops": ["create", "update"],
        "$kinds": { "$any": ["BlogPost"] }
      },
      "$js": "$this.title && $this.title.length > 0",
      "message": "Title is required",
      "severity": "error"
    },
    // effect: post-commit side effects (no pre-effect on schema.hooks)
    "BlogPost.postLog": {
      "type": "unit.effect",
      "target": {
        "ops": ["create"],
        "$kinds": { "$any": ["BlogPost"] }
      },
      "$js": "true"
    }
  }
}
BQL · hook de transformación remoto
{
  "kinds": {
    "Person": {
      "dataFields": {
        "name": { "valueType": "TEXT" }
      }
    }
  },
  "hooks": {
    "Person.upcase": {
      "type": "unit.transform",
      "target": {
        "ops": ["create", "update"],
        "$kinds": { "$any": ["Person"] }
      },
      "remote": "upcase_name@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
    }
  }
}
Cómo se ejecutan los hooks
  • El orden es transform → validate → effect
  • Los hooks de validación ven el estado final de la transacción y también pueden inspeccionar $input
  • Los hooks que apuntan a clases ancestras pueden incluir descendientes con $descendants
  • Los hooks de transformación pueden devolver parches en línea o con un return explícito
  • El cuerpo de un hook acepta $js, $ts (con los tipos borrados) o un módulo WASM remote fijado
Límites actuales
  • Los hooks mutan $this; las cascadas entre unidades todavía no están soportadas
  • unit.effect se ejecuta después del commit; las comprobaciones que bloquean van en unit.validate
  • Los hooks de transformación sobre link todavía no están implementados
  • Los bucles de transformación que no convergen fallan por la protección de profundidad máxima

Cuando se borra una unidad y esa ruptura corta sus relaciones, cada ruptura llega a los hooks de update de la unidad superviviente como un evento unlink en $delta.arcs. Ese mismo flujo aparece también en $history y en la salida normalizada de la mutación, así que un superviviente puede reaccionar a perder una conexión.

BQL · observar eventos unlink en un hook de update
{
  "hooks": {
    // Post survives when its author User is deleted; the broken arc
    // arrives as an unlink entry in $delta.arcs on the survivor's update.
    "Post.markUnlinkSeen": {
      "type": "unit.transform",
      "target": {
        "ops": ["update"],
        "$kinds": { "$any": ["Post"] }
      },
      "$js": "var arcs = ($delta && $delta.arcs) || []; return { unlinkSeen: arcs.some(a => a.$op === 'unlink') };"
    }
  }
}
#

Relaciones

  • Un vive en el lado de la relación y define quién puede conectarse y cuántos ()
  • Un vive en el lado del jugador y nombra la clase de relación más el rol que juega en ella
  • Las también son clases, así que pueden llevar sus propios campos de datos y consultarse directamente
  • target: "relation" devuelve las unidades de relación; target: "role" y targetRoles proyectan hasta el extremo o los extremos opuestos

Hay dos formas que conviene recordar. Para una dependencia simple como libro → autor, pon el en un lado y deja el inverso como . Eso es una relación directa, no un túnel. La regla de Giulietta es solo una recomendación para elegir el propietario. Cuando la conexión necesita sus propios campos, crea una clase de relación intermedia y expón un enlace en crudo más un túnel proyectado en cada lado si eso mejora la ergonomía de las consultas.

BQL · relación binaria directa: Author ↔ Book
// Direct relation, no tunnel.
// One valid ownership choice: Book owns the roleField, Author keeps the reverse linkField.
{
  "kinds": {
    "Author": {
      "dataFields": {
        "name": { "valueType": "TEXT" },
        "slug": { "valueType": "TEXT", "unique": true }
      },
      "linkFields": {
        "books": {
          "relation": "Book",
          "plays": "author"
        }
      }
    },
    "Book": {
      "dataFields": {
        "title": { "valueType": "TEXT" },
        "publishedYear": { "valueType": "INTEGER" }
      },
      "roleFields": {
        "author": {
          "playedBy": ["Author"],
          "cardinality": "ONE",
          "required": true
        }
      }
    }
  }
}
BQL · relación intermedia: Company ↔ Membership ↔ Employee
{
  "kinds": {
    "Company": {
      "dataFields": {
        "name": { "valueType": "TEXT" }
      },
      "linkFields": {
        "memberships": {
          "relation": "Membership",
          "plays": "company",
          "target": "relation"
        },
        "employees": {
          "relation": "Membership",
          "plays": "company",
          "target": "role",
          "targetRoles": ["employee"]
        }
      }
    },
    "Employee": {
      "dataFields": {
        "name": { "valueType": "TEXT" },
        "email": { "valueType": "EMAIL" }
      },
      "linkFields": {
        "memberships": {
          "relation": "Membership",
          "plays": "employee",
          "target": "relation"
        },
        "companies": {
          "relation": "Membership",
          "plays": "employee",
          "target": "role",
          "targetRoles": ["company"]
        }
      }
    },
    "Membership": {
      "dataFields": {
        "title": { "valueType": "TEXT" },
        "startDate": { "valueType": "DATE" }
      },
      "roleFields": {
        "company": { "playedBy": ["Company"], "cardinality": "ONE" },
        "employee": { "playedBy": ["Employee"], "cardinality": "ONE" }
      }
    }
  }
}
BQL · consultar los campos de datos de la relación
{
  "$kinds": "Company",
  "$fields": [
    "name",
    {
      "$expand": "memberships",
      "$fields": [
        "title",
        "startDate",
        { "$expand": "employee", "$fields": ["name", "email"] }
      ]
    }
  ]
}
ONE
0 o 1 conexión. Añade required para exactamente 1.
MANY
0..N conexiones. Admite límites min y max.
INTERVAL
Solo para campos de datos. Los campos de rol y de enlace lo rechazan. Los valores usan la forma de entrada { "#intervals": ... }. Los resultados no llevan clave de casting; su forma sigue el intervalFormat del campo.
Roles polimórficos

Un rol lo pueden jugar varias clases mediante playedBy: ["A", "B"]. Las creaciones anidadas deben indicar $setKinds para que el motor sepa qué clase crear; cuando playedBy tiene exactamente una clase, se infiere.

BQL · rol polimórfico + túnel proyectado
{
  "kinds": {
    "Employee": {
      "dataFields": { "name": { "valueType": "TEXT" } }
    },
    "Agency": {
      "dataFields": { "name": { "valueType": "TEXT" } }
    },
    "Project": {
      "dataFields": { "name": { "valueType": "TEXT" } },
      "linkFields": {
        "assignments": {
          "relation": "Assignment",
          "plays": "project",
          "target": "relation"
        },
        "assignees": {
          "relation": "Assignment",
          "plays": "project",
          "target": "role",
          "targetRoles": ["assignee"]
        }
      }
    },
    "Assignment": {
      "roleFields": {
        "project": { "playedBy": ["Project"], "cardinality": "ONE" },
        "assignee": { "playedBy": ["Employee", "Agency"], "cardinality": "ONE" }
      }
    }
  }
}

// Nested create: $setKinds disambiguates the polymorphic assignee
{
  "$setKinds": ["Assignment"],
  "project": "project_123",
  "assignee": { "$setKinds": ["Agency"], "name": "Acme Staffing" }
}
Relaciones simétricas y sobre sí mismas

symmetric: true en un campo de rol colapsa A↔B y B↔A en una sola arista: el duplicado se rechaza con SYMMETRIC_RELATION_EXISTS. Por defecto, cada rol rechaza que el mismo jugador aparezca en dos roles distintos de la misma unidad de relación (SELF_RELATION_FORBIDDEN); actívalo rol a rol con allowSelfRelation: true.

BQL · amistad simétrica + autorreferencia permitida en Document
{
  "kinds": {
    "Friendship": {
      "roleFields": {
        "friends": { "playedBy": ["User"], "cardinality": "MANY", "symmetric": true }
      }
    },
    "Reference": {
      "roleFields": {
        "from": { "playedBy": ["Document"], "allowSelfRelation": true },
        "to":   { "playedBy": ["Document"], "allowSelfRelation": true }
      }
    }
  }
}
#

Operaciones de esquema

Cuatro endpoints gestionan las en tiempo de ejecución: import, export, query y mutate. Import y export usan cuerpos JSON directos con opts.defaultSubspace. Query y mutate usan los sobres query / mutation.

BQL · definitions/import · esquema completo (solo aditivo)
// POST /definitions/import
{
  "schema": {
    "kinds": {
      "User": {
        "dataFields": {
          "name": { "valueType": "TEXT" },
          "email": { "valueType": "EMAIL", "unique": true }
        },
        "linkFields": {
          "posts": { "relation": "Post", "plays": "author" }
        }
      },
      "Post": {
        "dataFields": { "title": { "valueType": "TEXT" } },
        "roleFields": {
          "author": { "playedBy": ["User"], "cardinality": "ONE" }
        }
      }
    }
  },
  "opts": { "defaultSubspace": "main" }
}
BQL · definitions/export · definiciones actuales del subspace
// POST /definitions/export
{ "opts": { "defaultSubspace": "main" } }

// → { "data": { "$bzv": "<release>", "schema": { "kinds": { ... } }, "portals": { ... } } }
// The native artifact includes its schema branch; read data.schema for schema-only consumers.
// For per-field type metadata: GET /docs/list?section=term, then GET /docs/term/<TYPE>.

Los endpoints de definiciones mueven esquema, portales, automatizaciones y declaraciones de entorno. Las operaciones runs.transfer de /runs/query y /runs/mutate, con los bytes en /runs/artifacts, mueven artefactos almacenados y bundles .bzg de namespace o subspace. Usan por defecto el perfil portable: conservan los datos ordinarios seleccionados, incluidas las unidades User/Persona, y excluyen cuentas, credenciales, sesiones, permisos y autoridad activa del origen. Las constantes públicas conservan su valor; privateConstants y secrets viajan como nombres declarados sin configurar. Los templates de subspace incluyen las declaraciones que necesitan sus definiciones; importar env también requiere runs.transfer.import. La importación de bundles es asíncrona y solo se reanuda con el mismo artefacto y opciones. El perfil explícito sealedRecovery solo está disponible en rutas directas de bundle para operadores de base de datos o servidor y exige el alcance exacto del origen con definiciones y unidades; los artefactos almacenados lo rechazan.

BQL · exportación de bundle de namespace → importación en segundo plano
// Preview the selected namespace through the existing export planner.
POST /runs/query
{ "query": { "$type": "transfer", "$op": "planExport" } }

// Stage a portable export; writes pause only during artifact staging.
POST /runs/mutate
{ "mutation": {
  "$type": "transfer", "$op": "export", "profile": "portable", "scope": "namespace",
  "include": { "definitions": true, "units": true, "files": false },
  "subspaces": ["main"]
} }
GET /runs/artifacts/<returned-artifactId>?source=stored

// Stage raw bytes; retain the returned artifactId.
POST /runs/artifacts
Content-Type: application/octet-stream

// Import the staged artifact asynchronously in the selected namespace.
POST /runs/mutate
{ "mutation": {
  "$type": "transfer", "$op": "import", "profile": "portable", "scope": "namespace",
  "include": { "definitions": true, "units": true, "files": false },
  "source": { "kind": "uploaded", "artifactId": "<staged-id>" }
} }

// Poll the accepted importId. Only terminal.status === "succeeded" confirms completion.
POST /runs/query
{ "query": { "$type": "transfer", "$op": "query", "$iid": "<accepted-import-id>" } }

// SDK: portable subspace rename in the selected destination namespace.
const staged = await admin.runs.artifact.upload(file, { namespace: "destination" })
await admin.runs.mutate({ mutation: {
  $type: "transfer", $op: "import", profile: "portable", scope: "subspace", $subspace: "renamed",
  include: { definitions: true, units: true, files: false },
  source: { kind: "uploaded", artifactId: staged.data.artifactId }
} }, { namespace: "destination" })

// Sealed recovery uses the same custody owner with explicit sealedRecovery profile.
// It requires database/server authority and exact source coordinates.

El viaje de ida y vuelta de los FILE lo controla include.files. En las rutas de bundle vale false por defecto para evitar salidas de datos accidentales; ponlo a true para llevar todos los blobs, o a un objeto { "perSubspace": { ... } } para campos FILE concretos. Las rutas de artefactos a nivel de store ponen include.files a true por defecto porque la transferencia se queda en el servidor.

La autorización de los usuarios de aplicación también se escribe en las definiciones. Una clase sujeto se marca con userKind y un campo de identidad de correo. Las clases protegidas usan reglas permissions evaluadas con $me. Los portales usan reglas access para la admisión de app, de página o de endpoint.

BQL · userKind + permissions + acceso al portal
{
  "schema": {
    "kinds": {
      "Member": {
        "userKind": { "authFields": ["email"] },
        "idField": "email",
        "dataFields": {
          "email": { "valueType": "EMAIL", "unique": true },
          "name": { "valueType": "TEXT" }
        }
      },
      "Admin": {
        "extends": "Member",
        "userKind": { "authFields": ["email"] }
      },
      "Note": {
        "dataFields": {
          "title": { "valueType": "TEXT" },
          "privateText": { "valueType": "TEXT" },
          "ownerId": { "valueType": "TEXT", "required": true }
        },
        "permissions": {
          "query": "ownerId",
          "create": { "check": { "$me.$kinds": "Member", "ownerId": "$me" } },
          "fields": {
            "privateText": { "query": { "$me.$kinds": "Admin" } }
          }
        }
      }
    }
  },
  "env": {
    "domains": {
      "notes-auth": {
        "secrets": ["resend_api_key"]
      }
    }
  },
  "portals": {
    "apps": {
      "notes": {
				"slug": "notes",
        "env": ["notes-auth"],
        "access": { "$kinds": ["Member"] },
        "auth": {
          "providers": {
            "emailPassword": { "enabled": true }
          },
          "delivery": {
            "provider": "resend",
            "apiKey": { "$secret": "env.notes-auth.resend_api_key" },
            "fromAddress": "Notes <[email protected]>"
          },
          "signup": {
            "mode": "open",
            "blueprint": {
              "$setKinds": ["Member"],
              "$authAnchorKind": "Member",
              "email": { "$auth": "email" },
              "name": { "$input": "name" }
            }
          }
        },
        "pages": {
          "/": {
            "tsxSource": "export default function NotesPage() { return null }"
          }
        }
      }
    }
  }
}
BQL · definitions/query · cualquier tipo de definición
// POST /definitions/query: all kinds
{ "query": { "$type": "kind" }, "opts": { "defaultSubspace": "main" } }

// Query a specific kind by $did
{ "query": { "$type": "kind", "$did": "K:abc123" } }

// Query fields of a kind
{ "query": { "$type": "dataField", "$filter": { "ownerKind": "User" } } }
BQL · definitions/mutate · cambios incrementales
// POST /definitions/mutate
{
  "mutation": [
    {
      "$op": "create",
      "$type": "kind",
      "name": "Product",
      "dataFields": {
        "title": { "valueType": "TEXT" },
        "price": { "valueType": "INTEGER" }
      }
    },
    {
      "$op": "create",
      "$type": "roleField",
      "ownerKind": "Order",
      "name": "customer",
      "playedBy": ["User"],
      "cardinality": "ONE"
    },
    {
      "$op": "create",
      "$type": "linkField",
      "ownerKind": "User",
      "name": "orders",
      "relation": "Order",
      "plays": "customer",
      "target": "relation"
    }
  ],
  "opts": { "defaultSubspace": "main" }
}

Consultas

Cada consulta es un objeto JSON que se envía a POST /units/query. Los resultados vuelven como JSON y su forma es predecible a partir de tu consulta.

#

Fundamentos

Usa $kinds para seleccionar por clase, $id para traer una unidad concreta y $fields para controlar qué vuelve.

BQL · todos los usuarios
// Returns all User units (array)
{ "$kinds": "User", "$fields": "*" }
BQL · por ID
// Returns one unit (object or null)
{ "$id": { "User": "abc123" }, "$fields": "*" }
BQL · campos concretos + orden + paginación
{
  "$kinds": "User",
  "$fields": ["name", "email", "age"],
  "$sort": [{ "$by": "age", "$order": "desc" }],
  "$limit": 10,
  "$offset": 20
}

Para conjuntos de resultados grandes, prefiere $cursor antes que $offset. Pasa $cursor junto con $limit; la respuesta devuelve un token opaco meta.$nextCursor que reenvías tal cual como el siguiente $cursor. Si no aparece meta.$nextCursor, has llegado a la última página. Nunca analices ni construyas el token. La paginación por cursor requiere $limit, excluye $offset, solo vale en la consulta raíz y se rechaza con $groupBy y con agregados en la raíz.

BQL · paginación por cursor · reenviar $nextCursor
// First page
{ "$kinds": "Task", "$limit": 50 }

// → { "data": [ ... ], "meta": { "count": 50, "$nextCursor": "eyJ..." } }

// Next page: replay meta.$nextCursor verbatim as $cursor
{ "$kinds": "Task", "$limit": 50, "$cursor": "<token from meta.$nextCursor>" }

// No meta.$nextCursor in the response → last page
#

Proyecciones

$fields controla qué datos vuelven. Usa "*" para todos los campos, un array para campos concretos o $excludedFields para excluir. Los campos de arco en $fields devuelven los IDs sin expandir. Usa $expand para traer las unidades completas. Cuando varias clases compuestas definen el mismo nombre de campo de forma distinta, usa $definedOn para elegir la clase que lo define; una unidad multiclase realmente ambigua devuelve un marcador $ambiguous en lugar de adivinar.

BQL · modos de selección de campos
// All fields
{ "$kinds": "User", "$fields": "*" }

// Specific fields only
{ "$kinds": "User", "$fields": ["name", "email"] }

// All except some
{ "$kinds": "User", "$fields": "*", "$excludedFields": ["password"] }

// Arc fields without $expand → raw IDs
{ "$kinds": "Post", "$fields": ["title", "author"] }
// → { "title": "Hello", "author": "user_abc123" }
BQL · $definedOn · desambiguar un nombre de campo compuesto
{
  "$kinds": ["Bot", "User"],
  "$fields": [
    { "$fetch": "name", "$definedOn": "User", "$as": "userName" },
    { "$fetch": "name", "$definedOn": "Bot", "$as": "botName" }
  ]
}
#

Expandir relaciones

$expand recorre las relaciones e inserta las unidades conectadas. Sin N+1: BlitzStore agrupa todos los recorridos automáticamente. Puedes anidar $expand a cualquier profundidad, con sus propios $fields, $filter, $sort y $limit. Usa para incluir las marcas de tiempo de la conexión.

BQL · expandir con relaciones hermanas
{
  "$kinds": "Post",
  "$fields": [
    "title",
    { "$expand": "author", "$fields": ["name", "email"] },
    { "$expand": "comments", "$fields": ["text"] }
  ]
}
BQL · expansión anidada con filtro y orden
{
  "$kinds": "User",
  "$fields": [
    "name",
    {
      "$expand": "posts",
      "$filter": { "status": "published" },
      "$sort": [{ "$by": "title", "$order": "asc" }],
      "$limit": 5,
      "$fields": ["title", "status"]
    }
  ]
}

Usa $as para renombrar en la respuesta un campo expandido o virtual cuando el nombre del esquema no se lee bien en el cliente.

BQL · $as · renombrar un campo en la respuesta
{
  "$kinds": "User",
  "$fields": [
    { "$expand": "posts", "$as": "articles", "$fields": ["title"] }
  ]
}
#

Metadatos de arco

funciona como , pero envuelve cada resultado en un sobre de metadatos de con $arcCreatedAt (marca de tiempo ISO). Útil para ver cuándo se crearon las conexiones: amistades, membresías, trazas de auditoría.

BQL · $expandArc · conexiones con marcas de tiempo
{
  "$kinds": "Team",
  "$id": { "Team": "team1" },
  "$fields": [
    "name",
    { "$expandArc": "members", "$fields": ["name"] }
  ]
}

// Response wraps each member in an arc envelope:
// { "name": "Devs", "members": [
//   { "$arcCreatedAt": "2026-03-15T10:30:00Z", "$unit": { "$id": "...", "name": "Alice" } },
//   { "$arcCreatedAt": "2026-03-16T09:00:00Z", "$unit": { "$id": "...", "name": "Bob" } }
// ]}

Dentro de también puedes filtrar y ordenar las relaciones por cuándo se creó cada conexión, usando $arcCreatedAt como clave de filtro y de orden. El predicado sobre la hora del arco se evalúa antes de traer las unidades conectadas, así que las conexiones que no coinciden se descartan pronto.

BQL · $arcCreatedAt · filtrar y ordenar conexiones por hora de creación
{
  "$kinds": "Team",
  "$id": { "Team": "t1" },
  "$fields": [
    {
      "$expandArc": "members",
      "$filter": { "$arcCreatedAt": { "$gte": { "#datetime": "2026-01-01T00:00:00.000000Z" } } },
      "$sort": "-$arcCreatedAt",
      "$limit": 10
    }
  ]
}
  • Solo operadores de comparación: $gte, $gt, $lte, $lt, $eq, $neq
  • Como filtro, $arcCreatedAt debe ser una condición de nivel superior (o estar dentro de un $and de nivel superior)
  • Como clave de orden debe ser la clave principal (la primera)
  • Solo es válido dentro de , nunca en un normal
#

Agregaciones

$groupBy agrupa los resultados por valores de campo, pero no hace falta para obtener una única fila resumen en la raíz. Agrega con operadores $agg como COUNT, SUM, AVG, MIN, MAX y más. Funciona en el nivel raíz y anidado dentro de $expand.

BQL · agregado en la raíz sin agrupar
{
  "$kinds": "Order",
  "$aggFields": [
    { "%count": { "$agg": "COUNT" } },
    { "%total": { "$agg": "SUM", "$field": "amount" } },
    { "%avg": { "$agg": "AVG", "$field": "amount" } }
  ]
}

$groupBy emite por sí solo las filas con las claves de grupo distintas; añade $aggFields cuando esas filas necesiten métricas. $groupBy.$filter es el único filtro posterior a la agrupación, y el $sort, el $limit y el $offset del objeto $groupBy ordenan y paginan los grupos. El $sort / $limit / $offset a nivel de nodo acota las unidades candidatas antes de agrupar. Las claves de grupo pueden ser campos escalares almacenados o calculados, identidades de arco ONE, o expresiones $js/$ts/$code/$expr escritas por ti con un nombre $as. Las claves con valores MANY se rechazan de forma explícita en lugar de crear grupos ambiguos.

BQL · agrupar con varias agregaciones
{
  "$kinds": "Order",
  "$groupBy": {
    "$by": ["status"],
    "$filter": { "total": { "$gte": 1000 } },
    "$sort": [{ "$by": "total", "$order": "desc" }],
    "$limit": 5
  },
  "$aggFields": [
    { "%count": { "$agg": "COUNT" } },
    { "%total": { "$agg": "SUM", "$field": "amount" } }
  ]
}
BQL · agrupar por identidad de arco y por expresión normalizada
{
  "$kinds": "Order",
  "$groupBy": {
    "$by": [
      "customer",
      { "$js": "status.trim().toLowerCase()", "$as": "normalizedStatus" }
    ]
  },
  "$aggFields": [
    { "%count": { "$agg": "COUNT" } }
  ]
}
BQL · agregación dentro de $expand
{
  "$kinds": "Team",
  "$fields": [
    "name",
    {
      "$expand": "members",
      "$groupBy": ["position"],
      "$aggFields": [
        { "%count": { "$agg": "COUNT" } }
      ]
    }
  ]
}
COUNT
SUM
AVG
MIN
MAX
MEDIAN
LIST
SET
FIRST
LAST
EVERY
SOME
Todo junto
BQL · búsqueda polimórfica + campos calculados
{
  "$kinds": { "$all": ["Human", "Spanish"] },
  "$search": "senior backend rust",
  "$filter": { "role": { "$in": ["engineer", "designer"] } },
  "$fields": [
    "name", "salary", "bonus",
    { "%total": { "$js": "salary + bonus" } },
    {
      "$expand": "projects",
      "$sort": [{ "$by": "budget", "$order": "desc" }],
      "$limit": 3,
      "$fields": [
        "title", "budget", "spent",
        { "%remaining": { "$js": "budget - spent" } }
      ]
    }
  ]
}

Mutaciones

Envía las mutaciones a POST /units/mutate. Las operaciones se infieren de la forma de tu JSON, o fija $op de forma explícita.

#

Crear, actualizar y borrar

$setKinds sin selector crea y un selector con campos actualiza. Borrar requiere $op: "delete".

BQL · crear, actualizar, borrar
// Create: $setKinds without $id
{ "$setKinds": ["User"], "name": "Alice", "email": "[email protected]" }

// Update: $id with fields
{ "$id": "user_abc123", "email": "[email protected]" }

// Delete: $op with a selector
{ "$op": "delete", "$id": "user_abc123" }

// Bulk delete: with $filter
{ "$op": "delete", "$kinds": "Task", "$filter": { "done": true } }
create
$setKinds sin $id
update
$id con campos de datos
delete
$op: "delete" con un selector
#

Upsert

$op: "upsert" actualiza cuando la identidad resuelve una unidad existente y crea cuando no. Como puede crear, $setKinds es obligatorio. La identidad puede venir de $id o de $filter, pero nunca de los dos.

BQL · upsert por $id
// Kind has idField: "email"
// First call creates
{
  "$op": "upsert",
  "$id": "[email protected]",
  "$setKinds": ["User"],
  "email": "[email protected]",
  "name": "Alice"
}

// Second call updates same unit
{
  "$op": "upsert",
  "$id": "[email protected]",
  "$setKinds": ["User"],
  "name": "Alice V2"
}
BQL · upsert por $filter
// 0 matches -> create
{
  "$op": "upsert",
  "$setKinds": ["User"],
  "$filter": { "email": "[email protected]" },
  "email": "[email protected]",
  "name": "Bob"
}

// 1 match -> update
{
  "$op": "upsert",
  "$setKinds": ["User"],
  "$filter": { "email": "[email protected]" },
  "name": "Bob V2"
}
Selectores de identidad válidos
Usa $id para la identidad directa, o $filter para una búsqueda que coincida con 0 o 1 unidad.
Errores
$id + $filter no es válido. Un $filter que coincida con 2 o más unidades tampoco lo es.
#

Operaciones por lotes

Pasa un array para hacer mutaciones por lotes atómicas. O tienen éxito todas o se deshacen todas. Puedes mezclar creaciones, actualizaciones y borrados en el mismo lote. Usa $var para referenciar unidades entre operaciones, o anida las unidades hijas directamente dentro de su padre.

Referencias $var: captura una unidad creada y enlázala después en el mismo lote
BQL · lote con $var: crear dos unidades y unirlas
[
  { "$var": "_:user", "$setKinds": ["User"], "name": "Alice" },
  { "$var": "_:acct", "$setKinds": ["Account"], "provider": "github" },
  { "$setKinds": ["UserAccount"], "user": "_:user", "account": "_:acct" }
]
BQL · árbol anidado: incrustar las unidades hijas en el campo del padre
{
  "$setKinds": ["UserAccount"],
  "user": { "$setKinds": ["User"], "name": "Alice" },
  "account": { "$setKinds": ["Account"], "provider": "github" }
}
Anidamiento profundo (3 niveles)
BQL · anidamiento profundo (3 niveles)
// User → Tag → Group → Colors, all created atomically
{
  "$setKinds": ["User"],
  "tags": [{
    "$setKinds": ["Tag"],
    "group": {
      "$setKinds": ["Group"],
      "colors": [
        { "$setKinds": ["Color"], "name": "Red" },
        { "$setKinds": ["Color"], "name": "Blue" }
      ]
    }
  }]
}
Operaciones mezcladas en un lote
BQL · operaciones mezcladas en un lote
// Create + update in the same atomic batch
[
  { "$setKinds": ["User"], "name": "Bob", "status": "new" },
  { "$id": "existing_user_id", "status": "updated" }
]
#

Importación de datos

POST /units/import es un carril rápido de solo creación para sembrar conjuntos de datos. Los elementos raíz sin un $op explícito toman create por defecto; un elemento de arco también puede llevar $op: "link"; cualquier otro $op se rechaza. El constructor ordena topológicamente las referencias $var y trocea los arrays grandes, así que una sola carga puede llevar un conjunto de datos entero y respetar el orden de padres antes que hijos.

BQL · ids públicos en clases con idField
// Country has idField:"code". Send code as data;
// $id is used to resolve same-import arcs.
POST /units/import
{
  "units": [
    { "$id": "country:mex", "$kinds": ["Country"],
      "code": "MEX", "name": "Mexico" },
    { "$id": "country:bra", "$kinds": ["Country"],
      "code": "BRA", "name": "Brazil" },
    {
      "$id": "sticker:mex-logo", "$kinds": ["Sticker"],
      "code": "MEX1", "label": "Logo", "country": "country:mex"
    }
  ]
}
BQL · exportar → importar (se conserva la topología de arcos)
// units.export emits query-like units with $id, $kinds,
// data fields, and raw arc IDs. It omits $op, $var, $iid.
POST /units/import
{
  "units": [
    { "$id": "user:alice", "$kinds": ["User"],
      "name": "Alice", "posts": ["post:hello"] },
    { "$id": "post:hello", "$kinds": ["Post"],
      "title": "Hello", "author": "user:alice" }
  ]
}
$id en la importación
El $id de un objeto de creación importado resuelve las referencias de arco dentro de la misma importación y se descarta antes de crear; envía los valores de idField como campos de datos normales.
Referencias dentro del lote
Usa los valores $id exportados o $var: "_:<name>" en el padre y referéncialo desde los hijos. El constructor reserva los ULID por adelantado y ordena topológicamente. Los ULID internos nunca los elige quien llama.
Progreso por SSE
Añade Accept: text/event-stream a POST /units/import para recibir eventos de progreso por trozos; omítelo para obtener un JSON de una sola vez. opts.batchSize ajusta el troceado en el servidor (5000 por defecto): no lo dividas en el cliente.
#

Límites de tamaño de lote

units.mutate limita una sola llamada a 10.000 operaciones por defecto; definitions.mutate limita a 500 operaciones. Cuenta cada nodo de mutación raíz o anidado (create, update, upsert, delete, query), más cada destino explícito de arco link / unlink / replace. Una operación masiva acotada por filtro cuenta como una, coincida con las unidades que coincida: el tope acota el tamaño del lote de entrada, no el número de coincidencias. Los nodos se cuentan después de expandir $for / $if.

Superar el tope devuelve BATCH_TOO_LARGE, que dirige a quien envía datos hacia llamadas units.mutate más pequeñas o hacia unidades nuevas por trozos con units.import, y a quien envía definiciones hacia llamadas definitions.mutate más pequeñas. Sube el tope por llamada (hasta el tope duro del servidor) o por namespace (mutation.maxBatchSize / definitionMutation.maxBatchSize, solo al alza: la configuración del namespace no puede bajar el valor por defecto del servidor). maxTotalGeneratedItems (100.000) sigue siendo el techo absoluto de expansión de $for aunque subas maxBatchSize.

#

Control de flujo

$for genera elementos de mutación a partir de un bucle; $if los incluye de forma condicional. El control de flujo se expande antes de que corra el motor, así que el lote se mantiene plano y atómico. Ambos se pueden anidar y mezclar con elementos normales.

BQL · $for · origen de array o de $range
// Range source (inclusive)
{
  "$for": { "$in": { "$range": [1, 3] }, "$as": "_:i" },
  "$do": [
    { "$setKinds": ["User"], "name": "user{_:i}", "index": "_:i" }
  ]
}

// Array source
{
  "$for": { "$in": ["rust", "graph", "blitz"], "$as": "_:tag" },
  "$do": [{ "$setKinds": ["Tag"], "name": "_:tag" }]
}
BQL · $if · inclusión condicional
// With optional else branch
{
  "$if": { "$js": "1 > 2" },
  "$then": [{ "$setKinds": ["Status"], "value": "then" }],
  "$else": [{ "$setKinds": ["Status"], "value": "else" }]
}

Escribe {_:var} dentro de las cadenas para interpolar la variable del bucle. El total de iteraciones está acotado por los límites de consulta para evitar bucles descontrolados.

#

Operaciones de grafo

Gestiona las conexiones de con $op dentro de los campos de arco (mira para las marcas de tiempo). link añade conexiones, unlink las quita y replace las fija exactamente. La asignación directa (azúcar de DX) es la forma abreviada de replace.

BQL · operaciones de arco
// Link: add connections
{ "$id": "book1", "authors": { "$op": "link", "$id": "user1" } }

// Unlink: remove a connection
{ "$id": "book1", "authors": { "$op": "unlink", "$id": "user1" } }

// Replace: set exact connections
{ "$id": "book1", "authors": { "$op": "replace", "$id": ["user1", "user2"] } }

// DX sugar: direct assignment = replace
{ "$id": "book1", "authors": ["user1", "user2"] }
BQL · enlazar por filtro
{
  "$id": "book1",
  "authors": {
    "$op": "link",
    "$kinds": "User",
    "$filter": { "role": "dev" }
  }
}

En los linkField con target: "role", las escrituras con forma de extremo directo se rechazan. Usa una actualización proyectada del extremo para las unidades relacionadas existentes, o crea el árbol de relación de forma explícita.

BQL · consulta por túnel y actualización proyectada
// Query projected endpoints through a tunnel link
{
  "$kinds": "Candidate",
  "$fields": [
    "name",
    {
      "$expand": "interviewers",
      "$fields": ["name", "department"]
    }
  ]
}

// Update the projected endpoints selected by the tunnel
{
  "$id": "candidate_123",
  "interviewers": {
    "$op": "update",
    "$filter": { "department": "Ops" },
    "department": "Platform"
  }
}
BQL · creación del árbol de relación por túnel
{
  "$id": "candidate_123",
  "interviewers": {
    "$setKinds": ["Interview"],
    "date": "2026-04-11T09:00:00Z",
    "interviewer": {
      "$setKinds": ["Interviewer"],
      "name": "Dana",
      "department": "Platform"
    }
  }
}
#

Expresiones

Usa $js para expresiones JavaScript en línea o $ts para TypeScript (los tipos se borran antes de ejecutar; la forma explícita es $code: { $lang, $body }). Funciona en las escrituras de campos de datos al crear y al actualizar (incluidos el upsert y las actualizaciones de arco acotadas). Al crear solo ve los campos hermanos; al actualizar ve la unidad almacenada además de los hermanos, en orden topológico. La autorreferencia lee el valor almacenado antes de la escritura. Declara capturas con "$var": "_:name"; léelas en expresiones con $var.name o $var["name"]. Las dependencias circulares entre hermanos se rechazan. Los hooks ven la expresión tal como se escribió, no el valor evaluado. Comprueba la ausencia con isNull(x); los locales DECIMAL son objetos: usa decimal().

Una expresión que devuelve null limpia su destino, igual que un null literal. Los fallos en ejecución hacen fallar la mutación por defecto; al actualizar, onExprError: "skip" deja sin escribir solo el campo que falla y emite un aviso. Los objetos con forma de código dentro de los valores o arrays de una operación JSON son JSON literal, no expresiones ejecutables.

BQL · crear · evaluación topológica (tax depende de subtotal, total depende de ambos)
{
  "$setKinds": ["Invoice"],
  "subtotal": 100,
  "tax": { "$js": "subtotal * 0.21" },
  "total": { "$js": "subtotal + tax" }
}
BQL · actualizar · campos almacenados + autoincremento
{
  "$id": "line1",
  "total": { "$js": "price * qty" },
  "count": { "$js": "count + 1" }
}
BQL · transformaciones de cadenas y slugs
{
  "$setKinds": ["Post"],
  "title": "Hello World",
  "slug": { "$js": "title.toLowerCase().replaceAll(' ', '-')" }
}
BQL · expresión typescript · tipos borrados antes de ejecutar
{
  "$setKinds": ["Invoice"],
  "subtotal": 100,
  "total": { "$ts": "const rate: number = 0.21; subtotal * (1 + rate)" }
}
BQL · flujo completo · query + $var + $js
// Query an existing user, then create a post using their data
[
  { "$op": "query", "$var": "_:author", "$kinds": "User", "$id": { "User": "user1" } },
  {
    "$setKinds": ["Post"],
    "title": "Hello World",
    "authorName": { "$js": "$var.author.name" },
    "boost": { "$js": "$var.author.karma * 0.1" },
    "greeting": { "$js": "`Hello ${$var.author.name}!`" }
  }
]

$js también funciona en las consultas como campos virtuales: valores calculados que solo existen en la respuesta, con el prefijo %.

BQL · campos virtuales en consultas
{
  "$kinds": "Order",
  "$fields": [
    "name", "price", "quantity",
    { "%line_total": { "$js": "price * quantity" } },
    { "%tax_label": { "$js": "'Tax: ' + (price * 0.21).toFixed(2)" } }
  ]
}
Literales tipados

Fuerza que un valor JSON tenga un tipo concreto al escribir. La forma canónica es la clave de objeto con el prefijo #: el espacio de nombres $ está reservado para los operadores y selectores de BQL.

BQL · forma canónica de objeto con prefijo #
{
  "$setKinds": ["Invoice"],
  "created":  { "#datetime": "2024-01-15T10:30:00Z" },
  "dueDate":  { "#date":     "2024-02-15" },
  "openAt":   { "#time":     "09:00:00.000000" },
  "price":    { "#decimal":  "19.99" },
  "eta":      { "#duration": "1h30m" },
  "active":   { "#boolean":  "true" },
  "count":    { "#integer":  "42" },
  "ratio":    { "#float":    "3.14" },
  "owner":    { "#unit":     "01J..." },
  "rawText":  { "#text":     ".name" }
}

#datetime requiere zona horaria (Z o +02:00). #decimal conserva la base 10 exacta. #duration combina w/d/h/m/s/ms/u y admite negativos. #text escapa una cadena que si no se interpretaría como azúcar de DX (por ejemplo ".name" dentro de una expresión nativa). Escribe los castings como objetos, por ejemplo { "#datetime": "2024-01-15T10:30:00Z" }.

Valores de intervalo

Un campo INTERVAL almacena un conjunto de rangos. Consúltalo con $has (pertenencia de un punto), $all (superconjunto de intervalos) y $any (cualquier solapamiento). $contains es solo subcadena de TEXT.

BQL · escritura y consultas de intervalos
// Write: a Schedule with two ranges. Compact notation is the
// shortest spelling; a member array of nested tuples also works.
{
  "$setKinds": ["Schedule"],
  "officeHours": { "#intervals": "[09:00:00.000000,13:00:00.000000) U [14:00:00.000000,18:00:00.000000)" }
}

// Read: results never echo the "#intervals" cast. The shape follows the
// field's intervalFormat — "tuple" here, so bounds render as pairs:
// { "officeHours": [["09:00:00.000000","13:00:00.000000"], ["14:00:00.000000","18:00:00.000000"]] }

// Schedules whose office hours contain 10:30
{
  "$kinds": "Schedule",
  "$filter": { "officeHours": { "$has": { "#time": "10:30:00.000000" } } }
}

// Schedules whose office hours overlap a meeting window
{
  "$kinds": "Schedule",
  "$filter": {
    "officeHours": {
      "$any": {
        "#intervals": [[{ "#time": "12:30:00.000000" }, { "#time": "15:30:00.000000" }]]
      }
    }
  }
}
Expresiones nativas

$expr es un carril rápido verificado para funciones concretas de rutas calientes, con el nombre @namespace.fn. Dentro de una llamada con @, una cadena que empieza por . es azúcar de DX para referirse a un campo; envuélvela en #text para conservar la cadena literal. $js sigue siendo la superficie amplia para todo lo que aún no está cubierto.

BQL · campo virtual nativo @text.concat
{
  "$kinds": "User",
  "$fields": [
    "name",
    { "%fullName": { "$expr": { "@text.concat": [".name", " ", ".familyName"] } } }
  ]
}

Respuestas

Todas las respuestas siguen una estructura consistente. La forma de data es predecible a partir de tu consulta.

#

Forma de la respuesta

El campo data es un objeto o null cuando el selector es demostrablemente único: un mapa de claims escalares exactas, un $iid escalar o un filtro de igualdad sobre un campo de datos ONE unique (por ejemplo $filter: { "email": "x" } con email marcado como unique: true). La igualdad con null sobre un campo unique sigue siendo MANY. Un $id público escalar devuelve MANY. Las consultas guiadas solo por $kinds, por filtros no unique, por $id.$in, por un $or en la raíz o por $in devuelven un array no vacío o null, aunque el filtro coincida con una sola unidad. $limit: 1 no cambia la forma de la respuesta.

BQL · ONE · claim escalar
{
  "data": {
    "$id": { "User": "abc123" },
    "$kinds": ["User"],
    "name": "Alice",
    "email": "[email protected]"
  },
  "outcome": "completed"
}
BQL · MANY · por $kinds
{
  "data": [
    { "$id": { "User": "abc123" }, "$kinds": ["User"], "name": "Alice" },
    { "$id": { "User": "def456" }, "$kinds": ["User"], "name": "Bob" }
  ],
  "outcome": "completed",
  "meta": { "count": 2, "timingMs": 1.23 }
}

Configura opts.explain en las opciones de ejecución de una consulta o mutación para incluir el plan en la respuesta. "basic" devuelve la lista de pasos y las unidades que produce cada paso (unitsOut); "full" añade las unidades que lee cada paso (unitsIn) y los tiempos por paso. Útil para afinar.

BQL · opts.explain · plan y tiempos en la respuesta
{ "query": { "$kinds": "User", "$filter": { "status": "active" } }, "opts": { "explain": "full" } }

// Response shape
{
  "data": [ ... ],
  "outcome": "completed",
  "meta": { "count": 10, "timingMs": 1.8 },
  "explain": {
    "steps": [
      { "operation": "kindScan", "detail": { ... }, "unitsIn": 25000, "unitsOut": 10, "durationUs": 1200 }
    ],
    "totalDurationUs": 1800,
    "planner": { ... }
  }
}
#

Metadatos

Las unidades incluyen $id, $iid y $kinds por defecto, sujetos a los permisos de visibilidad. $meta solo es válido dentro de $fields. $meta no es una clave raíz de consulta; inclúyelo dentro de $fields para obtener todos los campos meta siempre activos. Los metadatos contextuales ($score, $history) hay que pedirlos de forma explícita. Los campos de marca de tiempo usan las claves públicas canónicas $createdAt y $updatedAt; ambos se serializan como cadenas ISO.

$id
Mapa de claims de identidad pública por clase hoja; {} si ninguna claim es pública
$iid
ULID interno (22 caracteres en base62)
$kinds
Todos los nombres de clase, ordenados; una lista vacía se muestra como null. Se omite si los permisos ocultan `$kinds`.
$version
Contador de mutaciones
$createdAt
Marca de tiempo ISO-8601 de creación
$updatedAt
Marca de tiempo ISO-8601 de la última mutación
$meta
Abreviatura: se expande a todo lo anterior
$score
Puntuación de relevancia BM25, solo con $search (hay que pedirla)
$history
Flujo de eventos por unidad (en línea con $fields; "$history" = IDs, { $expand: "$history" } = entradas)
#

Resultados y avisos

outcome es el estado de la operación. Los resultados completed, committed y partial usan 200; el trabajo aceptado usa 202; el trabajo rechazado usa normalmente 422 (o 429 por contrapresión).

  • issues es una única lista plana de diagnósticos ordenada por severidad
  • severity es error, warning o info
  • phase es request, execution, commit o postCommit

Una consulta cuyo $filter no ha coincidido con ninguna unidad (y que no era una búsqueda por identidad ni por $offset) tiene éxito con data: null y un aviso que explica el resultado vacío. Los arrays vacíos escritos en la consulta, incluido $in: [], se rechazan. Cada aviso tiene un código estable; el contexto propio de cada aviso va en details.

BQL · aviso de resultado vacío (HTTP 200)
{
  "data": null,
  "outcome": "completed",
  "issues": [
    { "code": "FILTER_MATCHED_NOTHING", "severity": "warning", "phase": "execution", "field": "$filter", "message": "filter matched 0 units" }
  ],
  "meta": { "count": 0, "timingMs": 0.21, "issues": { "total": 1, "returned": 1, "omitted": 0, "errors": 0, "warnings": 1, "info": 0 } }
}
BQL · respuesta rechazada (HTTP 422)
{
  "data": null,
  "outcome": "rejected",
  "issues": [
    { "code": "UNKNOWN_KIND", "severity": "error", "phase": "execution", "message": "unknown kind 'Userr'. Did you mean 'User'?" }
  ],
  "meta": { "timingMs": 0.12 }
}

Ramifica según issue.code con el catálogo tipado que exporta @blitzgraph/client-core (SERVER_ISSUE_CODES, con particiones separadas de errores y de diagnósticos): el catálogo se genera desde el servidor, así que un código desconocido significa que el cliente está desactualizado.

UNKNOWN_KIND
Se ha referenciado una clase que no está definida (incluye una sugerencia por errata)
BATCH_TOO_LARGE
El lote de mutación ha superado maxBatchSize (divídelo en llamadas units.mutate más pequeñas, o crea unidades nuevas por trozos con units.import)
AMBIGUOUS_FIELD_ACROSS_KINDS
Un campo escrito resuelve a definiciones en conflicto entre las clases de la unidad
UNKNOWN_OPTS_KEY
Clave de opción desconocida; incluye data: { unknown, allowed }
PERMISSION_DENIED
El token autenticado no tiene permiso para esa ruta
SIGN_IN_REQUIRED
La admisión al portal necesita una sesión de usuario de aplicación
ACCESS_FORBIDDEN
La regla de acceso del portal ha denegado al sujeto vivo
SECRETS_KEK_UNAVAILABLE
Las operaciones con secretos requieren BLITZSTORE_SECRET_ENV_VAULT_KEYRING
NOT_IMPLEMENTED
Opciones reservadas: parallel, limits.maxMemoryBytes, limits.maxRegexComplexity.
SUBSPACE_LIMIT_EXCEEDED
Crear el subspace superaría el límite de subspaces del namespace

Por defecto, las mutaciones acumulan todos los errores para que veas todos los problemas de una vez. Pon failFast: true en las opciones de mutación para parar en el primer error.

Referencia de la API

Todas las rutas HTTP y todas las operaciones tipadas, generadas desde el manifiesto de la API en su orden único. Los resúmenes son el texto del manifiesto. docs.get operation/<id> devuelve el esquema de entrada y las reglas de una fila.

#

Rutas

Cada ruta indica su método, su ruta y su id. Una ruta de plano como units.query recibe el sobre { query | mutation, opts? }; su $type y su $op eligen una de las operaciones de abajo.

definitions.query
POST /definitions/query
Read definitions: kinds, fields, apps, automations and library members, or lint automation candidates.
definitions.mutate
POST /definitions/mutate
Create, update or delete definitions in one atomic batch.
definitions.emailTemplate.preview
POST /definitions/email-templates/preview
Render a saved email template binding, or the built-in template, with sample values.
definitions.emailTemplate.previewDraft
POST /definitions/email-templates/preview-draft
Render an unsaved email template draft with sample values without writing it.
definitions.export
POST /definitions/export
Export the definitions of the selected subspace as one importable bundle.
definitions.import
POST /definitions/import
Import a definitions bundle into the selected subspace in one commit; the result lists each app's portal status and the imported member counts.
definitions.asset.blob.presign
POST /definitions/assets/blob/presign
Start a Library image or font upload session and return where to send the bytes.
definitions.asset.blob.finalize
POST /definitions/assets/blob/finalize
Verify a Library upload and return the sealed marker for an asset create or update.
definitions.asset.blob.abort
POST /definitions/assets/blob/abort
Cancel a Library upload session and discard its staged bytes.
definitions.asset.blob.upload
PUT /definitions/assets/blob/upload/{blobId}
Receive the raw bytes of a local Library upload session.
definitions.asset.bytes
POST /definitions/assets/bytes
Read the bytes of one Library asset with authenticated, uncached access.
units.query
POST /units/query
Read units with BQL: filters, fields, arcs, sorting and pagination.
units.mutate
POST /units/mutate
Create, update, link or delete units with BQL in one atomic batch.
units.live
POST /units/live
Stream the results of a BQL query and their later changes as server-sent events.
units.export
POST /units/export
Export the units of the selected subspace as JSON or as a stream of chunks.
units.import
POST /units/import
Create units in the selected subspace from inline arrays or an export; import never updates an existing unit and is not idempotent.
units.blob.presign
POST /units/blob/presign
Start a file upload session and return where to send the bytes.
units.blob.finalize
POST /units/blob/finalize
Finish an upload session and return the file marker to store on a unit.
units.blob.abort
POST /units/blob/abort
Cancel an upload session and discard its staged bytes.
units.blob.upload
PUT /units/blob/upload/{blobId}
Receive the raw bytes of a local upload session.
units.operations.query
POST /units/operations/query
Run a declared query operation of an app with its portal identity.
units.operations.mutate
POST /units/operations/mutate
Run a declared mutation operation of an app with its portal identity.
installations.query
POST /installations/query
Read app installations: status, resolution, access explanations and diagnostics.
installations.mutate
POST /installations/mutate
Change app installations: delivery receipts, identities, invites and enrollments.
runs.query
POST /runs/query
Read workflow tooling, run records, run events, workflow triggers and transfers.
runs.mutate
POST /runs/mutate
Start workflows and actions, signal or cancel runs, and manage transfers and triggers.
runs.agents.execute
POST /runs/agents/execute
Run one agent turn and stream its events as server-sent events.
runs.agents.model
POST /runs/agents/model
Stream one model completion for an agent turn with a reserved inference budget.
runs.transfer.exportStream
POST /runs/transfer/export-stream
Stream a sealed recovery export of the namespace as one binary bundle.
runs.stream
GET /runs/{iid}/stream
Stream the events of one run as server-sent events until it ends.
runs.artifact.upload
POST /runs/artifacts
Upload a transfer bundle and store it as an artifact for a later import.
runs.artifact.download
GET /runs/artifacts/{artifactId}
Download a stored transfer artifact as one binary bundle.
resources.query
POST /resources/query
Read resources: databases, namespaces, subspaces, configuration, history, env values, modules and agent setups.
resources.mutate
POST /resources/mutate
Change resources: databases, namespaces, subspaces, configuration, env values, modules and agent setups.
resources.module.upload
POST /resources/modules/{name}
Upload a WebAssembly code module under a name for actions to call.
resources.module.download
GET /resources/modules/{name}
Download the bytes of a stored WebAssembly code module.
server.query
POST /server/query
Read server-wide state: KV tables, metrics, host bindings and keys.
server.mutate
POST /server/mutate
Change server-wide state: grants, limits, keys, usage watermarks and blob quarantine.
docs.overview
GET /docs/overview
Read the agent primer: planes, concepts, operators and how to start.
docs.get
GET /docs/{section}/{name}
Read one docs page by section and name: a concept, operator, example, plane, operation, term, schema or error page.
docs.list
GET /docs/list
List the docs pages, optionally of one section or one plane.
docs.search
POST /docs/search
Search the docs pages by text; error pages are read by name only.
docs.batch
POST /docs/batch
Read several docs pages in one request; a missing page is a warning issue.
docs.check
POST /docs/check
Check a value against a named schema root with the acceptor the server runs for it.
public.root
GET /
Redirect a browser to Studio when the server hosts it.
public.whoami
GET /whoami
Describe the credential that authenticated this request.
public.info
GET /info
Describe the server build and its capabilities.
public.health
GET /health
Report whether the server is ready to serve requests.
public.health.live
GET /health/live
Report whether the server process is alive.
hooks.workflow
POST /hooks/{db}/{namespace}/{subspace}/{source}
Receive an external event for a workflow source and admit it as a run.
hooks.workflow.challenge
GET /hooks/{db}/{namespace}/{subspace}/{source}
Answer the verification challenge of an external event source.
#

Operaciones

Cada id de operación es <plane>.<type>.<op> y nombra la ruta que la transporta. El selector, la gramática de lectura y el permiso mínimo están en su página docs.get operation/<id>.

definitions.kind.query
definitions.query
Read kind definitions; a selector narrows the list.
definitions.kind.create
definitions.mutate
Create a kind definition.
definitions.kind.update
definitions.mutate
Change the kind definitions that the selector addresses.
definitions.kind.delete
definitions.mutate
Delete kind definitions by `$did`, by `$filter`, or every kind with `$all`.
definitions.embedder.query
definitions.query
Read embedder definitions; a selector narrows the list.
definitions.embedder.create
definitions.mutate
Create an embedder definition.
definitions.embedder.update
definitions.mutate
Change the embedder definitions that the selector addresses.
definitions.embedder.delete
definitions.mutate
Delete the embedder definitions that the selector addresses.
definitions.field.query
definitions.query
Read data, role and link field definitions together; a selector narrows the list.
definitions.dataField.query
definitions.query
Read data field definitions; a selector narrows the list.
definitions.dataField.create
definitions.mutate
Create a data field definition.
definitions.dataField.update
definitions.mutate
Change the data field definitions that the selector addresses.
definitions.dataField.delete
definitions.mutate
Delete the data field definitions that the selector addresses.
definitions.roleField.query
definitions.query
Read role field definitions; a selector narrows the list.
definitions.roleField.create
definitions.mutate
Create a role field definition.
definitions.roleField.update
definitions.mutate
Change the role field definitions that the selector addresses.
definitions.roleField.delete
definitions.mutate
Delete the role field definitions that the selector addresses.
definitions.linkField.query
definitions.query
Read link field definitions; a selector narrows the list.
definitions.linkField.create
definitions.mutate
Create a link field definition.
definitions.linkField.update
definitions.mutate
Change the link field definitions that the selector addresses.
definitions.linkField.delete
definitions.mutate
Delete the link field definitions that the selector addresses.
definitions.vectorField.query
definitions.query
Reserved: query a vector field definition; vector fields are not implemented yet.
definitions.vectorField.create
definitions.mutate
Reserved: create a vector field definition; vector fields are not implemented yet.
definitions.vectorField.update
definitions.mutate
Reserved: update a vector field definition; vector fields are not implemented yet.
definitions.vectorField.delete
definitions.mutate
Reserved: delete a vector field definition; vector fields are not implemented yet.
definitions.hook.query
definitions.query
Read schema hook definitions; a selector narrows the list.
definitions.hook.create
definitions.mutate
Create a schema hook definition.
definitions.hook.update
definitions.mutate
Change the schema hook definitions that the selector addresses.
definitions.hook.delete
definitions.mutate
Delete the schema hook definitions that the selector addresses.
definitions.action.query
definitions.query
Read action definitions; a selector narrows the list.
definitions.action.lint
definitions.query
Check unsaved action candidates together and return their diagnostics without writing them.
definitions.action.create
definitions.mutate
Create an action definition.
definitions.action.update
definitions.mutate
Change the action definitions that the selector addresses.
definitions.action.delete
definitions.mutate
Delete the action definitions that the selector addresses.
definitions.workflow.query
definitions.query
Read workflow definitions; a selector narrows the list.
definitions.workflow.lint
definitions.query
Check unsaved workflow candidates together as one batch and return their diagnostics, sharers and prerequisites without writing them.
definitions.workflow.create
definitions.mutate
Create a workflow definition.
definitions.workflow.update
definitions.mutate
Change the workflow definitions that the selector addresses.
definitions.workflow.delete
definitions.mutate
Delete the workflow definitions that the selector addresses.
definitions.app.query
definitions.query
Read app definitions; a selector narrows the list.
definitions.app.create
definitions.mutate
Create an app definition.
definitions.app.update
definitions.mutate
Change the app definitions that the selector addresses.
definitions.app.delete
definitions.mutate
Delete the app definitions that the selector addresses.
definitions.component.query
definitions.query
Read component definitions; a selector narrows the list.
definitions.component.create
definitions.mutate
Create a component definition.
definitions.component.update
definitions.mutate
Change the component definitions that the selector addresses.
definitions.component.delete
definitions.mutate
Delete the component definitions that the selector addresses.
definitions.emailTemplate.query
definitions.query
Read email template definitions; a selector narrows the list.
definitions.emailTemplate.create
definitions.mutate
Create an email template definition.
definitions.emailTemplate.update
definitions.mutate
Change the email template definitions that the selector addresses.
definitions.emailTemplate.delete
definitions.mutate
Delete the email template definitions that the selector addresses.
definitions.page.query
definitions.query
Read the page that an app declares at one path.
definitions.layout.query
definitions.query
Read the layout that an app declares at one path.
definitions.queryOp.query
definitions.query
Read query operation definitions; a selector narrows the list.
definitions.queryOp.create
definitions.mutate
Create a query operation definition.
definitions.queryOp.update
definitions.mutate
Change the query operation definitions that the selector addresses.
definitions.queryOp.delete
definitions.mutate
Delete the query operation definitions that the selector addresses.
definitions.mutationOp.query
definitions.query
Read mutation operation definitions; a selector narrows the list.
definitions.mutationOp.create
definitions.mutate
Create a mutation operation definition.
definitions.mutationOp.update
definitions.mutate
Change the mutation operation definitions that the selector addresses.
definitions.mutationOp.delete
definitions.mutate
Delete the mutation operation definitions that the selector addresses.
definitions.workflowOp.query
definitions.query
Read workflow operation definitions; a selector narrows the list.
definitions.workflowOp.create
definitions.mutate
Create a workflow operation definition.
definitions.workflowOp.update
definitions.mutate
Change the workflow operation definitions that the selector addresses.
definitions.workflowOp.delete
definitions.mutate
Delete the workflow operation definitions that the selector addresses.
definitions.operationsModule.query
definitions.query
Read the compiled references of every app operation in the selected subspace.
definitions.designToken.query
definitions.query
Read design token definitions; a selector narrows the list.
definitions.designToken.create
definitions.mutate
Create a design token definition.
definitions.designToken.update
definitions.mutate
Change the design token definitions that the selector addresses.
definitions.designToken.delete
definitions.mutate
Delete the design token definitions that the selector addresses.
definitions.asset.query
definitions.query
Read Library image and font asset definitions; a selector narrows the list.
definitions.asset.create
definitions.mutate
Create a Library asset from a finalized Library upload marker.
definitions.asset.update
definitions.mutate
Change the Library asset definitions that the selector addresses.
definitions.asset.delete
definitions.mutate
Delete the Library asset definitions that the selector addresses; referenced assets need `allowBrokenReferences`.
installations.installation.query
installations.query
Read the installation status of the apps in the selected subspace; `$iid` or `appSlug` narrows the list.
installations.installation.listDiagnostics
installations.query
Read one page of the diagnostics an installed app recorded, newest last.
installations.installation.resolve
installations.query
Resolve the portal URL that serves an installed app, or why it is not served yet.
installations.installation.explain
installations.query
Explain the access decision of an installed app for one page path.
installations.installation.evaluateAccess
installations.query
Evaluate the access verdicts of every page of an installed app.
installations.installation.recompile
installations.mutate
Recompile the installations of the selected subspace from their saved definitions.
installations.identity.query
installations.query
Reserved: read the portal identities of an installation.
installations.identity.update
installations.mutate
Disable a portal identity or change its email address.
installations.invite.query
installations.query
Reserved: read the pending portal invitations of an installation.
installations.invite.create
installations.mutate
Invite a person to a portal and send the invitation email.
installations.enrollment.query
installations.query
Reserved: read the pending holder enrollments of an installation.
installations.enrollment.create
installations.mutate
Enroll a holder in a portal and send the enrollment email.
installations.delivery.reset
installations.mutate
Clear the stored email delivery receipt of an app so the next delivery check starts fresh.
installations.delivery.takeTest
installations.mutate
Take the newest captured test email of an app from the test delivery outbox.
runs.workflow.analyzeBlastRadius
runs.query
Assess the static effects a saved workflow can have, without running it.
runs.workflow.render
runs.query
Render a saved workflow as a state diagram and a step outline.
runs.workflow.simulate
runs.query
Simulate a saved workflow with mocked steps and report every transition, without writing anything.
runs.workflow.test
runs.query
Run the test cases of a saved workflow against mocked steps and report each outcome.
runs.workflow.execute
runs.mutate
Start a run of a saved workflow; the required idempotency key makes a retry return the same run.
runs.action.execute
runs.mutate
Run a saved action once with the given input and return its output and its first log lines; the idempotency key, sent in the body or the Idempotency-Key header, makes a retry return the same run.
runs.action.query
runs.query
Read the retained history of action runs by `$iid` or by action name, newest first unless `$sort` names another order.
runs.run.query
runs.query
Read run records, newest first unless `$sort` names another order; `$filter` and the request selectors narrow the list.
runs.run.count
runs.query
Count the run records that the same `$filter` and request selectors as the list select, by status.
runs.run.inspect
runs.query
Inspect one run record by `$iid`, with its output and the signal it waits for.
runs.run.listEvents
runs.query
Read one page of the events one run recorded, oldest first.
runs.run.traceCascade
runs.query
Read the tree of runs that one run started, directly or through its children.
runs.run.metrics
runs.query
Read the scheduler queue metrics of the database or server.
runs.run.summaries
runs.query
Read the daily run totals per type and status.
runs.run.signal
runs.mutate
Deliver a named signal to a waiting workflow run, by `$iid` or by workflow and correlation key.
runs.run.cancel
runs.mutate
Cancel one run that has not finished.
runs.run.retry
runs.mutate
Queue one failed run to run again.
runs.agentSession.query
runs.query
Read the status of one agent session.
runs.agentSession.create
runs.mutate
Reserved: start an agent session.
runs.agentSession.setModel
runs.mutate
Reserved: change the model of an agent session.
runs.agentSession.interrupt
runs.mutate
Interrupt the running turn of an agent session.
runs.agentSession.signal
runs.mutate
Return a tool result to an agent session that waits for it.
runs.workflowTrigger.query
runs.query
Read the trigger sources of a workflow with their counters and rejected events.
runs.workflowTrigger.restart
runs.mutate
Restart a stopped trigger source of a workflow.
runs.workflowTrigger.replay
runs.mutate
Replay one rejected trigger event of a workflow as a new run.
runs.workflowTrigger.discard
runs.mutate
Discard one rejected trigger event of a workflow without running it.
runs.transfer.query
runs.query
Read the transfer record of the namespace; `$iid` narrows it to one import.
runs.transfer.planExport
runs.query
Estimate what an export of the namespace would contain, without exporting it.
runs.transfer.planImport
runs.query
Check whether an import would start fresh or resume, without importing.
runs.transfer.export
runs.mutate
Export the namespace into a stored transfer artifact.
runs.transfer.import
runs.mutate
Start importing a transfer artifact into the namespace.
runs.transfer.cancel
runs.mutate
Reserved: cancel a namespace transfer in progress.
runs.artifact.list
runs.query
Read the stored transfer artifacts of the database.
runs.artifact.delete
runs.mutate
Delete one stored transfer artifact.
resources.database.query
resources.query
Read the databases of the server; `$rid` narrows the list.
resources.database.queryDefault
resources.query
Read the default database of the server.
resources.database.measureSize
resources.query
Measure the stored bytes of one database per namespace.
resources.database.create
resources.mutate
Create a database on the server.
resources.database.update
resources.mutate
Make one database the default database of the server.
resources.database.delete
resources.mutate
Delete one database and all its namespaces.
resources.namespace.query
resources.query
Read the namespaces of the database; `$rid` narrows the list.
resources.namespace.create
resources.mutate
Create a namespace in the database.
resources.namespace.update
resources.mutate
Make one namespace the database default.
resources.namespace.delete
resources.mutate
Delete one namespace and all its subspaces.
resources.subspace.query
resources.query
Read the subspaces of the namespace; `$rid` narrows the list.
resources.subspace.lifecycleReceipt
resources.query
Read the latest lifecycle receipt of one subspace; data is null when none exists.
resources.subspace.create
resources.mutate
Create a subspace in the namespace.
resources.subspace.declare
resources.mutate
Create the engine-owned system subspace when it is absent.
resources.subspace.update
resources.mutate
Rename one subspace or make it the default subspace.
resources.subspace.delete
resources.mutate
Delete one subspace and everything stored in it.
resources.subspace.wipe
resources.mutate
Delete the units, definitions, schema or portals of one subspace and keep the subspace.
resources.subspace.replace
resources.mutate
Delete the data of one subspace and replace its definitions in one lifecycle operation.
resources.config.query
resources.query
Read the effective configuration of the namespace and its overrides.
resources.config.resolveEffectiveConfig
resources.query
Read the effective configuration of the namespace with the limits it enforces.
resources.config.update
resources.mutate
Change configuration overrides of the namespace.
resources.config.reset
resources.mutate
Remove every configuration override of the namespace.
resources.history.query
resources.query
Read the commit history of a subspace, newest first.
resources.env.query
resources.query
Read the env constants and secret metadata of the namespace; `domain` and `name` narrow the list.
resources.env.declare
resources.mutate
Declare an env constant or secret when it is absent; an existing value is kept.
resources.env.set
resources.mutate
Create or replace the value of an env constant or secret.
resources.env.rotate
resources.mutate
Replace the configured value of an env constant or secret; a name without one answers `ENV_VALUE_NOT_FOUND`.
resources.env.delete
resources.mutate
Delete one env constant or secret.
resources.env.deleteDomain
resources.mutate
Delete every env constant and secret of one domain.
resources.env.rotateKek
resources.mutate
Re-encrypt every env secret of the namespace under a new key-encryption key.
resources.module.query
resources.query
Read the WebAssembly code modules of the namespace; `name` narrows the list.
resources.module.delete
resources.mutate
Delete one WebAssembly code module.
resources.agentSetup.query
resources.query
Read the agent setups of the namespace with their credential custody and bindings.
resources.agentSetup.listModels
resources.query
Read the models that the credential of one agent setup can use.
resources.agentSetup.loginStatus
resources.query
Read the login state of the credential of one agent setup. A poll can store a refreshed credential.
resources.agentSetup.set
resources.mutate
Create or replace one agent setup.
resources.agentSetup.delete
resources.mutate
Delete one agent setup.
resources.agentSetup.bind
resources.mutate
Bind one agent setup to an agent role.
resources.agentSetup.unbind
resources.mutate
Remove the role binding of one agent setup.
resources.agentSetup.seal
resources.mutate
Store a sealed credential for one agent setup.
resources.agentSetup.unseal
resources.mutate
Remove the sealed credential of one agent setup.
resources.agentSetup.startLogin
resources.mutate
Start a device login for the credential of one agent setup.
resources.agentSetup.cancelLogin
resources.mutate
Cancel the device login in progress of one agent setup.
resources.agentSetup.logout
resources.mutate
Sign out the credential of one agent setup.
resources.definition.publish
resources.mutate
Publish definitions that hold secrets, which requires both definitions and resources write.
server.kv.query
server.query
Read the registered KV tables of the server.
server.kv.listEntries
server.query
Read one page of the entries of one registered KV table, with redacted values.
server.metrics.query
server.query
Read the live subscription and portal projection metrics of the server.
server.host.resolve
server.query
Resolve the database, namespace, subspace and app that one portal id serves.
server.key.query
server.query
Read the internal keys of the server; `$rid` narrows the list.
server.key.set
server.mutate
Store an internal key under one id.
server.key.delete
server.mutate
Delete one internal key.
server.grant.query
server.query
Reserved: read the grants of the server.
server.grant.set
server.mutate
Create or replace one grant.
server.grant.revoke
server.mutate
Revoke one grant.
server.limit.query
server.query
Reserved: read the limit overrides of the server.
server.limit.set
server.mutate
Create, replace or clear one limit override.
server.usage.export
server.mutate
Export the usage records after a watermark.
server.usage.acknowledge
server.mutate
Acknowledge exported usage records up to a watermark.
server.blob.quarantine
server.mutate
Record the malware scan verdict of one stored blob.