@utaba/deep-memory 0.16.0 → 0.17.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
@@ -29,6 +29,8 @@ __export(src_exports, {
29
29
  EmbeddingProviderRequiredError: () => EmbeddingProviderRequiredError,
30
30
  EntityNotFoundError: () => EntityNotFoundError,
31
31
  ExportError: () => ExportError,
32
+ GREMLIN_EDGE_PROJECTION_FIELDS: () => GREMLIN_EDGE_PROJECTION_FIELDS,
33
+ GREMLIN_VERTEX_PROJECTION_FIELDS: () => GREMLIN_VERTEX_PROJECTION_FIELDS,
32
34
  GovernanceDeniedError: () => GovernanceDeniedError,
33
35
  GraphTraversalProviderRequiredError: () => GraphTraversalProviderRequiredError,
34
36
  GremlinCompiler: () => GremlinCompiler,
@@ -50,7 +52,10 @@ __export(src_exports, {
50
52
  TraversalTimeoutError: () => TraversalTimeoutError,
51
53
  TraversalValidationError: () => TraversalValidationError,
52
54
  TraversalVocabularyError: () => TraversalVocabularyError,
55
+ UnsupportedQueryError: () => UnsupportedQueryError,
53
56
  VocabularyValidationError: () => VocabularyValidationError,
57
+ buildEdgeProjectChain: () => buildEdgeProjectChain,
58
+ buildVertexProjectChain: () => buildVertexProjectChain,
54
59
  createSafeSink: () => createSafeSink,
55
60
  generateId: () => generateId,
56
61
  isValidUuid: () => isValidUuid,
@@ -341,6 +346,14 @@ var TraversalTimeoutError = class extends DeepMemoryError {
341
346
  this.timeoutMs = timeoutMs;
342
347
  }
343
348
  };
349
+ var UnsupportedQueryError = class extends DeepMemoryError {
350
+ provider;
351
+ constructor(provider, message, suggestion) {
352
+ super("UNSUPPORTED_QUERY", message, suggestion);
353
+ this.name = "UnsupportedQueryError";
354
+ this.provider = provider;
355
+ }
356
+ };
344
357
 
345
358
  // src/core/DeepMemory.ts
346
359
  var import_node_crypto = require("crypto");
@@ -959,7 +972,7 @@ var EntityManager = class {
959
972
  let totalFailed = 0;
960
973
  const allErrors = [];
961
974
  let offset = 0;
962
- while (offset < total) {
975
+ while (total === void 0 || offset < total) {
963
976
  if (signal?.aborted) {
964
977
  throw new OperationAbortedError("reembedAll");
965
978
  }
@@ -986,7 +999,8 @@ var EntityManager = class {
986
999
  throw new OperationAbortedError("reembedAll");
987
1000
  }
988
1001
  const delayMs = options?.delayBetweenBatchesMs ?? 0;
989
- if (delayMs > 0 && offset < total) {
1002
+ const moreLikely = total === void 0 ? page.items.length === batchSize : offset < total;
1003
+ if (delayMs > 0 && moreLikely) {
990
1004
  await new Promise((resolve) => {
991
1005
  setTimeout(resolve, delayMs);
992
1006
  });
@@ -1415,22 +1429,29 @@ async function executeFallbackTraversal(repositoryId, storage, spec) {
1415
1429
  const dedup = spec.dedup !== false;
1416
1430
  const limit = spec.limit ?? 50;
1417
1431
  const offset = spec.offset ?? 0;
1418
- let resultEntries;
1432
+ let paged;
1433
+ let pagedAllRels = [];
1434
+ let total;
1435
+ let unionFullLength = 0;
1419
1436
  if (spec.returnMode === "all") {
1420
- resultEntries = allCollected;
1437
+ const unionElements = buildAllModeUnion(allCollected);
1438
+ unionFullLength = unionElements.length;
1439
+ const pageSlice = unionElements.slice(offset, offset + limit);
1440
+ const pageEntries = [];
1441
+ for (const element of pageSlice) {
1442
+ if (element.kind === "entity") {
1443
+ pageEntries.push(element.entry);
1444
+ } else {
1445
+ pagedAllRels.push(element.rel);
1446
+ }
1447
+ }
1448
+ paged = pageEntries;
1449
+ total = pageEntries.length + pagedAllRels.length;
1421
1450
  } else {
1422
- resultEntries = frontier;
1423
- }
1424
- if (dedup) {
1425
- const seen = /* @__PURE__ */ new Set();
1426
- resultEntries = resultEntries.filter((entry) => {
1427
- if (seen.has(entry.entity.id)) return false;
1428
- seen.add(entry.entity.id);
1429
- return true;
1430
- });
1451
+ const resultEntries = dedup && spec.returnMode !== "path" ? dedupEntriesById(frontier) : frontier;
1452
+ total = resultEntries.length;
1453
+ paged = resultEntries.slice(offset, offset + limit);
1431
1454
  }
1432
- const total = resultEntries.length;
1433
- const paged = resultEntries.slice(offset, offset + limit);
1434
1455
  const suppressEntities = spec.projection !== void 0 && spec.projection.includeEntities !== true;
1435
1456
  let entities;
1436
1457
  if (suppressEntities) {
@@ -1471,7 +1492,8 @@ async function executeFallbackTraversal(repositoryId, storage, spec) {
1471
1492
  const propNames = spec.projection.properties;
1472
1493
  const mode = spec.projection.mode ?? "values";
1473
1494
  const distinct = spec.projection.distinct ?? false;
1474
- const sourceEntries = dedup ? (() => {
1495
+ const aggregateDedup = spec.returnMode === "all" || dedup && spec.returnMode !== "path";
1496
+ const sourceEntries = aggregateDedup ? (() => {
1475
1497
  const seen = /* @__PURE__ */ new Set();
1476
1498
  return (spec.returnMode === "all" ? allCollected : frontier).filter((e) => {
1477
1499
  if (seen.has(e.entity.id)) return false;
@@ -1513,17 +1535,28 @@ async function executeFallbackTraversal(repositoryId, storage, spec) {
1513
1535
  }
1514
1536
  let relationships;
1515
1537
  let paths;
1516
- if (!suppressEntities && (spec.returnMode === "path" || spec.returnMode === "all")) {
1538
+ if (!suppressEntities && spec.returnMode === "all") {
1539
+ relationships = pagedAllRels.map((rel) => ({
1540
+ id: rel.id,
1541
+ type: rel.relationshipType,
1542
+ sourceEntityId: rel.sourceEntityId,
1543
+ targetEntityId: rel.targetEntityId,
1544
+ direction: "outbound",
1545
+ properties: rel.properties
1546
+ }));
1547
+ } else if (!suppressEntities && spec.returnMode === "path") {
1517
1548
  const relMap = /* @__PURE__ */ new Map();
1518
1549
  for (const entry of paged) {
1519
- for (const rel of entry.relationshipPath) {
1550
+ for (let i = 0; i < entry.relationshipPath.length; i++) {
1551
+ const rel = entry.relationshipPath[i];
1520
1552
  if (!relMap.has(rel.id)) {
1553
+ const fromId = entry.entityPath[i];
1521
1554
  relMap.set(rel.id, {
1522
1555
  id: rel.id,
1523
1556
  type: rel.relationshipType,
1524
1557
  sourceEntityId: rel.sourceEntityId,
1525
1558
  targetEntityId: rel.targetEntityId,
1526
- direction: "outbound",
1559
+ direction: fromId === rel.sourceEntityId ? "outbound" : "inbound",
1527
1560
  properties: rel.properties
1528
1561
  });
1529
1562
  }
@@ -1532,39 +1565,61 @@ async function executeFallbackTraversal(repositoryId, storage, spec) {
1532
1565
  relationships = Array.from(relMap.values());
1533
1566
  }
1534
1567
  if (!suppressEntities && spec.returnMode === "path") {
1568
+ const pathEntityIds = /* @__PURE__ */ new Set();
1569
+ for (const entry of paged) {
1570
+ for (const id of entry.entityPath) {
1571
+ pathEntityIds.add(id);
1572
+ }
1573
+ }
1574
+ const pathEntityMap = pathEntityIds.size > 0 ? await storage.getEntities(repositoryId, [...pathEntityIds]) : /* @__PURE__ */ new Map();
1575
+ if (pathEntityIds.size > 0) {
1576
+ storageCalls++;
1577
+ }
1535
1578
  paths = paged.map((entry) => ({
1536
1579
  length: entry.entityPath.length - 1,
1537
- entities: [(() => {
1538
- const e = projectEntity(entry.entity, detailLevel);
1539
- if (!spec.includeProvenance) delete e["provenance"];
1540
- return e;
1541
- })()],
1542
- relationships: entry.relationshipPath.map((rel) => ({
1543
- id: rel.id,
1544
- type: rel.relationshipType,
1545
- sourceEntityId: rel.sourceEntityId,
1546
- targetEntityId: rel.targetEntityId,
1547
- direction: "outbound",
1548
- properties: rel.properties
1549
- }))
1580
+ entities: entry.entityPath.map((id) => {
1581
+ const stored = pathEntityMap.get(id);
1582
+ if (!stored) {
1583
+ throw new EntityNotFoundError(id);
1584
+ }
1585
+ const projected = projectEntity(stored, detailLevel);
1586
+ if (!spec.includeProvenance) {
1587
+ delete projected["provenance"];
1588
+ }
1589
+ return projected;
1590
+ }),
1591
+ relationships: entry.relationshipPath.map((rel, i) => {
1592
+ const fromId = entry.entityPath[i];
1593
+ return {
1594
+ id: rel.id,
1595
+ type: rel.relationshipType,
1596
+ sourceEntityId: rel.sourceEntityId,
1597
+ targetEntityId: rel.targetEntityId,
1598
+ direction: fromId === rel.sourceEntityId ? "outbound" : "inbound",
1599
+ properties: rel.properties
1600
+ };
1601
+ })
1550
1602
  }));
1551
1603
  }
1552
1604
  const executionTimeMs = Date.now() - startTime;
1605
+ const returned = spec.returnMode === "all" ? total : paged.length;
1606
+ const hasMore = spec.returnMode === "all" ? offset + limit < unionFullLength : offset + limit < total;
1607
+ const truncated = spec.returnMode === "all" ? unionFullLength > limit + offset : total > limit + offset;
1553
1608
  return {
1554
1609
  entities,
1555
1610
  relationships,
1556
1611
  paths,
1557
1612
  aggregations,
1558
1613
  total,
1559
- returned: paged.length,
1560
- hasMore: offset + limit < total,
1614
+ returned,
1615
+ hasMore,
1561
1616
  queryMetadata: {
1562
1617
  executionTimeMs,
1563
1618
  appliedLimits: {
1564
1619
  maxResults: limit
1565
1620
  },
1566
- truncated: total > limit + offset,
1567
- truncationReason: total > limit + offset ? "result_limit" : void 0
1621
+ truncated,
1622
+ truncationReason: truncated ? "result_limit" : void 0
1568
1623
  }
1569
1624
  };
1570
1625
  }
@@ -1680,6 +1735,58 @@ function getTargetId(rel, currentEntityId) {
1680
1735
  }
1681
1736
  return rel.sourceEntityId;
1682
1737
  }
1738
+ function dedupEntriesById(entries) {
1739
+ const seen = /* @__PURE__ */ new Set();
1740
+ const result = [];
1741
+ for (const entry of entries) {
1742
+ if (!seen.has(entry.entity.id)) {
1743
+ seen.add(entry.entity.id);
1744
+ result.push(entry);
1745
+ }
1746
+ }
1747
+ return result;
1748
+ }
1749
+ function buildAllModeUnion(entries) {
1750
+ if (entries.length === 0) return [];
1751
+ let maxDepth = 0;
1752
+ const byDepth = /* @__PURE__ */ new Map();
1753
+ for (const entry of entries) {
1754
+ const depth = entry.relationshipPath.length;
1755
+ if (depth > maxDepth) maxDepth = depth;
1756
+ let bucket = byDepth.get(depth);
1757
+ if (!bucket) {
1758
+ bucket = [];
1759
+ byDepth.set(depth, bucket);
1760
+ }
1761
+ bucket.push(entry);
1762
+ }
1763
+ const seenEntities = /* @__PURE__ */ new Set();
1764
+ const seenRels = /* @__PURE__ */ new Set();
1765
+ const result = [];
1766
+ for (const entry of byDepth.get(0) ?? []) {
1767
+ if (!seenEntities.has(entry.entity.id)) {
1768
+ seenEntities.add(entry.entity.id);
1769
+ result.push({ kind: "entity", entry });
1770
+ }
1771
+ }
1772
+ for (let depth = 1; depth <= maxDepth; depth++) {
1773
+ const bucket = byDepth.get(depth) ?? [];
1774
+ for (const entry of bucket) {
1775
+ const newRel = entry.relationshipPath[depth - 1];
1776
+ if (newRel && !seenRels.has(newRel.id)) {
1777
+ seenRels.add(newRel.id);
1778
+ result.push({ kind: "relationship", rel: newRel });
1779
+ }
1780
+ }
1781
+ for (const entry of bucket) {
1782
+ if (!seenEntities.has(entry.entity.id)) {
1783
+ seenEntities.add(entry.entity.id);
1784
+ result.push({ kind: "entity", entry });
1785
+ }
1786
+ }
1787
+ }
1788
+ return result;
1789
+ }
1683
1790
 
1684
1791
  // src/relationships/GraphTraversal.ts
1685
1792
  var GraphTraversal = class {
@@ -1940,11 +2047,15 @@ var SearchOrchestrator = class {
1940
2047
  const offset = options?.offset ?? 0;
1941
2048
  const threshold = options?.similarityThreshold ?? this.defaultSimilarityThreshold;
1942
2049
  const queryEmbedding = await this.embedding.embed(query);
1943
- const allEntities = await this.storage.findEntities(this.repositoryId, {
1944
- entityTypes: options?.entityTypes,
1945
- limit: this.conceptSearchScanLimit,
1946
- offset: 0
1947
- });
2050
+ const allEntities = await this.storage.findEntities(
2051
+ this.repositoryId,
2052
+ {
2053
+ entityTypes: options?.entityTypes,
2054
+ limit: this.conceptSearchScanLimit,
2055
+ offset: 0
2056
+ },
2057
+ { loadEmbeddings: true }
2058
+ );
1948
2059
  const scored = [];
1949
2060
  for (const entity of allEntities.items) {
1950
2061
  let score;
@@ -3327,7 +3438,14 @@ var VocabularyEngine = class {
3327
3438
  async proposeExtension(proposal, proposedBy) {
3328
3439
  return this.proposeChange(proposal, proposedBy);
3329
3440
  }
3330
- /** Cascade-delete all data for a deleted vocabulary type */
3441
+ /**
3442
+ * Cascade-delete all data for a deleted vocabulary type.
3443
+ *
3444
+ * `deletedRelationships` may be `undefined` when the underlying provider
3445
+ * does not count cascaded edges (see StorageProvider.deleteEntitiesByType).
3446
+ * The return value is currently discarded by the only caller; the type is
3447
+ * preserved for symmetry with the storage contract.
3448
+ */
3331
3449
  async cascadeDeleteData(proposal) {
3332
3450
  if (proposal.proposalType === "delete_entity_type" && proposal.deleteEntityType) {
3333
3451
  return this.storage.deleteEntitiesByType(
@@ -3525,7 +3643,7 @@ var EventBus = class {
3525
3643
  };
3526
3644
 
3527
3645
  // src/portability/RepositoryExporter.ts
3528
- var LIBRARY_VERSION = true ? "0.16.0" : "0.1.0";
3646
+ var LIBRARY_VERSION = true ? "0.17.0" : "0.1.0";
3529
3647
  var RepositoryExporter = class {
3530
3648
  storage;
3531
3649
  provenance;
@@ -4628,10 +4746,15 @@ var DeepMemory = class {
4628
4746
  try {
4629
4747
  const result = await repo.reembedAll({
4630
4748
  ...options,
4749
+ // The repo signature widens `totalEntities` to `number | undefined`
4750
+ // because PaginatedResult.total may be undefined under some provider/
4751
+ // query combinations. The `reembed:progress` event contract is
4752
+ // `totalEntities: number`, so fall back to the cached stats count
4753
+ // when the inner layer doesn't supply one.
4631
4754
  onProgress: (progress) => this.globalEventBus.emit("reembed:progress", {
4632
4755
  repositoryId,
4633
4756
  processed: progress.processed,
4634
- totalEntities: progress.totalEntities,
4757
+ totalEntities: progress.totalEntities ?? stats.entityCount,
4635
4758
  failed: progress.failed
4636
4759
  }),
4637
4760
  onItemFailed: (failure) => this.globalEventBus.emit("reembed:item-failed", {
@@ -4690,6 +4813,69 @@ function createSafeSink(sink) {
4690
4813
 
4691
4814
  // src/relationships/compilers/GremlinCompiler.ts
4692
4815
  var DEFAULT_ESTIMATED_FANOUT_PER_HOP = 10;
4816
+ var VERTEX_PROJECTION = [
4817
+ ["__kind", `.by(constant('v'))`],
4818
+ ["id", `.by(id)`],
4819
+ ["entityType", `.by('entityType')`],
4820
+ ["entityLabel", `.by('entityLabel')`],
4821
+ ["slug", `.by('slug')`],
4822
+ ["summary", `.by(coalesce(values('summary'), constant('')))`],
4823
+ ["properties", `.by(coalesce(values('properties'), constant('{}')))`],
4824
+ ["data", `.by(coalesce(values('data'), constant('')))`],
4825
+ ["dataFormat", `.by(coalesce(values('dataFormat'), constant('')))`],
4826
+ ["createdBy", `.by('createdBy')`],
4827
+ ["createdByType", `.by('createdByType')`],
4828
+ ["createdAt", `.by('createdAt')`],
4829
+ ["createdInConversation", `.by(coalesce(values('createdInConversation'), constant('')))`],
4830
+ ["createdFromMessage", `.by(coalesce(values('createdFromMessage'), constant('')))`],
4831
+ ["modifiedBy", `.by('modifiedBy')`],
4832
+ ["modifiedByType", `.by('modifiedByType')`],
4833
+ ["modifiedAt", `.by('modifiedAt')`],
4834
+ ["modifiedInConversation", `.by(coalesce(values('modifiedInConversation'), constant('')))`],
4835
+ ["modifiedFromMessage", `.by(coalesce(values('modifiedFromMessage'), constant('')))`]
4836
+ ];
4837
+ var EDGE_PROJECTION = [
4838
+ ["__kind", `.by(constant('e'))`],
4839
+ ["id", `.by(id)`],
4840
+ ["relationshipType", `.by('relationshipType')`],
4841
+ ["sourceEntityId", `.by('sourceEntityId')`],
4842
+ ["targetEntityId", `.by('targetEntityId')`],
4843
+ ["properties", `.by(coalesce(values('properties'), constant('{}')))`],
4844
+ ["bidirectional", `.by(coalesce(values('bidirectional'), constant(false)))`],
4845
+ ["createdBy", `.by('createdBy')`],
4846
+ ["createdByType", `.by('createdByType')`],
4847
+ ["createdAt", `.by('createdAt')`],
4848
+ ["createdInConversation", `.by(coalesce(values('createdInConversation'), constant('')))`],
4849
+ ["createdFromMessage", `.by(coalesce(values('createdFromMessage'), constant('')))`],
4850
+ ["modifiedBy", `.by('modifiedBy')`],
4851
+ ["modifiedByType", `.by('modifiedByType')`],
4852
+ ["modifiedAt", `.by('modifiedAt')`],
4853
+ ["modifiedInConversation", `.by(coalesce(values('modifiedInConversation'), constant('')))`],
4854
+ ["modifiedFromMessage", `.by(coalesce(values('modifiedFromMessage'), constant('')))`]
4855
+ ];
4856
+ var EMBEDDING_PROJECTION_ENTRY = [
4857
+ "embedding",
4858
+ `.by(coalesce(values('embedding'), constant('')))`
4859
+ ];
4860
+ function buildProjectExpression(entries) {
4861
+ const keys = entries.map(([k]) => `'${k}'`).join(",");
4862
+ const bys = entries.map(([, by]) => by).join("");
4863
+ return `project(${keys})${bys}`;
4864
+ }
4865
+ var VERTEX_PROJECT_EXPR = buildProjectExpression(VERTEX_PROJECTION);
4866
+ var VERTEX_PROJECT_EXPR_WITH_EMBEDDING = buildProjectExpression([
4867
+ ...VERTEX_PROJECTION,
4868
+ EMBEDDING_PROJECTION_ENTRY
4869
+ ]);
4870
+ var EDGE_PROJECT_EXPR = buildProjectExpression(EDGE_PROJECTION);
4871
+ var GREMLIN_VERTEX_PROJECTION_FIELDS = VERTEX_PROJECTION.map(([k]) => k).filter((k) => k !== "__kind");
4872
+ var GREMLIN_EDGE_PROJECTION_FIELDS = EDGE_PROJECTION.map(([k]) => k).filter((k) => k !== "__kind");
4873
+ function buildVertexProjectChain(opts) {
4874
+ return opts?.withEmbedding ? VERTEX_PROJECT_EXPR_WITH_EMBEDDING : VERTEX_PROJECT_EXPR;
4875
+ }
4876
+ function buildEdgeProjectChain() {
4877
+ return EDGE_PROJECT_EXPR;
4878
+ }
4693
4879
  var GremlinCompiler = class {
4694
4880
  language = "gremlin";
4695
4881
  compile(spec, _vocabulary) {
@@ -4705,7 +4891,7 @@ var GremlinCompiler = class {
4705
4891
  parts.push("g.V()");
4706
4892
  if (spec.start.entityId) {
4707
4893
  const p = nextParam(spec.start.entityId);
4708
- parts.push(`.has('id', ${p})`);
4894
+ parts.push(`.hasId(${p})`);
4709
4895
  } else if (spec.start.entityType) {
4710
4896
  const p = nextParam(spec.start.entityType);
4711
4897
  parts.push(`.has('entityType', ${p})`);
@@ -4717,44 +4903,79 @@ var GremlinCompiler = class {
4717
4903
  }
4718
4904
  }
4719
4905
  const steps = spec.steps ?? [];
4720
- for (const step of steps) {
4721
- if (step.repeat) {
4722
- parts.push(compileRepeatStep(step, nextParam));
4723
- estimatedFanOut *= step.repeat.maxDepth * DEFAULT_ESTIMATED_FANOUT_PER_HOP;
4724
- } else if (step.relationshipFilter && step.relationshipFilter.length > 0) {
4725
- parts.push(compileEdgeStep(step, nextParam));
4726
- estimatedFanOut *= DEFAULT_ESTIMATED_FANOUT_PER_HOP;
4727
- } else {
4728
- parts.push(compileSimpleStep(step, nextParam));
4729
- estimatedFanOut *= DEFAULT_ESTIMATED_FANOUT_PER_HOP;
4906
+ const returnMode = spec.returnMode ?? "terminal";
4907
+ if (returnMode === "all") {
4908
+ if (steps.some((s) => s.repeat)) {
4909
+ throw new Error(
4910
+ "GremlinCompiler: 'all' returnMode does not support repeat steps. Use 'terminal' or 'path' mode, or unroll the repeat into explicit steps."
4911
+ );
4730
4912
  }
4731
- if (step.entityTypes && step.entityTypes.length > 0) {
4732
- const typeParams = step.entityTypes.map((t) => nextParam(t));
4733
- parts.push(`.has('entityType', within(${typeParams.join(", ")}))`);
4913
+ const compiledSteps = steps.map((step) => ({
4914
+ edge: compileEdgeOnly(step, nextParam),
4915
+ vertex: compileVertexHop(step),
4916
+ // Per plan §3: entity-type/property filters apply only on branches
4917
+ // ending at that depth's vertex. They are NOT included in the prefix
4918
+ // that deeper branches traverse through.
4919
+ entityFilters: compileEntityFilters(step, nextParam)
4920
+ }));
4921
+ const branches = [`__.identity().${VERTEX_PROJECT_EXPR}`];
4922
+ let prefix = "";
4923
+ for (const { edge, vertex, entityFilters } of compiledSteps) {
4924
+ branches.push(`__${prefix}${edge}.${EDGE_PROJECT_EXPR}`);
4925
+ branches.push(`__${prefix}${edge}${vertex}${entityFilters}.${VERTEX_PROJECT_EXPR}`);
4926
+ prefix = `${prefix}${edge}${vertex}`;
4927
+ estimatedFanOut *= DEFAULT_ESTIMATED_FANOUT_PER_HOP;
4734
4928
  }
4735
- if (step.entityFilter) {
4736
- for (const f of step.entityFilter) {
4737
- parts.push(compilePropertyFilter(f, nextParam));
4929
+ parts.push(`.union(${branches.join(", ")})`);
4930
+ parts.push(`.dedup().by(select('id'))`);
4931
+ const limit = spec.limit ?? 50;
4932
+ const offset = spec.offset ?? 0;
4933
+ const pOffset = nextParam(offset);
4934
+ const pEnd = nextParam(offset + limit);
4935
+ parts.push(`.range(${pOffset}, ${pEnd})`);
4936
+ params["_limit"] = limit;
4937
+ params["_offset"] = offset;
4938
+ } else {
4939
+ const useEdgeEmission = returnMode === "path";
4940
+ for (const step of steps) {
4941
+ if (step.repeat) {
4942
+ parts.push(compileRepeatStep(step, nextParam, useEdgeEmission));
4943
+ estimatedFanOut *= step.repeat.maxDepth * DEFAULT_ESTIMATED_FANOUT_PER_HOP;
4944
+ } else if (useEdgeEmission || step.relationshipFilter && step.relationshipFilter.length > 0) {
4945
+ parts.push(compileEdgeStep(step, nextParam));
4946
+ estimatedFanOut *= DEFAULT_ESTIMATED_FANOUT_PER_HOP;
4947
+ } else {
4948
+ parts.push(compileSimpleStep(step, nextParam));
4949
+ estimatedFanOut *= DEFAULT_ESTIMATED_FANOUT_PER_HOP;
4950
+ }
4951
+ if (step.entityTypes && step.entityTypes.length > 0) {
4952
+ const typeParams = step.entityTypes.map((t) => nextParam(t));
4953
+ parts.push(`.has('entityType', within(${typeParams.join(", ")}))`);
4954
+ }
4955
+ if (step.entityFilter) {
4956
+ for (const f of step.entityFilter) {
4957
+ parts.push(compilePropertyFilter(f, nextParam));
4958
+ }
4738
4959
  }
4739
4960
  }
4740
- }
4741
- if (spec.returnMode === "all") {
4742
- }
4743
- if (spec.dedup !== false) {
4744
- parts.push(".dedup()");
4745
- }
4746
- const limit = spec.limit ?? 50;
4747
- const offset = spec.offset ?? 0;
4748
- const pOffset = nextParam(offset);
4749
- const pEnd = nextParam(offset + limit);
4750
- parts.push(`.range(${pOffset}, ${pEnd})`);
4751
- params["_limit"] = limit;
4752
- params["_offset"] = offset;
4753
- if (spec.returnMode === "path") {
4754
- parts.push(".path()");
4755
- }
4756
- if (spec.returnMode !== "path") {
4757
- parts.push(".valueMap(true)");
4961
+ if (returnMode === "terminal" && spec.dedup !== false) {
4962
+ parts.push(".dedup()");
4963
+ }
4964
+ if (returnMode === "path") {
4965
+ parts.push(".simplePath()");
4966
+ }
4967
+ const limit = spec.limit ?? 50;
4968
+ const offset = spec.offset ?? 0;
4969
+ const pOffset = nextParam(offset);
4970
+ const pEnd = nextParam(offset + limit);
4971
+ parts.push(`.range(${pOffset}, ${pEnd})`);
4972
+ params["_limit"] = limit;
4973
+ params["_offset"] = offset;
4974
+ if (returnMode === "terminal") {
4975
+ parts.push(`.${VERTEX_PROJECT_EXPR}`);
4976
+ } else {
4977
+ parts.push(`.path().by(${VERTEX_PROJECT_EXPR}).by(${EDGE_PROJECT_EXPR})`);
4978
+ }
4758
4979
  }
4759
4980
  return {
4760
4981
  query: parts.join(""),
@@ -4775,7 +4996,7 @@ function compileSimpleStep(step, nextParam) {
4775
4996
  return types ? `.both(${typeArgs})` : ".both()";
4776
4997
  }
4777
4998
  }
4778
- function compileEdgeStep(step, nextParam) {
4999
+ function compileEdgeOnly(step, nextParam) {
4779
5000
  const parts = [];
4780
5001
  const types = step.relationshipTypes;
4781
5002
  const typeArgs = types ? types.map((t) => nextParam(t)).join(", ") : "";
@@ -4795,22 +5016,37 @@ function compileEdgeStep(step, nextParam) {
4795
5016
  parts.push(compilePropertyFilter(f, nextParam));
4796
5017
  }
4797
5018
  }
5019
+ return parts.join("");
5020
+ }
5021
+ function compileVertexHop(step) {
4798
5022
  switch (step.direction) {
4799
5023
  case "out":
4800
- parts.push(".inV()");
4801
- break;
5024
+ return ".inV()";
4802
5025
  case "in":
4803
- parts.push(".outV()");
4804
- break;
5026
+ return ".outV()";
4805
5027
  case "both":
4806
- parts.push(".otherV()");
4807
- break;
5028
+ return ".otherV()";
5029
+ }
5030
+ }
5031
+ function compileEntityFilters(step, nextParam) {
5032
+ const parts = [];
5033
+ if (step.entityTypes && step.entityTypes.length > 0) {
5034
+ const typeParams = step.entityTypes.map((t) => nextParam(t));
5035
+ parts.push(`.has('entityType', within(${typeParams.join(", ")}))`);
5036
+ }
5037
+ if (step.entityFilter) {
5038
+ for (const f of step.entityFilter) {
5039
+ parts.push(compilePropertyFilter(f, nextParam));
5040
+ }
4808
5041
  }
4809
5042
  return parts.join("");
4810
5043
  }
4811
- function compileRepeatStep(step, nextParam) {
5044
+ function compileEdgeStep(step, nextParam) {
5045
+ return compileEdgeOnly(step, nextParam) + compileVertexHop(step);
5046
+ }
5047
+ function compileRepeatStep(step, nextParam, useEdgeEmission) {
4812
5048
  const parts = [];
4813
- const innerStep = step.relationshipFilter?.length ? compileEdgeStep({ ...step, repeat: void 0 }, nextParam) : compileSimpleStep(step, nextParam);
5049
+ const innerStep = useEdgeEmission || step.relationshipFilter?.length ? compileEdgeStep({ ...step, repeat: void 0 }, nextParam) : compileSimpleStep(step, nextParam);
4814
5050
  if (step.repeat?.emitIntermediates !== false) {
4815
5051
  parts.push(".emit()");
4816
5052
  }
@@ -4819,10 +5055,13 @@ function compileRepeatStep(step, nextParam) {
4819
5055
  const untilParts = step.repeat.until.map((f) => compilePropertyFilter(f, nextParam));
4820
5056
  parts.push(`.until(${untilParts.join("")})`);
4821
5057
  }
4822
- if (step.repeat?.maxDepth) {
4823
- const p = nextParam(step.repeat.maxDepth);
4824
- parts.push(`.times(${p})`);
5058
+ const n = step.repeat.maxDepth;
5059
+ if (!Number.isInteger(n) || n < 1) {
5060
+ throw new Error(
5061
+ `GremlinCompiler: repeat.maxDepth must be a positive integer; got ${n}`
5062
+ );
4825
5063
  }
5064
+ parts.push(`.times(${n})`);
4826
5065
  if (step.repeat?.emitIntermediates === false) {
4827
5066
  parts.push(".emit()");
4828
5067
  }
@@ -5141,17 +5380,20 @@ var InMemoryStorageProvider = class {
5141
5380
  store.slugIndex.set(entity.slug, entity.id);
5142
5381
  return entity;
5143
5382
  }
5144
- async getEntity(repositoryId, entityId) {
5383
+ // The in-memory provider always carries the full entity (embedding and all).
5384
+ // The `loadEmbeddings` option is accepted for interface symmetry but ignored —
5385
+ // there is no light/full split to switch between when entities live in a Map.
5386
+ async getEntity(repositoryId, entityId, _options) {
5145
5387
  const store = this.getStore(repositoryId);
5146
5388
  return store.entities.get(entityId) ?? null;
5147
5389
  }
5148
- async getEntityBySlug(repositoryId, slug) {
5390
+ async getEntityBySlug(repositoryId, slug, _options) {
5149
5391
  const store = this.getStore(repositoryId);
5150
5392
  const id = store.slugIndex.get(slug);
5151
5393
  if (!id) return null;
5152
5394
  return store.entities.get(id) ?? null;
5153
5395
  }
5154
- async getEntities(repositoryId, entityIds) {
5396
+ async getEntities(repositoryId, entityIds, _options) {
5155
5397
  const store = this.getStore(repositoryId);
5156
5398
  const result = /* @__PURE__ */ new Map();
5157
5399
  for (const id of entityIds) {
@@ -5222,7 +5464,7 @@ var InMemoryStorageProvider = class {
5222
5464
  }
5223
5465
  return { deletedEntities: entityIds.size, deletedRelationships };
5224
5466
  }
5225
- async findEntities(repositoryId, query) {
5467
+ async findEntities(repositoryId, query, _options) {
5226
5468
  const store = this.getStore(repositoryId);
5227
5469
  let matches = Array.from(store.entities.values());
5228
5470
  if (query.entityTypes && query.entityTypes.length > 0) {
@@ -5745,6 +5987,8 @@ var NoOpEmbeddingProvider = class {
5745
5987
  EmbeddingProviderRequiredError,
5746
5988
  EntityNotFoundError,
5747
5989
  ExportError,
5990
+ GREMLIN_EDGE_PROJECTION_FIELDS,
5991
+ GREMLIN_VERTEX_PROJECTION_FIELDS,
5748
5992
  GovernanceDeniedError,
5749
5993
  GraphTraversalProviderRequiredError,
5750
5994
  GremlinCompiler,
@@ -5766,7 +6010,10 @@ var NoOpEmbeddingProvider = class {
5766
6010
  TraversalTimeoutError,
5767
6011
  TraversalValidationError,
5768
6012
  TraversalVocabularyError,
6013
+ UnsupportedQueryError,
5769
6014
  VocabularyValidationError,
6015
+ buildEdgeProjectChain,
6016
+ buildVertexProjectChain,
5770
6017
  createSafeSink,
5771
6018
  generateId,
5772
6019
  isValidUuid,