@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/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 bootstrapRegistry if not yet reloaded.
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
- this.dynamicRegistry = await createRegistryFromGraph(reader);
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 sourceUids = [this.startUid];
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 (sourceUids.length === 0) {
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 = sourceUids.length;
1603
+ const sourceCount = sources.length;
1471
1604
  let hopTruncated = false;
1472
- const tasks = sourceUids.map((uid) => async () => {
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 (direction2 === "forward") {
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 (direction2 === "forward" && hop.aType) {
1628
+ if (direction === "forward" && hop.aType) {
1494
1629
  params.aType = hop.aType;
1495
1630
  }
1496
- if (direction2 === "reverse" && hop.bType) {
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 edges = await this.reader.findEdges(params);
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
- edges = edges.filter(hop.filter);
1509
- edges = edges.slice(0, limit);
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 ? [...hopEdges] : hopEdges,
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 direction = hop.direction ?? "forward";
1528
- sourceUids = [...new Set(
1529
- hopEdges.map((e) => direction === "forward" ? e.bUid : e.aUid)
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 generateIndexConfig(collection, entities) {
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