@typicalday/firegraph 0.11.2 → 0.13.0

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 (78) hide show
  1. package/README.md +355 -78
  2. package/dist/backend-DuvHGgK1.d.cts +1897 -0
  3. package/dist/backend-DuvHGgK1.d.ts +1897 -0
  4. package/dist/backend.cjs +365 -5
  5. package/dist/backend.cjs.map +1 -1
  6. package/dist/backend.d.cts +25 -5
  7. package/dist/backend.d.ts +25 -5
  8. package/dist/backend.js +209 -7
  9. package/dist/backend.js.map +1 -1
  10. package/dist/chunk-2DHMNTV6.js +16 -0
  11. package/dist/chunk-2DHMNTV6.js.map +1 -0
  12. package/dist/chunk-4MMQ5W74.js +288 -0
  13. package/dist/chunk-4MMQ5W74.js.map +1 -0
  14. package/dist/{chunk-5753Y42M.js → chunk-C2QMD7RY.js} +6 -10
  15. package/dist/chunk-C2QMD7RY.js.map +1 -0
  16. package/dist/chunk-D4J7Z4FE.js +67 -0
  17. package/dist/chunk-D4J7Z4FE.js.map +1 -0
  18. package/dist/chunk-EQJUUVFG.js +14 -0
  19. package/dist/chunk-EQJUUVFG.js.map +1 -0
  20. package/dist/chunk-N5HFDWQX.js +23 -0
  21. package/dist/chunk-N5HFDWQX.js.map +1 -0
  22. package/dist/chunk-PAD7WFFU.js +573 -0
  23. package/dist/chunk-PAD7WFFU.js.map +1 -0
  24. package/dist/chunk-TK64DNVK.js +256 -0
  25. package/dist/chunk-TK64DNVK.js.map +1 -0
  26. package/dist/{chunk-NJSOD64C.js → chunk-WRTFC5NG.js} +438 -30
  27. package/dist/chunk-WRTFC5NG.js.map +1 -0
  28. package/dist/client-BKi3vk0Q.d.ts +34 -0
  29. package/dist/client-BrsaXtDV.d.cts +34 -0
  30. package/dist/cloudflare/index.cjs +1386 -74
  31. package/dist/cloudflare/index.cjs.map +1 -1
  32. package/dist/cloudflare/index.d.cts +217 -13
  33. package/dist/cloudflare/index.d.ts +217 -13
  34. package/dist/cloudflare/index.js +639 -180
  35. package/dist/cloudflare/index.js.map +1 -1
  36. package/dist/codegen/index.d.cts +1 -1
  37. package/dist/codegen/index.d.ts +1 -1
  38. package/dist/errors-BRc3I_eH.d.cts +73 -0
  39. package/dist/errors-BRc3I_eH.d.ts +73 -0
  40. package/dist/firestore-enterprise/index.cjs +3877 -0
  41. package/dist/firestore-enterprise/index.cjs.map +1 -0
  42. package/dist/firestore-enterprise/index.d.cts +141 -0
  43. package/dist/firestore-enterprise/index.d.ts +141 -0
  44. package/dist/firestore-enterprise/index.js +985 -0
  45. package/dist/firestore-enterprise/index.js.map +1 -0
  46. package/dist/firestore-standard/index.cjs +3117 -0
  47. package/dist/firestore-standard/index.cjs.map +1 -0
  48. package/dist/firestore-standard/index.d.cts +49 -0
  49. package/dist/firestore-standard/index.d.ts +49 -0
  50. package/dist/firestore-standard/index.js +283 -0
  51. package/dist/firestore-standard/index.js.map +1 -0
  52. package/dist/index.cjs +809 -534
  53. package/dist/index.cjs.map +1 -1
  54. package/dist/index.d.cts +24 -100
  55. package/dist/index.d.ts +24 -100
  56. package/dist/index.js +184 -531
  57. package/dist/index.js.map +1 -1
  58. package/dist/registry-Bc7h6WTM.d.cts +64 -0
  59. package/dist/registry-C2KUPVZj.d.ts +64 -0
  60. package/dist/{scope-path-B1G3YiA7.d.ts → scope-path-CROFZGr9.d.cts} +1 -56
  61. package/dist/{scope-path-B1G3YiA7.d.cts → scope-path-CROFZGr9.d.ts} +1 -56
  62. package/dist/{serialization-ZZ7RSDRX.js → serialization-OE2PFZMY.js} +6 -4
  63. package/dist/sqlite/index.cjs +3631 -0
  64. package/dist/sqlite/index.cjs.map +1 -0
  65. package/dist/sqlite/index.d.cts +111 -0
  66. package/dist/sqlite/index.d.ts +111 -0
  67. package/dist/sqlite/index.js +1164 -0
  68. package/dist/sqlite/index.js.map +1 -0
  69. package/package.json +33 -3
  70. package/dist/backend-U-MLShlg.d.ts +0 -97
  71. package/dist/backend-np4gEVhB.d.cts +0 -97
  72. package/dist/chunk-5753Y42M.js.map +0 -1
  73. package/dist/chunk-NJSOD64C.js.map +0 -1
  74. package/dist/chunk-R7CRGYY4.js +0 -94
  75. package/dist/chunk-R7CRGYY4.js.map +0 -1
  76. package/dist/types-BGWxcpI_.d.cts +0 -736
  77. package/dist/types-BGWxcpI_.d.ts +0 -736
  78. /package/dist/{serialization-ZZ7RSDRX.js.map → serialization-OE2PFZMY.js.map} +0 -0
