@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.cjs
CHANGED
|
@@ -59,6 +59,7 @@ __export(index_exports, {
|
|
|
59
59
|
computeNodeDocId: () => computeNodeDocId,
|
|
60
60
|
createBootstrapRegistry: () => createBootstrapRegistry,
|
|
61
61
|
createGraphClient: () => createGraphClient,
|
|
62
|
+
createMergedRegistry: () => createMergedRegistry,
|
|
62
63
|
createRegistry: () => createRegistry,
|
|
63
64
|
createRegistryFromGraph: () => createRegistryFromGraph,
|
|
64
65
|
createTraversal: () => createTraversal,
|
|
@@ -69,9 +70,11 @@ __export(index_exports, {
|
|
|
69
70
|
generateId: () => generateId,
|
|
70
71
|
generateIndexConfig: () => generateIndexConfig,
|
|
71
72
|
generateTypes: () => generateTypes,
|
|
73
|
+
isAncestorUid: () => isAncestorUid,
|
|
72
74
|
jsonSchemaToFieldMeta: () => jsonSchemaToFieldMeta,
|
|
73
75
|
matchScope: () => matchScope,
|
|
74
76
|
matchScopeAny: () => matchScopeAny,
|
|
77
|
+
resolveAncestorCollection: () => resolveAncestorCollection,
|
|
75
78
|
resolveView: () => resolveView
|
|
76
79
|
});
|
|
77
80
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -843,6 +846,9 @@ function matchSegments(path, pi, pattern, qi) {
|
|
|
843
846
|
function tripleKey(aType, axbType, bType) {
|
|
844
847
|
return `${aType}:${axbType}:${bType}`;
|
|
845
848
|
}
|
|
849
|
+
function tripleKeyFor(e) {
|
|
850
|
+
return tripleKey(e.aType, e.axbType, e.bType);
|
|
851
|
+
}
|
|
846
852
|
function createRegistry(input) {
|
|
847
853
|
const map = /* @__PURE__ */ new Map();
|
|
848
854
|
let entries;
|
|
@@ -853,14 +859,35 @@ function createRegistry(input) {
|
|
|
853
859
|
}
|
|
854
860
|
const entryList = Object.freeze([...entries]);
|
|
855
861
|
for (const entry of entries) {
|
|
862
|
+
if (entry.targetGraph && entry.targetGraph.includes("/")) {
|
|
863
|
+
throw new ValidationError(
|
|
864
|
+
`Entry (${entry.aType}) -[${entry.axbType}]-> (${entry.bType}) has invalid targetGraph "${entry.targetGraph}" \u2014 must be a single segment (no "/")`
|
|
865
|
+
);
|
|
866
|
+
}
|
|
856
867
|
const key = tripleKey(entry.aType, entry.axbType, entry.bType);
|
|
857
868
|
const validator = entry.jsonSchema ? compileSchema(entry.jsonSchema, `(${entry.aType}) -[${entry.axbType}]-> (${entry.bType})`) : void 0;
|
|
858
869
|
map.set(key, { entry, validate: validator });
|
|
859
870
|
}
|
|
871
|
+
const axbIndex = /* @__PURE__ */ new Map();
|
|
872
|
+
const axbBuild = /* @__PURE__ */ new Map();
|
|
873
|
+
for (const entry of entries) {
|
|
874
|
+
const existing = axbBuild.get(entry.axbType);
|
|
875
|
+
if (existing) {
|
|
876
|
+
existing.push(entry);
|
|
877
|
+
} else {
|
|
878
|
+
axbBuild.set(entry.axbType, [entry]);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
for (const [key, arr] of axbBuild) {
|
|
882
|
+
axbIndex.set(key, Object.freeze(arr));
|
|
883
|
+
}
|
|
860
884
|
return {
|
|
861
885
|
lookup(aType, axbType, bType) {
|
|
862
886
|
return map.get(tripleKey(aType, axbType, bType))?.entry;
|
|
863
887
|
},
|
|
888
|
+
lookupByAxbType(axbType) {
|
|
889
|
+
return axbIndex.get(axbType) ?? [];
|
|
890
|
+
},
|
|
864
891
|
validate(aType, axbType, bType, data, scopePath) {
|
|
865
892
|
const rec = map.get(tripleKey(aType, axbType, bType));
|
|
866
893
|
if (!rec) {
|
|
@@ -888,6 +915,45 @@ function createRegistry(input) {
|
|
|
888
915
|
}
|
|
889
916
|
};
|
|
890
917
|
}
|
|
918
|
+
function createMergedRegistry(base, extension) {
|
|
919
|
+
const baseKeys = new Set(base.entries().map(tripleKeyFor));
|
|
920
|
+
return {
|
|
921
|
+
lookup(aType, axbType, bType) {
|
|
922
|
+
return base.lookup(aType, axbType, bType) ?? extension.lookup(aType, axbType, bType);
|
|
923
|
+
},
|
|
924
|
+
lookupByAxbType(axbType) {
|
|
925
|
+
const baseResults = base.lookupByAxbType(axbType);
|
|
926
|
+
const extResults = extension.lookupByAxbType(axbType);
|
|
927
|
+
if (extResults.length === 0) return baseResults;
|
|
928
|
+
if (baseResults.length === 0) return extResults;
|
|
929
|
+
const seen = new Set(baseResults.map(tripleKeyFor));
|
|
930
|
+
const merged = [...baseResults];
|
|
931
|
+
for (const entry of extResults) {
|
|
932
|
+
if (!seen.has(tripleKeyFor(entry))) {
|
|
933
|
+
merged.push(entry);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
return Object.freeze(merged);
|
|
937
|
+
},
|
|
938
|
+
validate(aType, axbType, bType, data, scopePath) {
|
|
939
|
+
if (baseKeys.has(tripleKey(aType, axbType, bType))) {
|
|
940
|
+
return base.validate(aType, axbType, bType, data, scopePath);
|
|
941
|
+
}
|
|
942
|
+
return extension.validate(aType, axbType, bType, data, scopePath);
|
|
943
|
+
},
|
|
944
|
+
entries() {
|
|
945
|
+
const extEntries = extension.entries();
|
|
946
|
+
if (extEntries.length === 0) return base.entries();
|
|
947
|
+
const merged = [...base.entries()];
|
|
948
|
+
for (const entry of extEntries) {
|
|
949
|
+
if (!baseKeys.has(tripleKeyFor(entry))) {
|
|
950
|
+
merged.push(entry);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
return Object.freeze(merged);
|
|
954
|
+
}
|
|
955
|
+
};
|
|
956
|
+
}
|
|
891
957
|
function discoveryToEntries(discovery) {
|
|
892
958
|
const entries = [];
|
|
893
959
|
for (const [name, entity] of discovery.nodes) {
|
|
@@ -907,6 +973,12 @@ function discoveryToEntries(discovery) {
|
|
|
907
973
|
if (!topology) continue;
|
|
908
974
|
const fromTypes = Array.isArray(topology.from) ? topology.from : [topology.from];
|
|
909
975
|
const toTypes = Array.isArray(topology.to) ? topology.to : [topology.to];
|
|
976
|
+
const resolvedTargetGraph = entity.targetGraph ?? topology.targetGraph;
|
|
977
|
+
if (resolvedTargetGraph && resolvedTargetGraph.includes("/")) {
|
|
978
|
+
throw new ValidationError(
|
|
979
|
+
`Edge "${axbType}" has invalid targetGraph "${resolvedTargetGraph}" \u2014 must be a single segment (no "/")`
|
|
980
|
+
);
|
|
981
|
+
}
|
|
910
982
|
for (const aType of fromTypes) {
|
|
911
983
|
for (const bType of toTypes) {
|
|
912
984
|
entries.push({
|
|
@@ -918,7 +990,8 @@ function discoveryToEntries(discovery) {
|
|
|
918
990
|
inverseLabel: topology.inverseLabel,
|
|
919
991
|
titleField: entity.titleField,
|
|
920
992
|
subtitleField: entity.subtitleField,
|
|
921
|
-
allowedIn: entity.allowedIn
|
|
993
|
+
allowedIn: entity.allowedIn,
|
|
994
|
+
targetGraph: resolvedTargetGraph
|
|
922
995
|
});
|
|
923
996
|
}
|
|
924
997
|
}
|
|
@@ -968,7 +1041,8 @@ var EDGE_TYPE_SCHEMA = {
|
|
|
968
1041
|
subtitleField: { type: "string" },
|
|
969
1042
|
viewTemplate: { type: "string" },
|
|
970
1043
|
viewCss: { type: "string" },
|
|
971
|
-
allowedIn: { type: "array", items: { type: "string", minLength: 1 } }
|
|
1044
|
+
allowedIn: { type: "array", items: { type: "string", minLength: 1 } },
|
|
1045
|
+
targetGraph: { type: "string", minLength: 1, pattern: "^[^/]+$" }
|
|
972
1046
|
},
|
|
973
1047
|
additionalProperties: false
|
|
974
1048
|
};
|
|
@@ -1029,7 +1103,8 @@ async function createRegistryFromGraph(reader) {
|
|
|
1029
1103
|
inverseLabel: data.inverseLabel,
|
|
1030
1104
|
titleField: data.titleField,
|
|
1031
1105
|
subtitleField: data.subtitleField,
|
|
1032
|
-
allowedIn: data.allowedIn
|
|
1106
|
+
allowedIn: data.allowedIn,
|
|
1107
|
+
targetGraph: data.targetGraph
|
|
1033
1108
|
});
|
|
1034
1109
|
}
|
|
1035
1110
|
}
|
|
@@ -1045,14 +1120,12 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
1045
1120
|
this.db = db;
|
|
1046
1121
|
this.scopePath = scopePath;
|
|
1047
1122
|
this.adapter = createFirestoreAdapter(db, collectionPath);
|
|
1048
|
-
if (options?.registry && options?.registryMode) {
|
|
1049
|
-
throw new DynamicRegistryError(
|
|
1050
|
-
'Cannot provide both "registry" and "registryMode". Use "registry" for static mode or "registryMode" for dynamic mode.'
|
|
1051
|
-
);
|
|
1052
|
-
}
|
|
1053
1123
|
if (options?.registryMode) {
|
|
1054
1124
|
this.dynamicConfig = options.registryMode;
|
|
1055
1125
|
this.bootstrapRegistry = createBootstrapRegistry();
|
|
1126
|
+
if (options.registry) {
|
|
1127
|
+
this.staticRegistry = options.registry;
|
|
1128
|
+
}
|
|
1056
1129
|
const metaCollectionPath = options.registryMode.collection;
|
|
1057
1130
|
if (metaCollectionPath && metaCollectionPath !== collectionPath) {
|
|
1058
1131
|
this.metaAdapter = createFirestoreAdapter(db, metaCollectionPath);
|
|
@@ -1104,18 +1177,20 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
1104
1177
|
/**
|
|
1105
1178
|
* Get the appropriate registry for validating a write to the given type.
|
|
1106
1179
|
*
|
|
1107
|
-
* - Static mode: returns staticRegistry (or undefined if none set)
|
|
1108
|
-
* - Dynamic mode:
|
|
1180
|
+
* - Static-only mode: returns staticRegistry (or undefined if none set)
|
|
1181
|
+
* - Dynamic mode (pure or merged):
|
|
1109
1182
|
* - Meta-types (nodeType, edgeType): validated against bootstrapRegistry
|
|
1110
1183
|
* - Domain types: validated against dynamicRegistry (falls back to
|
|
1111
1184
|
* bootstrapRegistry which rejects unknown types)
|
|
1185
|
+
* - Merged mode: dynamicRegistry is a merged wrapper (static + dynamic
|
|
1186
|
+
* extension), so static entries take priority automatically.
|
|
1112
1187
|
*/
|
|
1113
1188
|
getRegistryForType(aType) {
|
|
1114
1189
|
if (!this.dynamicConfig) return this.staticRegistry;
|
|
1115
1190
|
if (aType === META_NODE_TYPE || aType === META_EDGE_TYPE) {
|
|
1116
1191
|
return this.bootstrapRegistry;
|
|
1117
1192
|
}
|
|
1118
|
-
return this.dynamicRegistry ?? this.bootstrapRegistry;
|
|
1193
|
+
return this.dynamicRegistry ?? this.staticRegistry ?? this.bootstrapRegistry;
|
|
1119
1194
|
}
|
|
1120
1195
|
/**
|
|
1121
1196
|
* Get the Firestore adapter for writing the given type.
|
|
@@ -1129,13 +1204,13 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
1129
1204
|
}
|
|
1130
1205
|
/**
|
|
1131
1206
|
* Get the combined registry for transaction/batch context.
|
|
1132
|
-
* In static mode, returns staticRegistry.
|
|
1207
|
+
* In static-only mode, returns staticRegistry.
|
|
1133
1208
|
* In dynamic mode, returns dynamicRegistry (which includes bootstrap entries)
|
|
1134
|
-
* or
|
|
1209
|
+
* or falls back to staticRegistry (merged mode) or bootstrapRegistry.
|
|
1135
1210
|
*/
|
|
1136
1211
|
getCombinedRegistry() {
|
|
1137
1212
|
if (!this.dynamicConfig) return this.staticRegistry;
|
|
1138
|
-
return this.dynamicRegistry ?? this.bootstrapRegistry;
|
|
1213
|
+
return this.dynamicRegistry ?? this.staticRegistry ?? this.bootstrapRegistry;
|
|
1139
1214
|
}
|
|
1140
1215
|
// ---------------------------------------------------------------------------
|
|
1141
1216
|
// Query dispatch
|
|
@@ -1283,6 +1358,33 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
1283
1358
|
);
|
|
1284
1359
|
}
|
|
1285
1360
|
// ---------------------------------------------------------------------------
|
|
1361
|
+
// Collection group query
|
|
1362
|
+
// ---------------------------------------------------------------------------
|
|
1363
|
+
async findEdgesGlobal(params, collectionName) {
|
|
1364
|
+
const name = collectionName ?? this.adapter.collectionPath.split("/").pop();
|
|
1365
|
+
const plan = buildEdgeQueryPlan(params);
|
|
1366
|
+
if (plan.strategy === "get") {
|
|
1367
|
+
throw new FiregraphError(
|
|
1368
|
+
"findEdgesGlobal() requires a query, not a direct document lookup. Omit one of aUid/axbType/bUid to force a query strategy.",
|
|
1369
|
+
"INVALID_QUERY"
|
|
1370
|
+
);
|
|
1371
|
+
}
|
|
1372
|
+
this.checkQuerySafety(plan.filters, params.allowCollectionScan);
|
|
1373
|
+
const collectionGroupRef = this.db.collectionGroup(name);
|
|
1374
|
+
let q = collectionGroupRef;
|
|
1375
|
+
for (const f of plan.filters) {
|
|
1376
|
+
q = q.where(f.field, f.op, f.value);
|
|
1377
|
+
}
|
|
1378
|
+
if (plan.options?.orderBy) {
|
|
1379
|
+
q = q.orderBy(plan.options.orderBy.field, plan.options.orderBy.direction ?? "asc");
|
|
1380
|
+
}
|
|
1381
|
+
if (plan.options?.limit !== void 0) {
|
|
1382
|
+
q = q.limit(plan.options.limit);
|
|
1383
|
+
}
|
|
1384
|
+
const snap = await q.get();
|
|
1385
|
+
return snap.docs.map((doc) => doc.data());
|
|
1386
|
+
}
|
|
1387
|
+
// ---------------------------------------------------------------------------
|
|
1286
1388
|
// Bulk operations
|
|
1287
1389
|
// ---------------------------------------------------------------------------
|
|
1288
1390
|
async removeNodeCascade(uid, options) {
|
|
@@ -1305,6 +1407,11 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
1305
1407
|
`Cannot define type "${name}": this name is reserved for the meta-registry.`
|
|
1306
1408
|
);
|
|
1307
1409
|
}
|
|
1410
|
+
if (this.staticRegistry?.lookup(name, NODE_RELATION, name)) {
|
|
1411
|
+
throw new DynamicRegistryError(
|
|
1412
|
+
`Cannot define node type "${name}": already defined in the static registry.`
|
|
1413
|
+
);
|
|
1414
|
+
}
|
|
1308
1415
|
const uid = generateDeterministicUid(META_NODE_TYPE, name);
|
|
1309
1416
|
const data = { name, jsonSchema };
|
|
1310
1417
|
if (description !== void 0) data.description = description;
|
|
@@ -1326,6 +1433,19 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
1326
1433
|
`Cannot define type "${name}": this name is reserved for the meta-registry.`
|
|
1327
1434
|
);
|
|
1328
1435
|
}
|
|
1436
|
+
if (this.staticRegistry) {
|
|
1437
|
+
const fromTypes = Array.isArray(topology.from) ? topology.from : [topology.from];
|
|
1438
|
+
const toTypes = Array.isArray(topology.to) ? topology.to : [topology.to];
|
|
1439
|
+
for (const aType of fromTypes) {
|
|
1440
|
+
for (const bType of toTypes) {
|
|
1441
|
+
if (this.staticRegistry.lookup(aType, name, bType)) {
|
|
1442
|
+
throw new DynamicRegistryError(
|
|
1443
|
+
`Cannot define edge type "${name}" for (${aType}) -> (${bType}): already defined in the static registry.`
|
|
1444
|
+
);
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1329
1449
|
const uid = generateDeterministicUid(META_EDGE_TYPE, name);
|
|
1330
1450
|
const data = {
|
|
1331
1451
|
name,
|
|
@@ -1334,6 +1454,7 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
1334
1454
|
};
|
|
1335
1455
|
if (jsonSchema !== void 0) data.jsonSchema = jsonSchema;
|
|
1336
1456
|
if (topology.inverseLabel !== void 0) data.inverseLabel = topology.inverseLabel;
|
|
1457
|
+
if (topology.targetGraph !== void 0) data.targetGraph = topology.targetGraph;
|
|
1337
1458
|
if (description !== void 0) data.description = description;
|
|
1338
1459
|
if (options?.titleField !== void 0) data.titleField = options.titleField;
|
|
1339
1460
|
if (options?.subtitleField !== void 0) data.subtitleField = options.subtitleField;
|
|
@@ -1349,7 +1470,12 @@ var GraphClientImpl = class _GraphClientImpl {
|
|
|
1349
1470
|
);
|
|
1350
1471
|
}
|
|
1351
1472
|
const reader = this.createMetaReader();
|
|
1352
|
-
|
|
1473
|
+
const dynamicOnly = await createRegistryFromGraph(reader);
|
|
1474
|
+
if (this.staticRegistry) {
|
|
1475
|
+
this.dynamicRegistry = createMergedRegistry(this.staticRegistry, dynamicOnly);
|
|
1476
|
+
} else {
|
|
1477
|
+
this.dynamicRegistry = dynamicOnly;
|
|
1478
|
+
}
|
|
1353
1479
|
}
|
|
1354
1480
|
/**
|
|
1355
1481
|
* Create a GraphReader for the meta-collection.
|
|
@@ -1408,6 +1534,10 @@ function generateId() {
|
|
|
1408
1534
|
var DEFAULT_LIMIT = 10;
|
|
1409
1535
|
var DEFAULT_MAX_READS = 100;
|
|
1410
1536
|
var DEFAULT_CONCURRENCY = 5;
|
|
1537
|
+
var _crossGraphWarned = false;
|
|
1538
|
+
function isGraphClient(reader) {
|
|
1539
|
+
return "subgraph" in reader && typeof reader.subgraph === "function";
|
|
1540
|
+
}
|
|
1411
1541
|
var Semaphore = class {
|
|
1412
1542
|
constructor(slots) {
|
|
1413
1543
|
this.slots = slots;
|
|
@@ -1433,9 +1563,10 @@ var Semaphore = class {
|
|
|
1433
1563
|
}
|
|
1434
1564
|
};
|
|
1435
1565
|
var TraversalBuilderImpl = class {
|
|
1436
|
-
constructor(reader, startUid) {
|
|
1566
|
+
constructor(reader, startUid, registry) {
|
|
1437
1567
|
this.reader = reader;
|
|
1438
1568
|
this.startUid = startUid;
|
|
1569
|
+
this.registry = registry;
|
|
1439
1570
|
}
|
|
1440
1571
|
hops = [];
|
|
1441
1572
|
follow(axbType, options) {
|
|
@@ -1452,11 +1583,13 @@ var TraversalBuilderImpl = class {
|
|
|
1452
1583
|
const semaphore = new Semaphore(concurrency);
|
|
1453
1584
|
let totalReads = 0;
|
|
1454
1585
|
let truncated = false;
|
|
1455
|
-
let
|
|
1586
|
+
let sources = [
|
|
1587
|
+
{ uid: this.startUid, reader: this.reader }
|
|
1588
|
+
];
|
|
1456
1589
|
const hopResults = [];
|
|
1457
1590
|
for (let depth = 0; depth < this.hops.length; depth++) {
|
|
1458
1591
|
const hop = this.hops[depth];
|
|
1459
|
-
if (
|
|
1592
|
+
if (sources.length === 0) {
|
|
1460
1593
|
hopResults.push({
|
|
1461
1594
|
axbType: hop.axbType,
|
|
1462
1595
|
depth,
|
|
@@ -1467,9 +1600,12 @@ var TraversalBuilderImpl = class {
|
|
|
1467
1600
|
continue;
|
|
1468
1601
|
}
|
|
1469
1602
|
const hopEdges = [];
|
|
1470
|
-
const sourceCount =
|
|
1603
|
+
const sourceCount = sources.length;
|
|
1471
1604
|
let hopTruncated = false;
|
|
1472
|
-
const
|
|
1605
|
+
const resolvedTargetGraph = this.resolveTargetGraph(hop);
|
|
1606
|
+
const direction = hop.direction ?? "forward";
|
|
1607
|
+
const isCrossGraph = direction === "forward" && !!resolvedTargetGraph;
|
|
1608
|
+
const tasks = sources.map(({ uid, reader: sourceReader }) => async () => {
|
|
1473
1609
|
if (totalReads >= maxReads) {
|
|
1474
1610
|
hopTruncated = true;
|
|
1475
1611
|
return;
|
|
@@ -1481,19 +1617,18 @@ var TraversalBuilderImpl = class {
|
|
|
1481
1617
|
return;
|
|
1482
1618
|
}
|
|
1483
1619
|
totalReads++;
|
|
1484
|
-
const direction2 = hop.direction ?? "forward";
|
|
1485
1620
|
const params = { axbType: hop.axbType };
|
|
1486
|
-
if (
|
|
1621
|
+
if (direction === "forward") {
|
|
1487
1622
|
params.aUid = uid;
|
|
1488
1623
|
if (hop.bType) params.bType = hop.bType;
|
|
1489
1624
|
} else {
|
|
1490
1625
|
params.bUid = uid;
|
|
1491
1626
|
if (hop.aType) params.aType = hop.aType;
|
|
1492
1627
|
}
|
|
1493
|
-
if (
|
|
1628
|
+
if (direction === "forward" && hop.aType) {
|
|
1494
1629
|
params.aType = hop.aType;
|
|
1495
1630
|
}
|
|
1496
|
-
if (
|
|
1631
|
+
if (direction === "reverse" && hop.bType) {
|
|
1497
1632
|
params.bType = hop.bType;
|
|
1498
1633
|
}
|
|
1499
1634
|
if (hop.orderBy) params.orderBy = hop.orderBy;
|
|
@@ -1503,31 +1638,58 @@ var TraversalBuilderImpl = class {
|
|
|
1503
1638
|
} else {
|
|
1504
1639
|
params.limit = limit;
|
|
1505
1640
|
}
|
|
1506
|
-
let
|
|
1641
|
+
let hopReader;
|
|
1642
|
+
let nextReader;
|
|
1643
|
+
if (isCrossGraph) {
|
|
1644
|
+
if (isGraphClient(this.reader)) {
|
|
1645
|
+
hopReader = this.reader.subgraph(uid, resolvedTargetGraph);
|
|
1646
|
+
nextReader = hopReader;
|
|
1647
|
+
} else {
|
|
1648
|
+
hopReader = sourceReader;
|
|
1649
|
+
nextReader = sourceReader;
|
|
1650
|
+
if (!_crossGraphWarned) {
|
|
1651
|
+
_crossGraphWarned = true;
|
|
1652
|
+
console.warn(
|
|
1653
|
+
`[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.`
|
|
1654
|
+
);
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
} else {
|
|
1658
|
+
hopReader = sourceReader;
|
|
1659
|
+
nextReader = sourceReader;
|
|
1660
|
+
}
|
|
1661
|
+
let edges2 = await hopReader.findEdges(params);
|
|
1507
1662
|
if (hop.filter) {
|
|
1508
|
-
|
|
1509
|
-
|
|
1663
|
+
edges2 = edges2.filter(hop.filter);
|
|
1664
|
+
edges2 = edges2.slice(0, limit);
|
|
1665
|
+
}
|
|
1666
|
+
for (const edge of edges2) {
|
|
1667
|
+
hopEdges.push({ edge, reader: nextReader });
|
|
1510
1668
|
}
|
|
1511
|
-
hopEdges.push(...edges);
|
|
1512
1669
|
} finally {
|
|
1513
1670
|
semaphore.release();
|
|
1514
1671
|
}
|
|
1515
1672
|
});
|
|
1516
1673
|
await Promise.all(tasks.map((task) => task()));
|
|
1674
|
+
const edges = hopEdges.map((h) => h.edge);
|
|
1517
1675
|
hopResults.push({
|
|
1518
1676
|
axbType: hop.axbType,
|
|
1519
1677
|
depth,
|
|
1520
|
-
edges: returnIntermediates ? [...
|
|
1678
|
+
edges: returnIntermediates ? [...edges] : edges,
|
|
1521
1679
|
sourceCount,
|
|
1522
1680
|
truncated: hopTruncated
|
|
1523
1681
|
});
|
|
1524
1682
|
if (hopTruncated) {
|
|
1525
1683
|
truncated = true;
|
|
1526
1684
|
}
|
|
1527
|
-
const
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1685
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1686
|
+
for (const { edge, reader: edgeReader } of hopEdges) {
|
|
1687
|
+
const nextUid = direction === "forward" ? edge.bUid : edge.aUid;
|
|
1688
|
+
if (!seen.has(nextUid)) {
|
|
1689
|
+
seen.set(nextUid, edgeReader);
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
sources = [...seen.entries()].map(([uid, reader]) => ({ uid, reader }));
|
|
1531
1693
|
}
|
|
1532
1694
|
const lastHop = hopResults[hopResults.length - 1];
|
|
1533
1695
|
return {
|
|
@@ -1537,9 +1699,25 @@ var TraversalBuilderImpl = class {
|
|
|
1537
1699
|
truncated
|
|
1538
1700
|
};
|
|
1539
1701
|
}
|
|
1702
|
+
/**
|
|
1703
|
+
* Resolve the targetGraph for a hop. Priority:
|
|
1704
|
+
* 1. Explicit `hop.targetGraph` (user override)
|
|
1705
|
+
* 2. Registry `targetGraph` for the axbType (if registry available)
|
|
1706
|
+
* 3. undefined (no cross-graph)
|
|
1707
|
+
*/
|
|
1708
|
+
resolveTargetGraph(hop) {
|
|
1709
|
+
if (hop.targetGraph) return hop.targetGraph;
|
|
1710
|
+
if (this.registry) {
|
|
1711
|
+
const entries = this.registry.lookupByAxbType(hop.axbType);
|
|
1712
|
+
for (const entry of entries) {
|
|
1713
|
+
if (entry.targetGraph) return entry.targetGraph;
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
return void 0;
|
|
1717
|
+
}
|
|
1540
1718
|
};
|
|
1541
|
-
function createTraversal(reader, startUid) {
|
|
1542
|
-
return new TraversalBuilderImpl(reader, startUid);
|
|
1719
|
+
function createTraversal(reader, startUid, registry) {
|
|
1720
|
+
return new TraversalBuilderImpl(reader, startUid, registry);
|
|
1543
1721
|
}
|
|
1544
1722
|
|
|
1545
1723
|
// src/views.ts
|
|
@@ -1788,7 +1966,8 @@ function loadEdgeEntity(dir, name) {
|
|
|
1788
1966
|
viewDefaults: meta?.viewDefaults,
|
|
1789
1967
|
viewsPath,
|
|
1790
1968
|
sampleData,
|
|
1791
|
-
allowedIn: meta?.allowedIn
|
|
1969
|
+
allowedIn: meta?.allowedIn,
|
|
1970
|
+
targetGraph: topology.targetGraph ?? meta?.targetGraph
|
|
1792
1971
|
};
|
|
1793
1972
|
}
|
|
1794
1973
|
function getSubdirectories(dir) {
|
|
@@ -1831,6 +2010,20 @@ function discoverEntities(entitiesDir) {
|
|
|
1831
2010
|
};
|
|
1832
2011
|
}
|
|
1833
2012
|
|
|
2013
|
+
// src/cross-graph.ts
|
|
2014
|
+
function resolveAncestorCollection(collectionPath, uid) {
|
|
2015
|
+
const segments = collectionPath.split("/");
|
|
2016
|
+
for (let i = 1; i < segments.length; i += 2) {
|
|
2017
|
+
if (segments[i] === uid) {
|
|
2018
|
+
return segments.slice(0, i).join("/");
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
return null;
|
|
2022
|
+
}
|
|
2023
|
+
function isAncestorUid(collectionPath, uid) {
|
|
2024
|
+
return resolveAncestorCollection(collectionPath, uid) !== null;
|
|
2025
|
+
}
|
|
2026
|
+
|
|
1834
2027
|
// src/codegen/index.ts
|
|
1835
2028
|
function pascalCase(s) {
|
|
1836
2029
|
return s.replace(
|
|
@@ -1916,7 +2109,43 @@ function extractSchemaFields(schema) {
|
|
|
1916
2109
|
if (s.type !== "object" || !s.properties) return [];
|
|
1917
2110
|
return Object.keys(s.properties);
|
|
1918
2111
|
}
|
|
1919
|
-
function
|
|
2112
|
+
function collectionGroupIndexes(collectionName) {
|
|
2113
|
+
return [
|
|
2114
|
+
{
|
|
2115
|
+
collectionGroup: collectionName,
|
|
2116
|
+
queryScope: "COLLECTION_GROUP",
|
|
2117
|
+
fields: [
|
|
2118
|
+
{ fieldPath: "aUid", order: "ASCENDING" },
|
|
2119
|
+
{ fieldPath: "axbType", order: "ASCENDING" }
|
|
2120
|
+
]
|
|
2121
|
+
},
|
|
2122
|
+
{
|
|
2123
|
+
collectionGroup: collectionName,
|
|
2124
|
+
queryScope: "COLLECTION_GROUP",
|
|
2125
|
+
fields: [
|
|
2126
|
+
{ fieldPath: "axbType", order: "ASCENDING" },
|
|
2127
|
+
{ fieldPath: "bUid", order: "ASCENDING" }
|
|
2128
|
+
]
|
|
2129
|
+
},
|
|
2130
|
+
{
|
|
2131
|
+
collectionGroup: collectionName,
|
|
2132
|
+
queryScope: "COLLECTION_GROUP",
|
|
2133
|
+
fields: [
|
|
2134
|
+
{ fieldPath: "aType", order: "ASCENDING" },
|
|
2135
|
+
{ fieldPath: "axbType", order: "ASCENDING" }
|
|
2136
|
+
]
|
|
2137
|
+
},
|
|
2138
|
+
{
|
|
2139
|
+
collectionGroup: collectionName,
|
|
2140
|
+
queryScope: "COLLECTION_GROUP",
|
|
2141
|
+
fields: [
|
|
2142
|
+
{ fieldPath: "axbType", order: "ASCENDING" },
|
|
2143
|
+
{ fieldPath: "bType", order: "ASCENDING" }
|
|
2144
|
+
]
|
|
2145
|
+
}
|
|
2146
|
+
];
|
|
2147
|
+
}
|
|
2148
|
+
function generateIndexConfig(collection, entities, registryEntries) {
|
|
1920
2149
|
const indexes = baseIndexes(collection);
|
|
1921
2150
|
if (entities) {
|
|
1922
2151
|
for (const [, entity] of entities.nodes) {
|
|
@@ -1948,6 +2177,17 @@ function generateIndexConfig(collection, entities) {
|
|
|
1948
2177
|
}
|
|
1949
2178
|
}
|
|
1950
2179
|
}
|
|
2180
|
+
if (registryEntries) {
|
|
2181
|
+
const targetGraphNames = /* @__PURE__ */ new Set();
|
|
2182
|
+
for (const entry of registryEntries) {
|
|
2183
|
+
if (entry.targetGraph) {
|
|
2184
|
+
targetGraphNames.add(entry.targetGraph);
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
for (const name of targetGraphNames) {
|
|
2188
|
+
indexes.push(...collectionGroupIndexes(name));
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
1951
2191
|
return { indexes, fieldOverrides: [] };
|
|
1952
2192
|
}
|
|
1953
2193
|
|
|
@@ -2257,6 +2497,7 @@ var QueryClient = class {
|
|
|
2257
2497
|
computeNodeDocId,
|
|
2258
2498
|
createBootstrapRegistry,
|
|
2259
2499
|
createGraphClient,
|
|
2500
|
+
createMergedRegistry,
|
|
2260
2501
|
createRegistry,
|
|
2261
2502
|
createRegistryFromGraph,
|
|
2262
2503
|
createTraversal,
|
|
@@ -2267,9 +2508,11 @@ var QueryClient = class {
|
|
|
2267
2508
|
generateId,
|
|
2268
2509
|
generateIndexConfig,
|
|
2269
2510
|
generateTypes,
|
|
2511
|
+
isAncestorUid,
|
|
2270
2512
|
jsonSchemaToFieldMeta,
|
|
2271
2513
|
matchScope,
|
|
2272
2514
|
matchScopeAny,
|
|
2515
|
+
resolveAncestorCollection,
|
|
2273
2516
|
resolveView
|
|
2274
2517
|
});
|
|
2275
2518
|
//# sourceMappingURL=index.cjs.map
|