API index and search · Build metadata
@rindle/devtools
0.0.0 · Public export map; development manifest version (0.0.0).
Ast
InterfaceDeclaration · Source: packages/client/src/ast.ts:71 · Supporting declarations
The query AST. table is the only required field; the builder omits empty/false fields
(matching the Rust skip_serializing_if), which the deserializer treats as absent.
export interface Ast {
table: string;
alias?: string;
/** Projection (PROJECTION-SUPPORT-DESIGN.md §6). Absent ⇒ select all columns; present ⇒
* project to just these (drives what syncs + what the view reports). Serializes only when
* set, matching the Rust `Ast.select`'s `skip_serializing_if`. */
select?: string[];
where?: Condition;
related?: CorrelatedSubquery[];
start?: Bound;
limit?: number;
one?: boolean;
/** Aggregate this (sub)query's rows instead of materializing them (`REDUCE-DESIGN.md`
* §9). `count` on a `related` subquery surfaces a scalar `commentCount` field; the
* builder lowers it to a scalar-projected singular relationship. Absent ⇒ ordinary rows. */
aggregate?: Aggregate;
/** The {@link Ast.aggregate} value is **precomputed** — supplied as rows of a (synthetic)
* source table rather than reduced from child rows. Set by the normalized client's AST
* rewrite (`AGGREGATE-SYNC-DESIGN.md` §3.3) so the local engine reads the server's count
* with a plain singular join + the same projection instead of a `reduce`. Only meaningful
* with `aggregate`; absent ⇒ `false`. */
aggregatePrecomputed?: boolean;
/** Top-level `GROUP BY` columns (names), meaningful only alongside a root {@link Ast.aggregate}
* (`REDUCE-DESIGN.md` §8). Empty + `aggregate` set ⇒ a **global** aggregate (one `[count]` row);
* non-empty ⇒ one `[group…, count]` row per distinct value-tuple. Distinct from a relationship
* aggregate's implicit grouping (the correlation child key). Absent on the wire ⇒ empty. */
groupBy?: string[];
/** `HAVING` — a filter over the **post-aggregation** rows of a root {@link Ast.aggregate}
* (`REDUCE-DESIGN.md` §4: a filter directly above the `reduce`). Its condition addresses the
* aggregate's *output* columns — the {@link Ast.groupBy} columns and the synthetic `count`
* column — not base-table columns (those go in {@link Ast.where}, which filters rows *below* the
* reduce). Absent ⇒ no post-aggregation filter. */
having?: Condition;
orderBy?: OrderPart[];
}attachDevtools
FunctionDeclaration · Source: packages/devtools/src/global.ts:73 · Supporting declarations
Attach a devtools pane to a running client (DEBUG-TOOLS-BROWSER-DESIGN §6.2). Call this ONLY in a
dev build (e.g. behind import.meta.env.DEV with a dynamic import("@rindle/devtools")) so the
pane, its core, and this registration tree-shake out of production. Returns the {@link DevtoolsCore}
(also discoverable via {@link getDevtoolsHub}); call core.detach() to unwind.
export declare function attachDevtools(target: DevtoolsTarget, opts?: DevtoolsCoreOptions): DevtoolsCore;BackendDevObserver
InterfaceDeclaration · Source: packages/client/src/types.ts:140 · Supporting declarations
Dev-only passive tap over a backend's authoritative server stream. Optional because in-process backends have no server stream, and older/custom backends may only expose the Store tap.
export interface BackendDevObserver {
onServerDelta?(qid: QueryId, ev: BackendServerDelta): void;
}BackendServerDelta
TypeAliasDeclaration · Source: packages/client/src/types.ts:134 · Supporting declarations
A dev-only authoritative server delta exposed by backends that can distinguish the upstream server stream from their local view/IVM stream. Flat remote backends surface clean {@link ChangeEvent}s; local-first normalized/optimistic backends surface the path-free {@link NormalizedEvent}s before they are folded into the local engine.
export type BackendServerDelta = {
format: "flat";
event: ChangeEvent;
} | {
format: "normalized";
event: NormalizedEvent;
};ChangeEvent
TypeAliasDeclaration · Source: packages/client/src/types.ts:79 · Supporting declarations
What a {@link Backend} pushes per query: the handshake (hello), the (possibly
chunked) hydrate snapshot, then incremental batches. The core builds an ArrayView
on hello, hydrates on snapshot, folds on batch — identically for any backend.
export type ChangeEvent = {
type: "hello";
schema: WireSchema;
comparatorVersion: number;
} | {
type: "snapshot";
adds: FlatChange[];
last: boolean;
} | {
type: "batch";
events: FlatChange[];
catchUp?: boolean;
};collectTables
FunctionDeclaration · Source: packages/devtools/src/ast.ts:11 · Supporting declarations
Every base table this query can read: the root, each related subquery, and any correlated
subquery in the where tree (recursively). Mirrors the optimistic backend's queryTables
derivation (DEBUG-TOOLS-BROWSER-DESIGN §4.2 — a count(comments) subquery names comment, so a
comment mutation flips this query's pending axis).
export declare function collectTables(ast: Ast, into?: Set<string>): Set<string>;DeltaEntry
InterfaceDeclaration · Source: packages/devtools/src/types.ts:141 · Supporting declarations
One entry of the live delta stream — the IVM change primitive made visible (§4.3).
export interface DeltaEntry {
seq: number;
at: number;
qid: number;
kind: DeltaKind;
/** Nesting depth of the change path: 0 = top-level row, >0 = a child (relationship) change. */
depth: number;
/** A compact, human-readable description of the change. */
label: string;
}DeltaKind
TypeAliasDeclaration · Source: packages/devtools/src/types.ts:138 · Supporting declarations
The kind of a delta-stream row. child is an Add/Remove/Edit addressed at a NESTED path
(depth > 0) — rindle's relationship-level change (DEBUG-TOOLS-BROWSER-DESIGN §4.3).
export type DeltaKind = "hello" | "snapshot" | "add" | "remove" | "edit";DevtoolsBackend
InterfaceDeclaration · Source: packages/devtools/src/types.ts:74 · Supporting declarations
Optional backend-side devtools capabilities. __inspect is present on OptimisticBackend;
__attachDevtoolsServerDeltas is present on backends that can surface authoritative server
frames separately from the Store's post-apply view stream.
export interface DevtoolsBackend {
__inspect?(): OptimisticInspect;
__attachDevtoolsServerDeltas?(observer: BackendDevObserver): () => void;
}DevtoolsCore
ClassDeclaration · Source: packages/devtools/src/core.ts:44 · Supporting declarations
export declare class DevtoolsCore {
private readonly store;
private readonly backend?;
private readonly detachStore;
private readonly detachServerDeltas?;
private readonly hasServerDeltaTap;
private readonly timelineCap;
private readonly deltaCap;
private readonly sampleRows;
private readonly autoFlush;
private readonly now;
private readonly pollHandle?;
private readonly timeline;
private readonly order;
/** The pending keys seen at the previous recompute — the diff basis for lifecycle transitions. */
private prevPendingKeys;
private deltas;
private deltaSeq;
/** Set by a `batch` delta, consumed by the next recompute: did view churn coincide with a
* confirmation this turn? (the §4.1 snap-back heuristic). */
private churnSeen;
private queries;
private optimistic?;
private readonly listeners;
private dirty;
private flushScheduled;
private snapshot;
/** Optional deregistration from the global hub (set by {@link attachDevtools}). */
onDetach?: () => void;
constructor(target: DevtoolsTarget, opts?: DevtoolsCoreOptions);
/** The current read-model. Reference-stable between updates (rebuilt only on recompute), so it is
* safe to feed a `useSyncExternalStore`-style binding. */
getState(): DevtoolsState;
/** Subscribe to updates; fires immediately with the current state, then after each recompute. */
subscribe(listener: () => void): () => void;
/** Force a synchronous recompute (also used by the safety-net poll and by tests). */
refresh(): void;
/** Clear the delta stream ring (a panel "clear" affordance). */
clearDeltas(): void;
/** Drop every SETTLED timeline row (confirmed/dropped), keeping live pending ones + the deltas. */
clearHistory(): void;
/** Detach from the client: stop the poll, drop the store tap, deregister from the global hub. */
detach(): void;
private attachServerDeltas;
private onStoreDelta;
private onServerDelta;
private onResultType;
private markDirty;
private flush;
private recompute;
/** Reconstruct the fork/rebase lifecycle by diffing this pending snapshot against the previous
* one (DEBUG-TOOLS-BROWSER-DESIGN §4.1): new keys → invoked; vanished keys → confirmed (mid ≤
* confirmedLmid) or dropped; a fold's `f:<foldKey>` → `m:<mid>` transition is linked, not double
* counted. */
private reconcileTimeline;
private rebuildQueries;
private pushServerDelta;
private pushChangeDelta;
private pushNormalizedDelta;
private appendDelta;
private addEntry;
private renameEntry;
}DevtoolsCoreOptions
InterfaceDeclaration · Source: packages/devtools/src/types.ts:167 · Supporting declarations
Construction options for {@link DevtoolsCore }.
export interface DevtoolsCoreOptions {
/** Max timeline rows retained (oldest settled rows drop first). Default 200. */
timelineCap?: number;
/** Max delta-stream rows retained. Default 500. */
deltaCap?: number;
/** Per-query row sample size pulled from the store. Default 25. */
sampleRows?: number;
/** Safety-net poll interval (ms) that catches state moves with no event — a fold's debounced
* flush, or a pending flip on an already-`complete` query. Default 0 (off); {@link attachDevtools}
* turns it on. */
pollMs?: number;
/** Coalesce event-driven recomputes onto a microtask. Default true; tests pass `false` and drive
* recomputes explicitly via {@link DevtoolsCore.refresh}. */
autoFlush?: boolean;
/** Injectable clock (ms). Default `Date.now`; tests inject a deterministic counter. */
now?: () => number;
}DevtoolsHub
InterfaceDeclaration · Source: packages/devtools/src/global.ts:14 · Supporting declarations
The discovery surface a panel binds to: the set of attached cores + a change subscription.
export interface DevtoolsHub {
readonly version: number;
readonly cores: readonly DevtoolsCore[];
/** Fires whenever a core attaches or detaches (so a panel can pick one up). */
subscribe(listener: () => void): () => void;
/** Register a core; returns its deregistration function. */
register(core: DevtoolsCore): () => void;
}DevtoolsState
InterfaceDeclaration · Source: packages/devtools/src/types.ts:154 · Supporting declarations
The whole devtools snapshot a panel renders. Reference-stable arrays between updates where the
underlying data did not change is NOT guaranteed — panels should treat each getState() as fresh.
export interface DevtoolsState {
/** Newest-last mutation timeline (capped). */
timeline: TimelineEntry[];
/** Every live materialized view. */
queries: QueryEntry[];
/** Newest-last delta stream ring (capped). */
deltas: DeltaEntry[];
/** The raw optimistic-loop snapshot, when the backend exposes one (a "loop" summary line). */
optimistic?: OptimisticInspect;
capabilities: {
optimistic: boolean;
};
}DevtoolsStore
InterfaceDeclaration · Source: packages/devtools/src/types.ts:65 · Supporting declarations
The Store surface the core reads (a structural subset of @rindle/client's Store). The delta
- resultType taps are the SUPPORTED app-facing seams (no private devtools back door).
export interface DevtoolsStore {
subscribeChanges(listener: (qid: QueryId, ev: ChangeEvent) => void): () => void;
subscribeResultType(listener: (qid: QueryId, rt: ResultType) => void): () => void;
__inspect(sampleRows?: number): StoreInspect;
}DevtoolsTarget
InterfaceDeclaration · Source: packages/devtools/src/types.ts:80 · Supporting declarations
What {@link attachDevtools } binds to: a createRindleClient app, or any { store, backend }.
export interface DevtoolsTarget {
store: DevtoolsStore;
/** Narrowed to {@link DevtoolsBackend} at runtime when it carries `__inspect` (capability probe). */
backend?: unknown;
}FoldInspect
InterfaceDeclaration · Source: packages/devtools/src/types.ts:35 · Supporting declarations
One folded entry's debounce window — mirror of @rindle/optimistic's FoldInspect.
export interface FoldInspect {
foldKey: string;
debounceMs: number;
maxWaitMs?: number;
deferAcrossWrites: boolean;
flushed: boolean;
}getDevtoolsCore
FunctionDeclaration · Source: packages/devtools/src/global.ts:64 · Supporting declarations
The most recently attached core, or undefined — the common single-app convenience a panel uses.
export declare function getDevtoolsCore(): DevtoolsCore | undefined;getDevtoolsHub
FunctionDeclaration · Source: packages/devtools/src/global.ts:58 · Supporting declarations
Get (creating on first use) the global devtools hub. A panel calls this to discover attached
clients; it is safe to call before any attachDevtools.
export declare function getDevtoolsHub(): DevtoolsHub;MutationState
TypeAliasDeclaration · Source: packages/devtools/src/types.ts:89 · Supporting declarations
A mutation's place in the fork/rebase lifecycle (DEBUG-TOOLS-BROWSER-DESIGN §4.1).
export type MutationState = "pending" | "confirmed" | "dropped";NormalizedOp
TypeAliasDeclaration · Source: packages/client/src/types.ts:100 · Supporting declarations
A table-tagged, path-free row delta — the normalized wire payload (the path-free twin
of {@link FlatChange}; NORMALIZED-CHANGES-DESIGN.md §3). Rows are positional (bare cells; a
json column is its raw JSON string). op is the discriminant. Lives here (not in
@rindle/normalized) so both the protocol (@rindle/remote) and the sync layer share one type.
export type NormalizedOp = {
table: string;
op: "add";
row: WireValue[];
} | {
table: string;
op: "remove";
row: WireValue[];
} | {
table: string;
op: "edit";
old: WireValue[];
new: WireValue[];
};OptimisticInspect
InterfaceDeclaration · Source: packages/devtools/src/types.ts:54 · Supporting declarations
A snapshot of the optimistic loop — mirror of @rindle/optimistic's OptimisticInspect.
export interface OptimisticInspect {
pending: PendingInspect[];
confirmedLmid: number;
nextMid: number;
appliedCv: number;
bufferedFrames: number;
pendingTables: string[];
}PendingInspect
InterfaceDeclaration · Source: packages/devtools/src/types.ts:44 · Supporting declarations
One pending mutation — mirror of @rindle/optimistic's PendingInspect.
export interface PendingInspect {
key: string;
mid: number | null;
name: string;
args: unknown;
tables: string[];
fold?: FoldInspect;
}QueryEntry
InterfaceDeclaration · Source: packages/devtools/src/types.ts:120 · Supporting declarations
One materialized view in the queries inspector (DEBUG-TOOLS-BROWSER-DESIGN §4.2).
export interface QueryEntry {
qid: number;
ast: Ast;
/** The root table (`ast.table`). */
table: string;
/** A one-line human summary of the AST (table, filters, order, limit, relationships). */
summary: string;
/** Every base table the query reads (root + correlated subqueries). */
tables: string[];
resultType: ResultType;
rowCount: number;
sample: readonly unknown[];
/** Does any pending mutation touch this query's tables? (the §7.2 pending axis). */
pending: boolean;
}QueryId
TypeAliasDeclaration · Source: packages/client/src/types.ts:128 · Supporting declarations
export type QueryId = number;ResultType
TypeAliasDeclaration · Source: packages/client/src/types.ts:155 · Supporting declarations
A query's SERVER-CHANNEL state, surfaced on its {@link ArrayView } (FOLDED-MUTATIONS-DESIGN §7 — formerly conflated with pending-ness, OPTIMISTIC-WRITES-DESIGN.md §6):
unknown— not hydrated: the server has not produced a first result for this query yet;complete— the server has answered. STAYScompletewhile a local mutation is pending (the prediction is the client's best current answer); reversion on rejection is an event (onRejected), not a downgrade of completeness;error— RESERVED for a future server-side, query-level error signal (seedesigns/101-QUERY-ERRORS-DESIGN.md); no longer produced by a pending mutation. "Is a prediction pending here?" is now a separate reactive axis (the backend'spending(qid)/onPending), not folded into this type. A backend with no server lifecycle (the in-process engine) leaves every viewcomplete.
export type ResultType = "unknown" | "complete" | "error";StoreInspect
InterfaceDeclaration · Source: packages/client/src/store.ts:88 · Supporting declarations
A frozen snapshot of the Store's live query state for a devtools pane ({@link Store.__inspect}).
export interface StoreInspect {
queries: QueryInspect[];
}summarizeAst
FunctionDeclaration · Source: packages/devtools/src/ast.ts:28 · Supporting declarations
A compact, one-line description of a query AST for the inspector header — best-effort and truncation-friendly (the full AST is available for a collapsible pretty-print alongside).
export declare function summarizeAst(ast: Ast): string;TimelineEntry
InterfaceDeclaration · Source: packages/devtools/src/types.ts:92 · Supporting declarations
One row of the mutation timeline — the optimistic loop made visible.
export interface TimelineEntry {
/** Stable identity across snapshots: the pending key (`m:<mid>` once a mid is dealt, else
* `f:<foldKey>` while a fold debounces). Retained after the entry settles. */
id: string;
/** The wire mutation id, or `null` for a still-folding entry. */
mid: number | null;
name: string;
args: unknown;
/** Tables the mutator touched (its pending-axis footprint). */
tables: string[];
state: MutationState;
/** True for a debounced/folded write; `fold` carries its window while it is live. */
folded: boolean;
fold?: FoldInspect;
/** Devtools-clock ms at first observation (invoke). */
invokedAt: number;
/** Devtools-clock ms when it left the pending stack (confirmed or dropped). */
settledAt?: number;
/** Heuristic (§4.1): view churn coincided with this mutation's confirmation — a POSSIBLE
* snap-back (the optimistic prediction diverged from the authoritative server result). Labeled
* "possible" because unrelated server data released in the same coherent batch also shows churn;
* a precise signal needs a reconcile-boundary event (a future engine seam). */
reconciledWithChurn: boolean;
/** qids of live queries whose tables this mutation touches (computed against the current views). */
affectedQueries: number[];
}