@happyvertical/smrt-web 0.51.32 → 0.51.33

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.
Files changed (47) hide show
  1. package/dist/__typechecks__/webmcp-tool-definition.d.ts +2 -0
  2. package/dist/__typechecks__/webmcp-tool-definition.d.ts.map +1 -0
  3. package/dist/ai.d.ts +330 -358
  4. package/dist/ai.d.ts.map +1 -0
  5. package/dist/capability-classification.d.ts +89 -0
  6. package/dist/capability-classification.d.ts.map +1 -0
  7. package/dist/capability.d.ts +191 -0
  8. package/dist/capability.d.ts.map +1 -0
  9. package/dist/data-query.d.ts +121 -0
  10. package/dist/data-query.d.ts.map +1 -0
  11. package/dist/data-surface-actions.d.ts +118 -0
  12. package/dist/data-surface-actions.d.ts.map +1 -0
  13. package/dist/durable-store.d.ts +88 -0
  14. package/dist/durable-store.d.ts.map +1 -0
  15. package/dist/index.d.ts +468 -2245
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/intents.d.ts +273 -300
  18. package/dist/intents.d.ts.map +1 -0
  19. package/dist/internal.d.ts +15 -0
  20. package/dist/internal.d.ts.map +1 -0
  21. package/dist/offline/durable-queue.d.ts +171 -0
  22. package/dist/offline/durable-queue.d.ts.map +1 -0
  23. package/dist/offline/engine.d.ts +359 -0
  24. package/dist/offline/engine.d.ts.map +1 -0
  25. package/dist/offline/leader.d.ts +57 -0
  26. package/dist/offline/leader.d.ts.map +1 -0
  27. package/dist/offline/types.d.ts +232 -0
  28. package/dist/offline/types.d.ts.map +1 -0
  29. package/dist/offline.d.ts +258 -0
  30. package/dist/offline.d.ts.map +1 -0
  31. package/dist/persistence/snapshot-store.d.ts +121 -0
  32. package/dist/persistence/snapshot-store.d.ts.map +1 -0
  33. package/dist/persistence.d.ts +157 -0
  34. package/dist/persistence.d.ts.map +1 -0
  35. package/dist/remote-query.d.ts +61 -0
  36. package/dist/remote-query.d.ts.map +1 -0
  37. package/dist/sse-client.d.ts +182 -0
  38. package/dist/sse-client.d.ts.map +1 -0
  39. package/dist/update-state/meta-store.d.ts +66 -0
  40. package/dist/update-state/meta-store.d.ts.map +1 -0
  41. package/dist/update-state.d.ts +115 -0
  42. package/dist/update-state.d.ts.map +1 -0
  43. package/dist/webmcp-tool-names.d.ts +106 -112
  44. package/dist/webmcp-tool-names.d.ts.map +1 -0
  45. package/dist/webmcp.d.ts +193 -653
  46. package/dist/webmcp.d.ts.map +1 -0
  47. package/package.json +4 -4
