@typicalday/firegraph 0.11.1 → 0.12.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 (44) hide show
  1. package/README.md +40 -15
  2. package/dist/backend-BsR0lnFL.d.ts +200 -0
  3. package/dist/backend-Ct-fLlkG.d.cts +200 -0
  4. package/dist/backend.cjs +143 -2
  5. package/dist/backend.cjs.map +1 -1
  6. package/dist/backend.d.cts +3 -3
  7. package/dist/backend.d.ts +3 -3
  8. package/dist/backend.js +13 -4
  9. package/dist/backend.js.map +1 -1
  10. package/dist/chunk-AWW4MUJ5.js +245 -0
  11. package/dist/chunk-AWW4MUJ5.js.map +1 -0
  12. package/dist/{chunk-5753Y42M.js → chunk-C2QMD7RY.js} +6 -10
  13. package/dist/chunk-C2QMD7RY.js.map +1 -0
  14. package/dist/chunk-EQJUUVFG.js +14 -0
  15. package/dist/chunk-EQJUUVFG.js.map +1 -0
  16. package/dist/{chunk-6SB34IPQ.js → chunk-HONQY4HF.js} +100 -28
  17. package/dist/chunk-HONQY4HF.js.map +1 -0
  18. package/dist/cloudflare/index.cjs +509 -102
  19. package/dist/cloudflare/index.cjs.map +1 -1
  20. package/dist/cloudflare/index.d.cts +45 -17
  21. package/dist/cloudflare/index.d.ts +45 -17
  22. package/dist/cloudflare/index.js +265 -74
  23. package/dist/cloudflare/index.js.map +1 -1
  24. package/dist/codegen/index.d.cts +1 -1
  25. package/dist/codegen/index.d.ts +1 -1
  26. package/dist/index.cjs +291 -47
  27. package/dist/index.cjs.map +1 -1
  28. package/dist/index.d.cts +59 -77
  29. package/dist/index.d.ts +59 -77
  30. package/dist/index.js +58 -28
  31. package/dist/index.js.map +1 -1
  32. package/dist/registry-B1qsVL0E.d.cts +64 -0
  33. package/dist/registry-Fi074zVa.d.ts +64 -0
  34. package/dist/{serialization-ZZ7RSDRX.js → serialization-OE2PFZMY.js} +6 -4
  35. package/dist/{types-BGWxcpI_.d.cts → types-DxYLy8Ol.d.cts} +36 -2
  36. package/dist/{types-BGWxcpI_.d.ts → types-DxYLy8Ol.d.ts} +36 -2
  37. package/package.json +8 -3
  38. package/dist/backend-U-MLShlg.d.ts +0 -97
  39. package/dist/backend-np4gEVhB.d.cts +0 -97
  40. package/dist/chunk-5753Y42M.js.map +0 -1
  41. package/dist/chunk-6SB34IPQ.js.map +0 -1
  42. package/dist/chunk-R7CRGYY4.js +0 -94
  43. package/dist/chunk-R7CRGYY4.js.map +0 -1
  44. /package/dist/{serialization-ZZ7RSDRX.js.map → serialization-OE2PFZMY.js.map} +0 -0
package/dist/index.cjs CHANGED
@@ -30,6 +30,21 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
30
30
  ));
31
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
32
 
33
+ // src/internal/serialization-tag.ts
34
+ function isTaggedValue(value) {
35
+ if (value === null || typeof value !== "object") return false;
36
+ const tag = value[SERIALIZATION_TAG];
37
+ return typeof tag === "string" && KNOWN_TYPES.has(tag);
38
+ }
39
+ var SERIALIZATION_TAG, KNOWN_TYPES;
40
+ var init_serialization_tag = __esm({
41
+ "src/internal/serialization-tag.ts"() {
42
+ "use strict";
43
+ SERIALIZATION_TAG = "__firegraph_ser__";
44
+ KNOWN_TYPES = /* @__PURE__ */ new Set(["Timestamp", "GeoPoint", "VectorValue", "DocumentReference"]);
45
+ }
46
+ });
47
+
33
48
  // src/serialization.ts
34
49
  var serialization_exports = {};
