Ultimate

Queries and live queries

A query is a read. live: true makes it subscribable. Never writes, never enqueues, never sends mail.

As of 2026-08-23. Stable API — semver from here (Upgrading). All three tiers of Realtime ship. Tier 3 (local-first) shipped in 21.0.0, opt-in: persist is an entity() option, read by realtime's IndexedDB store and one outbox (Realtime). persist is not a field query() accepts; writing it is a TS2353 excess property.

The canonical shape

// query
export const liveFeed = query({
  input: t.object({ orgId: t.uuid }),
  policy: can('feed:read'),
  live: true,
  sql: ({ orgId }) => db.posts.where({ orgId }).orderBy('createdAt').limit(50),
});

Fields

FieldRequiredRule
inputyesStandard Schema; t re-exported from @ultimat3/query, so a query file imports one package. The shipped provider is @ultimat3/schema's dependency-free builtin — ArkType, Zod and Valibot are optional swaps behind configureSchemaProvider, and no adapter ships. Parsed before policy, before sql. Becomes the GET query string, the client hook argument, and the MCP tool's JSON Schema
policyyescan('<perm>'), optionally with a predicate over { input, actor }. Evaluated at HTTP call, client hook, subscribe, and per delivered row
liveno — default falseregisters the query with the incremental matcher. Requires a deterministic, bounded sql
singleno — default falsetrue declares a read of ONE object (a detail page's row by id or slug). Only the wire changes: the HTTP GET answers the first row sql returns as the body, 404 X_NOT_FOUND when there is none (a list read answers 200 []), refuses _first/_after (400), and openapi.json documents one object, a 404 and no page controls. The typed client answers Promise<TRow> and has no .page. The MCP tool answers as the route does — the row, or X_NOT_FOUND — over tools/call alike (As of 2026-10). Every in-process caller — read(input), .as(), .page(), .live() — keeps readonly TRow[]. Anything but a boolean is X_QUERY_SINGLE_INVALID. As of 2026-09-29
sqlyes, on a rows read — never beside download(input) => SqlSource. from() (@ultimat3/query) wraps an already-resolved @ultimat3/entity repo call and restates where/orderBy/limit for the matcher to read back; select/preload happen inside that repo call, before from() ever sees a row. No ORM in the graph. SQL-transparent: toSQL() prints the statement verbatim so an agent can read it and self-correct
rowsnothe row's schema: Post.$schema or a t.object({ … }). The published contract of the answer: openapi.json, the mount's document, x.manifest.json and the MCP tool's outputSchema print its members (structure only, a sealed column absent). x verify refuses a member removed, retyped or made optional without a major. An entity's $schema also makes the answer the record envelope. Not a parse: a query's rows are never re-validated on the way out. Absent, or a schema that cannot be introspected, the document says only items: {}. As of 2026-11
mcpno — default not exposed{ expose: true, description } makes the read an MCP tool. Opt-in, unlike an action: a read hands rows to an agent, so silence exposes nothing
mcp.visibleTonoroles that may see the projected tool; a caller whose role is not named gets ToolNotFound, never Forbidden — the policy still decides every call
errorsno — x g query writes errors: []the 4xx codes a reader may branch on, closed: an action's rule (Actions), judged around sql and the source's execute. Undeclared in a test or dev run is X_ERROR_UNDECLARED; .contract() scans sql's own source; openapi.json and the MCP tool name each code. As of 2026-11
auditno — default falsetrue records every call in the installed AuditSink — allowed, denied, failed, and a memo or cache hit (replayed: true). See Audit
http.pathnopins the read's URL in place of /_x/query/<kebab>: http: { path: '/orders/:id' }. Lowercase static segments and :params, never /_x; each :param a REQUIRED t.string key of input: (X_QUERY_HTTP_PATH_INVALID at query(), as is an http.method other than 'GET'). The URL's value joins the search string before the input is parsed; a search string naming the key differently is 400 X_HTTP_PATH_INPUT_CONFLICT. client() fills the URL; queryClient by name cannot see a pin. openapi.json publishes {id} in: path, the key gone from the query parameters. As of 2026-11
etagnotrue answers GET with a strong ETag (a SHA-256 of the body and the actor's authority, so one actor's validator never matches another's) and cache-control: private, no-cache; a matching If-None-Match is a bodiless 304, decided AFTER the policy and the rate limit — a 304 is still a read of that person's rows, so a denied caller is 403 and the 304 spends the bucket. Opt-in: it moves the answer from no-store to the browser's disk. openapi.json documents the header, the ETag and the 304. On a live read, or beside download, X_QUERY_ETAG_INVALID. As of 2026-11
timeoutMsnothis read's own statement_timeout, tighter than the role's: the source runs inside withStatementTimeout (@ultimat3/db), one read-only transaction with a transaction-local bound. A timeoutMs read skips replicas — a transaction is pinned to the primary. Firing is X_DB_STATEMENT_TIMEOUT; above the bound in force is X_DB_STATEMENT_BOUND_INVALID (a read cannot extend what the role was sized for); not a whole positive number, X_QUERY_TIMEOUT_INVALID at query(). As of 2026-11
downloadno{ handle, expiresInMs? } in place of sql: the read answers a FILE at a short-lived signed URL. Every rows field beside it is X_QUERY_DOWNLOAD_INVALID. See A file at a signed URL
cache tagsderivedacquired automatically from the tables sql touches. Never hand-declared on a query

Nothing else is a query field. Sorting, paging, and filtering are input fields consumed by sql.

The fluent surface

Every projection this package owns is a method on the query — liveFeed.live(input), never toLiveQuery(liveFeed, input) — and every declared field is lifted onto it. The MCP read tool is @ultimat3/mcp's one projection, toolFrom(liveFeed) (As of 25.0.0 there is no .tool()). A query has no .def.

MemberIsRule
liveFeed(input, options?)the readthe policy's actor half → parse input → evaluate policy → build source → execute, through the cache tiers. A reader refused whatever they send is 401/403, never X_INPUT_INVALID
.as(actor, input, options?)the same read, as someone elsekeeps the surrounding context whole — services, clock, locale, trace — and swaps only the actor. null is the signed-out caller
.page(input, { first, after? })one bounded page{ rows, nextCursor, hasMore } — the one page shape, @ultimat3/core's Page, the same findMany answers; the endCursor/hasNextPage aliases left in 25.0.0. nextCursor is null exactly when hasMore is false (25.0.0: the last page used to keep a cursor), so while (page.hasMore) stops on the last page. The cursor is signed and scoped to queryHash(name, input) — the query's name and its parsed input, never first or after, which are controls rather than scope. There is no offset and there never will be. A repo.ts raw join pages with keyset() from @ultimat3/db (Entities and Migrations): the same Page, its own surface
.live(input, options?)the subscription descriptora LiveQuery carrying the same policy object, re-evaluated per subscriber
.client({ baseUrl })the typed browser methodGET /_x/query/live-feed?orgId=…, keys sorted so one input is one URL. .client(…).page(input, { first, after }) reads one page over the same route
.describe()the manifest rowname, capability, tags, ttl, live
.input .policy .cache .mcp .isLivethe declaration, liftedreadable. sql is not among them

sql is unreachable by design. The declaration lives in a private store inside read.ts and @ultimat3/query exports no reader for it, so sourceFor is the only thing that can build a source — one read path and one authz path, structurally rather than by convention. A hand-rolled object with kind: 'query' is X_QUERY_FOREIGN, never a registered read.

isLive is the declared boolean; live() is the subscription itself. Every projection needs the name defineApi({ queries }) stamps on at boot — before that, X_QUERY_UNREGISTERED.

Five projections

ProjectionDerived fromShape
HTTP GETname + inputGET /_x/query/live-feed?orgId=…, errors as UltimateError JSON
Read hookthe query's name, and liveconst feed = useQuery({ name: 'liveFeed', live: true }, { orgId }) in an island. It returns an AsyncState accessor; see below
Live subscriptionlive: trueWS frames {qid, op, row, lsn} patched into the page's one record store
Cache entrytags from sqlkey is the query name + a fingerprint of the parsed input + the sorted tag keys — the actor is not in it; see Caching and invalidation
MCP read toolinput + policy + nameone read tool per query, named for the export verbatim — liveFeed, never live_feed. The URL is kebab-cased; the tool name is not. Identical authz. See MCP and AI

The read hook, precisely

One read hook, useQuery, in 21.0.0. useLive and liveHookFor are deleted. Live-ness belongs to the query declaration, not to the hook an island picks.

import { useQuery } from '@ultimat3/realtime';

type FeedRow = { id: string; title: string };
declare const orgId: string;

const feed = useQuery<FeedRow>({ name: 'liveFeed', live: true }, { orgId });
// feed(): AsyncState<readonly FeedRow[]>; feed.refetch(); feed.release()
FactRule
The ref{ name, live?, entity? }: the registered name, never the query value. Importing the value drags its read path into the island; measured at 698,801 B for the reference feed
live: truerows arrive and move over the page's one socket
no liveone GET /_x/query/<kebab> through clientTransport. Name its entity to make the rows store records that any write moves; without it the list holds its rows itself and only refetch() moves them
What it returnsan AsyncState accessor: pending, refreshing (keeps the rows on screen), ready, failed
Lists hold idsthe rows come from the page's RecordStore, so a record updated through any path re-renders every list holding it
Inputread once, at call time. A changed input is a new useQuery
pollpollEvery(ms, { maxMs? }) from @ultimat3/realtime; non-live only, refused by type on a live ref: refetch every ms through visibleInterval from @ultimat3/core/page, which only an island importing pollEvery carries — hidden tab skipped with one make-up read on show, stopped by a lost session and by release(). A non-finite value is finiteOption's refusal
Lifetimethe caller owns release() (Solid: onCleanup)
Not derived, As of 2026-09-22live and entity are stated on the ref by hand, and nothing checks them against the query declaration. A ref saying live: true for a non-live query subscribes to nothing
Typesthe row type is the caller's annotation (useQuery<FeedRow>), not inferred from the query. That is the price of never importing the query value
A server renderanswers pending with no subscription
A browser bundle that never called installRealtime()X_REALTIME_UNINSTALLED

The HTTP GET, precisely

Mounted for every registered query by x dev and by a container, from one composition — nothing to wire in the app.

FactRule
Method + pathGET /_x/query/<kebab-export-name>, the URL .client() derives with no server import — or the read's http.path pin, in its place
Inputthe search string, coerced at the boundary (t.number from "12") then validated by the query's own schema. Repeated keys are an array, and keys are sorted so one input is one URL. The typed client sends a Date as its ISO instant and nothing for an empty array; the route reads a required array nobody sent as [], so { tags: [] } arrives (As of 2026-10). An optional or defaulted array keeps the schema's own answer for absence — [] passed to one arrives as absent
Bad input400 X_INPUT_INVALID, with x queries show <name> --json as the fix — the same code and line every other surface of that read answers
Authzevaluated once, inside the read, from the parsed input. auth: 'public' only for allow() — anything else is required, and an anonymous caller is 401 before the policy is reached
Cachingno-store. The URL names no actor while the rows are scoped to one, so a shared cache is something a CDN in front of the app configures knowingly. The read's own cache: tags ride along for a purge. etag: true is the declared exception: private, no-cache and a 304 for a held validator. The browser's own cache revalidates, so useQuery needs no code
Failuresapplication/problem+json carrying the code, cause and fix. A non-framework throw is the server's 500, never dressed as a read failure
A page?_first=20 answers .page()'s own envelope — { rows, nextCursor, hasMore } — and &_after=<nextCursor> continues it. Without a control the answer is the bare array. The keys carry an underscore because first is a legal input member (a listing's own page size); declaring _first or _after as input is X_QUERY_INPUT_UNENCODABLE. A size outside 1–10,000 or an _after without _first is 400 X_INPUT_INVALID; a cursor that is not this read's is 400 X_CURSOR_INVALID. As of 2026-09. A declared .limit() is the size of the listing (As of 2026-10): _first pages inside it and never past it, the last page inside it answers hasMore: false and nextCursor: null, and a read meant to be paged to the end declares no limit
In openapi.jsonevery read, as a GET path item: the input as in: query parameters, then _first and _after, and a 200 that is oneOf the rows and the envelope, each row carrying the rows: members inline. A single: true read: the input parameters only, a 200 that is the row (one object when no rows: is declared), a 404, and x-ultimate.single: true. A bearer mount's document carries the same operation at its prefix
One objectsingle: true answers the row itself, or 404 X_NOT_FOUND — see below

A read with a validator — etag: true

// `postRecord` in examples/dummy: the like island re-reads it on every load of /posts/{id}.
import { can } from '@ultimat3/policy';
import { from, query, t } from '@ultimat3/query';

interface PostRow {
  readonly id: string;
  readonly orgId: string;
  readonly likeCount: number;
}
declare const posts: readonly PostRow[];

export const postRecord = query({
  input: t.object({ orgId: t.uuid, postId: t.uuid }),
  policy: can('post:read'),
  etag: true,
  sql: ({ orgId, postId }) =>
    from<PostRow>('posts', posts).where({ orgId, id: postId }).orderBy('id').limit(1),
});
// GET /_x/query/post-record?…                      → 200, ETag: "…", cache-control: private, no-cache
// GET /_x/query/post-record?…  If-None-Match: "…"  → 304, no body (same actor, same bytes)
FactRule
Who sends If-None-Matchthe browser, from its own cache, on every reuse — useQuery and queryClient carry no code for it. An API client sends the ETag it holds
Provedexamples/dummy/apps/web/e2e/read-revalidates.e2e.test.ts: across a real reload, the read reaches the server and crosses the wire as headers only (transferSize 300), the island still handed the whole body
When to declare ita read re-asked while its answer rarely moves — a seed read, a poll. Not a read whose rows must never touch the device's disk
Not withlive: true (its cursor is its validator) or download: — X_QUERY_ETAG_INVALID at query()

A read of one object — single: true

export const postById = query({
  input: t.object({ id: t.uuid }),
  policy: can('post:read'),
  single: true,
  sql: ({ id }) => db.posts.where({ id }).limit(1),
});
// GET /_x/query/post-by-id?id=…  → 200 { id, title, … }  ·  404 X_NOT_FOUND
// await postById.client({ baseUrl })({ id })  → Promise<Row>
// await postById({ id })                       → readonly Row[]  (unchanged in process)
FactRule
The bodythe FIRST row sql returns — the one every in-process [0] of the same read takes. Say .limit(1): nothing counts the rest
No row404 X_NOT_FOUND, @ultimat3/entity's code, whose fix prints the SQL. The policy runs first, so a caller it denies is still 403
Page controls_first or _after is 400 X_INPUT_INVALID, and neither is published
rows:the envelope as usual, with the row as data
The MCP toolthe row itself, or X_NOT_FOUND — the route's answer on both counts, over tools/call. A declared rows: publishes the row as outputSchema, with no rows wrapper
Not changedread(input), .as(), .page() and .live() still answer rows. useQuery reads lists: a single read in an island is .client() or queryClient

Two spellings of that client, one URL

.client({ baseUrl }) binds one read; queryClient<Api['queries']>({ baseUrl }) binds every registered read at once, the way rpc<Api['actions']> does for writes. Both run queryClientMethodFor, so the URL cannot differ between them.

// apps/web/shared/client.ts — the app's one place for both
export const client = rpc<Api['actions']>({ baseUrl });         // writes
export const queries = queryClient<Api['queries']>({ baseUrl }); // reads
FactRule
When the map-wide one is the only optiona surface that may not import the feature — site/, where an edge into app/ is X_BOUNDARY_VIOLATION. .client() needs the query object; queryClient needs only the Api type
Why two clients and not oneactions and queries are two registries (defineApi's actions: and queries: keys) answering two methods. A read taken off the action client is a name that does not exist on Api['actions'] — a compile error, which is the point
Typesthe read's own input and row type. A list read answers readonly TRow[], limit(1) included; a single: true read answers TRow and rejects with X_NOT_FOUND when the route 404s
Failuresthe server's own code, off problem+json — X_INPUT_INVALID stays X_INPUT_INVALID. A gateway answering HTML, a network fault and a 2xx body that is not JSON are X_CLIENT_TRANSPORT_FAILED, the same code an action's client answers. It was X_RPC_FAILED until 21.0.0
Rows are JSON, with their instants revivedwhat the body parses to, plus every value the server answered as a Date handed back as one (As of 2026-10, #710): the route names them in x-ultimate-dates by path — one entry per column of a list — and clientTransport revives exactly those. The body and the OpenAPI document are unchanged; a client that ignores the header reads ISO strings. A query's rows are never parsed on the way out (a declared rows: publishes structure, it parses nothing), so the header is how the row type's Date stays true
then is not a readthe proxy answers undefined for it, so await queries resolves to the client instead of fetching /_x/query/then. Same rule in rpc

A file at a signed URL — download:

As of 2026-10-31. A read whose answer is bytes — a recording, an export, an attachment, a sealed object decrypted on the way out — is a query with download: instead of sql:. Same input, same policy, same name, same route; never a base64 string inside a JSON answer.

import { open } from '@ultimat3/core';
import { can } from '@ultimat3/policy';
import { query, queryClient, t } from '@ultimat3/query';
import { disk } from '@ultimat3/storage';

export const runRecording = query({
  input: t.object({ runId: t.uuid }),
  policy: can('recording:watch'),
  download: {
    expiresInMs: 600_000, // 1 000 … 3 600 000; default 300 000
    async handle({ input }) {
      const key = `runs/${input.runId}/recording`;
      const { bytes } = await disk('debug').get(key);
      const plain = await open(new TextDecoder().decode(bytes), { purpose: `run-debug:${key}` });
      return { body: plain, contentType: 'video/mp4', filename: `run-${input.runId}.mp4` };
    },
  },
});

// an island: mint, then hand the URL to the element that plays or saves it
declare const video: { src: string };
const client = queryClient<{ runRecording: typeof runRecording }>({ baseUrl: '' });
const { url } = await client.runRecording.url({ runId: '00000000-0000-4000-8000-000000000001' });
video.src = url;
StepWhat happens
client.runRecording.url(input)GET /_x/query/run-recording?runId=… with no _token — the MINT. The policy decides as for any read (401/403, the declared rateLimit:, audit: true records it); the answer is { url, expiresAt }
the tokenthe claims — query, fingerprint() of the parsed input, expiry on ctx.now(), the minting actor without facts — seal()ed with the app master key (AES-256-GCM, purpose bound to the query). The ACTOR is ciphertext: no id, role or grant is readable. The query's path and its input are plain in the URL, as on every read — keep a secret out of a download's input
GET <url>the SERVE, to whoever holds the URL — a <video>, a tab, a caller's own user — with no session. Seal checked first (X_QUERY_DOWNLOAD_TOKEN_INVALID, 403), then expiry (X_QUERY_DOWNLOAD_TOKEN_EXPIRED, 410), then the URL's input against the bound fingerprint (403). handle runs as the minting actor
the answerthe bytes, content-disposition (attachment default, inline on request; ASCII filename + exact filename*), nosniff, content-security-policy: default-src 'none'; img-src 'self'; media-src 'self'; sandbox (an inline file loads nothing elsewhere), referrer-policy: no-referrer, cache-control: no-store; content-length when the size is known; one Range: window as 206, 416 past the end

handle answers { body: Uint8Array | Blob | ReadableStream, contentType, filename, disposition?, size? }, or body: (range?) => BodyInit with size required. A Uint8Array or a Blob (a Bun.file, sliced lazily) is always range-served; a stream only with size, else whole with accept-ranges: none, and a window of one reads and drops its prefix. The function form opens only the window: (range) => range === undefined ? disk.stream(key) : disk.streamRange(key, [range.start, range.end]). A sealed object cannot take it, since an envelope opens whole. A sealed object is one AES-GCM envelope, so it is read with get() (bounded by maxGetBytes) and opened whole before a byte is served.

.live(), .page(), .as(), sourceFor and an MCP tool are X_QUERY_DOWNLOAD_ONLY; the manifest marks the read download: true (rows ↔ download is breaking), and x routes lists it on the read's own GET /_x/query/<kebab>. In-process, await runRecording(input, { ctx }) is the DownloadFile and await runRecording.url(input, { ctx }) the path.

Owns / never

AspectRule
Projects toHTTP GET, typed client hook, live subscription, cache entry with tags, MCP read tool
Ownsresult shape + row-level filtering
Neverwrite, enqueue a job, send mail, read headers or cookies, authorize inside sql
Neverreturn partial data to satisfy a policy — filter rows in sql, decide yes/no in policy

live: true requires deterministic, bounded SQL

x verify rejects a live query without both an orderBy and a limit.

RequirementWhyFailure
orderBy on a total orderthe matcher decides enters / leaves / moves within the result from the changed row alonex verify error naming the query
limitan unbounded result set has no bounded change buffer and no bounded reconnect snapshotx verify error naming the query
No now(), random(), or non-deterministic functionthe same (input, row) must always yield the same membership answerx verify error naming the expression
No cross-tenant predicatetenant scoping comes from ctx, not from inputX_FORBIDDEN at subscribe
The source reads as its subscriber's tenanta sync node shares one read per (query, input) per org, in a context carrying that org — so repo.list(limit) needs no org argument on a live query, exactly as on a requesta source naming another org is X_TENANCY_ACTOR_MISMATCH; a subscriber with no org reading a tenant table is X_TENANCY_ACTOR_ORG_REQUIRED

A patched row lands where order by returns it (As of 2026-10). The matcher, from()'s in-memory execution and the cursor fallback compare two values by the column's DECLARED kind, through @ultimat3/entity's own comparator — @ultimat3/query has none. A bigint() or decimal() column orders by its digits (its row value is decimal text: '9' before '10'), a uuid() is a value whatever its case, a timestamp() is an instant. The kinds resolve from the entity declared on the table from(name, …) names; a relation no entity declares has none, and its digits compare as text — as a text column's do. A custom SqlSource calling compareRows, matchesFilter or isAfterKey passes kindsOf(shape.entity); compareValues is gone.

A page is served order by <declared keys>, "id" asc, and so is a live window — As of 2026-08, the initial window, the matcher's patch positions and the keyset re-read a reconnect resumes with are one ordering. A row tied on every declared key lands where its id puts it, not after the tie group, and not wherever the database happened to return it. x queries show <name> --json prints the order. A row that reaches the matcher with no id is X_QUERY_NOT_PAGEABLE, never a patch aimed at a position no client holds.

A non-live query has none of these constraints — it is just a read.

A sealed column never rides on a row

Nothing parses a query's row on its way out: a declared rows: is published as the row's structure, never applied to the row. A .sealed() column stays behind because the repository row does not enumerate it (Sealed columns), As of 2026-10.

ProjectionSealed column
GET /_x/query/<name> — list, Page, single: true, the record envelope's data and recordsabsent
the MCP read tool, a cache: entry in a shared tierabsent
a live snapshot and every patchabsent
query(input) / .as(actor, input) called on the serverrow.password reads the plaintext
a derived row — rows.map((row) => ({ ...row, initials }))absent; the spread drops it
a derived row that names it — ({ id: row.id, token: row.token })sent. The only way to send a secret is to write its name
RefusedCode
live: true with a sealed column in .where() / .compare() / .orderBy()X_MATCHER_UNSUPPORTED at subscribe — a change row carries no sealed column, so the window could be read once and never patched

Row-level policy filtering

Policy is not a subscribe-time gate that then trusts the stream.

MomentCheck
Subscribepolicy evaluated against { input, actor }. Denied → X_FORBIDDEN, no subscription created
Initial snapshotevery row filtered through the same policy
Each incremental patchre-checked per row. A row that fails is dropped, never sent
Actor change (role revoked, org left)the subscription re-evaluates; rows that no longer pass are delivered as delete ops
Actor moves to another orgthe subscription is re-seated on that org's own window and sent its snapshot: a window is one tenant's, and two orgs never share one
A refused subscription, on the pageuseQuery's failed state carries the node's error as a branded UltimateError — its code, cause and fix — never X_INTERNAL
Topic guards (tier 1)X_TOPIC_FORBIDDEN — cause names the actor and topic, never the topic's data

One authz system. A live query cannot become a second door into your data — that is the failure mode that killed the allow/deny generation of frameworks (The eight primitives).

NULL

As of 2026-08, one rule for the generated SQL, the in-memory source behind from() and the live matcher alike. null and a column the row omits are the same absence.

OperatorNULL isGenerated SQL
= != ina value — it matches itself and nothing elseis null · is not null · is distinct from · in (…) or is null
> >= < <=unknown — a NULL on either side matches nothing"col" > $n, which already matches no NULL
orderBy, the cursorthe largest value: last ascending, first descendingasc nulls last · desc nulls first

where({ deletedAt: null }) emits "deletedAt" is null and binds no parameter. = $1 with a NULL argument is unknown in Postgres and unknown is never true, so before this the same read matched every row from a memory source and no row from a driver. A cursor across a nullable sort key had the defect one page later: page two stopped at the first NULL, and the rows behind it were unreachable. x queries show <name> --json prints the SQL that says so.

Both pagination systems now answer this the same way, As of 2026-08-24. @ultimat3/entity's repo cursor used to refuse a nullable sort key outright rather than answer where a NULL sorts; it now writes the same two orderings down (asc nulls last / desc nulls first), carries the absence in the cursor and reaches it in the seek. One answer, per axiom 1. What entity still refuses is narrower and is not about ordering: a nullable primary-key column used as the tiebreak — null = null is unknown, so two rows sharing a sort value are indistinguishable to the seek and one of them is served twice or never (Entities and migrations).

Request memo (tier 1) dedupe

Three components on one page calling liveFeed({ orgId }) resolve one query.

PropertyBehavior
Coverageevery query, cache: or not. cache: buys the tier behind the memo, never the memo itself
Storea map per request context, keyed by query name + input fingerprint + the query's tags
Lifetimeone request. No cross-request reuse, no eviction policy to tune
Hit cost~0
Concurrencythe entry is the read in flight, so a caller that races the first one joins it instead of starting a competing read
Scope safetytwo actors never share an entry — each request has its own context, and .as() reads in a child of it
Authorizationparsing, the policy and sql run on every call. What the memo holds is the execution, never the decision
Failurea rejection is evicted, so the next read in the request retries rather than replaying one failure
Invalidationnone — a write in the same request drops tier entries, not memo entries. fresh: true is the read-past, and what it read replaces the memo entry, so the next plain read of that key sees the write too

Streamed <Hole>s (Routes and render modes) are the common case: independent holes, one round trip to Postgres. The same memo is what keeps an uncached lookup called once per row of a list to one round trip — As of 2026-08, per row is what it used to cost.

The memo collapses the same read asked twice. Fifty different row lookups collapse one layer down, in the repo: findById issued across one microtask of a request is one where "id" in (…) (Entities and migrations → Point lookups batch themselves) — or, named on the chain instead of inferred from a loop, one preload() per relation (Entities and migrations → Preload states a relation the loop would infer).

Fifty counts collapse neither way — one count() per row is fifty different questions, so nothing above the repo can batch them. The repo answers them in one statement instead: db.likes.where({ orgId }).andWhere('postId', 'in', ids).countBy('postId') is a map keyed by the column's values, biggest group first, with a value nothing matched absent rather than 0 (Entities and migrations → A count per row is one grouped count).

Audit: who saw what

query({ audit: true }) writes one record per call into the sink an action's audit: true writes to — one contract, @ultimat3/core's AuditRecord / AuditSink, one installed sink — the auditSink adapter port, set as auditSink: in apps/<app>/runtime.ts (Adapters; 31.0.0 deleted setAuditSink). As of 2026-10.

Rule
What is a callread(input), .as(), the HTTP GET, .page() — one record each. sourceFor(…) + execute() (how @ultimat3/mcp serves a tool): one per execute(), or one at the build when it is refused
A memo or cache hitrecorded, replayed: true — an audit of a read is who saw what, not what the database computed
Outcomesallowed · denied (a policy refusal, before or after the input parse) · failed (an unparsed input, a spent rate limit, a throwing source)
surfaceserver · http · mcp — the same vocabulary an action's record uses
namethe export name. action holds the same value, deprecated, removed in 25.0.0 — read name
primitive'query' (an action's record says 'action') — what tells a read from a write in one sink
inputthe parsed input, never the raw payload; a persisting sink redacts it through auditableInput
rowsnever on the record, as an action's result never is
Not recordedexplainQuery(), describeSql(), the shared live window: built unenforced, no caller to attribute
No sink installedX_QUERY_AUDIT_SINK_MISSING, before the input parse
The sink refusesa denied/failed record: logged (audit.sink.failed), the caller gets the original error. An allowed record: the rows are withheld, X_QUERY_AUDIT_SINK_FAILED — a read commits nothing, so retry

Every cached query carries a tag

The contract. Not yet a gate — As of 2026-08 X_CACHE_UNTAGGED_QUERY is reserved: no code path raises it, and x errors explain X_CACHE_UNTAGGED_QUERY refuses it (Error codes → Reserved codes).

CaseToday
a cached query no tag coverscached under a key no invalidates fan-out reaches — stale until its ttlMs, and forever without one
a tag no entity declaresX_CACHE_TAG_UNKNOWN, fix: x manifest — the opposite mistake, and the one that is enforced
the x verify gateno step reads a query's tags. Nothing fails

Until it is a gate, tag coverage is a review item, not a build error. Declare the entity tag — tags() / entityTag() in Caching and invalidation.

Subscription caps

Load shedding is a decision with a typed error, not a fall-over.

CodeTriggerFix
X_SUBSCRIPTION_LIMITa socket, tenant or node reached a cap; the error names which scope refused, and which knobraise maxPerSocket / maxPerTenant / maxEntries on the LiveQueryRegistry (per socket, default 128), or unsubscribe unused live queries — and a channel topic cap answers maxTopicsPerSocket / maxTopicsPerNode on the ChannelHub. All constructor options, none an app.config.ts field
X_CURSOR_STALEresume cursor outside the change buffer and no snapshot path suppliedpass 'snapshot' to resumeFrom() so the fallback path can re-snapshot instead of failing
X_TRANSPORT_UNAVAILABLEthe fanout bus is unreachablex doctor transport

Verbatim shapes: packages/realtime/src/errors.ts. Full index: Error codes.

Testing

x test live — its own runner, its own fixture shape. Runs against a cloned Postgres plus an in-process replicator and in-process NATS.

AssertedWhy it catches regressions
Initial snapshotthe sql is right, ordered, and bounded
Incremental patch on writethe matcher's enter/leave/update decision is right for the changed row
Reconnect deltaresume from an LSN produces the same state as a fresh snapshot
Policy-filtered row never deliveredthe per-row re-check actually runs

Every query({ live: true }) emits a test covering snapshot + one patch + one policy-filtered row, green on the first run. Extend it as the query grows — an untested live query is a red build.

x test live --json
x verify              # runs all six test types

Introspection

CommandOutput
x queries list --jsonname, live, capability, tags, ttlMs — the table header is cmd-registries.ts's own
x queries show <name> --jsongenerated SQL, tag set, MCP tool shape
x cache graph --jsonwhat a write to each tag evicts, including this query's entry

Rules

  • One query per read shape. Two queries differing by a boolean is one query with a boolean input field.
  • Filter rows in sql; decide yes/no in policy. Never mix.
  • live: true needs orderBy + limit, always.
  • Presence, typing indicators, and cursors are tier 1 channels forever — never model ephemeral state as rows (Realtime).
  • A cache miss must be correct and merely slower. No query may depend on a hit.
  • NULL is a value to =/!=/in, unknown to >/<, and the largest value to orderBy. Never write a filter that reads a NULL a fourth way.
  • One order, three readers: the generated SQL, the cursor and the live matcher all serve <declared keys>, "id" asc. Never sort a window by the declared keys alone.
  • Cache keys are framework-generated. A hand-built key is a rejected PR.

Edit this page on GitHubThis page as markdown