/** * Persistence backends for the SDK's offline support. * * The store is a dumb, namespaced key/value surface with two areas: a read * cache (normalized rows, query snapshots and sync bookkeeping) and a mutation * queue (local writes waiting to reach the server). All structure — per-user * prefixes, the `row|`/`q|`/`meta|` namespaces, mutation ordering — is owned by * the {@link OfflineManager}; the store only promises that a prefix listing * comes back in lexicographic key order, which is what makes the queue a FIFO. * * Two implementations ship with the SDK: * - {@link IndexedDBOfflineStore} — the browser default; survives reloads. * - {@link MemoryOfflineStore} — the fallback everywhere IndexedDB does not * exist (Node, React Native, tests); survives only the process. * * Environments with neither (React Native + AsyncStorage, Electron main, …) * implement this interface and pass it via `offline.store`. */ /** A cached value plus the moment it was written, for LRU eviction. */ export interface OfflineCacheEntry { value: unknown; cachedAt: number; } /** A cache entry with its key, as returned by prefix listings. */ export interface OfflineCacheRecord extends OfflineCacheEntry { key: string; } /** What a mutation has to put back if the server rejects it. */ export interface MutationRollback { /** * The rows as they were locally *before* this mutation was applied, keyed * by id. A `null` value means "the row did not exist" — restoring it is a * delete, not a write. */ rows: Record | null>; } /** * A local write waiting to be replayed against the server. * * `mutationId` orders the queue globally (not per collection): a create in one * collection may be the parent a later insert in another references, so replay * must preserve the order the app issued the writes in. It is lexicographically * time-ordered and carries a random suffix, so two browser tabs writing in the * same millisecond produce distinct, still-roughly-ordered ids instead of * silently overwriting each other's queue entry. */ export interface PendingMutation { /** Unique, lexicographically sortable identity — also the queue key suffix. */ mutationId: string; collection: string; type: "create" | "createMany" | "update" | "updateMany" | "delete" | "deleteMany"; /** Target row id for update/delete, and the (client-generated) id of an offline create. */ id?: string | number; /** Target row ids for `deleteMany`. */ ids?: (string | number)[]; /** `{ id, data }` entries for `updateMany`. */ updates?: { id: string | number; data: Record; }[]; /** * True when the SDK minted this create's id itself. Only such creates may * cancel out against a later offline delete: a freshly generated UUID * cannot name a row the server already has, while a caller-supplied id * can — and there the delete must still replay to remove the server row. */ generatedId?: boolean; /** The payload: a row for create/update, an array of rows for createMany. */ data?: Record | Record[]; upsert?: boolean; queuedAt: number; /** How many times replay has been attempted (diagnostics for a stuck queue). */ attempts?: number; /** The last replay failure's message, when there was one. */ lastError?: string; /** Local state to restore if the server rejects this mutation. */ rollback?: MutationRollback; } export interface OfflineStore { getCache(key: string): Promise; setCache(key: string, entry: OfflineCacheEntry): Promise; /** Write many entries at once — one transaction where the backend has them. */ setCacheMany(entries: { key: string; entry: OfflineCacheEntry; }[]): Promise; deleteCache(keys: string[]): Promise; /** Every cache key starting with `prefix`, with its write time (for eviction). */ listCache(prefix: string): Promise<{ key: string; cachedAt: number; }[]>; /** As {@link listCache}, but with the values — the local query engine's input. */ listCacheEntries(prefix: string): Promise; enqueue(key: string, mutation: PendingMutation): Promise; dequeue(key: string): Promise; /** Queued mutations whose key starts with `prefix`, in lexicographic key order. */ listQueue(prefix: string): Promise; /** Remove every cache entry and queued mutation whose key starts with `prefix`. */ clear(prefix: string): Promise; } export declare function createMutationId(now?: number): string; /** * In-memory store: the default outside the browser and the workhorse of the * test suite. Values are deep-copied on the way in and out so a caller * mutating a returned row cannot silently edit the "persisted" copy — the * IndexedDB implementation gets the same guarantee for free from structured * cloning, and the two must not differ in aliasing behaviour. */ export declare class MemoryOfflineStore implements OfflineStore { private cache; private queue; getCache(key: string): Promise; setCache(key: string, entry: OfflineCacheEntry): Promise; setCacheMany(entries: { key: string; entry: OfflineCacheEntry; }[]): Promise; deleteCache(keys: string[]): Promise; listCache(prefix: string): Promise<{ key: string; cachedAt: number; }[]>; listCacheEntries(prefix: string): Promise; enqueue(key: string, mutation: PendingMutation): Promise; dequeue(key: string): Promise; listQueue(prefix: string): Promise; clear(prefix: string): Promise; } /** * IndexedDB-backed store — the browser default, so cached rows and queued * writes survive a reload or a browser restart. Everything lives in one * database with two object stores; keys are the manager's full prefixed * strings, so multiple users (scopes) share the database without ever * sharing entries. */ export declare class IndexedDBOfflineStore implements OfflineStore { private dbPromise?; private open; private store; getCache(key: string): Promise; setCache(key: string, entry: OfflineCacheEntry): Promise; setCacheMany(entries: { key: string; entry: OfflineCacheEntry; }[]): Promise; deleteCache(keys: string[]): Promise; listCache(prefix: string): Promise<{ key: string; cachedAt: number; }[]>; listCacheEntries(prefix: string): Promise; enqueue(key: string, mutation: PendingMutation): Promise; dequeue(key: string): Promise; listQueue(prefix: string): Promise; clear(prefix: string): Promise; }