@utaba/deep-memory 0.1.0 → 0.2.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
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var src_exports = {};
22
22
  __export(src_exports, {
23
+ CypherCompiler: () => CypherCompiler,
23
24
  DeepMemory: () => DeepMemory,
24
25
  DeepMemoryError: () => DeepMemoryError,
25
26
  DuplicateEntityError: () => DuplicateEntityError,
@@ -29,6 +30,8 @@ __export(src_exports, {
29
30
  EntityNotFoundError: () => EntityNotFoundError,
30
31
  ExportError: () => ExportError,
31
32
  GovernanceDeniedError: () => GovernanceDeniedError,
33
+ GraphTraversalProviderRequiredError: () => GraphTraversalProviderRequiredError,
34
+ GremlinCompiler: () => GremlinCompiler,
32
35
  ImportError: () => ImportError,
33
36
  InMemorySearchProvider: () => InMemorySearchProvider,
34
37
  InMemoryStorageProvider: () => InMemoryStorageProvider,
@@ -40,6 +43,9 @@ __export(src_exports, {
40
43
  RelationshipConstraintError: () => RelationshipConstraintError,
41
44
  RelationshipNotFoundError: () => RelationshipNotFoundError,
42
45
  RepositoryNotFoundError: () => RepositoryNotFoundError,
46
+ TraversalTimeoutError: () => TraversalTimeoutError,
47
+ TraversalValidationError: () => TraversalValidationError,
48
+ TraversalVocabularyError: () => TraversalVocabularyError,
43
49
  VocabularyValidationError: () => VocabularyValidationError,
44
50
  generateId: () => generateId,
45
51
  isValidUuid: () => isValidUuid,
@@ -240,6 +246,52 @@ var ProviderError = class extends DeepMemoryError {
240
246
  this.name = "ProviderError";
241
247
  }
242
248
  };
249
+ var GraphTraversalProviderRequiredError = class extends DeepMemoryError {
250
+ constructor() {
251
+ super(
252
+ "GRAPH_TRAVERSAL_PROVIDER_REQUIRED",
253
+ "GraphTraversalProvider required: no graph traversal provider is configured",
254
+ `Provide a GraphTraversalProvider in the DeepMemory config to use native graph queries. Structured traversals via traverse() work without a provider using fallback BFS.`
255
+ );
256
+ this.name = "GraphTraversalProviderRequiredError";
257
+ }
258
+ };
259
+ var TraversalValidationError = class extends DeepMemoryError {
260
+ errors;
261
+ constructor(errors) {
262
+ super(
263
+ "TRAVERSAL_VALIDATION_FAILED",
264
+ `Traversal validation failed: ${errors.join("; ")}`,
265
+ `Check the TraversalSpec structure. Each spec needs a start (entityId, entityType, or filter), at least one step, and a returnMode.`
266
+ );
267
+ this.name = "TraversalValidationError";
268
+ this.errors = errors;
269
+ }
270
+ };
271
+ var TraversalVocabularyError = class extends DeepMemoryError {
272
+ unknownTypes;
273
+ constructor(unknownTypes) {
274
+ super(
275
+ "TRAVERSAL_VOCABULARY_ERROR",
276
+ `Traversal references unknown types: ${unknownTypes.join(", ")}`,
277
+ `Use getVocabulary() to see valid entity and relationship types for this repository.`
278
+ );
279
+ this.name = "TraversalVocabularyError";
280
+ this.unknownTypes = unknownTypes;
281
+ }
282
+ };
283
+ var TraversalTimeoutError = class extends DeepMemoryError {
284
+ timeoutMs;
285
+ constructor(timeoutMs) {
286
+ super(
287
+ "TRAVERSAL_TIMEOUT",
288
+ `Traversal timed out after ${timeoutMs}ms`,
289
+ `Try reducing the traversal depth, adding more specific filters, or increasing the timeout on your GraphTraversalProvider.`
290
+ );
291
+ this.name = "TraversalTimeoutError";
292
+ this.timeoutMs = timeoutMs;
293
+ }
294
+ };
243
295
 
244
296
  // src/entities/IdGenerator.ts
245
297
  function slugify(text) {
@@ -280,50 +332,54 @@ var EntityManager = class {
280
332
  this.storage = storage;
281
333
  this.embedding = embedding;
282
334
  }
283
- /** Create a new entity with vocabulary validation, ID generation, provenance, and events */
284
- async create(input) {
285
- const validation = await this.vocabularyEngine.validateEntity(input);
286
- if (!validation.valid) {
287
- const errorMsg = validation.errors.map((e) => e.message).join("; ");
288
- const suggestions = validation.errors.filter((e) => e.suggestion).map((e) => e.suggestion);
289
- await this.eventBus.emit("validation:failed", {
290
- operation: "createEntity",
291
- error: errorMsg,
292
- suggestions
293
- });
294
- throw new VocabularyValidationError(validation.errors);
295
- }
296
- const hookResult = await this.eventBus.emitHook("entity:creating", { input });
297
- if (hookResult.cancelled) {
298
- throw new OperationCancelledError("Entity creation", hookResult.reason ?? "cancelled by hook");
299
- }
300
- const id = input.id ?? generateEntityId();
301
- const slug = await generateUniqueSlug(
302
- input.entityType,
303
- input.label,
304
- async (candidateSlug) => {
305
- const existing = await this.storage.getEntityBySlug(this.repositoryId, candidateSlug);
306
- return existing !== null;
335
+ /** Create one or more entities with vocabulary validation, ID generation, provenance, and events */
336
+ async create(inputs) {
337
+ const results = [];
338
+ for (const input of inputs) {
339
+ const validation = await this.vocabularyEngine.validateEntity(input);
340
+ if (!validation.valid) {
341
+ const errorMsg = validation.errors.map((e) => e.message).join("; ");
342
+ const suggestions = validation.errors.filter((e) => e.suggestion).map((e) => e.suggestion);
343
+ await this.eventBus.emit("validation:failed", {
344
+ operation: "createEntity",
345
+ error: errorMsg,
346
+ suggestions
347
+ });
348
+ throw new VocabularyValidationError(validation.errors);
307
349
  }
308
- );
309
- const provenance = this.provenanceTracker.stampCreate();
310
- const entityEmbedding = await this.generateEmbedding(input.label, input.summary);
311
- const storedEntity = {
312
- id,
313
- slug,
314
- entityType: input.entityType,
315
- label: input.label,
316
- summary: input.summary,
317
- properties: input.properties ?? {},
318
- data: input.data,
319
- dataFormat: input.dataFormat,
320
- provenance,
321
- embedding: entityEmbedding
322
- };
323
- const created = await this.storage.createEntity(this.repositoryId, storedEntity);
324
- const entity = storedToEntity(created);
325
- await this.eventBus.emit("entity:created", { entity });
326
- return entity;
350
+ const hookResult = await this.eventBus.emitHook("entity:creating", { input });
351
+ if (hookResult.cancelled) {
352
+ throw new OperationCancelledError("Entity creation", hookResult.reason ?? "cancelled by hook");
353
+ }
354
+ const id = input.id ?? generateEntityId();
355
+ const slug = await generateUniqueSlug(
356
+ input.entityType,
357
+ input.label,
358
+ async (candidateSlug) => {
359
+ const existing = await this.storage.getEntityBySlug(this.repositoryId, candidateSlug);
360
+ return existing !== null;
361
+ }
362
+ );
363
+ const provenance = this.provenanceTracker.stampCreate();
364
+ const entityEmbedding = await this.generateEmbedding(input.label, input.summary);
365
+ const storedEntity = {
366
+ id,
367
+ slug,
368
+ entityType: input.entityType,
369
+ label: input.label,
370
+ summary: input.summary,
371
+ properties: input.properties ?? {},
372
+ data: input.data,
373
+ dataFormat: input.dataFormat,
374
+ provenance,
375
+ embedding: entityEmbedding
376
+ };
377
+ const created = await this.storage.createEntity(this.repositoryId, storedEntity);
378
+ const entity = storedToEntity(created);
379
+ await this.eventBus.emit("entity:created", { entity });
380
+ results.push(entity);
381
+ }
382
+ return results;
327
383
  }
328
384
  /** Update an existing entity */
329
385
  async update(entityId, updates) {
@@ -655,55 +711,59 @@ var RelationshipManager = class {
655
711
  this.eventBus = eventBus;
656
712
  this.storage = storage;
657
713
  }
658
- /** Create a relationship with vocabulary validation, provenance, and events */
659
- async create(input) {
660
- const sourceEntity = await this.storage.getEntity(this.repositoryId, input.sourceEntityId);
661
- if (!sourceEntity) {
662
- throw new EntityNotFoundError(input.sourceEntityId);
663
- }
664
- const targetEntity = await this.storage.getEntity(this.repositoryId, input.targetEntityId);
665
- if (!targetEntity) {
666
- throw new EntityNotFoundError(input.targetEntityId);
667
- }
668
- const validation = await this.vocabularyEngine.validateRelationship(
669
- input,
670
- sourceEntity.entityType,
671
- targetEntity.entityType
672
- );
673
- if (!validation.valid) {
674
- await this.eventBus.emit("validation:failed", {
675
- operation: "createRelationship",
676
- error: validation.errors.map((e) => e.message).join("; "),
677
- suggestions: validation.errors.filter((e) => e.suggestion).map((e) => e.suggestion)
678
- });
679
- throw new VocabularyValidationError(validation.errors);
680
- }
681
- const hookResult = await this.eventBus.emitHook("relationship:creating", { input });
682
- if (hookResult.cancelled) {
683
- throw new OperationCancelledError(
684
- "Relationship creation",
685
- hookResult.reason ?? "cancelled by hook"
714
+ /** Create one or more relationships with vocabulary validation, provenance, and events */
715
+ async create(inputs) {
716
+ const results = [];
717
+ for (const input of inputs) {
718
+ const sourceEntity = await this.storage.getEntity(this.repositoryId, input.sourceEntityId);
719
+ if (!sourceEntity) {
720
+ throw new EntityNotFoundError(input.sourceEntityId);
721
+ }
722
+ const targetEntity = await this.storage.getEntity(this.repositoryId, input.targetEntityId);
723
+ if (!targetEntity) {
724
+ throw new EntityNotFoundError(input.targetEntityId);
725
+ }
726
+ const validation = await this.vocabularyEngine.validateRelationship(
727
+ input,
728
+ sourceEntity.entityType,
729
+ targetEntity.entityType
686
730
  );
731
+ if (!validation.valid) {
732
+ await this.eventBus.emit("validation:failed", {
733
+ operation: "createRelationship",
734
+ error: validation.errors.map((e) => e.message).join("; "),
735
+ suggestions: validation.errors.filter((e) => e.suggestion).map((e) => e.suggestion)
736
+ });
737
+ throw new VocabularyValidationError(validation.errors);
738
+ }
739
+ const hookResult = await this.eventBus.emitHook("relationship:creating", { input });
740
+ if (hookResult.cancelled) {
741
+ throw new OperationCancelledError(
742
+ "Relationship creation",
743
+ hookResult.reason ?? "cancelled by hook"
744
+ );
745
+ }
746
+ const normalizedType = toScreamingSnakeCase(input.relationshipType);
747
+ const vocabulary = await this.vocabularyEngine.getVocabulary();
748
+ const relType = vocabulary.relationshipTypes.find((rt) => rt.type === normalizedType);
749
+ const bidirectional = relType?.bidirectional ?? false;
750
+ const id = input.id ?? generateRelationshipId();
751
+ const provenance = this.provenanceTracker.stampCreate();
752
+ const storedRelationship = {
753
+ id,
754
+ relationshipType: normalizedType,
755
+ sourceEntityId: input.sourceEntityId,
756
+ targetEntityId: input.targetEntityId,
757
+ properties: input.properties ?? {},
758
+ bidirectional,
759
+ provenance
760
+ };
761
+ const created = await this.storage.createRelationship(this.repositoryId, storedRelationship);
762
+ const relationship = storedToRelationship(created);
763
+ await this.eventBus.emit("relationship:created", { relationship });
764
+ results.push(relationship);
687
765
  }
688
- const normalizedType = toScreamingSnakeCase(input.relationshipType);
689
- const vocabulary = await this.vocabularyEngine.getVocabulary();
690
- const relType = vocabulary.relationshipTypes.find((rt) => rt.type === normalizedType);
691
- const bidirectional = relType?.bidirectional ?? false;
692
- const id = input.id ?? generateRelationshipId();
693
- const provenance = this.provenanceTracker.stampCreate();
694
- const storedRelationship = {
695
- id,
696
- relationshipType: normalizedType,
697
- sourceEntityId: input.sourceEntityId,
698
- targetEntityId: input.targetEntityId,
699
- properties: input.properties ?? {},
700
- bidirectional,
701
- provenance
702
- };
703
- const created = await this.storage.createRelationship(this.repositoryId, storedRelationship);
704
- const relationship = storedToRelationship(created);
705
- await this.eventBus.emit("relationship:created", { relationship });
706
- return relationship;
766
+ return results;
707
767
  }
708
768
  /** Remove a relationship by ID */
709
769
  async remove(relationshipId) {
@@ -784,11 +844,706 @@ function projectEntity(stored, level) {
784
844
  }
785
845
  }
786
846
 
847
+ // src/relationships/TraversalValidator.ts
848
+ var DEFAULT_MAX_STEPS = 6;
849
+ var DEFAULT_MAX_LIMIT = 200;
850
+ var DEFAULT_FALLBACK_MAX_DEPTH = 10;
851
+ function validateTraversalSpec(spec, vocabulary, capabilities) {
852
+ const errors = [];
853
+ if (!spec.start) {
854
+ errors.push("start is required");
855
+ } else {
856
+ const hasEntityId = spec.start.entityId !== void 0 && spec.start.entityId !== "";
857
+ const hasEntityType = spec.start.entityType !== void 0 && spec.start.entityType !== "";
858
+ const hasFilter = spec.start.filter !== void 0 && spec.start.filter.length > 0;
859
+ if (!hasEntityId && !hasEntityType && !hasFilter) {
860
+ errors.push("start must have at least one of entityId, entityType, or filter");
861
+ }
862
+ if (hasEntityType && !hasEntityId && (spec.limit === void 0 || spec.limit === 0)) {
863
+ errors.push("start.entityType without entityId requires limit on the spec to prevent full type scans");
864
+ }
865
+ }
866
+ if (!spec.steps || spec.steps.length === 0) {
867
+ errors.push("steps must contain at least one step");
868
+ }
869
+ const maxSteps = capabilities?.maxTraversalDepth ?? DEFAULT_MAX_STEPS;
870
+ if (spec.steps && spec.steps.length > maxSteps) {
871
+ errors.push(`steps length ${spec.steps.length} exceeds maximum ${maxSteps}`);
872
+ }
873
+ if (spec.steps) {
874
+ for (let i = 0; i < spec.steps.length; i++) {
875
+ const step = spec.steps[i];
876
+ if (!step.direction || !["out", "in", "both"].includes(step.direction)) {
877
+ errors.push(`steps[${i}].direction must be 'out', 'in', or 'both'`);
878
+ }
879
+ if (step.repeat) {
880
+ if (step.repeat.maxDepth === void 0 || step.repeat.maxDepth <= 0) {
881
+ errors.push(`steps[${i}].repeat.maxDepth must be a positive number`);
882
+ }
883
+ }
884
+ }
885
+ const maxProviderDepth = capabilities?.maxTraversalDepth ?? DEFAULT_FALLBACK_MAX_DEPTH;
886
+ let totalDepth = 0;
887
+ for (const step of spec.steps) {
888
+ totalDepth += step.repeat ? step.repeat.maxDepth : 1;
889
+ }
890
+ if (totalDepth > maxProviderDepth) {
891
+ errors.push(`total potential depth ${totalDepth} exceeds provider maximum ${maxProviderDepth}`);
892
+ }
893
+ }
894
+ if (spec.returnMode && !["terminal", "path", "all"].includes(spec.returnMode)) {
895
+ errors.push(`returnMode must be 'terminal', 'path', or 'all'`);
896
+ }
897
+ if (spec.limit !== void 0) {
898
+ if (spec.limit < 1 || spec.limit > DEFAULT_MAX_LIMIT) {
899
+ errors.push(`limit must be between 1 and ${DEFAULT_MAX_LIMIT}`);
900
+ }
901
+ }
902
+ if (spec.offset !== void 0 && spec.offset < 0) {
903
+ errors.push("offset must be >= 0");
904
+ }
905
+ if (vocabulary && spec.steps) {
906
+ const entityTypeSet = new Set(vocabulary.entityTypes.map((et) => et.type));
907
+ const relationshipTypeSet = new Set(vocabulary.relationshipTypes.map((rt) => rt.type));
908
+ const unknownTypes = [];
909
+ if (spec.start?.entityType && !entityTypeSet.has(spec.start.entityType)) {
910
+ unknownTypes.push(`entity type "${spec.start.entityType}"`);
911
+ }
912
+ for (let i = 0; i < spec.steps.length; i++) {
913
+ const step = spec.steps[i];
914
+ if (step.relationshipTypes) {
915
+ for (const rt of step.relationshipTypes) {
916
+ if (!relationshipTypeSet.has(rt)) {
917
+ unknownTypes.push(`relationship type "${rt}" in steps[${i}]`);
918
+ }
919
+ }
920
+ }
921
+ if (step.entityTypes) {
922
+ for (const et of step.entityTypes) {
923
+ if (!entityTypeSet.has(et)) {
924
+ unknownTypes.push(`entity type "${et}" in steps[${i}]`);
925
+ }
926
+ }
927
+ }
928
+ }
929
+ if (unknownTypes.length > 0) {
930
+ errors.push(`Unknown vocabulary types: ${unknownTypes.join(", ")}`);
931
+ }
932
+ }
933
+ return {
934
+ valid: errors.length === 0,
935
+ errors
936
+ };
937
+ }
938
+
939
+ // src/relationships/compilers/GremlinCompiler.ts
940
+ var DEFAULT_ESTIMATED_FANOUT_PER_HOP = 10;
941
+ var GremlinCompiler = class {
942
+ language = "gremlin";
943
+ compile(spec, _vocabulary) {
944
+ const parts = [];
945
+ const params = {};
946
+ let paramIndex = 0;
947
+ let estimatedFanOut = 1;
948
+ const nextParam = (value) => {
949
+ const name = `p${paramIndex++}`;
950
+ params[name] = value;
951
+ return name;
952
+ };
953
+ parts.push("g.V()");
954
+ if (spec.start.entityId) {
955
+ const p = nextParam(spec.start.entityId);
956
+ parts.push(`.has('id', ${p})`);
957
+ } else if (spec.start.entityType) {
958
+ const p = nextParam(spec.start.entityType);
959
+ parts.push(`.has('entityType', ${p})`);
960
+ estimatedFanOut *= DEFAULT_ESTIMATED_FANOUT_PER_HOP;
961
+ }
962
+ if (spec.start.filter) {
963
+ for (const f of spec.start.filter) {
964
+ parts.push(compilePropertyFilter(f, nextParam));
965
+ }
966
+ }
967
+ for (const step of spec.steps) {
968
+ if (step.repeat) {
969
+ parts.push(compileRepeatStep(step, nextParam));
970
+ estimatedFanOut *= step.repeat.maxDepth * DEFAULT_ESTIMATED_FANOUT_PER_HOP;
971
+ } else if (step.relationshipFilter && step.relationshipFilter.length > 0) {
972
+ parts.push(compileEdgeStep(step, nextParam));
973
+ estimatedFanOut *= DEFAULT_ESTIMATED_FANOUT_PER_HOP;
974
+ } else {
975
+ parts.push(compileSimpleStep(step, nextParam));
976
+ estimatedFanOut *= DEFAULT_ESTIMATED_FANOUT_PER_HOP;
977
+ }
978
+ if (step.entityTypes && step.entityTypes.length > 0) {
979
+ const typeParams = step.entityTypes.map((t) => nextParam(t));
980
+ parts.push(`.has('entityType', within(${typeParams.join(", ")}))`);
981
+ }
982
+ if (step.entityFilter) {
983
+ for (const f of step.entityFilter) {
984
+ parts.push(compilePropertyFilter(f, nextParam));
985
+ }
986
+ }
987
+ }
988
+ if (spec.returnMode === "all") {
989
+ }
990
+ if (spec.dedup !== false) {
991
+ parts.push(".dedup()");
992
+ }
993
+ const limit = spec.limit ?? 50;
994
+ const offset = spec.offset ?? 0;
995
+ const pOffset = nextParam(offset);
996
+ const pEnd = nextParam(offset + limit);
997
+ parts.push(`.range(${pOffset}, ${pEnd})`);
998
+ params["_limit"] = limit;
999
+ params["_offset"] = offset;
1000
+ if (spec.returnMode === "path") {
1001
+ parts.push(".path()");
1002
+ }
1003
+ if (spec.returnMode !== "path") {
1004
+ parts.push(".valueMap(true)");
1005
+ }
1006
+ return {
1007
+ query: parts.join(""),
1008
+ params,
1009
+ estimatedFanOut: Math.min(estimatedFanOut, 1e4)
1010
+ };
1011
+ }
1012
+ };
1013
+ function compileSimpleStep(step, nextParam) {
1014
+ const types = step.relationshipTypes;
1015
+ const typeArgs = types ? types.map((t) => nextParam(t)).join(", ") : "";
1016
+ switch (step.direction) {
1017
+ case "out":
1018
+ return types ? `.out(${typeArgs})` : ".out()";
1019
+ case "in":
1020
+ return types ? `.in(${typeArgs})` : ".in()";
1021
+ case "both":
1022
+ return types ? `.both(${typeArgs})` : ".both()";
1023
+ }
1024
+ }
1025
+ function compileEdgeStep(step, nextParam) {
1026
+ const parts = [];
1027
+ const types = step.relationshipTypes;
1028
+ const typeArgs = types ? types.map((t) => nextParam(t)).join(", ") : "";
1029
+ switch (step.direction) {
1030
+ case "out":
1031
+ parts.push(types ? `.outE(${typeArgs})` : ".outE()");
1032
+ break;
1033
+ case "in":
1034
+ parts.push(types ? `.inE(${typeArgs})` : ".inE()");
1035
+ break;
1036
+ case "both":
1037
+ parts.push(types ? `.bothE(${typeArgs})` : ".bothE()");
1038
+ break;
1039
+ }
1040
+ if (step.relationshipFilter) {
1041
+ for (const f of step.relationshipFilter) {
1042
+ parts.push(compilePropertyFilter(f, nextParam));
1043
+ }
1044
+ }
1045
+ switch (step.direction) {
1046
+ case "out":
1047
+ parts.push(".inV()");
1048
+ break;
1049
+ case "in":
1050
+ parts.push(".outV()");
1051
+ break;
1052
+ case "both":
1053
+ parts.push(".otherV()");
1054
+ break;
1055
+ }
1056
+ return parts.join("");
1057
+ }
1058
+ function compileRepeatStep(step, nextParam) {
1059
+ const parts = [];
1060
+ const innerStep = step.relationshipFilter?.length ? compileEdgeStep({ ...step, repeat: void 0 }, nextParam) : compileSimpleStep(step, nextParam);
1061
+ if (step.repeat?.emitIntermediates !== false) {
1062
+ parts.push(".emit()");
1063
+ }
1064
+ parts.push(`.repeat(${innerStep.startsWith(".") ? `__${innerStep}` : innerStep})`);
1065
+ if (step.repeat?.until && step.repeat.until.length > 0) {
1066
+ const untilParts = step.repeat.until.map((f) => compilePropertyFilter(f, nextParam));
1067
+ parts.push(`.until(${untilParts.join("")})`);
1068
+ }
1069
+ if (step.repeat?.maxDepth) {
1070
+ const p = nextParam(step.repeat.maxDepth);
1071
+ parts.push(`.times(${p})`);
1072
+ }
1073
+ if (step.repeat?.emitIntermediates === false) {
1074
+ parts.push(".emit()");
1075
+ }
1076
+ return parts.join("");
1077
+ }
1078
+ function compilePropertyFilter(filter, nextParam) {
1079
+ const key = nextParam(filter.key);
1080
+ switch (filter.operator) {
1081
+ case "eq": {
1082
+ const val = nextParam(filter.value);
1083
+ return `.has(${key}, ${val})`;
1084
+ }
1085
+ case "neq": {
1086
+ const val = nextParam(filter.value);
1087
+ return `.has(${key}, neq(${val}))`;
1088
+ }
1089
+ case "gt": {
1090
+ const val = nextParam(filter.value);
1091
+ return `.has(${key}, gt(${val}))`;
1092
+ }
1093
+ case "gte": {
1094
+ const val = nextParam(filter.value);
1095
+ return `.has(${key}, gte(${val}))`;
1096
+ }
1097
+ case "lt": {
1098
+ const val = nextParam(filter.value);
1099
+ return `.has(${key}, lt(${val}))`;
1100
+ }
1101
+ case "lte": {
1102
+ const val = nextParam(filter.value);
1103
+ return `.has(${key}, lte(${val}))`;
1104
+ }
1105
+ case "contains": {
1106
+ const val = nextParam(filter.value);
1107
+ return `.has(${key}, containing(${val}))`;
1108
+ }
1109
+ case "isNull":
1110
+ return `.hasNot(${key})`;
1111
+ case "isNotNull":
1112
+ return `.has(${key})`;
1113
+ }
1114
+ }
1115
+
1116
+ // src/relationships/compilers/CypherCompiler.ts
1117
+ var DEFAULT_ESTIMATED_FANOUT_PER_HOP2 = 10;
1118
+ var CypherCompiler = class {
1119
+ language = "cypher";
1120
+ compile(spec, _vocabulary) {
1121
+ const params = {};
1122
+ let paramIndex = 0;
1123
+ let estimatedFanOut = 1;
1124
+ const whereClauses = [];
1125
+ let nodeIndex = 0;
1126
+ const nextParam = (value) => {
1127
+ const name = `p${paramIndex++}`;
1128
+ params[name] = value;
1129
+ return `$${name}`;
1130
+ };
1131
+ const startNode = `n${nodeIndex++}`;
1132
+ let matchParts = [];
1133
+ if (spec.start.entityId) {
1134
+ matchParts.push(`(${startNode})`);
1135
+ whereClauses.push(`${startNode}.id = ${nextParam(spec.start.entityId)}`);
1136
+ } else if (spec.start.entityType) {
1137
+ matchParts.push(`(${startNode})`);
1138
+ whereClauses.push(`${startNode}.entityType = ${nextParam(spec.start.entityType)}`);
1139
+ estimatedFanOut *= DEFAULT_ESTIMATED_FANOUT_PER_HOP2;
1140
+ } else {
1141
+ matchParts.push(`(${startNode})`);
1142
+ }
1143
+ if (spec.start.filter) {
1144
+ for (const f of spec.start.filter) {
1145
+ whereClauses.push(compilePropertyFilterCypher(startNode, f, nextParam));
1146
+ }
1147
+ }
1148
+ let lastNode = startNode;
1149
+ for (let i = 0; i < spec.steps.length; i++) {
1150
+ const step = spec.steps[i];
1151
+ const targetNode = `n${nodeIndex++}`;
1152
+ const relAlias = `r${i}`;
1153
+ const relTypes = step.relationshipTypes?.length ? step.relationshipTypes.join("|") : "";
1154
+ const relTypePattern = relTypes ? `:${relTypes}` : "";
1155
+ const depthPattern = step.repeat ? `*1..${step.repeat.maxDepth}` : "";
1156
+ let pattern;
1157
+ switch (step.direction) {
1158
+ case "out":
1159
+ pattern = `-[${relAlias}${relTypePattern}${depthPattern}]->(${targetNode})`;
1160
+ break;
1161
+ case "in":
1162
+ pattern = `<-[${relAlias}${relTypePattern}${depthPattern}]-(${targetNode})`;
1163
+ break;
1164
+ case "both":
1165
+ pattern = `-[${relAlias}${relTypePattern}${depthPattern}]-(${targetNode})`;
1166
+ break;
1167
+ }
1168
+ matchParts.push(pattern);
1169
+ estimatedFanOut *= step.repeat ? step.repeat.maxDepth * DEFAULT_ESTIMATED_FANOUT_PER_HOP2 : DEFAULT_ESTIMATED_FANOUT_PER_HOP2;
1170
+ if (step.entityTypes && step.entityTypes.length > 0) {
1171
+ const typeParam = nextParam(step.entityTypes);
1172
+ whereClauses.push(`${targetNode}.entityType IN ${typeParam}`);
1173
+ }
1174
+ if (step.entityFilter) {
1175
+ for (const f of step.entityFilter) {
1176
+ whereClauses.push(compilePropertyFilterCypher(targetNode, f, nextParam));
1177
+ }
1178
+ }
1179
+ if (step.relationshipFilter) {
1180
+ for (const f of step.relationshipFilter) {
1181
+ whereClauses.push(compilePropertyFilterCypher(relAlias, f, nextParam));
1182
+ }
1183
+ }
1184
+ lastNode = targetNode;
1185
+ }
1186
+ const matchClause = `MATCH ${matchParts[0]}${matchParts.slice(1).join("")}`;
1187
+ const whereClause = whereClauses.length > 0 ? `
1188
+ WHERE ${whereClauses.join(" AND ")}` : "";
1189
+ let returnClause;
1190
+ if (spec.returnMode === "path") {
1191
+ const allNodes = Array.from({ length: nodeIndex }, (_, i) => `n${i}`);
1192
+ const allRels = spec.steps.map((_, i) => `r${i}`);
1193
+ returnClause = `RETURN ${[...allNodes, ...allRels].join(", ")}`;
1194
+ } else if (spec.returnMode === "all") {
1195
+ const allNodes = Array.from({ length: nodeIndex }, (_, i) => `n${i}`);
1196
+ returnClause = spec.dedup !== false ? `RETURN DISTINCT ${allNodes.join(", ")}` : `RETURN ${allNodes.join(", ")}`;
1197
+ } else {
1198
+ returnClause = spec.dedup !== false ? `RETURN DISTINCT ${lastNode}` : `RETURN ${lastNode}`;
1199
+ }
1200
+ const offset = spec.offset ?? 0;
1201
+ const limit = spec.limit ?? 50;
1202
+ const skipClause = offset > 0 ? `
1203
+ SKIP ${nextParam(offset)}` : "";
1204
+ const limitClause = `
1205
+ LIMIT ${nextParam(limit)}`;
1206
+ params["_limit"] = limit;
1207
+ params["_offset"] = offset;
1208
+ const query = `${matchClause}${whereClause}
1209
+ ${returnClause}${skipClause}${limitClause}`;
1210
+ return {
1211
+ query,
1212
+ params,
1213
+ estimatedFanOut: Math.min(estimatedFanOut, 1e4)
1214
+ };
1215
+ }
1216
+ };
1217
+ function compilePropertyFilterCypher(nodeAlias, filter, nextParam) {
1218
+ const prop = `${nodeAlias}.${filter.key}`;
1219
+ switch (filter.operator) {
1220
+ case "eq":
1221
+ return `${prop} = ${nextParam(filter.value)}`;
1222
+ case "neq":
1223
+ return `${prop} <> ${nextParam(filter.value)}`;
1224
+ case "gt":
1225
+ return `${prop} > ${nextParam(filter.value)}`;
1226
+ case "gte":
1227
+ return `${prop} >= ${nextParam(filter.value)}`;
1228
+ case "lt":
1229
+ return `${prop} < ${nextParam(filter.value)}`;
1230
+ case "lte":
1231
+ return `${prop} <= ${nextParam(filter.value)}`;
1232
+ case "contains":
1233
+ return `${prop} CONTAINS ${nextParam(filter.value)}`;
1234
+ case "isNull":
1235
+ return `${prop} IS NULL`;
1236
+ case "isNotNull":
1237
+ return `${prop} IS NOT NULL`;
1238
+ }
1239
+ }
1240
+
1241
+ // src/relationships/PropertyFilterMatcher.ts
1242
+ function matchesPropertyFilters(properties, filters) {
1243
+ return filters.every((filter) => matchesSingleFilter(properties, filter));
1244
+ }
1245
+ function matchesSingleFilter(properties, filter) {
1246
+ const value = properties[filter.key];
1247
+ switch (filter.operator) {
1248
+ case "isNull":
1249
+ return value === null || value === void 0;
1250
+ case "isNotNull":
1251
+ return value !== null && value !== void 0;
1252
+ case "eq":
1253
+ return value === filter.value;
1254
+ case "neq":
1255
+ return value !== filter.value;
1256
+ case "gt":
1257
+ return typeof value === "number" && typeof filter.value === "number" ? value > filter.value : typeof value === "string" && typeof filter.value === "string" ? value > filter.value : false;
1258
+ case "lt":
1259
+ return typeof value === "number" && typeof filter.value === "number" ? value < filter.value : typeof value === "string" && typeof filter.value === "string" ? value < filter.value : false;
1260
+ case "gte":
1261
+ return typeof value === "number" && typeof filter.value === "number" ? value >= filter.value : typeof value === "string" && typeof filter.value === "string" ? value >= filter.value : false;
1262
+ case "lte":
1263
+ return typeof value === "number" && typeof filter.value === "number" ? value <= filter.value : typeof value === "string" && typeof filter.value === "string" ? value <= filter.value : false;
1264
+ case "contains":
1265
+ return typeof value === "string" && typeof filter.value === "string" ? value.toLowerCase().includes(filter.value.toLowerCase()) : false;
1266
+ default:
1267
+ return false;
1268
+ }
1269
+ }
1270
+
1271
+ // src/relationships/FallbackTraversalExecutor.ts
1272
+ var MAX_STORAGE_CALLS = 500;
1273
+ var REL_FETCH_LIMIT = 1e3;
1274
+ async function executeFallbackTraversal(repositoryId, storage, spec) {
1275
+ const startTime = Date.now();
1276
+ let storageCalls = 0;
1277
+ const checkCallLimit = () => {
1278
+ if (storageCalls >= MAX_STORAGE_CALLS) {
1279
+ throw new Error(`Fallback traversal exceeded ${MAX_STORAGE_CALLS} storage calls. Consider using a GraphTraversalProvider for large traversals.`);
1280
+ }
1281
+ };
1282
+ let frontier = [];
1283
+ if (spec.start.entityId) {
1284
+ const entity = await resolveEntity(repositoryId, storage, spec.start.entityId);
1285
+ storageCalls++;
1286
+ if (entity) {
1287
+ frontier.push({ entity, entityPath: [entity.id], relationshipPath: [] });
1288
+ } else {
1289
+ throw new EntityNotFoundError(spec.start.entityId);
1290
+ }
1291
+ } else if (spec.start.entityType) {
1292
+ const result = await storage.findEntities(repositoryId, {
1293
+ entityTypes: [spec.start.entityType],
1294
+ limit: spec.limit ?? 50,
1295
+ offset: 0
1296
+ });
1297
+ storageCalls++;
1298
+ frontier = result.items.map((e) => ({
1299
+ entity: e,
1300
+ entityPath: [e.id],
1301
+ relationshipPath: []
1302
+ }));
1303
+ } else if (spec.start.filter && spec.start.filter.length > 0) {
1304
+ const result = await storage.findEntities(repositoryId, {
1305
+ limit: spec.limit ?? 50,
1306
+ offset: 0
1307
+ });
1308
+ storageCalls++;
1309
+ frontier = result.items.filter((e) => matchesPropertyFilters(e.properties, spec.start.filter)).map((e) => ({
1310
+ entity: e,
1311
+ entityPath: [e.id],
1312
+ relationshipPath: []
1313
+ }));
1314
+ }
1315
+ if (spec.start.filter && spec.start.filter.length > 0 && (spec.start.entityId || spec.start.entityType)) {
1316
+ frontier = frontier.filter(
1317
+ (entry) => matchesPropertyFilters(entry.entity.properties, spec.start.filter)
1318
+ );
1319
+ }
1320
+ const allCollected = spec.returnMode === "all" ? [...frontier] : [];
1321
+ for (const step of spec.steps) {
1322
+ checkCallLimit();
1323
+ if (step.repeat) {
1324
+ const result = await executeRepeatStep(
1325
+ repositoryId,
1326
+ storage,
1327
+ frontier,
1328
+ step,
1329
+ () => {
1330
+ storageCalls++;
1331
+ checkCallLimit();
1332
+ }
1333
+ );
1334
+ frontier = result.terminal;
1335
+ if (spec.returnMode === "all") {
1336
+ allCollected.push(...result.intermediates);
1337
+ }
1338
+ } else {
1339
+ frontier = await executeSingleStep(
1340
+ repositoryId,
1341
+ storage,
1342
+ frontier,
1343
+ step,
1344
+ () => {
1345
+ storageCalls++;
1346
+ checkCallLimit();
1347
+ }
1348
+ );
1349
+ if (spec.returnMode === "all") {
1350
+ allCollected.push(...frontier);
1351
+ }
1352
+ }
1353
+ }
1354
+ const detailLevel = spec.detailLevel ?? "summary";
1355
+ const dedup = spec.dedup !== false;
1356
+ const limit = spec.limit ?? 50;
1357
+ const offset = spec.offset ?? 0;
1358
+ let resultEntries;
1359
+ if (spec.returnMode === "all") {
1360
+ resultEntries = allCollected;
1361
+ } else {
1362
+ resultEntries = frontier;
1363
+ }
1364
+ if (dedup) {
1365
+ const seen = /* @__PURE__ */ new Set();
1366
+ resultEntries = resultEntries.filter((entry) => {
1367
+ if (seen.has(entry.entity.id)) return false;
1368
+ seen.add(entry.entity.id);
1369
+ return true;
1370
+ });
1371
+ }
1372
+ const total = resultEntries.length;
1373
+ const paged = resultEntries.slice(offset, offset + limit);
1374
+ const entities = paged.map((entry) => projectEntity(entry.entity, detailLevel));
1375
+ let relationships;
1376
+ let paths;
1377
+ if (spec.returnMode === "path" || spec.returnMode === "all") {
1378
+ const relMap = /* @__PURE__ */ new Map();
1379
+ for (const entry of paged) {
1380
+ for (const rel of entry.relationshipPath) {
1381
+ if (!relMap.has(rel.id)) {
1382
+ relMap.set(rel.id, {
1383
+ id: rel.id,
1384
+ type: rel.relationshipType,
1385
+ sourceEntityId: rel.sourceEntityId,
1386
+ targetEntityId: rel.targetEntityId,
1387
+ direction: "outbound",
1388
+ properties: rel.properties
1389
+ });
1390
+ }
1391
+ }
1392
+ }
1393
+ relationships = Array.from(relMap.values());
1394
+ }
1395
+ if (spec.returnMode === "path") {
1396
+ paths = paged.map((entry) => ({
1397
+ length: entry.entityPath.length - 1,
1398
+ entities: [projectEntity(entry.entity, detailLevel)],
1399
+ relationships: entry.relationshipPath.map((rel) => ({
1400
+ id: rel.id,
1401
+ type: rel.relationshipType,
1402
+ sourceEntityId: rel.sourceEntityId,
1403
+ targetEntityId: rel.targetEntityId,
1404
+ direction: "outbound",
1405
+ properties: rel.properties
1406
+ }))
1407
+ }));
1408
+ }
1409
+ const executionTimeMs = Date.now() - startTime;
1410
+ return {
1411
+ entities,
1412
+ relationships,
1413
+ paths,
1414
+ total,
1415
+ returned: paged.length,
1416
+ hasMore: offset + limit < total,
1417
+ queryMetadata: {
1418
+ executionTimeMs,
1419
+ appliedLimits: {
1420
+ maxResults: limit
1421
+ },
1422
+ truncated: total > limit + offset,
1423
+ truncationReason: total > limit + offset ? "result_limit" : void 0
1424
+ }
1425
+ };
1426
+ }
1427
+ async function executeSingleStep(repositoryId, storage, frontier, step, onStorageCall) {
1428
+ const direction = mapDirection(step.direction);
1429
+ const pendingEdges = [];
1430
+ for (const entry of frontier) {
1431
+ const rels = await storage.getEntityRelationships(repositoryId, entry.entity.id, {
1432
+ direction,
1433
+ relationshipTypes: step.relationshipTypes,
1434
+ limit: REL_FETCH_LIMIT
1435
+ });
1436
+ onStorageCall();
1437
+ for (const rel of rels.items) {
1438
+ if (step.relationshipFilter && step.relationshipFilter.length > 0) {
1439
+ if (!matchesPropertyFilters(rel.properties, step.relationshipFilter)) {
1440
+ continue;
1441
+ }
1442
+ }
1443
+ const targetId = getTargetId(rel, entry.entity.id);
1444
+ pendingEdges.push({ entry, rel, targetId });
1445
+ }
1446
+ }
1447
+ if (pendingEdges.length === 0) return [];
1448
+ const uniqueTargetIds = [...new Set(pendingEdges.map((e) => e.targetId))];
1449
+ const entityMap = await storage.getEntities(repositoryId, uniqueTargetIds);
1450
+ onStorageCall();
1451
+ const nextFrontier = [];
1452
+ for (const { entry, rel, targetId } of pendingEdges) {
1453
+ const targetEntity = entityMap.get(targetId);
1454
+ if (!targetEntity) continue;
1455
+ if (step.entityTypes && step.entityTypes.length > 0) {
1456
+ if (!step.entityTypes.includes(targetEntity.entityType)) continue;
1457
+ }
1458
+ if (step.entityFilter && step.entityFilter.length > 0) {
1459
+ if (!matchesPropertyFilters(targetEntity.properties, step.entityFilter)) continue;
1460
+ }
1461
+ nextFrontier.push({
1462
+ entity: targetEntity,
1463
+ entityPath: [...entry.entityPath, targetEntity.id],
1464
+ relationshipPath: [...entry.relationshipPath, rel]
1465
+ });
1466
+ }
1467
+ return nextFrontier;
1468
+ }
1469
+ async function executeRepeatStep(repositoryId, storage, frontier, step, onStorageCall) {
1470
+ const maxDepth = step.repeat?.maxDepth ?? 1;
1471
+ const emitIntermediates = step.repeat?.emitIntermediates !== false;
1472
+ const untilFilters = step.repeat?.until;
1473
+ let current = frontier;
1474
+ const allIntermediates = [];
1475
+ const visited = /* @__PURE__ */ new Set();
1476
+ for (const entry of frontier) {
1477
+ visited.add(entry.entity.id);
1478
+ }
1479
+ for (let depth = 0; depth < maxDepth; depth++) {
1480
+ const next = await executeSingleStep(repositoryId, storage, current, step, onStorageCall);
1481
+ const unvisited = next.filter((entry) => {
1482
+ if (visited.has(entry.entity.id)) return false;
1483
+ visited.add(entry.entity.id);
1484
+ return true;
1485
+ });
1486
+ if (unvisited.length === 0) break;
1487
+ if (untilFilters && untilFilters.length > 0) {
1488
+ const matching = [];
1489
+ const continuing = [];
1490
+ for (const entry of unvisited) {
1491
+ if (matchesPropertyFilters(entry.entity.properties, untilFilters)) {
1492
+ matching.push(entry);
1493
+ } else {
1494
+ continuing.push(entry);
1495
+ }
1496
+ }
1497
+ if (emitIntermediates) {
1498
+ allIntermediates.push(...continuing);
1499
+ }
1500
+ allIntermediates.push(...matching);
1501
+ if (matching.length > 0 && continuing.length === 0) {
1502
+ current = matching;
1503
+ break;
1504
+ }
1505
+ current = continuing;
1506
+ } else {
1507
+ if (emitIntermediates) {
1508
+ allIntermediates.push(...unvisited);
1509
+ }
1510
+ current = unvisited;
1511
+ }
1512
+ }
1513
+ return {
1514
+ terminal: current,
1515
+ intermediates: allIntermediates
1516
+ };
1517
+ }
1518
+ async function resolveEntity(repositoryId, storage, idOrSlug) {
1519
+ const byId = await storage.getEntity(repositoryId, idOrSlug);
1520
+ if (byId) return byId;
1521
+ return storage.getEntityBySlug(repositoryId, idOrSlug);
1522
+ }
1523
+ function mapDirection(direction) {
1524
+ switch (direction) {
1525
+ case "out":
1526
+ return "outbound";
1527
+ case "in":
1528
+ return "inbound";
1529
+ case "both":
1530
+ return "both";
1531
+ }
1532
+ }
1533
+ function getTargetId(rel, currentEntityId) {
1534
+ if (rel.sourceEntityId === currentEntityId) {
1535
+ return rel.targetEntityId;
1536
+ }
1537
+ return rel.sourceEntityId;
1538
+ }
1539
+
787
1540
  // src/relationships/GraphTraversal.ts
788
1541
  var GraphTraversal = class {
789
- constructor(repositoryId, storage) {
1542
+ constructor(repositoryId, storage, graphTraversalProvider, vocabularyEngine) {
790
1543
  this.repositoryId = repositoryId;
791
1544
  this.storage = storage;
1545
+ this.graphTraversalProvider = graphTraversalProvider;
1546
+ this.vocabularyEngine = vocabularyEngine;
792
1547
  }
793
1548
  /** BFS neighbourhood exploration from a centre entity */
794
1549
  async exploreNeighbourhood(entityId, options) {
@@ -920,6 +1675,51 @@ var GraphTraversal = class {
920
1675
  }))
921
1676
  };
922
1677
  }
1678
+ /**
1679
+ * Execute a structured traversal.
1680
+ * Validates the spec, compiles to native query if a provider exists,
1681
+ * falls back to application-level BFS otherwise.
1682
+ */
1683
+ async traverse(spec) {
1684
+ const capabilities = this.graphTraversalProvider?.getCapabilities();
1685
+ const vocabulary = this.vocabularyEngine ? await this.vocabularyEngine.getVocabulary() : void 0;
1686
+ const validation = validateTraversalSpec(spec, vocabulary, capabilities);
1687
+ if (!validation.valid) {
1688
+ const vocabErrors = validation.errors.filter((e) => e.startsWith("Unknown vocabulary types:"));
1689
+ const structErrors = validation.errors.filter((e) => !e.startsWith("Unknown vocabulary types:"));
1690
+ if (vocabErrors.length > 0) {
1691
+ const unknownTypes = vocabErrors.join("; ").replace(/Unknown vocabulary types: /g, "").split(", ").map((t) => t.trim());
1692
+ throw new TraversalVocabularyError(unknownTypes);
1693
+ }
1694
+ throw new TraversalValidationError(structErrors.length > 0 ? structErrors : validation.errors);
1695
+ }
1696
+ if (this.graphTraversalProvider && vocabulary) {
1697
+ const language = capabilities.nativeQueryLanguage;
1698
+ let compiledQuery;
1699
+ if (language === "gremlin") {
1700
+ const compiler = new GremlinCompiler();
1701
+ const compiled = compiler.compile(spec, vocabulary);
1702
+ compiledQuery = compiled.query;
1703
+ } else if (language === "cypher") {
1704
+ const compiler = new CypherCompiler();
1705
+ const compiled = compiler.compile(spec, vocabulary);
1706
+ compiledQuery = compiled.query;
1707
+ }
1708
+ return this.graphTraversalProvider.traverse(this.repositoryId, spec, compiledQuery);
1709
+ }
1710
+ return executeFallbackTraversal(this.repositoryId, this.storage, spec);
1711
+ }
1712
+ /**
1713
+ * Execute a native graph query.
1714
+ * Pass-through to GraphTraversalProvider.executeNativeQuery().
1715
+ * Throws GraphTraversalProviderRequiredError if no provider registered.
1716
+ */
1717
+ async executeNativeQuery(query, params) {
1718
+ if (!this.graphTraversalProvider) {
1719
+ throw new GraphTraversalProviderRequiredError();
1720
+ }
1721
+ return this.graphTraversalProvider.executeNativeQuery(this.repositoryId, query, params);
1722
+ }
923
1723
  };
924
1724
 
925
1725
  // src/search/SearchOrchestrator.ts
@@ -1128,7 +1928,9 @@ var MemoryRepository = class {
1128
1928
  );
1129
1929
  this.graphTraversal = new GraphTraversal(
1130
1930
  config.repositoryId,
1131
- config.storage
1931
+ config.storage,
1932
+ config.graphTraversal,
1933
+ config.vocabularyEngine
1132
1934
  );
1133
1935
  this.searchOrchestrator = new SearchOrchestrator({
1134
1936
  repositoryId: config.repositoryId,
@@ -1152,19 +1954,21 @@ var MemoryRepository = class {
1152
1954
  return this.proposeVocabularyChange(proposal);
1153
1955
  }
1154
1956
  // ─── Entities ──────────────────────────────────────────────────────
1155
- async createEntity(input) {
1156
- const entity = await this.entityManager.create(input);
1957
+ async createEntities(inputs) {
1958
+ const entities = await this.entityManager.create(inputs);
1157
1959
  if (this.search) {
1158
- await this.search.indexEntity(this.repositoryId, {
1159
- entityId: entity.id,
1160
- entityType: entity.entityType,
1161
- label: entity.label,
1162
- summary: entity.summary,
1163
- properties: entity.properties,
1164
- data: entity.data
1165
- });
1960
+ for (const entity of entities) {
1961
+ await this.search.indexEntity(this.repositoryId, {
1962
+ entityId: entity.id,
1963
+ entityType: entity.entityType,
1964
+ label: entity.label,
1965
+ summary: entity.summary,
1966
+ properties: entity.properties,
1967
+ data: entity.data
1968
+ });
1969
+ }
1166
1970
  }
1167
- return entity;
1971
+ return entities;
1168
1972
  }
1169
1973
  async updateEntity(entityId, updates) {
1170
1974
  const entity = await this.entityManager.update(entityId, updates);
@@ -1251,8 +2055,8 @@ var MemoryRepository = class {
1251
2055
  }
1252
2056
  }
1253
2057
  // ─── Relationships ─────────────────────────────────────────────────
1254
- async createRelationship(input) {
1255
- return this.relationshipManager.create(input);
2058
+ async createRelationships(inputs) {
2059
+ return this.relationshipManager.create(inputs);
1256
2060
  }
1257
2061
  async removeRelationship(relationshipId) {
1258
2062
  return this.relationshipManager.remove(relationshipId);
@@ -1346,6 +2150,12 @@ var MemoryRepository = class {
1346
2150
  async findPaths(sourceId, targetId, options) {
1347
2151
  return this.graphTraversal.findPaths(sourceId, targetId, options);
1348
2152
  }
2153
+ async traverse(spec) {
2154
+ return this.graphTraversal.traverse(spec);
2155
+ }
2156
+ async executeNativeQuery(query, params) {
2157
+ return this.graphTraversal.executeNativeQuery(query, params);
2158
+ }
1349
2159
  // ─── Search ────────────────────────────────────────────────────────
1350
2160
  async searchByConcept(query, options) {
1351
2161
  return this.searchOrchestrator.searchByConcept(query, options);
@@ -2430,13 +3240,15 @@ var EventBus = class {
2430
3240
  };
2431
3241
 
2432
3242
  // src/portability/RepositoryExporter.ts
2433
- var LIBRARY_VERSION = "0.1.0";
3243
+ var LIBRARY_VERSION = true ? "0.2.0" : "0.1.0";
2434
3244
  var RepositoryExporter = class {
2435
3245
  storage;
2436
3246
  provenance;
3247
+ legal;
2437
3248
  constructor(config) {
2438
3249
  this.storage = config.storage;
2439
3250
  this.provenance = config.provenance;
3251
+ this.legal = config.legal;
2440
3252
  }
2441
3253
  /**
2442
3254
  * Stream a repository export as an async generator.
@@ -2470,6 +3282,9 @@ var RepositoryExporter = class {
2470
3282
  relationshipTypeBreakdown: stats.relationshipTypeBreakdown
2471
3283
  }
2472
3284
  };
3285
+ if (this.legal) {
3286
+ manifest.legal = this.legal;
3287
+ }
2473
3288
  if (repo.metadata?.embeddingModelId) {
2474
3289
  manifest.embedding = {
2475
3290
  modelId: repo.metadata.embeddingModelId,
@@ -3038,6 +3853,7 @@ var DeepMemory = class {
3038
3853
  embedding;
3039
3854
  /** Reserved for future distributed locking support */
3040
3855
  lock;
3856
+ graphTraversalProvider;
3041
3857
  provenance;
3042
3858
  globalEventBus;
3043
3859
  initialised = false;
@@ -3046,24 +3862,42 @@ var DeepMemory = class {
3046
3862
  this.search = config.search;
3047
3863
  this.embedding = config.embedding;
3048
3864
  this.lock = config.lock;
3865
+ this.graphTraversalProvider = config.graphTraversal;
3049
3866
  this.provenance = new ProvenanceTracker(config.provenance);
3050
3867
  this.globalEventBus = new EventBus(config.provenance);
3051
3868
  }
3052
- /** Initialise the storage provider (call once before use) */
3869
+ /** Initialise the storage provider and optional providers (call once before use) */
3053
3870
  async ensureInitialised() {
3054
3871
  if (!this.initialised) {
3055
3872
  if (this.storage.initialise) {
3056
3873
  await this.storage.initialise();
3057
3874
  }
3875
+ if (this.graphTraversalProvider?.initialise) {
3876
+ await this.graphTraversalProvider.initialise();
3877
+ }
3058
3878
  this.initialised = true;
3059
3879
  }
3060
3880
  }
3061
3881
  /** Create or migrate the storage schema. Call once at deployment / startup, not per-request. */
3062
3882
  async ensureSchema() {
3063
- await this.ensureInitialised();
3883
+ const noOpResult = {
3884
+ databaseCreated: false,
3885
+ schemaCreated: false,
3886
+ alreadyUpToDate: true,
3887
+ schemaVersion: 0
3888
+ };
3889
+ try {
3890
+ await this.ensureInitialised();
3891
+ } catch {
3892
+ }
3893
+ let result = noOpResult;
3064
3894
  if (this.storage.ensureSchema) {
3065
- await this.storage.ensureSchema();
3895
+ result = await this.storage.ensureSchema();
3896
+ }
3897
+ if (!this.initialised) {
3898
+ await this.ensureInitialised();
3066
3899
  }
3900
+ return result;
3067
3901
  }
3068
3902
  validateRepositoryId(repositoryId) {
3069
3903
  if (!isValidUuid(repositoryId)) {
@@ -3120,6 +3954,7 @@ var DeepMemory = class {
3120
3954
  storage: this.storage,
3121
3955
  search: this.search,
3122
3956
  embedding: this.embedding,
3957
+ graphTraversal: this.graphTraversalProvider,
3123
3958
  vocabularyEngine,
3124
3959
  provenanceTracker: this.provenance,
3125
3960
  eventBus
@@ -3151,6 +3986,7 @@ var DeepMemory = class {
3151
3986
  storage: this.storage,
3152
3987
  search: this.search,
3153
3988
  embedding: this.embedding,
3989
+ graphTraversal: this.graphTraversalProvider,
3154
3990
  vocabularyEngine,
3155
3991
  provenanceTracker: this.provenance,
3156
3992
  eventBus
@@ -3192,11 +4028,12 @@ var DeepMemory = class {
3192
4028
  await this.globalEventBus.emit("repository:deleted", { repositoryId });
3193
4029
  }
3194
4030
  /** Export a repository to a portable archive */
3195
- async exportRepository(repositoryId) {
4031
+ async exportRepository(repositoryId, options) {
3196
4032
  await this.ensureInitialised();
3197
4033
  const exporter = new RepositoryExporter({
3198
4034
  storage: this.storage,
3199
- provenance: this.provenance.getContext()
4035
+ provenance: this.provenance.getContext(),
4036
+ legal: options?.legal
3200
4037
  });
3201
4038
  const archive = await exporter.export(repositoryId);
3202
4039
  await this.globalEventBus.emit("export:completed", {
@@ -3233,11 +4070,12 @@ var DeepMemory = class {
3233
4070
  * Yields: manifest → vocabulary → entity chunks → relationship chunks.
3234
4071
  * Use for large repositories to avoid loading everything into memory.
3235
4072
  */
3236
- async *exportRepositoryStream(repositoryId) {
4073
+ async *exportRepositoryStream(repositoryId, options) {
3237
4074
  await this.ensureInitialised();
3238
4075
  const exporter = new RepositoryExporter({
3239
4076
  storage: this.storage,
3240
- provenance: this.provenance.getContext()
4077
+ provenance: this.provenance.getContext(),
4078
+ legal: options?.legal
3241
4079
  });
3242
4080
  await this.globalEventBus.emit("export:started", { repositoryId });
3243
4081
  let entityCount = 0;
@@ -3294,6 +4132,9 @@ var DeepMemory = class {
3294
4132
  /** Dispose of all resources */
3295
4133
  async dispose() {
3296
4134
  this.globalEventBus.removeAllListeners();
4135
+ if (this.graphTraversalProvider?.dispose) {
4136
+ await this.graphTraversalProvider.dispose();
4137
+ }
3297
4138
  if (this.storage.dispose) {
3298
4139
  await this.storage.dispose();
3299
4140
  }
@@ -3301,36 +4142,6 @@ var DeepMemory = class {
3301
4142
  }
3302
4143
  };
3303
4144
 
3304
- // src/relationships/PropertyFilterMatcher.ts
3305
- function matchesPropertyFilters(properties, filters) {
3306
- return filters.every((filter) => matchesSingleFilter(properties, filter));
3307
- }
3308
- function matchesSingleFilter(properties, filter) {
3309
- const value = properties[filter.key];
3310
- switch (filter.operator) {
3311
- case "isNull":
3312
- return value === null || value === void 0;
3313
- case "isNotNull":
3314
- return value !== null && value !== void 0;
3315
- case "eq":
3316
- return value === filter.value;
3317
- case "neq":
3318
- return value !== filter.value;
3319
- case "gt":
3320
- return typeof value === "number" && typeof filter.value === "number" ? value > filter.value : typeof value === "string" && typeof filter.value === "string" ? value > filter.value : false;
3321
- case "lt":
3322
- return typeof value === "number" && typeof filter.value === "number" ? value < filter.value : typeof value === "string" && typeof filter.value === "string" ? value < filter.value : false;
3323
- case "gte":
3324
- return typeof value === "number" && typeof filter.value === "number" ? value >= filter.value : typeof value === "string" && typeof filter.value === "string" ? value >= filter.value : false;
3325
- case "lte":
3326
- return typeof value === "number" && typeof filter.value === "number" ? value <= filter.value : typeof value === "string" && typeof filter.value === "string" ? value <= filter.value : false;
3327
- case "contains":
3328
- return typeof value === "string" && typeof filter.value === "string" ? value.toLowerCase().includes(filter.value.toLowerCase()) : false;
3329
- default:
3330
- return false;
3331
- }
3332
- }
3333
-
3334
4145
  // src/providers-builtin/InMemoryStorageProvider.ts
3335
4146
  var InMemoryStorageProvider = class {
3336
4147
  stores = /* @__PURE__ */ new Map();
@@ -4026,6 +4837,7 @@ var NoOpEmbeddingProvider = class {
4026
4837
  };
4027
4838
  // Annotate the CommonJS export names for ESM import in node:
4028
4839
  0 && (module.exports = {
4840
+ CypherCompiler,
4029
4841
  DeepMemory,
4030
4842
  DeepMemoryError,
4031
4843
  DuplicateEntityError,
@@ -4035,6 +4847,8 @@ var NoOpEmbeddingProvider = class {
4035
4847
  EntityNotFoundError,
4036
4848
  ExportError,
4037
4849
  GovernanceDeniedError,
4850
+ GraphTraversalProviderRequiredError,
4851
+ GremlinCompiler,
4038
4852
  ImportError,
4039
4853
  InMemorySearchProvider,
4040
4854
  InMemoryStorageProvider,
@@ -4046,6 +4860,9 @@ var NoOpEmbeddingProvider = class {
4046
4860
  RelationshipConstraintError,
4047
4861
  RelationshipNotFoundError,
4048
4862
  RepositoryNotFoundError,
4863
+ TraversalTimeoutError,
4864
+ TraversalValidationError,
4865
+ TraversalVocabularyError,
4049
4866
  VocabularyValidationError,
4050
4867
  generateId,
4051
4868
  isValidUuid,