API index and search · Build metadata
Supporting declarations
packages/client/src/view.ts. These declarations explain referenced types. Only package-page symbols are package exports.
ViewTypes
/** Per-level column types (parallel to the WireSchema), used to JSON.parse json columns on
* projection. Built by the Store from the typed schema; absent ⇒ no parsing (bare values). */
export interface ViewTypes {
columnTypes: ColType[];
rels: Record<number, ViewTypes>;
}ChangePhase
/** The phase of an {@link ArrayView.onChanges} delivery: the initial hydrate `snapshot` vs a later
* incremental `batch` — the same distinction a narrator draws (`ChangeEvent` `snapshot`/`batch`). */
export type ChangePhase = "snapshot" | "batch";ViewChangeListener
/** A per-view change listener ({@link ArrayView.onChanges}): the net `FlatChange[]` this view folded,
* the {@link ChangePhase} it arrived on, and the view's `WireSchema` (the position→name source for
* `resolveChange`). This is the DIFF the data channel ({@link ArrayView.subscribe}) discards — the
* seam a narrator drives off. The `schema` is passed (not closed over) because the first `snapshot`
* fires synchronously inside `materialize`, before the caller holds the view handle. */
export type ViewChangeListener = (changes: FlatChange[], phase: ChangePhase, schema: WireSchema) => void;ArrayView
/** The public ArrayView contract `materialize()` returns. */
export interface ArrayView<R> {
/** The current materialized result (reference-stable where data is unchanged). */
readonly data: readonly R[];
/** The engine query id the Store assigned this view (1:1 with the view). The same `qid` the raw
* change stream ({@link Store.subscribeChanges}) tags each frame with, so a consumer can
* correlate this query with its `ChangeEvent`s (e.g. to bind a narrator) straight off
* `materialize(query).qid` — no separate handle needed. */
readonly qid: QueryId;
/** The query's view `WireSchema` (the position→name source for {@link resolveChange}), captured
* from its `hello` frame. `null` while PENDING — a remote backend's `hello` arrives async; an
* in-process backend (wasm/replica) populates it synchronously during `materialize`. */
readonly schema: WireSchema | null;
/** The query's SERVER-CHANNEL state (`unknown` while loading, `complete` once server-authoritative;
* the `error` variant is reserved and currently unproduced). A pending optimistic mutation no
* longer moves this — it is a separate axis (FOLDED-MUTATIONS-DESIGN §7). `complete` for backends
* with no server lifecycle. Changes notify subscribers. */
readonly resultType: ResultType;
/** Subscribe; fires immediately with the current data, then after each applied batch (and after
* a {@link resultType} change — re-read `resultType` in the listener). */
subscribe(listener: (data: readonly R[]) => void): () => void;
/** Subscribe to this view's folded CHANGE stream — the `FlatChange[]` it applies, NET of no-op
* cycles (a rebase's balanced `remove`+`add` / edit round-trip cancels, so a correctly predicted
* optimistic write delivers nothing here). Carries the diff {@link subscribe} throws away; the
* per-view seam a narrator rides. Does NOT replay on subscribe — attach via
* `store.materialize(query, { onChanges })` to catch a synchronous backend's first snapshot.
* A view with any change listener also enriches its own `remove` ops with the evicted subtree
* (per-view, no global opt-in). Returns a detach function. */
onChanges(listener: ViewChangeListener): () => void;
/** Tear down + stop receiving updates. */
destroy(): void;
}SingularArrayView
/** What a top-level `.one()` query materializes to: the single row (or `null`), not an array.
* A thin adapter over a {@link FlatArrayView} — all the folding is shared; only the result
* boundary unwraps (`data[0] ?? null`). Reference identity of the row is preserved. */
export interface SingularArrayView<R> {
/** The single current row, or `null` when the query matches nothing. */
readonly data: R | null;
/** The engine query id — see {@link ArrayView.qid}. */
readonly qid: QueryId;
/** The query's view `WireSchema` — see {@link ArrayView.schema}. */
readonly schema: WireSchema | null;
/** The query's lifecycle state — see {@link ArrayView.resultType}. */
readonly resultType: ResultType;
/** Subscribe; fires immediately with the current row, then after each applied batch (and after
* a {@link resultType} change). */
subscribe(listener: (data: R | null) => void): () => void;
/** Subscribe to the view's folded CHANGE stream — see {@link ArrayView.onChanges}. The changes
* are the same positional `FlatChange`s as the plural view (a `.one()` is just the list capped to
* one), so a narrator resolves them identically. */
onChanges(listener: ViewChangeListener): () => void;
/** Tear down + stop receiving updates. */
destroy(): void;
}SingularView
/** Wrap a plural {@link FlatArrayView} as a {@link SingularArrayView} for a `.one()` query
* (the engine caps it to `limit = 1`, so the top list holds at most one node). */
export declare class SingularView<R> implements SingularArrayView<R> {
private readonly inner;
constructor(inner: ArrayView<R>);
get data(): R | null;
get qid(): QueryId;
get schema(): WireSchema | null;
get resultType(): ResultType;
subscribe(listener: (data: R | null) => void): () => void;
onChanges(listener: ViewChangeListener): () => void;
destroy(): void;
}rowsEqual
/** Elementwise equality over positional bare-cell rows — THE row comparator for every JS-side
* diff (views, aggregate heads, the persistence mirror), exported so no caller grows a drifted
* private copy. Per cell: `===` keeps `-0 === 0` (the engine's key semantics treat them equal);
* the `Object.is` arm makes a NaN cell equal itself (under `!==` alone, a NaN-bearing row never
* matches any copy of itself, so every diff re-emits it as a spurious edit forever). */
export declare function rowsEqual(a: WireValue[], b: WireValue[]): boolean;FlatArrayView
export declare class FlatArrayView<R = unknown> implements ArrayView<R> {
private readonly _qid;
private _schema;
private types?;
private seeded;
private top;
private dirty;
private cached;
private rt;
private readonly listeners;
private readonly changeListeners;
private pendingSegments;
constructor(schema?: WireSchema, types?: ViewTypes, qid?: QueryId);
get qid(): QueryId;
get schema(): WireSchema | null;
get resultType(): ResultType;
/** Set the query's lifecycle state (the Store routes the backend's per-query signal here).
* Notifies subscribers on a change so a status-bound listener (React `useQueryStatus`) re-reads,
* WITHOUT re-projecting data (it is unchanged). */
setResultType(rt: ResultType): void;
/** (Re)bind to a schema and clear the tree IN PLACE. The first `hello` (pending → ready)
* and a re-hydrate (gap → new epoch — FLAT-CHANGES-DESIGN.md §2.3) both go through here, so
* the caller's view reference and its subscribers survive a re-subscribe. Does NOT notify —
* the snapshot that follows (`applyChanges`) does, avoiding an empty-then-filled flicker.
* KEEPS any SSR `seeded` rows: they are retired only when the first live snapshot lands
* ({@link retireSeed}, driven by the Store), so `data` shows the seed — not an empty tree —
* across the whole `hello`→first-`snapshot` gap. */
reset(schema: WireSchema, types?: ViewTypes): void;
/** Install a pre-projected SSR first-paint snapshot (SSR-DESIGN.md §6). The rows are already
* in result shape (json columns parsed, relationships nested), so a view with no live backend
* (the server one-shot Store) reads them directly, and a browser view shows them until its
* first live snapshot lands ({@link retireSeed}). Does NOT notify — it is set at materialize
* time, before any subscriber, and the live snapshot that follows notifies. */
seed(rows: readonly R[]): void;
/** Retire the SSR first-paint seed — the Store calls this as it folds the maintained tree's first
* live snapshot, so `data` switches from the seed to the live tree with no empty gap between them
* (the seed deliberately survived the earlier `reset`/`hello`). Idempotent. Does NOT notify — the
* snapshot fold it accompanies does; BUT when that fold is empty (a 0-row result, or rows already
* in `top`) it notifies nothing, so the Store forces a {@link notify} on the strength of the `true`
* return here — else the view reads the live tree yet never re-renders (a frozen seed). Returns
* whether a live seed was actually cleared (so the Store knows a forced notify is owed). */
retireSeed(): boolean;
/** Apply a batch (the hydrate snapshot or one transaction's events) in order, then
* notify subscribers once. Order is significant (FLAT-CHANGES-DESIGN.md §5.4). A no-op
* while pending (changes never precede the `hello` that resets the schema).
*
* `enrichRemoves` ⇒ before a removed node is dropped, reconstruct its full subtree and attach it
* to the `remove` op's `node` (in place, so the same event object the Store fans out to its
* change subscribers carries it). Off by default — paid only when a consumer asked for it, and
* only on a real eviction (an rc-decrement that keeps the row leaves `node` absent). A view with
* an attached {@link onChanges} listener ALSO enriches (per-view, no global opt-in), so a
* narrator can resolve a removed row's subs whether or not the store-global counter is set.
*
* `deferNotify` ⇒ fold but do NOT notify; the caller is responsible for calling {@link flush}
* later. The Store uses this to fold every view in one commit before notifying any subscriber
* (cross-view-atomic notification — `Store.onCommitBoundary`); standalone use leaves it off, so
* a bare view still fires its subscribers after each applied batch.
*
* `phase` tags the {@link onChanges} delivery (`snapshot` for the hydrate, `batch` otherwise); it
* does not affect the fold. Returns whether the batch changed the view (so a deferring caller
* knows it must be flushed). */
applyChanges(events: FlatChange[], enrichRemoves?: boolean, deferNotify?: boolean, phase?: ChangePhase): boolean;
/** Notify subscribers with the current data. The deferred half of {@link applyChanges} (when
* `deferNotify` was set): the Store calls this at the commit-notify barrier — after every view
* touched by the same commit has folded — so a subscriber that re-reads a sibling view inside
* its callback observes post-commit data, never a torn mid-commit state. */
flush(): void;
get data(): readonly R[];
subscribe(listener: (data: readonly R[]) => void): () => void;
onChanges(listener: ViewChangeListener): () => void;
/** Net the folded batch and hand the survivors to the change listeners, AFTER the data `notify`
* (the order `Store.subscribeChanges` consumers already observe). A batch that nets to nothing —
* a correctly predicted rebase — makes no call, so a narrator sees only real change. Each listener
* is isolated: a throwing one reports to `note` (the caller re-raises the first) and the rest still
* run, so one bad narration template never starves a sibling listener nor corrupts the view. */
private deliverChanges;
destroy(): void;
private applyAt;
private applyOp;
private applyAdd;
/** Drop one path to `row`. Returns the evicted {@link Node} (its full subtree intact) when the
* last path went away — `null` when another path still holds it (rc decremented, nothing left
* the result). */
private applyRemove;
private applyEdit;
private project;
/** Notify data subscribers with the current {@link data}. Normally driven by a fold ({@link
* applyChanges}/{@link flush}); the Store also calls it directly to land a seed retirement whose
* accompanying fold was empty (see {@link retireSeed}). */
notify(): void;
}