@utaba/deep-memory 0.15.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");
@@ -894,10 +907,15 @@ var EntityManager = class {
894
907
  attempt++;
895
908
  if (attempt > maxRetries) {
896
909
  const errorMsg = err instanceof Error ? err.message : String(err);
910
+ const failureMessage = `embedBatch failed after ${maxRetries} retries: ${errorMsg}`;
911
+ const failed = entries.map(([id]) => ({ entityId: id, error: failureMessage }));
912
+ for (const { entityId, error } of failed) {
913
+ await options?.onItemFailed?.(entityId, error);
914
+ }
897
915
  return {
898
916
  processed: 0,
899
917
  failed: entries.length,
900
- errors: entries.map(([id]) => ({ entityId: id, error: `embedBatch failed after ${maxRetries} retries: ${errorMsg}` })),
918
+ errors: failed,
901
919
  modelId: this.embedding.modelId(),
902
920
  dimensions: this.embedding.dimensions()
903
921
  };
@@ -920,7 +938,9 @@ var EntityManager = class {
920
938
  });
921
939
  processed++;
922
940
  } catch (err) {
923
- errors.push({ entityId: id, error: err instanceof Error ? err.message : String(err) });
941
+ const errorMessage = err instanceof Error ? err.message : String(err);
942
+ errors.push({ entityId: id, error: errorMessage });
943
+ await options?.onItemFailed?.(id, errorMessage);
924
944
  }
925
945
  }
