API index and search · Build metadata
Supporting declarations
packages/optimistic/src/local-persist.ts. These declarations explain referenced types. Only package-page symbols are package exports.
PersistLocalOptions
export interface PersistLocalOptions {
/** The storage identity (§3.2): one IDB database per (origin, user). NOT the mutator principal —
* this is fixed for the client's lifetime; an anonymous mode passes its own sentinel (`"anon"`). */
user: string;
/** Call `navigator.storage.persist()` to resist eviction (§3.2). Default false — it can prompt. */
requestPersistentStorage?: boolean;
/** Reports storage-operation failures without rejecting local writes. Default `console.error`.
* Missing browser APIs can warn and disable a feature instead; this hook does not receive
* every channel failure. Persistence and cross-tab delivery remain best effort. */
onError?: (e: Error) => void;
/** Test seam: a fake IDB/locks/channel environment. Defaults to the browser globals. */
env?: PersistEnv;
}LocalPersistence
/** The attached layer's handle. `createRindleClient` awaits {@link ready} before returning (§5.2). */
export interface LocalPersistence {
/** Resolves after the initial restore attempt. It can resolve in a degraded mode when browser
* storage is unavailable or an operation fails; it is not a durability guarantee. */
readonly ready: Promise<void>;
/** Best-effort final persist/forward (the `pagehide` hook, §9): re-posts unacked ops (any
* role), then a leader drains its persist queue and retries P9-degraded batches. */
flush(): Promise<void>;
/** Release leadership (or abandon the queued lock request), close the channel + IDB (P10).
* A leader DRAINS its queued persist steps first (they carry already-committed writes) and
* releases the lock only after the last one lands; a follower re-posts its unacked ops. */
close(): void;
/** Introspection for tests/devtools: current role. */
role(): "leader" | "follower";
}deleteLocalPersistence
/** Delete a user's local-persistence database — the sanctioned LOGOUT hook (§3.2). Never called
* implicitly; the old database otherwise remains on disk (fast re-login, and a privacy decision
* the app owns). Close this tab's live client for `user` first; a SIBLING tab's connection is
* released automatically (it closes on `versionchange` and degrades to broadcast-only), so a
* multi-tab logout completes instead of parking behind the other tab forever. */
export declare function deleteLocalPersistence(user: string, env?: PersistEnv): Promise<void>;attachLocalPersistence
/** Attach the persistence layer to a backend (standalone form — `createRindleClient` calls this
* for you). The observer is wired SYNCHRONOUSLY, but the mirror starts empty: attach before the
* first `writeLocal` (§5.2, a v1 constraint `createRindleClient` satisfies by construction).
* Await `handle.ready` before first render if the app wants restored rows at first paint. */
export declare function attachLocalPersistence<S extends ColsMap>(backend: OptimisticBackend<S>, schema: Schema<S>, opts: PersistLocalOptions): LocalPersistence;PersistDb
/** The narrow storage surface the layer needs (§3.1): two fixed stores, `rows` + `meta`. Each
* method is one atomic IDB transaction; a resolved promise means the txn COMPLETED — the P1
* ("persist-then-broadcast") anchor. */
export interface PersistDb {
getMeta(): Promise<PersistMeta | undefined>;
putMeta(meta: PersistMeta): Promise<void>;
getAllRows(): Promise<StoredRow[]>;
/** Apply one commit batch atomically: `row === null` deletes, else puts. */
putBatch(batch: RowState[]): Promise<void>;
/** The P7 gate: clear `rows` and write `meta` in ONE transaction (never a half state). */
reset(meta: PersistMeta): Promise<void>;
/** Delete specific records (the leader's stale-table sweep, §3.1). */
deleteRows(keys: Array<{
table: string;
pkKey: string;
}>): Promise<void>;
close(): void;
}PersistChannel
export interface PersistChannel {
post(msg: unknown): void;
onMessage(handler: (msg: unknown) => void): void;
close(): void;
}PersistEnv
export interface PersistEnv {
/** Open (creating if needed) the database. Resolves `null` when storage is unavailable —
* the layer degrades to broadcast-only (§3.2). */
openDatabase(name: string): Promise<PersistDb | null>;
deleteDatabase(name: string): Promise<void>;
/** Open the tab-coherence channel; `null` when BroadcastChannel is unavailable (single-tab). */
createChannel(name: string): PersistChannel | null;
/** Queue for the exclusive leadership lock (§4.1): `onAcquired`'s promise HOLDS the lock until
* it resolves; `signal` aborts a still-queued request. When Web Locks are unavailable, grant
* immediately ONLY if the runtime is provably single-context (no channel) — see
* {@link leaderElectionUnavailable}. */
requestLock(name: string, signal: AbortSignal, onAcquired: () => Promise<void>): void;
/** The runtime has tabs (a channel) but NO exclusive lock (Firefox <96, Safari 15.1–15.3,
* Node): every context would self-promote into concurrent leaders over one database — P2
* violated, permanent divergence. When set, the layer runs INERT: local tables still work,
* session-scoped (the 201 baseline), with no persistence and no cross-tab replication. */
leaderElectionUnavailable?: boolean;
/** `navigator.storage.persist()` (§3.2), best-effort. */
requestPersistentStorage?(): void;
}PersistMeta
export interface PersistMeta {
schemaHash: string;
epoch: number;
}StoredRow
export interface StoredRow {
table: string;
pkKey: string;
row: WireValue[];
}RowState
/** The replication unit everywhere (§4.3): idempotent full-row state. `row: null` = tombstone
* (live protocol only — removes DELETE the IDB record; there are no persisted tombstones). */
export interface RowState {
table: string;
pkKey: string;
row: WireValue[] | null;
}defaultEnv
/** The real-browser environment: IndexedDB + BroadcastChannel + `navigator.locks`, each reached
* structurally and each degrading gracefully when absent (§3.2). */
export declare function defaultEnv(): PersistEnv;