35
50
  __export(serialization_exports, {
@@ -38,11 +53,6 @@ __export(serialization_exports, {
38
53
  isTaggedValue: () => isTaggedValue,
39
54
  serializeFirestoreTypes: () => serializeFirestoreTypes
40
55
  });
41
- function isTaggedValue(value) {
42
- if (value === null || typeof value !== "object") return false;
43
- const tag = value[SERIALIZATION_TAG];
44
- return typeof tag === "string" && KNOWN_TYPES.has(tag);
45
- }
46
56
  function isTimestamp(value) {
47
57
  return value instanceof import_firestore.Timestamp;
48
58
  }
@@ -143,13 +153,13 @@ function deserializeValue(value, db) {
143
153
  }
144
154
  return result;
145
155
  }
146
- var import_firestore, SERIALIZATION_TAG, KNOWN_TYPES, _docRefWarned;
156
+ var import_firestore, _docRefWarned;
147
157
  var init_serialization = __esm({
148
158
  "src/serialization.ts"() {
149
159
  "use strict";
150
160
  import_firestore = require("@google-cloud/firestore");
151
- SERIALIZATION_TAG = "__firegraph_ser__";
152
- KNOWN_TYPES = /* @__PURE__ */ new Set(["Timestamp", "GeoPoint", "VectorValue", "DocumentReference"]);
161
+ init_serialization_tag();
162
+ init_serialization_tag();
153
163
  _docRefWarned = false;
154
164
  }
155
165
  });
@@ -202,6 +212,7 @@ __export(index_exports, {
202
212
  defaultExecutor: () => defaultExecutor,
203
213
  defineConfig: () => defineConfig,
204
214
  defineViews: () => defineViews,
215
+ deleteField: () => deleteField,
205
216
  deserializeFirestoreTypes: () => deserializeFirestoreTypes,
206
217
  destroySandboxWorker: () => destroySandboxWorker,
207
218
  discoverEntities: () => discoverEntities,
@@ -255,6 +266,146 @@ function computeEdgeDocId(aUid, axbType, bUid) {
255
266
  return `${shard}${SHARD_SEPARATOR}${aUid}${SHARD_SEPARATOR}${axbType}${SHARD_SEPARATOR}${bUid}`;
256
267
  }
257
268
 
269
+ // src/internal/write-plan.ts
270
+ init_serialization_tag();
271
+ var DELETE_FIELD = /* @__PURE__ */ Symbol.for("firegraph.deleteField");
272
+ function deleteField() {
273
+ return DELETE_FIELD;
274
+ }
275
+ function isDeleteSentinel(value) {
276
+ return value === DELETE_FIELD;
277
+ }
278
+ var FIRESTORE_TERMINAL_CTOR = /* @__PURE__ */ new Set([
279
+ "Timestamp",
280
+ "GeoPoint",
281
+ "VectorValue",
282
+ "DocumentReference",
283
+ "FieldValue",
284
+ "NumericIncrementTransform",
285
+ "ArrayUnionTransform",
286
+ "ArrayRemoveTransform",
287
+ "ServerTimestampTransform",
288
+ "DeleteTransform"
289
+ ]);
290
+ function isTerminalValue(value) {
291
+ if (value === null) return true;
292
+ const t = typeof value;
293
+ if (t !== "object") return true;
294
+ if (Array.isArray(value)) return true;
295
+ if (isTaggedValue(value)) return true;
296
+ const proto = Object.getPrototypeOf(value);
297
+ if (proto === null || proto === Object.prototype) return false;
298
+ const ctor = value.constructor;
299
+ if (ctor && typeof ctor.name === "string" && FIRESTORE_TERMINAL_CTOR.has(ctor.name)) return true;
300
+ return true;
301
+ }
302
+ var SAFE_KEY_RE = /^[A-Za-z_][A-Za-z0-9_-]*$/;
303
+ function assertUpdatePayloadExclusive(update) {
304
+ if (update.replaceData !== void 0 && update.dataOps !== void 0) {
305
+ throw new Error(
306
+ "firegraph: UpdatePayload cannot specify both `replaceData` and `dataOps`. Use one or the other \u2014 `replaceData` is the migration-write-back form, `dataOps` is the standard partial-update form."
307
+ );
308
+ }
309
+ }
310
+ function assertNoDeleteSentinels(data, callerLabel) {
311
+ walkForDeleteSentinels(data, [], { kind: "root" }, ({ path }) => {
312
+ const where = path.length === 0 ? "<root>" : path.map((p) => JSON.stringify(p)).join(" > ");
313
+ throw new Error(
314
+ `firegraph: ${callerLabel} payload contains a deleteField() sentinel at ${where}. deleteField() is only valid inside updateNode/updateEdge \u2014 full-data writes (put*, replace*) cannot delete individual fields. Use updateNode with a deleteField() value, or omit the field from the replace payload.`
315
+ );
316
+ });
317
+ }
318
+ function walkForDeleteSentinels(node, path, parent, visit) {
319
+ if (node === null || node === void 0) return;
320
+ if (isDeleteSentinel(node)) {
321
+ visit({ path, parent });
322
+ return;
323
+ }
324
+ if (typeof node !== "object") return;
325
+ if (isTaggedValue(node)) return;
326
+ if (Array.isArray(node)) {
327
+ for (let i = 0; i < node.length; i++) {
328
+ walkForDeleteSentinels(node[i], [...path, String(i)], { kind: "array", index: i }, visit);
329
+ }
330
+ return;
331
+ }
332
+ const proto = Object.getPrototypeOf(node);
333
+ if (proto !== null && proto !== Object.prototype) return;
334
+ const obj = node;
335
+ for (const key of Object.keys(obj)) {
336
+ walkForDeleteSentinels(obj[key], [...path, key], { kind: "object" }, visit);
337
+ }
338
+ }
339
+ function assertSafePath(path) {
340
+ for (const seg of path) {
341
+ if (!SAFE_KEY_RE.test(seg)) {
342
+ throw new Error(
343
+ `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.`
344
+ );
345
+ }
346
+ }
347
+ }
348
+ function flattenPatch(data) {
349
+ const ops = [];
350
+ walk(data, [], ops);
351
+ return ops;
352
+ }
353
+ function assertNoDeleteSentinelsInArrayValue(arr, arrayPath) {
354
+ walkForDeleteSentinels(arr, arrayPath, { kind: "root" }, ({ parent }) => {
355
+ const arrayPathStr = arrayPath.length === 0 ? "<root>" : arrayPath.map((p) => JSON.stringify(p)).join(" > ");
356
+ if (parent.kind === "array") {
357
+ throw new Error(
358
+ `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.`
359
+ );
360
+ }
361
+ throw new Error(
362
+ `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.`
363
+ );
364
+ });
365
+ }
366
+ function walk(node, path, out) {
367
+ if (node === void 0) return;
368
+ if (isDeleteSentinel(node)) {
369
+ if (path.length === 0) {
370
+ throw new Error("firegraph: deleteField() cannot be the entire update payload.");
371
+ }
372
+ assertSafePath(path);
373
+ out.push({ path: [...path], value: void 0, delete: true });
374
+ return;
375
+ }
376
+ if (isTerminalValue(node)) {
377
+ if (path.length === 0) {
378
+ throw new Error(
379
+ "firegraph: update payload must be a plain object. Got " + (node === null ? "null" : Array.isArray(node) ? "array" : typeof node) + "."
380
+ );
381
+ }
382
+ if (Array.isArray(node)) {
383
+ assertNoDeleteSentinelsInArrayValue(node, path);
384
+ }
385
+ assertSafePath(path);
386
+ out.push({ path: [...path], value: node, delete: false });
387
+ return;
388
+ }
389
+ const obj = node;
390
+ const keys = Object.keys(obj);
391
+ if (keys.length === 0) {
392
+ if (path.length > 0) {
393
+ assertSafePath(path);
394
+ out.push({ path: [...path], value: {}, delete: false });
395
+ }
396
+ return;
397
+ }
398
+ for (const key of keys) {
399
+ if (key === SERIALIZATION_TAG) {
400
+ const where = path.length === 0 ? "<root>" : path.map((p) => JSON.stringify(p)).join(" > ");
401
+ throw new Error(
402
+ `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.`
403
+ );
404
+ }
405
+ walk(obj[key], [...path, key], out);
406
+ }
407
+ }
408
+
258
409
  // src/batch.ts
259
410
  function buildWritableNodeRecord(aType, uid, data) {
260
411
  return { aType, aUid: uid, axbType: NODE_RELATION, bType: aType, bUid: uid, data };
@@ -269,6 +420,19 @@ var GraphBatchImpl = class {
269
420
  this.scopePath = scopePath;
270
421
  }
271
422
  async putNode(aType, uid, data) {
423
+ this.writeNode(aType, uid, data, "merge");
424
+ }
425
+ async putEdge(aType, aUid, axbType, bType, bUid, data) {
426
+ this.writeEdge(aType, aUid, axbType, bType, bUid, data, "merge");
427
+ }
428
+ async replaceNode(aType, uid, data) {
429
+ this.writeNode(aType, uid, data, "replace");
430
+ }
431
+ async replaceEdge(aType, aUid, axbType, bType, bUid, data) {
432
+ this.writeEdge(aType, aUid, axbType, bType, bUid, data, "replace");
433
+ }
434
+ writeNode(aType, uid, data, mode) {
435
+ assertNoDeleteSentinels(data, mode === "replace" ? "replaceNode" : "putNode");
272
436
  if (this.registry) {
273
437
  this.registry.validate(aType, NODE_RELATION, aType, data, this.scopePath);
274
438
  }
@@ -280,9 +444,10 @@ var GraphBatchImpl = class {
280
444
  record.v = entry.schemaVersion;
281
445
  }
282
446
  }
283
- this.backend.setDoc(docId, record);
447
+ this.backend.setDoc(docId, record, mode);
284
448
  }
285
- async putEdge(aType, aUid, axbType, bType, bUid, data) {
449
+ writeEdge(aType, aUid, axbType, bType, bUid, data, mode) {
450
+ assertNoDeleteSentinels(data, mode === "replace" ? "replaceEdge" : "putEdge");
286
451
  if (this.registry) {
287
452
  this.registry.validate(aType, axbType, bType, data, this.scopePath);
288
453
  }
@@ -294,11 +459,15 @@ var GraphBatchImpl = class {
294
459
  record.v = entry.schemaVersion;
295
460
  }
296
461
  }
297
- this.backend.setDoc(docId, record);
462
+ this.backend.setDoc(docId, record, mode);
298
463
  }
299
464
  async updateNode(uid, data) {
300
465
  const docId = computeNodeDocId(uid);
301
- this.backend.updateDoc(docId, { dataFields: data });
466
+ this.backend.updateDoc(docId, { dataOps: flattenPatch(data) });
467
+ }
468
+ async updateEdge(aUid, axbType, bUid, data) {
469
+ const docId = computeEdgeDocId(aUid, axbType, bUid);
470
+ this.backend.updateDoc(docId, { dataOps: flattenPatch(data) });
302
471
  }
303
472
  async removeNode(uid) {
304
473
  const docId = computeNodeDocId(uid);
@@ -396,23 +565,29 @@ var CrossBackendTransactionError = class extends FiregraphError {
396
565
  };
397
566
 
398
567
  // src/json-schema.ts
399
- var import_ajv = __toESM(require("ajv"), 1);
400
- var import_ajv_formats = __toESM(require("ajv-formats"), 1);
401
- var ajv = new import_ajv.default({ allErrors: true, strict: false });
402
- (0, import_ajv_formats.default)(ajv);
568
+ var import_json_schema = require("@cfworker/json-schema");
569
+ var MAX_RENDERED_ERRORS = 20;
403
570
  function compileSchema(schema, label) {
404
- const validate = ajv.compile(schema);
571
+ const validator = new import_json_schema.Validator(schema, "2020-12", false);
405
572
  return (data) => {
406
- if (!validate(data)) {
407
- const errors = validate.errors ?? [];
408
- const messages = errors.map((err) => `${err.instancePath || "/"}${err.message ? ": " + err.message : ""}`).join("; ");
573
+ const result = validator.validate(data);
574
+ if (!result.valid) {
575
+ const total = result.errors.length;
576
+ const head = result.errors.slice(0, MAX_RENDERED_ERRORS).map(formatError).join("; ");
577
+ const overflow = total > MAX_RENDERED_ERRORS ? ` (+${total - MAX_RENDERED_ERRORS} more)` : "";
409
578
  throw new ValidationError(
410
- `Data validation failed${label ? " for " + label : ""}: ${messages}`,
411
- errors
579
+ `Data validation failed${label ? " for " + label : ""}: ${head}${overflow}`,
580
+ result.errors
412
581
  );
413
582
  }
414
583
  };
415
584
  }
585
+ function formatError(err) {
586
+ const path = err.instanceLocation.replace(/^#/, "") || "/";
587
+ const keyword = err.keyword ? `[${err.keyword}] ` : "";
588
+ const detail = err.error ? `: ${keyword}${err.error}` : "";
589
+ return `${path}${detail}`;
590
+ }
416
591
  function jsonSchemaToFieldMeta(schema) {
417
592
  if (!schema || schema.type !== "object" || !schema.properties) return [];
418
593
  const requiredSet = new Set(Array.isArray(schema.required) ? schema.required : []);
@@ -1145,8 +1320,11 @@ var BOOTSTRAP_ENTRIES = [
1145
1320
  description: "Meta-type: defines an edge type"
1146
1321
  }
1147
1322
  ];
1323
+ var _bootstrapRegistry = null;
1148
1324
  function createBootstrapRegistry() {
1149
- return createRegistry([...BOOTSTRAP_ENTRIES]);
1325
+ if (_bootstrapRegistry) return _bootstrapRegistry;
1326
+ _bootstrapRegistry = createRegistry([...BOOTSTRAP_ENTRIES]);
1327
+ return _bootstrapRegistry;
1150
1328
  }
1151
1329
  function generateDeterministicUid(metaType, name) {
1152
1330
  const hash = (0, import_node_crypto3.createHash)("sha256").update(`${metaType}:${name}`).digest("base64url");
@@ -1400,6 +1578,19 @@ var GraphTransactionImpl = class {
1400
1578
  return results.map((r) => r.record);
1401
1579
  }
1402
1580
  async putNode(aType, uid, data) {
1581
+ await this.writeNode(aType, uid, data, "merge");
1582
+ }
1583
+ async putEdge(aType, aUid, axbType, bType, bUid, data) {
1584
+ await this.writeEdge(aType, aUid, axbType, bType, bUid, data, "merge");
1585
+ }
1586
+ async replaceNode(aType, uid, data) {
1587
+ await this.writeNode(aType, uid, data, "replace");
1588
+ }
1589
+ async replaceEdge(aType, aUid, axbType, bType, bUid, data) {
1590
+ await this.writeEdge(aType, aUid, axbType, bType, bUid, data, "replace");
1591
+ }
1592
+ async writeNode(aType, uid, data, mode) {
1593
+ assertNoDeleteSentinels(data, mode === "replace" ? "replaceNode" : "putNode");
1403
1594
  if (this.registry) {
1404
1595
  this.registry.validate(aType, NODE_RELATION, aType, data, this.scopePath);
1405
1596
  }
@@ -1411,9 +1602,10 @@ var GraphTransactionImpl = class {
1411
1602
  record.v = entry.schemaVersion;
1412
1603
  }
1413
1604
  }
1414
- await this.backend.setDoc(docId, record);
1605
+ await this.backend.setDoc(docId, record, mode);
1415
1606
  }
1416
- async putEdge(aType, aUid, axbType, bType, bUid, data) {
1607
+ async writeEdge(aType, aUid, axbType, bType, bUid, data, mode) {
1608
+ assertNoDeleteSentinels(data, mode === "replace" ? "replaceEdge" : "putEdge");
1417
1609
  if (this.registry) {
1418
1610
  this.registry.validate(aType, axbType, bType, data, this.scopePath);
1419
1611
  }
@@ -1425,11 +1617,15 @@ var GraphTransactionImpl = class {
1425
1617
  record.v = entry.schemaVersion;
1426
1618
  }
1427
1619
  }
1428
- await this.backend.setDoc(docId, record);
1620
+ await this.backend.setDoc(docId, record, mode);
1429
1621
  }
1430
1622
  async updateNode(uid, data) {
1431
1623
  const docId = computeNodeDocId(uid);
1432
- await this.backend.updateDoc(docId, { dataFields: data });
1624
+ await this.backend.updateDoc(docId, { dataOps: flattenPatch(data) });
1625
+ }
1626
+ async updateEdge(aUid, axbType, bUid, data) {
1627
+ const docId = computeEdgeDocId(aUid, axbType, bUid);
1628
+ await this.backend.updateDoc(docId, { dataOps: flattenPatch(data) });
1433
1629
  }
1434
1630
  async removeNode(uid) {
1435
1631
  const docId = computeNodeDocId(uid);
@@ -1628,6 +1824,19 @@ var GraphClientImpl = class _GraphClientImpl {
1628
1824
  // GraphWriter
1629
1825
  // ---------------------------------------------------------------------------
1630
1826
  async putNode(aType, uid, data) {
1827
+ await this.writeNode(aType, uid, data, "merge");
1828
+ }
1829
+ async putEdge(aType, aUid, axbType, bType, bUid, data) {
1830
+ await this.writeEdge(aType, aUid, axbType, bType, bUid, data, "merge");
1831
+ }
1832
+ async replaceNode(aType, uid, data) {
1833
+ await this.writeNode(aType, uid, data, "replace");
1834
+ }
1835
+ async replaceEdge(aType, aUid, axbType, bType, bUid, data) {
1836
+ await this.writeEdge(aType, aUid, axbType, bType, bUid, data, "replace");
1837
+ }
1838
+ async writeNode(aType, uid, data, mode) {
1839
+ assertNoDeleteSentinels(data, mode === "replace" ? "replaceNode" : "putNode");
1631
1840
  const registry = this.getRegistryForType(aType);
1632
1841
  if (registry) {
1633
1842
  registry.validate(aType, NODE_RELATION, aType, data, this.backend.scopePath);
@@ -1641,9 +1850,10 @@ var GraphClientImpl = class _GraphClientImpl {
1641
1850
  record.v = entry.schemaVersion;
1642
1851
  }
1643
1852
  }
1644
- await backend.setDoc(docId, record);
1853
+ await backend.setDoc(docId, record, mode);
1645
1854
  }
1646
- async putEdge(aType, aUid, axbType, bType, bUid, data) {
1855
+ async writeEdge(aType, aUid, axbType, bType, bUid, data, mode) {
1856
+ assertNoDeleteSentinels(data, mode === "replace" ? "replaceEdge" : "putEdge");
1647
1857
  const registry = this.getRegistryForType(aType);
1648
1858
  if (registry) {
1649
1859
  registry.validate(aType, axbType, bType, data, this.backend.scopePath);
@@ -1657,11 +1867,15 @@ var GraphClientImpl = class _GraphClientImpl {
1657
1867
  record.v = entry.schemaVersion;
1658
1868
  }
1659
1869
  }
1660
- await backend.setDoc(docId, record);
1870
+ await backend.setDoc(docId, record, mode);
1661
1871
  }
1662
1872
  async updateNode(uid, data) {
1663
1873
  const docId = computeNodeDocId(uid);
1664
- await this.backend.updateDoc(docId, { dataFields: data });
1874
+ await this.backend.updateDoc(docId, { dataOps: flattenPatch(data) });
1875
+ }
1876
+ async updateEdge(aUid, axbType, bUid, data) {
1877
+ const docId = computeEdgeDocId(aUid, axbType, bUid);
1878
+ await this.backend.updateDoc(docId, { dataOps: flattenPatch(data) });
1665
1879
  }
1666
1880
  async removeNode(uid) {
1667
1881
  const docId = computeNodeDocId(uid);
@@ -2335,8 +2549,12 @@ function createFirestoreAdapter(db, collectionPath) {
2335
2549
  if (!snap.exists) return null;
2336
2550
  return snap.data();
2337
2551
  },
2338
- async setDoc(docId, data) {
2339
- await collectionRef.doc(docId).set(data);
2552
+ async setDoc(docId, data, options) {
2553
+ if (options?.merge) {
2554
+ await collectionRef.doc(docId).set(data, { merge: true });
2555
+ } else {
2556
+ await collectionRef.doc(docId).set(data);
2557
+ }
2340
2558
  },
2341
2559
  async updateDoc(docId, data) {
2342
2560
  await collectionRef.doc(docId).update(data);
@@ -2368,8 +2586,12 @@ function createTransactionAdapter(db, collectionPath, tx) {
2368
2586
  if (!snap.exists) return null;
2369
2587
  return snap.data();
2370
2588
  },
2371
- setDoc(docId, data) {
2372
- tx.set(collectionRef.doc(docId), data);
2589
+ setDoc(docId, data, options) {
2590
+ if (options?.merge) {
2591
+ tx.set(collectionRef.doc(docId), data, { merge: true });
2592
+ } else {
2593
+ tx.set(collectionRef.doc(docId), data);
2594
+ }
2373
2595
  },
2374
2596
  updateDoc(docId, data) {
2375
2597
  tx.update(collectionRef.doc(docId), data);
@@ -2397,8 +2619,12 @@ function createBatchAdapter(db, collectionPath) {
2397
2619
  const collectionRef = db.collection(collectionPath);
2398
2620
  const batch = db.batch();
2399
2621
  return {
2400
- setDoc(docId, data) {
2401
- batch.set(collectionRef.doc(docId), data);
2622
+ setDoc(docId, data, options) {
2623
+ if (options?.merge) {
2624
+ batch.set(collectionRef.doc(docId), data, { merge: true });
2625
+ } else {
2626
+ batch.set(collectionRef.doc(docId), data);
2627
+ }
2402
2628
  },
2403
2629
  updateDoc(docId, data) {
2404
2630
  batch.update(collectionRef.doc(docId), data);
@@ -2474,16 +2700,21 @@ function createPipelineQueryAdapter(db, collectionPath) {
2474
2700
  }
2475
2701
 
2476
2702
  // src/internal/firestore-backend.ts
2703
+ function dottedDataPath(op) {
2704
+ assertSafePath(op.path);
2705
+ return `data.${op.path.join(".")}`;
2706
+ }
2477
2707
  function buildFirestoreUpdate(update, db) {
2708
+ assertUpdatePayloadExclusive(update);
2478
2709
  const out = {
2479
2710
  updatedAt: import_firestore2.FieldValue.serverTimestamp()
2480
2711
  };
2481
2712
  if (update.replaceData) {
2482
2713
  out.data = deserializeFirestoreTypes(update.replaceData, db);
2483
- }
2484
- if (update.dataFields) {
2485
- for (const [k, v] of Object.entries(update.dataFields)) {
2486
- out[`data.${k}`] = v;
2714
+ } else if (update.dataOps) {
2715
+ for (const op of update.dataOps) {
2716
+ const key = dottedDataPath(op);
2717
+ out[key] = op.delete ? import_firestore2.FieldValue.delete() : op.value;
2487
2718
  }
2488
2719
  }
2489
2720
  if (update.v !== void 0) {
@@ -2517,8 +2748,12 @@ var FirestoreTransactionBackend = class {
2517
2748
  query(filters, options) {
2518
2749
  return this.adapter.query(filters, options);
2519
2750
  }
2520
- async setDoc(docId, record) {
2521
- this.adapter.setDoc(docId, stampWritableRecord(record));
2751
+ async setDoc(docId, record, mode) {
2752
+ this.adapter.setDoc(
2753
+ docId,
2754
+ stampWritableRecord(record),
2755
+ mode === "merge" ? { merge: true } : void 0
2756
+ );
2522
2757
  }
2523
2758
  async updateDoc(docId, update) {
2524
2759
  this.adapter.updateDoc(docId, buildFirestoreUpdate(update, this.db));
@@ -2532,8 +2767,12 @@ var FirestoreBatchBackend = class {
2532
2767
  this.adapter = adapter;
2533
2768
  this.db = db;
2534
2769
  }
2535
- setDoc(docId, record) {
2536
- this.adapter.setDoc(docId, stampWritableRecord(record));
2770
+ setDoc(docId, record, mode) {
2771
+ this.adapter.setDoc(
2772
+ docId,
2773
+ stampWritableRecord(record),
2774
+ mode === "merge" ? { merge: true } : void 0
2775
+ );
2537
2776
  }
2538
2777
  updateDoc(docId, update) {
2539
2778
  this.adapter.updateDoc(docId, buildFirestoreUpdate(update, this.db));
@@ -2571,8 +2810,12 @@ var FirestoreBackendImpl = class _FirestoreBackendImpl {
2571
2810
  return this.adapter.query(filters, options);
2572
2811
  }
2573
2812
  // --- Writes ---
2574
- setDoc(docId, record) {
2575
- return this.adapter.setDoc(docId, stampWritableRecord(record));
2813
+ setDoc(docId, record, mode) {
2814
+ return this.adapter.setDoc(
2815
+ docId,
2816
+ stampWritableRecord(record),
2817
+ mode === "merge" ? { merge: true } : void 0
2818
+ );
2576
2819
  }
2577
2820
  updateDoc(docId, update) {
2578
2821
  return this.adapter.updateDoc(docId, buildFirestoreUpdate(update, this.db));
@@ -3432,6 +3675,7 @@ function defineViews(input) {
3432
3675
  defaultExecutor,
3433
3676
  defineConfig,
3434
3677
  defineViews,
3678
+ deleteField,
3435
3679
  deserializeFirestoreTypes,
3436
3680
  destroySandboxWorker,
3437
3681
  discoverEntities,