@happyvertical/smrt-web 0.40.43 → 0.40.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["released","release","reason"],"sources":["../src/capability.ts","../src/durable-store.ts","../src/offline/durable-queue.ts","../src/offline/leader.ts","../src/offline/types.ts","../src/offline/engine.ts","../src/offline.ts","../src/persistence/snapshot-store.ts","../src/persistence.ts","../src/sse-client.ts","../src/update-state/meta-store.ts","../src/update-state.ts","../src/webmcp.ts","../src/index.ts"],"sourcesContent":["/**\n * @happyvertical/smrt-web — capability extension seam (#1755).\n *\n * A capability is a small plug-in that hooks the {@link createSmrtCollection}\n * lifecycle so a follow-on client slice — the offline outbox (#1762),\n * persistence (#1764), or live SSE invalidation (#1763-client) — can live in\n * its OWN module instead of contending on `index.ts`. The factory runs the six\n * hooks below at fixed points; a collection with no capabilities is\n * byte-for-byte the collection of today (the no-op guarantee).\n *\n * ## The engine boundary is sacred\n *\n * Every type here is expressed ENTIRELY in existing SMRT-owned terms\n * (`SmrtWebCollectionDefinition`, `SmrtCrudFetchers`, `SmrtWebRow`, plain TS).\n * No `@tanstack/*` type — no `QueryClient`, no engine `Collection` — appears on\n * the capability surface, so the build's `.d.ts` boundary check stays green and\n * the engine stays swappable. A capability that genuinely needs deeper engine\n * access reaches it through the existing `getEngineCollection()` unknown-bridge\n * from inside its own module — NEVER by widening these types.\n *\n * These types intentionally import nothing from `./index.ts` beyond the\n * SMRT-owned contracts they name, so the seam is reviewable and unit-testable on\n * its own (see {@link runWrapMutation}).\n */\n\nimport type {\n SmrtCrudFetchers,\n SmrtWebCollectionDefinition,\n SmrtWebRow,\n SmrtWebSubscription,\n} from './index.js';\n\n/**\n * The context every capability hook receives — a stable, engine-free view of\n * the collection being built. All fields are the exact values the factory uses\n * internally (the resolved cache key/id, the same fetchers), so a capability\n * keys its own storage or subscriptions off the identical discriminators.\n */\nexport interface SmrtWebCapabilityContext<TData extends object = object> {\n /** The generated definition this collection materializes. */\n readonly definition: SmrtWebCollectionDefinition<TData>;\n /** The CRUD fetchers the collection persists through. */\n readonly fetchers: SmrtCrudFetchers;\n /**\n * The `queryKey` segments the collection's reads use — a LIVE view: from\n * `onAttach`/`warmStart` onward it is the final key (including every\n * capability-contributed segment); read during `contributeCacheKey` it omits\n * that capability's own not-yet-applied segment. Treat as read-only.\n */\n readonly cacheKey: readonly string[];\n /** The engine-collection id string (mirrors {@link cacheKey}). */\n readonly cacheId: string;\n /**\n * Enter the SAME relationship-derived invalidation the factory runs after a\n * settled mutation. A capability calls this to refetch this collection and its\n * manifest-related collections in response to an EXTERNAL trigger (e.g. an SSE\n * message) without reaching into the engine.\n */\n invalidate(): void;\n /**\n * A snapshot of the collection's current rows as plain public DTOs — the same\n * projection as {@link SmrtWebCollection.toArray}, so the engine's virtual\n * props never cross the boundary. The persistence slice (#1764) reads this in\n * its write-back to serialize the current cache to disk. Populated by the real\n * factory; a hand-built test context may omit it, so callers guard for its\n * presence.\n *\n * SAFE ONLY FROM `onAttach` ONWARD. The factory's implementation closes over\n * the engine collection, which is constructed AFTER `contributeCacheKey` and\n * `warmStart` run — calling this from those earlier hooks would throw\n * (temporal-dead-zone). Read rows from `onAttach`, `onSettled`, or an external\n * trigger, never during construction.\n */\n snapshot?(): ReadonlyArray<SmrtWebRow<TData>>;\n /**\n * Subscribe to the collection's change notifications — the same stream as\n * {@link SmrtWebCollection.subscribeChanges} (plain-DTO payloads). The\n * persistence slice (#1764) wires its debounced write-back here in `onAttach`\n * and detaches in `teardown`. Engine-free by construction: the payload is\n * `unknown` (the capability only needs the change SIGNAL, then re-reads via\n * {@link snapshot}), so no engine type crosses the seam. Populated by the real\n * factory; a hand-built test context may omit it.\n *\n * SAFE ONLY FROM `onAttach` ONWARD (same reason as {@link snapshot}): it closes\n * over the engine collection built after the pre-construction hooks.\n */\n subscribe?(callback: (changes: unknown) => void): SmrtWebSubscription;\n}\n\n/**\n * A single mutation described in SMRT-owned terms — handed to\n * {@link SmrtWebCapability.wrapMutation} and {@link SmrtWebCapability.onSettled}\n * so a capability can inspect or intercept a write without touching the engine's\n * transaction type.\n */\nexport interface SmrtWebMutationEnvelope {\n /** Which mutation kind this describes. */\n readonly kind: 'insert' | 'update' | 'delete';\n /** The row key: the client-local id on insert, else the target row's id. */\n readonly key: string;\n /**\n * The write payload: the full row on insert, the changed fields on update,\n * and an empty object on delete (the key carries the target).\n */\n readonly data: Record<string, unknown>;\n /**\n * The server `updated_at` / `updatedAt` value the mutation is based on, when\n * known. Update/delete handlers capture this from the original row so offline\n * replay can preserve the sync/apply conflict guard even when the write\n * payload only carries changed fields (or no fields for delete).\n */\n readonly baseUpdatedAt?: string;\n}\n\n/** The outcome handed to {@link SmrtWebCapability.wrapMutation}. */\nexport type WrapMutationOutcome =\n | { handled: true; result: unknown }\n | { handled: false }\n | undefined;\n\n/** The settle outcome handed to {@link SmrtWebCapability.onSettled}. */\nexport type MutationSettleOutcome =\n | { ok: true; result: unknown }\n | { ok: false; error: unknown };\n\n/**\n * A capability plugged into {@link createSmrtCollection}. Every hook is\n * optional; a capability implements only the points its slice needs. Hooks fire\n * in these fixed places, and capabilities run in ARRAY ORDER:\n *\n * - `contributeCacheKey` — ONCE, before the engine collection is constructed.\n * - `warmStart` — ONCE, before the first read (seeds the cache).\n * - `wrapMutation` — per mutation, BEFORE the fetcher, with a chance to handle\n * the write itself (offline).\n * - `onSettled` — per mutation, AFTER it settles (success AND fetcher-throw).\n * - `onAttach` — ONCE, right after the engine collection is constructed; the\n * ONLY place a capability registers an external (non-mutation) trigger such as\n * an SSE subscription.\n * - `teardown` — in `cleanup()`, after the engine's own cleanup.\n */\nexport interface SmrtWebCapability<TData extends object = object> {\n /** Diagnostic name (e.g. `'offline-outbox'`). */\n readonly name: string;\n\n /**\n * Contribute extra cache-key segments so this capability can partition the\n * collection's cache (e.g. an outbox variant). Runs ONCE, before construction;\n * returned segments are appended to the collection's cache key/id. Return\n * `undefined` (or omit) to contribute nothing.\n */\n contributeCacheKey?(\n ctx: SmrtWebCapabilityContext<TData>,\n ): string[] | undefined;\n\n /**\n * Provide rows to seed the cache before the first read — the persistence\n * slice's rehydrate-from-disk path. Runs ONCE, before construction. NOTE:\n * caller-supplied `initialData` (fresher same-request SSR truth) WINS over any\n * capability `warmStart`. Return `undefined` to contribute no seed.\n */\n warmStart?(\n ctx: SmrtWebCapabilityContext<TData>,\n ): Promise<SmrtWebRow<TData>[] | undefined> | SmrtWebRow<TData>[] | undefined;\n\n /**\n * Intercept a mutation BEFORE its fetcher runs. Return `{ handled: true,\n * result }` to take over the write (the fetcher is skipped and `result`\n * reconciles the optimistic row — the offline path); return `{ handled: false\n * }` or `undefined` to fall through to the real fetcher. With multiple\n * capabilities the FIRST `{ handled: true }` wins and later capabilities'\n * `wrapMutation` are skipped.\n */\n wrapMutation?(\n envelope: SmrtWebMutationEnvelope,\n ctx: SmrtWebCapabilityContext<TData>,\n ): Promise<WrapMutationOutcome> | WrapMutationOutcome;\n\n /**\n * Observe a mutation after it settles — on BOTH a successful persist and a\n * fetcher throw (before the optimistic rollback propagates). Never swallows\n * the error: a thrown fetcher error still rolls the transaction back.\n */\n onSettled?(\n envelope: SmrtWebMutationEnvelope,\n outcome: MutationSettleOutcome,\n ctx: SmrtWebCapabilityContext<TData>,\n ): void;\n\n /**\n * Run ONCE, right after the engine collection is constructed. The ONLY hook\n * where a capability wires an external trigger (SSE subscription, focus\n * listener) — `ctx.invalidate()` is callable here.\n */\n onAttach?(ctx: SmrtWebCapabilityContext<TData>): void;\n\n /** Run inside `cleanup()`, AFTER the engine's own cleanup. Awaited if async. */\n teardown?(ctx: SmrtWebCapabilityContext<TData>): void | Promise<void>;\n}\n\n/**\n * Run the `wrapMutation` hook across `capabilities` in array order for one\n * mutation, short-circuiting on the FIRST capability that returns `{ handled:\n * true }` (later capabilities' `wrapMutation` are then skipped). A capability\n * that returns `{ handled: false }`, `undefined`, or omits the hook declines,\n * and the next is tried. When every capability declines, resolves `{ handled:\n * false }` so the factory falls through to the real fetcher.\n */\nexport async function runWrapMutation<TData extends object>(\n capabilities: readonly SmrtWebCapability<TData>[],\n envelope: SmrtWebMutationEnvelope,\n ctx: SmrtWebCapabilityContext<TData>,\n): Promise<{ handled: true; result: unknown } | { handled: false }> {\n for (const capability of capabilities) {\n if (!capability.wrapMutation) continue;\n const outcome = await capability.wrapMutation(envelope, ctx);\n if (outcome?.handled) {\n return { handled: true, result: outcome.result };\n }\n }\n return { handled: false };\n}\n","/**\n * @happyvertical/smrt-web — shared durable-store foundation (#1755).\n *\n * The ONE SMRT-layer namespacing + wipe registry that the future offline\n * outbox (#1762) and persistence (#1764) slices both build on. Pure bookkeeping\n * — ZERO client-data-engine (`@tanstack/*`) imports — so it stays inside the\n * engine-absorption boundary and ships now, before either consumer exists, so\n * the two slices agree on it from day one.\n *\n * Why a SMRT-layer registry rather than one storage engine: TanStack DB\n * persistence (SQLite-WASM / OPFS) and `@tanstack/offline-transactions`\n * (IndexedDB) are SEPARATE storage engines. There is no single primitive that\n * spans both, so the shared foundation lives one level up — a deterministic\n * namespace both slices key their own storage under, plus a registry so\n * {@link wipeDurableStore} can clear BOTH through one call without the outbox\n * and persistence modules importing each other. A logout / tenant-switch wipes\n * every durable artifact for a namespace in one place.\n *\n * Nothing in this package calls these yet — the seam ships ahead of its\n * consumers by design (see PRD #1755).\n */\n\n/**\n * The identity a durable namespace is derived from. Combining the API base with\n * the tenant + identity + manifest hash means a logout, a tenant switch, or a\n * schema change each land on a DIFFERENT namespace, so durable artifacts are\n * never reused across those boundaries.\n *\n * `manifestHash` is supplied by the caller (its source is #1764's call — a\n * schema-shape digest); this module is source-agnostic and treats it as an\n * opaque discriminator.\n */\nexport interface DurableStoreKey {\n /** API base path the durable data was fetched against (e.g. `/api/v1`). */\n apiBase: string;\n /** Active tenant id, if any — absent means the single-tenant / global scope. */\n tenantId?: string;\n /** Authenticated identity id, if any — absent means anonymous. */\n identityId?: string;\n /** Opaque schema-shape digest; a change forces a fresh namespace. */\n manifestHash: string;\n}\n\n/**\n * Compute the deterministic storage namespace for a {@link DurableStoreKey}.\n * The same key always yields the same string; **any differing segment yields a\n * different one** (injective) — this is critical because the namespace IS the\n * tenant/identity/api isolation + wipe boundary, so a collision would reuse or\n * wipe durable data ACROSS those boundaries.\n *\n * Injectivity is guaranteed three ways: every segment is `encodeURIComponent`-\n * encoded, so a raw `:` in a value becomes `%3A` and can never be mistaken for a\n * separator; an ABSENT `tenantId` / `identityId` maps to the empty string while\n * a PRESENT value is prefixed with `_` (so undefined→``, ``→`_`, `x`→`_x`) — so\n * an explicitly-EMPTY id no longer collides with \"no id\", nor does a real id of\n * `-`. Both future slices key their own storage primitive (IndexedDB store name,\n * OPFS path, …) under this string.\n */\nexport function durableStoreNamespace(key: DurableStoreKey): string {\n // Optional segments: encode PRESENCE distinctly. undefined → '' (absent);\n // any present value (including '') → `_<encoded>`, so '' → '_' can never equal\n // the absent '' .\n const optional = (value: string | undefined): string =>\n value === undefined ? '' : `_${encodeURIComponent(value)}`;\n return `smrt-web:${encodeURIComponent(key.apiBase)}:${optional(key.tenantId)}:${optional(key.identityId)}:${encodeURIComponent(key.manifestHash)}`;\n}\n\n/**\n * A durable artifact registered under a namespace — the outbox queue (#1762) or\n * a persisted collection store (#1764). Each owns its storage engine and\n * exposes only a `clear()` so {@link wipeDurableStore} can tear it down without\n * knowing which engine backs it.\n */\nexport interface DurableResource {\n /** Which slice owns this artifact — for diagnostics and selective sweeps. */\n readonly kind: 'outbox' | 'persisted-collection';\n /** Drop this artifact's durable storage. Best-effort; may reject. */\n clear(): Promise<void>;\n}\n\n/**\n * Registry of durable artifacts keyed by namespace. A `Set` per namespace so a\n * resource registers and unregisters without positional bookkeeping, and so two\n * slices (outbox + persistence) coexist under one namespace.\n */\nconst registry = new Map<string, Set<DurableResource>>();\n\n/**\n * Register a durable artifact under a namespace so {@link wipeDurableStore} can\n * later clear it. Returns an unregister function that removes just this\n * resource — call it when the artifact is disposed on its own (before any\n * namespace-wide wipe) so it is not cleared twice.\n */\nexport function registerDurableResource(\n namespace: string,\n resource: DurableResource,\n): () => void {\n let resources = registry.get(namespace);\n if (!resources) {\n resources = new Set<DurableResource>();\n registry.set(namespace, resources);\n }\n resources.add(resource);\n\n return () => {\n const current = registry.get(namespace);\n if (!current) return;\n current.delete(resource);\n if (current.size === 0) registry.delete(namespace);\n };\n}\n\n/**\n * Clear every durable artifact registered under `namespace`, then drop the\n * namespace. This is the single teardown point a logout / tenant-switch calls:\n * it fans out across BOTH the outbox and persistence slices via the registry,\n * so neither module needs to import the other.\n *\n * Best-effort: a resource whose `clear()` rejects does not abort the sweep —\n * every registered resource is still cleared (a wipe is a teardown, not a\n * transaction). A safe no-op on an unknown or empty namespace.\n */\nexport async function wipeDurableStore(namespace: string): Promise<void> {\n const resources = registry.get(namespace);\n if (!resources || resources.size === 0) {\n registry.delete(namespace);\n return;\n }\n // Snapshot so a resource that unregisters itself inside clear() cannot mutate\n // the set mid-iteration. allSettled keeps a rejected clear() from aborting the\n // others.\n const snapshot = [...resources];\n registry.delete(namespace);\n await Promise.allSettled(snapshot.map((resource) => resource.clear()));\n}\n","/**\n * @happyvertical/smrt-web — the durable IndexedDB outbox queue (#1762).\n *\n * A raw IndexedDB FIFO queue: the persistence half of the offline outbox. One\n * database per durable-store namespace ({@link durableStoreNamespace}), one\n * object store (`outbox`) keyed by an AUTO-INCREMENTING `seq` — so insertion\n * order IS replay order for free — plus a `state` index so the replay loop can\n * scan only rows that still need work.\n *\n * ## Why raw IndexedDB, not `@tanstack/offline-transactions`\n *\n * The decisive finding (#1762): the TanStack offline layer's public API is\n * engine-typed (`Collection<…>`, `mutationFns`), so importing it emits a\n * `@tanstack/` specifier into `dist/offline.d.ts`, which\n * `scripts/check-smrt-web-engine-boundary.mjs` scans for unconditionally — the\n * build would fail. It also pins `@tanstack/db` exactly and competes with\n * smrt-web's OWN mutation lifecycle. So the outbox is hand-rolled over browser\n * globals (`indexedDB`, `navigator.locks`) with ZERO new runtime dependency.\n *\n * This module is engine-free (no `@tanstack/*` import): it stays inside the\n * engine-absorption boundary (#1761) and speaks only the sync-apply contract's\n * op shape (`packages/core/src/sync/apply.ts`).\n *\n * Row payloads are stored as structured-clone-safe plain objects; IndexedDB\n * clones on write, so callers may keep mutating the source object after enqueue.\n */\n\nimport type { SyncApplyOp } from './types.js';\n\n/** IndexedDB object-store name inside every outbox database. */\nexport const OUTBOX_STORE = 'outbox';\n/** Index over {@link OutboxRow.state} — the replay scan reads it. */\nexport const OUTBOX_STATE_INDEX = 'state';\n/** IndexedDB schema version. Bump only on a store/index migration. */\nexport const OUTBOX_DB_VERSION = 1;\n\n/**\n * Durable lifecycle state of one queued mutation. Deliberately a SUPERSET of\n * the app-observable sync state: `pending` rows are due for (re)send, while\n * `synced`/`failed` are terminal tombstones removed after each drain. The\n * app-facing {@link OutboxSyncState} (`pending`/`uploading`/`synced`/`failed`)\n * is derived by the engine — `uploading` is a transient in-flight marker the\n * engine emits, never persisted; this enum is the on-disk truth.\n */\nexport type OutboxRowState = 'pending' | 'synced' | 'failed';\n\n/**\n * One durable outbox row — a single client-authored mutation awaiting replay\n * through `POST {basePath}/sync/apply`. Fields mirror the sync-apply item so a\n * row maps to a batch item with no translation, plus the durable bookkeeping the\n * replay loop needs (`state`, `attempts`, `nextAttemptAt`, `lastError`).\n */\nexport interface OutboxRow {\n /** Auto-incrementing primary key — monotonic, so `seq` order IS FIFO. */\n seq?: number;\n /**\n * The sync-apply `itemId`: the client's idempotency/correlation handle. A\n * fresh UUID minted at enqueue time, distinct from {@link id} (the row UUID),\n * so it survives coalescing and is echoed back verbatim by the endpoint.\n */\n itemId: string;\n /** The collection route segment (e.g. `products`) — sync-apply `object`. */\n object: string;\n /** Which mutation kind — sync-apply `op`. */\n op: SyncApplyOp;\n /** The client-generated row UUID — sync-apply `id`. */\n id: string;\n /** Field data for create/update; omitted for delete. */\n payload?: Record<string, unknown>;\n /** The server `updated_at` the client last saw — the conflict guard base. */\n baseUpdatedAt?: string;\n /** On-disk lifecycle state. */\n state: OutboxRowState;\n /** How many replay attempts this row has made (drives backoff). */\n attempts: number;\n /** Epoch ms before which this row must not be retried (backoff gate). */\n nextAttemptAt: number;\n /** Last replay error message, for diagnostics/`snapshot()`. */\n lastError?: string;\n /** Epoch ms the row was first enqueued — stable ordering tiebreak + age. */\n enqueuedAt: number;\n}\n\n/** The fields a caller supplies to {@link DurableOutboxQueue.enqueue}. */\nexport interface EnqueueInput {\n itemId: string;\n object: string;\n op: SyncApplyOp;\n id: string;\n payload?: Record<string, unknown>;\n baseUpdatedAt?: string;\n}\n\n/**\n * Promisify a single IndexedDB request. Rejects with the request's error (or a\n * generic one) so callers get a real rejection, never a silent `undefined`.\n */\nfunction promisifyRequest<T>(request: IDBRequest<T>): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n request.onsuccess = () => resolve(request.result);\n request.onerror = () =>\n reject(request.error ?? new Error('[smrt-web] IndexedDB request failed'));\n });\n}\n\n/**\n * Promisify a transaction's completion. IndexedDB writes are only durable once\n * the TRANSACTION completes (not when the request succeeds), so mutations await\n * this — a queue that reported success before the txn committed could lose a\n * write on a crash in the commit window, defeating the whole point.\n */\nfunction awaitTransaction(tx: IDBTransaction): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n tx.oncomplete = () => resolve();\n tx.onerror = () =>\n reject(tx.error ?? new Error('[smrt-web] IndexedDB transaction failed'));\n tx.onabort = () =>\n reject(tx.error ?? new Error('[smrt-web] IndexedDB transaction aborted'));\n });\n}\n\n/**\n * Feature-detect a usable IndexedDB. Some environments expose `indexedDB` but\n * throw on `open()` (private-mode Firefox historically, sandboxed iframes), so a\n * real probe opens a throwaway database. Returns `false` rather than throwing so\n * the engine can degrade to a no-op instead of crashing the host.\n */\nexport async function probeIndexedDb(): Promise<boolean> {\n const idb = (globalThis as { indexedDB?: IDBFactory }).indexedDB;\n if (!idb) return false;\n const probeName = '__smrt_web_outbox_probe__';\n try {\n const db = await new Promise<IDBDatabase>((resolve, reject) => {\n const request = idb.open(probeName, 1);\n request.onsuccess = () => resolve(request.result);\n request.onerror = () =>\n reject(request.error ?? new Error('probe failed'));\n request.onblocked = () => reject(new Error('probe blocked'));\n });\n db.close();\n // Best-effort cleanup; ignore failure.\n try {\n idb.deleteDatabase(probeName);\n } catch {\n /* ignore */\n }\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * The durable FIFO outbox queue over one IndexedDB database. Constructed via\n * {@link openDurableOutboxQueue} (async open + schema upgrade), then used\n * through the async methods below (each resolves once its txn commits).\n *\n * NOT concurrency-controlled across tabs on its own — the OutboxEngine layers\n * Web Locks leader election on top so exactly one tab drains it. Within one tab,\n * IndexedDB's own transaction serialization is sufficient.\n */\nexport class DurableOutboxQueue {\n private readonly db: IDBDatabase;\n /** The IndexedDB database name (== the durable-store namespace). */\n readonly dbName: string;\n\n constructor(db: IDBDatabase, dbName: string) {\n this.db = db;\n this.dbName = dbName;\n }\n\n /**\n * Append a mutation to the tail of the queue in state `pending`, due\n * immediately (`nextAttemptAt = 0`, `attempts = 0`). Resolves with the\n * assigned `seq` once the write is durably committed.\n */\n async enqueue(input: EnqueueInput): Promise<number> {\n const now = Date.now();\n const row: OutboxRow = {\n itemId: input.itemId,\n object: input.object,\n op: input.op,\n id: input.id,\n payload: input.payload,\n baseUpdatedAt: input.baseUpdatedAt,\n state: 'pending',\n attempts: 0,\n nextAttemptAt: 0,\n enqueuedAt: now,\n };\n const tx = this.db.transaction(OUTBOX_STORE, 'readwrite');\n const store = tx.objectStore(OUTBOX_STORE);\n const seq = await promisifyRequest(store.add(row));\n await awaitTransaction(tx);\n return seq as number;\n }\n\n /**\n * Persist a state transition (and any of attempts/backoff/error) for the row\n * at `seq`, reading-then-writing inside ONE transaction so a concurrent drain\n * in the same tab can't lose the update. A no-op if the row is already gone\n * (removed by a prior terminal transition).\n */\n async markState(\n seq: number,\n patch: Partial<\n Pick<OutboxRow, 'state' | 'attempts' | 'nextAttemptAt' | 'lastError'>\n >,\n ): Promise<void> {\n const tx = this.db.transaction(OUTBOX_STORE, 'readwrite');\n const store = tx.objectStore(OUTBOX_STORE);\n const existing = await promisifyRequest(\n store.get(seq) as IDBRequest<OutboxRow | undefined>,\n );\n if (!existing) {\n // Nothing to update; let the txn commit as a no-op.\n await awaitTransaction(tx);\n return;\n }\n const next: OutboxRow = { ...existing, ...patch, seq };\n await promisifyRequest(store.put(next));\n await awaitTransaction(tx);\n }\n\n /**\n * All rows that are due to (re)send at `now`: state `pending` AND\n * `nextAttemptAt <= now`, in ascending `seq` (FIFO). Uses the `state` index to\n * avoid scanning terminal tombstones. Terminal rows (`synced`/`failed`) are\n * excluded — they await removal, not replay.\n */\n async listPending(now: number): Promise<OutboxRow[]> {\n const tx = this.db.transaction(OUTBOX_STORE, 'readonly');\n const index = tx.objectStore(OUTBOX_STORE).index(OUTBOX_STATE_INDEX);\n const rows = await promisifyRequest(\n index.getAll(IDBKeyRange.only('pending')) as IDBRequest<OutboxRow[]>,\n );\n await awaitTransaction(tx);\n return rows\n .filter((row) => row.nextAttemptAt <= now)\n .sort((a, b) => (a.seq ?? 0) - (b.seq ?? 0));\n }\n\n /** Remove the row at `seq` (a terminal transition drops it). */\n async remove(seq: number): Promise<void> {\n const tx = this.db.transaction(OUTBOX_STORE, 'readwrite');\n await promisifyRequest(tx.objectStore(OUTBOX_STORE).delete(seq));\n await awaitTransaction(tx);\n }\n\n /** Every row currently in the queue (any state), ascending `seq`. */\n async all(): Promise<OutboxRow[]> {\n const tx = this.db.transaction(OUTBOX_STORE, 'readonly');\n const rows = await promisifyRequest(\n tx.objectStore(OUTBOX_STORE).getAll() as IDBRequest<OutboxRow[]>,\n );\n await awaitTransaction(tx);\n return rows.sort((a, b) => (a.seq ?? 0) - (b.seq ?? 0));\n }\n\n /**\n * Drop every row — the durable-store `clear()` for `wipeDurableStore`. Empties\n * the store but keeps the database (and its `seq` autoincrement counter) so a\n * subsequent enqueue still gets fresh monotonic ids.\n */\n async clear(): Promise<void> {\n const tx = this.db.transaction(OUTBOX_STORE, 'readwrite');\n await promisifyRequest(tx.objectStore(OUTBOX_STORE).clear());\n await awaitTransaction(tx);\n }\n\n /** Close the underlying database handle (called on engine dispose). */\n close(): void {\n this.db.close();\n }\n}\n\n/**\n * Open (creating/upgrading as needed) the outbox database for `dbName` and wrap\n * it in a {@link DurableOutboxQueue}. `dbName` MUST be a\n * {@link durableStoreNamespace} string — already `encodeURIComponent`-safe and\n * carrying the api/tenant/identity/manifest isolation boundary, so two\n * identities never share one queue database.\n *\n * The `onupgradeneeded` handler creates the `outbox` store keyed by\n * autoincrementing `seq` and the `state` index. Idempotent: re-opening an\n * existing database at the same version skips the upgrade — which is exactly the\n * \"reload\" path (a new tab re-opens the same durable backing and sees prior\n * rows).\n */\nexport function openDurableOutboxQueue(\n dbName: string,\n): Promise<DurableOutboxQueue> {\n const idb = (globalThis as { indexedDB?: IDBFactory }).indexedDB;\n if (!idb) {\n return Promise.reject(\n new Error('[smrt-web] IndexedDB is unavailable in this environment'),\n );\n }\n return new Promise<DurableOutboxQueue>((resolve, reject) => {\n const request = idb.open(dbName, OUTBOX_DB_VERSION);\n request.onupgradeneeded = () => {\n const db = request.result;\n if (!db.objectStoreNames.contains(OUTBOX_STORE)) {\n const store = db.createObjectStore(OUTBOX_STORE, {\n keyPath: 'seq',\n autoIncrement: true,\n });\n store.createIndex(OUTBOX_STATE_INDEX, 'state', { unique: false });\n }\n };\n request.onsuccess = () =>\n resolve(new DurableOutboxQueue(request.result, dbName));\n request.onerror = () =>\n reject(\n request.error ??\n new Error(`[smrt-web] failed to open outbox database \"${dbName}\"`),\n );\n request.onblocked = () =>\n reject(\n new Error(`[smrt-web] opening outbox database \"${dbName}\" was blocked`),\n );\n });\n}\n","/**\n * @happyvertical/smrt-web — Web Locks leader election for the outbox (#1762).\n *\n * With multiple tabs open, exactly ONE must replay the shared outbox queue, or\n * two tabs would POST the same batch concurrently. The mechanism is the native\n * Web Locks API (`navigator.locks`): a tab requests an EXCLUSIVE lock keyed by\n * the durable-store namespace and holds it — via a promise that resolves only on\n * release — for as long as it should be leader. The browser grants the lock to\n * exactly one waiter at a time and AUTO-RELEASES it on tab crash/close/navigation\n * with no heartbeat, so the next waiting tab becomes leader instantly and there\n * is never a stuck lock.\n *\n * Why Web Locks over a BroadcastChannel election (v1 decision, locked): Web\n * Locks gives crash-safe, race-free, browser-arbitrated exclusion for free —\n * a hand-rolled BroadcastChannel election has to solve liveness (heartbeats,\n * takeover on a silent crash) itself. BroadcastChannel is a possible future\n * enhancement (cross-tab state fan-out), not the election primitive.\n *\n * ## Single-tab fallback (documented gap)\n *\n * When `navigator.locks` is absent (older Safari, non-browser hosts), this warns\n * ONCE and falls back to acquiring leadership immediately and unconditionally.\n * The outbox STILL replays — a lone tab is trivially the only replayer — but the\n * multi-tab exactly-one-replayer guarantee does not hold across tabs that all\n * took the fallback. This is a deliberate v1 limitation, NOT masked by a\n * BroadcastChannel shim.\n *\n * Engine-free: no `@tanstack/*` import — stays inside the boundary (#1761).\n */\n\n/**\n * A handle to relinquish leadership: call it to release the Web Lock (or cancel\n * a still-pending request). Idempotent.\n */\nexport type LeadershipHandle = () => void;\n\n/** Structural view of the Web Locks API surface this module uses. */\ninterface LockManagerLike {\n request(\n name: string,\n options: { signal?: AbortSignal; mode?: 'exclusive' | 'shared' },\n callback: () => Promise<unknown>,\n ): Promise<unknown>;\n}\n\n/** Detect the Web Locks API on the current `navigator`. */\nfunction getLockManager(): LockManagerLike | undefined {\n const nav = (globalThis as { navigator?: { locks?: unknown } }).navigator;\n const locks = nav?.locks as LockManagerLike | undefined;\n if (locks && typeof locks.request === 'function') return locks;\n return undefined;\n}\n\n/** Gate so the single-tab-fallback warning is logged at most once per session. */\nlet warnedNoLocks = false;\n\n/**\n * Acquire cross-tab leadership for `lockName` (the outbox's\n * `smrt-web-outbox-leader:<namespace>`).\n *\n * When the lock is granted this tab becomes leader and `onAcquired()` fires; the\n * tab stays leader — holding the lock — until the returned handle is called (or\n * the tab dies, when the browser auto-releases). On release, `onReleased()`\n * fires. Requesting is non-blocking: this returns synchronously with the\n * release handle while the request waits in the background for its turn.\n *\n * Contract:\n * - The lock is requested EXCLUSIVE; the callback returns a promise that stays\n * pending until the release handle resolves it, so the browser considers the\n * lock held for exactly that window.\n * - Calling the handle before the lock is even granted aborts the pending\n * request (via `AbortSignal`) so a torn-down engine never becomes leader\n * later. `onReleased` still fires so callers can settle their own state.\n * - No Web Locks support → single-tab fallback: `onAcquired()` fires on the next\n * microtask (leadership is immediate and unconditional), the handle just fires\n * `onReleased()`.\n */\nexport function acquireLeadership(\n lockName: string,\n onAcquired: () => void,\n onReleased: () => void,\n): LeadershipHandle {\n const locks = getLockManager();\n\n // --- Single-tab fallback: no Web Locks in this environment. ---\n if (!locks) {\n if (!warnedNoLocks) {\n warnedNoLocks = true;\n // biome-ignore lint/suspicious/noConsole: smrt-web has no logger dep (TanStack-only); the single-tab-fallback gap is surfaced via a one-time console.warn by design (#1762)\n console.warn(\n '[smrt-web] Web Locks API unavailable — the offline outbox falls back to single-tab leadership; the multi-tab exactly-one-replayer guarantee does not hold across tabs.',\n );\n }\n let released = false;\n const release = () => {\n if (released) return;\n released = true;\n onReleased();\n };\n // Grant leadership asynchronously so callers can wire state before it fires,\n // matching the Web-Locks path (never synchronous inside the constructor).\n queueMicrotask(() => {\n if (!released) onAcquired();\n });\n return release;\n }\n\n // --- Web Locks path. ---\n const controller = new AbortController();\n // The promise the lock callback returns; it resolves ONLY when we release, so\n // the browser holds the lock for exactly the leadership window.\n let releaseHeldLock: (() => void) | undefined;\n let released = false;\n let acquired = false;\n\n const release: LeadershipHandle = () => {\n if (released) return;\n released = true;\n if (acquired && releaseHeldLock) {\n // We hold the lock: resolve the callback's promise to release it.\n releaseHeldLock();\n } else {\n // Still waiting in the queue: abort the pending request so we never\n // become leader after teardown.\n controller.abort();\n }\n onReleased();\n };\n\n void locks\n .request(lockName, { signal: controller.signal, mode: 'exclusive' }, () => {\n // Granted — we are now leader. Hold the lock until release() resolves this.\n acquired = true;\n // If release() already ran while the request was pending, the abort above\n // fired instead and this callback never runs; guard anyway.\n if (released) return Promise.resolve();\n onAcquired();\n return new Promise<void>((resolve) => {\n releaseHeldLock = resolve;\n });\n })\n .catch((error: unknown) => {\n // An AbortError is the expected outcome when release() cancels a pending\n // request — not a fault. Any other rejection means the lock could not be\n // held; surface it so a lock subsystem problem is visible, and ensure\n // onReleased still fires so callers settle.\n const name = (error as { name?: string })?.name;\n if (name !== 'AbortError') {\n // biome-ignore lint/suspicious/noConsole: smrt-web has no logger dep; a lock-request failure is surfaced via console.warn (#1762)\n console.warn('[smrt-web] outbox leader lock request failed', error);\n }\n if (!released) {\n released = true;\n onReleased();\n }\n });\n\n return release;\n}\n","/**\n * @happyvertical/smrt-web — offline outbox shared types (#1762).\n *\n * The SMRT-owned vocabulary the outbox modules (`durable-queue`, `leader`,\n * `engine`) and the public surface (`offline.ts`) share. Kept in its own leaf\n * module — imported by everything, importing nothing from the package — so there\n * is no cycle back through `index.ts` and the whole outbox stays inside the\n * engine-absorption boundary (#1761): NOT ONE `@tanstack/*` type appears here.\n *\n * The sync-apply op/status/reason names mirror\n * `packages/core/src/sync/apply.ts` (the replay target) verbatim, so a queue row\n * maps to a batch item and a batch result maps to a state transition with no\n * translation layer. smrt-web has no inter-smrt dependency (dependency-DAG\n * guardrails), so these are re-declared here rather than imported from core.\n */\n\n/**\n * Mutation kinds the sync-apply batch endpoint accepts. Mirrors core's\n * `SyncApplyOp`. The outbox maps the capability seam's insert/update/delete\n * envelope kinds onto these (`insert → create`).\n */\nexport type SyncApplyOp = 'create' | 'update' | 'delete';\n\n/**\n * Per-item outcome status from a sync-apply batch result. Mirrors core's\n * `SyncApplyStatus`. The engine maps each onto a durable transition per the\n * contract's \"Web outbox (#1762)\" consumer notes.\n */\nexport type SyncApplyStatus = 'applied' | 'conflict' | 'rejected';\n\n/**\n * Machine-readable reasons a sync-apply item was `conflict` or `rejected`.\n * Mirrors core's `SyncApplyReason` union. Drives the engine's result mapping:\n * `auth_required`/`forbidden` pause the loop; `write_failed` retries; the rest\n * are terminal.\n */\nexport type SyncApplyReason =\n | 'invalid_item'\n | 'invalid_id'\n | 'invalid_payload'\n | 'unknown_object'\n | 'op_not_allowed'\n | 'auth_required'\n | 'forbidden'\n | 'not_found'\n | 'id_conflict'\n | 'write_failed'\n | 'stale_write'\n | 'create_conflict';\n\n/**\n * One item in a `POST {basePath}/sync/apply` request body. Mirrors core's\n * `SyncApplyItem`. Built from an {@link OutboxRow} 1:1.\n */\nexport interface SyncApplyItem {\n itemId: string;\n object: string;\n op: SyncApplyOp;\n id: string;\n payload?: Record<string, unknown>;\n baseUpdatedAt?: string;\n}\n\n/**\n * One entry in a sync-apply batch response's `results[]`. Mirrors core's\n * `SyncApplyItemResult`. `results[i]` corresponds to `items[i]` positionally;\n * `itemId` is echoed for convenience.\n */\nexport interface SyncApplyItemResult {\n itemId: string | null;\n id: string | null;\n status: SyncApplyStatus;\n reason?: SyncApplyReason;\n updatedAt?: string;\n}\n\n/** The HTTP-200 response body of `POST {basePath}/sync/apply`. */\nexport interface SyncApplyBatchResponse {\n results: SyncApplyItemResult[];\n}\n\n/**\n * Maximum items the endpoint accepts in one batch (core's\n * `MAX_SYNC_APPLY_BATCH_SIZE`). The engine chunks a larger backlog into\n * successive POSTs, oldest-first, so FIFO order is preserved across chunks.\n */\nexport const MAX_SYNC_APPLY_BATCH_SIZE = 1000;\n\n/**\n * The URL segments of the batch apply endpoint relative to the API base path:\n * `POST {basePath}/sync/apply` (core's `SYNC_APPLY_ROUTE_SEGMENTS`).\n */\nexport const SYNC_APPLY_ROUTE_SEGMENTS = ['sync', 'apply'] as const;\n\n/**\n * The app-observable sync state of a queued mutation — the SAME four-state\n * machine the KMP mobile foundation exposes (ADR 0001), so web and mobile\n * outbox indicators render identically:\n *\n * - `pending` — enqueued, awaiting (re)send.\n * - `uploading` — currently in a sync-apply POST.\n * - `synced` — the mutation's effect is confirmed on the server (a terminal\n * success — INCLUDING a surfaced conflict, which is a RESOLVED outcome: the\n * server state won and the item left the queue).\n * - `failed` — a terminal rejection the client cannot resolve by retrying\n * (`invalid_*`, `not_found`, `id_conflict`, `op_not_allowed`, …) — surfaced\n * for app-level handling.\n */\nexport type OutboxSyncState = 'pending' | 'uploading' | 'synced' | 'failed';\n\n/**\n * A sync-state transition delivered to {@link OfflineOutboxConfig.onSyncStateChange}.\n * A PUSH callback (not a subscribable store): smrt-svelte wraps it into a\n * reactive binding later (#1762 follow-on). Carries enough for an app to render\n * a per-row indicator and a retry affordance.\n */\nexport interface SyncStateEvent {\n /** The queue row's `itemId` (its idempotency handle) this state is for. */\n itemId: string;\n /** The client-generated row UUID the mutation targets. */\n rowId: string;\n /** The collection route segment (e.g. `products`). */\n object: string;\n /** The new observable state. */\n state: OutboxSyncState;\n /** How many replay attempts have run (0 until the first send). */\n attempts: number;\n /** The last replay error message, when `state` is `pending` after a retry. */\n error?: string;\n}\n\n/**\n * A conflict surfaced to {@link OfflineOutboxConfig.onConflict} when a replayed\n * item comes back `conflict`. Per the contract this is a RESOLVED outcome — the\n * server state won, the item is removed, and its terminal observable state is\n * `synced` — so an app treats it as \"your write was superseded; here is the\n * server's `updatedAt` to rebase from\", NOT as a failure to retry.\n */\nexport interface OutboxConflict {\n /** The queue row's `itemId`. */\n itemId: string;\n /** The collection route segment. */\n object: string;\n /** The client-generated row UUID that conflicted. */\n rowId: string;\n /**\n * Why the item conflicted: `stale_write` (an update/delete whose\n * `baseUpdatedAt` was older than the server row) or `create_conflict` (a\n * create landing on an existing, diverged row).\n */\n reason: 'stale_write' | 'create_conflict';\n /**\n * The server row's `updated_at` after processing — the value to persist as\n * the new `baseUpdatedAt` before re-editing. Present when the endpoint\n * returned it.\n */\n serverUpdatedAt?: string;\n}\n\n/** Exponential-backoff tuning for retryable replay failures. */\nexport interface OutboxBackoff {\n /** Delay before the first retry, ms (default 1000). */\n initialDelayMs?: number;\n /** Multiplier applied per attempt (default 2). */\n multiplier?: number;\n /** Ceiling on the computed delay, ms (default 60000). */\n maxDelayMs?: number;\n}\n\n/** Resolved backoff config (defaults filled in). */\nexport interface ResolvedBackoff {\n initialDelayMs: number;\n multiplier: number;\n maxDelayMs: number;\n}\n\n/** Backoff defaults, per the blueprint (initial=1s, ×2, cap 60s). */\nexport const DEFAULT_BACKOFF: ResolvedBackoff = {\n initialDelayMs: 1000,\n multiplier: 2,\n maxDelayMs: 60000,\n};\n\n/**\n * Compute the backoff delay before the NEXT attempt given how many attempts\n * have already been made. Exponential with a ceiling, then multiplied by a\n * jitter factor in `[0.5, 1.0)` to spread a thundering herd of tabs/rows\n * reconnecting at once: `min(maxDelay, initial * mult ** attempts) * jitter`.\n *\n * `attempts` is the count BEFORE this retry (so the first retry, after attempts\n * became 1, uses `initial * mult ** 1`? no — see below). We pass the attempt\n * count AFTER incrementing, and index the exponent off `attempts - 1` so the\n * first retry waits ~`initialDelayMs`. `random` is injectable for deterministic\n * tests.\n */\nexport function computeBackoffDelay(\n attempts: number,\n backoff: ResolvedBackoff,\n random: () => number = Math.random,\n): number {\n const exponent = Math.max(0, attempts - 1);\n const raw = backoff.initialDelayMs * backoff.multiplier ** exponent;\n const capped = Math.min(backoff.maxDelayMs, raw);\n // Jitter in [0.5, 1.0): full jitter's lower half, so a retry never waits\n // longer than the capped delay but is spread over half of it.\n const jitter = 0.5 + random() * 0.5;\n return Math.round(capped * jitter);\n}\n","/**\n * @happyvertical/smrt-web — the shared, namespace-keyed outbox engine (#1762).\n *\n * The engine owns the durable queue, the leader lock, and the replay loop for\n * ONE durable-store namespace, and is REF-COUNTED so that N collections sharing\n * a namespace share exactly ONE engine — one IndexedDB database, one leader\n * lock, one FIFO queue. This sharing is REQUIRED for correctness, not an\n * optimization: if each collection held its OWN leader lock, two tabs could each\n * win a different collection's lock and both replay the (shared) queue,\n * double-POSTing. One lock per namespace ⇒ one replayer per namespace across all\n * tabs.\n *\n * Replay maps sync-apply results onto durable transitions per the contract's\n * \"Web outbox (#1762)\" consumer notes\n * (docs/content/architecture/sync-apply-contract.md):\n *\n * | apply result | queue transition |\n * |-------------------------------------|----------------------------------------|\n * | `applied` | remove → `synced` |\n * | `conflict` (stale_write/create_conflict) | remove + fire onConflict → `synced` (a RESOLVED outcome) |\n * | `rejected` `write_failed` | keep, attempts++, backoff → `pending` |\n * | `rejected` `auth_required`/`forbidden` | PAUSE the loop, keep queued → `pending` |\n * | `rejected` other (`invalid_*`/`unknown_object`/`not_found`/`op_not_allowed`/`id_conflict`) | remove → `failed` (terminal) |\n * | network reject / non-200 / lost response | whole batch stays `pending`, drain stops behind it (blind replay is safe by construction — idempotency) |\n *\n * Because items carry client-generated UUIDs and the endpoint is idempotent\n * (`_insertOnly` create + no-op re-apply), a batch that was sent but whose\n * response was lost can be blindly re-sent with no duplicate rows — which is\n * why the network-failure path simply leaves the batch `pending`.\n *\n * Engine-free public surface: no `@tanstack/*` import — inside the boundary\n * (#1761). The one engine-adjacent value it receives is the SMRT-owned\n * durable-store namespace + registration hooks passed in `config`.\n */\n\nimport {\n type DurableOutboxQueue,\n type OutboxRow,\n openDurableOutboxQueue,\n probeIndexedDb,\n} from './durable-queue.js';\nimport { acquireLeadership, type LeadershipHandle } from './leader.js';\nimport {\n computeBackoffDelay,\n MAX_SYNC_APPLY_BATCH_SIZE,\n type OutboxConflict,\n type ResolvedBackoff,\n SYNC_APPLY_ROUTE_SEGMENTS,\n type SyncApplyBatchResponse,\n type SyncApplyItem,\n type SyncApplyItemResult,\n type SyncApplyOp,\n type SyncStateEvent,\n} from './types.js';\n\n/** Maps a capability-seam envelope kind to a sync-apply op. */\nexport function envelopeKindToOp(\n kind: 'insert' | 'update' | 'delete',\n): SyncApplyOp {\n return kind === 'insert' ? 'create' : kind;\n}\n\n/**\n * The bookkeeping a namespace's durable-store registration needs, supplied by\n * the public surface so the engine can key its queue and register for\n * {@link wipeDurableStore} WITHOUT importing `durable-store.ts` itself (keeping\n * this module's dependency surface minimal and the namespacing single-sourced in\n * the caller). `namespace` is the {@link durableStoreNamespace} string.\n */\nexport interface OutboxEngineConfig {\n /** The durable-store namespace string — the IDB dbName + lock-name root. */\n namespace: string;\n /** Absolute base path the sync-apply endpoint lives under (e.g. `/api/v1`). */\n syncApplyBasePath: string;\n /** Fetch implementation (injectable for tests/SSR). */\n fetchFn: typeof fetch;\n /** Resolved backoff parameters. */\n backoff: ResolvedBackoff;\n /**\n * Register this engine's queue as a durable resource so `wipeDurableStore`\n * can clear it; returns the unregister fn. Wraps `registerDurableResource`\n * from the caller so the engine stays decoupled from that module.\n */\n registerResource: (clear: () => Promise<void>) => () => void;\n /** Injectable RNG for deterministic backoff jitter in tests. */\n random?: () => number;\n}\n\n/**\n * A pending optimistic write to enqueue, in capability-seam terms. Callbacks are\n * NOT carried here — they are registered per-`object` at\n * {@link OutboxEngine.registerCollection} so that reloaded rows (which this\n * session never enqueued) still route their events to the collection.\n */\nexport interface OutboxEnqueueRequest {\n kind: 'insert' | 'update' | 'delete';\n /** The collection route segment (definition.name). */\n object: string;\n /** The client-generated row UUID (the optimistic row's id). */\n rowId: string;\n /** Full row (insert) / changed fields (update) / ignored (delete). */\n data: Record<string, unknown>;\n /** The server updated_at last seen for this row, for the conflict guard. */\n baseUpdatedAt?: string;\n}\n\n/** A read-only view of one queued item, for {@link OutboxEngine.snapshot}. */\nexport interface OutboxSnapshotItem {\n itemId: string;\n object: string;\n op: SyncApplyOp;\n rowId: string;\n state: 'pending' | 'synced' | 'failed';\n attempts: number;\n nextAttemptAt: number;\n lastError?: string;\n}\n\n/** Generate a fresh UUID itemId, falling back when crypto.randomUUID is absent. */\nfunction newItemId(): string {\n const cryptoRef = (globalThis as { crypto?: Crypto }).crypto;\n if (cryptoRef?.randomUUID) return cryptoRef.randomUUID();\n return `item-${Date.now()}-${Math.random().toString(36).slice(2)}`;\n}\n\n/** Is `navigator.onLine` telling us we're definitely offline? */\nfunction isDefinitelyOffline(): boolean {\n const nav = (globalThis as { navigator?: { onLine?: boolean } }).navigator;\n return nav?.onLine === false;\n}\n\n/**\n * The shared per-namespace outbox engine. Create/lookup via\n * {@link getOrCreateOutboxEngine}; never construct directly (the module keeps\n * the ref-counted registry).\n */\nexport class OutboxEngine {\n private readonly config: OutboxEngineConfig;\n /**\n * Per-collection callback sets, keyed by the collection route segment\n * (`object`). Keyed by `object` — NOT by the queue row's `itemId` — precisely\n * so replayed rows that were REHYDRATED from IndexedDB after a reload (whose\n * itemIds this session never enqueued) still route their state/conflict events\n * to the reloaded collection's callbacks. A `Set` per object so N collections\n * sharing one engine+object each get every event (the common case is one\n * collection per object, but the shared-engine model does not forbid more).\n */\n private readonly listenersByObject = new Map<\n string,\n Set<{\n onSyncStateChange?: (event: SyncStateEvent) => void;\n onConflict?: (conflict: OutboxConflict) => void;\n }>\n >();\n\n /** Ref count: number of collections currently attached to this engine. */\n private refCount = 0;\n /** The durable queue, once opened. undefined while opening / if IDB absent. */\n private queue: DurableOutboxQueue | undefined;\n /** Resolves once the async open settles (success or degraded). */\n private readonly ready: Promise<void>;\n /** True when IndexedDB was unavailable and the engine is a durable no-op. */\n private degraded = false;\n /** Leadership handle; set once we've requested the leader lock. */\n private leadership: LeadershipHandle | undefined;\n /** True while this tab holds leadership. */\n private isLeader = false;\n /** Unregister fn from the durable-store registry. */\n private unregisterResource: (() => void) | undefined;\n /** True once dispose() ran — guards late async continuations. */\n private disposed = false;\n /**\n * Paused by an auth_required/forbidden result: the loop stops draining until\n * a later enqueue (the app re-authenticated and is writing again) or an\n * explicit retry wakes it. Items stay queued.\n */\n private paused = false;\n /** True while a drain pass is running, to coalesce concurrent triggers. */\n private draining = false;\n /** A drain requested while one was in flight — run one more pass after. */\n private drainQueued = false;\n /** Timer for the next backoff-scheduled drain, if any. */\n private backoffTimer: ReturnType<typeof setTimeout> | undefined;\n /** The `online` event listener, so we can remove it on dispose. */\n private onlineListener: (() => void) | undefined;\n\n constructor(config: OutboxEngineConfig) {\n this.config = config;\n this.ready = this.open();\n this.wireOnlineListener();\n this.requestLeadership();\n // Kick a drain once the queue has finished opening. Leadership can be\n // granted (single-tab fallback: a microtask; Web Locks: whenever the lock\n // frees) BEFORE the async `open()` resolves — in which case that early\n // `drain()` returned at the `!this.queue` guard and nothing re-triggered it.\n // Draining after `ready` closes that race, so a freshly-constructed engine\n // that is already leader with a backlog on disk (the reload / leader-handoff\n // paths) replays without waiting for an external event.\n void this.ready.then(() => {\n if (!this.disposed) void this.drain();\n });\n }\n\n /** Open the durable queue (or mark degraded if IndexedDB is unusable). */\n private async open(): Promise<void> {\n const usable = await probeIndexedDb();\n if (!usable) {\n this.degraded = true;\n // biome-ignore lint/suspicious/noConsole: smrt-web has no logger dep; a degraded (no-IndexedDB) outbox is surfaced via console.warn (#1762)\n console.warn(\n '[smrt-web] IndexedDB unavailable — the offline outbox is disabled; offline writes will not be durable.',\n );\n return;\n }\n try {\n this.queue = await openDurableOutboxQueue(this.config.namespace);\n // Register for wipeDurableStore now that the queue exists.\n this.unregisterResource = this.config.registerResource(async () => {\n // A wipe clears the durable rows; drop the in-flight schedule too.\n await this.queue?.clear();\n });\n if (this.disposed) {\n // Disposed while opening — tear the just-opened queue back down.\n this.queue.close();\n this.queue = undefined;\n this.unregisterResource?.();\n this.unregisterResource = undefined;\n return;\n }\n } catch (error) {\n this.degraded = true;\n // biome-ignore lint/suspicious/noConsole: surface an outbox open failure (#1762)\n console.warn('[smrt-web] failed to open the offline outbox', error);\n }\n }\n\n /** Wake the drain loop immediately when connectivity returns. */\n private wireOnlineListener(): void {\n const target = globalThis as {\n addEventListener?: (t: string, l: () => void) => void;\n };\n if (typeof target.addEventListener !== 'function') return;\n const listener = () => {\n // Reconnected: an auth pause is unrelated to connectivity, so leave\n // `paused` as-is, but a network-stalled backlog should retry now.\n void this.drain();\n };\n target.addEventListener('online', listener);\n this.onlineListener = listener;\n }\n\n /** Request cross-tab leadership; drain whenever we hold it. */\n private requestLeadership(): void {\n const lockName = `smrt-web-outbox-leader:${this.config.namespace}`;\n this.leadership = acquireLeadership(\n lockName,\n () => {\n this.isLeader = true;\n void this.drain();\n },\n () => {\n this.isLeader = false;\n },\n );\n }\n\n /**\n * Attach a collection: register its per-object callbacks and bump the ref\n * count. Returns the exact callback record registered so the caller can pass\n * it back to {@link unregisterCollection} for precise removal (two collections\n * on the same object must each detach only their own callbacks). Registering\n * by `object` is what lets rehydrated rows (reloaded from IDB) reach this\n * collection's callbacks even though this session never enqueued them.\n */\n registerCollection(binding: {\n object: string;\n onSyncStateChange?: (event: SyncStateEvent) => void;\n onConflict?: (conflict: OutboxConflict) => void;\n }): {\n onSyncStateChange?: (event: SyncStateEvent) => void;\n onConflict?: (conflict: OutboxConflict) => void;\n } {\n this.refCount += 1;\n const record = {\n onSyncStateChange: binding.onSyncStateChange,\n onConflict: binding.onConflict,\n };\n let set = this.listenersByObject.get(binding.object);\n if (!set) {\n set = new Set();\n this.listenersByObject.set(binding.object, set);\n }\n set.add(record);\n return record;\n }\n\n /**\n * Detach a collection: remove its callback record and decrement the ref count;\n * when it reaches zero, dispose the engine (release the lock, unregister from\n * the durable-store registry, close IndexedDB). The durable ROWS are NOT\n * cleared — they must survive to replay after a reload; only the in-memory\n * engine is torn down. Returns true if it disposed.\n */\n async unregisterCollection(object: string, record: object): Promise<boolean> {\n const set = this.listenersByObject.get(object);\n if (set) {\n set.delete(record as never);\n if (set.size === 0) this.listenersByObject.delete(object);\n }\n this.refCount = Math.max(0, this.refCount - 1);\n if (this.refCount > 0) return false;\n await this.dispose();\n return true;\n }\n\n /** Current ref count (test/introspection aid). */\n get referenceCount(): number {\n return this.refCount;\n }\n\n /**\n * Enqueue an optimistic write into the durable queue and fire the initial\n * `pending` state, then kick a drain. Resolves once the row is durably\n * committed (so the caller's `wrapMutation` only reports handled after\n * persistence). Replay events for this row (and for rows this session did not\n * enqueue — reloaded from disk) route to the registered per-`object`\n * callbacks, so a reload does not lose observability.\n *\n * In degraded (no-IndexedDB) mode the write is NOT durable, so this returns\n * `undefined` and the capability falls through to the real fetcher instead\n * of acknowledging an optimistic-only write.\n */\n async enqueue(request: OutboxEnqueueRequest): Promise<string | undefined> {\n await this.ready;\n if (!this.queue || this.degraded) return undefined;\n\n const itemId = newItemId();\n const op = envelopeKindToOp(request.kind);\n\n // A delete carries no payload; create/update carry the row/changed fields.\n const payload = op === 'delete' ? undefined : request.data;\n\n await this.queue.enqueue({\n itemId,\n object: request.object,\n op,\n id: request.rowId,\n payload,\n baseUpdatedAt: request.baseUpdatedAt,\n });\n\n this.emit({\n itemId,\n rowId: request.rowId,\n object: request.object,\n state: 'pending',\n attempts: 0,\n });\n\n // A fresh write means there's work; if an auth pause was in effect the app\n // is evidently active again, so clear it and try.\n this.paused = false;\n void this.drain();\n return itemId;\n }\n\n /**\n * Force a retry of a specific queued item now: clears its backoff gate and\n * wakes the loop. The bridge `OutboxHandle.retry(itemId)` calls this so an app\n * \"retry\" button can flush a backed-off or auth-paused item. A no-op for an\n * item that is not (or no longer) queued.\n */\n async retry(itemId: string): Promise<void> {\n await this.ready;\n if (!this.queue) return;\n const rows = await this.queue.all();\n const row = rows.find((r) => r.itemId === itemId && r.state === 'pending');\n if (!row || row.seq === undefined) return;\n await this.queue.markState(row.seq, { nextAttemptAt: 0 });\n this.paused = false;\n void this.drain();\n }\n\n /**\n * A read-only snapshot of the durable queue — the basis of\n * `OutboxHandle.snapshot()`. Because the READ cache is NOT rehydrated after a\n * reload in this slice (that's #1764's warmStart), the snapshot + the raw IDB\n * store are how a test/app proves durability, not `collection.toArray()`.\n */\n async snapshot(): Promise<OutboxSnapshotItem[]> {\n await this.ready;\n if (!this.queue) return [];\n const rows = await this.queue.all();\n return rows.map((row) => ({\n itemId: row.itemId,\n object: row.object,\n op: row.op,\n rowId: row.id,\n state: row.state,\n attempts: row.attempts,\n nextAttemptAt: row.nextAttemptAt,\n lastError: row.lastError,\n }));\n }\n\n /**\n * Deliver a state event to every callback registered for the event's\n * collection `object` (best-effort). Routing by `object` — not `itemId` —\n * means a row REHYDRATED from IndexedDB after a reload still reaches the\n * reloaded collection's callback even though this session never enqueued it.\n */\n private emit(event: SyncStateEvent): void {\n const set = this.listenersByObject.get(event.object);\n if (!set) return;\n for (const listener of set) {\n try {\n listener.onSyncStateChange?.(event);\n } catch (error) {\n // biome-ignore lint/suspicious/noConsole: a throwing app callback must not break the loop (#1762)\n console.warn('[smrt-web] onSyncStateChange callback threw', error);\n }\n }\n }\n\n /** Deliver a conflict to every callback registered for its collection. */\n private emitConflict(conflict: OutboxConflict): void {\n const set = this.listenersByObject.get(conflict.object);\n if (!set) return;\n for (const listener of set) {\n try {\n listener.onConflict?.(conflict);\n } catch (error) {\n // biome-ignore lint/suspicious/noConsole: a throwing app callback must not break the loop (#1762)\n console.warn('[smrt-web] onConflict callback threw', error);\n }\n }\n }\n\n /**\n * The replay loop. Gated on: (a) holding leadership, (b) not paused by an\n * auth failure, (c) `navigator.onLine !== false`, (d) IndexedDB usable. Drains\n * all rows due now (`nextAttemptAt <= now`), oldest-first, chunked into\n * batches of ≤1000 per POST, one POST at a time to preserve FIFO across\n * chunks. Concurrency-coalesced: a drain requested while one runs sets a flag\n * to run exactly one more pass, so overlapping triggers never interleave.\n */\n private async drain(): Promise<void> {\n if (this.draining) {\n this.drainQueued = true;\n return;\n }\n this.draining = true;\n try {\n // Loop so a queued re-request (or a freshly-eligible backoff row) runs\n // without re-entrancy.\n for (;;) {\n this.drainQueued = false;\n await this.drainOnce();\n if (!this.drainQueued) break;\n }\n } finally {\n this.draining = false;\n }\n }\n\n /** One drain pass: send every currently-due batch, then schedule backoff. */\n private async drainOnce(): Promise<void> {\n if (this.disposed) return;\n if (!this.isLeader) return;\n if (this.paused) return;\n if (this.degraded || !this.queue) return;\n if (isDefinitelyOffline()) return;\n\n const pending = (await this.queue.all()).filter(\n (row) => row.state === 'pending',\n );\n if (pending.length === 0) return;\n\n const now = Date.now();\n const firstBlocked = pending.findIndex((row) => row.nextAttemptAt > now);\n const due = firstBlocked === -1 ? pending : pending.slice(0, firstBlocked);\n if (due.length === 0) {\n // The oldest pending row is backed off; FIFO forbids draining newer rows.\n await this.scheduleNextBackoff();\n return;\n }\n\n // Chunk oldest-first into ≤1000-item batches; send one at a time so FIFO\n // holds across chunks.\n for (let i = 0; i < due.length; i += MAX_SYNC_APPLY_BATCH_SIZE) {\n if (this.disposed || this.paused || !this.isLeader) break;\n const chunk = due.slice(i, i + MAX_SYNC_APPLY_BATCH_SIZE);\n const drained = await this.sendBatch(chunk);\n if (!drained) break;\n }\n\n // After processing, some rows may have been re-queued with a backoff gate;\n // schedule the next wake.\n await this.scheduleNextBackoff();\n }\n\n /**\n * Send one chunk through `POST {basePath}/sync/apply` and map results back\n * onto durable transitions. On a network reject / non-200 / lost/mismatched\n * response the WHOLE chunk stays `pending` (blind replay is safe) — every row\n * goes back to `pending` with an incremented attempt + backoff so the loop\n * doesn't hot-spin. Returns false when a retryable row remains pending, which\n * stops this drain pass so newer FIFO chunks do not overtake it.\n */\n private async sendBatch(chunk: OutboxRow[]): Promise<boolean> {\n // Mark the chunk uploading (observable), build the request items in order.\n for (const row of chunk) {\n this.emit({\n itemId: row.itemId,\n rowId: row.id,\n object: row.object,\n state: 'uploading',\n attempts: row.attempts,\n });\n }\n const items: SyncApplyItem[] = chunk.map((row) => ({\n itemId: row.itemId,\n object: row.object,\n op: row.op,\n id: row.id,\n payload: row.payload,\n baseUpdatedAt: row.baseUpdatedAt,\n }));\n\n let results: SyncApplyItemResult[] | undefined;\n try {\n results = await this.postBatch(items);\n } catch {\n // Network reject / non-200 / lost response: keep the whole batch pending.\n await this.requeueBatch(chunk, 'network error during sync');\n return false;\n }\n if (!results) {\n await this.requeueBatch(chunk, 'unexpected sync response shape');\n return false;\n }\n\n // Results are positional (results[i] ↔ items[i] ↔ chunk[i]). Map each.\n let drained = true;\n for (let i = 0; i < chunk.length; i += 1) {\n const row = chunk[i];\n const result = results[i];\n // A missing/short result array for this position → treat as retryable\n // (leave pending); safer than dropping the item.\n if (!result) {\n await this.requeueRow(row, 'missing result for item');\n drained = false;\n continue;\n }\n const applied = await this.applyResult(row, result);\n drained = drained && applied;\n }\n return drained;\n }\n\n /**\n * POST a batch to `{basePath}/sync/apply`. Throws on a non-2xx or a network\n * error (the caller treats a throw as \"response lost → keep pending\"). Returns\n * the positional `results` array, or `undefined` on a malformed 200 body.\n */\n private async postBatch(\n items: SyncApplyItem[],\n ): Promise<SyncApplyItemResult[] | undefined> {\n const url = `${this.config.syncApplyBasePath}/${SYNC_APPLY_ROUTE_SEGMENTS.join('/')}`;\n const response = await this.config.fetchFn(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ items }),\n });\n if (!response.ok) {\n // HTTP 400 (bad batch) and any other non-2xx: throw so the batch stays\n // pending. A 400 on a well-formed client batch is unexpected; blind\n // replay stays safe by construction, so retrying is acceptable.\n throw new Error(`[smrt-web] sync/apply returned HTTP ${response.status}`);\n }\n const body = (await response\n .json()\n .catch(() => null)) as SyncApplyBatchResponse | null;\n if (!body || !Array.isArray(body.results)) return undefined;\n return body.results;\n }\n\n /**\n * Map one positional apply result onto a durable transition + observable\n * state, per the contract's consumer notes. See the class doc's mapping table.\n */\n private async applyResult(\n row: OutboxRow,\n result: SyncApplyItemResult,\n ): Promise<boolean> {\n if (row.seq === undefined) return true;\n\n if (result.status === 'applied') {\n await this.finishSynced(row);\n return true;\n }\n\n if (result.status === 'conflict') {\n // A conflict is a RESOLVED outcome: the server state won, the item leaves\n // the queue, its terminal observable state is `synced`, and the app is\n // notified so it can rebase from the returned updatedAt.\n const reason =\n result.reason === 'create_conflict' ? 'create_conflict' : 'stale_write';\n this.emitConflict({\n itemId: row.itemId,\n object: row.object,\n rowId: row.id,\n reason,\n serverUpdatedAt: result.updatedAt,\n });\n await this.finishSynced(row);\n return true;\n }\n\n // status === 'rejected'\n const reason = result.reason;\n if (reason === 'auth_required' || reason === 'forbidden') {\n // Pause the WHOLE loop until re-auth; keep the item queued as `pending`.\n this.paused = true;\n await this.queue?.markState(row.seq, {\n state: 'pending',\n lastError: `sync ${reason}`,\n });\n this.emit({\n itemId: row.itemId,\n rowId: row.id,\n object: row.object,\n state: 'pending',\n attempts: row.attempts,\n error: `sync ${reason}`,\n });\n return false;\n }\n\n if (reason === 'write_failed') {\n // Retryable: keep, count an attempt, back off.\n await this.requeueRow(row, 'sync write_failed');\n return false;\n }\n\n // Any other rejection (invalid_item/invalid_id/invalid_payload/\n // unknown_object/not_found/op_not_allowed/id_conflict) is terminal — a\n // retry cannot succeed. Remove and surface `failed`.\n await this.queue?.remove(row.seq);\n this.emit({\n itemId: row.itemId,\n rowId: row.id,\n object: row.object,\n state: 'failed',\n attempts: row.attempts,\n error: reason ? `sync ${reason}` : 'sync rejected',\n });\n return true;\n }\n\n /** Remove a successfully-applied (or conflict-resolved) row → `synced`. */\n private async finishSynced(row: OutboxRow): Promise<void> {\n if (row.seq !== undefined) await this.queue?.remove(row.seq);\n this.emit({\n itemId: row.itemId,\n rowId: row.id,\n object: row.object,\n state: 'synced',\n attempts: row.attempts,\n });\n }\n\n /** Re-queue every row of a failed batch (network path) with backoff. */\n private async requeueBatch(chunk: OutboxRow[], error: string): Promise<void> {\n for (const row of chunk) {\n await this.requeueRow(row, error);\n }\n }\n\n /** Re-queue one row: attempts++, backoff gate, `pending` event. */\n private async requeueRow(row: OutboxRow, error: string): Promise<void> {\n if (row.seq === undefined) return;\n const attempts = row.attempts + 1;\n const delay = computeBackoffDelay(\n attempts,\n this.config.backoff,\n this.config.random,\n );\n const nextAttemptAt = Date.now() + delay;\n await this.queue?.markState(row.seq, {\n state: 'pending',\n attempts,\n nextAttemptAt,\n lastError: error,\n });\n this.emit({\n itemId: row.itemId,\n rowId: row.id,\n object: row.object,\n state: 'pending',\n attempts,\n error,\n });\n }\n\n /**\n * Schedule the next drain for the soonest backed-off row's `nextAttemptAt`.\n * Only one timer is ever pending; a sooner schedule replaces a later one.\n */\n private async scheduleNextBackoff(): Promise<void> {\n if (this.disposed || this.paused || !this.queue) return;\n const rows = await this.queue.all();\n const firstPending = rows.find((r) => r.state === 'pending');\n if (!firstPending) return;\n const now = Date.now();\n // FIFO: the oldest pending row gates every newer row, even if a newer row\n // has no backoff delay.\n const delay = Math.max(0, firstPending.nextAttemptAt - now);\n if (this.backoffTimer) clearTimeout(this.backoffTimer);\n const timers = globalThis as {\n setTimeout?: typeof setTimeout;\n };\n if (typeof timers.setTimeout !== 'function') return;\n this.backoffTimer = timers.setTimeout(() => {\n this.backoffTimer = undefined;\n void this.drain();\n }, delay);\n // Node's timer keeps the process alive; unref so tests/SSR don't hang.\n (this.backoffTimer as { unref?: () => void }).unref?.();\n }\n\n /**\n * Tear down the in-memory engine: release leadership, remove the online\n * listener, clear timers, unregister from the durable-store registry, and\n * close IndexedDB. Does NOT clear the durable rows — they must survive to\n * replay on the next load.\n */\n private async dispose(): Promise<void> {\n if (this.disposed) return;\n this.disposed = true;\n if (this.backoffTimer) {\n clearTimeout(this.backoffTimer);\n this.backoffTimer = undefined;\n }\n const target = globalThis as {\n removeEventListener?: (t: string, l: () => void) => void;\n };\n if (\n this.onlineListener &&\n typeof target.removeEventListener === 'function'\n ) {\n target.removeEventListener('online', this.onlineListener);\n this.onlineListener = undefined;\n }\n this.leadership?.();\n this.leadership = undefined;\n this.unregisterResource?.();\n this.unregisterResource = undefined;\n // Wait for any in-flight open to settle before closing.\n await this.ready.catch(() => undefined);\n this.queue?.close();\n this.queue = undefined;\n this.listenersByObject.clear();\n }\n}\n\n/**\n * Module-scoped, ref-counted registry of engines by namespace string. This is\n * the mechanism that makes N collections sharing a namespace share ONE engine.\n * Keyed by the durable-store namespace, which already folds\n * api/tenant/identity/manifest — so a logout/tenant-switch lands on a different\n * key and a fresh engine.\n */\nconst engines = new Map<string, OutboxEngine>();\n\n/**\n * Get the shared engine for `config.namespace`, creating it on first use and\n * ref-counting it. Every collection opting into the outbox under the same\n * namespace gets the SAME engine — one IndexedDB db, one leader lock, one FIFO\n * queue. The caller MUST pair each `getOrCreateOutboxEngine(...).registerCollection()`\n * with a later `unregisterCollection()` (via `teardown`) so the engine disposes\n * when its last collection detaches.\n */\nexport function getOrCreateOutboxEngine(\n config: OutboxEngineConfig,\n): OutboxEngine {\n let engine = engines.get(config.namespace);\n if (!engine) {\n engine = new OutboxEngine(config);\n engines.set(config.namespace, engine);\n // Auto-evict from the shared registry once the engine disposes, so a later\n // collection under the same namespace gets a fresh engine rather than a\n // torn-down one. We detect disposal by wrapping unregisterCollection at the\n // call site (below in registerCollection/unregisterCollection helpers).\n }\n return engine;\n}\n\n/** The per-collection callback binding registered when a collection attaches. */\nexport interface OutboxCollectionBinding {\n object: string;\n onSyncStateChange?: (event: SyncStateEvent) => void;\n onConflict?: (conflict: OutboxConflict) => void;\n}\n\n/**\n * Attach a collection to the namespace's engine (creating it if needed),\n * register its per-`object` callbacks, and increment its ref count. Returns the\n * engine plus the exact callback `record` to hand back to\n * {@link releaseOutboxEngine} for precise removal. Pair with\n * {@link releaseOutboxEngine}.\n */\nexport function acquireOutboxEngine(\n config: OutboxEngineConfig,\n binding: OutboxCollectionBinding,\n): { engine: OutboxEngine; record: object } {\n const engine = getOrCreateOutboxEngine(config);\n const record = engine.registerCollection(binding);\n return { engine, record };\n}\n\n/**\n * Detach a collection from an engine (removing its callback record) and, if it\n * was the last one, dispose it and evict it from the shared registry so the\n * namespace starts fresh next time.\n */\nexport async function releaseOutboxEngine(\n namespace: string,\n engine: OutboxEngine,\n object: string,\n record: object,\n): Promise<void> {\n const disposed = await engine.unregisterCollection(object, record);\n if (disposed && engines.get(namespace) === engine) {\n engines.delete(namespace);\n }\n}\n\n/** Test-only: current engine count (for leak assertions). */\nexport function _outboxEngineCount(): number {\n return engines.size;\n}\n","/**\n * @happyvertical/smrt-web — the durable offline outbox capability (#1762).\n *\n * The web twin of the KMP mobile write queue: mutations against opted-in\n * collections are captured in a durable browser-side outbox (IndexedDB) that\n * survives reloads and crashes, then replayed FIFO against the idempotent\n * sync-apply batch contract when connectivity returns, with exponential-backoff\n * retries. Because items carry client-generated UUIDs and the endpoint is\n * idempotent (`_insertOnly` create + no-op re-apply), blind retries after an\n * ambiguous failure (request sent, response lost) can never duplicate rows — see\n * the \"Idempotency\" section of docs/content/architecture/sync-apply-contract.md.\n *\n * This is a {@link SmrtWebCapability} plug-in, so it lives in its OWN module and\n * an app opts a collection in by passing `offlineOutbox(config)` in that\n * collection's `capabilities` array. A collection WITHOUT it is byte-for-byte\n * the collection of today (the seam's no-op guarantee), which is exactly the\n * \"offline is opt-in per model\" acceptance criterion.\n *\n * ## How it plugs into the seam\n *\n * - `wrapMutation(envelope, ctx)` — BEFORE the fetcher, it enqueues the write\n * durably and returns `{ handled: true, result: envelope.data }`. The factory\n * then SUPPRESSES the post-mutation refetch + `invalidateRelated()` (its\n * `{ refetch: false }` path), so the optimistic row the app just inserted\n * STANDS instead of being dropped by a refetch of a server list that has never\n * seen the offline write. If the durable queue is unavailable, it returns\n * `{ handled: false }` so the normal fetcher path runs instead of silently\n * acknowledging a non-durable write. A handled write never touches\n * `ctx.fetchers.create` — it replays ONLY through `sync/apply`, which is\n * load-bearing: the normal REST create strips the client id (#1540) and would\n * mint a NEW server id, orphaning the optimistic row; sync/apply's\n * strict-insert path preserves the client UUID (#1540, #1540's\n * mass-assignment guard on the sync path).\n * - `onAttach(ctx)` — attaches this collection to the shared, namespace-keyed\n * {@link OutboxEngine} (ref-counted so N collections share ONE engine / IDB\n * db / leader lock / FIFO queue).\n * - `teardown(ctx)` — detaches; the last detach disposes the engine (the durable\n * ROWS survive for the next load).\n *\n * ## Observable state (push callbacks, not a store)\n *\n * Per-mutation sync state — `pending → uploading → synced` (and `→ failed` /\n * conflict) — is delivered through {@link OfflineOutboxConfig.onSyncStateChange}\n * and {@link OfflineOutboxConfig.onConflict}, PUSH callbacks matching the KMP\n * foundation's `pending/uploading/synced/failed` state machine. This is\n * deliberately not a subscribable store: smrt-svelte wraps it into a reactive\n * binding in a later slice. Programmatic access to the durable queue is via\n * {@link getOutboxHandle} (a bridge like `getEngineCollection`).\n *\n * ## Known gap (this slice's scope)\n *\n * The outbox does NOT rehydrate the READ cache after a reload — a reloaded tab's\n * `collection.toArray()` will NOT show captured-offline rows until a fetch runs;\n * that read-side rehydrate is #1764's `warmStart`. So the outbox's durability is\n * proven via {@link OutboxHandle.snapshot} / the raw IndexedDB store, NOT via\n * `collection.toArray()`. The WRITE side (capture → durable → exactly-once\n * replay) is complete here.\n *\n * Engine-free public surface: no `@tanstack/*` type appears — inside the\n * engine-absorption boundary (#1761), verified by\n * scripts/check-smrt-web-engine-boundary.mjs.\n */\n\nimport type { SmrtWebCapability } from './capability.js';\nimport {\n type DurableStoreKey,\n durableStoreNamespace,\n registerDurableResource,\n} from './durable-store.js';\nimport {\n acquireOutboxEngine,\n type OutboxEngine,\n releaseOutboxEngine,\n} from './offline/engine.js';\nimport {\n DEFAULT_BACKOFF,\n type OutboxBackoff,\n type OutboxConflict,\n type ResolvedBackoff,\n type SyncStateEvent,\n} from './offline/types.js';\n\nexport type {\n OutboxBackoff,\n OutboxConflict,\n OutboxSyncState,\n SyncStateEvent,\n} from './offline/types.js';\n\n/**\n * Configuration for {@link offlineOutbox}. Generic in the collection's row type\n * `TData` so the capability matches the collection it plugs into.\n */\nexport interface OfflineOutboxConfig<TData extends object = object> {\n /**\n * The generated collection definition this outbox serves — its `name` is the\n * sync-apply `object` route segment, so it MUST be the SAME definition passed\n * to {@link createSmrtCollection}. (Named `object` to mirror the capability\n * context; carries the collection's REST route segment.)\n */\n object: { name: string; _row?: TData };\n /**\n * The durable-store identity this outbox's queue is namespaced under — folds\n * api base / tenant / identity / manifest hash, so a logout or tenant switch\n * lands on a different IndexedDB database (never cross-identity reuse). Shared\n * with #1764's persistence slice via the same {@link durableStoreNamespace}.\n * `manifestHash` is opaque caller-supplied config for #1762; its canonical\n * source is #1764's call (a schema-shape digest) — comment your call site.\n */\n namespace: DurableStoreKey;\n /**\n * API base path the sync-apply endpoint lives under (`POST\n * {syncApplyBasePath}/sync/apply`). Defaults to `/api/v1`, matching the REST\n * generator's default. Set to the SvelteKit route base (`/api`) when replaying\n * against the generated SvelteKit `sync/apply/+server.ts`.\n */\n syncApplyBasePath?: string;\n /** Fetch implementation override (tests, SSR). Defaults to global fetch. */\n fetchFn?: typeof fetch;\n /** Exponential-backoff tuning for retryable replay failures. */\n backoff?: OutboxBackoff;\n /**\n * Called on every observable sync-state transition of a captured mutation\n * (`pending → uploading → synced`, `→ failed`, and the `pending` re-arm after\n * a retryable failure). A push callback; smrt-svelte turns it into a reactive\n * binding later.\n */\n onSyncStateChange?: (event: SyncStateEvent) => void;\n /**\n * Called when a replayed mutation comes back `conflict` — a RESOLVED outcome\n * (server state won; the item leaves the queue with terminal state `synced`).\n * Persist `serverUpdatedAt` as the new `baseUpdatedAt`, refetch the row, and\n * surface the conflict to the user as appropriate.\n */\n onConflict?: (conflict: OutboxConflict) => void;\n /** Test-only: inject a deterministic RNG for backoff jitter. */\n random?: () => number;\n}\n\n/**\n * A programmatic handle to one namespace's outbox — the bridge pattern (like\n * `getEngineCollection`) for trusted callers (an app's outbox panel, a\n * smrt-svelte binding, tests). Fetched via {@link getOutboxHandle}.\n */\nexport interface OutboxHandle {\n /**\n * A read-only snapshot of the durable queue (every state). The way to prove\n * durability after a reload, since this slice does not rehydrate the read\n * cache (that's #1764).\n */\n snapshot(): Promise<OutboxSnapshotItem[]>;\n /**\n * Force a retry of a specific queued item now (clears its backoff gate, wakes\n * the loop, and clears an auth pause). A no-op for an item no longer queued.\n */\n retry(itemId: string): Promise<void>;\n}\n\n/** One item in an {@link OutboxHandle.snapshot} result. */\nexport interface OutboxSnapshotItem {\n /** The queue row's idempotency handle. */\n itemId: string;\n /** The collection route segment. */\n object: string;\n /** The mutation kind, in sync-apply terms. */\n op: 'create' | 'update' | 'delete';\n /** The client-generated row UUID. */\n rowId: string;\n /** The on-disk lifecycle state. */\n state: 'pending' | 'synced' | 'failed';\n /** Replay attempts so far. */\n attempts: number;\n /** Epoch ms before which this row won't be retried (backoff). */\n nextAttemptAt: number;\n /** Last replay error, if any. */\n lastError?: string;\n}\n\n/** Fill backoff defaults. */\nfunction resolveBackoff(backoff?: OutboxBackoff): ResolvedBackoff {\n return {\n initialDelayMs: backoff?.initialDelayMs ?? DEFAULT_BACKOFF.initialDelayMs,\n multiplier: backoff?.multiplier ?? DEFAULT_BACKOFF.multiplier,\n maxDelayMs: backoff?.maxDelayMs ?? DEFAULT_BACKOFF.maxDelayMs,\n };\n}\n\n/**\n * Registry mapping a namespace string to its live engine + bridge handle, so\n * {@link getOutboxHandle} can reach an engine attached by a running collection.\n * A weak-ish bookkeeping map: entries are added on `onAttach` and removed on the\n * final `teardown`.\n */\nconst handlesByNamespace = new Map<string, OutboxEngine>();\n\n/** Extract a timestamp from a payload for direct wrapMutation callers. */\nfunction getPayloadUpdatedAt(\n data: Record<string, unknown>,\n): string | undefined {\n const value = data.updatedAt ?? data.updated_at;\n if (typeof value === 'string') return value;\n if (value instanceof Date) return value.toISOString();\n return undefined;\n}\n\n/**\n * Build a durable offline-outbox capability for a collection. Add the returned\n * capability to the collection's `capabilities` array; a collection without it\n * is unaffected (the seam's no-op guarantee — the \"opt-in per model\" AC).\n *\n * The same `namespace` across multiple collections shares ONE engine (one IDB\n * db, one leader lock, one FIFO queue) — so cross-collection ordering and the\n * multi-tab single-replayer guarantee hold across every opted-in collection of a\n * given identity.\n */\nexport function offlineOutbox<TData extends object = object>(\n config: OfflineOutboxConfig<TData>,\n): SmrtWebCapability<TData> {\n const namespace = durableStoreNamespace(config.namespace);\n const syncApplyBasePath = config.syncApplyBasePath ?? '/api/v1';\n const fetchFn =\n config.fetchFn ??\n ((...args) => globalThis.fetch(...(args as [RequestInfo])));\n const backoff = resolveBackoff(config.backoff);\n\n const object = config.object.name;\n // The engine this collection attaches to (set in onAttach, released in\n // teardown). Held here so wrapMutation can enqueue through it. `record` is the\n // exact callback binding registered, handed back on teardown for precise\n // removal (two collections on one object each detach only their own).\n let engine: OutboxEngine | undefined;\n let record: object | undefined;\n\n return {\n name: 'offline-outbox',\n\n onAttach() {\n const acquired = acquireOutboxEngine(\n {\n namespace,\n syncApplyBasePath,\n fetchFn,\n backoff,\n random: config.random,\n registerResource: (clear) =>\n registerDurableResource(namespace, { kind: 'outbox', clear }),\n },\n {\n object,\n onSyncStateChange: config.onSyncStateChange,\n onConflict: config.onConflict,\n },\n );\n engine = acquired.engine;\n record = acquired.record;\n handlesByNamespace.set(namespace, engine);\n },\n\n async wrapMutation(envelope) {\n // If onAttach hasn't run yet (shouldn't happen — wrapMutation fires only\n // after construction), fall through so the write isn't silently dropped.\n if (!engine) return { handled: false };\n const itemId = await engine.enqueue({\n kind: envelope.kind,\n object,\n rowId: envelope.key,\n data: envelope.data,\n baseUpdatedAt:\n envelope.baseUpdatedAt ?? getPayloadUpdatedAt(envelope.data),\n });\n if (!itemId) return { handled: false };\n // The optimistic row (envelope.data) stands in for the fetcher result; the\n // factory suppresses the refetch so it isn't dropped. Exactly-once replay\n // then reconciles it server-side via sync/apply.\n return { handled: true, result: envelope.data };\n },\n\n async teardown() {\n if (!engine || !record) return;\n const current = engine;\n const currentRecord = record;\n engine = undefined;\n record = undefined;\n // Detach; the final detach disposes the engine. Only drop the handle map\n // entry if THIS release actually evicted the engine (ref count hit 0).\n const before = current.referenceCount;\n await releaseOutboxEngine(namespace, current, object, currentRecord);\n if (before <= 1 && handlesByNamespace.get(namespace) === current) {\n handlesByNamespace.delete(namespace);\n }\n },\n };\n}\n\n/**\n * Retrieve the programmatic {@link OutboxHandle} for a durable-store namespace,\n * or `undefined` if no opted-in collection is currently attached under it. The\n * bridge for trusted callers (outbox UI, smrt-svelte binding, tests) to read the\n * durable queue and force retries. Pass the SAME\n * `durableStoreNamespace(config.namespace)` string the capability used.\n *\n * Mirrors the `getEngineCollection` bridge convention: an escape hatch that\n * returns a narrow SMRT-owned surface rather than the engine itself.\n */\nexport function getOutboxHandle(namespace: string): OutboxHandle | undefined {\n const engine = handlesByNamespace.get(namespace);\n if (!engine) return undefined;\n return {\n snapshot: () => engine.snapshot(),\n retry: (itemId: string) => engine.retry(itemId),\n };\n}\n","/**\n * @happyvertical/smrt-web — the durable collection snapshot store (#1764).\n *\n * The read-side twin of the offline outbox's write queue: a raw IndexedDB store\n * that persists ONE blob per (namespace, collection) — the collection's last\n * known rows — so a reload can warm-start (render stale instantly) before the\n * engine revalidates in the background. One database per durable-store namespace\n * ({@link durableStoreNamespace}), one object store (`snapshots`) keyed by the\n * collection name, each record `{ collection, rows }`.\n *\n * ## Why raw IndexedDB, mirroring offline/durable-queue.ts\n *\n * Same decisive finding as #1762's outbox: the TanStack persistence layer's\n * public API is engine-typed, so importing it would emit a `@tanstack/`\n * specifier into a `.d.ts` and fail `scripts/check-smrt-web-engine-boundary.mjs`.\n * So persistence is hand-rolled over `indexedDB` with ZERO new runtime\n * dependency and stays inside the engine-absorption boundary (#1761) — this\n * module speaks only plain rows, never an engine type.\n *\n * Rows are stored as structured-clone-safe plain objects (the public DTOs the\n * factory already projects); IndexedDB clones on write, so callers may keep\n * mutating the source array after a save.\n *\n * The namespace already folds api base / tenant / identity / manifest hash (see\n * {@link durableStoreNamespace}), so a logout, tenant switch, or contract-\n * changing deploy lands on a DIFFERENT database — a stale-schema snapshot is\n * simply never found (the read returns nothing and the engine fetches fresh),\n * which is exactly the \"manifest-hash change drops persisted caches\" guarantee\n * without any explicit invalidation.\n */\n\n/** IndexedDB object-store name inside every snapshot database. */\nexport const SNAPSHOT_STORE = 'snapshots';\n/** IndexedDB schema version. Bump only on a store/index migration. */\nexport const SNAPSHOT_DB_VERSION = 1;\n/**\n * Suffix appended to the durable-store namespace to form this store's OWN\n * IndexedDB database name. Distinct DBs per store are REQUIRED for correctness:\n * the merged outbox (#1762) already opens the BARE namespace as a v1 database\n * with only its `outbox` object store. If persistence opened the same bare\n * namespace at v1, whichever store opened SECOND would find the existing v1 DB,\n * `onupgradeneeded` would NOT fire, and `objectStore('snapshots')` would throw\n * `NotFoundError` — silently disabling persistence (or the outbox). A distinct\n * DB name sidesteps the collision entirely; `wipeDurableStore` still clears both\n * because it fans out via each registered resource's `clear()`, never by DB name.\n */\nexport const SNAPSHOT_DB_SUFFIX = '::snapshots';\n\n/** One persisted collection snapshot record. */\nexport interface SnapshotRecord {\n /** The REST collection name — the store key. */\n collection: string;\n /** The rows last seen for this collection (plain public DTOs). */\n rows: Array<Record<string, unknown>>;\n}\n\n/**\n * Promisify a single IndexedDB request. Rejects with the request's error (or a\n * generic one) so callers get a real rejection, never a silent `undefined`.\n */\nfunction promisifyRequest<T>(request: IDBRequest<T>): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n request.onsuccess = () => resolve(request.result);\n request.onerror = () =>\n reject(request.error ?? new Error('[smrt-web] IndexedDB request failed'));\n });\n}\n\n/**\n * Promisify a transaction's completion. IndexedDB writes are only durable once\n * the TRANSACTION completes (not when the request succeeds), so mutations await\n * this — a save that reported success before the txn committed could lose the\n * snapshot on a crash in the commit window.\n */\nfunction awaitTransaction(tx: IDBTransaction): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n tx.oncomplete = () => resolve();\n tx.onerror = () =>\n reject(tx.error ?? new Error('[smrt-web] IndexedDB transaction failed'));\n tx.onabort = () =>\n reject(tx.error ?? new Error('[smrt-web] IndexedDB transaction aborted'));\n });\n}\n\n/**\n * Feature-detect a usable IndexedDB. Some environments expose `indexedDB` but\n * throw on `open()` (private-mode Firefox historically, sandboxed iframes), so a\n * real probe opens a throwaway database. Returns `false` rather than throwing so\n * the persistence capability can degrade to non-persistent instead of crashing\n * the host — matching the outbox's `probeIndexedDb()` posture.\n */\nexport async function probeIndexedDb(): Promise<boolean> {\n const idb = (globalThis as { indexedDB?: IDBFactory }).indexedDB;\n if (!idb) return false;\n const probeName = '__smrt_web_snapshot_probe__';\n try {\n const db = await new Promise<IDBDatabase>((resolve, reject) => {\n const request = idb.open(probeName, 1);\n request.onsuccess = () => resolve(request.result);\n request.onerror = () =>\n reject(request.error ?? new Error('probe failed'));\n request.onblocked = () => reject(new Error('probe blocked'));\n });\n db.close();\n try {\n idb.deleteDatabase(probeName);\n } catch {\n /* ignore */\n }\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * The durable snapshot store over one IndexedDB database. Constructed via\n * {@link openSnapshotStore} (async open + schema upgrade), then used through the\n * async methods below (each resolves once its txn commits).\n *\n * One store backs EVERY opted-in collection under a namespace, each keyed by its\n * collection name — so N persisted collections of one identity share one\n * database (like the outbox's shared queue), and the durable-store `clear()`\n * wipes them all at once.\n */\nexport class SnapshotStore {\n private readonly db: IDBDatabase;\n /** The IndexedDB database name (== the durable-store namespace). */\n readonly dbName: string;\n\n constructor(db: IDBDatabase, dbName: string) {\n this.db = db;\n this.dbName = dbName;\n }\n\n /**\n * Read the persisted rows for `collection`, or `undefined` if none were ever\n * saved (or the record is malformed). `undefined` is the warm-start \"nothing\n * on disk\" signal — the engine then fetches fresh.\n */\n async load(\n collection: string,\n ): Promise<Array<Record<string, unknown>> | undefined> {\n const tx = this.db.transaction(SNAPSHOT_STORE, 'readonly');\n const record = await promisifyRequest(\n tx.objectStore(SNAPSHOT_STORE).get(collection) as IDBRequest<\n SnapshotRecord | undefined\n >,\n );\n await awaitTransaction(tx);\n if (!record || !Array.isArray(record.rows)) return undefined;\n return record.rows;\n }\n\n /**\n * Write (replacing) the snapshot for `collection`. Resolves once the write is\n * durably committed. A single blob per collection — the whole current row set,\n * not a delta — so a restore is one read with no reconciliation.\n */\n async save(\n collection: string,\n rows: Array<Record<string, unknown>>,\n ): Promise<void> {\n const tx = this.db.transaction(SNAPSHOT_STORE, 'readwrite');\n const store = tx.objectStore(SNAPSHOT_STORE);\n const record: SnapshotRecord = { collection, rows };\n await promisifyRequest(store.put(record));\n await awaitTransaction(tx);\n }\n\n /**\n * Drop the snapshot for a single `collection` (its capability's own teardown\n * does NOT clear — the persisted rows must survive for the next load; this is\n * only for an explicit targeted purge). Kept for completeness / tests.\n */\n async remove(collection: string): Promise<void> {\n const tx = this.db.transaction(SNAPSHOT_STORE, 'readwrite');\n await promisifyRequest(tx.objectStore(SNAPSHOT_STORE).delete(collection));\n await awaitTransaction(tx);\n }\n\n /**\n * Drop EVERY snapshot — the durable-store `clear()` for `wipeDurableStore`.\n * Empties the store but keeps the database so a subsequent save still works.\n */\n async clear(): Promise<void> {\n const tx = this.db.transaction(SNAPSHOT_STORE, 'readwrite');\n await promisifyRequest(tx.objectStore(SNAPSHOT_STORE).clear());\n await awaitTransaction(tx);\n }\n\n /** Close the underlying database handle (called on the last detach). */\n close(): void {\n this.db.close();\n }\n}\n\n/**\n * Open (creating/upgrading as needed) the snapshot database for `namespace` and\n * wrap it in a {@link SnapshotStore}. `namespace` MUST be a\n * {@link durableStoreNamespace} string — already `encodeURIComponent`-safe and\n * carrying the api/tenant/identity/manifest isolation boundary, so two\n * identities never share one snapshot database.\n *\n * The actual IndexedDB database name is `${namespace}${SNAPSHOT_DB_SUFFIX}` — a\n * DISTINCT database from the merged outbox's bare-namespace one, so persistence +\n * outbox under a shared namespace never collide on a single-store v1 DB (see\n * {@link SNAPSHOT_DB_SUFFIX}).\n *\n * The `onupgradeneeded` handler creates the `snapshots` store keyed by\n * `collection`. Idempotent: re-opening an existing database at the same version\n * skips the upgrade — the reload path (a new tab re-opens the same durable\n * backing and sees prior snapshots).\n */\nexport function openSnapshotStore(namespace: string): Promise<SnapshotStore> {\n const idb = (globalThis as { indexedDB?: IDBFactory }).indexedDB;\n if (!idb) {\n return Promise.reject(\n new Error('[smrt-web] IndexedDB is unavailable in this environment'),\n );\n }\n const dbName = `${namespace}${SNAPSHOT_DB_SUFFIX}`;\n return new Promise<SnapshotStore>((resolve, reject) => {\n const request = idb.open(dbName, SNAPSHOT_DB_VERSION);\n request.onupgradeneeded = () => {\n const db = request.result;\n if (!db.objectStoreNames.contains(SNAPSHOT_STORE)) {\n db.createObjectStore(SNAPSHOT_STORE, { keyPath: 'collection' });\n }\n };\n request.onsuccess = () =>\n resolve(new SnapshotStore(request.result, dbName));\n request.onerror = () =>\n reject(\n request.error ??\n new Error(`[smrt-web] failed to open snapshot database \"${dbName}\"`),\n );\n request.onblocked = () =>\n reject(\n new Error(\n `[smrt-web] opening snapshot database \"${dbName}\" was blocked`,\n ),\n );\n });\n}\n","/**\n * @happyvertical/smrt-web — the durable persistence capability (#1764).\n *\n * The read-side twin of the offline outbox (#1762): an opted-in collection\n * WARM-STARTS from a durable browser snapshot on load — rendering stale rows\n * instantly — then the engine revalidates in the background via its normal SWR.\n * A reload therefore paints last-known data with zero network round-trip before\n * the fresh fetch lands. This is the read-cache rehydrate the outbox explicitly\n * left to #1764 (see offline.ts \"Known gap\").\n *\n * This is a {@link SmrtWebCapability} plug-in, so it lives in its OWN module and\n * an app opts a collection in by passing `persistCollection(config)` in that\n * collection's `capabilities` array. A collection WITHOUT it is byte-for-byte\n * the collection of today (the seam's no-op guarantee) — nothing persists\n * implicitly, which is exactly the \"persistence is opt-in per model\" acceptance\n * criterion. Sensitive models simply omit the capability and never touch disk.\n *\n * ## How it plugs into the seam\n *\n * - `warmStart(ctx)` — reads the persisted snapshot for\n * `(durableStoreNamespace(namespace), collection)` and returns its rows to\n * seed the cache (the factory's rehydrate path; `preload()` awaits the async\n * return so it suppresses the first `list()`). If the namespace's\n * `manifestHash` changed, the key differs → NO snapshot is found → an empty\n * return → a fresh fetch with NO stale-schema hydration. So \"a manifest-hash\n * change drops persisted caches\" is AUTOMATIC via the namespace including the\n * hash — no explicit invalidation.\n * - `onAttach(ctx)` — subscribes to the collection's changes (via the seam's\n * engine-free `ctx.subscribe`) and persists the current row snapshot\n * (`ctx.snapshot`) DEBOUNCED (trailing) to avoid thrashing IndexedDB on a\n * burst of writes. Also registers the shared store as a durable resource so a\n * logout `wipeDurableStore` clears it.\n * - `teardown(ctx)` — unsubscribes, unregisters the durable resource BEFORE\n * closing IndexedDB (the #1762 discipline: a later `wipeDurableStore` is then a\n * no-op, never a double-clear), and the LAST detach closes the shared db. The\n * durable SNAPSHOT survives for the next load.\n *\n * ## Namespace segregation + logout wipe (shared with the outbox)\n *\n * The durable namespace folds api base / tenant / identity / manifest hash, so\n * switching users on one device lands on a DIFFERENT IndexedDB database — one\n * user can never read another's persisted rows. The SAME namespace is shared\n * with the outbox (#1762), so registering this store under it means a single\n * {@link wipeDurableStore} on logout clears BOTH the persisted collections AND\n * the outbox queue in one call (the \"logout wipe clears persisted data AND\n * outbox state\" AC).\n *\n * ## Degrade, don't crash\n *\n * If IndexedDB is unavailable (a probe `open()` throws — private mode, sandboxed\n * iframe), the capability `console.warn`s ONCE and behaves as non-persistent:\n * `warmStart` returns nothing (normal fetch), write-back is a no-op. It never\n * throws — matching the outbox's `probeIndexedDb()` posture.\n *\n * Engine-free public surface: no `@tanstack/*` type appears — inside the\n * engine-absorption boundary (#1761), verified by\n * scripts/check-smrt-web-engine-boundary.mjs.\n */\n\nimport type { SmrtWebCapability } from './capability.js';\nimport {\n type DurableStoreKey,\n durableStoreNamespace,\n registerDurableResource,\n} from './durable-store.js';\nimport {\n openSnapshotStore,\n probeIndexedDb,\n type SnapshotStore,\n} from './persistence/snapshot-store.js';\n\n/** Default trailing-debounce window (ms) for the write-back. */\nexport const DEFAULT_PERSIST_DEBOUNCE_MS = 250;\n\n/**\n * Configuration for {@link persistCollection}. Generic in the collection's row\n * type `TData` so the capability matches the collection it plugs into.\n */\nexport interface PersistCollectionConfig<TData extends object = object> {\n /**\n * The REST collection name this snapshot is keyed by — MUST be the SAME\n * `definition.name` passed to {@link createSmrtCollection} (it is the store\n * key under the namespace). Provided explicitly (not read off the ctx) so the\n * config is self-describing and testable.\n */\n collection: string;\n /**\n * The durable-store identity this snapshot is namespaced under — folds api\n * base / tenant / identity / manifest hash, so a logout, tenant switch, or a\n * contract-changing deploy lands on a different IndexedDB database (never\n * cross-identity reuse, and a shape change drops old snapshots). Shared with\n * the outbox (#1762) via the same {@link durableStoreNamespace}, so one\n * {@link wipeDurableStore} clears both.\n *\n * The canonical `manifestHash` source is the build-time inject — the\n * `manifestHash` constant the generated `@happyvertical/smrt-virt-web` module\n * exports (#1764). Thread it here at the call site.\n */\n namespace: DurableStoreKey;\n /**\n * Trailing-debounce window (ms) for the write-back snapshot. A burst of\n * mutations within the window collapses to ONE IndexedDB write of the final\n * row set. Default {@link DEFAULT_PERSIST_DEBOUNCE_MS}. Set 0 to write on\n * every change (tests).\n */\n debounceMs?: number;\n}\n\n/**\n * A shared, ref-counted snapshot store for one namespace: N opted-in\n * collections of a given identity share ONE IndexedDB database (like the\n * outbox's shared queue). Opened lazily on first attach, closed on last detach.\n */\ninterface SharedSnapshotEngine {\n /** The open store, once its async open resolves. */\n store: SnapshotStore | undefined;\n /** Resolves to the store (or undefined if IndexedDB is unavailable). */\n ready: Promise<SnapshotStore | undefined>;\n /** How many attached collections reference this engine. */\n refCount: number;\n /** Unregister the durable resource registered on first attach. */\n unregister: (() => void) | undefined;\n}\n\n/** Registry mapping a namespace string to its shared snapshot engine. */\nconst enginesByNamespace = new Map<string, SharedSnapshotEngine>();\n\n/** One-time IndexedDB-unavailable warning, module-wide. */\nlet warnedNoIndexedDb = false;\nfunction warnNoIndexedDbOnce(): void {\n if (warnedNoIndexedDb) return;\n warnedNoIndexedDb = true;\n // biome-ignore lint/suspicious/noConsole: smrt-web has no logger dep (TanStack-only); a degraded-to-non-persistent notice surfaces via console.warn by design (#1764)\n console.warn(\n '[smrt-web] IndexedDB is unavailable; persistence is disabled (collections behave as non-persistent).',\n );\n}\n\n/**\n * Acquire (or create) the shared snapshot engine for `namespace`, bumping its\n * ref count. On first acquire it probes IndexedDB, opens the store, and\n * registers ONE durable resource so {@link wipeDurableStore} clears every\n * persisted collection under the namespace. A probe failure resolves the engine\n * with `store: undefined` (non-persistent) and warns once.\n */\nfunction acquireSnapshotEngine(namespace: string): SharedSnapshotEngine {\n const existing = enginesByNamespace.get(namespace);\n if (existing) {\n existing.refCount += 1;\n return existing;\n }\n\n // Build the engine, then attach `ready` — the async open assigns back onto\n // `engine`, so it must exist first (avoids a use-before-assigned self-ref).\n const engine: SharedSnapshotEngine = {\n store: undefined,\n refCount: 1,\n unregister: undefined,\n ready: Promise.resolve(undefined),\n };\n engine.ready = (async () => {\n const usable = await probeIndexedDb();\n if (!usable) {\n warnNoIndexedDbOnce();\n return undefined;\n }\n try {\n const store = await openSnapshotStore(namespace);\n engine.store = store;\n // Register the shared store as a durable resource so a logout wipe clears\n // every persisted collection under this namespace at once — alongside the\n // outbox's own registered resource.\n engine.unregister = registerDurableResource(namespace, {\n kind: 'persisted-collection',\n clear: () => store.clear(),\n });\n return store;\n } catch {\n warnNoIndexedDbOnce();\n return undefined;\n }\n })();\n enginesByNamespace.set(namespace, engine);\n return engine;\n}\n\n/**\n * Release one reference to the shared engine. The LAST release unregisters the\n * durable resource (so a later wipe is a no-op) and closes the database. Awaits\n * `ready` first so a release that races the async open still tears down cleanly.\n */\nasync function releaseSnapshotEngine(namespace: string): Promise<void> {\n const engine = enginesByNamespace.get(namespace);\n if (!engine) return;\n engine.refCount -= 1;\n if (engine.refCount > 0) return;\n enginesByNamespace.delete(namespace);\n // Ensure the open settled before we unregister/close (a teardown right after\n // construction can outrun the async open).\n await engine.ready;\n // Unregister BEFORE closing so a concurrent wipeDurableStore neither\n // double-clears nor touches a closed db (the #1762 discipline).\n engine.unregister?.();\n engine.unregister = undefined;\n engine.store?.close();\n engine.store = undefined;\n}\n\n/**\n * Build a durable persistence capability for a collection. Add the returned\n * capability to the collection's `capabilities` array; a collection without it\n * is unaffected (the seam's no-op guarantee — the \"opt-in per model\" AC).\n *\n * The same `namespace` across multiple collections shares ONE snapshot database\n * (one open, one durable resource, one wipe), and each collection is keyed by\n * its own `collection` name within it.\n */\nexport function persistCollection<TData extends object = object>(\n config: PersistCollectionConfig<TData>,\n): SmrtWebCapability<TData> {\n const namespace = durableStoreNamespace(config.namespace);\n const collectionName = config.collection;\n const debounceMs = config.debounceMs ?? DEFAULT_PERSIST_DEBOUNCE_MS;\n\n // Per-collection instance state (set in onAttach, torn down in teardown).\n let engine: SharedSnapshotEngine | undefined;\n let subscription: { unsubscribe(): void } | undefined;\n let debounceTimer: ReturnType<typeof setTimeout> | undefined;\n // The latest snapshot getter, captured from ctx at attach; used by the flush\n // so it always writes the CURRENT rows.\n let readSnapshot: (() => ReadonlyArray<Record<string, unknown>>) | undefined;\n let detached = false;\n // Single-flight write-back: `flushing` is the in-progress write; `dirty` marks\n // that a change arrived while a write was running, so the flusher re-reads the\n // (possibly newer) snapshot and writes again. This makes the LAST state always\n // win regardless of how the async store-open / save promises interleave — the\n // race that a naive \"fire a flush per change\" loses.\n let flushing: Promise<void> | undefined;\n let dirty = false;\n\n const doFlush = async (): Promise<void> => {\n while (dirty && !detached) {\n dirty = false;\n if (!engine || !readSnapshot) return;\n const store = await engine.ready;\n if (detached || !store) return;\n // Clone to a plain array so the persisted snapshot is a stable copy of the\n // rows at this instant (IndexedDB clones on write, but decouple the ref set\n // now for determinism).\n const rows = readSnapshot().map((row) => ({ ...row }));\n try {\n await store.save(collectionName, rows);\n } catch {\n // Best-effort: a failed write must not break the collection. The next\n // change re-attempts; a quota error simply means the reload won't warm.\n }\n }\n };\n\n /** Kick the single-flight flusher, coalescing concurrent requests. */\n const runFlush = (): void => {\n dirty = true;\n if (flushing) return;\n flushing = doFlush().finally(() => {\n flushing = undefined;\n // A change that arrived during the final await re-armed `dirty`; run again.\n if (dirty && !detached) runFlush();\n });\n };\n\n const scheduleFlush = (): void => {\n if (detached) return;\n // ALWAYS via a timer (even at 0ms), so a pending write is CANCELABLE. This\n // is load-bearing at teardown: the engine's own cleanup() fires a\n // \"rows removed\" change BEFORE this capability's teardown unsubscribes, and\n // a synchronous flush there would persist an empty snapshot over the good\n // one. Because teardown runs right after `await collection.cleanup()` and a\n // setTimeout(0) is a macrotask, teardown's clearTimeout cancels that\n // cleanup-triggered flush before it can run. Mid-life writes fire normally.\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(\n () => {\n debounceTimer = undefined;\n runFlush();\n },\n Math.max(0, debounceMs),\n );\n // Don't keep the event loop alive solely for a pending write (Node/test\n // parity, matching the outbox's poll-timer unref).\n (debounceTimer as { unref?: () => void }).unref?.();\n };\n\n return {\n name: 'persistence',\n\n async warmStart(ctx) {\n // Rehydrate from disk. A different namespace (tenant/identity/manifest\n // change) means this collection's snapshot is in a different database and\n // simply isn't found here → undefined → the engine fetches fresh with no\n // stale-schema hydration. `ctx.definition.name` must match config.collection;\n // we key on the explicit config value (the store key) for determinism.\n const acquired = acquireSnapshotEngine(namespace);\n engine = acquired;\n // Enable write-back ONLY when the seam actually exposes a row snapshot.\n // `ctx.snapshot` is optional (a hand-built test ctx may omit it); without\n // it, `() => [] ` would persist an EMPTY snapshot over a prior good one, so\n // treat write-back as a no-op instead (readSnapshot stays undefined).\n readSnapshot = ctx.snapshot ? () => ctx.snapshot?.() ?? [] : undefined;\n const store = await acquired.ready;\n if (!store) return undefined; // non-persistent (no IndexedDB)\n const rows = await store.load(collectionName);\n if (!rows || rows.length === 0) return undefined;\n // The persisted rows are plain DTOs already carrying `id`; hand them back\n // as the seed. (SmrtWebRow<TData> = TData & { id: string }.)\n return rows as Array<TData & { id: string }>;\n },\n\n onAttach(ctx) {\n // If warmStart didn't run (shouldn't happen — it runs before construction\n // when initialData is absent; but initialData WINS and skips warmStart),\n // acquire here so write-back still persists.\n if (!engine) {\n engine = acquireSnapshotEngine(namespace);\n }\n if (!readSnapshot && ctx.snapshot) {\n readSnapshot = () => ctx.snapshot?.() ?? [];\n }\n // Write-back requires BOTH a row snapshot to read and a change stream to\n // trigger on. If the seam omits either (both are optional hooks), leave\n // persistence write-back a NO-OP — never schedule a flush that would\n // persist an empty snapshot over a prior good one (honors the optional-hook\n // contract; the real factory always populates both, so no prod impact).\n if (!ctx.snapshot || !ctx.subscribe || !readSnapshot) return;\n // Subscribe to changes and persist (debounced) on each. The seam's\n // `ctx.subscribe` mirrors the public handle's subscribeChanges (plain-DTO\n // payloads); we only need the SIGNAL, then re-read via ctx.snapshot.\n subscription = ctx.subscribe(() => scheduleFlush());\n // Persist once on attach too, so a collection seeded by initialData (which\n // skipped warmStart) still writes an initial snapshot for the next load.\n scheduleFlush();\n },\n\n async teardown() {\n // teardown runs AFTER the engine collection's own cleanup(), which fires a\n // \"rows removed\" change. Two guards keep that from persisting an empty\n // snapshot over the good one: (1) `detached = true` FIRST makes any\n // subsequent scheduleFlush a no-op; (2) clearing the debounce timer cancels\n // a flush the cleanup change scheduled just before this ran (it's a pending\n // macrotask, so clearing it here wins). The per-change write-back already\n // persisted the latest rows DURING the collection's life, so nothing is\n // lost. We then drain any in-flight write so it commits before closing.\n detached = true;\n if (debounceTimer) {\n clearTimeout(debounceTimer);\n debounceTimer = undefined;\n }\n subscription?.unsubscribe();\n subscription = undefined;\n readSnapshot = undefined;\n // Wait out any write already in flight so it durably commits.\n if (flushing) await flushing;\n const current = engine;\n engine = undefined;\n if (current) {\n await releaseSnapshotEngine(namespace);\n }\n },\n };\n}\n","/**\n * @happyvertical/smrt-web — live-updates subscriber (#1763, CLIENT half).\n *\n * The browser half of live cache invalidation: ONE app-wide subscriber that\n * turns the server's coarse change signals into collection refetches, so a\n * dashboard reflects another session's writes without a manual refresh or\n * aggressive polling. It speaks the two channels the #1763 server half\n * generated:\n *\n * - the push channel — the generated `_events` Server-Sent-Events route\n * (`packages/core/src/generators/events-route.ts`): NAMED events\n * `event: change` / `event: resync` whose `data` is `{table, operation,\n * rowId, tenantId}`, with the cursor `seq` carried ONLY in the SSE `id:`\n * field (mirrored by the browser to `MessageEvent.lastEventId`), plus\n * `event: manifest` for live contract detection. Heartbeats are\n * `: heartbeat` comment lines EventSource ignores natively.\n * - the pull channel — the generated `_changes` route\n * (`packages/core/src/generators/changes-route.ts`): `GET {changesUrl}\n * ?since=&tables=&limit=` →\n * `{changes, cursor, resyncRequired?, resyncCursor?}`, the full fallback\n * where SSE is unavailable.\n *\n * ## No row payload, no tenant logic, no authorization here\n *\n * The wire carries only a signal (table + row id + operation), never a row.\n * A signal makes the subscriber INVALIDATE — the refetch re-reads through the\n * authorized collection routes, so authorization and tenant-scoping stay\n * ENTIRELY on the read path, exactly as the server enforces (the `_events`\n * stream is auth-guarded and tenant-scoped at connection open; `_changes` is\n * tenant-scoped per request). This module therefore does NO tenant filtering\n * and needs no identity — it just maps `table → invalidate`.\n *\n * ## One app-wide instance (mirrors createSmrtWebClient)\n *\n * A consumer constructs ONE subscriber and passes it to every\n * {@link liveInvalidation} capability — the same \"one shared handle\" convention\n * as {@link createSmrtWebClient}. It is NOT auto-derived per collection: one\n * EventSource / one poll loop feeds every registered collection.\n *\n * ## Idempotent invalidation → no client-side dedup\n *\n * Re-invalidating on a replayed change (a reconnect replays the tail from the\n * cursor) is safe: invalidation only schedules a background refetch, and the\n * factory's relationship-derived invalidation is itself idempotent\n * (over-invalidation merely refetches). So the subscriber deliberately does NO\n * seq dedup — a replayed signal STILL fires, which is exactly what guarantees a\n * gap misses no invalidation. `lastSeq` is a resume cursor for the poll\n * fallback, not a dedup filter.\n *\n * Engine boundary: no `@happyvertical/smrt-*` import and no `@tanstack/*`\n * import — URLs and `fetch` arrive as config, and the refetch primitive arrives\n * as the `invalidate` callback each capability wires from `ctx.invalidate()`.\n * smrt-web has no logger dependency, so faults are surfaced via `console.warn`\n * (matching `warnCapability` / core's `deliverLocally` style).\n *\n * The server may also emit a named `manifest` event at connection open (#1859):\n * `{ manifestHash }`. When this differs from the running build hash configured\n * here, the subscriber latches `updateState.contract`.\n */\n\nimport type { SmrtWebCapability } from './capability.js';\nimport type { UpdateState } from './update-state.js';\n\n/**\n * The minimal `EventSource` surface the subscriber uses — declared here so a\n * test injects a fake without a real DOM, and so this module compiles without\n * DOM `lib` beyond the ambient global. A real `EventSource` satisfies it\n * structurally.\n */\nexport interface SmrtWebEventSource {\n /** Native reconnect fires this after (re)connect. */\n onopen: ((this: unknown, ev: unknown) => unknown) | null;\n /**\n * Fires on a stream error. A transient drop leaves `readyState` at OPEN\n * (0→…) and the browser auto-reconnects with `Last-Event-ID`; a fatal error\n * (server 401 / route disabled) leaves it CLOSED.\n */\n onerror: ((this: unknown, ev: unknown) => unknown) | null;\n /**\n * Fires only for UNNAMED (`message`) events. The `_events` frames are NAMED\n * (`change` / `resync`), so this never fires for them — the subscriber uses\n * {@link addEventListener} instead. Present only to satisfy the structural\n * type of a real EventSource.\n */\n onmessage: ((this: unknown, ev: unknown) => unknown) | null;\n /** Register a listener for a NAMED event (`change`, `resync`). */\n addEventListener(\n type: string,\n listener: (ev: { data: string; lastEventId: string }) => void,\n ): void;\n /** Close the stream (stops reconnection). */\n close(): void;\n /** `CONNECTING` (0), `OPEN` (1), or `CLOSED` (2). */\n readonly readyState: number;\n}\n\n/** EventSource.CLOSED — a fatal, non-reconnecting state. */\nconst EVENT_SOURCE_CLOSED = 2;\n\n/** Factory that constructs an {@link SmrtWebEventSource} for a URL. */\nexport type SmrtWebEventSourceFactory = (\n url: string,\n init: { withCredentials: boolean },\n) => SmrtWebEventSource | null | undefined;\n\n/** Configuration for {@link createSmrtWebEventSubscriber}. */\nexport interface SmrtWebEventSubscriberConfig {\n /** Absolute or same-origin URL of the generated `_events` SSE route. */\n eventsUrl: string;\n /** Absolute or same-origin URL of the generated `_changes` route (fallback). */\n changesUrl: string;\n /** `fetch` implementation for the polling fallback. Defaults to global fetch. */\n fetchFn?: typeof fetch;\n /**\n * Factory for the EventSource. Defaults to `new globalThis.EventSource(url,\n * { withCredentials })`, guarded by a `typeof` check — when EventSource is\n * absent (or the factory yields nothing) the subscriber starts on the polling\n * fallback instead.\n */\n eventSourceFactory?: SmrtWebEventSourceFactory;\n /** Poll interval for the `_changes` fallback (ms). Default 5000. */\n pollIntervalMs?: number;\n /** `withCredentials` for the EventSource (cookie auth). Default true. */\n withCredentials?: boolean;\n /**\n * The RUNNING build's web-collection shape digest. When paired with\n * `updateState`, a differing server hash from the `_events` manifest frame\n * latches the contract update signal (#1859).\n */\n manifestHash?: string;\n /**\n * Optional update-state primitive from `createUpdateState()`. Only its\n * contract signal is used here; bundle polling remains the caller's job.\n */\n updateState?: Pick<UpdateState, 'notifyContractUpdated'>;\n}\n\n/** Which transport a subscriber is currently using. */\nexport type SmrtWebSubscriberTransport = 'sse' | 'polling' | 'idle';\n\n/**\n * The ONE app-wide live-updates subscriber. Construct once (see\n * {@link createSmrtWebEventSubscriber}) and pass it to every\n * {@link liveInvalidation} capability.\n */\nexport interface SmrtWebEventSubscriber {\n /**\n * The live transport: `'sse'` while the EventSource is the source of truth,\n * `'polling'` when running (or downgraded to) the `_changes` fallback,\n * `'idle'` before any transport starts.\n */\n readonly transport: SmrtWebSubscriberTransport;\n /**\n * Register an invalidator for a physical table. Returns an unregister\n * function. Multiple invalidators may share a table (two live collections);\n * a `change` for that table fires them all.\n */\n registerTable(table: string, invalidate: () => void): () => void;\n /** Invalidate every registered table (a `resync` / `resyncRequired`). */\n invalidateAll(): void;\n /** Tear down the live transport and drop all registrations. */\n close(): void;\n}\n\n/**\n * Default EventSource factory: `new globalThis.EventSource(url, init)` when the\n * global exists, else `undefined` so the caller falls back to polling.\n */\nfunction defaultEventSourceFactory(\n url: string,\n init: { withCredentials: boolean },\n): SmrtWebEventSource | undefined {\n const EventSourceCtor = (\n globalThis as { EventSource?: new (u: string, i?: unknown) => unknown }\n ).EventSource;\n if (typeof EventSourceCtor !== 'function') return undefined;\n return new EventSourceCtor(url, init) as unknown as SmrtWebEventSource;\n}\n\n/**\n * One entry of a `_changes` page (mirrors core's `ChangeFeedEntry`) — the only\n * fields the subscriber reads. Declared locally so this module keeps its\n * zero-`@happyvertical/smrt-*`-import boundary.\n */\ninterface ChangeEntryLike {\n table: string;\n seq?: number;\n}\n\n/** A `_changes` page (mirrors core's `ChangeFeedPage`). */\ninterface ChangesPageLike {\n changes: ChangeEntryLike[];\n cursor: number;\n resyncRequired?: boolean;\n resyncCursor?: number;\n}\n\n/**\n * Create the app-wide live-updates subscriber. Feature-detects ONCE at\n * construction: an available EventSource connects the SSE stream; otherwise it\n * starts the `_changes` poll loop. A fatal SSE error later downgrades to\n * polling for the rest of the subscriber's life (no flap-back).\n */\nexport function createSmrtWebEventSubscriber(\n config: SmrtWebEventSubscriberConfig,\n): SmrtWebEventSubscriber {\n const {\n eventsUrl,\n changesUrl,\n fetchFn = (...args: Parameters<typeof fetch>) => globalThis.fetch(...args),\n eventSourceFactory = defaultEventSourceFactory,\n pollIntervalMs = 5000,\n withCredentials = true,\n manifestHash,\n updateState,\n } = config;\n\n // table → set of invalidators. A Set so a collection registers/unregisters\n // without positional bookkeeping and two collections coexist under one table.\n const tableInvalidators = new Map<string, Set<() => void>>();\n\n // The resume cursor: the highest seq observed (SSE) or the last `_changes`\n // cursor (polling). NOT a dedup filter — invalidation is idempotent.\n let lastSeq: number | null = null;\n\n let transport: SmrtWebSubscriberTransport = 'idle';\n let eventSource: SmrtWebEventSource | null = null;\n let pollTimer: ReturnType<typeof setInterval> | null = null;\n let closed = false;\n\n const registeredTables = (): string[] =>\n [...tableInvalidators.keys()].sort((a, b) => a.localeCompare(b));\n\n const buildChangesUrl = (since: number, tables: string[]): string => {\n const url = new URL(changesUrl, 'http://smrt.local/');\n url.searchParams.set('since', String(since));\n url.searchParams.set('tables', tables.join(','));\n if (/^[a-zA-Z][a-zA-Z\\d+\\-.]*:/.test(changesUrl)) return url.href;\n const pathQueryHash = `${url.pathname}${url.search}${url.hash}`;\n if (changesUrl.startsWith('//')) return `//${url.host}${pathQueryHash}`;\n if (changesUrl.startsWith('/')) return pathQueryHash;\n return pathQueryHash.startsWith('/')\n ? pathQueryHash.slice(1)\n : pathQueryHash;\n };\n\n const isFiniteNumber = (value: unknown): value is number =>\n typeof value === 'number' && Number.isFinite(value);\n\n /**\n * Report a fault without a logger dep (smrt-web is TanStack-only) — a\n * swallowed diagnostic in the package's fail-loud-in-the-console style.\n */\n const warn = (message: string, error?: unknown): void => {\n // biome-ignore lint/suspicious/noConsole: smrt-web has no logger dep (TanStack-only); live-subscriber faults surface via console.warn by design (#1763)\n console.warn(`[smrt-web] live subscriber: ${message}`, error);\n };\n\n /**\n * Fire one set of invalidators, each in its OWN try/catch (snapshot first so\n * an invalidator that unregisters mid-delivery can't mutate the set) — one\n * throwing invalidator never blocks its siblings.\n */\n const fireAll = (invalidators: Set<() => void> | undefined): void => {\n if (!invalidators || invalidators.size === 0) return;\n for (const invalidate of [...invalidators]) {\n try {\n invalidate();\n } catch (error) {\n warn('an invalidator threw; ignoring', error);\n }\n }\n };\n\n /** Invalidate every collection registered for `table`. */\n const invalidateTable = (table: string): void => {\n fireAll(tableInvalidators.get(table));\n };\n\n /** Invalidate every registered collection across every table. */\n const invalidateAll = (): void => {\n for (const invalidators of tableInvalidators.values()) {\n fireAll(invalidators);\n }\n };\n\n /** Keep the resume cursor from an SSE `id:` / MessageEvent.lastEventId. */\n const advanceLastSeqFromEventId = (lastEventId: string): void => {\n const seq = Number(lastEventId);\n if (Number.isFinite(seq)) lastSeq = seq;\n };\n\n /**\n * Parse a `change` frame's `data` DEFENSIVELY: malformed JSON (or a payload\n * with no string `table`) is logged and dropped — NEVER thrown back into the\n * EventSource message loop (a throw there would break subsequent delivery).\n */\n const onChange = (ev: { data: string; lastEventId: string }): void => {\n if (closed) return;\n let table: string | undefined;\n try {\n const parsed = JSON.parse(ev.data) as { table?: unknown };\n if (typeof parsed.table === 'string') table = parsed.table;\n } catch (error) {\n warn('dropping malformed change frame', error);\n return;\n }\n if (table === undefined) {\n warn('dropping change frame with no table', ev.data);\n return;\n }\n // The browser mirrors the SSE `id:` field to lastEventId; keep it as the\n // resume cursor (used only by the poll fallback), guarding a non-numeric id.\n advanceLastSeqFromEventId(ev.lastEventId);\n invalidateTable(table);\n };\n\n /**\n * A `resync` frame: the server's cursor is stale — invalidate everything and\n * keep the server-provided horizon from `id:` for any later polling downgrade.\n */\n const onResync = (ev: { data: string; lastEventId: string }): void => {\n if (closed) return;\n advanceLastSeqFromEventId(ev.lastEventId);\n invalidateAll();\n };\n\n /**\n * Connection-open server contract frame (#1859). It has no cursor and carries\n * no user data; malformed frames are dropped like malformed change frames.\n */\n const onManifest = (ev: { data: string; lastEventId: string }): void => {\n if (closed || manifestHash === undefined || !updateState) return;\n try {\n const parsed = JSON.parse(ev.data) as { manifestHash?: unknown };\n const serverHash = parsed.manifestHash;\n if (typeof serverHash !== 'string' || serverHash.length === 0) {\n warn('dropping manifest frame with no manifestHash', ev.data);\n return;\n }\n if (serverHash !== manifestHash) {\n updateState.notifyContractUpdated();\n }\n } catch (error) {\n warn('dropping malformed manifest frame', error);\n }\n };\n\n /**\n * Poll `_changes` once from the resume cursor. `resyncRequired` (HTTP 200)\n * invalidates everything, then resumes from the server-provided\n * `resyncCursor` horizon so a pruned `since=0` does not loop forever.\n * Otherwise each change invalidates its table and the cursor advances to the\n * page cursor. A fetch rejection is caught+logged; the interval keeps ticking\n * (self-heals) — a path distinct from `resyncRequired`.\n */\n const poll = async (): Promise<void> => {\n if (closed) return;\n const tables = registeredTables();\n if (tables.length === 0) return;\n try {\n const since = lastSeq ?? 0;\n const url = buildChangesUrl(since, tables);\n const response = await fetchFn(url, { credentials: 'include' });\n if (closed) return;\n const page = (await response.json()) as ChangesPageLike;\n if (closed) return;\n if (page.resyncRequired) {\n invalidateAll();\n if (isFiniteNumber(page.resyncCursor)) {\n lastSeq = page.resyncCursor;\n } else if (isFiniteNumber(page.cursor) && page.cursor > since) {\n lastSeq = page.cursor;\n } else {\n lastSeq = null;\n }\n return;\n }\n for (const change of page.changes ?? []) {\n if (typeof change.table === 'string') invalidateTable(change.table);\n }\n if (typeof page.cursor === 'number') lastSeq = page.cursor;\n } catch (error) {\n // A rejection (network down) or a bad body — logged; the interval keeps\n // ticking so the next poll self-heals.\n warn('poll failed; will retry on the next interval', error);\n }\n };\n\n /** Start the `_changes` poll loop — the full fallback / downgrade target. */\n const startPolling = (): void => {\n if (closed || transport === 'polling') return;\n transport = 'polling';\n pollTimer = setInterval(() => {\n void poll();\n }, pollIntervalMs);\n // Don't keep the event loop alive solely for polling (Node/test parity).\n (pollTimer as { unref?: () => void }).unref?.();\n };\n\n /**\n * Connect the SSE stream. The frames are NAMED events, so `change`/`resync`\n * are wired via `addEventListener` — `onmessage` would NEVER fire for them.\n * A transient `onerror` needs no code (EventSource auto-reconnects with\n * Last-Event-ID); a FATAL error (readyState CLOSED — server 401 / route\n * disabled) downgrades to polling once and stays there.\n */\n const connectSse = (source: SmrtWebEventSource): void => {\n transport = 'sse';\n eventSource = source;\n source.addEventListener('change', onChange);\n source.addEventListener('resync', onResync);\n source.addEventListener('manifest', onManifest);\n source.onerror = () => {\n if (closed) return;\n // Only a fatal (CLOSED) error means SSE is unavailable for good — fall\n // back to polling. A transient drop (still CONNECTING/OPEN) is handled\n // natively by the browser's reconnect; do nothing.\n if (source.readyState === EVENT_SOURCE_CLOSED) {\n // Close the dead source and stop listening to it, then downgrade. No\n // flap-back: once polling, we never re-attempt SSE.\n try {\n source.close();\n } catch {\n // ignore — already dead\n }\n eventSource = null;\n startPolling();\n }\n };\n };\n\n // Feature-detect ONCE. An EventSource we can construct → SSE; otherwise (or\n // if the factory yields nothing) → polling.\n const initialSource = eventSourceFactory(eventsUrl, { withCredentials });\n if (initialSource) {\n connectSse(initialSource);\n } else {\n startPolling();\n }\n\n return {\n get transport() {\n return transport;\n },\n registerTable(table, invalidate) {\n let set = tableInvalidators.get(table);\n if (!set) {\n set = new Set<() => void>();\n tableInvalidators.set(table, set);\n }\n set.add(invalidate);\n return () => {\n const current = tableInvalidators.get(table);\n if (!current) return;\n current.delete(invalidate);\n if (current.size === 0) tableInvalidators.delete(table);\n };\n },\n invalidateAll,\n close() {\n if (closed) return;\n closed = true;\n if (eventSource) {\n try {\n eventSource.close();\n } catch {\n // ignore — best-effort teardown\n }\n eventSource = null;\n }\n if (pollTimer) {\n clearInterval(pollTimer);\n pollTimer = null;\n }\n tableInvalidators.clear();\n transport = 'idle';\n },\n };\n}\n\n/** Configuration for the {@link liveInvalidation} capability. */\nexport interface LiveInvalidationConfig {\n /** The one app-wide subscriber from {@link createSmrtWebEventSubscriber}. */\n subscriber: Pick<\n SmrtWebEventSubscriber,\n 'registerTable' | 'invalidateAll' | 'transport'\n >;\n /**\n * The PHYSICAL table name this collection reads. EXPLICIT because a\n * `SmrtWebCollectionDefinition` has no physical-table field and STI children\n * share one base table — the subscriber keys signals by physical table, so a\n * guess would mis-route invalidations.\n */\n tableName: string;\n}\n\n/**\n * A thin per-collection capability that subscribes THIS collection to live\n * signals for its `tableName`. On attach it registers `ctx.invalidate` (the\n * factory's relationship-derived refetch primitive) with the shared subscriber;\n * on teardown it unregisters. All transport, reconnection, and fan-out live in\n * the {@link createSmrtWebEventSubscriber}; this capability is just the wire\n * between one collection and that one subscriber.\n */\nexport function liveInvalidation<TData extends object = object>(\n config: LiveInvalidationConfig,\n): SmrtWebCapability<TData> {\n const { subscriber, tableName } = config;\n let unregister: (() => void) | undefined;\n return {\n name: 'live-invalidation',\n onAttach(ctx) {\n unregister = subscriber.registerTable(tableName, () => ctx.invalidate());\n },\n teardown() {\n unregister?.();\n unregister = undefined;\n },\n };\n}\n","/**\n * @happyvertical/smrt-web — the durable version-meta store (#1764).\n *\n * A tiny raw IndexedDB key/value store for version-awareness bookkeeping —\n * currently the single \"last-seen manifestHash\" per durable-store namespace,\n * which contract-change detection ({@link ../update-state}) compares the running\n * build against across loads. One database per namespace\n * ({@link durableStoreNamespace}), one object store (`meta`) keyed by a string\n * key, each record `{ key, value }`.\n *\n * Separate from the collection snapshot store (persistence/snapshot-store.ts):\n * that one holds per-collection row blobs; this holds scalar version state, and\n * update-awareness must work even for an app that persists no collection. Both\n * register under the SAME namespace so one {@link wipeDurableStore} clears\n * everything on logout.\n *\n * Raw IndexedDB, ZERO new runtime dependency — inside the engine-absorption\n * boundary (#1761), mirroring offline/durable-queue.ts.\n */\n\n/** IndexedDB object-store name inside every version-meta database. */\nexport const META_STORE = 'meta';\n/** IndexedDB schema version. Bump only on a store/index migration. */\nexport const META_DB_VERSION = 1;\n/**\n * Suffix appended to the durable-store namespace to form this store's OWN\n * IndexedDB database name — DISTINCT from both the merged outbox's bare-namespace\n * DB and the snapshot store's `::snapshots` DB. Required so persistence + outbox\n * + version-meta under one shared namespace never collide on a single-store v1\n * database (a second opener would find the existing v1 DB, skip\n * `onupgradeneeded`, and throw `NotFoundError` on `objectStore('meta')`).\n * `wipeDurableStore` still clears all three via each registered `clear()`.\n */\nexport const META_DB_SUFFIX = '::meta';\n/** The reserved key under which the last-seen manifest hash is stored. */\nexport const LAST_SEEN_MANIFEST_HASH_KEY = 'lastSeenManifestHash';\n\n/** One key/value meta record. */\ninterface MetaRecord {\n key: string;\n value: string;\n}\n\n/** Promisify a single IndexedDB request. */\nfunction promisifyRequest<T>(request: IDBRequest<T>): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n request.onsuccess = () => resolve(request.result);\n request.onerror = () =>\n reject(request.error ?? new Error('[smrt-web] IndexedDB request failed'));\n });\n}\n\n/** Promisify a transaction's durable completion. */\nfunction awaitTransaction(tx: IDBTransaction): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n tx.oncomplete = () => resolve();\n tx.onerror = () =>\n reject(tx.error ?? new Error('[smrt-web] IndexedDB transaction failed'));\n tx.onabort = () =>\n reject(tx.error ?? new Error('[smrt-web] IndexedDB transaction aborted'));\n });\n}\n\n/**\n * The durable version-meta store over one IndexedDB database. Constructed via\n * {@link openVersionMetaStore}; used through the async methods below.\n */\nexport class VersionMetaStore {\n private readonly db: IDBDatabase;\n /** The IndexedDB database name (== the durable-store namespace). */\n readonly dbName: string;\n\n constructor(db: IDBDatabase, dbName: string) {\n this.db = db;\n this.dbName = dbName;\n }\n\n /** Read the value for `key`, or `undefined` if unset / malformed. */\n async get(key: string): Promise<string | undefined> {\n const tx = this.db.transaction(META_STORE, 'readonly');\n const record = await promisifyRequest(\n tx.objectStore(META_STORE).get(key) as IDBRequest<MetaRecord | undefined>,\n );\n await awaitTransaction(tx);\n return record && typeof record.value === 'string'\n ? record.value\n : undefined;\n }\n\n /** Write (replacing) the value for `key`; resolves once durably committed. */\n async set(key: string, value: string): Promise<void> {\n const tx = this.db.transaction(META_STORE, 'readwrite');\n const record: MetaRecord = { key, value };\n await promisifyRequest(tx.objectStore(META_STORE).put(record));\n await awaitTransaction(tx);\n }\n\n /**\n * Drop EVERY meta record — the durable-store `clear()` for\n * {@link wipeDurableStore}, so a logout also clears the last-seen manifest\n * hash (the AC \"wipe clears the last-seen-hash record\").\n */\n async clear(): Promise<void> {\n const tx = this.db.transaction(META_STORE, 'readwrite');\n await promisifyRequest(tx.objectStore(META_STORE).clear());\n await awaitTransaction(tx);\n }\n\n /** Close the underlying database handle. */\n close(): void {\n this.db.close();\n }\n}\n\n/**\n * Open (creating/upgrading as needed) the version-meta database for `namespace`\n * and wrap it in a {@link VersionMetaStore}. `namespace` MUST be a\n * {@link durableStoreNamespace} string. The actual IndexedDB database name is\n * `${namespace}${META_DB_SUFFIX}` — a DISTINCT database from the outbox's and\n * the snapshot store's, so the three never collide (see {@link META_DB_SUFFIX}).\n */\nexport function openVersionMetaStore(\n namespace: string,\n): Promise<VersionMetaStore> {\n const idb = (globalThis as { indexedDB?: IDBFactory }).indexedDB;\n if (!idb) {\n return Promise.reject(\n new Error('[smrt-web] IndexedDB is unavailable in this environment'),\n );\n }\n const dbName = `${namespace}${META_DB_SUFFIX}`;\n return new Promise<VersionMetaStore>((resolve, reject) => {\n const request = idb.open(dbName, META_DB_VERSION);\n request.onupgradeneeded = () => {\n const db = request.result;\n if (!db.objectStoreNames.contains(META_STORE)) {\n db.createObjectStore(META_STORE, { keyPath: 'key' });\n }\n };\n request.onsuccess = () =>\n resolve(new VersionMetaStore(request.result, dbName));\n request.onerror = () =>\n reject(\n request.error ??\n new Error(`[smrt-web] failed to open meta database \"${dbName}\"`),\n );\n request.onblocked = () =>\n reject(\n new Error(`[smrt-web] opening meta database \"${dbName}\" was blocked`),\n );\n });\n}\n","/**\n * @happyvertical/smrt-web — the framework-free `updateAvailable` primitive (#1764).\n *\n * A tiny pub/sub carrying an app's \"an update is available\" state, driven by TWO\n * INDEPENDENT signals so the presentation (a toast / reload prompt, shipped as a\n * Svelte binding in smrt-svelte) can react to either:\n *\n * - **bundle** — the client BUNDLE changed on the server (a new deploy of the\n * front-end assets). SvelteKit already detects this via its native `updated`\n * store (version.json polling); the consumer/smrt-svelte binding pushes it in\n * here through {@link UpdateState.notifyBundleUpdated}. This module owns no\n * polling — it just holds the state and fans out.\n * - **contract** — the API CONTRACT changed: the manifest hash, which only SMRT\n * knows. Detected here under the BUILD-TIME-INJECT decision (#1764): on init\n * we compare the RUNNING build's `manifestHash` (passed in by the consumer,\n * imported from `@happyvertical/smrt-virt-web`) against a persisted\n * \"last-seen manifestHash\" in durable storage. If they differ, the contract\n * changed across this load → the `contract` signal fires and the new value is\n * stored. So a returning user whose app was rebuilt against a new schema is\n * told to reload.\n *\n * `updateAvailable` is simply `bundle || contract` — either signal means \"an\n * update is available\".\n *\n * ## Scope: load-time contract detection only (documented follow-up)\n *\n * Contract detection here fires at MOST once per load (the compare-on-init).\n * LIVE-while-open contract detection — learning mid-session that the server\n * redeployed a new contract, via the `_events` SSE manifest frame — pushes the\n * same sticky `contract` signal through {@link UpdateState.notifyContractUpdated}.\n *\n * ## Durable last-seen storage + logout wipe\n *\n * The last-seen hash lives in the version-meta store\n * (update-state/meta-store.ts) under the durable-store namespace, registered as\n * a durable resource so a logout {@link wipeDurableStore} clears it too (the AC\n * \"wipe clears the last-seen-hash record\"). If IndexedDB is unavailable the\n * primitive still works for the bundle signal; contract detection degrades to a\n * no-op (no durable baseline to compare against) rather than throwing.\n *\n * Engine-free (no `@tanstack/*` import) and framework-free (no Svelte/DOM) — the\n * reactive binding is a separate smrt-svelte adapter (#1764 PART E).\n */\n\nimport {\n type DurableStoreKey,\n durableStoreNamespace,\n registerDurableResource,\n} from './durable-store.js';\nimport {\n LAST_SEEN_MANIFEST_HASH_KEY,\n openVersionMetaStore,\n type VersionMetaStore,\n} from './update-state/meta-store.js';\n\n/** The two independent update signals plus their derived union. */\nexport interface UpdateAvailableState {\n /** The client bundle changed on the server (SvelteKit `updated`). */\n readonly bundle: boolean;\n /** The API contract (manifest hash) changed across this load. */\n readonly contract: boolean;\n /** `bundle || contract` — either means an update is available. */\n readonly updateAvailable: boolean;\n}\n\n/** Configuration for {@link createUpdateState}. */\nexport interface UpdateStateConfig {\n /**\n * The RUNNING build's web-collection shape digest — the `manifestHash`\n * constant the generated `@happyvertical/smrt-virt-web` module exports\n * (#1764). Compared against the persisted last-seen value to detect a contract\n * change across loads. Omit to disable contract detection (bundle-only).\n */\n manifestHash?: string;\n /**\n * The durable-store identity the last-seen manifest hash is namespaced under —\n * folds api base / tenant / identity (and, yes, the manifest hash itself, but\n * see below). Shared with persistence/outbox so one {@link wipeDurableStore}\n * clears the last-seen record too.\n *\n * NOTE the namespace's OWN `manifestHash` segment should be a STABLE value\n * (e.g. the app's api-version or a constant), NOT the running build hash —\n * otherwise a contract change would move the namespace and the compare would\n * never find the prior value (always \"first run\"). Contract change is detected\n * by the STORED VALUE under a stable namespace, not by the namespace moving.\n */\n namespace: DurableStoreKey;\n}\n\n/** The framework-free update-available primitive. */\nexport interface UpdateState {\n /** The current signal state (a fresh immutable snapshot). */\n get(): UpdateAvailableState;\n /**\n * Subscribe to state changes; the callback fires immediately with the current\n * state and again on every transition. Returns an unsubscribe function.\n */\n subscribe(callback: (state: UpdateAvailableState) => void): () => void;\n /**\n * Push the BUNDLE signal — call from the consumer/smrt-svelte binding when\n * SvelteKit's `updated` store flips true. Idempotent: once set it stays set\n * (a bundle update does not un-happen).\n */\n notifyBundleUpdated(): void;\n /**\n * Push the API CONTRACT signal — call when a live transport observes a server\n * `manifestHash` that differs from this running build (#1859). Idempotent:\n * once set it stays set (a contract mismatch does not un-happen).\n */\n notifyContractUpdated(): void;\n /**\n * Resolves once the async contract detection has settled (compared the running\n * hash against the persisted last-seen value and fired the signal if needed).\n * Primarily for tests/SSR; UI code just subscribes.\n */\n readonly ready: Promise<void>;\n /** Detach the durable resource (does NOT wipe — that's wipeDurableStore). */\n dispose(): void;\n}\n\n/**\n * Create the framework-free `updateAvailable` primitive. Kicks off async\n * contract detection immediately (compare running vs. persisted manifest hash);\n * {@link UpdateState.notifyBundleUpdated} feeds the bundle signal, and\n * {@link UpdateState.notifyContractUpdated} feeds the live contract signal. A\n * change to EITHER signal notifies subscribers.\n */\nexport function createUpdateState(config: UpdateStateConfig): UpdateState {\n const namespace = durableStoreNamespace(config.namespace);\n let bundle = false;\n let contract = false;\n const subscribers = new Set<(state: UpdateAvailableState) => void>();\n\n // The open meta store + its durable-resource unregister, so dispose() can\n // detach and a wipe can clear the last-seen hash.\n let metaStore: VersionMetaStore | undefined;\n let unregister: (() => void) | undefined;\n let disposed = false;\n\n const snapshot = (): UpdateAvailableState => ({\n bundle,\n contract,\n updateAvailable: bundle || contract,\n });\n\n const notify = (): void => {\n const state = snapshot();\n for (const callback of [...subscribers]) {\n try {\n callback(state);\n } catch (error) {\n // biome-ignore lint/suspicious/noConsole: smrt-web has no logger dep (TanStack-only); a throwing update subscriber surfaces via console.warn by design (#1764)\n console.warn('[smrt-web] updateAvailable subscriber threw', error);\n }\n }\n };\n\n const setBundle = (): void => {\n if (bundle) return;\n bundle = true;\n notify();\n };\n const setContract = (): void => {\n if (contract) return;\n contract = true;\n notify();\n };\n\n // Async contract detection under BUILD-TIME INJECT (#1764): compare the\n // running build's manifestHash against the persisted last-seen value. On a\n // mismatch fire `contract` and store the new value; on first-ever run just\n // store it (no signal). Degrades to a no-op if IndexedDB is unavailable or no\n // running hash was supplied.\n const ready: Promise<void> = (async () => {\n const runningHash = config.manifestHash;\n if (runningHash === undefined) return;\n let store: VersionMetaStore;\n try {\n store = await openVersionMetaStore(namespace);\n } catch {\n // No durable baseline available → cannot detect a contract change. The\n // bundle signal still works; this is a graceful degrade, not an error.\n return;\n }\n if (disposed) {\n store.close();\n return;\n }\n metaStore = store;\n // Register so a logout wipe clears the last-seen hash too (shared namespace\n // with persistence + outbox).\n unregister = registerDurableResource(namespace, {\n kind: 'persisted-collection',\n clear: () => store.clear(),\n });\n let lastSeen: string | undefined;\n try {\n lastSeen = await store.get(LAST_SEEN_MANIFEST_HASH_KEY);\n } catch {\n lastSeen = undefined;\n }\n if (disposed) return;\n if (lastSeen !== undefined && lastSeen !== runningHash) {\n // The contract changed across this load — signal, then persist the new\n // baseline so a subsequent load does not re-fire for the same change.\n setContract();\n }\n if (lastSeen !== runningHash) {\n try {\n await store.set(LAST_SEEN_MANIFEST_HASH_KEY, runningHash);\n } catch {\n /* best-effort */\n }\n }\n })();\n\n return {\n get: snapshot,\n subscribe(callback) {\n subscribers.add(callback);\n // Fire immediately with the current state (store-like ergonomics).\n try {\n callback(snapshot());\n } catch (error) {\n // biome-ignore lint/suspicious/noConsole: smrt-web has no logger dep (TanStack-only); a throwing update subscriber surfaces via console.warn by design (#1764)\n console.warn('[smrt-web] updateAvailable subscriber threw', error);\n }\n return () => {\n subscribers.delete(callback);\n };\n },\n notifyBundleUpdated: setBundle,\n notifyContractUpdated: setContract,\n ready,\n dispose() {\n disposed = true;\n unregister?.();\n unregister = undefined;\n metaStore?.close();\n metaStore = undefined;\n subscribers.clear();\n },\n };\n}\n","/**\n * WebMCP browser tool registration (#1812, tracer).\n *\n * Registers each collection's generated {@link WebToolDescriptor}s with the\n * browser's WebMCP registry (`document.modelContext.registerTool`) so an\n * in-page AI agent can discover and invoke them — see\n * https://developer.chrome.com/docs/ai/webmcp. Each tool's `execute` runs\n * through the collection's REST fetchers AS THE PAGE'S AUTHENTICATED USER, so\n * the generated REST guards (auth, tenant gate #1554, writable + sensitive-field\n * policy #1540) are the security boundary — nothing is re-implemented here.\n *\n * Framework-agnostic and engine-free: this module imports no UI framework and no\n * client-data engine. `document.modelContext` is a browser global, not a\n * dependency. Reads/writes go through {@link createDefinitionFetchers} (plain\n * fetch), so registering tools never drags the TanStack engine onto a page.\n *\n * TRACER SCOPE: CRUD actions (list/get/create/update/delete) are fully wired;\n * custom actions return a clear \"not wired\" payload. The full slice can route\n * `execute` through the shared-client collection instead of raw fetchers so\n * agent mutations reflect in on-page live collections (cache coherence).\n */\n\nimport {\n createDefinitionFetchers,\n type SmrtCrudFetchers,\n type SmrtWebCollectionDefinition,\n unwrapItemResult,\n unwrapListResult,\n} from './index.js';\n\n/** The subset of Chrome's WebMCP `registerTool` input this tracer emits. */\ninterface WebMcpToolRegistration {\n name: string;\n description: string;\n inputSchema: Record<string, unknown>;\n annotations?: { readOnlyHint?: boolean; untrustedContentHint?: boolean };\n execute: (args: Record<string, unknown>) => Promise<string> | string;\n}\n\n/** The slice of `document.modelContext` this module depends on. */\ninterface ModelContextLike {\n // WebMCP removes a tool via an AbortSignal passed as the second arg, NOT via a\n // returned handle — see https://developer.chrome.com/docs/ai/webmcp/imperative-api.\n registerTool: (\n tool: WebMcpToolRegistration,\n options?: { signal?: AbortSignal },\n ) => void;\n}\n\n/**\n * Feature-detect `document.modelContext`. Returns undefined off-WebMCP (SSR,\n * non-Chrome, or Chrome without the origin trial) so every entry point no-ops.\n */\nfunction getModelContext(): ModelContextLike | undefined {\n const doc = (globalThis as { document?: { modelContext?: unknown } })\n .document;\n const mc = doc?.modelContext;\n if (mc && typeof (mc as ModelContextLike).registerTool === 'function') {\n return mc as ModelContextLike;\n }\n return undefined;\n}\n\nexport interface RegisterWebMcpToolsOptions {\n /** REST base path for the fetchers (default `/api/v1`). */\n basePath?: string;\n /** Injectable fetch (tests / SSR-safe wrappers). */\n fetchFn?: typeof fetch;\n /**\n * Override how a definition's CRUD fetchers are built. Defaults to\n * {@link createDefinitionFetchers}; the primary seam for testing `execute`\n * without a live server.\n */\n resolveFetchers?: (\n definition: SmrtWebCollectionDefinition,\n ) => SmrtCrudFetchers;\n /** Predicate to include/exclude individual tools (e.g. reads-only surfaces). */\n filter?: (\n definition: SmrtWebCollectionDefinition,\n descriptor: NonNullable<\n SmrtWebCollectionDefinition['toolDescriptors']\n >[number],\n ) => boolean;\n}\n\n/**\n * Register every collection's generated tool descriptors with WebMCP.\n *\n * @returns a disposer that deregisters all tools this call registered. On a\n * browser without WebMCP the call is a no-op and the disposer is inert.\n */\nexport function registerWebMcpTools(\n definitions: SmrtWebCollectionDefinition[],\n options: RegisterWebMcpToolsOptions = {},\n): () => void {\n const ctx = getModelContext();\n if (!ctx) return () => {};\n\n const basePath = options.basePath ?? '/api/v1';\n // ONE controller deregisters every tool this call registers: WebMCP removes a\n // tool when the signal it was registered with aborts, so the returned disposer\n // simply aborts. Idempotent — a second call is a harmless no-op.\n const controller = new AbortController();\n\n for (const definition of definitions) {\n const descriptors = definition.toolDescriptors;\n if (!descriptors || descriptors.length === 0) continue;\n\n const fetchers = options.resolveFetchers\n ? options.resolveFetchers(definition)\n : createDefinitionFetchers(definition, basePath, options.fetchFn);\n\n for (const descriptor of descriptors) {\n if (options.filter && !options.filter(definition, descriptor)) continue;\n\n ctx.registerTool(\n {\n name: descriptor.name,\n description: descriptor.description,\n inputSchema: descriptor.inputSchema,\n annotations: { readOnlyHint: descriptor.readOnly },\n execute: (args) =>\n dispatch(fetchers, definition, descriptor.action, args ?? {}),\n },\n { signal: controller.signal },\n );\n }\n }\n\n return () => controller.abort();\n}\n\n/** Require and return a string `id` from tool args, or throw a clear error. */\nfunction requireId(args: Record<string, unknown>, action: string): string {\n const id = args.id;\n if (typeof id !== 'string' || id.length === 0) {\n throw new Error(`WebMCP ${action} requires a string 'id' argument`);\n }\n return id;\n}\n\n/**\n * Resolve a `get` identifier: `id`, or `slug` as a fallback. The generated REST\n * route and `collection.get()` both resolve either, and the get tool schema\n * advertises both, so a slug-only call must work.\n */\nfunction requireIdentifier(args: Record<string, unknown>): string {\n const value = args.id ?? args.slug;\n if (typeof value !== 'string' || value.length === 0) {\n throw new Error(\"WebMCP get requires a string 'id' or 'slug' argument\");\n }\n return value;\n}\n\n/** Narrow the list-tool args to the fetcher's query params. */\nfunction listParams(args: Record<string, unknown>): Record<string, unknown> {\n const params: Record<string, unknown> = {};\n if (args.limit !== undefined) params.limit = args.limit;\n if (args.offset !== undefined) params.offset = args.offset;\n if (args.orderBy !== undefined) params.orderBy = args.orderBy;\n if (args.where !== undefined) params.where = args.where;\n return params;\n}\n\n/**\n * Route a tool call to the collection's REST fetchers and return a STRING —\n * WebMCP's `execute` contract. Reuses the package's existing payload\n * normalization ({@link unwrapListResult} / {@link unwrapItemResult}), so\n * `{ error }` bodies surface as thrown `SmrtWebRequestError`s the agent sees.\n */\nasync function dispatch(\n fetchers: SmrtCrudFetchers,\n definition: SmrtWebCollectionDefinition,\n action: string,\n args: Record<string, unknown>,\n): Promise<string> {\n switch (action) {\n case 'list': {\n const rows = unwrapListResult(\n await fetchers.list(listParams(args)),\n definition.name,\n );\n return JSON.stringify(rows);\n }\n\n case 'get': {\n if (!fetchers.get)\n throw new Error(`${definition.name} has no get action`);\n const row = unwrapItemResult(\n await fetchers.get(requireIdentifier(args)),\n `get(${definition.name})`,\n );\n return JSON.stringify(row);\n }\n\n case 'create': {\n const row = unwrapItemResult(\n await fetchers.create(args),\n `create(${definition.name})`,\n );\n return JSON.stringify(row);\n }\n\n case 'update': {\n if (!fetchers.update) {\n throw new Error(`${definition.name} has no update action`);\n }\n const { id: _id, ...body } = args;\n const row = unwrapItemResult(\n await fetchers.update(requireId(args, 'update'), body),\n `update(${definition.name})`,\n );\n return JSON.stringify(row);\n }\n\n case 'delete': {\n if (!fetchers.delete) {\n throw new Error(`${definition.name} has no delete action`);\n }\n await fetchers.delete(requireId(args, 'delete'));\n return JSON.stringify({ success: true });\n }\n\n default:\n // Custom actions (e.g. invoice_record_payment) hit bespoke REST routes not\n // covered by the CRUD fetchers. Wired in the full slice (#1812).\n return JSON.stringify({\n error: `WebMCP custom action '${action}' is not wired in the tracer`,\n action,\n collection: definition.name,\n });\n }\n}\n","/**\n * @happyvertical/smrt-web — browser client data runtime (#1761).\n *\n * A typed collection factory that materializes the manifest-generated web\n * collection definitions (`@happyvertical/smrt-virt-web`) as cached, reactive\n * collections over the generated SMRT REST surface.\n *\n * This package is the **engine-absorption boundary**: the client-data engine\n * (currently TanStack DB) is an implementation detail held entirely inside\n * this module. Its types never appear on the public API — collections are\n * handed back as the SMRT-owned {@link SmrtWebCollection}, and the shared cache\n * as the opaque {@link SmrtWebClient} — so the engine stays swappable without a\n * consumer-visible break. Consumers never import `@tanstack/*` directly.\n *\n * Framework-agnostic by construction: this entry imports no UI framework.\n * Svelte live-query bindings ship separately (see PRD #1755) so this core never\n * pulls the Svelte-only `@tanstack/svelte-db` export condition.\n *\n * Scope of this slice:\n * - stale-while-revalidate reads (a `staleTimeMs` window, background revalidation)\n * - concurrent-read dedup (one network request per in-flight collection load)\n * - optimistic create that persists through the generated REST surface and\n * rolls back automatically when the server errors\n * - relationship-derived invalidation (#1761): a settled mutation invalidates\n * the caches of the collections related to the mutated one, with the edges\n * derived from the manifest (`definition.relationships`) — no hand-wired\n * cache keys. Cross-collection reach requires a shared client from\n * {@link createSmrtWebClient}; with a private client only the mutated\n * collection refetches.\n * - hydration seeding (#1761): rows fetched server-side (a SvelteKit\n * `+page.server.ts` load) seed the shared cache via\n * {@link CreateSmrtCollectionOptions.initialData}, so the first client read\n * serves them WITHOUT a duplicate first-render fetch.\n *\n * Deliberately NOT here yet (see PRD #1755): offline outbox, SSE invalidation,\n * persistence, version awareness.\n */\n\nimport { createCollection } from '@tanstack/db';\nimport { QueryClient } from '@tanstack/query-core';\nimport { queryCollectionOptions } from '@tanstack/query-db-collection';\n\nimport type {\n SmrtWebCapability,\n SmrtWebCapabilityContext,\n SmrtWebMutationEnvelope,\n} from './capability.js';\nimport { runWrapMutation } from './capability.js';\n\n// Re-export the capability seam (#1755) and the shared durable-store foundation\n// through this single entry — the package ships one export subpath, so both are\n// reachable as `@happyvertical/smrt-web`. Kept in their own modules so each is\n// reviewable and testable on its own; surfaced here for consumers-to-be (the\n// offline outbox #1762, persistence #1764, and live SSE invalidation\n// #1763-client slices).\nexport type {\n MutationSettleOutcome,\n SmrtWebCapability,\n SmrtWebCapabilityContext,\n SmrtWebMutationEnvelope,\n WrapMutationOutcome,\n} from './capability.js';\nexport { runWrapMutation } from './capability.js';\nexport type { DurableResource, DurableStoreKey } from './durable-store.js';\nexport {\n durableStoreNamespace,\n registerDurableResource,\n wipeDurableStore,\n} from './durable-store.js';\n// The durable offline outbox capability (#1762) — the first concrete capability\n// over the seam above. A hand-rolled IndexedDB queue + Web Locks leader election\n// (NOT @tanstack/offline-transactions, which would leak an engine type into the\n// .d.ts and fail the boundary check). Surfaced here as the root barrel; no\n// subpath. The public surface is engine-free by construction.\nexport type {\n OfflineOutboxConfig,\n OutboxBackoff,\n OutboxConflict,\n OutboxHandle,\n OutboxSnapshotItem,\n OutboxSyncState,\n SyncStateEvent,\n} from './offline.js';\nexport { getOutboxHandle, offlineOutbox } from './offline.js';\n// Durable persistence capability (#1764): an opted-in collection warm-starts\n// from a durable IndexedDB snapshot on load, then revalidates in the background;\n// namespace-keyed (api/tenant/identity/manifest hash) so a contract-changing\n// deploy drops old caches and no user reads another's rows. Its own module for\n// the same reason as the seam — reviewable/testable on its own (real\n// fake-indexeddb + real engine). Engine-free public surface.\nexport type { PersistCollectionConfig } from './persistence.js';\nexport {\n DEFAULT_PERSIST_DEBOUNCE_MS,\n persistCollection,\n} from './persistence.js';\n// Live-updates subscriber (#1763-client): the ONE app-wide SSE `_events`\n// subscriber (with `_changes` polling fallback) and the thin per-collection\n// `liveInvalidation` capability that wires a collection's `ctx.invalidate()` to\n// it. Its own module for the same reason as the seam above — reviewable and\n// testable on its own (mock only EventSource + fetch).\nexport type {\n LiveInvalidationConfig,\n SmrtWebEventSource,\n SmrtWebEventSourceFactory,\n SmrtWebEventSubscriber,\n SmrtWebEventSubscriberConfig,\n SmrtWebSubscriberTransport,\n} from './sse-client.js';\nexport {\n createSmrtWebEventSubscriber,\n liveInvalidation,\n} from './sse-client.js';\n// Version awareness (#1764): the framework-free `updateAvailable` primitive with\n// two independent signals — `bundle` (fed by the smrt-svelte binding from\n// SvelteKit's `updated` store) and `contract` (build-time-inject manifest-hash\n// compare across loads). The reactive Svelte adapter ships in smrt-svelte.\nexport type {\n UpdateAvailableState,\n UpdateState,\n UpdateStateConfig,\n} from './update-state.js';\nexport { createUpdateState } from './update-state.js';\n\n// ---------------------------------------------------------------------------\n// Generated definition contract (mirrors @happyvertical/smrt-virt-web)\n// ---------------------------------------------------------------------------\n\n/**\n * Field metadata emitted per column by the `@happyvertical/smrt-virt-web`\n * virtual module (generated from the package manifest).\n */\nexport interface SmrtWebFieldDefinition {\n type: string;\n required?: boolean;\n default?: unknown;\n}\n\n/** The relationship kinds a generated web collection edge can describe. */\nexport type SmrtWebRelationshipKind =\n | 'foreignKey'\n | 'crossPackageRef'\n | 'oneToMany'\n | 'manyToMany';\n\n/**\n * A manifest-derived edge from this collection to a sibling REST collection,\n * emitted by the `@happyvertical/smrt-virt-web` virtual module. When a mutation\n * on this collection settles, the caches of the collections named by these\n * edges are invalidated (relationship-derived invalidation, #1761), so a\n * dependent view refetches without any hand-wired cache key.\n *\n * SMRT-owned data — no client-engine (`@tanstack/*`) type appears here, so it\n * stays inside the engine-absorption boundary.\n */\nexport interface SmrtWebRelationship {\n /** The declaring field carrying the relationship (e.g. `groupId`, `items`). */\n field: string;\n /** The relationship kind, mirroring the manifest field type. */\n kind: SmrtWebRelationshipKind;\n /** REST collection name the edge resolves to (e.g. `ad_groups`). */\n relatedCollection: string;\n}\n\n/**\n * One generated collection definition: everything needed to construct a client\n * collection over the generated REST surface. The `_row` property is a phantom\n * type carrier threaded through codegen — it never exists at runtime, it only\n * lets factories infer the row type from a definition.\n */\n/**\n * One WebMCP / MCP tool descriptor for a collection action (#1812). Emitted by\n * the core web-collections codegen as PLAIN DATA (this package has no smrt\n * dependency), shaped to match Chrome's `document.modelContext.registerTool`\n * input — see https://developer.chrome.com/docs/ai/webmcp. Consumed by\n * {@link registerWebMcpTools} in `./webmcp`.\n */\nexport interface WebToolDescriptor {\n /** The action this tool performs (`list` | `get` | … | a custom method name). */\n action: string;\n /** Tool id, `${className.toLowerCase()}_${action}` (e.g. `product_list`). */\n name: string;\n description: string;\n /** JSON Schema for the tool's arguments. */\n inputSchema: Record<string, unknown>;\n /** True for non-mutating reads → WebMCP `annotations.readOnlyHint`. */\n readOnly: boolean;\n}\n\nexport interface SmrtWebCollectionDefinition<TData extends object = object> {\n /** REST collection name (e.g. `products`). */\n name: string;\n /** Source class name (e.g. `Product`). */\n className: string;\n /** Path under the API base path (e.g. `/products`). */\n endpoint: string;\n /** Primary key field name (`id` for SmrtObject). */\n idField: string;\n /** CRUD + custom actions exposed by the api decorator config. */\n actions: string[];\n /**\n * WebMCP/MCP tool descriptors for the exposed actions (#1812). Optional so\n * hand-built definitions (older codegen, tests) still satisfy the type; a\n * missing value means \"no WebMCP tools to register\".\n */\n toolDescriptors?: WebToolDescriptor[];\n /** Persisted field metadata keyed by field name. */\n fields: Record<string, SmrtWebFieldDefinition>;\n /**\n * Manifest-derived relationship edges to sibling REST collections. Drives\n * relationship-derived cache invalidation: a settled mutation on this\n * collection invalidates the caches of the collections these edges name.\n * Optional so hand-built definitions (older codegen, tests) still satisfy the\n * type; a missing value means \"no derived edges\".\n */\n relationships?: SmrtWebRelationship[];\n /** Phantom row-type carrier — never present at runtime. */\n _row?: TData;\n}\n\n// ---------------------------------------------------------------------------\n// Fetcher contract + payload normalization\n// ---------------------------------------------------------------------------\n\n/**\n * The per-collection CRUD surface of the generated REST client\n * (`createClient(basePath).<collection>` from `@happyvertical/smrt-virt-client`).\n *\n * Return types are `unknown` on purpose: generated fetchers resolve with\n * whatever the server sent, so this package normalizes and validates payloads\n * centrally — see {@link unwrapListResult} / {@link unwrapItemResult}.\n */\nexport interface SmrtCrudFetchers {\n list(params?: Record<string, unknown>): Promise<unknown>;\n get?(id: string): Promise<unknown>;\n create(data: Record<string, unknown>): Promise<unknown>;\n update?(id: string, data: Record<string, unknown>): Promise<unknown>;\n delete?(id: string): Promise<unknown>;\n}\n\n/**\n * Raised when a generated-client call resolved with an error payload\n * (`{ error: string }` from the generated REST routes) or an unexpected shape.\n * Thrown inside a mutation handler, this triggers the automatic rollback of\n * optimistic state.\n */\nexport class SmrtWebRequestError extends Error {\n readonly payload: unknown;\n\n constructor(message: string, payload?: unknown) {\n super(message);\n this.name = 'SmrtWebRequestError';\n this.payload = payload;\n }\n}\n\n/** A row as stored in the client collection: the DTO plus a required key. */\nexport type SmrtWebRow<TData extends object> = TData & { id: string };\n\n/**\n * Normalize a generated-client list result to an array of rows.\n *\n * The generated REST routes return a bare JSON array; `{ error }` payloads are\n * surfaced as failures. The `{ data: [...] }` envelope is tolerated for\n * ApiResponse-shaped clients (e.g. a mock client).\n */\nexport function unwrapListResult(\n result: unknown,\n collectionName: string,\n): Array<Record<string, unknown>> {\n if (Array.isArray(result)) {\n return result as Array<Record<string, unknown>>;\n }\n if (result && typeof result === 'object') {\n const record = result as Record<string, unknown>;\n if (typeof record.error === 'string') {\n throw new SmrtWebRequestError(\n `[smrt-web] list(${collectionName}) failed: ${record.error}`,\n result,\n );\n }\n if (Array.isArray(record.data)) {\n return record.data as Array<Record<string, unknown>>;\n }\n }\n throw new SmrtWebRequestError(\n `[smrt-web] list(${collectionName}) returned an unexpected payload shape`,\n result,\n );\n}\n\n/**\n * Normalize a generated-client item result (create/update) to a row.\n * `{ error }` payloads become failures — inside mutation handlers this is what\n * makes optimistic state roll back.\n */\nexport function unwrapItemResult(\n result: unknown,\n context: string,\n): Record<string, unknown> {\n if (result && typeof result === 'object' && !Array.isArray(result)) {\n const record = result as Record<string, unknown>;\n if (typeof record.error === 'string') {\n throw new SmrtWebRequestError(\n `[smrt-web] ${context} failed: ${record.error}`,\n result,\n );\n }\n if (\n record.data &&\n typeof record.data === 'object' &&\n !Array.isArray(record.data)\n ) {\n return record.data as Record<string, unknown>;\n }\n return record;\n }\n throw new SmrtWebRequestError(\n `[smrt-web] ${context} returned an unexpected payload shape`,\n result,\n );\n}\n\n/** SMRT WHERE operator → the generated REST route's `field[op]=value` token. */\nconst SMRT_TO_REST_OPERATOR: Record<string, string> = {\n '>': 'gt',\n '>=': 'gte',\n '<': 'lt',\n '<=': 'lte',\n '!=': 'ne',\n in: 'in',\n like: 'like',\n};\n\n/**\n * Serialize `list` query params into the query string the generated REST list\n * route parses (`handleList` in `core/src/generators/rest.ts`): `limit`,\n * `offset`, `orderBy` as scalars, and `where` entries as `field=value`\n * (equality) or `field[op]=value` for a `{ op, value }` condition (`in` joins an\n * array with commas). Called with no params (the collection runtime's argument-\n * free `list()`) it returns `''`, so the bare-URL behavior is unchanged.\n */\nexport function buildListQuery(params?: Record<string, unknown>): string {\n if (!params) return '';\n const search = new URLSearchParams();\n const { limit, offset, orderBy, where } = params;\n if (limit !== undefined) search.set('limit', String(limit));\n if (offset !== undefined) search.set('offset', String(offset));\n if (orderBy !== undefined) {\n search.set(\n 'orderBy',\n Array.isArray(orderBy) ? orderBy.join(', ') : String(orderBy),\n );\n }\n if (where && typeof where === 'object') {\n for (const [field, condition] of Object.entries(\n where as Record<string, unknown>,\n )) {\n if (\n condition &&\n typeof condition === 'object' &&\n !Array.isArray(condition) &&\n 'op' in condition &&\n 'value' in condition\n ) {\n const { op, value } = condition as { op: string; value: unknown };\n const restOp = SMRT_TO_REST_OPERATOR[op];\n const token = Array.isArray(value) ? value.join(',') : String(value);\n search.set(restOp ? `${field}[${restOp}]` : field, token);\n } else if (condition !== undefined && condition !== null) {\n search.set(field, String(condition));\n }\n }\n }\n const qs = search.toString();\n return qs ? `?${qs}` : '';\n}\n\n/**\n * Build CRUD fetchers from a generated collection definition — the same URL\n * scheme and payload handling as the generated REST client\n * (`basePath + endpoint`), with one improvement: HTTP error statuses reject\n * with the server's `{ error }` body instead of resolving with it.\n */\nexport function createDefinitionFetchers(\n definition: SmrtWebCollectionDefinition<object>,\n basePath = '/api/v1',\n fetchFn: typeof fetch = (...args) => globalThis.fetch(...args),\n): SmrtCrudFetchers {\n const collectionUrl = `${basePath}${definition.endpoint}`;\n const headers = { 'Content-Type': 'application/json' };\n\n const parse = async (response: Response): Promise<unknown> => {\n const payload: unknown = await response.json().catch(() => null);\n if (!response.ok) {\n const message =\n payload &&\n typeof payload === 'object' &&\n typeof (payload as Record<string, unknown>).error === 'string'\n ? String((payload as Record<string, unknown>).error)\n : `HTTP ${response.status}`;\n throw new SmrtWebRequestError(\n `[smrt-web] ${definition.name} request failed: ${message}`,\n payload,\n );\n }\n return payload;\n };\n\n return {\n list: async (params) =>\n parse(\n await fetchFn(`${collectionUrl}${buildListQuery(params)}`, { headers }),\n ),\n get: async (id) =>\n parse(await fetchFn(`${collectionUrl}/${id}`, { headers })),\n create: async (data) =>\n parse(\n await fetchFn(collectionUrl, {\n method: 'POST',\n headers,\n body: JSON.stringify(data),\n }),\n ),\n update: async (id, data) =>\n parse(\n await fetchFn(`${collectionUrl}/${id}`, {\n method: 'PUT',\n headers,\n body: JSON.stringify(data),\n }),\n ),\n delete: async (id) => {\n const response = await fetchFn(`${collectionUrl}/${id}`, {\n method: 'DELETE',\n headers,\n });\n if (!response.ok) {\n throw new SmrtWebRequestError(\n `[smrt-web] delete(${definition.name}) failed: HTTP ${response.status}`,\n );\n }\n return true;\n },\n };\n}\n\n/**\n * Generate a client-local id for optimistic inserts. The generated REST layer\n * strips client-supplied ids on create (mass-assignment guard #1540), so this\n * id only identifies the optimistic row until the post-persist refetch swaps in\n * the server-assigned row.\n */\nexport function newLocalId(): string {\n const cryptoRef = globalThis.crypto as Crypto | undefined;\n if (cryptoRef?.randomUUID) {\n return cryptoRef.randomUUID();\n }\n return `local-${Date.now()}-${Math.random().toString(36).slice(2)}`;\n}\n\n// ---------------------------------------------------------------------------\n// Engine-absorbing public surface (no @tanstack/* types leak past here)\n// ---------------------------------------------------------------------------\n\n/**\n * Opaque handle to the shared client cache / request-dedup layer. Create one\n * with {@link createSmrtWebClient} and pass the SAME instance to every\n * collection that should share a cache and deduplicate in-flight requests.\n *\n * The engine (currently a TanStack Query client) is intentionally hidden behind\n * this brand so it stays swappable — do not depend on its concrete shape.\n */\nexport interface SmrtWebClient {\n /** Phantom brand — this handle wraps the hidden client-cache engine. */\n readonly __smrtWebClient: 'SmrtWebClient';\n}\n\n/**\n * Engine-side shape of a {@link SmrtWebClient}. Never exported, so the engine\n * type never reaches the public surface. Extends the public brand so the value\n * created here carries the brand at runtime (enabling the validation below).\n */\ninterface SmrtWebClientEngine extends SmrtWebClient {\n readonly queryClient: QueryClient;\n}\n\n/**\n * Create a shared client-cache handle. Pass the returned handle as\n * {@link CreateSmrtCollectionOptions.client} to every collection that should\n * share a cache and deduplicate requests app-wide.\n */\nexport function createSmrtWebClient(): SmrtWebClient {\n const engine: SmrtWebClientEngine = {\n __smrtWebClient: 'SmrtWebClient',\n queryClient: new QueryClient(),\n };\n return engine;\n}\n\nfunction resolveQueryClient(client?: SmrtWebClient): QueryClient {\n if (!client) return new QueryClient();\n const engine = client as Partial<SmrtWebClientEngine>;\n if (engine.__smrtWebClient !== 'SmrtWebClient' || !engine.queryClient) {\n throw new SmrtWebRequestError(\n '[smrt-web] options.client must be a handle from createSmrtWebClient()',\n );\n }\n return engine.queryClient;\n}\n\n/**\n * Project an engine row to a plain public DTO. The client-data engine decorates\n * stored rows with enumerable virtual props (`$synced`/`$origin`/`$key`/\n * `$collectionId`) that would otherwise cross the SMRT boundary through spread\n * or JSON serialization. The `$` prefix is reserved for the engine; SMRT\n * columns never begin with it.\n */\nfunction toPlainRow<TData extends object>(row: unknown): SmrtWebRow<TData> {\n const plain: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(row as Record<string, unknown>)) {\n if (key.charCodeAt(0) !== 36 /* '$' */) plain[key] = value;\n }\n return plain as SmrtWebRow<TData>;\n}\n\n/** Project the row values carried by a change notification to plain DTOs. */\nfunction projectChanges(changes: unknown): unknown {\n if (!Array.isArray(changes)) return changes;\n return changes.map((change) => {\n if (!change || typeof change !== 'object') return change;\n const record = change as Record<string, unknown>;\n const projected: Record<string, unknown> = { ...record };\n if (record.value && typeof record.value === 'object') {\n projected.value = toPlainRow(record.value);\n }\n if (record.previousValue && typeof record.previousValue === 'object') {\n projected.previousValue = toPlainRow(record.previousValue);\n }\n return projected;\n });\n}\n\n/**\n * A pending optimistic mutation. Await {@link isPersisted} to observe the\n * server outcome: it resolves once the write has been persisted through the\n * REST surface, and rejects (rolling the optimistic state back) on error.\n */\nexport interface SmrtWebTransaction {\n readonly isPersisted: { readonly promise: Promise<unknown> };\n}\n\n/** A change-subscription handle. Call {@link unsubscribe} to detach. */\nexport interface SmrtWebSubscription {\n unsubscribe(): void;\n}\n\n/**\n * A live, cached collection of plain-DTO rows — the SMRT-owned public contract\n * over the client-data engine. Exposes only the committed surface; the engine's\n * own type is never named here so it stays swappable.\n */\nexport interface SmrtWebCollection<TData extends object> {\n /** All rows currently in the collection (plain DTOs, insertion order). */\n readonly toArray: ReadonlyArray<SmrtWebRow<TData>>;\n /** Number of rows currently in the collection. */\n readonly size: number;\n /** True when a row with `key` is present. */\n has(key: string): boolean;\n /** The row with `key`, or `undefined`. */\n get(key: string): SmrtWebRow<TData> | undefined;\n /** Resolve once the first load has completed. */\n preload(): Promise<void>;\n /** Tear down subscriptions and cached state. */\n cleanup(): Promise<void>;\n /** Subscribe to change notifications; returns a detach handle. */\n subscribeChanges(callback: (changes: unknown) => void): SmrtWebSubscription;\n /**\n * Optimistically insert a row and persist it through the create fetcher. The\n * row is visible synchronously; the returned transaction settles on the\n * server outcome (see {@link SmrtWebTransaction}).\n */\n insert(row: SmrtWebRow<TData>): SmrtWebTransaction;\n}\n\n/** Extract a row's server revision timestamp for sync/apply conflict guards. */\nfunction getBaseUpdatedAt(row: unknown): string | undefined {\n if (!row || typeof row !== 'object') return undefined;\n const record = row as Record<string, unknown>;\n const value = record.updatedAt ?? record.updated_at;\n if (typeof value === 'string') return value;\n if (value instanceof Date) return value.toISOString();\n return undefined;\n}\n\n/**\n * Options for {@link createSmrtCollection}. Generic in the collection's row\n * type `TData` so {@link initialData} is checked against the same DTO the\n * collection stores; every other option is row-type-agnostic, so the parameter\n * defaults to `object` and can be omitted at call sites that pass no seed.\n */\nexport interface CreateSmrtCollectionOptions<TData extends object = object> {\n /**\n * Generated REST client surface for this collection, e.g.\n * `createClient('/api/v1').products` from the virt-client module. When\n * omitted, fetchers are derived from the definition's endpoint and `basePath`\n * with the same URL scheme and payload shapes the generated client uses.\n */\n fetchers?: SmrtCrudFetchers;\n /** API base path for definition-derived fetchers (default `/api/v1`). */\n basePath?: string;\n /** Fetch implementation override (tests, SSR). Defaults to global fetch. */\n fetchFn?: typeof fetch;\n /**\n * Shared cache handle from {@link createSmrtWebClient}. Pass one app-wide\n * instance so collections share a cache and deduplicate requests; a private\n * cache is created when omitted.\n */\n client?: SmrtWebClient;\n /**\n * Cache namespace for this collection's reads. Fold a backend / tenant /\n * preview discriminator in here when the SAME generated collection is\n * materialized against DIFFERENT backends while sharing one {@link client} —\n * without it those reads share a cache key and could serve one backend's rows\n * for the other for the whole `staleTimeMs` window. Omit for the common\n * single-backend case.\n */\n scope?: string;\n /**\n * Stale-while-revalidate window in milliseconds (default 30s): reads within\n * the window are served from the local collection without a network request;\n * the first read after it revalidates in the background.\n */\n staleTimeMs?: number;\n /** Retry failed loads (default false: fail fast, surface errors). */\n retry?: boolean;\n /**\n * Rows to seed this collection's cache with, before its first read — the\n * hydration path for server-rendered data (#1761). Fetch rows in a SvelteKit\n * `+page.server.ts` load, pass them here on the client, and the first read\n * serves them from cache WITHOUT a duplicate first-render network request\n * (SMRT-owned type, so no engine type appears on the option).\n *\n * The seed is written to the cache with a fresh timestamp, so it counts as\n * fresh for `staleTimeMs`: with the default window the first read does not\n * fetch, and the collection revalidates in the background only once the window\n * elapses (or immediately if `staleTimeMs` is 0). Seed the SAME rows the\n * server serialized so the pre- and post-hydration renders match.\n *\n * Seeds the SAME cache key the reads use — so with a shared {@link client},\n * fold the backend / tenant discriminator into {@link scope} to match, exactly\n * as reads do; otherwise one backend's seed would serve the other for the\n * `staleTimeMs` window.\n */\n initialData?: SmrtWebRow<TData>[];\n /**\n * Capability plug-ins hooking the collection lifecycle (#1755) — the seam\n * that lets the offline outbox (#1762), persistence (#1764), and live SSE\n * invalidation (#1763-client) slices each live in their own module instead of\n * contending on this factory. Capabilities run in array order at six fixed\n * points (see {@link SmrtWebCapability}). Additive and defaulting to none: an\n * undefined or empty array is byte-for-byte the collection of today (the\n * no-op guarantee), so this ships zero concrete capabilities by design.\n */\n capabilities?: SmrtWebCapability<TData>[];\n}\n\n/**\n * Registry mapping a public collection handle to its underlying engine\n * collection. Keyed weakly so a handle and its engine collection are collected\n * together. Read only through {@link getEngineCollection}.\n */\nconst engineCollections = new WeakMap<object, unknown>();\n\n/**\n * Retrieve the underlying engine collection backing a handle — an advanced\n * bridge for trusted framework bindings (e.g. the smrt-svelte live-query\n * binding), which must feed the engine collection to the query builder. Returns\n * `unknown` so no engine type crosses the boundary; callers cast. Throws for a\n * handle not produced by {@link createSmrtCollection}. Not needed for normal\n * use.\n */\nexport function getEngineCollection<TData extends object>(\n handle: SmrtWebCollection<TData>,\n): unknown {\n const engine = engineCollections.get(handle);\n if (engine === undefined) {\n throw new SmrtWebRequestError(\n '[smrt-web] getEngineCollection: not a smrt-web collection handle',\n );\n }\n return engine;\n}\n\n/**\n * Report a capability hook that threw or rejected, without letting it break the\n * collection (#1755). Capability hooks are third-party plug-ins; one misbehaving\n * hook must not reject a successful mutation, abort construction, or wedge\n * `cleanup()`. smrt-web has no logger dependency (TanStack-only), so this uses\n * `console.warn` — a swallowed diagnostic, matching the package's fail-loud-in-\n * the-console style.\n */\nfunction warnCapability(\n capability: { name: string },\n hook: string,\n error: unknown,\n): void {\n // biome-ignore lint/suspicious/noConsole: smrt-web has no logger dep (TanStack-only); a swallowed capability fault is surfaced via console.warn by design (#1755)\n console.warn(\n `[smrt-web] capability \"${capability.name}\" ${hook} threw; ignoring`,\n error,\n );\n}\n\n/**\n * Create a typed client collection over a generated SMRT collection definition\n * and the matching generated REST client fetchers.\n *\n * Reads: stale-while-revalidate. The first subscriber triggers a fetch;\n * re-subscribing within `staleTimeMs` serves local data with no request. N\n * concurrent identical reads coalesce into one network request.\n *\n * Writes: `collection.insert({ ...data, id: newLocalId() })` applies instantly,\n * persists through `fetchers.create()` (the temp id is stripped — the server\n * assigns the real one), then refetches to reconcile. A failed create rejects\n * the transaction and the optimistic row rolls back automatically.\n *\n * Relationship-derived invalidation: once a create/update/delete has persisted,\n * the query caches of this collection AND the collections named by\n * `definition.relationships` (manifest-derived edges) are invalidated, so\n * dependent views refetch. Reaching OTHER collections requires them to share\n * this collection's `client` (see {@link createSmrtWebClient}); with a private\n * client only this collection refetches.\n *\n * Hydration seeding: pass rows fetched server-side as\n * {@link CreateSmrtCollectionOptions.initialData} and the collection's first\n * read is served from them with NO network request (until `staleTimeMs`\n * elapses) — the SvelteKit `+page.server.ts` → hydrate path.\n */\nexport function createSmrtCollection<TData extends object>(\n definition: SmrtWebCollectionDefinition<TData>,\n options: CreateSmrtCollectionOptions<TData>,\n): SmrtWebCollection<TData> {\n type Row = SmrtWebRow<TData>;\n\n const { staleTimeMs = 30_000, retry = false, scope, initialData } = options;\n const capabilities = options.capabilities ?? [];\n const fetchers =\n options.fetchers ??\n createDefinitionFetchers(definition, options.basePath, options.fetchFn);\n const queryClient = resolveQueryClient(options.client);\n const idField = definition.idField || 'id';\n\n // Scope discriminates the cache key so a shared client can materialize the\n // same collection against different backends without cross-serving reads.\n // `let` so capabilities can extend the scheme via `contributeCacheKey` below;\n // with no capabilities these stay exactly the base `smrt:(scope:)name` form.\n let cacheId = scope\n ? `smrt:${scope}:${definition.name}`\n : `smrt:${definition.name}`;\n let queryKey = scope\n ? ['smrt', scope, definition.name]\n : ['smrt', definition.name];\n\n // Relationship-derived invalidation target set (#1761): the collections\n // whose caches a settled mutation on THIS collection must invalidate. Always\n // includes this collection itself (so its own read revalidates) plus every\n // manifest-derived related collection. Built once; a settled write matches\n // any cached query whose collection-name segment (the LAST queryKey element,\n // mirroring the `['smrt', (scope,) name]` scheme above) is in this set.\n //\n // Over-invalidation is safe — a stale query merely refetches. Under-\n // invalidation is the bug (a dependent view showing stale rows), so the\n // predicate matches by collection name across ALL scopes rather than an exact\n // key: a mutation in one scope refreshes the related collection in every\n // scope sharing the client.\n const invalidationTargets = new Set<string>([definition.name]);\n for (const relationship of definition.relationships ?? []) {\n invalidationTargets.add(relationship.relatedCollection);\n }\n\n /**\n * Invalidate the query caches of this collection and its manifest-derived\n * related collections. Cross-collection reach requires those collections to\n * share this collection's `client` (from {@link createSmrtWebClient}); with a\n * private client only THIS collection's query lives here, so only it\n * refetches. Fire-and-forget: invalidation schedules a background refetch and\n * must not delay the mutation's own settle.\n */\n const invalidateRelated = (): void => {\n void queryClient.invalidateQueries({\n predicate: (query) => {\n const key = query.queryKey;\n if (!Array.isArray(key) || key.length === 0) return false;\n const collectionSegment = key[key.length - 1];\n return (\n typeof collectionSegment === 'string' &&\n invalidationTargets.has(collectionSegment)\n );\n },\n });\n };\n\n // The engine-free context every capability hook receives (#1755). `cacheKey`\n // / `cacheId` are getter-backed over the live `let` bindings, so a capability\n // observes the key AS IT STANDS when it reads: during `contributeCacheKey`\n // (below) that is the base key plus any EARLIER capability's segments (not its\n // own, not-yet-applied one); from `onAttach`/`warmStart` onward it is the\n // FINAL key. `invalidate` enters the same `invalidateRelated()` the factory\n // runs post-mutation, so a capability can refetch off an external trigger.\n const ctx: SmrtWebCapabilityContext<TData> = {\n definition,\n fetchers,\n get cacheKey() {\n return queryKey;\n },\n get cacheId() {\n return cacheId;\n },\n invalidate: () => invalidateRelated(),\n // Engine-free read + subscribe surface for capabilities (#1764 persistence\n // write-back). Both close over the engine `collection` created below and are\n // only ever called from `onAttach` onward (after it exists), so referencing\n // it here is safe. They mirror the public handle's `toArray` /\n // `subscribeChanges` exactly — projecting to plain DTOs so the engine's\n // virtual props never cross the seam.\n snapshot: () => collection.toArray.map((row) => toPlainRow<TData>(row)),\n subscribe: (callback) => {\n const subscription = collection.subscribeChanges((changes: unknown) =>\n callback(projectChanges(changes)),\n );\n return { unsubscribe: () => subscription.unsubscribe() };\n },\n };\n\n // contributeCacheKey (#1755): fold each capability's returned segments into\n // the cache key/id, extending the base scope-based scheme. Runs ONCE, before\n // construction. Isolated per capability: a throwing contributeCacheKey is\n // logged and skipped rather than aborting construction. With no capabilities\n // this loop is empty and the keys are untouched — the no-op path.\n //\n // CRITICAL — segments are spliced in JUST BEFORE the collection name, so the\n // name stays the LAST queryKey element. `invalidateRelated()`'s predicate\n // (and thus relationship-derived invalidation #1761, and a capability's own\n // `ctx.invalidate()`) identifies a collection by `key[key.length - 1]`;\n // appending segments after the name would hide it from that predicate and\n // silently break invalidation for any collection using contributeCacheKey\n // (exactly the #1764 persistence slice). `cacheId` is an opaque engine id the\n // predicate never reads, so it can keep appending.\n for (const capability of capabilities) {\n let extra: string[] | undefined;\n try {\n extra = capability.contributeCacheKey?.(ctx);\n } catch (error) {\n warnCapability(capability, 'contributeCacheKey', error);\n }\n if (extra && extra.length > 0) {\n // Rebuild as [prefix..., ...extra, name] — name remains last.\n const name = queryKey[queryKey.length - 1];\n const prefix = queryKey.slice(0, -1);\n queryKey = [...prefix, ...extra, name];\n cacheId = `${cacheId}:${extra.join(':')}`;\n }\n }\n\n // Seed the query cache BEFORE the collection's engine starts its sync, so the\n // first read populates from the seed instead of fetching (verified: zero\n // list() calls). `setQueryData` stamps a fresh `dataUpdatedAt`, so the seed\n // counts as fresh for `staleTime` — the first read serves it with no request,\n // and revalidation fires only once `staleTimeMs` elapses (or immediately when\n // it is 0). An explicit empty seed is honored too: it means \"zero rows\", a\n // valid fresh state that likewise suppresses the first fetch.\n //\n // Two seed sources with a fixed precedence:\n // 1. `initialData` — hydration seeding (#1761): rows a SvelteKit\n // `+page.server.ts` load serialized, the fresher same-request SSR truth.\n // 2. a capability `warmStart` (#1755) — e.g. the persistence slice\n // rehydrating from disk. Only the FIRST capability that returns rows\n // contributes.\n // `initialData` WINS: it is resolved first, and `warmStart` is only consulted\n // when `initialData` is undefined. With no capabilities, step 2 never runs and\n // this is byte-identical to the #1761 seed — the no-op path.\n //\n // Seed via the ATOMIC updater form: a plain get-then-set would let two\n // collections sharing this key and materialized in the same tick both observe\n // `undefined` and have the later seed clobber the earlier one.\n // `(existing) => existing ?? seed` keeps the first seed (or any already-cached\n // rows, which may be newer than this late payload) in a single cache write.\n const seedCache = (rows: Row[]): void => {\n queryClient.setQueryData<Row[]>(queryKey, (existing) => existing ?? rows);\n };\n // Set by cleanup(); guards the async-warmStart continuations (#1755) so a\n // collection torn down while a rehydrate is still in flight neither seeds the\n // SHARED query cache after teardown (which could suppress the next collection's\n // real fetch on the same client/key) nor preloads the dead engine.\n let disposed = false;\n // Resolve a warmStart result to rows, isolating a sync throw / async reject\n // (logged, treated as \"no rows\") so a misbehaving provider can neither abort\n // construction nor leak an unhandled rejection.\n const warmRowsFrom = async (\n capability: SmrtWebCapability<TData>,\n warm: Promise<Row[] | undefined> | Row[] | undefined,\n ): Promise<Row[] | undefined> => {\n try {\n return await warm;\n } catch (error) {\n warnCapability(capability, 'warmStart', error);\n return undefined;\n }\n };\n\n // A pending async `warmStart` (persistence rehydrating from OPFS/IndexedDB).\n // Construction stays synchronous, but `preload()` (below) AWAITS this before\n // starting the engine's own load, so an async rehydrate reliably suppresses\n // the first `list()` on the preload path. A subscribe-driven read that races\n // an unresolved warmStart may still fetch once — bounded, and it self-heals\n // via the atomic seed updater. Undefined when there is no async warmStart.\n let warmStartPending: Promise<void> | undefined;\n if (initialData !== undefined) {\n seedCache(initialData);\n } else {\n // Honor \"the FIRST capability that returns ROWS\" across BOTH sync and async\n // warmStart. A sync provider that returns rows seeds inline immediately (so\n // it suppresses even a non-preload read). The FIRST provider that returns a\n // Promise hands off to an ORDERED async chain covering itself and every\n // LATER capability, in array order: it awaits each, skips a result that is\n // undefined or throws/rejects, and seeds the first that yields rows. So an\n // async miss or reject no longer blocks a later provider from seeding.\n for (let i = 0; i < capabilities.length; i += 1) {\n const capability = capabilities[i];\n let warm: Promise<Row[] | undefined> | Row[] | undefined;\n try {\n warm = capability.warmStart?.(ctx);\n } catch (error) {\n // A synchronous warmStart throw must not abort construction.\n warnCapability(capability, 'warmStart', error);\n continue;\n }\n if (warm === undefined) continue;\n if (warm instanceof Promise) {\n const firstPromise = warm;\n warmStartPending = (async () => {\n // This capability first (its promise is already in flight), then each\n // later capability in order until one yields rows.\n let rows = await warmRowsFrom(capability, firstPromise);\n for (\n let j = i + 1;\n rows === undefined && j < capabilities.length;\n j += 1\n ) {\n const later = capabilities[j];\n let laterWarm: Promise<Row[] | undefined> | Row[] | undefined;\n try {\n laterWarm = later.warmStart?.(ctx);\n } catch (error) {\n warnCapability(later, 'warmStart', error);\n continue;\n }\n if (laterWarm === undefined) continue;\n rows = await warmRowsFrom(later, laterWarm);\n }\n // Skip the seed if the collection was cleaned up while the rehydrate\n // was in flight — a late write to the shared cache could otherwise\n // suppress the NEXT collection's real fetch on the same client/key.\n if (rows !== undefined && !disposed) seedCache(rows);\n })();\n break;\n }\n seedCache(warm);\n break;\n }\n }\n\n // Notify every capability that a mutation settled (#1755) — on BOTH a\n // successful persist and a fetcher throw. Per-capability try/catch so this\n // genuinely NEVER throws: a throwing onSettled must not reject a SUCCESSFUL\n // mutation nor mask a fetcher error, and one bad capability must not stop the\n // others being notified. With no capabilities this loop is empty — the no-op\n // path.\n const notifySettled = (\n envelope: SmrtWebMutationEnvelope,\n outcome: { ok: true; result: unknown } | { ok: false; error: unknown },\n ): void => {\n for (const capability of capabilities) {\n try {\n capability.onSettled?.(envelope, outcome, ctx);\n } catch (error) {\n warnCapability(capability, 'onSettled', error);\n }\n }\n };\n\n /**\n * Persist one mutation through the capability seam then the real fetcher.\n * First offers the write to `wrapMutation` (array order, first-handled-wins):\n * a handling capability's result stands in for the fetcher (the offline\n * path), otherwise `runFetcher` performs the real write. On success notifies\n * `onSettled({ ok: true })`; on ANY throw notifies `onSettled({ ok: false })`\n * and RE-THROWS so the optimistic state still rolls back.\n *\n * Returns `handled` so the caller can suppress the engine's post-mutation\n * refetch for an offline write (#1762): a handled write never hit the server,\n * so refetching the server list would DROP the optimistic row the outbox must\n * keep until it replays. With no capabilities `runWrapMutation` returns\n * `{ handled: false }` and both notify loops are empty, so this reduces to\n * `await runFetcher()` with `handled: false` — behavior identical to before\n * the seam.\n */\n const persistMutation = async (\n envelope: SmrtWebMutationEnvelope,\n runFetcher: () => Promise<unknown>,\n ): Promise<{ handled: boolean; result: unknown }> => {\n try {\n const wrapped = await runWrapMutation(capabilities, envelope, ctx);\n const result = wrapped.handled ? wrapped.result : await runFetcher();\n notifySettled(envelope, { ok: true, result });\n return { handled: wrapped.handled, result };\n } catch (error) {\n notifySettled(envelope, { ok: false, error });\n throw error;\n }\n };\n\n const collection = createCollection(\n queryCollectionOptions<Row>({\n id: cacheId,\n queryKey,\n queryClient,\n staleTime: staleTimeMs,\n retry,\n queryFn: async () =>\n unwrapListResult(await fetchers.list(), definition.name) as Array<Row>,\n getKey: (row) => String((row as Record<string, unknown>)[idField]),\n onInsert: async ({ transaction }) => {\n let anyHandled = false;\n for (const mutation of transaction.mutations) {\n const modified = mutation.modified as Record<string, unknown>;\n // Offer the write to the capability seam first (#1755), then fall\n // through to the real create fetcher. The envelope carries the FULL\n // optimistic row; the fetcher closure strips the client-local id: the\n // generated REST layer rejects or ignores client-supplied ids on\n // create (#1540), and the follow-up refetch swaps the optimistic row\n // for the server-assigned one.\n const envelope: SmrtWebMutationEnvelope = {\n kind: 'insert',\n key: String(modified[idField]),\n data: modified,\n };\n const outcome = await persistMutation(envelope, async () => {\n const { [idField]: _localId, ...data } = modified;\n return unwrapItemResult(\n await fetchers.create(data),\n `create(${definition.name})`,\n );\n });\n anyHandled = anyHandled || outcome.handled;\n }\n // If a capability handled the write offline (#1762) it never reached the\n // server, so BOTH the engine's own post-mutation refetch (suppressed via\n // `{ refetch: false }`) and `invalidateRelated()` are skipped — else the\n // server list, which lacks the offline row, would drop the optimistic\n // row the outbox must keep until it replays. A mixed batch (some\n // handled, some not) is conservative: if ANY mutation was handled we\n // suppress, so an unsent row is never dropped; the common case is one\n // mutation per transaction. An UNHANDLED batch behaves exactly as\n // before: refetch runs and related caches invalidate.\n if (anyHandled) return { refetch: false };\n invalidateRelated();\n },\n onUpdate: fetchers.update\n ? async ({ transaction }) => {\n let anyHandled = false;\n for (const mutation of transaction.mutations) {\n const key = String(mutation.key);\n const changes = mutation.changes as Record<string, unknown>;\n const envelope: SmrtWebMutationEnvelope = {\n kind: 'update',\n key,\n data: changes,\n baseUpdatedAt: getBaseUpdatedAt(mutation.original),\n };\n const outcome = await persistMutation(envelope, async () =>\n unwrapItemResult(\n // biome-ignore lint/style/noNonNullAssertion: guarded by the surrounding ternary\n await fetchers.update!(key, changes),\n `update(${definition.name})`,\n ),\n );\n anyHandled = anyHandled || outcome.handled;\n }\n if (anyHandled) return { refetch: false };\n invalidateRelated();\n }\n : undefined,\n onDelete: fetchers.delete\n ? async ({ transaction }) => {\n let anyHandled = false;\n for (const mutation of transaction.mutations) {\n const key = String(mutation.key);\n const envelope: SmrtWebMutationEnvelope = {\n kind: 'delete',\n key,\n data: {},\n baseUpdatedAt: getBaseUpdatedAt(mutation.original),\n };\n const outcome = await persistMutation(envelope, async () =>\n // biome-ignore lint/style/noNonNullAssertion: guarded by the surrounding ternary\n fetchers.delete!(key),\n );\n anyHandled = anyHandled || outcome.handled;\n }\n if (anyHandled) return { refetch: false };\n invalidateRelated();\n }\n : undefined,\n }),\n );\n\n // Wrap the engine collection in the SMRT-owned public surface. The wrapper\n // projects rows to plain DTOs at every read boundary (toArray/get and change\n // payloads) so the engine's virtual props never escape, and confines the\n // engine's own types to this module.\n const handle: SmrtWebCollection<TData> = {\n get toArray() {\n return collection.toArray.map((row) => toPlainRow<TData>(row));\n },\n get size() {\n return collection.size;\n },\n has(key) {\n return collection.has(key);\n },\n get(key) {\n const row = collection.get(key);\n return row === undefined ? undefined : toPlainRow<TData>(row);\n },\n preload() {\n // Gate the first read on a pending async warmStart (#1755) so an async\n // rehydrate (persistence over OPFS/IndexedDB) reliably seeds the cache\n // BEFORE the engine's own load runs — otherwise the fetch that persistence\n // was meant to suppress would race ahead. Resolved/rejected warmStart is\n // already handled (seeded or logged); we only need to await settlement.\n //\n // When there is NO pending warmStart (incl. the no-op path and the\n // `initialData` seed path) return the engine promise DIRECTLY — no extra\n // async wrapper — so the microtask timing is byte-identical to before the\n // seam (a wrapper tick would let a staleTime:0 revalidation land early).\n if (!warmStartPending) return collection.preload();\n // If cleanup() ran while the warmStart was still pending, don't preload the\n // torn-down engine — resolve to nothing.\n return warmStartPending.then(() => {\n if (disposed) return;\n return collection.preload();\n });\n },\n async cleanup() {\n // Mark disposed FIRST so any still-pending async warmStart continuation\n // sees it and skips seeding the shared cache / preloading the dead engine\n // (#1755, Fix G) — even though the continuation runs on a later tick.\n disposed = true;\n // Tear down the engine first, THEN each capability (#1755) — so a\n // capability's teardown runs against a stopped engine. Awaited so async\n // teardowns (closing an SSE stream, flushing a store) complete before\n // cleanup() resolves. Per-capability try/catch so one rejecting teardown\n // does not skip the others nor reject cleanup(). With no capabilities this\n // awaits nothing extra.\n await collection.cleanup();\n for (const capability of capabilities) {\n try {\n await capability.teardown?.(ctx);\n } catch (error) {\n warnCapability(capability, 'teardown', error);\n }\n }\n },\n subscribeChanges(callback) {\n const subscription = collection.subscribeChanges((changes: unknown) =>\n callback(projectChanges(changes)),\n );\n return { unsubscribe: () => subscription.unsubscribe() };\n },\n insert(row) {\n return collection.insert(row) as unknown as SmrtWebTransaction;\n },\n };\n\n engineCollections.set(handle, collection);\n\n // onAttach (#1755): now that the engine collection exists, let each capability\n // wire an external (non-mutation) trigger — an SSE subscription, a focus\n // listener — with a callable `ctx.invalidate()`. Runs ONCE, in array order.\n // Per-capability try/catch so a throwing onAttach cannot break construction\n // after the handle is already registered, nor skip later capabilities. With\n // no capabilities this loop is empty — the no-op path.\n for (const capability of capabilities) {\n try {\n capability.onAttach?.(ctx);\n } catch (error) {\n warnCapability(capability, 'onAttach', error);\n }\n }\n\n return handle;\n}\n\n// ---------------------------------------------------------------------------\n// WebMCP browser tool registration (#1812)\n// ---------------------------------------------------------------------------\n\nexport {\n type RegisterWebMcpToolsOptions,\n registerWebMcpTools,\n} from './webmcp.js';\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["released","release","reason"],"sources":["../src/capability.ts","../src/durable-store.ts","../src/offline/durable-queue.ts","../src/offline/leader.ts","../src/offline/types.ts","../src/offline/engine.ts","../src/offline.ts","../src/persistence/snapshot-store.ts","../src/persistence.ts","../src/sse-client.ts","../src/update-state/meta-store.ts","../src/update-state.ts","../src/webmcp.ts","../src/index.ts"],"sourcesContent":["/**\n * @happyvertical/smrt-web — capability extension seam (#1755).\n *\n * A capability is a small plug-in that hooks the {@link createSmrtCollection}\n * lifecycle so a follow-on client slice — the offline outbox (#1762),\n * persistence (#1764), or live SSE invalidation (#1763-client) — can live in\n * its OWN module instead of contending on `index.ts`. The factory runs the six\n * hooks below at fixed points; a collection with no capabilities is\n * byte-for-byte the collection of today (the no-op guarantee).\n *\n * ## The engine boundary is sacred\n *\n * Every type here is expressed ENTIRELY in existing SMRT-owned terms\n * (`SmrtWebCollectionDefinition`, `SmrtCrudFetchers`, `SmrtWebRow`, plain TS).\n * No `@tanstack/*` type — no `QueryClient`, no engine `Collection` — appears on\n * the capability surface, so the build's `.d.ts` boundary check stays green and\n * the engine stays swappable. A capability that genuinely needs deeper engine\n * access reaches it through the existing `getEngineCollection()` unknown-bridge\n * from inside its own module — NEVER by widening these types.\n *\n * These types intentionally import nothing from `./index.ts` beyond the\n * SMRT-owned contracts they name, so the seam is reviewable and unit-testable on\n * its own (see {@link runWrapMutation}).\n */\n\nimport type {\n SmrtCrudFetchers,\n SmrtWebCollectionDefinition,\n SmrtWebRow,\n SmrtWebSubscription,\n} from './index.js';\n\n/**\n * The context every capability hook receives — a stable, engine-free view of\n * the collection being built. All fields are the exact values the factory uses\n * internally (the resolved cache key/id, the same fetchers), so a capability\n * keys its own storage or subscriptions off the identical discriminators.\n */\nexport interface SmrtWebCapabilityContext<TData extends object = object> {\n /** The generated definition this collection materializes. */\n readonly definition: SmrtWebCollectionDefinition<TData>;\n /** The CRUD fetchers the collection persists through. */\n readonly fetchers: SmrtCrudFetchers;\n /**\n * The `queryKey` segments the collection's reads use — a LIVE view: from\n * `onAttach`/`warmStart` onward it is the final key (including every\n * capability-contributed segment); read during `contributeCacheKey` it omits\n * that capability's own not-yet-applied segment. Treat as read-only.\n */\n readonly cacheKey: readonly string[];\n /** The engine-collection id string (mirrors {@link cacheKey}). */\n readonly cacheId: string;\n /**\n * Enter the SAME relationship-derived invalidation the factory runs after a\n * settled mutation. A capability calls this to refetch this collection and its\n * manifest-related collections in response to an EXTERNAL trigger (e.g. an SSE\n * message) without reaching into the engine.\n */\n invalidate(): void;\n /**\n * A snapshot of the collection's current rows as plain public DTOs — the same\n * projection as {@link SmrtWebCollection.toArray}, so the engine's virtual\n * props never cross the boundary. The persistence slice (#1764) reads this in\n * its write-back to serialize the current cache to disk. Populated by the real\n * factory; a hand-built test context may omit it, so callers guard for its\n * presence.\n *\n * SAFE ONLY FROM `onAttach` ONWARD. The factory's implementation closes over\n * the engine collection, which is constructed AFTER `contributeCacheKey` and\n * `warmStart` run — calling this from those earlier hooks would throw\n * (temporal-dead-zone). Read rows from `onAttach`, `onSettled`, or an external\n * trigger, never during construction.\n */\n snapshot?(): ReadonlyArray<SmrtWebRow<TData>>;\n /**\n * Subscribe to the collection's change notifications — the same stream as\n * {@link SmrtWebCollection.subscribeChanges} (plain-DTO payloads). The\n * persistence slice (#1764) wires its debounced write-back here in `onAttach`\n * and detaches in `teardown`. Engine-free by construction: the payload is\n * `unknown` (the capability only needs the change SIGNAL, then re-reads via\n * {@link snapshot}), so no engine type crosses the seam. Populated by the real\n * factory; a hand-built test context may omit it.\n *\n * SAFE ONLY FROM `onAttach` ONWARD (same reason as {@link snapshot}): it closes\n * over the engine collection built after the pre-construction hooks.\n */\n subscribe?(callback: (changes: unknown) => void): SmrtWebSubscription;\n}\n\n/**\n * A single mutation described in SMRT-owned terms — handed to\n * {@link SmrtWebCapability.wrapMutation} and {@link SmrtWebCapability.onSettled}\n * so a capability can inspect or intercept a write without touching the engine's\n * transaction type.\n */\nexport interface SmrtWebMutationEnvelope {\n /** Which mutation kind this describes. */\n readonly kind: 'insert' | 'update' | 'delete';\n /** The row key: the client-local id on insert, else the target row's id. */\n readonly key: string;\n /**\n * The write payload: the full row on insert, the changed fields on update,\n * and an empty object on delete (the key carries the target).\n */\n readonly data: Record<string, unknown>;\n /**\n * The server `updated_at` / `updatedAt` value the mutation is based on, when\n * known. Update/delete handlers capture this from the original row so offline\n * replay can preserve the sync/apply conflict guard even when the write\n * payload only carries changed fields (or no fields for delete).\n */\n readonly baseUpdatedAt?: string;\n}\n\n/** The outcome handed to {@link SmrtWebCapability.wrapMutation}. */\nexport type WrapMutationOutcome =\n | { handled: true; result: unknown }\n | { handled: false }\n | undefined;\n\n/** The settle outcome handed to {@link SmrtWebCapability.onSettled}. */\nexport type MutationSettleOutcome =\n | { ok: true; result: unknown }\n | { ok: false; error: unknown };\n\n/**\n * A capability plugged into {@link createSmrtCollection}. Every hook is\n * optional; a capability implements only the points its slice needs. Hooks fire\n * in these fixed places, and capabilities run in ARRAY ORDER:\n *\n * - `contributeCacheKey` — ONCE, before the engine collection is constructed.\n * - `warmStart` — ONCE, before the first read (seeds the cache).\n * - `wrapMutation` — per mutation, BEFORE the fetcher, with a chance to handle\n * the write itself (offline).\n * - `onSettled` — per mutation, AFTER it settles (success AND fetcher-throw).\n * - `onAttach` — ONCE, right after the engine collection is constructed; the\n * ONLY place a capability registers an external (non-mutation) trigger such as\n * an SSE subscription.\n * - `teardown` — in `cleanup()`, after the engine's own cleanup.\n */\nexport interface SmrtWebCapability<TData extends object = object> {\n /** Diagnostic name (e.g. `'offline-outbox'`). */\n readonly name: string;\n\n /**\n * Contribute extra cache-key segments so this capability can partition the\n * collection's cache (e.g. an outbox variant). Runs ONCE, before construction;\n * returned segments are appended to the collection's cache key/id. Return\n * `undefined` (or omit) to contribute nothing.\n */\n contributeCacheKey?(\n ctx: SmrtWebCapabilityContext<TData>,\n ): string[] | undefined;\n\n /**\n * Provide rows to seed the cache before the first read — the persistence\n * slice's rehydrate-from-disk path. Runs ONCE, before construction. NOTE:\n * caller-supplied `initialData` (fresher same-request SSR truth) WINS over any\n * capability `warmStart`. Return `undefined` to contribute no seed.\n */\n warmStart?(\n ctx: SmrtWebCapabilityContext<TData>,\n ): Promise<SmrtWebRow<TData>[] | undefined> | SmrtWebRow<TData>[] | undefined;\n\n /**\n * Intercept a mutation BEFORE its fetcher runs. Return `{ handled: true,\n * result }` to take over the write (the fetcher is skipped and `result`\n * reconciles the optimistic row — the offline path); return `{ handled: false\n * }` or `undefined` to fall through to the real fetcher. With multiple\n * capabilities the FIRST `{ handled: true }` wins and later capabilities'\n * `wrapMutation` are skipped.\n */\n wrapMutation?(\n envelope: SmrtWebMutationEnvelope,\n ctx: SmrtWebCapabilityContext<TData>,\n ): Promise<WrapMutationOutcome> | WrapMutationOutcome;\n\n /**\n * Observe a mutation after it settles — on BOTH a successful persist and a\n * fetcher throw (before the optimistic rollback propagates). Never swallows\n * the error: a thrown fetcher error still rolls the transaction back.\n */\n onSettled?(\n envelope: SmrtWebMutationEnvelope,\n outcome: MutationSettleOutcome,\n ctx: SmrtWebCapabilityContext<TData>,\n ): void;\n\n /**\n * Run ONCE, right after the engine collection is constructed. The ONLY hook\n * where a capability wires an external trigger (SSE subscription, focus\n * listener) — `ctx.invalidate()` is callable here.\n */\n onAttach?(ctx: SmrtWebCapabilityContext<TData>): void;\n\n /** Run inside `cleanup()`, AFTER the engine's own cleanup. Awaited if async. */\n teardown?(ctx: SmrtWebCapabilityContext<TData>): void | Promise<void>;\n}\n\n/**\n * Run the `wrapMutation` hook across `capabilities` in array order for one\n * mutation, short-circuiting on the FIRST capability that returns `{ handled:\n * true }` (later capabilities' `wrapMutation` are then skipped). A capability\n * that returns `{ handled: false }`, `undefined`, or omits the hook declines,\n * and the next is tried. When every capability declines, resolves `{ handled:\n * false }` so the factory falls through to the real fetcher.\n */\nexport async function runWrapMutation<TData extends object>(\n capabilities: readonly SmrtWebCapability<TData>[],\n envelope: SmrtWebMutationEnvelope,\n ctx: SmrtWebCapabilityContext<TData>,\n): Promise<{ handled: true; result: unknown } | { handled: false }> {\n for (const capability of capabilities) {\n if (!capability.wrapMutation) continue;\n const outcome = await capability.wrapMutation(envelope, ctx);\n if (outcome?.handled) {\n return { handled: true, result: outcome.result };\n }\n }\n return { handled: false };\n}\n","/**\n * @happyvertical/smrt-web — shared durable-store foundation (#1755).\n *\n * The ONE SMRT-layer namespacing + wipe registry that the future offline\n * outbox (#1762) and persistence (#1764) slices both build on. Pure bookkeeping\n * — ZERO client-data-engine (`@tanstack/*`) imports — so it stays inside the\n * engine-absorption boundary and ships now, before either consumer exists, so\n * the two slices agree on it from day one.\n *\n * Why a SMRT-layer registry rather than one storage engine: TanStack DB\n * persistence (SQLite-WASM / OPFS) and `@tanstack/offline-transactions`\n * (IndexedDB) are SEPARATE storage engines. There is no single primitive that\n * spans both, so the shared foundation lives one level up — a deterministic\n * namespace both slices key their own storage under, plus a registry so\n * {@link wipeDurableStore} can clear BOTH through one call without the outbox\n * and persistence modules importing each other. A logout / tenant-switch wipes\n * every durable artifact for a namespace in one place.\n *\n * Nothing in this package calls these yet — the seam ships ahead of its\n * consumers by design (see PRD #1755).\n */\n\n/**\n * The identity a durable namespace is derived from. Combining the API base with\n * the tenant + identity + manifest hash means a logout, a tenant switch, or a\n * schema change each land on a DIFFERENT namespace, so durable artifacts are\n * never reused across those boundaries.\n *\n * `manifestHash` is supplied by the caller (its source is #1764's call — a\n * schema-shape digest); this module is source-agnostic and treats it as an\n * opaque discriminator.\n */\nexport interface DurableStoreKey {\n /** API base path the durable data was fetched against (e.g. `/api/v1`). */\n apiBase: string;\n /** Active tenant id, if any — absent means the single-tenant / global scope. */\n tenantId?: string;\n /** Authenticated identity id, if any — absent means anonymous. */\n identityId?: string;\n /** Opaque schema-shape digest; a change forces a fresh namespace. */\n manifestHash: string;\n}\n\n/**\n * Compute the deterministic storage namespace for a {@link DurableStoreKey}.\n * The same key always yields the same string; **any differing segment yields a\n * different one** (injective) — this is critical because the namespace IS the\n * tenant/identity/api isolation + wipe boundary, so a collision would reuse or\n * wipe durable data ACROSS those boundaries.\n *\n * Injectivity is guaranteed three ways: every segment is `encodeURIComponent`-\n * encoded, so a raw `:` in a value becomes `%3A` and can never be mistaken for a\n * separator; an ABSENT `tenantId` / `identityId` maps to the empty string while\n * a PRESENT value is prefixed with `_` (so undefined→``, ``→`_`, `x`→`_x`) — so\n * an explicitly-EMPTY id no longer collides with \"no id\", nor does a real id of\n * `-`. Both future slices key their own storage primitive (IndexedDB store name,\n * OPFS path, …) under this string.\n */\nexport function durableStoreNamespace(key: DurableStoreKey): string {\n // Optional segments: encode PRESENCE distinctly. undefined → '' (absent);\n // any present value (including '') → `_<encoded>`, so '' → '_' can never equal\n // the absent '' .\n const optional = (value: string | undefined): string =>\n value === undefined ? '' : `_${encodeURIComponent(value)}`;\n return `smrt-web:${encodeURIComponent(key.apiBase)}:${optional(key.tenantId)}:${optional(key.identityId)}:${encodeURIComponent(key.manifestHash)}`;\n}\n\n/**\n * A durable artifact registered under a namespace — the outbox queue (#1762) or\n * a persisted collection store (#1764). Each owns its storage engine and\n * exposes only a `clear()` so {@link wipeDurableStore} can tear it down without\n * knowing which engine backs it.\n */\nexport interface DurableResource {\n /** Which slice owns this artifact — for diagnostics and selective sweeps. */\n readonly kind: 'outbox' | 'persisted-collection';\n /** Drop this artifact's durable storage. Best-effort; may reject. */\n clear(): Promise<void>;\n}\n\n/**\n * Registry of durable artifacts keyed by namespace. A `Set` per namespace so a\n * resource registers and unregisters without positional bookkeeping, and so two\n * slices (outbox + persistence) coexist under one namespace.\n */\nconst registry = new Map<string, Set<DurableResource>>();\n\n/**\n * Register a durable artifact under a namespace so {@link wipeDurableStore} can\n * later clear it. Returns an unregister function that removes just this\n * resource — call it when the artifact is disposed on its own (before any\n * namespace-wide wipe) so it is not cleared twice.\n */\nexport function registerDurableResource(\n namespace: string,\n resource: DurableResource,\n): () => void {\n let resources = registry.get(namespace);\n if (!resources) {\n resources = new Set<DurableResource>();\n registry.set(namespace, resources);\n }\n resources.add(resource);\n\n return () => {\n const current = registry.get(namespace);\n if (!current) return;\n current.delete(resource);\n if (current.size === 0) registry.delete(namespace);\n };\n}\n\n/**\n * Clear every durable artifact registered under `namespace`, then drop the\n * namespace. This is the single teardown point a logout / tenant-switch calls:\n * it fans out across BOTH the outbox and persistence slices via the registry,\n * so neither module needs to import the other.\n *\n * Best-effort: a resource whose `clear()` rejects does not abort the sweep —\n * every registered resource is still cleared (a wipe is a teardown, not a\n * transaction). A safe no-op on an unknown or empty namespace.\n */\nexport async function wipeDurableStore(namespace: string): Promise<void> {\n const resources = registry.get(namespace);\n if (!resources || resources.size === 0) {\n registry.delete(namespace);\n return;\n }\n // Snapshot so a resource that unregisters itself inside clear() cannot mutate\n // the set mid-iteration. allSettled keeps a rejected clear() from aborting the\n // others.\n const snapshot = [...resources];\n registry.delete(namespace);\n await Promise.allSettled(snapshot.map((resource) => resource.clear()));\n}\n","/**\n * @happyvertical/smrt-web — the durable IndexedDB outbox queue (#1762).\n *\n * A raw IndexedDB FIFO queue: the persistence half of the offline outbox. One\n * database per durable-store namespace ({@link durableStoreNamespace}), one\n * object store (`outbox`) keyed by an AUTO-INCREMENTING `seq` — so insertion\n * order IS replay order for free — plus a `state` index so the replay loop can\n * scan only rows that still need work.\n *\n * ## Why raw IndexedDB, not `@tanstack/offline-transactions`\n *\n * The decisive finding (#1762): the TanStack offline layer's public API is\n * engine-typed (`Collection<…>`, `mutationFns`), so importing it emits a\n * `@tanstack/` specifier into `dist/offline.d.ts`, which\n * `scripts/check-smrt-web-engine-boundary.mjs` scans for unconditionally — the\n * build would fail. It also pins `@tanstack/db` exactly and competes with\n * smrt-web's OWN mutation lifecycle. So the outbox is hand-rolled over browser\n * globals (`indexedDB`, `navigator.locks`) with ZERO new runtime dependency.\n *\n * This module is engine-free (no `@tanstack/*` import): it stays inside the\n * engine-absorption boundary (#1761) and speaks only the sync-apply contract's\n * op shape (`packages/core/src/sync/apply.ts`).\n *\n * Row payloads are stored as structured-clone-safe plain objects; IndexedDB\n * clones on write, so callers may keep mutating the source object after enqueue.\n */\n\nimport type { SyncApplyOp } from './types.js';\n\n/** IndexedDB object-store name inside every outbox database. */\nexport const OUTBOX_STORE = 'outbox';\n/** Index over {@link OutboxRow.state} — the replay scan reads it. */\nexport const OUTBOX_STATE_INDEX = 'state';\n/** IndexedDB schema version. Bump only on a store/index migration. */\nexport const OUTBOX_DB_VERSION = 1;\n\n/**\n * Durable lifecycle state of one queued mutation. Deliberately a SUPERSET of\n * the app-observable sync state: `pending` rows are due for (re)send, while\n * `synced`/`failed` are terminal tombstones removed after each drain. The\n * app-facing {@link OutboxSyncState} (`pending`/`uploading`/`synced`/`failed`)\n * is derived by the engine — `uploading` is a transient in-flight marker the\n * engine emits, never persisted; this enum is the on-disk truth.\n */\nexport type OutboxRowState = 'pending' | 'synced' | 'failed';\n\n/**\n * One durable outbox row — a single client-authored mutation awaiting replay\n * through `POST {basePath}/sync/apply`. Fields mirror the sync-apply item so a\n * row maps to a batch item with no translation, plus the durable bookkeeping the\n * replay loop needs (`state`, `attempts`, `nextAttemptAt`, `lastError`).\n */\nexport interface OutboxRow {\n /** Auto-incrementing primary key — monotonic, so `seq` order IS FIFO. */\n seq?: number;\n /**\n * The sync-apply `itemId`: the client's idempotency/correlation handle. A\n * fresh UUID minted at enqueue time, distinct from {@link id} (the row UUID),\n * so it survives coalescing and is echoed back verbatim by the endpoint.\n */\n itemId: string;\n /** The collection route segment (e.g. `products`) — sync-apply `object`. */\n object: string;\n /** Which mutation kind — sync-apply `op`. */\n op: SyncApplyOp;\n /** The client-generated row UUID — sync-apply `id`. */\n id: string;\n /** Field data for create/update; omitted for delete. */\n payload?: Record<string, unknown>;\n /** The server `updated_at` the client last saw — the conflict guard base. */\n baseUpdatedAt?: string;\n /** On-disk lifecycle state. */\n state: OutboxRowState;\n /** How many replay attempts this row has made (drives backoff). */\n attempts: number;\n /** Epoch ms before which this row must not be retried (backoff gate). */\n nextAttemptAt: number;\n /** Last replay error message, for diagnostics/`snapshot()`. */\n lastError?: string;\n /** Epoch ms the row was first enqueued — stable ordering tiebreak + age. */\n enqueuedAt: number;\n}\n\n/** The fields a caller supplies to {@link DurableOutboxQueue.enqueue}. */\nexport interface EnqueueInput {\n itemId: string;\n object: string;\n op: SyncApplyOp;\n id: string;\n payload?: Record<string, unknown>;\n baseUpdatedAt?: string;\n}\n\n/**\n * Promisify a single IndexedDB request. Rejects with the request's error (or a\n * generic one) so callers get a real rejection, never a silent `undefined`.\n */\nfunction promisifyRequest<T>(request: IDBRequest<T>): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n request.onsuccess = () => resolve(request.result);\n request.onerror = () =>\n reject(request.error ?? new Error('[smrt-web] IndexedDB request failed'));\n });\n}\n\n/**\n * Promisify a transaction's completion. IndexedDB writes are only durable once\n * the TRANSACTION completes (not when the request succeeds), so mutations await\n * this — a queue that reported success before the txn committed could lose a\n * write on a crash in the commit window, defeating the whole point.\n */\nfunction awaitTransaction(tx: IDBTransaction): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n tx.oncomplete = () => resolve();\n tx.onerror = () =>\n reject(tx.error ?? new Error('[smrt-web] IndexedDB transaction failed'));\n tx.onabort = () =>\n reject(tx.error ?? new Error('[smrt-web] IndexedDB transaction aborted'));\n });\n}\n\n/**\n * Feature-detect a usable IndexedDB. Some environments expose `indexedDB` but\n * throw on `open()` (private-mode Firefox historically, sandboxed iframes), so a\n * real probe opens a throwaway database. Returns `false` rather than throwing so\n * the engine can degrade to a no-op instead of crashing the host.\n */\nexport async function probeIndexedDb(): Promise<boolean> {\n const idb = (globalThis as { indexedDB?: IDBFactory }).indexedDB;\n if (!idb) return false;\n const probeName = '__smrt_web_outbox_probe__';\n try {\n const db = await new Promise<IDBDatabase>((resolve, reject) => {\n const request = idb.open(probeName, 1);\n request.onsuccess = () => resolve(request.result);\n request.onerror = () =>\n reject(request.error ?? new Error('probe failed'));\n request.onblocked = () => reject(new Error('probe blocked'));\n });\n db.close();\n // Best-effort cleanup; ignore failure.\n try {\n idb.deleteDatabase(probeName);\n } catch {\n /* ignore */\n }\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * The durable FIFO outbox queue over one IndexedDB database. Constructed via\n * {@link openDurableOutboxQueue} (async open + schema upgrade), then used\n * through the async methods below (each resolves once its txn commits).\n *\n * NOT concurrency-controlled across tabs on its own — the OutboxEngine layers\n * Web Locks leader election on top so exactly one tab drains it. Within one tab,\n * IndexedDB's own transaction serialization is sufficient.\n */\nexport class DurableOutboxQueue {\n private readonly db: IDBDatabase;\n /** The IndexedDB database name (== the durable-store namespace). */\n readonly dbName: string;\n\n constructor(db: IDBDatabase, dbName: string) {\n this.db = db;\n this.dbName = dbName;\n }\n\n /**\n * Append a mutation to the tail of the queue in state `pending`, due\n * immediately (`nextAttemptAt = 0`, `attempts = 0`). Resolves with the\n * assigned `seq` once the write is durably committed.\n */\n async enqueue(input: EnqueueInput): Promise<number> {\n const now = Date.now();\n const row: OutboxRow = {\n itemId: input.itemId,\n object: input.object,\n op: input.op,\n id: input.id,\n payload: input.payload,\n baseUpdatedAt: input.baseUpdatedAt,\n state: 'pending',\n attempts: 0,\n nextAttemptAt: 0,\n enqueuedAt: now,\n };\n const tx = this.db.transaction(OUTBOX_STORE, 'readwrite');\n const store = tx.objectStore(OUTBOX_STORE);\n const seq = await promisifyRequest(store.add(row));\n await awaitTransaction(tx);\n return seq as number;\n }\n\n /**\n * Persist a state transition (and any of attempts/backoff/error) for the row\n * at `seq`, reading-then-writing inside ONE transaction so a concurrent drain\n * in the same tab can't lose the update. A no-op if the row is already gone\n * (removed by a prior terminal transition).\n */\n async markState(\n seq: number,\n patch: Partial<\n Pick<OutboxRow, 'state' | 'attempts' | 'nextAttemptAt' | 'lastError'>\n >,\n ): Promise<void> {\n const tx = this.db.transaction(OUTBOX_STORE, 'readwrite');\n const store = tx.objectStore(OUTBOX_STORE);\n const existing = await promisifyRequest(\n store.get(seq) as IDBRequest<OutboxRow | undefined>,\n );\n if (!existing) {\n // Nothing to update; let the txn commit as a no-op.\n await awaitTransaction(tx);\n return;\n }\n const next: OutboxRow = { ...existing, ...patch, seq };\n await promisifyRequest(store.put(next));\n await awaitTransaction(tx);\n }\n\n /**\n * All rows that are due to (re)send at `now`: state `pending` AND\n * `nextAttemptAt <= now`, in ascending `seq` (FIFO). Uses the `state` index to\n * avoid scanning terminal tombstones. Terminal rows (`synced`/`failed`) are\n * excluded — they await removal, not replay.\n */\n async listPending(now: number): Promise<OutboxRow[]> {\n const tx = this.db.transaction(OUTBOX_STORE, 'readonly');\n const index = tx.objectStore(OUTBOX_STORE).index(OUTBOX_STATE_INDEX);\n const rows = await promisifyRequest(\n index.getAll(IDBKeyRange.only('pending')) as IDBRequest<OutboxRow[]>,\n );\n await awaitTransaction(tx);\n return rows\n .filter((row) => row.nextAttemptAt <= now)\n .sort((a, b) => (a.seq ?? 0) - (b.seq ?? 0));\n }\n\n /** Remove the row at `seq` (a terminal transition drops it). */\n async remove(seq: number): Promise<void> {\n const tx = this.db.transaction(OUTBOX_STORE, 'readwrite');\n await promisifyRequest(tx.objectStore(OUTBOX_STORE).delete(seq));\n await awaitTransaction(tx);\n }\n\n /** Every row currently in the queue (any state), ascending `seq`. */\n async all(): Promise<OutboxRow[]> {\n const tx = this.db.transaction(OUTBOX_STORE, 'readonly');\n const rows = await promisifyRequest(\n tx.objectStore(OUTBOX_STORE).getAll() as IDBRequest<OutboxRow[]>,\n );\n await awaitTransaction(tx);\n return rows.sort((a, b) => (a.seq ?? 0) - (b.seq ?? 0));\n }\n\n /**\n * Drop every row — the durable-store `clear()` for `wipeDurableStore`. Empties\n * the store but keeps the database (and its `seq` autoincrement counter) so a\n * subsequent enqueue still gets fresh monotonic ids.\n */\n async clear(): Promise<void> {\n const tx = this.db.transaction(OUTBOX_STORE, 'readwrite');\n await promisifyRequest(tx.objectStore(OUTBOX_STORE).clear());\n await awaitTransaction(tx);\n }\n\n /** Close the underlying database handle (called on engine dispose). */\n close(): void {\n this.db.close();\n }\n}\n\n/**\n * Open (creating/upgrading as needed) the outbox database for `dbName` and wrap\n * it in a {@link DurableOutboxQueue}. `dbName` MUST be a\n * {@link durableStoreNamespace} string — already `encodeURIComponent`-safe and\n * carrying the api/tenant/identity/manifest isolation boundary, so two\n * identities never share one queue database.\n *\n * The `onupgradeneeded` handler creates the `outbox` store keyed by\n * autoincrementing `seq` and the `state` index. Idempotent: re-opening an\n * existing database at the same version skips the upgrade — which is exactly the\n * \"reload\" path (a new tab re-opens the same durable backing and sees prior\n * rows).\n */\nexport function openDurableOutboxQueue(\n dbName: string,\n): Promise<DurableOutboxQueue> {\n const idb = (globalThis as { indexedDB?: IDBFactory }).indexedDB;\n if (!idb) {\n return Promise.reject(\n new Error('[smrt-web] IndexedDB is unavailable in this environment'),\n );\n }\n return new Promise<DurableOutboxQueue>((resolve, reject) => {\n const request = idb.open(dbName, OUTBOX_DB_VERSION);\n request.onupgradeneeded = () => {\n const db = request.result;\n if (!db.objectStoreNames.contains(OUTBOX_STORE)) {\n const store = db.createObjectStore(OUTBOX_STORE, {\n keyPath: 'seq',\n autoIncrement: true,\n });\n store.createIndex(OUTBOX_STATE_INDEX, 'state', { unique: false });\n }\n };\n request.onsuccess = () =>\n resolve(new DurableOutboxQueue(request.result, dbName));\n request.onerror = () =>\n reject(\n request.error ??\n new Error(`[smrt-web] failed to open outbox database \"${dbName}\"`),\n );\n request.onblocked = () =>\n reject(\n new Error(`[smrt-web] opening outbox database \"${dbName}\" was blocked`),\n );\n });\n}\n","/**\n * @happyvertical/smrt-web — Web Locks leader election for the outbox (#1762).\n *\n * With multiple tabs open, exactly ONE must replay the shared outbox queue, or\n * two tabs would POST the same batch concurrently. The mechanism is the native\n * Web Locks API (`navigator.locks`): a tab requests an EXCLUSIVE lock keyed by\n * the durable-store namespace and holds it — via a promise that resolves only on\n * release — for as long as it should be leader. The browser grants the lock to\n * exactly one waiter at a time and AUTO-RELEASES it on tab crash/close/navigation\n * with no heartbeat, so the next waiting tab becomes leader instantly and there\n * is never a stuck lock.\n *\n * Why Web Locks over a BroadcastChannel election (v1 decision, locked): Web\n * Locks gives crash-safe, race-free, browser-arbitrated exclusion for free —\n * a hand-rolled BroadcastChannel election has to solve liveness (heartbeats,\n * takeover on a silent crash) itself. BroadcastChannel is a possible future\n * enhancement (cross-tab state fan-out), not the election primitive.\n *\n * ## Single-tab fallback (documented gap)\n *\n * When `navigator.locks` is absent (older Safari, non-browser hosts), this warns\n * ONCE and falls back to acquiring leadership immediately and unconditionally.\n * The outbox STILL replays — a lone tab is trivially the only replayer — but the\n * multi-tab exactly-one-replayer guarantee does not hold across tabs that all\n * took the fallback. This is a deliberate v1 limitation, NOT masked by a\n * BroadcastChannel shim.\n *\n * Engine-free: no `@tanstack/*` import — stays inside the boundary (#1761).\n */\n\n/**\n * A handle to relinquish leadership: call it to release the Web Lock (or cancel\n * a still-pending request). Idempotent.\n */\nexport type LeadershipHandle = () => void;\n\n/** Structural view of the Web Locks API surface this module uses. */\ninterface LockManagerLike {\n request(\n name: string,\n options: { signal?: AbortSignal; mode?: 'exclusive' | 'shared' },\n callback: () => Promise<unknown>,\n ): Promise<unknown>;\n}\n\n/** Detect the Web Locks API on the current `navigator`. */\nfunction getLockManager(): LockManagerLike | undefined {\n const nav = (globalThis as { navigator?: { locks?: unknown } }).navigator;\n const locks = nav?.locks as LockManagerLike | undefined;\n if (locks && typeof locks.request === 'function') return locks;\n return undefined;\n}\n\n/** Gate so the single-tab-fallback warning is logged at most once per session. */\nlet warnedNoLocks = false;\n\n/**\n * Acquire cross-tab leadership for `lockName` (the outbox's\n * `smrt-web-outbox-leader:<namespace>`).\n *\n * When the lock is granted this tab becomes leader and `onAcquired()` fires; the\n * tab stays leader — holding the lock — until the returned handle is called (or\n * the tab dies, when the browser auto-releases). On release, `onReleased()`\n * fires. Requesting is non-blocking: this returns synchronously with the\n * release handle while the request waits in the background for its turn.\n *\n * Contract:\n * - The lock is requested EXCLUSIVE; the callback returns a promise that stays\n * pending until the release handle resolves it, so the browser considers the\n * lock held for exactly that window.\n * - Calling the handle before the lock is even granted aborts the pending\n * request (via `AbortSignal`) so a torn-down engine never becomes leader\n * later. `onReleased` still fires so callers can settle their own state.\n * - No Web Locks support → single-tab fallback: `onAcquired()` fires on the next\n * microtask (leadership is immediate and unconditional), the handle just fires\n * `onReleased()`.\n */\nexport function acquireLeadership(\n lockName: string,\n onAcquired: () => void,\n onReleased: () => void,\n): LeadershipHandle {\n const locks = getLockManager();\n\n // --- Single-tab fallback: no Web Locks in this environment. ---\n if (!locks) {\n if (!warnedNoLocks) {\n warnedNoLocks = true;\n // biome-ignore lint/suspicious/noConsole: smrt-web has no logger dep (TanStack-only); the single-tab-fallback gap is surfaced via a one-time console.warn by design (#1762)\n console.warn(\n '[smrt-web] Web Locks API unavailable — the offline outbox falls back to single-tab leadership; the multi-tab exactly-one-replayer guarantee does not hold across tabs.',\n );\n }\n let released = false;\n const release = () => {\n if (released) return;\n released = true;\n onReleased();\n };\n // Grant leadership asynchronously so callers can wire state before it fires,\n // matching the Web-Locks path (never synchronous inside the constructor).\n queueMicrotask(() => {\n if (!released) onAcquired();\n });\n return release;\n }\n\n // --- Web Locks path. ---\n const controller = new AbortController();\n // The promise the lock callback returns; it resolves ONLY when we release, so\n // the browser holds the lock for exactly the leadership window.\n let releaseHeldLock: (() => void) | undefined;\n let released = false;\n let acquired = false;\n\n const release: LeadershipHandle = () => {\n if (released) return;\n released = true;\n if (acquired && releaseHeldLock) {\n // We hold the lock: resolve the callback's promise to release it.\n releaseHeldLock();\n } else {\n // Still waiting in the queue: abort the pending request so we never\n // become leader after teardown.\n controller.abort();\n }\n onReleased();\n };\n\n void locks\n .request(lockName, { signal: controller.signal, mode: 'exclusive' }, () => {\n // Granted — we are now leader. Hold the lock until release() resolves this.\n acquired = true;\n // If release() already ran while the request was pending, the abort above\n // fired instead and this callback never runs; guard anyway.\n if (released) return Promise.resolve();\n onAcquired();\n return new Promise<void>((resolve) => {\n releaseHeldLock = resolve;\n });\n })\n .catch((error: unknown) => {\n // An AbortError is the expected outcome when release() cancels a pending\n // request — not a fault. Any other rejection means the lock could not be\n // held; surface it so a lock subsystem problem is visible, and ensure\n // onReleased still fires so callers settle.\n const name = (error as { name?: string })?.name;\n if (name !== 'AbortError') {\n // biome-ignore lint/suspicious/noConsole: smrt-web has no logger dep; a lock-request failure is surfaced via console.warn (#1762)\n console.warn('[smrt-web] outbox leader lock request failed', error);\n }\n if (!released) {\n released = true;\n onReleased();\n }\n });\n\n return release;\n}\n","/**\n * @happyvertical/smrt-web — offline outbox shared types (#1762).\n *\n * The SMRT-owned vocabulary the outbox modules (`durable-queue`, `leader`,\n * `engine`) and the public surface (`offline.ts`) share. Kept in its own leaf\n * module — imported by everything, importing nothing from the package — so there\n * is no cycle back through `index.ts` and the whole outbox stays inside the\n * engine-absorption boundary (#1761): NOT ONE `@tanstack/*` type appears here.\n *\n * The sync-apply op/status/reason names mirror\n * `packages/core/src/sync/apply.ts` (the replay target) verbatim, so a queue row\n * maps to a batch item and a batch result maps to a state transition with no\n * translation layer. smrt-web has no inter-smrt dependency (dependency-DAG\n * guardrails), so these are re-declared here rather than imported from core.\n */\n\n/**\n * Mutation kinds the sync-apply batch endpoint accepts. Mirrors core's\n * `SyncApplyOp`. The outbox maps the capability seam's insert/update/delete\n * envelope kinds onto these (`insert → create`).\n */\nexport type SyncApplyOp = 'create' | 'update' | 'delete';\n\n/**\n * Per-item outcome status from a sync-apply batch result. Mirrors core's\n * `SyncApplyStatus`. The engine maps each onto a durable transition per the\n * contract's \"Web outbox (#1762)\" consumer notes.\n */\nexport type SyncApplyStatus = 'applied' | 'conflict' | 'rejected';\n\n/**\n * Machine-readable reasons a sync-apply item was `conflict` or `rejected`.\n * Mirrors core's `SyncApplyReason` union. Drives the engine's result mapping:\n * `auth_required`/`forbidden` pause the loop; `write_failed` retries; the rest\n * are terminal.\n */\nexport type SyncApplyReason =\n | 'invalid_item'\n | 'invalid_id'\n | 'invalid_payload'\n | 'unknown_object'\n | 'op_not_allowed'\n | 'auth_required'\n | 'forbidden'\n | 'not_found'\n | 'id_conflict'\n | 'write_failed'\n | 'stale_write'\n | 'create_conflict';\n\n/**\n * One item in a `POST {basePath}/sync/apply` request body. Mirrors core's\n * `SyncApplyItem`. Built from an {@link OutboxRow} 1:1.\n */\nexport interface SyncApplyItem {\n itemId: string;\n object: string;\n op: SyncApplyOp;\n id: string;\n payload?: Record<string, unknown>;\n baseUpdatedAt?: string;\n}\n\n/**\n * One entry in a sync-apply batch response's `results[]`. Mirrors core's\n * `SyncApplyItemResult`. `results[i]` corresponds to `items[i]` positionally;\n * `itemId` is echoed for convenience.\n */\nexport interface SyncApplyItemResult {\n itemId: string | null;\n id: string | null;\n status: SyncApplyStatus;\n reason?: SyncApplyReason;\n updatedAt?: string;\n}\n\n/** The HTTP-200 response body of `POST {basePath}/sync/apply`. */\nexport interface SyncApplyBatchResponse {\n results: SyncApplyItemResult[];\n}\n\n/**\n * Maximum items the endpoint accepts in one batch (core's\n * `MAX_SYNC_APPLY_BATCH_SIZE`). The engine chunks a larger backlog into\n * successive POSTs, oldest-first, so FIFO order is preserved across chunks.\n */\nexport const MAX_SYNC_APPLY_BATCH_SIZE = 1000;\n\n/**\n * The URL segments of the batch apply endpoint relative to the API base path:\n * `POST {basePath}/sync/apply` (core's `SYNC_APPLY_ROUTE_SEGMENTS`).\n */\nexport const SYNC_APPLY_ROUTE_SEGMENTS = ['sync', 'apply'] as const;\n\n/**\n * The app-observable sync state of a queued mutation — the SAME four-state\n * machine the KMP mobile foundation exposes (ADR 0001), so web and mobile\n * outbox indicators render identically:\n *\n * - `pending` — enqueued, awaiting (re)send.\n * - `uploading` — currently in a sync-apply POST.\n * - `synced` — the mutation's effect is confirmed on the server (a terminal\n * success — INCLUDING a surfaced conflict, which is a RESOLVED outcome: the\n * server state won and the item left the queue).\n * - `failed` — a terminal rejection the client cannot resolve by retrying\n * (`invalid_*`, `not_found`, `id_conflict`, `op_not_allowed`, …) — surfaced\n * for app-level handling.\n */\nexport type OutboxSyncState = 'pending' | 'uploading' | 'synced' | 'failed';\n\n/**\n * A sync-state transition delivered to {@link OfflineOutboxConfig.onSyncStateChange}.\n * A PUSH callback (not a subscribable store): smrt-svelte wraps it into a\n * reactive binding later (#1762 follow-on). Carries enough for an app to render\n * a per-row indicator and a retry affordance.\n */\nexport interface SyncStateEvent {\n /** The queue row's `itemId` (its idempotency handle) this state is for. */\n itemId: string;\n /** The client-generated row UUID the mutation targets. */\n rowId: string;\n /** The collection route segment (e.g. `products`). */\n object: string;\n /** The new observable state. */\n state: OutboxSyncState;\n /** How many replay attempts have run (0 until the first send). */\n attempts: number;\n /** The last replay error message, when `state` is `pending` after a retry. */\n error?: string;\n}\n\n/**\n * A conflict surfaced to {@link OfflineOutboxConfig.onConflict} when a replayed\n * item comes back `conflict`. Per the contract this is a RESOLVED outcome — the\n * server state won, the item is removed, and its terminal observable state is\n * `synced` — so an app treats it as \"your write was superseded; here is the\n * server's `updatedAt` to rebase from\", NOT as a failure to retry.\n */\nexport interface OutboxConflict {\n /** The queue row's `itemId`. */\n itemId: string;\n /** The collection route segment. */\n object: string;\n /** The client-generated row UUID that conflicted. */\n rowId: string;\n /**\n * Why the item conflicted: `stale_write` (an update/delete whose\n * `baseUpdatedAt` was older than the server row) or `create_conflict` (a\n * create landing on an existing, diverged row).\n */\n reason: 'stale_write' | 'create_conflict';\n /**\n * The server row's `updated_at` after processing — the value to persist as\n * the new `baseUpdatedAt` before re-editing. Present when the endpoint\n * returned it.\n */\n serverUpdatedAt?: string;\n}\n\n/** Exponential-backoff tuning for retryable replay failures. */\nexport interface OutboxBackoff {\n /** Delay before the first retry, ms (default 1000). */\n initialDelayMs?: number;\n /** Multiplier applied per attempt (default 2). */\n multiplier?: number;\n /** Ceiling on the computed delay, ms (default 60000). */\n maxDelayMs?: number;\n}\n\n/** Resolved backoff config (defaults filled in). */\nexport interface ResolvedBackoff {\n initialDelayMs: number;\n multiplier: number;\n maxDelayMs: number;\n}\n\n/** Backoff defaults, per the blueprint (initial=1s, ×2, cap 60s). */\nexport const DEFAULT_BACKOFF: ResolvedBackoff = {\n initialDelayMs: 1000,\n multiplier: 2,\n maxDelayMs: 60000,\n};\n\n/**\n * Compute the backoff delay before the NEXT attempt given how many attempts\n * have already been made. Exponential with a ceiling, then multiplied by a\n * jitter factor in `[0.5, 1.0)` to spread a thundering herd of tabs/rows\n * reconnecting at once: `min(maxDelay, initial * mult ** attempts) * jitter`.\n *\n * `attempts` is the count BEFORE this retry (so the first retry, after attempts\n * became 1, uses `initial * mult ** 1`? no — see below). We pass the attempt\n * count AFTER incrementing, and index the exponent off `attempts - 1` so the\n * first retry waits ~`initialDelayMs`. `random` is injectable for deterministic\n * tests.\n */\nexport function computeBackoffDelay(\n attempts: number,\n backoff: ResolvedBackoff,\n random: () => number = Math.random,\n): number {\n const exponent = Math.max(0, attempts - 1);\n const raw = backoff.initialDelayMs * backoff.multiplier ** exponent;\n const capped = Math.min(backoff.maxDelayMs, raw);\n // Jitter in [0.5, 1.0): full jitter's lower half, so a retry never waits\n // longer than the capped delay but is spread over half of it.\n const jitter = 0.5 + random() * 0.5;\n return Math.round(capped * jitter);\n}\n","/**\n * @happyvertical/smrt-web — the shared, namespace-keyed outbox engine (#1762).\n *\n * The engine owns the durable queue, the leader lock, and the replay loop for\n * ONE durable-store namespace, and is REF-COUNTED so that N collections sharing\n * a namespace share exactly ONE engine — one IndexedDB database, one leader\n * lock, one FIFO queue. This sharing is REQUIRED for correctness, not an\n * optimization: if each collection held its OWN leader lock, two tabs could each\n * win a different collection's lock and both replay the (shared) queue,\n * double-POSTing. One lock per namespace ⇒ one replayer per namespace across all\n * tabs.\n *\n * Replay maps sync-apply results onto durable transitions per the contract's\n * \"Web outbox (#1762)\" consumer notes\n * (docs/content/architecture/sync-apply-contract.md):\n *\n * | apply result | queue transition |\n * |-------------------------------------|----------------------------------------|\n * | `applied` | remove → `synced` |\n * | `conflict` (stale_write/create_conflict) | remove + fire onConflict → `synced` (a RESOLVED outcome) |\n * | `rejected` `write_failed` | keep, attempts++, backoff → `pending` |\n * | `rejected` `auth_required`/`forbidden` | PAUSE the loop, keep queued → `pending` |\n * | `rejected` other (`invalid_*`/`unknown_object`/`not_found`/`op_not_allowed`/`id_conflict`) | remove → `failed` (terminal) |\n * | network reject / non-200 / lost response | whole batch stays `pending`, drain stops behind it (blind replay is safe by construction — idempotency) |\n *\n * Because items carry client-generated UUIDs and the endpoint is idempotent\n * (`_insertOnly` create + no-op re-apply), a batch that was sent but whose\n * response was lost can be blindly re-sent with no duplicate rows — which is\n * why the network-failure path simply leaves the batch `pending`.\n *\n * Engine-free public surface: no `@tanstack/*` import — inside the boundary\n * (#1761). The one engine-adjacent value it receives is the SMRT-owned\n * durable-store namespace + registration hooks passed in `config`.\n */\n\nimport {\n type DurableOutboxQueue,\n type OutboxRow,\n openDurableOutboxQueue,\n probeIndexedDb,\n} from './durable-queue.js';\nimport { acquireLeadership, type LeadershipHandle } from './leader.js';\nimport {\n computeBackoffDelay,\n MAX_SYNC_APPLY_BATCH_SIZE,\n type OutboxConflict,\n type ResolvedBackoff,\n SYNC_APPLY_ROUTE_SEGMENTS,\n type SyncApplyBatchResponse,\n type SyncApplyItem,\n type SyncApplyItemResult,\n type SyncApplyOp,\n type SyncStateEvent,\n} from './types.js';\n\n/** Maps a capability-seam envelope kind to a sync-apply op. */\nexport function envelopeKindToOp(\n kind: 'insert' | 'update' | 'delete',\n): SyncApplyOp {\n return kind === 'insert' ? 'create' : kind;\n}\n\n/**\n * The bookkeeping a namespace's durable-store registration needs, supplied by\n * the public surface so the engine can key its queue and register for\n * {@link wipeDurableStore} WITHOUT importing `durable-store.ts` itself (keeping\n * this module's dependency surface minimal and the namespacing single-sourced in\n * the caller). `namespace` is the {@link durableStoreNamespace} string.\n */\nexport interface OutboxEngineConfig {\n /** The durable-store namespace string — the IDB dbName + lock-name root. */\n namespace: string;\n /** Absolute base path the sync-apply endpoint lives under (e.g. `/api/v1`). */\n syncApplyBasePath: string;\n /** Fetch implementation (injectable for tests/SSR). */\n fetchFn: typeof fetch;\n /** Resolved backoff parameters. */\n backoff: ResolvedBackoff;\n /**\n * Register this engine's queue as a durable resource so `wipeDurableStore`\n * can clear it; returns the unregister fn. Wraps `registerDurableResource`\n * from the caller so the engine stays decoupled from that module.\n */\n registerResource: (clear: () => Promise<void>) => () => void;\n /** Injectable RNG for deterministic backoff jitter in tests. */\n random?: () => number;\n}\n\n/**\n * A pending optimistic write to enqueue, in capability-seam terms. Callbacks are\n * NOT carried here — they are registered per-`object` at\n * {@link OutboxEngine.registerCollection} so that reloaded rows (which this\n * session never enqueued) still route their events to the collection.\n */\nexport interface OutboxEnqueueRequest {\n kind: 'insert' | 'update' | 'delete';\n /** The collection route segment (definition.name). */\n object: string;\n /** The client-generated row UUID (the optimistic row's id). */\n rowId: string;\n /** Full row (insert) / changed fields (update) / ignored (delete). */\n data: Record<string, unknown>;\n /** The server updated_at last seen for this row, for the conflict guard. */\n baseUpdatedAt?: string;\n}\n\n/** A read-only view of one queued item, for {@link OutboxEngine.snapshot}. */\nexport interface OutboxSnapshotItem {\n itemId: string;\n object: string;\n op: SyncApplyOp;\n rowId: string;\n state: 'pending' | 'synced' | 'failed';\n attempts: number;\n nextAttemptAt: number;\n lastError?: string;\n}\n\n/** Generate a fresh UUID itemId, falling back when crypto.randomUUID is absent. */\nfunction newItemId(): string {\n const cryptoRef = (globalThis as { crypto?: Crypto }).crypto;\n if (cryptoRef?.randomUUID) return cryptoRef.randomUUID();\n return `item-${Date.now()}-${Math.random().toString(36).slice(2)}`;\n}\n\n/** Is `navigator.onLine` telling us we're definitely offline? */\nfunction isDefinitelyOffline(): boolean {\n const nav = (globalThis as { navigator?: { onLine?: boolean } }).navigator;\n return nav?.onLine === false;\n}\n\n/**\n * The shared per-namespace outbox engine. Create/lookup via\n * {@link getOrCreateOutboxEngine}; never construct directly (the module keeps\n * the ref-counted registry).\n */\nexport class OutboxEngine {\n private readonly config: OutboxEngineConfig;\n /**\n * Per-collection callback sets, keyed by the collection route segment\n * (`object`). Keyed by `object` — NOT by the queue row's `itemId` — precisely\n * so replayed rows that were REHYDRATED from IndexedDB after a reload (whose\n * itemIds this session never enqueued) still route their state/conflict events\n * to the reloaded collection's callbacks. A `Set` per object so N collections\n * sharing one engine+object each get every event (the common case is one\n * collection per object, but the shared-engine model does not forbid more).\n */\n private readonly listenersByObject = new Map<\n string,\n Set<{\n onSyncStateChange?: (event: SyncStateEvent) => void;\n onConflict?: (conflict: OutboxConflict) => void;\n }>\n >();\n\n /** Ref count: number of collections currently attached to this engine. */\n private refCount = 0;\n /** The durable queue, once opened. undefined while opening / if IDB absent. */\n private queue: DurableOutboxQueue | undefined;\n /** Resolves once the async open settles (success or degraded). */\n private readonly ready: Promise<void>;\n /** True when IndexedDB was unavailable and the engine is a durable no-op. */\n private degraded = false;\n /** Leadership handle; set once we've requested the leader lock. */\n private leadership: LeadershipHandle | undefined;\n /** True while this tab holds leadership. */\n private isLeader = false;\n /** Unregister fn from the durable-store registry. */\n private unregisterResource: (() => void) | undefined;\n /** True once dispose() ran — guards late async continuations. */\n private disposed = false;\n /**\n * Paused by an auth_required/forbidden result: the loop stops draining until\n * a later enqueue (the app re-authenticated and is writing again) or an\n * explicit retry wakes it. Items stay queued.\n */\n private paused = false;\n /** True while a drain pass is running, to coalesce concurrent triggers. */\n private draining = false;\n /** A drain requested while one was in flight — run one more pass after. */\n private drainQueued = false;\n /** Timer for the next backoff-scheduled drain, if any. */\n private backoffTimer: ReturnType<typeof setTimeout> | undefined;\n /** The `online` event listener, so we can remove it on dispose. */\n private onlineListener: (() => void) | undefined;\n\n constructor(config: OutboxEngineConfig) {\n this.config = config;\n this.ready = this.open();\n this.wireOnlineListener();\n this.requestLeadership();\n // Kick a drain once the queue has finished opening. Leadership can be\n // granted (single-tab fallback: a microtask; Web Locks: whenever the lock\n // frees) BEFORE the async `open()` resolves — in which case that early\n // `drain()` returned at the `!this.queue` guard and nothing re-triggered it.\n // Draining after `ready` closes that race, so a freshly-constructed engine\n // that is already leader with a backlog on disk (the reload / leader-handoff\n // paths) replays without waiting for an external event.\n void this.ready.then(() => {\n if (!this.disposed) void this.drain();\n });\n }\n\n /** Open the durable queue (or mark degraded if IndexedDB is unusable). */\n private async open(): Promise<void> {\n const usable = await probeIndexedDb();\n if (!usable) {\n this.degraded = true;\n // biome-ignore lint/suspicious/noConsole: smrt-web has no logger dep; a degraded (no-IndexedDB) outbox is surfaced via console.warn (#1762)\n console.warn(\n '[smrt-web] IndexedDB unavailable — the offline outbox is disabled; offline writes will not be durable.',\n );\n return;\n }\n try {\n this.queue = await openDurableOutboxQueue(this.config.namespace);\n // Register for wipeDurableStore now that the queue exists.\n this.unregisterResource = this.config.registerResource(async () => {\n // A wipe clears the durable rows; drop the in-flight schedule too.\n await this.queue?.clear();\n });\n if (this.disposed) {\n // Disposed while opening — tear the just-opened queue back down.\n this.queue.close();\n this.queue = undefined;\n this.unregisterResource?.();\n this.unregisterResource = undefined;\n return;\n }\n } catch (error) {\n this.degraded = true;\n // biome-ignore lint/suspicious/noConsole: surface an outbox open failure (#1762)\n console.warn('[smrt-web] failed to open the offline outbox', error);\n }\n }\n\n /** Wake the drain loop immediately when connectivity returns. */\n private wireOnlineListener(): void {\n const target = globalThis as {\n addEventListener?: (t: string, l: () => void) => void;\n };\n if (typeof target.addEventListener !== 'function') return;\n const listener = () => {\n // Reconnected: an auth pause is unrelated to connectivity, so leave\n // `paused` as-is, but a network-stalled backlog should retry now.\n void this.drain();\n };\n target.addEventListener('online', listener);\n this.onlineListener = listener;\n }\n\n /** Request cross-tab leadership; drain whenever we hold it. */\n private requestLeadership(): void {\n const lockName = `smrt-web-outbox-leader:${this.config.namespace}`;\n this.leadership = acquireLeadership(\n lockName,\n () => {\n this.isLeader = true;\n void this.drain();\n },\n () => {\n this.isLeader = false;\n },\n );\n }\n\n /**\n * Attach a collection: register its per-object callbacks and bump the ref\n * count. Returns the exact callback record registered so the caller can pass\n * it back to {@link unregisterCollection} for precise removal (two collections\n * on the same object must each detach only their own callbacks). Registering\n * by `object` is what lets rehydrated rows (reloaded from IDB) reach this\n * collection's callbacks even though this session never enqueued them.\n */\n registerCollection(binding: {\n object: string;\n onSyncStateChange?: (event: SyncStateEvent) => void;\n onConflict?: (conflict: OutboxConflict) => void;\n }): {\n onSyncStateChange?: (event: SyncStateEvent) => void;\n onConflict?: (conflict: OutboxConflict) => void;\n } {\n this.refCount += 1;\n const record = {\n onSyncStateChange: binding.onSyncStateChange,\n onConflict: binding.onConflict,\n };\n let set = this.listenersByObject.get(binding.object);\n if (!set) {\n set = new Set();\n this.listenersByObject.set(binding.object, set);\n }\n set.add(record);\n return record;\n }\n\n /**\n * Detach a collection: remove its callback record and decrement the ref count;\n * when it reaches zero, dispose the engine (release the lock, unregister from\n * the durable-store registry, close IndexedDB). The durable ROWS are NOT\n * cleared — they must survive to replay after a reload; only the in-memory\n * engine is torn down. Returns true if it disposed.\n */\n async unregisterCollection(object: string, record: object): Promise<boolean> {\n const set = this.listenersByObject.get(object);\n if (set) {\n set.delete(record as never);\n if (set.size === 0) this.listenersByObject.delete(object);\n }\n this.refCount = Math.max(0, this.refCount - 1);\n if (this.refCount > 0) return false;\n await this.dispose();\n return true;\n }\n\n /** Current ref count (test/introspection aid). */\n get referenceCount(): number {\n return this.refCount;\n }\n\n /**\n * Enqueue an optimistic write into the durable queue and fire the initial\n * `pending` state, then kick a drain. Resolves once the row is durably\n * committed (so the caller's `wrapMutation` only reports handled after\n * persistence). Replay events for this row (and for rows this session did not\n * enqueue — reloaded from disk) route to the registered per-`object`\n * callbacks, so a reload does not lose observability.\n *\n * In degraded (no-IndexedDB) mode the write is NOT durable, so this returns\n * `undefined` and the capability falls through to the real fetcher instead\n * of acknowledging an optimistic-only write.\n */\n async enqueue(request: OutboxEnqueueRequest): Promise<string | undefined> {\n await this.ready;\n if (!this.queue || this.degraded) return undefined;\n\n const itemId = newItemId();\n const op = envelopeKindToOp(request.kind);\n\n // A delete carries no payload; create/update carry the row/changed fields.\n const payload = op === 'delete' ? undefined : request.data;\n\n await this.queue.enqueue({\n itemId,\n object: request.object,\n op,\n id: request.rowId,\n payload,\n baseUpdatedAt: request.baseUpdatedAt,\n });\n\n this.emit({\n itemId,\n rowId: request.rowId,\n object: request.object,\n state: 'pending',\n attempts: 0,\n });\n\n // A fresh write means there's work; if an auth pause was in effect the app\n // is evidently active again, so clear it and try.\n this.paused = false;\n void this.drain();\n return itemId;\n }\n\n /**\n * Force a retry of a specific queued item now: clears its backoff gate and\n * wakes the loop. The bridge `OutboxHandle.retry(itemId)` calls this so an app\n * \"retry\" button can flush a backed-off or auth-paused item. A no-op for an\n * item that is not (or no longer) queued.\n */\n async retry(itemId: string): Promise<void> {\n await this.ready;\n if (!this.queue) return;\n const rows = await this.queue.all();\n const row = rows.find((r) => r.itemId === itemId && r.state === 'pending');\n if (!row || row.seq === undefined) return;\n await this.queue.markState(row.seq, { nextAttemptAt: 0 });\n this.paused = false;\n void this.drain();\n }\n\n /**\n * A read-only snapshot of the durable queue — the basis of\n * `OutboxHandle.snapshot()`. Because the READ cache is NOT rehydrated after a\n * reload in this slice (that's #1764's warmStart), the snapshot + the raw IDB\n * store are how a test/app proves durability, not `collection.toArray()`.\n */\n async snapshot(): Promise<OutboxSnapshotItem[]> {\n await this.ready;\n if (!this.queue) return [];\n const rows = await this.queue.all();\n return rows.map((row) => ({\n itemId: row.itemId,\n object: row.object,\n op: row.op,\n rowId: row.id,\n state: row.state,\n attempts: row.attempts,\n nextAttemptAt: row.nextAttemptAt,\n lastError: row.lastError,\n }));\n }\n\n /**\n * Deliver a state event to every callback registered for the event's\n * collection `object` (best-effort). Routing by `object` — not `itemId` —\n * means a row REHYDRATED from IndexedDB after a reload still reaches the\n * reloaded collection's callback even though this session never enqueued it.\n */\n private emit(event: SyncStateEvent): void {\n const set = this.listenersByObject.get(event.object);\n if (!set) return;\n for (const listener of set) {\n try {\n listener.onSyncStateChange?.(event);\n } catch (error) {\n // biome-ignore lint/suspicious/noConsole: a throwing app callback must not break the loop (#1762)\n console.warn('[smrt-web] onSyncStateChange callback threw', error);\n }\n }\n }\n\n /** Deliver a conflict to every callback registered for its collection. */\n private emitConflict(conflict: OutboxConflict): void {\n const set = this.listenersByObject.get(conflict.object);\n if (!set) return;\n for (const listener of set) {\n try {\n listener.onConflict?.(conflict);\n } catch (error) {\n // biome-ignore lint/suspicious/noConsole: a throwing app callback must not break the loop (#1762)\n console.warn('[smrt-web] onConflict callback threw', error);\n }\n }\n }\n\n /**\n * The replay loop. Gated on: (a) holding leadership, (b) not paused by an\n * auth failure, (c) `navigator.onLine !== false`, (d) IndexedDB usable. Drains\n * all rows due now (`nextAttemptAt <= now`), oldest-first, chunked into\n * batches of ≤1000 per POST, one POST at a time to preserve FIFO across\n * chunks. Concurrency-coalesced: a drain requested while one runs sets a flag\n * to run exactly one more pass, so overlapping triggers never interleave.\n */\n private async drain(): Promise<void> {\n if (this.draining) {\n this.drainQueued = true;\n return;\n }\n this.draining = true;\n try {\n // Loop so a queued re-request (or a freshly-eligible backoff row) runs\n // without re-entrancy.\n for (;;) {\n this.drainQueued = false;\n await this.drainOnce();\n if (!this.drainQueued) break;\n }\n } finally {\n this.draining = false;\n }\n }\n\n /** One drain pass: send every currently-due batch, then schedule backoff. */\n private async drainOnce(): Promise<void> {\n if (this.disposed) return;\n if (!this.isLeader) return;\n if (this.paused) return;\n if (this.degraded || !this.queue) return;\n if (isDefinitelyOffline()) return;\n\n const pending = (await this.queue.all()).filter(\n (row) => row.state === 'pending',\n );\n if (pending.length === 0) return;\n\n const now = Date.now();\n const firstBlocked = pending.findIndex((row) => row.nextAttemptAt > now);\n const due = firstBlocked === -1 ? pending : pending.slice(0, firstBlocked);\n if (due.length === 0) {\n // The oldest pending row is backed off; FIFO forbids draining newer rows.\n await this.scheduleNextBackoff();\n return;\n }\n\n // Chunk oldest-first into ≤1000-item batches; send one at a time so FIFO\n // holds across chunks.\n for (let i = 0; i < due.length; i += MAX_SYNC_APPLY_BATCH_SIZE) {\n if (this.disposed || this.paused || !this.isLeader) break;\n const chunk = due.slice(i, i + MAX_SYNC_APPLY_BATCH_SIZE);\n const drained = await this.sendBatch(chunk);\n if (!drained) break;\n }\n\n // After processing, some rows may have been re-queued with a backoff gate;\n // schedule the next wake.\n await this.scheduleNextBackoff();\n }\n\n /**\n * Send one chunk through `POST {basePath}/sync/apply` and map results back\n * onto durable transitions. On a network reject / non-200 / lost/mismatched\n * response the WHOLE chunk stays `pending` (blind replay is safe) — every row\n * goes back to `pending` with an incremented attempt + backoff so the loop\n * doesn't hot-spin. Returns false when a retryable row remains pending, which\n * stops this drain pass so newer FIFO chunks do not overtake it.\n */\n private async sendBatch(chunk: OutboxRow[]): Promise<boolean> {\n // Mark the chunk uploading (observable), build the request items in order.\n for (const row of chunk) {\n this.emit({\n itemId: row.itemId,\n rowId: row.id,\n object: row.object,\n state: 'uploading',\n attempts: row.attempts,\n });\n }\n const items: SyncApplyItem[] = chunk.map((row) => ({\n itemId: row.itemId,\n object: row.object,\n op: row.op,\n id: row.id,\n payload: row.payload,\n baseUpdatedAt: row.baseUpdatedAt,\n }));\n\n let results: SyncApplyItemResult[] | undefined;\n try {\n results = await this.postBatch(items);\n } catch {\n // Network reject / non-200 / lost response: keep the whole batch pending.\n await this.requeueBatch(chunk, 'network error during sync');\n return false;\n }\n if (!results) {\n await this.requeueBatch(chunk, 'unexpected sync response shape');\n return false;\n }\n\n // Results are positional (results[i] ↔ items[i] ↔ chunk[i]). Map each.\n let drained = true;\n for (let i = 0; i < chunk.length; i += 1) {\n const row = chunk[i];\n const result = results[i];\n // A missing/short result array for this position → treat as retryable\n // (leave pending); safer than dropping the item.\n if (!result) {\n await this.requeueRow(row, 'missing result for item');\n drained = false;\n continue;\n }\n const applied = await this.applyResult(row, result);\n drained = drained && applied;\n }\n return drained;\n }\n\n /**\n * POST a batch to `{basePath}/sync/apply`. Throws on a non-2xx or a network\n * error (the caller treats a throw as \"response lost → keep pending\"). Returns\n * the positional `results` array, or `undefined` on a malformed 200 body.\n */\n private async postBatch(\n items: SyncApplyItem[],\n ): Promise<SyncApplyItemResult[] | undefined> {\n const url = `${this.config.syncApplyBasePath}/${SYNC_APPLY_ROUTE_SEGMENTS.join('/')}`;\n const response = await this.config.fetchFn(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ items }),\n });\n if (!response.ok) {\n // HTTP 400 (bad batch) and any other non-2xx: throw so the batch stays\n // pending. A 400 on a well-formed client batch is unexpected; blind\n // replay stays safe by construction, so retrying is acceptable.\n throw new Error(`[smrt-web] sync/apply returned HTTP ${response.status}`);\n }\n const body = (await response\n .json()\n .catch(() => null)) as SyncApplyBatchResponse | null;\n if (!body || !Array.isArray(body.results)) return undefined;\n return body.results;\n }\n\n /**\n * Map one positional apply result onto a durable transition + observable\n * state, per the contract's consumer notes. See the class doc's mapping table.\n */\n private async applyResult(\n row: OutboxRow,\n result: SyncApplyItemResult,\n ): Promise<boolean> {\n if (row.seq === undefined) return true;\n\n if (result.status === 'applied') {\n await this.finishSynced(row);\n return true;\n }\n\n if (result.status === 'conflict') {\n // A conflict is a RESOLVED outcome: the server state won, the item leaves\n // the queue, its terminal observable state is `synced`, and the app is\n // notified so it can rebase from the returned updatedAt.\n const reason =\n result.reason === 'create_conflict' ? 'create_conflict' : 'stale_write';\n this.emitConflict({\n itemId: row.itemId,\n object: row.object,\n rowId: row.id,\n reason,\n serverUpdatedAt: result.updatedAt,\n });\n await this.finishSynced(row);\n return true;\n }\n\n // status === 'rejected'\n const reason = result.reason;\n if (reason === 'auth_required' || reason === 'forbidden') {\n // Pause the WHOLE loop until re-auth; keep the item queued as `pending`.\n this.paused = true;\n await this.queue?.markState(row.seq, {\n state: 'pending',\n lastError: `sync ${reason}`,\n });\n this.emit({\n itemId: row.itemId,\n rowId: row.id,\n object: row.object,\n state: 'pending',\n attempts: row.attempts,\n error: `sync ${reason}`,\n });\n return false;\n }\n\n if (reason === 'write_failed') {\n // Retryable: keep, count an attempt, back off.\n await this.requeueRow(row, 'sync write_failed');\n return false;\n }\n\n // Any other rejection (invalid_item/invalid_id/invalid_payload/\n // unknown_object/not_found/op_not_allowed/id_conflict) is terminal — a\n // retry cannot succeed. Remove and surface `failed`.\n await this.queue?.remove(row.seq);\n this.emit({\n itemId: row.itemId,\n rowId: row.id,\n object: row.object,\n state: 'failed',\n attempts: row.attempts,\n error: reason ? `sync ${reason}` : 'sync rejected',\n });\n return true;\n }\n\n /** Remove a successfully-applied (or conflict-resolved) row → `synced`. */\n private async finishSynced(row: OutboxRow): Promise<void> {\n if (row.seq !== undefined) await this.queue?.remove(row.seq);\n this.emit({\n itemId: row.itemId,\n rowId: row.id,\n object: row.object,\n state: 'synced',\n attempts: row.attempts,\n });\n }\n\n /** Re-queue every row of a failed batch (network path) with backoff. */\n private async requeueBatch(chunk: OutboxRow[], error: string): Promise<void> {\n for (const row of chunk) {\n await this.requeueRow(row, error);\n }\n }\n\n /** Re-queue one row: attempts++, backoff gate, `pending` event. */\n private async requeueRow(row: OutboxRow, error: string): Promise<void> {\n if (row.seq === undefined) return;\n const attempts = row.attempts + 1;\n const delay = computeBackoffDelay(\n attempts,\n this.config.backoff,\n this.config.random,\n );\n const nextAttemptAt = Date.now() + delay;\n await this.queue?.markState(row.seq, {\n state: 'pending',\n attempts,\n nextAttemptAt,\n lastError: error,\n });\n this.emit({\n itemId: row.itemId,\n rowId: row.id,\n object: row.object,\n state: 'pending',\n attempts,\n error,\n });\n }\n\n /**\n * Schedule the next drain for the soonest backed-off row's `nextAttemptAt`.\n * Only one timer is ever pending; a sooner schedule replaces a later one.\n */\n private async scheduleNextBackoff(): Promise<void> {\n if (this.disposed || this.paused || !this.queue) return;\n const rows = await this.queue.all();\n const firstPending = rows.find((r) => r.state === 'pending');\n if (!firstPending) return;\n const now = Date.now();\n // FIFO: the oldest pending row gates every newer row, even if a newer row\n // has no backoff delay.\n const delay = Math.max(0, firstPending.nextAttemptAt - now);\n if (this.backoffTimer) clearTimeout(this.backoffTimer);\n const timers = globalThis as {\n setTimeout?: typeof setTimeout;\n };\n if (typeof timers.setTimeout !== 'function') return;\n this.backoffTimer = timers.setTimeout(() => {\n this.backoffTimer = undefined;\n void this.drain();\n }, delay);\n // Node's timer keeps the process alive; unref so tests/SSR don't hang.\n (this.backoffTimer as { unref?: () => void }).unref?.();\n }\n\n /**\n * Tear down the in-memory engine: release leadership, remove the online\n * listener, clear timers, unregister from the durable-store registry, and\n * close IndexedDB. Does NOT clear the durable rows — they must survive to\n * replay on the next load.\n */\n private async dispose(): Promise<void> {\n if (this.disposed) return;\n this.disposed = true;\n if (this.backoffTimer) {\n clearTimeout(this.backoffTimer);\n this.backoffTimer = undefined;\n }\n const target = globalThis as {\n removeEventListener?: (t: string, l: () => void) => void;\n };\n if (\n this.onlineListener &&\n typeof target.removeEventListener === 'function'\n ) {\n target.removeEventListener('online', this.onlineListener);\n this.onlineListener = undefined;\n }\n this.leadership?.();\n this.leadership = undefined;\n this.unregisterResource?.();\n this.unregisterResource = undefined;\n // Wait for any in-flight open to settle before closing.\n await this.ready.catch(() => undefined);\n this.queue?.close();\n this.queue = undefined;\n this.listenersByObject.clear();\n }\n}\n\n/**\n * Module-scoped, ref-counted registry of engines by namespace string. This is\n * the mechanism that makes N collections sharing a namespace share ONE engine.\n * Keyed by the durable-store namespace, which already folds\n * api/tenant/identity/manifest — so a logout/tenant-switch lands on a different\n * key and a fresh engine.\n */\nconst engines = new Map<string, OutboxEngine>();\n\n/**\n * Get the shared engine for `config.namespace`, creating it on first use and\n * ref-counting it. Every collection opting into the outbox under the same\n * namespace gets the SAME engine — one IndexedDB db, one leader lock, one FIFO\n * queue. The caller MUST pair each `getOrCreateOutboxEngine(...).registerCollection()`\n * with a later `unregisterCollection()` (via `teardown`) so the engine disposes\n * when its last collection detaches.\n */\nexport function getOrCreateOutboxEngine(\n config: OutboxEngineConfig,\n): OutboxEngine {\n let engine = engines.get(config.namespace);\n if (!engine) {\n engine = new OutboxEngine(config);\n engines.set(config.namespace, engine);\n // Auto-evict from the shared registry once the engine disposes, so a later\n // collection under the same namespace gets a fresh engine rather than a\n // torn-down one. We detect disposal by wrapping unregisterCollection at the\n // call site (below in registerCollection/unregisterCollection helpers).\n }\n return engine;\n}\n\n/** The per-collection callback binding registered when a collection attaches. */\nexport interface OutboxCollectionBinding {\n object: string;\n onSyncStateChange?: (event: SyncStateEvent) => void;\n onConflict?: (conflict: OutboxConflict) => void;\n}\n\n/**\n * Attach a collection to the namespace's engine (creating it if needed),\n * register its per-`object` callbacks, and increment its ref count. Returns the\n * engine plus the exact callback `record` to hand back to\n * {@link releaseOutboxEngine} for precise removal. Pair with\n * {@link releaseOutboxEngine}.\n */\nexport function acquireOutboxEngine(\n config: OutboxEngineConfig,\n binding: OutboxCollectionBinding,\n): { engine: OutboxEngine; record: object } {\n const engine = getOrCreateOutboxEngine(config);\n const record = engine.registerCollection(binding);\n return { engine, record };\n}\n\n/**\n * Detach a collection from an engine (removing its callback record) and, if it\n * was the last one, dispose it and evict it from the shared registry so the\n * namespace starts fresh next time.\n */\nexport async function releaseOutboxEngine(\n namespace: string,\n engine: OutboxEngine,\n object: string,\n record: object,\n): Promise<void> {\n const disposed = await engine.unregisterCollection(object, record);\n if (disposed && engines.get(namespace) === engine) {\n engines.delete(namespace);\n }\n}\n\n/** Test-only: current engine count (for leak assertions). */\nexport function _outboxEngineCount(): number {\n return engines.size;\n}\n","/**\n * @happyvertical/smrt-web — the durable offline outbox capability (#1762).\n *\n * The web twin of the KMP mobile write queue: mutations against opted-in\n * collections are captured in a durable browser-side outbox (IndexedDB) that\n * survives reloads and crashes, then replayed FIFO against the idempotent\n * sync-apply batch contract when connectivity returns, with exponential-backoff\n * retries. Because items carry client-generated UUIDs and the endpoint is\n * idempotent (`_insertOnly` create + no-op re-apply), blind retries after an\n * ambiguous failure (request sent, response lost) can never duplicate rows — see\n * the \"Idempotency\" section of docs/content/architecture/sync-apply-contract.md.\n *\n * This is a {@link SmrtWebCapability} plug-in, so it lives in its OWN module and\n * an app opts a collection in by passing `offlineOutbox(config)` in that\n * collection's `capabilities` array. A collection WITHOUT it is byte-for-byte\n * the collection of today (the seam's no-op guarantee), which is exactly the\n * \"offline is opt-in per model\" acceptance criterion.\n *\n * ## How it plugs into the seam\n *\n * - `wrapMutation(envelope, ctx)` — BEFORE the fetcher, it enqueues the write\n * durably and returns `{ handled: true, result: envelope.data }`. The factory\n * then SUPPRESSES the post-mutation refetch + `invalidateRelated()` (its\n * `{ refetch: false }` path), so the optimistic row the app just inserted\n * STANDS instead of being dropped by a refetch of a server list that has never\n * seen the offline write. If the durable queue is unavailable, it returns\n * `{ handled: false }` so the normal fetcher path runs instead of silently\n * acknowledging a non-durable write. A handled write never touches\n * `ctx.fetchers.create` — it replays ONLY through `sync/apply`, which is\n * load-bearing: the normal REST create strips the client id (#1540) and would\n * mint a NEW server id, orphaning the optimistic row; sync/apply's\n * strict-insert path preserves the client UUID (#1540, #1540's\n * mass-assignment guard on the sync path).\n * - `onAttach(ctx)` — attaches this collection to the shared, namespace-keyed\n * {@link OutboxEngine} (ref-counted so N collections share ONE engine / IDB\n * db / leader lock / FIFO queue).\n * - `teardown(ctx)` — detaches; the last detach disposes the engine (the durable\n * ROWS survive for the next load).\n *\n * ## Observable state (push callbacks, not a store)\n *\n * Per-mutation sync state — `pending → uploading → synced` (and `→ failed` /\n * conflict) — is delivered through {@link OfflineOutboxConfig.onSyncStateChange}\n * and {@link OfflineOutboxConfig.onConflict}, PUSH callbacks matching the KMP\n * foundation's `pending/uploading/synced/failed` state machine. This is\n * deliberately not a subscribable store: smrt-svelte wraps it into a reactive\n * binding in a later slice. Programmatic access to the durable queue is via\n * {@link getOutboxHandle} (a bridge like `getEngineCollection`).\n *\n * ## Known gap (this slice's scope)\n *\n * The outbox does NOT rehydrate the READ cache after a reload — a reloaded tab's\n * `collection.toArray()` will NOT show captured-offline rows until a fetch runs;\n * that read-side rehydrate is #1764's `warmStart`. So the outbox's durability is\n * proven via {@link OutboxHandle.snapshot} / the raw IndexedDB store, NOT via\n * `collection.toArray()`. The WRITE side (capture → durable → exactly-once\n * replay) is complete here.\n *\n * Engine-free public surface: no `@tanstack/*` type appears — inside the\n * engine-absorption boundary (#1761), verified by\n * scripts/check-smrt-web-engine-boundary.mjs.\n */\n\nimport type { SmrtWebCapability } from './capability.js';\nimport {\n type DurableStoreKey,\n durableStoreNamespace,\n registerDurableResource,\n} from './durable-store.js';\nimport {\n acquireOutboxEngine,\n type OutboxEngine,\n releaseOutboxEngine,\n} from './offline/engine.js';\nimport {\n DEFAULT_BACKOFF,\n type OutboxBackoff,\n type OutboxConflict,\n type ResolvedBackoff,\n type SyncStateEvent,\n} from './offline/types.js';\n\nexport type {\n OutboxBackoff,\n OutboxConflict,\n OutboxSyncState,\n SyncStateEvent,\n} from './offline/types.js';\n\n/**\n * Configuration for {@link offlineOutbox}. Generic in the collection's row type\n * `TData` so the capability matches the collection it plugs into.\n */\nexport interface OfflineOutboxConfig<TData extends object = object> {\n /**\n * The generated collection definition this outbox serves — its `name` is the\n * sync-apply `object` route segment, so it MUST be the SAME definition passed\n * to {@link createSmrtCollection}. (Named `object` to mirror the capability\n * context; carries the collection's REST route segment.)\n */\n object: { name: string; _row?: TData };\n /**\n * The durable-store identity this outbox's queue is namespaced under — folds\n * api base / tenant / identity / manifest hash, so a logout or tenant switch\n * lands on a different IndexedDB database (never cross-identity reuse). Shared\n * with #1764's persistence slice via the same {@link durableStoreNamespace}.\n * `manifestHash` is opaque caller-supplied config for #1762; its canonical\n * source is #1764's call (a schema-shape digest) — comment your call site.\n */\n namespace: DurableStoreKey;\n /**\n * API base path the sync-apply endpoint lives under (`POST\n * {syncApplyBasePath}/sync/apply`). Defaults to `/api/v1`, matching the REST\n * generator's default. Set to the SvelteKit route base (`/api`) when replaying\n * against the generated SvelteKit `sync/apply/+server.ts`.\n */\n syncApplyBasePath?: string;\n /** Fetch implementation override (tests, SSR). Defaults to global fetch. */\n fetchFn?: typeof fetch;\n /** Exponential-backoff tuning for retryable replay failures. */\n backoff?: OutboxBackoff;\n /**\n * Called on every observable sync-state transition of a captured mutation\n * (`pending → uploading → synced`, `→ failed`, and the `pending` re-arm after\n * a retryable failure). A push callback; smrt-svelte turns it into a reactive\n * binding later.\n */\n onSyncStateChange?: (event: SyncStateEvent) => void;\n /**\n * Called when a replayed mutation comes back `conflict` — a RESOLVED outcome\n * (server state won; the item leaves the queue with terminal state `synced`).\n * Persist `serverUpdatedAt` as the new `baseUpdatedAt`, refetch the row, and\n * surface the conflict to the user as appropriate.\n */\n onConflict?: (conflict: OutboxConflict) => void;\n /** Test-only: inject a deterministic RNG for backoff jitter. */\n random?: () => number;\n}\n\n/**\n * A programmatic handle to one namespace's outbox — the bridge pattern (like\n * `getEngineCollection`) for trusted callers (an app's outbox panel, a\n * smrt-svelte binding, tests). Fetched via {@link getOutboxHandle}.\n */\nexport interface OutboxHandle {\n /**\n * A read-only snapshot of the durable queue (every state). The way to prove\n * durability after a reload, since this slice does not rehydrate the read\n * cache (that's #1764).\n */\n snapshot(): Promise<OutboxSnapshotItem[]>;\n /**\n * Force a retry of a specific queued item now (clears its backoff gate, wakes\n * the loop, and clears an auth pause). A no-op for an item no longer queued.\n */\n retry(itemId: string): Promise<void>;\n}\n\n/** One item in an {@link OutboxHandle.snapshot} result. */\nexport interface OutboxSnapshotItem {\n /** The queue row's idempotency handle. */\n itemId: string;\n /** The collection route segment. */\n object: string;\n /** The mutation kind, in sync-apply terms. */\n op: 'create' | 'update' | 'delete';\n /** The client-generated row UUID. */\n rowId: string;\n /** The on-disk lifecycle state. */\n state: 'pending' | 'synced' | 'failed';\n /** Replay attempts so far. */\n attempts: number;\n /** Epoch ms before which this row won't be retried (backoff). */\n nextAttemptAt: number;\n /** Last replay error, if any. */\n lastError?: string;\n}\n\n/** Fill backoff defaults. */\nfunction resolveBackoff(backoff?: OutboxBackoff): ResolvedBackoff {\n return {\n initialDelayMs: backoff?.initialDelayMs ?? DEFAULT_BACKOFF.initialDelayMs,\n multiplier: backoff?.multiplier ?? DEFAULT_BACKOFF.multiplier,\n maxDelayMs: backoff?.maxDelayMs ?? DEFAULT_BACKOFF.maxDelayMs,\n };\n}\n\n/**\n * Registry mapping a namespace string to its live engine + bridge handle, so\n * {@link getOutboxHandle} can reach an engine attached by a running collection.\n * A weak-ish bookkeeping map: entries are added on `onAttach` and removed on the\n * final `teardown`.\n */\nconst handlesByNamespace = new Map<string, OutboxEngine>();\n\n/** Extract a timestamp from a payload for direct wrapMutation callers. */\nfunction getPayloadUpdatedAt(\n data: Record<string, unknown>,\n): string | undefined {\n const value = data.updatedAt ?? data.updated_at;\n if (typeof value === 'string') return value;\n if (value instanceof Date) return value.toISOString();\n return undefined;\n}\n\n/**\n * Build a durable offline-outbox capability for a collection. Add the returned\n * capability to the collection's `capabilities` array; a collection without it\n * is unaffected (the seam's no-op guarantee — the \"opt-in per model\" AC).\n *\n * The same `namespace` across multiple collections shares ONE engine (one IDB\n * db, one leader lock, one FIFO queue) — so cross-collection ordering and the\n * multi-tab single-replayer guarantee hold across every opted-in collection of a\n * given identity.\n */\nexport function offlineOutbox<TData extends object = object>(\n config: OfflineOutboxConfig<TData>,\n): SmrtWebCapability<TData> {\n const namespace = durableStoreNamespace(config.namespace);\n const syncApplyBasePath = config.syncApplyBasePath ?? '/api/v1';\n const fetchFn =\n config.fetchFn ??\n ((...args) => globalThis.fetch(...(args as [RequestInfo])));\n const backoff = resolveBackoff(config.backoff);\n\n const object = config.object.name;\n // The engine this collection attaches to (set in onAttach, released in\n // teardown). Held here so wrapMutation can enqueue through it. `record` is the\n // exact callback binding registered, handed back on teardown for precise\n // removal (two collections on one object each detach only their own).\n let engine: OutboxEngine | undefined;\n let record: object | undefined;\n\n return {\n name: 'offline-outbox',\n\n onAttach() {\n const acquired = acquireOutboxEngine(\n {\n namespace,\n syncApplyBasePath,\n fetchFn,\n backoff,\n random: config.random,\n registerResource: (clear) =>\n registerDurableResource(namespace, { kind: 'outbox', clear }),\n },\n {\n object,\n onSyncStateChange: config.onSyncStateChange,\n onConflict: config.onConflict,\n },\n );\n engine = acquired.engine;\n record = acquired.record;\n handlesByNamespace.set(namespace, engine);\n },\n\n async wrapMutation(envelope) {\n // If onAttach hasn't run yet (shouldn't happen — wrapMutation fires only\n // after construction), fall through so the write isn't silently dropped.\n if (!engine) return { handled: false };\n const itemId = await engine.enqueue({\n kind: envelope.kind,\n object,\n rowId: envelope.key,\n data: envelope.data,\n baseUpdatedAt:\n envelope.baseUpdatedAt ?? getPayloadUpdatedAt(envelope.data),\n });\n if (!itemId) return { handled: false };\n // The optimistic row (envelope.data) stands in for the fetcher result; the\n // factory suppresses the refetch so it isn't dropped. Exactly-once replay\n // then reconciles it server-side via sync/apply.\n return { handled: true, result: envelope.data };\n },\n\n async teardown() {\n if (!engine || !record) return;\n const current = engine;\n const currentRecord = record;\n engine = undefined;\n record = undefined;\n // Detach; the final detach disposes the engine. Only drop the handle map\n // entry if THIS release actually evicted the engine (ref count hit 0).\n const before = current.referenceCount;\n await releaseOutboxEngine(namespace, current, object, currentRecord);\n if (before <= 1 && handlesByNamespace.get(namespace) === current) {\n handlesByNamespace.delete(namespace);\n }\n },\n };\n}\n\n/**\n * Retrieve the programmatic {@link OutboxHandle} for a durable-store namespace,\n * or `undefined` if no opted-in collection is currently attached under it. The\n * bridge for trusted callers (outbox UI, smrt-svelte binding, tests) to read the\n * durable queue and force retries. Pass the SAME\n * `durableStoreNamespace(config.namespace)` string the capability used.\n *\n * Mirrors the `getEngineCollection` bridge convention: an escape hatch that\n * returns a narrow SMRT-owned surface rather than the engine itself.\n */\nexport function getOutboxHandle(namespace: string): OutboxHandle | undefined {\n const engine = handlesByNamespace.get(namespace);\n if (!engine) return undefined;\n return {\n snapshot: () => engine.snapshot(),\n retry: (itemId: string) => engine.retry(itemId),\n };\n}\n","/**\n * @happyvertical/smrt-web — the durable collection snapshot store (#1764).\n *\n * The read-side twin of the offline outbox's write queue: a raw IndexedDB store\n * that persists ONE blob per (namespace, collection) — the collection's last\n * known rows — so a reload can warm-start (render stale instantly) before the\n * engine revalidates in the background. One database per durable-store namespace\n * ({@link durableStoreNamespace}), one object store (`snapshots`) keyed by the\n * collection name, each record `{ collection, rows }`.\n *\n * ## Why raw IndexedDB, mirroring offline/durable-queue.ts\n *\n * Same decisive finding as #1762's outbox: the TanStack persistence layer's\n * public API is engine-typed, so importing it would emit a `@tanstack/`\n * specifier into a `.d.ts` and fail `scripts/check-smrt-web-engine-boundary.mjs`.\n * So persistence is hand-rolled over `indexedDB` with ZERO new runtime\n * dependency and stays inside the engine-absorption boundary (#1761) — this\n * module speaks only plain rows, never an engine type.\n *\n * Rows are stored as structured-clone-safe plain objects (the public DTOs the\n * factory already projects); IndexedDB clones on write, so callers may keep\n * mutating the source array after a save.\n *\n * The namespace already folds api base / tenant / identity / manifest hash (see\n * {@link durableStoreNamespace}), so a logout, tenant switch, or contract-\n * changing deploy lands on a DIFFERENT database — a stale-schema snapshot is\n * simply never found (the read returns nothing and the engine fetches fresh),\n * which is exactly the \"manifest-hash change drops persisted caches\" guarantee\n * without any explicit invalidation.\n */\n\n/** IndexedDB object-store name inside every snapshot database. */\nexport const SNAPSHOT_STORE = 'snapshots';\n/** IndexedDB schema version. Bump only on a store/index migration. */\nexport const SNAPSHOT_DB_VERSION = 1;\n/**\n * Suffix appended to the durable-store namespace to form this store's OWN\n * IndexedDB database name. Distinct DBs per store are REQUIRED for correctness:\n * the merged outbox (#1762) already opens the BARE namespace as a v1 database\n * with only its `outbox` object store. If persistence opened the same bare\n * namespace at v1, whichever store opened SECOND would find the existing v1 DB,\n * `onupgradeneeded` would NOT fire, and `objectStore('snapshots')` would throw\n * `NotFoundError` — silently disabling persistence (or the outbox). A distinct\n * DB name sidesteps the collision entirely; `wipeDurableStore` still clears both\n * because it fans out via each registered resource's `clear()`, never by DB name.\n */\nexport const SNAPSHOT_DB_SUFFIX = '::snapshots';\n\n/** One persisted collection snapshot record. */\nexport interface SnapshotRecord {\n /** The REST collection name — the store key. */\n collection: string;\n /** The rows last seen for this collection (plain public DTOs). */\n rows: Array<Record<string, unknown>>;\n}\n\n/**\n * Promisify a single IndexedDB request. Rejects with the request's error (or a\n * generic one) so callers get a real rejection, never a silent `undefined`.\n */\nfunction promisifyRequest<T>(request: IDBRequest<T>): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n request.onsuccess = () => resolve(request.result);\n request.onerror = () =>\n reject(request.error ?? new Error('[smrt-web] IndexedDB request failed'));\n });\n}\n\n/**\n * Promisify a transaction's completion. IndexedDB writes are only durable once\n * the TRANSACTION completes (not when the request succeeds), so mutations await\n * this — a save that reported success before the txn committed could lose the\n * snapshot on a crash in the commit window.\n */\nfunction awaitTransaction(tx: IDBTransaction): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n tx.oncomplete = () => resolve();\n tx.onerror = () =>\n reject(tx.error ?? new Error('[smrt-web] IndexedDB transaction failed'));\n tx.onabort = () =>\n reject(tx.error ?? new Error('[smrt-web] IndexedDB transaction aborted'));\n });\n}\n\n/**\n * Feature-detect a usable IndexedDB. Some environments expose `indexedDB` but\n * throw on `open()` (private-mode Firefox historically, sandboxed iframes), so a\n * real probe opens a throwaway database. Returns `false` rather than throwing so\n * the persistence capability can degrade to non-persistent instead of crashing\n * the host — matching the outbox's `probeIndexedDb()` posture.\n */\nexport async function probeIndexedDb(): Promise<boolean> {\n const idb = (globalThis as { indexedDB?: IDBFactory }).indexedDB;\n if (!idb) return false;\n const probeName = '__smrt_web_snapshot_probe__';\n try {\n const db = await new Promise<IDBDatabase>((resolve, reject) => {\n const request = idb.open(probeName, 1);\n request.onsuccess = () => resolve(request.result);\n request.onerror = () =>\n reject(request.error ?? new Error('probe failed'));\n request.onblocked = () => reject(new Error('probe blocked'));\n });\n db.close();\n try {\n idb.deleteDatabase(probeName);\n } catch {\n /* ignore */\n }\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * The durable snapshot store over one IndexedDB database. Constructed via\n * {@link openSnapshotStore} (async open + schema upgrade), then used through the\n * async methods below (each resolves once its txn commits).\n *\n * One store backs EVERY opted-in collection under a namespace, each keyed by its\n * collection name — so N persisted collections of one identity share one\n * database (like the outbox's shared queue), and the durable-store `clear()`\n * wipes them all at once.\n */\nexport class SnapshotStore {\n private readonly db: IDBDatabase;\n /** The IndexedDB database name (== the durable-store namespace). */\n readonly dbName: string;\n\n constructor(db: IDBDatabase, dbName: string) {\n this.db = db;\n this.dbName = dbName;\n }\n\n /**\n * Read the persisted rows for `collection`, or `undefined` if none were ever\n * saved (or the record is malformed). `undefined` is the warm-start \"nothing\n * on disk\" signal — the engine then fetches fresh.\n */\n async load(\n collection: string,\n ): Promise<Array<Record<string, unknown>> | undefined> {\n const tx = this.db.transaction(SNAPSHOT_STORE, 'readonly');\n const record = await promisifyRequest(\n tx.objectStore(SNAPSHOT_STORE).get(collection) as IDBRequest<\n SnapshotRecord | undefined\n >,\n );\n await awaitTransaction(tx);\n if (!record || !Array.isArray(record.rows)) return undefined;\n return record.rows;\n }\n\n /**\n * Write (replacing) the snapshot for `collection`. Resolves once the write is\n * durably committed. A single blob per collection — the whole current row set,\n * not a delta — so a restore is one read with no reconciliation.\n */\n async save(\n collection: string,\n rows: Array<Record<string, unknown>>,\n ): Promise<void> {\n const tx = this.db.transaction(SNAPSHOT_STORE, 'readwrite');\n const store = tx.objectStore(SNAPSHOT_STORE);\n const record: SnapshotRecord = { collection, rows };\n await promisifyRequest(store.put(record));\n await awaitTransaction(tx);\n }\n\n /**\n * Drop the snapshot for a single `collection` (its capability's own teardown\n * does NOT clear — the persisted rows must survive for the next load; this is\n * only for an explicit targeted purge). Kept for completeness / tests.\n */\n async remove(collection: string): Promise<void> {\n const tx = this.db.transaction(SNAPSHOT_STORE, 'readwrite');\n await promisifyRequest(tx.objectStore(SNAPSHOT_STORE).delete(collection));\n await awaitTransaction(tx);\n }\n\n /**\n * Drop EVERY snapshot — the durable-store `clear()` for `wipeDurableStore`.\n * Empties the store but keeps the database so a subsequent save still works.\n */\n async clear(): Promise<void> {\n const tx = this.db.transaction(SNAPSHOT_STORE, 'readwrite');\n await promisifyRequest(tx.objectStore(SNAPSHOT_STORE).clear());\n await awaitTransaction(tx);\n }\n\n /** Close the underlying database handle (called on the last detach). */\n close(): void {\n this.db.close();\n }\n}\n\n/**\n * Open (creating/upgrading as needed) the snapshot database for `namespace` and\n * wrap it in a {@link SnapshotStore}. `namespace` MUST be a\n * {@link durableStoreNamespace} string — already `encodeURIComponent`-safe and\n * carrying the api/tenant/identity/manifest isolation boundary, so two\n * identities never share one snapshot database.\n *\n * The actual IndexedDB database name is `${namespace}${SNAPSHOT_DB_SUFFIX}` — a\n * DISTINCT database from the merged outbox's bare-namespace one, so persistence +\n * outbox under a shared namespace never collide on a single-store v1 DB (see\n * {@link SNAPSHOT_DB_SUFFIX}).\n *\n * The `onupgradeneeded` handler creates the `snapshots` store keyed by\n * `collection`. Idempotent: re-opening an existing database at the same version\n * skips the upgrade — the reload path (a new tab re-opens the same durable\n * backing and sees prior snapshots).\n */\nexport function openSnapshotStore(namespace: string): Promise<SnapshotStore> {\n const idb = (globalThis as { indexedDB?: IDBFactory }).indexedDB;\n if (!idb) {\n return Promise.reject(\n new Error('[smrt-web] IndexedDB is unavailable in this environment'),\n );\n }\n const dbName = `${namespace}${SNAPSHOT_DB_SUFFIX}`;\n return new Promise<SnapshotStore>((resolve, reject) => {\n const request = idb.open(dbName, SNAPSHOT_DB_VERSION);\n request.onupgradeneeded = () => {\n const db = request.result;\n if (!db.objectStoreNames.contains(SNAPSHOT_STORE)) {\n db.createObjectStore(SNAPSHOT_STORE, { keyPath: 'collection' });\n }\n };\n request.onsuccess = () =>\n resolve(new SnapshotStore(request.result, dbName));\n request.onerror = () =>\n reject(\n request.error ??\n new Error(`[smrt-web] failed to open snapshot database \"${dbName}\"`),\n );\n request.onblocked = () =>\n reject(\n new Error(\n `[smrt-web] opening snapshot database \"${dbName}\" was blocked`,\n ),\n );\n });\n}\n","/**\n * @happyvertical/smrt-web — the durable persistence capability (#1764).\n *\n * The read-side twin of the offline outbox (#1762): an opted-in collection\n * WARM-STARTS from a durable browser snapshot on load — rendering stale rows\n * instantly — then the engine revalidates in the background via its normal SWR.\n * A reload therefore paints last-known data with zero network round-trip before\n * the fresh fetch lands. This is the read-cache rehydrate the outbox explicitly\n * left to #1764 (see offline.ts \"Known gap\").\n *\n * This is a {@link SmrtWebCapability} plug-in, so it lives in its OWN module and\n * an app opts a collection in by passing `persistCollection(config)` in that\n * collection's `capabilities` array. A collection WITHOUT it is byte-for-byte\n * the collection of today (the seam's no-op guarantee) — nothing persists\n * implicitly, which is exactly the \"persistence is opt-in per model\" acceptance\n * criterion. Sensitive models simply omit the capability and never touch disk.\n *\n * ## How it plugs into the seam\n *\n * - `warmStart(ctx)` — reads the persisted snapshot for\n * `(durableStoreNamespace(namespace), collection)` and returns its rows to\n * seed the cache (the factory's rehydrate path; `preload()` awaits the async\n * return so it suppresses the first `list()`). If the namespace's\n * `manifestHash` changed, the key differs → NO snapshot is found → an empty\n * return → a fresh fetch with NO stale-schema hydration. So \"a manifest-hash\n * change drops persisted caches\" is AUTOMATIC via the namespace including the\n * hash — no explicit invalidation.\n * - `onAttach(ctx)` — subscribes to the collection's changes (via the seam's\n * engine-free `ctx.subscribe`) and persists the current row snapshot\n * (`ctx.snapshot`) DEBOUNCED (trailing) to avoid thrashing IndexedDB on a\n * burst of writes. Also registers the shared store as a durable resource so a\n * logout `wipeDurableStore` clears it.\n * - `teardown(ctx)` — unsubscribes, unregisters the durable resource BEFORE\n * closing IndexedDB (the #1762 discipline: a later `wipeDurableStore` is then a\n * no-op, never a double-clear), and the LAST detach closes the shared db. The\n * durable SNAPSHOT survives for the next load.\n *\n * ## Namespace segregation + logout wipe (shared with the outbox)\n *\n * The durable namespace folds api base / tenant / identity / manifest hash, so\n * switching users on one device lands on a DIFFERENT IndexedDB database — one\n * user can never read another's persisted rows. The SAME namespace is shared\n * with the outbox (#1762), so registering this store under it means a single\n * {@link wipeDurableStore} on logout clears BOTH the persisted collections AND\n * the outbox queue in one call (the \"logout wipe clears persisted data AND\n * outbox state\" AC).\n *\n * ## Degrade, don't crash\n *\n * If IndexedDB is unavailable (a probe `open()` throws — private mode, sandboxed\n * iframe), the capability `console.warn`s ONCE and behaves as non-persistent:\n * `warmStart` returns nothing (normal fetch), write-back is a no-op. It never\n * throws — matching the outbox's `probeIndexedDb()` posture.\n *\n * Engine-free public surface: no `@tanstack/*` type appears — inside the\n * engine-absorption boundary (#1761), verified by\n * scripts/check-smrt-web-engine-boundary.mjs.\n */\n\nimport type { SmrtWebCapability } from './capability.js';\nimport {\n type DurableStoreKey,\n durableStoreNamespace,\n registerDurableResource,\n} from './durable-store.js';\nimport {\n openSnapshotStore,\n probeIndexedDb,\n type SnapshotStore,\n} from './persistence/snapshot-store.js';\n\n/** Default trailing-debounce window (ms) for the write-back. */\nexport const DEFAULT_PERSIST_DEBOUNCE_MS = 250;\n\n/**\n * Configuration for {@link persistCollection}. Generic in the collection's row\n * type `TData` so the capability matches the collection it plugs into.\n */\nexport interface PersistCollectionConfig<TData extends object = object> {\n /**\n * The REST collection name this snapshot is keyed by — MUST be the SAME\n * `definition.name` passed to {@link createSmrtCollection} (it is the store\n * key under the namespace). Provided explicitly (not read off the ctx) so the\n * config is self-describing and testable.\n */\n collection: string;\n /**\n * The durable-store identity this snapshot is namespaced under — folds api\n * base / tenant / identity / manifest hash, so a logout, tenant switch, or a\n * contract-changing deploy lands on a different IndexedDB database (never\n * cross-identity reuse, and a shape change drops old snapshots). Shared with\n * the outbox (#1762) via the same {@link durableStoreNamespace}, so one\n * {@link wipeDurableStore} clears both.\n *\n * The canonical `manifestHash` source is the build-time inject — the\n * `manifestHash` constant the generated `@happyvertical/smrt-virt-web` module\n * exports (#1764). Thread it here at the call site.\n */\n namespace: DurableStoreKey;\n /**\n * Trailing-debounce window (ms) for the write-back snapshot. A burst of\n * mutations within the window collapses to ONE IndexedDB write of the final\n * row set. Default {@link DEFAULT_PERSIST_DEBOUNCE_MS}. Set 0 to write on\n * every change (tests).\n */\n debounceMs?: number;\n}\n\n/**\n * A shared, ref-counted snapshot store for one namespace: N opted-in\n * collections of a given identity share ONE IndexedDB database (like the\n * outbox's shared queue). Opened lazily on first attach, closed on last detach.\n */\ninterface SharedSnapshotEngine {\n /** The open store, once its async open resolves. */\n store: SnapshotStore | undefined;\n /** Resolves to the store (or undefined if IndexedDB is unavailable). */\n ready: Promise<SnapshotStore | undefined>;\n /** How many attached collections reference this engine. */\n refCount: number;\n /** Unregister the durable resource registered on first attach. */\n unregister: (() => void) | undefined;\n}\n\n/** Registry mapping a namespace string to its shared snapshot engine. */\nconst enginesByNamespace = new Map<string, SharedSnapshotEngine>();\n\n/** One-time IndexedDB-unavailable warning, module-wide. */\nlet warnedNoIndexedDb = false;\nfunction warnNoIndexedDbOnce(): void {\n if (warnedNoIndexedDb) return;\n warnedNoIndexedDb = true;\n // biome-ignore lint/suspicious/noConsole: smrt-web has no logger dep (TanStack-only); a degraded-to-non-persistent notice surfaces via console.warn by design (#1764)\n console.warn(\n '[smrt-web] IndexedDB is unavailable; persistence is disabled (collections behave as non-persistent).',\n );\n}\n\n/**\n * Acquire (or create) the shared snapshot engine for `namespace`, bumping its\n * ref count. On first acquire it probes IndexedDB, opens the store, and\n * registers ONE durable resource so {@link wipeDurableStore} clears every\n * persisted collection under the namespace. A probe failure resolves the engine\n * with `store: undefined` (non-persistent) and warns once.\n */\nfunction acquireSnapshotEngine(namespace: string): SharedSnapshotEngine {\n const existing = enginesByNamespace.get(namespace);\n if (existing) {\n existing.refCount += 1;\n return existing;\n }\n\n // Build the engine, then attach `ready` — the async open assigns back onto\n // `engine`, so it must exist first (avoids a use-before-assigned self-ref).\n const engine: SharedSnapshotEngine = {\n store: undefined,\n refCount: 1,\n unregister: undefined,\n ready: Promise.resolve(undefined),\n };\n engine.ready = (async () => {\n const usable = await probeIndexedDb();\n if (!usable) {\n warnNoIndexedDbOnce();\n return undefined;\n }\n try {\n const store = await openSnapshotStore(namespace);\n engine.store = store;\n // Register the shared store as a durable resource so a logout wipe clears\n // every persisted collection under this namespace at once — alongside the\n // outbox's own registered resource.\n engine.unregister = registerDurableResource(namespace, {\n kind: 'persisted-collection',\n clear: () => store.clear(),\n });\n return store;\n } catch {\n warnNoIndexedDbOnce();\n return undefined;\n }\n })();\n enginesByNamespace.set(namespace, engine);\n return engine;\n}\n\n/**\n * Release one reference to the shared engine. The LAST release unregisters the\n * durable resource (so a later wipe is a no-op) and closes the database. Awaits\n * `ready` first so a release that races the async open still tears down cleanly.\n */\nasync function releaseSnapshotEngine(namespace: string): Promise<void> {\n const engine = enginesByNamespace.get(namespace);\n if (!engine) return;\n engine.refCount -= 1;\n if (engine.refCount > 0) return;\n enginesByNamespace.delete(namespace);\n // Ensure the open settled before we unregister/close (a teardown right after\n // construction can outrun the async open).\n await engine.ready;\n // Unregister BEFORE closing so a concurrent wipeDurableStore neither\n // double-clears nor touches a closed db (the #1762 discipline).\n engine.unregister?.();\n engine.unregister = undefined;\n engine.store?.close();\n engine.store = undefined;\n}\n\n/**\n * Build a durable persistence capability for a collection. Add the returned\n * capability to the collection's `capabilities` array; a collection without it\n * is unaffected (the seam's no-op guarantee — the \"opt-in per model\" AC).\n *\n * The same `namespace` across multiple collections shares ONE snapshot database\n * (one open, one durable resource, one wipe), and each collection is keyed by\n * its own `collection` name within it.\n */\nexport function persistCollection<TData extends object = object>(\n config: PersistCollectionConfig<TData>,\n): SmrtWebCapability<TData> {\n const namespace = durableStoreNamespace(config.namespace);\n const collectionName = config.collection;\n const debounceMs = config.debounceMs ?? DEFAULT_PERSIST_DEBOUNCE_MS;\n\n // Per-collection instance state (set in onAttach, torn down in teardown).\n let engine: SharedSnapshotEngine | undefined;\n let subscription: { unsubscribe(): void } | undefined;\n let debounceTimer: ReturnType<typeof setTimeout> | undefined;\n // The latest snapshot getter, captured from ctx at attach; used by the flush\n // so it always writes the CURRENT rows.\n let readSnapshot: (() => ReadonlyArray<Record<string, unknown>>) | undefined;\n let detached = false;\n // Single-flight write-back: `flushing` is the in-progress write; `dirty` marks\n // that a change arrived while a write was running, so the flusher re-reads the\n // (possibly newer) snapshot and writes again. This makes the LAST state always\n // win regardless of how the async store-open / save promises interleave — the\n // race that a naive \"fire a flush per change\" loses.\n let flushing: Promise<void> | undefined;\n let dirty = false;\n\n const doFlush = async (): Promise<void> => {\n while (dirty && !detached) {\n dirty = false;\n if (!engine || !readSnapshot) return;\n const store = await engine.ready;\n if (detached || !store) return;\n // Clone to a plain array so the persisted snapshot is a stable copy of the\n // rows at this instant (IndexedDB clones on write, but decouple the ref set\n // now for determinism).\n const rows = readSnapshot().map((row) => ({ ...row }));\n try {\n await store.save(collectionName, rows);\n } catch {\n // Best-effort: a failed write must not break the collection. The next\n // change re-attempts; a quota error simply means the reload won't warm.\n }\n }\n };\n\n /** Kick the single-flight flusher, coalescing concurrent requests. */\n const runFlush = (): void => {\n dirty = true;\n if (flushing) return;\n flushing = doFlush().finally(() => {\n flushing = undefined;\n // A change that arrived during the final await re-armed `dirty`; run again.\n if (dirty && !detached) runFlush();\n });\n };\n\n const scheduleFlush = (): void => {\n if (detached) return;\n // ALWAYS via a timer (even at 0ms), so a pending write is CANCELABLE. This\n // is load-bearing at teardown: the engine's own cleanup() fires a\n // \"rows removed\" change BEFORE this capability's teardown unsubscribes, and\n // a synchronous flush there would persist an empty snapshot over the good\n // one. Because teardown runs right after `await collection.cleanup()` and a\n // setTimeout(0) is a macrotask, teardown's clearTimeout cancels that\n // cleanup-triggered flush before it can run. Mid-life writes fire normally.\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(\n () => {\n debounceTimer = undefined;\n runFlush();\n },\n Math.max(0, debounceMs),\n );\n // Don't keep the event loop alive solely for a pending write (Node/test\n // parity, matching the outbox's poll-timer unref).\n (debounceTimer as { unref?: () => void }).unref?.();\n };\n\n return {\n name: 'persistence',\n\n async warmStart(ctx) {\n // Rehydrate from disk. A different namespace (tenant/identity/manifest\n // change) means this collection's snapshot is in a different database and\n // simply isn't found here → undefined → the engine fetches fresh with no\n // stale-schema hydration. `ctx.definition.name` must match config.collection;\n // we key on the explicit config value (the store key) for determinism.\n const acquired = acquireSnapshotEngine(namespace);\n engine = acquired;\n // Enable write-back ONLY when the seam actually exposes a row snapshot.\n // `ctx.snapshot` is optional (a hand-built test ctx may omit it); without\n // it, `() => [] ` would persist an EMPTY snapshot over a prior good one, so\n // treat write-back as a no-op instead (readSnapshot stays undefined).\n readSnapshot = ctx.snapshot ? () => ctx.snapshot?.() ?? [] : undefined;\n const store = await acquired.ready;\n if (!store) return undefined; // non-persistent (no IndexedDB)\n const rows = await store.load(collectionName);\n if (!rows || rows.length === 0) return undefined;\n // The persisted rows are plain DTOs already carrying `id`; hand them back\n // as the seed. (SmrtWebRow<TData> = TData & { id: string }.)\n return rows as Array<TData & { id: string }>;\n },\n\n onAttach(ctx) {\n // If warmStart didn't run (shouldn't happen — it runs before construction\n // when initialData is absent; but initialData WINS and skips warmStart),\n // acquire here so write-back still persists.\n if (!engine) {\n engine = acquireSnapshotEngine(namespace);\n }\n if (!readSnapshot && ctx.snapshot) {\n readSnapshot = () => ctx.snapshot?.() ?? [];\n }\n // Write-back requires BOTH a row snapshot to read and a change stream to\n // trigger on. If the seam omits either (both are optional hooks), leave\n // persistence write-back a NO-OP — never schedule a flush that would\n // persist an empty snapshot over a prior good one (honors the optional-hook\n // contract; the real factory always populates both, so no prod impact).\n if (!ctx.snapshot || !ctx.subscribe || !readSnapshot) return;\n // Subscribe to changes and persist (debounced) on each. The seam's\n // `ctx.subscribe` mirrors the public handle's subscribeChanges (plain-DTO\n // payloads); we only need the SIGNAL, then re-read via ctx.snapshot.\n subscription = ctx.subscribe(() => scheduleFlush());\n // Persist once on attach too, so a collection seeded by initialData (which\n // skipped warmStart) still writes an initial snapshot for the next load.\n scheduleFlush();\n },\n\n async teardown() {\n // teardown runs AFTER the engine collection's own cleanup(), which fires a\n // \"rows removed\" change. Two guards keep that from persisting an empty\n // snapshot over the good one: (1) `detached = true` FIRST makes any\n // subsequent scheduleFlush a no-op; (2) clearing the debounce timer cancels\n // a flush the cleanup change scheduled just before this ran (it's a pending\n // macrotask, so clearing it here wins). The per-change write-back already\n // persisted the latest rows DURING the collection's life, so nothing is\n // lost. We then drain any in-flight write so it commits before closing.\n detached = true;\n if (debounceTimer) {\n clearTimeout(debounceTimer);\n debounceTimer = undefined;\n }\n subscription?.unsubscribe();\n subscription = undefined;\n readSnapshot = undefined;\n // Wait out any write already in flight so it durably commits.\n if (flushing) await flushing;\n const current = engine;\n engine = undefined;\n if (current) {\n await releaseSnapshotEngine(namespace);\n }\n },\n };\n}\n","/**\n * @happyvertical/smrt-web — live-updates subscriber (#1763, CLIENT half).\n *\n * The browser half of live cache invalidation: ONE app-wide subscriber that\n * turns the server's coarse change signals into collection refetches, so a\n * dashboard reflects another session's writes without a manual refresh or\n * aggressive polling. It speaks the two channels the #1763 server half\n * generated:\n *\n * - the push channel — the generated `_events` Server-Sent-Events route\n * (`packages/core/src/generators/events-route.ts`): NAMED events\n * `event: change` / `event: resync` whose `data` is `{table, operation,\n * rowId, tenantId}`, with the cursor `seq` carried ONLY in the SSE `id:`\n * field (mirrored by the browser to `MessageEvent.lastEventId`), plus\n * `event: manifest` for live contract detection. Heartbeats are\n * `: heartbeat` comment lines EventSource ignores natively.\n * - the pull channel — the generated `_changes` route\n * (`packages/core/src/generators/changes-route.ts`): `GET {changesUrl}\n * ?since=&tables=&limit=` →\n * `{changes, cursor, resyncRequired?, resyncCursor?}`, the full fallback\n * where SSE is unavailable.\n *\n * ## No row payload, no tenant logic, no authorization here\n *\n * The wire carries only a signal (table + row id + operation), never a row.\n * A signal makes the subscriber INVALIDATE — the refetch re-reads through the\n * authorized collection routes, so authorization and tenant-scoping stay\n * ENTIRELY on the read path, exactly as the server enforces (the `_events`\n * stream is auth-guarded and tenant-scoped at connection open; `_changes` is\n * tenant-scoped per request). This module therefore does NO tenant filtering\n * and needs no identity — it just maps `table → invalidate`.\n *\n * ## One app-wide instance (mirrors createSmrtWebClient)\n *\n * A consumer constructs ONE subscriber and passes it to every\n * {@link liveInvalidation} capability — the same \"one shared handle\" convention\n * as {@link createSmrtWebClient}. It is NOT auto-derived per collection: one\n * EventSource / one poll loop feeds every registered collection.\n *\n * ## Idempotent invalidation → no client-side dedup\n *\n * Re-invalidating on a replayed change (a reconnect replays the tail from the\n * cursor) is safe: invalidation only schedules a background refetch, and the\n * factory's relationship-derived invalidation is itself idempotent\n * (over-invalidation merely refetches). So the subscriber deliberately does NO\n * seq dedup — a replayed signal STILL fires, which is exactly what guarantees a\n * gap misses no invalidation. `lastSeq` is a resume cursor for the poll\n * fallback, not a dedup filter.\n *\n * Engine boundary: no `@happyvertical/smrt-*` import and no `@tanstack/*`\n * import — URLs and `fetch` arrive as config, and the refetch primitive arrives\n * as the `invalidate` callback each capability wires from `ctx.invalidate()`.\n * smrt-web has no logger dependency, so faults are surfaced via `console.warn`\n * (matching `warnCapability` / core's `deliverLocally` style).\n *\n * The server may also emit a named `manifest` event at connection open (#1859):\n * `{ manifestHash }`. When this differs from the running build hash configured\n * here, the subscriber latches `updateState.contract`.\n */\n\nimport type { SmrtWebCapability } from './capability.js';\nimport type { UpdateState } from './update-state.js';\n\n/**\n * The minimal `EventSource` surface the subscriber uses — declared here so a\n * test injects a fake without a real DOM, and so this module compiles without\n * DOM `lib` beyond the ambient global. A real `EventSource` satisfies it\n * structurally.\n */\nexport interface SmrtWebEventSource {\n /** Native reconnect fires this after (re)connect. */\n onopen: ((this: unknown, ev: unknown) => unknown) | null;\n /**\n * Fires on a stream error. A transient drop leaves `readyState` at OPEN\n * (0→…) and the browser auto-reconnects with `Last-Event-ID`; a fatal error\n * (server 401 / route disabled) leaves it CLOSED.\n */\n onerror: ((this: unknown, ev: unknown) => unknown) | null;\n /**\n * Fires only for UNNAMED (`message`) events. The `_events` frames are NAMED\n * (`change` / `resync`), so this never fires for them — the subscriber uses\n * {@link addEventListener} instead. Present only to satisfy the structural\n * type of a real EventSource.\n */\n onmessage: ((this: unknown, ev: unknown) => unknown) | null;\n /** Register a listener for a NAMED event (`change`, `resync`). */\n addEventListener(\n type: string,\n listener: (ev: { data: string; lastEventId: string }) => void,\n ): void;\n /** Close the stream (stops reconnection). */\n close(): void;\n /** `CONNECTING` (0), `OPEN` (1), or `CLOSED` (2). */\n readonly readyState: number;\n}\n\n/** EventSource.CLOSED — a fatal, non-reconnecting state. */\nconst EVENT_SOURCE_CLOSED = 2;\n\n/** Factory that constructs an {@link SmrtWebEventSource} for a URL. */\nexport type SmrtWebEventSourceFactory = (\n url: string,\n init: { withCredentials: boolean },\n) => SmrtWebEventSource | null | undefined;\n\n/** Configuration for {@link createSmrtWebEventSubscriber}. */\nexport interface SmrtWebEventSubscriberConfig {\n /** Absolute or same-origin URL of the generated `_events` SSE route. */\n eventsUrl: string;\n /** Absolute or same-origin URL of the generated `_changes` route (fallback). */\n changesUrl: string;\n /** `fetch` implementation for the polling fallback. Defaults to global fetch. */\n fetchFn?: typeof fetch;\n /**\n * Factory for the EventSource. Defaults to `new globalThis.EventSource(url,\n * { withCredentials })`, guarded by a `typeof` check — when EventSource is\n * absent (or the factory yields nothing) the subscriber starts on the polling\n * fallback instead.\n */\n eventSourceFactory?: SmrtWebEventSourceFactory;\n /** Poll interval for the `_changes` fallback (ms). Default 5000. */\n pollIntervalMs?: number;\n /** `withCredentials` for the EventSource (cookie auth). Default true. */\n withCredentials?: boolean;\n /**\n * The RUNNING build's web-collection shape digest. When paired with\n * `updateState`, a differing server hash from the `_events` manifest frame\n * latches the contract update signal (#1859).\n */\n manifestHash?: string;\n /**\n * Optional update-state primitive from `createUpdateState()`. Only its\n * contract signal is used here; bundle polling remains the caller's job.\n */\n updateState?: Pick<UpdateState, 'notifyContractUpdated'>;\n}\n\n/** Which transport a subscriber is currently using. */\nexport type SmrtWebSubscriberTransport = 'sse' | 'polling' | 'idle';\n\n/**\n * The ONE app-wide live-updates subscriber. Construct once (see\n * {@link createSmrtWebEventSubscriber}) and pass it to every\n * {@link liveInvalidation} capability.\n */\nexport interface SmrtWebEventSubscriber {\n /**\n * The live transport: `'sse'` while the EventSource is the source of truth,\n * `'polling'` when running (or downgraded to) the `_changes` fallback,\n * `'idle'` before any transport starts.\n */\n readonly transport: SmrtWebSubscriberTransport;\n /**\n * Register an invalidator for a physical table. Returns an unregister\n * function. Multiple invalidators may share a table (two live collections);\n * a `change` for that table fires them all.\n */\n registerTable(table: string, invalidate: () => void): () => void;\n /** Invalidate every registered table (a `resync` / `resyncRequired`). */\n invalidateAll(): void;\n /** Tear down the live transport and drop all registrations. */\n close(): void;\n}\n\n/**\n * Default EventSource factory: `new globalThis.EventSource(url, init)` when the\n * global exists, else `undefined` so the caller falls back to polling.\n */\nfunction defaultEventSourceFactory(\n url: string,\n init: { withCredentials: boolean },\n): SmrtWebEventSource | undefined {\n const EventSourceCtor = (\n globalThis as { EventSource?: new (u: string, i?: unknown) => unknown }\n ).EventSource;\n if (typeof EventSourceCtor !== 'function') return undefined;\n return new EventSourceCtor(url, init) as unknown as SmrtWebEventSource;\n}\n\n/**\n * One entry of a `_changes` page (mirrors core's `ChangeFeedEntry`) — the only\n * fields the subscriber reads. Declared locally so this module keeps its\n * zero-`@happyvertical/smrt-*`-import boundary.\n */\ninterface ChangeEntryLike {\n table: string;\n seq?: number;\n}\n\n/** A `_changes` page (mirrors core's `ChangeFeedPage`). */\ninterface ChangesPageLike {\n changes: ChangeEntryLike[];\n cursor: number;\n resyncRequired?: boolean;\n resyncCursor?: number;\n}\n\n/**\n * Create the app-wide live-updates subscriber. Feature-detects ONCE at\n * construction: an available EventSource connects the SSE stream; otherwise it\n * starts the `_changes` poll loop. A fatal SSE error later downgrades to\n * polling for the rest of the subscriber's life (no flap-back).\n */\nexport function createSmrtWebEventSubscriber(\n config: SmrtWebEventSubscriberConfig,\n): SmrtWebEventSubscriber {\n const {\n eventsUrl,\n changesUrl,\n fetchFn = (...args: Parameters<typeof fetch>) => globalThis.fetch(...args),\n eventSourceFactory = defaultEventSourceFactory,\n pollIntervalMs = 5000,\n withCredentials = true,\n manifestHash,\n updateState,\n } = config;\n\n // table → set of invalidators. A Set so a collection registers/unregisters\n // without positional bookkeeping and two collections coexist under one table.\n const tableInvalidators = new Map<string, Set<() => void>>();\n\n // The resume cursor: the highest seq observed (SSE) or the last `_changes`\n // cursor (polling). NOT a dedup filter — invalidation is idempotent.\n let lastSeq: number | null = null;\n\n let transport: SmrtWebSubscriberTransport = 'idle';\n let eventSource: SmrtWebEventSource | null = null;\n let pollTimer: ReturnType<typeof setInterval> | null = null;\n let closed = false;\n\n const registeredTables = (): string[] =>\n [...tableInvalidators.keys()].sort((a, b) => a.localeCompare(b));\n\n const buildChangesUrl = (since: number, tables: string[]): string => {\n const url = new URL(changesUrl, 'http://smrt.local/');\n url.searchParams.set('since', String(since));\n url.searchParams.set('tables', tables.join(','));\n if (/^[a-zA-Z][a-zA-Z\\d+\\-.]*:/.test(changesUrl)) return url.href;\n const pathQueryHash = `${url.pathname}${url.search}${url.hash}`;\n if (changesUrl.startsWith('//')) return `//${url.host}${pathQueryHash}`;\n if (changesUrl.startsWith('/')) return pathQueryHash;\n return pathQueryHash.startsWith('/')\n ? pathQueryHash.slice(1)\n : pathQueryHash;\n };\n\n const isFiniteNumber = (value: unknown): value is number =>\n typeof value === 'number' && Number.isFinite(value);\n\n /**\n * Report a fault without a logger dep (smrt-web is TanStack-only) — a\n * swallowed diagnostic in the package's fail-loud-in-the-console style.\n */\n const warn = (message: string, error?: unknown): void => {\n // biome-ignore lint/suspicious/noConsole: smrt-web has no logger dep (TanStack-only); live-subscriber faults surface via console.warn by design (#1763)\n console.warn(`[smrt-web] live subscriber: ${message}`, error);\n };\n\n /**\n * Fire one set of invalidators, each in its OWN try/catch (snapshot first so\n * an invalidator that unregisters mid-delivery can't mutate the set) — one\n * throwing invalidator never blocks its siblings.\n */\n const fireAll = (invalidators: Set<() => void> | undefined): void => {\n if (!invalidators || invalidators.size === 0) return;\n for (const invalidate of [...invalidators]) {\n try {\n invalidate();\n } catch (error) {\n warn('an invalidator threw; ignoring', error);\n }\n }\n };\n\n /** Invalidate every collection registered for `table`. */\n const invalidateTable = (table: string): void => {\n fireAll(tableInvalidators.get(table));\n };\n\n /** Invalidate every registered collection across every table. */\n const invalidateAll = (): void => {\n for (const invalidators of tableInvalidators.values()) {\n fireAll(invalidators);\n }\n };\n\n /** Keep the resume cursor from an SSE `id:` / MessageEvent.lastEventId. */\n const advanceLastSeqFromEventId = (lastEventId: string): void => {\n const seq = Number(lastEventId);\n if (Number.isFinite(seq)) lastSeq = seq;\n };\n\n /**\n * Parse a `change` frame's `data` DEFENSIVELY: malformed JSON (or a payload\n * with no string `table`) is logged and dropped — NEVER thrown back into the\n * EventSource message loop (a throw there would break subsequent delivery).\n */\n const onChange = (ev: { data: string; lastEventId: string }): void => {\n if (closed) return;\n let table: string | undefined;\n try {\n const parsed = JSON.parse(ev.data) as { table?: unknown };\n if (typeof parsed.table === 'string') table = parsed.table;\n } catch (error) {\n warn('dropping malformed change frame', error);\n return;\n }\n if (table === undefined) {\n warn('dropping change frame with no table', ev.data);\n return;\n }\n // The browser mirrors the SSE `id:` field to lastEventId; keep it as the\n // resume cursor (used only by the poll fallback), guarding a non-numeric id.\n advanceLastSeqFromEventId(ev.lastEventId);\n invalidateTable(table);\n };\n\n /**\n * A `resync` frame: the server's cursor is stale — invalidate everything and\n * keep the server-provided horizon from `id:` for any later polling downgrade.\n */\n const onResync = (ev: { data: string; lastEventId: string }): void => {\n if (closed) return;\n advanceLastSeqFromEventId(ev.lastEventId);\n invalidateAll();\n };\n\n /**\n * Connection-open server contract frame (#1859). It has no cursor and carries\n * no user data; malformed frames are dropped like malformed change frames.\n */\n const onManifest = (ev: { data: string; lastEventId: string }): void => {\n if (closed || manifestHash === undefined || !updateState) return;\n try {\n const parsed = JSON.parse(ev.data) as { manifestHash?: unknown };\n const serverHash = parsed.manifestHash;\n if (typeof serverHash !== 'string' || serverHash.length === 0) {\n warn('dropping manifest frame with no manifestHash', ev.data);\n return;\n }\n if (serverHash !== manifestHash) {\n updateState.notifyContractUpdated();\n }\n } catch (error) {\n warn('dropping malformed manifest frame', error);\n }\n };\n\n /**\n * Poll `_changes` once from the resume cursor. `resyncRequired` (HTTP 200)\n * invalidates everything, then resumes from the server-provided\n * `resyncCursor` horizon so a pruned `since=0` does not loop forever.\n * Otherwise each change invalidates its table and the cursor advances to the\n * page cursor. A fetch rejection is caught+logged; the interval keeps ticking\n * (self-heals) — a path distinct from `resyncRequired`.\n */\n const poll = async (): Promise<void> => {\n if (closed) return;\n const tables = registeredTables();\n if (tables.length === 0) return;\n try {\n const since = lastSeq ?? 0;\n const url = buildChangesUrl(since, tables);\n const response = await fetchFn(url, { credentials: 'include' });\n if (closed) return;\n const page = (await response.json()) as ChangesPageLike;\n if (closed) return;\n if (page.resyncRequired) {\n invalidateAll();\n if (isFiniteNumber(page.resyncCursor)) {\n lastSeq = page.resyncCursor;\n } else if (isFiniteNumber(page.cursor) && page.cursor > since) {\n lastSeq = page.cursor;\n } else {\n lastSeq = null;\n }\n return;\n }\n for (const change of page.changes ?? []) {\n if (typeof change.table === 'string') invalidateTable(change.table);\n }\n if (typeof page.cursor === 'number') lastSeq = page.cursor;\n } catch (error) {\n // A rejection (network down) or a bad body — logged; the interval keeps\n // ticking so the next poll self-heals.\n warn('poll failed; will retry on the next interval', error);\n }\n };\n\n /** Start the `_changes` poll loop — the full fallback / downgrade target. */\n const startPolling = (): void => {\n if (closed || transport === 'polling') return;\n transport = 'polling';\n pollTimer = setInterval(() => {\n void poll();\n }, pollIntervalMs);\n // Don't keep the event loop alive solely for polling (Node/test parity).\n (pollTimer as { unref?: () => void }).unref?.();\n };\n\n /**\n * Connect the SSE stream. The frames are NAMED events, so `change`/`resync`\n * are wired via `addEventListener` — `onmessage` would NEVER fire for them.\n * A transient `onerror` needs no code (EventSource auto-reconnects with\n * Last-Event-ID); a FATAL error (readyState CLOSED — server 401 / route\n * disabled) downgrades to polling once and stays there.\n */\n const connectSse = (source: SmrtWebEventSource): void => {\n transport = 'sse';\n eventSource = source;\n source.addEventListener('change', onChange);\n source.addEventListener('resync', onResync);\n source.addEventListener('manifest', onManifest);\n source.onerror = () => {\n if (closed) return;\n // Only a fatal (CLOSED) error means SSE is unavailable for good — fall\n // back to polling. A transient drop (still CONNECTING/OPEN) is handled\n // natively by the browser's reconnect; do nothing.\n if (source.readyState === EVENT_SOURCE_CLOSED) {\n // Close the dead source and stop listening to it, then downgrade. No\n // flap-back: once polling, we never re-attempt SSE.\n try {\n source.close();\n } catch {\n // ignore — already dead\n }\n eventSource = null;\n startPolling();\n }\n };\n };\n\n // Feature-detect ONCE. An EventSource we can construct → SSE; otherwise (or\n // if the factory yields nothing) → polling.\n const initialSource = eventSourceFactory(eventsUrl, { withCredentials });\n if (initialSource) {\n connectSse(initialSource);\n } else {\n startPolling();\n }\n\n return {\n get transport() {\n return transport;\n },\n registerTable(table, invalidate) {\n let set = tableInvalidators.get(table);\n if (!set) {\n set = new Set<() => void>();\n tableInvalidators.set(table, set);\n }\n set.add(invalidate);\n return () => {\n const current = tableInvalidators.get(table);\n if (!current) return;\n current.delete(invalidate);\n if (current.size === 0) tableInvalidators.delete(table);\n };\n },\n invalidateAll,\n close() {\n if (closed) return;\n closed = true;\n if (eventSource) {\n try {\n eventSource.close();\n } catch {\n // ignore — best-effort teardown\n }\n eventSource = null;\n }\n if (pollTimer) {\n clearInterval(pollTimer);\n pollTimer = null;\n }\n tableInvalidators.clear();\n transport = 'idle';\n },\n };\n}\n\n/** Configuration for the {@link liveInvalidation} capability. */\nexport interface LiveInvalidationConfig {\n /** The one app-wide subscriber from {@link createSmrtWebEventSubscriber}. */\n subscriber: Pick<\n SmrtWebEventSubscriber,\n 'registerTable' | 'invalidateAll' | 'transport'\n >;\n /**\n * The PHYSICAL table name this collection reads. EXPLICIT because a\n * `SmrtWebCollectionDefinition` has no physical-table field and STI children\n * share one base table — the subscriber keys signals by physical table, so a\n * guess would mis-route invalidations.\n */\n tableName: string;\n}\n\n/**\n * A thin per-collection capability that subscribes THIS collection to live\n * signals for its `tableName`. On attach it registers `ctx.invalidate` (the\n * factory's relationship-derived refetch primitive) with the shared subscriber;\n * on teardown it unregisters. All transport, reconnection, and fan-out live in\n * the {@link createSmrtWebEventSubscriber}; this capability is just the wire\n * between one collection and that one subscriber.\n */\nexport function liveInvalidation<TData extends object = object>(\n config: LiveInvalidationConfig,\n): SmrtWebCapability<TData> {\n const { subscriber, tableName } = config;\n let unregister: (() => void) | undefined;\n return {\n name: 'live-invalidation',\n onAttach(ctx) {\n unregister = subscriber.registerTable(tableName, () => ctx.invalidate());\n },\n teardown() {\n unregister?.();\n unregister = undefined;\n },\n };\n}\n","/**\n * @happyvertical/smrt-web — the durable version-meta store (#1764).\n *\n * A tiny raw IndexedDB key/value store for version-awareness bookkeeping —\n * currently the single \"last-seen manifestHash\" per durable-store namespace,\n * which contract-change detection ({@link ../update-state}) compares the running\n * build against across loads. One database per namespace\n * ({@link durableStoreNamespace}), one object store (`meta`) keyed by a string\n * key, each record `{ key, value }`.\n *\n * Separate from the collection snapshot store (persistence/snapshot-store.ts):\n * that one holds per-collection row blobs; this holds scalar version state, and\n * update-awareness must work even for an app that persists no collection. Both\n * register under the SAME namespace so one {@link wipeDurableStore} clears\n * everything on logout.\n *\n * Raw IndexedDB, ZERO new runtime dependency — inside the engine-absorption\n * boundary (#1761), mirroring offline/durable-queue.ts.\n */\n\n/** IndexedDB object-store name inside every version-meta database. */\nexport const META_STORE = 'meta';\n/** IndexedDB schema version. Bump only on a store/index migration. */\nexport const META_DB_VERSION = 1;\n/**\n * Suffix appended to the durable-store namespace to form this store's OWN\n * IndexedDB database name — DISTINCT from both the merged outbox's bare-namespace\n * DB and the snapshot store's `::snapshots` DB. Required so persistence + outbox\n * + version-meta under one shared namespace never collide on a single-store v1\n * database (a second opener would find the existing v1 DB, skip\n * `onupgradeneeded`, and throw `NotFoundError` on `objectStore('meta')`).\n * `wipeDurableStore` still clears all three via each registered `clear()`.\n */\nexport const META_DB_SUFFIX = '::meta';\n/** The reserved key under which the last-seen manifest hash is stored. */\nexport const LAST_SEEN_MANIFEST_HASH_KEY = 'lastSeenManifestHash';\n\n/** One key/value meta record. */\ninterface MetaRecord {\n key: string;\n value: string;\n}\n\n/** Promisify a single IndexedDB request. */\nfunction promisifyRequest<T>(request: IDBRequest<T>): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n request.onsuccess = () => resolve(request.result);\n request.onerror = () =>\n reject(request.error ?? new Error('[smrt-web] IndexedDB request failed'));\n });\n}\n\n/** Promisify a transaction's durable completion. */\nfunction awaitTransaction(tx: IDBTransaction): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n tx.oncomplete = () => resolve();\n tx.onerror = () =>\n reject(tx.error ?? new Error('[smrt-web] IndexedDB transaction failed'));\n tx.onabort = () =>\n reject(tx.error ?? new Error('[smrt-web] IndexedDB transaction aborted'));\n });\n}\n\n/**\n * The durable version-meta store over one IndexedDB database. Constructed via\n * {@link openVersionMetaStore}; used through the async methods below.\n */\nexport class VersionMetaStore {\n private readonly db: IDBDatabase;\n /** The IndexedDB database name (== the durable-store namespace). */\n readonly dbName: string;\n\n constructor(db: IDBDatabase, dbName: string) {\n this.db = db;\n this.dbName = dbName;\n }\n\n /** Read the value for `key`, or `undefined` if unset / malformed. */\n async get(key: string): Promise<string | undefined> {\n const tx = this.db.transaction(META_STORE, 'readonly');\n const record = await promisifyRequest(\n tx.objectStore(META_STORE).get(key) as IDBRequest<MetaRecord | undefined>,\n );\n await awaitTransaction(tx);\n return record && typeof record.value === 'string'\n ? record.value\n : undefined;\n }\n\n /** Write (replacing) the value for `key`; resolves once durably committed. */\n async set(key: string, value: string): Promise<void> {\n const tx = this.db.transaction(META_STORE, 'readwrite');\n const record: MetaRecord = { key, value };\n await promisifyRequest(tx.objectStore(META_STORE).put(record));\n await awaitTransaction(tx);\n }\n\n /**\n * Drop EVERY meta record — the durable-store `clear()` for\n * {@link wipeDurableStore}, so a logout also clears the last-seen manifest\n * hash (the AC \"wipe clears the last-seen-hash record\").\n */\n async clear(): Promise<void> {\n const tx = this.db.transaction(META_STORE, 'readwrite');\n await promisifyRequest(tx.objectStore(META_STORE).clear());\n await awaitTransaction(tx);\n }\n\n /** Close the underlying database handle. */\n close(): void {\n this.db.close();\n }\n}\n\n/**\n * Open (creating/upgrading as needed) the version-meta database for `namespace`\n * and wrap it in a {@link VersionMetaStore}. `namespace` MUST be a\n * {@link durableStoreNamespace} string. The actual IndexedDB database name is\n * `${namespace}${META_DB_SUFFIX}` — a DISTINCT database from the outbox's and\n * the snapshot store's, so the three never collide (see {@link META_DB_SUFFIX}).\n */\nexport function openVersionMetaStore(\n namespace: string,\n): Promise<VersionMetaStore> {\n const idb = (globalThis as { indexedDB?: IDBFactory }).indexedDB;\n if (!idb) {\n return Promise.reject(\n new Error('[smrt-web] IndexedDB is unavailable in this environment'),\n );\n }\n const dbName = `${namespace}${META_DB_SUFFIX}`;\n return new Promise<VersionMetaStore>((resolve, reject) => {\n const request = idb.open(dbName, META_DB_VERSION);\n request.onupgradeneeded = () => {\n const db = request.result;\n if (!db.objectStoreNames.contains(META_STORE)) {\n db.createObjectStore(META_STORE, { keyPath: 'key' });\n }\n };\n request.onsuccess = () =>\n resolve(new VersionMetaStore(request.result, dbName));\n request.onerror = () =>\n reject(\n request.error ??\n new Error(`[smrt-web] failed to open meta database \"${dbName}\"`),\n );\n request.onblocked = () =>\n reject(\n new Error(`[smrt-web] opening meta database \"${dbName}\" was blocked`),\n );\n });\n}\n","/**\n * @happyvertical/smrt-web — the framework-free `updateAvailable` primitive (#1764).\n *\n * A tiny pub/sub carrying an app's \"an update is available\" state, driven by TWO\n * INDEPENDENT signals so the presentation (a toast / reload prompt, shipped as a\n * Svelte binding in smrt-svelte) can react to either:\n *\n * - **bundle** — the client BUNDLE changed on the server (a new deploy of the\n * front-end assets). SvelteKit already detects this via its native `updated`\n * store (version.json polling); the consumer/smrt-svelte binding pushes it in\n * here through {@link UpdateState.notifyBundleUpdated}. This module owns no\n * polling — it just holds the state and fans out.\n * - **contract** — the API CONTRACT changed: the manifest hash, which only SMRT\n * knows. Detected here under the BUILD-TIME-INJECT decision (#1764): on init\n * we compare the RUNNING build's `manifestHash` (passed in by the consumer,\n * imported from `@happyvertical/smrt-virt-web`) against a persisted\n * \"last-seen manifestHash\" in durable storage. If they differ, the contract\n * changed across this load → the `contract` signal fires and the new value is\n * stored. So a returning user whose app was rebuilt against a new schema is\n * told to reload.\n *\n * `updateAvailable` is simply `bundle || contract` — either signal means \"an\n * update is available\".\n *\n * ## Scope: load-time contract detection only (documented follow-up)\n *\n * Contract detection here fires at MOST once per load (the compare-on-init).\n * LIVE-while-open contract detection — learning mid-session that the server\n * redeployed a new contract, via the `_events` SSE manifest frame — pushes the\n * same sticky `contract` signal through {@link UpdateState.notifyContractUpdated}.\n *\n * ## Durable last-seen storage + logout wipe\n *\n * The last-seen hash lives in the version-meta store\n * (update-state/meta-store.ts) under the durable-store namespace, registered as\n * a durable resource so a logout {@link wipeDurableStore} clears it too (the AC\n * \"wipe clears the last-seen-hash record\"). If IndexedDB is unavailable the\n * primitive still works for the bundle signal; contract detection degrades to a\n * no-op (no durable baseline to compare against) rather than throwing.\n *\n * Engine-free (no `@tanstack/*` import) and framework-free (no Svelte/DOM) — the\n * reactive binding is a separate smrt-svelte adapter (#1764 PART E).\n */\n\nimport {\n type DurableStoreKey,\n durableStoreNamespace,\n registerDurableResource,\n} from './durable-store.js';\nimport {\n LAST_SEEN_MANIFEST_HASH_KEY,\n openVersionMetaStore,\n type VersionMetaStore,\n} from './update-state/meta-store.js';\n\n/** The two independent update signals plus their derived union. */\nexport interface UpdateAvailableState {\n /** The client bundle changed on the server (SvelteKit `updated`). */\n readonly bundle: boolean;\n /** The API contract (manifest hash) changed across this load. */\n readonly contract: boolean;\n /** `bundle || contract` — either means an update is available. */\n readonly updateAvailable: boolean;\n}\n\n/** Configuration for {@link createUpdateState}. */\nexport interface UpdateStateConfig {\n /**\n * The RUNNING build's web-collection shape digest — the `manifestHash`\n * constant the generated `@happyvertical/smrt-virt-web` module exports\n * (#1764). Compared against the persisted last-seen value to detect a contract\n * change across loads. Omit to disable contract detection (bundle-only).\n */\n manifestHash?: string;\n /**\n * The durable-store identity the last-seen manifest hash is namespaced under —\n * folds api base / tenant / identity (and, yes, the manifest hash itself, but\n * see below). Shared with persistence/outbox so one {@link wipeDurableStore}\n * clears the last-seen record too.\n *\n * NOTE the namespace's OWN `manifestHash` segment should be a STABLE value\n * (e.g. the app's api-version or a constant), NOT the running build hash —\n * otherwise a contract change would move the namespace and the compare would\n * never find the prior value (always \"first run\"). Contract change is detected\n * by the STORED VALUE under a stable namespace, not by the namespace moving.\n */\n namespace: DurableStoreKey;\n}\n\n/** The framework-free update-available primitive. */\nexport interface UpdateState {\n /** The current signal state (a fresh immutable snapshot). */\n get(): UpdateAvailableState;\n /**\n * Subscribe to state changes; the callback fires immediately with the current\n * state and again on every transition. Returns an unsubscribe function.\n */\n subscribe(callback: (state: UpdateAvailableState) => void): () => void;\n /**\n * Push the BUNDLE signal — call from the consumer/smrt-svelte binding when\n * SvelteKit's `updated` store flips true. Idempotent: once set it stays set\n * (a bundle update does not un-happen).\n */\n notifyBundleUpdated(): void;\n /**\n * Push the API CONTRACT signal — call when a live transport observes a server\n * `manifestHash` that differs from this running build (#1859). Idempotent:\n * once set it stays set (a contract mismatch does not un-happen).\n */\n notifyContractUpdated(): void;\n /**\n * Resolves once the async contract detection has settled (compared the running\n * hash against the persisted last-seen value and fired the signal if needed).\n * Primarily for tests/SSR; UI code just subscribes.\n */\n readonly ready: Promise<void>;\n /** Detach the durable resource (does NOT wipe — that's wipeDurableStore). */\n dispose(): void;\n}\n\n/**\n * Create the framework-free `updateAvailable` primitive. Kicks off async\n * contract detection immediately (compare running vs. persisted manifest hash);\n * {@link UpdateState.notifyBundleUpdated} feeds the bundle signal, and\n * {@link UpdateState.notifyContractUpdated} feeds the live contract signal. A\n * change to EITHER signal notifies subscribers.\n */\nexport function createUpdateState(config: UpdateStateConfig): UpdateState {\n const namespace = durableStoreNamespace(config.namespace);\n let bundle = false;\n let contract = false;\n const subscribers = new Set<(state: UpdateAvailableState) => void>();\n\n // The open meta store + its durable-resource unregister, so dispose() can\n // detach and a wipe can clear the last-seen hash.\n let metaStore: VersionMetaStore | undefined;\n let unregister: (() => void) | undefined;\n let disposed = false;\n\n const snapshot = (): UpdateAvailableState => ({\n bundle,\n contract,\n updateAvailable: bundle || contract,\n });\n\n const notify = (): void => {\n const state = snapshot();\n for (const callback of [...subscribers]) {\n try {\n callback(state);\n } catch (error) {\n // biome-ignore lint/suspicious/noConsole: smrt-web has no logger dep (TanStack-only); a throwing update subscriber surfaces via console.warn by design (#1764)\n console.warn('[smrt-web] updateAvailable subscriber threw', error);\n }\n }\n };\n\n const setBundle = (): void => {\n if (bundle) return;\n bundle = true;\n notify();\n };\n const setContract = (): void => {\n if (contract) return;\n contract = true;\n notify();\n };\n\n // Async contract detection under BUILD-TIME INJECT (#1764): compare the\n // running build's manifestHash against the persisted last-seen value. On a\n // mismatch fire `contract` and store the new value; on first-ever run just\n // store it (no signal). Degrades to a no-op if IndexedDB is unavailable or no\n // running hash was supplied.\n const ready: Promise<void> = (async () => {\n const runningHash = config.manifestHash;\n if (runningHash === undefined) return;\n let store: VersionMetaStore;\n try {\n store = await openVersionMetaStore(namespace);\n } catch {\n // No durable baseline available → cannot detect a contract change. The\n // bundle signal still works; this is a graceful degrade, not an error.\n return;\n }\n if (disposed) {\n store.close();\n return;\n }\n metaStore = store;\n // Register so a logout wipe clears the last-seen hash too (shared namespace\n // with persistence + outbox).\n unregister = registerDurableResource(namespace, {\n kind: 'persisted-collection',\n clear: () => store.clear(),\n });\n let lastSeen: string | undefined;\n try {\n lastSeen = await store.get(LAST_SEEN_MANIFEST_HASH_KEY);\n } catch {\n lastSeen = undefined;\n }\n if (disposed) return;\n if (lastSeen !== undefined && lastSeen !== runningHash) {\n // The contract changed across this load — signal, then persist the new\n // baseline so a subsequent load does not re-fire for the same change.\n setContract();\n }\n if (lastSeen !== runningHash) {\n try {\n await store.set(LAST_SEEN_MANIFEST_HASH_KEY, runningHash);\n } catch {\n /* best-effort */\n }\n }\n })();\n\n return {\n get: snapshot,\n subscribe(callback) {\n subscribers.add(callback);\n // Fire immediately with the current state (store-like ergonomics).\n try {\n callback(snapshot());\n } catch (error) {\n // biome-ignore lint/suspicious/noConsole: smrt-web has no logger dep (TanStack-only); a throwing update subscriber surfaces via console.warn by design (#1764)\n console.warn('[smrt-web] updateAvailable subscriber threw', error);\n }\n return () => {\n subscribers.delete(callback);\n };\n },\n notifyBundleUpdated: setBundle,\n notifyContractUpdated: setContract,\n ready,\n dispose() {\n disposed = true;\n unregister?.();\n unregister = undefined;\n metaStore?.close();\n metaStore = undefined;\n subscribers.clear();\n },\n };\n}\n","/**\n * WebMCP browser tool registration (#1812, tracer).\n *\n * Registers each collection's generated {@link WebToolDescriptor}s with the\n * browser's WebMCP registry (`document.modelContext.registerTool`) so an\n * in-page AI agent can discover and invoke them — see\n * https://developer.chrome.com/docs/ai/webmcp. Each tool's `execute` runs\n * through the collection's REST fetchers AS THE PAGE'S AUTHENTICATED USER, so\n * the generated REST guards (auth, tenant gate #1554, writable + sensitive-field\n * policy #1540) are the security boundary — nothing is re-implemented here.\n *\n * Framework-agnostic and engine-free: this module imports no UI framework and no\n * client-data engine. `document.modelContext` is a browser global, not a\n * dependency. Reads/writes go through {@link createDefinitionFetchers} (plain\n * fetch), so registering tools never drags the TanStack engine onto a page.\n *\n * TRACER SCOPE: CRUD actions (list/get/create/update/delete) are fully wired;\n * custom actions return a clear \"not wired\" payload. The full slice can route\n * `execute` through the shared-client collection instead of raw fetchers so\n * agent mutations reflect in on-page live collections (cache coherence).\n */\n\nimport {\n createDefinitionFetchers,\n type SmrtCrudFetchers,\n type SmrtWebCollectionDefinition,\n unwrapItemResult,\n unwrapListResult,\n} from './index.js';\n\n/** The subset of Chrome's WebMCP `registerTool` input this tracer emits. */\ninterface WebMcpToolRegistration {\n name: string;\n description: string;\n inputSchema: Record<string, unknown>;\n annotations?: { readOnlyHint?: boolean; untrustedContentHint?: boolean };\n execute: (args: Record<string, unknown>) => Promise<string> | string;\n}\n\n/** The slice of `document.modelContext` this module depends on. */\ninterface ModelContextLike {\n // WebMCP removes a tool via an AbortSignal passed as the second arg, NOT via a\n // returned handle — see https://developer.chrome.com/docs/ai/webmcp/imperative-api.\n registerTool: (\n tool: WebMcpToolRegistration,\n options?: { signal?: AbortSignal },\n ) => void;\n}\n\n/**\n * Feature-detect `document.modelContext`. Returns undefined off-WebMCP (SSR,\n * non-Chrome, or Chrome without the origin trial) so every entry point no-ops.\n */\nfunction getModelContext(): ModelContextLike | undefined {\n const doc = (globalThis as { document?: { modelContext?: unknown } })\n .document;\n const mc = doc?.modelContext;\n if (mc && typeof (mc as ModelContextLike).registerTool === 'function') {\n return mc as ModelContextLike;\n }\n return undefined;\n}\n\nexport interface RegisterWebMcpToolsOptions {\n /** REST base path for the fetchers (default `/api/v1`). */\n basePath?: string;\n /** Injectable fetch (tests / SSR-safe wrappers). */\n fetchFn?: typeof fetch;\n /**\n * Override how a definition's CRUD fetchers are built. Defaults to\n * {@link createDefinitionFetchers}; the primary seam for testing `execute`\n * without a live server.\n */\n resolveFetchers?: (\n definition: SmrtWebCollectionDefinition,\n ) => SmrtCrudFetchers;\n /** Predicate to include/exclude individual tools (e.g. reads-only surfaces). */\n filter?: (\n definition: SmrtWebCollectionDefinition,\n descriptor: NonNullable<\n SmrtWebCollectionDefinition['toolDescriptors']\n >[number],\n ) => boolean;\n}\n\n/**\n * Register every collection's generated tool descriptors with WebMCP.\n *\n * @returns a disposer that deregisters all tools this call registered. On a\n * browser without WebMCP the call is a no-op and the disposer is inert.\n */\nexport function registerWebMcpTools(\n definitions: SmrtWebCollectionDefinition[],\n options: RegisterWebMcpToolsOptions = {},\n): () => void {\n const ctx = getModelContext();\n if (!ctx) return () => {};\n\n const basePath = options.basePath ?? '/api/v1';\n // ONE controller deregisters every tool this call registers: WebMCP removes a\n // tool when the signal it was registered with aborts, so the returned disposer\n // simply aborts. Idempotent — a second call is a harmless no-op.\n const controller = new AbortController();\n\n for (const definition of definitions) {\n const descriptors = definition.toolDescriptors;\n if (!descriptors || descriptors.length === 0) continue;\n\n const fetchers = options.resolveFetchers\n ? options.resolveFetchers(definition)\n : createDefinitionFetchers(definition, basePath, options.fetchFn);\n\n for (const descriptor of descriptors) {\n if (options.filter && !options.filter(definition, descriptor)) continue;\n\n ctx.registerTool(\n {\n name: descriptor.name,\n description: descriptor.description,\n inputSchema: descriptor.inputSchema,\n annotations: { readOnlyHint: descriptor.readOnly },\n execute: (args) =>\n dispatch(fetchers, definition, descriptor.action, args ?? {}),\n },\n { signal: controller.signal },\n );\n }\n }\n\n return () => controller.abort();\n}\n\n/** Require and return a string `id` from tool args, or throw a clear error. */\nfunction requireId(args: Record<string, unknown>, action: string): string {\n const id = args.id;\n if (typeof id !== 'string' || id.length === 0) {\n throw new Error(`WebMCP ${action} requires a string 'id' argument`);\n }\n return id;\n}\n\n/**\n * Resolve a `get` identifier: `id`, or `slug` as a fallback. The generated REST\n * route and `collection.get()` both resolve either, and the get tool schema\n * advertises both, so a slug-only call must work.\n */\nfunction requireIdentifier(args: Record<string, unknown>): string {\n const value = args.id ?? args.slug;\n if (typeof value !== 'string' || value.length === 0) {\n throw new Error(\"WebMCP get requires a string 'id' or 'slug' argument\");\n }\n return value;\n}\n\n/** Narrow the list-tool args to the fetcher's query params. */\nfunction listParams(args: Record<string, unknown>): Record<string, unknown> {\n const params: Record<string, unknown> = {};\n if (args.limit !== undefined) params.limit = args.limit;\n if (args.offset !== undefined) params.offset = args.offset;\n if (args.orderBy !== undefined) params.orderBy = args.orderBy;\n if (args.where !== undefined) params.where = args.where;\n return params;\n}\n\n/**\n * Route a tool call to the collection's REST fetchers and return a STRING —\n * WebMCP's `execute` contract. Reuses the package's existing payload\n * normalization ({@link unwrapListResult} / {@link unwrapItemResult}), so\n * `{ error }` bodies surface as thrown `SmrtWebRequestError`s the agent sees.\n */\nasync function dispatch(\n fetchers: SmrtCrudFetchers,\n definition: SmrtWebCollectionDefinition,\n action: string,\n args: Record<string, unknown>,\n): Promise<string> {\n switch (action) {\n case 'list': {\n const rows = unwrapListResult(\n await fetchers.list(listParams(args)),\n definition.name,\n );\n return JSON.stringify(rows);\n }\n\n case 'get': {\n if (!fetchers.get)\n throw new Error(`${definition.name} has no get action`);\n const row = unwrapItemResult(\n await fetchers.get(requireIdentifier(args)),\n `get(${definition.name})`,\n );\n return JSON.stringify(row);\n }\n\n case 'create': {\n const row = unwrapItemResult(\n await fetchers.create(args),\n `create(${definition.name})`,\n );\n return JSON.stringify(row);\n }\n\n case 'update': {\n if (!fetchers.update) {\n throw new Error(`${definition.name} has no update action`);\n }\n const { id: _id, ...body } = args;\n const row = unwrapItemResult(\n await fetchers.update(requireId(args, 'update'), body),\n `update(${definition.name})`,\n );\n return JSON.stringify(row);\n }\n\n case 'delete': {\n if (!fetchers.delete) {\n throw new Error(`${definition.name} has no delete action`);\n }\n await fetchers.delete(requireId(args, 'delete'));\n return JSON.stringify({ success: true });\n }\n\n default:\n // Custom actions (e.g. invoice_record_payment) hit bespoke REST routes not\n // covered by the CRUD fetchers. Wired in the full slice (#1812).\n return JSON.stringify({\n error: `WebMCP custom action '${action}' is not wired in the tracer`,\n action,\n collection: definition.name,\n });\n }\n}\n","/**\n * @happyvertical/smrt-web — browser client data runtime (#1761).\n *\n * A typed collection factory that materializes the manifest-generated web\n * collection definitions (`@happyvertical/smrt-virt-web`) as cached, reactive\n * collections over the generated SMRT REST surface.\n *\n * This package is the **engine-absorption boundary**: the client-data engine\n * (currently TanStack DB) is an implementation detail held entirely inside\n * this module. Its types never appear on the public API — collections are\n * handed back as the SMRT-owned {@link SmrtWebCollection}, and the shared cache\n * as the opaque {@link SmrtWebClient} — so the engine stays swappable without a\n * consumer-visible break. Consumers never import `@tanstack/*` directly.\n *\n * Framework-agnostic by construction: this entry imports no UI framework.\n * Svelte live-query bindings ship separately (see PRD #1755) so this core never\n * pulls the Svelte-only `@tanstack/svelte-db` export condition.\n *\n * Scope of this slice:\n * - stale-while-revalidate reads (a `staleTimeMs` window, background revalidation)\n * - concurrent-read dedup (one network request per in-flight collection load)\n * - optimistic create that persists through the generated REST surface and\n * rolls back automatically when the server errors\n * - relationship-derived invalidation (#1761): a settled mutation invalidates\n * the caches of the collections related to the mutated one, with the edges\n * derived from the manifest (`definition.relationships`) — no hand-wired\n * cache keys. Cross-collection reach requires a shared client from\n * {@link createSmrtWebClient}; with a private client only the mutated\n * collection refetches.\n * - hydration seeding (#1761): rows fetched server-side (a SvelteKit\n * `+page.server.ts` load) seed the shared cache via\n * {@link CreateSmrtCollectionOptions.initialData}, so the first client read\n * serves them WITHOUT a duplicate first-render fetch.\n *\n * Deliberately NOT here yet (see PRD #1755): offline outbox, SSE invalidation,\n * persistence, version awareness.\n */\n\nimport { createCollection } from '@tanstack/db';\nimport { QueryClient } from '@tanstack/query-core';\nimport { queryCollectionOptions } from '@tanstack/query-db-collection';\n\nimport type {\n SmrtWebCapability,\n SmrtWebCapabilityContext,\n SmrtWebMutationEnvelope,\n} from './capability.js';\nimport { runWrapMutation } from './capability.js';\n\n// Re-export the capability seam (#1755) and the shared durable-store foundation\n// through this single entry — the package ships one export subpath, so both are\n// reachable as `@happyvertical/smrt-web`. Kept in their own modules so each is\n// reviewable and testable on its own; surfaced here for consumers-to-be (the\n// offline outbox #1762, persistence #1764, and live SSE invalidation\n// #1763-client slices).\nexport type {\n MutationSettleOutcome,\n SmrtWebCapability,\n SmrtWebCapabilityContext,\n SmrtWebMutationEnvelope,\n WrapMutationOutcome,\n} from './capability.js';\nexport { runWrapMutation } from './capability.js';\nexport type { DurableResource, DurableStoreKey } from './durable-store.js';\nexport {\n durableStoreNamespace,\n registerDurableResource,\n wipeDurableStore,\n} from './durable-store.js';\n// The durable offline outbox capability (#1762) — the first concrete capability\n// over the seam above. A hand-rolled IndexedDB queue + Web Locks leader election\n// (NOT @tanstack/offline-transactions, which would leak an engine type into the\n// .d.ts and fail the boundary check). Surfaced here as the root barrel; no\n// subpath. The public surface is engine-free by construction.\nexport type {\n OfflineOutboxConfig,\n OutboxBackoff,\n OutboxConflict,\n OutboxHandle,\n OutboxSnapshotItem,\n OutboxSyncState,\n SyncStateEvent,\n} from './offline.js';\nexport { getOutboxHandle, offlineOutbox } from './offline.js';\n// Durable persistence capability (#1764): an opted-in collection warm-starts\n// from a durable IndexedDB snapshot on load, then revalidates in the background;\n// namespace-keyed (api/tenant/identity/manifest hash) so a contract-changing\n// deploy drops old caches and no user reads another's rows. Its own module for\n// the same reason as the seam — reviewable/testable on its own (real\n// fake-indexeddb + real engine). Engine-free public surface.\nexport type { PersistCollectionConfig } from './persistence.js';\nexport {\n DEFAULT_PERSIST_DEBOUNCE_MS,\n persistCollection,\n} from './persistence.js';\n// Live-updates subscriber (#1763-client): the ONE app-wide SSE `_events`\n// subscriber (with `_changes` polling fallback) and the thin per-collection\n// `liveInvalidation` capability that wires a collection's `ctx.invalidate()` to\n// it. Its own module for the same reason as the seam above — reviewable and\n// testable on its own (mock only EventSource + fetch).\nexport type {\n LiveInvalidationConfig,\n SmrtWebEventSource,\n SmrtWebEventSourceFactory,\n SmrtWebEventSubscriber,\n SmrtWebEventSubscriberConfig,\n SmrtWebSubscriberTransport,\n} from './sse-client.js';\nexport {\n createSmrtWebEventSubscriber,\n liveInvalidation,\n} from './sse-client.js';\n// Version awareness (#1764): the framework-free `updateAvailable` primitive with\n// two independent signals — `bundle` (fed by the smrt-svelte binding from\n// SvelteKit's `updated` store) and `contract` (build-time-inject manifest-hash\n// compare across loads). The reactive Svelte adapter ships in smrt-svelte.\nexport type {\n UpdateAvailableState,\n UpdateState,\n UpdateStateConfig,\n} from './update-state.js';\nexport { createUpdateState } from './update-state.js';\n\n// ---------------------------------------------------------------------------\n// Generated definition contract (mirrors @happyvertical/smrt-virt-web)\n// ---------------------------------------------------------------------------\n\n/**\n * The field types core's web-collection emission actually produces —\n * relationship pseudo-columns (`oneToMany`/`manyToMany`) and STI `meta`\n * internals never reach an emitted definition. Textual mirror of core's\n * `WebFieldType` (this package is deliberately smrt-dependency-free, so it\n * cannot import the source type); the four co-managed sites are core's\n * runtime type, the ambient virt-web d.ts, the physical `@smrt/web` d.ts,\n * and this mirror.\n */\nexport type SmrtWebFieldType =\n | 'text'\n | 'decimal'\n | 'boolean'\n | 'integer'\n | 'datetime'\n | 'json'\n | 'foreignKey'\n | 'crossPackageRef';\n\n/** Static `@field({ ui })` hints (#2046) — the field-policy rail seed. */\nexport interface SmrtWebFieldUIHints {\n basic?: boolean;\n group?: string;\n order?: number;\n locked?: boolean;\n}\n\n/**\n * Field metadata emitted per column by the `@happyvertical/smrt-virt-web`\n * virtual module (generated from the package manifest).\n */\nexport interface SmrtWebFieldDefinition {\n type: SmrtWebFieldType;\n required?: boolean;\n default?: unknown;\n /** Developer-authored `@field({ description })` (#2046) — end-user help seed. */\n description?: string;\n /** Static `@field({ ui })` hints (#2046). */\n ui?: SmrtWebFieldUIHints;\n}\n\n/** The relationship kinds a generated web collection edge can describe. */\nexport type SmrtWebRelationshipKind =\n | 'foreignKey'\n | 'crossPackageRef'\n | 'oneToMany'\n | 'manyToMany';\n\n/**\n * A manifest-derived edge from this collection to a sibling REST collection,\n * emitted by the `@happyvertical/smrt-virt-web` virtual module. When a mutation\n * on this collection settles, the caches of the collections named by these\n * edges are invalidated (relationship-derived invalidation, #1761), so a\n * dependent view refetches without any hand-wired cache key.\n *\n * SMRT-owned data — no client-engine (`@tanstack/*`) type appears here, so it\n * stays inside the engine-absorption boundary.\n */\nexport interface SmrtWebRelationship {\n /** The declaring field carrying the relationship (e.g. `groupId`, `items`). */\n field: string;\n /** The relationship kind, mirroring the manifest field type. */\n kind: SmrtWebRelationshipKind;\n /** REST collection name the edge resolves to (e.g. `ad_groups`). */\n relatedCollection: string;\n}\n\n/**\n * One generated collection definition: everything needed to construct a client\n * collection over the generated REST surface. The `_row` property is a phantom\n * type carrier threaded through codegen — it never exists at runtime, it only\n * lets factories infer the row type from a definition.\n */\n/**\n * One WebMCP / MCP tool descriptor for a collection action (#1812). Emitted by\n * the core web-collections codegen as PLAIN DATA (this package has no smrt\n * dependency), shaped to match Chrome's `document.modelContext.registerTool`\n * input — see https://developer.chrome.com/docs/ai/webmcp. Consumed by\n * {@link registerWebMcpTools} in `./webmcp`.\n */\nexport interface WebToolDescriptor {\n /** The action this tool performs (`list` | `get` | … | a custom method name). */\n action: string;\n /** Tool id, `${className.toLowerCase()}_${action}` (e.g. `product_list`). */\n name: string;\n description: string;\n /** JSON Schema for the tool's arguments. */\n inputSchema: Record<string, unknown>;\n /** True for non-mutating reads → WebMCP `annotations.readOnlyHint`. */\n readOnly: boolean;\n}\n\nexport interface SmrtWebCollectionDefinition<TData extends object = object> {\n /** REST collection name (e.g. `products`). */\n name: string;\n /** Source class name (e.g. `Product`). */\n className: string;\n /** Path under the API base path (e.g. `/products`). */\n endpoint: string;\n /** Primary key field name (`id` for SmrtObject). */\n idField: string;\n /** CRUD + custom actions exposed by the api decorator config. */\n actions: string[];\n /**\n * WebMCP/MCP tool descriptors for the exposed actions (#1812). Optional so\n * hand-built definitions (older codegen, tests) still satisfy the type; a\n * missing value means \"no WebMCP tools to register\".\n */\n toolDescriptors?: WebToolDescriptor[];\n /** Persisted field metadata keyed by field name. */\n fields: Record<string, SmrtWebFieldDefinition>;\n /**\n * Manifest-derived relationship edges to sibling REST collections. Drives\n * relationship-derived cache invalidation: a settled mutation on this\n * collection invalidates the caches of the collections these edges name.\n * Optional so hand-built definitions (older codegen, tests) still satisfy the\n * type; a missing value means \"no derived edges\".\n */\n relationships?: SmrtWebRelationship[];\n /** Phantom row-type carrier — never present at runtime. */\n _row?: TData;\n}\n\n// ---------------------------------------------------------------------------\n// Fetcher contract + payload normalization\n// ---------------------------------------------------------------------------\n\n/**\n * The per-collection CRUD surface of the generated REST client\n * (`createClient(basePath).<collection>` from `@happyvertical/smrt-virt-client`).\n *\n * Return types are `unknown` on purpose: generated fetchers resolve with\n * whatever the server sent, so this package normalizes and validates payloads\n * centrally — see {@link unwrapListResult} / {@link unwrapItemResult}.\n */\nexport interface SmrtCrudFetchers {\n list(params?: Record<string, unknown>): Promise<unknown>;\n get?(id: string): Promise<unknown>;\n create(data: Record<string, unknown>): Promise<unknown>;\n update?(id: string, data: Record<string, unknown>): Promise<unknown>;\n delete?(id: string): Promise<unknown>;\n}\n\n/**\n * Raised when a generated-client call resolved with an error payload\n * (`{ error: string }` from the generated REST routes) or an unexpected shape.\n * Thrown inside a mutation handler, this triggers the automatic rollback of\n * optimistic state.\n */\nexport class SmrtWebRequestError extends Error {\n readonly payload: unknown;\n\n constructor(message: string, payload?: unknown) {\n super(message);\n this.name = 'SmrtWebRequestError';\n this.payload = payload;\n }\n}\n\n/** A row as stored in the client collection: the DTO plus a required key. */\nexport type SmrtWebRow<TData extends object> = TData & { id: string };\n\n/**\n * Normalize a generated-client list result to an array of rows.\n *\n * The generated REST routes return a bare JSON array; `{ error }` payloads are\n * surfaced as failures. The `{ data: [...] }` envelope is tolerated for\n * ApiResponse-shaped clients (e.g. a mock client).\n */\nexport function unwrapListResult(\n result: unknown,\n collectionName: string,\n): Array<Record<string, unknown>> {\n if (Array.isArray(result)) {\n return result as Array<Record<string, unknown>>;\n }\n if (result && typeof result === 'object') {\n const record = result as Record<string, unknown>;\n if (typeof record.error === 'string') {\n throw new SmrtWebRequestError(\n `[smrt-web] list(${collectionName}) failed: ${record.error}`,\n result,\n );\n }\n if (Array.isArray(record.data)) {\n return record.data as Array<Record<string, unknown>>;\n }\n }\n throw new SmrtWebRequestError(\n `[smrt-web] list(${collectionName}) returned an unexpected payload shape`,\n result,\n );\n}\n\n/**\n * Normalize a generated-client item result (create/update) to a row.\n * `{ error }` payloads become failures — inside mutation handlers this is what\n * makes optimistic state roll back.\n */\nexport function unwrapItemResult(\n result: unknown,\n context: string,\n): Record<string, unknown> {\n if (result && typeof result === 'object' && !Array.isArray(result)) {\n const record = result as Record<string, unknown>;\n if (typeof record.error === 'string') {\n throw new SmrtWebRequestError(\n `[smrt-web] ${context} failed: ${record.error}`,\n result,\n );\n }\n if (\n record.data &&\n typeof record.data === 'object' &&\n !Array.isArray(record.data)\n ) {\n return record.data as Record<string, unknown>;\n }\n return record;\n }\n throw new SmrtWebRequestError(\n `[smrt-web] ${context} returned an unexpected payload shape`,\n result,\n );\n}\n\n/** SMRT WHERE operator → the generated REST route's `field[op]=value` token. */\nconst SMRT_TO_REST_OPERATOR: Record<string, string> = {\n '>': 'gt',\n '>=': 'gte',\n '<': 'lt',\n '<=': 'lte',\n '!=': 'ne',\n in: 'in',\n like: 'like',\n};\n\n/**\n * Serialize `list` query params into the query string the generated REST list\n * route parses (`handleList` in `core/src/generators/rest.ts`): `limit`,\n * `offset`, `orderBy` as scalars, and `where` entries as `field=value`\n * (equality) or `field[op]=value` for a `{ op, value }` condition (`in` joins an\n * array with commas). Called with no params (the collection runtime's argument-\n * free `list()`) it returns `''`, so the bare-URL behavior is unchanged.\n */\nexport function buildListQuery(params?: Record<string, unknown>): string {\n if (!params) return '';\n const search = new URLSearchParams();\n const { limit, offset, orderBy, where } = params;\n if (limit !== undefined) search.set('limit', String(limit));\n if (offset !== undefined) search.set('offset', String(offset));\n if (orderBy !== undefined) {\n search.set(\n 'orderBy',\n Array.isArray(orderBy) ? orderBy.join(', ') : String(orderBy),\n );\n }\n if (where && typeof where === 'object') {\n for (const [field, condition] of Object.entries(\n where as Record<string, unknown>,\n )) {\n if (\n condition &&\n typeof condition === 'object' &&\n !Array.isArray(condition) &&\n 'op' in condition &&\n 'value' in condition\n ) {\n const { op, value } = condition as { op: string; value: unknown };\n const restOp = SMRT_TO_REST_OPERATOR[op];\n const token = Array.isArray(value) ? value.join(',') : String(value);\n search.set(restOp ? `${field}[${restOp}]` : field, token);\n } else if (condition !== undefined && condition !== null) {\n search.set(field, String(condition));\n }\n }\n }\n const qs = search.toString();\n return qs ? `?${qs}` : '';\n}\n\n/**\n * Build CRUD fetchers from a generated collection definition — the same URL\n * scheme and payload handling as the generated REST client\n * (`basePath + endpoint`), with one improvement: HTTP error statuses reject\n * with the server's `{ error }` body instead of resolving with it.\n */\nexport function createDefinitionFetchers(\n definition: SmrtWebCollectionDefinition<object>,\n basePath = '/api/v1',\n fetchFn: typeof fetch = (...args) => globalThis.fetch(...args),\n): SmrtCrudFetchers {\n const collectionUrl = `${basePath}${definition.endpoint}`;\n const headers = { 'Content-Type': 'application/json' };\n\n const parse = async (response: Response): Promise<unknown> => {\n const payload: unknown = await response.json().catch(() => null);\n if (!response.ok) {\n const message =\n payload &&\n typeof payload === 'object' &&\n typeof (payload as Record<string, unknown>).error === 'string'\n ? String((payload as Record<string, unknown>).error)\n : `HTTP ${response.status}`;\n throw new SmrtWebRequestError(\n `[smrt-web] ${definition.name} request failed: ${message}`,\n payload,\n );\n }\n return payload;\n };\n\n return {\n list: async (params) =>\n parse(\n await fetchFn(`${collectionUrl}${buildListQuery(params)}`, { headers }),\n ),\n get: async (id) =>\n parse(await fetchFn(`${collectionUrl}/${id}`, { headers })),\n create: async (data) =>\n parse(\n await fetchFn(collectionUrl, {\n method: 'POST',\n headers,\n body: JSON.stringify(data),\n }),\n ),\n update: async (id, data) =>\n parse(\n await fetchFn(`${collectionUrl}/${id}`, {\n method: 'PUT',\n headers,\n body: JSON.stringify(data),\n }),\n ),\n delete: async (id) => {\n const response = await fetchFn(`${collectionUrl}/${id}`, {\n method: 'DELETE',\n headers,\n });\n if (!response.ok) {\n throw new SmrtWebRequestError(\n `[smrt-web] delete(${definition.name}) failed: HTTP ${response.status}`,\n );\n }\n return true;\n },\n };\n}\n\n/**\n * Generate a client-local id for optimistic inserts. The generated REST layer\n * strips client-supplied ids on create (mass-assignment guard #1540), so this\n * id only identifies the optimistic row until the post-persist refetch swaps in\n * the server-assigned row.\n */\nexport function newLocalId(): string {\n const cryptoRef = globalThis.crypto as Crypto | undefined;\n if (cryptoRef?.randomUUID) {\n return cryptoRef.randomUUID();\n }\n return `local-${Date.now()}-${Math.random().toString(36).slice(2)}`;\n}\n\n// ---------------------------------------------------------------------------\n// Engine-absorbing public surface (no @tanstack/* types leak past here)\n// ---------------------------------------------------------------------------\n\n/**\n * Opaque handle to the shared client cache / request-dedup layer. Create one\n * with {@link createSmrtWebClient} and pass the SAME instance to every\n * collection that should share a cache and deduplicate in-flight requests.\n *\n * The engine (currently a TanStack Query client) is intentionally hidden behind\n * this brand so it stays swappable — do not depend on its concrete shape.\n */\nexport interface SmrtWebClient {\n /** Phantom brand — this handle wraps the hidden client-cache engine. */\n readonly __smrtWebClient: 'SmrtWebClient';\n}\n\n/**\n * Engine-side shape of a {@link SmrtWebClient}. Never exported, so the engine\n * type never reaches the public surface. Extends the public brand so the value\n * created here carries the brand at runtime (enabling the validation below).\n */\ninterface SmrtWebClientEngine extends SmrtWebClient {\n readonly queryClient: QueryClient;\n}\n\n/**\n * Create a shared client-cache handle. Pass the returned handle as\n * {@link CreateSmrtCollectionOptions.client} to every collection that should\n * share a cache and deduplicate requests app-wide.\n */\nexport function createSmrtWebClient(): SmrtWebClient {\n const engine: SmrtWebClientEngine = {\n __smrtWebClient: 'SmrtWebClient',\n queryClient: new QueryClient(),\n };\n return engine;\n}\n\nfunction resolveQueryClient(client?: SmrtWebClient): QueryClient {\n if (!client) return new QueryClient();\n const engine = client as Partial<SmrtWebClientEngine>;\n if (engine.__smrtWebClient !== 'SmrtWebClient' || !engine.queryClient) {\n throw new SmrtWebRequestError(\n '[smrt-web] options.client must be a handle from createSmrtWebClient()',\n );\n }\n return engine.queryClient;\n}\n\n/**\n * Project an engine row to a plain public DTO. The client-data engine decorates\n * stored rows with enumerable virtual props (`$synced`/`$origin`/`$key`/\n * `$collectionId`) that would otherwise cross the SMRT boundary through spread\n * or JSON serialization. The `$` prefix is reserved for the engine; SMRT\n * columns never begin with it.\n */\nfunction toPlainRow<TData extends object>(row: unknown): SmrtWebRow<TData> {\n const plain: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(row as Record<string, unknown>)) {\n if (key.charCodeAt(0) !== 36 /* '$' */) plain[key] = value;\n }\n return plain as SmrtWebRow<TData>;\n}\n\n/** Project the row values carried by a change notification to plain DTOs. */\nfunction projectChanges(changes: unknown): unknown {\n if (!Array.isArray(changes)) return changes;\n return changes.map((change) => {\n if (!change || typeof change !== 'object') return change;\n const record = change as Record<string, unknown>;\n const projected: Record<string, unknown> = { ...record };\n if (record.value && typeof record.value === 'object') {\n projected.value = toPlainRow(record.value);\n }\n if (record.previousValue && typeof record.previousValue === 'object') {\n projected.previousValue = toPlainRow(record.previousValue);\n }\n return projected;\n });\n}\n\n/**\n * A pending optimistic mutation. Await {@link isPersisted} to observe the\n * server outcome: it resolves once the write has been persisted through the\n * REST surface, and rejects (rolling the optimistic state back) on error.\n */\nexport interface SmrtWebTransaction {\n readonly isPersisted: { readonly promise: Promise<unknown> };\n}\n\n/** A change-subscription handle. Call {@link unsubscribe} to detach. */\nexport interface SmrtWebSubscription {\n unsubscribe(): void;\n}\n\n/**\n * A live, cached collection of plain-DTO rows — the SMRT-owned public contract\n * over the client-data engine. Exposes only the committed surface; the engine's\n * own type is never named here so it stays swappable.\n */\nexport interface SmrtWebCollection<TData extends object> {\n /** All rows currently in the collection (plain DTOs, insertion order). */\n readonly toArray: ReadonlyArray<SmrtWebRow<TData>>;\n /** Number of rows currently in the collection. */\n readonly size: number;\n /** True when a row with `key` is present. */\n has(key: string): boolean;\n /** The row with `key`, or `undefined`. */\n get(key: string): SmrtWebRow<TData> | undefined;\n /** Resolve once the first load has completed. */\n preload(): Promise<void>;\n /** Tear down subscriptions and cached state. */\n cleanup(): Promise<void>;\n /** Subscribe to change notifications; returns a detach handle. */\n subscribeChanges(callback: (changes: unknown) => void): SmrtWebSubscription;\n /**\n * Optimistically insert a row and persist it through the create fetcher. The\n * row is visible synchronously; the returned transaction settles on the\n * server outcome (see {@link SmrtWebTransaction}).\n */\n insert(row: SmrtWebRow<TData>): SmrtWebTransaction;\n}\n\n/** Extract a row's server revision timestamp for sync/apply conflict guards. */\nfunction getBaseUpdatedAt(row: unknown): string | undefined {\n if (!row || typeof row !== 'object') return undefined;\n const record = row as Record<string, unknown>;\n const value = record.updatedAt ?? record.updated_at;\n if (typeof value === 'string') return value;\n if (value instanceof Date) return value.toISOString();\n return undefined;\n}\n\n/**\n * Options for {@link createSmrtCollection}. Generic in the collection's row\n * type `TData` so {@link initialData} is checked against the same DTO the\n * collection stores; every other option is row-type-agnostic, so the parameter\n * defaults to `object` and can be omitted at call sites that pass no seed.\n */\nexport interface CreateSmrtCollectionOptions<TData extends object = object> {\n /**\n * Generated REST client surface for this collection, e.g.\n * `createClient('/api/v1').products` from the virt-client module. When\n * omitted, fetchers are derived from the definition's endpoint and `basePath`\n * with the same URL scheme and payload shapes the generated client uses.\n */\n fetchers?: SmrtCrudFetchers;\n /** API base path for definition-derived fetchers (default `/api/v1`). */\n basePath?: string;\n /** Fetch implementation override (tests, SSR). Defaults to global fetch. */\n fetchFn?: typeof fetch;\n /**\n * Shared cache handle from {@link createSmrtWebClient}. Pass one app-wide\n * instance so collections share a cache and deduplicate requests; a private\n * cache is created when omitted.\n */\n client?: SmrtWebClient;\n /**\n * Cache namespace for this collection's reads. Fold a backend / tenant /\n * preview discriminator in here when the SAME generated collection is\n * materialized against DIFFERENT backends while sharing one {@link client} —\n * without it those reads share a cache key and could serve one backend's rows\n * for the other for the whole `staleTimeMs` window. Omit for the common\n * single-backend case.\n */\n scope?: string;\n /**\n * Stale-while-revalidate window in milliseconds (default 30s): reads within\n * the window are served from the local collection without a network request;\n * the first read after it revalidates in the background.\n */\n staleTimeMs?: number;\n /** Retry failed loads (default false: fail fast, surface errors). */\n retry?: boolean;\n /**\n * Rows to seed this collection's cache with, before its first read — the\n * hydration path for server-rendered data (#1761). Fetch rows in a SvelteKit\n * `+page.server.ts` load, pass them here on the client, and the first read\n * serves them from cache WITHOUT a duplicate first-render network request\n * (SMRT-owned type, so no engine type appears on the option).\n *\n * The seed is written to the cache with a fresh timestamp, so it counts as\n * fresh for `staleTimeMs`: with the default window the first read does not\n * fetch, and the collection revalidates in the background only once the window\n * elapses (or immediately if `staleTimeMs` is 0). Seed the SAME rows the\n * server serialized so the pre- and post-hydration renders match.\n *\n * Seeds the SAME cache key the reads use — so with a shared {@link client},\n * fold the backend / tenant discriminator into {@link scope} to match, exactly\n * as reads do; otherwise one backend's seed would serve the other for the\n * `staleTimeMs` window.\n */\n initialData?: SmrtWebRow<TData>[];\n /**\n * Capability plug-ins hooking the collection lifecycle (#1755) — the seam\n * that lets the offline outbox (#1762), persistence (#1764), and live SSE\n * invalidation (#1763-client) slices each live in their own module instead of\n * contending on this factory. Capabilities run in array order at six fixed\n * points (see {@link SmrtWebCapability}). Additive and defaulting to none: an\n * undefined or empty array is byte-for-byte the collection of today (the\n * no-op guarantee), so this ships zero concrete capabilities by design.\n */\n capabilities?: SmrtWebCapability<TData>[];\n}\n\n/**\n * Registry mapping a public collection handle to its underlying engine\n * collection. Keyed weakly so a handle and its engine collection are collected\n * together. Read only through {@link getEngineCollection}.\n */\nconst engineCollections = new WeakMap<object, unknown>();\n\n/**\n * Retrieve the underlying engine collection backing a handle — an advanced\n * bridge for trusted framework bindings (e.g. the smrt-svelte live-query\n * binding), which must feed the engine collection to the query builder. Returns\n * `unknown` so no engine type crosses the boundary; callers cast. Throws for a\n * handle not produced by {@link createSmrtCollection}. Not needed for normal\n * use.\n */\nexport function getEngineCollection<TData extends object>(\n handle: SmrtWebCollection<TData>,\n): unknown {\n const engine = engineCollections.get(handle);\n if (engine === undefined) {\n throw new SmrtWebRequestError(\n '[smrt-web] getEngineCollection: not a smrt-web collection handle',\n );\n }\n return engine;\n}\n\n/**\n * Report a capability hook that threw or rejected, without letting it break the\n * collection (#1755). Capability hooks are third-party plug-ins; one misbehaving\n * hook must not reject a successful mutation, abort construction, or wedge\n * `cleanup()`. smrt-web has no logger dependency (TanStack-only), so this uses\n * `console.warn` — a swallowed diagnostic, matching the package's fail-loud-in-\n * the-console style.\n */\nfunction warnCapability(\n capability: { name: string },\n hook: string,\n error: unknown,\n): void {\n // biome-ignore lint/suspicious/noConsole: smrt-web has no logger dep (TanStack-only); a swallowed capability fault is surfaced via console.warn by design (#1755)\n console.warn(\n `[smrt-web] capability \"${capability.name}\" ${hook} threw; ignoring`,\n error,\n );\n}\n\n/**\n * Create a typed client collection over a generated SMRT collection definition\n * and the matching generated REST client fetchers.\n *\n * Reads: stale-while-revalidate. The first subscriber triggers a fetch;\n * re-subscribing within `staleTimeMs` serves local data with no request. N\n * concurrent identical reads coalesce into one network request.\n *\n * Writes: `collection.insert({ ...data, id: newLocalId() })` applies instantly,\n * persists through `fetchers.create()` (the temp id is stripped — the server\n * assigns the real one), then refetches to reconcile. A failed create rejects\n * the transaction and the optimistic row rolls back automatically.\n *\n * Relationship-derived invalidation: once a create/update/delete has persisted,\n * the query caches of this collection AND the collections named by\n * `definition.relationships` (manifest-derived edges) are invalidated, so\n * dependent views refetch. Reaching OTHER collections requires them to share\n * this collection's `client` (see {@link createSmrtWebClient}); with a private\n * client only this collection refetches.\n *\n * Hydration seeding: pass rows fetched server-side as\n * {@link CreateSmrtCollectionOptions.initialData} and the collection's first\n * read is served from them with NO network request (until `staleTimeMs`\n * elapses) — the SvelteKit `+page.server.ts` → hydrate path.\n */\nexport function createSmrtCollection<TData extends object>(\n definition: SmrtWebCollectionDefinition<TData>,\n options: CreateSmrtCollectionOptions<TData>,\n): SmrtWebCollection<TData> {\n type Row = SmrtWebRow<TData>;\n\n const { staleTimeMs = 30_000, retry = false, scope, initialData } = options;\n const capabilities = options.capabilities ?? [];\n const fetchers =\n options.fetchers ??\n createDefinitionFetchers(definition, options.basePath, options.fetchFn);\n const queryClient = resolveQueryClient(options.client);\n const idField = definition.idField || 'id';\n\n // Scope discriminates the cache key so a shared client can materialize the\n // same collection against different backends without cross-serving reads.\n // `let` so capabilities can extend the scheme via `contributeCacheKey` below;\n // with no capabilities these stay exactly the base `smrt:(scope:)name` form.\n let cacheId = scope\n ? `smrt:${scope}:${definition.name}`\n : `smrt:${definition.name}`;\n let queryKey = scope\n ? ['smrt', scope, definition.name]\n : ['smrt', definition.name];\n\n // Relationship-derived invalidation target set (#1761): the collections\n // whose caches a settled mutation on THIS collection must invalidate. Always\n // includes this collection itself (so its own read revalidates) plus every\n // manifest-derived related collection. Built once; a settled write matches\n // any cached query whose collection-name segment (the LAST queryKey element,\n // mirroring the `['smrt', (scope,) name]` scheme above) is in this set.\n //\n // Over-invalidation is safe — a stale query merely refetches. Under-\n // invalidation is the bug (a dependent view showing stale rows), so the\n // predicate matches by collection name across ALL scopes rather than an exact\n // key: a mutation in one scope refreshes the related collection in every\n // scope sharing the client.\n const invalidationTargets = new Set<string>([definition.name]);\n for (const relationship of definition.relationships ?? []) {\n invalidationTargets.add(relationship.relatedCollection);\n }\n\n /**\n * Invalidate the query caches of this collection and its manifest-derived\n * related collections. Cross-collection reach requires those collections to\n * share this collection's `client` (from {@link createSmrtWebClient}); with a\n * private client only THIS collection's query lives here, so only it\n * refetches. Fire-and-forget: invalidation schedules a background refetch and\n * must not delay the mutation's own settle.\n */\n const invalidateRelated = (): void => {\n void queryClient.invalidateQueries({\n predicate: (query) => {\n const key = query.queryKey;\n if (!Array.isArray(key) || key.length === 0) return false;\n const collectionSegment = key[key.length - 1];\n return (\n typeof collectionSegment === 'string' &&\n invalidationTargets.has(collectionSegment)\n );\n },\n });\n };\n\n // The engine-free context every capability hook receives (#1755). `cacheKey`\n // / `cacheId` are getter-backed over the live `let` bindings, so a capability\n // observes the key AS IT STANDS when it reads: during `contributeCacheKey`\n // (below) that is the base key plus any EARLIER capability's segments (not its\n // own, not-yet-applied one); from `onAttach`/`warmStart` onward it is the\n // FINAL key. `invalidate` enters the same `invalidateRelated()` the factory\n // runs post-mutation, so a capability can refetch off an external trigger.\n const ctx: SmrtWebCapabilityContext<TData> = {\n definition,\n fetchers,\n get cacheKey() {\n return queryKey;\n },\n get cacheId() {\n return cacheId;\n },\n invalidate: () => invalidateRelated(),\n // Engine-free read + subscribe surface for capabilities (#1764 persistence\n // write-back). Both close over the engine `collection` created below and are\n // only ever called from `onAttach` onward (after it exists), so referencing\n // it here is safe. They mirror the public handle's `toArray` /\n // `subscribeChanges` exactly — projecting to plain DTOs so the engine's\n // virtual props never cross the seam.\n snapshot: () => collection.toArray.map((row) => toPlainRow<TData>(row)),\n subscribe: (callback) => {\n const subscription = collection.subscribeChanges((changes: unknown) =>\n callback(projectChanges(changes)),\n );\n return { unsubscribe: () => subscription.unsubscribe() };\n },\n };\n\n // contributeCacheKey (#1755): fold each capability's returned segments into\n // the cache key/id, extending the base scope-based scheme. Runs ONCE, before\n // construction. Isolated per capability: a throwing contributeCacheKey is\n // logged and skipped rather than aborting construction. With no capabilities\n // this loop is empty and the keys are untouched — the no-op path.\n //\n // CRITICAL — segments are spliced in JUST BEFORE the collection name, so the\n // name stays the LAST queryKey element. `invalidateRelated()`'s predicate\n // (and thus relationship-derived invalidation #1761, and a capability's own\n // `ctx.invalidate()`) identifies a collection by `key[key.length - 1]`;\n // appending segments after the name would hide it from that predicate and\n // silently break invalidation for any collection using contributeCacheKey\n // (exactly the #1764 persistence slice). `cacheId` is an opaque engine id the\n // predicate never reads, so it can keep appending.\n for (const capability of capabilities) {\n let extra: string[] | undefined;\n try {\n extra = capability.contributeCacheKey?.(ctx);\n } catch (error) {\n warnCapability(capability, 'contributeCacheKey', error);\n }\n if (extra && extra.length > 0) {\n // Rebuild as [prefix..., ...extra, name] — name remains last.\n const name = queryKey[queryKey.length - 1];\n const prefix = queryKey.slice(0, -1);\n queryKey = [...prefix, ...extra, name];\n cacheId = `${cacheId}:${extra.join(':')}`;\n }\n }\n\n // Seed the query cache BEFORE the collection's engine starts its sync, so the\n // first read populates from the seed instead of fetching (verified: zero\n // list() calls). `setQueryData` stamps a fresh `dataUpdatedAt`, so the seed\n // counts as fresh for `staleTime` — the first read serves it with no request,\n // and revalidation fires only once `staleTimeMs` elapses (or immediately when\n // it is 0). An explicit empty seed is honored too: it means \"zero rows\", a\n // valid fresh state that likewise suppresses the first fetch.\n //\n // Two seed sources with a fixed precedence:\n // 1. `initialData` — hydration seeding (#1761): rows a SvelteKit\n // `+page.server.ts` load serialized, the fresher same-request SSR truth.\n // 2. a capability `warmStart` (#1755) — e.g. the persistence slice\n // rehydrating from disk. Only the FIRST capability that returns rows\n // contributes.\n // `initialData` WINS: it is resolved first, and `warmStart` is only consulted\n // when `initialData` is undefined. With no capabilities, step 2 never runs and\n // this is byte-identical to the #1761 seed — the no-op path.\n //\n // Seed via the ATOMIC updater form: a plain get-then-set would let two\n // collections sharing this key and materialized in the same tick both observe\n // `undefined` and have the later seed clobber the earlier one.\n // `(existing) => existing ?? seed` keeps the first seed (or any already-cached\n // rows, which may be newer than this late payload) in a single cache write.\n const seedCache = (rows: Row[]): void => {\n queryClient.setQueryData<Row[]>(queryKey, (existing) => existing ?? rows);\n };\n // Set by cleanup(); guards the async-warmStart continuations (#1755) so a\n // collection torn down while a rehydrate is still in flight neither seeds the\n // SHARED query cache after teardown (which could suppress the next collection's\n // real fetch on the same client/key) nor preloads the dead engine.\n let disposed = false;\n // Resolve a warmStart result to rows, isolating a sync throw / async reject\n // (logged, treated as \"no rows\") so a misbehaving provider can neither abort\n // construction nor leak an unhandled rejection.\n const warmRowsFrom = async (\n capability: SmrtWebCapability<TData>,\n warm: Promise<Row[] | undefined> | Row[] | undefined,\n ): Promise<Row[] | undefined> => {\n try {\n return await warm;\n } catch (error) {\n warnCapability(capability, 'warmStart', error);\n return undefined;\n }\n };\n\n // A pending async `warmStart` (persistence rehydrating from OPFS/IndexedDB).\n // Construction stays synchronous, but `preload()` (below) AWAITS this before\n // starting the engine's own load, so an async rehydrate reliably suppresses\n // the first `list()` on the preload path. A subscribe-driven read that races\n // an unresolved warmStart may still fetch once — bounded, and it self-heals\n // via the atomic seed updater. Undefined when there is no async warmStart.\n let warmStartPending: Promise<void> | undefined;\n if (initialData !== undefined) {\n seedCache(initialData);\n } else {\n // Honor \"the FIRST capability that returns ROWS\" across BOTH sync and async\n // warmStart. A sync provider that returns rows seeds inline immediately (so\n // it suppresses even a non-preload read). The FIRST provider that returns a\n // Promise hands off to an ORDERED async chain covering itself and every\n // LATER capability, in array order: it awaits each, skips a result that is\n // undefined or throws/rejects, and seeds the first that yields rows. So an\n // async miss or reject no longer blocks a later provider from seeding.\n for (let i = 0; i < capabilities.length; i += 1) {\n const capability = capabilities[i];\n let warm: Promise<Row[] | undefined> | Row[] | undefined;\n try {\n warm = capability.warmStart?.(ctx);\n } catch (error) {\n // A synchronous warmStart throw must not abort construction.\n warnCapability(capability, 'warmStart', error);\n continue;\n }\n if (warm === undefined) continue;\n if (warm instanceof Promise) {\n const firstPromise = warm;\n warmStartPending = (async () => {\n // This capability first (its promise is already in flight), then each\n // later capability in order until one yields rows.\n let rows = await warmRowsFrom(capability, firstPromise);\n for (\n let j = i + 1;\n rows === undefined && j < capabilities.length;\n j += 1\n ) {\n const later = capabilities[j];\n let laterWarm: Promise<Row[] | undefined> | Row[] | undefined;\n try {\n laterWarm = later.warmStart?.(ctx);\n } catch (error) {\n warnCapability(later, 'warmStart', error);\n continue;\n }\n if (laterWarm === undefined) continue;\n rows = await warmRowsFrom(later, laterWarm);\n }\n // Skip the seed if the collection was cleaned up while the rehydrate\n // was in flight — a late write to the shared cache could otherwise\n // suppress the NEXT collection's real fetch on the same client/key.\n if (rows !== undefined && !disposed) seedCache(rows);\n })();\n break;\n }\n seedCache(warm);\n break;\n }\n }\n\n // Notify every capability that a mutation settled (#1755) — on BOTH a\n // successful persist and a fetcher throw. Per-capability try/catch so this\n // genuinely NEVER throws: a throwing onSettled must not reject a SUCCESSFUL\n // mutation nor mask a fetcher error, and one bad capability must not stop the\n // others being notified. With no capabilities this loop is empty — the no-op\n // path.\n const notifySettled = (\n envelope: SmrtWebMutationEnvelope,\n outcome: { ok: true; result: unknown } | { ok: false; error: unknown },\n ): void => {\n for (const capability of capabilities) {\n try {\n capability.onSettled?.(envelope, outcome, ctx);\n } catch (error) {\n warnCapability(capability, 'onSettled', error);\n }\n }\n };\n\n /**\n * Persist one mutation through the capability seam then the real fetcher.\n * First offers the write to `wrapMutation` (array order, first-handled-wins):\n * a handling capability's result stands in for the fetcher (the offline\n * path), otherwise `runFetcher` performs the real write. On success notifies\n * `onSettled({ ok: true })`; on ANY throw notifies `onSettled({ ok: false })`\n * and RE-THROWS so the optimistic state still rolls back.\n *\n * Returns `handled` so the caller can suppress the engine's post-mutation\n * refetch for an offline write (#1762): a handled write never hit the server,\n * so refetching the server list would DROP the optimistic row the outbox must\n * keep until it replays. With no capabilities `runWrapMutation` returns\n * `{ handled: false }` and both notify loops are empty, so this reduces to\n * `await runFetcher()` with `handled: false` — behavior identical to before\n * the seam.\n */\n const persistMutation = async (\n envelope: SmrtWebMutationEnvelope,\n runFetcher: () => Promise<unknown>,\n ): Promise<{ handled: boolean; result: unknown }> => {\n try {\n const wrapped = await runWrapMutation(capabilities, envelope, ctx);\n const result = wrapped.handled ? wrapped.result : await runFetcher();\n notifySettled(envelope, { ok: true, result });\n return { handled: wrapped.handled, result };\n } catch (error) {\n notifySettled(envelope, { ok: false, error });\n throw error;\n }\n };\n\n const collection = createCollection(\n queryCollectionOptions<Row>({\n id: cacheId,\n queryKey,\n queryClient,\n staleTime: staleTimeMs,\n retry,\n queryFn: async () =>\n unwrapListResult(await fetchers.list(), definition.name) as Array<Row>,\n getKey: (row) => String((row as Record<string, unknown>)[idField]),\n onInsert: async ({ transaction }) => {\n let anyHandled = false;\n for (const mutation of transaction.mutations) {\n const modified = mutation.modified as Record<string, unknown>;\n // Offer the write to the capability seam first (#1755), then fall\n // through to the real create fetcher. The envelope carries the FULL\n // optimistic row; the fetcher closure strips the client-local id: the\n // generated REST layer rejects or ignores client-supplied ids on\n // create (#1540), and the follow-up refetch swaps the optimistic row\n // for the server-assigned one.\n const envelope: SmrtWebMutationEnvelope = {\n kind: 'insert',\n key: String(modified[idField]),\n data: modified,\n };\n const outcome = await persistMutation(envelope, async () => {\n const { [idField]: _localId, ...data } = modified;\n return unwrapItemResult(\n await fetchers.create(data),\n `create(${definition.name})`,\n );\n });\n anyHandled = anyHandled || outcome.handled;\n }\n // If a capability handled the write offline (#1762) it never reached the\n // server, so BOTH the engine's own post-mutation refetch (suppressed via\n // `{ refetch: false }`) and `invalidateRelated()` are skipped — else the\n // server list, which lacks the offline row, would drop the optimistic\n // row the outbox must keep until it replays. A mixed batch (some\n // handled, some not) is conservative: if ANY mutation was handled we\n // suppress, so an unsent row is never dropped; the common case is one\n // mutation per transaction. An UNHANDLED batch behaves exactly as\n // before: refetch runs and related caches invalidate.\n if (anyHandled) return { refetch: false };\n invalidateRelated();\n },\n onUpdate: fetchers.update\n ? async ({ transaction }) => {\n let anyHandled = false;\n for (const mutation of transaction.mutations) {\n const key = String(mutation.key);\n const changes = mutation.changes as Record<string, unknown>;\n const envelope: SmrtWebMutationEnvelope = {\n kind: 'update',\n key,\n data: changes,\n baseUpdatedAt: getBaseUpdatedAt(mutation.original),\n };\n const outcome = await persistMutation(envelope, async () =>\n unwrapItemResult(\n // biome-ignore lint/style/noNonNullAssertion: guarded by the surrounding ternary\n await fetchers.update!(key, changes),\n `update(${definition.name})`,\n ),\n );\n anyHandled = anyHandled || outcome.handled;\n }\n if (anyHandled) return { refetch: false };\n invalidateRelated();\n }\n : undefined,\n onDelete: fetchers.delete\n ? async ({ transaction }) => {\n let anyHandled = false;\n for (const mutation of transaction.mutations) {\n const key = String(mutation.key);\n const envelope: SmrtWebMutationEnvelope = {\n kind: 'delete',\n key,\n data: {},\n baseUpdatedAt: getBaseUpdatedAt(mutation.original),\n };\n const outcome = await persistMutation(envelope, async () =>\n // biome-ignore lint/style/noNonNullAssertion: guarded by the surrounding ternary\n fetchers.delete!(key),\n );\n anyHandled = anyHandled || outcome.handled;\n }\n if (anyHandled) return { refetch: false };\n invalidateRelated();\n }\n : undefined,\n }),\n );\n\n // Wrap the engine collection in the SMRT-owned public surface. The wrapper\n // projects rows to plain DTOs at every read boundary (toArray/get and change\n // payloads) so the engine's virtual props never escape, and confines the\n // engine's own types to this module.\n const handle: SmrtWebCollection<TData> = {\n get toArray() {\n return collection.toArray.map((row) => toPlainRow<TData>(row));\n },\n get size() {\n return collection.size;\n },\n has(key) {\n return collection.has(key);\n },\n get(key) {\n const row = collection.get(key);\n return row === undefined ? undefined : toPlainRow<TData>(row);\n },\n preload() {\n // Gate the first read on a pending async warmStart (#1755) so an async\n // rehydrate (persistence over OPFS/IndexedDB) reliably seeds the cache\n // BEFORE the engine's own load runs — otherwise the fetch that persistence\n // was meant to suppress would race ahead. Resolved/rejected warmStart is\n // already handled (seeded or logged); we only need to await settlement.\n //\n // When there is NO pending warmStart (incl. the no-op path and the\n // `initialData` seed path) return the engine promise DIRECTLY — no extra\n // async wrapper — so the microtask timing is byte-identical to before the\n // seam (a wrapper tick would let a staleTime:0 revalidation land early).\n if (!warmStartPending) return collection.preload();\n // If cleanup() ran while the warmStart was still pending, don't preload the\n // torn-down engine — resolve to nothing.\n return warmStartPending.then(() => {\n if (disposed) return;\n return collection.preload();\n });\n },\n async cleanup() {\n // Mark disposed FIRST so any still-pending async warmStart continuation\n // sees it and skips seeding the shared cache / preloading the dead engine\n // (#1755, Fix G) — even though the continuation runs on a later tick.\n disposed = true;\n // Tear down the engine first, THEN each capability (#1755) — so a\n // capability's teardown runs against a stopped engine. Awaited so async\n // teardowns (closing an SSE stream, flushing a store) complete before\n // cleanup() resolves. Per-capability try/catch so one rejecting teardown\n // does not skip the others nor reject cleanup(). With no capabilities this\n // awaits nothing extra.\n await collection.cleanup();\n for (const capability of capabilities) {\n try {\n await capability.teardown?.(ctx);\n } catch (error) {\n warnCapability(capability, 'teardown', error);\n }\n }\n },\n subscribeChanges(callback) {\n const subscription = collection.subscribeChanges((changes: unknown) =>\n callback(projectChanges(changes)),\n );\n return { unsubscribe: () => subscription.unsubscribe() };\n },\n insert(row) {\n return collection.insert(row) as unknown as SmrtWebTransaction;\n },\n };\n\n engineCollections.set(handle, collection);\n\n // onAttach (#1755): now that the engine collection exists, let each capability\n // wire an external (non-mutation) trigger — an SSE subscription, a focus\n // listener — with a callable `ctx.invalidate()`. Runs ONCE, in array order.\n // Per-capability try/catch so a throwing onAttach cannot break construction\n // after the handle is already registered, nor skip later capabilities. With\n // no capabilities this loop is empty — the no-op path.\n for (const capability of capabilities) {\n try {\n capability.onAttach?.(ctx);\n } catch (error) {\n warnCapability(capability, 'onAttach', error);\n }\n }\n\n return handle;\n}\n\n// ---------------------------------------------------------------------------\n// WebMCP browser tool registration (#1812)\n// ---------------------------------------------------------------------------\n\nexport {\n type RegisterWebMcpToolsOptions,\n registerWebMcpTools,\n} from './webmcp.js';\n"],"mappings":"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"}