Skip to main content
You write a relation, declare a question, and get an answer. A relation is built with the same q builder as views. A question fixes one relation, its arguments and its bounds on the server. Asking it produces an answer: a retained, typed result you page, count and release. Read it in React through useAnswer. Arguments and row types flow through the generated API. The hook retains one answer identity, follows it, and releases it when its arguments change or the component unmounts. Refresh explicitly asks again.

Choose the answer you need

A complete answer that exceeds its bounds fails instead of returning a partial answer. Browse does not compute a population-wide total or sort. New dataset publications do not change either result. Current read and disclosure rules still apply to every read.

Declare the input

An installed dataset can be exposed as a virtual table. Import its existing defineDataset declaration; deployment administration owns catalog binding and credentials. The dataset must have a published version and managed retention.
bijection/schema.ts
The read rule authorizes the selected input, including fields used by predicates. The disclosure rule separately authorizes retained answers and their computation. See disclosure rules. A virtual table has no local documents or indexes and cannot be written through ctx.db. Relations over datasets currently support present scalar string, integer, finite float, boolean and byte fields, including nullable fields. Project unsupported optional or nested fields out of the relation. Complete answers can combine dataset inputs with bounded local inputs; combining dataset inputs with live SQL inputs is refused.

Declare the question

Import defineQuestion from the generated server module so the answer’s row type is resolved against your schema. The client supplies arguments; the question owns the relation and its bounds. Use q.arg("name") to bind a declared argument into the fixed relation.
bijection/analytics.ts
A complete answer is the default. Use result: "browse" only for snapshot traversal. limits.max_rows and limits.max_bytes bound that answer; exceeding either refuses the answer. Answers expire rather than silently being asked again.

Use the generated workspace

For a complete question named regionalSales, code generation exposes its native-admitted arguments and answer columns:
question.explain(args) previews the question through the planner without reserving an answer. execution.explain() reads the plan admitted for that answer without replanning. status() observes existing work; cancel() requests cancellation; release() ends custody. Losing a reply does not mean native work failed. A question defaults to complete; browse answers have pages and exact snapshot row references. Browse results have neither collect() nor facets(). Integers remain bigint in arguments, result rows and histogram bounds. Facets count every row of this retained answer, including its selected predicates. They do not count the displayed page or ask the source again. The response names its answer, request digest, result digest and exact population size. Null and absent values are counted separately. Integer buckets have inclusive bounds; calendar buckets have closed-open UTC epoch-millisecond bounds, with Monday weeks. A request names at most eight answer columns and at most 64 values or buckets per column. A field exceeding its distribution bound is reported as unavailable; other fields remain exact. Unavailable fields contain no partial counts. Native reads remain bounded by the retained result and reply limits, and recheck current authorization, expiry and erasure before releasing the distribution. Source-wide facets remain separate observations, explicitly labelled in Explore. Node configures requests: fileRequestStore(privateDirectory) from bijection/node once. The same durable journal retains answer locators and operation requests; it stores no relation literals, result rows or credentials in answer locators. Recover an existing answer through the same generated question:
Recovery checks the callable and the native-admitted contract digest. A changed question or another question cannot read an old answer as its own typed result. Recovery does not ask the sources again. Current authorization and erasure rules still apply. For an answer whose question changed or was removed, use workspace.answers.resume(answer_id) to inspect its native state without claiming the current question’s row type. workspace.answers.recoverable() lists locators from the same private journal. Without the generated workspace, AnswerClient from bijection/browser asks an ad-hoc relation or a question reference: client.ask(relation, bounds) or client.ask(question, args). In an action, ctx.answers offers the same lifecycle (reserve, ask, get, page, facets, cancel, release, keep), plus collect for one bounded answer returned at once and explain.

Read collections and relationships

Ordinary rows, declared relationships and series have separate generated readers. Their names come from the schema and their paging meaning comes from the native reader. Withheld fields stay absent; projection does not grant access.
A collection cursor contains its original basis and read. Relationship and series cursors carry their native exact continuation. New reads check current access; a generated contract change refuses typed reuse.
bijection query reads collections and public query functions. bijection questions ask reserves and saves an answer identity before any source work and prints it; after a lost reply, rerun the same command with --answer-id to retry under that identity. bijection answers reads, pages, counts, cancels and releases a saved answer; none of its commands submits source work.

Read it in React

Use useAnswerExecution(execution) to observe an existing handle. useAnswerPage(answer) reads one bounded page when its read() method is called, and useAnswerFacets(answer, fields) observes distributions of that same answer. These adapters do not submit work during rendering. Their unavailable state describes an observation failure, not an invented native terminal state. Switching handles discards late replies. The shared subscription connection observes changes to the answer and its read authority. During revalidation or a lost connection, managed pages and facets are hidden; a late reply cannot restore them. Reconnecting checks the same answer and rereads the accepted page boundary. useAnswer(question, args) is the managed convenience path: it asks the question, owns the answer’s custody for the component and releases it.
A browse page contains at most 1,000 rows and 4 MiB of row values. page_size can request a smaller page. Both result kinds replace the displayed page, keeping client memory bounded. A filtered browse page can be empty while is_next_page_available is true. Use answer.collect() explicitly to return a readonly row array within the complete answer’s bounds. Each public page contains only rows and next_cursor; identity, count, basis and completion metadata belong to the result handle. Changing page_size changes subsequent reads without asking again. rerun() asks again for a new answer; reconnect() reauthorizes the existing one. Pass "skip" instead of arguments to suspend the hook. Integers remain bigint throughout the flow.

Open a snapshot row

Browse pages return one SnapshotRowReference per row. result.readRow(reference) reopens that exact row under current authorization:
A reference becomes unavailable when its answer is released or expires. It grants no authority by itself. A native table export may also supply reference.currentObject. This is a separate reference to the current document. Read it through the ordinary object API, which checks current access and can return a changed value or a deleted object. Never pass the snapshot reference itself to a mutation or treat its key as a current ID. Explore presents these as separate snapshot details and an Open current object link when the object is exposed by the application.