Query builder
The TypeScript query builder creates immutable, serializable query values. The browser runs them against a local replica; the server runs the same representation against its authorized database view.
New here? Start with Read data.
Start from an entity or trait
Section titled “Start from an entity or trait”db.query.from(Task)db.query.from(Assignable)An entity source returns that exact type. A trait source returns every visible entity that implements the trait. One query belongs to one db handle.
Predicates
Section titled “Predicates”query .where({ done: false }) .where(Query.gte(Task.priority, 3)) .where(Query.gte(Task.createdAt, since))Import Query from ramose/db. The object form expresses equality; query stages such as Query.gte, Query.startsWith, and Query.not express other filters. Multiple stages passed to .where must all match.
Filtering by an entity
Section titled “Filtering by an entity”The ids a query publishes are the ids the next query filters by, at any reference field and at id itself:
db.query.from(Task).where({ assignee: me.id })The value is an EntityId, or the ClientRef of an entity this device created. Both are branded with the entity they name, so an identity of another type is a compile error. A ClientRef keeps naming the same entity when the server issues its EntityId: the query follows it without being rebuilt.
An identity the local replica holds nothing for — never received, or issued to a different replica — matches nothing. That is an answer, not an error, and the query starts answering if the entity arrives.
An entity identity is only meaningful where an entity can be held. At a scalar field, in a text comparison, or inside a select(…) collection’s own where, one is refused when the query is built rather than quietly matching nothing.
Relationships
Section titled “Relationships”Traverse declared references and relationships from a bound source. Relationship clauses participate in the query; nested projection controls returned shape.
db.query .from(Task) .where({ assignee: viewer.id }) .select({ id: Task.id, title: Task.title, assignee: Task.assignee.select({ name: User.name }).optional, })Traversal never bypasses policy. Hidden facts are absent from the database capability the query sees.
Projection
Section titled “Projection”Without an explicit projection, db.query.from returns entity handles and portable Query.from returns plain rows. .select({...}) returns the declared plain-data shape. Select only fields needed by the caller, especially for MCP and nested results.
Ordering and pages
Section titled “Ordering and pages”query .orderBy(Task.updatedAt, "desc") .orderBy(Task.id, "asc") .limit(50).after(cursor)Use a stable tie-breaker for deterministic pages. Cursors are opaque and scoped to the query, database, authorization view, and basis that produced them.
Cardinality
Section titled “Cardinality”query // zero or morequery.one() // zero or onequery.oneOrFail() // exactly one.oneOrFail() fails when the authorized query does not produce exactly one result. Do not use the distinction between zero and hidden as an authorization signal.
Rules and recursion
Section titled “Rules and recursion”Use Query.build when a read needs relational clauses or aggregates. Its callback supplies the clause builder:
const taskCount = Query.build(function* (q) { const task = yield* Query.entities(Task) return q.value(q.count(task))})Query.rule uses the same builder as its first callback argument. Remaining arguments are the rule’s bound variables; return a variable for its result:
const taskTitle = Query.rule("taskTitle", function* (q, task: Query.Var) { const title = yield* q.fact(task, Task.title) return title.v})
const titles = Query.build(function* (q) { const task = yield* Query.entities(Task) const title = yield* taskTitle(task) return q.rows({ title })})Keep recursive rules bounded. Rules and fluent queries use the same engine.
Lowering and portability
Section titled “Lowering and portability”Every supported builder query lowers to QueryDocumentV1. Unsupported closures, functions, classes, and runtime objects are rejected before transport. Use the document form directly for language-neutral clients and MCP.
Cost controls
Section titled “Cost controls”The runtime budgets candidate work, projection width, relationship expansion, recursion, page size, and execution time. query_budget_exceeded is not retried automatically; narrow the request.