@@ -0,0 +1,15 @@
1
+ /** Internal coordination state shared by the smrt-web runtime modules. */
2
+ /**
3
+ * TanStack transactions resolve with an opaque transaction object rather than
4
+ * the value returned by the REST mutation handler. Keep the latter beside the
5
+ * public collection handle so WebMCP can return the server's canonical row
6
+ * without exposing engine internals.
7
+ */
8
+ export declare const persistedMutationResults: WeakMap<object, Map<string, unknown>>;
9
+ /**
10
+ * Engine-owned hydration boundary. The implementation registers this callback
11
+ * where the typed query-collection API is available; WebMCP only asks the
12
+ * boundary to hydrate a row and never reaches into engine internals.
13
+ */
14
+ export declare const mutationTargetHydrators: WeakMap<object, (row: Record<string, unknown>) => Promise<void>>;
15
+ //# sourceMappingURL=internal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["../src/internal.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAE1E;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,uCAGlC,CAAC;AAEJ;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,wBAE5B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAC9C,CAAC"}
@@ -0,0 +1,171 @@
1
+ /**
2
+ * @happyvertical/smrt-web — the durable IndexedDB outbox queue (#1762).
3
+ *
4
+ * A raw IndexedDB FIFO queue: the persistence half of the offline outbox. One
5
+ * database per durable-store namespace ({@link durableStoreNamespace}), one
6
+ * object store (`outbox`) keyed by an AUTO-INCREMENTING `seq` — so insertion
7
+ * order IS replay order for free — plus a `state` index so the replay loop can
8
+ * scan only rows that still need work.
9
+ *
10
+ * ## Why raw IndexedDB, not `@tanstack/offline-transactions`
11
+ *
12
+ * The decisive finding (#1762): the TanStack offline layer's public API is
13
+ * engine-typed (`Collection<…>`, `mutationFns`), so importing it emits a
14
+ * `@tanstack/` specifier into `dist/offline.d.ts`, which
15
+ * `scripts/check-smrt-web-engine-boundary.mjs` scans for unconditionally — the
16
+ * build would fail. It also pins `@tanstack/db` exactly and competes with
17
+ * smrt-web's OWN mutation lifecycle. So the outbox is hand-rolled over browser
18
+ * globals (`indexedDB`, `navigator.locks`) with ZERO new runtime dependency.
19
+ *
20
+ * This module is engine-free (no `@tanstack/*` import): it stays inside the
21
+ * engine-absorption boundary (#1761) and speaks only the sync-apply contract's
22
+ * op shape (`packages/core/src/sync/apply.ts`).
23
+ *
24
+ * Row payloads are stored as structured-clone-safe plain objects; IndexedDB
25
+ * clones on write, so callers may keep mutating the source object after enqueue.
26
+ */
27
+ import type { SyncApplyOp } from './types.js';
28
+ /** IndexedDB object-store name inside every outbox database. */
29
+ export declare const OUTBOX_STORE = "outbox";
30
+ /** Index over {@link OutboxRow.state} — the replay scan reads it. */
31
+ export declare const OUTBOX_STATE_INDEX = "state";
32
+ /** IndexedDB schema version. Bump only on a store/index migration. */
33
+ export declare const OUTBOX_DB_VERSION = 1;
34
+ /**
35
+ * Durable lifecycle state of one queued mutation. Deliberately a SUPERSET of
36
+ * the app-observable sync state: `pending` rows are due for (re)send, while
37
+ * `synced`/`failed` are terminal tombstones removed after each drain. The
38
+ * app-facing {@link OutboxSyncState} (`pending`/`uploading`/`synced`/`failed`)
39
+ * is derived by the engine — `uploading` is a transient in-flight marker the
40
+ * engine emits, never persisted; this enum is the on-disk truth.
41
+ */
42
+ export type OutboxRowState = 'pending' | 'synced' | 'failed';
43
+ /**
44
+ * One durable outbox row — a single client-authored mutation awaiting replay
45
+ * through `POST {basePath}/sync/apply`. Fields mirror the sync-apply item so a
46
+ * row maps to a batch item with no translation, plus the durable bookkeeping the
47
+ * replay loop needs (`state`, `attempts`, `nextAttemptAt`, `lastError`).
48
+ */
49
+ export interface OutboxRow {
50
+ /** Auto-incrementing primary key — monotonic, so `seq` order IS FIFO. */
51
+ seq?: number;
52
+ /**
53
+ * The sync-apply `itemId`: the client's idempotency/correlation handle. A
54
+ * fresh UUID minted at enqueue time, distinct from {@link id} (the row UUID),
55
+ * so it survives coalescing and is echoed back verbatim by the endpoint.
56
+ */
57
+ itemId: string;
58
+ /** The collection route segment (e.g. `products`) — sync-apply `object`. */
59
+ object: string;
60
+ /** Which mutation kind — sync-apply `op`. */
61
+ op: SyncApplyOp;
62
+ /** The client-generated row UUID — sync-apply `id`. */
63
+ id: string;
64
+ /** Field data for create/update; omitted for delete. */
65
+ payload?: Record<string, unknown>;
66
+ /** The server `updated_at` the client last saw — the conflict guard base. */
67
+ baseUpdatedAt?: string;
68
+ /**
69
+ * The consumer-declared replay transport this row routes through (#3021),
70
+ * by name. Absent ⇒ the row replays through `sync/apply`. Present ⇒ it
71
+ * replays ONLY through the transport registered under this name, and is held
72
+ * (never re-routed to `sync/apply`) until one is. Additive: rows written
73
+ * before #3021 lack it and keep their sync-apply meaning, so no schema bump.
74
+ */
75
+ transport?: string;
76
+ /**
77
+ * A value the row's transport pinned on its first attempt (#3021) — e.g. the
78
+ * built server request — so every retry under the same idempotency key
79
+ * resends an identical request. Structured-clone-safe.
80
+ */
81
+ pinned?: unknown;
82
+ /** On-disk lifecycle state. */
83
+ state: OutboxRowState;
84
+ /** How many replay attempts this row has made (drives backoff). */
85
+ attempts: number;
86
+ /** Epoch ms before which this row must not be retried (backoff gate). */
87
+ nextAttemptAt: number;
88
+ /** Last replay error message, for diagnostics/`snapshot()`. */
89
+ lastError?: string;
90
+ /** Epoch ms the row was first enqueued — stable ordering tiebreak + age. */
91
+ enqueuedAt: number;
92
+ }
93
+ /** The fields a caller supplies to {@link DurableOutboxQueue.enqueue}. */
94
+ export interface EnqueueInput {
95
+ itemId: string;
96
+ object: string;
97
+ op: SyncApplyOp;
98
+ id: string;
99
+ payload?: Record<string, unknown>;
100
+ baseUpdatedAt?: string;
101
+ transport?: string;
102
+ }
103
+ /**
104
+ * Feature-detect a usable IndexedDB. Some environments expose `indexedDB` but
105
+ * throw on `open()` (private-mode Firefox historically, sandboxed iframes), so a
106
+ * real probe opens a throwaway database. Returns `false` rather than throwing so
107
+ * the engine can degrade to a no-op instead of crashing the host.
108
+ */
109
+ export declare function probeIndexedDb(): Promise<boolean>;
110
+ /**
111
+ * The durable FIFO outbox queue over one IndexedDB database. Constructed via
112
+ * {@link openDurableOutboxQueue} (async open + schema upgrade), then used
113
+ * through the async methods below (each resolves once its txn commits).
114
+ *
115
+ * NOT concurrency-controlled across tabs on its own — the OutboxEngine layers
116
+ * Web Locks leader election on top so exactly one tab drains it. Within one tab,
117
+ * IndexedDB's own transaction serialization is sufficient.
118
+ */
119
+ export declare class DurableOutboxQueue {
120
+ private readonly db;
121
+ /** The IndexedDB database name (== the durable-store namespace). */
122
+ readonly dbName: string;
123
+ constructor(db: IDBDatabase, dbName: string);
124
+ /**
125
+ * Append a mutation to the tail of the queue in state `pending`, due
126
+ * immediately (`nextAttemptAt = 0`, `attempts = 0`). Resolves with the
127
+ * assigned `seq` once the write is durably committed.
128
+ */
129
+ enqueue(input: EnqueueInput): Promise<number>;
130
+ /**
131
+ * Persist a state transition (and any of attempts/backoff/error) for the row
132
+ * at `seq`, reading-then-writing inside ONE transaction so a concurrent drain
133
+ * in the same tab can't lose the update. A no-op if the row is already gone
134
+ * (removed by a prior terminal transition).
135
+ */
136
+ markState(seq: number, patch: Partial<Pick<OutboxRow, 'state' | 'attempts' | 'nextAttemptAt' | 'lastError' | 'pinned'>>): Promise<void>;
137
+ /**
138
+ * All rows that are due to (re)send at `now`: state `pending` AND
139
+ * `nextAttemptAt <= now`, in ascending `seq` (FIFO). Uses the `state` index to
140
+ * avoid scanning terminal tombstones. Terminal rows (`synced`/`failed`) are
141
+ * excluded — they await removal, not replay.
142
+ */
143
+ listPending(now: number): Promise<OutboxRow[]>;
144
+ /** Remove the row at `seq` (a terminal transition drops it). */
145
+ remove(seq: number): Promise<void>;
146
+ /** Every row currently in the queue (any state), ascending `seq`. */
147
+ all(): Promise<OutboxRow[]>;
148
+ /**
149
+ * Drop every row — the durable-store `clear()` for `wipeDurableStore`. Empties
150
+ * the store but keeps the database (and its `seq` autoincrement counter) so a
151
+ * subsequent enqueue still gets fresh monotonic ids.
152
+ */
153
+ clear(): Promise<void>;
154
+ /** Close the underlying database handle (called on engine dispose). */
155
+ close(): void;
156
+ }
157
+ /**
158
+ * Open (creating/upgrading as needed) the outbox database for `dbName` and wrap
159
+ * it in a {@link DurableOutboxQueue}. `dbName` MUST be a
160
+ * {@link durableStoreNamespace} string — already `encodeURIComponent`-safe and
161
+ * carrying the api/tenant/identity/manifest isolation boundary, so two
162
+ * identities never share one queue database.
163
+ *
164
+ * The `onupgradeneeded` handler creates the `outbox` store keyed by
165
+ * autoincrementing `seq` and the `state` index. Idempotent: re-opening an
166
+ * existing database at the same version skips the upgrade — which is exactly the
167
+ * "reload" path (a new tab re-opens the same durable backing and sees prior
168
+ * rows).
169
+ */
170
+ export declare function openDurableOutboxQueue(dbName: string): Promise<DurableOutboxQueue>;
171
+ //# sourceMappingURL=durable-queue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"durable-queue.d.ts","sourceRoot":"","sources":["../../src/offline/durable-queue.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,359 @@
1
+ /**
2
+ * @happyvertical/smrt-web — the shared, namespace-keyed outbox engine (#1762).
3
+ *
4
+ * The engine owns the durable queue, the leader lock, and the replay loop for
5
+ * ONE durable-store namespace, and is REF-COUNTED so that N collections sharing
6
+ * a namespace share exactly ONE engine — one IndexedDB database, one leader
7
+ * lock, one FIFO queue. This sharing is REQUIRED for correctness, not an
8
+ * optimization: if each collection held its OWN leader lock, two tabs could each
9
+ * win a different collection's lock and both replay the (shared) queue,
10
+ * double-POSTing. One lock per namespace route ⇒ one replayer per route across
11
+ * all tabs. Since #3021 a route is `sync-apply` (every sync-apply collection;
12
+ * keeps the pre-#3021 lock name) or one consumer-declared transport, and a tab
13
+ * leads only routes it has a binding for, so FIFO and backoff are per route.
14
+ *
15
+ * Replay maps sync-apply results onto durable transitions per the contract's
16
+ * "Web outbox (#1762)" consumer notes
17
+ * (docs/content/architecture/sync-apply-contract.md):
18
+ *
19
+ * | apply result | queue transition |
20
+ * |-------------------------------------|----------------------------------------|
21
+ * | `applied` | remove → `synced` |
22
+ * | `conflict` (stale_write/create_conflict) | remove + fire onConflict → `synced` (a RESOLVED outcome) |
23
+ * | `rejected` `write_failed` | keep, attempts++, backoff → `pending` |
24
+ * | `rejected` `auth_required`/`forbidden` | PAUSE the loop, keep queued → `pending` |
25
+ * | `rejected` other (`invalid_*`/`unknown_object`/`not_found`/`op_not_allowed`/`id_conflict`) | remove → `failed` (terminal) |
26
+ * | network reject / non-200 / lost response | whole batch stays `pending`, drain stops behind it (blind replay is safe by construction — idempotency) |
27
+ *
28
+ * Because items carry client-generated UUIDs and the endpoint is idempotent
29
+ * (`_insertOnly` create + no-op re-apply), a batch that was sent but whose
30
+ * response was lost can be blindly re-sent with no duplicate rows — which is
31
+ * why the network-failure path simply leaves the batch `pending`.
32
+ *
33
+ * Engine-free public surface: no `@tanstack/*` import — inside the boundary
34
+ * (#1761). The one engine-adjacent value it receives is the SMRT-owned
35
+ * durable-store namespace + registration hooks passed in `config`.
36
+ */
37
+ import { type OutboxCommandTransport, type OutboxConflict, type ResolvedBackoff, type SyncApplyOp, type SyncStateEvent } from './types.js';
38
+ /** Maps a capability-seam envelope kind to a sync-apply op. */
39
+ export declare function envelopeKindToOp(kind: 'insert' | 'update' | 'delete'): SyncApplyOp;
40
+ /**
41
+ * The bookkeeping a namespace's durable-store registration needs, supplied by
42
+ * the public surface so the engine can key its queue and register for
43
+ * {@link wipeDurableStore} WITHOUT importing `durable-store.ts` itself (keeping
44
+ * this module's dependency surface minimal and the namespacing single-sourced in
45
+ * the caller). `namespace` is the {@link durableStoreNamespace} string.
46
+ */
47
+ export interface OutboxEngineConfig {
48
+ /** The durable-store namespace string — the IDB dbName + lock-name root. */
49
+ namespace: string;
50
+ /** Resolved backoff parameters. */
51
+ backoff: ResolvedBackoff;
52
+ /**
53
+ * Register this engine's queue as a durable resource so `wipeDurableStore`
54
+ * can clear it; returns the unregister fn. Wraps `registerDurableResource`
55
+ * from the caller so the engine stays decoupled from that module.
56
+ */
57
+ registerResource: (clear: () => Promise<void>) => () => void;
58
+ /** Injectable RNG for deterministic backoff jitter in tests. */
59
+ random?: () => number;
60
+ }
61
+ /**
62
+ * A pending optimistic write to enqueue, in capability-seam terms. Callbacks are
63
+ * NOT carried here — they are registered per-`object` at
64
+ * {@link OutboxEngine.registerCollection} so that reloaded rows (which this
65
+ * session never enqueued) still route their events to the collection.
66
+ */
67
+ export interface OutboxEnqueueRequest {
68
+ kind: 'insert' | 'update' | 'delete';
69
+ /** The collection route segment (definition.name). */
70
+ object: string;
71
+ /**
72
+ * The client-generated row UUID (the optimistic row's id). Empty ⇒ the
73
+ * minted item id stands in (a transport command with no target row).
74
+ */
75
+ rowId: string;
76
+ /** Full row (insert) / changed fields (update) / ignored (delete). */
77
+ data: Record<string, unknown>;
78
+ /** The server updated_at last seen for this row, for the conflict guard. */
79
+ baseUpdatedAt?: string;
80
+ /**
81
+ * Replay through the transport registered under this name instead of
82
+ * `sync/apply` (#3021). Absent ⇒ sync-apply.
83
+ */
84
+ transport?: string;
85
+ }
86
+ /**
87
+ * Where a namespace's sync-apply rows are POSTed. Supplied by the binding that
88
+ * replays through sync-apply (an `offlineOutbox` without a `transport`), not by
89
+ * the engine's creator: a command-only queue may create the shared engine
90
+ * first, and must not pin a sync-apply base path it never declared.
91
+ */
92
+ export interface OutboxSyncApplyTarget {
93
+ /** Absolute base path the sync-apply endpoint lives under (e.g. `/api/v1`). */
94
+ basePath: string;
95
+ /** Fetch implementation (injectable for tests/SSR). */
96
+ fetchFn: typeof fetch;
97
+ }
98
+ /** A read-only view of one queued item, for {@link OutboxEngine.snapshot}. */
99
+ export interface OutboxSnapshotItem {
100
+ itemId: string;
101
+ object: string;
102
+ op: SyncApplyOp;
103
+ rowId: string;
104
+ state: 'pending' | 'synced' | 'failed';
105
+ attempts: number;
106
+ nextAttemptAt: number;
107
+ lastError?: string;
108
+ /** The replay transport name, when the item replays through one (#3021). */
109
+ transport?: string;
110
+ }
111
+ /**
112
+ * The shared per-namespace outbox engine. Create/lookup via
113
+ * {@link getOrCreateOutboxEngine}; never construct directly (the module keeps
114
+ * the ref-counted registry).
115
+ */
116
+ export declare class OutboxEngine {
117
+ private readonly config;
118
+ /**
119
+ * Per-collection callback sets, keyed by the collection route segment
120
+ * (`object`). Keyed by `object` — NOT by the queue row's `itemId` — precisely
121
+ * so replayed rows that were REHYDRATED from IndexedDB after a reload (whose
122
+ * itemIds this session never enqueued) still route their state/conflict events
123
+ * to the reloaded collection's callbacks. A `Set` per object so N collections
124
+ * sharing one engine+object each get every event (the common case is one
125
+ * collection per object, but the shared-engine model does not forbid more).
126
+ */
127
+ private readonly listenersByObject;
128
+ /**
129
+ * The sync-apply endpoint, adopted from the first binding that declares one.
130
+ * A tab without one never leads the sync-apply route, so its rows wait for a
131
+ * tab that does (never dropped) — see `drainOnce`.
132
+ */
133
+ private syncApply;
134
+ /** Ref count: number of collections currently attached to this engine. */
135
+ private refCount;
136
+ /** The durable queue, once opened. undefined while opening / if IDB absent. */
137
+ private queue;
138
+ /** Resolves once the async open settles (success or degraded). */
139
+ private readonly ready;
140
+ /** True when IndexedDB was unavailable and the engine is a durable no-op. */
141
+ private degraded;
142
+ /**
143
+ * Per-route leadership, requested when the first binding serving a route
144
+ * attaches and released when the last one detaches. Exactly one tab replays a
145
+ * given route; a tab never leads a route it has no binding for.
146
+ */
147
+ private readonly leaders;
148
+ /** Unregister fn from the durable-store registry. */
149
+ private unregisterResource;
150
+ /** True once dispose() ran — guards late async continuations. */
151
+ private disposed;
152
+ /**
153
+ * Paused by an auth_required/forbidden result: the loop stops draining until
154
+ * a later enqueue (the app re-authenticated and is writing again) or an
155
+ * explicit retry wakes it. Items stay queued.
156
+ */
157
+ private paused;
158
+ /** Wipes issued but not yet cleared; replay is suspended meanwhile. */
159
+ private wipesPending;
160
+ /** Those wipes, so `dispose()` can let them finish before closing. */
161
+ private readonly pendingWipes;
162
+ /** True while a drain pass is running, to coalesce concurrent triggers. */
163
+ private draining;
164
+ /** The running drain, while `draining`. */
165
+ private activeDrain;
166
+ /** A drain requested while one was in flight — run one more pass after. */
167
+ private drainQueued;
168
+ /** Timer for the next backoff-scheduled drain, if any. */
169
+ private backoffTimer;
170
+ /** The `online` event listener, so we can remove it on dispose. */
171
+ private onlineListener;
172
+ constructor(config: OutboxEngineConfig);
173
+ /** Open the durable queue (or mark degraded if IndexedDB is unusable). */
174
+ private open;
175
+ /**
176
+ * Register the queue as a durable resource. A wipe drops the namespace's
177
+ * registrations, so the clear callback forgets ours and the next enqueue
178
+ * registers again — otherwise a second wipe would miss rows written after
179
+ * the first.
180
+ */
181
+ private registerForWipe;
182
+ /** Wake the drain loop immediately when connectivity returns. */
183
+ private wireOnlineListener;
184
+ /**
185
+ * Request cross-tab leadership for `route` (ref-counted per tab). The
186
+ * sync-apply route keeps the pre-#3021 lock name, so it stays mutually
187
+ * exclusive with tabs running an older build; each transport route has its
188
+ * own lock.
189
+ */
190
+ private acquireRoute;
191
+ /** Drop one reference to `route`; the last one releases its lock. */
192
+ private releaseRoute;
193
+ /** Does this tab currently lead `route`? */
194
+ private leads;
195
+ /**
196
+ * Attach a collection: register its per-object callbacks and bump the ref
197
+ * count. Returns the exact callback record registered so the caller can pass
198
+ * it back to {@link unregisterCollection} for precise removal (two collections
199
+ * on the same object must each detach only their own callbacks). Registering
200
+ * by `object` is what lets rehydrated rows (reloaded from IDB) reach this
201
+ * collection's callbacks even though this session never enqueued them.
202
+ */
203
+ registerCollection(binding: OutboxCollectionBinding): object;
204
+ /** The first live transport registered under `name`, if any. */
205
+ private transportFor;
206
+ /**
207
+ * Detach a collection: remove its callback record and decrement the ref count;
208
+ * when it reaches zero, dispose the engine (release the lock, unregister from
209
+ * the durable-store registry, close IndexedDB). The durable ROWS are NOT
210
+ * cleared — they must survive to replay after a reload; only the in-memory
211
+ * engine is torn down. Returns true if it disposed.
212
+ */
213
+ unregisterCollection(object: string, record: object): Promise<boolean>;
214
+ /** Current ref count (test/introspection aid). */
215
+ get referenceCount(): number;
216
+ /**
217
+ * Enqueue an optimistic write into the durable queue and fire the initial
218
+ * `pending` state, then kick a drain. Resolves once the row is durably
219
+ * committed (so the caller's `wrapMutation` only reports handled after
220
+ * persistence). Replay events for this row (and for rows this session did not
221
+ * enqueue — reloaded from disk) route to the registered per-`object`
222
+ * callbacks, so a reload does not lose observability.
223
+ *
224
+ * In degraded (no-IndexedDB) mode the write is NOT durable, so this returns
225
+ * `undefined` and the capability falls through to the real fetcher instead
226
+ * of acknowledging an optimistic-only write.
227
+ */
228
+ enqueue(request: OutboxEnqueueRequest): Promise<string | undefined>;
229
+ /**
230
+ * Force a retry of a specific queued item now: clears its backoff gate and
231
+ * wakes the loop. The bridge `OutboxHandle.retry(itemId)` calls this so an app
232
+ * "retry" button can flush a backed-off or auth-paused item. A no-op for an
233
+ * item that is not (or no longer) queued.
234
+ */
235
+ retry(itemId: string): Promise<void>;
236
+ /**
237
+ * A read-only snapshot of the durable queue — the basis of
238
+ * `OutboxHandle.snapshot()`. Because the READ cache is NOT rehydrated after a
239
+ * reload in this slice (that's #1764's warmStart), the snapshot + the raw IDB
240
+ * store are how a test/app proves durability, not `collection.toArray()`.
241
+ */
242
+ snapshot(): Promise<OutboxSnapshotItem[]>;
243
+ /**
244
+ * Deliver a state event to every callback registered for the event's
245
+ * collection `object` (best-effort). Routing by `object` — not `itemId` —
246
+ * means a row REHYDRATED from IndexedDB after a reload still reaches the
247
+ * reloaded collection's callback even though this session never enqueued it.
248
+ */
249
+ private emit;
250
+ /** Deliver a conflict to every callback registered for its collection. */
251
+ private emitConflict;
252
+ /**
253
+ * The replay loop. Gated on: (a) leading at least one route, (b) not paused by an
254
+ * auth failure, (c) `navigator.onLine !== false`, (d) IndexedDB usable. Drains
255
+ * every led route's rows due now (`nextAttemptAt <= now`), oldest-first per
256
+ * route; sync-apply chunks into batches of ≤1000 per POST, one send at a time
257
+ * to preserve each route's FIFO. Concurrency-coalesced: a drain requested while one runs sets a flag
258
+ * to run exactly one more pass, so overlapping triggers never interleave.
259
+ */
260
+ private drain;
261
+ /**
262
+ * Settles once the drain in flight (if any) has finished its request AND its
263
+ * durable result transition. A route's lock is released only after this, so
264
+ * another tab can never replay a row this tab is still settling.
265
+ */
266
+ private settled;
267
+ /** One drain pass: send every currently-due batch, then schedule backoff. */
268
+ private drainOnce;
269
+ /**
270
+ * Replay one transport row through its consumer-declared transport (#3021)
271
+ * and map the outcome through the SAME transitions as a sync-apply result. A
272
+ * throw is the ambiguous path (request may or may not have landed): the row
273
+ * stays `pending` with backoff and is resent under the same idempotency key.
274
+ * Returns false when the row remains pending, stopping this route's pass
275
+ * (FIFO per route).
276
+ */
277
+ private sendCommand;
278
+ /**
279
+ * Send one chunk through `POST {basePath}/sync/apply` and map results back
280
+ * onto durable transitions. On a network reject / non-200 / lost/mismatched
281
+ * response the WHOLE chunk stays `pending` (blind replay is safe) — every row
282
+ * goes back to `pending` with an incremented attempt + backoff so the loop
283
+ * doesn't hot-spin. Returns false when a retryable row remains pending, which
284
+ * stops this drain pass so newer FIFO chunks do not overtake it.
285
+ */
286
+ private sendBatch;
287
+ /**
288
+ * POST a batch to `{basePath}/sync/apply`. Throws on a non-2xx or a network
289
+ * error (the caller treats a throw as "response lost → keep pending"). Returns
290
+ * the positional `results` array, or `undefined` on a malformed 200 body.
291
+ */
292
+ private postBatch;
293
+ /**
294
+ * Map one positional apply result onto a durable transition + observable
295
+ * state, per the contract's consumer notes. See the class doc's mapping table.
296
+ */
297
+ private applyResult;
298
+ /** Remove a successfully-applied (or conflict-resolved) row → `synced`. */
299
+ private finishSynced;
300
+ /** Re-queue every row of a failed batch (network path) with backoff. */
301
+ private requeueBatch;
302
+ /** Re-queue one row: attempts++, backoff gate, `pending` event. */
303
+ private requeueRow;
304
+ /**
305
+ * Schedule the next drain for the soonest backed-off row's `nextAttemptAt`.
306
+ * Only one timer is ever pending; a sooner schedule replaces a later one.
307
+ */
308
+ private scheduleNextBackoff;
309
+ /**
310
+ * Tear down the in-memory engine: release leadership, remove the online
311
+ * listener, clear timers, unregister from the durable-store registry, and
312
+ * close IndexedDB. Does NOT clear the durable rows — they must survive to
313
+ * replay on the next load.
314
+ */
315
+ private dispose;
316
+ }
317
+ /**
318
+ * Get the shared engine for `config.namespace`, creating it on first use and
319
+ * ref-counting it. Every collection opting into the outbox under the same
320
+ * namespace gets the SAME engine — one IndexedDB db, one leader lock, one FIFO
321
+ * queue. The caller MUST pair each `getOrCreateOutboxEngine(...).registerCollection()`
322
+ * with a later `unregisterCollection()` (via `teardown`) so the engine disposes
323
+ * when its last collection detaches.
324
+ */
325
+ export declare function getOrCreateOutboxEngine(config: OutboxEngineConfig): OutboxEngine;
326
+ /**
327
+ * The per-collection (or per-command-queue) binding registered on attach.
328
+ * `object` is the event-routing key AND, for a `transport` binding, the name
329
+ * transport rows are queued under.
330
+ */
331
+ export interface OutboxCollectionBinding {
332
+ object: string;
333
+ onSyncStateChange?: (event: SyncStateEvent) => void;
334
+ onConflict?: (conflict: OutboxConflict) => void;
335
+ /** Replay rows queued under `object` through this transport (#3021). */
336
+ transport?: OutboxCommandTransport;
337
+ /** Declares the sync-apply endpoint (sync-apply bindings only). */
338
+ syncApply?: OutboxSyncApplyTarget;
339
+ }
340
+ /**
341
+ * Attach a collection to the namespace's engine (creating it if needed),
342
+ * register its per-`object` callbacks, and increment its ref count. Returns the
343
+ * engine plus the exact callback `record` to hand back to
344
+ * {@link releaseOutboxEngine} for precise removal. Pair with
345
+ * {@link releaseOutboxEngine}.
346
+ */
347
+ export declare function acquireOutboxEngine(config: OutboxEngineConfig, binding: OutboxCollectionBinding): {
348
+ engine: OutboxEngine;
349
+ record: object;
350
+ };
351
+ /**
352
+ * Detach a collection from an engine (removing its callback record) and, if it
353
+ * was the last one, dispose it and evict it from the shared registry so the
354
+ * namespace starts fresh next time.
355
+ */
356
+ export declare function releaseOutboxEngine(namespace: string, engine: OutboxEngine, object: string, record: object): Promise<void>;
357
+ /** Test-only: current engine count (for leak assertions). */
358
+ export declare function _outboxEngineCount(): number;
359
+ //# sourceMappingURL=engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/offline/engine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * @happyvertical/smrt-web — Web Locks leader election for the outbox (#1762).
3
+ *
4
+ * With multiple tabs open, exactly ONE must replay the shared outbox queue, or
5
+ * two tabs would POST the same batch concurrently. The mechanism is the native
6
+ * Web Locks API (`navigator.locks`): a tab requests an EXCLUSIVE lock keyed by
7
+ * the durable-store namespace and holds it — via a promise that resolves only on
8
+ * release — for as long as it should be leader. The browser grants the lock to
9
+ * exactly one waiter at a time and AUTO-RELEASES it on tab crash/close/navigation
10
+ * with no heartbeat, so the next waiting tab becomes leader instantly and there
11
+ * is never a stuck lock.
12
+ *
13
+ * Why Web Locks over a BroadcastChannel election (v1 decision, locked): Web
14
+ * Locks gives crash-safe, race-free, browser-arbitrated exclusion for free —
15
+ * a hand-rolled BroadcastChannel election has to solve liveness (heartbeats,
16
+ * takeover on a silent crash) itself. BroadcastChannel is a possible future
17
+ * enhancement (cross-tab state fan-out), not the election primitive.
18
+ *
19
+ * ## Single-tab fallback (documented gap)
20
+ *
21
+ * When `navigator.locks` is absent (older Safari, non-browser hosts), this warns
22
+ * ONCE and falls back to acquiring leadership immediately and unconditionally.
23
+ * The outbox STILL replays — a lone tab is trivially the only replayer — but the
24
+ * multi-tab exactly-one-replayer guarantee does not hold across tabs that all
25
+ * took the fallback. This is a deliberate v1 limitation, NOT masked by a
26
+ * BroadcastChannel shim.
27
+ *
28
+ * Engine-free: no `@tanstack/*` import — stays inside the boundary (#1761).
29
+ */
30
+ /**
31
+ * A handle to relinquish leadership: call it to release the Web Lock (or cancel
32
+ * a still-pending request). Idempotent.
33
+ */
34
+ export type LeadershipHandle = () => void;
35
+ /**
36
+ * Acquire cross-tab leadership for `lockName` (the outbox's
37
+ * `smrt-web-outbox-leader:<namespace>`).
38
+ *
39
+ * When the lock is granted this tab becomes leader and `onAcquired()` fires; the
40
+ * tab stays leader — holding the lock — until the returned handle is called (or
41
+ * the tab dies, when the browser auto-releases). On release, `onReleased()`
42
+ * fires. Requesting is non-blocking: this returns synchronously with the
43
+ * release handle while the request waits in the background for its turn.
44
+ *
45
+ * Contract:
46
+ * - The lock is requested EXCLUSIVE; the callback returns a promise that stays
47
+ * pending until the release handle resolves it, so the browser considers the
48
+ * lock held for exactly that window.
49
+ * - Calling the handle before the lock is even granted aborts the pending
50
+ * request (via `AbortSignal`) so a torn-down engine never becomes leader
51
+ * later. `onReleased` still fires so callers can settle their own state.
52
+ * - No Web Locks support → single-tab fallback: `onAcquired()` fires on the next
53
+ * microtask (leadership is immediate and unconditional), the handle just fires
54
+ * `onReleased()`.
55
+ */
56
+ export declare function acquireLeadership(lockName: string, onAcquired: () => void, onReleased: () => void): LeadershipHandle;
57
+ //# sourceMappingURL=leader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"leader.d.ts","sourceRoot":"","sources":["../../src/offline/leader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG,MAAM,IAAI,CAAC;AAsB1C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,IAAI,EACtB,UAAU,EAAE,MAAM,IAAI,GACrB,gBAAgB,CA6ElB"}