@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.
- package/README.md +355 -78
- package/dist/backend-DuvHGgK1.d.cts +1897 -0
- package/dist/backend-DuvHGgK1.d.ts +1897 -0
- package/dist/backend.cjs +365 -5
- package/dist/backend.cjs.map +1 -1
- package/dist/backend.d.cts +25 -5
- package/dist/backend.d.ts +25 -5
- package/dist/backend.js +209 -7
- package/dist/backend.js.map +1 -1
- package/dist/chunk-2DHMNTV6.js +16 -0
- package/dist/chunk-2DHMNTV6.js.map +1 -0
- package/dist/chunk-4MMQ5W74.js +288 -0
- package/dist/chunk-4MMQ5W74.js.map +1 -0
- package/dist/{chunk-5753Y42M.js → chunk-C2QMD7RY.js} +6 -10
- package/dist/chunk-C2QMD7RY.js.map +1 -0
- package/dist/chunk-D4J7Z4FE.js +67 -0
- package/dist/chunk-D4J7Z4FE.js.map +1 -0
- package/dist/chunk-EQJUUVFG.js +14 -0
- package/dist/chunk-EQJUUVFG.js.map +1 -0
- package/dist/chunk-N5HFDWQX.js +23 -0
- package/dist/chunk-N5HFDWQX.js.map +1 -0
- package/dist/chunk-PAD7WFFU.js +573 -0
- package/dist/chunk-PAD7WFFU.js.map +1 -0
- package/dist/chunk-TK64DNVK.js +256 -0
- package/dist/chunk-TK64DNVK.js.map +1 -0
- package/dist/{chunk-NJSOD64C.js → chunk-WRTFC5NG.js} +438 -30
- package/dist/chunk-WRTFC5NG.js.map +1 -0
- package/dist/client-BKi3vk0Q.d.ts +34 -0
- package/dist/client-BrsaXtDV.d.cts +34 -0
- package/dist/cloudflare/index.cjs +1386 -74
- package/dist/cloudflare/index.cjs.map +1 -1
- package/dist/cloudflare/index.d.cts +217 -13
- package/dist/cloudflare/index.d.ts +217 -13
- package/dist/cloudflare/index.js +639 -180
- package/dist/cloudflare/index.js.map +1 -1
- package/dist/codegen/index.d.cts +1 -1
- package/dist/codegen/index.d.ts +1 -1
- package/dist/errors-BRc3I_eH.d.cts +73 -0
- package/dist/errors-BRc3I_eH.d.ts +73 -0
- package/dist/firestore-enterprise/index.cjs +3877 -0
- package/dist/firestore-enterprise/index.cjs.map +1 -0
- package/dist/firestore-enterprise/index.d.cts +141 -0
- package/dist/firestore-enterprise/index.d.ts +141 -0
- package/dist/firestore-enterprise/index.js +985 -0
- package/dist/firestore-enterprise/index.js.map +1 -0
- package/dist/firestore-standard/index.cjs +3117 -0
- package/dist/firestore-standard/index.cjs.map +1 -0
- package/dist/firestore-standard/index.d.cts +49 -0
- package/dist/firestore-standard/index.d.ts +49 -0
- package/dist/firestore-standard/index.js +283 -0
- package/dist/firestore-standard/index.js.map +1 -0
- package/dist/index.cjs +809 -534
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -100
- package/dist/index.d.ts +24 -100
- package/dist/index.js +184 -531
- package/dist/index.js.map +1 -1
- package/dist/registry-Bc7h6WTM.d.cts +64 -0
- package/dist/registry-C2KUPVZj.d.ts +64 -0
- package/dist/{scope-path-B1G3YiA7.d.ts → scope-path-CROFZGr9.d.cts} +1 -56
- package/dist/{scope-path-B1G3YiA7.d.cts → scope-path-CROFZGr9.d.ts} +1 -56
- package/dist/{serialization-ZZ7RSDRX.js → serialization-OE2PFZMY.js} +6 -4
- package/dist/sqlite/index.cjs +3631 -0
- package/dist/sqlite/index.cjs.map +1 -0
- package/dist/sqlite/index.d.cts +111 -0
- package/dist/sqlite/index.d.ts +111 -0
- package/dist/sqlite/index.js +1164 -0
- package/dist/sqlite/index.js.map +1 -0
- package/package.json +33 -3
- package/dist/backend-U-MLShlg.d.ts +0 -97
- package/dist/backend-np4gEVhB.d.cts +0 -97
- package/dist/chunk-5753Y42M.js.map +0 -1
- package/dist/chunk-NJSOD64C.js.map +0 -1
- package/dist/chunk-R7CRGYY4.js +0 -94
- package/dist/chunk-R7CRGYY4.js.map +0 -1
- package/dist/types-BGWxcpI_.d.cts +0 -736
- package/dist/types-BGWxcpI_.d.ts +0 -736
- /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
|
-
|
|
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-
|
|
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
|
-
|
|
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, {
|
|
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
|
|
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, {
|
|
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
|
|
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, {
|
|
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
|
|
2014
|
+
function createGraphClient(backend, options, metaBackend) {
|
|
1602
2015
|
return new GraphClientImpl(backend, options, metaBackend);
|
|
1603
2016
|
}
|
|
2017
|
+
var createGraphClientFromBackend = createGraphClient;
|
|
1604
2018
|
|
|
1605
|
-
// src/
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
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
|
-
|
|
2056
|
+
createGraphClient,
|
|
1649
2057
|
createGraphClientFromBackend,
|
|
1650
|
-
|
|
2058
|
+
generateId
|
|
1651
2059
|
};
|
|
1652
|
-
//# sourceMappingURL=chunk-
|
|
2060
|
+
//# sourceMappingURL=chunk-WRTFC5NG.js.map
|