Rindle

API index and search · Build metadata

Supporting declarations

packages/daemon-client/src/index.ts. These declarations explain referenced types. Only package-page symbols are package exports.

Exact source

WireValue

export type WireValue = number | string | boolean | null;

StreamMode

export type StreamMode = "flat" | "normalized";

MaterializationPolicy

export type MaterializationPolicy = {
    kind: "pinned";
    name?: string;
} | {
    kind: "whileSubscribed";
    idleTtlMs?: number;
};

MaterializeInput

export interface MaterializeInput {
    ast: unknown;
    mode?: StreamMode;
    policy?: MaterializationPolicy;
    subject?: string;
    leaseTtlMs?: number;
    maxSubscribers?: number;
    metadata?: Record<string, unknown>;
    /** The browser's opaque follower-affinity ticket (FOLLOWER-AFFINITY-DESIGN.md §3), forwarded by
     *  the api-server. {@link HttpRindleDaemonClient} lifts it into the `Rindle-Affinity` request
     *  HEADER (never the body) so the fleet edge routes this `/materialize` to the follower the
     *  browser's ws is pinned to (§4) — both legs co-locate on one follower. Placement, not
     *  authorization: the follower verifies it, the api-server forwards it opaquely. Absent ⇒ no
     *  affinity (single daemon / affinity off), byte-identical to today. */
    affinity?: string;
}

MaterializeOutput

export interface MaterializeOutput {
    materializationId: string;
    queryKey?: string;
    leaseToken: string;
    reused?: boolean;
    /** Fresh follower-placement ticket returned by an affinity-enabled daemon. A machine client
     *  opening a separate ws for this follower-local lease (for example a room upstream) offers it
     *  as a subprotocol so the fleet edge routes that socket back to the minting follower. */
    affinity?: string;
}

DematerializeInput

export interface DematerializeInput {
    materializationId?: string;
    queryKey?: string;
    name?: string;
}

DematerializeOutput

export interface DematerializeOutput {
    removed: boolean;
}

SqlStatement

export interface SqlStatement {
    sql: string;
    params?: WireValue[];
}

WriteProducer

/** A foreign (non-client) writer's durable identity: a stable, low-cardinality `id` plus that
 *  producer's own gapless sequence — the same `(producer, sequence)` model Kafka uses.
 *
 *  The daemon stores ONE row per producer and overwrites it in place, so the dedup state is
 *  bounded by how many producers you have, not by how much you write. The contract that buys:
 *  number a producer's writes `1, 2, 3, …` and send them **in order**. `seq <= last` is absorbed
 *  (`applied: false`, nothing runs), `seq == last + 1` applies, and anything beyond is a `409`
 *  gap. Express concurrency with several producer ids, not by fanning out under one — and keep
 *  ids stable and few, because a fresh id per process re-introduces unbounded growth. */
export interface WriteProducer {
    id: string;
    seq: number;
}

SqlTxn

export interface SqlTxn {
    /** A foreign write's dedup watermark. Mutually exclusive with `clientID`/`mid` — supplying
     *  both is a `400`, because a silent precedence rule is what let the two write authorities
     *  disagree about which receipt they stored. */
    producer?: WriteProducer;
    clientID?: string;
    mid?: number;
    statements: SqlStatement[];
}

SqlTxnOutput

export interface SqlTxnOutput {
    applied?: boolean;
    cv?: number;
    txId?: number | string;
    /** Replicator-master commit identity (the public mutation SQL facade calls this `cursor`). */
    cursor?: string;
    /** Flat convenience mirror of this client's entry in `lmidAdvances`. */
    lmid?: number;
    lmidAdvances?: Array<{
        clientID: string;
        lmid: number;
    }>;
}

SqlRead

