Rindle

API index and search · Build metadata

@rindle/devtools

0.0.0 · Public export map; development manifest version (0.0.0).

Source revision 05d0bf2c2e56 · build details
Source revision: 05d0bf2c2e56.
TypeScript input SHA-256: aabe6cfcc4172b870d5e272142958e9ea8d8784c2aa23133156e5a7ee633318e
Generated 2026-09-04T23:58:25.590Z with TypeScript 6.0.3. Public TypeScript checks and declaration emit passed. Package runtime tests are separate.

Entry point source

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. STAYS complete while 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 (see designs/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's pending(qid) / onPending), not folded into this type. A backend with no server lifecycle (the in-process engine) leaves every view complete.
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[];
}