@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
package/dist/index.d.ts CHANGED
@@ -1,2245 +1,468 @@
1
- /**
2
- * Serialize `list` query params into the query string the generated REST list
3
- * route parses (`handleList` in `core/src/generators/rest.ts`): `limit`,
4
- * `offset`, `orderBy` as scalars, and `where` entries as `field=value`
5
- * (equality) or `field[op]=value` for a `{ op, value }` condition (`in` joins an
6
- * array with commas). Called with no params (the collection runtime's argument-
7
- * free `list()`) it returns `''`, so the bare-URL behavior is unchanged.
8
- */
9
- export declare function buildListQuery(params?: Record<string, unknown>): string;
10
-
11
- /**
12
- * Default outcome mapping for a data-surface action result. `ok` → applied; a
13
- * transient server reason → retryable `write_failed`; an authentication reason
14
- * → pause for re-auth; a stale revision/preview → `conflict` (a RESOLVED
15
- * outcome the app rebases from); anything else — including `denied` — is a
16
- * terminal rejection surfaced as `failed`.
17
- */
18
- export declare function classifySmrtWebDataSurfaceActionResult(result: SmrtWebDataSurfaceActionResult): OutboxCommandResult;
19
-
20
- /**
21
- * Compile a declared intent plus a mounted binding into a bespoke tool spec.
22
- *
23
- * The `execute` this returns is built here from `intent.target`; nothing an
24
- * author wrote is called. It dispatches exactly one registry command with
25
- * `source: 'agent'`, so every policy the registry enforces — staged review,
26
- * local-gesture proof, sensitivity, writability — applies unchanged.
27
- *
28
- * @throws if the binding does not match the intent's declared target.
29
- */
30
- export declare function compileViewIntentToolSpec(intent: ViewIntent, binding: ViewIntentBinding): ViewIntentToolSpec;
31
-
32
- /**
33
- * Build CRUD fetchers from a generated collection definition — the same URL
34
- * scheme and payload handling as the generated REST client
35
- * (`basePath + endpoint`), with one improvement: HTTP error statuses reject
36
- * instead of resolving with them. Typed/legacy 4xx detail is retained; 5xx
37
- * failures stay opaque and payload-free.
38
- */
39
- export declare function createDefinitionFetchers(definition: Pick<SmrtWebCollectionDefinition<object>, 'name' | 'endpoint'>, basePath?: string, fetchFn?: typeof fetch): SmrtCrudFetchers;
40
-
41
- /**
42
- * Create a typed client collection over a generated SMRT collection definition
43
- * and the matching generated REST client fetchers.
44
- *
45
- * Reads: stale-while-revalidate. The first subscriber triggers a fetch;
46
- * re-subscribing within `staleTimeMs` serves local data with no request. N
47
- * concurrent identical reads coalesce into one network request.
48
- *
49
- * Writes: `collection.insert({ ...data, id: newLocalId() })` applies instantly,
50
- * persists through `fetchers.create()` (the temp id is stripped — the server
51
- * assigns the real one), then refetches to reconcile. A failed create rejects
52
- * the transaction and the optimistic row rolls back automatically.
53
- *
54
- * Relationship-derived invalidation: once a create/update/delete has persisted,
55
- * the query caches of this collection AND the collections named by
56
- * `definition.relationships` (manifest-derived edges) are invalidated, so
57
- * dependent views refetch. Reaching OTHER collections requires them to share
58
- * this collection's `client` (see {@link createSmrtWebClient}); with a private
59
- * client only this collection refetches.
60
- *
61
- * Hydration seeding: pass rows fetched server-side as
62
- * {@link CreateSmrtCollectionOptions.initialData} and the collection's first
63
- * read is served from them with NO network request (until `staleTimeMs`
64
- * elapses) — the SvelteKit `+page.server.ts` → hydrate path.
65
- */
66
- export declare function createSmrtCollection<TData extends object>(definition: SmrtWebCollectionDefinition<TData>, options: CreateSmrtCollectionOptions<TData>): SmrtWebCollection<TData>;
67
-
68
- /**
69
- * Options for {@link createSmrtCollection}. Generic in the collection's row
70
- * type `TData` so {@link initialData} is checked against the same DTO the
71
- * collection stores; every other option is row-type-agnostic, so the parameter
72
- * defaults to `object` and can be omitted at call sites that pass no seed.
73
- */
74
- export declare interface CreateSmrtCollectionOptions<TData extends object = object> {
75
- /**
76
- * Generated REST client surface for this collection, e.g.
77
- * `createClient('/api/v1').products` from the virt-client module. When
78
- * omitted, fetchers are derived from the definition's endpoint and `basePath`
79
- * with the same URL scheme and payload shapes the generated client uses.
80
- */
81
- fetchers?: SmrtCrudFetchers;
82
- /** API base path for definition-derived fetchers (default `/api/v1`). */
83
- basePath?: string;
84
- /** Fetch implementation override (tests, SSR). Defaults to global fetch. */
85
- fetchFn?: typeof fetch;
86
- /**
87
- * Shared cache handle from {@link createSmrtWebClient}. Pass one app-wide
88
- * instance so collections share a cache and deduplicate requests; a private
89
- * cache is created when omitted.
90
- */
91
- client?: SmrtWebClient;
92
- /**
93
- * Cache namespace for this collection's reads. Fold a backend / tenant /
94
- * preview discriminator in here when the SAME generated collection is
95
- * materialized against DIFFERENT backends while sharing one {@link client} —
96
- * without it those reads share a cache key and could serve one backend's rows
97
- * for the other for the whole `staleTimeMs` window. Omit for the common
98
- * single-backend case.
99
- */
100
- scope?: string;
101
- /**
102
- * Stale-while-revalidate window in milliseconds (default 30s): reads within
103
- * the window are served from the local collection without a network request;
104
- * the first read after it revalidates in the background.
105
- */
106
- staleTimeMs?: number;
107
- /** Retry failed loads (default false: fail fast, surface errors). */
108
- retry?: boolean;
109
- /**
110
- * Rows to seed this collection's cache with, before its first read — the
111
- * hydration path for server-rendered data (#1761). Fetch rows in a SvelteKit
112
- * `+page.server.ts` load, pass them here on the client, and the first read
113
- * serves them from cache WITHOUT a duplicate first-render network request
114
- * (SMRT-owned type, so no engine type appears on the option).
115
- *
116
- * The seed is written to the cache with a fresh timestamp, so it counts as
117
- * fresh for `staleTimeMs`: with the default window the first read does not
118
- * fetch, and the collection revalidates in the background only once the window
119
- * elapses (or immediately if `staleTimeMs` is 0). Seed the SAME rows the
120
- * server serialized so the pre- and post-hydration renders match.
121
- *
122
- * Seeds the SAME cache key the reads use — so with a shared {@link client},
123
- * fold the backend / tenant discriminator into {@link scope} to match, exactly
124
- * as reads do; otherwise one backend's seed would serve the other for the
125
- * `staleTimeMs` window.
126
- */
127
- initialData?: SmrtWebRow<TData>[];
128
- /**
129
- * Capability plug-ins hooking the collection lifecycle (#1755) — the seam
130
- * that lets the offline outbox (#1762), persistence (#1764), and live SSE
131
- * invalidation (#1763-client) slices each live in their own module instead of
132
- * contending on this factory. Capabilities run in array order at six fixed
133
- * points (see {@link SmrtWebCapability}). Additive and defaulting to none: an
134
- * undefined or empty array is byte-for-byte the collection of today (the
135
- * no-op guarantee), so this ships zero concrete capabilities by design.
136
- */
137
- capabilities?: SmrtWebCapability<TData>[];
138
- }
139
-
140
- /**
141
- * Create a shared client-cache handle. Pass the returned handle as
142
- * {@link CreateSmrtCollectionOptions.client} to every collection that should
143
- * share a cache and deduplicate requests app-wide.
144
- */
145
- export declare function createSmrtWebClient(): SmrtWebClient;
146
-
147
- /**
148
- * Create the app-wide live-updates subscriber. Feature-detects ONCE at
149
- * construction: an available EventSource connects the SSE stream; otherwise it
150
- * starts the `_changes` poll loop. A fatal SSE error later downgrades to
151
- * polling for the rest of the subscriber's life (no flap-back).
152
- */
153
- export declare function createSmrtWebEventSubscriber(config: SmrtWebEventSubscriberConfig): SmrtWebEventSubscriber;
154
-
155
- /**
156
- * Create a query controller over one SMRT collection. Query rows stay in a
157
- * separate keyed cache; the collection's full list is never loaded.
158
- */
159
- export declare function createSmrtWebQuery<TData extends object>(_collection: SmrtWebCollection<TData>, transport: SmrtWebQueryTransport, options?: {
160
- staleTimeMs?: number;
161
- }): SmrtWebQuery<TData>;
162
-
163
- /**
164
- * Create the framework-free `updateAvailable` primitive. Kicks off async
165
- * contract detection immediately (compare running vs. persisted manifest hash);
166
- * {@link UpdateState.notifyBundleUpdated} feeds the bundle signal, and
167
- * {@link UpdateState.notifyContractUpdated} feeds the live contract signal. A
168
- * change to EITHER signal notifies subscribers.
169
- */
170
- export declare function createUpdateState(config: UpdateStateConfig): UpdateState;
171
-
172
- /**
173
- * An {@link OutboxCommandTransport} that replays a queued write as a
174
- * data-surface action `apply` (optionally preceded by `preview`) through the
175
- * surface's own server adapter — its authority, allowlist, and principal
176
- * scoping stay on the server path (#3021). The outbox's durable idempotency
177
- * key becomes the action's `idempotencyKey`, so a resend after a lost response
178
- * replays the server's recorded result instead of applying twice. Malformed or
179
- * mismatched replies throw, which the outbox treats as retryable.
180
- */
181
- export declare function dataSurfaceActionCommandTransport(options: SmrtWebDataSurfaceActionCommandTransportOptions): OutboxCommandTransport;
182
-
183
- /** Default trailing-debounce window (ms) for the write-back. */
184
- export declare const DEFAULT_PERSIST_DEBOUNCE_MS = 250;
185
-
186
- /**
187
- * A durable artifact registered under a namespace — the outbox queue (#1762) or
188
- * a persisted collection store (#1764). Each owns its storage engine and
189
- * exposes only a `clear()` so {@link wipeDurableStore} can tear it down without
190
- * knowing which engine backs it.
191
- */
192
- export declare interface DurableResource {
193
- /** Which slice owns this artifact — for diagnostics and selective sweeps. */
194
- readonly kind: 'outbox' | 'persisted-collection';
195
- /** Drop this artifact's durable storage. Best-effort; may reject. */
196
- clear(): Promise<void>;
197
- }
198
-
199
- /**
200
- * @happyvertical/smrt-web — shared durable-store foundation (#1755).
201
- *
202
- * The ONE SMRT-layer namespacing + wipe registry that the future offline
203
- * outbox (#1762) and persistence (#1764) slices both build on. Pure bookkeeping
204
- * — ZERO client-data-engine (`@tanstack/*`) imports — so it stays inside the
205
- * engine-absorption boundary and ships now, before either consumer exists, so
206
- * the two slices agree on it from day one.
207
- *
208
- * Why a SMRT-layer registry rather than one storage engine: TanStack DB
209
- * persistence (SQLite-WASM / OPFS) and `@tanstack/offline-transactions`
210
- * (IndexedDB) are SEPARATE storage engines. There is no single primitive that
211
- * spans both, so the shared foundation lives one level up — a deterministic
212
- * namespace both slices key their own storage under, plus a registry so
213
- * {@link wipeDurableStore} can clear BOTH through one call without the outbox
214
- * and persistence modules importing each other. A logout / tenant-switch wipes
215
- * every durable artifact for a namespace in one place.
216
- *
217
- * Nothing in this package calls these yet — the seam ships ahead of its
218
- * consumers by design (see PRD #1755).
219
- */
220
- /**
221
- * The identity a durable namespace is derived from. Combining the API base with
222
- * the tenant + identity + manifest hash means a logout, a tenant switch, or a
223
- * schema change each land on a DIFFERENT namespace, so durable artifacts are
224
- * never reused across those boundaries.
225
- *
226
- * `manifestHash` is supplied by the caller (its source is #1764's call — a
227
- * schema-shape digest); this module is source-agnostic and treats it as an
228
- * opaque discriminator.
229
- */
230
- export declare interface DurableStoreKey {
231
- /** API base path the durable data was fetched against (e.g. `/api/v1`). */
232
- apiBase: string;
233
- /** Active tenant id, if any — absent means the single-tenant / global scope. */
234
- tenantId?: string;
235
- /** Authenticated identity id, if any — absent means anonymous. */
236
- identityId?: string;
237
- /** Opaque schema-shape digest; a change forces a fresh namespace. */
238
- manifestHash: string;
239
- }
240
-
241
- /**
242
- * Compute the deterministic storage namespace for a {@link DurableStoreKey}.
243
- * The same key always yields the same string; **any differing segment yields a
244
- * different one** (injective) — this is critical because the namespace IS the
245
- * tenant/identity/api isolation + wipe boundary, so a collision would reuse or
246
- * wipe durable data ACROSS those boundaries.
247
- *
248
- * Injectivity is guaranteed three ways: every segment is `encodeURIComponent`-
249
- * encoded, so a raw `:` in a value becomes `%3A` and can never be mistaken for a
250
- * separator; an ABSENT `tenantId` / `identityId` maps to the empty string while
251
- * a PRESENT value is prefixed with `_` (so undefined→``, ``→`_`, `x`→`_x`) — so
252
- * an explicitly-EMPTY id no longer collides with "no id", nor does a real id of
253
- * `-`. Both future slices key their own storage primitive (IndexedDB store name,
254
- * OPFS path, …) under this string.
255
- */
256
- export declare function durableStoreNamespace(key: DurableStoreKey): string;
257
-
258
- /** Run a browser transport and return the defensive normalized envelope. */
259
- export declare function executeSmrtWebDataQuery(transport: SmrtWebDataQueryTransport, request: SmrtWebDataQueryRequest, options?: {
260
- signal?: AbortSignal;
261
- }): Promise<SmrtWebDataQueryResult>;
262
-
263
- export declare function executeSmrtWebDataSurfaceAction(transport: SmrtWebDataSurfaceActionTransport, request: SmrtWebDataSurfaceActionRequest, options?: {
264
- signal?: AbortSignal;
265
- }): Promise<SmrtWebDataSurfaceActionResult>;
266
-
267
- /**
268
- * Retrieve the underlying engine collection backing a handle — an advanced
269
- * bridge for trusted framework bindings (e.g. the smrt-svelte live-query
270
- * binding), which must feed the engine collection to the query builder. Returns
271
- * `unknown` so no engine type crosses the boundary; callers cast. Throws for a
272
- * handle not produced by {@link createSmrtCollection}. Not needed for normal
273
- * use.
274
- */
275
- export declare function getEngineCollection<TData extends object>(handle: SmrtWebCollection<TData>): unknown;
276
-
277
- /**
278
- * Retrieve the programmatic {@link OutboxHandle} for a durable-store namespace,
279
- * or `undefined` if no opted-in collection is currently attached under it. The
280
- * bridge for trusted callers (outbox UI, smrt-svelte binding, tests) to read the
281
- * durable queue and force retries. Pass the SAME
282
- * `durableStoreNamespace(config.namespace)` string the capability used.
283
- *
284
- * Mirrors the `getEngineCollection` bridge convention: an escape hatch that
285
- * returns a narrow SMRT-owned surface rather than the engine itself.
286
- */
287
- export declare function getOutboxHandle(namespace: string): OutboxHandle | undefined;
288
-
289
- /**
290
- * Invalidate materialized collection caches through the opaque SMRT client.
291
- *
292
- * This is the public cache-coherence seam for transports that execute a write
293
- * without constructing a {@link SmrtWebCollection} (for example, a tool-only
294
- * WebMCP definition). Matching is by collection name across every scope on the
295
- * shared client, mirroring settled collection mutations: over-invalidation is
296
- * safe, while leaving a related page cache stale is not.
297
- */
298
- export declare function invalidateSmrtWebCollections(client: SmrtWebClient, collectionNames: readonly string[]): void;
299
-
300
- /**
301
- * A thin per-collection capability that subscribes THIS collection to live
302
- * signals for its `tableName`. On attach it registers `ctx.invalidate` (the
303
- * factory's relationship-derived refetch primitive) with the shared subscriber;
304
- * on teardown it unregisters. All transport, reconnection, and fan-out live in
305
- * the {@link createSmrtWebEventSubscriber}; this capability is just the wire
306
- * between one collection and that one subscriber.
307
- */
308
- export declare function liveInvalidation<TData extends object = object>(config: LiveInvalidationConfig): SmrtWebCapability<TData>;
309
-
310
- /** Configuration for the {@link liveInvalidation} capability. */
311
- export declare interface LiveInvalidationConfig {
312
- /** The one app-wide subscriber from {@link createSmrtWebEventSubscriber}. */
313
- subscriber: Pick<SmrtWebEventSubscriber, 'registerTable' | 'invalidateAll' | 'transport'>;
314
- /**
315
- * The PHYSICAL table name this collection reads. EXPLICIT because a
316
- * `SmrtWebCollectionDefinition` has no physical-table field and STI children
317
- * share one base table — the subscriber keys signals by physical table, so a
318
- * guess would mis-route invalidations.
319
- */
320
- tableName: string;
321
- }
322
-
323
- export declare const MAX_SMRT_WEB_DATA_QUERY_CONTAINER_ITEMS = 1000;
324
-
325
- export declare const MAX_SMRT_WEB_DATA_QUERY_FACET_VALUES = 1000;
326
-
327
- export declare const MAX_SMRT_WEB_DATA_QUERY_FACETS = 20;
328
-
329
- export declare const MAX_SMRT_WEB_DATA_QUERY_OFFSET = 1000000;
330
-
331
- export declare const MAX_SMRT_WEB_DATA_QUERY_PAGE_LIMIT = 1000;
332
-
333
- /** Mirrors core's hard output limits without adding an inter-SMRT dependency. */
334
- export declare const MAX_SMRT_WEB_DATA_QUERY_RESULT_BYTES = 10000000;
335
-
336
- export declare const MAX_SMRT_WEB_DATA_QUERY_ROWS = 1000;
337
-
338
- export declare const MAX_SMRT_WEB_DATA_QUERY_STRING_LENGTH = 65536;
339
-
340
- export declare const MAX_SMRT_WEB_DATA_QUERY_WARNINGS = 100;
341
-
342
- export declare const MAX_SMRT_WEB_DATA_SURFACE_ACTION_CONTAINER_ITEMS = 1000;
343
-
344
- export declare const MAX_SMRT_WEB_DATA_SURFACE_ACTION_IDENTIFIER_LENGTH = 256;
345
-
346
- export declare const MAX_SMRT_WEB_DATA_SURFACE_ACTION_JSON_DEPTH = 16;
347
-
348
- export declare const MAX_SMRT_WEB_DATA_SURFACE_ACTION_RESULT_BYTES = 1000000;
349
-
350
- /** The settle outcome handed to {@link SmrtWebCapability.onSettled}. */
351
- export declare type MutationSettleOutcome = {
352
- ok: true;
353
- result: unknown;
354
- } | {
355
- ok: false;
356
- error: unknown;
357
- };
358
-
359
- /**
360
- * Generate a client-local id for optimistic inserts. The generated REST layer
361
- * strips client-supplied ids on create (mass-assignment guard #1540), so this
362
- * id only identifies the optimistic row until the post-persist refetch swaps in
363
- * the server-assigned row.
364
- */
365
- export declare function newLocalId(): string;
366
-
367
- /**
368
- * Check the server-produced, normalized result envelope before a browser
369
- * surface consumes it. This validates portable shape only; the server's core
370
- * normalizer remains the authorization, projection, and query-policy boundary.
371
- */
372
- export declare function normalizeSmrtWebDataQueryResult(value: unknown): SmrtWebDataQueryResult;
373
-
374
- /** Fail closed before an action result reaches browser state. */
375
- export declare function normalizeSmrtWebDataSurfaceActionResult(value: unknown): SmrtWebDataSurfaceActionResult;
376
-
377
- /** One write for {@link OfflineCommandQueue.enqueue}. */
378
- export declare interface OfflineCommandInput {
379
- /** Command arguments — structured-clone-safe; persisted verbatim. */
380
- payload?: Record<string, unknown>;
381
- /** The row the write targets, when there is one. Defaults to the item id. */
382
- rowId?: string;
383
- /** Mutation kind, passed through to the transport. Defaults to `create`. */
384
- op?: SyncApplyOp;
385
- /** The server `updated_at` last seen, passed through to the transport. */
386
- baseUpdatedAt?: string;
387
- }
388
-
389
- /** A durable queue of consumer-declared server operations (#3021). */
390
- export declare interface OfflineCommandQueue {
391
- /**
392
- * Durably queue one write and kick replay. Resolves to its idempotency key
393
- * once the write is committed to IndexedDB, or `undefined` when the durable
394
- * queue is unavailable (no IndexedDB) or the queue was disposed — the write
395
- * was NOT captured, so the caller must perform it online itself or surface
396
- * the failure; it is never acknowledged non-durably.
397
- */
398
- enqueue(input: OfflineCommandInput): Promise<string | undefined>;
399
- /** The namespace's durable queue (every route), like {@link OutboxHandle}. */
400
- snapshot(): Promise<OutboxSnapshotItem[]>;
401
- /** Force a retry of a queued item now (clears backoff and an auth pause). */
402
- retry(itemId: string): Promise<void>;
403
- /** Detach. Queued writes stay on disk and replay on the next attach. */
404
- dispose(): Promise<void>;
405
- }
406
-
407
- /**
408
- * A durable offline queue for writes that go through a consumer-declared
409
- * server operation — a data-surface action, a named remote command, a
410
- * permission-gated service — rather than a generated collection (#3021).
411
- *
412
- * Shares the namespace's outbox engine with every {@link offlineOutbox}: one
413
- * IndexedDB queue, a cross-tab leader per route, FIFO within the route, the
414
- * `pending → uploading → synced | failed` state machine, backoff, auth pause,
415
- * and the `wipeDurableStore` registration. Each replay carries the write's
416
- * durable `idempotencyKey`, which the server operation must dedupe on — that
417
- * is what makes a blind resend after a lost response safe.
418
- *
419
- * Attaches immediately; call `dispose()` when the owning view unmounts.
420
- */
421
- export declare function offlineCommandQueue(config: OfflineCommandQueueConfig): OfflineCommandQueue;
422
-
423
- /** Configuration for {@link offlineCommandQueue}. */
424
- export declare interface OfflineCommandQueueConfig {
425
- /**
426
- * The queue's name — the key queued writes are stored and routed under. It
427
- * shares the namespace's durable queue and per-object event routing with
428
- * every collection outbox, so it MUST NOT equal a sibling collection's
429
- * `definition.name` (their events would cross). Durable: a write queued under a name replays only once a queue
430
- * with that name (and a transport) is attached again, e.g. after a reload.
431
- */
432
- name: string;
433
- /** The durable-store identity — the SAME key the app's other outboxes use. */
434
- namespace: DurableStoreKey;
435
- /** Replays each queued write through the consumer's own server operation. */
436
- transport: OutboxCommandTransport;
437
- /** Exponential-backoff tuning for retryable replay failures. */
438
- backoff?: OutboxBackoff;
439
- /** Push callback for every sync-state transition (see {@link offlineOutbox}). */
440
- onSyncStateChange?: (event: SyncStateEvent) => void;
441
- /** Push callback for a `conflict` outcome (a RESOLVED outcome). */
442
- onConflict?: (conflict: OutboxConflict) => void;
443
- /** Test-only: inject a deterministic RNG for backoff jitter. */
444
- random?: () => number;
445
- }
446
-
447
- /**
448
- * Build a durable offline-outbox capability for a collection. Add the returned
449
- * capability to the collection's `capabilities` array; a collection without it
450
- * is unaffected (the seam's no-op guarantee — the "opt-in per model" AC).
451
- *
452
- * The same `namespace` across multiple collections shares ONE engine (one IDB
453
- * db, one queue) — so cross-collection ordering and the multi-tab
454
- * single-replayer guarantee hold across every sync-apply collection of a given
455
- * identity (one route). A collection with a `transport` is its own route.
456
- */
457
- export declare function offlineOutbox<TData extends object = object>(config: OfflineOutboxConfig<TData>): SmrtWebCapability<TData>;
458
-
459
- /**
460
- * Configuration for {@link offlineOutbox}. Generic in the collection's row type
461
- * `TData` so the capability matches the collection it plugs into.
462
- */
463
- export declare interface OfflineOutboxConfig<TData extends object = object> {
464
- /**
465
- * The generated collection definition this outbox serves — its `name` is the
466
- * sync-apply `object` route segment, so it MUST be the SAME definition passed
467
- * to {@link createSmrtCollection}. (Named `object` to mirror the capability
468
- * context; carries the collection's REST route segment.)
469
- */
470
- object: {
471
- name: string;
472
- _row?: TData;
473
- };
474
- /**
475
- * The durable-store identity this outbox's queue is namespaced under — folds
476
- * api base / tenant / identity / manifest hash, so a logout or tenant switch
477
- * lands on a different IndexedDB database (never cross-identity reuse). Shared
478
- * with #1764's persistence slice via the same {@link durableStoreNamespace}.
479
- * `manifestHash` is opaque caller-supplied config for #1762; its canonical
480
- * source is #1764's call (a schema-shape digest) — comment your call site.
481
- */
482
- namespace: DurableStoreKey;
483
- /**
484
- * API base path the sync-apply endpoint lives under (`POST
485
- * {syncApplyBasePath}/sync/apply`). Defaults to `/api/v1`, matching the REST
486
- * generator's default. Set to the SvelteKit route base (`/api`) when replaying
487
- * against the generated SvelteKit `sync/apply/+server.ts`. Ignored when
488
- * {@link transport} is set.
489
- */
490
- syncApplyBasePath?: string;
491
- /**
492
- * Replay this collection's queued writes through a consumer-declared server
493
- * operation instead of the generated `sync/apply` (#3021) — for a model whose
494
- * generated write verbs are closed because a hand-written, permission-gated
495
- * service owns its write rules. Rows are queued under `object.name` and replay
496
- * ONLY through this transport (never `sync/apply`), each carrying its durable
497
- * `idempotencyKey`, with the same four-state machine, per-route FIFO, and
498
- * backoff.
499
- * See {@link offlineCommandQueue} for writes with no collection at all.
500
- */
501
- transport?: OutboxCommandTransport;
502
- /** Fetch implementation override (tests, SSR). Defaults to global fetch. */
503
- fetchFn?: typeof fetch;
504
- /** Exponential-backoff tuning for retryable replay failures. */
505
- backoff?: OutboxBackoff;
506
- /**
507
- * Called on every observable sync-state transition of a captured mutation
508
- * (`pending → uploading → synced`, `→ failed`, and the `pending` re-arm after
509
- * a retryable failure). A push callback; smrt-svelte turns it into a reactive
510
- * binding later.
511
- */
512
- onSyncStateChange?: (event: SyncStateEvent) => void;
513
- /**
514
- * Called when a replayed mutation comes back `conflict` — a RESOLVED outcome
515
- * (server state won; the item leaves the queue with terminal state `synced`).
516
- * Persist `serverUpdatedAt` as the new `baseUpdatedAt`, refetch the row, and
517
- * surface the conflict to the user as appropriate.
518
- */
519
- onConflict?: (conflict: OutboxConflict) => void;
520
- /** Test-only: inject a deterministic RNG for backoff jitter. */
521
- random?: () => number;
522
- }
523
-
524
- /** Exponential-backoff tuning for retryable replay failures. */
525
- export declare interface OutboxBackoff {
526
- /** Delay before the first retry, ms (default 1000). */
527
- initialDelayMs?: number;
528
- /** Multiplier applied per attempt (default 2). */
529
- multiplier?: number;
530
- /** Ceiling on the computed delay, ms (default 60000). */
531
- maxDelayMs?: number;
532
- }
533
-
534
- /**
535
- * One queued write handed to a consumer-declared {@link OutboxCommandTransport}
536
- * at replay time (#3021). The transport replays it through a server operation
537
- * the consumer owns — a data-surface action, a named remote command, a
538
- * hand-written permission-gated service — instead of the generated
539
- * `sync/apply`, which calls `collection.create()` directly.
540
- */
541
- export declare interface OutboxCommand {
542
- /**
543
- * The durable, client-minted idempotency key (the queue row's `itemId`). It
544
- * is IDENTICAL on every attempt of this write — including a blind resend
545
- * after a lost response — so the server operation MUST dedupe on it; that is
546
- * what makes the outbox's ambiguous-failure retries safe on this path.
547
- */
548
- idempotencyKey: string;
549
- /** The transport name the write was queued under. */
550
- name: string;
551
- /** The mutation kind in sync-apply terms. */
552
- op: SyncApplyOp;
553
- /** The client-generated row id the write targets. */
554
- rowId: string;
555
- /** Row / changed fields (create, update); absent for delete. */
556
- payload?: Record<string, unknown>;
557
- /** The server `updated_at` the client last saw, when the caller supplied one. */
558
- baseUpdatedAt?: string;
559
- /** 1-based number of this attempt (`1` on the first send). */
560
- attempt: number;
561
- /** The value this write's transport pinned on an earlier attempt, if any. */
562
- pinned?: unknown;
563
- /**
564
- * Durably pin a structured-clone-safe value to this write before sending,
565
- * so a later attempt (including after a reload) sees it as `pinned`. A
566
- * transport whose server fingerprints the request per idempotency key pins
567
- * the request it built, so a resend after a lost response is identical.
568
- */
569
- pin(value: unknown): Promise<void>;
570
- }
571
-
572
- /**
573
- * The server's outcome for one {@link OutboxCommand}, in the SAME vocabulary
574
- * the sync-apply replay maps — so the four-state machine and backoff are
575
- * shared, not re-derived:
576
- *
577
- * - `applied` → removed, `synced`.
578
- * - `conflict` → removed, `onConflict` fired, `synced` (a RESOLVED outcome).
579
- * - `rejected` with `write_failed` → kept, backoff, `pending` (retryable).
580
- * - `rejected` with `auth_required` / `forbidden` → the whole loop pauses until
581
- * re-auth (a later enqueue or `retry()`), kept `pending`.
582
- * - `rejected` with any other reason → removed, `failed` (terminal).
583
- *
584
- * A transport that THROWS (network error, lost response, 5xx) is the ambiguous
585
- * path: the write stays `pending` with backoff and is blindly resent later
586
- * under the same {@link OutboxCommand.idempotencyKey}.
587
- */
588
- export declare type OutboxCommandResult = {
589
- status: 'applied';
590
- updatedAt?: string;
591
- } | {
592
- status: 'conflict';
593
- reason?: 'stale_write' | 'create_conflict';
594
- updatedAt?: string;
595
- } | {
596
- status: 'rejected';
597
- reason: SyncApplyReason | (string & {});
598
- };
599
-
600
- /** Replays one queued write through a consumer-declared server operation. */
601
- export declare type OutboxCommandTransport = (command: OutboxCommand) => Promise<OutboxCommandResult>;
602
-
603
- /**
604
- * A conflict surfaced to {@link OfflineOutboxConfig.onConflict} when a replayed
605
- * item comes back `conflict`. Per the contract this is a RESOLVED outcome — the
606
- * server state won, the item is removed, and its terminal observable state is
607
- * `synced` — so an app treats it as "your write was superseded; here is the
608
- * server's `updatedAt` to rebase from", NOT as a failure to retry.
609
- */
610
- export declare interface OutboxConflict {
611
- /** The queue row's `itemId`. */
612
- itemId: string;
613
- /** The collection route segment. */
614
- object: string;
615
- /** The client-generated row UUID that conflicted. */
616
- rowId: string;
617
- /**
618
- * Why the item conflicted: `stale_write` (an update/delete whose
619
- * `baseUpdatedAt` was older than the server row) or `create_conflict` (a
620
- * create landing on an existing, diverged row).
621
- */
622
- reason: 'stale_write' | 'create_conflict';
623
- /**
624
- * The server row's `updated_at` after processing — the value to persist as
625
- * the new `baseUpdatedAt` before re-editing. Present when the endpoint
626
- * returned it.
627
- */
628
- serverUpdatedAt?: string;
629
- }
630
-
631
- /**
632
- * A programmatic handle to one namespace's outbox — the bridge pattern (like
633
- * `getEngineCollection`) for trusted callers (an app's outbox panel, a
634
- * smrt-svelte binding, tests). Fetched via {@link getOutboxHandle}.
635
- */
636
- export declare interface OutboxHandle {
637
- /**
638
- * A read-only snapshot of the durable queue (every state). The way to prove
639
- * durability after a reload, since this slice does not rehydrate the read
640
- * cache (that's #1764).
641
- */
642
- snapshot(): Promise<OutboxSnapshotItem[]>;
643
- /**
644
- * Force a retry of a specific queued item now (clears its backoff gate, wakes
645
- * the loop, and clears an auth pause). A no-op for an item no longer queued.
646
- */
647
- retry(itemId: string): Promise<void>;
648
- }
649
-
650
- /** One item in an {@link OutboxHandle.snapshot} result. */
651
- export declare interface OutboxSnapshotItem {
652
- /** The transport name, when the item replays through one (#3021). */
653
- transport?: string;
654
- /** The queue row's idempotency handle. */
655
- itemId: string;
656
- /** The collection route segment. */
657
- object: string;
658
- /** The mutation kind, in sync-apply terms. */
659
- op: 'create' | 'update' | 'delete';
660
- /** The client-generated row UUID. */
661
- rowId: string;
662
- /** The on-disk lifecycle state. */
663
- state: 'pending' | 'synced' | 'failed';
664
- /** Replay attempts so far. */
665
- attempts: number;
666
- /** Epoch ms before which this row won't be retried (backoff). */
667
- nextAttemptAt: number;
668
- /** Last replay error, if any. */
669
- lastError?: string;
670
- }
671
-
672
- /**
673
- * The app-observable sync state of a queued mutation — the SAME four-state
674
- * machine the KMP mobile foundation exposes (ADR 0001), so web and mobile
675
- * outbox indicators render identically:
676
- *
677
- * - `pending` — enqueued, awaiting (re)send.
678
- * - `uploading` — currently in a sync-apply POST.
679
- * - `synced` — the mutation's effect is confirmed on the server (a terminal
680
- * success — INCLUDING a surfaced conflict, which is a RESOLVED outcome: the
681
- * server state won and the item left the queue).
682
- * - `failed` — a terminal rejection the client cannot resolve by retrying
683
- * (`invalid_*`, `not_found`, `id_conflict`, `op_not_allowed`, …) — surfaced
684
- * for app-level handling.
685
- */
686
- export declare type OutboxSyncState = 'pending' | 'uploading' | 'synced' | 'failed';
687
-
688
- /**
689
- * Build a durable persistence capability for a collection. Add the returned
690
- * capability to the collection's `capabilities` array; a collection without it
691
- * is unaffected (the seam's no-op guarantee — the "opt-in per model" AC).
692
- *
693
- * The same `namespace` across multiple collections shares ONE snapshot database
694
- * (one open, one durable resource, one wipe), and each collection is keyed by
695
- * its own `collection` name within it.
696
- */
697
- export declare function persistCollection<TData extends object = object>(config: PersistCollectionConfig<TData>): SmrtWebCapability<TData>;
698
-
699
- /**
700
- * Configuration for {@link persistCollection}. Generic in the collection's row
701
- * type `TData` so the capability matches the collection it plugs into.
702
- */
703
- export declare interface PersistCollectionConfig<TData extends object = object> {
704
- /**
705
- * The REST collection name this snapshot is keyed by — MUST be the SAME
706
- * `definition.name` passed to {@link createSmrtCollection} (it is the store
707
- * key under the namespace). Provided explicitly (not read off the ctx) so the
708
- * config is self-describing and testable.
709
- */
710
- collection: string;
711
- /**
712
- * The durable-store identity this snapshot is namespaced under — folds api
713
- * base / tenant / identity / manifest hash, so a logout, tenant switch, or a
714
- * contract-changing deploy lands on a different IndexedDB database (never
715
- * cross-identity reuse, and a shape change drops old snapshots). Shared with
716
- * the outbox (#1762) via the same {@link durableStoreNamespace}, so one
717
- * {@link wipeDurableStore} clears both.
718
- *
719
- * The canonical `manifestHash` source is the build-time inject — the
720
- * `manifestHash` constant the generated `@happyvertical/smrt-virt-web` module
721
- * exports (#1764). Thread it here at the call site.
722
- */
723
- namespace: DurableStoreKey;
724
- /**
725
- * Trailing-debounce window (ms) for the write-back snapshot. A burst of
726
- * mutations within the window collapses to ONE IndexedDB write of the final
727
- * row set. Default {@link DEFAULT_PERSIST_DEBOUNCE_MS}. Set 0 to write on
728
- * every change (tests).
729
- */
730
- debounceMs?: number;
731
- }
732
-
733
- /**
734
- * Persist and rehydrate a mounted data surface's rows (#3021) — the
735
- * `persistCollection` twin for a surface (e.g. one registered with
736
- * `mountListDataSurface`) whose rows come from a server-scoped data-surface
737
- * query rather than a generated `list` route. The page owns its rows, so it
738
- * owns the calls: `load()` before the first fetch to paint stale rows, then
739
- * `save(rows)` whenever it refreshes them.
740
- *
741
- * Shares the namespace's snapshot database, durable-resource registration,
742
- * and IndexedDB-unavailable degrade with {@link persistCollection}: a
743
- * manifest-hash, tenant, or identity change lands on a different database, and
744
- * `wipeDurableStore(namespace)` clears the surface's rows with the rest.
745
- */
746
- export declare function persistDataSurface<TRow extends object = object>(config: PersistDataSurfaceConfig): PersistedDataSurface<TRow>;
747
-
748
- /** Configuration for {@link persistDataSurface}. */
749
- export declare interface PersistDataSurfaceConfig {
750
- /**
751
- * The durable-store identity — the SAME key the app's collections and
752
- * outbox use. It folds the principal (`identityId`) and tenant, so a surface
753
- * whose rows the server scopes per principal lands on a per-principal
754
- * database: another user on the device never reads them, and one
755
- * `wipeDurableStore(namespace)` on logout clears them with everything else.
756
- */
757
- namespace: DurableStoreKey;
758
- /** The mounted surface's identity (`descriptor.identity`) — the store key. */
759
- identity: SmrtWebDataSurfaceIdentity;
760
- /** Trailing-debounce window for `save` (ms). Default {@link DEFAULT_PERSIST_DEBOUNCE_MS}. */
761
- debounceMs?: number;
762
- }
763
-
764
- /** A durable row snapshot for one mounted data surface (#3021). */
765
- export declare interface PersistedDataSurface<TRow extends object = object> {
766
- /**
767
- * The rows last saved for this surface under this namespace, or `undefined`
768
- * when none exist (first load, a different principal/tenant/manifest, a
769
- * wipe, or no IndexedDB). Render them as stale, then revalidate. After a
770
- * `wipeDurableStore` of its namespace a handle is inert (`load` finds
771
- * nothing, `save` is ignored); create a new one for the next scope.
772
- */
773
- load(): Promise<TRow[] | undefined>;
774
- /**
775
- * Replace the persisted rows (debounced, trailing; last call wins). Pass the
776
- * surface's CURRENT rows whenever the page refreshes them. Rows must be
777
- * structured-clone-safe plain objects.
778
- */
779
- save(rows: readonly TRow[]): void;
780
- /** Write any pending `save` now. */
781
- flush(): Promise<void>;
782
- /** Flush a pending `save`, then release the shared store. */
783
- dispose(): Promise<void>;
784
- }
785
-
786
- /**
787
- * Register a durable artifact under a namespace so {@link wipeDurableStore} can
788
- * later clear it. Returns an unregister function that removes just this
789
- * resource — call it when the artifact is disposed on its own (before any
790
- * namespace-wide wipe) so it is not cleared twice.
791
- */
792
- export declare function registerDurableResource(namespace: string, resource: DurableResource): () => void;
793
-
794
- /**
795
- * Register one declared view intent (#2588) against a mounted registry
796
- * binding, for as long as the returned disposer is not called.
797
- *
798
- * This is the ONLY way an intent reaches the browser, and it goes through
799
- * {@link registerWebMcpBespokeTool}, so an intent inherits the same
800
- * fail-closed `effects` exposure policy as a generated model tool. The
801
- * intent's classification was resolved at declaration time by the shared
802
- * rule, and the hints `compileViewIntentToolSpec` emits round-trip through
803
- * the bespoke registrar's own re-resolution unchanged.
804
- *
805
- * The `execute` registered here is constructed by
806
- * `compileViewIntentToolSpec` from `intent.target`. No author-supplied code
807
- * runs, and the only thing it can do is dispatch one browser registry
808
- * command — the runtime half of the no-REST invariant.
809
- *
810
- * The intent's derived tool name is claimed against the document-global
811
- * tool-name lock (#2613), so an id whose flattened name collides with a
812
- * generated model tool or a fixed UI tool throws
813
- * {@link WebMcpToolNameCollisionError} here rather than silently losing the
814
- * tool at the host. `defineIntent`'s own intent-vs-intent check cannot see
815
- * either, because both depend on a runtime `namespace` / `prefix` the
816
- * declaration never sees.
817
- *
818
- * @throws {WebMcpToolNameCollisionError} when the derived name is already held.
819
- */
820
- export declare function registerViewIntent(intent: ViewIntent, binding: ViewIntentBinding, options?: RegisterWebMcpBespokeToolOptions): WebMcpRegistrationDisposer;
821
-
822
- /**
823
- * Register one hand-written browser tool through the same fail-closed effect
824
- * classification and `effects` exposure policy as {@link registerWebMcpTools}
825
- * (#2586). A tool with no `annotations`, or with annotations that leave its
826
- * effect undeclared, classifies destructive/non-idempotent/open-world — the
827
- * same default `actionSemantics` gives an undeclared custom model action —
828
- * and is excluded unless the policy allows `destructive`. `namespace` and
829
- * `maxTools` are out of scope for a bespoke tool; see
830
- * {@link RegisterWebMcpBespokeToolOptions}.
831
- *
832
- * The tool's name is also claimed against the
833
- * document-global tool-name lock before the browser sees it. A name a
834
- * generated model tool, a fixed UI tool, or another live bespoke tool /
835
- * intent already holds throws {@link WebMcpToolNameCollisionError}
836
- * SYNCHRONOUSLY, naming the colliding name and the owner that holds it.
837
- * Previously the host accepted the call and silently dropped the losing
838
- * registration. Disposing releases the name, so register/dispose/re-register
839
- * under one name still succeeds.
840
- *
841
- * @returns a disposer that deregisters the tool this call registered (a
842
- * no-op double-call). On a browser without WebMCP, or when policy excludes
843
- * the tool's effect, the call is a no-op and the disposer is inert.
844
- * @throws {WebMcpToolNameCollisionError} when the name is already held.
845
- */
846
- export declare function registerWebMcpBespokeTool(spec: WebMcpBespokeToolSpec, options?: RegisterWebMcpBespokeToolOptions): WebMcpRegistrationDisposer;
847
-
848
- export declare interface RegisterWebMcpBespokeToolOptions {
849
- /**
850
- * Allowed effects. Omitted means read-only exposure — the same default as
851
- * {@link registerWebMcpTools}. `namespace` and `maxTools` deliberately do
852
- * not apply to a bespoke tool (#2586): a component author already chose a
853
- * stable name, and counting one intent against a shared budget could make
854
- * an unrelated generated tool set fail to register.
855
- */
856
- effects?: readonly WebMcpToolEffect[];
857
- /**
858
- * Which path this registration belongs to, used ONLY to label its
859
- * tool-name reservation (#2613) so a later collision names the right
860
- * source. Defaults to `bespoke`.
861
- *
862
- * A UI-framework binding that compiles a declared view intent itself and
863
- * registers it here — rather than through {@link registerViewIntent} —
864
- * must pass `'intent'`. `useViewIntent` in `@happyvertical/smrt-svelte`
865
- * does exactly that, because reusing `useWebMcpTool`'s single WebMCP
866
- * lifecycle is a documented invariant of that package; without this it
867
- * would be the only shipped intent path and every intent collision would
868
- * blame a `useWebMcpTool` call that does not exist.
869
- *
870
- * Purely a diagnostic label: it grants no capability, narrows no policy,
871
- * and changes nothing about how the tool registers or executes.
872
- */
873
- owner?: Extract<WebMcpToolNameOwner, 'intent' | 'bespoke'>;
874
- }
875
-
876
- /**
877
- * Register every collection's generated tool descriptors with WebMCP.
878
- *
879
- * Every selected tool name is claimed against the document-global tool-name
880
- * lock (#2613) before the first browser registration, so a collision with a
881
- * UI-layer or bespoke tool throws {@link WebMcpToolNameCollisionError} with
882
- * the offending name and its current owner instead of reaching the host.
883
- *
884
- * @returns a disposer that deregisters all tools this call registered. On a
885
- * browser without WebMCP the call is a no-op and the disposer is inert.
886
- * @throws {WebMcpToolNameCollisionError} when a selected name is already held.
887
- */
888
- export declare function registerWebMcpTools(definitions: readonly WebMcpRegistrationDefinition[], options?: RegisterWebMcpToolsOptions): WebMcpRegistrationDisposer;
889
-
890
- export declare interface RegisterWebMcpToolsOptions extends WebMcpExposurePolicy {
891
- /** REST base path for the fetchers (default `/api/v1`). */
892
- basePath?: string;
893
- /** Injectable fetch (tests / SSR-safe wrappers). */
894
- fetchFn?: typeof fetch;
895
- /** Shared smrt-web cache handle used by page collections. */
896
- client?: SmrtWebClient;
897
- /** Optional cache scope matching the page collection's scope. */
898
- scope?: string;
899
- /**
900
- * Override how a definition's CRUD fetchers are built. Defaults to
901
- * {@link createDefinitionFetchers}; the primary seam for testing `execute`
902
- * without a live server.
903
- */
904
- resolveFetchers?: (definition: SmrtWebCollectionDefinition) => SmrtCrudFetchers;
905
- /**
906
- * Override direct REST fetchers for a canonical tool-only definition.
907
- * Fetchers are optional because a get-only or custom-action-only model does
908
- * not have a list or create route.
909
- */
910
- resolveToolFetchers?: (definition: WebMcpToolDefinition) => Partial<SmrtCrudFetchers>;
911
- /** Predicate to include/exclude individual tools (e.g. reads-only surfaces). */
912
- filter?: (definition: SmrtWebCollectionDefinition, descriptor: NonNullable<SmrtWebCollectionDefinition['toolDescriptors']>[number]) => boolean;
913
- /** Predicate for canonical per-tool definitions. */
914
- filterTool?: (definition: WebMcpToolDefinition) => boolean;
915
- }
916
-
917
- /**
918
- * Reserve `names` for `owner` against the document's WebMCP registry.
919
- *
920
- * Reservation is ALL-OR-NOTHING: if any name is already held — by this path
921
- * or another — nothing is reserved and a {@link WebMcpToolNameCollisionError}
922
- * naming the colliding name and its current owner is thrown synchronously,
923
- * before any tool reaches `document.modelContext`. Duplicates within `names`
924
- * itself collide the same way.
925
- *
926
- * Off-WebMCP (no document) the call is a no-op returning an inert
927
- * reservation, matching every registrar's own no-op guarantee.
928
- *
929
- * A reservation releases only the names it took: `release()` never drops a
930
- * name a later reservation has since acquired, so a dispose that lands after
931
- * a same-name re-registration cannot revoke the new holder's claim.
932
- */
933
- export declare function reserveWebMcpToolNames(names: readonly string[], owner: WebMcpToolNameOwner, options?: ReserveWebMcpToolNamesOptions): WebMcpToolNameReservation;
934
-
935
- declare interface ReserveWebMcpToolNamesOptions {
936
- /**
937
- * The document whose `modelContext` the names are registered against.
938
- * Defaults to `globalThis.document`. Registrars that accept an injectable
939
- * document (tests, non-window hosts) must pass the SAME object they read
940
- * `modelContext` from, or their reservations land in a different table.
941
- */
942
- document?: unknown;
943
- }
944
-
945
- /**
946
- * Run the `wrapMutation` hook across `capabilities` in array order for one
947
- * mutation, short-circuiting on the FIRST capability that returns `{ handled:
948
- * true }` (later capabilities' `wrapMutation` are then skipped). A capability
949
- * that returns `{ handled: false }`, `undefined`, or omits the hook declines,
950
- * and the next is tried. When every capability declines, resolves `{ handled:
951
- * false }` so the factory falls through to the real fetcher.
952
- */
953
- export declare function runWrapMutation<TData extends object>(capabilities: readonly SmrtWebCapability<TData>[], envelope: SmrtWebMutationEnvelope, ctx: SmrtWebCapabilityContext<TData>): Promise<{
954
- handled: true;
955
- result: unknown;
956
- } | {
957
- handled: false;
958
- }>;
959
-
960
- /**
961
- * The per-collection CRUD surface of the generated REST client
962
- * (`createClient(basePath).<collection>` from `@happyvertical/smrt-virt-client`).
963
- *
964
- * Return types are `unknown` on purpose: generated fetchers resolve with
965
- * whatever the server sent, so this package normalizes and validates payloads
966
- * centrally — see {@link unwrapListResult} / {@link unwrapItemResult}.
967
- */
968
- export declare interface SmrtCrudFetchers {
969
- list(params?: Record<string, unknown>): Promise<unknown>;
970
- get?(id: string): Promise<unknown>;
971
- create(data: Record<string, unknown>): Promise<unknown>;
972
- update?(id: string, data: Record<string, unknown>): Promise<unknown>;
973
- delete?(id: string): Promise<unknown>;
974
- /** Invoke a generated custom action route. */
975
- custom?(action: string, args: Record<string, unknown>, route?: SmrtWebToolRouteDescriptor): Promise<unknown>;
976
- }
977
-
978
- /**
979
- * A capability plugged into {@link createSmrtCollection}. Every hook is
980
- * optional; a capability implements only the points its slice needs. Hooks fire
981
- * in these fixed places, and capabilities run in ARRAY ORDER:
982
- *
983
- * - `contributeCacheKey` — ONCE, before the engine collection is constructed.
984
- * - `warmStart` — ONCE, before the first read (seeds the cache).
985
- * - `wrapMutation` — per mutation, BEFORE the fetcher, with a chance to handle
986
- * the write itself (offline).
987
- * - `onSettled` — per mutation, AFTER it settles (success AND fetcher-throw).
988
- * - `onAttach` — ONCE, right after the engine collection is constructed; the
989
- * ONLY place a capability registers an external (non-mutation) trigger such as
990
- * an SSE subscription.
991
- * - `teardown` — in `cleanup()`, after the engine's own cleanup.
992
- */
993
- export declare interface SmrtWebCapability<TData extends object = object> {
994
- /** Diagnostic name (e.g. `'offline-outbox'`). */
995
- readonly name: string;
996
- /**
997
- * Contribute extra cache-key segments so this capability can partition the
998
- * collection's cache (e.g. an outbox variant). Runs ONCE, before construction;
999
- * returned segments are appended to the collection's cache key/id. Return
1000
- * `undefined` (or omit) to contribute nothing.
1001
- */
1002
- contributeCacheKey?(ctx: SmrtWebCapabilityContext<TData>): string[] | undefined;
1003
- /**
1004
- * Provide rows to seed the cache before the first read — the persistence
1005
- * slice's rehydrate-from-disk path. Runs ONCE, before construction. NOTE:
1006
- * caller-supplied `initialData` (fresher same-request SSR truth) WINS over any
1007
- * capability `warmStart`. Return `undefined` to contribute no seed.
1008
- */
1009
- warmStart?(ctx: SmrtWebCapabilityContext<TData>): Promise<SmrtWebRow<TData>[] | undefined> | SmrtWebRow<TData>[] | undefined;
1010
- /**
1011
- * Intercept a mutation BEFORE its fetcher runs. Return `{ handled: true,
1012
- * result }` to take over the write (the fetcher is skipped and `result`
1013
- * reconciles the optimistic row — the offline path); return `{ handled: false
1014
- * }` or `undefined` to fall through to the real fetcher. With multiple
1015
- * capabilities the FIRST `{ handled: true }` wins and later capabilities'
1016
- * `wrapMutation` are skipped.
1017
- */
1018
- wrapMutation?(envelope: SmrtWebMutationEnvelope, ctx: SmrtWebCapabilityContext<TData>): Promise<WrapMutationOutcome> | WrapMutationOutcome;
1019
- /**
1020
- * Observe a mutation after it settles — on BOTH a successful persist and a
1021
- * fetcher throw (before the optimistic rollback propagates). Never swallows
1022
- * the error: a thrown fetcher error still rolls the transaction back.
1023
- */
1024
- onSettled?(envelope: SmrtWebMutationEnvelope, outcome: MutationSettleOutcome, ctx: SmrtWebCapabilityContext<TData>): void;
1025
- /**
1026
- * Run ONCE, right after the engine collection is constructed. The ONLY hook
1027
- * where a capability wires an external trigger (SSE subscription, focus
1028
- * listener) — `ctx.invalidate()` is callable here.
1029
- */
1030
- onAttach?(ctx: SmrtWebCapabilityContext<TData>): void;
1031
- /** Run inside `cleanup()`, AFTER the engine's own cleanup. Awaited if async. */
1032
- teardown?(ctx: SmrtWebCapabilityContext<TData>): void | Promise<void>;
1033
- }
1034
-
1035
- /**
1036
- * The context every capability hook receives — a stable, engine-free view of
1037
- * the collection being built. All fields are the exact values the factory uses
1038
- * internally (the resolved cache key/id, the same fetchers), so a capability
1039
- * keys its own storage or subscriptions off the identical discriminators.
1040
- */
1041
- export declare interface SmrtWebCapabilityContext<TData extends object = object> {
1042
- /** The generated definition this collection materializes. */
1043
- readonly definition: SmrtWebCollectionDefinition<TData>;
1044
- /** The CRUD fetchers the collection persists through. */
1045
- readonly fetchers: SmrtCrudFetchers;
1046
- /**
1047
- * The `queryKey` segments the collection's reads use — a LIVE view: from
1048
- * `onAttach`/`warmStart` onward it is the final key (including every
1049
- * capability-contributed segment); read during `contributeCacheKey` it omits
1050
- * that capability's own not-yet-applied segment. Treat as read-only.
1051
- */
1052
- readonly cacheKey: readonly string[];
1053
- /** The engine-collection id string (mirrors {@link cacheKey}). */
1054
- readonly cacheId: string;
1055
- /**
1056
- * Enter the SAME relationship-derived invalidation the factory runs after a
1057
- * settled mutation. A capability calls this to refetch this collection and its
1058
- * manifest-related collections in response to an EXTERNAL trigger (e.g. an SSE
1059
- * message) without reaching into the engine.
1060
- */
1061
- invalidate(): void;
1062
- /**
1063
- * A snapshot of the collection's current rows as plain public DTOs — the same
1064
- * projection as {@link SmrtWebCollection.toArray}, so the engine's virtual
1065
- * props never cross the boundary. The persistence slice (#1764) reads this in
1066
- * its write-back to serialize the current cache to disk. Populated by the real
1067
- * factory; a hand-built test context may omit it, so callers guard for its
1068
- * presence.
1069
- *
1070
- * SAFE ONLY FROM `onAttach` ONWARD. The factory's implementation closes over
1071
- * the engine collection, which is constructed AFTER `contributeCacheKey` and
1072
- * `warmStart` run — calling this from those earlier hooks would throw
1073
- * (temporal-dead-zone). Read rows from `onAttach`, `onSettled`, or an external
1074
- * trigger, never during construction.
1075
- */
1076
- snapshot?(): ReadonlyArray<SmrtWebRow<TData>>;
1077
- /**
1078
- * Subscribe to the collection's change notifications — the same stream as
1079
- * {@link SmrtWebCollection.subscribeChanges} (plain-DTO payloads). The
1080
- * persistence slice (#1764) wires its debounced write-back here in `onAttach`
1081
- * and detaches in `teardown`. Engine-free by construction: the payload is
1082
- * `unknown` (the capability only needs the change SIGNAL, then re-reads via
1083
- * {@link snapshot}), so no engine type crosses the seam. Populated by the real
1084
- * factory; a hand-built test context may omit it.
1085
- *
1086
- * SAFE ONLY FROM `onAttach` ONWARD (same reason as {@link snapshot}): it closes
1087
- * over the engine collection built after the pre-construction hooks.
1088
- */
1089
- subscribe?(callback: (changes: unknown) => void): SmrtWebSubscription;
1090
- }
1091
-
1092
- /**
1093
- * Opaque handle to the shared client cache / request-dedup layer. Create one
1094
- * with {@link createSmrtWebClient} and pass the SAME instance to every
1095
- * collection that should share a cache and deduplicate in-flight requests.
1096
- *
1097
- * The engine (currently a TanStack Query client) is intentionally hidden behind
1098
- * this brand so it stays swappable — do not depend on its concrete shape.
1099
- */
1100
- export declare interface SmrtWebClient {
1101
- /** Phantom brand — this handle wraps the hidden client-cache engine. */
1102
- readonly __smrtWebClient: 'SmrtWebClient';
1103
- }
1104
-
1105
- /**
1106
- * A live, cached collection of plain-DTO rows — the SMRT-owned public contract
1107
- * over the client-data engine. Exposes only the committed surface; the engine's
1108
- * own type is never named here so it stays swappable.
1109
- */
1110
- export declare interface SmrtWebCollection<TData extends object> {
1111
- /** All rows currently in the collection (plain DTOs, insertion order). */
1112
- readonly toArray: ReadonlyArray<SmrtWebRow<TData>>;
1113
- /** Number of rows currently in the collection. */
1114
- readonly size: number;
1115
- /** True when a row with `key` is present. */
1116
- has(key: string): boolean;
1117
- /** The row with `key`, or `undefined`. */
1118
- get(key: string): SmrtWebRow<TData> | undefined;
1119
- /** Resolve once the first load has completed. */
1120
- preload(): Promise<void>;
1121
- /** Tear down subscriptions and cached state. */
1122
- cleanup(): Promise<void>;
1123
- /** Subscribe to change notifications; returns a detach handle. */
1124
- subscribeChanges(callback: (changes: unknown) => void): SmrtWebSubscription;
1125
- /**
1126
- * Optimistically insert a row and persist it through the create fetcher. The
1127
- * row is visible synchronously; the returned transaction settles on the
1128
- * server outcome (see {@link SmrtWebTransaction}).
1129
- */
1130
- insert(row: SmrtWebRow<TData>): SmrtWebTransaction;
1131
- /** Optimistically update a row and persist it through the REST surface. */
1132
- update(key: string, changes: Omit<Partial<SmrtWebRow<TData>>, 'id'>): SmrtWebTransaction;
1133
- /** Optimistically delete a row and persist it through the REST surface. */
1134
- delete(key: string): SmrtWebTransaction;
1135
- /** Execute a custom action and invalidate this collection's related caches. */
1136
- action(action: string, args: Record<string, unknown>, route?: SmrtWebToolRouteDescriptor): Promise<unknown>;
1137
- }
1138
-
1139
- export declare interface SmrtWebCollectionDefinition<TData extends object = object> {
1140
- /** REST collection name (e.g. `products`). */
1141
- name: string;
1142
- /**
1143
- * Canonical qualified model identity (e.g.
1144
- * `@happyvertical/smrt-products:Product`). Generated definitions always
1145
- * provide it; optional here for source compatibility with manual literals
1146
- * authored before policy-aware web collections.
1147
- */
1148
- objectRef?: string;
1149
- /** Source class name (e.g. `Product`). */
1150
- className: string;
1151
- /** Path under the API base path (e.g. `/products`). */
1152
- endpoint: string;
1153
- /** Primary key field name (`id` for SmrtObject). */
1154
- idField: string;
1155
- /** CRUD + custom actions exposed by the api decorator config. */
1156
- actions: string[];
1157
- /**
1158
- * WebMCP/MCP tool descriptors for the exposed actions (#1812). Optional so
1159
- * hand-built definitions (older codegen, tests) still satisfy the type; a
1160
- * missing value means "no WebMCP tools to register".
1161
- */
1162
- toolDescriptors?: WebToolDescriptor[];
1163
- /** Persisted field metadata keyed by field name. */
1164
- fields: Record<string, SmrtWebFieldDefinition>;
1165
- /**
1166
- * Manifest-derived relationship edges to sibling REST collections. Drives
1167
- * relationship-derived cache invalidation: a settled mutation on this
1168
- * collection invalidates the caches of the collections these edges name.
1169
- * Optional so hand-built definitions (older codegen, tests) still satisfy the
1170
- * type; a missing value means "no derived edges".
1171
- */
1172
- relationships?: SmrtWebRelationship[];
1173
- /** Phantom row-type carrier — never present at runtime. */
1174
- _row?: TData;
1175
- }
1176
-
1177
- export declare interface SmrtWebDataQueryFacetResult {
1178
- field: string;
1179
- values: Array<{
1180
- value: SmrtWebDataQueryScalar;
1181
- count: number;
1182
- }>;
1183
- truncated: boolean;
1184
- }
1185
-
1186
- export declare type SmrtWebDataQueryFilter = {
1187
- kind: 'condition';
1188
- field: string;
1189
- operator: SmrtWebDataQueryFilterOperator;
1190
- value: SmrtWebDataQueryScalar | SmrtWebDataQueryScalar[];
1191
- } | {
1192
- kind: 'all' | 'any';
1193
- filters: SmrtWebDataQueryFilter[];
1194
- } | {
1195
- kind: 'not';
1196
- filter: SmrtWebDataQueryFilter;
1197
- };
1198
-
1199
- export declare type SmrtWebDataQueryFilterOperator = 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte' | 'in' | 'notIn' | 'like';
1200
-
1201
- export declare interface SmrtWebDataQueryRequest {
1202
- version: 1;
1203
- requestId: string;
1204
- mode: 'rows' | 'count' | 'facets';
1205
- projection?: string[];
1206
- filter?: SmrtWebDataQueryFilter;
1207
- sort?: Array<{
1208
- field: string;
1209
- direction: 'asc' | 'desc';
1210
- }>;
1211
- page?: {
1212
- kind: 'offset';
1213
- offset: number;
1214
- limit: number;
1215
- } | {
1216
- kind: 'cursor';
1217
- after?: string;
1218
- limit: number;
1219
- };
1220
- consistency?: {
1221
- mode: 'eventual' | 'snapshot';
1222
- asOf?: string;
1223
- };
1224
- facets?: Array<{
1225
- field: string;
1226
- limit: number;
1227
- }>;
1228
- }
1229
-
1230
- export declare interface SmrtWebDataQueryResult {
1231
- version: 1;
1232
- requestId: string;
1233
- queryFingerprint: string;
1234
- identityField: string;
1235
- rows: Array<Record<string, unknown>>;
1236
- page?: {
1237
- kind: 'offset';
1238
- limit: number;
1239
- offset: number;
1240
- hasMore: boolean;
1241
- } | {
1242
- kind: 'cursor';
1243
- limit: number;
1244
- nextCursor?: string;
1245
- hasMore: boolean;
1246
- };
1247
- total: SmrtWebDataQueryTotal;
1248
- facets?: SmrtWebDataQueryFacetResult[];
1249
- freshness: {
1250
- state: 'fresh' | 'stale' | 'unknown';
1251
- asOf?: string;
1252
- };
1253
- warnings: string[];
1254
- truncated: boolean;
1255
- }
1256
-
1257
- /**
1258
- * Browser transport for the canonical bounded data-query envelope (#2444).
1259
- *
1260
- * This is a textual structural mirror of `@happyvertical/smrt-types` rather
1261
- * than an import: smrt-web deliberately has no inter-SMRT dependencies. The
1262
- * authenticated server adapter owns allowlist, authorization, fingerprint,
1263
- * and byte-limit enforcement. This module gives browser callers a stable
1264
- * request/result shape and rejects malformed response envelopes before they
1265
- * reach a mounted surface.
1266
- */
1267
- export declare type SmrtWebDataQueryScalar = string | number | boolean | null;
1268
-
1269
- export declare type SmrtWebDataQueryTotal = {
1270
- kind: 'exact' | 'estimated';
1271
- value: number;
1272
- asOf?: string;
1273
- } | {
1274
- kind: 'unavailable';
1275
- reason?: string;
1276
- };
1277
-
1278
- /** Browser transport seam; REST, WebMCP, and test transports share it. */
1279
- export declare interface SmrtWebDataQueryTransport {
1280
- query(request: SmrtWebDataQueryRequest, options?: {
1281
- signal?: AbortSignal;
1282
- }): Promise<unknown>;
1283
- }
1284
-
1285
- /**
1286
- * The replay-time request for {@link dataSurfaceActionCommandTransport}: every
1287
- * action-request field except those the adapter owns — `version`, `phase`,
1288
- * `requestId`, `idempotencyKey`, and `confirmationToken`.
1289
- */
1290
- export declare type SmrtWebDataSurfaceActionCommandRequest = Omit<SmrtWebDataSurfaceActionRequest, 'version' | 'phase' | 'requestId' | 'idempotencyKey' | 'confirmationToken'>;
1291
-
1292
- export declare interface SmrtWebDataSurfaceActionCommandTransportOptions {
1293
- /** The same action transport the mounted surface uses online. */
1294
- transport: SmrtWebDataSurfaceActionTransport;
1295
- /**
1296
- * Build the action request for a queued write at its FIRST replay attempt,
1297
- * so it carries the surface's `expectedRevision` as of replay rather than
1298
- * capture. The built request is pinned durably to the write and reused on
1299
- * every later attempt (including after a reload): the server fingerprints
1300
- * the request per idempotency key, so a resend after a lost response must be
1301
- * identical to replay the recorded result instead of `idempotency_conflict`.
1302
- */
1303
- request: (command: OutboxCommand) => SmrtWebDataSurfaceActionCommandRequest | Promise<SmrtWebDataSurfaceActionCommandRequest>;
1304
- /**
1305
- * Run `preview` first and apply with its `confirmationToken` — for actions
1306
- * whose server adapter requires confirmation. Default false (apply only).
1307
- * The token is pinned before apply; a retry applies with it directly (the
1308
- * server replays a completed result) and previews again only when the token
1309
- * turns out unusable.
1310
- */
1311
- preview?: boolean;
1312
- /** Map a non-throwing result onto an outbox outcome. Default below. */
1313
- classify?: (result: SmrtWebDataSurfaceActionResult) => OutboxCommandResult;
1314
- }
1315
-
1316
- export declare interface SmrtWebDataSurfaceActionRequest {
1317
- version: 1;
1318
- requestId: string;
1319
- identity: SmrtWebDataSurfaceIdentity;
1320
- actionId: string;
1321
- phase: 'preview' | 'apply';
1322
- expectedRevision: number;
1323
- /** Required by the server for apply; retries with the same key replay safely. */
1324
- idempotencyKey?: string;
1325
- selection: SmrtWebDataSurfaceSelection;
1326
- payload?: SmrtWebDataSurfaceJsonValue;
1327
- confirmationToken?: string;
1328
- }
1329
-
1330
- export declare interface SmrtWebDataSurfaceActionResult {
1331
- version: 1;
1332
- requestId: string;
1333
- identity: SmrtWebDataSurfaceIdentity;
1334
- actionId: string;
1335
- phase: 'preview' | 'apply';
1336
- ok: boolean;
1337
- reason?: string;
1338
- confirmationToken?: string;
1339
- details?: {
1340
- [key: string]: SmrtWebDataSurfaceJsonValue;
1341
- };
1342
- }
1343
-
1344
- export declare interface SmrtWebDataSurfaceActionTransport {
1345
- action(request: SmrtWebDataSurfaceActionRequest, options?: {
1346
- signal?: AbortSignal;
1347
- }): Promise<unknown>;
1348
- }
1349
-
1350
- export declare interface SmrtWebDataSurfaceIdentity {
1351
- surfaceId: string;
1352
- kind: 'table' | 'list' | 'report' | 'custom';
1353
- subject?: {
1354
- type: string;
1355
- id: string;
1356
- label?: string;
1357
- };
1358
- }
1359
-
1360
- export declare type SmrtWebDataSurfaceJsonPrimitive = string | number | boolean | null;
1361
-
1362
- export declare type SmrtWebDataSurfaceJsonValue = SmrtWebDataSurfaceJsonPrimitive | SmrtWebDataSurfaceJsonValue[] | {
1363
- [key: string]: SmrtWebDataSurfaceJsonValue;
1364
- };
1365
-
1366
- export declare type SmrtWebDataSurfaceSelection = {
1367
- scope: 'current-page';
1368
- } | {
1369
- scope: 'explicit-ids';
1370
- rowIds: Array<string | number>;
1371
- } | {
1372
- scope: 'all-matching';
1373
- queryFingerprint: string;
1374
- };
1375
-
1376
- /**
1377
- * The minimal `EventSource` surface the subscriber uses — declared here so a
1378
- * test injects a fake without a real DOM, and so this module compiles without
1379
- * DOM `lib` beyond the ambient global. A real `EventSource` satisfies it
1380
- * structurally.
1381
- */
1382
- export declare interface SmrtWebEventSource {
1383
- /** Native reconnect fires this after (re)connect. */
1384
- onopen: ((this: unknown, ev: unknown) => unknown) | null;
1385
- /**
1386
- * Fires on a stream error. A transient drop leaves `readyState` at OPEN
1387
- * (0→…) and the browser auto-reconnects with `Last-Event-ID`; a fatal error
1388
- * (server 401 / route disabled) leaves it CLOSED.
1389
- */
1390
- onerror: ((this: unknown, ev: unknown) => unknown) | null;
1391
- /**
1392
- * Fires only for UNNAMED (`message`) events. The `_events` frames are NAMED
1393
- * (`change` / `resync`), so this never fires for them — the subscriber uses
1394
- * {@link addEventListener} instead. Present only to satisfy the structural
1395
- * type of a real EventSource.
1396
- */
1397
- onmessage: ((this: unknown, ev: unknown) => unknown) | null;
1398
- /** Register a listener for a NAMED event (`change`, `resync`). */
1399
- addEventListener(type: string, listener: (ev: {
1400
- data: string;
1401
- lastEventId: string;
1402
- }) => void): void;
1403
- /** Close the stream (stops reconnection). */
1404
- close(): void;
1405
- /** `CONNECTING` (0), `OPEN` (1), or `CLOSED` (2). */
1406
- readonly readyState: number;
1407
- }
1408
-
1409
- /** Factory that constructs an {@link SmrtWebEventSource} for a URL. */
1410
- export declare type SmrtWebEventSourceFactory = (url: string, init: {
1411
- withCredentials: boolean;
1412
- }) => SmrtWebEventSource | null | undefined;
1413
-
1414
- /**
1415
- * The ONE app-wide live-updates subscriber. Construct once (see
1416
- * {@link createSmrtWebEventSubscriber}) and pass it to every
1417
- * {@link liveInvalidation} capability.
1418
- */
1419
- export declare interface SmrtWebEventSubscriber {
1420
- /**
1421
- * The live transport: `'sse'` while the EventSource is the source of truth,
1422
- * `'polling'` when running (or downgraded to) the `_changes` fallback,
1423
- * `'idle'` before any transport starts.
1424
- */
1425
- readonly transport: SmrtWebSubscriberTransport;
1426
- /**
1427
- * Register an invalidator for a physical table. Returns an unregister
1428
- * function. Multiple invalidators may share a table (two live collections);
1429
- * a `change` for that table fires them all.
1430
- */
1431
- registerTable(table: string, invalidate: () => void): () => void;
1432
- /** Invalidate every registered table (a `resync` / `resyncRequired`). */
1433
- invalidateAll(): void;
1434
- /** Tear down the live transport and drop all registrations. */
1435
- close(): void;
1436
- }
1437
-
1438
- /** Configuration for {@link createSmrtWebEventSubscriber}. */
1439
- export declare interface SmrtWebEventSubscriberConfig {
1440
- /** Absolute or same-origin URL of the generated `_events` SSE route. */
1441
- eventsUrl: string;
1442
- /** Absolute or same-origin URL of the generated `_changes` route (fallback). */
1443
- changesUrl: string;
1444
- /** `fetch` implementation for the polling fallback. Defaults to global fetch. */
1445
- fetchFn?: typeof fetch;
1446
- /**
1447
- * Factory for the EventSource. Defaults to `new globalThis.EventSource(url,
1448
- * { withCredentials })`, guarded by a `typeof` check — when EventSource is
1449
- * absent (or the factory yields nothing) the subscriber starts on the polling
1450
- * fallback instead.
1451
- */
1452
- eventSourceFactory?: SmrtWebEventSourceFactory;
1453
- /** Poll interval for the `_changes` fallback (ms). Default 5000. */
1454
- pollIntervalMs?: number;
1455
- /** `withCredentials` for the EventSource (cookie auth). Default true. */
1456
- withCredentials?: boolean;
1457
- /**
1458
- * The RUNNING build's web-collection shape digest. When paired with
1459
- * `updateState`, a differing server hash from the `_events` manifest frame
1460
- * latches the contract update signal (#1859).
1461
- */
1462
- manifestHash?: string;
1463
- /**
1464
- * Optional update-state primitive from `createUpdateState()`. Only its
1465
- * contract signal is used here; bundle polling remains the caller's job.
1466
- */
1467
- updateState?: Pick<UpdateState, 'notifyContractUpdated'>;
1468
- }
1469
-
1470
- /**
1471
- * Field metadata emitted per column by the `@happyvertical/smrt-virt-web`
1472
- * virtual module (generated from the package manifest).
1473
- */
1474
- export declare interface SmrtWebFieldDefinition {
1475
- type: SmrtWebFieldType;
1476
- required?: boolean;
1477
- /** Whether the public field contract explicitly permits `null`. */
1478
- nullable?: boolean;
1479
- default?: unknown;
1480
- /** Developer-authored `@field({ description })` (#2046) — end-user help seed. */
1481
- description?: string;
1482
- /** Static `@field({ ui })` hints (#2046). */
1483
- ui?: SmrtWebFieldUIHints;
1484
- }
1485
-
1486
- /**
1487
- * The field types core's web-collection emission actually produces —
1488
- * relationship pseudo-columns (`oneToMany`/`manyToMany`) and STI `meta`
1489
- * internals never reach an emitted definition. Textual mirror of core's
1490
- * `WebFieldType` (this package is deliberately smrt-dependency-free, so it
1491
- * cannot import the source type); the four co-managed sites are core's
1492
- * runtime type, the ambient virt-web d.ts, the physical `@smrt/web` d.ts,
1493
- * and this mirror.
1494
- */
1495
- export declare type SmrtWebFieldType = 'text' | 'decimal' | 'boolean' | 'integer' | 'datetime' | 'json' | 'foreignKey' | 'crossPackageRef';
1496
-
1497
- /** Static `@field({ ui })` hints (#2046) — the field-policy rail seed. */
1498
- export declare interface SmrtWebFieldUIHints {
1499
- basic?: boolean;
1500
- group?: string;
1501
- order?: number;
1502
- locked?: boolean;
1503
- }
1504
-
1505
- /**
1506
- * A single mutation described in SMRT-owned terms — handed to
1507
- * {@link SmrtWebCapability.wrapMutation} and {@link SmrtWebCapability.onSettled}
1508
- * so a capability can inspect or intercept a write without touching the engine's
1509
- * transaction type.
1510
- */
1511
- export declare interface SmrtWebMutationEnvelope {
1512
- /** Which mutation kind this describes. */
1513
- readonly kind: 'insert' | 'update' | 'delete';
1514
- /** The row key: the client-local id on insert, else the target row's id. */
1515
- readonly key: string;
1516
- /**
1517
- * The write payload: the full row on insert, the changed fields on update,
1518
- * and an empty object on delete (the key carries the target).
1519
- */
1520
- readonly data: Record<string, unknown>;
1521
- /**
1522
- * The server `updated_at` / `updatedAt` value the mutation is based on, when
1523
- * known. Update/delete handlers capture this from the original row so offline
1524
- * replay can preserve the sync/apply conflict guard even when the write
1525
- * payload only carries changed fields (or no fields for delete).
1526
- */
1527
- readonly baseUpdatedAt?: string;
1528
- }
1529
-
1530
- export declare interface SmrtWebQuery<TData extends object = object> {
1531
- readonly state: SmrtWebQueryState<TData>;
1532
- readonly request: SmrtWebDataQueryRequest | undefined;
1533
- execute(request: SmrtWebDataQueryRequest, options?: SmrtWebQueryRunOptions): Promise<SmrtWebDataQueryResult>;
1534
- refresh(options?: Omit<SmrtWebQueryRunOptions, 'mode' | 'force'>): Promise<SmrtWebDataQueryResult | undefined>;
1535
- retry(): Promise<SmrtWebDataQueryResult | undefined>;
1536
- subscribe(listener: (state: SmrtWebQueryState<TData>) => void): () => void;
1537
- subscribeLive(): SmrtWebQueryLiveSubscription | undefined;
1538
- invalidate(): void;
1539
- dispose(): void;
1540
- }
1541
-
1542
- export declare interface SmrtWebQueryLiveSubscription {
1543
- unsubscribe(): void;
1544
- reconnect(): void;
1545
- }
1546
-
1547
- /** How a query run is allowed to affect the visible query state. */
1548
- export declare type SmrtWebQueryMode = 'visible' | 'background' | 'prefetch' | 'silent';
1549
-
1550
- export declare interface SmrtWebQueryPage {
1551
- kind: 'offset' | 'cursor';
1552
- limit: number;
1553
- offset?: number;
1554
- nextCursor?: string;
1555
- hasMore: boolean;
1556
- }
1557
-
1558
- export declare interface SmrtWebQueryRunOptions {
1559
- mode?: SmrtWebQueryMode;
1560
- signal?: AbortSignal;
1561
- /** Cancel this run after the given number of milliseconds. */
1562
- deadlineMs?: number;
1563
- /** Bypass the keyed cache and execute a fresh request (used by refresh). */
1564
- force?: boolean;
1565
- }
1566
-
1567
- export declare interface SmrtWebQueryState<TData extends object = object> {
1568
- readonly rows: ReadonlyArray<TData>;
1569
- readonly page: SmrtWebQueryPage | undefined;
1570
- readonly total: SmrtWebDataQueryResult['total'] | undefined;
1571
- readonly loading: boolean;
1572
- readonly refreshing: boolean;
1573
- readonly stale: boolean;
1574
- readonly error: unknown;
1575
- readonly lastUpdated: number | undefined;
1576
- readonly result: SmrtWebDataQueryResult | undefined;
1577
- }
1578
-
1579
- export declare interface SmrtWebQueryTransport extends SmrtWebDataQueryTransport {
1580
- /** Subscribe to changes for this exact query, not the entire collection. */
1581
- subscribe?(request: SmrtWebDataQueryRequest, onResult: (result: unknown) => void, options?: {
1582
- signal?: AbortSignal;
1583
- }): SmrtWebQueryLiveSubscription | {
1584
- unsubscribe(): void;
1585
- };
1586
- }
1587
-
1588
- /**
1589
- * A manifest-derived edge from this collection to a sibling REST collection,
1590
- * emitted by the `@happyvertical/smrt-virt-web` virtual module. When a mutation
1591
- * on this collection settles, the caches of the collections named by these
1592
- * edges are invalidated (relationship-derived invalidation, #1761), so a
1593
- * dependent view refetches without any hand-wired cache key.
1594
- *
1595
- * SMRT-owned data — no client-engine (`@tanstack/*`) type appears here, so it
1596
- * stays inside the engine-absorption boundary.
1597
- */
1598
- export declare interface SmrtWebRelationship {
1599
- /** The declaring field carrying the relationship (e.g. `groupId`, `items`). */
1600
- field: string;
1601
- /** The relationship kind, mirroring the manifest field type. */
1602
- kind: SmrtWebRelationshipKind;
1603
- /** REST collection name the edge resolves to (e.g. `ad_groups`). */
1604
- relatedCollection: string;
1605
- }
1606
-
1607
- /** The relationship kinds a generated web collection edge can describe. */
1608
- export declare type SmrtWebRelationshipKind = 'foreignKey' | 'crossPackageRef' | 'oneToMany' | 'manyToMany';
1609
-
1610
- /**
1611
- * Raised when a generated-client call resolved with an error payload
1612
- * (`{ error: string }` from the generated REST routes) or an unexpected shape.
1613
- * Thrown inside a mutation handler, this triggers the automatic rollback of
1614
- * optimistic state. HTTP 5xx failures are always opaque: their server payload
1615
- * is neither parsed nor retained because it may contain internal detail.
1616
- */
1617
- export declare class SmrtWebRequestError extends Error {
1618
- readonly payload: unknown;
1619
- readonly status?: number;
1620
- readonly code?: string;
1621
- constructor(message: string, payload?: unknown, status?: number, code?: string);
1622
- }
1623
-
1624
- /** A row as stored in the client collection: the DTO plus a required key. */
1625
- export declare type SmrtWebRow<TData extends object> = TData & {
1626
- id: string;
1627
- };
1628
-
1629
- /** Which transport a subscriber is currently using. */
1630
- export declare type SmrtWebSubscriberTransport = 'sse' | 'polling' | 'idle';
1631
-
1632
- /** A change-subscription handle. Call {@link unsubscribe} to detach. */
1633
- export declare interface SmrtWebSubscription {
1634
- unsubscribe(): void;
1635
- }
1636
-
1637
- export declare interface SmrtWebToolRouteDescriptor {
1638
- method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
1639
- scope: 'item' | 'collection';
1640
- /** Route segments below the collection endpoint; dynamic segments use `[x]`. */
1641
- path: string[];
1642
- /** Transport names rewritten by the tool schema (e.g. `actionId` → `id`). */
1643
- parameterAliases?: Record<string, string>;
1644
- /** The generated method accepts one `options` bag as its sole argument. */
1645
- optionsBag?: boolean;
1646
- }
1647
-
1648
- /**
1649
- * A pending optimistic mutation. Await {@link isPersisted} to observe the
1650
- * server outcome: it resolves once the write has been persisted through the
1651
- * REST surface, and rejects (rolling the optimistic state back) on error.
1652
- */
1653
- export declare interface SmrtWebTransaction {
1654
- readonly isPersisted: {
1655
- readonly promise: Promise<unknown>;
1656
- };
1657
- }
1658
-
1659
- /**
1660
- * @happyvertical/smrt-web — offline outbox shared types (#1762).
1661
- *
1662
- * The SMRT-owned vocabulary the outbox modules (`durable-queue`, `leader`,
1663
- * `engine`) and the public surface (`offline.ts`) share. Kept in its own leaf
1664
- * module — imported by everything, importing nothing from the package — so there
1665
- * is no cycle back through `index.ts` and the whole outbox stays inside the
1666
- * engine-absorption boundary (#1761): NOT ONE `@tanstack/*` type appears here.
1667
- *
1668
- * The sync-apply op/status/reason names mirror
1669
- * `packages/core/src/sync/apply.ts` (the replay target) verbatim, so a queue row
1670
- * maps to a batch item and a batch result maps to a state transition with no
1671
- * translation layer. smrt-web has no inter-smrt dependency (dependency-DAG
1672
- * guardrails), so these are re-declared here rather than imported from core.
1673
- */
1674
- /**
1675
- * Mutation kinds the sync-apply batch endpoint accepts. Mirrors core's
1676
- * `SyncApplyOp`. The outbox maps the capability seam's insert/update/delete
1677
- * envelope kinds onto these (`insert → create`).
1678
- */
1679
- declare type SyncApplyOp = 'create' | 'update' | 'delete';
1680
-
1681
- /**
1682
- * Machine-readable reasons a sync-apply item was `conflict` or `rejected`.
1683
- * Mirrors core's `SyncApplyReason` union. Drives the engine's result mapping:
1684
- * `auth_required`/`forbidden` pause the loop; `write_failed` retries; the rest
1685
- * are terminal.
1686
- */
1687
- declare type SyncApplyReason = 'invalid_item' | 'invalid_id' | 'invalid_payload' | 'unknown_object' | 'op_not_allowed' | 'auth_required' | 'forbidden' | 'not_found' | 'id_conflict' | 'write_failed' | 'stale_write' | 'create_conflict';
1688
-
1689
- /**
1690
- * A sync-state transition delivered to {@link OfflineOutboxConfig.onSyncStateChange}.
1691
- * A PUSH callback (not a subscribable store): smrt-svelte wraps it into a
1692
- * reactive binding later (#1762 follow-on). Carries enough for an app to render
1693
- * a per-row indicator and a retry affordance.
1694
- */
1695
- export declare interface SyncStateEvent {
1696
- /** The queue row's `itemId` (its idempotency handle) this state is for. */
1697
- itemId: string;
1698
- /** The client-generated row UUID the mutation targets. */
1699
- rowId: string;
1700
- /** The collection route segment (e.g. `products`). */
1701
- object: string;
1702
- /** The new observable state. */
1703
- state: OutboxSyncState;
1704
- /** How many replay attempts have run (0 until the first send). */
1705
- attempts: number;
1706
- /** The last replay error message, when `state` is `pending` after a retry. */
1707
- error?: string;
1708
- }
1709
-
1710
- /** Reject generated REST error envelopes while preserving opaque successes. */
1711
- export declare function throwIfSmrtWebError(result: unknown, context: string): unknown;
1712
-
1713
- /**
1714
- * Normalize a generated-client item result (create/update) to a row.
1715
- * `{ error }` payloads become failures — inside mutation handlers this is what
1716
- * makes optimistic state roll back.
1717
- */
1718
- export declare function unwrapItemResult(result: unknown, context: string): Record<string, unknown>;
1719
-
1720
- /**
1721
- * Normalize a generated-client list result to an array of rows.
1722
- *
1723
- * The generated REST routes return a bare JSON array; `{ error }` payloads are
1724
- * surfaced as failures. The `{ data: [...] }` envelope is tolerated for
1725
- * ApiResponse-shaped clients (e.g. a mock client).
1726
- */
1727
- export declare function unwrapListResult(result: unknown, collectionName: string): Array<Record<string, unknown>>;
1728
-
1729
- /** The two independent update signals plus their derived union. */
1730
- export declare interface UpdateAvailableState {
1731
- /** The client bundle changed on the server (SvelteKit `updated`). */
1732
- readonly bundle: boolean;
1733
- /** The API contract (manifest hash) changed across this load. */
1734
- readonly contract: boolean;
1735
- /** `bundle || contract` — either means an update is available. */
1736
- readonly updateAvailable: boolean;
1737
- }
1738
-
1739
- /** The framework-free update-available primitive. */
1740
- export declare interface UpdateState {
1741
- /** The current signal state (a fresh immutable snapshot). */
1742
- get(): UpdateAvailableState;
1743
- /**
1744
- * Subscribe to state changes; the callback fires immediately with the current
1745
- * state and again on every transition. Returns an unsubscribe function.
1746
- */
1747
- subscribe(callback: (state: UpdateAvailableState) => void): () => void;
1748
- /**
1749
- * Push the BUNDLE signal — call from the consumer/smrt-svelte binding when
1750
- * SvelteKit's `updated` store flips true. Idempotent: once set it stays set
1751
- * (a bundle update does not un-happen).
1752
- */
1753
- notifyBundleUpdated(): void;
1754
- /**
1755
- * Push the API CONTRACT signal — call when a live transport observes a server
1756
- * `manifestHash` that differs from this running build (#1859). Idempotent:
1757
- * once set it stays set (a contract mismatch does not un-happen).
1758
- */
1759
- notifyContractUpdated(): void;
1760
- /**
1761
- * Resolves once the async contract detection has settled (compared the running
1762
- * hash against the persisted last-seen value and fired the signal if needed).
1763
- * Primarily for tests/SSR; UI code just subscribes.
1764
- */
1765
- readonly ready: Promise<void>;
1766
- /** Detach the durable resource (does NOT wipe — that's wipeDurableStore). */
1767
- dispose(): void;
1768
- }
1769
-
1770
- /** Configuration for {@link createUpdateState}. */
1771
- export declare interface UpdateStateConfig {
1772
- /**
1773
- * The RUNNING build's web-collection shape digest — the `manifestHash`
1774
- * constant the generated `@happyvertical/smrt-virt-web` module exports
1775
- * (#1764). Compared against the persisted last-seen value to detect a contract
1776
- * change across loads. Omit to disable contract detection (bundle-only).
1777
- */
1778
- manifestHash?: string;
1779
- /**
1780
- * The durable-store identity the last-seen manifest hash is namespaced under —
1781
- * folds api base / tenant / identity (and, yes, the manifest hash itself, but
1782
- * see below). Shared with persistence/outbox so one {@link wipeDurableStore}
1783
- * clears the last-seen record too.
1784
- *
1785
- * NOTE the namespace's OWN `manifestHash` segment should be a STABLE value
1786
- * (e.g. the app's api-version or a constant), NOT the running build hash —
1787
- * otherwise a contract change would move the namespace and the compare would
1788
- * never find the prior value (always "first run"). Contract change is detected
1789
- * by the STORED VALUE under a stable namespace, not by the namespace moving.
1790
- */
1791
- namespace: DurableStoreKey;
1792
- }
1793
-
1794
- /** Validate that an opaque cache handle came from {@link createSmrtWebClient}. */
1795
- export declare function validateSmrtWebClient(client: SmrtWebClient): void;
1796
-
1797
- /**
1798
- * A validated, frozen intent.
1799
- *
1800
- * `kind: 'intent'` and `id` are the exported identity a `smrt-playbooks`
1801
- * step reference (`{ kind: 'intent', id }`) names, and this object is
1802
- * directly assignable to that package's `PlaybookIntentRecord` seam —
1803
- * `{ id, classification, planes }` — so {@link resolveViewIntent} can be
1804
- * passed as its `intents` resolver unchanged.
1805
- */
1806
- export declare interface ViewIntent {
1807
- readonly kind: 'intent';
1808
- readonly id: string;
1809
- readonly description: string;
1810
- readonly inputSchema: Record<string, unknown>;
1811
- /** Resolved through the shared fail-closed rule at declaration time. */
1812
- readonly classification: WebMcpCapabilityClassification;
1813
- readonly target: ViewIntentTarget;
1814
- /**
1815
- * An intent moves mounted browser state, so it is browser-valid only. A
1816
- * server-side agent reaches one through the #2446 command/ack bridge, which
1817
- * a playbook must declare explicitly.
1818
- */
1819
- readonly planes: readonly ['browser'];
1820
- }
1821
-
1822
- /** The mounted identity a binding supplies for an intent's lifetime. */
1823
- export declare type ViewIntentBinding = {
1824
- registry: 'control';
1825
- registryPort: ViewIntentControlRegistryPort;
1826
- identity: ViewIntentControlIdentity;
1827
- } | {
1828
- registry: 'dataSurface';
1829
- registryPort: ViewIntentDataSurfaceRegistryPort;
1830
- identity: ViewIntentDataSurfaceIdentity;
1831
- };
1832
-
1833
- /**
1834
- * Control commands a view intent may dispatch. Structurally mirrors
1835
- * `ControlCommandAction` in `@happyvertical/smrt-ui/forms`; this package
1836
- * cannot import that one (see AGENTS.md "No inter-smrt dependencies").
1837
- */
1838
- export declare type ViewIntentControlAction = 'focus' | 'reveal' | 'highlight' | 'explain' | 'validate' | 'stage' | 'apply' | 'discard' | 'clear' | 'undo';
1839
-
1840
- /** A mounted control's full registry key, subject included. */
1841
- export declare interface ViewIntentControlIdentity {
1842
- formId: string;
1843
- controlId: string;
1844
- subject?: ViewIntentSubject;
1845
- }
1846
-
1847
- /**
1848
- * The `ControlInteractionRegistry` surface an intent uses. Declared
1849
- * structurally so this package takes no dependency on
1850
- * `@happyvertical/smrt-ui`; the real registry satisfies it.
1851
- */
1852
- export declare interface ViewIntentControlRegistryPort {
1853
- execute(command: {
1854
- action: ViewIntentControlAction;
1855
- identity: ViewIntentControlIdentity;
1856
- value?: unknown;
1857
- durationMs?: number;
1858
- revision?: number;
1859
- }, context?: {
1860
- source: 'agent';
1861
- }): Promise<{
1862
- ok: boolean;
1863
- reason?: string;
1864
- }>;
1865
- }
1866
-
1867
- /**
1868
- * A control-registry target: the intent dispatches exactly one
1869
- * `ControlInteractionRegistry` command against a mounted control.
1870
- *
1871
- * `formId`/`controlId` are the statically declared half of the identity. A
1872
- * binding supplies the mounted identity and must MATCH anything declared
1873
- * here — a declaration is authority over a binding, never the other way
1874
- * round.
1875
- */
1876
- export declare interface ViewIntentControlTarget {
1877
- registry: 'control';
1878
- action: ViewIntentControlAction;
1879
- formId?: string;
1880
- controlId?: string;
1881
- }
1882
-
1883
- /** A mounted data surface's full registry key, subject included. */
1884
- export declare interface ViewIntentDataSurfaceIdentity {
1885
- surfaceId: string;
1886
- kind: ViewIntentDataSurfaceKind;
1887
- subject?: ViewIntentSubject;
1888
- }
1889
-
1890
- /** Mirrors `DataSurfaceIdentity['kind']` in `@happyvertical/smrt-ui/data`. */
1891
- export declare type ViewIntentDataSurfaceKind = 'table' | 'list' | 'report' | 'custom';
1892
-
1893
- /** The `DataSurfaceRegistry` surface an intent uses. */
1894
- export declare interface ViewIntentDataSurfaceRegistryPort {
1895
- inspect(identity: ViewIntentDataSurfaceIdentity): {
1896
- revision: number;
1897
- } | undefined;
1898
- execute(command: {
1899
- version: 1;
1900
- commandId: string;
1901
- identity: ViewIntentDataSurfaceIdentity;
1902
- expectedRevision: number;
1903
- controlId: string;
1904
- payload?: unknown;
1905
- }): Promise<{
1906
- ok: boolean;
1907
- revision?: number;
1908
- reason?: string;
1909
- }>;
1910
- }
1911
-
1912
- /**
1913
- * A data-surface target: the intent dispatches one
1914
- * `DataSurfaceVisibleCommand` — a browser-visible state transition, never a
1915
- * server-side query or mutation.
1916
- */
1917
- export declare interface ViewIntentDataSurfaceTarget {
1918
- registry: 'dataSurface';
1919
- /** The visible-command `controlId` the mounted surface implements. */
1920
- controlId: string;
1921
- surfaceId?: string;
1922
- kind?: ViewIntentDataSurfaceKind;
1923
- }
1924
-
1925
- /**
1926
- * The literal object passed to {@link defineIntent}. Every field is data;
1927
- * none is a function, a URL, or a route.
1928
- */
1929
- export declare interface ViewIntentDeclaration {
1930
- /**
1931
- * Stable, namespaced identity — at least one dot, lowercase, e.g.
1932
- * `orders.filter_by_status`. It is the intent's name in the manifest
1933
- * (#2591) and in a playbook step (`{ kind: 'intent', id }`, #2589), so it
1934
- * must not be derived from anything that can change (a namespace, a
1935
- * generated tool name, a route).
1936
- */
1937
- id: string;
1938
- /** Human/agent-readable description. Becomes the tool description. */
1939
- description: string;
1940
- /** JSON Schema for the tool's arguments. Must be plain JSON. */
1941
- inputSchema?: Record<string, unknown>;
1942
- /**
1943
- * Partial capability declaration resolved by the shared fail-closed rule
1944
- * (#2587). Omitted entirely, an intent classifies destructive,
1945
- * non-idempotent, open-world.
1946
- */
1947
- capability?: WebMcpCapabilityDeclaration;
1948
- /** Which registry this compiles into, and what it addresses there. */
1949
- target: ViewIntentTarget;
1950
- }
1951
-
1952
- /**
1953
- * The optional record a registry identity is qualified by. Rich forms use it
1954
- * to tell apart controls that share a `formId`/`controlId` across records.
1955
- * Structurally mirrors `ControlIdentity['subject']` /
1956
- * `DataSurfaceIdentity['subject']` in `@happyvertical/smrt-ui`.
1957
- */
1958
- export declare interface ViewIntentSubject {
1959
- type: string;
1960
- id: string;
1961
- label?: string;
1962
- }
1963
-
1964
- export declare type ViewIntentTarget = ViewIntentControlTarget | ViewIntentDataSurfaceTarget;
1965
-
1966
- /** Derive the WebMCP tool name for an intent id. */
1967
- export declare function viewIntentToolName(id: string): string;
1968
-
1969
- /**
1970
- * A bespoke tool spec, structurally identical to
1971
- * `WebMcpBespokeToolSpec` in `./webmcp.js`. Declared here rather than
1972
- * imported so this module stays free of even a type edge to the registrar
1973
- * entry.
1974
- */
1975
- export declare interface ViewIntentToolSpec {
1976
- name: string;
1977
- description: string;
1978
- inputSchema: Record<string, unknown>;
1979
- annotations: WebMcpCapabilityAnnotations;
1980
- execute: (args: Record<string, unknown>) => Promise<string>;
1981
- }
1982
-
1983
- /**
1984
- * A hand-written browser tool from application code — not generated from a
1985
- * `@smrt()` model. Structurally identical to the WebMCP `registerTool` input;
1986
- * kept as a separate type so this framework-agnostic module never depends on
1987
- * a UI layer's tool-spec type.
1988
- */
1989
- export declare interface WebMcpBespokeToolSpec {
1990
- name: string;
1991
- description: string;
1992
- inputSchema: Record<string, unknown>;
1993
- annotations?: {
1994
- readOnlyHint?: boolean;
1995
- destructiveHint?: boolean;
1996
- idempotentHint?: boolean;
1997
- openWorldHint?: boolean;
1998
- untrustedContentHint?: boolean;
1999
- };
2000
- execute: (args: Record<string, unknown>, options?: WebMcpToolExecutionOptions) => string | Promise<string>;
2001
- }
2002
-
2003
- /** The MCP-shaped annotation set a resolved classification emits. */
2004
- declare interface WebMcpCapabilityAnnotations {
2005
- readOnlyHint: boolean;
2006
- destructiveHint: boolean;
2007
- idempotentHint: boolean;
2008
- openWorldHint: boolean;
2009
- untrustedContentHint: boolean;
2010
- }
2011
-
2012
- /** A fully resolved classification. */
2013
- declare interface WebMcpCapabilityClassification {
2014
- effect: WebMcpToolEffect;
2015
- /**
2016
- * Derived, never declared: every non-read effect is annotated destructive
2017
- * to the browser, so a `write` capability cannot claim the MCP
2018
- * additive-only guarantee.
2019
- */
2020
- destructive: boolean;
2021
- idempotent: boolean;
2022
- openWorld: boolean;
2023
- }
2024
-
2025
- /**
2026
- * An author-supplied, partial classification. Any omitted field resolves
2027
- * through the fail-closed rule on {@link resolveDeclaredCapability}, never
2028
- * through a CRUD- or name-based guess.
2029
- */
2030
- declare interface WebMcpCapabilityDeclaration {
2031
- effect?: WebMcpToolEffect;
2032
- idempotent?: boolean;
2033
- openWorld?: boolean;
2034
- }
2035
-
2036
- export declare interface WebMcpExposurePolicy {
2037
- /** Allowed effects. Omitted means read-only exposure. */
2038
- effects?: readonly WebMcpToolEffect[];
2039
- /** Prefix every registered tool name with `<namespace>_`. */
2040
- namespace?: string;
2041
- /** Optional maximum tools registered by one call. */
2042
- maxTools?: number;
2043
- }
2044
-
2045
- /** Accepted legacy collection definitions and canonical per-tool definitions. */
2046
- export declare type WebMcpRegistrationDefinition = SmrtWebCollectionDefinition | WebMcpToolDefinition;
2047
-
2048
- /** Disposes a registration and exposes completion of browser registration. */
2049
- export declare interface WebMcpRegistrationDisposer {
2050
- (): void;
2051
- /** Rejects if the browser rejects any tool; all sibling tools are aborted. */
2052
- readonly ready: Promise<void>;
2053
- }
2054
-
2055
- /**
2056
- * Canonical data-only definition for one API-backed browser tool. Unlike a
2057
- * collection definition, this does not imply that a list route or client cache
2058
- * exists. Generated definitions carry explicit semantics; manual legacy
2059
- * literals may omit them and register with fail-closed defaults.
2060
- */
2061
- export declare interface WebMcpToolDefinition extends WebToolDescriptor {
2062
- /** Stable definition identity is `(collection, action)`. */
2063
- collection: string;
2064
- /** Canonical qualified row-model identity. */
2065
- objectRef: string;
2066
- /** Row-model class used by the shared MCP tool vocabulary. */
2067
- className: string;
2068
- endpoint: string;
2069
- idField: string;
2070
- idType: 'uuid' | 'text';
2071
- /** Complete generated route metadata, including CRUD actions. */
2072
- route: SmrtWebToolRouteDescriptor;
2073
- /** Cache-invalidation edges for materialized sibling collections. */
2074
- relationships: SmrtWebRelationship[];
2075
- }
2076
-
2077
- /**
2078
- * The one capability classification rule this package applies, extracted so
2079
- * every declaration site in smrt-web shares a single implementation (#2587,
2080
- * #2588).
2081
- *
2082
- * Two sites consume it today: `webmcp.ts` (canonical definitions trusted
2083
- * through it directly, and its legacy CRUD switch's fail-closed default
2084
- * branch) and `intents.ts` (a declared view intent, which is never a CRUD
2085
- * verb and so resolves through the declaration rule alone). Keeping the rule
2086
- * here rather than private to `webmcp.ts` is what lets the intent path be a
2087
- * dependency-free module that never pulls the client-data engine.
2088
- *
2089
- * This module structurally mirrors `CapabilityEffect` / `CapabilityDeclaration`
2090
- * / `CapabilityClassification` in `@happyvertical/smrt-types` rather than
2091
- * importing them — this package's dependency-DAG guardrails keep it free of
2092
- * every `@happyvertical/*` dependency (see AGENTS.md "No inter-smrt
2093
- * dependencies"), the same reason `data-query.ts` mirrors that package's
2094
- * bounded query envelope structurally instead of importing it.
2095
- */
2096
- /**
2097
- * Browser/agent-visible effect classification for a capability. `'read'`
2098
- * never mutates; `'write'` mutates within the application; `'destructive'`
2099
- * may remove or irreversibly change data.
2100
- */
2101
- export declare type WebMcpToolEffect = 'read' | 'write' | 'destructive';
2102
-
2103
- /** Host-supplied per-invocation options, distinct from registration lifetime. */
2104
- export declare interface WebMcpToolExecutionOptions {
2105
- /** Caller cancellation. Older hosts may omit execution options entirely. */
2106
- signal?: AbortSignal;
2107
- }
2108
-
2109
- /**
2110
- * Thrown synchronously when a name is already held. Carries the colliding
2111
- * name and the owner that holds it so a host can report both without parsing
2112
- * the message.
2113
- */
2114
- export declare class WebMcpToolNameCollisionError extends Error {
2115
- readonly toolName: string;
2116
- /** The path that already holds {@link toolName}. */
2117
- readonly owner: WebMcpToolNameOwner;
2118
- /** The path that tried to take it. */
2119
- readonly requestedBy: WebMcpToolNameOwner;
2120
- constructor(toolName: string,
2121
- /** The path that already holds {@link toolName}. */
2122
- owner: WebMcpToolNameOwner,
2123
- /** The path that tried to take it. */
2124
- requestedBy: WebMcpToolNameOwner);
2125
- }
2126
-
2127
- /**
2128
- * Document-global WebMCP tool-name lock (#2613).
2129
- *
2130
- * A browser tool name is derived independently on three paths that all end at
2131
- * the same `document.modelContext`:
2132
- *
2133
- * 1. **generated** model tools — `<namespace>_<model>_<action>`, built by
2134
- * `registerWebMcpTools` from the exposure policy;
2135
- * 2. **ui** — the six fixed `smrt_ui_*` tools a UI layer registers under a
2136
- * configurable prefix (`registerWebMcpUiTools` in `@happyvertical/smrt-svelte`);
2137
- * 3. **intent** / **bespoke** — declared view intents (#2588) and hand-written
2138
- * `useWebMcpTool` tools, whose names come from the declaration `id` or the
2139
- * spec `name`.
2140
- *
2141
- * None of the three can see the others at declaration time: a namespace and a
2142
- * UI prefix are runtime values a declaration never sees. `registerWebMcpTools`
2143
- * rejects duplicates WITHIN its own prospective set, but nothing checked
2144
- * ACROSS the paths, so a collision reached the host — which rejects the later
2145
- * registration and leaves the tool silently absent.
2146
- *
2147
- * This module is the shared reservation table those paths coordinate through.
2148
- * It is an AVAILABILITY guard, not a trust boundary: every path stays
2149
- * fail-closed and browser-only regardless, and a collision loses a tool rather
2150
- * than granting one. The authenticated REST surface remains the auth, tenant,
2151
- * and field-write boundary.
2152
- *
2153
- * ## Why the table lives on the document, not in this module
2154
- *
2155
- * The lock must be shared by every registrar reachable from one page, which
2156
- * spans package boundaries (`smrt-web` and a UI layer) and therefore possibly
2157
- * more than one copy of this module — a second bundle chunk, a duplicated
2158
- * dependency, or an HMR reload that replaces module state while the host
2159
- * registry keeps every tool it already accepted. Module-level state loses the
2160
- * reservations in all of those cases, and a lost reservation is worse than no
2161
- * lock: it lets a duplicate through, or (on the release side) strands a name
2162
- * forever. The table is therefore stored on the `document` object itself under
2163
- * a cross-realm `Symbol.for` key, which every copy of this module resolves to
2164
- * the same slot.
2165
- *
2166
- * The table is additionally stamped with the `modelContext` it was built for.
2167
- * When a host installs a NEW model context on the same document — a fresh
2168
- * origin-trial registry, or a test harness swapping the double — every tool
2169
- * the old registry held is gone, so the table resets rather than stranding
2170
- * those names.
2171
- *
2172
- * This module is dependency-free by design (see the package's dependency-DAG
2173
- * guardrails) and ships as the `@happyvertical/smrt-web/webmcp-tool-names`
2174
- * entry so a UI layer can reserve its fixed tool names without pulling in the
2175
- * client-data engine.
2176
- */
2177
- /** Which registration path holds a reserved WebMCP tool name. */
2178
- export declare type WebMcpToolNameOwner = 'generated' | 'ui' | 'intent' | 'bespoke';
2179
-
2180
- /**
2181
- * The owner currently holding `name` on this document, or undefined. For
2182
- * diagnostics and tests; registration paths use
2183
- * {@link reserveWebMcpToolNames}, which is atomic.
2184
- */
2185
- export declare function webMcpToolNameOwner(name: string, options?: ReserveWebMcpToolNamesOptions): WebMcpToolNameOwner | undefined;
2186
-
2187
- /** Releases every name one reservation holds. Idempotent. */
2188
- export declare interface WebMcpToolNameReservation {
2189
- release(): void;
2190
- }
2191
-
2192
- /**
2193
- * One generated collection definition: everything needed to construct a client
2194
- * collection over the generated REST surface. The `_row` property is a phantom
2195
- * type carrier threaded through codegen — it never exists at runtime, it only
2196
- * lets factories infer the row type from a definition.
2197
- */
2198
- /**
2199
- * One WebMCP / MCP tool descriptor for a collection action (#1812). Emitted by
2200
- * the core web-collections codegen as PLAIN DATA (this package has no smrt
2201
- * dependency), shaped to match Chrome's `document.modelContext.registerTool`
2202
- * input — see https://developer.chrome.com/docs/ai/webmcp. Consumed by
2203
- * {@link registerWebMcpTools} in `./webmcp`.
2204
- */
2205
- export declare interface WebToolDescriptor {
2206
- /** The action this tool performs (`list` | `get` | … | a custom method name). */
2207
- action: string;
2208
- /** Tool id, `${className.toLowerCase()}_${action}` (e.g. `product_list`). */
2209
- name: string;
2210
- description: string;
2211
- /** JSON Schema for the tool's arguments. */
2212
- inputSchema: Record<string, unknown>;
2213
- /** True for non-mutating reads → WebMCP `annotations.readOnlyHint`. */
2214
- readOnly: boolean;
2215
- /** Capability effect used by WebMCP exposure policy. */
2216
- effect?: 'read' | 'write' | 'destructive';
2217
- /** Whether repeating the tool with the same arguments is safe. */
2218
- idempotent?: boolean;
2219
- /** Whether the tool may interact outside the SMRT application. */
2220
- openWorld?: boolean;
2221
- /** Generated custom-route transport metadata. */
2222
- route?: SmrtWebToolRouteDescriptor;
2223
- }
2224
-
2225
- /**
2226
- * Clear every durable artifact registered under `namespace`, then drop the
2227
- * namespace. This is the single teardown point a logout / tenant-switch calls:
2228
- * it fans out across BOTH the outbox and persistence slices via the registry,
2229
- * so neither module needs to import the other.
2230
- *
2231
- * Best-effort: a resource whose `clear()` rejects does not abort the sweep —
2232
- * every registered resource is still cleared (a wipe is a teardown, not a
2233
- * transaction). A safe no-op on an unknown or empty namespace.
2234
- */
2235
- export declare function wipeDurableStore(namespace: string): Promise<void>;
2236
-
2237
- /** The outcome handed to {@link SmrtWebCapability.wrapMutation}. */
2238
- export declare type WrapMutationOutcome = {
2239
- handled: true;
2240
- result: unknown;
2241
- } | {
2242
- handled: false;
2243
- } | undefined;
2244
-
2245
- export { }
1
+ /**
2
+ * @happyvertical/smrt-web — browser client data runtime (#1761).
3
+ *
4
+ * A typed collection factory that materializes the manifest-generated web
5
+ * collection definitions (`@happyvertical/smrt-virt-web`) as cached, reactive
6
+ * collections over the generated SMRT REST surface.
7
+ *
8
+ * This package is the **engine-absorption boundary**: the client-data engine
9
+ * (currently TanStack DB) is an implementation detail held entirely inside
10
+ * this module. Its types never appear on the public API — collections are
11
+ * handed back as the SMRT-owned {@link SmrtWebCollection}, and the shared cache
12
+ * as the opaque {@link SmrtWebClient} — so the engine stays swappable without a
13
+ * consumer-visible break. Consumers never import `@tanstack/*` directly.
14
+ *
15
+ * Framework-agnostic by construction: this entry imports no UI framework.
16
+ * Svelte live-query bindings ship separately (see PRD #1755) so this core never
17
+ * pulls the Svelte-only `@tanstack/svelte-db` export condition.
18
+ *
19
+ * Scope of this slice:
20
+ * - stale-while-revalidate reads (a `staleTimeMs` window, background revalidation)
21
+ * - concurrent-read dedup (one network request per in-flight collection load)
22
+ * - optimistic create that persists through the generated REST surface and
23
+ * rolls back automatically when the server errors
24
+ * - relationship-derived invalidation (#1761): a settled mutation invalidates
25
+ * the caches of the collections related to the mutated one, with the edges
26
+ * derived from the manifest (`definition.relationships`) — no hand-wired
27
+ * cache keys. Cross-collection reach requires a shared client from
28
+ * {@link createSmrtWebClient}; with a private client only the mutated
29
+ * collection refetches.
30
+ * - hydration seeding (#1761): rows fetched server-side (a SvelteKit
31
+ * `+page.server.ts` load) seed the shared cache via
32
+ * {@link CreateSmrtCollectionOptions.initialData}, so the first client read
33
+ * serves them WITHOUT a duplicate first-render fetch.
34
+ *
35
+ * Deliberately NOT here yet (see PRD #1755): offline outbox, SSE invalidation,
36
+ * persistence, version awareness.
37
+ */
38
+ import type { SmrtWebCapability } from './capability.js';
39
+ export type { MutationSettleOutcome, SmrtWebCapability, SmrtWebCapabilityContext, SmrtWebMutationEnvelope, WrapMutationOutcome, } from './capability.js';
40
+ export { runWrapMutation } from './capability.js';
41
+ export type { SmrtWebDataQueryFacetResult, SmrtWebDataQueryFilter, SmrtWebDataQueryFilterOperator, SmrtWebDataQueryRequest, SmrtWebDataQueryResult, SmrtWebDataQueryScalar, SmrtWebDataQueryTotal, SmrtWebDataQueryTransport, } from './data-query.js';
42
+ export { executeSmrtWebDataQuery, MAX_SMRT_WEB_DATA_QUERY_CONTAINER_ITEMS, MAX_SMRT_WEB_DATA_QUERY_FACET_VALUES, MAX_SMRT_WEB_DATA_QUERY_FACETS, MAX_SMRT_WEB_DATA_QUERY_OFFSET, MAX_SMRT_WEB_DATA_QUERY_PAGE_LIMIT, MAX_SMRT_WEB_DATA_QUERY_RESULT_BYTES, MAX_SMRT_WEB_DATA_QUERY_ROWS, MAX_SMRT_WEB_DATA_QUERY_STRING_LENGTH, MAX_SMRT_WEB_DATA_QUERY_WARNINGS, normalizeSmrtWebDataQueryResult, } from './data-query.js';
43
+ export type { SmrtWebDataSurfaceActionCommandRequest, SmrtWebDataSurfaceActionCommandTransportOptions, SmrtWebDataSurfaceActionRequest, SmrtWebDataSurfaceActionResult, SmrtWebDataSurfaceActionTransport, SmrtWebDataSurfaceIdentity, SmrtWebDataSurfaceJsonPrimitive, SmrtWebDataSurfaceJsonValue, SmrtWebDataSurfaceSelection, } from './data-surface-actions.js';
44
+ export { classifySmrtWebDataSurfaceActionResult, dataSurfaceActionCommandTransport, executeSmrtWebDataSurfaceAction, MAX_SMRT_WEB_DATA_SURFACE_ACTION_CONTAINER_ITEMS, MAX_SMRT_WEB_DATA_SURFACE_ACTION_IDENTIFIER_LENGTH, MAX_SMRT_WEB_DATA_SURFACE_ACTION_JSON_DEPTH, MAX_SMRT_WEB_DATA_SURFACE_ACTION_RESULT_BYTES, normalizeSmrtWebDataSurfaceActionResult, } from './data-surface-actions.js';
45
+ export type { DurableResource, DurableStoreKey } from './durable-store.js';
46
+ export { durableStoreNamespace, registerDurableResource, wipeDurableStore, } from './durable-store.js';
47
+ export type { OfflineCommandInput, OfflineCommandQueue, OfflineCommandQueueConfig, OfflineOutboxConfig, OutboxBackoff, OutboxCommand, OutboxCommandResult, OutboxCommandTransport, OutboxConflict, OutboxHandle, OutboxSnapshotItem, OutboxSyncState, SyncStateEvent, } from './offline.js';
48
+ export { getOutboxHandle, offlineCommandQueue, offlineOutbox, } from './offline.js';
49
+ export type { PersistCollectionConfig, PersistDataSurfaceConfig, PersistedDataSurface, } from './persistence.js';
50
+ export { DEFAULT_PERSIST_DEBOUNCE_MS, persistCollection, persistDataSurface, } from './persistence.js';
51
+ export type { SmrtWebQuery, SmrtWebQueryLiveSubscription, SmrtWebQueryMode, SmrtWebQueryPage, SmrtWebQueryRunOptions, SmrtWebQueryState, SmrtWebQueryTransport, } from './remote-query.js';
52
+ export { createSmrtWebQuery } from './remote-query.js';
53
+ export type { LiveInvalidationConfig, SmrtWebEventSource, SmrtWebEventSourceFactory, SmrtWebEventSubscriber, SmrtWebEventSubscriberConfig, SmrtWebSubscriberTransport, } from './sse-client.js';
54
+ export { createSmrtWebEventSubscriber, liveInvalidation, } from './sse-client.js';
55
+ export type { UpdateAvailableState, UpdateState, UpdateStateConfig, } from './update-state.js';
56
+ export { createUpdateState } from './update-state.js';
57
+ /**
58
+ * The field types core's web-collection emission actually produces —
59
+ * relationship pseudo-columns (`oneToMany`/`manyToMany`) and STI `meta`
60
+ * internals never reach an emitted definition. Textual mirror of core's
61
+ * `WebFieldType` (this package is deliberately smrt-dependency-free, so it
62
+ * cannot import the source type); the four co-managed sites are core's
63
+ * runtime type, the ambient virt-web d.ts, the physical `@smrt/web` d.ts,
64
+ * and this mirror.
65
+ */
66
+ export type SmrtWebFieldType = 'text' | 'decimal' | 'boolean' | 'integer' | 'datetime' | 'json' | 'foreignKey' | 'crossPackageRef';
67
+ /** Static `@field({ ui })` hints (#2046) — the field-policy rail seed. */
68
+ export interface SmrtWebFieldUIHints {
69
+ basic?: boolean;
70
+ group?: string;
71
+ order?: number;
72
+ locked?: boolean;
73
+ }
74
+ /**
75
+ * Field metadata emitted per column by the `@happyvertical/smrt-virt-web`
76
+ * virtual module (generated from the package manifest).
77
+ */
78
+ export interface SmrtWebFieldDefinition {
79
+ type: SmrtWebFieldType;
80
+ required?: boolean;
81
+ /** Whether the public field contract explicitly permits `null`. */
82
+ nullable?: boolean;
83
+ default?: unknown;
84
+ /** Developer-authored `@field({ description })` (#2046) — end-user help seed. */
85
+ description?: string;
86
+ /** Static `@field({ ui })` hints (#2046). */
87
+ ui?: SmrtWebFieldUIHints;
88
+ }
89
+ /** The relationship kinds a generated web collection edge can describe. */
90
+ export type SmrtWebRelationshipKind = 'foreignKey' | 'crossPackageRef' | 'oneToMany' | 'manyToMany';
91
+ /**
92
+ * A manifest-derived edge from this collection to a sibling REST collection,
93
+ * emitted by the `@happyvertical/smrt-virt-web` virtual module. When a mutation
94
+ * on this collection settles, the caches of the collections named by these
95
+ * edges are invalidated (relationship-derived invalidation, #1761), so a
96
+ * dependent view refetches without any hand-wired cache key.
97
+ *
98
+ * SMRT-owned data — no client-engine (`@tanstack/*`) type appears here, so it
99
+ * stays inside the engine-absorption boundary.
100
+ */
101
+ export interface SmrtWebRelationship {
102
+ /** The declaring field carrying the relationship (e.g. `groupId`, `items`). */
103
+ field: string;
104
+ /** The relationship kind, mirroring the manifest field type. */
105
+ kind: SmrtWebRelationshipKind;
106
+ /** REST collection name the edge resolves to (e.g. `ad_groups`). */
107
+ relatedCollection: string;
108
+ }
109
+ /**
110
+ * One generated collection definition: everything needed to construct a client
111
+ * collection over the generated REST surface. The `_row` property is a phantom
112
+ * type carrier threaded through codegen — it never exists at runtime, it only
113
+ * lets factories infer the row type from a definition.
114
+ */
115
+ /**
116
+ * One WebMCP / MCP tool descriptor for a collection action (#1812). Emitted by
117
+ * the core web-collections codegen as PLAIN DATA (this package has no smrt
118
+ * dependency), shaped to match Chrome's `document.modelContext.registerTool`
119
+ * input — see https://developer.chrome.com/docs/ai/webmcp. Consumed by
120
+ * {@link registerWebMcpTools} in `./webmcp`.
121
+ */
122
+ export interface WebToolDescriptor {
123
+ /** The action this tool performs (`list` | `get` | … | a custom method name). */
124
+ action: string;
125
+ /** Tool id, `${className.toLowerCase()}_${action}` (e.g. `product_list`). */
126
+ name: string;
127
+ description: string;
128
+ /** JSON Schema for the tool's arguments. */
129
+ inputSchema: Record<string, unknown>;
130
+ /** True for non-mutating reads → WebMCP `annotations.readOnlyHint`. */
131
+ readOnly: boolean;
132
+ /** Capability effect used by WebMCP exposure policy. */
133
+ effect?: 'read' | 'write' | 'destructive';
134
+ /** Whether repeating the tool with the same arguments is safe. */
135
+ idempotent?: boolean;
136
+ /** Whether the tool may interact outside the SMRT application. */
137
+ openWorld?: boolean;
138
+ /** Generated custom-route transport metadata. */
139
+ route?: SmrtWebToolRouteDescriptor;
140
+ }
141
+ export interface SmrtWebToolRouteDescriptor {
142
+ method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
143
+ scope: 'item' | 'collection';
144
+ /** Route segments below the collection endpoint; dynamic segments use `[x]`. */
145
+ path: string[];
146
+ /** Transport names rewritten by the tool schema (e.g. `actionId` → `id`). */
147
+ parameterAliases?: Record<string, string>;
148
+ /** The generated method accepts one `options` bag as its sole argument. */
149
+ optionsBag?: boolean;
150
+ }
151
+ /**
152
+ * Canonical data-only definition for one API-backed browser tool. Unlike a
153
+ * collection definition, this does not imply that a list route or client cache
154
+ * exists. Generated definitions carry explicit semantics; manual legacy
155
+ * literals may omit them and register with fail-closed defaults.
156
+ */
157
+ export interface WebMcpToolDefinition extends WebToolDescriptor {
158
+ /** Stable definition identity is `(collection, action)`. */
159
+ collection: string;
160
+ /** Canonical qualified row-model identity. */
161
+ objectRef: string;
162
+ /** Row-model class used by the shared MCP tool vocabulary. */
163
+ className: string;
164
+ endpoint: string;
165
+ idField: string;
166
+ idType: 'uuid' | 'text';
167
+ /** Complete generated route metadata, including CRUD actions. */
168
+ route: SmrtWebToolRouteDescriptor;
169
+ /** Cache-invalidation edges for materialized sibling collections. */
170
+ relationships: SmrtWebRelationship[];
171
+ }
172
+ export interface SmrtWebCollectionDefinition<TData extends object = object> {
173
+ /** REST collection name (e.g. `products`). */
174
+ name: string;
175
+ /**
176
+ * Canonical qualified model identity (e.g.
177
+ * `@happyvertical/smrt-products:Product`). Generated definitions always
178
+ * provide it; optional here for source compatibility with manual literals
179
+ * authored before policy-aware web collections.
180
+ */
181
+ objectRef?: string;
182
+ /** Source class name (e.g. `Product`). */
183
+ className: string;
184
+ /** Path under the API base path (e.g. `/products`). */
185
+ endpoint: string;
186
+ /** Primary key field name (`id` for SmrtObject). */
187
+ idField: string;
188
+ /** CRUD + custom actions exposed by the api decorator config. */
189
+ actions: string[];
190
+ /**
191
+ * WebMCP/MCP tool descriptors for the exposed actions (#1812). Optional so
192
+ * hand-built definitions (older codegen, tests) still satisfy the type; a
193
+ * missing value means "no WebMCP tools to register".
194
+ */
195
+ toolDescriptors?: WebToolDescriptor[];
196
+ /** Persisted field metadata keyed by field name. */
197
+ fields: Record<string, SmrtWebFieldDefinition>;
198
+ /**
199
+ * Manifest-derived relationship edges to sibling REST collections. Drives
200
+ * relationship-derived cache invalidation: a settled mutation on this
201
+ * collection invalidates the caches of the collections these edges name.
202
+ * Optional so hand-built definitions (older codegen, tests) still satisfy the
203
+ * type; a missing value means "no derived edges".
204
+ */
205
+ relationships?: SmrtWebRelationship[];
206
+ /** Phantom row-type carrier — never present at runtime. */
207
+ _row?: TData;
208
+ }
209
+ /**
210
+ * The per-collection CRUD surface of the generated REST client
211
+ * (`createClient(basePath).<collection>` from `@happyvertical/smrt-virt-client`).
212
+ *
213
+ * Return types are `unknown` on purpose: generated fetchers resolve with
214
+ * whatever the server sent, so this package normalizes and validates payloads
215
+ * centrally — see {@link unwrapListResult} / {@link unwrapItemResult}.
216
+ */
217
+ export interface SmrtCrudFetchers {
218
+ list(params?: Record<string, unknown>): Promise<unknown>;
219
+ get?(id: string): Promise<unknown>;
220
+ create(data: Record<string, unknown>): Promise<unknown>;
221
+ update?(id: string, data: Record<string, unknown>): Promise<unknown>;
222
+ delete?(id: string): Promise<unknown>;
223
+ /** Invoke a generated custom action route. */
224
+ custom?(action: string, args: Record<string, unknown>, route?: SmrtWebToolRouteDescriptor): Promise<unknown>;
225
+ }
226
+ /**
227
+ * Raised when a generated-client call resolved with an error payload
228
+ * (`{ error: string }` from the generated REST routes) or an unexpected shape.
229
+ * Thrown inside a mutation handler, this triggers the automatic rollback of
230
+ * optimistic state. HTTP 5xx failures are always opaque: their server payload
231
+ * is neither parsed nor retained because it may contain internal detail.
232
+ */
233
+ export declare class SmrtWebRequestError extends Error {
234
+ readonly payload: unknown;
235
+ readonly status?: number;
236
+ readonly code?: string;
237
+ constructor(message: string, payload?: unknown, status?: number, code?: string);
238
+ }
239
+ /** A row as stored in the client collection: the DTO plus a required key. */
240
+ export type SmrtWebRow<TData extends object> = TData & {
241
+ id: string;
242
+ };
243
+ /**
244
+ * Normalize a generated-client list result to an array of rows.
245
+ *
246
+ * The generated REST routes return a bare JSON array; `{ error }` payloads are
247
+ * surfaced as failures. The `{ data: [...] }` envelope is tolerated for
248
+ * ApiResponse-shaped clients (e.g. a mock client).
249
+ */
250
+ export declare function unwrapListResult(result: unknown, collectionName: string): Array<Record<string, unknown>>;
251
+ /**
252
+ * Normalize a generated-client item result (create/update) to a row.
253
+ * `{ error }` payloads become failures — inside mutation handlers this is what
254
+ * makes optimistic state roll back.
255
+ */
256
+ export declare function unwrapItemResult(result: unknown, context: string): Record<string, unknown>;
257
+ /** Reject generated REST error envelopes while preserving opaque successes. */
258
+ export declare function throwIfSmrtWebError(result: unknown, context: string): unknown;
259
+ /**
260
+ * Serialize `list` query params into the query string the generated REST list
261
+ * route parses (`handleList` in `core/src/generators/rest.ts`): `limit`,
262
+ * `offset`, `orderBy` as scalars, and `where` entries as `field=value`
263
+ * (equality) or `field[op]=value` for a `{ op, value }` condition (`in` joins an
264
+ * array with commas). Called with no params (the collection runtime's argument-
265
+ * free `list()`) it returns `''`, so the bare-URL behavior is unchanged.
266
+ */
267
+ export declare function buildListQuery(params?: Record<string, unknown>): string;
268
+ /**
269
+ * Build CRUD fetchers from a generated collection definition — the same URL
270
+ * scheme and payload handling as the generated REST client
271
+ * (`basePath + endpoint`), with one improvement: HTTP error statuses reject
272
+ * instead of resolving with them. Typed/legacy 4xx detail is retained; 5xx
273
+ * failures stay opaque and payload-free.
274
+ */
275
+ export declare function createDefinitionFetchers(definition: Pick<SmrtWebCollectionDefinition<object>, 'name' | 'endpoint'>, basePath?: string, fetchFn?: typeof fetch): SmrtCrudFetchers;
276
+ /**
277
+ * Generate a client-local id for optimistic inserts. The generated REST layer
278
+ * strips client-supplied ids on create (mass-assignment guard #1540), so this
279
+ * id only identifies the optimistic row until the post-persist refetch swaps in
280
+ * the server-assigned row.
281
+ */
282
+ export declare function newLocalId(): string;
283
+ /**
284
+ * Opaque handle to the shared client cache / request-dedup layer. Create one
285
+ * with {@link createSmrtWebClient} and pass the SAME instance to every
286
+ * collection that should share a cache and deduplicate in-flight requests.
287
+ *
288
+ * The engine (currently a TanStack Query client) is intentionally hidden behind
289
+ * this brand so it stays swappable — do not depend on its concrete shape.
290
+ */
291
+ export interface SmrtWebClient {
292
+ /** Phantom brand — this handle wraps the hidden client-cache engine. */
293
+ readonly __smrtWebClient: 'SmrtWebClient';
294
+ }
295
+ /**
296
+ * Create a shared client-cache handle. Pass the returned handle as
297
+ * {@link CreateSmrtCollectionOptions.client} to every collection that should
298
+ * share a cache and deduplicate requests app-wide.
299
+ */
300
+ export declare function createSmrtWebClient(): SmrtWebClient;
301
+ /** Validate that an opaque cache handle came from {@link createSmrtWebClient}. */
302
+ export declare function validateSmrtWebClient(client: SmrtWebClient): void;
303
+ /**
304
+ * Invalidate materialized collection caches through the opaque SMRT client.
305
+ *
306
+ * This is the public cache-coherence seam for transports that execute a write
307
+ * without constructing a {@link SmrtWebCollection} (for example, a tool-only
308
+ * WebMCP definition). Matching is by collection name across every scope on the
309
+ * shared client, mirroring settled collection mutations: over-invalidation is
310
+ * safe, while leaving a related page cache stale is not.
311
+ */
312
+ export declare function invalidateSmrtWebCollections(client: SmrtWebClient, collectionNames: readonly string[]): void;
313
+ /**
314
+ * A pending optimistic mutation. Await {@link isPersisted} to observe the
315
+ * server outcome: it resolves once the write has been persisted through the
316
+ * REST surface, and rejects (rolling the optimistic state back) on error.
317
+ */
318
+ export interface SmrtWebTransaction {
319
+ readonly isPersisted: {
320
+ readonly promise: Promise<unknown>;
321
+ };
322
+ }
323
+ /** A change-subscription handle. Call {@link unsubscribe} to detach. */
324
+ export interface SmrtWebSubscription {
325
+ unsubscribe(): void;
326
+ }
327
+ /**
328
+ * A live, cached collection of plain-DTO rows — the SMRT-owned public contract
329
+ * over the client-data engine. Exposes only the committed surface; the engine's
330
+ * own type is never named here so it stays swappable.
331
+ */
332
+ export interface SmrtWebCollection<TData extends object> {
333
+ /** All rows currently in the collection (plain DTOs, insertion order). */
334
+ readonly toArray: ReadonlyArray<SmrtWebRow<TData>>;
335
+ /** Number of rows currently in the collection. */
336
+ readonly size: number;
337
+ /** True when a row with `key` is present. */
338
+ has(key: string): boolean;
339
+ /** The row with `key`, or `undefined`. */
340
+ get(key: string): SmrtWebRow<TData> | undefined;
341
+ /** Resolve once the first load has completed. */
342
+ preload(): Promise<void>;
343
+ /** Tear down subscriptions and cached state. */
344
+ cleanup(): Promise<void>;
345
+ /** Subscribe to change notifications; returns a detach handle. */
346
+ subscribeChanges(callback: (changes: unknown) => void): SmrtWebSubscription;
347
+ /**
348
+ * Optimistically insert a row and persist it through the create fetcher. The
349
+ * row is visible synchronously; the returned transaction settles on the
350
+ * server outcome (see {@link SmrtWebTransaction}).
351
+ */
352
+ insert(row: SmrtWebRow<TData>): SmrtWebTransaction;
353
+ /** Optimistically update a row and persist it through the REST surface. */
354
+ update(key: string, changes: Omit<Partial<SmrtWebRow<TData>>, 'id'>): SmrtWebTransaction;
355
+ /** Optimistically delete a row and persist it through the REST surface. */
356
+ delete(key: string): SmrtWebTransaction;
357
+ /** Execute a custom action and invalidate this collection's related caches. */
358
+ action(action: string, args: Record<string, unknown>, route?: SmrtWebToolRouteDescriptor): Promise<unknown>;
359
+ }
360
+ /**
361
+ * Options for {@link createSmrtCollection}. Generic in the collection's row
362
+ * type `TData` so {@link initialData} is checked against the same DTO the
363
+ * collection stores; every other option is row-type-agnostic, so the parameter
364
+ * defaults to `object` and can be omitted at call sites that pass no seed.
365
+ */
366
+ export interface CreateSmrtCollectionOptions<TData extends object = object> {
367
+ /**
368
+ * Generated REST client surface for this collection, e.g.
369
+ * `createClient('/api/v1').products` from the virt-client module. When
370
+ * omitted, fetchers are derived from the definition's endpoint and `basePath`
371
+ * with the same URL scheme and payload shapes the generated client uses.
372
+ */
373
+ fetchers?: SmrtCrudFetchers;
374
+ /** API base path for definition-derived fetchers (default `/api/v1`). */
375
+ basePath?: string;
376
+ /** Fetch implementation override (tests, SSR). Defaults to global fetch. */
377
+ fetchFn?: typeof fetch;
378
+ /**
379
+ * Shared cache handle from {@link createSmrtWebClient}. Pass one app-wide
380
+ * instance so collections share a cache and deduplicate requests; a private
381
+ * cache is created when omitted.
382
+ */
383
+ client?: SmrtWebClient;
384
+ /**
385
+ * Cache namespace for this collection's reads. Fold a backend / tenant /
386
+ * preview discriminator in here when the SAME generated collection is
387
+ * materialized against DIFFERENT backends while sharing one {@link client} —
388
+ * without it those reads share a cache key and could serve one backend's rows
389
+ * for the other for the whole `staleTimeMs` window. Omit for the common
390
+ * single-backend case.
391
+ */
392
+ scope?: string;
393
+ /**
394
+ * Stale-while-revalidate window in milliseconds (default 30s): reads within
395
+ * the window are served from the local collection without a network request;
396
+ * the first read after it revalidates in the background.
397
+ */
398
+ staleTimeMs?: number;
399
+ /** Retry failed loads (default false: fail fast, surface errors). */
400
+ retry?: boolean;
401
+ /**
402
+ * Rows to seed this collection's cache with, before its first read — the
403
+ * hydration path for server-rendered data (#1761). Fetch rows in a SvelteKit
404
+ * `+page.server.ts` load, pass them here on the client, and the first read
405
+ * serves them from cache WITHOUT a duplicate first-render network request
406
+ * (SMRT-owned type, so no engine type appears on the option).
407
+ *
408
+ * The seed is written to the cache with a fresh timestamp, so it counts as
409
+ * fresh for `staleTimeMs`: with the default window the first read does not
410
+ * fetch, and the collection revalidates in the background only once the window
411
+ * elapses (or immediately if `staleTimeMs` is 0). Seed the SAME rows the
412
+ * server serialized so the pre- and post-hydration renders match.
413
+ *
414
+ * Seeds the SAME cache key the reads use — so with a shared {@link client},
415
+ * fold the backend / tenant discriminator into {@link scope} to match, exactly
416
+ * as reads do; otherwise one backend's seed would serve the other for the
417
+ * `staleTimeMs` window.
418
+ */
419
+ initialData?: SmrtWebRow<TData>[];
420
+ /**
421
+ * Capability plug-ins hooking the collection lifecycle (#1755) — the seam
422
+ * that lets the offline outbox (#1762), persistence (#1764), and live SSE
423
+ * invalidation (#1763-client) slices each live in their own module instead of
424
+ * contending on this factory. Capabilities run in array order at six fixed
425
+ * points (see {@link SmrtWebCapability}). Additive and defaulting to none: an
426
+ * undefined or empty array is byte-for-byte the collection of today (the
427
+ * no-op guarantee), so this ships zero concrete capabilities by design.
428
+ */
429
+ capabilities?: SmrtWebCapability<TData>[];
430
+ }
431
+ /**
432
+ * Retrieve the underlying engine collection backing a handle — an advanced
433
+ * bridge for trusted framework bindings (e.g. the smrt-svelte live-query
434
+ * binding), which must feed the engine collection to the query builder. Returns
435
+ * `unknown` so no engine type crosses the boundary; callers cast. Throws for a
436
+ * handle not produced by {@link createSmrtCollection}. Not needed for normal
437
+ * use.
438
+ */
439
+ export declare function getEngineCollection<TData extends object>(handle: SmrtWebCollection<TData>): unknown;
440
+ /**
441
+ * Create a typed client collection over a generated SMRT collection definition
442
+ * and the matching generated REST client fetchers.
443
+ *
444
+ * Reads: stale-while-revalidate. The first subscriber triggers a fetch;
445
+ * re-subscribing within `staleTimeMs` serves local data with no request. N
446
+ * concurrent identical reads coalesce into one network request.
447
+ *
448
+ * Writes: `collection.insert({ ...data, id: newLocalId() })` applies instantly,
449
+ * persists through `fetchers.create()` (the temp id is stripped — the server
450
+ * assigns the real one), then refetches to reconcile. A failed create rejects
451
+ * the transaction and the optimistic row rolls back automatically.
452
+ *
453
+ * Relationship-derived invalidation: once a create/update/delete has persisted,
454
+ * the query caches of this collection AND the collections named by
455
+ * `definition.relationships` (manifest-derived edges) are invalidated, so
456
+ * dependent views refetch. Reaching OTHER collections requires them to share
457
+ * this collection's `client` (see {@link createSmrtWebClient}); with a private
458
+ * client only this collection refetches.
459
+ *
460
+ * Hydration seeding: pass rows fetched server-side as
461
+ * {@link CreateSmrtCollectionOptions.initialData} and the collection's first
462
+ * read is served from them with NO network request (until `staleTimeMs`
463
+ * elapses) — the SvelteKit `+page.server.ts` → hydrate path.
464
+ */
465
+ export declare function createSmrtCollection<TData extends object>(definition: SmrtWebCollectionDefinition<TData>, options: CreateSmrtCollectionOptions<TData>): SmrtWebCollection<TData>;
466
+ export { type RegisterWebMcpBespokeToolOptions, type RegisterWebMcpToolsOptions, registerViewIntent, registerWebMcpBespokeTool, registerWebMcpTools, reserveWebMcpToolNames, type WebMcpBespokeToolSpec, type WebMcpExposurePolicy, type WebMcpRegistrationDefinition, type WebMcpRegistrationDisposer, type WebMcpToolEffect, type WebMcpToolExecutionOptions, WebMcpToolNameCollisionError, type WebMcpToolNameOwner, type WebMcpToolNameReservation, webMcpToolNameOwner, } from './webmcp.js';
467
+ export { compileViewIntentToolSpec, type ViewIntent, type ViewIntentBinding, type ViewIntentControlAction, type ViewIntentControlIdentity, type ViewIntentControlRegistryPort, type ViewIntentControlTarget, type ViewIntentDataSurfaceIdentity, type ViewIntentDataSurfaceKind, type ViewIntentDataSurfaceRegistryPort, type ViewIntentDataSurfaceTarget, type ViewIntentDeclaration, type ViewIntentSubject, type ViewIntentTarget, type ViewIntentToolSpec, viewIntentToolName, } from './intents.js';
468
+ //# sourceMappingURL=index.d.ts.map