@utaba/deep-memory 0.16.0 → 0.18.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,52 @@ 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
+ const pageEntityIds = /* @__PURE__ */ new Set();
1442
+ for (const element of pageSlice) {
1443
+ if (element.kind === "entity") {
1444
+ if (!pageEntityIds.has(element.entry.entity.id)) {
1445
+ pageEntityIds.add(element.entry.entity.id);
1446
+ pageEntries.push(element.entry);
1447
+ }
1448
+ } else {
1449
+ pagedAllRels.push(element.rel);
1450
+ }
1451
+ }
1452
+ if (pagedAllRels.length > 0) {
1453
+ const entityIndex = /* @__PURE__ */ new Map();
1454
+ for (const el of unionElements) {
1455
+ if (el.kind === "entity" && !entityIndex.has(el.entry.entity.id)) {
1456
+ entityIndex.set(el.entry.entity.id, el.entry);
1457
+ }
1458
+ }
1459
+ for (const rel of pagedAllRels) {
1460
+ for (const endpointId of [rel.sourceEntityId, rel.targetEntityId]) {
1461
+ if (!pageEntityIds.has(endpointId)) {
1462
+ const entry = entityIndex.get(endpointId);
1463
+ if (entry) {
1464
+ pageEntityIds.add(endpointId);
1465
+ pageEntries.push(entry);
1466
+ }
1467
+ }
1468
+ }
1469
+ }
1470
+ }
1471
+ paged = pageEntries;
1472
+ total = pageEntries.length + pagedAllRels.length;
1421
1473
  } 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
- });
1474
+ const resultEntries = dedup && spec.returnMode !== "path" ? dedupEntriesById(frontier) : frontier;
1475
+ total = resultEntries.length;
1476
+ paged = resultEntries.slice(offset, offset + limit);
1431
1477
  }
1432
- const total = resultEntries.length;
1433
- const paged = resultEntries.slice(offset, offset + limit);
1434
1478
  const suppressEntities = spec.projection !== void 0 && spec.projection.includeEntities !== true;
1435
1479
  let entities;
1436
1480
  if (suppressEntities) {
@@ -1451,13 +1495,13 @@ async function executeFallbackTraversal(repositoryId, storage, spec) {
1451
1495
  direction: "both",
1452
1496
  limit: 1e4
1453
1497
  });
1454
- const summary = { outbound: {}, inbound: {} };
1498
+ const summary = { out: {}, in: {} };
1455
1499
  for (const rel of result.items) {
1456
1500
  if (rel.sourceEntityId === entry.entity.id) {
1457
- summary.outbound[rel.relationshipType] = (summary.outbound[rel.relationshipType] ?? 0) + 1;
1501
+ summary.out[rel.relationshipType] = (summary.out[rel.relationshipType] ?? 0) + 1;
1458
1502
  }
1459
1503
  if (rel.targetEntityId === entry.entity.id) {
1460
- summary.inbound[rel.relationshipType] = (summary.inbound[rel.relationshipType] ?? 0) + 1;
1504
+ summary.in[rel.relationshipType] = (summary.in[rel.relationshipType] ?? 0) + 1;
1461
1505
  }
1462
1506
  }
1463
1507
  return summary;