/** A raw read: one `SELECT` and its positional parameters. The read counterpart to {@link SqlTxn}
 *  — no idempotency/mutation fields (reads are idempotent).
 *
 *  `consistency` / `routingKey` are **client-tier routing directives**: `consistency` is consumed
 *  by {@link SplitDaemonClient}, `routingKey` by the read router. They ride along the wire (the
 *  router is reached through an `HttpRindleDaemonClient`, so `routingKey` must) but the daemon
 *  itself ignores them and reads only `sql` + `params`. `consistency` defaults to `"eventual"`
 *  (route to a replica, scaling reads off the write-master); set `"strong"` to route to the master
 *  for read-your-writes after a write to the same data. `routingKey` co-locates a replica read on a
 *  chosen follower (e.g. by tenant); absent, the router places it by the SQL text. */
export interface SqlRead {
    sql: string;
    params?: WireValue[];
    consistency?: "eventual" | "strong";
    routingKey?: string;
}

SqlReadOutput

/** The `/execute-sql-read` response: result columns in order and each row as a bare cell array
 *  (NOT keyed objects — the daemon keeps per-cell object construction off the write-master; zip
 *  `cols` with each row client-side if you want objects). Lossless for duplicate/aliased columns. */
export interface SqlReadOutput {
    cols: string[];
    rows: WireValue[][];
}

RowChangeTxn

export type RowChangeTxn = {
    source: string;
    offset: string;
    changes: Array<{
        table: string;
        op: "add";
        row: WireValue[];
    } | {
        table: string;
        op: "remove";
        old: WireValue[];
    } | {
        table: string;
        op: "edit";
        old: WireValue[];
        row: WireValue[];
    }>;
    /** The room write-behind extensions (RINDLE-REALTIME §5.3, each opt-in by
     *  presence): the placement fence (`doc` + `epoch` — a stale epoch gets a
     *  `409 {error:"fenced"}`), the §8.3 batch identity (`batchHash` — same id +
     *  different body is a loud 500), and the CAS preconditions (`cas: true` — a miss
     *  gets a `409 {error:"conflict", conflicts}` with authoritative images, nothing
     *  applied). Plain change sources send none and are byte-compatible unchanged. */
    doc?: string;
    epoch?: number;
    batchHash?: string;
    cas?: boolean;
};

RowChangeTxnOutput

export interface RowChangeTxnOutput {
    /** `false` = the `(source, offset)` keyset absorbed a replay. */
    applied?: boolean;
    cv?: number;
}

ClaimRoomEpochInput

/** `/claim-room-epoch` (RINDLE-REALTIME §2.5): bump + return the placement epoch. */
export interface ClaimRoomEpochInput {
    doc: string;
}

ClaimRoomEpochOutput

export interface ClaimRoomEpochOutput {
    epoch: number;
}

RoomLmidsInput

/** `/room-lmids` (§3.3): the domain-scoped ledger's lmid per client under `doc` — the
 *  room's boot probe. `doc` scopes the read to `_rindle_room_client_mutations` (§7.1), so a
 *  client's room stream never aliases its slow-path daemon stream. */
export interface RoomLmidsInput {
    doc: string;
    clients: string[];
}

RoomLmidsOutput

export interface RoomLmidsOutput {
    lmids: Record<string, number>;
}

MutationSessionBegin

/** `/mutate-session/begin` (DAEMON-INTERACTIVE-TXN-DESIGN.md §4.1): open an interactive write
 *  transaction on the master, held open across round trips under the daemon-owned deadline.
 *  Client-attributed sessions (`clientID` + `mid`) get mid dedup/gap resolution UP FRONT — an
 *  absorbed replay opens no session and the caller must skip the mutator. `statements` is the
 *  accumulated write prefix to replay into the fresh transaction (sound: nothing before the
 *  first read observed DB state); `query` rides the begin so a one-read mutator pays exactly
 *  one extra round trip over the batch path. */