package/dist/backend.cjs CHANGED
@@ -20,10 +20,17 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/backend.ts
21
21
  var backend_exports = {};
22
22
  __export(backend_exports, {
23
+ CapabilityNotSupportedError: () => CapabilityNotSupportedError,
23
24
  CrossBackendTransactionError: () => CrossBackendTransactionError,
25
+ DELETE_FIELD: () => DELETE_FIELD,
24
26
  appendStorageScope: () => appendStorageScope,
27
+ createCapabilities: () => createCapabilities,
25
28
  createRoutingBackend: () => createRoutingBackend,
29
+ deleteField: () => deleteField,
30
+ flattenPatch: () => flattenPatch,
31
+ intersectCapabilities: () => intersectCapabilities,
26
32
  isAncestorScopeUid: () => isAncestorScopeUid,
33
+ isDeleteSentinel: () => isDeleteSentinel,
27
34
  parseStorageScope: () => parseStorageScope,
28
35
  resolveAncestorScope: () => resolveAncestorScope
29
36
  });
@@ -43,6 +50,34 @@ var CrossBackendTransactionError = class extends FiregraphError {
43
50
  this.name = "CrossBackendTransactionError";
44
51
  }
45
52
  };
53
+ var CapabilityNotSupportedError = class extends FiregraphError {
54
+ constructor(capability, backendDescription) {
55
+ super(
56
+ `Capability "${capability}" is not supported by ${backendDescription}.`,
57
+ "CAPABILITY_NOT_SUPPORTED"
58
+ );
59
+ this.capability = capability;
60
+ this.name = "CapabilityNotSupportedError";
61
+ }
62
+ };
63
+
64
+ // src/internal/backend.ts
65
+ function createCapabilities(caps) {
66
+ return {
67
+ has: (capability) => caps.has(capability),
68
+ values: () => caps.values()
69
+ };
70
+ }
71
+ function intersectCapabilities(parts) {
72
+ if (parts.length === 0) return createCapabilities(/* @__PURE__ */ new Set());
73
+ const sets = parts.map((p) => new Set(p.values()));
74
+ const [first, ...rest] = sets;
75
+ const intersection = /* @__PURE__ */ new Set();
76
+ for (const c of first) {
77
+ if (rest.every((s) => s.has(c))) intersection.add(c);
78
+ }
79
+ return createCapabilities(intersection);
80
+ }
46
81
 
47
82
  // src/internal/routing-backend.ts
48
83
  function assertValidSubgraphArgs(parentNodeUid, name) {
@@ -60,16 +95,70 @@ function assertValidSubgraphArgs(parentNodeUid, name) {
60
95
  }
61
96
  }