@@ -1471,7 +1515,8 @@ async function executeFallbackTraversal(repositoryId, storage, spec) {
1471
1515
  const propNames = spec.projection.properties;
1472
1516
  const mode = spec.projection.mode ?? "values";
1473
1517
  const distinct = spec.projection.distinct ?? false;
1474
- const sourceEntries = dedup ? (() => {
1518
+ const aggregateDedup = spec.returnMode === "all" || dedup && spec.returnMode !== "path";
1519
+ const sourceEntries = aggregateDedup ? (() => {
1475
1520
  const seen = /* @__PURE__ */ new Set();
1476
1521
  return (spec.returnMode === "all" ? allCollected : frontier).filter((e) => {
1477
1522
  if (seen.has(e.entity.id)) return false;
@@ -1513,17 +1558,28 @@ async function executeFallbackTraversal(repositoryId, storage, spec) {
1513
1558
  }
1514
1559
  let relationships;
1515
1560
  let paths;
1516
- if (!suppressEntities && (spec.returnMode === "path" || spec.returnMode === "all")) {
1561
+ if (!suppressEntities && spec.returnMode === "all") {
1562
+ relationships = pagedAllRels.map((rel) => ({
1563
+ id: rel.id,
1564
+ type: rel.relationshipType,
1565
+ sourceEntityId: rel.sourceEntityId,
1566
+ targetEntityId: rel.targetEntityId,
1567
+ direction: "out",
1568
+ properties: rel.properties
1569
+ }));
1570
+ } else if (!suppressEntities && spec.returnMode === "path") {
1517
1571
  const relMap = /* @__PURE__ */ new Map();
1518
1572
  for (const entry of paged) {
1519
- for (const rel of entry.relationshipPath) {
1573
+ for (let i = 0; i < entry.relationshipPath.length; i++) {
1574
+ const rel = entry.relationshipPath[i];
1520
1575
  if (!relMap.has(rel.id)) {
1576
+ const fromId = entry.entityPath[i];
1521
1577
  relMap.set(rel.id, {
1522
1578
  id: rel.id,
1523
1579
  type: rel.relationshipType,
1524
1580
  sourceEntityId: rel.sourceEntityId,
1525
1581
  targetEntityId: rel.targetEntityId,
1526
- direction: "outbound",
1582
+ direction: fromId === rel.sourceEntityId ? "out" : "in",
1527
1583
  properties: rel.properties
1528
1584
  });
1529
1585
  }
@@ -1532,44 +1588,66 @@ async function executeFallbackTraversal(repositoryId, storage, spec) {
1532
1588
  relationships = Array.from(relMap.values());
1533
1589
  }
1534
1590
  if (!suppressEntities && spec.returnMode === "path") {
1591
+ const pathEntityIds = /* @__PURE__ */ new Set();
1592
+ for (const entry of paged) {
1593
+ for (const id of entry.entityPath) {
1594
+ pathEntityIds.add(id);
1595
+ }
1596
+ }
1597
+ const pathEntityMap = pathEntityIds.size > 0 ? await storage.getEntities(repositoryId, [...pathEntityIds]) : /* @__PURE__ */ new Map();
1598
+ if (pathEntityIds.size > 0) {
1599
+ storageCalls++;
1600
+ }
1535
1601
  paths = paged.map((entry) => ({
1536
1602
  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
- }))
1603
+ entities: entry.entityPath.map((id) => {
1604
+ const stored = pathEntityMap.get(id);
1605
+ if (!stored) {
1606
+ throw new EntityNotFoundError(id);
1607
+ }
1608
+ const projected = projectEntity(stored, detailLevel);
1609
+ if (!spec.includeProvenance) {
1610
+ delete projected["provenance"];
1611
+ }
1612
+ return projected;
1613
+ }),
1614
+ relationships: entry.relationshipPath.map((rel, i) => {
1615
+ const fromId = entry.entityPath[i];
1616
+ return {
1617
+ id: rel.id,
1618
+ type: rel.relationshipType,
1619
+ sourceEntityId: rel.sourceEntityId,
1620
+ targetEntityId: rel.targetEntityId,
1621
+ direction: fromId === rel.sourceEntityId ? "out" : "in",
1622
+ properties: rel.properties
1623
+ };
1624
+ })
1550
1625
  }));
1551
1626
  }
1552
1627
  const executionTimeMs = Date.now() - startTime;
1628
+ const returned = spec.returnMode === "all" ? total : paged.length;
1629
+ const hasMore = spec.returnMode === "all" ? offset + limit < unionFullLength : offset + limit < total;
1630
+ const truncated = spec.returnMode === "all" ? unionFullLength > limit + offset : total > limit + offset;
1553
1631
  return {
1554
1632
  entities,
1555
1633
  relationships,
1556
1634
  paths,
1557
1635
  aggregations,
1558
1636
  total,
1559
- returned: paged.length,
1560
- hasMore: offset + limit < total,
1637
+ returned,
1638
+ hasMore,
1561
1639
  queryMetadata: {
1562
1640
  executionTimeMs,
1563
1641
  appliedLimits: {
1564
1642
  maxResults: limit
1565
1643
  },
1566
- truncated: total > limit + offset,
1567
- truncationReason: total > limit + offset ? "result_limit" : void 0
1644
+ truncated,
1645
+ truncationReason: truncated ? "result_limit" : void 0
1568
1646
  }
1569
1647
  };
1570
1648
  }
1571
1649
  async function executeSingleStep(repositoryId, storage, frontier, step, onStorageCall) {
1572
- const direction = mapDirection(step.direction);
1650
+ const direction = step.direction;
1573
1651
  const pendingEdges = [];
1574
1652
  for (const entry of frontier) {
1575
1653
  const rels = await storage.getEntityRelationships(repositoryId, entry.entity.id, {
@@ -1664,22 +1742,64 @@ async function resolveEntity(repositoryId, storage, idOrSlug) {
1664
1742
  if (byId) return byId;
1665
1743
  return storage.getEntityBySlug(repositoryId, idOrSlug);
1666
1744
  }
1667
- function mapDirection(direction) {
1668
- switch (direction) {
1669
- case "out":
1670
- return "outbound";
1671
- case "in":
1672
- return "inbound";
1673
- case "both":
1674
- return "both";
1675
- }
1676
- }
1677
1745
  function getTargetId(rel, currentEntityId) {
1678
1746
  if (rel.sourceEntityId === currentEntityId) {
1679
1747
  return rel.targetEntityId;
1680
1748
  }
1681
1749
  return rel.sourceEntityId;
1682
1750
  }
1751
+ function dedupEntriesById(entries) {
1752
+ const seen = /* @__PURE__ */ new Set();
1753
+ const result = [];
1754
+ for (const entry of entries) {
1755
+ if (!seen.has(entry.entity.id)) {
1756
+ seen.add(entry.entity.id);
1757
+ result.push(entry);
1758
+ }
1759
+ }
1760
+ return result;
1761
+ }
1762
+ function buildAllModeUnion(entries) {
1763
+ if (entries.length === 0) return [];
1764
+ let maxDepth = 0;
1765
+ const byDepth = /* @__PURE__ */ new Map();
1766
+ for (const entry of entries) {
1767
+ const depth = entry.relationshipPath.length;
1768
+ if (depth > maxDepth) maxDepth = depth;
1769
+ let bucket = byDepth.get(depth);
1770
+ if (!bucket) {
1771
+ bucket = [];
1772
+ byDepth.set(depth, bucket);
1773
+ }
1774
+ bucket.push(entry);
1775
+ }
1776
+ const seenEntities = /* @__PURE__ */ new Set();
1777
+ const seenRels = /* @__PURE__ */ new Set();
1778
+ const result = [];
1779
+ for (const entry of byDepth.get(0) ?? []) {
1780
+ if (!seenEntities.has(entry.entity.id)) {
1781
+ seenEntities.add(entry.entity.id);
1782
+ result.push({ kind: "entity", entry });
1783
+ }
1784
+ }
1785
+ for (let depth = 1; depth <= maxDepth; depth++) {
1786
+ const bucket = byDepth.get(depth) ?? [];
1787
+ for (const entry of bucket) {
1788
+ if (!seenEntities.has(entry.entity.id)) {
1789
+ seenEntities.add(entry.entity.id);
1790
+ result.push({ kind: "entity", entry });
1791
+ }
1792
+ }
1793
+ for (const entry of bucket) {
1794
+ const newRel = entry.relationshipPath[depth - 1];
1795
+ if (newRel && !seenRels.has(newRel.id)) {
1796
+ seenRels.add(newRel.id);
1797
+ result.push({ kind: "relationship", rel: newRel });
1798
+ }
1799
+ }
1800
+ }
1801
+ return result;
1802
+ }
1683
1803
 
1684
1804
  // src/relationships/GraphTraversal.ts
1685
1805
  var GraphTraversal = class {
@@ -1816,7 +1936,7 @@ var GraphTraversal = class {
1816
1936
  return {
1817
1937
  id: rid,
1818
1938
  type: r?.type ?? "unknown",
1819
- direction: r?.sourceEntityId === currentEntityId ? "outbound" : "inbound",
1939
+ direction: r?.sourceEntityId === currentEntityId ? "out" : "in",
1820
1940
  properties: r?.properties ?? {}
1821
1941
  };
1822
1942
  })
@@ -1940,11 +2060,15 @@ var SearchOrchestrator = class {
1940
2060
  const offset = options?.offset ?? 0;
1941
2061
  const threshold = options?.similarityThreshold ?? this.defaultSimilarityThreshold;
1942
2062
  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
- });
2063
+ const allEntities = await this.storage.findEntities(
2064
+ this.repositoryId,
2065
+ {
2066
+ entityTypes: options?.entityTypes,
2067
+ limit: this.conceptSearchScanLimit,
2068
+ offset: 0
2069
+ },
2070
+ { loadEmbeddings: true }
2071
+ );
1948
2072
  const scored = [];
1949
2073
  for (const entity of allEntities.items) {
1950
2074
  let score;
@@ -2439,7 +2563,7 @@ var MemoryRepository = class {
2439
2563
  inbound[rel.relationshipType] = (inbound[rel.relationshipType] ?? 0) + 1;
2440
2564
  }
2441
2565
  }
2442
- return { outbound, inbound };
2566
+ return { out: outbound, in: inbound };
2443
2567
  }
2444
2568
  async getRelationshipsForEntities(entityIds) {
2445
2569
  const seen = /* @__PURE__ */ new Set();
@@ -2647,9 +2771,10 @@ var MemoryRepository = class {
2647
2771
  ### Step 1 \u2014 Discover before you traverse
2648
2772
 
2649
2773
  Before following relationships from an entity, check that it actually has relationships.
2650
- Graph queries include \`relationshipSummary\` on every returned entity by default
2651
- (outbound and inbound counts by type). Inspect this before traversing \u2014 entities with
2652
- zero outbound counts for the relationship type you need have no connections to follow.
2774
+ Graph queries include \`relationshipSummary\` on every returned entity by default \u2014
2775
+ \`out\` (entity is source) and \`in\` (entity is target) counts by relationship type.
2776
+ Inspect this before traversing: entities with zero counts for the relationship type
2777
+ you need have no connections to follow.
2653
2778
  To skip summaries and reduce response size, set \`includeRelationshipSummary: false\`.
2654
2779
 
2655
2780
  ### Step 2 \u2014 Use projection for aggregation
@@ -3327,7 +3452,14 @@ var VocabularyEngine = class {
3327
3452
  async proposeExtension(proposal, proposedBy) {
3328
3453
  return this.proposeChange(proposal, proposedBy);
3329
3454
  }
3330
- /** Cascade-delete all data for a deleted vocabulary type */
3455
+ /**
3456
+ * Cascade-delete all data for a deleted vocabulary type.
3457
+ *
3458
+ * `deletedRelationships` may be `undefined` when the underlying provider
3459
+ * does not count cascaded edges (see StorageProvider.deleteEntitiesByType).
3460
+ * The return value is currently discarded by the only caller; the type is
3461
+ * preserved for symmetry with the storage contract.
3462
+ */
3331
3463
  async cascadeDeleteData(proposal) {
3332
3464
  if (proposal.proposalType === "delete_entity_type" && proposal.deleteEntityType) {
3333
3465
  return this.storage.deleteEntitiesByType(
@@ -3525,7 +3657,7 @@ var EventBus = class {
3525
3657
  };
3526
3658
 
3527
3659
  // src/portability/RepositoryExporter.ts
3528
- var LIBRARY_VERSION = true ? "0.16.0" : "0.1.0";
3660
+ var LIBRARY_VERSION = true ? "0.18.0" : "0.1.0";
3529
3661
  var RepositoryExporter = class {
3530
3662
  storage;
3531
3663
  provenance;
@@ -4628,10 +4760,15 @@ var DeepMemory = class {
4628
4760
  try {
4629
4761
  const result = await repo.reembedAll({
4630
4762
  ...options,
4763
+ // The repo signature widens `totalEntities` to `number | undefined`
4764
+ // because PaginatedResult.total may be undefined under some provider/
4765
+ // query combinations. The `reembed:progress` event contract is
4766
+ // `totalEntities: number`, so fall back to the cached stats count
4767
+ // when the inner layer doesn't supply one.
4631
4768
  onProgress: (progress) => this.globalEventBus.emit("reembed:progress", {
4632
4769
  repositoryId,
4633
4770
  processed: progress.processed,
4634
- totalEntities: progress.totalEntities,
4771
+ totalEntities: progress.totalEntities ?? stats.entityCount,
4635
4772
  failed: progress.failed
4636
4773
  }),
4637
4774
  onItemFailed: (failure) => this.globalEventBus.emit("reembed:item-failed", {
@@ -4690,6 +4827,69 @@ function createSafeSink(sink) {
4690
4827
 
4691
4828
  // src/relationships/compilers/GremlinCompiler.ts
4692
4829
  var DEFAULT_ESTIMATED_FANOUT_PER_HOP = 10;
4830
+ var VERTEX_PROJECTION = [
4831
+ ["__kind", `.by(constant('v'))`],
4832
+ ["id", `.by(id)`],
4833
+ ["entityType", `.by('entityType')`],
4834
+ ["entityLabel", `.by('entityLabel')`],
4835
+ ["slug", `.by('slug')`],
4836
+ ["summary", `.by(coalesce(values('summary'), constant('')))`],
4837
+ ["properties", `.by(coalesce(values('properties'), constant('{}')))`],
4838
+ ["data", `.by(coalesce(values('data'), constant('')))`],
4839
+ ["dataFormat", `.by(coalesce(values('dataFormat'), constant('')))`],
4840
+ ["createdBy", `.by('createdBy')`],
4841
+ ["createdByType", `.by('createdByType')`],
4842
+ ["createdAt", `.by('createdAt')`],
4843
+ ["createdInConversation", `.by(coalesce(values('createdInConversation'), constant('')))`],
4844
+ ["createdFromMessage", `.by(coalesce(values('createdFromMessage'), constant('')))`],
4845
+ ["modifiedBy", `.by('modifiedBy')`],
4846
+ ["modifiedByType", `.by('modifiedByType')`],
4847
+ ["modifiedAt", `.by('modifiedAt')`],
4848
+ ["modifiedInConversation", `.by(coalesce(values('modifiedInConversation'), constant('')))`],
4849
+ ["modifiedFromMessage", `.by(coalesce(values('modifiedFromMessage'), constant('')))`]
4850
+ ];
4851
+ var EDGE_PROJECTION = [
4852
+ ["__kind", `.by(constant('e'))`],
4853
+ ["id", `.by(id)`],
4854
+ ["relationshipType", `.by('relationshipType')`],
4855
+ ["sourceEntityId", `.by('sourceEntityId')`],
4856
+ ["targetEntityId", `.by('targetEntityId')`],
4857
+ ["properties", `.by(coalesce(values('properties'), constant('{}')))`],
4858
+ ["bidirectional", `.by(coalesce(values('bidirectional'), constant(false)))`],
4859
+ ["createdBy", `.by('createdBy')`],
4860
+ ["createdByType", `.by('createdByType')`],
4861
+ ["createdAt", `.by('createdAt')`],
4862
+ ["createdInConversation", `.by(coalesce(values('createdInConversation'), constant('')))`],
4863
+ ["createdFromMessage", `.by(coalesce(values('createdFromMessage'), constant('')))`],
4864
+ ["modifiedBy", `.by('modifiedBy')`],
4865
+ ["modifiedByType", `.by('modifiedByType')`],
4866
+ ["modifiedAt", `.by('modifiedAt')`],
4867
+ ["modifiedInConversation", `.by(coalesce(values('modifiedInConversation'), constant('')))`],
4868
+ ["modifiedFromMessage", `.by(coalesce(values('modifiedFromMessage'), constant('')))`]
4869
+ ];
4870
+ var EMBEDDING_PROJECTION_ENTRY = [
4871
+ "embedding",
4872
+ `.by(coalesce(values('embedding'), constant('')))`
4873
+ ];
4874
+ function buildProjectExpression(entries) {
4875
+ const keys = entries.map(([k]) => `'${k}'`).join(",");
4876
+ const bys = entries.map(([, by]) => by).join("");
4877
+ return `project(${keys})${bys}`;
4878
+ }
4879
+ var VERTEX_PROJECT_EXPR = buildProjectExpression(VERTEX_PROJECTION);
4880
+ var VERTEX_PROJECT_EXPR_WITH_EMBEDDING = buildProjectExpression([
4881
+ ...VERTEX_PROJECTION,
4882
+ EMBEDDING_PROJECTION_ENTRY
4883
+ ]);
4884
+ var EDGE_PROJECT_EXPR = buildProjectExpression(EDGE_PROJECTION);
4885
+ var GREMLIN_VERTEX_PROJECTION_FIELDS = VERTEX_PROJECTION.map(([k]) => k).filter((k) => k !== "__kind");
4886
+ var GREMLIN_EDGE_PROJECTION_FIELDS = EDGE_PROJECTION.map(([k]) => k).filter((k) => k !== "__kind");
4887
+ function buildVertexProjectChain(opts) {
4888
+ return opts?.withEmbedding ? VERTEX_PROJECT_EXPR_WITH_EMBEDDING : VERTEX_PROJECT_EXPR;
4889
+ }
4890
+ function buildEdgeProjectChain() {
4891
+ return EDGE_PROJECT_EXPR;
4892
+ }
4693
4893
  var GremlinCompiler = class {
4694
4894
  language = "gremlin";
4695
4895
  compile(spec, _vocabulary) {
@@ -4705,7 +4905,7 @@ var GremlinCompiler = class {
4705
4905
  parts.push("g.V()");
4706
4906
  if (spec.start.entityId) {
4707
4907
  const p = nextParam(spec.start.entityId);
4708
- parts.push(`.has('id', ${p})`);
4908
+ parts.push(`.hasId(${p})`);
4709
4909
  } else if (spec.start.entityType) {
4710
4910
  const p = nextParam(spec.start.entityType);
4711
4911
  parts.push(`.has('entityType', ${p})`);
@@ -4717,44 +4917,79 @@ var GremlinCompiler = class {
4717
4917
  }
4718
4918
  }
4719
4919
  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;
4920
+ const returnMode = spec.returnMode ?? "terminal";
4921
+ if (returnMode === "all") {
4922
+ if (steps.some((s) => s.repeat)) {
4923
+ throw new Error(
4924
+ "GremlinCompiler: 'all' returnMode does not support repeat steps. Use 'terminal' or 'path' mode, or unroll the repeat into explicit steps."
4925
+ );
4730
4926
  }
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(", ")}))`);
4927
+ const compiledSteps = steps.map((step) => ({
4928
+ edge: compileEdgeOnly(step, nextParam),
4929
+ vertex: compileVertexHop(step),
4930
+ // Per plan §3: entity-type/property filters apply only on branches
4931
+ // ending at that depth's vertex. They are NOT included in the prefix
4932
+ // that deeper branches traverse through.
4933
+ entityFilters: compileEntityFilters(step, nextParam)
4934
+ }));
4935
+ const branches = [`__.identity().${VERTEX_PROJECT_EXPR}`];
4936
+ let prefix = "";
4937
+ for (const { edge, vertex, entityFilters } of compiledSteps) {
4938
+ branches.push(`__${prefix}${edge}${vertex}${entityFilters}.${VERTEX_PROJECT_EXPR}`);
4939
+ branches.push(`__${prefix}${edge}.${EDGE_PROJECT_EXPR}`);
4940
+ prefix = `${prefix}${edge}${vertex}`;
4941
+ estimatedFanOut *= DEFAULT_ESTIMATED_FANOUT_PER_HOP;
4734
4942
  }
4735
- if (step.entityFilter) {
4736
- for (const f of step.entityFilter) {
4737
- parts.push(compilePropertyFilter(f, nextParam));
4943
+ parts.push(`.union(${branches.join(", ")})`);
4944
+ parts.push(`.dedup().by(select('id'))`);
4945
+ const limit = spec.limit ?? 50;
4946
+ const offset = spec.offset ?? 0;
4947
+ const pOffset = nextParam(offset);
4948
+ const pEnd = nextParam(offset + limit);
4949
+ parts.push(`.range(${pOffset}, ${pEnd})`);
4950
+ params["_limit"] = limit;
4951
+ params["_offset"] = offset;
4952
+ } else {
4953
+ const useEdgeEmission = returnMode === "path";
4954
+ for (const step of steps) {
4955
+ if (step.repeat) {
4956
+ parts.push(compileRepeatStep(step, nextParam, useEdgeEmission));
4957
+ estimatedFanOut *= step.repeat.maxDepth * DEFAULT_ESTIMATED_FANOUT_PER_HOP;
4958
+ } else if (useEdgeEmission || step.relationshipFilter && step.relationshipFilter.length > 0) {
4959
+ parts.push(compileEdgeStep(step, nextParam));
4960
+ estimatedFanOut *= DEFAULT_ESTIMATED_FANOUT_PER_HOP;
4961
+ } else {
4962
+ parts.push(compileSimpleStep(step, nextParam));
4963
+ estimatedFanOut *= DEFAULT_ESTIMATED_FANOUT_PER_HOP;
4964
+ }
4965
+ if (step.entityTypes && step.entityTypes.length > 0) {
4966
+ const typeParams = step.entityTypes.map((t) => nextParam(t));
4967
+ parts.push(`.has('entityType', within(${typeParams.join(", ")}))`);
4968
+ }
4969
+ if (step.entityFilter) {
4970
+ for (const f of step.entityFilter) {
4971
+ parts.push(compilePropertyFilter(f, nextParam));
4972
+ }
4738
4973
  }
4739
4974
  }
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)");
4975
+ if (returnMode === "terminal" && spec.dedup !== false) {
4976
+ parts.push(".dedup()");
4977
+ }
4978
+ if (returnMode === "path") {
4979
+ parts.push(".simplePath()");
4980
+ }
4981
+ const limit = spec.limit ?? 50;
4982
+ const offset = spec.offset ?? 0;
4983
+ const pOffset = nextParam(offset);
4984
+ const pEnd = nextParam(offset + limit);
4985
+ parts.push(`.range(${pOffset}, ${pEnd})`);
4986
+ params["_limit"] = limit;
4987
+ params["_offset"] = offset;
4988
+ if (returnMode === "terminal") {
4989
+ parts.push(`.${VERTEX_PROJECT_EXPR}`);
4990
+ } else {
4991
+ parts.push(`.path().by(${VERTEX_PROJECT_EXPR}).by(${EDGE_PROJECT_EXPR})`);
4992
+ }
4758
4993
  }
4759
4994
  return {
4760
4995
  query: parts.join(""),
@@ -4775,7 +5010,7 @@ function compileSimpleStep(step, nextParam) {
4775
5010
  return types ? `.both(${typeArgs})` : ".both()";
4776
5011
  }
4777
5012
  }
4778
- function compileEdgeStep(step, nextParam) {
5013
+ function compileEdgeOnly(step, nextParam) {
4779
5014
  const parts = [];
4780
5015
  const types = step.relationshipTypes;
4781
5016
  const typeArgs = types ? types.map((t) => nextParam(t)).join(", ") : "";
@@ -4795,22 +5030,37 @@ function compileEdgeStep(step, nextParam) {
4795
5030
  parts.push(compilePropertyFilter(f, nextParam));
4796
5031
  }
4797
5032
  }
5033
+ return parts.join("");
5034
+ }
5035
+ function compileVertexHop(step) {
4798
5036
  switch (step.direction) {
4799
5037
  case "out":
4800
- parts.push(".inV()");
4801
- break;
5038
+ return ".inV()";
4802
5039
  case "in":
4803
- parts.push(".outV()");
4804
- break;
5040
+ return ".outV()";
4805
5041
  case "both":
4806
- parts.push(".otherV()");
4807
- break;
5042
+ return ".otherV()";
5043
+ }
5044
+ }
5045
+ function compileEntityFilters(step, nextParam) {
5046
+ const parts = [];
5047
+ if (step.entityTypes && step.entityTypes.length > 0) {
5048
+ const typeParams = step.entityTypes.map((t) => nextParam(t));
5049
+ parts.push(`.has('entityType', within(${typeParams.join(", ")}))`);
5050
+ }
5051
+ if (step.entityFilter) {
5052
+ for (const f of step.entityFilter) {
5053
+ parts.push(compilePropertyFilter(f, nextParam));
5054
+ }
4808
5055
  }
4809
5056
  return parts.join("");
4810
5057
  }
4811
- function compileRepeatStep(step, nextParam) {
5058
+ function compileEdgeStep(step, nextParam) {
5059
+ return compileEdgeOnly(step, nextParam) + compileVertexHop(step);
5060
+ }
5061
+ function compileRepeatStep(step, nextParam, useEdgeEmission) {
4812
5062
  const parts = [];
4813
- const innerStep = step.relationshipFilter?.length ? compileEdgeStep({ ...step, repeat: void 0 }, nextParam) : compileSimpleStep(step, nextParam);
5063
+ const innerStep = useEdgeEmission || step.relationshipFilter?.length ? compileEdgeStep({ ...step, repeat: void 0 }, nextParam) : compileSimpleStep(step, nextParam);
4814
5064
  if (step.repeat?.emitIntermediates !== false) {
4815
5065
  parts.push(".emit()");
4816
5066
  }
@@ -4819,10 +5069,13 @@ function compileRepeatStep(step, nextParam) {
4819
5069
  const untilParts = step.repeat.until.map((f) => compilePropertyFilter(f, nextParam));
4820
5070
  parts.push(`.until(${untilParts.join("")})`);
4821
5071
  }
4822
- if (step.repeat?.maxDepth) {
4823
- const p = nextParam(step.repeat.maxDepth);
4824
- parts.push(`.times(${p})`);
5072
+ const n = step.repeat.maxDepth;
5073
+ if (!Number.isInteger(n) || n < 1) {
5074
+ throw new Error(
5075
+ `GremlinCompiler: repeat.maxDepth must be a positive integer; got ${n}`
5076
+ );
4825
5077
  }
5078
+ parts.push(`.times(${n})`);
4826
5079
  if (step.repeat?.emitIntermediates === false) {
4827
5080
  parts.push(".emit()");
4828
5081
  }
@@ -5141,17 +5394,20 @@ var InMemoryStorageProvider = class {
5141
5394
  store.slugIndex.set(entity.slug, entity.id);
5142
5395
  return entity;
5143
5396
  }
5144
- async getEntity(repositoryId, entityId) {
5397
+ // The in-memory provider always carries the full entity (embedding and all).
5398
+ // The `loadEmbeddings` option is accepted for interface symmetry but ignored —
5399
+ // there is no light/full split to switch between when entities live in a Map.
5400
+ async getEntity(repositoryId, entityId, _options) {
5145
5401
  const store = this.getStore(repositoryId);
5146
5402
  return store.entities.get(entityId) ?? null;
5147
5403
  }
5148
- async getEntityBySlug(repositoryId, slug) {
5404
+ async getEntityBySlug(repositoryId, slug, _options) {
5149
5405
  const store = this.getStore(repositoryId);
5150
5406
  const id = store.slugIndex.get(slug);
5151
5407
  if (!id) return null;
5152
5408
  return store.entities.get(id) ?? null;
5153
5409
  }
5154
- async getEntities(repositoryId, entityIds) {
5410
+ async getEntities(repositoryId, entityIds, _options) {
5155
5411
  const store = this.getStore(repositoryId);
5156
5412
  const result = /* @__PURE__ */ new Map();
5157
5413
  for (const id of entityIds) {
@@ -5222,7 +5478,7 @@ var InMemoryStorageProvider = class {
5222
5478
  }
5223
5479
  return { deletedEntities: entityIds.size, deletedRelationships };
5224
5480
  }
5225
- async findEntities(repositoryId, query) {
5481
+ async findEntities(repositoryId, query, _options) {
5226
5482
  const store = this.getStore(repositoryId);
5227
5483
  let matches = Array.from(store.entities.values());
5228
5484
  if (query.entityTypes && query.entityTypes.length > 0) {
@@ -5275,9 +5531,9 @@ var InMemoryStorageProvider = class {
5275
5531
  const isTarget = rel.targetEntityId === entityId;
5276
5532
  const isBidirectionalTarget = rel.bidirectional && isTarget;
5277
5533
  switch (direction) {
5278
- case "outbound":
5534
+ case "out":
5279
5535
  return isSource || isBidirectionalTarget;
5280
- case "inbound":
5536
+ case "in":
5281
5537
  return isTarget || rel.bidirectional && isSource;
5282
5538
  case "both":
5283
5539
  default:
@@ -5375,17 +5631,17 @@ var InMemoryStorageProvider = class {
5375
5631
  const isTarget = rel.targetEntityId === frontierEntityId;
5376
5632
  let matchesDirection = false;
5377
5633
  let connectedEntityId;
5378
- if (isSource && (options.direction === "outbound" || options.direction === "both")) {
5634
+ if (isSource && (options.direction === "out" || options.direction === "both")) {
5379
5635
  matchesDirection = true;
5380
5636
  connectedEntityId = rel.targetEntityId;
5381
- } else if (isTarget && (options.direction === "inbound" || options.direction === "both")) {
5637
+ } else if (isTarget && (options.direction === "in" || options.direction === "both")) {
5382
5638
  matchesDirection = true;
5383
5639
  connectedEntityId = rel.sourceEntityId;
5384
5640
  } else if (rel.bidirectional) {
5385
- if (isSource && options.direction === "inbound") {
5641
+ if (isSource && options.direction === "in") {
5386
5642
  matchesDirection = true;
5387
5643
  connectedEntityId = rel.targetEntityId;
5388
- } else if (isTarget && options.direction === "outbound") {
5644
+ } else if (isTarget && options.direction === "out") {
5389
5645
  matchesDirection = true;
5390
5646
  connectedEntityId = rel.sourceEntityId;
5391
5647
  }
@@ -5745,6 +6001,8 @@ var NoOpEmbeddingProvider = class {
5745
6001
  EmbeddingProviderRequiredError,
5746
6002
  EntityNotFoundError,
5747
6003
  ExportError,
6004
+ GREMLIN_EDGE_PROJECTION_FIELDS,
6005
+ GREMLIN_VERTEX_PROJECTION_FIELDS,
5748
6006
  GovernanceDeniedError,
5749
6007
  GraphTraversalProviderRequiredError,
5750
6008
  GremlinCompiler,
@@ -5766,7 +6024,10 @@ var NoOpEmbeddingProvider = class {
5766
6024
  TraversalTimeoutError,
5767
6025
  TraversalValidationError,
5768
6026
  TraversalVocabularyError,
6027
+ UnsupportedQueryError,
5769
6028
  VocabularyValidationError,
6029
+ buildEdgeProjectChain,
6030
+ buildVertexProjectChain,
5770
6031
  createSafeSink,
5771
6032
  generateId,
5772
6033
  isValidUuid,