export interface MutationSessionBegin {
    clientID?: string;
    mid?: number;
    /** A foreign session's dedup watermark; see {@link WriteProducer}. Mutually exclusive with
     *  `clientID`/`mid`. */
    producer?: WriteProducer;
    statements?: SqlStatement[];
    query?: SqlStatement;
}

MutationSessionBeginOutput

export interface MutationSessionBeginOutput {
    /** Present iff a session opened. */
    sessionId?: string;
    /** The `query` read's result, when one rode the begin (`{cols, rows}`, bare cell arrays). */
    read?: SqlReadOutput;
    /** True when mid/producer-watermark dedup absorbed the envelope at begin: NO session opened, the
     *  mutator must not run — the remaining fields are the authoritative replay output, the same
     *  shape {@link RindleDaemonClient.executeSqlTxn} answers for a deduped mid. */
    absorbed?: boolean;
    applied?: boolean;
    lmid?: number;
    lmidAdvances?: Array<{
        clientID: string;
        lmid: number;
    }>;
}

MutationSessionExec

export interface MutationSessionExec {
    sessionId: string;
    statements: SqlStatement[];
}

MutationSessionQuery

/** A read through the open transaction (read-your-writes) — flat `{sessionId, sql, params}`,
 *  answered in the `/execute-sql-read` shape. */
export interface MutationSessionQuery {
    sessionId: string;
    sql: string;
    params?: WireValue[];
}

MutationSessionRef

export interface MutationSessionRef {
    sessionId: string;
}

MutationRejection

export interface MutationRejection {
    clientID: string;
    mid: number;
    reason?: string;
}

MutationRejectionOutput

export interface MutationRejectionOutput {
    cv?: number;
    lmid?: number;
}

MigrateInput

/** One pure migration file: either ordered DDL statements or ordered DML statements. The opaque
 *  `id` is the journaled idempotency key. `checksum` is the canonical SHA-256 content identity
 *  sent by current deploy tooling; it is required for data migrations and optional only for
 *  compatibility with legacy private DDL callers. `overrideHash` is an operator-reviewed checksum
 *  for the content this migration originally applied with. DDL may be additive or destructive,
 *  while `RENAME` and raw `blob` remain unsupported. */
export interface MigrateInput {
    id: string;
    checksum?: string;
    overrideHash?: string;
    statements: string[];
}

MigrateOutput

/** The `/migrate` response: `applied` is false when `id` was already journaled (an idempotent
 *  replay ran nothing); `hashOverridden` reports that the supplied override matched the journaled
 *  content identity; `schemaVersion` is the version the daemon will report AFTER it serves the new
 *  shape (the latest applied migration id, `DRIZZLE-MIGRATIONS-DESIGN.md` §5.3). `restarting` is
 *  true when the daemon is self-restarting to re-introspect (a supervised daemon with
 *  `RINDLE_RESTART_ON_MIGRATE`) — the caller should wait for it to come back before its next call. */
export interface MigrateOutput {
    applied: boolean;
    hashOverridden?: boolean;
    schemaVersion?: string;
    restarting?: boolean;
}

MigrateResultItem

/** One entry in a {@link MigrateBatchOutput}: the migration `id`, whether this apply ran it
 *  (false = the `id` was already journaled, an idempotent no-op), and whether its override matched
 *  the journaled content identity. */
export interface MigrateResultItem {
    id: string;
    applied: boolean;
    hashOverridden?: boolean;
    schemaVersion?: string;
}

MigrateBatchOutput

/** The response to the ARRAY form of `/migrate` ({@link HttpRindleDaemonClient.migrateBatch}): a
 *  whole ordered set applied in one round-trip so a supervised daemon self-restarts ONCE for the
 *  set instead of once per migration. `results` are the migrations processed before any failure, in
 *  order; `applied` counts the newly-applied ones; `schemaVersion` is the latest applied id;
 *  `restarting` is true when the daemon is self-restarting to re-introspect (wait for it before the
 *  next call). On a failed migration `error`/`failedId` name it — the batch stops there, earlier
 *  migrations are committed (forward-only), and the daemon does NOT restart: fix forward and
 *  re-send. Unlike the single-migration form, this always arrives as HTTP 200; inspect `error`. */
