API index and search · Build metadata
Supporting declarations
packages/client/src/store.ts. These declarations explain referenced types. Only package-page symbols are package exports.
DehydratedQuery
/** One query's SSR snapshot, keyed by its `viewKey` ({@link stableKey} of the AST): the
* pre-projected first-paint `rows` plus the `cvMin` watermark they reflect (SSR-DESIGN.md §6.2).
* Serializable as-is into the HTML — `rows` are already JSON values (json columns parsed). */
export interface DehydratedQuery {
rows: unknown[];
cvMin: number;
}DehydratedState
/** The whole dehydrated cache: every preloaded query's snapshot, keyed by `viewKey`. The server
* builds it with {@link Store.dehydrate}; the browser seeds it with {@link Store.hydrate}. */
export type DehydratedState = Record<string, DehydratedQuery>;AssembledNode
/** A single assembled (nested-by-name) row from `POST /query` (SSR-DESIGN.md §3.3): the cells
* under `cols`, each in-view relationship inlined by its alias (a nested array / object, or a
* scalar for a `countAs` aggregate). {@link Store.assembleSnapshot} converts these to the
* view's projected result shape. */
export interface AssembledNode {
cols: Record<string, WireValue>;
[rel: string]: unknown;
}WriteTx
/** The write transaction handed to `store.write(tx => …)`. Rows are objects keyed by column;
* the Store positionalizes them (and stringifies json columns) before the backend sees them.
*
* `add` takes an {@link InsertOf} row — a nullable column may be omitted (it is filled with `null`,
* design 206 §7). `remove`/`edit` take a full {@link RowOf} row: they identify an EXISTING row, so
* every column (nullable ones as their actual `T | null` value) must be present. */
export interface WriteTx<S extends ColsMap> {
add<N extends keyof S & string>(table: N, row: InsertOf<S[N]>): void;
remove<N extends keyof S & string>(table: N, row: RowOf<S[N]>): void;
edit<N extends keyof S & string>(table: N, oldRow: RowOf<S[N]>, newRow: RowOf<S[N]>): void;
}CachedQueryView
export interface CachedQueryView<Q extends Query<any, any, any>> {
readonly view: ReturnType<Q["materialize"]>;
/** Retain this query's named remote footprint and release it later. Ad-hoc local queries
* return a no-op release function. */
retain(query: Q): () => void;
destroy(): void;
}SyncQueryLease
export interface SyncQueryLease {
readonly resultType: ResultType;
subscribe(listener: () => void): () => void;
release(): void;
}QueryInspect
/** One live materialized view's read-only summary for a devtools pane (DEBUG-TOOLS-BROWSER-DESIGN
* §4.2 — "surface, not instrument"). All fields are already held by the {@link Store}; this is the
* single read-only accessor over the otherwise-private `views`/`asts` maps. */
export interface QueryInspect {
/** The Store-assigned query id (also the backend's qid — the Store passes it straight through). */
qid: QueryId;
/** The query's AST (`Store.asts`), for the inspector's pretty-print / table-derivation. */
ast: Ast;
/** The view's SERVER-CHANNEL state (`unknown` while loading, `complete` once authoritative). */
resultType: ResultType;
/** Current materialized row count (`view.data.length`). */
rowCount: number;
/** A capped peek at the projected rows (reference-stable objects off the live view). */
sample: readonly unknown[];
}StoreInspect
/** A frozen snapshot of the Store's live query state for a devtools pane ({@link Store.__inspect}). */
export interface StoreInspect {
queries: QueryInspect[];
}Store
export declare class Store<S extends ColsMap> {
/** Type-safe query entry: `store.query.issue.where.closed(false).materialize()`. */
readonly query: QueryRoot<S>;
private readonly schema;
private readonly backend;
private nextId;
private readonly views;
private readonly asts;
private readonly syncLeases;
private readonly seeds;
private changeListeners?;
private removedSubtreeWanted;
private resultTypeListeners?;
private readonly hasResultTypeLifecycle;
private commitDepth;
private readonly pendingFlush;
private readonly pendingChanges;
constructor(schema: Schema<S>, backend: Backend);
/** Materialize any fluent query object. Named queries subscribe remotely by `(name,args)`;
* ad-hoc builder queries are local-only for local-first backends.
*
* `opts.onChanges` binds a narrator to this view's DIFF stream ({@link ArrayView.onChanges}) — the
* per-view seam that replaces filtering the store-global {@link subscribeChanges} by `qid`. It is
* wired BEFORE the backend registers the query, so a synchronous backend's first `snapshot` (fired
* inside `registerQuery`, before this returns) is delivered too. */
materialize<Q extends Query<any, any, any>>(query: Q, opts?: {
onChanges?: ViewChangeListener;
}): ReturnType<Q["materialize"]>;
/** One-shot AUTHORITATIVE read: materialize `query`, wait until its result is server-authoritative
* ({@link ResultType} `"complete"`), read the data once, then destroy the view — resolving with the
* plain result rather than a live subscription. Rejects if the query enters the `"error"` state. Use
* it for exports, imports, undo snapshots — anywhere that wants the current answer as a value.
*
* A synchronous local-first backend (wasm/replica) has already delivered the first snapshot inside
* {@link materialize}, so the view is `"complete"` on entry and this settles on the next microtask
* without ever attaching a listener; a remote backend settles when the first live snapshot lands.
* The query is NEVER left subscribed — the view is destroyed before the promise settles either way.
* (A remote query that never completes leaves the promise pending, exactly as a `resultType` poll
* would; race a timeout at the call site if you need one.) */
readOnce<Q extends Query<any, any, any>>(query: Q): Promise<ReturnType<Q["materialize"]>["data"]>;
/** True when the backend can retain a remote named query independently from the local
* materialized AST view. React uses this to keep one local view per AST while still sending
* every mounted `(name,args)` lease through the backend. */
canRetainRemoteQueries(): boolean;
/** Build one local AST view, with remote syncing retained separately through the returned
* handle. This is a lower-level API for UI bindings; ordinary app code should keep using
* `materialize(query)`. */
createCachedQueryView<Q extends Query<any, any, any>>(query: Q): CachedQueryView<Q>;
/** Retain a named remote query purely for normalized/local-first coverage. This does not
* register or materialize the query AST locally, so React can keep server sync coverage alive
* without subscribing to the broad coverage result tree. */
retainSyncQuery<Q extends Query<any, any, any>>(query: Q): SyncQueryLease;
/** Apply a batch of mutations (object rows → positional). Resolves when the backend has
* accepted them (local: applied; remote: sent). The resulting view updates flow back via
* the backend's event stream. */
write(fn: (tx: WriteTx<S>) => void): Promise<void>;
/** Direct-commit write to LOCAL-only tables (`201-LOCAL-ONLY-TABLES-DESIGN.md` §6): the
* client-authoritative path for selection state, draft text, view prefs, scratch rows. It
* bypasses the optimistic pending stack entirely — a local table is untracked, so it never
* rebases, reverts, or waits on a server confirmation (it "moves on its own").
*
* Rejects a synced/tracked table (M2): a direct write to one would be un-applied on the very
* next server rewind. Local writes also must NOT live inside a replayable mutator (M1) — the
* server runs the mutator from `args` alone and cannot see local tables; use this instead.
* Same keyed `WriteTx` shape as {@link write}. `async` so the no-seam / M2 guards surface as a
* REJECTED promise (the `Promise<void>` contract) rather than a synchronous throw that escapes a
* caller's `.catch` and crashes the event handler / render frame. */
writeLocal(fn: (tx: WriteTx<S>) => void): Promise<void>;
/** Drain a keyed `WriteTx` callback into positional {@link Mutation}s (shared by
* {@link write} / {@link writeLocal}; json columns stringified, rows in column order). */
private collectMutations;
/** Seed a query's first-paint snapshot from a `POST /query` response (server side): convert the
* assembled rows to the view's projected shape and stash them by `viewKey`. A view materialized
* for this AST (during the synchronous render) reads the seed; {@link dehydrate} serializes it. */
seedAssembled(ast: Ast, rows: AssembledNode[], cvMin: number): void;
/** The dehydrated first-paint cache for every preloaded query — embed it in the HTML and pass it
* to {@link hydrate} in the browser (SSR-DESIGN.md §6.2). */
dehydrate(): DehydratedState;
/** Seed the browser store from the server's {@link dehydrate} output (SSR-DESIGN.md §6.2): each
* view materialized for a hydrated AST shows these rows until its first live `hello` reconciles. */
hydrate(state: DehydratedState): void;
/** A query's hydrated first-paint snapshot, by `viewKey` — what React's `getServerSnapshot`
* reads so an SSR render (and the matching client hydration pass) sees the seeded rows without
* opening a subscription. */
seedSnapshot(viewKey: string): DehydratedQuery | undefined;
primaryKeyFor(table: string): readonly string[];
/** Convert assembled (nested-by-name) rows (SSR-DESIGN.md §3.3) into the view's projected result
* shape: spread `cols` (parsing json columns), recurse into each relationship by its alias
* (plural → array, `.one()` → object/null, `countAs` → bare scalar). */
assembleSnapshot(ast: Ast, rows: AssembledNode[]): unknown[];
private assembleNode;
private registerMaterialized;
private retainRemote;
private onEvent;
/** Retire a view's SSR seed — from the view (so `data` switches from the seed to the maintained
* tree) AND from the seeds map (so no later mount re-seeds a now-live query) — but ONLY once the
* query is AUTHORITATIVE (`resultType === "complete"`). Called BEFORE the fold it accompanies, so
* that fold's notify already reflects the live tree with no empty gap. Idempotent.
*
* The gate is the fix for the synchronous optimistic/wasm backend: it fires a query's FIRST snapshot
* from LOCAL, not-yet-synced state while the query is still `unknown` (`registerMaterialized` marks a
* lifecycle-backed remote view `unknown` up front for exactly this), then delivers the authoritative
* rows one event later as a `catchUp` batch — having already flipped the query to `complete`. So the
* seed survives the pre-sync snapshot (`unknown` ⇒ skip) and retires on the catch-up (`complete` ⇒
* retire). A lifecycle-LESS backend (pure wasm, the SSR one-shot, tests) is `complete` from creation,
* so its first snapshot retires the seed exactly as before this gate existed. */
private retireSeedIfLive;
/** Retire the SSR seed (if authoritative) and fold the accompanying hydration delta — BEFORE the
* fold so its notify already reflects the live tree with no empty gap. The subtlety: a hydration
* can fold NOTHING — a 0-row authoritative result, or one whose rows are already present in `top`
* (a query whose result is fully covered by an already-hydrated sibling: the shared rows dedup to
* zero net base mutations). Then {@link FlatArrayView.applyChanges} notifies nothing, so the
* seed→tree switch would never reach subscribers and the view freezes on the stale seed. Guard
* against that: if the seed retired but the fold was a no-op, force the handoff notify (inline, or
* via the commit-boundary flush). Flash-safe — the forced notify only fires when there was nothing
* to fold, so `data` is already the correct live tree by then. */
private foldHydration;
/** Fold a batch into its view, then notify now or — inside a commit bracket — defer the view's
* notification to the commit boundary, so all sibling views fold first (cross-view-atomic
* notification; see `commitDepth`). */
private applyAndTrack;
/** Deliver everything deferred during the just-ended commit, after every affected view has folded
* (cross-view-atomic notification): view subscribers first, then the raw change stream
* (narrators/devtools), each frame in arrival order. A throwing listener does not stop the others
* — the first error is re-raised only once the whole flush completes (mirroring the backend's
* per-query isolation). View subscribers run before change listeners, preserving the per-event
* order that held before coalescing (a view's subscribers fired before its change frame). */
private flushCommit;
/** Subscribe to the raw per-query {@link ChangeEvent} stream (hello / snapshot / batch) the Store
* routes to its views, tagged by `qid`. Fired AFTER the event is folded — and, for a commit that
* fans out to several queries (the in-process engine's `onCommitBoundary`), after EVERY view in
* that commit has folded — so a listener that re-reads ANY view (its own or a sibling) sees
* post-commit state, never a torn mid-commit one. Frames keep their arrival (engine-dispatch)
* order, one per affected query. This is the supported way to drive change-derived layers
* (e.g. {@link resolveChange} → @rindle/narrator, or a devtools pane) off a live store — attach
* BEFORE `materialize` to catch a synchronous backend's first `hello`+`snapshot`.
* The per-query view `WireSchema` rides the `hello` frame (also readable via `view.schema`).
*
* The third listener arg is the post-fold {@link ArrayView} for this `qid` (so a template wanting
* list context — current `data`, `schema`, `resultType` — needn't look it up). It is ALWAYS the
* plural view, even for a top-level `.one()` query: the Store retains the list-shaped view, not the
* SingularView wrapper handed back from `materialize`. `undefined` only if the view is mid-teardown.
*
* `opts.removedSubtree` enriches every `remove` op on this stream with the full removed subtree
* ({@link FlatOp.node}), so a consumer can resolve a removed row's nested subs exactly as on an
* `add` (a bare remove carries only the leaving row). It is reconstructed client-side from the
* view — no wire/engine cost — and paid only on real evictions while at least one subscriber asks.
*
* Returns a detach function; multiple listeners may attach. */
subscribeChanges(listener: (qid: QueryId, ev: ChangeEvent, view?: ArrayView<unknown>) => void, opts?: {
removedSubtree?: boolean;
}): () => void;
/** Subscribe to per-query {@link ResultType} transitions (the server-channel lifecycle the backend
* pushes — `unknown` → `complete`, etc.), tagged by `qid`. Fired only on a CHANGE (never replayed
* on attach; read `view.resultType` for the current value). The supported seam for a status-driven
* layer (a devtools pane). Returns a detach function; multiple listeners may attach. */
subscribeResultType(listener: (qid: QueryId, rt: ResultType) => void): () => void;
/** A read-only snapshot of every live materialized view (DEBUG-TOOLS-BROWSER-DESIGN §4.2): its
* qid, AST, {@link ResultType}, row count, and a capped row sample. Built fresh on each call from
* the live `views`/`asts` maps — never cached, never mutating. `sampleRows` caps the per-query
* peek (default 50) so a large view doesn't bloat the snapshot. */
__inspect(sampleRows?: number): StoreInspect;
private columns;
/** An object row → a positional cell array in the table's column order (json → string). */
private positionalize;
/** The per-level column types parallel to the WireSchema, so the view parses json columns. */
private viewTypes;
}