@typicalday/firegraph 0.2.0 → 0.4.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 +254 -7
- package/dist/codegen/index.d.cts +1 -1
- package/dist/codegen/index.d.ts +1 -1
- package/dist/editor/server/index.mjs +140 -16
- package/dist/{index-wSlVH5Nv.d.cts → index-DR3jF5_b.d.cts} +72 -2
- package/dist/{index-wSlVH5Nv.d.ts → index-DR3jF5_b.d.ts} +72 -2
- package/dist/index.cjs +280 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +81 -6
- package/dist/index.d.ts +81 -6
- package/dist/index.js +277 -37
- package/dist/index.js.map +1 -1
- package/package.json +23 -25
package/dist/index.js
CHANGED
|
@@ -773,6 +773,9 @@ function matchSegments(path, pi, pattern, qi) {
|
|
|
773
773
|
function tripleKey(aType, axbType, bType) {
|
|
774
774
|
return `${aType}:${axbType}:${bType}`;
|
|
775
775
|
}
|
|
776
|
+
function tripleKeyFor(e) {
|
|
777
|
+
return tripleKey(e.aType, e.axbType, e.bType);
|
|
778
|
+
}
|
|
776
779
|
function createRegistry(input) {
|
|
777
780
|
const map = /* @__PURE__ */ new Map();
|
|
778
781
|
let entries;
|
|
@@ -783,14 +786,35 @@ function createRegistry(input) {
|
|
|
783
786
|
}
|
|
784
787
|
const entryList = Object.freeze([...entries]);
|
|
785
788
|
for (const entry of entries) {
|
|
789
|
+
if (entry.targetGraph && entry.targetGraph.includes("/")) {
|
|
790
|
+
throw new ValidationError(
|
|
791
|
+
`Entry (${entry.aType}) -[${entry.axbType}]-> (${entry.bType}) has invalid targetGraph "${entry.targetGraph}" \u2014 must be a single segment (no "/")`
|
|
792
|
+
);
|
|
793
|
+
}
|
|
786
794
|
const key = tripleKey(entry.aType, entry.axbType, entry.bType);
|
|
787
795
|
const validator = entry.jsonSchema ? compileSchema(entry.jsonSchema, `(${entry.aType}) -[${entry.axbType}]-> (${entry.bType})`) : void 0;
|
|
788
796
|
map.set(key, { entry, validate: validator });
|
|
789
797
|
}
|
|
798
|
+
const axbIndex = /* @__PURE__ */ new Map();
|
|
799
|
+
const axbBuild = /* @__PURE__ */ new Map();
|
|
800
|
+
for (const entry of entries) {
|
|
801
|
+
const existing = axbBuild.get(entry.axbType);
|
|
802
|
+
if (existing) {
|
|
803
|
+
existing.push(entry);
|
|
804
|
+
} else {
|
|
805
|
+
axbBuild.set(entry.axbType, [entry]);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
for (const [key, arr] of axbBuild) {
|
|
809
|
+
axbIndex.set(key, Object.freeze(arr));
|
|
810
|
+
}
|
|
790
811
|
return {
|
|
791
812
|
lookup(aType, axbType, bType) {
|
|
792
813
|
return map.get(tripleKey(aType, axbType, bType))?.entry;
|
|
793
814
|
},
|
|
815
|
+
lookupByAxbType(axbType) {
|
|
816
|
+
return axbIndex.get(axbType) ?? [];
|
|
817
|
+
},
|
|
794
818
|
validate(aType, axbType, bType, data, scopePath) {
|
|
795
819
|
const rec = map.get(tripleKey(aType, axbType, bType));
|
|
796
820
|
if (!rec) {
|
|
@@ -818,6 +842,45 @@ function createRegistry(input) {
|
|
|
818
842
|
}
|
|
819
843
|
};
|
|
820
844
|
}
|
|
845
|
+
function createMergedRegistry(base, extension) {
|
|
846
|
+
const baseKeys = new Set(base.entries().map(tripleKeyFor));
|
|
847
|
+
return {
|
|
848
|
+
lookup(aType, axbType, bType) {
|
|
849
|
+
return base.lookup(aType, axbType, bType) ?? extension.lookup(aType, axbType, bType);
|
|
850
|
+
},
|
|
851
|
+
lookupByAxbType(axbType) {
|
|
852
|
+
const baseResults = base.lookupByAxbType(axbType);
|
|
853
|
+
const extResults = extension.lookupByAxbType(axbType);
|
|
854
|
+
if (extResults.length === 0) return baseResults;
|
|
855
|
+
if (baseResults.length === 0) return extResults;
|
|
856
|
+
const seen = new Set(baseResults.map(tripleKeyFor));
|
|
857
|
+
const merged = [...baseResults];
|
|
858
|
+
for (const entry of extResults) {
|
|
859
|
+
if (!seen.has(tripleKeyFor(entry))) {
|
|
860
|
+
merged.push(entry);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
return Object.freeze(merged);
|
|
864
|
+
},
|
|
865
|
+
validate(aType, axbType, bType, data, scopePath) {
|
|
866
|
+
if (baseKeys.has(tripleKey(aType, axbType, bType))) {
|
|
867
|
+
return base.validate(aType, axbType, bType, data, scopePath);
|
|
868
|
+
}
|
|
869
|
+
return extension.validate(aType, axbType, bType, data, scopePath);
|
|
870
|
+
},
|
|
871
|
+
entries() {
|
|
872
|
+
const extEntries = extension.entries();
|
|
873
|
+
if (extEntries.length === 0) return base.entries();
|
|
874
|
+
const merged = [...base.entries()];
|
|
875
|
+
for (const entry of extEntries) {
|
|
876
|
+
if (!baseKeys.has(tripleKeyFor(entry))) {
|
|
877
|
+
merged.push(entry);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
return Object.freeze(merged);
|
|
881
|
+
}
|
|
882
|
+
};
|
|
883
|
+
}
|
|
821
884
|
function discoveryToEntries(discovery) {
|
|
822
885
|
const entries = [];
|
|
823
886
|
for (const [name, entity] of discovery.nodes) {
|
|
@@ -837,6 +900,12 @@ function discoveryToEntries(discovery) {
|
|
|
837
900
|
if (!topology) continue;
|
|
838
901
|
const fromTypes = Array.isArray(topology.from) ? topology.from : [topology.from];
|
|
839
902
|
const toTypes = Array.isArray(topology.to) ? topology.to : [topology.to];
|
|
903
|
+
const resolvedTargetGraph = entity.targetGraph ?? topology.targetGraph;
|
|
904
|
+
if (resolvedTargetGraph && resolvedTargetGraph.includes("/")) {
|
|
905
|
+
throw new ValidationError(
|
|
906
|
+
`Edge "${axbType}" has invalid targetGraph "${resolvedTargetGraph}" \u2014 must be a single segment (no "/")`
|
|
907
|
+
);
|
|
908
|
+
}
|
|
840
909
|
for (const aType of fromTypes) {
|
|
841
910
|
for (const bType of toTypes) {
|
|
842
911
|
entries.push({
|
|
@@ -848,7 +917,8 @@ function discoveryToEntries(discovery) {
|
|
|
848
917
|
inverseLabel: topology.inverseLabel,
|
|
849
918
|
titleField: entity.titleField,
|
|
850
919
|
subtitleField: entity.subtitleField,
|
|
851
|
-
allowedIn: entity.allowedIn
|
|
920
|
+
allowedIn: entity.allowedIn,
|
|
921
|
+
targetGraph: resolvedTargetGraph
|
|
852
922
|
});
|
|
853
923
|
}
|
|
854
924
|
}
|
|
@@ -898,7 +968,8 @@ var EDGE_TYPE_SCHEMA = {
|
|
|
898
968
|
subtitleField: { type: "string" },
|
|
899
969
|
viewTemplate: { type: "string" },
|
|
900
970
|
viewCss: { type: "string" },
|
|
901
|
-
allowedIn: { type: "array", items: { type: "string", minLength: 1 } }
|
|
971
|
+
allowedIn: { type: "array", items: { type: "string", minLength: 1 } },
|
|
972
|
+
targetGraph: { type: "string", minLength: 1, pattern: "^[^/]+$" }
|
|
902
973
|
},
|
|
903
974
|
additionalProperties: false
|
|
904
975
|
};
|
|
@@ -959,7 +1030,8 @@ async function createRegistryFromGraph(reader) {
|
|
|
959
1030
|
inverseLabel: data.inverseLabel,
|
|
960
1031
|
titleField: data.titleField,
|
|
961
1032
|
subtitleField: data.subtitleField,
|
|
962
|
-
allowedIn: data.allowedIn
|
|
1033
|
+
allowedIn: data.allowedIn,
|
|
1034
|
+
targetGraph: data.targetGraph
|
|
963
1035
|
});
|
|
964
1036
|
}
|
|
965
1037
|
}
|
|
@@ -975,14 +1047,12 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
975
1047
|
this.db = db;
|
|
976
1048
|
this.scopePath = scopePath;
|
|
977
1049
|
this.adapter = createFirestoreAdapter(db, collectionPath);
|
|
978
|
-
if (options?.registry && options?.registryMode) {
|
|
979
|
-
throw new DynamicRegistryError(
|
|
980
|
-
'Cannot provide both "registry" and "registryMode". Use "registry" for static mode or "registryMode" for dynamic mode.'
|
|
981
|
-
);
|
|
982
|
-
}
|
|
983
1050
|
if (options?.registryMode) {
|
|
984
1051
|
this.dynamicConfig = options.registryMode;
|
|
985
1052
|
this.bootstrapRegistry = createBootstrapRegistry();
|
|
1053
|
+
if (options.registry) {
|
|
1054
|
+
this.staticRegistry = options.registry;
|
|
1055
|
+
}
|
|
986
1056
|
const metaCollectionPath = options.registryMode.collection;
|
|
987
1057
|
if (metaCollectionPath && metaCollectionPath !== collectionPath) {
|
|
988
1058
|
this.metaAdapter = createFirestoreAdapter(db, metaCollectionPath);
|
|
@@ -1034,18 +1104,20 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
1034
1104
|
/**
|
|
1035
1105
|
* Get the appropriate registry for validating a write to the given type.
|
|
1036
1106
|
*
|
|
1037
|
-
* - Static mode: returns staticRegistry (or undefined if none set)
|
|
1038
|
-
* - Dynamic mode:
|
|
1107
|
+
* - Static-only mode: returns staticRegistry (or undefined if none set)
|
|
1108
|
+
* - Dynamic mode (pure or merged):
|
|
1039
1109
|
* - Meta-types (nodeType, edgeType): validated against bootstrapRegistry
|
|
1040
1110
|
* - Domain types: validated against dynamicRegistry (falls back to
|
|
1041
1111
|
* bootstrapRegistry which rejects unknown types)
|
|
1112
|
+
* - Merged mode: dynamicRegistry is a merged wrapper (static + dynamic
|
|
1113
|
+
* extension), so static entries take priority automatically.
|
|
1042
1114
|
*/
|
|
1043
1115
|
getRegistryForType(aType) {
|
|
1044
1116
|
if (!this.dynamicConfig) return this.staticRegistry;
|
|
1045
1117
|
if (aType === META_NODE_TYPE || aType === META_EDGE_TYPE) {
|
|
1046
1118
|
return this.bootstrapRegistry;
|
|
1047
1119
|
}
|
|
1048
|
-
return this.dynamicRegistry ?? this.bootstrapRegistry;
|
|
1120
|
+
return this.dynamicRegistry ?? this.staticRegistry ?? this.bootstrapRegistry;
|
|
1049
1121
|
}
|
|
1050
1122
|
/**
|
|
1051
1123
|
* Get the Firestore adapter for writing the given type.
|
|
@@ -1059,13 +1131,13 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
1059
1131
|
}
|
|
1060
1132
|
/**
|
|
1061
1133
|
* Get the combined registry for transaction/batch context.
|
|
1062
|
-
* In static mode, returns staticRegistry.
|
|
1134
|
+
* In static-only mode, returns staticRegistry.
|
|
1063
1135
|
* In dynamic mode, returns dynamicRegistry (which includes bootstrap entries)
|
|
1064
|
-
* or
|
|
1136
|
+
* or falls back to staticRegistry (merged mode) or bootstrapRegistry.
|
|
1065
1137
|
*/
|
|
1066
1138
|
getCombinedRegistry() {
|
|
1067
1139
|
if (!this.dynamicConfig) return this.staticRegistry;
|
|
1068
|
-
return this.dynamicRegistry ?? this.bootstrapRegistry;
|
|
1140
|
+
return this.dynamicRegistry ?? this.staticRegistry ?? this.bootstrapRegistry;
|
|
1069
1141
|
}
|
|
1070
1142
|
// ---------------------------------------------------------------------------
|
|
1071
1143
|
// Query dispatch
|
|
@@ -1213,6 +1285,33 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
1213
1285
|
);
|
|
1214
1286
|
}
|
|
1215
1287
|
// ---------------------------------------------------------------------------
|
|
1288
|
+
// Collection group query
|
|
1289
|
+
// ---------------------------------------------------------------------------
|
|
1290
|
+
async findEdgesGlobal(params, collectionName) {
|
|
1291
|
+
const name = collectionName ?? this.adapter.collectionPath.split("/").pop();
|
|
1292
|
+
const plan = buildEdgeQueryPlan(params);
|
|
1293
|
+
if (plan.strategy === "get") {
|
|
1294
|
+
throw new FiregraphError(
|
|
1295
|
+
"findEdgesGlobal() requires a query, not a direct document lookup. Omit one of aUid/axbType/bUid to force a query strategy.",
|
|
1296
|
+
"INVALID_QUERY"
|
|
1297
|
+
);
|
|
1298
|
+
}
|
|
1299
|
+
this.checkQuerySafety(plan.filters, params.allowCollectionScan);
|
|
1300
|
+
const collectionGroupRef = this.db.collectionGroup(name);
|
|
1301
|
+
let q = collectionGroupRef;
|
|
1302
|
+
for (const f of plan.filters) {
|
|
1303
|
+
q = q.where(f.field, f.op, f.value);
|
|
1304
|
+
}
|
|
1305
|
+
if (plan.options?.orderBy) {
|
|
1306
|
+
q = q.orderBy(plan.options.orderBy.field, plan.options.orderBy.direction ?? "asc");
|
|
1307
|
+
}
|
|
1308
|
+
if (plan.options?.limit !== void 0) {
|
|
1309
|
+
q = q.limit(plan.options.limit);
|
|
1310
|
+
}
|
|
1311
|
+
const snap = await q.get();
|
|
1312
|
+
return snap.docs.map((doc) => doc.data());
|
|
1313
|
+
}
|
|
1314
|
+
// ---------------------------------------------------------------------------
|
|
1216
1315
|
// Bulk operations
|
|
1217
1316
|
// ---------------------------------------------------------------------------
|
|
1218
1317
|
async removeNodeCascade(uid, options) {
|
|
@@ -1235,6 +1334,11 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
1235
1334
|
`Cannot define type "${name}": this name is reserved for the meta-registry.`
|
|
1236
1335
|
);
|
|
1237
1336
|
}
|
|
1337
|
+
if (this.staticRegistry?.lookup(name, NODE_RELATION, name)) {
|
|
1338
|
+
throw new DynamicRegistryError(
|
|
1339
|
+
`Cannot define node type "${name}": already defined in the static registry.`
|
|
1340
|
+
);
|
|
1341
|
+
}
|
|
1238
1342
|
const uid = generateDeterministicUid(META_NODE_TYPE, name);
|
|
1239
1343
|
const data = { name, jsonSchema };
|
|
1240
1344
|
if (description !== void 0) data.description = description;
|
|
@@ -1256,6 +1360,19 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
1256
1360
|
`Cannot define type "${name}": this name is reserved for the meta-registry.`
|
|
1257
1361
|
);
|
|
1258
1362
|
}
|
|
1363
|
+
if (this.staticRegistry) {
|
|
1364
|
+
const fromTypes = Array.isArray(topology.from) ? topology.from : [topology.from];
|
|
1365
|
+
const toTypes = Array.isArray(topology.to) ? topology.to : [topology.to];
|
|
1366
|
+
for (const aType of fromTypes) {
|
|
1367
|
+
for (const bType of toTypes) {
|
|
1368
|
+
if (this.staticRegistry.lookup(aType, name, bType)) {
|
|
1369
|
+
throw new DynamicRegistryError(
|
|
1370
|
+
`Cannot define edge type "${name}" for (${aType}) -> (${bType}): already defined in the static registry.`
|
|
1371
|
+
);
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1259
1376
|
const uid = generateDeterministicUid(META_EDGE_TYPE, name);
|
|
1260
1377
|
const data = {
|
|
1261
1378
|
name,
|
|
@@ -1264,6 +1381,7 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
1264
1381
|
};
|
|
1265
1382
|
if (jsonSchema !== void 0) data.jsonSchema = jsonSchema;
|
|
1266
1383
|
if (topology.inverseLabel !== void 0) data.inverseLabel = topology.inverseLabel;
|
|
1384
|
+
if (topology.targetGraph !== void 0) data.targetGraph = topology.targetGraph;
|
|
1267
1385
|
if (description !== void 0) data.description = description;
|
|
1268
1386
|
if (options?.titleField !== void 0) data.titleField = options.titleField;
|
|
1269
1387
|
if (options?.subtitleField !== void 0) data.subtitleField = options.subtitleField;
|
|
@@ -1279,7 +1397,12 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
1279
1397
|
);
|
|
1280
1398
|
}
|
|
1281
1399
|
const reader = this.createMetaReader();
|
|
1282
|
-
|
|
1400
|
+
const dynamicOnly = await createRegistryFromGraph(reader);
|
|
1401
|
+
if (this.staticRegistry) {
|
|
1402
|
+
this.dynamicRegistry = createMergedRegistry(this.staticRegistry, dynamicOnly);
|
|
1403
|
+
} else {
|
|
1404
|
+
this.dynamicRegistry = dynamicOnly;
|
|
1405
|
+
}
|
|
1283
1406
|
}
|
|
1284
1407
|
/**
|
|
1285
1408
|
* Create a GraphReader for the meta-collection.
|
|
@@ -1338,6 +1461,10 @@ function generateId() {
|
|
|
1338
1461
|
var DEFAULT_LIMIT = 10;
|
|
1339
1462
|
var DEFAULT_MAX_READS = 100;
|
|
1340
1463
|
var DEFAULT_CONCURRENCY = 5;
|
|
1464
|
+
var _crossGraphWarned = false;
|
|
1465
|
+
function isGraphClient(reader) {
|
|
1466
|
+
return "subgraph" in reader && typeof reader.subgraph === "function";
|
|
1467
|
+
}
|
|
1341
1468
|
var Semaphore = class {
|
|
1342
1469
|
constructor(slots) {
|
|
1343
1470
|
this.slots = slots;
|
|
@@ -1363,9 +1490,10 @@ var Semaphore = class {
|
|
|
1363
1490
|
}
|
|
1364
1491
|
};
|
|
1365
1492
|
var TraversalBuilderImpl = class {
|
|
1366
|
-
constructor(reader, startUid) {
|
|
1493
|
+
constructor(reader, startUid, registry) {
|
|
1367
1494
|
this.reader = reader;
|
|
1368
1495
|
this.startUid = startUid;
|
|
1496
|
+
this.registry = registry;
|
|
1369
1497
|
}
|
|
1370
1498
|
hops = [];
|
|
1371
1499
|
follow(axbType, options) {
|
|
@@ -1382,11 +1510,13 @@ var TraversalBuilderImpl = class {
|
|
|
1382
1510
|
const semaphore = new Semaphore(concurrency);
|
|
1383
1511
|
let totalReads = 0;
|
|
1384
1512
|
let truncated = false;
|
|
1385
|
-
let
|
|
1513
|
+
let sources = [
|
|
1514
|
+
{ uid: this.startUid, reader: this.reader }
|
|
1515
|
+
];
|
|
1386
1516
|
const hopResults = [];
|
|
1387
1517
|
for (let depth = 0; depth < this.hops.length; depth++) {
|
|
1388
1518
|
const hop = this.hops[depth];
|
|
1389
|
-
if (
|
|
1519
|
+
if (sources.length === 0) {
|
|
1390
1520
|
hopResults.push({
|
|
1391
1521
|
axbType: hop.axbType,
|
|
1392
1522
|
depth,
|
|
@@ -1397,9 +1527,12 @@ var TraversalBuilderImpl = class {
|
|
|
1397
1527
|
continue;
|
|
1398
1528
|
}
|
|
1399
1529
|
const hopEdges = [];
|
|
1400
|
-
const sourceCount =
|
|
1530
|
+
const sourceCount = sources.length;
|
|
1401
1531
|
let hopTruncated = false;
|
|
1402
|
-
const
|
|
1532
|
+
const resolvedTargetGraph = this.resolveTargetGraph(hop);
|
|
1533
|
+
const direction = hop.direction ?? "forward";
|
|
1534
|
+
const isCrossGraph = direction === "forward" && !!resolvedTargetGraph;
|
|
1535
|
+
const tasks = sources.map(({ uid, reader: sourceReader }) => async () => {
|
|
1403
1536
|
if (totalReads >= maxReads) {
|
|
1404
1537
|
hopTruncated = true;
|
|
1405
1538
|
return;
|
|
@@ -1411,19 +1544,18 @@ var TraversalBuilderImpl = class {
|
|
|
1411
1544
|
return;
|
|
1412
1545
|
}
|
|
1413
1546
|
totalReads++;
|
|
1414
|
-
const direction2 = hop.direction ?? "forward";
|
|
1415
1547
|
const params = { axbType: hop.axbType };
|
|
1416
|
-
if (
|
|
1548
|
+
if (direction === "forward") {
|
|
1417
1549
|
params.aUid = uid;
|
|
1418
1550
|
if (hop.bType) params.bType = hop.bType;
|
|
1419
1551
|
} else {
|
|
1420
1552
|
params.bUid = uid;
|
|
1421
1553
|
if (hop.aType) params.aType = hop.aType;
|
|
1422
1554
|
}
|
|
1423
|
-
if (
|
|
1555
|
+
if (direction === "forward" && hop.aType) {
|
|
1424
1556
|
params.aType = hop.aType;
|
|
1425
1557
|
}
|
|
1426
|
-
if (
|
|
1558
|
+
if (direction === "reverse" && hop.bType) {
|
|
1427
1559
|
params.bType = hop.bType;
|
|
1428
1560
|
}
|
|
1429
1561
|
if (hop.orderBy) params.orderBy = hop.orderBy;
|
|
@@ -1433,31 +1565,58 @@ var TraversalBuilderImpl = class {
|
|
|
1433
1565
|
} else {
|
|
1434
1566
|
params.limit = limit;
|
|
1435
1567
|
}
|
|
1436
|
-
let
|
|
1568
|
+
let hopReader;
|
|
1569
|
+
let nextReader;
|
|
1570
|
+
if (isCrossGraph) {
|
|
1571
|
+
if (isGraphClient(this.reader)) {
|
|
1572
|
+
hopReader = this.reader.subgraph(uid, resolvedTargetGraph);
|
|
1573
|
+
nextReader = hopReader;
|
|
1574
|
+
} else {
|
|
1575
|
+
hopReader = sourceReader;
|
|
1576
|
+
nextReader = sourceReader;
|
|
1577
|
+
if (!_crossGraphWarned) {
|
|
1578
|
+
_crossGraphWarned = true;
|
|
1579
|
+
console.warn(
|
|
1580
|
+
`[firegraph] Traversal hop "${hop.axbType}" has targetGraph "${resolvedTargetGraph}" but the reader does not support subgraph(). Cross-graph hop will query the current collection instead. Pass a GraphClient to createTraversal() to enable cross-graph traversal.`
|
|
1581
|
+
);
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
} else {
|
|
1585
|
+
hopReader = sourceReader;
|
|
1586
|
+
nextReader = sourceReader;
|
|
1587
|
+
}
|
|
1588
|
+
let edges2 = await hopReader.findEdges(params);
|
|
1437
1589
|
if (hop.filter) {
|
|
1438
|
-
|
|
1439
|
-
|
|
1590
|
+
edges2 = edges2.filter(hop.filter);
|
|
1591
|
+
edges2 = edges2.slice(0, limit);
|
|
1592
|
+
}
|
|
1593
|
+
for (const edge of edges2) {
|
|
1594
|
+
hopEdges.push({ edge, reader: nextReader });
|
|
1440
1595
|
}
|
|
1441
|
-
hopEdges.push(...edges);
|
|
1442
1596
|
} finally {
|
|
1443
1597
|
semaphore.release();
|
|
1444
1598
|
}
|
|
1445
1599
|
});
|
|
1446
1600
|
await Promise.all(tasks.map((task) => task()));
|
|
1601
|
+
const edges = hopEdges.map((h) => h.edge);
|
|
1447
1602
|
hopResults.push({
|
|
1448
1603
|
axbType: hop.axbType,
|
|
1449
1604
|
depth,
|
|
1450
|
-
edges: returnIntermediates ? [...
|
|
1605
|
+
edges: returnIntermediates ? [...edges] : edges,
|
|
1451
1606
|
sourceCount,
|
|
1452
1607
|
truncated: hopTruncated
|
|
1453
1608
|
});
|
|
1454
1609
|
if (hopTruncated) {
|
|
1455
1610
|
truncated = true;
|
|
1456
1611
|
}
|
|
1457
|
-
const
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1612
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1613
|
+
for (const { edge, reader: edgeReader } of hopEdges) {
|
|
1614
|
+
const nextUid = direction === "forward" ? edge.bUid : edge.aUid;
|
|
1615
|
+
if (!seen.has(nextUid)) {
|
|
1616
|
+
seen.set(nextUid, edgeReader);
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
sources = [...seen.entries()].map(([uid, reader]) => ({ uid, reader }));
|
|
1461
1620
|
}
|
|
1462
1621
|
const lastHop = hopResults[hopResults.length - 1];
|
|
1463
1622
|
return {
|
|
@@ -1467,9 +1626,25 @@ var TraversalBuilderImpl = class {
|
|
|
1467
1626
|
truncated
|
|
1468
1627
|
};
|
|
1469
1628
|
}
|
|
1629
|
+
/**
|
|
1630
|
+
* Resolve the targetGraph for a hop. Priority:
|
|
1631
|
+
* 1. Explicit `hop.targetGraph` (user override)
|
|
1632
|
+
* 2. Registry `targetGraph` for the axbType (if registry available)
|
|
1633
|
+
* 3. undefined (no cross-graph)
|
|
1634
|
+
*/
|
|
1635
|
+
resolveTargetGraph(hop) {
|
|
1636
|
+
if (hop.targetGraph) return hop.targetGraph;
|
|
1637
|
+
if (this.registry) {
|
|
1638
|
+
const entries = this.registry.lookupByAxbType(hop.axbType);
|
|
1639
|
+
for (const entry of entries) {
|
|
1640
|
+
if (entry.targetGraph) return entry.targetGraph;
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
return void 0;
|
|
1644
|
+
}
|
|
1470
1645
|
};
|
|
1471
|
-
function createTraversal(reader, startUid) {
|
|
1472
|
-
return new TraversalBuilderImpl(reader, startUid);
|
|
1646
|
+
function createTraversal(reader, startUid, registry) {
|
|
1647
|
+
return new TraversalBuilderImpl(reader, startUid, registry);
|
|
1473
1648
|
}
|
|
1474
1649
|
|
|
1475
1650
|
// src/views.ts
|
|
@@ -1717,7 +1892,8 @@ function loadEdgeEntity(dir, name) {
|
|
|
1717
1892
|
viewDefaults: meta?.viewDefaults,
|
|
1718
1893
|
viewsPath,
|
|
1719
1894
|
sampleData,
|
|
1720
|
-
allowedIn: meta?.allowedIn
|
|
1895
|
+
allowedIn: meta?.allowedIn,
|
|
1896
|
+
targetGraph: topology.targetGraph ?? meta?.targetGraph
|
|
1721
1897
|
};
|
|
1722
1898
|
}
|
|
1723
1899
|
function getSubdirectories(dir) {
|
|
@@ -1760,6 +1936,20 @@ function discoverEntities(entitiesDir) {
|
|
|
1760
1936
|
};
|
|
1761
1937
|
}
|
|
1762
1938
|
|
|
1939
|
+
// src/cross-graph.ts
|
|
1940
|
+
function resolveAncestorCollection(collectionPath, uid) {
|
|
1941
|
+
const segments = collectionPath.split("/");
|
|
1942
|
+
for (let i = 1; i < segments.length; i += 2) {
|
|
1943
|
+
if (segments[i] === uid) {
|
|
1944
|
+
return segments.slice(0, i).join("/");
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
return null;
|
|
1948
|
+
}
|
|
1949
|
+
function isAncestorUid(collectionPath, uid) {
|
|
1950
|
+
return resolveAncestorCollection(collectionPath, uid) !== null;
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1763
1953
|
// src/indexes.ts
|
|
1764
1954
|
function baseIndexes(collection) {
|
|
1765
1955
|
return [
|
|
@@ -1802,7 +1992,43 @@ function extractSchemaFields(schema) {
|
|
|
1802
1992
|
if (s.type !== "object" || !s.properties) return [];
|
|
1803
1993
|
return Object.keys(s.properties);
|
|
1804
1994
|
}
|
|
1805
|
-
function
|
|
1995
|
+
function collectionGroupIndexes(collectionName) {
|
|
1996
|
+
return [
|
|
1997
|
+
{
|
|
1998
|
+
collectionGroup: collectionName,
|
|
1999
|
+
queryScope: "COLLECTION_GROUP",
|
|
2000
|
+
fields: [
|
|
2001
|
+
{ fieldPath: "aUid", order: "ASCENDING" },
|
|
2002
|
+
{ fieldPath: "axbType", order: "ASCENDING" }
|
|
2003
|
+
]
|
|
2004
|
+
},
|
|
2005
|
+
{
|
|
2006
|
+
collectionGroup: collectionName,
|
|
2007
|
+
queryScope: "COLLECTION_GROUP",
|
|
2008
|
+
fields: [
|
|
2009
|
+
{ fieldPath: "axbType", order: "ASCENDING" },
|
|
2010
|
+
{ fieldPath: "bUid", order: "ASCENDING" }
|
|
2011
|
+
]
|
|
2012
|
+
},
|
|
2013
|
+
{
|
|
2014
|
+
collectionGroup: collectionName,
|
|
2015
|
+
queryScope: "COLLECTION_GROUP",
|
|
2016
|
+
fields: [
|
|
2017
|
+
{ fieldPath: "aType", order: "ASCENDING" },
|
|
2018
|
+
{ fieldPath: "axbType", order: "ASCENDING" }
|
|
2019
|
+
]
|
|
2020
|
+
},
|
|
2021
|
+
{
|
|
2022
|
+
collectionGroup: collectionName,
|
|
2023
|
+
queryScope: "COLLECTION_GROUP",
|
|
2024
|
+
fields: [
|
|
2025
|
+
{ fieldPath: "axbType", order: "ASCENDING" },
|
|
2026
|
+
{ fieldPath: "bType", order: "ASCENDING" }
|
|
2027
|
+
]
|
|
2028
|
+
}
|
|
2029
|
+
];
|
|
2030
|
+
}
|
|
2031
|
+
function generateIndexConfig(collection, entities, registryEntries) {
|
|
1806
2032
|
const indexes = baseIndexes(collection);
|
|
1807
2033
|
if (entities) {
|
|
1808
2034
|
for (const [, entity] of entities.nodes) {
|
|
@@ -1834,6 +2060,17 @@ function generateIndexConfig(collection, entities) {
|
|
|
1834
2060
|
}
|
|
1835
2061
|
}
|
|
1836
2062
|
}
|
|
2063
|
+
if (registryEntries) {
|
|
2064
|
+
const targetGraphNames = /* @__PURE__ */ new Set();
|
|
2065
|
+
for (const entry of registryEntries) {
|
|
2066
|
+
if (entry.targetGraph) {
|
|
2067
|
+
targetGraphNames.add(entry.targetGraph);
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
for (const name of targetGraphNames) {
|
|
2071
|
+
indexes.push(...collectionGroupIndexes(name));
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
1837
2074
|
return { indexes, fieldOverrides: [] };
|
|
1838
2075
|
}
|
|
1839
2076
|
export {
|
|
@@ -1866,6 +2103,7 @@ export {
|
|
|
1866
2103
|
computeNodeDocId,
|
|
1867
2104
|
createBootstrapRegistry,
|
|
1868
2105
|
createGraphClient,
|
|
2106
|
+
createMergedRegistry,
|
|
1869
2107
|
createRegistry,
|
|
1870
2108
|
createRegistryFromGraph,
|
|
1871
2109
|
createTraversal,
|
|
@@ -1876,9 +2114,11 @@ export {
|
|
|
1876
2114
|
generateId,
|
|
1877
2115
|
generateIndexConfig,
|
|
1878
2116
|
generateTypes,
|
|
2117
|
+
isAncestorUid,
|
|
1879
2118
|
jsonSchemaToFieldMeta,
|
|
1880
2119
|
matchScope,
|
|
1881
2120
|
matchScopeAny,
|
|
2121
|
+
resolveAncestorCollection,
|
|
1882
2122
|
resolveView
|
|
1883
2123
|
};
|
|
1884
2124
|
//# sourceMappingURL=index.js.map
|