export interface MigrateBatchOutput {
    results: MigrateResultItem[];
    applied: number;
    schemaVersion?: string;
    restarting?: boolean;
    error?: string;
    errorCode?: string;
    failedId?: string;
}

QueryOnceInput

/** The SSR one-shot read (`SSR-DESIGN.md` §3). Materialize-or-reuse the query, read its current
 *  view ONCE, return it assembled; registers no subscriber. `visibilityKey`/`ttlMs` are optional. */
export interface QueryOnceInput {
    ast: unknown;
    visibilityKey?: string;
    ttlMs?: number;
    /** The browser's opaque follower-affinity ticket — see {@link MaterializeInput.affinity}. Lifted
     *  into the `Rindle-Affinity` header so an SSR / one-shot read co-locates on the same pinned
     *  follower the browser's ws is (or will be) on. */
    affinity?: string;
}

QueryOnceOutput

/** The `POST /query` response: assembled `rows` (nested by name, ready to hydrate without an
 *  engine), the `cvMin` baseline they reflect, the per-query `schema` hello, and `queryKey`. */
export interface QueryOnceOutput {
    queryKey?: string;
    cvMin?: number;
    bootId?: string;
    schema?: unknown;
    rows: Array<{
        cols: Record<string, WireValue>;
        [rel: string]: unknown;
    }>;
}

SchemaColumn

/** One column in a {@link SchemaTable}: its name and the daemon's `ColType` wire name. */
export interface SchemaColumn {
    name: string;
    /** The daemon's `ColType` wire name. Because the daemon only ever *introspects* the SQLite file
     *  and affinity is lossy, this is `"string"` or `"number"` in practice — a column's app-level
     *  `"boolean"`/`"json"` intent is not recoverable from the file (`DRIZZLE-MIGRATIONS-DESIGN.md`
     *  §6.2). The full vocabulary is `"string" | "number" | "boolean" | "json"`. */
    type: string;
    /** `true` when the column is nullable (introspected `pragma_table_info.notnull == 0`); the
     *  generated column becomes `.nullable()`, typing it `T | null`. PK columns are always `false`
     *  (row identity). Absent on older daemons ⇒ treat as non-nullable. See design 206. */
    nullable?: boolean;
}

SchemaTable

/** One base table in the daemon's introspected schema: its name, ordered columns, and PK columns. */
export interface SchemaTable {
    name: string;
    columns: SchemaColumn[];
    primaryKey: string[];
}

SchemaOutput

/** The daemon's introspected base-table schema (`GET /schema`) — the input to client-schema
 *  codegen (`DRIZZLE-MIGRATIONS-DESIGN.md` §6.2). Tables are sorted by name and the daemon's own
 *  bookkeeping tables (`_rindle_*`) are excluded. */
export interface SchemaOutput {
    tables: SchemaTable[];
}

RindleDaemonClient

