@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.js
CHANGED
|
@@ -191,6 +191,52 @@ var ProviderError = class extends DeepMemoryError {
|
|
|
191
191
|
this.name = "ProviderError";
|
|
192
192
|
}
|
|
193
193
|
};
|
|
194
|
+
var GraphTraversalProviderRequiredError = class extends DeepMemoryError {
|
|
195
|
+
constructor() {
|
|
196
|
+
super(
|
|
197
|
+
"GRAPH_TRAVERSAL_PROVIDER_REQUIRED",
|
|
198
|
+
"GraphTraversalProvider required: no graph traversal provider is configured",
|
|
199
|
+
`Provide a GraphTraversalProvider in the DeepMemory config to use native graph queries. Structured traversals via traverse() work without a provider using fallback BFS.`
|
|
200
|
+
);
|
|
201
|
+
this.name = "GraphTraversalProviderRequiredError";
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
var TraversalValidationError = class extends DeepMemoryError {
|
|
205
|
+
errors;
|
|
206
|
+
constructor(errors) {
|
|
207
|
+
super(
|
|
208
|
+
"TRAVERSAL_VALIDATION_FAILED",
|
|
209
|
+
`Traversal validation failed: ${errors.join("; ")}`,
|
|
210
|
+
`Check the TraversalSpec structure. Each spec needs a start (entityId, entityType, or filter), at least one step, and a returnMode.`
|
|
211
|
+
);
|
|
212
|
+
this.name = "TraversalValidationError";
|
|
213
|
+
this.errors = errors;
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
var TraversalVocabularyError = class extends DeepMemoryError {
|
|
217
|
+
unknownTypes;
|
|
218
|
+
constructor(unknownTypes) {
|
|
219
|
+
super(
|
|
220
|
+
"TRAVERSAL_VOCABULARY_ERROR",
|
|
221
|
+
`Traversal references unknown types: ${unknownTypes.join(", ")}`,
|
|
222
|
+
`Use getVocabulary() to see valid entity and relationship types for this repository.`
|
|
223
|
+
);
|
|
224
|
+
this.name = "TraversalVocabularyError";
|
|
225
|
+
this.unknownTypes = unknownTypes;
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
var TraversalTimeoutError = class extends DeepMemoryError {
|
|
229
|
+
timeoutMs;
|
|
230
|
+
constructor(timeoutMs) {
|
|
231
|
+
super(
|
|
232
|
+
"TRAVERSAL_TIMEOUT",
|
|
233
|
+
`Traversal timed out after ${timeoutMs}ms`,
|
|
234
|
+
`Try reducing the traversal depth, adding more specific filters, or increasing the timeout on your GraphTraversalProvider.`
|
|
235
|
+
);
|
|
236
|
+
this.name = "TraversalTimeoutError";
|
|
237
|
+
this.timeoutMs = timeoutMs;
|
|
238
|
+
}
|
|
239
|
+
};
|
|
194
240
|
|
|
195
241
|
// src/entities/IdGenerator.ts
|
|
196
242
|
function slugify(text) {
|
|
@@ -231,50 +277,54 @@ var EntityManager = class {
|
|
|
231
277
|
this.storage = storage;
|
|
232
278
|
this.embedding = embedding;
|
|
233
279
|
}
|
|
234
|
-
/** Create
|
|
235
|
-
async create(
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
if (hookResult.cancelled) {
|
|
249
|
-
throw new OperationCancelledError("Entity creation", hookResult.reason ?? "cancelled by hook");
|
|
250
|
-
}
|
|
251
|
-
const id = input.id ?? generateEntityId();
|
|
252
|
-
const slug = await generateUniqueSlug(
|
|
253
|
-
input.entityType,
|
|
254
|
-
input.label,
|
|
255
|
-
async (candidateSlug) => {
|
|
256
|
-
const existing = await this.storage.getEntityBySlug(this.repositoryId, candidateSlug);
|
|
257
|
-
return existing !== null;
|
|
280
|
+
/** Create one or more entities with vocabulary validation, ID generation, provenance, and events */
|
|
281
|
+
async create(inputs) {
|
|
282
|
+
const results = [];
|
|
283
|
+
for (const input of inputs) {
|
|
284
|
+
const validation = await this.vocabularyEngine.validateEntity(input);
|
|
285
|
+
if (!validation.valid) {
|
|
286
|
+
const errorMsg = validation.errors.map((e) => e.message).join("; ");
|
|
287
|
+
const suggestions = validation.errors.filter((e) => e.suggestion).map((e) => e.suggestion);
|
|
288
|
+
await this.eventBus.emit("validation:failed", {
|
|
289
|
+
operation: "createEntity",
|
|
290
|
+
error: errorMsg,
|
|
291
|
+
suggestions
|
|
292
|
+
});
|
|
293
|
+
throw new VocabularyValidationError(validation.errors);
|
|
258
294
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
id
|
|
264
|
-
slug
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
295
|
+
const hookResult = await this.eventBus.emitHook("entity:creating", { input });
|
|
296
|
+
if (hookResult.cancelled) {
|
|
297
|
+
throw new OperationCancelledError("Entity creation", hookResult.reason ?? "cancelled by hook");
|
|
298
|
+
}
|
|
299
|
+
const id = input.id ?? generateEntityId();
|
|
300
|
+
const slug = await generateUniqueSlug(
|
|
301
|
+
input.entityType,
|
|
302
|
+
input.label,
|
|
303
|
+
async (candidateSlug) => {
|
|
304
|
+
const existing = await this.storage.getEntityBySlug(this.repositoryId, candidateSlug);
|
|
305
|
+
return existing !== null;
|
|
306
|
+
}
|
|
307
|
+
);
|
|
308
|
+
const provenance = this.provenanceTracker.stampCreate();
|
|
309
|
+
const entityEmbedding = await this.generateEmbedding(input.label, input.summary);
|
|
310
|
+
const storedEntity = {
|
|
311
|
+
id,
|
|
312
|
+
slug,
|
|
313
|
+
entityType: input.entityType,
|
|
314
|
+
label: input.label,
|
|
315
|
+
summary: input.summary,
|
|
316
|
+
properties: input.properties ?? {},
|
|
317
|
+
data: input.data,
|
|
318
|
+
dataFormat: input.dataFormat,
|
|
319
|
+
provenance,
|
|
320
|
+
embedding: entityEmbedding
|
|
321
|
+
};
|
|
322
|
+
const created = await this.storage.createEntity(this.repositoryId, storedEntity);
|
|
323
|
+
const entity = storedToEntity(created);
|
|
324
|
+
await this.eventBus.emit("entity:created", { entity });
|
|
325
|
+
results.push(entity);
|
|
326
|
+
}
|
|
327
|
+
return results;
|
|
278
328
|
}
|
|
279
329
|
/** Update an existing entity */
|
|
280
330
|
async update(entityId, updates) {
|
|
@@ -606,55 +656,59 @@ var RelationshipManager = class {
|
|
|
606
656
|
this.eventBus = eventBus;
|
|
607
657
|
this.storage = storage;
|
|
608
658
|
}
|
|
609
|
-
/** Create
|
|
610
|
-
async create(
|
|
611
|
-
const
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
await this.eventBus.emit("validation:failed", {
|
|
626
|
-
operation: "createRelationship",
|
|
627
|
-
error: validation.errors.map((e) => e.message).join("; "),
|
|
628
|
-
suggestions: validation.errors.filter((e) => e.suggestion).map((e) => e.suggestion)
|
|
629
|
-
});
|
|
630
|
-
throw new VocabularyValidationError(validation.errors);
|
|
631
|
-
}
|
|
632
|
-
const hookResult = await this.eventBus.emitHook("relationship:creating", { input });
|
|
633
|
-
if (hookResult.cancelled) {
|
|
634
|
-
throw new OperationCancelledError(
|
|
635
|
-
"Relationship creation",
|
|
636
|
-
hookResult.reason ?? "cancelled by hook"
|
|
659
|
+
/** Create one or more relationships with vocabulary validation, provenance, and events */
|
|
660
|
+
async create(inputs) {
|
|
661
|
+
const results = [];
|
|
662
|
+
for (const input of inputs) {
|
|
663
|
+
const sourceEntity = await this.storage.getEntity(this.repositoryId, input.sourceEntityId);
|
|
664
|
+
if (!sourceEntity) {
|
|
665
|
+
throw new EntityNotFoundError(input.sourceEntityId);
|
|
666
|
+
}
|
|
667
|
+
const targetEntity = await this.storage.getEntity(this.repositoryId, input.targetEntityId);
|
|
668
|
+
if (!targetEntity) {
|
|
669
|
+
throw new EntityNotFoundError(input.targetEntityId);
|
|
670
|
+
}
|
|
671
|
+
const validation = await this.vocabularyEngine.validateRelationship(
|
|
672
|
+
input,
|
|
673
|
+
sourceEntity.entityType,
|
|
674
|
+
targetEntity.entityType
|
|
637
675
|
);
|
|
676
|
+
if (!validation.valid) {
|
|
677
|
+
await this.eventBus.emit("validation:failed", {
|
|
678
|
+
operation: "createRelationship",
|
|
679
|
+
error: validation.errors.map((e) => e.message).join("; "),
|
|
680
|
+
suggestions: validation.errors.filter((e) => e.suggestion).map((e) => e.suggestion)
|
|
681
|
+
});
|
|
682
|
+
throw new VocabularyValidationError(validation.errors);
|
|
683
|
+
}
|
|
684
|
+
const hookResult = await this.eventBus.emitHook("relationship:creating", { input });
|
|
685
|
+
if (hookResult.cancelled) {
|
|
686
|
+
throw new OperationCancelledError(
|
|
687
|
+
"Relationship creation",
|
|
688
|
+
hookResult.reason ?? "cancelled by hook"
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
const normalizedType = toScreamingSnakeCase(input.relationshipType);
|
|
692
|
+
const vocabulary = await this.vocabularyEngine.getVocabulary();
|
|
693
|
+
const relType = vocabulary.relationshipTypes.find((rt) => rt.type === normalizedType);
|
|
694
|
+
const bidirectional = relType?.bidirectional ?? false;
|
|
695
|
+
const id = input.id ?? generateRelationshipId();
|
|
696
|
+
const provenance = this.provenanceTracker.stampCreate();
|
|
697
|
+
const storedRelationship = {
|
|
698
|
+
id,
|
|
699
|
+
relationshipType: normalizedType,
|
|
700
|
+
sourceEntityId: input.sourceEntityId,
|
|
701
|
+
targetEntityId: input.targetEntityId,
|
|
702
|
+
properties: input.properties ?? {},
|
|
703
|
+
bidirectional,
|
|
704
|
+
provenance
|
|
705
|
+
};
|
|
706
|
+
const created = await this.storage.createRelationship(this.repositoryId, storedRelationship);
|
|
707
|
+
const relationship = storedToRelationship(created);
|
|
708
|
+
await this.eventBus.emit("relationship:created", { relationship });
|
|
709
|
+
results.push(relationship);
|
|
638
710
|
}
|
|
639
|
-
|
|
640
|
-
const vocabulary = await this.vocabularyEngine.getVocabulary();
|
|
641
|
-
const relType = vocabulary.relationshipTypes.find((rt) => rt.type === normalizedType);
|
|
642
|
-
const bidirectional = relType?.bidirectional ?? false;
|
|
643
|
-
const id = input.id ?? generateRelationshipId();
|
|
644
|
-
const provenance = this.provenanceTracker.stampCreate();
|
|
645
|
-
const storedRelationship = {
|
|
646
|
-
id,
|
|
647
|
-
relationshipType: normalizedType,
|
|
648
|
-
sourceEntityId: input.sourceEntityId,
|
|
649
|
-
targetEntityId: input.targetEntityId,
|
|
650
|
-
properties: input.properties ?? {},
|
|
651
|
-
bidirectional,
|
|
652
|
-
provenance
|
|
653
|
-
};
|
|
654
|
-
const created = await this.storage.createRelationship(this.repositoryId, storedRelationship);
|
|
655
|
-
const relationship = storedToRelationship(created);
|
|
656
|
-
await this.eventBus.emit("relationship:created", { relationship });
|
|
657
|
-
return relationship;
|
|
711
|
+
return results;
|
|
658
712
|
}
|
|
659
713
|
/** Remove a relationship by ID */
|
|
660
714
|
async remove(relationshipId) {
|
|
@@ -735,11 +789,806 @@ function projectEntity(stored, level) {
|
|
|
735
789
|
}
|
|
736
790
|
}
|
|
737
791
|
|
|
792
|
+
// src/relationships/TraversalValidator.ts
|
|
793
|
+
var DEFAULT_MAX_STEPS = 6;
|
|
794
|
+
var DEFAULT_MAX_LIMIT = 200;
|
|
795
|
+
var DEFAULT_FALLBACK_MAX_DEPTH = 10;
|
|
796
|
+
function validateTraversalSpec(spec, vocabulary, capabilities) {
|
|
797
|
+
const errors = [];
|
|
798
|
+
const steps = spec.steps ?? [];
|
|
799
|
+
if (!spec.start) {
|
|
800
|
+
errors.push("start is required");
|
|
801
|
+
} else {
|
|
802
|
+
const hasEntityId = spec.start.entityId !== void 0 && spec.start.entityId !== "";
|
|
803
|
+
const hasEntityType = spec.start.entityType !== void 0 && spec.start.entityType !== "";
|
|
804
|
+
const hasFilter = spec.start.filter !== void 0 && spec.start.filter.length > 0;
|
|
805
|
+
if (!hasEntityId && !hasEntityType && !hasFilter) {
|
|
806
|
+
errors.push("start must have at least one of entityId, entityType, or filter");
|
|
807
|
+
}
|
|
808
|
+
if (hasEntityType && !hasEntityId && (spec.limit === void 0 || spec.limit === 0)) {
|
|
809
|
+
errors.push("start.entityType without entityId requires limit on the spec to prevent full type scans");
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
const maxSteps = capabilities?.maxTraversalDepth ?? DEFAULT_MAX_STEPS;
|
|
813
|
+
if (steps.length > maxSteps) {
|
|
814
|
+
errors.push(`steps length ${steps.length} exceeds maximum ${maxSteps}`);
|
|
815
|
+
}
|
|
816
|
+
for (let i = 0; i < steps.length; i++) {
|
|
817
|
+
const step = steps[i];
|
|
818
|
+
if (!step.direction || !["out", "in", "both"].includes(step.direction)) {
|
|
819
|
+
errors.push(`steps[${i}].direction must be 'out', 'in', or 'both'`);
|
|
820
|
+
}
|
|
821
|
+
if (step.repeat) {
|
|
822
|
+
if (step.repeat.maxDepth === void 0 || step.repeat.maxDepth <= 0) {
|
|
823
|
+
errors.push(`steps[${i}].repeat.maxDepth must be a positive number`);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
if (steps.length > 0) {
|
|
828
|
+
const maxProviderDepth = capabilities?.maxTraversalDepth ?? DEFAULT_FALLBACK_MAX_DEPTH;
|
|
829
|
+
let totalDepth = 0;
|
|
830
|
+
for (const step of steps) {
|
|
831
|
+
totalDepth += step.repeat ? step.repeat.maxDepth : 1;
|
|
832
|
+
}
|
|
833
|
+
if (totalDepth > maxProviderDepth) {
|
|
834
|
+
errors.push(`total potential depth ${totalDepth} exceeds provider maximum ${maxProviderDepth}`);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
if (spec.returnMode === "path" && steps.length === 0) {
|
|
838
|
+
errors.push("returnMode 'path' requires at least one step");
|
|
839
|
+
}
|
|
840
|
+
if (spec.returnMode && !["terminal", "path", "all"].includes(spec.returnMode)) {
|
|
841
|
+
errors.push(`returnMode must be 'terminal', 'path', or 'all'`);
|
|
842
|
+
}
|
|
843
|
+
if (spec.projection) {
|
|
844
|
+
if (!spec.projection.properties || spec.projection.properties.length === 0) {
|
|
845
|
+
errors.push("projection.properties must contain at least one property name");
|
|
846
|
+
}
|
|
847
|
+
if (spec.projection.mode && !["count", "values"].includes(spec.projection.mode)) {
|
|
848
|
+
errors.push("projection.mode must be 'count' or 'values'");
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
if (spec.limit !== void 0) {
|
|
852
|
+
if (spec.limit < 1 || spec.limit > DEFAULT_MAX_LIMIT) {
|
|
853
|
+
errors.push(`limit must be between 1 and ${DEFAULT_MAX_LIMIT}`);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
if (spec.offset !== void 0 && spec.offset < 0) {
|
|
857
|
+
errors.push("offset must be >= 0");
|
|
858
|
+
}
|
|
859
|
+
if (vocabulary) {
|
|
860
|
+
const entityTypeSet = new Set(vocabulary.entityTypes.map((et) => et.type));
|
|
861
|
+
const relationshipTypeSet = new Set(vocabulary.relationshipTypes.map((rt) => rt.type));
|
|
862
|
+
const unknownTypes = [];
|
|
863
|
+
if (spec.start?.entityType && !entityTypeSet.has(spec.start.entityType)) {
|
|
864
|
+
unknownTypes.push(`entity type "${spec.start.entityType}"`);
|
|
865
|
+
}
|
|
866
|
+
for (let i = 0; i < steps.length; i++) {
|
|
867
|
+
const step = steps[i];
|
|
868
|
+
if (step.relationshipTypes) {
|
|
869
|
+
for (const rt of step.relationshipTypes) {
|
|
870
|
+
if (!relationshipTypeSet.has(rt)) {
|
|
871
|
+
unknownTypes.push(`relationship type "${rt}" in steps[${i}]`);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
if (step.entityTypes) {
|
|
876
|
+
for (const et of step.entityTypes) {
|
|
877
|
+
if (!entityTypeSet.has(et)) {
|
|
878
|
+
unknownTypes.push(`entity type "${et}" in steps[${i}]`);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
if (unknownTypes.length > 0) {
|
|
884
|
+
errors.push(`Unknown vocabulary types: ${unknownTypes.join(", ")}`);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
return {
|
|
888
|
+
valid: errors.length === 0,
|
|
889
|
+
errors
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
// src/relationships/compilers/GremlinCompiler.ts
|
|
894
|
+
var DEFAULT_ESTIMATED_FANOUT_PER_HOP = 10;
|
|
895
|
+
var GremlinCompiler = class {
|
|
896
|
+
language = "gremlin";
|
|
897
|
+
compile(spec, _vocabulary) {
|
|
898
|
+
const parts = [];
|
|
899
|
+
const params = {};
|
|
900
|
+
let paramIndex = 0;
|
|
901
|
+
let estimatedFanOut = 1;
|
|
902
|
+
const nextParam = (value) => {
|
|
903
|
+
const name = `p${paramIndex++}`;
|
|
904
|
+
params[name] = value;
|
|
905
|
+
return name;
|
|
906
|
+
};
|
|
907
|
+
parts.push("g.V()");
|
|
908
|
+
if (spec.start.entityId) {
|
|
909
|
+
const p = nextParam(spec.start.entityId);
|
|
910
|
+
parts.push(`.has('id', ${p})`);
|
|
911
|
+
} else if (spec.start.entityType) {
|
|
912
|
+
const p = nextParam(spec.start.entityType);
|
|
913
|
+
parts.push(`.has('entityType', ${p})`);
|
|
914
|
+
estimatedFanOut *= DEFAULT_ESTIMATED_FANOUT_PER_HOP;
|
|
915
|
+
}
|
|
916
|
+
if (spec.start.filter) {
|
|
917
|
+
for (const f of spec.start.filter) {
|
|
918
|
+
parts.push(compilePropertyFilter(f, nextParam));
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
const steps = spec.steps ?? [];
|
|
922
|
+
for (const step of steps) {
|
|
923
|
+
if (step.repeat) {
|
|
924
|
+
parts.push(compileRepeatStep(step, nextParam));
|
|
925
|
+
estimatedFanOut *= step.repeat.maxDepth * DEFAULT_ESTIMATED_FANOUT_PER_HOP;
|
|
926
|
+
} else if (step.relationshipFilter && step.relationshipFilter.length > 0) {
|
|
927
|
+
parts.push(compileEdgeStep(step, nextParam));
|
|
928
|
+
estimatedFanOut *= DEFAULT_ESTIMATED_FANOUT_PER_HOP;
|
|
929
|
+
} else {
|
|
930
|
+
parts.push(compileSimpleStep(step, nextParam));
|
|
931
|
+
estimatedFanOut *= DEFAULT_ESTIMATED_FANOUT_PER_HOP;
|
|
932
|
+
}
|
|
933
|
+
if (step.entityTypes && step.entityTypes.length > 0) {
|
|
934
|
+
const typeParams = step.entityTypes.map((t) => nextParam(t));
|
|
935
|
+
parts.push(`.has('entityType', within(${typeParams.join(", ")}))`);
|
|
936
|
+
}
|
|
937
|
+
if (step.entityFilter) {
|
|
938
|
+
for (const f of step.entityFilter) {
|
|
939
|
+
parts.push(compilePropertyFilter(f, nextParam));
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
if (spec.returnMode === "all") {
|
|
944
|
+
}
|
|
945
|
+
if (spec.dedup !== false) {
|
|
946
|
+
parts.push(".dedup()");
|
|
947
|
+
}
|
|
948
|
+
const limit = spec.limit ?? 50;
|
|
949
|
+
const offset = spec.offset ?? 0;
|
|
950
|
+
const pOffset = nextParam(offset);
|
|
951
|
+
const pEnd = nextParam(offset + limit);
|
|
952
|
+
parts.push(`.range(${pOffset}, ${pEnd})`);
|
|
953
|
+
params["_limit"] = limit;
|
|
954
|
+
params["_offset"] = offset;
|
|
955
|
+
if (spec.returnMode === "path") {
|
|
956
|
+
parts.push(".path()");
|
|
957
|
+
}
|
|
958
|
+
if (spec.returnMode !== "path") {
|
|
959
|
+
parts.push(".valueMap(true)");
|
|
960
|
+
}
|
|
961
|
+
return {
|
|
962
|
+
query: parts.join(""),
|
|
963
|
+
params,
|
|
964
|
+
estimatedFanOut: Math.min(estimatedFanOut, 1e4)
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
};
|
|
968
|
+
function compileSimpleStep(step, nextParam) {
|
|
969
|
+
const types = step.relationshipTypes;
|
|
970
|
+
const typeArgs = types ? types.map((t) => nextParam(t)).join(", ") : "";
|
|
971
|
+
switch (step.direction) {
|
|
972
|
+
case "out":
|
|
973
|
+
return types ? `.out(${typeArgs})` : ".out()";
|
|
974
|
+
case "in":
|
|
975
|
+
return types ? `.in(${typeArgs})` : ".in()";
|
|
976
|
+
case "both":
|
|
977
|
+
return types ? `.both(${typeArgs})` : ".both()";
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
function compileEdgeStep(step, nextParam) {
|
|
981
|
+
const parts = [];
|
|
982
|
+
const types = step.relationshipTypes;
|
|
983
|
+
const typeArgs = types ? types.map((t) => nextParam(t)).join(", ") : "";
|
|
984
|
+
switch (step.direction) {
|
|
985
|
+
case "out":
|
|
986
|
+
parts.push(types ? `.outE(${typeArgs})` : ".outE()");
|
|
987
|
+
break;
|
|
988
|
+
case "in":
|
|
989
|
+
parts.push(types ? `.inE(${typeArgs})` : ".inE()");
|
|
990
|
+
break;
|
|
991
|
+
case "both":
|
|
992
|
+
parts.push(types ? `.bothE(${typeArgs})` : ".bothE()");
|
|
993
|
+
break;
|
|
994
|
+
}
|
|
995
|
+
if (step.relationshipFilter) {
|
|
996
|
+
for (const f of step.relationshipFilter) {
|
|
997
|
+
parts.push(compilePropertyFilter(f, nextParam));
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
switch (step.direction) {
|
|
1001
|
+
case "out":
|
|
1002
|
+
parts.push(".inV()");
|
|
1003
|
+
break;
|
|
1004
|
+
case "in":
|
|
1005
|
+
parts.push(".outV()");
|
|
1006
|
+
break;
|
|
1007
|
+
case "both":
|
|
1008
|
+
parts.push(".otherV()");
|
|
1009
|
+
break;
|
|
1010
|
+
}
|
|
1011
|
+
return parts.join("");
|
|
1012
|
+
}
|
|
1013
|
+
function compileRepeatStep(step, nextParam) {
|
|
1014
|
+
const parts = [];
|
|
1015
|
+
const innerStep = step.relationshipFilter?.length ? compileEdgeStep({ ...step, repeat: void 0 }, nextParam) : compileSimpleStep(step, nextParam);
|
|
1016
|
+
if (step.repeat?.emitIntermediates !== false) {
|
|
1017
|
+
parts.push(".emit()");
|
|
1018
|
+
}
|
|
1019
|
+
parts.push(`.repeat(${innerStep.startsWith(".") ? `__${innerStep}` : innerStep})`);
|
|
1020
|
+
if (step.repeat?.until && step.repeat.until.length > 0) {
|
|
1021
|
+
const untilParts = step.repeat.until.map((f) => compilePropertyFilter(f, nextParam));
|
|
1022
|
+
parts.push(`.until(${untilParts.join("")})`);
|
|
1023
|
+
}
|
|
1024
|
+
if (step.repeat?.maxDepth) {
|
|
1025
|
+
const p = nextParam(step.repeat.maxDepth);
|
|
1026
|
+
parts.push(`.times(${p})`);
|
|
1027
|
+
}
|
|
1028
|
+
if (step.repeat?.emitIntermediates === false) {
|
|
1029
|
+
parts.push(".emit()");
|
|
1030
|
+
}
|
|
1031
|
+
return parts.join("");
|
|
1032
|
+
}
|
|
1033
|
+
function compilePropertyFilter(filter, nextParam) {
|
|
1034
|
+
const key = nextParam(filter.key);
|
|
1035
|
+
switch (filter.operator) {
|
|
1036
|
+
case "eq": {
|
|
1037
|
+
const val = nextParam(filter.value);
|
|
1038
|
+
return `.has(${key}, ${val})`;
|
|
1039
|
+
}
|
|
1040
|
+
case "neq": {
|
|
1041
|
+
const val = nextParam(filter.value);
|
|
1042
|
+
return `.has(${key}, neq(${val}))`;
|
|
1043
|
+
}
|
|
1044
|
+
case "gt": {
|
|
1045
|
+
const val = nextParam(filter.value);
|
|
1046
|
+
return `.has(${key}, gt(${val}))`;
|
|
1047
|
+
}
|
|
1048
|
+
case "gte": {
|
|
1049
|
+
const val = nextParam(filter.value);
|
|
1050
|
+
return `.has(${key}, gte(${val}))`;
|
|
1051
|
+
}
|
|
1052
|
+
case "lt": {
|
|
1053
|
+
const val = nextParam(filter.value);
|
|
1054
|
+
return `.has(${key}, lt(${val}))`;
|
|
1055
|
+
}
|
|
1056
|
+
case "lte": {
|
|
1057
|
+
const val = nextParam(filter.value);
|
|
1058
|
+
return `.has(${key}, lte(${val}))`;
|
|
1059
|
+
}
|
|
1060
|
+
case "contains": {
|
|
1061
|
+
const val = nextParam(filter.value);
|
|
1062
|
+
return `.has(${key}, containing(${val}))`;
|
|
1063
|
+
}
|
|
1064
|
+
case "isNull":
|
|
1065
|
+
return `.hasNot(${key})`;
|
|
1066
|
+
case "isNotNull":
|
|
1067
|
+
return `.has(${key})`;
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
// src/relationships/compilers/CypherCompiler.ts
|
|
1072
|
+
var DEFAULT_ESTIMATED_FANOUT_PER_HOP2 = 10;
|
|
1073
|
+
var CypherCompiler = class {
|
|
1074
|
+
language = "cypher";
|
|
1075
|
+
compile(spec, _vocabulary) {
|
|
1076
|
+
const params = {};
|
|
1077
|
+
let paramIndex = 0;
|
|
1078
|
+
let estimatedFanOut = 1;
|
|
1079
|
+
const whereClauses = [];
|
|
1080
|
+
let nodeIndex = 0;
|
|
1081
|
+
const nextParam = (value) => {
|
|
1082
|
+
const name = `p${paramIndex++}`;
|
|
1083
|
+
params[name] = value;
|
|
1084
|
+
return `$${name}`;
|
|
1085
|
+
};
|
|
1086
|
+
const startNode = `n${nodeIndex++}`;
|
|
1087
|
+
let matchParts = [];
|
|
1088
|
+
if (spec.start.entityId) {
|
|
1089
|
+
matchParts.push(`(${startNode})`);
|
|
1090
|
+
whereClauses.push(`${startNode}.id = ${nextParam(spec.start.entityId)}`);
|
|
1091
|
+
} else if (spec.start.entityType) {
|
|
1092
|
+
matchParts.push(`(${startNode})`);
|
|
1093
|
+
whereClauses.push(`${startNode}.entityType = ${nextParam(spec.start.entityType)}`);
|
|
1094
|
+
estimatedFanOut *= DEFAULT_ESTIMATED_FANOUT_PER_HOP2;
|
|
1095
|
+
} else {
|
|
1096
|
+
matchParts.push(`(${startNode})`);
|
|
1097
|
+
}
|
|
1098
|
+
if (spec.start.filter) {
|
|
1099
|
+
for (const f of spec.start.filter) {
|
|
1100
|
+
whereClauses.push(compilePropertyFilterCypher(startNode, f, nextParam));
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
let lastNode = startNode;
|
|
1104
|
+
const steps = spec.steps ?? [];
|
|
1105
|
+
for (let i = 0; i < steps.length; i++) {
|
|
1106
|
+
const step = steps[i];
|
|
1107
|
+
const targetNode = `n${nodeIndex++}`;
|
|
1108
|
+
const relAlias = `r${i}`;
|
|
1109
|
+
const relTypes = step.relationshipTypes?.length ? step.relationshipTypes.join("|") : "";
|
|
1110
|
+
const relTypePattern = relTypes ? `:${relTypes}` : "";
|
|
1111
|
+
const depthPattern = step.repeat ? `*1..${step.repeat.maxDepth}` : "";
|
|
1112
|
+
let pattern;
|
|
1113
|
+
switch (step.direction) {
|
|
1114
|
+
case "out":
|
|
1115
|
+
pattern = `-[${relAlias}${relTypePattern}${depthPattern}]->(${targetNode})`;
|
|
1116
|
+
break;
|
|
1117
|
+
case "in":
|
|
1118
|
+
pattern = `<-[${relAlias}${relTypePattern}${depthPattern}]-(${targetNode})`;
|
|
1119
|
+
break;
|
|
1120
|
+
case "both":
|
|
1121
|
+
pattern = `-[${relAlias}${relTypePattern}${depthPattern}]-(${targetNode})`;
|
|
1122
|
+
break;
|
|
1123
|
+
}
|
|
1124
|
+
matchParts.push(pattern);
|
|
1125
|
+
estimatedFanOut *= step.repeat ? step.repeat.maxDepth * DEFAULT_ESTIMATED_FANOUT_PER_HOP2 : DEFAULT_ESTIMATED_FANOUT_PER_HOP2;
|
|
1126
|
+
if (step.entityTypes && step.entityTypes.length > 0) {
|
|
1127
|
+
const typeParam = nextParam(step.entityTypes);
|
|
1128
|
+
whereClauses.push(`${targetNode}.entityType IN ${typeParam}`);
|
|
1129
|
+
}
|
|
1130
|
+
if (step.entityFilter) {
|
|
1131
|
+
for (const f of step.entityFilter) {
|
|
1132
|
+
whereClauses.push(compilePropertyFilterCypher(targetNode, f, nextParam));
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
if (step.relationshipFilter) {
|
|
1136
|
+
for (const f of step.relationshipFilter) {
|
|
1137
|
+
whereClauses.push(compilePropertyFilterCypher(relAlias, f, nextParam));
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
lastNode = targetNode;
|
|
1141
|
+
}
|
|
1142
|
+
const matchClause = `MATCH ${matchParts[0]}${matchParts.slice(1).join("")}`;
|
|
1143
|
+
const whereClause = whereClauses.length > 0 ? `
|
|
1144
|
+
WHERE ${whereClauses.join(" AND ")}` : "";
|
|
1145
|
+
let returnClause;
|
|
1146
|
+
if (spec.returnMode === "path") {
|
|
1147
|
+
const allNodes = Array.from({ length: nodeIndex }, (_, i) => `n${i}`);
|
|
1148
|
+
const allRels = steps.map((_, i) => `r${i}`);
|
|
1149
|
+
returnClause = `RETURN ${[...allNodes, ...allRels].join(", ")}`;
|
|
1150
|
+
} else if (spec.returnMode === "all") {
|
|
1151
|
+
const allNodes = Array.from({ length: nodeIndex }, (_, i) => `n${i}`);
|
|
1152
|
+
returnClause = spec.dedup !== false ? `RETURN DISTINCT ${allNodes.join(", ")}` : `RETURN ${allNodes.join(", ")}`;
|
|
1153
|
+
} else {
|
|
1154
|
+
returnClause = spec.dedup !== false ? `RETURN DISTINCT ${lastNode}` : `RETURN ${lastNode}`;
|
|
1155
|
+
}
|
|
1156
|
+
const offset = spec.offset ?? 0;
|
|
1157
|
+
const limit = spec.limit ?? 50;
|
|
1158
|
+
const skipClause = offset > 0 ? `
|
|
1159
|
+
SKIP ${nextParam(offset)}` : "";
|
|
1160
|
+
const limitClause = `
|
|
1161
|
+
LIMIT ${nextParam(limit)}`;
|
|
1162
|
+
params["_limit"] = limit;
|
|
1163
|
+
params["_offset"] = offset;
|
|
1164
|
+
const query = `${matchClause}${whereClause}
|
|
1165
|
+
${returnClause}${skipClause}${limitClause}`;
|
|
1166
|
+
return {
|
|
1167
|
+
query,
|
|
1168
|
+
params,
|
|
1169
|
+
estimatedFanOut: Math.min(estimatedFanOut, 1e4)
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
};
|
|
1173
|
+
function compilePropertyFilterCypher(nodeAlias, filter, nextParam) {
|
|
1174
|
+
const prop = `${nodeAlias}.${filter.key}`;
|
|
1175
|
+
switch (filter.operator) {
|
|
1176
|
+
case "eq":
|
|
1177
|
+
return `${prop} = ${nextParam(filter.value)}`;
|
|
1178
|
+
case "neq":
|
|
1179
|
+
return `${prop} <> ${nextParam(filter.value)}`;
|
|
1180
|
+
case "gt":
|
|
1181
|
+
return `${prop} > ${nextParam(filter.value)}`;
|
|
1182
|
+
case "gte":
|
|
1183
|
+
return `${prop} >= ${nextParam(filter.value)}`;
|
|
1184
|
+
case "lt":
|
|
1185
|
+
return `${prop} < ${nextParam(filter.value)}`;
|
|
1186
|
+
case "lte":
|
|
1187
|
+
return `${prop} <= ${nextParam(filter.value)}`;
|
|
1188
|
+
case "contains":
|
|
1189
|
+
return `${prop} CONTAINS ${nextParam(filter.value)}`;
|
|
1190
|
+
case "isNull":
|
|
1191
|
+
return `${prop} IS NULL`;
|
|
1192
|
+
case "isNotNull":
|
|
1193
|
+
return `${prop} IS NOT NULL`;
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
// src/relationships/PropertyFilterMatcher.ts
|
|
1198
|
+
function matchesPropertyFilters(properties, filters) {
|
|
1199
|
+
return filters.every((filter) => matchesSingleFilter(properties, filter));
|
|
1200
|
+
}
|
|
1201
|
+
function matchesSingleFilter(properties, filter) {
|
|
1202
|
+
const value = properties[filter.key];
|
|
1203
|
+
switch (filter.operator) {
|
|
1204
|
+
case "isNull":
|
|
1205
|
+
return value === null || value === void 0;
|
|
1206
|
+
case "isNotNull":
|
|
1207
|
+
return value !== null && value !== void 0;
|
|
1208
|
+
case "eq":
|
|
1209
|
+
return value === filter.value;
|
|
1210
|
+
case "neq":
|
|
1211
|
+
return value !== filter.value;
|
|
1212
|
+
case "gt":
|
|
1213
|
+
return typeof value === "number" && typeof filter.value === "number" ? value > filter.value : typeof value === "string" && typeof filter.value === "string" ? value > filter.value : false;
|
|
1214
|
+
case "lt":
|
|
1215
|
+
return typeof value === "number" && typeof filter.value === "number" ? value < filter.value : typeof value === "string" && typeof filter.value === "string" ? value < filter.value : false;
|
|
1216
|
+
case "gte":
|
|
1217
|
+
return typeof value === "number" && typeof filter.value === "number" ? value >= filter.value : typeof value === "string" && typeof filter.value === "string" ? value >= filter.value : false;
|
|
1218
|
+
case "lte":
|
|
1219
|
+
return typeof value === "number" && typeof filter.value === "number" ? value <= filter.value : typeof value === "string" && typeof filter.value === "string" ? value <= filter.value : false;
|
|
1220
|
+
case "contains":
|
|
1221
|
+
return typeof value === "string" && typeof filter.value === "string" ? value.toLowerCase().includes(filter.value.toLowerCase()) : false;
|
|
1222
|
+
default:
|
|
1223
|
+
return false;
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
// src/relationships/FallbackTraversalExecutor.ts
|
|
1228
|
+
var MAX_STORAGE_CALLS = 500;
|
|
1229
|
+
var MAX_ENTITY_SCAN = 1e4;
|
|
1230
|
+
var REL_FETCH_LIMIT = 1e3;
|
|
1231
|
+
async function executeFallbackTraversal(repositoryId, storage, spec) {
|
|
1232
|
+
const startTime = Date.now();
|
|
1233
|
+
let storageCalls = 0;
|
|
1234
|
+
const checkCallLimit = () => {
|
|
1235
|
+
if (storageCalls >= MAX_STORAGE_CALLS) {
|
|
1236
|
+
throw new Error(`Fallback traversal exceeded ${MAX_STORAGE_CALLS} storage calls. Consider using a GraphTraversalProvider for large traversals.`);
|
|
1237
|
+
}
|
|
1238
|
+
};
|
|
1239
|
+
const isVertexQuery = !spec.steps || spec.steps.length === 0;
|
|
1240
|
+
const needsFullScan = isVertexQuery || spec.projection !== void 0;
|
|
1241
|
+
const fetchLimit = needsFullScan ? MAX_ENTITY_SCAN : spec.limit ?? 50;
|
|
1242
|
+
let frontier = [];
|
|
1243
|
+
if (spec.start.entityId) {
|
|
1244
|
+
const entity = await resolveEntity(repositoryId, storage, spec.start.entityId);
|
|
1245
|
+
storageCalls++;
|
|
1246
|
+
if (entity) {
|
|
1247
|
+
frontier.push({ entity, entityPath: [entity.id], relationshipPath: [] });
|
|
1248
|
+
} else {
|
|
1249
|
+
throw new EntityNotFoundError(spec.start.entityId);
|
|
1250
|
+
}
|
|
1251
|
+
} else if (spec.start.entityType) {
|
|
1252
|
+
const result = await storage.findEntities(repositoryId, {
|
|
1253
|
+
entityTypes: [spec.start.entityType],
|
|
1254
|
+
limit: fetchLimit,
|
|
1255
|
+
offset: 0
|
|
1256
|
+
});
|
|
1257
|
+
storageCalls++;
|
|
1258
|
+
frontier = result.items.map((e) => ({
|
|
1259
|
+
entity: e,
|
|
1260
|
+
entityPath: [e.id],
|
|
1261
|
+
relationshipPath: []
|
|
1262
|
+
}));
|
|
1263
|
+
} else if (spec.start.filter && spec.start.filter.length > 0) {
|
|
1264
|
+
const result = await storage.findEntities(repositoryId, {
|
|
1265
|
+
limit: fetchLimit,
|
|
1266
|
+
offset: 0
|
|
1267
|
+
});
|
|
1268
|
+
storageCalls++;
|
|
1269
|
+
frontier = result.items.filter((e) => matchesPropertyFilters(e.properties, spec.start.filter)).map((e) => ({
|
|
1270
|
+
entity: e,
|
|
1271
|
+
entityPath: [e.id],
|
|
1272
|
+
relationshipPath: []
|
|
1273
|
+
}));
|
|
1274
|
+
}
|
|
1275
|
+
if (spec.start.filter && spec.start.filter.length > 0 && (spec.start.entityId || spec.start.entityType)) {
|
|
1276
|
+
frontier = frontier.filter(
|
|
1277
|
+
(entry) => matchesPropertyFilters(entry.entity.properties, spec.start.filter)
|
|
1278
|
+
);
|
|
1279
|
+
}
|
|
1280
|
+
const steps = spec.steps ?? [];
|
|
1281
|
+
const allCollected = spec.returnMode === "all" ? [...frontier] : [];
|
|
1282
|
+
for (const step of steps) {
|
|
1283
|
+
checkCallLimit();
|
|
1284
|
+
if (step.repeat) {
|
|
1285
|
+
const result = await executeRepeatStep(
|
|
1286
|
+
repositoryId,
|
|
1287
|
+
storage,
|
|
1288
|
+
frontier,
|
|
1289
|
+
step,
|
|
1290
|
+
() => {
|
|
1291
|
+
storageCalls++;
|
|
1292
|
+
checkCallLimit();
|
|
1293
|
+
}
|
|
1294
|
+
);
|
|
1295
|
+
frontier = result.terminal;
|
|
1296
|
+
if (spec.returnMode === "all") {
|
|
1297
|
+
allCollected.push(...result.intermediates);
|
|
1298
|
+
}
|
|
1299
|
+
} else {
|
|
1300
|
+
frontier = await executeSingleStep(
|
|
1301
|
+
repositoryId,
|
|
1302
|
+
storage,
|
|
1303
|
+
frontier,
|
|
1304
|
+
step,
|
|
1305
|
+
() => {
|
|
1306
|
+
storageCalls++;
|
|
1307
|
+
checkCallLimit();
|
|
1308
|
+
}
|
|
1309
|
+
);
|
|
1310
|
+
if (spec.returnMode === "all") {
|
|
1311
|
+
allCollected.push(...frontier);
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
const detailLevel = spec.detailLevel ?? "summary";
|
|
1316
|
+
const dedup = spec.dedup !== false;
|
|
1317
|
+
const limit = spec.limit ?? 50;
|
|
1318
|
+
const offset = spec.offset ?? 0;
|
|
1319
|
+
let resultEntries;
|
|
1320
|
+
if (spec.returnMode === "all") {
|
|
1321
|
+
resultEntries = allCollected;
|
|
1322
|
+
} else {
|
|
1323
|
+
resultEntries = frontier;
|
|
1324
|
+
}
|
|
1325
|
+
if (dedup) {
|
|
1326
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1327
|
+
resultEntries = resultEntries.filter((entry) => {
|
|
1328
|
+
if (seen.has(entry.entity.id)) return false;
|
|
1329
|
+
seen.add(entry.entity.id);
|
|
1330
|
+
return true;
|
|
1331
|
+
});
|
|
1332
|
+
}
|
|
1333
|
+
const total = resultEntries.length;
|
|
1334
|
+
const paged = resultEntries.slice(offset, offset + limit);
|
|
1335
|
+
const suppressEntities = spec.projection !== void 0 && spec.projection.includeEntities !== true;
|
|
1336
|
+
let entities;
|
|
1337
|
+
if (suppressEntities) {
|
|
1338
|
+
entities = [];
|
|
1339
|
+
} else {
|
|
1340
|
+
entities = paged.map((entry) => {
|
|
1341
|
+
const projected = projectEntity(entry.entity, detailLevel);
|
|
1342
|
+
if (!spec.includeProvenance) {
|
|
1343
|
+
delete projected["provenance"];
|
|
1344
|
+
}
|
|
1345
|
+
return projected;
|
|
1346
|
+
});
|
|
1347
|
+
if (spec.includeRelationshipSummary && entities.length > 0) {
|
|
1348
|
+
const summaryResults = await Promise.all(
|
|
1349
|
+
paged.map(async (entry) => {
|
|
1350
|
+
storageCalls++;
|
|
1351
|
+
const result = await storage.getEntityRelationships(repositoryId, entry.entity.id, {
|
|
1352
|
+
direction: "both",
|
|
1353
|
+
limit: 1e4
|
|
1354
|
+
});
|
|
1355
|
+
const summary = { outbound: {}, inbound: {} };
|
|
1356
|
+
for (const rel of result.items) {
|
|
1357
|
+
if (rel.sourceEntityId === entry.entity.id) {
|
|
1358
|
+
summary.outbound[rel.relationshipType] = (summary.outbound[rel.relationshipType] ?? 0) + 1;
|
|
1359
|
+
}
|
|
1360
|
+
if (rel.targetEntityId === entry.entity.id) {
|
|
1361
|
+
summary.inbound[rel.relationshipType] = (summary.inbound[rel.relationshipType] ?? 0) + 1;
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
return summary;
|
|
1365
|
+
})
|
|
1366
|
+
);
|
|
1367
|
+
entities = entities.map((e, i) => ({ ...e, relationshipSummary: summaryResults[i] }));
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
let aggregations;
|
|
1371
|
+
if (spec.projection) {
|
|
1372
|
+
const propNames = spec.projection.properties;
|
|
1373
|
+
const mode = spec.projection.mode ?? "values";
|
|
1374
|
+
const distinct = spec.projection.distinct ?? false;
|
|
1375
|
+
const sourceEntries = dedup ? (() => {
|
|
1376
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1377
|
+
return (spec.returnMode === "all" ? allCollected : frontier).filter((e) => {
|
|
1378
|
+
if (seen.has(e.entity.id)) return false;
|
|
1379
|
+
seen.add(e.entity.id);
|
|
1380
|
+
return true;
|
|
1381
|
+
});
|
|
1382
|
+
})() : spec.returnMode === "all" ? allCollected : frontier;
|
|
1383
|
+
if (mode === "count" || distinct) {
|
|
1384
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1385
|
+
for (const entry of sourceEntries) {
|
|
1386
|
+
const vals = {};
|
|
1387
|
+
for (const prop of propNames) {
|
|
1388
|
+
vals[prop] = entry.entity.properties[prop] ?? null;
|
|
1389
|
+
}
|
|
1390
|
+
const key = JSON.stringify(vals);
|
|
1391
|
+
const existing = groups.get(key);
|
|
1392
|
+
if (existing) {
|
|
1393
|
+
existing.count++;
|
|
1394
|
+
} else {
|
|
1395
|
+
groups.set(key, { values: vals, count: 1 });
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
aggregations = Array.from(groups.values()).map((g) => ({
|
|
1399
|
+
values: g.values,
|
|
1400
|
+
...mode === "count" ? { count: g.count } : {}
|
|
1401
|
+
}));
|
|
1402
|
+
if (mode === "count") {
|
|
1403
|
+
aggregations.sort((a, b) => (b.count ?? 0) - (a.count ?? 0));
|
|
1404
|
+
}
|
|
1405
|
+
} else {
|
|
1406
|
+
aggregations = sourceEntries.map((entry) => {
|
|
1407
|
+
const vals = {};
|
|
1408
|
+
for (const prop of propNames) {
|
|
1409
|
+
vals[prop] = entry.entity.properties[prop] ?? null;
|
|
1410
|
+
}
|
|
1411
|
+
return { values: vals };
|
|
1412
|
+
});
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
let relationships;
|
|
1416
|
+
let paths;
|
|
1417
|
+
if (!suppressEntities && (spec.returnMode === "path" || spec.returnMode === "all")) {
|
|
1418
|
+
const relMap = /* @__PURE__ */ new Map();
|
|
1419
|
+
for (const entry of paged) {
|
|
1420
|
+
for (const rel of entry.relationshipPath) {
|
|
1421
|
+
if (!relMap.has(rel.id)) {
|
|
1422
|
+
relMap.set(rel.id, {
|
|
1423
|
+
id: rel.id,
|
|
1424
|
+
type: rel.relationshipType,
|
|
1425
|
+
sourceEntityId: rel.sourceEntityId,
|
|
1426
|
+
targetEntityId: rel.targetEntityId,
|
|
1427
|
+
direction: "outbound",
|
|
1428
|
+
properties: rel.properties
|
|
1429
|
+
});
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
relationships = Array.from(relMap.values());
|
|
1434
|
+
}
|
|
1435
|
+
if (!suppressEntities && spec.returnMode === "path") {
|
|
1436
|
+
paths = paged.map((entry) => ({
|
|
1437
|
+
length: entry.entityPath.length - 1,
|
|
1438
|
+
entities: [(() => {
|
|
1439
|
+
const e = projectEntity(entry.entity, detailLevel);
|
|
1440
|
+
if (!spec.includeProvenance) delete e["provenance"];
|
|
1441
|
+
return e;
|
|
1442
|
+
})()],
|
|
1443
|
+
relationships: entry.relationshipPath.map((rel) => ({
|
|
1444
|
+
id: rel.id,
|
|
1445
|
+
type: rel.relationshipType,
|
|
1446
|
+
sourceEntityId: rel.sourceEntityId,
|
|
1447
|
+
targetEntityId: rel.targetEntityId,
|
|
1448
|
+
direction: "outbound",
|
|
1449
|
+
properties: rel.properties
|
|
1450
|
+
}))
|
|
1451
|
+
}));
|
|
1452
|
+
}
|
|
1453
|
+
const executionTimeMs = Date.now() - startTime;
|
|
1454
|
+
return {
|
|
1455
|
+
entities,
|
|
1456
|
+
relationships,
|
|
1457
|
+
paths,
|
|
1458
|
+
aggregations,
|
|
1459
|
+
total,
|
|
1460
|
+
returned: paged.length,
|
|
1461
|
+
hasMore: offset + limit < total,
|
|
1462
|
+
queryMetadata: {
|
|
1463
|
+
executionTimeMs,
|
|
1464
|
+
appliedLimits: {
|
|
1465
|
+
maxResults: limit
|
|
1466
|
+
},
|
|
1467
|
+
truncated: total > limit + offset,
|
|
1468
|
+
truncationReason: total > limit + offset ? "result_limit" : void 0
|
|
1469
|
+
}
|
|
1470
|
+
};
|
|
1471
|
+
}
|
|
1472
|
+
async function executeSingleStep(repositoryId, storage, frontier, step, onStorageCall) {
|
|
1473
|
+
const direction = mapDirection(step.direction);
|
|
1474
|
+
const pendingEdges = [];
|
|
1475
|
+
for (const entry of frontier) {
|
|
1476
|
+
const rels = await storage.getEntityRelationships(repositoryId, entry.entity.id, {
|
|
1477
|
+
direction,
|
|
1478
|
+
relationshipTypes: step.relationshipTypes,
|
|
1479
|
+
limit: REL_FETCH_LIMIT
|
|
1480
|
+
});
|
|
1481
|
+
onStorageCall();
|
|
1482
|
+
for (const rel of rels.items) {
|
|
1483
|
+
if (step.relationshipFilter && step.relationshipFilter.length > 0) {
|
|
1484
|
+
if (!matchesPropertyFilters(rel.properties, step.relationshipFilter)) {
|
|
1485
|
+
continue;
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
const targetId = getTargetId(rel, entry.entity.id);
|
|
1489
|
+
pendingEdges.push({ entry, rel, targetId });
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
if (pendingEdges.length === 0) return [];
|
|
1493
|
+
const uniqueTargetIds = [...new Set(pendingEdges.map((e) => e.targetId))];
|
|
1494
|
+
const entityMap = await storage.getEntities(repositoryId, uniqueTargetIds);
|
|
1495
|
+
onStorageCall();
|
|
1496
|
+
const nextFrontier = [];
|
|
1497
|
+
for (const { entry, rel, targetId } of pendingEdges) {
|
|
1498
|
+
const targetEntity = entityMap.get(targetId);
|
|
1499
|
+
if (!targetEntity) continue;
|
|
1500
|
+
if (step.entityTypes && step.entityTypes.length > 0) {
|
|
1501
|
+
if (!step.entityTypes.includes(targetEntity.entityType)) continue;
|
|
1502
|
+
}
|
|
1503
|
+
if (step.entityFilter && step.entityFilter.length > 0) {
|
|
1504
|
+
if (!matchesPropertyFilters(targetEntity.properties, step.entityFilter)) continue;
|
|
1505
|
+
}
|
|
1506
|
+
nextFrontier.push({
|
|
1507
|
+
entity: targetEntity,
|
|
1508
|
+
entityPath: [...entry.entityPath, targetEntity.id],
|
|
1509
|
+
relationshipPath: [...entry.relationshipPath, rel]
|
|
1510
|
+
});
|
|
1511
|
+
}
|
|
1512
|
+
return nextFrontier;
|
|
1513
|
+
}
|
|
1514
|
+
async function executeRepeatStep(repositoryId, storage, frontier, step, onStorageCall) {
|
|
1515
|
+
const maxDepth = step.repeat?.maxDepth ?? 1;
|
|
1516
|
+
const emitIntermediates = step.repeat?.emitIntermediates !== false;
|
|
1517
|
+
const untilFilters = step.repeat?.until;
|
|
1518
|
+
let current = frontier;
|
|
1519
|
+
const allIntermediates = [];
|
|
1520
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1521
|
+
for (const entry of frontier) {
|
|
1522
|
+
visited.add(entry.entity.id);
|
|
1523
|
+
}
|
|
1524
|
+
for (let depth = 0; depth < maxDepth; depth++) {
|
|
1525
|
+
const next = await executeSingleStep(repositoryId, storage, current, step, onStorageCall);
|
|
1526
|
+
const unvisited = next.filter((entry) => {
|
|
1527
|
+
if (visited.has(entry.entity.id)) return false;
|
|
1528
|
+
visited.add(entry.entity.id);
|
|
1529
|
+
return true;
|
|
1530
|
+
});
|
|
1531
|
+
if (unvisited.length === 0) break;
|
|
1532
|
+
if (untilFilters && untilFilters.length > 0) {
|
|
1533
|
+
const matching = [];
|
|
1534
|
+
const continuing = [];
|
|
1535
|
+
for (const entry of unvisited) {
|
|
1536
|
+
if (matchesPropertyFilters(entry.entity.properties, untilFilters)) {
|
|
1537
|
+
matching.push(entry);
|
|
1538
|
+
} else {
|
|
1539
|
+
continuing.push(entry);
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
if (emitIntermediates) {
|
|
1543
|
+
allIntermediates.push(...continuing);
|
|
1544
|
+
}
|
|
1545
|
+
allIntermediates.push(...matching);
|
|
1546
|
+
if (matching.length > 0 && continuing.length === 0) {
|
|
1547
|
+
current = matching;
|
|
1548
|
+
break;
|
|
1549
|
+
}
|
|
1550
|
+
current = continuing;
|
|
1551
|
+
} else {
|
|
1552
|
+
if (emitIntermediates) {
|
|
1553
|
+
allIntermediates.push(...unvisited);
|
|
1554
|
+
}
|
|
1555
|
+
current = unvisited;
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
return {
|
|
1559
|
+
terminal: current,
|
|
1560
|
+
intermediates: allIntermediates
|
|
1561
|
+
};
|
|
1562
|
+
}
|
|
1563
|
+
async function resolveEntity(repositoryId, storage, idOrSlug) {
|
|
1564
|
+
const byId = await storage.getEntity(repositoryId, idOrSlug);
|
|
1565
|
+
if (byId) return byId;
|
|
1566
|
+
return storage.getEntityBySlug(repositoryId, idOrSlug);
|
|
1567
|
+
}
|
|
1568
|
+
function mapDirection(direction) {
|
|
1569
|
+
switch (direction) {
|
|
1570
|
+
case "out":
|
|
1571
|
+
return "outbound";
|
|
1572
|
+
case "in":
|
|
1573
|
+
return "inbound";
|
|
1574
|
+
case "both":
|
|
1575
|
+
return "both";
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
function getTargetId(rel, currentEntityId) {
|
|
1579
|
+
if (rel.sourceEntityId === currentEntityId) {
|
|
1580
|
+
return rel.targetEntityId;
|
|
1581
|
+
}
|
|
1582
|
+
return rel.sourceEntityId;
|
|
1583
|
+
}
|
|
1584
|
+
|
|
738
1585
|
// src/relationships/GraphTraversal.ts
|
|
739
1586
|
var GraphTraversal = class {
|
|
740
|
-
constructor(repositoryId, storage) {
|
|
1587
|
+
constructor(repositoryId, storage, graphTraversalProvider, vocabularyEngine) {
|
|
741
1588
|
this.repositoryId = repositoryId;
|
|
742
1589
|
this.storage = storage;
|
|
1590
|
+
this.graphTraversalProvider = graphTraversalProvider;
|
|
1591
|
+
this.vocabularyEngine = vocabularyEngine;
|
|
743
1592
|
}
|
|
744
1593
|
/** BFS neighbourhood exploration from a centre entity */
|
|
745
1594
|
async exploreNeighbourhood(entityId, options) {
|
|
@@ -871,6 +1720,51 @@ var GraphTraversal = class {
|
|
|
871
1720
|
}))
|
|
872
1721
|
};
|
|
873
1722
|
}
|
|
1723
|
+
/**
|
|
1724
|
+
* Execute a structured traversal.
|
|
1725
|
+
* Validates the spec, compiles to native query if a provider exists,
|
|
1726
|
+
* falls back to application-level BFS otherwise.
|
|
1727
|
+
*/
|
|
1728
|
+
async traverse(spec) {
|
|
1729
|
+
const capabilities = this.graphTraversalProvider?.getCapabilities();
|
|
1730
|
+
const vocabulary = this.vocabularyEngine ? await this.vocabularyEngine.getVocabulary() : void 0;
|
|
1731
|
+
const validation = validateTraversalSpec(spec, vocabulary, capabilities);
|
|
1732
|
+
if (!validation.valid) {
|
|
1733
|
+
const vocabErrors = validation.errors.filter((e) => e.startsWith("Unknown vocabulary types:"));
|
|
1734
|
+
const structErrors = validation.errors.filter((e) => !e.startsWith("Unknown vocabulary types:"));
|
|
1735
|
+
if (vocabErrors.length > 0) {
|
|
1736
|
+
const unknownTypes = vocabErrors.join("; ").replace(/Unknown vocabulary types: /g, "").split(", ").map((t) => t.trim());
|
|
1737
|
+
throw new TraversalVocabularyError(unknownTypes);
|
|
1738
|
+
}
|
|
1739
|
+
throw new TraversalValidationError(structErrors.length > 0 ? structErrors : validation.errors);
|
|
1740
|
+
}
|
|
1741
|
+
if (this.graphTraversalProvider && vocabulary) {
|
|
1742
|
+
const language = capabilities.nativeQueryLanguage;
|
|
1743
|
+
let compiledQuery;
|
|
1744
|
+
if (language === "gremlin") {
|
|
1745
|
+
const compiler = new GremlinCompiler();
|
|
1746
|
+
const compiled = compiler.compile(spec, vocabulary);
|
|
1747
|
+
compiledQuery = compiled.query;
|
|
1748
|
+
} else if (language === "cypher") {
|
|
1749
|
+
const compiler = new CypherCompiler();
|
|
1750
|
+
const compiled = compiler.compile(spec, vocabulary);
|
|
1751
|
+
compiledQuery = compiled.query;
|
|
1752
|
+
}
|
|
1753
|
+
return this.graphTraversalProvider.traverse(this.repositoryId, spec, compiledQuery);
|
|
1754
|
+
}
|
|
1755
|
+
return executeFallbackTraversal(this.repositoryId, this.storage, spec);
|
|
1756
|
+
}
|
|
1757
|
+
/**
|
|
1758
|
+
* Execute a native graph query.
|
|
1759
|
+
* Pass-through to GraphTraversalProvider.executeNativeQuery().
|
|
1760
|
+
* Throws GraphTraversalProviderRequiredError if no provider registered.
|
|
1761
|
+
*/
|
|
1762
|
+
async executeNativeQuery(query, params) {
|
|
1763
|
+
if (!this.graphTraversalProvider) {
|
|
1764
|
+
throw new GraphTraversalProviderRequiredError();
|
|
1765
|
+
}
|
|
1766
|
+
return this.graphTraversalProvider.executeNativeQuery(this.repositoryId, query, params);
|
|
1767
|
+
}
|
|
874
1768
|
};
|
|
875
1769
|
|
|
876
1770
|
// src/search/SearchOrchestrator.ts
|
|
@@ -1079,7 +1973,9 @@ var MemoryRepository = class {
|
|
|
1079
1973
|
);
|
|
1080
1974
|
this.graphTraversal = new GraphTraversal(
|
|
1081
1975
|
config.repositoryId,
|
|
1082
|
-
config.storage
|
|
1976
|
+
config.storage,
|
|
1977
|
+
config.graphTraversal,
|
|
1978
|
+
config.vocabularyEngine
|
|
1083
1979
|
);
|
|
1084
1980
|
this.searchOrchestrator = new SearchOrchestrator({
|
|
1085
1981
|
repositoryId: config.repositoryId,
|
|
@@ -1103,19 +1999,21 @@ var MemoryRepository = class {
|
|
|
1103
1999
|
return this.proposeVocabularyChange(proposal);
|
|
1104
2000
|
}
|
|
1105
2001
|
// ─── Entities ──────────────────────────────────────────────────────
|
|
1106
|
-
async
|
|
1107
|
-
const
|
|
2002
|
+
async createEntities(inputs) {
|
|
2003
|
+
const entities = await this.entityManager.create(inputs);
|
|
1108
2004
|
if (this.search) {
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
2005
|
+
for (const entity of entities) {
|
|
2006
|
+
await this.search.indexEntity(this.repositoryId, {
|
|
2007
|
+
entityId: entity.id,
|
|
2008
|
+
entityType: entity.entityType,
|
|
2009
|
+
label: entity.label,
|
|
2010
|
+
summary: entity.summary,
|
|
2011
|
+
properties: entity.properties,
|
|
2012
|
+
data: entity.data
|
|
2013
|
+
});
|
|
2014
|
+
}
|
|
1117
2015
|
}
|
|
1118
|
-
return
|
|
2016
|
+
return entities;
|
|
1119
2017
|
}
|
|
1120
2018
|
async updateEntity(entityId, updates) {
|
|
1121
2019
|
const entity = await this.entityManager.update(entityId, updates);
|
|
@@ -1202,8 +2100,8 @@ var MemoryRepository = class {
|
|
|
1202
2100
|
}
|
|
1203
2101
|
}
|
|
1204
2102
|
// ─── Relationships ─────────────────────────────────────────────────
|
|
1205
|
-
async
|
|
1206
|
-
return this.relationshipManager.create(
|
|
2103
|
+
async createRelationships(inputs) {
|
|
2104
|
+
return this.relationshipManager.create(inputs);
|
|
1207
2105
|
}
|
|
1208
2106
|
async removeRelationship(relationshipId) {
|
|
1209
2107
|
return this.relationshipManager.remove(relationshipId);
|
|
@@ -1297,6 +2195,12 @@ var MemoryRepository = class {
|
|
|
1297
2195
|
async findPaths(sourceId, targetId, options) {
|
|
1298
2196
|
return this.graphTraversal.findPaths(sourceId, targetId, options);
|
|
1299
2197
|
}
|
|
2198
|
+
async traverse(spec) {
|
|
2199
|
+
return this.graphTraversal.traverse(spec);
|
|
2200
|
+
}
|
|
2201
|
+
async executeNativeQuery(query, params) {
|
|
2202
|
+
return this.graphTraversal.executeNativeQuery(query, params);
|
|
2203
|
+
}
|
|
1300
2204
|
// ─── Search ────────────────────────────────────────────────────────
|
|
1301
2205
|
async searchByConcept(query, options) {
|
|
1302
2206
|
return this.searchOrchestrator.searchByConcept(query, options);
|
|
@@ -1386,6 +2290,58 @@ var MemoryRepository = class {
|
|
|
1386
2290
|
async getStats() {
|
|
1387
2291
|
return this.storage.getRepositoryStats(this.repositoryId);
|
|
1388
2292
|
}
|
|
2293
|
+
// ─── Bulk Operations ──────────────────────────────────────────────
|
|
2294
|
+
/** Delete all entities and relationships in this repository, preserving the repository and vocabulary */
|
|
2295
|
+
async deleteAllContents() {
|
|
2296
|
+
return this.storage.deleteAllContents(this.repositoryId);
|
|
2297
|
+
}
|
|
2298
|
+
/**
|
|
2299
|
+
* Returns a static markdown guide describing the recommended query strategy
|
|
2300
|
+
* for AI agents and other consumers of the repository.
|
|
2301
|
+
*/
|
|
2302
|
+
getQueryGuide() {
|
|
2303
|
+
return `## Query Strategy Guide
|
|
2304
|
+
|
|
2305
|
+
### Step 1 \u2014 Discover before you traverse
|
|
2306
|
+
|
|
2307
|
+
Before following relationships from an entity, check that it actually has relationships.
|
|
2308
|
+
\`memory_query_graph\` includes \`relationshipSummary\` on every returned entity by default
|
|
2309
|
+
(outbound and inbound counts by type). Inspect this before traversing \u2014 entities with
|
|
2310
|
+
zero outbound counts for the relationship type you need have no connections to follow.
|
|
2311
|
+
To skip summaries and reduce response size, set \`includeRelationshipSummary: false\`.
|
|
2312
|
+
|
|
2313
|
+
### Step 2 \u2014 Use projection for aggregation
|
|
2314
|
+
|
|
2315
|
+
To understand what data exists (e.g. all distinct values of a property, or counts by
|
|
2316
|
+
category), use \`memory_query_graph\` with \`projection\` instead of fetching full entities:
|
|
2317
|
+
\`{ start: { entityType: "..." }, projection: { properties: ["propName"], distinct: true }, limit: 200 }\`
|
|
2318
|
+
|
|
2319
|
+
This returns only the aggregated values \u2014 no entity objects \u2014 keeping responses lightweight.
|
|
2320
|
+
|
|
2321
|
+
### Step 3 \u2014 Traverse efficiently
|
|
2322
|
+
|
|
2323
|
+
- **Omit relationshipTypes** in a traversal step to follow all relationship types at once,
|
|
2324
|
+
rather than making separate calls per type.
|
|
2325
|
+
- **Use multi-step traversals** for multi-hop patterns. A two-step query
|
|
2326
|
+
(e.g. Equipment \u2192 Component \u2192 MaintenanceProcedure) is one call, not two sequential calls.
|
|
2327
|
+
- **Use returnMode "all"** when you need intermediate entities along the path, not just
|
|
2328
|
+
the terminal nodes.
|
|
2329
|
+
|
|
2330
|
+
### Step 4 \u2014 Use semantic search for known-unknowns
|
|
2331
|
+
|
|
2332
|
+
\`memory_search_by_concept\` is best for finding specific entities when you know roughly
|
|
2333
|
+
what you're looking for but not the exact label or type. It is not efficient for broad
|
|
2334
|
+
discovery \u2014 use projection or find_entities for that.
|
|
2335
|
+
|
|
2336
|
+
### Common anti-patterns to avoid
|
|
2337
|
+
|
|
2338
|
+
- **Don't traverse from entities with zero relationship counts.** The \`relationshipSummary\` on each entity tells you what is connected before you traverse.
|
|
2339
|
+
- **Don't split queries by relationship type.** One step with no type filter is better
|
|
2340
|
+
than three separate single-type calls.
|
|
2341
|
+
- **Don't use sequential single-hop traversals** when a multi-step query achieves the
|
|
2342
|
+
same result in one call.
|
|
2343
|
+
- **Don't fetch full entities when you only need property values.** Use projection.`;
|
|
2344
|
+
}
|
|
1389
2345
|
// ─── Events ────────────────────────────────────────────────────────
|
|
1390
2346
|
on(event, handler) {
|
|
1391
2347
|
return this.eventBus.on(event, handler);
|
|
@@ -2381,13 +3337,15 @@ var EventBus = class {
|
|
|
2381
3337
|
};
|
|
2382
3338
|
|
|
2383
3339
|
// src/portability/RepositoryExporter.ts
|
|
2384
|
-
var LIBRARY_VERSION = "0.1.0";
|
|
3340
|
+
var LIBRARY_VERSION = true ? "0.3.0" : "0.1.0";
|
|
2385
3341
|
var RepositoryExporter = class {
|
|
2386
3342
|
storage;
|
|
2387
3343
|
provenance;
|
|
3344
|
+
legal;
|
|
2388
3345
|
constructor(config) {
|
|
2389
3346
|
this.storage = config.storage;
|
|
2390
3347
|
this.provenance = config.provenance;
|
|
3348
|
+
this.legal = config.legal;
|
|
2391
3349
|
}
|
|
2392
3350
|
/**
|
|
2393
3351
|
* Stream a repository export as an async generator.
|
|
@@ -2421,6 +3379,9 @@ var RepositoryExporter = class {
|
|
|
2421
3379
|
relationshipTypeBreakdown: stats.relationshipTypeBreakdown
|
|
2422
3380
|
}
|
|
2423
3381
|
};
|
|
3382
|
+
if (this.legal) {
|
|
3383
|
+
manifest.legal = this.legal;
|
|
3384
|
+
}
|
|
2424
3385
|
if (repo.metadata?.embeddingModelId) {
|
|
2425
3386
|
manifest.embedding = {
|
|
2426
3387
|
modelId: repo.metadata.embeddingModelId,
|
|
@@ -2719,13 +3680,22 @@ function describeChange(change) {
|
|
|
2719
3680
|
}
|
|
2720
3681
|
|
|
2721
3682
|
// src/portability/RepositoryImporter.ts
|
|
3683
|
+
function estimateChunkCount(totalEntities, totalRelationships) {
|
|
3684
|
+
const ESTIMATED_CHUNK_SIZE = 100;
|
|
3685
|
+
return Math.max(
|
|
3686
|
+
1,
|
|
3687
|
+
Math.ceil(totalEntities / ESTIMATED_CHUNK_SIZE) + Math.ceil(totalRelationships / ESTIMATED_CHUNK_SIZE)
|
|
3688
|
+
);
|
|
3689
|
+
}
|
|
2722
3690
|
var RepositoryImporter = class {
|
|
2723
3691
|
storage;
|
|
2724
3692
|
actorId;
|
|
3693
|
+
onProgress;
|
|
2725
3694
|
migrationEngine = new MigrationEngine();
|
|
2726
3695
|
constructor(config) {
|
|
2727
3696
|
this.storage = config.storage;
|
|
2728
3697
|
this.actorId = config.actorId;
|
|
3698
|
+
this.onProgress = config.onProgress;
|
|
2729
3699
|
}
|
|
2730
3700
|
/**
|
|
2731
3701
|
* Import an export archive according to the provided options.
|
|
@@ -2776,26 +3746,34 @@ var RepositoryImporter = class {
|
|
|
2776
3746
|
return this.importStreamMerge(header, chunks, options, warnings);
|
|
2777
3747
|
}
|
|
2778
3748
|
}
|
|
2779
|
-
/** Streaming create mode —
|
|
3749
|
+
/** Streaming create mode — fast bulk inserts with no existence checks */
|
|
2780
3750
|
async importStreamCreate(header, chunks, options, warnings) {
|
|
2781
3751
|
const target = options.target;
|
|
2782
|
-
const
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
3752
|
+
const existing = await this.storage.getRepository(target.repositoryId);
|
|
3753
|
+
if (!existing) {
|
|
3754
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3755
|
+
await this.storage.createRepository({
|
|
3756
|
+
repositoryId: target.repositoryId,
|
|
3757
|
+
type: target.config.type,
|
|
3758
|
+
label: target.config.label,
|
|
3759
|
+
description: target.config.description,
|
|
3760
|
+
governanceConfig: target.config.governance ?? { mode: "open" },
|
|
3761
|
+
createdAt: now,
|
|
3762
|
+
createdBy: this.actorId
|
|
3763
|
+
});
|
|
3764
|
+
}
|
|
2792
3765
|
await this.storage.saveVocabulary(target.repositoryId, header.vocabulary);
|
|
3766
|
+
const totalEntities = header.manifest?.statistics?.entityCount ?? 0;
|
|
3767
|
+
const totalRelationships = header.manifest?.statistics?.relationshipCount ?? 0;
|
|
3768
|
+
const totalChunks = estimateChunkCount(totalEntities, totalRelationships);
|
|
2793
3769
|
let entitiesImported = 0;
|
|
2794
3770
|
let relationshipsImported = 0;
|
|
3771
|
+
let chunksCompleted = 0;
|
|
2795
3772
|
for await (const chunk of chunks) {
|
|
2796
|
-
const bulkResult = await this.storage.importBulk(target.repositoryId, [chunk]);
|
|
3773
|
+
const bulkResult = await this.storage.importBulk(target.repositoryId, [chunk], { skipExistenceCheck: true });
|
|
2797
3774
|
entitiesImported += bulkResult.entitiesImported;
|
|
2798
3775
|
relationshipsImported += bulkResult.relationshipsImported;
|
|
3776
|
+
chunksCompleted++;
|
|
2799
3777
|
for (const e of bulkResult.errors) {
|
|
2800
3778
|
warnings.push({
|
|
2801
3779
|
code: "import_error",
|
|
@@ -2803,6 +3781,15 @@ var RepositoryImporter = class {
|
|
|
2803
3781
|
id: e.item
|
|
2804
3782
|
});
|
|
2805
3783
|
}
|
|
3784
|
+
await this.onProgress?.({
|
|
3785
|
+
repositoryId: target.repositoryId,
|
|
3786
|
+
entitiesImported,
|
|
3787
|
+
relationshipsImported,
|
|
3788
|
+
totalEntities,
|
|
3789
|
+
totalRelationships,
|
|
3790
|
+
chunksCompleted,
|
|
3791
|
+
totalChunks
|
|
3792
|
+
});
|
|
2806
3793
|
}
|
|
2807
3794
|
return {
|
|
2808
3795
|
success: true,
|
|
@@ -2850,11 +3837,15 @@ var RepositoryImporter = class {
|
|
|
2850
3837
|
if (migrationResult.mergedVocabulary && migrationResult.mergedVocabulary !== targetVocabulary) {
|
|
2851
3838
|
await this.storage.saveVocabulary(repositoryId, migrationResult.mergedVocabulary);
|
|
2852
3839
|
}
|
|
3840
|
+
const totalEntities = header.manifest?.statistics?.entityCount ?? 0;
|
|
3841
|
+
const totalRelationships = header.manifest?.statistics?.relationshipCount ?? 0;
|
|
3842
|
+
const totalChunks = estimateChunkCount(totalEntities, totalRelationships);
|
|
2853
3843
|
const entityConflict = options.entityConflict ?? "skip";
|
|
2854
3844
|
let entitiesImported = 0;
|
|
2855
3845
|
let entitiesSkipped = 0;
|
|
2856
3846
|
let relationshipsImported = 0;
|
|
2857
3847
|
let relationshipsSkipped = 0;
|
|
3848
|
+
let chunksCompleted = 0;
|
|
2858
3849
|
for await (const chunk of chunks) {
|
|
2859
3850
|
if (chunk.entities) {
|
|
2860
3851
|
for (const entity of chunk.entities) {
|
|
@@ -2936,6 +3927,16 @@ var RepositoryImporter = class {
|
|
|
2936
3927
|
}
|
|
2937
3928
|
}
|
|
2938
3929
|
}
|
|
3930
|
+
chunksCompleted++;
|
|
3931
|
+
await this.onProgress?.({
|
|
3932
|
+
repositoryId,
|
|
3933
|
+
entitiesImported,
|
|
3934
|
+
relationshipsImported,
|
|
3935
|
+
totalEntities,
|
|
3936
|
+
totalRelationships,
|
|
3937
|
+
chunksCompleted,
|
|
3938
|
+
totalChunks
|
|
3939
|
+
});
|
|
2939
3940
|
}
|
|
2940
3941
|
const vocabExtensions = migrationResult.warnings.filter(
|
|
2941
3942
|
(w) => w.code === "entity_type_added" || w.code === "relationship_type_added"
|
|
@@ -2989,6 +3990,7 @@ var DeepMemory = class {
|
|
|
2989
3990
|
embedding;
|
|
2990
3991
|
/** Reserved for future distributed locking support */
|
|
2991
3992
|
lock;
|
|
3993
|
+
graphTraversalProvider;
|
|
2992
3994
|
provenance;
|
|
2993
3995
|
globalEventBus;
|
|
2994
3996
|
initialised = false;
|
|
@@ -2997,24 +3999,42 @@ var DeepMemory = class {
|
|
|
2997
3999
|
this.search = config.search;
|
|
2998
4000
|
this.embedding = config.embedding;
|
|
2999
4001
|
this.lock = config.lock;
|
|
4002
|
+
this.graphTraversalProvider = config.graphTraversal;
|
|
3000
4003
|
this.provenance = new ProvenanceTracker(config.provenance);
|
|
3001
4004
|
this.globalEventBus = new EventBus(config.provenance);
|
|
3002
4005
|
}
|
|
3003
|
-
/** Initialise the storage provider (call once before use) */
|
|
4006
|
+
/** Initialise the storage provider and optional providers (call once before use) */
|
|
3004
4007
|
async ensureInitialised() {
|
|
3005
4008
|
if (!this.initialised) {
|
|
3006
4009
|
if (this.storage.initialise) {
|
|
3007
4010
|
await this.storage.initialise();
|
|
3008
4011
|
}
|
|
4012
|
+
if (this.graphTraversalProvider?.initialise) {
|
|
4013
|
+
await this.graphTraversalProvider.initialise();
|
|
4014
|
+
}
|
|
3009
4015
|
this.initialised = true;
|
|
3010
4016
|
}
|
|
3011
4017
|
}
|
|
3012
4018
|
/** Create or migrate the storage schema. Call once at deployment / startup, not per-request. */
|
|
3013
4019
|
async ensureSchema() {
|
|
3014
|
-
|
|
4020
|
+
const noOpResult = {
|
|
4021
|
+
databaseCreated: false,
|
|
4022
|
+
schemaCreated: false,
|
|
4023
|
+
alreadyUpToDate: true,
|
|
4024
|
+
schemaVersion: 0
|
|
4025
|
+
};
|
|
4026
|
+
try {
|
|
4027
|
+
await this.ensureInitialised();
|
|
4028
|
+
} catch {
|
|
4029
|
+
}
|
|
4030
|
+
let result = noOpResult;
|
|
3015
4031
|
if (this.storage.ensureSchema) {
|
|
3016
|
-
await this.storage.ensureSchema();
|
|
4032
|
+
result = await this.storage.ensureSchema();
|
|
3017
4033
|
}
|
|
4034
|
+
if (!this.initialised) {
|
|
4035
|
+
await this.ensureInitialised();
|
|
4036
|
+
}
|
|
4037
|
+
return result;
|
|
3018
4038
|
}
|
|
3019
4039
|
validateRepositoryId(repositoryId) {
|
|
3020
4040
|
if (!isValidUuid(repositoryId)) {
|
|
@@ -3071,6 +4091,7 @@ var DeepMemory = class {
|
|
|
3071
4091
|
storage: this.storage,
|
|
3072
4092
|
search: this.search,
|
|
3073
4093
|
embedding: this.embedding,
|
|
4094
|
+
graphTraversal: this.graphTraversalProvider,
|
|
3074
4095
|
vocabularyEngine,
|
|
3075
4096
|
provenanceTracker: this.provenance,
|
|
3076
4097
|
eventBus
|
|
@@ -3102,6 +4123,7 @@ var DeepMemory = class {
|
|
|
3102
4123
|
storage: this.storage,
|
|
3103
4124
|
search: this.search,
|
|
3104
4125
|
embedding: this.embedding,
|
|
4126
|
+
graphTraversal: this.graphTraversalProvider,
|
|
3105
4127
|
vocabularyEngine,
|
|
3106
4128
|
provenanceTracker: this.provenance,
|
|
3107
4129
|
eventBus
|
|
@@ -3139,15 +4161,57 @@ var DeepMemory = class {
|
|
|
3139
4161
|
async deleteRepository(repositoryId) {
|
|
3140
4162
|
await this.ensureInitialised();
|
|
3141
4163
|
this.validateRepositoryId(repositoryId);
|
|
3142
|
-
await this.storage.
|
|
4164
|
+
const stats = await this.storage.getRepositoryStats(repositoryId);
|
|
4165
|
+
await this.globalEventBus.emit("delete:started", {
|
|
4166
|
+
repositoryId,
|
|
4167
|
+
totalEntities: stats.entityCount,
|
|
4168
|
+
totalRelationships: stats.relationshipCount
|
|
4169
|
+
});
|
|
4170
|
+
await this.storage.deleteRepository(
|
|
4171
|
+
repositoryId,
|
|
4172
|
+
(progress) => this.globalEventBus.emit("delete:progress", { repositoryId, ...progress })
|
|
4173
|
+
);
|
|
4174
|
+
await this.globalEventBus.emit("delete:completed", {
|
|
4175
|
+
repositoryId,
|
|
4176
|
+
entitiesDeleted: stats.entityCount,
|
|
4177
|
+
relationshipsDeleted: stats.relationshipCount
|
|
4178
|
+
});
|
|
3143
4179
|
await this.globalEventBus.emit("repository:deleted", { repositoryId });
|
|
3144
4180
|
}
|
|
4181
|
+
/** Delete all entities and relationships in a repository, preserving the repository and vocabulary */
|
|
4182
|
+
async deleteAllContents(repositoryId) {
|
|
4183
|
+
await this.ensureInitialised();
|
|
4184
|
+
this.validateRepositoryId(repositoryId);
|
|
4185
|
+
const stats = await this.storage.getRepositoryStats(repositoryId);
|
|
4186
|
+
await this.globalEventBus.emit("delete:started", {
|
|
4187
|
+
repositoryId,
|
|
4188
|
+
totalEntities: stats.entityCount,
|
|
4189
|
+
totalRelationships: stats.relationshipCount
|
|
4190
|
+
});
|
|
4191
|
+
const result = await this.storage.deleteAllContents(
|
|
4192
|
+
repositoryId,
|
|
4193
|
+
(progress) => this.globalEventBus.emit("delete:progress", { repositoryId, ...progress })
|
|
4194
|
+
);
|
|
4195
|
+
await this.globalEventBus.emit("delete:completed", {
|
|
4196
|
+
repositoryId,
|
|
4197
|
+
entitiesDeleted: result.deletedEntities,
|
|
4198
|
+
relationshipsDeleted: result.deletedRelationships
|
|
4199
|
+
});
|
|
4200
|
+
return result;
|
|
4201
|
+
}
|
|
3145
4202
|
/** Export a repository to a portable archive */
|
|
3146
|
-
async exportRepository(repositoryId) {
|
|
4203
|
+
async exportRepository(repositoryId, options) {
|
|
3147
4204
|
await this.ensureInitialised();
|
|
4205
|
+
const stats = await this.storage.getRepositoryStats(repositoryId);
|
|
4206
|
+
await this.globalEventBus.emit("export:started", {
|
|
4207
|
+
repositoryId,
|
|
4208
|
+
totalEntities: stats.entityCount,
|
|
4209
|
+
totalRelationships: stats.relationshipCount
|
|
4210
|
+
});
|
|
3148
4211
|
const exporter = new RepositoryExporter({
|
|
3149
4212
|
storage: this.storage,
|
|
3150
|
-
provenance: this.provenance.getContext()
|
|
4213
|
+
provenance: this.provenance.getContext(),
|
|
4214
|
+
legal: options?.legal
|
|
3151
4215
|
});
|
|
3152
4216
|
const archive = await exporter.export(repositoryId);
|
|
3153
4217
|
await this.globalEventBus.emit("export:completed", {
|
|
@@ -3160,9 +4224,11 @@ var DeepMemory = class {
|
|
|
3160
4224
|
/** Import a repository from a portable archive */
|
|
3161
4225
|
async importRepository(archive, options) {
|
|
3162
4226
|
await this.ensureInitialised();
|
|
4227
|
+
await this.globalEventBus.emit("import:started", { repositoryId: options.target.repositoryId });
|
|
3163
4228
|
const importer = new RepositoryImporter({
|
|
3164
4229
|
storage: this.storage,
|
|
3165
|
-
actorId: this.provenance.getContext().actorId
|
|
4230
|
+
actorId: this.provenance.getContext().actorId,
|
|
4231
|
+
onProgress: (progress) => this.globalEventBus.emit("import:progress", progress)
|
|
3166
4232
|
});
|
|
3167
4233
|
const result = await importer.import(archive, options);
|
|
3168
4234
|
if (result.success) {
|
|
@@ -3184,21 +4250,51 @@ var DeepMemory = class {
|
|
|
3184
4250
|
* Yields: manifest → vocabulary → entity chunks → relationship chunks.
|
|
3185
4251
|
* Use for large repositories to avoid loading everything into memory.
|
|
3186
4252
|
*/
|
|
3187
|
-
async *exportRepositoryStream(repositoryId) {
|
|
4253
|
+
async *exportRepositoryStream(repositoryId, options) {
|
|
3188
4254
|
await this.ensureInitialised();
|
|
3189
4255
|
const exporter = new RepositoryExporter({
|
|
3190
4256
|
storage: this.storage,
|
|
3191
|
-
provenance: this.provenance.getContext()
|
|
4257
|
+
provenance: this.provenance.getContext(),
|
|
4258
|
+
legal: options?.legal
|
|
3192
4259
|
});
|
|
3193
|
-
await this.
|
|
4260
|
+
const stats = await this.storage.getRepositoryStats(repositoryId);
|
|
4261
|
+
const totalEntities = stats.entityCount;
|
|
4262
|
+
const totalRelationships = stats.relationshipCount;
|
|
4263
|
+
const estimatedChunkSize = 100;
|
|
4264
|
+
const totalChunks = Math.max(
|
|
4265
|
+
1,
|
|
4266
|
+
Math.ceil(totalEntities / estimatedChunkSize) + Math.ceil(totalRelationships / estimatedChunkSize)
|
|
4267
|
+
);
|
|
4268
|
+
await this.globalEventBus.emit("export:started", { repositoryId, totalEntities, totalRelationships });
|
|
3194
4269
|
let entityCount = 0;
|
|
3195
4270
|
let relationshipCount = 0;
|
|
4271
|
+
let chunksCompleted = 0;
|
|
3196
4272
|
for await (const item of exporter.exportStream(repositoryId)) {
|
|
3197
4273
|
yield item;
|
|
3198
4274
|
if (item.type === "entities") {
|
|
3199
4275
|
entityCount += item.data.length;
|
|
4276
|
+
chunksCompleted++;
|
|
4277
|
+
await this.globalEventBus.emit("export:progress", {
|
|
4278
|
+
repositoryId,
|
|
4279
|
+
entitiesExported: entityCount,
|
|
4280
|
+
relationshipsExported: relationshipCount,
|
|
4281
|
+
totalEntities,
|
|
4282
|
+
totalRelationships,
|
|
4283
|
+
chunksCompleted,
|
|
4284
|
+
totalChunks
|
|
4285
|
+
});
|
|
3200
4286
|
} else if (item.type === "relationships") {
|
|
3201
4287
|
relationshipCount += item.data.length;
|
|
4288
|
+
chunksCompleted++;
|
|
4289
|
+
await this.globalEventBus.emit("export:progress", {
|
|
4290
|
+
repositoryId,
|
|
4291
|
+
entitiesExported: entityCount,
|
|
4292
|
+
relationshipsExported: relationshipCount,
|
|
4293
|
+
totalEntities,
|
|
4294
|
+
totalRelationships,
|
|
4295
|
+
chunksCompleted,
|
|
4296
|
+
totalChunks
|
|
4297
|
+
});
|
|
3202
4298
|
}
|
|
3203
4299
|
}
|
|
3204
4300
|
await this.globalEventBus.emit("export:completed", {
|
|
@@ -3215,7 +4311,8 @@ var DeepMemory = class {
|
|
|
3215
4311
|
await this.ensureInitialised();
|
|
3216
4312
|
const importer = new RepositoryImporter({
|
|
3217
4313
|
storage: this.storage,
|
|
3218
|
-
actorId: this.provenance.getContext().actorId
|
|
4314
|
+
actorId: this.provenance.getContext().actorId,
|
|
4315
|
+
onProgress: (progress) => this.globalEventBus.emit("import:progress", progress)
|
|
3219
4316
|
});
|
|
3220
4317
|
await this.globalEventBus.emit("import:started", { repositoryId: options.target.repositoryId });
|
|
3221
4318
|
const result = await importer.importStream(header, chunks, options);
|
|
@@ -3245,6 +4342,9 @@ var DeepMemory = class {
|
|
|
3245
4342
|
/** Dispose of all resources */
|
|
3246
4343
|
async dispose() {
|
|
3247
4344
|
this.globalEventBus.removeAllListeners();
|
|
4345
|
+
if (this.graphTraversalProvider?.dispose) {
|
|
4346
|
+
await this.graphTraversalProvider.dispose();
|
|
4347
|
+
}
|
|
3248
4348
|
if (this.storage.dispose) {
|
|
3249
4349
|
await this.storage.dispose();
|
|
3250
4350
|
}
|
|
@@ -3252,36 +4352,6 @@ var DeepMemory = class {
|
|
|
3252
4352
|
}
|
|
3253
4353
|
};
|
|
3254
4354
|
|
|
3255
|
-
// src/relationships/PropertyFilterMatcher.ts
|
|
3256
|
-
function matchesPropertyFilters(properties, filters) {
|
|
3257
|
-
return filters.every((filter) => matchesSingleFilter(properties, filter));
|
|
3258
|
-
}
|
|
3259
|
-
function matchesSingleFilter(properties, filter) {
|
|
3260
|
-
const value = properties[filter.key];
|
|
3261
|
-
switch (filter.operator) {
|
|
3262
|
-
case "isNull":
|
|
3263
|
-
return value === null || value === void 0;
|
|
3264
|
-
case "isNotNull":
|
|
3265
|
-
return value !== null && value !== void 0;
|
|
3266
|
-
case "eq":
|
|
3267
|
-
return value === filter.value;
|
|
3268
|
-
case "neq":
|
|
3269
|
-
return value !== filter.value;
|
|
3270
|
-
case "gt":
|
|
3271
|
-
return typeof value === "number" && typeof filter.value === "number" ? value > filter.value : typeof value === "string" && typeof filter.value === "string" ? value > filter.value : false;
|
|
3272
|
-
case "lt":
|
|
3273
|
-
return typeof value === "number" && typeof filter.value === "number" ? value < filter.value : typeof value === "string" && typeof filter.value === "string" ? value < filter.value : false;
|
|
3274
|
-
case "gte":
|
|
3275
|
-
return typeof value === "number" && typeof filter.value === "number" ? value >= filter.value : typeof value === "string" && typeof filter.value === "string" ? value >= filter.value : false;
|
|
3276
|
-
case "lte":
|
|
3277
|
-
return typeof value === "number" && typeof filter.value === "number" ? value <= filter.value : typeof value === "string" && typeof filter.value === "string" ? value <= filter.value : false;
|
|
3278
|
-
case "contains":
|
|
3279
|
-
return typeof value === "string" && typeof filter.value === "string" ? value.toLowerCase().includes(filter.value.toLowerCase()) : false;
|
|
3280
|
-
default:
|
|
3281
|
-
return false;
|
|
3282
|
-
}
|
|
3283
|
-
}
|
|
3284
|
-
|
|
3285
4355
|
// src/providers-builtin/InMemoryStorageProvider.ts
|
|
3286
4356
|
var InMemoryStorageProvider = class {
|
|
3287
4357
|
stores = /* @__PURE__ */ new Map();
|
|
@@ -3366,12 +4436,21 @@ var InMemoryStorageProvider = class {
|
|
|
3366
4436
|
if (updates.metadata !== void 0) repo.metadata = { ...repo.metadata, ...updates.metadata };
|
|
3367
4437
|
return repo;
|
|
3368
4438
|
}
|
|
3369
|
-
async deleteRepository(repositoryId) {
|
|
4439
|
+
async deleteRepository(repositoryId, _onProgress) {
|
|
3370
4440
|
if (!this.stores.has(repositoryId)) {
|
|
3371
4441
|
throw new RepositoryNotFoundError(repositoryId);
|
|
3372
4442
|
}
|
|
3373
4443
|
this.stores.delete(repositoryId);
|
|
3374
4444
|
}
|
|
4445
|
+
async deleteAllContents(repositoryId, _onProgress) {
|
|
4446
|
+
const store = this.getStore(repositoryId);
|
|
4447
|
+
const deletedEntities = store.entities.size;
|
|
4448
|
+
const deletedRelationships = store.relationships.size;
|
|
4449
|
+
store.entities.clear();
|
|
4450
|
+
store.slugIndex.clear();
|
|
4451
|
+
store.relationships.clear();
|
|
4452
|
+
return { deletedEntities, deletedRelationships };
|
|
4453
|
+
}
|
|
3375
4454
|
async getRepositoryStats(repositoryId) {
|
|
3376
4455
|
const store = this.getStore(repositoryId);
|
|
3377
4456
|
const entityTypeBreakdown = {};
|
|
@@ -3826,7 +4905,7 @@ var InMemoryStorageProvider = class {
|
|
|
3826
4905
|
yield { type: "relationships", data: [], sequence: 0, isLast: true };
|
|
3827
4906
|
}
|
|
3828
4907
|
}
|
|
3829
|
-
async importBulk(repositoryId, data) {
|
|
4908
|
+
async importBulk(repositoryId, data, _options) {
|
|
3830
4909
|
const store = this.getStore(repositoryId);
|
|
3831
4910
|
let entitiesImported = 0;
|
|
3832
4911
|
let relationshipsImported = 0;
|
|
@@ -3976,6 +5055,7 @@ var NoOpEmbeddingProvider = class {
|
|
|
3976
5055
|
}
|
|
3977
5056
|
};
|
|
3978
5057
|
export {
|
|
5058
|
+
CypherCompiler,
|
|
3979
5059
|
DeepMemory,
|
|
3980
5060
|
DeepMemoryError,
|
|
3981
5061
|
DuplicateEntityError,
|
|
@@ -3985,6 +5065,8 @@ export {
|
|
|
3985
5065
|
EntityNotFoundError,
|
|
3986
5066
|
ExportError,
|
|
3987
5067
|
GovernanceDeniedError,
|
|
5068
|
+
GraphTraversalProviderRequiredError,
|
|
5069
|
+
GremlinCompiler,
|
|
3988
5070
|
ImportError,
|
|
3989
5071
|
InMemorySearchProvider,
|
|
3990
5072
|
InMemoryStorageProvider,
|
|
@@ -3996,6 +5078,9 @@ export {
|
|
|
3996
5078
|
RelationshipConstraintError,
|
|
3997
5079
|
RelationshipNotFoundError,
|
|
3998
5080
|
RepositoryNotFoundError,
|
|
5081
|
+
TraversalTimeoutError,
|
|
5082
|
+
TraversalValidationError,
|
|
5083
|
+
TraversalVocabularyError,
|
|
3999
5084
|
VocabularyValidationError,
|
|
4000
5085
|
generateId,
|
|
4001
5086
|
isValidUuid,
|