Skip to content

Queries

Queries

A query is a declared, access-checked read of a view or of owned records, so reads are as visible to the tool as writes. Never hand-write an HTTP read endpoint over runtime.store: declare a query.

.query('MyOrders', {
access: 'user',
input: t.object({ status: t.optional(t.enum('placed', 'cancelled')) }),
view: 'customer-orders',
// The caller's own rows: the actor scopes the filter, never an input field.
where: (input, actor) => q.eq('customerId', actor.id).eq('status', input.status),
order: [['orderId', 'desc']],
examples: [
{
name: "only the caller's orders, newest id first",
actor: { id: 'c1', roles: [] },
input: {},
rows: [{ orderId: 'o1', customerId: 'c1', status: 'placed', totalCents: 1 }, { orderId: 'o2', customerId: 'c2', status: 'placed', totalCents: 1 }, { orderId: 'o3', customerId: 'c1', status: 'cancelled', totalCents: 1 }],
expect: { items: ['o3', 'o1'] },
},
{ name: 'anonymous callers are unauthenticated', actor: null, input: {}, expect: { rejects: 'unauthenticated' } },
],
})
  • where uses q.eq / in / notIn / gte / lte / between / contains / prefix; undefined values skip the filter.
  • where(input, actor, { state, now }) also sees the query’s reads and the runtime clock (pure), so a page can depend on the caller’s records and the time: q.in('storeId', state.following?.storeIds ?? []).gte('expiresAt', now).
  • page: { size, max } bounds the page. Keyset pagination: next is an opaque cursor (order values + key), stable while rows are inserted.
  • atlas check errors on unknown fields and on an order no index serves after the eq filters (ATLAS023, ATLAS024), and warns on a filter no index helps.

Guarded view queries. A list only some callers may see is a view query with reads and a pure guard that runs before the page is read: guard: ({ state }, r) => (state.member ? null : r.reject('not-store-member')).

.query('MyProduct', {
access: 'user', input: t.object({ id: t.string }),
reads: { product: { from: 'products', key: (input) => input.id } },
select: ({ state, actor }, r) => (state.product?.sellerId === actor.id ? state.product : r.reject('product-not-found')),
output: Product,
examples: [/* { name, actor, input, state, expect: { result: { id: 'p1' } } } */],
})

reads work as in commands; select({ input, state, actor, now }) is pure; the output shape strips extras and validates. Sensitive output only with non-public access (ATLAS022). r.reject(code) for domain codes.

await runtime.query('StoreProducts', { storeId: 's1' }, { actor, after, limit });
// { outcome: 'ok', result: { items, next } } | rejection

Access is required on every query (ATLAS021), as for commands, but never 'internal'. Scope “my rows” with the actor in where or in a read key, never an input field.

Codes: unknown-query, invalid-input (input, after, limit), unauthenticated, forbidden, query-failed (select, guard or where threw; the text is in the trace), store-failed, upcast-failed, result-invalid, plus domain codes from select.

Views lag their commit. A dispatch can wait for them:

await runtime.dispatch(cmd, { actor, await: 'views' }); // or ['store-profiles'], timeoutMs (default 2000)
// -> { outcome: 'accepted', facts, awaited: true } (false: timed out; the command committed anyway, never a failure)
  • 'views' waits for every view fed by the emitted facts; a list of names waits for those views.
  • 'all' waits for the whole cascade: every capability consumer fed by the dispatch’s facts and sent commands, then by what those deliveries wrote, and so on.
  • Edge subscribers and scheduled commands are never awaited. It waits for delivery and never delivers: without workers anywhere it times out with awaited: false.
  • Over HTTP: the Atlas-Await: views header (or view names, or all), honored for identified callers only unless the router’s allowAwait says otherwise.