@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
@@ -6,8 +6,10 @@ import {
6
6
  QuerySafetyError,
7
7
  RegistryScopeError,
8
8
  RegistryViolationError,
9
- ValidationError
10
- } from "./chunk-R7CRGYY4.js";
9
+ ValidationError,
10
+ assertNoDeleteSentinels,
11
+ flattenPatch
12
+ } from "./chunk-TK64DNVK.js";
11
13
 
12
14
  // src/internal/constants.ts
13
15
  var NODE_RELATION = "is";
@@ -649,7 +651,7 @@ function hashSource(source) {
649
651
  var _serializationModule = null;
650
652
  async function loadSerialization() {
651
653
  if (_serializationModule) return _serializationModule;
652
- _serializationModule = await import("./serialization-ZZ7RSDRX.js");
654
+ _serializationModule = await import("./serialization-OE2PFZMY.js");
653
655
  return _serializationModule;
654
656
  }
655
657
  function defaultExecutor(source) {
@@ -969,6 +971,19 @@ var GraphBatchImpl = class {
969
971
  this.scopePath = scopePath;
970
972
  }
971
973
  async putNode(aType, uid, data) {
974
+ this.writeNode(aType, uid, data, "merge");
975
+ }
976
+ async putEdge(aType, aUid, axbType, bType, bUid, data) {
977
+ this.writeEdge(aType, aUid, axbType, bType, bUid, data, "merge");
978
+ }
979
+ async replaceNode(aType, uid, data) {
980
+ this.writeNode(aType, uid, data, "replace");
981
+ }
982
+ async replaceEdge(aType, aUid, axbType, bType, bUid, data) {
983
+ this.writeEdge(aType, aUid, axbType, bType, bUid, data, "replace");
984
+ }
985
+ writeNode(aType, uid, data, mode) {
986
+ assertNoDeleteSentinels(data, mode === "replace" ? "replaceNode" : "putNode");
972
987
  if (this.registry) {
973
988
  this.registry.validate(aType, NODE_RELATION, aType, data, this.scopePath);
974
989
  }
@@ -980,9 +995,10 @@ var GraphBatchImpl = class {
980
995
  record.v = entry.schemaVersion;
981
996
  }
982
997
  }
983
- this.backend.setDoc(docId, record);
998
+ this.backend.setDoc(docId, record, mode);
984
999
  }
985
- async putEdge(aType, aUid, axbType, bType, bUid, data) {
1000
+ writeEdge(aType, aUid, axbType, bType, bUid, data, mode) {
1001
+ assertNoDeleteSentinels(data, mode === "replace" ? "replaceEdge" : "putEdge");
986
1002
  if (this.registry) {
987
1003
  this.registry.validate(aType, axbType, bType, data, this.scopePath);
988
1004
  }
@@ -994,11 +1010,15 @@ var GraphBatchImpl = class {
994
1010
  record.v = entry.schemaVersion;
995
1011
  }
996
1012
  }
997
- this.backend.setDoc(docId, record);
1013
+ this.backend.setDoc(docId, record, mode);
998
1014
  }
999
1015
  async updateNode(uid, data) {
1000
1016
  const docId = computeNodeDocId(uid);
1001
- this.backend.updateDoc(docId, { dataFields: data });
1017
+ this.backend.updateDoc(docId, { dataOps: flattenPatch(data) });
1018
+ }
1019
+ async updateEdge(aUid, axbType, bUid, data) {
1020
+ const docId = computeEdgeDocId(aUid, axbType, bUid);
1021
+ this.backend.updateDoc(docId, { dataOps: flattenPatch(data) });
1002
1022
  }
1003
1023
  async removeNode(uid) {
1004
1024
  const docId = computeNodeDocId(uid);
@@ -1107,6 +1127,19 @@ var GraphTransactionImpl = class {
1107
1127
  return results.map((r) => r.record);
1108
1128
  }
1109
1129
  async putNode(aType, uid, data) {
1130
+ await this.writeNode(aType, uid, data, "merge");
1131
+ }
1132
+ async putEdge(aType, aUid, axbType, bType, bUid, data) {
1133
+ await this.writeEdge(aType, aUid, axbType, bType, bUid, data, "merge");
1134
+ }
1135
+ async replaceNode(aType, uid, data) {
1136
+ await this.writeNode(aType, uid, data, "replace");
1137
+ }
1138
+ async replaceEdge(aType, aUid, axbType, bType, bUid, data) {
1139
+ await this.writeEdge(aType, aUid, axbType, bType, bUid, data, "replace");
1140
+ }
1141
+ async writeNode(aType, uid, data, mode) {
1142
+ assertNoDeleteSentinels(data, mode === "replace" ? "replaceNode" : "putNode");
1110
1143
  if (this.registry) {
1111
1144
  this.registry.validate(aType, NODE_RELATION, aType, data, this.scopePath);
1112
1145
  }
@@ -1118,9 +1151,10 @@ var GraphTransactionImpl = class {
1118
1151
  record.v = entry.schemaVersion;
1119
1152
  }
1120
1153
  }
1121
- await this.backend.setDoc(docId, record);
1154
+ await this.backend.setDoc(docId, record, mode);
1122
1155
  }
1123
- async putEdge(aType, aUid, axbType, bType, bUid, data) {
1156
+ async writeEdge(aType, aUid, axbType, bType, bUid, data, mode) {
1157
+ assertNoDeleteSentinels(data, mode === "replace" ? "replaceEdge" : "putEdge");
1124
1158
  if (this.registry) {
1125
1159
  this.registry.validate(aType, axbType, bType, data, this.scopePath);
1126
1160
  }
@@ -1132,11 +1166,15 @@ var GraphTransactionImpl = class {
1132
1166
  record.v = entry.schemaVersion;
1133
1167
  }
1134
1168
  }
1135
- await this.backend.setDoc(docId, record);
1169
+ await this.backend.setDoc(docId, record, mode);
1136
1170
  }
1137
1171
  async updateNode(uid, data) {
1138
1172
  const docId = computeNodeDocId(uid);
1139
- await this.backend.updateDoc(docId, { dataFields: data });
1173
+ await this.backend.updateDoc(docId, { dataOps: flattenPatch(data) });
1174
+ }
1175
+ async updateEdge(aUid, axbType, bUid, data) {
1176
+ const docId = computeEdgeDocId(aUid, axbType, bUid);
1177
+ await this.backend.updateDoc(docId, { dataOps: flattenPatch(data) });
1140
1178
  }
1141
1179
  async removeNode(uid) {
1142
1180
  const docId = computeNodeDocId(uid);
@@ -1174,6 +1212,15 @@ var GraphClientImpl = class _GraphClientImpl {
1174
1212
  this.scanProtection = options?.scanProtection ?? "error";
1175
1213
  }
1176
1214
  scanProtection;
1215
+ /**
1216
+ * Capability set of the underlying backend. Mirrors `backend.capabilities`
1217
+ * verbatim so callers can portability-check (`client.capabilities.has(
1218
+ * 'query.join')`) without reaching for the backend handle. Static for the
1219
+ * lifetime of the client.
1220
+ */
1221
+ get capabilities() {
1222
+ return this.backend.capabilities;
1223
+ }
1177
1224
  // Static mode
1178
1225
  staticRegistry;
1179
1226
  // Dynamic mode
@@ -1335,6 +1382,19 @@ var GraphClientImpl = class _GraphClientImpl {
1335
1382
  // GraphWriter
1336
1383
  // ---------------------------------------------------------------------------
1337
1384
  async putNode(aType, uid, data) {
1385
+ await this.writeNode(aType, uid, data, "merge");
1386
+ }
1387
+ async putEdge(aType, aUid, axbType, bType, bUid, data) {
1388
+ await this.writeEdge(aType, aUid, axbType, bType, bUid, data, "merge");
1389
+ }
1390
+ async replaceNode(aType, uid, data) {
1391
+ await this.writeNode(aType, uid, data, "replace");
1392
+ }
1393
+ async replaceEdge(aType, aUid, axbType, bType, bUid, data) {
1394
+ await this.writeEdge(aType, aUid, axbType, bType, bUid, data, "replace");
1395
+ }
1396
+ async writeNode(aType, uid, data, mode) {
1397
+ assertNoDeleteSentinels(data, mode === "replace" ? "replaceNode" : "putNode");
1338
1398
  const registry = this.getRegistryForType(aType);
1339
1399
  if (registry) {
1340
1400
  registry.validate(aType, NODE_RELATION, aType, data, this.backend.scopePath);
@@ -1348,9 +1408,10 @@ var GraphClientImpl = class _GraphClientImpl {
1348
1408
  record.v = entry.schemaVersion;
1349
1409
  }
1350
1410
  }
1351
- await backend.setDoc(docId, record);
1411
+ await backend.setDoc(docId, record, mode);
1352
1412
  }
1353
- async putEdge(aType, aUid, axbType, bType, bUid, data) {
1413
+ async writeEdge(aType, aUid, axbType, bType, bUid, data, mode) {
1414
+ assertNoDeleteSentinels(data, mode === "replace" ? "replaceEdge" : "putEdge");
1354
1415
  const registry = this.getRegistryForType(aType);
1355
1416
  if (registry) {
1356
1417
  registry.validate(aType, axbType, bType, data, this.backend.scopePath);
@@ -1364,11 +1425,15 @@ var GraphClientImpl = class _GraphClientImpl {
1364
1425
  record.v = entry.schemaVersion;
1365
1426
  }
1366
1427
  }
1367
- await backend.setDoc(docId, record);
1428
+ await backend.setDoc(docId, record, mode);
1368
1429
  }
1369
1430
  async updateNode(uid, data) {
1370
1431
  const docId = computeNodeDocId(uid);
1371
- await this.backend.updateDoc(docId, { dataFields: data });
1432
+ await this.backend.updateDoc(docId, { dataOps: flattenPatch(data) });
1433
+ }
1434
+ async updateEdge(aUid, axbType, bUid, data) {
1435
+ const docId = computeEdgeDocId(aUid, axbType, bUid);
1436
+ await this.backend.updateDoc(docId, { dataOps: flattenPatch(data) });
1372
1437
  }
1373
1438
  async removeNode(uid) {
1374
1439
  const docId = computeNodeDocId(uid);
@@ -1450,6 +1515,33 @@ var GraphClientImpl = class _GraphClientImpl {
1450
1515
  return this.applyMigrations(records);
1451
1516
  }
1452
1517
  // ---------------------------------------------------------------------------
1518
+ // Aggregate query (capability: query.aggregate)
1519
+ // ---------------------------------------------------------------------------
1520
+ async aggregate(params) {
1521
+ if (!this.backend.aggregate) {
1522
+ throw new FiregraphError(
1523
+ "aggregate() is not supported by the current storage backend.",
1524
+ "UNSUPPORTED_OPERATION"
1525
+ );
1526
+ }
1527
+ const hasAnyFilter = params.aType || params.aUid || params.axbType || params.bType || params.bUid || params.where && params.where.length > 0;
1528
+ if (!hasAnyFilter) {
1529
+ this.checkQuerySafety([], params.allowCollectionScan);
1530
+ const result2 = await this.backend.aggregate(params.aggregates, []);
1531
+ return result2;
1532
+ }
1533
+ const plan = buildEdgeQueryPlan(params);
1534
+ if (plan.strategy === "get") {
1535
+ throw new FiregraphError(
1536
+ "aggregate() requires a query, not a direct document lookup. Omit one of aUid/axbType/bUid to force a query strategy.",
1537
+ "INVALID_QUERY"
1538
+ );
1539
+ }
1540
+ this.checkQuerySafety(plan.filters, params.allowCollectionScan);
1541
+ const result = await this.backend.aggregate(params.aggregates, plan.filters);
1542
+ return result;
1543
+ }
1544
+ // ---------------------------------------------------------------------------
1453
1545
  // Bulk operations
1454
1546
  // ---------------------------------------------------------------------------
1455
1547
  async removeNodeCascade(uid, options) {
@@ -1459,6 +1551,327 @@ var GraphClientImpl = class _GraphClientImpl {
1459
1551
  return this.backend.bulkRemoveEdges(params, this, options);
1460
1552
  }
1461
1553
  // ---------------------------------------------------------------------------
1554
+ // Server-side DML (capability: query.dml)
1555
+ // ---------------------------------------------------------------------------
1556
+ /**
1557
+ * Single-statement bulk DELETE. Translates `params` to a filter list via
1558
+ * `buildEdgeQueryPlan` (the same plan `findEdges` uses) and dispatches to
1559
+ * `backend.bulkDelete`. The fetch-then-delete loop in `bulkRemoveEdges`
1560
+ * is the cap-less fallback; this method is the fast path on backends
1561
+ * declaring `query.dml`.
1562
+ *
1563
+ * Scan-protection rules match `findEdges`: a query with no identifying
1564
+ * fields requires `allowCollectionScan: true` to pass. A bare-empty
1565
+ * filter set (no `aType`, `aUid`, etc., no `where`) is allowed at this
1566
+ * layer — shared SQLite bounds the blast radius via its leading `scope`
1567
+ * predicate — but the DO RPC backend rejects empty filters at the wire
1568
+ * boundary as defense-in-depth. To wipe a routed subgraph DO, use
1569
+ * `removeNodeCascade` on the parent node instead.
1570
+ */
1571
+ async bulkDelete(params, options) {
1572
+ if (!this.backend.bulkDelete) {
1573
+ throw new FiregraphError(
1574
+ "bulkDelete() is not supported by the current storage backend. Fall back to bulkRemoveEdges() for backends without query.dml (e.g. Firestore Standard).",
1575
+ "UNSUPPORTED_OPERATION"
1576
+ );
1577
+ }
1578
+ const filters = this.buildDmlFilters(params);
1579
+ return this.backend.bulkDelete(filters, options);
1580
+ }
1581
+ /**
1582
+ * Single-statement bulk UPDATE. Same translation path as `bulkDelete`,
1583
+ * but the patch is deep-merged into each matching row's `data` via the
1584
+ * shared `flattenPatch` pipeline. Identifying columns are immutable
1585
+ * through this path (see `BulkUpdatePatch` JSDoc).
1586
+ *
1587
+ * Empty-patch rejection happens inside the backend (`compileBulkUpdate`)
1588
+ * — a `data: {}` payload would only rewrite `updated_at`, which is
1589
+ * almost certainly a bug.
1590
+ */
1591
+ async bulkUpdate(params, patch, options) {
1592
+ if (!this.backend.bulkUpdate) {
1593
+ throw new FiregraphError(
1594
+ "bulkUpdate() is not supported by the current storage backend.",
1595
+ "UNSUPPORTED_OPERATION"
1596
+ );
1597
+ }
1598
+ const filters = this.buildDmlFilters(params);
1599
+ return this.backend.bulkUpdate(filters, patch, options);
1600
+ }
1601
+ // ---------------------------------------------------------------------------
1602
+ // Multi-source fan-out (capability: query.join)
1603
+ // ---------------------------------------------------------------------------
1604
+ /**
1605
+ * Fan out from `params.sources` over a single edge type in one round trip.
1606
+ * On backends without `query.join`, throws `UNSUPPORTED_OPERATION` — the
1607
+ * cap-less fallback is the per-source `findEdges` loop, which lives in
1608
+ * `traverse.ts` (the higher-level traversal walker) rather than here.
1609
+ *
1610
+ * `expand()` is intentionally edge-type-only — the source set is a flat
1611
+ * UID list and the hop matches one `axbType`. Multi-axbType expansions
1612
+ * become multiple `expand()` calls, one per relation.
1613
+ *
1614
+ * `params.sources.length === 0` short-circuits to an empty result. The
1615
+ * backend never sees the call. (`compileExpand` itself rejects empty
1616
+ * because `IN ()` is not valid SQL.)
1617
+ */
1618
+ async expand(params) {
1619
+ if (!this.backend.expand) {
1620
+ throw new FiregraphError(
1621
+ "expand() is not supported by the current storage backend. Backends without `query.join` can use createTraversal() instead \u2014 the per-hop loop is functionally equivalent (just slower).",
1622
+ "UNSUPPORTED_OPERATION"
1623
+ );
1624
+ }
1625
+ if (params.sources.length === 0) {
1626
+ return params.hydrate ? { edges: [], targets: [] } : { edges: [] };
1627
+ }
1628
+ return this.backend.expand(params);
1629
+ }
1630
+ // ---------------------------------------------------------------------------
1631
+ // Engine-level multi-hop traversal (capability: traversal.serverSide)
1632
+ // ---------------------------------------------------------------------------
1633
+ /**
1634
+ * Compile a multi-hop traversal spec into one server-side nested
1635
+ * Pipeline and dispatch a single round trip.
1636
+ *
1637
+ * Backends declaring `traversal.serverSide` (Firestore Enterprise
1638
+ * today) install this method; everywhere else, it throws
1639
+ * `UNSUPPORTED_OPERATION`. The capability gate matches the type-level
1640
+ * surface — `GraphClient<C>` only exposes `runEngineTraversal` when
1641
+ * `'traversal.serverSide' extends C`.
1642
+ *
1643
+ * Most callers should not invoke this method directly; the
1644
+ * `createTraversal(...).run()` builder routes through it
1645
+ * automatically when `engineTraversal: 'auto'` (the default) and
1646
+ * the spec is eligible per `firestore-traverse-compiler.ts`. Calling
1647
+ * directly is appropriate for benchmarking or for callers that have
1648
+ * already shaped their hop chain into the strict
1649
+ * `EngineTraversalParams` shape.
1650
+ *
1651
+ * `params.sources.length === 0` short-circuits to empty per-hop
1652
+ * arrays. The backend never sees the call.
1653
+ */
1654
+ async runEngineTraversal(params) {
1655
+ if (!this.backend.runEngineTraversal) {
1656
+ throw new FiregraphError(
1657
+ "runEngineTraversal() is not supported by the current storage backend. Backends without `traversal.serverSide` can use createTraversal() instead \u2014 the per-hop loop is functionally equivalent for in-graph specs (different round-trip profile).",
1658
+ "UNSUPPORTED_OPERATION"
1659
+ );
1660
+ }
1661
+ if (params.sources.length === 0) {
1662
+ return {
1663
+ hops: params.hops.map(() => ({ edges: [], sourceCount: 0 })),
1664
+ totalReads: 0
1665
+ };
1666
+ }
1667
+ return this.backend.runEngineTraversal(params);
1668
+ }
1669
+ // ---------------------------------------------------------------------------
1670
+ // Server-side projection (capability: query.select)
1671
+ // ---------------------------------------------------------------------------
1672
+ /**
1673
+ * Server-side projection — fetch only the requested fields from each
1674
+ * matching edge. The backend translates the call into a projecting query
1675
+ * (`SELECT json_extract(...)` on SQLite/DO, `Query.select(...)` on
1676
+ * Firestore Standard, classic projection on Enterprise) so the wire
1677
+ * payload is reduced to just the requested fields.
1678
+ *
1679
+ * Resolution rules for `select` (mirrored across all backends):
1680
+ *
1681
+ * - Built-in envelope fields (`aType`, `aUid`, `axbType`, `bType`,
1682
+ * `bUid`, `createdAt`, `updatedAt`, `v`) → resolve to the typed
1683
+ * column / Firestore field directly.
1684
+ * - `'data'` literal → returns the whole user payload.
1685
+ * - `'data.<x>'` → explicit nested path, returned at the same shape.
1686
+ * - bare name → rewritten to `data.<name>` (the canonical "give me a
1687
+ * few keys out of the JSON payload" shape).
1688
+ *
1689
+ * Empty `select: []` is rejected with `INVALID_QUERY`. Duplicate entries
1690
+ * are de-duped (first-occurrence order preserved); the result row carries
1691
+ * one slot per unique field.
1692
+ *
1693
+ * Migrations are *not* applied to the result. The caller asked for a
1694
+ * partial shape, and rehydrating it through the migration pipeline would
1695
+ * require synthesising every absent field — see
1696
+ * `StorageBackend.findEdgesProjected` for the rationale.
1697
+ *
1698
+ * Scan protection follows the `findEdges` rules: a query with no
1699
+ * identifying fields requires `allowCollectionScan: true` to pass. The
1700
+ * cap-less fallback would be `findEdges` + JS-side projection, but that
1701
+ * defeats the wire-payload reduction; backends without `query.select`
1702
+ * throw `UNSUPPORTED_OPERATION` rather than silently materialising full
1703
+ * rows.
1704
+ */
1705
+ async findEdgesProjected(params) {
1706
+ if (!this.backend.findEdgesProjected) {
1707
+ throw new FiregraphError(
1708
+ "findEdgesProjected() is not supported by the current storage backend. There is no client-side fallback because the wire-payload reduction is the entire point of the API \u2014 use findEdges() and project in JS if the backend does not declare `query.select`.",
1709
+ "UNSUPPORTED_OPERATION"
1710
+ );
1711
+ }
1712
+ if (params.select.length === 0) {
1713
+ throw new FiregraphError(
1714
+ "findEdgesProjected() requires a non-empty `select` list.",
1715
+ "INVALID_QUERY"
1716
+ );
1717
+ }
1718
+ const plan = buildEdgeQueryPlan(params);
1719
+ let filters;
1720
+ let options;
1721
+ if (plan.strategy === "get") {
1722
+ filters = [
1723
+ { field: "aUid", op: "==", value: params.aUid },
1724
+ { field: "axbType", op: "==", value: params.axbType },
1725
+ { field: "bUid", op: "==", value: params.bUid }
1726
+ ];
1727
+ if (params.aType) filters.push({ field: "aType", op: "==", value: params.aType });
1728
+ if (params.bType) filters.push({ field: "bType", op: "==", value: params.bType });
1729
+ options = void 0;
1730
+ } else {
1731
+ filters = plan.filters;
1732
+ options = plan.options;
1733
+ }
1734
+ this.checkQuerySafety(filters, params.allowCollectionScan);
1735
+ const rows = await this.backend.findEdgesProjected(params.select, filters, options);
1736
+ return rows;
1737
+ }
1738
+ /**
1739
+ * Native vector / nearest-neighbour search (capability `search.vector`).
1740
+ *
1741
+ * Resolves to the top-K records by similarity, sorted nearest-first
1742
+ * (`EUCLIDEAN` / `COSINE`) or highest-first (`DOT_PRODUCT`). The wrapper
1743
+ * is intentionally thin: capability check, scan-protection, then forward
1744
+ * `params` verbatim to the backend. All field-path normalisation and
1745
+ * SDK-shape validation lives in the shared
1746
+ * `runFirestoreFindNearest` helper that both Firestore editions call —
1747
+ * keeping it there means the validation surface stays in lockstep with
1748
+ * the SDK call site, regardless of which backend is plugged in.
1749
+ *
1750
+ * Migrations are NOT applied. The vector index walked the raw stored
1751
+ * shape; rehydrating each row through the migration pipeline would
1752
+ * change the candidate set the index already chose. If you need
1753
+ * migrated shape, follow up with `getNode` / `findEdges` on the
1754
+ * returned UIDs — those paths apply migrations normally.
1755
+ *
1756
+ * Scan-protection mirrors `findEdges`: if no identifying filters
1757
+ * (`aType` / `axbType` / `bType`) and no `where` clauses are supplied,
1758
+ * the request must opt in via `allowCollectionScan: true`. The ANN
1759
+ * query still walks the candidate set the WHERE clause produces, so
1760
+ * an unfiltered nearest-neighbour search over a million-row collection
1761
+ * is the same scan trap as an unfiltered `findEdges`.
1762
+ *
1763
+ * Backends without `search.vector` throw `UNSUPPORTED_OPERATION` —
1764
+ * there is no client-side fallback because emulating ANN over the
1765
+ * generic backend surface (`findEdges` + JS-side cosine) doesn't scale
1766
+ * past trivial datasets and would give callers the wrong mental model
1767
+ * about cost.
1768
+ */
1769
+ async findNearest(params) {
1770
+ if (!this.backend.findNearest) {
1771
+ throw new FiregraphError(
1772
+ "findNearest() is not supported by the current storage backend. Vector search requires a backend that declares `search.vector` (currently Firestore Standard and Enterprise). There is no client-side fallback because emulating ANN on top of the generic backend surface does not scale beyond toy datasets.",
1773
+ "UNSUPPORTED_OPERATION"
1774
+ );
1775
+ }
1776
+ const filters = [];
1777
+ if (params.aType) filters.push({ field: "aType", op: "==", value: params.aType });
1778
+ if (params.axbType) filters.push({ field: "axbType", op: "==", value: params.axbType });
1779
+ if (params.bType) filters.push({ field: "bType", op: "==", value: params.bType });
1780
+ if (params.where) filters.push(...params.where);
1781
+ this.checkQuerySafety(filters, params.allowCollectionScan);
1782
+ return this.backend.findNearest(params);
1783
+ }
1784
+ /**
1785
+ * Native full-text search (capability `search.fullText`).
1786
+ *
1787
+ * Returns the top-N records by relevance, ordered by the search
1788
+ * index's score. Only Firestore Enterprise declares this capability
1789
+ * today — the underlying Pipelines `search({ query: documentMatches(...) })`
1790
+ * stage requires Enterprise's FTS index. Standard does not declare
1791
+ * the cap (FTS is an Enterprise-only product feature, not a
1792
+ * typed-API gap), and the SQLite-shaped backends have no native
1793
+ * FTS index. Backends without `search.fullText` throw
1794
+ * `UNSUPPORTED_OPERATION` from this wrapper.
1795
+ *
1796
+ * Scan-protection mirrors `findNearest`: a search with no
1797
+ * identifying filters (`aType` / `axbType` / `bType`) walks every
1798
+ * row the index scored, so the request must opt in via
1799
+ * `allowCollectionScan: true`.
1800
+ *
1801
+ * Migrations are NOT applied. The FTS index walked the raw stored
1802
+ * shape; rehydrating each row through the migration pipeline would
1803
+ * change the candidate set the index already scored. If you need
1804
+ * migrated shape, follow up with `getNode` / `findEdges` on the
1805
+ * returned UIDs.
1806
+ */
1807
+ async fullTextSearch(params) {
1808
+ if (!this.backend.fullTextSearch) {
1809
+ throw new FiregraphError(
1810
+ "fullTextSearch() is not supported by the current storage backend. Full-text search requires a backend that declares `search.fullText` (currently Firestore Enterprise only \u2014 FTS is an Enterprise product feature). There is no client-side fallback because emulating FTS over the generic backend surface would not scale beyond toy datasets.",
1811
+ "UNSUPPORTED_OPERATION"
1812
+ );
1813
+ }
1814
+ const filters = [];
1815
+ if (params.aType) filters.push({ field: "aType", op: "==", value: params.aType });
1816
+ if (params.axbType) filters.push({ field: "axbType", op: "==", value: params.axbType });
1817
+ if (params.bType) filters.push({ field: "bType", op: "==", value: params.bType });
1818
+ this.checkQuerySafety(filters, params.allowCollectionScan);
1819
+ return this.backend.fullTextSearch(params);
1820
+ }
1821
+ /**
1822
+ * Native geospatial distance search (capability `search.geo`).
1823
+ *
1824
+ * Returns rows whose `geoField` lies within `radiusMeters` of
1825
+ * `point`, ordered nearest-first by default. Only Firestore
1826
+ * Enterprise declares this capability — same Enterprise-only
1827
+ * gating as `fullTextSearch`. Backends without `search.geo` throw
1828
+ * `UNSUPPORTED_OPERATION` from this wrapper.
1829
+ *
1830
+ * Scan-protection mirrors `findNearest` and `fullTextSearch`.
1831
+ *
1832
+ * Migrations are NOT applied — same rationale as the other search
1833
+ * extensions.
1834
+ */
1835
+ async geoSearch(params) {
1836
+ if (!this.backend.geoSearch) {
1837
+ throw new FiregraphError(
1838
+ "geoSearch() is not supported by the current storage backend. Geospatial search requires a backend that declares `search.geo` (currently Firestore Enterprise only \u2014 geo queries are an Enterprise product feature). There is no client-side fallback because emulating geo over the generic backend surface (haversine over `findEdges`) would not scale beyond trivial datasets.",
1839
+ "UNSUPPORTED_OPERATION"
1840
+ );
1841
+ }
1842
+ const filters = [];
1843
+ if (params.aType) filters.push({ field: "aType", op: "==", value: params.aType });
1844
+ if (params.axbType) filters.push({ field: "axbType", op: "==", value: params.axbType });
1845
+ if (params.bType) filters.push({ field: "bType", op: "==", value: params.bType });
1846
+ this.checkQuerySafety(filters, params.allowCollectionScan);
1847
+ return this.backend.geoSearch(params);
1848
+ }
1849
+ /**
1850
+ * Translate a `FindEdgesParams` into the `QueryFilter[]` shape the
1851
+ * backend `bulkDelete` / `bulkUpdate` methods expect. Mirrors the
1852
+ * `aggregate()` plan: a bare-empty params object becomes an empty
1853
+ * filter list (after a scan-protection check); a GET-shape (all three
1854
+ * identifiers) is rejected so we never silently turn a single-row
1855
+ * lookup into a server-side DML; otherwise we run `buildEdgeQueryPlan`
1856
+ * and surface its filters.
1857
+ */
1858
+ buildDmlFilters(params) {
1859
+ const hasAnyFilter = params.aType || params.aUid || params.axbType || params.bType || params.bUid || params.where && params.where.length > 0;
1860
+ if (!hasAnyFilter) {
1861
+ this.checkQuerySafety([], params.allowCollectionScan);
1862
+ return [];
1863
+ }
1864
+ const plan = buildEdgeQueryPlan(params);
1865
+ if (plan.strategy === "get") {
1866
+ throw new FiregraphError(
1867
+ "bulkDelete() / bulkUpdate() require a query, not a direct document lookup. Use removeEdge() / updateEdge() for single-row operations, or omit one of aUid/axbType/bUid to force a query strategy.",
1868
+ "INVALID_QUERY"
1869
+ );
1870
+ }
1871
+ this.checkQuerySafety(plan.filters, params.allowCollectionScan);
1872
+ return plan.filters;
1873
+ }
1874
+ // ---------------------------------------------------------------------------
1462
1875
  // Dynamic registry methods
1463
1876
  // ---------------------------------------------------------------------------
1464
1877
  async defineNodeType(name, jsonSchema, description, options) {
@@ -1598,21 +2011,16 @@ var GraphClientImpl = class _GraphClientImpl {
1598
2011
  };
1599
2012
  }
1600
2013
  };
1601
- function createGraphClientFromBackend(backend, options, metaBackend) {
2014
+ function createGraphClient(backend, options, metaBackend) {
1602
2015
  return new GraphClientImpl(backend, options, metaBackend);
1603
2016
  }
2017
+ var createGraphClientFromBackend = createGraphClient;
1604
2018
 
1605
- // src/default-indexes.ts
1606
- var DEFAULT_CORE_INDEXES = Object.freeze([
1607
- { fields: ["aUid"] },
1608
- { fields: ["bUid"] },
1609
- { fields: ["aType"] },
1610
- { fields: ["bType"] },
1611
- { fields: ["aUid", "axbType"] },
1612
- { fields: ["axbType", "bUid"] },
1613
- { fields: ["aType", "axbType"] },
1614
- { fields: ["axbType", "bType"] }
1615
- ]);
2019
+ // src/id.ts
2020
+ import { nanoid } from "nanoid";
2021
+ function generateId() {
2022
+ return nanoid();
2023
+ }
1616
2024
 
1617
2025
  export {
1618
2026
  NODE_RELATION,
@@ -1645,8 +2053,8 @@ export {
1645
2053
  buildEdgeQueryPlan,
1646
2054
  buildNodeQueryPlan,
1647
2055
  analyzeQuerySafety,
1648
- GraphClientImpl,
2056
+ createGraphClient,
1649
2057
  createGraphClientFromBackend,
1650
- DEFAULT_CORE_INDEXES
2058
+ generateId
1651
2059
  };
1652
- //# sourceMappingURL=chunk-NJSOD64C.js.map
2060
+ //# sourceMappingURL=chunk-WRTFC5NG.js.map