@typicalday/firegraph 0.11.2 → 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 +38 -5
  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-NJSOD64C.js → chunk-HONQY4HF.js} +80 -17
  17. package/dist/chunk-HONQY4HF.js.map +1 -0
  18. package/dist/cloudflare/index.cjs +458 -73
  19. package/dist/cloudflare/index.cjs.map +1 -1
  20. package/dist/cloudflare/index.d.cts +8 -5
  21. package/dist/cloudflare/index.d.ts +8 -5
  22. package/dist/cloudflare/index.js +234 -56
  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 +271 -36
  27. package/dist/index.cjs.map +1 -1
  28. package/dist/index.d.cts +21 -69
  29. package/dist/index.d.ts +21 -69
  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 +1 -1
  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-NJSOD64C.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);
@@ -1409,6 +1578,19 @@ var GraphTransactionImpl = class {
1409
1578
  return results.map((r) => r.record);
1410
1579
  }
1411
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");
1412
1594
  if (this.registry) {
1413
1595
  this.registry.validate(aType, NODE_RELATION, aType, data, this.scopePath);
1414
1596
  }
@@ -1420,9 +1602,10 @@ var GraphTransactionImpl = class {
1420
1602
  record.v = entry.schemaVersion;
1421
1603
  }
1422
1604
  }
1423
- await this.backend.setDoc(docId, record);
1605
+ await this.backend.setDoc(docId, record, mode);
1424
1606
  }
1425
- 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");
1426
1609
  if (this.registry) {
1427
1610
  this.registry.validate(aType, axbType, bType, data, this.scopePath);
1428
1611
  }
@@ -1434,11 +1617,15 @@ var GraphTransactionImpl = class {
1434
1617
  record.v = entry.schemaVersion;
1435
1618
  }
1436
1619
  }
1437
- await this.backend.setDoc(docId, record);
1620
+ await this.backend.setDoc(docId, record, mode);
1438
1621
  }
1439
1622
  async updateNode(uid, data) {
1440
1623
  const docId = computeNodeDocId(uid);
1441
- 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) });
1442
1629
  }
1443
1630
  async removeNode(uid) {
1444
1631
  const docId = computeNodeDocId(uid);
@@ -1637,6 +1824,19 @@ var GraphClientImpl = class _GraphClientImpl {
1637
1824
  // GraphWriter
1638
1825
  // ---------------------------------------------------------------------------
1639
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");
1640
1840
  const registry = this.getRegistryForType(aType);
1641
1841
  if (registry) {
1642
1842
  registry.validate(aType, NODE_RELATION, aType, data, this.backend.scopePath);
@@ -1650,9 +1850,10 @@ var GraphClientImpl = class _GraphClientImpl {
1650
1850
  record.v = entry.schemaVersion;
1651
1851
  }
1652
1852
  }
1653
- await backend.setDoc(docId, record);
1853
+ await backend.setDoc(docId, record, mode);
1654
1854
  }
1655
- 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");
1656
1857
  const registry = this.getRegistryForType(aType);
1657
1858
  if (registry) {
1658
1859
  registry.validate(aType, axbType, bType, data, this.backend.scopePath);
@@ -1666,11 +1867,15 @@ var GraphClientImpl = class _GraphClientImpl {
1666
1867
  record.v = entry.schemaVersion;
1667
1868
  }
1668
1869
  }
1669
- await backend.setDoc(docId, record);
1870
+ await backend.setDoc(docId, record, mode);
1670
1871
  }
1671
1872
  async updateNode(uid, data) {
1672
1873
  const docId = computeNodeDocId(uid);
1673
- 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) });
1674
1879
  }
1675
1880
  async removeNode(uid) {
1676
1881
  const docId = computeNodeDocId(uid);
@@ -2344,8 +2549,12 @@ function createFirestoreAdapter(db, collectionPath) {
2344
2549
  if (!snap.exists) return null;
2345
2550
  return snap.data();
2346
2551
  },
2347
- async setDoc(docId, data) {
2348
- 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
+ }
2349
2558
  },
2350
2559
  async updateDoc(docId, data) {
2351
2560
  await collectionRef.doc(docId).update(data);
@@ -2377,8 +2586,12 @@ function createTransactionAdapter(db, collectionPath, tx) {
2377
2586
  if (!snap.exists) return null;
2378
2587
  return snap.data();
2379
2588
  },
2380
- setDoc(docId, data) {
2381
- 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
+ }
2382
2595
  },
2383
2596
  updateDoc(docId, data) {
2384
2597
  tx.update(collectionRef.doc(docId), data);
@@ -2406,8 +2619,12 @@ function createBatchAdapter(db, collectionPath) {
2406
2619
  const collectionRef = db.collection(collectionPath);
2407
2620
  const batch = db.batch();
2408
2621
  return {
2409
- setDoc(docId, data) {
2410
- 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
+ }
2411
2628
  },
2412
2629
  updateDoc(docId, data) {
2413
2630
  batch.update(collectionRef.doc(docId), data);
@@ -2483,16 +2700,21 @@ function createPipelineQueryAdapter(db, collectionPath) {
2483
2700
  }
2484
2701
 
2485
2702
  // src/internal/firestore-backend.ts
2703
+ function dottedDataPath(op) {
2704
+ assertSafePath(op.path);
2705
+ return `data.${op.path.join(".")}`;
2706
+ }
2486
2707
  function buildFirestoreUpdate(update, db) {
2708
+ assertUpdatePayloadExclusive(update);
2487
2709
  const out = {
2488
2710
  updatedAt: import_firestore2.FieldValue.serverTimestamp()
2489
2711
  };
2490
2712
  if (update.replaceData) {
2491
2713
  out.data = deserializeFirestoreTypes(update.replaceData, db);
2492
- }
2493
- if (update.dataFields) {
2494
- for (const [k, v] of Object.entries(update.dataFields)) {
2495
- 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;
2496
2718
  }
2497
2719
  }
2498
2720
  if (update.v !== void 0) {
@@ -2526,8 +2748,12 @@ var FirestoreTransactionBackend = class {
2526
2748
  query(filters, options) {
2527
2749
  return this.adapter.query(filters, options);
2528
2750
  }
2529
- async setDoc(docId, record) {
2530
- 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
+ );
2531
2757
  }
2532
2758
  async updateDoc(docId, update) {
2533
2759
  this.adapter.updateDoc(docId, buildFirestoreUpdate(update, this.db));
@@ -2541,8 +2767,12 @@ var FirestoreBatchBackend = class {
2541
2767
  this.adapter = adapter;
2542
2768
  this.db = db;
2543
2769
  }
2544
- setDoc(docId, record) {
2545
- 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
+ );
2546
2776
  }
2547
2777
  updateDoc(docId, update) {
2548
2778
  this.adapter.updateDoc(docId, buildFirestoreUpdate(update, this.db));
@@ -2580,8 +2810,12 @@ var FirestoreBackendImpl = class _FirestoreBackendImpl {
2580
2810
  return this.adapter.query(filters, options);
2581
2811
  }
2582
2812
  // --- Writes ---
2583
- setDoc(docId, record) {
2584
- 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
+ );
2585
2819
  }
2586
2820
  updateDoc(docId, update) {
2587
2821
  return this.adapter.updateDoc(docId, buildFirestoreUpdate(update, this.db));
@@ -3441,6 +3675,7 @@ function defineViews(input) {
3441
3675
  defaultExecutor,
3442
3676
  defineConfig,
3443
3677
  defineViews,
3678
+ deleteField,
3444
3679
  deserializeFirestoreTypes,
3445
3680
  destroySandboxWorker,
3446
3681
  discoverEntities,