@repo/datadata/persistence-idb
The IndexedDB persistence adapter, for offline support in the browser.
Functions
createIndexedDbPersistence
export declare function createIndexedDbPersistence(options: IndexedDbPersistenceOptions): IndexedDbPersistence;The browser ClientPersistenceAdapter: one IndexedDB database per (folder, principal) namespace, shared by every tab on that namespace. Each session journals under its own random owner id and holds a Web Lock named for it; records whose owner lock is FREE belong to a dead session and are adopted — re-stamped to the adopter in the store, then merged and replayed by the client (IndexedDbPersistence.load adopts on boot, IndexedDbPersistence.adoptOrphanedWrites in the client's periodic sweep). Double-adoption is excluded by holding the dead owner's lock across the re-stamp transaction; double-REPLAY (a "dead" session that was merely frozen coming back) is safe by construction — event-id dedup, CRDT idempotency, create reconciliation, guard misses.
Construction is synchronous and environment-safe: everything IndexedDB/lock related happens lazily on first use, and an environment without IndexedDB (SSR, workers without storage) yields an inert adapter — the client then behaves exactly as if no persistence was configured. Where the Web Locks API is missing but IndexedDB exists (older engines, some test environments), liveness is unknowable and every sibling is presumed dead: mutual adoption between two live tabs is then possible, and tolerable, because replay is idempotent.
Interfaces
IndexedDbPersistence
export interface IndexedDbPersistence extends ClientPersistenceAdapterThe IndexedDB-backed ClientPersistenceAdapter createIndexedDbPersistence returns, with the document cache, sibling notifications and orphan adoption all implemented.
adoptOrphanedWrites(): Promise<PersistedClientState | null>;Adoption of dead siblings' queues — see ClientPersistenceAdapter.
deleteCachedDocument(docId: string): Promise<void>;Drop one document's cached record; see ClientPersistenceAdapter.deleteCachedDocument.
getCachedDocument(docId: string): Promise<PersistedCachedDocument | undefined>;One document's cached record, or undefined; see ClientPersistenceAdapter.getCachedDocument.
loadCachedDocuments(): Promise<PersistedCachedDocument[]>;Every cached record of the current format version; see ClientPersistenceAdapter.loadCachedDocuments.
readonly ownerId: string;This session's owner id: every journaled record is stamped with it, and a Web Lock named for it is held until IndexedDbPersistence.release — a sibling session that can ACQUIRE the lock knows this session is dead and adopts its records. Exposed for diagnostics/tests.
putCachedDocument(record: PersistedCachedDocument): Promise<void>;Upsert one cached record under the conflict policy; see ClientPersistenceAdapter.putCachedDocument.
release(): void;Release the owner lock and close the database, so a successor session (a sibling tab, or a replacement client in this tab across an SPA route change) can adopt this session's remaining records. Idempotent. An owning client (ClientConfig.ownsPersistence, the default) calls this from destroy(); only a host that opted out needs to call it itself.
watchCachedDocuments(listener: (record: PersistedCachedDocument) => void): () => void;Cache change-notify via BroadcastChannel — see ClientPersistenceAdapter.
watchDocumentWrites(listener: (change: SiblingWritesChange) => void): () => void;Write-queue change-notify via BroadcastChannel — see ClientPersistenceAdapter.
IndexedDbPersistenceOptions
export interface IndexedDbPersistenceOptionsOptions for createIndexedDbPersistence.
lockTimeoutMs?: number;How long to wait for a SIBLING session's owner lock before concluding that session is alive (and leaving its records alone). A short wait — rather than a bare try-lock — lets adoption win the handover race against a predecessor's just-released lock (an SPA route change re-creating the client): the release is processed asynchronously, so an immediate try-lock could lose spuriously and strand the orphaned queue until the next sweep. Default: 1000.
logger?: Logger;Where the adapter's diagnostics go (currently only the write-after-release warning). Default: a browser console logger.
namespace: string;The storage namespace this queue belongs to. MUST be scoped to (folder, principal) — e.g. ${folderId}:${subject} — never folder-only: the queue carries document data, so a folder-only key would leak documents across principals sharing one origin. Wipe (clear()) on principal change.