> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bijection.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Questions and answers

> Write a relation, declare a question, and read its retained answer

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

| Result | Meaning | Paging |
| - | - | - |
| `complete` | One complete bounded answer, including joins, grouping and sorting | Pages come from the same saved result; `row_count` is exact |
| `browse` | Rows from one retained dataset snapshot, optionally filtered and projected | Each page is read from that snapshot; `row_count` is `null` |

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.

```ts bijection/schema.ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import { defineSchema, defineVirtualTable } from "bijection/server";
import { internal } from "./_generated/api";
import { sales } from "./datasets";

export default defineSchema({
  warehouseSales: defineVirtualTable(sales).access({
    read: internal.access.readSales,
    disclose: internal.access.discloseSales,
    reads: [],
  }),
});
```

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](/access/disclosure). 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.

```ts bijection/analytics.ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import { q } from "bijection/server";
import { v } from "bijection/values";
import { defineQuestion } from "./_generated/server";

export const regionalSales = defineQuestion({
  args: { region: v.string() },
  relation: q
    .table("warehouseSales")
    .filter(q.eq(q.field("region"), q.arg("region")))
    .select({ region: q.field("region"), quantity: q.field("quantity") }),
});
```

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:

```ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import { createWorkspace } from "./bijection/_generated/client";

const workspace = createWorkspace({
  url: deploymentUrl,
  token: getAccessToken,
});
const question = workspace.questions.analytics.regionalSales;
const plan = await question.explain({ region: "EU" });
const execution = await question.ask({ region: "EU" });
const answer = await execution.result();
for await (const page of answer.pages({ page_size: 100 })) render(page.rows);
const facets = await answer.facets([
  { kind: "terms", field: "region" },
  { kind: "integer", field: "quantity", bins: 24 },
]);
```

`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:

```ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
for await (const locator of question.recoverable()) {
  const recovered = question.resume(locator.answer_id);
  const state = await recovered.status();
}
```

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.

```ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
const page = await workspace.collections.customers.page({ limit: 20 });
const next = page.cursor
  ? await workspace.collections.customers.page({ cursor: page.cursor })
  : undefined;
const invoices = await workspace.collections.customers.related(customerId, "invoices", { limit: 20 });
const samples = await workspace.collections.readings.samples("sensor-a", {
  from: 0n, to: 1000000n, limit: 20,
});
```

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.

```bash theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
bijection query customers --page-size 20
bijection query related customers CUSTOMER_ID invoices --page-size 20
bijection questions ask function:analytics:regionalSales --args '{"region":"EU"}'
bijection answers resume ANSWER_ID
```

`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.

```tsx theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
import { useAnswer } from "bijection/react";
import { workspace } from "./workspace";

export function Sales({ region }: { region: string }) {
  const result = useAnswer(
    workspace.questions.analytics.regionalSales,
    { region },
    {
      page_size: 50,
    },
  );
  if (result.state === "idle" || result.state === "loading")
    return <p>Loading sales…</p>;
  if (result.state === "failed") return <p>{result.error.message}</p>;
  if (result.state === "unavailable")
    return <button onClick={result.reconnect}>Reconnect</button>;
  return (
    <section>
      <button onClick={result.rerun}>Refresh</button>
      <ul>
        {result.page.rows.map((row, index) => (
          <li key={index}>{row.quantity.toString()}</li>
        ))}
      </ul>
      <button
        disabled={!result.is_next_page_available || result.is_page_loading}
        onClick={result.nextPage}
      >
        Next page
      </button>
    </section>
  );
}
```

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:

```ts theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
const result = await execution.result();
if (result.kind === "browse") {
  for await (const page of result.pages({ page_size: 100 })) {
    for (const entry of page.rows) {
      render(entry.value, entry.reference);
      const sameRow = await result.readRow(entry.reference);
    }
  }
}
```

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.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.