926
946
  return {
@@ -952,7 +972,7 @@ var EntityManager = class {
952
972
  let totalFailed = 0;
953
973
  const allErrors = [];
954
974
  let offset = 0;
955
- while (offset < total) {
975
+ while (total === void 0 || offset < total) {
956
976
  if (signal?.aborted) {
957
977
  throw new OperationAbortedError("reembedAll");
958
978
  }
@@ -963,7 +983,8 @@ var EntityManager = class {
963
983
  if (page.items.length === 0) break;
964
984
  const ids = page.items.map((e) => e.id);
965
985
  const result = await this.reembedEntities(ids, {
966
- maxRetries: options?.maxRetries
986
+ maxRetries: options?.maxRetries,
987
+ onItemFailed: options?.onItemFailed
967
988
  });
968
989
  totalProcessed += result.processed;
969
990
  totalFailed += result.failed;
@@ -978,7 +999,8 @@ var EntityManager = class {
978
999
  throw new OperationAbortedError("reembedAll");
979
1000
  }
980
1001
  const delayMs = options?.delayBetweenBatchesMs ?? 0;
981
- if (delayMs > 0 && offset < total) {
1002
+ const moreLikely = total === void 0 ? page.items.length === batchSize : offset < total;
1003
+ if (delayMs > 0 && moreLikely) {
982
1004
  await new Promise((resolve) => {
983
1005
  setTimeout(resolve, delayMs);
984
1006
  });
@@ -1407,22 +1429,29 @@ async function executeFallbackTraversal(repositoryId, storage, spec) {
1407
1429
  const dedup = spec.dedup !== false;
1408
1430
  const limit = spec.limit ?? 50;
1409
1431
  const offset = spec.offset ?? 0;
1410
- let resultEntries;
1432
+ let paged;
1433
+ let pagedAllRels = [];
1434
+ let total;
1435
+ let unionFullLength = 0;
1411
1436
  if (spec.returnMode === "all") {
1412
- 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;
1413
1450
  } else {
1414
- resultEntries = frontier;
1451
+ const resultEntries = dedup && spec.returnMode !== "path" ? dedupEntriesById(frontier) : frontier;
1452
+ total = resultEntries.length;
1453
+ paged = resultEntries.slice(offset, offset + limit);
1415
1454
  }
1416
- if (dedup) {
1417
- const seen = /* @__PURE__ */ new Set();
1418
- resultEntries = resultEntries.filter((entry) => {
1419
- if (seen.has(entry.entity.id)) return false;
1420
- seen.add(entry.entity.id);
1421
- return true;
1422
- });
1423
- }
1424
- const total = resultEntries.length;
1425
- const paged = resultEntries.slice(offset, offset + limit);
1426
1455
  const suppressEntities = spec.projection !== void 0 && spec.projection.includeEntities !== true;
1427
1456
  let entities;
1428
1457
  if (suppressEntities) {
@@ -1463,7 +1492,8 @@ async function executeFallbackTraversal(repositoryId, storage, spec) {
1463
1492
  const propNames = spec.projection.properties;
1464
1493
  const mode = spec.projection.mode ?? "values";
1465
1494
  const distinct = spec.projection.distinct ?? false;
1466
- const sourceEntries = dedup ? (() => {
1495
+ const aggregateDedup = spec.returnMode === "all" || dedup && spec.returnMode !== "path";
1496
+ const sourceEntries = aggregateDedup ? (() => {
1467
1497
  const seen = /* @__PURE__ */ new Set();
1468
1498
  return (spec.returnMode === "all" ? allCollected : frontier).filter((e) => {
1469
1499
  if (seen.has(e.entity.id)) return false;
@@ -1505,17 +1535,28 @@ async function executeFallbackTraversal(repositoryId, storage, spec) {
1505
1535
  }
1506
1536
  let relationships;
1507
1537
  let paths;
1508
- 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") {
1509
1548
  const relMap = /* @__PURE__ */ new Map();
1510
1549
  for (const entry of paged) {
1511
- for (const rel of entry.relationshipPath) {
1550
+ for (let i = 0; i < entry.relationshipPath.length; i++) {
1551
+ const rel = entry.relationshipPath[i];
1512
1552
  if (!relMap.has(rel.id)) {
1553
+ const fromId = entry.entityPath[i];
1513
1554
  relMap.set(rel.id, {
1514
1555
  id: rel.id,
1515
1556
  type: rel.relationshipType,
1516
1557
  sourceEntityId: rel.sourceEntityId,
1517
1558
  targetEntityId: rel.targetEntityId,
1518
- direction: "outbound",
1559
+ direction: fromId === rel.sourceEntityId ? "outbound" : "inbound",
1519
1560
  properties: rel.properties
1520
1561
  });
1521
1562
  }
@@ -1524,39 +1565,61 @@ async function executeFallbackTraversal(repositoryId, storage, spec) {
1524
1565
  relationships = Array.from(relMap.values());
1525
1566
  }
1526
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
+ }
1527
1578
  paths = paged.map((entry) => ({
1528
1579
  length: entry.entityPath.length - 1,
1529
- entities: [(() => {
1530
- const e = projectEntity(entry.entity, detailLevel);
1531
- if (!spec.includeProvenance) delete e["provenance"];
1532
- return e;
1533
- })()],
1534
- relationships: entry.relationshipPath.map((rel) => ({
1535
- id: rel.id,
1536
- type: rel.relationshipType,
1537
- sourceEntityId: rel.sourceEntityId,
1538
- targetEntityId: rel.targetEntityId,
1539
- direction: "outbound",
1540
- properties: rel.properties
1541
- }))
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
+ })
1542
1602
  }));
1543
1603
  }
1544
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;
1545
1608
  return {
1546
1609
  entities,
1547
1610
  relationships,
1548
1611
  paths,
1549
1612
  aggregations,
1550
1613
  total,
1551
- returned: paged.length,
1552
- hasMore: offset + limit < total,
1614
+ returned,
1615
+ hasMore,
1553
1616
  queryMetadata: {
1554
1617
  executionTimeMs,
1555
1618
  appliedLimits: {
1556
1619
  maxResults: limit
1557
1620
  },
1558
- truncated: total > limit + offset,
1559
- truncationReason: total > limit + offset ? "result_limit" : void 0
1621
+ truncated,
1622
+ truncationReason: truncated ? "result_limit" : void 0
1560
1623
  }
1561
1624
  };
1562
1625
  }
@@ -1672,6 +1735,58 @@ function getTargetId(rel, currentEntityId) {
1672
1735
  }
1673
1736
  return rel.sourceEntityId;
1674
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
+ }
1675
1790
 
1676
1791
  // src/relationships/GraphTraversal.ts
1677
1792
  var GraphTraversal = class {
@@ -1932,11 +2047,15 @@ var SearchOrchestrator = class {
1932
2047
  const offset = options?.offset ?? 0;
1933
2048
  const threshold = options?.similarityThreshold ?? this.defaultSimilarityThreshold;
1934
2049
  const queryEmbedding = await this.embedding.embed(query);
1935
- const allEntities = await this.storage.findEntities(this.repositoryId, {
1936
- entityTypes: options?.entityTypes,
1937
- limit: this.conceptSearchScanLimit,
1938
- offset: 0
1939
- });
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
+ );
1940
2059
  const scored = [];
1941
2060
  for (const entity of allEntities.items) {
1942
2061
  let score;
@@ -2392,6 +2511,9 @@ var MemoryRepository = class {
2392
2511
  signal: options?.signal,
2393
2512
  onProgress: async (processed, total, failed) => {
2394
2513
  await options?.onProgress?.({ processed, totalEntities: total, failed });
2514
+ },
2515
+ onItemFailed: async (entityId, error) => {
2516
+ await options?.onItemFailed?.({ entityId, error });
2395
2517
  }
2396
2518
  });
2397
2519
  await this.storage.updateRepository(this.repositoryId, {
@@ -3316,7 +3438,14 @@ var VocabularyEngine = class {
3316
3438
  async proposeExtension(proposal, proposedBy) {
3317
3439
  return this.proposeChange(proposal, proposedBy);
3318
3440
  }
3319
- /** 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
+ */
3320
3449
  async cascadeDeleteData(proposal) {
3321
3450
  if (proposal.proposalType === "delete_entity_type" && proposal.deleteEntityType) {
3322
3451
  return this.storage.deleteEntitiesByType(
@@ -3514,7 +3643,7 @@ var EventBus = class {
3514
3643
  };
3515
3644
 
3516
3645
  // src/portability/RepositoryExporter.ts
3517
- var LIBRARY_VERSION = true ? "0.15.0" : "0.1.0";
3646
+ var LIBRARY_VERSION = true ? "0.17.0" : "0.1.0";
3518
3647
  var RepositoryExporter = class {
3519
3648
  storage;
3520
3649
  provenance;
@@ -3868,12 +3997,14 @@ var RepositoryImporter = class {
3868
3997
  storage;
3869
3998
  actorId;
3870
3999
  onProgress;
4000
+ onItemFailed;
3871
4001
  signal;
3872
4002
  migrationEngine = new MigrationEngine();
3873
4003
  constructor(config) {
3874
4004
  this.storage = config.storage;
3875
4005
  this.actorId = config.actorId;
3876
4006
  this.onProgress = config.onProgress;
4007
+ this.onItemFailed = config.onItemFailed;
3877
4008
  this.signal = config.signal;
3878
4009
  }
3879
4010
  throwIfAborted() {
@@ -3970,6 +4101,8 @@ var RepositoryImporter = class {
3970
4101
  message: `Failed to import ${e.item}: ${e.error}`,
3971
4102
  id: e.item
3972
4103
  });
4104
+ const itemType = chunk.entities?.some((x) => x.id === e.item) ? "entity" : chunk.relationships?.some((x) => x.id === e.item) ? "relationship" : "entity";
4105
+ await this.onItemFailed?.({ itemId: e.item, itemType, error: e.error });
3973
4106
  }
3974
4107
  await this.onProgress?.({
3975
4108
  repositoryId: target.repositoryId,
@@ -4106,11 +4239,13 @@ var RepositoryImporter = class {
4106
4239
  const targetExists = await this.storage.getEntity(repositoryId, rel.targetEntityId);
4107
4240
  if (!sourceExists || !targetExists) {
4108
4241
  relationshipsSkipped++;
4242
+ const errorMsg = `source or target entity missing`;
4109
4243
  warnings.push({
4110
4244
  code: "relationship_orphaned",
4111
- message: `Relationship "${rel.id}" skipped \u2014 source or target entity missing`,
4245
+ message: `Relationship "${rel.id}" skipped \u2014 ${errorMsg}`,
4112
4246
  relationshipId: rel.id
4113
4247
  });
4248
+ await this.onItemFailed?.({ itemId: rel.id, itemType: "relationship", error: errorMsg });
4114
4249
  } else {
4115
4250
  try {
4116
4251
  await this.storage.createRelationship(repositoryId, rel);
@@ -4457,6 +4592,12 @@ var DeepMemory = class {
4457
4592
  storage: this.storage,
4458
4593
  actorId: this.provenance.getContext().actorId,
4459
4594
  onProgress: (progress) => this.globalEventBus.emit("import:progress", progress),
4595
+ onItemFailed: (failure) => this.globalEventBus.emit("import:item-failed", {
4596
+ repositoryId: options.target.repositoryId,
4597
+ itemId: failure.itemId,
4598
+ itemType: failure.itemType,
4599
+ error: failure.error
4600
+ }),
4460
4601
  signal: options.signal
4461
4602
  });
4462
4603
  try {
@@ -4550,6 +4691,12 @@ var DeepMemory = class {
4550
4691
  storage: this.storage,
4551
4692
  actorId: this.provenance.getContext().actorId,
4552
4693
  onProgress: (progress) => this.globalEventBus.emit("import:progress", progress),
4694
+ onItemFailed: (failure) => this.globalEventBus.emit("import:item-failed", {
4695
+ repositoryId: options.target.repositoryId,
4696
+ itemId: failure.itemId,
4697
+ itemType: failure.itemType,
4698
+ error: failure.error
4699
+ }),
4553
4700
  signal: options.signal
4554
4701
  });
4555
4702
  await this.globalEventBus.emit("import:started", { repositoryId: options.target.repositoryId });
@@ -4599,11 +4746,21 @@ var DeepMemory = class {
4599
4746
  try {
4600
4747
  const result = await repo.reembedAll({
4601
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.
4602
4754
  onProgress: (progress) => this.globalEventBus.emit("reembed:progress", {
4603
4755
  repositoryId,
4604
4756
  processed: progress.processed,
4605
- totalEntities: progress.totalEntities,
4757
+ totalEntities: progress.totalEntities ?? stats.entityCount,
4606
4758
  failed: progress.failed
4759
+ }),
4760
+ onItemFailed: (failure) => this.globalEventBus.emit("reembed:item-failed", {
4761
+ repositoryId,
4762
+ entityId: failure.entityId,
4763
+ error: failure.error
4607
4764
  })
4608
4765
  });
4609
4766
  await this.globalEventBus.emit("reembed:completed", {
@@ -4656,6 +4813,69 @@ function createSafeSink(sink) {
4656
4813
 
4657
4814
  // src/relationships/compilers/GremlinCompiler.ts
4658
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
+ }
4659
4879
  var GremlinCompiler = class {
4660
4880
  language = "gremlin";
4661
4881
  compile(spec, _vocabulary) {
@@ -4671,7 +4891,7 @@ var GremlinCompiler = class {
4671
4891
  parts.push("g.V()");
4672
4892
  if (spec.start.entityId) {
4673
4893
  const p = nextParam(spec.start.entityId);
4674
- parts.push(`.has('id', ${p})`);
4894
+ parts.push(`.hasId(${p})`);
4675
4895
  } else if (spec.start.entityType) {
4676
4896
  const p = nextParam(spec.start.entityType);
4677
4897
  parts.push(`.has('entityType', ${p})`);
@@ -4683,44 +4903,79 @@ var GremlinCompiler = class {
4683
4903
  }
4684
4904
  }
4685
4905
  const steps = spec.steps ?? [];
4686
- for (const step of steps) {
4687
- if (step.repeat) {
4688
- parts.push(compileRepeatStep(step, nextParam));
4689
- estimatedFanOut *= step.repeat.maxDepth * DEFAULT_ESTIMATED_FANOUT_PER_HOP;
4690
- } else if (step.relationshipFilter && step.relationshipFilter.length > 0) {
4691
- parts.push(compileEdgeStep(step, nextParam));
4692
- estimatedFanOut *= DEFAULT_ESTIMATED_FANOUT_PER_HOP;
4693
- } else {
4694
- parts.push(compileSimpleStep(step, nextParam));
4695
- 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
+ );
4696
4912
  }
4697
- if (step.entityTypes && step.entityTypes.length > 0) {
4698
- const typeParams = step.entityTypes.map((t) => nextParam(t));
4699
- 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;
4700
4928
  }
4701
- if (step.entityFilter) {
4702
- for (const f of step.entityFilter) {
4703
- 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
+ }
4704
4959
  }
4705
4960
  }
4706
- }
4707
- if (spec.returnMode === "all") {
4708
- }
4709
- if (spec.dedup !== false) {
4710
- parts.push(".dedup()");
4711
- }
4712
- const limit = spec.limit ?? 50;
4713
- const offset = spec.offset ?? 0;
4714
- const pOffset = nextParam(offset);
4715
- const pEnd = nextParam(offset + limit);
4716
- parts.push(`.range(${pOffset}, ${pEnd})`);
4717
- params["_limit"] = limit;
4718
- params["_offset"] = offset;
4719
- if (spec.returnMode === "path") {
4720
- parts.push(".path()");
4721
- }
4722
- if (spec.returnMode !== "path") {
4723
- 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
+ }
4724
4979
  }
4725
4980
  return {
4726
4981
  query: parts.join(""),
@@ -4741,7 +4996,7 @@ function compileSimpleStep(step, nextParam) {
4741
4996
  return types ? `.both(${typeArgs})` : ".both()";
4742
4997
  }
4743
4998
  }
4744
- function compileEdgeStep(step, nextParam) {
4999
+ function compileEdgeOnly(step, nextParam) {
4745
5000
  const parts = [];
4746
5001
  const types = step.relationshipTypes;
4747
5002
  const typeArgs = types ? types.map((t) => nextParam(t)).join(", ") : "";
@@ -4761,22 +5016,37 @@ function compileEdgeStep(step, nextParam) {
4761
5016
  parts.push(compilePropertyFilter(f, nextParam));
4762
5017
  }
4763
5018
  }
5019
+ return parts.join("");
5020
+ }
5021
+ function compileVertexHop(step) {
4764
5022
  switch (step.direction) {
4765
5023
  case "out":
4766
- parts.push(".inV()");
4767
- break;
5024
+ return ".inV()";
4768
5025
  case "in":
4769
- parts.push(".outV()");
4770
- break;
5026
+ return ".outV()";
4771
5027
  case "both":
4772
- parts.push(".otherV()");
4773
- 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
+ }
4774
5041
  }
4775
5042
  return parts.join("");
4776
5043
  }
4777
- function compileRepeatStep(step, nextParam) {
5044
+ function compileEdgeStep(step, nextParam) {
5045
+ return compileEdgeOnly(step, nextParam) + compileVertexHop(step);
5046
+ }
5047
+ function compileRepeatStep(step, nextParam, useEdgeEmission) {
4778
5048
  const parts = [];
4779
- 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);
4780
5050
  if (step.repeat?.emitIntermediates !== false) {
4781
5051
  parts.push(".emit()");
4782
5052
  }
@@ -4785,10 +5055,13 @@ function compileRepeatStep(step, nextParam) {
4785
5055
  const untilParts = step.repeat.until.map((f) => compilePropertyFilter(f, nextParam));
4786
5056
  parts.push(`.until(${untilParts.join("")})`);
4787
5057
  }
4788
- if (step.repeat?.maxDepth) {
4789
- const p = nextParam(step.repeat.maxDepth);
4790
- 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
+ );
4791
5063
  }
5064
+ parts.push(`.times(${n})`);
4792
5065
  if (step.repeat?.emitIntermediates === false) {
4793
5066
  parts.push(".emit()");
4794
5067
  }
@@ -5107,17 +5380,20 @@ var InMemoryStorageProvider = class {
5107
5380
  store.slugIndex.set(entity.slug, entity.id);
5108
5381
  return entity;
5109
5382
  }
5110
- 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) {
5111
5387
  const store = this.getStore(repositoryId);
5112
5388
  return store.entities.get(entityId) ?? null;
5113
5389
  }
5114
- async getEntityBySlug(repositoryId, slug) {
5390
+ async getEntityBySlug(repositoryId, slug, _options) {
5115
5391
  const store = this.getStore(repositoryId);
5116
5392
  const id = store.slugIndex.get(slug);
5117
5393
  if (!id) return null;
5118
5394
  return store.entities.get(id) ?? null;
5119
5395
  }
5120
- async getEntities(repositoryId, entityIds) {
5396
+ async getEntities(repositoryId, entityIds, _options) {
5121
5397
  const store = this.getStore(repositoryId);
5122
5398
  const result = /* @__PURE__ */ new Map();
5123
5399
  for (const id of entityIds) {
@@ -5188,7 +5464,7 @@ var InMemoryStorageProvider = class {
5188
5464
  }
5189
5465
  return { deletedEntities: entityIds.size, deletedRelationships };
5190
5466
  }
5191
- async findEntities(repositoryId, query) {
5467
+ async findEntities(repositoryId, query, _options) {
5192
5468
  const store = this.getStore(repositoryId);
5193
5469
  let matches = Array.from(store.entities.values());
5194
5470
  if (query.entityTypes && query.entityTypes.length > 0) {
@@ -5711,6 +5987,8 @@ var NoOpEmbeddingProvider = class {
5711
5987
  EmbeddingProviderRequiredError,
5712
5988
  EntityNotFoundError,
5713
5989
  ExportError,
5990
+ GREMLIN_EDGE_PROJECTION_FIELDS,
5991
+ GREMLIN_VERTEX_PROJECTION_FIELDS,
5714
5992
  GovernanceDeniedError,
5715
5993
  GraphTraversalProviderRequiredError,
5716
5994
  GremlinCompiler,
@@ -5732,7 +6010,10 @@ var NoOpEmbeddingProvider = class {
5732
6010
  TraversalTimeoutError,
5733
6011
  TraversalValidationError,
5734
6012
  TraversalVocabularyError,
6013
+ UnsupportedQueryError,
5735
6014
  VocabularyValidationError,
6015
+ buildEdgeProjectChain,
6016
+ buildVertexProjectChain,
5736
6017
  createSafeSink,
5737
6018
  generateId,
5738
6019
  isValidUuid,