@utaba/deep-memory 0.1.0 → 0.3.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/{StorageProvider-CkFjdboX.d.cts → StorageProvider-CSbaIY6X.d.cts} +21 -5
- package/dist/{StorageProvider-CJjz8uBY.d.ts → StorageProvider-DqW-zpd0.d.ts} +21 -5
- package/dist/index.cjs +1252 -161
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +86 -15
- package/dist/index.d.ts +86 -15
- package/dist/index.js +1246 -161
- package/dist/index.js.map +1 -1
- package/dist/{portability-DdlNYXGX.d.cts → portability-1KBMVBGJ.d.cts} +62 -1
- package/dist/{portability-DdlNYXGX.d.ts → portability-1KBMVBGJ.d.ts} +62 -1
- package/dist/providers/index.cjs.map +1 -1
- package/dist/providers/index.d.cts +62 -3
- package/dist/providers/index.d.ts +62 -3
- package/dist/testing/conformance.cjs +22 -0
- package/dist/testing/conformance.cjs.map +1 -1
- package/dist/testing/conformance.d.cts +2 -2
- package/dist/testing/conformance.d.ts +2 -2
- package/dist/testing/conformance.js +22 -0
- package/dist/testing/conformance.js.map +1 -1
- package/dist/traversal-B4SUysM-.d.ts +264 -0
- package/dist/traversal-DQynd-to.d.cts +264 -0
- package/dist/types/index.cjs.map +1 -1
- package/dist/types/index.d.cts +28 -3
- package/dist/types/index.d.ts +28 -3
- package/package.json +1 -1
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
|
|
284
|
-
async create(
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
id
|
|
313
|
-
slug
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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
|
|
659
|
-
async create(
|
|
660
|
-
const
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
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
|
-
|
|
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,806 @@ 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
|
+
const steps = spec.steps ?? [];
|
|
854
|
+
if (!spec.start) {
|
|
855
|
+
errors.push("start is required");
|
|
856
|
+
} else {
|
|
857
|
+
const hasEntityId = spec.start.entityId !== void 0 && spec.start.entityId !== "";
|
|
858
|
+
const hasEntityType = spec.start.entityType !== void 0 && spec.start.entityType !== "";
|
|
859
|
+
const hasFilter = spec.start.filter !== void 0 && spec.start.filter.length > 0;
|
|
860
|
+
if (!hasEntityId && !hasEntityType && !hasFilter) {
|
|
861
|
+
errors.push("start must have at least one of entityId, entityType, or filter");
|
|
862
|
+
}
|
|
863
|
+
if (hasEntityType && !hasEntityId && (spec.limit === void 0 || spec.limit === 0)) {
|
|
864
|
+
errors.push("start.entityType without entityId requires limit on the spec to prevent full type scans");
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
const maxSteps = capabilities?.maxTraversalDepth ?? DEFAULT_MAX_STEPS;
|
|
868
|
+
if (steps.length > maxSteps) {
|
|
869
|
+
errors.push(`steps length ${steps.length} exceeds maximum ${maxSteps}`);
|
|
870
|
+
}
|
|
871
|
+
for (let i = 0; i < steps.length; i++) {
|
|
872
|
+
const step = steps[i];
|
|
873
|
+
if (!step.direction || !["out", "in", "both"].includes(step.direction)) {
|
|
874
|
+
errors.push(`steps[${i}].direction must be 'out', 'in', or 'both'`);
|
|
875
|
+
}
|
|
876
|
+
if (step.repeat) {
|
|
877
|
+
if (step.repeat.maxDepth === void 0 || step.repeat.maxDepth <= 0) {
|
|
878
|
+
errors.push(`steps[${i}].repeat.maxDepth must be a positive number`);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
if (steps.length > 0) {
|
|
883
|
+
const maxProviderDepth = capabilities?.maxTraversalDepth ?? DEFAULT_FALLBACK_MAX_DEPTH;
|
|
884
|
+
let totalDepth = 0;
|
|
885
|
+
for (const step of steps) {
|
|
886
|
+
totalDepth += step.repeat ? step.repeat.maxDepth : 1;
|
|
887
|
+
}
|
|
888
|
+
if (totalDepth > maxProviderDepth) {
|
|
889
|
+
errors.push(`total potential depth ${totalDepth} exceeds provider maximum ${maxProviderDepth}`);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
if (spec.returnMode === "path" && steps.length === 0) {
|
|
893
|
+
errors.push("returnMode 'path' requires at least one step");
|
|
894
|
+
}
|
|
895
|
+
if (spec.returnMode && !["terminal", "path", "all"].includes(spec.returnMode)) {
|
|
896
|
+
errors.push(`returnMode must be 'terminal', 'path', or 'all'`);
|
|
897
|
+
}
|
|
898
|
+
if (spec.projection) {
|
|
899
|
+
if (!spec.projection.properties || spec.projection.properties.length === 0) {
|
|
900
|
+
errors.push("projection.properties must contain at least one property name");
|
|
901
|
+
}
|
|
902
|
+
if (spec.projection.mode && !["count", "values"].includes(spec.projection.mode)) {
|
|
903
|
+
errors.push("projection.mode must be 'count' or 'values'");
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
if (spec.limit !== void 0) {
|
|
907
|
+
if (spec.limit < 1 || spec.limit > DEFAULT_MAX_LIMIT) {
|
|
908
|
+
errors.push(`limit must be between 1 and ${DEFAULT_MAX_LIMIT}`);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
if (spec.offset !== void 0 && spec.offset < 0) {
|
|
912
|
+
errors.push("offset must be >= 0");
|
|
913
|
+
}
|
|
914
|
+
if (vocabulary) {
|
|
915
|
+
const entityTypeSet = new Set(vocabulary.entityTypes.map((et) => et.type));
|
|
916
|
+
const relationshipTypeSet = new Set(vocabulary.relationshipTypes.map((rt) => rt.type));
|
|
917
|
+
const unknownTypes = [];
|
|
918
|
+
if (spec.start?.entityType && !entityTypeSet.has(spec.start.entityType)) {
|
|
919
|
+
unknownTypes.push(`entity type "${spec.start.entityType}"`);
|
|
920
|
+
}
|
|
921
|
+
for (let i = 0; i < steps.length; i++) {
|
|
922
|
+
const step = steps[i];
|
|
923
|
+
if (step.relationshipTypes) {
|
|
924
|
+
for (const rt of step.relationshipTypes) {
|
|
925
|
+
if (!relationshipTypeSet.has(rt)) {
|
|
926
|
+
unknownTypes.push(`relationship type "${rt}" in steps[${i}]`);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
if (step.entityTypes) {
|
|
931
|
+
for (const et of step.entityTypes) {
|
|
932
|
+
if (!entityTypeSet.has(et)) {
|
|
933
|
+
unknownTypes.push(`entity type "${et}" in steps[${i}]`);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
if (unknownTypes.length > 0) {
|
|
939
|
+
errors.push(`Unknown vocabulary types: ${unknownTypes.join(", ")}`);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
return {
|
|
943
|
+
valid: errors.length === 0,
|
|
944
|
+
errors
|
|
945
|
+
};
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// src/relationships/compilers/GremlinCompiler.ts
|
|
949
|
+
var DEFAULT_ESTIMATED_FANOUT_PER_HOP = 10;
|
|
950
|
+
var GremlinCompiler = class {
|
|
951
|
+
language = "gremlin";
|
|
952
|
+
compile(spec, _vocabulary) {
|
|
953
|
+
const parts = [];
|
|
954
|
+
const params = {};
|
|
955
|
+
let paramIndex = 0;
|
|
956
|
+
let estimatedFanOut = 1;
|
|
957
|
+
const nextParam = (value) => {
|
|
958
|
+
const name = `p${paramIndex++}`;
|
|
959
|
+
params[name] = value;
|
|
960
|
+
return name;
|
|
961
|
+
};
|
|
962
|
+
parts.push("g.V()");
|
|
963
|
+
if (spec.start.entityId) {
|
|
964
|
+
const p = nextParam(spec.start.entityId);
|
|
965
|
+
parts.push(`.has('id', ${p})`);
|
|
966
|
+
} else if (spec.start.entityType) {
|
|
967
|
+
const p = nextParam(spec.start.entityType);
|
|
968
|
+
parts.push(`.has('entityType', ${p})`);
|
|
969
|
+
estimatedFanOut *= DEFAULT_ESTIMATED_FANOUT_PER_HOP;
|
|
970
|
+
}
|
|
971
|
+
if (spec.start.filter) {
|
|
972
|
+
for (const f of spec.start.filter) {
|
|
973
|
+
parts.push(compilePropertyFilter(f, nextParam));
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
const steps = spec.steps ?? [];
|
|
977
|
+
for (const step of steps) {
|
|
978
|
+
if (step.repeat) {
|
|
979
|
+
parts.push(compileRepeatStep(step, nextParam));
|
|
980
|
+
estimatedFanOut *= step.repeat.maxDepth * DEFAULT_ESTIMATED_FANOUT_PER_HOP;
|
|
981
|
+
} else if (step.relationshipFilter && step.relationshipFilter.length > 0) {
|
|
982
|
+
parts.push(compileEdgeStep(step, nextParam));
|
|
983
|
+
estimatedFanOut *= DEFAULT_ESTIMATED_FANOUT_PER_HOP;
|
|
984
|
+
} else {
|
|
985
|
+
parts.push(compileSimpleStep(step, nextParam));
|
|
986
|
+
estimatedFanOut *= DEFAULT_ESTIMATED_FANOUT_PER_HOP;
|
|
987
|
+
}
|
|
988
|
+
if (step.entityTypes && step.entityTypes.length > 0) {
|
|
989
|
+
const typeParams = step.entityTypes.map((t) => nextParam(t));
|
|
990
|
+
parts.push(`.has('entityType', within(${typeParams.join(", ")}))`);
|
|
991
|
+
}
|
|
992
|
+
if (step.entityFilter) {
|
|
993
|
+
for (const f of step.entityFilter) {
|
|
994
|
+
parts.push(compilePropertyFilter(f, nextParam));
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
if (spec.returnMode === "all") {
|
|
999
|
+
}
|
|
1000
|
+
if (spec.dedup !== false) {
|
|
1001
|
+
parts.push(".dedup()");
|
|
1002
|
+
}
|
|
1003
|
+
const limit = spec.limit ?? 50;
|
|
1004
|
+
const offset = spec.offset ?? 0;
|
|
1005
|
+
const pOffset = nextParam(offset);
|
|
1006
|
+
const pEnd = nextParam(offset + limit);
|
|
1007
|
+
parts.push(`.range(${pOffset}, ${pEnd})`);
|
|
1008
|
+
params["_limit"] = limit;
|
|
1009
|
+
params["_offset"] = offset;
|
|
1010
|
+
if (spec.returnMode === "path") {
|
|
1011
|
+
parts.push(".path()");
|
|
1012
|
+
}
|
|
1013
|
+
if (spec.returnMode !== "path") {
|
|
1014
|
+
parts.push(".valueMap(true)");
|
|
1015
|
+
}
|
|
1016
|
+
return {
|
|
1017
|
+
query: parts.join(""),
|
|
1018
|
+
params,
|
|
1019
|
+
estimatedFanOut: Math.min(estimatedFanOut, 1e4)
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
};
|
|
1023
|
+
function compileSimpleStep(step, nextParam) {
|
|
1024
|
+
const types = step.relationshipTypes;
|
|
1025
|
+
const typeArgs = types ? types.map((t) => nextParam(t)).join(", ") : "";
|
|
1026
|
+
switch (step.direction) {
|
|
1027
|
+
case "out":
|
|
1028
|
+
return types ? `.out(${typeArgs})` : ".out()";
|
|
1029
|
+
case "in":
|
|
1030
|
+
return types ? `.in(${typeArgs})` : ".in()";
|
|
1031
|
+
case "both":
|
|
1032
|
+
return types ? `.both(${typeArgs})` : ".both()";
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
function compileEdgeStep(step, nextParam) {
|
|
1036
|
+
const parts = [];
|
|
1037
|
+
const types = step.relationshipTypes;
|
|
1038
|
+
const typeArgs = types ? types.map((t) => nextParam(t)).join(", ") : "";
|
|
1039
|
+
switch (step.direction) {
|
|
1040
|
+
case "out":
|
|
1041
|
+
parts.push(types ? `.outE(${typeArgs})` : ".outE()");
|
|
1042
|
+
break;
|
|
1043
|
+
case "in":
|
|
1044
|
+
parts.push(types ? `.inE(${typeArgs})` : ".inE()");
|
|
1045
|
+
break;
|
|
1046
|
+
case "both":
|
|
1047
|
+
parts.push(types ? `.bothE(${typeArgs})` : ".bothE()");
|
|
1048
|
+
break;
|
|
1049
|
+
}
|
|
1050
|
+
if (step.relationshipFilter) {
|
|
1051
|
+
for (const f of step.relationshipFilter) {
|
|
1052
|
+
parts.push(compilePropertyFilter(f, nextParam));
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
switch (step.direction) {
|
|
1056
|
+
case "out":
|
|
1057
|
+
parts.push(".inV()");
|
|
1058
|
+
break;
|
|
1059
|
+
case "in":
|
|
1060
|
+
parts.push(".outV()");
|
|
1061
|
+
break;
|
|
1062
|
+
case "both":
|
|
1063
|
+
parts.push(".otherV()");
|
|
1064
|
+
break;
|
|
1065
|
+
}
|
|
1066
|
+
return parts.join("");
|
|
1067
|
+
}
|
|
1068
|
+
function compileRepeatStep(step, nextParam) {
|
|
1069
|
+
const parts = [];
|
|
1070
|
+
const innerStep = step.relationshipFilter?.length ? compileEdgeStep({ ...step, repeat: void 0 }, nextParam) : compileSimpleStep(step, nextParam);
|
|
1071
|
+
if (step.repeat?.emitIntermediates !== false) {
|
|
1072
|
+
parts.push(".emit()");
|
|
1073
|
+
}
|
|
1074
|
+
parts.push(`.repeat(${innerStep.startsWith(".") ? `__${innerStep}` : innerStep})`);
|
|
1075
|
+
if (step.repeat?.until && step.repeat.until.length > 0) {
|
|
1076
|
+
const untilParts = step.repeat.until.map((f) => compilePropertyFilter(f, nextParam));
|
|
1077
|
+
parts.push(`.until(${untilParts.join("")})`);
|
|
1078
|
+
}
|
|
1079
|
+
if (step.repeat?.maxDepth) {
|
|
1080
|
+
const p = nextParam(step.repeat.maxDepth);
|
|
1081
|
+
parts.push(`.times(${p})`);
|
|
1082
|
+
}
|
|
1083
|
+
if (step.repeat?.emitIntermediates === false) {
|
|
1084
|
+
parts.push(".emit()");
|
|
1085
|
+
}
|
|
1086
|
+
return parts.join("");
|
|
1087
|
+
}
|
|
1088
|
+
function compilePropertyFilter(filter, nextParam) {
|
|
1089
|
+
const key = nextParam(filter.key);
|
|
1090
|
+
switch (filter.operator) {
|
|
1091
|
+
case "eq": {
|
|
1092
|
+
const val = nextParam(filter.value);
|
|
1093
|
+
return `.has(${key}, ${val})`;
|
|
1094
|
+
}
|
|
1095
|
+
case "neq": {
|
|
1096
|
+
const val = nextParam(filter.value);
|
|
1097
|
+
return `.has(${key}, neq(${val}))`;
|
|
1098
|
+
}
|
|
1099
|
+
case "gt": {
|
|
1100
|
+
const val = nextParam(filter.value);
|
|
1101
|
+
return `.has(${key}, gt(${val}))`;
|
|
1102
|
+
}
|
|
1103
|
+
case "gte": {
|
|
1104
|
+
const val = nextParam(filter.value);
|
|
1105
|
+
return `.has(${key}, gte(${val}))`;
|
|
1106
|
+
}
|
|
1107
|
+
case "lt": {
|
|
1108
|
+
const val = nextParam(filter.value);
|
|
1109
|
+
return `.has(${key}, lt(${val}))`;
|
|
1110
|
+
}
|
|
1111
|
+
case "lte": {
|
|
1112
|
+
const val = nextParam(filter.value);
|
|
1113
|
+
return `.has(${key}, lte(${val}))`;
|
|
1114
|
+
}
|
|
1115
|
+
case "contains": {
|
|
1116
|
+
const val = nextParam(filter.value);
|
|
1117
|
+
return `.has(${key}, containing(${val}))`;
|
|
1118
|
+
}
|
|
1119
|
+
case "isNull":
|
|
1120
|
+
return `.hasNot(${key})`;
|
|
1121
|
+
case "isNotNull":
|
|
1122
|
+
return `.has(${key})`;
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
// src/relationships/compilers/CypherCompiler.ts
|
|
1127
|
+
var DEFAULT_ESTIMATED_FANOUT_PER_HOP2 = 10;
|
|
1128
|
+
var CypherCompiler = class {
|
|
1129
|
+
language = "cypher";
|
|
1130
|
+
compile(spec, _vocabulary) {
|
|
1131
|
+
const params = {};
|
|
1132
|
+
let paramIndex = 0;
|
|
1133
|
+
let estimatedFanOut = 1;
|
|
1134
|
+
const whereClauses = [];
|
|
1135
|
+
let nodeIndex = 0;
|
|
1136
|
+
const nextParam = (value) => {
|
|
1137
|
+
const name = `p${paramIndex++}`;
|
|
1138
|
+
params[name] = value;
|
|
1139
|
+
return `$${name}`;
|
|
1140
|
+
};
|
|
1141
|
+
const startNode = `n${nodeIndex++}`;
|
|
1142
|
+
let matchParts = [];
|
|
1143
|
+
if (spec.start.entityId) {
|
|
1144
|
+
matchParts.push(`(${startNode})`);
|
|
1145
|
+
whereClauses.push(`${startNode}.id = ${nextParam(spec.start.entityId)}`);
|
|
1146
|
+
} else if (spec.start.entityType) {
|
|
1147
|
+
matchParts.push(`(${startNode})`);
|
|
1148
|
+
whereClauses.push(`${startNode}.entityType = ${nextParam(spec.start.entityType)}`);
|
|
1149
|
+
estimatedFanOut *= DEFAULT_ESTIMATED_FANOUT_PER_HOP2;
|
|
1150
|
+
} else {
|
|
1151
|
+
matchParts.push(`(${startNode})`);
|
|
1152
|
+
}
|
|
1153
|
+
if (spec.start.filter) {
|
|
1154
|
+
for (const f of spec.start.filter) {
|
|
1155
|
+
whereClauses.push(compilePropertyFilterCypher(startNode, f, nextParam));
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
let lastNode = startNode;
|
|
1159
|
+
const steps = spec.steps ?? [];
|
|
1160
|
+
for (let i = 0; i < steps.length; i++) {
|
|
1161
|
+
const step = steps[i];
|
|
1162
|
+
const targetNode = `n${nodeIndex++}`;
|
|
1163
|
+
const relAlias = `r${i}`;
|
|
1164
|
+
const relTypes = step.relationshipTypes?.length ? step.relationshipTypes.join("|") : "";
|
|
1165
|
+
const relTypePattern = relTypes ? `:${relTypes}` : "";
|
|
1166
|
+
const depthPattern = step.repeat ? `*1..${step.repeat.maxDepth}` : "";
|
|
1167
|
+
let pattern;
|
|
1168
|
+
switch (step.direction) {
|
|
1169
|
+
case "out":
|
|
1170
|
+
pattern = `-[${relAlias}${relTypePattern}${depthPattern}]->(${targetNode})`;
|
|
1171
|
+
break;
|
|
1172
|
+
case "in":
|
|
1173
|
+
pattern = `<-[${relAlias}${relTypePattern}${depthPattern}]-(${targetNode})`;
|
|
1174
|
+
break;
|
|
1175
|
+
case "both":
|
|
1176
|
+
pattern = `-[${relAlias}${relTypePattern}${depthPattern}]-(${targetNode})`;
|
|
1177
|
+
break;
|
|
1178
|
+
}
|
|
1179
|
+
matchParts.push(pattern);
|
|
1180
|
+
estimatedFanOut *= step.repeat ? step.repeat.maxDepth * DEFAULT_ESTIMATED_FANOUT_PER_HOP2 : DEFAULT_ESTIMATED_FANOUT_PER_HOP2;
|
|
1181
|
+
if (step.entityTypes && step.entityTypes.length > 0) {
|
|
1182
|
+
const typeParam = nextParam(step.entityTypes);
|
|
1183
|
+
whereClauses.push(`${targetNode}.entityType IN ${typeParam}`);
|
|
1184
|
+
}
|
|
1185
|
+
if (step.entityFilter) {
|
|
1186
|
+
for (const f of step.entityFilter) {
|
|
1187
|
+
whereClauses.push(compilePropertyFilterCypher(targetNode, f, nextParam));
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
if (step.relationshipFilter) {
|
|
1191
|
+
for (const f of step.relationshipFilter) {
|
|
1192
|
+
whereClauses.push(compilePropertyFilterCypher(relAlias, f, nextParam));
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
lastNode = targetNode;
|
|
1196
|
+
}
|
|
1197
|
+
const matchClause = `MATCH ${matchParts[0]}${matchParts.slice(1).join("")}`;
|
|
1198
|
+
const whereClause = whereClauses.length > 0 ? `
|
|
1199
|
+
WHERE ${whereClauses.join(" AND ")}` : "";
|
|
1200
|
+
let returnClause;
|
|
1201
|
+
if (spec.returnMode === "path") {
|
|
1202
|
+
const allNodes = Array.from({ length: nodeIndex }, (_, i) => `n${i}`);
|
|
1203
|
+
const allRels = steps.map((_, i) => `r${i}`);
|
|
1204
|
+
returnClause = `RETURN ${[...allNodes, ...allRels].join(", ")}`;
|
|
1205
|
+
} else if (spec.returnMode === "all") {
|
|
1206
|
+
const allNodes = Array.from({ length: nodeIndex }, (_, i) => `n${i}`);
|
|
1207
|
+
returnClause = spec.dedup !== false ? `RETURN DISTINCT ${allNodes.join(", ")}` : `RETURN ${allNodes.join(", ")}`;
|
|
1208
|
+
} else {
|
|
1209
|
+
returnClause = spec.dedup !== false ? `RETURN DISTINCT ${lastNode}` : `RETURN ${lastNode}`;
|
|
1210
|
+
}
|
|
1211
|
+
const offset = spec.offset ?? 0;
|
|
1212
|
+
const limit = spec.limit ?? 50;
|
|
1213
|
+
const skipClause = offset > 0 ? `
|
|
1214
|
+
SKIP ${nextParam(offset)}` : "";
|
|
1215
|
+
const limitClause = `
|
|
1216
|
+
LIMIT ${nextParam(limit)}`;
|
|
1217
|
+
params["_limit"] = limit;
|
|
1218
|
+
params["_offset"] = offset;
|
|
1219
|
+
const query = `${matchClause}${whereClause}
|
|
1220
|
+
${returnClause}${skipClause}${limitClause}`;
|
|
1221
|
+
return {
|
|
1222
|
+
query,
|
|
1223
|
+
params,
|
|
1224
|
+
estimatedFanOut: Math.min(estimatedFanOut, 1e4)
|
|
1225
|
+
};
|
|
1226
|
+
}
|
|
1227
|
+
};
|
|
1228
|
+
function compilePropertyFilterCypher(nodeAlias, filter, nextParam) {
|
|
1229
|
+
const prop = `${nodeAlias}.${filter.key}`;
|
|
1230
|
+
switch (filter.operator) {
|
|
1231
|
+
case "eq":
|
|
1232
|
+
return `${prop} = ${nextParam(filter.value)}`;
|
|
1233
|
+
case "neq":
|
|
1234
|
+
return `${prop} <> ${nextParam(filter.value)}`;
|
|
1235
|
+
case "gt":
|
|
1236
|
+
return `${prop} > ${nextParam(filter.value)}`;
|
|
1237
|
+
case "gte":
|
|
1238
|
+
return `${prop} >= ${nextParam(filter.value)}`;
|
|
1239
|
+
case "lt":
|
|
1240
|
+
return `${prop} < ${nextParam(filter.value)}`;
|
|
1241
|
+
case "lte":
|
|
1242
|
+
return `${prop} <= ${nextParam(filter.value)}`;
|
|
1243
|
+
case "contains":
|
|
1244
|
+
return `${prop} CONTAINS ${nextParam(filter.value)}`;
|
|
1245
|
+
case "isNull":
|
|
1246
|
+
return `${prop} IS NULL`;
|
|
1247
|
+
case "isNotNull":
|
|
1248
|
+
return `${prop} IS NOT NULL`;
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
// src/relationships/PropertyFilterMatcher.ts
|
|
1253
|
+
function matchesPropertyFilters(properties, filters) {
|
|
1254
|
+
return filters.every((filter) => matchesSingleFilter(properties, filter));
|
|
1255
|
+
}
|
|
1256
|
+
function matchesSingleFilter(properties, filter) {
|
|
1257
|
+
const value = properties[filter.key];
|
|
1258
|
+
switch (filter.operator) {
|
|
1259
|
+
case "isNull":
|
|
1260
|
+
return value === null || value === void 0;
|
|
1261
|
+
case "isNotNull":
|
|
1262
|
+
return value !== null && value !== void 0;
|
|
1263
|
+
case "eq":
|
|
1264
|
+
return value === filter.value;
|
|
1265
|
+
case "neq":
|
|
1266
|
+
return value !== filter.value;
|
|
1267
|
+
case "gt":
|
|
1268
|
+
return typeof value === "number" && typeof filter.value === "number" ? value > filter.value : typeof value === "string" && typeof filter.value === "string" ? value > filter.value : false;
|
|
1269
|
+
case "lt":
|
|
1270
|
+
return typeof value === "number" && typeof filter.value === "number" ? value < filter.value : typeof value === "string" && typeof filter.value === "string" ? value < filter.value : false;
|
|
1271
|
+
case "gte":
|
|
1272
|
+
return typeof value === "number" && typeof filter.value === "number" ? value >= filter.value : typeof value === "string" && typeof filter.value === "string" ? value >= filter.value : false;
|
|
1273
|
+
case "lte":
|
|
1274
|
+
return typeof value === "number" && typeof filter.value === "number" ? value <= filter.value : typeof value === "string" && typeof filter.value === "string" ? value <= filter.value : false;
|
|
1275
|
+
case "contains":
|
|
1276
|
+
return typeof value === "string" && typeof filter.value === "string" ? value.toLowerCase().includes(filter.value.toLowerCase()) : false;
|
|
1277
|
+
default:
|
|
1278
|
+
return false;
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
// src/relationships/FallbackTraversalExecutor.ts
|
|
1283
|
+
var MAX_STORAGE_CALLS = 500;
|
|
1284
|
+
var MAX_ENTITY_SCAN = 1e4;
|
|
1285
|
+
var REL_FETCH_LIMIT = 1e3;
|
|
1286
|
+
async function executeFallbackTraversal(repositoryId, storage, spec) {
|
|
1287
|
+
const startTime = Date.now();
|
|
1288
|
+
let storageCalls = 0;
|
|
1289
|
+
const checkCallLimit = () => {
|
|
1290
|
+
if (storageCalls >= MAX_STORAGE_CALLS) {
|
|
1291
|
+
throw new Error(`Fallback traversal exceeded ${MAX_STORAGE_CALLS} storage calls. Consider using a GraphTraversalProvider for large traversals.`);
|
|
1292
|
+
}
|
|
1293
|
+
};
|
|
1294
|
+
const isVertexQuery = !spec.steps || spec.steps.length === 0;
|
|
1295
|
+
const needsFullScan = isVertexQuery || spec.projection !== void 0;
|
|
1296
|
+
const fetchLimit = needsFullScan ? MAX_ENTITY_SCAN : spec.limit ?? 50;
|
|
1297
|
+
let frontier = [];
|
|
1298
|
+
if (spec.start.entityId) {
|
|
1299
|
+
const entity = await resolveEntity(repositoryId, storage, spec.start.entityId);
|
|
1300
|
+
storageCalls++;
|
|
1301
|
+
if (entity) {
|
|
1302
|
+
frontier.push({ entity, entityPath: [entity.id], relationshipPath: [] });
|
|
1303
|
+
} else {
|
|
1304
|
+
throw new EntityNotFoundError(spec.start.entityId);
|
|
1305
|
+
}
|
|
1306
|
+
} else if (spec.start.entityType) {
|
|
1307
|
+
const result = await storage.findEntities(repositoryId, {
|
|
1308
|
+
entityTypes: [spec.start.entityType],
|
|
1309
|
+
limit: fetchLimit,
|
|
1310
|
+
offset: 0
|
|
1311
|
+
});
|
|
1312
|
+
storageCalls++;
|
|
1313
|
+
frontier = result.items.map((e) => ({
|
|
1314
|
+
entity: e,
|
|
1315
|
+
entityPath: [e.id],
|
|
1316
|
+
relationshipPath: []
|
|
1317
|
+
}));
|
|
1318
|
+
} else if (spec.start.filter && spec.start.filter.length > 0) {
|
|
1319
|
+
const result = await storage.findEntities(repositoryId, {
|
|
1320
|
+
limit: fetchLimit,
|
|
1321
|
+
offset: 0
|
|
1322
|
+
});
|
|
1323
|
+
storageCalls++;
|
|
1324
|
+
frontier = result.items.filter((e) => matchesPropertyFilters(e.properties, spec.start.filter)).map((e) => ({
|
|
1325
|
+
entity: e,
|
|
1326
|
+
entityPath: [e.id],
|
|
1327
|
+
relationshipPath: []
|
|
1328
|
+
}));
|
|
1329
|
+
}
|
|
1330
|
+
if (spec.start.filter && spec.start.filter.length > 0 && (spec.start.entityId || spec.start.entityType)) {
|
|
1331
|
+
frontier = frontier.filter(
|
|
1332
|
+
(entry) => matchesPropertyFilters(entry.entity.properties, spec.start.filter)
|
|
1333
|
+
);
|
|
1334
|
+
}
|
|
1335
|
+
const steps = spec.steps ?? [];
|
|
1336
|
+
const allCollected = spec.returnMode === "all" ? [...frontier] : [];
|
|
1337
|
+
for (const step of steps) {
|
|
1338
|
+
checkCallLimit();
|
|
1339
|
+
if (step.repeat) {
|
|
1340
|
+
const result = await executeRepeatStep(
|
|
1341
|
+
repositoryId,
|
|
1342
|
+
storage,
|
|
1343
|
+
frontier,
|
|
1344
|
+
step,
|
|
1345
|
+
() => {
|
|
1346
|
+
storageCalls++;
|
|
1347
|
+
checkCallLimit();
|
|
1348
|
+
}
|
|
1349
|
+
);
|
|
1350
|
+
frontier = result.terminal;
|
|
1351
|
+
if (spec.returnMode === "all") {
|
|
1352
|
+
allCollected.push(...result.intermediates);
|
|
1353
|
+
}
|
|
1354
|
+
} else {
|
|
1355
|
+
frontier = await executeSingleStep(
|
|
1356
|
+
repositoryId,
|
|
1357
|
+
storage,
|
|
1358
|
+
frontier,
|
|
1359
|
+
step,
|
|
1360
|
+
() => {
|
|
1361
|
+
storageCalls++;
|
|
1362
|
+
checkCallLimit();
|
|
1363
|
+
}
|
|
1364
|
+
);
|
|
1365
|
+
if (spec.returnMode === "all") {
|
|
1366
|
+
allCollected.push(...frontier);
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
const detailLevel = spec.detailLevel ?? "summary";
|
|
1371
|
+
const dedup = spec.dedup !== false;
|
|
1372
|
+
const limit = spec.limit ?? 50;
|
|
1373
|
+
const offset = spec.offset ?? 0;
|
|
1374
|
+
let resultEntries;
|
|
1375
|
+
if (spec.returnMode === "all") {
|
|
1376
|
+
resultEntries = allCollected;
|
|
1377
|
+
} else {
|
|
1378
|
+
resultEntries = frontier;
|
|
1379
|
+
}
|
|
1380
|
+
if (dedup) {
|
|
1381
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1382
|
+
resultEntries = resultEntries.filter((entry) => {
|
|
1383
|
+
if (seen.has(entry.entity.id)) return false;
|
|
1384
|
+
seen.add(entry.entity.id);
|
|
1385
|
+
return true;
|
|
1386
|
+
});
|
|
1387
|
+
}
|
|
1388
|
+
const total = resultEntries.length;
|
|
1389
|
+
const paged = resultEntries.slice(offset, offset + limit);
|
|
1390
|
+
const suppressEntities = spec.projection !== void 0 && spec.projection.includeEntities !== true;
|
|
1391
|
+
let entities;
|
|
1392
|
+
if (suppressEntities) {
|
|
1393
|
+
entities = [];
|
|
1394
|
+
} else {
|
|
1395
|
+
entities = paged.map((entry) => {
|
|
1396
|
+
const projected = projectEntity(entry.entity, detailLevel);
|
|
1397
|
+
if (!spec.includeProvenance) {
|
|
1398
|
+
delete projected["provenance"];
|
|
1399
|
+
}
|
|
1400
|
+
return projected;
|
|
1401
|
+
});
|
|
1402
|
+
if (spec.includeRelationshipSummary && entities.length > 0) {
|
|
1403
|
+
const summaryResults = await Promise.all(
|
|
1404
|
+
paged.map(async (entry) => {
|
|
1405
|
+
storageCalls++;
|
|
1406
|
+
const result = await storage.getEntityRelationships(repositoryId, entry.entity.id, {
|
|
1407
|
+
direction: "both",
|
|
1408
|
+
limit: 1e4
|
|
1409
|
+
});
|
|
1410
|
+
const summary = { outbound: {}, inbound: {} };
|
|
1411
|
+
for (const rel of result.items) {
|
|
1412
|
+
if (rel.sourceEntityId === entry.entity.id) {
|
|
1413
|
+
summary.outbound[rel.relationshipType] = (summary.outbound[rel.relationshipType] ?? 0) + 1;
|
|
1414
|
+
}
|
|
1415
|
+
if (rel.targetEntityId === entry.entity.id) {
|
|
1416
|
+
summary.inbound[rel.relationshipType] = (summary.inbound[rel.relationshipType] ?? 0) + 1;
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
return summary;
|
|
1420
|
+
})
|
|
1421
|
+
);
|
|
1422
|
+
entities = entities.map((e, i) => ({ ...e, relationshipSummary: summaryResults[i] }));
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
let aggregations;
|
|
1426
|
+
if (spec.projection) {
|
|
1427
|
+
const propNames = spec.projection.properties;
|
|
1428
|
+
const mode = spec.projection.mode ?? "values";
|
|
1429
|
+
const distinct = spec.projection.distinct ?? false;
|
|
1430
|
+
const sourceEntries = dedup ? (() => {
|
|
1431
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1432
|
+
return (spec.returnMode === "all" ? allCollected : frontier).filter((e) => {
|
|
1433
|
+
if (seen.has(e.entity.id)) return false;
|
|
1434
|
+
seen.add(e.entity.id);
|
|
1435
|
+
return true;
|
|
1436
|
+
});
|
|
1437
|
+
})() : spec.returnMode === "all" ? allCollected : frontier;
|
|
1438
|
+
if (mode === "count" || distinct) {
|
|
1439
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1440
|
+
for (const entry of sourceEntries) {
|
|
1441
|
+
const vals = {};
|
|
1442
|
+
for (const prop of propNames) {
|
|
1443
|
+
vals[prop] = entry.entity.properties[prop] ?? null;
|
|
1444
|
+
}
|
|
1445
|
+
const key = JSON.stringify(vals);
|
|
1446
|
+
const existing = groups.get(key);
|
|
1447
|
+
if (existing) {
|
|
1448
|
+
existing.count++;
|
|
1449
|
+
} else {
|
|
1450
|
+
groups.set(key, { values: vals, count: 1 });
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
aggregations = Array.from(groups.values()).map((g) => ({
|
|
1454
|
+
values: g.values,
|
|
1455
|
+
...mode === "count" ? { count: g.count } : {}
|
|
1456
|
+
}));
|
|
1457
|
+
if (mode === "count") {
|
|
1458
|
+
aggregations.sort((a, b) => (b.count ?? 0) - (a.count ?? 0));
|
|
1459
|
+
}
|
|
1460
|
+
} else {
|
|
1461
|
+
aggregations = sourceEntries.map((entry) => {
|
|
1462
|
+
const vals = {};
|
|
1463
|
+
for (const prop of propNames) {
|
|
1464
|
+
vals[prop] = entry.entity.properties[prop] ?? null;
|
|
1465
|
+
}
|
|
1466
|
+
return { values: vals };
|
|
1467
|
+
});
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
let relationships;
|
|
1471
|
+
let paths;
|
|
1472
|
+
if (!suppressEntities && (spec.returnMode === "path" || spec.returnMode === "all")) {
|
|
1473
|
+
const relMap = /* @__PURE__ */ new Map();
|
|
1474
|
+
for (const entry of paged) {
|
|
1475
|
+
for (const rel of entry.relationshipPath) {
|
|
1476
|
+
if (!relMap.has(rel.id)) {
|
|
1477
|
+
relMap.set(rel.id, {
|
|
1478
|
+
id: rel.id,
|
|
1479
|
+
type: rel.relationshipType,
|
|
1480
|
+
sourceEntityId: rel.sourceEntityId,
|
|
1481
|
+
targetEntityId: rel.targetEntityId,
|
|
1482
|
+
direction: "outbound",
|
|
1483
|
+
properties: rel.properties
|
|
1484
|
+
});
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
relationships = Array.from(relMap.values());
|
|
1489
|
+
}
|
|
1490
|
+
if (!suppressEntities && spec.returnMode === "path") {
|
|
1491
|
+
paths = paged.map((entry) => ({
|
|
1492
|
+
length: entry.entityPath.length - 1,
|
|
1493
|
+
entities: [(() => {
|
|
1494
|
+
const e = projectEntity(entry.entity, detailLevel);
|
|
1495
|
+
if (!spec.includeProvenance) delete e["provenance"];
|
|
1496
|
+
return e;
|
|
1497
|
+
})()],
|
|
1498
|
+
relationships: entry.relationshipPath.map((rel) => ({
|
|
1499
|
+
id: rel.id,
|
|
1500
|
+
type: rel.relationshipType,
|
|
1501
|
+
sourceEntityId: rel.sourceEntityId,
|
|
1502
|
+
targetEntityId: rel.targetEntityId,
|
|
1503
|
+
direction: "outbound",
|
|
1504
|
+
properties: rel.properties
|
|
1505
|
+
}))
|
|
1506
|
+
}));
|
|
1507
|
+
}
|
|
1508
|
+
const executionTimeMs = Date.now() - startTime;
|
|
1509
|
+
return {
|
|
1510
|
+
entities,
|
|
1511
|
+
relationships,
|
|
1512
|
+
paths,
|
|
1513
|
+
aggregations,
|
|
1514
|
+
total,
|
|
1515
|
+
returned: paged.length,
|
|
1516
|
+
hasMore: offset + limit < total,
|
|
1517
|
+
queryMetadata: {
|
|
1518
|
+
executionTimeMs,
|
|
1519
|
+
appliedLimits: {
|
|
1520
|
+
maxResults: limit
|
|
1521
|
+
},
|
|
1522
|
+
truncated: total > limit + offset,
|
|
1523
|
+
truncationReason: total > limit + offset ? "result_limit" : void 0
|
|
1524
|
+
}
|
|
1525
|
+
};
|
|
1526
|
+
}
|
|
1527
|
+
async function executeSingleStep(repositoryId, storage, frontier, step, onStorageCall) {
|
|
1528
|
+
const direction = mapDirection(step.direction);
|
|
1529
|
+
const pendingEdges = [];
|
|
1530
|
+
for (const entry of frontier) {
|
|
1531
|
+
const rels = await storage.getEntityRelationships(repositoryId, entry.entity.id, {
|
|
1532
|
+
direction,
|
|
1533
|
+
relationshipTypes: step.relationshipTypes,
|
|
1534
|
+
limit: REL_FETCH_LIMIT
|
|
1535
|
+
});
|
|
1536
|
+
onStorageCall();
|
|
1537
|
+
for (const rel of rels.items) {
|
|
1538
|
+
if (step.relationshipFilter && step.relationshipFilter.length > 0) {
|
|
1539
|
+
if (!matchesPropertyFilters(rel.properties, step.relationshipFilter)) {
|
|
1540
|
+
continue;
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
const targetId = getTargetId(rel, entry.entity.id);
|
|
1544
|
+
pendingEdges.push({ entry, rel, targetId });
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
if (pendingEdges.length === 0) return [];
|
|
1548
|
+
const uniqueTargetIds = [...new Set(pendingEdges.map((e) => e.targetId))];
|
|
1549
|
+
const entityMap = await storage.getEntities(repositoryId, uniqueTargetIds);
|
|
1550
|
+
onStorageCall();
|
|
1551
|
+
const nextFrontier = [];
|
|
1552
|
+
for (const { entry, rel, targetId } of pendingEdges) {
|
|
1553
|
+
const targetEntity = entityMap.get(targetId);
|
|
1554
|
+
if (!targetEntity) continue;
|
|
1555
|
+
if (step.entityTypes && step.entityTypes.length > 0) {
|
|
1556
|
+
if (!step.entityTypes.includes(targetEntity.entityType)) continue;
|
|
1557
|
+
}
|
|
1558
|
+
if (step.entityFilter && step.entityFilter.length > 0) {
|
|
1559
|
+
if (!matchesPropertyFilters(targetEntity.properties, step.entityFilter)) continue;
|
|
1560
|
+
}
|
|
1561
|
+
nextFrontier.push({
|
|
1562
|
+
entity: targetEntity,
|
|
1563
|
+
entityPath: [...entry.entityPath, targetEntity.id],
|
|
1564
|
+
relationshipPath: [...entry.relationshipPath, rel]
|
|
1565
|
+
});
|
|
1566
|
+
}
|
|
1567
|
+
return nextFrontier;
|
|
1568
|
+
}
|
|
1569
|
+
async function executeRepeatStep(repositoryId, storage, frontier, step, onStorageCall) {
|
|
1570
|
+
const maxDepth = step.repeat?.maxDepth ?? 1;
|
|
1571
|
+
const emitIntermediates = step.repeat?.emitIntermediates !== false;
|
|
1572
|
+
const untilFilters = step.repeat?.until;
|
|
1573
|
+
let current = frontier;
|
|
1574
|
+
const allIntermediates = [];
|
|
1575
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1576
|
+
for (const entry of frontier) {
|
|
1577
|
+
visited.add(entry.entity.id);
|
|
1578
|
+
}
|
|
1579
|
+
for (let depth = 0; depth < maxDepth; depth++) {
|
|
1580
|
+
const next = await executeSingleStep(repositoryId, storage, current, step, onStorageCall);
|
|
1581
|
+
const unvisited = next.filter((entry) => {
|
|
1582
|
+
if (visited.has(entry.entity.id)) return false;
|
|
1583
|
+
visited.add(entry.entity.id);
|
|
1584
|
+
return true;
|
|
1585
|
+
});
|
|
1586
|
+
if (unvisited.length === 0) break;
|
|
1587
|
+
if (untilFilters && untilFilters.length > 0) {
|
|
1588
|
+
const matching = [];
|
|
1589
|
+
const continuing = [];
|
|
1590
|
+
for (const entry of unvisited) {
|
|
1591
|
+
if (matchesPropertyFilters(entry.entity.properties, untilFilters)) {
|
|
1592
|
+
matching.push(entry);
|
|
1593
|
+
} else {
|
|
1594
|
+
continuing.push(entry);
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
if (emitIntermediates) {
|
|
1598
|
+
allIntermediates.push(...continuing);
|
|
1599
|
+
}
|
|
1600
|
+
allIntermediates.push(...matching);
|
|
1601
|
+
if (matching.length > 0 && continuing.length === 0) {
|
|
1602
|
+
current = matching;
|
|
1603
|
+
break;
|
|
1604
|
+
}
|
|
1605
|
+
current = continuing;
|
|
1606
|
+
} else {
|
|
1607
|
+
if (emitIntermediates) {
|
|
1608
|
+
allIntermediates.push(...unvisited);
|
|
1609
|
+
}
|
|
1610
|
+
current = unvisited;
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
return {
|
|
1614
|
+
terminal: current,
|
|
1615
|
+
intermediates: allIntermediates
|
|
1616
|
+
};
|
|
1617
|
+
}
|
|
1618
|
+
async function resolveEntity(repositoryId, storage, idOrSlug) {
|
|
1619
|
+
const byId = await storage.getEntity(repositoryId, idOrSlug);
|
|
1620
|
+
if (byId) return byId;
|
|
1621
|
+
return storage.getEntityBySlug(repositoryId, idOrSlug);
|
|
1622
|
+
}
|
|
1623
|
+
function mapDirection(direction) {
|
|
1624
|
+
switch (direction) {
|
|
1625
|
+
case "out":
|
|
1626
|
+
return "outbound";
|
|
1627
|
+
case "in":
|
|
1628
|
+
return "inbound";
|
|
1629
|
+
case "both":
|
|
1630
|
+
return "both";
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
function getTargetId(rel, currentEntityId) {
|
|
1634
|
+
if (rel.sourceEntityId === currentEntityId) {
|
|
1635
|
+
return rel.targetEntityId;
|
|
1636
|
+
}
|
|
1637
|
+
return rel.sourceEntityId;
|
|
1638
|
+
}
|
|
1639
|
+
|
|
787
1640
|
// src/relationships/GraphTraversal.ts
|
|
788
1641
|
var GraphTraversal = class {
|
|
789
|
-
constructor(repositoryId, storage) {
|
|
1642
|
+
constructor(repositoryId, storage, graphTraversalProvider, vocabularyEngine) {
|
|
790
1643
|
this.repositoryId = repositoryId;
|
|
791
1644
|
this.storage = storage;
|
|
1645
|
+
this.graphTraversalProvider = graphTraversalProvider;
|
|
1646
|
+
this.vocabularyEngine = vocabularyEngine;
|
|
792
1647
|
}
|
|
793
1648
|
/** BFS neighbourhood exploration from a centre entity */
|
|
794
1649
|
async exploreNeighbourhood(entityId, options) {
|
|
@@ -920,6 +1775,51 @@ var GraphTraversal = class {
|
|
|
920
1775
|
}))
|
|
921
1776
|
};
|
|
922
1777
|
}
|
|
1778
|
+
/**
|
|
1779
|
+
* Execute a structured traversal.
|
|
1780
|
+
* Validates the spec, compiles to native query if a provider exists,
|
|
1781
|
+
* falls back to application-level BFS otherwise.
|
|
1782
|
+
*/
|
|
1783
|
+
async traverse(spec) {
|
|
1784
|
+
const capabilities = this.graphTraversalProvider?.getCapabilities();
|
|
1785
|
+
const vocabulary = this.vocabularyEngine ? await this.vocabularyEngine.getVocabulary() : void 0;
|
|
1786
|
+
const validation = validateTraversalSpec(spec, vocabulary, capabilities);
|
|
1787
|
+
if (!validation.valid) {
|
|
1788
|
+
const vocabErrors = validation.errors.filter((e) => e.startsWith("Unknown vocabulary types:"));
|
|
1789
|
+
const structErrors = validation.errors.filter((e) => !e.startsWith("Unknown vocabulary types:"));
|
|
1790
|
+
if (vocabErrors.length > 0) {
|
|
1791
|
+
const unknownTypes = vocabErrors.join("; ").replace(/Unknown vocabulary types: /g, "").split(", ").map((t) => t.trim());
|
|
1792
|
+
throw new TraversalVocabularyError(unknownTypes);
|
|
1793
|
+
}
|
|
1794
|
+
throw new TraversalValidationError(structErrors.length > 0 ? structErrors : validation.errors);
|
|
1795
|
+
}
|
|
1796
|
+
if (this.graphTraversalProvider && vocabulary) {
|
|
1797
|
+
const language = capabilities.nativeQueryLanguage;
|
|
1798
|
+
let compiledQuery;
|
|
1799
|
+
if (language === "gremlin") {
|
|
1800
|
+
const compiler = new GremlinCompiler();
|
|
1801
|
+
const compiled = compiler.compile(spec, vocabulary);
|
|
1802
|
+
compiledQuery = compiled.query;
|
|
1803
|
+
} else if (language === "cypher") {
|
|
1804
|
+
const compiler = new CypherCompiler();
|
|
1805
|
+
const compiled = compiler.compile(spec, vocabulary);
|
|
1806
|
+
compiledQuery = compiled.query;
|
|
1807
|
+
}
|
|
1808
|
+
return this.graphTraversalProvider.traverse(this.repositoryId, spec, compiledQuery);
|
|
1809
|
+
}
|
|
1810
|
+
return executeFallbackTraversal(this.repositoryId, this.storage, spec);
|
|
1811
|
+
}
|
|
1812
|
+
/**
|
|
1813
|
+
* Execute a native graph query.
|
|
1814
|
+
* Pass-through to GraphTraversalProvider.executeNativeQuery().
|
|
1815
|
+
* Throws GraphTraversalProviderRequiredError if no provider registered.
|
|
1816
|
+
*/
|
|
1817
|
+
async executeNativeQuery(query, params) {
|
|
1818
|
+
if (!this.graphTraversalProvider) {
|
|
1819
|
+
throw new GraphTraversalProviderRequiredError();
|
|
1820
|
+
}
|
|
1821
|
+
return this.graphTraversalProvider.executeNativeQuery(this.repositoryId, query, params);
|
|
1822
|
+
}
|
|
923
1823
|
};
|
|
924
1824
|
|
|
925
1825
|
// src/search/SearchOrchestrator.ts
|
|
@@ -1128,7 +2028,9 @@ var MemoryRepository = class {
|
|
|
1128
2028
|
);
|
|
1129
2029
|
this.graphTraversal = new GraphTraversal(
|
|
1130
2030
|
config.repositoryId,
|
|
1131
|
-
config.storage
|
|
2031
|
+
config.storage,
|
|
2032
|
+
config.graphTraversal,
|
|
2033
|
+
config.vocabularyEngine
|
|
1132
2034
|
);
|
|
1133
2035
|
this.searchOrchestrator = new SearchOrchestrator({
|
|
1134
2036
|
repositoryId: config.repositoryId,
|
|
@@ -1152,19 +2054,21 @@ var MemoryRepository = class {
|
|
|
1152
2054
|
return this.proposeVocabularyChange(proposal);
|
|
1153
2055
|
}
|
|
1154
2056
|
// ─── Entities ──────────────────────────────────────────────────────
|
|
1155
|
-
async
|
|
1156
|
-
const
|
|
2057
|
+
async createEntities(inputs) {
|
|
2058
|
+
const entities = await this.entityManager.create(inputs);
|
|
1157
2059
|
if (this.search) {
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
2060
|
+
for (const entity of entities) {
|
|
2061
|
+
await this.search.indexEntity(this.repositoryId, {
|
|
2062
|
+
entityId: entity.id,
|
|
2063
|
+
entityType: entity.entityType,
|
|
2064
|
+
label: entity.label,
|
|
2065
|
+
summary: entity.summary,
|
|
2066
|
+
properties: entity.properties,
|
|
2067
|
+
data: entity.data
|
|
2068
|
+
});
|
|
2069
|
+
}
|
|
1166
2070
|
}
|
|
1167
|
-
return
|
|
2071
|
+
return entities;
|
|
1168
2072
|
}
|
|
1169
2073
|
async updateEntity(entityId, updates) {
|
|
1170
2074
|
const entity = await this.entityManager.update(entityId, updates);
|
|
@@ -1251,8 +2155,8 @@ var MemoryRepository = class {
|
|
|
1251
2155
|
}
|
|
1252
2156
|
}
|
|
1253
2157
|
// ─── Relationships ─────────────────────────────────────────────────
|
|
1254
|
-
async
|
|
1255
|
-
return this.relationshipManager.create(
|
|
2158
|
+
async createRelationships(inputs) {
|
|
2159
|
+
return this.relationshipManager.create(inputs);
|
|
1256
2160
|
}
|
|
1257
2161
|
async removeRelationship(relationshipId) {
|
|
1258
2162
|
return this.relationshipManager.remove(relationshipId);
|
|
@@ -1346,6 +2250,12 @@ var MemoryRepository = class {
|
|
|
1346
2250
|
async findPaths(sourceId, targetId, options) {
|
|
1347
2251
|
return this.graphTraversal.findPaths(sourceId, targetId, options);
|
|
1348
2252
|
}
|
|
2253
|
+
async traverse(spec) {
|
|
2254
|
+
return this.graphTraversal.traverse(spec);
|
|
2255
|
+
}
|
|
2256
|
+
async executeNativeQuery(query, params) {
|
|
2257
|
+
return this.graphTraversal.executeNativeQuery(query, params);
|
|
2258
|
+
}
|
|
1349
2259
|
// ─── Search ────────────────────────────────────────────────────────
|
|
1350
2260
|
async searchByConcept(query, options) {
|
|
1351
2261
|
return this.searchOrchestrator.searchByConcept(query, options);
|
|
@@ -1435,6 +2345,58 @@ var MemoryRepository = class {
|
|
|
1435
2345
|
async getStats() {
|
|
1436
2346
|
return this.storage.getRepositoryStats(this.repositoryId);
|
|
1437
2347
|
}
|
|
2348
|
+
// ─── Bulk Operations ──────────────────────────────────────────────
|
|
2349
|
+
/** Delete all entities and relationships in this repository, preserving the repository and vocabulary */
|
|
2350
|
+
async deleteAllContents() {
|
|
2351
|
+
return this.storage.deleteAllContents(this.repositoryId);
|
|
2352
|
+
}
|
|
2353
|
+
/**
|
|
2354
|
+
* Returns a static markdown guide describing the recommended query strategy
|
|
2355
|
+
* for AI agents and other consumers of the repository.
|
|
2356
|
+
*/
|
|
2357
|
+
getQueryGuide() {
|
|
2358
|
+
return `## Query Strategy Guide
|
|
2359
|
+
|
|
2360
|
+
### Step 1 \u2014 Discover before you traverse
|
|
2361
|
+
|
|
2362
|
+
Before following relationships from an entity, check that it actually has relationships.
|
|
2363
|
+
\`memory_query_graph\` includes \`relationshipSummary\` on every returned entity by default
|
|
2364
|
+
(outbound and inbound counts by type). Inspect this before traversing \u2014 entities with
|
|
2365
|
+
zero outbound counts for the relationship type you need have no connections to follow.
|
|
2366
|
+
To skip summaries and reduce response size, set \`includeRelationshipSummary: false\`.
|
|
2367
|
+
|
|
2368
|
+
### Step 2 \u2014 Use projection for aggregation
|
|
2369
|
+
|
|
2370
|
+
To understand what data exists (e.g. all distinct values of a property, or counts by
|
|
2371
|
+
category), use \`memory_query_graph\` with \`projection\` instead of fetching full entities:
|
|
2372
|
+
\`{ start: { entityType: "..." }, projection: { properties: ["propName"], distinct: true }, limit: 200 }\`
|
|
2373
|
+
|
|
2374
|
+
This returns only the aggregated values \u2014 no entity objects \u2014 keeping responses lightweight.
|
|
2375
|
+
|
|
2376
|
+
### Step 3 \u2014 Traverse efficiently
|
|
2377
|
+
|
|
2378
|
+
- **Omit relationshipTypes** in a traversal step to follow all relationship types at once,
|
|
2379
|
+
rather than making separate calls per type.
|
|
2380
|
+
- **Use multi-step traversals** for multi-hop patterns. A two-step query
|
|
2381
|
+
(e.g. Equipment \u2192 Component \u2192 MaintenanceProcedure) is one call, not two sequential calls.
|
|
2382
|
+
- **Use returnMode "all"** when you need intermediate entities along the path, not just
|
|
2383
|
+
the terminal nodes.
|
|
2384
|
+
|
|
2385
|
+
### Step 4 \u2014 Use semantic search for known-unknowns
|
|
2386
|
+
|
|
2387
|
+
\`memory_search_by_concept\` is best for finding specific entities when you know roughly
|
|
2388
|
+
what you're looking for but not the exact label or type. It is not efficient for broad
|
|
2389
|
+
discovery \u2014 use projection or find_entities for that.
|
|
2390
|
+
|
|
2391
|
+
### Common anti-patterns to avoid
|
|
2392
|
+
|
|
2393
|
+
- **Don't traverse from entities with zero relationship counts.** The \`relationshipSummary\` on each entity tells you what is connected before you traverse.
|
|
2394
|
+
- **Don't split queries by relationship type.** One step with no type filter is better
|
|
2395
|
+
than three separate single-type calls.
|
|
2396
|
+
- **Don't use sequential single-hop traversals** when a multi-step query achieves the
|
|
2397
|
+
same result in one call.
|
|
2398
|
+
- **Don't fetch full entities when you only need property values.** Use projection.`;
|
|
2399
|
+
}
|
|
1438
2400
|
// ─── Events ────────────────────────────────────────────────────────
|
|
1439
2401
|
on(event, handler) {
|
|
1440
2402
|
return this.eventBus.on(event, handler);
|
|
@@ -2430,13 +3392,15 @@ var EventBus = class {
|
|
|
2430
3392
|
};
|
|
2431
3393
|
|
|
2432
3394
|
// src/portability/RepositoryExporter.ts
|
|
2433
|
-
var LIBRARY_VERSION = "0.1.0";
|
|
3395
|
+
var LIBRARY_VERSION = true ? "0.3.0" : "0.1.0";
|
|
2434
3396
|
var RepositoryExporter = class {
|
|
2435
3397
|
storage;
|
|
2436
3398
|
provenance;
|
|
3399
|
+
legal;
|
|
2437
3400
|
constructor(config) {
|
|
2438
3401
|
this.storage = config.storage;
|
|
2439
3402
|
this.provenance = config.provenance;
|
|
3403
|
+
this.legal = config.legal;
|
|
2440
3404
|
}
|
|
2441
3405
|
/**
|
|
2442
3406
|
* Stream a repository export as an async generator.
|
|
@@ -2470,6 +3434,9 @@ var RepositoryExporter = class {
|
|
|
2470
3434
|
relationshipTypeBreakdown: stats.relationshipTypeBreakdown
|
|
2471
3435
|
}
|
|
2472
3436
|
};
|
|
3437
|
+
if (this.legal) {
|
|
3438
|
+
manifest.legal = this.legal;
|
|
3439
|
+
}
|
|
2473
3440
|
if (repo.metadata?.embeddingModelId) {
|
|
2474
3441
|
manifest.embedding = {
|
|
2475
3442
|
modelId: repo.metadata.embeddingModelId,
|
|
@@ -2768,13 +3735,22 @@ function describeChange(change) {
|
|
|
2768
3735
|
}
|
|
2769
3736
|
|
|
2770
3737
|
// src/portability/RepositoryImporter.ts
|
|
3738
|
+
function estimateChunkCount(totalEntities, totalRelationships) {
|
|
3739
|
+
const ESTIMATED_CHUNK_SIZE = 100;
|
|
3740
|
+
return Math.max(
|
|
3741
|
+
1,
|
|
3742
|
+
Math.ceil(totalEntities / ESTIMATED_CHUNK_SIZE) + Math.ceil(totalRelationships / ESTIMATED_CHUNK_SIZE)
|
|
3743
|
+
);
|
|
3744
|
+
}
|
|
2771
3745
|
var RepositoryImporter = class {
|
|
2772
3746
|
storage;
|
|
2773
3747
|
actorId;
|
|
3748
|
+
onProgress;
|
|
2774
3749
|
migrationEngine = new MigrationEngine();
|
|
2775
3750
|
constructor(config) {
|
|
2776
3751
|
this.storage = config.storage;
|
|
2777
3752
|
this.actorId = config.actorId;
|
|
3753
|
+
this.onProgress = config.onProgress;
|
|
2778
3754
|
}
|
|
2779
3755
|
/**
|
|
2780
3756
|
* Import an export archive according to the provided options.
|
|
@@ -2825,26 +3801,34 @@ var RepositoryImporter = class {
|
|
|
2825
3801
|
return this.importStreamMerge(header, chunks, options, warnings);
|
|
2826
3802
|
}
|
|
2827
3803
|
}
|
|
2828
|
-
/** Streaming create mode —
|
|
3804
|
+
/** Streaming create mode — fast bulk inserts with no existence checks */
|
|
2829
3805
|
async importStreamCreate(header, chunks, options, warnings) {
|
|
2830
3806
|
const target = options.target;
|
|
2831
|
-
const
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
3807
|
+
const existing = await this.storage.getRepository(target.repositoryId);
|
|
3808
|
+
if (!existing) {
|
|
3809
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3810
|
+
await this.storage.createRepository({
|
|
3811
|
+
repositoryId: target.repositoryId,
|
|
3812
|
+
type: target.config.type,
|
|
3813
|
+
label: target.config.label,
|
|
3814
|
+
description: target.config.description,
|
|
3815
|
+
governanceConfig: target.config.governance ?? { mode: "open" },
|
|
3816
|
+
createdAt: now,
|
|
3817
|
+
createdBy: this.actorId
|
|
3818
|
+
});
|
|
3819
|
+
}
|
|
2841
3820
|
await this.storage.saveVocabulary(target.repositoryId, header.vocabulary);
|
|
3821
|
+
const totalEntities = header.manifest?.statistics?.entityCount ?? 0;
|
|
3822
|
+
const totalRelationships = header.manifest?.statistics?.relationshipCount ?? 0;
|
|
3823
|
+
const totalChunks = estimateChunkCount(totalEntities, totalRelationships);
|
|
2842
3824
|
let entitiesImported = 0;
|
|
2843
3825
|
let relationshipsImported = 0;
|
|
3826
|
+
let chunksCompleted = 0;
|
|
2844
3827
|
for await (const chunk of chunks) {
|
|
2845
|
-
const bulkResult = await this.storage.importBulk(target.repositoryId, [chunk]);
|
|
3828
|
+
const bulkResult = await this.storage.importBulk(target.repositoryId, [chunk], { skipExistenceCheck: true });
|
|
2846
3829
|
entitiesImported += bulkResult.entitiesImported;
|
|
2847
3830
|
relationshipsImported += bulkResult.relationshipsImported;
|
|
3831
|
+
chunksCompleted++;
|
|
2848
3832
|
for (const e of bulkResult.errors) {
|
|
2849
3833
|
warnings.push({
|
|
2850
3834
|
code: "import_error",
|
|
@@ -2852,6 +3836,15 @@ var RepositoryImporter = class {
|
|
|
2852
3836
|
id: e.item
|
|
2853
3837
|
});
|
|
2854
3838
|
}
|
|
3839
|
+
await this.onProgress?.({
|
|
3840
|
+
repositoryId: target.repositoryId,
|
|
3841
|
+
entitiesImported,
|
|
3842
|
+
relationshipsImported,
|
|
3843
|
+
totalEntities,
|
|
3844
|
+
totalRelationships,
|
|
3845
|
+
chunksCompleted,
|
|
3846
|
+
totalChunks
|
|
3847
|
+
});
|
|
2855
3848
|
}
|
|
2856
3849
|
return {
|
|
2857
3850
|
success: true,
|
|
@@ -2899,11 +3892,15 @@ var RepositoryImporter = class {
|
|
|
2899
3892
|
if (migrationResult.mergedVocabulary && migrationResult.mergedVocabulary !== targetVocabulary) {
|
|
2900
3893
|
await this.storage.saveVocabulary(repositoryId, migrationResult.mergedVocabulary);
|
|
2901
3894
|
}
|
|
3895
|
+
const totalEntities = header.manifest?.statistics?.entityCount ?? 0;
|
|
3896
|
+
const totalRelationships = header.manifest?.statistics?.relationshipCount ?? 0;
|
|
3897
|
+
const totalChunks = estimateChunkCount(totalEntities, totalRelationships);
|
|
2902
3898
|
const entityConflict = options.entityConflict ?? "skip";
|
|
2903
3899
|
let entitiesImported = 0;
|
|
2904
3900
|
let entitiesSkipped = 0;
|
|
2905
3901
|
let relationshipsImported = 0;
|
|
2906
3902
|
let relationshipsSkipped = 0;
|
|
3903
|
+
let chunksCompleted = 0;
|
|
2907
3904
|
for await (const chunk of chunks) {
|
|
2908
3905
|
if (chunk.entities) {
|
|
2909
3906
|
for (const entity of chunk.entities) {
|
|
@@ -2985,6 +3982,16 @@ var RepositoryImporter = class {
|
|
|
2985
3982
|
}
|
|
2986
3983
|
}
|
|
2987
3984
|
}
|
|
3985
|
+
chunksCompleted++;
|
|
3986
|
+
await this.onProgress?.({
|
|
3987
|
+
repositoryId,
|
|
3988
|
+
entitiesImported,
|
|
3989
|
+
relationshipsImported,
|
|
3990
|
+
totalEntities,
|
|
3991
|
+
totalRelationships,
|
|
3992
|
+
chunksCompleted,
|
|
3993
|
+
totalChunks
|
|
3994
|
+
});
|
|
2988
3995
|
}
|
|
2989
3996
|
const vocabExtensions = migrationResult.warnings.filter(
|
|
2990
3997
|
(w) => w.code === "entity_type_added" || w.code === "relationship_type_added"
|
|
@@ -3038,6 +4045,7 @@ var DeepMemory = class {
|
|
|
3038
4045
|
embedding;
|
|
3039
4046
|
/** Reserved for future distributed locking support */
|
|
3040
4047
|
lock;
|
|
4048
|
+
graphTraversalProvider;
|
|
3041
4049
|
provenance;
|
|
3042
4050
|
globalEventBus;
|
|
3043
4051
|
initialised = false;
|
|
@@ -3046,24 +4054,42 @@ var DeepMemory = class {
|
|
|
3046
4054
|
this.search = config.search;
|
|
3047
4055
|
this.embedding = config.embedding;
|
|
3048
4056
|
this.lock = config.lock;
|
|
4057
|
+
this.graphTraversalProvider = config.graphTraversal;
|
|
3049
4058
|
this.provenance = new ProvenanceTracker(config.provenance);
|
|
3050
4059
|
this.globalEventBus = new EventBus(config.provenance);
|
|
3051
4060
|
}
|
|
3052
|
-
/** Initialise the storage provider (call once before use) */
|
|
4061
|
+
/** Initialise the storage provider and optional providers (call once before use) */
|
|
3053
4062
|
async ensureInitialised() {
|
|
3054
4063
|
if (!this.initialised) {
|
|
3055
4064
|
if (this.storage.initialise) {
|
|
3056
4065
|
await this.storage.initialise();
|
|
3057
4066
|
}
|
|
4067
|
+
if (this.graphTraversalProvider?.initialise) {
|
|
4068
|
+
await this.graphTraversalProvider.initialise();
|
|
4069
|
+
}
|
|
3058
4070
|
this.initialised = true;
|
|
3059
4071
|
}
|
|
3060
4072
|
}
|
|
3061
4073
|
/** Create or migrate the storage schema. Call once at deployment / startup, not per-request. */
|
|
3062
4074
|
async ensureSchema() {
|
|
3063
|
-
|
|
4075
|
+
const noOpResult = {
|
|
4076
|
+
databaseCreated: false,
|
|
4077
|
+
schemaCreated: false,
|
|
4078
|
+
alreadyUpToDate: true,
|
|
4079
|
+
schemaVersion: 0
|
|
4080
|
+
};
|
|
4081
|
+
try {
|
|
4082
|
+
await this.ensureInitialised();
|
|
4083
|
+
} catch {
|
|
4084
|
+
}
|
|
4085
|
+
let result = noOpResult;
|
|
3064
4086
|
if (this.storage.ensureSchema) {
|
|
3065
|
-
await this.storage.ensureSchema();
|
|
4087
|
+
result = await this.storage.ensureSchema();
|
|
3066
4088
|
}
|
|
4089
|
+
if (!this.initialised) {
|
|
4090
|
+
await this.ensureInitialised();
|
|
4091
|
+
}
|
|
4092
|
+
return result;
|
|
3067
4093
|
}
|
|
3068
4094
|
validateRepositoryId(repositoryId) {
|
|
3069
4095
|
if (!isValidUuid(repositoryId)) {
|
|
@@ -3120,6 +4146,7 @@ var DeepMemory = class {
|
|
|
3120
4146
|
storage: this.storage,
|
|
3121
4147
|
search: this.search,
|
|
3122
4148
|
embedding: this.embedding,
|
|
4149
|
+
graphTraversal: this.graphTraversalProvider,
|
|
3123
4150
|
vocabularyEngine,
|
|
3124
4151
|
provenanceTracker: this.provenance,
|
|
3125
4152
|
eventBus
|
|
@@ -3151,6 +4178,7 @@ var DeepMemory = class {
|
|
|
3151
4178
|
storage: this.storage,
|
|
3152
4179
|
search: this.search,
|
|
3153
4180
|
embedding: this.embedding,
|
|
4181
|
+
graphTraversal: this.graphTraversalProvider,
|
|
3154
4182
|
vocabularyEngine,
|
|
3155
4183
|
provenanceTracker: this.provenance,
|
|
3156
4184
|
eventBus
|
|
@@ -3188,15 +4216,57 @@ var DeepMemory = class {
|
|
|
3188
4216
|
async deleteRepository(repositoryId) {
|
|
3189
4217
|
await this.ensureInitialised();
|
|
3190
4218
|
this.validateRepositoryId(repositoryId);
|
|
3191
|
-
await this.storage.
|
|
4219
|
+
const stats = await this.storage.getRepositoryStats(repositoryId);
|
|
4220
|
+
await this.globalEventBus.emit("delete:started", {
|
|
4221
|
+
repositoryId,
|
|
4222
|
+
totalEntities: stats.entityCount,
|
|
4223
|
+
totalRelationships: stats.relationshipCount
|
|
4224
|
+
});
|
|
4225
|
+
await this.storage.deleteRepository(
|
|
4226
|
+
repositoryId,
|
|
4227
|
+
(progress) => this.globalEventBus.emit("delete:progress", { repositoryId, ...progress })
|
|
4228
|
+
);
|
|
4229
|
+
await this.globalEventBus.emit("delete:completed", {
|
|
4230
|
+
repositoryId,
|
|
4231
|
+
entitiesDeleted: stats.entityCount,
|
|
4232
|
+
relationshipsDeleted: stats.relationshipCount
|
|
4233
|
+
});
|
|
3192
4234
|
await this.globalEventBus.emit("repository:deleted", { repositoryId });
|
|
3193
4235
|
}
|
|
4236
|
+
/** Delete all entities and relationships in a repository, preserving the repository and vocabulary */
|
|
4237
|
+
async deleteAllContents(repositoryId) {
|
|
4238
|
+
await this.ensureInitialised();
|
|
4239
|
+
this.validateRepositoryId(repositoryId);
|
|
4240
|
+
const stats = await this.storage.getRepositoryStats(repositoryId);
|
|
4241
|
+
await this.globalEventBus.emit("delete:started", {
|
|
4242
|
+
repositoryId,
|
|
4243
|
+
totalEntities: stats.entityCount,
|
|
4244
|
+
totalRelationships: stats.relationshipCount
|
|
4245
|
+
});
|
|
4246
|
+
const result = await this.storage.deleteAllContents(
|
|
4247
|
+
repositoryId,
|
|
4248
|
+
(progress) => this.globalEventBus.emit("delete:progress", { repositoryId, ...progress })
|
|
4249
|
+
);
|
|
4250
|
+
await this.globalEventBus.emit("delete:completed", {
|
|
4251
|
+
repositoryId,
|
|
4252
|
+
entitiesDeleted: result.deletedEntities,
|
|
4253
|
+
relationshipsDeleted: result.deletedRelationships
|
|
4254
|
+
});
|
|
4255
|
+
return result;
|
|
4256
|
+
}
|
|
3194
4257
|
/** Export a repository to a portable archive */
|
|
3195
|
-
async exportRepository(repositoryId) {
|
|
4258
|
+
async exportRepository(repositoryId, options) {
|
|
3196
4259
|
await this.ensureInitialised();
|
|
4260
|
+
const stats = await this.storage.getRepositoryStats(repositoryId);
|
|
4261
|
+
await this.globalEventBus.emit("export:started", {
|
|
4262
|
+
repositoryId,
|
|
4263
|
+
totalEntities: stats.entityCount,
|
|
4264
|
+
totalRelationships: stats.relationshipCount
|
|
4265
|
+
});
|
|
3197
4266
|
const exporter = new RepositoryExporter({
|
|
3198
4267
|
storage: this.storage,
|
|
3199
|
-
provenance: this.provenance.getContext()
|
|
4268
|
+
provenance: this.provenance.getContext(),
|
|
4269
|
+
legal: options?.legal
|
|
3200
4270
|
});
|
|
3201
4271
|
const archive = await exporter.export(repositoryId);
|
|
3202
4272
|
await this.globalEventBus.emit("export:completed", {
|
|
@@ -3209,9 +4279,11 @@ var DeepMemory = class {
|
|
|
3209
4279
|
/** Import a repository from a portable archive */
|
|
3210
4280
|
async importRepository(archive, options) {
|
|
3211
4281
|
await this.ensureInitialised();
|
|
4282
|
+
await this.globalEventBus.emit("import:started", { repositoryId: options.target.repositoryId });
|
|
3212
4283
|
const importer = new RepositoryImporter({
|
|
3213
4284
|
storage: this.storage,
|
|
3214
|
-
actorId: this.provenance.getContext().actorId
|
|
4285
|
+
actorId: this.provenance.getContext().actorId,
|
|
4286
|
+
onProgress: (progress) => this.globalEventBus.emit("import:progress", progress)
|
|
3215
4287
|
});
|
|
3216
4288
|
const result = await importer.import(archive, options);
|
|
3217
4289
|
if (result.success) {
|
|
@@ -3233,21 +4305,51 @@ var DeepMemory = class {
|
|
|
3233
4305
|
* Yields: manifest → vocabulary → entity chunks → relationship chunks.
|
|
3234
4306
|
* Use for large repositories to avoid loading everything into memory.
|
|
3235
4307
|
*/
|
|
3236
|
-
async *exportRepositoryStream(repositoryId) {
|
|
4308
|
+
async *exportRepositoryStream(repositoryId, options) {
|
|
3237
4309
|
await this.ensureInitialised();
|
|
3238
4310
|
const exporter = new RepositoryExporter({
|
|
3239
4311
|
storage: this.storage,
|
|
3240
|
-
provenance: this.provenance.getContext()
|
|
4312
|
+
provenance: this.provenance.getContext(),
|
|
4313
|
+
legal: options?.legal
|
|
3241
4314
|
});
|
|
3242
|
-
await this.
|
|
4315
|
+
const stats = await this.storage.getRepositoryStats(repositoryId);
|
|
4316
|
+
const totalEntities = stats.entityCount;
|
|
4317
|
+
const totalRelationships = stats.relationshipCount;
|
|
4318
|
+
const estimatedChunkSize = 100;
|
|
4319
|
+
const totalChunks = Math.max(
|
|
4320
|
+
1,
|
|
4321
|
+
Math.ceil(totalEntities / estimatedChunkSize) + Math.ceil(totalRelationships / estimatedChunkSize)
|
|
4322
|
+
);
|
|
4323
|
+
await this.globalEventBus.emit("export:started", { repositoryId, totalEntities, totalRelationships });
|
|
3243
4324
|
let entityCount = 0;
|
|
3244
4325
|
let relationshipCount = 0;
|
|
4326
|
+
let chunksCompleted = 0;
|
|
3245
4327
|
for await (const item of exporter.exportStream(repositoryId)) {
|
|
3246
4328
|
yield item;
|
|
3247
4329
|
if (item.type === "entities") {
|
|
3248
4330
|
entityCount += item.data.length;
|
|
4331
|
+
chunksCompleted++;
|
|
4332
|
+
await this.globalEventBus.emit("export:progress", {
|
|
4333
|
+
repositoryId,
|
|
4334
|
+
entitiesExported: entityCount,
|
|
4335
|
+
relationshipsExported: relationshipCount,
|
|
4336
|
+
totalEntities,
|
|
4337
|
+
totalRelationships,
|
|
4338
|
+
chunksCompleted,
|
|
4339
|
+
totalChunks
|
|
4340
|
+
});
|
|
3249
4341
|
} else if (item.type === "relationships") {
|
|
3250
4342
|
relationshipCount += item.data.length;
|
|
4343
|
+
chunksCompleted++;
|
|
4344
|
+
await this.globalEventBus.emit("export:progress", {
|
|
4345
|
+
repositoryId,
|
|
4346
|
+
entitiesExported: entityCount,
|
|
4347
|
+
relationshipsExported: relationshipCount,
|
|
4348
|
+
totalEntities,
|
|
4349
|
+
totalRelationships,
|
|
4350
|
+
chunksCompleted,
|
|
4351
|
+
totalChunks
|
|
4352
|
+
});
|
|
3251
4353
|
}
|
|
3252
4354
|
}
|
|
3253
4355
|
await this.globalEventBus.emit("export:completed", {
|
|
@@ -3264,7 +4366,8 @@ var DeepMemory = class {
|
|
|
3264
4366
|
await this.ensureInitialised();
|
|
3265
4367
|
const importer = new RepositoryImporter({
|
|
3266
4368
|
storage: this.storage,
|
|
3267
|
-
actorId: this.provenance.getContext().actorId
|
|
4369
|
+
actorId: this.provenance.getContext().actorId,
|
|
4370
|
+
onProgress: (progress) => this.globalEventBus.emit("import:progress", progress)
|
|
3268
4371
|
});
|
|
3269
4372
|
await this.globalEventBus.emit("import:started", { repositoryId: options.target.repositoryId });
|
|
3270
4373
|
const result = await importer.importStream(header, chunks, options);
|
|
@@ -3294,6 +4397,9 @@ var DeepMemory = class {
|
|
|
3294
4397
|
/** Dispose of all resources */
|
|
3295
4398
|
async dispose() {
|
|
3296
4399
|
this.globalEventBus.removeAllListeners();
|
|
4400
|
+
if (this.graphTraversalProvider?.dispose) {
|
|
4401
|
+
await this.graphTraversalProvider.dispose();
|
|
4402
|
+
}
|
|
3297
4403
|
if (this.storage.dispose) {
|
|
3298
4404
|
await this.storage.dispose();
|
|
3299
4405
|
}
|
|
@@ -3301,36 +4407,6 @@ var DeepMemory = class {
|
|
|
3301
4407
|
}
|
|
3302
4408
|
};
|
|
3303
4409
|
|
|
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
4410
|
// src/providers-builtin/InMemoryStorageProvider.ts
|
|
3335
4411
|
var InMemoryStorageProvider = class {
|
|
3336
4412
|
stores = /* @__PURE__ */ new Map();
|
|
@@ -3415,12 +4491,21 @@ var InMemoryStorageProvider = class {
|
|
|
3415
4491
|
if (updates.metadata !== void 0) repo.metadata = { ...repo.metadata, ...updates.metadata };
|
|
3416
4492
|
return repo;
|
|
3417
4493
|
}
|
|
3418
|
-
async deleteRepository(repositoryId) {
|
|
4494
|
+
async deleteRepository(repositoryId, _onProgress) {
|
|
3419
4495
|
if (!this.stores.has(repositoryId)) {
|
|
3420
4496
|
throw new RepositoryNotFoundError(repositoryId);
|
|
3421
4497
|
}
|
|
3422
4498
|
this.stores.delete(repositoryId);
|
|
3423
4499
|
}
|
|
4500
|
+
async deleteAllContents(repositoryId, _onProgress) {
|
|
4501
|
+
const store = this.getStore(repositoryId);
|
|
4502
|
+
const deletedEntities = store.entities.size;
|
|
4503
|
+
const deletedRelationships = store.relationships.size;
|
|
4504
|
+
store.entities.clear();
|
|
4505
|
+
store.slugIndex.clear();
|
|
4506
|
+
store.relationships.clear();
|
|
4507
|
+
return { deletedEntities, deletedRelationships };
|
|
4508
|
+
}
|
|
3424
4509
|
async getRepositoryStats(repositoryId) {
|
|
3425
4510
|
const store = this.getStore(repositoryId);
|
|
3426
4511
|
const entityTypeBreakdown = {};
|
|
@@ -3875,7 +4960,7 @@ var InMemoryStorageProvider = class {
|
|
|
3875
4960
|
yield { type: "relationships", data: [], sequence: 0, isLast: true };
|
|
3876
4961
|
}
|
|
3877
4962
|
}
|
|
3878
|
-
async importBulk(repositoryId, data) {
|
|
4963
|
+
async importBulk(repositoryId, data, _options) {
|
|
3879
4964
|
const store = this.getStore(repositoryId);
|
|
3880
4965
|
let entitiesImported = 0;
|
|
3881
4966
|
let relationshipsImported = 0;
|
|
@@ -4026,6 +5111,7 @@ var NoOpEmbeddingProvider = class {
|
|
|
4026
5111
|
};
|
|
4027
5112
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4028
5113
|
0 && (module.exports = {
|
|
5114
|
+
CypherCompiler,
|
|
4029
5115
|
DeepMemory,
|
|
4030
5116
|
DeepMemoryError,
|
|
4031
5117
|
DuplicateEntityError,
|
|
@@ -4035,6 +5121,8 @@ var NoOpEmbeddingProvider = class {
|
|
|
4035
5121
|
EntityNotFoundError,
|
|
4036
5122
|
ExportError,
|
|
4037
5123
|
GovernanceDeniedError,
|
|
5124
|
+
GraphTraversalProviderRequiredError,
|
|
5125
|
+
GremlinCompiler,
|
|
4038
5126
|
ImportError,
|
|
4039
5127
|
InMemorySearchProvider,
|
|
4040
5128
|
InMemoryStorageProvider,
|
|
@@ -4046,6 +5134,9 @@ var NoOpEmbeddingProvider = class {
|
|
|
4046
5134
|
RelationshipConstraintError,
|
|
4047
5135
|
RelationshipNotFoundError,
|
|
4048
5136
|
RepositoryNotFoundError,
|
|
5137
|
+
TraversalTimeoutError,
|
|
5138
|
+
TraversalValidationError,
|
|
5139
|
+
TraversalVocabularyError,
|
|
4049
5140
|
VocabularyValidationError,
|
|
4050
5141
|
generateId,
|
|
4051
5142
|
isValidUuid,
|