API index and search · Build metadata
@rindle/room
0.0.0 · Public export map; development manifest version (0.0.0).
AuthorityApplyResult
TypeAliasDeclaration · Source: packages/room/src/authority.ts:27 · Supporting declarations
The apply outcome. Anything else — network failure, a 5xx — is a throw: plain
errors retry (same body, same id; dedup absorbs a landed retry), errors marked
fatal: true (an identity mismatch — same id, different body — or a malformed
refusal) kill the incarnation loudly instead.
export type AuthorityApplyResult = {
kind: "ok";
applied: boolean;
cv?: number;
} | {
kind: "conflict";
conflicts: AuthorityConflict[];
} | {
kind: "fenced";
currentEpoch?: number;
};AuthorityConflict
InterfaceDeclaration · Source: packages/room/src/authority.ts:17 · Supporting declarations
One CAS rejection in the authority's 409 conflict body: the row's authoritative
current image (null = absent), keyed by table + pk cells.
export interface AuthorityConflict {
table: string;
pk: unknown[];
current: unknown[] | null;
}createRoomShell
FunctionDeclaration · Source: packages/room/src/shell.ts:1810 · Supporting declarations
Boot a room shell: init the wasm, mint the upstream lease, connect the upstream leg,
and serve the downstream ws (+ the private control plane, if configured). Returns
once the ports are bound and the upstream connection is underway — await
shell.awaitLive() for the seed.
export declare function createRoomShell(opts: RoomShellOptions): Promise<RoomShell>;DownstreamOptions
InterfaceDeclaration · Source: packages/room/src/shell.ts:96 · Supporting declarations
The downstream half: how clients are authorized and served (§4/§10.1).
export interface DownstreamOptions {
/** This room's document id — lease tokens for any other doc are refused. */
docId: string;
/** Token key ring: `kid` → shared secret (the API server signs with the same ring). */
tokenKeys: Record<string, string>;
/** Idle grace before an unsubscribed query's pipeline is reclaimed (default 30s). */
idleTtlMs?: number;
/** Lease-expiry / idle-sweep cadence (default 1s). */
sweepIntervalMs?: number;
/** How long a revocation keeps refusing pre-revocation tokens (default 30min — set
* it ≥ the longest token TTL the API server mints). */
revocationWindowMs?: number;
/** The room's private control plane (`POST /revoke`, `GET /stats`). Omit to run
* without one (no revocation surface). */
control?: {
authToken: string;
port?: number;
};
/** The write plane (§5.1). Omit to run read-only (writes are refused). */
writes?: WritesOptions;
/** Per-socket downstream send budget in bytes (§9; default 4 MiB). A socket whose
* queued bytes would exceed it is terminated with a gap — re-subscribing is the
* repair. */
sendBudgetBytes?: number;
}fatalAuthorityError
FunctionDeclaration · Source: packages/room/src/authority.ts:51 · Supporting declarations
export declare function fatalAuthorityError(message: string): FatalAuthorityError;httpAuthority
FunctionDeclaration · Source: packages/room/src/authority.ts:73 · Supporting declarations
The HTTP [RoomAuthority]: the API-server host as the room's sole counterpart
(§5.3.1). Maps 409 {error:"fenced"} / 409 {error:"conflict"} to their results,
a 500 mentioning a batch-identity mismatch to a fatal error, and anything else
non-OK to a retryable throw.
export declare function httpAuthority(opts: HttpAuthorityOptions): RoomAuthority;HttpAuthorityOptions
InterfaceDeclaration · Source: packages/room/src/authority.ts:57 · Supporting declarations
export interface HttpAuthorityOptions {
/** The API server's apply endpoint (e.g. `http://…/api/rindle/apply-row-change-txn`). */
applyUrl: string;
/** The epoch-claim endpoint (e.g. `http://…/api/rindle/claim-room-epoch`). */
claimUrl: string;
/** The lmid-probe endpoint (e.g. `http://…/api/rindle/room-lmids`). */
lmidsUrl: string;
/** Extra headers on every call — the epoch-bound flush credential rides here. */
headers?: Record<string, string>;
fetch?: typeof fetch;
}initRoomWasm
FunctionDeclaration · Source: packages/room/src/wasm.ts:15 · Supporting declarations
Initialize the room wasm module (idempotent). Call once before WasmRoom.open.
Node: bytes are read from the package. Browser/bundler: the wasm is fetched. Pass
moduleOrPath to override (a WebAssembly.Module, URL, or bytes).
export declare function initRoomWasm(moduleOrPath?: unknown): Promise<void>;journalEntryOutcome
FunctionDeclaration · Source: packages/room/src/journal.ts:50 · Supporting declarations
The ONE reading rule for an entry's verdict across journal generations: outcome
when present, else the legacy rejected flag, else applied.
export declare function journalEntryOutcome(entry: RoomJournalEntry): "applied" | "rejected" | "deopt";KeyedRow
TypeAliasDeclaration · Source: packages/room/src/mutation-tx.ts:22 · Supporting declarations
A row keyed by column name.
export type KeyedRow = Record<string, WireValue>;memoryJournal
FunctionDeclaration · Source: packages/room/src/journal.ts:84 · Supporting declarations
An in-process journal: survives incarnations within one shell process (and, handed to a second shell, a simulated process crash — the T2 harness). Not durable beyond the process, by definition.
export declare function memoryJournal(): RoomJournal;mintRoomToken
FunctionDeclaration · Source: packages/room/src/token.ts:126 · Supporting declarations
Sign a room lease token. This runs on the API-server side (or a test playing it).
export declare function mintRoomToken(opts: MintRoomTokenOptions): Promise<string>;MintRoomTokenOptions
InterfaceDeclaration · Source: packages/room/src/token.ts:46 · Supporting declarations
export interface MintRoomTokenOptions {
doc: string;
ast: unknown;
/** The subject (user id) this token authorizes — the §4.1 revocation key. */
sub: string;
/** Key id + its secret (utf-8; give every room the same `keys` map). */
kid: string;
key: string;
/** Expiry, ms from `now`. Keep short (minutes) — the §4.1 TTL backstop. */
ttlMs: number;
/** Mint time; defaults to `Date.now()`. Injectable for tests. */
now?: number;
/** The room's {@link scopeSpecsHash} for the profile this lease serves — stamped so the
* shell can flag scope skew (see {@link RoomTokenPayload.scopesHash}). Omit to not stamp. */
scopesHash?: string;
}MutationTx
InterfaceDeclaration · Source: packages/room/src/mutation-tx.ts:28 · Supporting declarations
The §4.2 write handle. Keyed methods are schema-checked; positional methods are the
raw wire shape (cells in schema column order, pk cells in primaryKey order).
query is not available in room mutators yet (it throws) — typed so a client
registry that uses it still registers, and fails loudly at run time.
export interface MutationTx {
/** Read one row by primary key (e.g. `tx.row("issue", { id: 1 })`). */
row(table: string, pk: KeyedRow): KeyedRow | undefined;
/** Insert a FULL row (every column named; missing or unknown columns throw). */
insert(table: string, row: KeyedRow): void;
/** Update the row identified by the pk columns; only the named non-pk columns
* change. A missing row is a NO-OP (rebase-friendly). */
update(table: string, row: KeyedRow): void;
/** Insert, or fully replace when the pk already exists (a FULL row, like insert). */
upsert(table: string, row: KeyedRow): void;
/** Delete the row identified by the pk columns. A missing row is a NO-OP. */
delete(table: string, pk: KeyedRow): void;
/** NOT SUPPORTED in room mutators yet — throws. (Typed to accept the client
* builder's queries so shared registries typecheck.) */
query(query: {
ast(): unknown;
}): never;
get(table: string, pk: WireValue[]): WireValue[] | undefined;
add(table: string, row: WireValue[]): void;
remove(table: string, row: WireValue[]): void;
edit(table: string, oldRow: WireValue[], newRow: (WireValue | undefined)[]): void;
}RoomAuthority
InterfaceDeclaration · Source: packages/room/src/authority.ts:32 · Supporting declarations
export interface RoomAuthority {
/** Claim the placement epoch for `doc` (§2.5) — once per shell process, at boot,
* AFTER resubmitting the previous incarnation's unconfirmed batches (their recorded
* epochs must still be current to land). */
claimEpoch(doc: string): Promise<number>;
/** The authority's ledger lmid per client (0 = none) — the boot probe that lets
* journal replay absorb already-durable mutations as dedup. */
lmids(doc: string, clients: string[]): Promise<Record<string, number>>;
/** Apply one batch: `body` is the exact journaled `/apply-row-change-txn` body
* string, sent verbatim. */
applyRowChangeTxn(body: string): Promise<AuthorityApplyResult>;
}RoomFlushRecord
InterfaceDeclaration · Source: packages/room/src/journal.ts:57 · Supporting declarations
One journaled flush batch: the room's flush-stream position, the placement epoch it
was built under, and the EXACT /apply-row-change-txn body string — resubmitted
verbatim, never rebuilt (§5.3 step 4).
export interface RoomFlushRecord {
seq: number;
epoch: number;
body: string;
}RoomJournal
InterfaceDeclaration · Source: packages/room/src/journal.ts:63 · Supporting declarations
export interface RoomJournal {
/** Append `entries` durably, in order. Resolving is the ack gate (§8.1): the shell
* advances lmid rows only after this resolves. A rejection is fatal to the
* incarnation — an ack that might not survive must never be sent. */
append(entries: RoomJournalEntry[]): Promise<void>;
/** Every entry ever appended, in append order — the boot-time replay source. */
replay(): Promise<RoomJournalEntry[]>;
/** Journal one built flush batch, BEFORE its first send. Same durability contract
* as `append`: a rejection is fatal (an unjournaled batch must never reach the
* wire — a retry could otherwise rebuild different bytes under the same id). */
appendFlush(record: RoomFlushRecord): Promise<void>;
/** The authority settled flush `seq` (committed, deduped, or dead) — drop it. */
confirmFlush(seq: number): Promise<void>;
/** Unconfirmed flush records in seq order, plus the highest seq ever appended
* (0 = none) — the boot-time resubmission source and the seq seed. */
replayFlushes(): Promise<{
records: RoomFlushRecord[];
maxSeq: number;
}>;
}RoomJournalEntry
InterfaceDeclaration · Source: packages/room/src/journal.ts:22 · Supporting declarations
One journaled mutation: the wire envelope plus its recorded outcome. Replay applies
the OUTCOME — a non-applied (rejected/deopt) entry consumes its mid without
running the mutator.
export interface RoomJournalEntry {
clientID: string;
mid: number;
name: string;
args: unknown;
/** The connection's authenticated subject at push time (the lease token's `sub`,
* shell-stamped — managed-writes §3.3): recovery is re-invocation, so identity is
* an input that must survive the crash. `""` = an entry journaled before the
* identity plane existed (mutators see it as unauthenticated). */
sub: string;
/** The recorded verdict (H-iv-b): `"applied"` replays by RE-INVOKING the mutator
* (§3.3 — and against a moved base the re-invocation may legitimately reject or
* DEOPT; the journal record is never rewritten, the shell's recorded-outcome map
* reflects what the replaying incarnation produced); `"rejected"` (final — authz /
* validation / unknown mutator) and `"deopt"` (the §3.3 commit gate refused; the
* client was told to re-route the mutation) both replay as a consumed-mid-no-effect
* WITHOUT running anything — re-judging a deopt could invent effects the client
* already re-routed elsewhere. Absent = a legacy pre-H-iv-b entry: read through
* {@link journalEntryOutcome}. */
outcome?: "applied" | "rejected" | "deopt";
/** Legacy pre-H-iv-b flag, superseded by {@link outcome} but still WRITTEN (`true`)
* alongside BOTH non-applied outcomes: a legacy reader replays either kind as a
* consumed-mid-no-effect, which is exactly right. */
rejected?: boolean;
}RoomMutator
TypeAliasDeclaration · Source: packages/room/src/mutation-tx.ts:67 · Supporting declarations
A room mutator: deterministic, replayable — re-invoked on journal replay against
the freshly re-subscribed base (§3.3), so the same purity rules as a client
mutator apply (no clock, no randomness, a pure function of (base, args, ctx)).
An auth check against ctx re-runs on replay against the freshly rebuilt base —
a mutation that passed before a crash can replay as rejected if the permission
row changed in between; that is §3.3's intended rebase behavior.
export type RoomMutator = (tx: MutationTx, args: never, ctx: RoomMutatorCtx) => void;RoomMutatorCtx
InterfaceDeclaration · Source: packages/room/src/mutation-tx.ts:55 · Supporting declarations
The ambient authorization context a room mutator runs under (managed-writes design
§3.2). Shell-stamped from the connection's DO-verified lease token — NEVER
client-supplied — so per-row/per-field rules checked against it are trustworthy.
The shape deliberately matches the shared-generator drivers' ctx.user, so one
registry body runs identically in all three homes (client / api-server / room).
export interface RoomMutatorCtx {
/** The authenticated subject (the lease token's `sub`). `""` on replay of an entry
* journaled before the identity plane — treat as unauthenticated. */
user: string;
}RoomScopeSpec
InterfaceDeclaration · Source: packages/room/src/shell.ts:124 · Supporting declarations
One table's §3.3 scope spec (H-iv-b): an element of the api-server's
RoomBootResponse.scopes, passed VERBATIM to the wasm room's enableWritesV2.
Structurally identical to @rindle/api-server's RoomScopeSpec (declared locally —
the shell must not depend on the api-server package; TS structural typing keeps the
two in lockstep at the host's threading site).
export interface RoomScopeSpec {
table: string;
/** The footprint's row-local predicate for this table (a wire `Condition`) — drives
* the commit gate's absent-read proof. Absent ⇒ absent reads on this table always
* deopt (fail closed). */
footprintWhere?: unknown;
writable: {
kind: "none";
} | {
kind: "predicate";
where?: unknown;
joinKeyCols: string[];
};
}RoomShell
InterfaceDeclaration · Source: packages/room/src/shell.ts:182 · Supporting declarations
export interface RoomShell {
/** The bound downstream port. */
readonly port: number;
/** The bound control-plane port (0 when no control plane was configured). */
readonly controlPort: number;
/** Resolves when the CURRENT incarnation is live (seq-0 snapshot applied);
* immediately if it already is. */
awaitLive(): Promise<void>;
/** The last-applied upstream commit version, if live. */
cv(): number | undefined;
/** The upstream subscription epoch of the current incarnation, if any. */
upstreamEpoch(): number | undefined;
/** This incarnation's downstream bootId (rotates on every re-subscribe). */
bootId(): string;
/** Fire the write-behind flush now (instead of the debounce) and await its
* settlement — deterministic flushing for tests and drain-before-close. No-op
* without an authority. */
flushNow(): Promise<void>;
close(): Promise<void>;
}RoomShellOptions
InterfaceDeclaration · Source: packages/room/src/shell.ts:173 · Supporting declarations
export interface RoomShellOptions {
upstream: UpstreamOptions;
downstream: DownstreamOptions;
/** Downstream ws port (default 0 = ephemeral; bound on 127.0.0.1). */
port?: number;
/** Diagnostic sink (default: silent). */
log?: (line: string) => void;
}RoomTokenError
ClassDeclaration · Source: packages/room/src/token.ts:149 · Supporting declarations
Why a token was refused. The reason is terse and safe to echo in a queryError.
export declare class RoomTokenError extends Error {
readonly reason: string;
constructor(reason: string);
}RoomTokenPayload
InterfaceDeclaration · Source: packages/room/src/token.ts:29 · Supporting declarations
The room's self-authorizing signed lease token (RINDLE-REALTIME-DESIGN.md §10.1).
The API server is the authority (§4): it authenticates the user, resolves the named
query to an approved AST, and signs this token. The token then IS the lease — the
room materializes on first presentation, so no pre-placement /materialize control
call ever touches it (the §10.1 inversion; on the DO shell the Worker verifies the
same signature statelessly before get(id)).
Shape: rt1.<base64url(payload)>.<base64url(hmac-sha256(prefix.payload))> with
payload { v: 1, doc, ast, sub, iat, exp, kid }:
doc— the room/document id the token authorizes (a token for one room presented to another is refused);ast— the approved wireAst(the client never composes this; §4's "ASTs never cross the public wire" holds because the token is opaque TO THE CLIENT — it carries it, it cannot mint or alter it);sub— the subject (user id): the revocation key (§4.1);iat/exp— issued-at / expiry, ms epoch. Renewal is re-authorization: clients obtain a fresh token through the API server, never extend this one.iatis what lets a revocation refuse pre-revocation tokens while a genuine re-grant (a newer token) passes immediately;kid— which shared secret signed it (rotation).
HMAC via WebCrypto (crypto.subtle) so the exact same code verifies in Node (the
test shell) and in a Cloudflare Worker/DO (P4) — no node:crypto import.
export interface RoomTokenPayload {
v: 1;
doc: string;
ast: unknown;
sub: string;
iat: number;
exp: number;
kid: string;
/** A short fingerprint of the room's compiled scope specs at mint time
* ({@link scopeSpecsHash}). Advisory, not a credential: the room's §3.3 gate is the
* contract regardless. It lets the shell detect SCOPE SKEW — a room profile edited
* while a room is already live arms the gate with the OLD specs (a one-shot at boot)
* while fresh leases prove against the NEW ones, so every routed write silently
* deopt-loops. Optional so a pre-stamp api-server / older token still verifies. */
scopesHash?: string;
}scopeSpecsHash
FunctionDeclaration · Source: packages/room/src/token.ts:87 · Supporting declarations
A stable short fingerprint of the compiled scope specs — the scope-skew tripwire.
NOT security (the gate re-proves every write): FNV-1a-32 over the {@link canonicalJson}
form, 8 hex chars. Both the api-server (stamping the lease token) and the room shell
(hashing the boot-wire scopes it armed the gate with) run this over the SAME compiler
output, so an unchanged profile ⇒ equal hash and a profile edited under a live room ⇒
mismatch. A collision only costs a missed diagnostic, never correctness. Accepts either
wire's spec array (RoomScopeSpec[] / RoomTableSpec[] — structurally identical).
export declare function scopeSpecsHash(specs: readonly unknown[]): string;UpstreamOptions
InterfaceDeclaration · Source: packages/room/src/shell.ts:80 · Supporting declarations
The upstream half of the shell's config: where rindled lives and what to follow.
export interface UpstreamOptions {
/** rindled's public subscription plane, e.g. `ws://127.0.0.1:7601`. */
wsUrl: string;
/** rindled's private control plane, e.g. `http://127.0.0.1:7600` (lease minting). */
controlUrl: string;
/** Bearer token for the control plane (required unless rindled runs unauthenticated). */
authToken?: string;
/** The document footprint — a wire `Ast` (§3.1). What this room follows and serves from. */
footprintAst: unknown;
/** Lease TTL passed to `/materialize` (rindled's default when omitted). */
leaseTtlMs?: number;
/** The `init` clientID on the upstream socket (diagnostic identity). */
clientId?: string;
}verifyRoomToken
FunctionDeclaration · Source: packages/room/src/token.ts:172 · Supporting declarations
Verify a token's signature and claims; returns the payload (with the approved AST)
or throws {@link RoomTokenError}. Signature is checked FIRST — no claim is trusted
(not even kid's existence beyond the key lookup) before the MAC passes.
export declare function verifyRoomToken(token: string, opts: VerifyRoomTokenOptions): Promise<RoomTokenPayload>;VerifyRoomTokenOptions
InterfaceDeclaration · Source: packages/room/src/token.ts:158 · Supporting declarations
export interface VerifyRoomTokenOptions {
/** The room's own doc id — a token for any other doc is refused. */
doc: string;
/** kid → shared secret. Unknown kids are refused (never "try them all"). */
keys: Record<string, string>;
/** Verification time; defaults to `Date.now()`. Injectable for tests. */
now?: number;
}WasmRoom
ClassDeclaration · Source: packages/room/pkg/rindle_room.d.ts:11 · Supporting declarations
One room instance: the upstream-fed base store plus its downstream serving state
(materialized queries deduped by QueryKey, subscribers addressed by the host's
opaque keys) and — once enableWrites runs — the §5.1
write plane, with at most one mutation transaction open at a time (the room is
an actor; the host serializes mutations onto it).
/* tslint:disable */
/* eslint-disable */
/**
* One room instance: the upstream-fed base store plus its downstream serving state
* (materialized queries deduped by `QueryKey`, subscribers addressed by the host's
* opaque keys) and — once [`enableWrites`](Self::enable_writes) runs — the §5.1
* write plane, with **at most one mutation transaction open at a time** (the room is
* an actor; the host serializes mutations onto it).
*/
export class WasmRoom {
private constructor();
free(): void;
[Symbol.dispose](): void;
/**
* Advance the ledger rows for journal-committed mutations — **the ack** (§5.1
* step 4; call once the journal append resolves). `entries_json` is a JSON array
* of `{clientID, mid}`. Returns `{"headCv": n}` — drain
* [`commitAll`](Self::commit_all) to ship the lmid frames — or `{"headCv": null}`
* when everything was already covered.
*/
ack(entries_json: string): string;
/**
* The ledger row's current value for `clientID` (0 if none) — what the wire has
* confirmed.
*/
ackedLmid(client_id: string): number;
/**
* The highest mid applied to the head for `clientID` (0 if none) — the dedup
* watermark; diagnostics and the crash-replay tests read it.
*/
appliedMid(client_id: string): number;
/**
* Apply one upstream `nbatch` frame's `batch` object (JSON text). Returns a status
* object (JSON text): `{applied: "snapshot", rows}` | `{applied: "live", ops}` |
* `{applied: "duplicate"}` | `{applied: "staleEpoch", expected, got}`. Throws when
* the frame is garbage or the apply poisons the store — in both cases the host
* tears down and re-subscribes under a new epoch (§3.4); there is no repair call.
*
* After a `live`/`snapshot` apply, drain the fan-out with
* [`commitAll`](Self::commit_all).
*/
apply(batch_json: string): string;
/**
* Build the §5.3 net-effect batch from the dirty entries + ledger co-edits.
* Returns `undefined` when there is nothing to flush, else JSON text
* `{changes, batchHash, lmids}`: `changes` is the `/apply-row-change-txn`
* `changes[]` array (ingest shape, old-images = the CAS base chain), `batchHash`
* the batch identity computed over its canonical bytes, `lmids` the
* `{clientID, mid}` advances the batch covers. At most one flush may be in
* flight — resolve with [`flushOk`](Self::flush_ok) or
* [`flushConflict`](Self::flush_conflict); retries resubmit the SAME journaled
* body, never a rebuilt one.
*/
beginFlush(): string | undefined;
/**
* Open the transaction for `(clientID, mid)`. Returns `{"begin": "tx"}` (run the
* mutator, then [`commitMutation`](Self::commit_mutation) or
* [`rejectMutation`](Self::reject_mutation)) or `{"begin": "dedup"}` (an absorbed
* redelivery — send nothing). Throws on a mid gap with the exact
* `mutation gap for …` message the client's recovery keys on — forward it on an
* `error` frame. At most one transaction may be open.
*
* `name`/`args_json` (optional, Slice I-ii) are the wire envelope's mutator name
* and JSON-text args: should this mid end non-applied, its durable outcome row
* echoes them on a DEOPT (self-contained re-invoke, mirroring the H-iv-b frame).
* Omitting them degrades deopt rows to NULL name/args, nothing else.
*/
beginMutation(client_id: string, mid: number, name?: string | null, args_json?: string | null): string;
/**
* Drain every materialized query once and return the fan-out as JSON text:
* `[{sub, batch}, …]` — one entry per attached subscriber per query with a
* non-empty net delta, each `batch` stamped with that subscriber's own epoch/seq.
* Empty array when nothing changed (or the store is no longer live — a dead
* incarnation must not ship a torn frame).
*/
commitAll(): string;
/**
* Apply the open transaction to the head as one local commit: dirty entries are
* captured, the applied watermark advances, and every attached query sees the ops.
* Returns `{"headCv": n}` — drain [`commitAll`](Self::commit_all) and fan out NOW
* (§5.1 step 2: data fanout is never gated on durability), then journal the
* envelope and [`ack`](Self::ack) once the append commits. Throws only when the
* commit tears the head (store poisoned — tear down).
*
* Under [`enableWritesV2`](Self::enable_writes_v2) the §3.3 commit gate runs
* first; a containment violation returns `{"deopt": {reason, table, pk, …}}`
* instead (`reason`: `"writeOutOfScope"` (+ `op`) | `"joinKeyChanged"`
* (+ `column`) | `"absentReadUnproven"`): **nothing was applied**, the tx is
* consumed and the mid burnt exactly like [`rejectMutation`](Self::reject_mutation)
* (do NOT call it too) — journal the envelope as rejected and [`ack`](Self::ack)
* as usual, answering the client with a deopt. Under the v1 enable the return is
* always `{"headCv": n}`, byte-identical to before.
*/
commitMutation(): string;
/**
* The last-applied upstream commit version (`undefined` before the seq-0 snapshot).
* This is the journal cursor's `cv` half — persist `(epoch, cv)` to resume (§3.3).
*/
cv(): number | undefined;
/**
* How many `(table, pk)` keys are dirty (§5.3 — P3's flush input).
*/
dirtyLen(): number;
/**
* The highest mid the write authority has committed for `clientID` (0 if none) —
* §8.1's `durable` level.
*/
durableLmid(client_id: string): number;
/**
* Enable the §5.1 write plane over a live store: `owned_json` is a JSON array of
* the tables mutators may write (§3.2's owned set — must all be in the upstream
* hello). Registers the `_rindle_client_mutations` ledger; call once, right after
* the seq-0 snapshot applies and before serving. Idempotence is deliberate-ly NOT
* provided: enabling twice is a shell bug and throws.
*/
enableWrites(owned_json: string): void;
/**
* Enable the write plane with per-table **scope specs** — the §3.3 room commit
* gate (H-iv-a). `specs_json` is a JSON array of
* `{ table, footprintWhere?, writable }` where `writable` is the lease block's
* `RoomTableSpec.writable` shape (`{kind:"none"}` for a context table,
* `{kind:"predicate", where?, joinKeyCols}` for a writable one) and
* `footprintWhere` is the footprint's row-local wire `Condition` for the table
* (drives the absent-read proof). Predicates compile NOW, loudly — a malformed or
* non-row-local spec (e.g. a `correlatedSubquery`) throws and nothing is enabled.
* With the gate armed, [`commitMutation`](Self::commit_mutation) can return a
* `{"deopt": …}` verdict. Same once-only/live-store rules as
* [`enableWrites`](Self::enable_writes), which remains the legacy table-granular
* entry (its gate proves nothing and never deopts).
*/
enableWritesV2(specs_json: string): void;
/**
* The upstream subscription epoch this store was opened under.
*/
epoch(): number;
/**
* The authority rejected the in-flight batch on CAS (§5.4 `flush_conflict`):
* `conflicts_json` is its `409` body's `conflicts` array —
* `[{table, pk, current}]`, `current: null` for an absent row. Each conflicted
* key converges to the authority's image (the local net effect is dropped);
* everything else stays dirty and retries next flush. Returns
* `{"headCv": n | null}` — drain [`commitAll`](Self::commit_all) after a
* non-null cv to ship the corrective frames.
*/
flushConflict(conflicts_json: string): string;
/**
* Whether a §5.3 flush is in flight (diagnostics; the shell also guards).
*/
flushInFlight(): boolean;
/**
* The authority committed the in-flight batch (§5.3 step 6): consumed entries
* retire (or re-base onto the image just written, if re-dirtied mid-flight),
* durable watermarks advance, `pending ≤` them retires. Returns
* `{"retired": n, "dirty": n}` — a non-zero `dirty` means writes landed during
* the flight; schedule the next flush.
*/
flushOk(): string;
/**
* The room's own commit order — what downstream frames are stamped with and what
* the shell's `progress {cvMin}` release points are computed from. Advances on
* every upstream apply and every local commit (see `RoomStore::head_cv`).
*/
headCv(): number;
/**
* Baseline present and not poisoned — the store is serving.
*/
isLive(): boolean;
/**
* A failed apply left this incarnation dead (§3.4): tear down, re-subscribe.
*/
isPoisoned(): boolean;
/**
* Subscribe `sub` to the reserved per-client lmid query (`_rindle/clientLmid`):
* the room composes the `_rindle_client_mutations WHERE client_id = <clientID>`
* AST itself — identity comes from the connection's `init`, never client args.
* Same return shape and envelope rules as [`subscribe`](Self::subscribe).
*/
lmidSubscribe(sub: string, epoch: number, client_id: string, now_ms: number): string;
/**
* How many downstream queries are currently materialized (post-dedup).
*/
materializationCount(): number;
/**
* Open the room's base store from the upstream `nhello` frame's `hello` object
* (JSON text). `idle_ttl_ms` is the downstream retention grace: a materialized
* query with no subscribers for that long is reclaimed by [`sweep`](Self::sweep).
* Rejects malformed hellos loudly — nothing is constructed.
*/
static open(hello_json: string, idle_ttl_ms: number): WasmRoom;
/**
* Applied-but-not-retired mutations (everything, until P3's write-behind).
*/
pendingLen(): number;
/**
* Drop the open transaction's staged ops and consume its mid anyway — the reject
* path (unknown mutator, mutator threw, staging refusal, a replayed non-applied
* journal entry). The stream stays contiguous; the eventual [`ack`](Self::ack)
* advances the ledger with no effects, snapping the author's prediction back.
*
* `kind` (`"rejected"` (default) | `"deopt"`) + `reason` are the shell's H-iv-b
* classification of the verdict: the outcome is RECORDED (Slice I-ii) and its
* durable row rides the next flush beside the covering lmid co-edit — one
* authority transaction, so a post-downgrade client resolving through the daemon
* never reads a burnt non-applied mid as applied. A deopt row echoes the
* [`beginMutation`](Self::begin_mutation) envelope's `name`/`args`.
*/
rejectMutation(kind?: string | null, reason?: string | null): void;
/**
* Seed the durable watermarks from the write authority's ledger — the boot probe
* (§3.3; T2's committed-before-crash row). Call after [`enableWrites`] and
* **before** the journal replay: seeded mids replay as dedup instead of
* re-invoking. `seeds_json` is a JSON array of `{clientID, lmid}`. Returns
* `{"headCv": n | null}` — the ledger rows ride a local commit so the client's
* lmid slice confirms them.
*
* [`enableWrites`]: Self::enable_writes
*/
seedDurable(seeds_json: string): string;
/**
* Materialize-on-presentation (§10.1): attach subscriber `sub` (the host's opaque
* routing key) at `epoch` to the query for this approved wire `Ast` (JSON text) —
* reusing the pipeline on a `QueryKey` hit. Returns
* `{queryKey, reused, hello, snapshot}` (JSON text): send `nhello(hello)` then
* `nbatch(snapshot)` (its `seq` is 0), then this subscriber's entries from each
* [`commitAll`](Self::commit_all). Re-subscribing an existing `sub` replaces its
* envelope — pass the bumped `epoch`, exactly as before.
*/
subscribe(sub: string, epoch: number, ast_json: string, now_ms: number): string;
/**
* How many downstream subscribers are currently attached.
*/
subscriberCount(): number;
/**
* Reclaim downstream queries idle past their grace window, on the host's clock.
* Returns how many were destroyed.
*/
sweep(now_ms: number): number;
/**
* Stage an add of a full-width row (JSON-array text). Clean refusal (throw, head
* untouched) when the key exists, the table is not owned, or the width is wrong —
* catch it and reject the mutation.
*/
txAdd(table: string, row_json: string): void;
/**
* Stage an edit to a full-width row (JSON-array text) keyed by its pk cells. The
* shell resolves partial updates against [`txGet`](Self::tx_get) first — only
* concrete cells cross this boundary (JSON has no `undefined`). The pk must not
* change; the row must exist.
*/
txEdit(table: string, row_json: string): void;
/**
* The effective row for `pk_json` (a JSON array of pk cells, `primaryKey` order):
* the live head under this transaction's own staged writes — read-your-writes for
* read-dependent mutators, and what the keyed `update`/`delete`/`upsert` layers
* probe before composing their writes. Returns the row as JSON-array text, or
* `undefined` when absent.
*/
txGet(table: string, pk_json: string): string | undefined;
/**
* Stage a remove of the row keyed `pk_json` (JSON array of pk cells). The change
* is composed from the effective row; a missing key is a clean refusal (the keyed
* `delete` layer no-ops before calling this).
*/
txRemove(table: string, pk_json: string): void;
/**
* Detach subscriber `sub` (unsubscribe, socket close, lease expiry, revocation —
* the host decides why). Idempotent: returns whether it was attached. Its query
* stays materialized for `idle_ttl_ms` (a quick re-subscribe is cheap), then
* [`sweep`](Self::sweep) reclaims it.
*/
unsubscribe(sub: string, now_ms: number): boolean;
}WireValue
TypeAliasDeclaration · Source: packages/room/src/mutation-tx.ts:20 · Supporting declarations
A bare wire cell (the client stack's WireValue).
export type WireValue = number | string | boolean | null;WritesOptions
InterfaceDeclaration · Source: packages/room/src/shell.ts:136 · Supporting declarations
The §5.1 write plane: the room's own mutator registry over its owned tables.
export interface WritesOptions {
/** The server registry (§4.2): named mutators run against the shared head. PLAIN
* synchronous `(tx, args, ctx)` functions only — a `shared(...)` GENERATOR registry
* does NOT register verbatim (nothing drives it here; the shell rejects a mutator
* that returns a generator/promise — see `assertSyncMutatorReturn`). */
mutators: Record<string, RoomMutator>;
/** §3.2's owned set — the only tables mutators may write. Must all be in the
* upstream footprint; followed tables and the ledger are never writable. */
ownedTables: string[];
/** The §3.3 per-table scope specs from the boot wire (`RoomBootResponse.scopes` —
* H-iv-b). Present ⇒ the write plane enables in v2 GATED mode (`enableWritesV2`):
* staged writes validate against the writable predicates, join-key edits refuse,
* absent reads must prove against `footprintWhere`, context (`kind:"none"`) tables
* become txGet-READABLE, and a violating commit returns a structured DEOPT instead
* of applying. The scopes' writable tables must be a SUBSET of {@link ownedTables}
* (the host's own declaration) — a wider server scope throws at construction, so a
* self-hoster's owned set can never be extended from the wire. Absent ⇒ the v1
* table-granular write plane, byte-identical to before. */
scopes?: RoomScopeSpec[];
/** The durable sidecar an ack means (§8.1). Defaults to `memoryJournal()` — the
* "survives nothing beyond the process" class; hosts bring their own. */
journal?: RoomJournal;
/** The journal group-commit window (default 5ms): mutations arriving within it
* share one append, and their acks ride one ledger commit. */
groupCommitMs?: number;
/** The write authority (§5.3.1) — the API server's `/apply-row-change-txn` host
* (or the P3 gate's mock). Omit to run journal-only (P2 semantics: nothing is
* ever durable upstream). With an authority, the shell claims a placement epoch
* at boot, probes durable lmids before replay, and write-behinds on the flush
* cadence (§5.3). */
authority?: RoomAuthority;
/** The flush debounce (§5.3; default 250ms, within the design's ≤1s budget). */
flushDebounceMs?: number;
/** Flush immediately once this many keys are dirty (default 512). */
flushDirtyMax?: number;
}