export interface RindleDaemonClient {
    materialize(input: MaterializeInput): Promise<MaterializeOutput>;
    dematerialize(input: DematerializeInput): Promise<DematerializeOutput>;
    executeSqlTxn(input: SqlTxn): Promise<SqlTxnOutput>;
    /** Raw SQL read (a single `SELECT` + params) against the latest committed snapshot, returning
     *  `{ cols, rows }` (bare row arrays). The read counterpart to {@link executeSqlTxn}. Under
     *  {@link SplitDaemonClient} this defaults to a replica (`consistency:"eventual"`); pass
     *  `consistency:"strong"` for read-your-writes on the master. */
    executeSqlRead(input: SqlRead): Promise<SqlReadOutput>;
    applyRowChangeTxn(input: RowChangeTxn): Promise<RowChangeTxnOutput>;
    rejectMutation(input: MutationRejection): Promise<MutationRejectionOutput>;
    /** SSR one-shot read (`SSR-DESIGN.md` §3): the current assembled view, no subscription. */
    query(input: QueryOnceInput): Promise<QueryOnceOutput>;
    /** Apply one schema migration through the daemon's controlled DDL channel + journal it
     *  (`DRIZZLE-MIGRATIONS-DESIGN.md` §5.1). Idempotent by `id`. The new shape is served only after
     *  the daemon is bounced (migrate-at-bounce, §5.2) — this applies DDL to the file but does not
     *  re-introspect the running engine. Drive it from a migration runner, not the request path. */
    migrate(input: MigrateInput): Promise<MigrateOutput>;
    /** Open an interactive mutation session (DAEMON-INTERACTIVE-TXN-DESIGN.md §4) — the write
     *  transaction a read-bearing mutator lazily upgrades onto. Optional as a group with the four
     *  ops below: only the daemon's control plane serves sessions (master-only — a follower's
     *  write-fence rejects begin); a client that lacks them forces the legacy committed-state
     *  read path. Session ops against a finished/expired session reject with a
     *  {@link DaemonHttpError} whose `status` is 410 — treat it as infra (retry the envelope;
     *  begin-time dedup absorbs a committed outcome). */
    beginMutationSession?(input: MutationSessionBegin): Promise<MutationSessionBeginOutput>;
    execInMutationSession?(input: MutationSessionExec): Promise<unknown>;
    queryInMutationSession?(input: MutationSessionQuery): Promise<SqlReadOutput>;
    commitMutationSession?(input: MutationSessionRef): Promise<SqlTxnOutput>;
    rollbackMutationSession?(input: MutationSessionRef): Promise<unknown>;
    /** Claim the next placement epoch for a room's doc (RINDLE-REALTIME §2.5). Optional:
     *  only daemons serving as a room write authority implement it; the API server's
     *  room host refuses loudly when its configured client lacks it. */
    claimRoomEpoch?(input: ClaimRoomEpochInput): Promise<ClaimRoomEpochOutput>;
    /** The room's boot probe (§3.3). Optional, same contract as {@link claimRoomEpoch}. */
    roomLmids?(input: RoomLmidsInput): Promise<RoomLmidsOutput>;
}

HttpRindleDaemonClientPaths

export interface HttpRindleDaemonClientPaths {
    materialize: string;
    dematerialize: string;
    executeSqlTxn: string;
    executeSqlRead: string;
    applyRowChangeTxn: string;
    rejectMutation: string;
    query: string;
    migrate: string;
    schema: string;
    claimRoomEpoch: string;
    roomLmids: string;
    mutateSessionBegin: string;
    mutateSessionExec: string;
    mutateSessionQuery: string;
    mutateSessionCommit: string;
    mutateSessionRollback: string;
}

HeaderValue

export type HeaderValue = string | undefined;

HeadersInput

export type HeadersInput = Record<string, HeaderValue>;

HeadersFactory

export type HeadersFactory = () => HeadersInput | PromiseLike<HeadersInput>;

FetchResponseLike

export interface FetchResponseLike {
    ok: boolean;
    status: number;
    statusText: string;
    text(): Promise<string>;
    /** Optional response headers — the real `fetch` Response exposes these. Used to read the
     *  daemon's `Rindle-Boot-Id` (see {@link HttpRindleDaemonClientOptions.onBootId}). */
    headers?: {
        get(name: string): string | null;
    };
}

FetchLike

export type FetchLike = (input: string, init: {
    method: string;
    headers: Record<string, string>;
    body: string;
}) => Promise<FetchResponseLike>;

HttpRindleDaemonClientOptions