62
97
  var RoutingStorageBackend = class _RoutingStorageBackend {
63
- constructor(base, options, storageScope, logicalScopePath) {
98
+ constructor(base, options, storageScope, logicalScopePath, capabilities) {
64
99
  this.base = base;
65
100
  this.options = options;
66
101
  this.collectionPath = base.collectionPath;
67
102
  this.scopePath = logicalScopePath;
68
103
  this.storageScope = storageScope;
104
+ if (capabilities) {
105
+ this.capabilities = capabilities;
106
+ } else if (options.routedCapabilities && options.routedCapabilities.length > 0) {
107
+ this.capabilities = intersectCapabilities([base.capabilities, ...options.routedCapabilities]);
108
+ } else {
109
+ this.capabilities = base.capabilities;
110
+ }
69
111
  if (base.findEdgesGlobal) {
70
112
  this.findEdgesGlobal = (params, collectionName) => base.findEdgesGlobal(params, collectionName);
71
113
  }
114
+ if (base.aggregate && this.capabilities.has("query.aggregate")) {
115
+ this.aggregate = (spec, filters) => base.aggregate(spec, filters);
116
+ }
117
+ if (base.bulkDelete && this.capabilities.has("query.dml")) {
118
+ this.bulkDelete = (filters, options2) => base.bulkDelete(filters, options2);
119
+ }
120
+ if (base.bulkUpdate && this.capabilities.has("query.dml")) {
121
+ this.bulkUpdate = (filters, patch, options2) => base.bulkUpdate(filters, patch, options2);
122
+ }
123
+ if (base.expand && this.capabilities.has("query.join")) {
124
+ this.expand = (params) => base.expand(params);
125
+ }
126
+ if (base.runEngineTraversal && this.capabilities.has("traversal.serverSide")) {
127
+ this.runEngineTraversal = (params) => base.runEngineTraversal(params);
128
+ }
129
+ if (base.findEdgesProjected && this.capabilities.has("query.select")) {
130
+ this.findEdgesProjected = (select, filters, options2) => base.findEdgesProjected(select, filters, options2);
131
+ }
132
+ if (base.findNearest && this.capabilities.has("search.vector")) {
133
+ this.findNearest = (params) => base.findNearest(params);
134
+ }
135
+ if (base.fullTextSearch && this.capabilities.has("search.fullText")) {
136
+ this.fullTextSearch = (params) => base.fullTextSearch(params);
137
+ }
138
+ if (base.geoSearch && this.capabilities.has("search.geo")) {
139
+ this.geoSearch = (params) => base.geoSearch(params);
140
+ }
72
141
  }
142
+ /**
143
+ * Effective capability set for this wrapper.
144
+ *
145
+ * - **Root wrapper** (`createRoutingBackend(...)` direct return): if the
146
+ * caller supplied `options.routedCapabilities`, the cap set is the
147
+ * intersection of `base.capabilities` and every set in that list. If
148
+ * not, the cap set mirrors `base.capabilities` (suitable when routes
149
+ * target peers of the same backend type — no capability differential
150
+ * to honour).
151
+ * - **Child wrapper** (returned from `subgraph()`): the cap set mirrors
152
+ * the *wrapped* backend (either `base.subgraph(...)` or the backend
153
+ * returned by `route()`). Each child handle reflects what's safe to
154
+ * call against the specific backend it targets — invariant 3 holds
155
+ * per-instance.
156
+ *
157
+ * This satisfies invariant 5 (intersection across mixed-backend graphs)
158
+ * when callers opt in, and falls back to a non-lying mirror when they
159
+ * don't.
160
+ */
161
+ capabilities;
73
162
  collectionPath;
74
163
  /**
75
164
  * Logical (names-only) scope path for *this* wrapper. Tracked
@@ -96,6 +185,125 @@ var RoutingStorageBackend = class _RoutingStorageBackend {
96
185
  * shape in the `StorageBackend` interface.
97
186
  */
98
187
  findEdgesGlobal;
188
+ /**
189
+ * Same conditional-install pattern as `findEdgesGlobal`. The router's
190
+ * declared capability set is mirrored from the base (or intersected with
191
+ * the user's `routedCapabilities`) — if `query.aggregate` is in that
192
+ * set, the underlying method must be present, otherwise `client.aggregate()`
193
+ * would resolve `UNSUPPORTED_OPERATION` despite the cap claim. This
194
+ * ensures the "declared capability ⇒ method exists" invariant holds
195
+ * through routing wrappers (Phase 4 audit C1).
196
+ */
197
+ aggregate;
198
+ /**
199
+ * DML pass-throughs. Same conditional-install pattern as `aggregate`:
200
+ * gated on BOTH the base method's existence AND `this.capabilities`
201
+ * advertising `query.dml`. If `routedCapabilities` intersected
202
+ * `query.dml` away (e.g. one routed peer is Firestore Standard which
203
+ * has no pipeline-DML support), the methods are *not* installed even
204
+ * though `base.bulkDelete` exists — otherwise the router would silently
205
+ * outperform what the declared cap set promises across hops. This
206
+ * preserves the "declared capability ⇒ method exists" invariant in
207
+ * both directions (Phase 5).
208
+ */
209
+ bulkDelete;
210
+ bulkUpdate;
211
+ /**
212
+ * Multi-source fan-out pass-through. Same conditional-install pattern as
213
+ * `aggregate` and the bulk-DML methods: gated on BOTH the base method's
214
+ * existence AND `this.capabilities` advertising `query.join`. If
215
+ * `routedCapabilities` intersected `query.join` away (e.g. one routed peer
216
+ * is Firestore Standard which has no pipeline-join support), the method is
217
+ * not installed even though `base.expand` exists. This preserves the
218
+ * "declared capability ⇒ method exists" invariant in both directions.
219
+ *
220
+ * Like `aggregate` and bulk DML, `expand` runs against the base backend
221
+ * only — it cannot fan out across routed children, since each routed
222
+ * subgraph is a separate physical store. Cross-graph hops (which resolve
223
+ * to per-source subgraph readers) are therefore never dispatched through
224
+ * `expand` by `traverse.ts`; the same constraint applies here, naturally.
225
+ */
226
+ expand;
227
+ /**
228
+ * Engine-level multi-hop traversal pass-through. Same conditional-install
229
+ * pattern as `aggregate`, bulk DML, and `expand`: gated on BOTH the base
230
+ * method's existence AND `this.capabilities` advertising
231
+ * `traversal.serverSide`. If `routedCapabilities` intersected
232
+ * `traversal.serverSide` away (e.g. one routed peer is a SQLite-shaped
233
+ * backend that has no nested-pipeline path), the method is not installed
234
+ * even though `base.runEngineTraversal` exists. This preserves the
235
+ * "declared capability ⇒ method exists" invariant in both directions.
236
+ *
237
+ * Like the other extensions, engine traversal runs against the base
238
+ * backend only — a routed child's own `runEngineTraversal` is reached
239
+ * through `.subgraph().runEngineTraversal()` against the routed handle.
240
+ * Cross-graph hops never reach this method anyway: the traversal
241
+ * compiler in `firestore-traverse-compiler.ts` rejects specs whose
242
+ * hops carry `targetGraph`, falling back to the per-hop loop. Routed
243
+ * children are physically distinct backends, so even an "in-graph"
244
+ * traversal across a routed-child boundary is structurally a
245
+ * cross-backend hop and never compiles for engine dispatch.
246
+ */
247
+ runEngineTraversal;
248
+ /**
249
+ * Server-side projection pass-through. Same conditional-install pattern as
250
+ * `aggregate`, bulk DML, and `expand`: gated on BOTH the base method's
251
+ * existence AND `this.capabilities` advertising `query.select`. If
252
+ * `routedCapabilities` intersected `query.select` away (e.g. one routed
253
+ * peer doesn't implement projection), the method is not installed even
254
+ * though `base.findEdgesProjected` exists. This preserves the "declared
255
+ * capability ⇒ method exists" invariant in both directions.
256
+ *
257
+ * Like `aggregate` and `expand`, projection runs against the base backend
258
+ * only — a routed child's own projection is reached through
259
+ * `.subgraph().findEdgesProjected()` against the routed handle.
260
+ */
261
+ findEdgesProjected;
262
+ /**
263
+ * Vector / nearest-neighbour pass-through. Same conditional-install
264
+ * pattern as `aggregate`, bulk DML, `expand`, and `findEdgesProjected`:
265
+ * gated on BOTH the base method's existence AND `this.capabilities`
266
+ * advertising `search.vector`. If `routedCapabilities` intersected
267
+ * `search.vector` away (e.g. one routed peer is a SQLite-shaped backend
268
+ * that has no native ANN index), the method is not installed even
269
+ * though `base.findNearest` exists. This preserves the "declared
270
+ * capability ⇒ method exists" invariant in both directions.
271
+ *
272
+ * Like the other extensions, vector search runs against the base
273
+ * backend only — a routed child's own `findNearest` is reached through
274
+ * `.subgraph().findNearest()` against the routed handle.
275
+ */
276
+ findNearest;
277
+ /**
278
+ * Full-text search pass-through. Same conditional-install pattern as
279
+ * `findNearest`: gated on BOTH the base method's existence AND
280
+ * `this.capabilities` advertising `search.fullText`. If
281
+ * `routedCapabilities` intersected `search.fullText` away (e.g. one
282
+ * routed peer is Firestore Standard or a SQLite-shaped backend that
283
+ * has no native FTS index), the method is not installed even though
284
+ * `base.fullTextSearch` exists. This preserves the "declared
285
+ * capability ⇒ method exists" invariant in both directions.
286
+ *
287
+ * Like the other extensions, FTS runs against the base backend only —
288
+ * a routed child's own `fullTextSearch` is reached through
289
+ * `.subgraph().fullTextSearch()` against the routed handle.
290
+ */
291
+ fullTextSearch;
292
+ /**
293
+ * Geospatial distance pass-through. Same conditional-install pattern
294
+ * as `fullTextSearch`: gated on BOTH the base method's existence AND
295
+ * `this.capabilities` advertising `search.geo`. If `routedCapabilities`
296
+ * intersected `search.geo` away (e.g. one routed peer is Firestore
297
+ * Standard or a SQLite-shaped backend that has no native geo index),
298
+ * the method is not installed even though `base.geoSearch` exists.
299
+ * This preserves the "declared capability ⇒ method exists" invariant
300
+ * in both directions.
301
+ *
302
+ * Like the other extensions, geo search runs against the base backend
303
+ * only — a routed child's own `geoSearch` is reached through
304
+ * `.subgraph().geoSearch()` against the routed handle.
305
+ */
306
+ geoSearch;
99
307
  // --- Pass-through reads ---
100
308
  getDoc(docId) {
101
309
  return this.base.getDoc(docId);
@@ -104,8 +312,8 @@ var RoutingStorageBackend = class _RoutingStorageBackend {
104
312
  return this.base.query(filters, options);
105
313
  }
106
314
  // --- Pass-through writes ---
107
- setDoc(docId, record) {
108
- return this.base.setDoc(docId, record);
315
+ setDoc(docId, record, mode) {
316
+ return this.base.setDoc(docId, record, mode);
109
317
  }
110
318
  updateDoc(docId, update) {
111
319
  return this.base.updateDoc(docId, update);
@@ -132,10 +340,22 @@ var RoutingStorageBackend = class _RoutingStorageBackend {
132
340
  storageScope: childStorageScope
133
341
  });
134
342
  if (routed) {
135
- return new _RoutingStorageBackend(routed, this.options, childStorageScope, childScopePath);
343
+ return new _RoutingStorageBackend(
344
+ routed,
345
+ this.options,
346
+ childStorageScope,
347
+ childScopePath,
348
+ routed.capabilities
349
+ );
136
350
  }
137
351
  const childBase = this.base.subgraph(parentNodeUid, name);
138
- return new _RoutingStorageBackend(childBase, this.options, childStorageScope, childScopePath);
352
+ return new _RoutingStorageBackend(
353
+ childBase,
354
+ this.options,
355
+ childStorageScope,
356
+ childScopePath,
357
+ childBase.capabilities
358
+ );
139
359
  }
140
360
  // --- Bulk operations: delegate, but cascade is base-scope only ---
141
361
  removeNodeCascade(uid, reader, options) {
@@ -161,6 +381,139 @@ function createRoutingBackend(base, options) {
161
381
  return new RoutingStorageBackend(base, options, "", base.scopePath);
162
382
  }
163
383
 
384
+ // src/internal/serialization-tag.ts
385
+ var SERIALIZATION_TAG = "__firegraph_ser__";
386
+ var KNOWN_TYPES = /* @__PURE__ */ new Set(["Timestamp", "GeoPoint", "VectorValue", "DocumentReference"]);
387
+ function isTaggedValue(value) {
388
+ if (value === null || typeof value !== "object") return false;
389
+ const tag = value[SERIALIZATION_TAG];
390
+ return typeof tag === "string" && KNOWN_TYPES.has(tag);
391
+ }
392
+
393
+ // src/internal/write-plan.ts
394
+ var DELETE_FIELD = /* @__PURE__ */ Symbol.for("firegraph.deleteField");
395
+ function deleteField() {
396
+ return DELETE_FIELD;
397
+ }
398
+ function isDeleteSentinel(value) {
399
+ return value === DELETE_FIELD;
400
+ }
401
+ var FIRESTORE_TERMINAL_CTOR = /* @__PURE__ */ new Set([
402
+ "Timestamp",
403
+ "GeoPoint",
404
+ "VectorValue",
405
+ "DocumentReference",
406
+ "FieldValue",
407
+ "NumericIncrementTransform",
408
+ "ArrayUnionTransform",
409
+ "ArrayRemoveTransform",
410
+ "ServerTimestampTransform",
411
+ "DeleteTransform"
412
+ ]);
413
+ function isTerminalValue(value) {
414
+ if (value === null) return true;
415
+ const t = typeof value;
416
+ if (t !== "object") return true;
417
+ if (Array.isArray(value)) return true;
418
+ if (isTaggedValue(value)) return true;
419
+ const proto = Object.getPrototypeOf(value);
420
+ if (proto === null || proto === Object.prototype) return false;
421
+ const ctor = value.constructor;
422
+ if (ctor && typeof ctor.name === "string" && FIRESTORE_TERMINAL_CTOR.has(ctor.name)) return true;
423
+ return true;
424
+ }
425
+ var SAFE_KEY_RE = /^[A-Za-z_][A-Za-z0-9_-]*$/;
426
+ function walkForDeleteSentinels(node, path, parent, visit) {
427
+ if (node === null || node === void 0) return;
428
+ if (isDeleteSentinel(node)) {
429
+ visit({ path, parent });
430
+ return;
431
+ }
432
+ if (typeof node !== "object") return;
433
+ if (isTaggedValue(node)) return;
434
+ if (Array.isArray(node)) {
435
+ for (let i = 0; i < node.length; i++) {
436
+ walkForDeleteSentinels(node[i], [...path, String(i)], { kind: "array", index: i }, visit);
437
+ }
438
+ return;
439
+ }
440
+ const proto = Object.getPrototypeOf(node);
441
+ if (proto !== null && proto !== Object.prototype) return;
442
+ const obj = node;
443
+ for (const key of Object.keys(obj)) {
444
+ walkForDeleteSentinels(obj[key], [...path, key], { kind: "object" }, visit);
445
+ }
446
+ }
447
+ function assertSafePath(path) {
448
+ for (const seg of path) {
449
+ if (!SAFE_KEY_RE.test(seg)) {
450
+ throw new Error(
451
+ `firegraph: unsafe object key ${JSON.stringify(seg)} at path ${path.map((p) => JSON.stringify(p)).join(" > ")}. Keys used inside update payloads must match /^[A-Za-z_][A-Za-z0-9_-]*$/ so they can be embedded safely in SQLite JSON paths.`
452
+ );
453
+ }
454
+ }
455
+ }
456
+ function flattenPatch(data) {
457
+ const ops = [];
458
+ walk(data, [], ops);
459
+ return ops;
460
+ }
461
+ function assertNoDeleteSentinelsInArrayValue(arr, arrayPath) {
462
+ walkForDeleteSentinels(arr, arrayPath, { kind: "root" }, ({ parent }) => {
463
+ const arrayPathStr = arrayPath.length === 0 ? "<root>" : arrayPath.map((p) => JSON.stringify(p)).join(" > ");
464
+ if (parent.kind === "array") {
465
+ throw new Error(
466
+ `firegraph: deleteField() sentinel at index ${parent.index} inside an array at path ${arrayPathStr}. Arrays are terminal in update payloads (replaced as a unit), so the sentinel would be silently dropped by JSON serialization. To remove the field entirely, pass deleteField() in place of the whole array.`
467
+ );
468
+ }
469
+ throw new Error(
470
+ `firegraph: deleteField() sentinel inside an array element at path ${arrayPathStr}. Arrays are terminal in update payloads \u2014 the sentinel would be silently dropped by JSON serialization.`
471
+ );
472
+ });
473
+ }
474
+ function walk(node, path, out) {
475
+ if (node === void 0) return;
476
+ if (isDeleteSentinel(node)) {
477
+ if (path.length === 0) {
478
+ throw new Error("firegraph: deleteField() cannot be the entire update payload.");
479
+ }
480
+ assertSafePath(path);
481
+ out.push({ path: [...path], value: void 0, delete: true });
482
+ return;
483
+ }
484
+ if (isTerminalValue(node)) {
485
+ if (path.length === 0) {
486
+ throw new Error(
487
+ "firegraph: update payload must be a plain object. Got " + (node === null ? "null" : Array.isArray(node) ? "array" : typeof node) + "."
488
+ );
489
+ }
490
+ if (Array.isArray(node)) {
491
+ assertNoDeleteSentinelsInArrayValue(node, path);
492
+ }
493
+ assertSafePath(path);
494
+ out.push({ path: [...path], value: node, delete: false });
495
+ return;
496
+ }
497
+ const obj = node;
498
+ const keys = Object.keys(obj);
499
+ if (keys.length === 0) {
500
+ if (path.length > 0) {
501
+ assertSafePath(path);
502
+ out.push({ path: [...path], value: {}, delete: false });
503
+ }
504
+ return;
505
+ }
506
+ for (const key of keys) {
507
+ if (key === SERIALIZATION_TAG) {
508
+ const where = path.length === 0 ? "<root>" : path.map((p) => JSON.stringify(p)).join(" > ");
509
+ throw new Error(
510
+ `firegraph: update payload contains a literal \`${SERIALIZATION_TAG}\` key at ${where}. That key is reserved for firegraph's serialization envelope and cannot appear on a plain object in user data. Use a different field name, or pass a recognized tagged value through replaceNode/replaceEdge instead.`
511
+ );
512
+ }
513
+ walk(obj[key], [...path, key], out);
514
+ }
515
+ }
516
+
164
517
  // src/scope-path.ts
165
518
  function parseStorageScope(scope) {
166
519
  if (scope === "") return [];
@@ -212,10 +565,17 @@ function appendStorageScope(parentScope, uid, name) {
212
565
  }
213
566
  // Annotate the CommonJS export names for ESM import in node:
214
567
  0 && (module.exports = {
568
+ CapabilityNotSupportedError,
215
569
  CrossBackendTransactionError,
570
+ DELETE_FIELD,
216
571
  appendStorageScope,
572
+ createCapabilities,
217
573
  createRoutingBackend,
574
+ deleteField,
575
+ flattenPatch,
576
+ intersectCapabilities,
218
577
  isAncestorScopeUid,
578
+ isDeleteSentinel,
219
579
  parseStorageScope,
220
580
  resolveAncestorScope
221
581
  });