export interface HttpRindleDaemonClientOptions {
    baseUrl: string;
    fetch?: FetchLike;
    headers?: HeadersInput | HeadersFactory;
    paths?: Partial<HttpRindleDaemonClientPaths>;
    /** Fired with the daemon's boot id on the first control-plane response and again whenever it
     *  CHANGES — i.e. the daemon restarted (it keeps no durable lease/materialization state). Use
     *  it to re-assert pins / re-materialize. Treat every call as "(re)assert now"; it rides
     *  responses you already make (e.g. an ingester's writes), so no polling is needed. Must not
     *  throw or reject — handle your own errors (e.g. `onBootId: () => server.assertPins().catch(log)`). */
    onBootId?: (bootId: string) => void;
}

DaemonHttpError

export declare class DaemonHttpError extends Error {
    readonly status: number;
    readonly statusText: string;
    readonly body: string;
    constructor(status: number, statusText: string, body: string);
}

HttpRindleDaemonClient

export declare class HttpRindleDaemonClient implements RindleDaemonClient {
    private readonly baseUrl;
    private readonly fetchImpl;
    private readonly headers?;
    private readonly paths;
    private readonly onBootId?;
    private lastBootId?;
    constructor(opts: HttpRindleDaemonClientOptions);
    materialize(input: MaterializeInput): Promise<MaterializeOutput>;
    dematerialize(input: DematerializeInput): Promise<DematerializeOutput>;
    executeSqlTxn(input: SqlTxn): Promise<SqlTxnOutput>;
    executeSqlRead(input: SqlRead): Promise<SqlReadOutput>;
    applyRowChangeTxn(input: RowChangeTxn): Promise<RowChangeTxnOutput>;
    claimRoomEpoch(input: ClaimRoomEpochInput): Promise<ClaimRoomEpochOutput>;
    roomLmids(input: RoomLmidsInput): Promise<RoomLmidsOutput>;
    rejectMutation(input: MutationRejection): Promise<MutationRejectionOutput>;
    beginMutationSession(input: MutationSessionBegin): Promise<MutationSessionBeginOutput>;
    execInMutationSession(input: MutationSessionExec): Promise<unknown>;
    queryInMutationSession(input: MutationSessionQuery): Promise<SqlReadOutput>;
    commitMutationSession(input: MutationSessionRef): Promise<SqlTxnOutput>;
    rollbackMutationSession(input: MutationSessionRef): Promise<unknown>;
    query(input: QueryOnceInput): Promise<QueryOnceOutput>;
    migrate(input: MigrateInput): Promise<MigrateOutput>;
    /** Apply an ordered SET of migrations in one round-trip — the array form of {@link migrate}. The
     *  daemon applies them in order (stopping at the first failure) and, when supervised, self-restarts
     *  ONCE for the whole set. Drive it from a migration runner (`rindle migrate apply` does). See
     *  {@link MigrateBatchOutput} — inspect its `error` field rather than relying on the HTTP status. */
    migrateBatch(inputs: MigrateInput[]): Promise<MigrateBatchOutput>;
    /** The daemon's introspected base-table schema for client-schema codegen
     *  (`DRIZZLE-MIGRATIONS-DESIGN.md` §6.2). Read-only `GET /schema`, bearer-auth'd when the daemon
     *  has a token configured. Deliberately NOT on {@link RindleDaemonClient}: schema codegen is a
     *  dev/CI concern, not part of the runtime control-plane contract a router/splitter satisfies. */
    schema(): Promise<SchemaOutput>;
    private post;
    /** A bearer-auth'd GET (the read-only `/schema`, `/version`-style routes). Carries no request
     *  body — `defaultFetch` strips the placeholder before the real fetch (WHATWG fetch rejects a
     *  GET body). */
    private get;
    /** Notice the daemon's boot id on any response; fire `onBootId` on the first one and on every
     *  change (a restart). Updates `lastBootId` BEFORE the hook so a re-assert it triggers (which
     *  POSTs again, same boot id) does not re-fire — no recursion. */
    private observeBootId;
    private resolveHeaders;
}