@utaba/deep-memory 0.2.0 → 0.4.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-DR9HLYHJ.d.ts → StorageProvider-CBhwqavV.d.cts} +11 -6
- package/dist/{StorageProvider-C3tKhfuw.d.cts → StorageProvider-ynfpc5wz.d.ts} +11 -6
- package/dist/index.cjs +391 -102
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +33 -14
- package/dist/index.d.ts +33 -14
- package/dist/index.js +391 -102
- package/dist/index.js.map +1 -1
- package/dist/{portability-DdjsCB07.d.cts → portability-B-8J4nQQ.d.cts} +36 -18
- package/dist/{portability-DdjsCB07.d.ts → portability-B-8J4nQQ.d.ts} +36 -18
- package/dist/providers/index.d.cts +11 -5
- package/dist/providers/index.d.ts +11 -5
- package/dist/testing/conformance.cjs +26 -4
- 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 +26 -4
- package/dist/testing/conformance.js.map +1 -1
- package/dist/{traversal-9JdiqjZ9.d.ts → traversal-BdmDLMxV.d.ts} +98 -11
- package/dist/{traversal-BB_yERbf.d.cts → traversal-Llalk03r.d.cts} +98 -11
- package/dist/types/index.cjs.map +1 -1
- package/dist/types/index.d.cts +28 -4
- package/dist/types/index.d.ts +28 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -277,6 +277,12 @@ var EntityManager = class {
|
|
|
277
277
|
this.storage = storage;
|
|
278
278
|
this.embedding = embedding;
|
|
279
279
|
}
|
|
280
|
+
repositoryId;
|
|
281
|
+
vocabularyEngine;
|
|
282
|
+
provenanceTracker;
|
|
283
|
+
eventBus;
|
|
284
|
+
storage;
|
|
285
|
+
embedding;
|
|
280
286
|
/** Create one or more entities with vocabulary validation, ID generation, provenance, and events */
|
|
281
287
|
async create(inputs) {
|
|
282
288
|
const results = [];
|
|
@@ -640,7 +646,7 @@ function jaroWinklerSimilarity(a, b, scalingFactor = 0.1) {
|
|
|
640
646
|
}
|
|
641
647
|
return jaro + commonPrefix * Math.min(scalingFactor, 0.25) * (1 - jaro);
|
|
642
648
|
}
|
|
643
|
-
function
|
|
649
|
+
function normalizeTypeName(name) {
|
|
644
650
|
return name.toLowerCase().trim().replace(/[-\s.]+/g, "_").replace(/[^a-z0-9_]/g, "");
|
|
645
651
|
}
|
|
646
652
|
function toScreamingSnakeCase(name) {
|
|
@@ -656,6 +662,11 @@ var RelationshipManager = class {
|
|
|
656
662
|
this.eventBus = eventBus;
|
|
657
663
|
this.storage = storage;
|
|
658
664
|
}
|
|
665
|
+
repositoryId;
|
|
666
|
+
vocabularyEngine;
|
|
667
|
+
provenanceTracker;
|
|
668
|
+
eventBus;
|
|
669
|
+
storage;
|
|
659
670
|
/** Create one or more relationships with vocabulary validation, provenance, and events */
|
|
660
671
|
async create(inputs) {
|
|
661
672
|
const results = [];
|
|
@@ -795,6 +806,7 @@ var DEFAULT_MAX_LIMIT = 200;
|
|
|
795
806
|
var DEFAULT_FALLBACK_MAX_DEPTH = 10;
|
|
796
807
|
function validateTraversalSpec(spec, vocabulary, capabilities) {
|
|
797
808
|
const errors = [];
|
|
809
|
+
const steps = spec.steps ?? [];
|
|
798
810
|
if (!spec.start) {
|
|
799
811
|
errors.push("start is required");
|
|
800
812
|
} else {
|
|
@@ -808,37 +820,45 @@ function validateTraversalSpec(spec, vocabulary, capabilities) {
|
|
|
808
820
|
errors.push("start.entityType without entityId requires limit on the spec to prevent full type scans");
|
|
809
821
|
}
|
|
810
822
|
}
|
|
811
|
-
if (!spec.steps || spec.steps.length === 0) {
|
|
812
|
-
errors.push("steps must contain at least one step");
|
|
813
|
-
}
|
|
814
823
|
const maxSteps = capabilities?.maxTraversalDepth ?? DEFAULT_MAX_STEPS;
|
|
815
|
-
if (
|
|
816
|
-
errors.push(`steps length ${
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
if (step.repeat) {
|
|
825
|
-
|
|
826
|
-
errors.push(`steps[${i}].repeat.maxDepth must be a positive number`);
|
|
827
|
-
}
|
|
824
|
+
if (steps.length > maxSteps) {
|
|
825
|
+
errors.push(`steps length ${steps.length} exceeds maximum ${maxSteps}`);
|
|
826
|
+
}
|
|
827
|
+
for (let i = 0; i < steps.length; i++) {
|
|
828
|
+
const step = steps[i];
|
|
829
|
+
if (!step.direction || !["out", "in", "both"].includes(step.direction)) {
|
|
830
|
+
errors.push(`steps[${i}].direction must be 'out', 'in', or 'both'`);
|
|
831
|
+
}
|
|
832
|
+
if (step.repeat) {
|
|
833
|
+
if (step.repeat.maxDepth === void 0 || step.repeat.maxDepth <= 0) {
|
|
834
|
+
errors.push(`steps[${i}].repeat.maxDepth must be a positive number`);
|
|
828
835
|
}
|
|
829
836
|
}
|
|
837
|
+
}
|
|
838
|
+
if (steps.length > 0) {
|
|
830
839
|
const maxProviderDepth = capabilities?.maxTraversalDepth ?? DEFAULT_FALLBACK_MAX_DEPTH;
|
|
831
840
|
let totalDepth = 0;
|
|
832
|
-
for (const step of
|
|
841
|
+
for (const step of steps) {
|
|
833
842
|
totalDepth += step.repeat ? step.repeat.maxDepth : 1;
|
|
834
843
|
}
|
|
835
844
|
if (totalDepth > maxProviderDepth) {
|
|
836
845
|
errors.push(`total potential depth ${totalDepth} exceeds provider maximum ${maxProviderDepth}`);
|
|
837
846
|
}
|
|
838
847
|
}
|
|
848
|
+
if (spec.returnMode === "path" && steps.length === 0) {
|
|
849
|
+
errors.push("returnMode 'path' requires at least one step");
|
|
850
|
+
}
|
|
839
851
|
if (spec.returnMode && !["terminal", "path", "all"].includes(spec.returnMode)) {
|
|
840
852
|
errors.push(`returnMode must be 'terminal', 'path', or 'all'`);
|
|
841
853
|
}
|
|
854
|
+
if (spec.projection) {
|
|
855
|
+
if (!spec.projection.properties || spec.projection.properties.length === 0) {
|
|
856
|
+
errors.push("projection.properties must contain at least one property name");
|
|
857
|
+
}
|
|
858
|
+
if (spec.projection.mode && !["count", "values"].includes(spec.projection.mode)) {
|
|
859
|
+
errors.push("projection.mode must be 'count' or 'values'");
|
|
860
|
+
}
|
|
861
|
+
}
|
|
842
862
|
if (spec.limit !== void 0) {
|
|
843
863
|
if (spec.limit < 1 || spec.limit > DEFAULT_MAX_LIMIT) {
|
|
844
864
|
errors.push(`limit must be between 1 and ${DEFAULT_MAX_LIMIT}`);
|
|
@@ -847,15 +867,15 @@ function validateTraversalSpec(spec, vocabulary, capabilities) {
|
|
|
847
867
|
if (spec.offset !== void 0 && spec.offset < 0) {
|
|
848
868
|
errors.push("offset must be >= 0");
|
|
849
869
|
}
|
|
850
|
-
if (vocabulary
|
|
870
|
+
if (vocabulary) {
|
|
851
871
|
const entityTypeSet = new Set(vocabulary.entityTypes.map((et) => et.type));
|
|
852
872
|
const relationshipTypeSet = new Set(vocabulary.relationshipTypes.map((rt) => rt.type));
|
|
853
873
|
const unknownTypes = [];
|
|
854
874
|
if (spec.start?.entityType && !entityTypeSet.has(spec.start.entityType)) {
|
|
855
875
|
unknownTypes.push(`entity type "${spec.start.entityType}"`);
|
|
856
876
|
}
|
|
857
|
-
for (let i = 0; i <
|
|
858
|
-
const step =
|
|
877
|
+
for (let i = 0; i < steps.length; i++) {
|
|
878
|
+
const step = steps[i];
|
|
859
879
|
if (step.relationshipTypes) {
|
|
860
880
|
for (const rt of step.relationshipTypes) {
|
|
861
881
|
if (!relationshipTypeSet.has(rt)) {
|
|
@@ -909,7 +929,8 @@ var GremlinCompiler = class {
|
|
|
909
929
|
parts.push(compilePropertyFilter(f, nextParam));
|
|
910
930
|
}
|
|
911
931
|
}
|
|
912
|
-
|
|
932
|
+
const steps = spec.steps ?? [];
|
|
933
|
+
for (const step of steps) {
|
|
913
934
|
if (step.repeat) {
|
|
914
935
|
parts.push(compileRepeatStep(step, nextParam));
|
|
915
936
|
estimatedFanOut *= step.repeat.maxDepth * DEFAULT_ESTIMATED_FANOUT_PER_HOP;
|
|
@@ -1091,8 +1112,9 @@ var CypherCompiler = class {
|
|
|
1091
1112
|
}
|
|
1092
1113
|
}
|
|
1093
1114
|
let lastNode = startNode;
|
|
1094
|
-
|
|
1095
|
-
|
|
1115
|
+
const steps = spec.steps ?? [];
|
|
1116
|
+
for (let i = 0; i < steps.length; i++) {
|
|
1117
|
+
const step = steps[i];
|
|
1096
1118
|
const targetNode = `n${nodeIndex++}`;
|
|
1097
1119
|
const relAlias = `r${i}`;
|
|
1098
1120
|
const relTypes = step.relationshipTypes?.length ? step.relationshipTypes.join("|") : "";
|
|
@@ -1134,7 +1156,7 @@ WHERE ${whereClauses.join(" AND ")}` : "";
|
|
|
1134
1156
|
let returnClause;
|
|
1135
1157
|
if (spec.returnMode === "path") {
|
|
1136
1158
|
const allNodes = Array.from({ length: nodeIndex }, (_, i) => `n${i}`);
|
|
1137
|
-
const allRels =
|
|
1159
|
+
const allRels = steps.map((_, i) => `r${i}`);
|
|
1138
1160
|
returnClause = `RETURN ${[...allNodes, ...allRels].join(", ")}`;
|
|
1139
1161
|
} else if (spec.returnMode === "all") {
|
|
1140
1162
|
const allNodes = Array.from({ length: nodeIndex }, (_, i) => `n${i}`);
|
|
@@ -1215,6 +1237,7 @@ function matchesSingleFilter(properties, filter) {
|
|
|
1215
1237
|
|
|
1216
1238
|
// src/relationships/FallbackTraversalExecutor.ts
|
|
1217
1239
|
var MAX_STORAGE_CALLS = 500;
|
|
1240
|
+
var MAX_ENTITY_SCAN = 1e4;
|
|
1218
1241
|
var REL_FETCH_LIMIT = 1e3;
|
|
1219
1242
|
async function executeFallbackTraversal(repositoryId, storage, spec) {
|
|
1220
1243
|
const startTime = Date.now();
|
|
@@ -1224,6 +1247,9 @@ async function executeFallbackTraversal(repositoryId, storage, spec) {
|
|
|
1224
1247
|
throw new Error(`Fallback traversal exceeded ${MAX_STORAGE_CALLS} storage calls. Consider using a GraphTraversalProvider for large traversals.`);
|
|
1225
1248
|
}
|
|
1226
1249
|
};
|
|
1250
|
+
const isVertexQuery = !spec.steps || spec.steps.length === 0;
|
|
1251
|
+
const needsFullScan = isVertexQuery || spec.projection !== void 0;
|
|
1252
|
+
const fetchLimit = needsFullScan ? MAX_ENTITY_SCAN : spec.limit ?? 50;
|
|
1227
1253
|
let frontier = [];
|
|
1228
1254
|
if (spec.start.entityId) {
|
|
1229
1255
|
const entity = await resolveEntity(repositoryId, storage, spec.start.entityId);
|
|
@@ -1236,7 +1262,7 @@ async function executeFallbackTraversal(repositoryId, storage, spec) {
|
|
|
1236
1262
|
} else if (spec.start.entityType) {
|
|
1237
1263
|
const result = await storage.findEntities(repositoryId, {
|
|
1238
1264
|
entityTypes: [spec.start.entityType],
|
|
1239
|
-
limit:
|
|
1265
|
+
limit: fetchLimit,
|
|
1240
1266
|
offset: 0
|
|
1241
1267
|
});
|
|
1242
1268
|
storageCalls++;
|
|
@@ -1247,7 +1273,7 @@ async function executeFallbackTraversal(repositoryId, storage, spec) {
|
|
|
1247
1273
|
}));
|
|
1248
1274
|
} else if (spec.start.filter && spec.start.filter.length > 0) {
|
|
1249
1275
|
const result = await storage.findEntities(repositoryId, {
|
|
1250
|
-
limit:
|
|
1276
|
+
limit: fetchLimit,
|
|
1251
1277
|
offset: 0
|
|
1252
1278
|
});
|
|
1253
1279
|
storageCalls++;
|
|
@@ -1262,8 +1288,9 @@ async function executeFallbackTraversal(repositoryId, storage, spec) {
|
|
|
1262
1288
|
(entry) => matchesPropertyFilters(entry.entity.properties, spec.start.filter)
|
|
1263
1289
|
);
|
|
1264
1290
|
}
|
|
1291
|
+
const steps = spec.steps ?? [];
|
|
1265
1292
|
const allCollected = spec.returnMode === "all" ? [...frontier] : [];
|
|
1266
|
-
for (const step of
|
|
1293
|
+
for (const step of steps) {
|
|
1267
1294
|
checkCallLimit();
|
|
1268
1295
|
if (step.repeat) {
|
|
1269
1296
|
const result = await executeRepeatStep(
|
|
@@ -1316,10 +1343,89 @@ async function executeFallbackTraversal(repositoryId, storage, spec) {
|
|
|
1316
1343
|
}
|
|
1317
1344
|
const total = resultEntries.length;
|
|
1318
1345
|
const paged = resultEntries.slice(offset, offset + limit);
|
|
1319
|
-
const
|
|
1346
|
+
const suppressEntities = spec.projection !== void 0 && spec.projection.includeEntities !== true;
|
|
1347
|
+
let entities;
|
|
1348
|
+
if (suppressEntities) {
|
|
1349
|
+
entities = [];
|
|
1350
|
+
} else {
|
|
1351
|
+
entities = paged.map((entry) => {
|
|
1352
|
+
const projected = projectEntity(entry.entity, detailLevel);
|
|
1353
|
+
if (!spec.includeProvenance) {
|
|
1354
|
+
delete projected["provenance"];
|
|
1355
|
+
}
|
|
1356
|
+
return projected;
|
|
1357
|
+
});
|
|
1358
|
+
if (spec.includeRelationshipSummary && entities.length > 0) {
|
|
1359
|
+
const summaryResults = await Promise.all(
|
|
1360
|
+
paged.map(async (entry) => {
|
|
1361
|
+
storageCalls++;
|
|
1362
|
+
const result = await storage.getEntityRelationships(repositoryId, entry.entity.id, {
|
|
1363
|
+
direction: "both",
|
|
1364
|
+
limit: 1e4
|
|
1365
|
+
});
|
|
1366
|
+
const summary = { outbound: {}, inbound: {} };
|
|
1367
|
+
for (const rel of result.items) {
|
|
1368
|
+
if (rel.sourceEntityId === entry.entity.id) {
|
|
1369
|
+
summary.outbound[rel.relationshipType] = (summary.outbound[rel.relationshipType] ?? 0) + 1;
|
|
1370
|
+
}
|
|
1371
|
+
if (rel.targetEntityId === entry.entity.id) {
|
|
1372
|
+
summary.inbound[rel.relationshipType] = (summary.inbound[rel.relationshipType] ?? 0) + 1;
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
return summary;
|
|
1376
|
+
})
|
|
1377
|
+
);
|
|
1378
|
+
entities = entities.map((e, i) => ({ ...e, relationshipSummary: summaryResults[i] }));
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
let aggregations;
|
|
1382
|
+
if (spec.projection) {
|
|
1383
|
+
const propNames = spec.projection.properties;
|
|
1384
|
+
const mode = spec.projection.mode ?? "values";
|
|
1385
|
+
const distinct = spec.projection.distinct ?? false;
|
|
1386
|
+
const sourceEntries = dedup ? (() => {
|
|
1387
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1388
|
+
return (spec.returnMode === "all" ? allCollected : frontier).filter((e) => {
|
|
1389
|
+
if (seen.has(e.entity.id)) return false;
|
|
1390
|
+
seen.add(e.entity.id);
|
|
1391
|
+
return true;
|
|
1392
|
+
});
|
|
1393
|
+
})() : spec.returnMode === "all" ? allCollected : frontier;
|
|
1394
|
+
if (mode === "count" || distinct) {
|
|
1395
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1396
|
+
for (const entry of sourceEntries) {
|
|
1397
|
+
const vals = {};
|
|
1398
|
+
for (const prop of propNames) {
|
|
1399
|
+
vals[prop] = entry.entity.properties[prop] ?? null;
|
|
1400
|
+
}
|
|
1401
|
+
const key = JSON.stringify(vals);
|
|
1402
|
+
const existing = groups.get(key);
|
|
1403
|
+
if (existing) {
|
|
1404
|
+
existing.count++;
|
|
1405
|
+
} else {
|
|
1406
|
+
groups.set(key, { values: vals, count: 1 });
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
aggregations = Array.from(groups.values()).map((g) => ({
|
|
1410
|
+
values: g.values,
|
|
1411
|
+
...mode === "count" ? { count: g.count } : {}
|
|
1412
|
+
}));
|
|
1413
|
+
if (mode === "count") {
|
|
1414
|
+
aggregations.sort((a, b) => (b.count ?? 0) - (a.count ?? 0));
|
|
1415
|
+
}
|
|
1416
|
+
} else {
|
|
1417
|
+
aggregations = sourceEntries.map((entry) => {
|
|
1418
|
+
const vals = {};
|
|
1419
|
+
for (const prop of propNames) {
|
|
1420
|
+
vals[prop] = entry.entity.properties[prop] ?? null;
|
|
1421
|
+
}
|
|
1422
|
+
return { values: vals };
|
|
1423
|
+
});
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1320
1426
|
let relationships;
|
|
1321
1427
|
let paths;
|
|
1322
|
-
if (spec.returnMode === "path" || spec.returnMode === "all") {
|
|
1428
|
+
if (!suppressEntities && (spec.returnMode === "path" || spec.returnMode === "all")) {
|
|
1323
1429
|
const relMap = /* @__PURE__ */ new Map();
|
|
1324
1430
|
for (const entry of paged) {
|
|
1325
1431
|
for (const rel of entry.relationshipPath) {
|
|
@@ -1337,10 +1443,14 @@ async function executeFallbackTraversal(repositoryId, storage, spec) {
|
|
|
1337
1443
|
}
|
|
1338
1444
|
relationships = Array.from(relMap.values());
|
|
1339
1445
|
}
|
|
1340
|
-
if (spec.returnMode === "path") {
|
|
1446
|
+
if (!suppressEntities && spec.returnMode === "path") {
|
|
1341
1447
|
paths = paged.map((entry) => ({
|
|
1342
1448
|
length: entry.entityPath.length - 1,
|
|
1343
|
-
entities: [
|
|
1449
|
+
entities: [(() => {
|
|
1450
|
+
const e = projectEntity(entry.entity, detailLevel);
|
|
1451
|
+
if (!spec.includeProvenance) delete e["provenance"];
|
|
1452
|
+
return e;
|
|
1453
|
+
})()],
|
|
1344
1454
|
relationships: entry.relationshipPath.map((rel) => ({
|
|
1345
1455
|
id: rel.id,
|
|
1346
1456
|
type: rel.relationshipType,
|
|
@@ -1356,6 +1466,7 @@ async function executeFallbackTraversal(repositoryId, storage, spec) {
|
|
|
1356
1466
|
entities,
|
|
1357
1467
|
relationships,
|
|
1358
1468
|
paths,
|
|
1469
|
+
aggregations,
|
|
1359
1470
|
total,
|
|
1360
1471
|
returned: paged.length,
|
|
1361
1472
|
hasMore: offset + limit < total,
|
|
@@ -1490,8 +1601,12 @@ var GraphTraversal = class {
|
|
|
1490
1601
|
this.graphTraversalProvider = graphTraversalProvider;
|
|
1491
1602
|
this.vocabularyEngine = vocabularyEngine;
|
|
1492
1603
|
}
|
|
1493
|
-
|
|
1494
|
-
|
|
1604
|
+
repositoryId;
|
|
1605
|
+
storage;
|
|
1606
|
+
graphTraversalProvider;
|
|
1607
|
+
vocabularyEngine;
|
|
1608
|
+
/** BFS neighborhood exploration from a center entity */
|
|
1609
|
+
async exploreNeighborhood(entityId, options) {
|
|
1495
1610
|
const storageOptions = {
|
|
1496
1611
|
depth: options?.depth ?? 1,
|
|
1497
1612
|
relationshipTypes: options?.relationshipTypes,
|
|
@@ -1501,13 +1616,13 @@ var GraphTraversal = class {
|
|
|
1501
1616
|
offsetPerType: options?.offsetPerType ?? 0,
|
|
1502
1617
|
relationshipPropertyFilters: options?.relationshipPropertyFilters
|
|
1503
1618
|
};
|
|
1504
|
-
const storageResult = await this.storage.
|
|
1619
|
+
const storageResult = await this.storage.exploreNeighborhood(
|
|
1505
1620
|
this.repositoryId,
|
|
1506
1621
|
entityId,
|
|
1507
1622
|
storageOptions
|
|
1508
1623
|
);
|
|
1509
|
-
const
|
|
1510
|
-
if (!
|
|
1624
|
+
const centerEntity = await this.storage.getEntity(this.repositoryId, entityId);
|
|
1625
|
+
if (!centerEntity) {
|
|
1511
1626
|
throw new EntityNotFoundError(entityId);
|
|
1512
1627
|
}
|
|
1513
1628
|
const detailLevel = options?.detailLevel ?? "summary";
|
|
@@ -1531,11 +1646,11 @@ var GraphTraversal = class {
|
|
|
1531
1646
|
return layer;
|
|
1532
1647
|
});
|
|
1533
1648
|
return {
|
|
1534
|
-
|
|
1535
|
-
id:
|
|
1536
|
-
slug:
|
|
1537
|
-
entityType:
|
|
1538
|
-
label:
|
|
1649
|
+
center: {
|
|
1650
|
+
id: centerEntity.id,
|
|
1651
|
+
slug: centerEntity.slug,
|
|
1652
|
+
entityType: centerEntity.entityType,
|
|
1653
|
+
label: centerEntity.label
|
|
1539
1654
|
},
|
|
1540
1655
|
layers,
|
|
1541
1656
|
statistics: {
|
|
@@ -2089,8 +2204,8 @@ var MemoryRepository = class {
|
|
|
2089
2204
|
};
|
|
2090
2205
|
}
|
|
2091
2206
|
// ─── Graph Traversal ───────────────────────────────────────────────
|
|
2092
|
-
async
|
|
2093
|
-
return this.graphTraversal.
|
|
2207
|
+
async exploreNeighborhood(entityId, options) {
|
|
2208
|
+
return this.graphTraversal.exploreNeighborhood(entityId, options);
|
|
2094
2209
|
}
|
|
2095
2210
|
async findPaths(sourceId, targetId, options) {
|
|
2096
2211
|
return this.graphTraversal.findPaths(sourceId, targetId, options);
|
|
@@ -2120,7 +2235,7 @@ var MemoryRepository = class {
|
|
|
2120
2235
|
storageOptions
|
|
2121
2236
|
);
|
|
2122
2237
|
const entity = await this.storage.getEntity(this.repositoryId, entityId);
|
|
2123
|
-
const
|
|
2238
|
+
const centerRef = {
|
|
2124
2239
|
id: entityId,
|
|
2125
2240
|
slug: entity?.slug ?? entityId,
|
|
2126
2241
|
label: entity?.label ?? entityId
|
|
@@ -2166,7 +2281,7 @@ var MemoryRepository = class {
|
|
|
2166
2281
|
timestamp: e.timestamp,
|
|
2167
2282
|
eventType: e.eventType,
|
|
2168
2283
|
description: `${rel.type} relationship created ${direction} ${otherRef.label}`,
|
|
2169
|
-
relatedEntities: [
|
|
2284
|
+
relatedEntities: [centerRef, otherRef],
|
|
2170
2285
|
relationship: relDetail
|
|
2171
2286
|
};
|
|
2172
2287
|
}
|
|
@@ -2174,13 +2289,13 @@ var MemoryRepository = class {
|
|
|
2174
2289
|
return {
|
|
2175
2290
|
timestamp: e.timestamp,
|
|
2176
2291
|
eventType: e.eventType,
|
|
2177
|
-
description: `${
|
|
2178
|
-
relatedEntities: [
|
|
2292
|
+
description: `${centerRef.label} ${action}`,
|
|
2293
|
+
relatedEntities: [centerRef]
|
|
2179
2294
|
};
|
|
2180
2295
|
});
|
|
2181
2296
|
return {
|
|
2182
2297
|
id: entityId,
|
|
2183
|
-
slug:
|
|
2298
|
+
slug: centerRef.slug,
|
|
2184
2299
|
totalEvents: storageResult.total,
|
|
2185
2300
|
returned: events.length,
|
|
2186
2301
|
events
|
|
@@ -2190,6 +2305,58 @@ var MemoryRepository = class {
|
|
|
2190
2305
|
async getStats() {
|
|
2191
2306
|
return this.storage.getRepositoryStats(this.repositoryId);
|
|
2192
2307
|
}
|
|
2308
|
+
// ─── Bulk Operations ──────────────────────────────────────────────
|
|
2309
|
+
/** Delete all entities and relationships in this repository, preserving the repository and vocabulary */
|
|
2310
|
+
async deleteAllContents() {
|
|
2311
|
+
return this.storage.deleteAllContents(this.repositoryId);
|
|
2312
|
+
}
|
|
2313
|
+
/**
|
|
2314
|
+
* Returns a static markdown guide describing the recommended query strategy
|
|
2315
|
+
* for AI agents and other consumers of the repository.
|
|
2316
|
+
*/
|
|
2317
|
+
getQueryGuide() {
|
|
2318
|
+
return `## Query Strategy Guide
|
|
2319
|
+
|
|
2320
|
+
### Step 1 \u2014 Discover before you traverse
|
|
2321
|
+
|
|
2322
|
+
Before following relationships from an entity, check that it actually has relationships.
|
|
2323
|
+
\`memory_query_graph\` includes \`relationshipSummary\` on every returned entity by default
|
|
2324
|
+
(outbound and inbound counts by type). Inspect this before traversing \u2014 entities with
|
|
2325
|
+
zero outbound counts for the relationship type you need have no connections to follow.
|
|
2326
|
+
To skip summaries and reduce response size, set \`includeRelationshipSummary: false\`.
|
|
2327
|
+
|
|
2328
|
+
### Step 2 \u2014 Use projection for aggregation
|
|
2329
|
+
|
|
2330
|
+
To understand what data exists (e.g. all distinct values of a property, or counts by
|
|
2331
|
+
category), use \`memory_query_graph\` with \`projection\` instead of fetching full entities:
|
|
2332
|
+
\`{ start: { entityType: "..." }, projection: { properties: ["propName"], distinct: true }, limit: 200 }\`
|
|
2333
|
+
|
|
2334
|
+
This returns only the aggregated values \u2014 no entity objects \u2014 keeping responses lightweight.
|
|
2335
|
+
|
|
2336
|
+
### Step 3 \u2014 Traverse efficiently
|
|
2337
|
+
|
|
2338
|
+
- **Omit relationshipTypes** in a traversal step to follow all relationship types at once,
|
|
2339
|
+
rather than making separate calls per type.
|
|
2340
|
+
- **Use multi-step traversals** for multi-hop patterns. A two-step query
|
|
2341
|
+
(e.g. Equipment \u2192 Component \u2192 MaintenanceProcedure) is one call, not two sequential calls.
|
|
2342
|
+
- **Use returnMode "all"** when you need intermediate entities along the path, not just
|
|
2343
|
+
the terminal nodes.
|
|
2344
|
+
|
|
2345
|
+
### Step 4 \u2014 Use semantic search for known-unknowns
|
|
2346
|
+
|
|
2347
|
+
\`memory_search_by_concept\` is best for finding specific entities when you know roughly
|
|
2348
|
+
what you're looking for but not the exact label or type. It is not efficient for broad
|
|
2349
|
+
discovery \u2014 use projection or find_entities for that.
|
|
2350
|
+
|
|
2351
|
+
### Common anti-patterns to avoid
|
|
2352
|
+
|
|
2353
|
+
- **Don't traverse from entities with zero relationship counts.** The \`relationshipSummary\` on each entity tells you what is connected before you traverse.
|
|
2354
|
+
- **Don't split queries by relationship type.** One step with no type filter is better
|
|
2355
|
+
than three separate single-type calls.
|
|
2356
|
+
- **Don't use sequential single-hop traversals** when a multi-step query achieves the
|
|
2357
|
+
same result in one call.
|
|
2358
|
+
- **Don't fetch full entities when you only need property values.** Use projection.`;
|
|
2359
|
+
}
|
|
2193
2360
|
// ─── Events ────────────────────────────────────────────────────────
|
|
2194
2361
|
on(event, handler) {
|
|
2195
2362
|
return this.eventBus.on(event, handler);
|
|
@@ -2211,14 +2378,14 @@ var SemanticDeduplicator = class {
|
|
|
2211
2378
|
/**
|
|
2212
2379
|
* Check whether a proposed type name is a duplicate of an existing type.
|
|
2213
2380
|
* Uses embedding similarity if an EmbeddingProvider is available, otherwise falls back
|
|
2214
|
-
* to Jaro-Winkler string similarity on
|
|
2381
|
+
* to Jaro-Winkler string similarity on normalized type names.
|
|
2215
2382
|
*/
|
|
2216
2383
|
async checkDuplicate(proposedType, proposedDescription, existingTypes) {
|
|
2217
2384
|
if (existingTypes.length === 0) {
|
|
2218
2385
|
return { isDuplicate: false, matches: [] };
|
|
2219
2386
|
}
|
|
2220
|
-
const normProposed =
|
|
2221
|
-
const exactMatch = existingTypes.find((t) =>
|
|
2387
|
+
const normProposed = normalizeTypeName(proposedType);
|
|
2388
|
+
const exactMatch = existingTypes.find((t) => normalizeTypeName(t.type) === normProposed);
|
|
2222
2389
|
if (exactMatch) {
|
|
2223
2390
|
return {
|
|
2224
2391
|
isDuplicate: true,
|
|
@@ -2254,10 +2421,10 @@ var SemanticDeduplicator = class {
|
|
|
2254
2421
|
};
|
|
2255
2422
|
}
|
|
2256
2423
|
checkWithStringSimilarity(proposedType, existingTypes) {
|
|
2257
|
-
const normProposed =
|
|
2424
|
+
const normProposed = normalizeTypeName(proposedType);
|
|
2258
2425
|
const matches = [];
|
|
2259
2426
|
for (const existing of existingTypes) {
|
|
2260
|
-
const normExisting =
|
|
2427
|
+
const normExisting = normalizeTypeName(existing.type);
|
|
2261
2428
|
const similarity = jaroWinklerSimilarity(normProposed, normExisting);
|
|
2262
2429
|
if (similarity >= this.threshold) {
|
|
2263
2430
|
matches.push({
|
|
@@ -2418,7 +2585,7 @@ function canPropose(governanceConfig, _proposal) {
|
|
|
2418
2585
|
case "locked":
|
|
2419
2586
|
return {
|
|
2420
2587
|
allowed: false,
|
|
2421
|
-
reason: "Vocabulary is locked. Only
|
|
2588
|
+
reason: "Vocabulary is locked. Only organization admins can modify the vocabulary via the admin API."
|
|
2422
2589
|
};
|
|
2423
2590
|
case "managed":
|
|
2424
2591
|
case "open":
|
|
@@ -2858,9 +3025,9 @@ function validateEntityUpdate(input, entityTypeDef) {
|
|
|
2858
3025
|
return ok();
|
|
2859
3026
|
}
|
|
2860
3027
|
function validateRelationship(input, vocabulary, sourceEntityType, targetEntityType) {
|
|
2861
|
-
const
|
|
3028
|
+
const normalizedType = toScreamingSnakeCase(input.relationshipType);
|
|
2862
3029
|
const relTypeDef = vocabulary.relationshipTypes.find(
|
|
2863
|
-
(rt) => rt.type ===
|
|
3030
|
+
(rt) => rt.type === normalizedType
|
|
2864
3031
|
);
|
|
2865
3032
|
if (!relTypeDef) {
|
|
2866
3033
|
const closest = findClosestType(input.relationshipType, vocabulary.relationshipTypes);
|
|
@@ -3185,7 +3352,7 @@ var EventBus = class {
|
|
|
3185
3352
|
};
|
|
3186
3353
|
|
|
3187
3354
|
// src/portability/RepositoryExporter.ts
|
|
3188
|
-
var LIBRARY_VERSION = true ? "0.
|
|
3355
|
+
var LIBRARY_VERSION = true ? "0.4.0" : "0.1.0";
|
|
3189
3356
|
var RepositoryExporter = class {
|
|
3190
3357
|
storage;
|
|
3191
3358
|
provenance;
|
|
@@ -3401,7 +3568,7 @@ function diffArrays(from, to) {
|
|
|
3401
3568
|
// src/portability/MigrationEngine.ts
|
|
3402
3569
|
var MigrationEngine = class {
|
|
3403
3570
|
/**
|
|
3404
|
-
*
|
|
3571
|
+
* Analyze and migrate vocabulary differences between source and target.
|
|
3405
3572
|
*
|
|
3406
3573
|
* @param sourceVocabulary - vocabulary from the export archive
|
|
3407
3574
|
* @param targetVocabulary - vocabulary in the target repository
|
|
@@ -3528,13 +3695,22 @@ function describeChange(change) {
|
|
|
3528
3695
|
}
|
|
3529
3696
|
|
|
3530
3697
|
// src/portability/RepositoryImporter.ts
|
|
3698
|
+
function estimateChunkCount(totalEntities, totalRelationships) {
|
|
3699
|
+
const ESTIMATED_CHUNK_SIZE = 100;
|
|
3700
|
+
return Math.max(
|
|
3701
|
+
1,
|
|
3702
|
+
Math.ceil(totalEntities / ESTIMATED_CHUNK_SIZE) + Math.ceil(totalRelationships / ESTIMATED_CHUNK_SIZE)
|
|
3703
|
+
);
|
|
3704
|
+
}
|
|
3531
3705
|
var RepositoryImporter = class {
|
|
3532
3706
|
storage;
|
|
3533
3707
|
actorId;
|
|
3708
|
+
onProgress;
|
|
3534
3709
|
migrationEngine = new MigrationEngine();
|
|
3535
3710
|
constructor(config) {
|
|
3536
3711
|
this.storage = config.storage;
|
|
3537
3712
|
this.actorId = config.actorId;
|
|
3713
|
+
this.onProgress = config.onProgress;
|
|
3538
3714
|
}
|
|
3539
3715
|
/**
|
|
3540
3716
|
* Import an export archive according to the provided options.
|
|
@@ -3585,26 +3761,34 @@ var RepositoryImporter = class {
|
|
|
3585
3761
|
return this.importStreamMerge(header, chunks, options, warnings);
|
|
3586
3762
|
}
|
|
3587
3763
|
}
|
|
3588
|
-
/** Streaming create mode —
|
|
3764
|
+
/** Streaming create mode — fast bulk inserts with no existence checks */
|
|
3589
3765
|
async importStreamCreate(header, chunks, options, warnings) {
|
|
3590
3766
|
const target = options.target;
|
|
3591
|
-
const
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3767
|
+
const existing = await this.storage.getRepository(target.repositoryId);
|
|
3768
|
+
if (!existing) {
|
|
3769
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3770
|
+
await this.storage.createRepository({
|
|
3771
|
+
repositoryId: target.repositoryId,
|
|
3772
|
+
type: target.config.type,
|
|
3773
|
+
label: target.config.label,
|
|
3774
|
+
description: target.config.description,
|
|
3775
|
+
governanceConfig: target.config.governance ?? { mode: "open" },
|
|
3776
|
+
createdAt: now,
|
|
3777
|
+
createdBy: this.actorId
|
|
3778
|
+
});
|
|
3779
|
+
}
|
|
3601
3780
|
await this.storage.saveVocabulary(target.repositoryId, header.vocabulary);
|
|
3781
|
+
const totalEntities = header.manifest?.statistics?.entityCount ?? 0;
|
|
3782
|
+
const totalRelationships = header.manifest?.statistics?.relationshipCount ?? 0;
|
|
3783
|
+
const totalChunks = estimateChunkCount(totalEntities, totalRelationships);
|
|
3602
3784
|
let entitiesImported = 0;
|
|
3603
3785
|
let relationshipsImported = 0;
|
|
3786
|
+
let chunksCompleted = 0;
|
|
3604
3787
|
for await (const chunk of chunks) {
|
|
3605
|
-
const bulkResult = await this.storage.importBulk(target.repositoryId, [chunk]);
|
|
3788
|
+
const bulkResult = await this.storage.importBulk(target.repositoryId, [chunk], { skipExistenceCheck: true });
|
|
3606
3789
|
entitiesImported += bulkResult.entitiesImported;
|
|
3607
3790
|
relationshipsImported += bulkResult.relationshipsImported;
|
|
3791
|
+
chunksCompleted++;
|
|
3608
3792
|
for (const e of bulkResult.errors) {
|
|
3609
3793
|
warnings.push({
|
|
3610
3794
|
code: "import_error",
|
|
@@ -3612,6 +3796,15 @@ var RepositoryImporter = class {
|
|
|
3612
3796
|
id: e.item
|
|
3613
3797
|
});
|
|
3614
3798
|
}
|
|
3799
|
+
await this.onProgress?.({
|
|
3800
|
+
repositoryId: target.repositoryId,
|
|
3801
|
+
entitiesImported,
|
|
3802
|
+
relationshipsImported,
|
|
3803
|
+
totalEntities,
|
|
3804
|
+
totalRelationships,
|
|
3805
|
+
chunksCompleted,
|
|
3806
|
+
totalChunks
|
|
3807
|
+
});
|
|
3615
3808
|
}
|
|
3616
3809
|
return {
|
|
3617
3810
|
success: true,
|
|
@@ -3659,11 +3852,15 @@ var RepositoryImporter = class {
|
|
|
3659
3852
|
if (migrationResult.mergedVocabulary && migrationResult.mergedVocabulary !== targetVocabulary) {
|
|
3660
3853
|
await this.storage.saveVocabulary(repositoryId, migrationResult.mergedVocabulary);
|
|
3661
3854
|
}
|
|
3855
|
+
const totalEntities = header.manifest?.statistics?.entityCount ?? 0;
|
|
3856
|
+
const totalRelationships = header.manifest?.statistics?.relationshipCount ?? 0;
|
|
3857
|
+
const totalChunks = estimateChunkCount(totalEntities, totalRelationships);
|
|
3662
3858
|
const entityConflict = options.entityConflict ?? "skip";
|
|
3663
3859
|
let entitiesImported = 0;
|
|
3664
3860
|
let entitiesSkipped = 0;
|
|
3665
3861
|
let relationshipsImported = 0;
|
|
3666
3862
|
let relationshipsSkipped = 0;
|
|
3863
|
+
let chunksCompleted = 0;
|
|
3667
3864
|
for await (const chunk of chunks) {
|
|
3668
3865
|
if (chunk.entities) {
|
|
3669
3866
|
for (const entity of chunk.entities) {
|
|
@@ -3745,6 +3942,16 @@ var RepositoryImporter = class {
|
|
|
3745
3942
|
}
|
|
3746
3943
|
}
|
|
3747
3944
|
}
|
|
3945
|
+
chunksCompleted++;
|
|
3946
|
+
await this.onProgress?.({
|
|
3947
|
+
repositoryId,
|
|
3948
|
+
entitiesImported,
|
|
3949
|
+
relationshipsImported,
|
|
3950
|
+
totalEntities,
|
|
3951
|
+
totalRelationships,
|
|
3952
|
+
chunksCompleted,
|
|
3953
|
+
totalChunks
|
|
3954
|
+
});
|
|
3748
3955
|
}
|
|
3749
3956
|
const vocabExtensions = migrationResult.warnings.filter(
|
|
3750
3957
|
(w) => w.code === "entity_type_added" || w.code === "relationship_type_added"
|
|
@@ -3801,7 +4008,7 @@ var DeepMemory = class {
|
|
|
3801
4008
|
graphTraversalProvider;
|
|
3802
4009
|
provenance;
|
|
3803
4010
|
globalEventBus;
|
|
3804
|
-
|
|
4011
|
+
initialized = false;
|
|
3805
4012
|
constructor(config) {
|
|
3806
4013
|
this.storage = config.storage;
|
|
3807
4014
|
this.search = config.search;
|
|
@@ -3811,16 +4018,16 @@ var DeepMemory = class {
|
|
|
3811
4018
|
this.provenance = new ProvenanceTracker(config.provenance);
|
|
3812
4019
|
this.globalEventBus = new EventBus(config.provenance);
|
|
3813
4020
|
}
|
|
3814
|
-
/**
|
|
3815
|
-
async
|
|
3816
|
-
if (!this.
|
|
3817
|
-
if (this.storage.
|
|
3818
|
-
await this.storage.
|
|
4021
|
+
/** Initialize the storage provider and optional providers (call once before use) */
|
|
4022
|
+
async ensureInitialized() {
|
|
4023
|
+
if (!this.initialized) {
|
|
4024
|
+
if (this.storage.initialize) {
|
|
4025
|
+
await this.storage.initialize();
|
|
3819
4026
|
}
|
|
3820
|
-
if (this.graphTraversalProvider?.
|
|
3821
|
-
await this.graphTraversalProvider.
|
|
4027
|
+
if (this.graphTraversalProvider?.initialize) {
|
|
4028
|
+
await this.graphTraversalProvider.initialize();
|
|
3822
4029
|
}
|
|
3823
|
-
this.
|
|
4030
|
+
this.initialized = true;
|
|
3824
4031
|
}
|
|
3825
4032
|
}
|
|
3826
4033
|
/** Create or migrate the storage schema. Call once at deployment / startup, not per-request. */
|
|
@@ -3832,15 +4039,15 @@ var DeepMemory = class {
|
|
|
3832
4039
|
schemaVersion: 0
|
|
3833
4040
|
};
|
|
3834
4041
|
try {
|
|
3835
|
-
await this.
|
|
4042
|
+
await this.ensureInitialized();
|
|
3836
4043
|
} catch {
|
|
3837
4044
|
}
|
|
3838
4045
|
let result = noOpResult;
|
|
3839
4046
|
if (this.storage.ensureSchema) {
|
|
3840
4047
|
result = await this.storage.ensureSchema();
|
|
3841
4048
|
}
|
|
3842
|
-
if (!this.
|
|
3843
|
-
await this.
|
|
4049
|
+
if (!this.initialized) {
|
|
4050
|
+
await this.ensureInitialized();
|
|
3844
4051
|
}
|
|
3845
4052
|
return result;
|
|
3846
4053
|
}
|
|
@@ -3855,7 +4062,7 @@ var DeepMemory = class {
|
|
|
3855
4062
|
}
|
|
3856
4063
|
/** Create a new memory repository */
|
|
3857
4064
|
async createRepository(config) {
|
|
3858
|
-
await this.
|
|
4065
|
+
await this.ensureInitialized();
|
|
3859
4066
|
const repositoryId = config.repositoryId ?? generateId();
|
|
3860
4067
|
this.validateRepositoryId(repositoryId);
|
|
3861
4068
|
const context = this.provenance.getContext();
|
|
@@ -3912,7 +4119,7 @@ var DeepMemory = class {
|
|
|
3912
4119
|
}
|
|
3913
4120
|
/** Open an existing repository */
|
|
3914
4121
|
async openRepository(repositoryId) {
|
|
3915
|
-
await this.
|
|
4122
|
+
await this.ensureInitialized();
|
|
3916
4123
|
this.validateRepositoryId(repositoryId);
|
|
3917
4124
|
const storedRepo = await this.storage.getRepository(repositoryId);
|
|
3918
4125
|
if (!storedRepo) {
|
|
@@ -3941,7 +4148,7 @@ var DeepMemory = class {
|
|
|
3941
4148
|
}
|
|
3942
4149
|
/** List all repositories */
|
|
3943
4150
|
async listRepositories(filter) {
|
|
3944
|
-
await this.
|
|
4151
|
+
await this.ensureInitialized();
|
|
3945
4152
|
const result = await this.storage.listRepositories(filter);
|
|
3946
4153
|
return {
|
|
3947
4154
|
items: result.items.map((stored) => ({
|
|
@@ -3959,7 +4166,7 @@ var DeepMemory = class {
|
|
|
3959
4166
|
}
|
|
3960
4167
|
/** Update repository metadata and settings */
|
|
3961
4168
|
async updateRepository(repositoryId, updates) {
|
|
3962
|
-
await this.
|
|
4169
|
+
await this.ensureInitialized();
|
|
3963
4170
|
this.validateRepositoryId(repositoryId);
|
|
3964
4171
|
const updated = await this.storage.updateRepository(repositoryId, updates);
|
|
3965
4172
|
await this.globalEventBus.emit("repository:updated", { repositoryId });
|
|
@@ -3967,14 +4174,55 @@ var DeepMemory = class {
|
|
|
3967
4174
|
}
|
|
3968
4175
|
/** Delete a repository */
|
|
3969
4176
|
async deleteRepository(repositoryId) {
|
|
3970
|
-
await this.
|
|
4177
|
+
await this.ensureInitialized();
|
|
3971
4178
|
this.validateRepositoryId(repositoryId);
|
|
3972
|
-
await this.storage.
|
|
4179
|
+
const stats = await this.storage.getRepositoryStats(repositoryId);
|
|
4180
|
+
await this.globalEventBus.emit("delete:started", {
|
|
4181
|
+
repositoryId,
|
|
4182
|
+
totalEntities: stats.entityCount,
|
|
4183
|
+
totalRelationships: stats.relationshipCount
|
|
4184
|
+
});
|
|
4185
|
+
await this.storage.deleteRepository(
|
|
4186
|
+
repositoryId,
|
|
4187
|
+
(progress) => this.globalEventBus.emit("delete:progress", { repositoryId, ...progress })
|
|
4188
|
+
);
|
|
4189
|
+
await this.globalEventBus.emit("delete:completed", {
|
|
4190
|
+
repositoryId,
|
|
4191
|
+
entitiesDeleted: stats.entityCount,
|
|
4192
|
+
relationshipsDeleted: stats.relationshipCount
|
|
4193
|
+
});
|
|
3973
4194
|
await this.globalEventBus.emit("repository:deleted", { repositoryId });
|
|
3974
4195
|
}
|
|
4196
|
+
/** Delete all entities and relationships in a repository, preserving the repository and vocabulary */
|
|
4197
|
+
async deleteAllContents(repositoryId) {
|
|
4198
|
+
await this.ensureInitialized();
|
|
4199
|
+
this.validateRepositoryId(repositoryId);
|
|
4200
|
+
const stats = await this.storage.getRepositoryStats(repositoryId);
|
|
4201
|
+
await this.globalEventBus.emit("delete:started", {
|
|
4202
|
+
repositoryId,
|
|
4203
|
+
totalEntities: stats.entityCount,
|
|
4204
|
+
totalRelationships: stats.relationshipCount
|
|
4205
|
+
});
|
|
4206
|
+
const result = await this.storage.deleteAllContents(
|
|
4207
|
+
repositoryId,
|
|
4208
|
+
(progress) => this.globalEventBus.emit("delete:progress", { repositoryId, ...progress })
|
|
4209
|
+
);
|
|
4210
|
+
await this.globalEventBus.emit("delete:completed", {
|
|
4211
|
+
repositoryId,
|
|
4212
|
+
entitiesDeleted: result.deletedEntities,
|
|
4213
|
+
relationshipsDeleted: result.deletedRelationships
|
|
4214
|
+
});
|
|
4215
|
+
return result;
|
|
4216
|
+
}
|
|
3975
4217
|
/** Export a repository to a portable archive */
|
|
3976
4218
|
async exportRepository(repositoryId, options) {
|
|
3977
|
-
await this.
|
|
4219
|
+
await this.ensureInitialized();
|
|
4220
|
+
const stats = await this.storage.getRepositoryStats(repositoryId);
|
|
4221
|
+
await this.globalEventBus.emit("export:started", {
|
|
4222
|
+
repositoryId,
|
|
4223
|
+
totalEntities: stats.entityCount,
|
|
4224
|
+
totalRelationships: stats.relationshipCount
|
|
4225
|
+
});
|
|
3978
4226
|
const exporter = new RepositoryExporter({
|
|
3979
4227
|
storage: this.storage,
|
|
3980
4228
|
provenance: this.provenance.getContext(),
|
|
@@ -3990,10 +4238,12 @@ var DeepMemory = class {
|
|
|
3990
4238
|
}
|
|
3991
4239
|
/** Import a repository from a portable archive */
|
|
3992
4240
|
async importRepository(archive, options) {
|
|
3993
|
-
await this.
|
|
4241
|
+
await this.ensureInitialized();
|
|
4242
|
+
await this.globalEventBus.emit("import:started", { repositoryId: options.target.repositoryId });
|
|
3994
4243
|
const importer = new RepositoryImporter({
|
|
3995
4244
|
storage: this.storage,
|
|
3996
|
-
actorId: this.provenance.getContext().actorId
|
|
4245
|
+
actorId: this.provenance.getContext().actorId,
|
|
4246
|
+
onProgress: (progress) => this.globalEventBus.emit("import:progress", progress)
|
|
3997
4247
|
});
|
|
3998
4248
|
const result = await importer.import(archive, options);
|
|
3999
4249
|
if (result.success) {
|
|
@@ -4016,21 +4266,50 @@ var DeepMemory = class {
|
|
|
4016
4266
|
* Use for large repositories to avoid loading everything into memory.
|
|
4017
4267
|
*/
|
|
4018
4268
|
async *exportRepositoryStream(repositoryId, options) {
|
|
4019
|
-
await this.
|
|
4269
|
+
await this.ensureInitialized();
|
|
4020
4270
|
const exporter = new RepositoryExporter({
|
|
4021
4271
|
storage: this.storage,
|
|
4022
4272
|
provenance: this.provenance.getContext(),
|
|
4023
4273
|
legal: options?.legal
|
|
4024
4274
|
});
|
|
4025
|
-
await this.
|
|
4275
|
+
const stats = await this.storage.getRepositoryStats(repositoryId);
|
|
4276
|
+
const totalEntities = stats.entityCount;
|
|
4277
|
+
const totalRelationships = stats.relationshipCount;
|
|
4278
|
+
const estimatedChunkSize = 100;
|
|
4279
|
+
const totalChunks = Math.max(
|
|
4280
|
+
1,
|
|
4281
|
+
Math.ceil(totalEntities / estimatedChunkSize) + Math.ceil(totalRelationships / estimatedChunkSize)
|
|
4282
|
+
);
|
|
4283
|
+
await this.globalEventBus.emit("export:started", { repositoryId, totalEntities, totalRelationships });
|
|
4026
4284
|
let entityCount = 0;
|
|
4027
4285
|
let relationshipCount = 0;
|
|
4286
|
+
let chunksCompleted = 0;
|
|
4028
4287
|
for await (const item of exporter.exportStream(repositoryId)) {
|
|
4029
4288
|
yield item;
|
|
4030
4289
|
if (item.type === "entities") {
|
|
4031
4290
|
entityCount += item.data.length;
|
|
4291
|
+
chunksCompleted++;
|
|
4292
|
+
await this.globalEventBus.emit("export:progress", {
|
|
4293
|
+
repositoryId,
|
|
4294
|
+
entitiesExported: entityCount,
|
|
4295
|
+
relationshipsExported: relationshipCount,
|
|
4296
|
+
totalEntities,
|
|
4297
|
+
totalRelationships,
|
|
4298
|
+
chunksCompleted,
|
|
4299
|
+
totalChunks
|
|
4300
|
+
});
|
|
4032
4301
|
} else if (item.type === "relationships") {
|
|
4033
4302
|
relationshipCount += item.data.length;
|
|
4303
|
+
chunksCompleted++;
|
|
4304
|
+
await this.globalEventBus.emit("export:progress", {
|
|
4305
|
+
repositoryId,
|
|
4306
|
+
entitiesExported: entityCount,
|
|
4307
|
+
relationshipsExported: relationshipCount,
|
|
4308
|
+
totalEntities,
|
|
4309
|
+
totalRelationships,
|
|
4310
|
+
chunksCompleted,
|
|
4311
|
+
totalChunks
|
|
4312
|
+
});
|
|
4034
4313
|
}
|
|
4035
4314
|
}
|
|
4036
4315
|
await this.globalEventBus.emit("export:completed", {
|
|
@@ -4044,10 +4323,11 @@ var DeepMemory = class {
|
|
|
4044
4323
|
* Use for large repositories to avoid loading everything into memory.
|
|
4045
4324
|
*/
|
|
4046
4325
|
async importRepositoryStream(header, chunks, options) {
|
|
4047
|
-
await this.
|
|
4326
|
+
await this.ensureInitialized();
|
|
4048
4327
|
const importer = new RepositoryImporter({
|
|
4049
4328
|
storage: this.storage,
|
|
4050
|
-
actorId: this.provenance.getContext().actorId
|
|
4329
|
+
actorId: this.provenance.getContext().actorId,
|
|
4330
|
+
onProgress: (progress) => this.globalEventBus.emit("import:progress", progress)
|
|
4051
4331
|
});
|
|
4052
4332
|
await this.globalEventBus.emit("import:started", { repositoryId: options.target.repositoryId });
|
|
4053
4333
|
const result = await importer.importStream(header, chunks, options);
|
|
@@ -4083,7 +4363,7 @@ var DeepMemory = class {
|
|
|
4083
4363
|
if (this.storage.dispose) {
|
|
4084
4364
|
await this.storage.dispose();
|
|
4085
4365
|
}
|
|
4086
|
-
this.
|
|
4366
|
+
this.initialized = false;
|
|
4087
4367
|
}
|
|
4088
4368
|
};
|
|
4089
4369
|
|
|
@@ -4098,7 +4378,7 @@ var InMemoryStorageProvider = class {
|
|
|
4098
4378
|
return store;
|
|
4099
4379
|
}
|
|
4100
4380
|
// ─── Lifecycle ─────────────────────────────────────────────────────
|
|
4101
|
-
async
|
|
4381
|
+
async initialize() {
|
|
4102
4382
|
}
|
|
4103
4383
|
async dispose() {
|
|
4104
4384
|
this.stores.clear();
|
|
@@ -4171,12 +4451,21 @@ var InMemoryStorageProvider = class {
|
|
|
4171
4451
|
if (updates.metadata !== void 0) repo.metadata = { ...repo.metadata, ...updates.metadata };
|
|
4172
4452
|
return repo;
|
|
4173
4453
|
}
|
|
4174
|
-
async deleteRepository(repositoryId) {
|
|
4454
|
+
async deleteRepository(repositoryId, _onProgress) {
|
|
4175
4455
|
if (!this.stores.has(repositoryId)) {
|
|
4176
4456
|
throw new RepositoryNotFoundError(repositoryId);
|
|
4177
4457
|
}
|
|
4178
4458
|
this.stores.delete(repositoryId);
|
|
4179
4459
|
}
|
|
4460
|
+
async deleteAllContents(repositoryId, _onProgress) {
|
|
4461
|
+
const store = this.getStore(repositoryId);
|
|
4462
|
+
const deletedEntities = store.entities.size;
|
|
4463
|
+
const deletedRelationships = store.relationships.size;
|
|
4464
|
+
store.entities.clear();
|
|
4465
|
+
store.slugIndex.clear();
|
|
4466
|
+
store.relationships.clear();
|
|
4467
|
+
return { deletedEntities, deletedRelationships };
|
|
4468
|
+
}
|
|
4180
4469
|
async getRepositoryStats(repositoryId) {
|
|
4181
4470
|
const store = this.getStore(repositoryId);
|
|
4182
4471
|
const entityTypeBreakdown = {};
|
|
@@ -4406,7 +4695,7 @@ var InMemoryStorageProvider = class {
|
|
|
4406
4695
|
return { deletedRelationships };
|
|
4407
4696
|
}
|
|
4408
4697
|
// ─── Graph Traversal ───────────────────────────────────────────────
|
|
4409
|
-
async
|
|
4698
|
+
async exploreNeighborhood(repositoryId, entityId, options) {
|
|
4410
4699
|
const store = this.getStore(repositoryId);
|
|
4411
4700
|
if (!store.entities.has(entityId)) {
|
|
4412
4701
|
throw new EntityNotFoundError(entityId);
|
|
@@ -4472,7 +4761,7 @@ var InMemoryStorageProvider = class {
|
|
|
4472
4761
|
if (nextFrontier.size === 0) break;
|
|
4473
4762
|
}
|
|
4474
4763
|
return {
|
|
4475
|
-
|
|
4764
|
+
centerId: entityId,
|
|
4476
4765
|
layers
|
|
4477
4766
|
};
|
|
4478
4767
|
}
|
|
@@ -4631,7 +4920,7 @@ var InMemoryStorageProvider = class {
|
|
|
4631
4920
|
yield { type: "relationships", data: [], sequence: 0, isLast: true };
|
|
4632
4921
|
}
|
|
4633
4922
|
}
|
|
4634
|
-
async importBulk(repositoryId, data) {
|
|
4923
|
+
async importBulk(repositoryId, data, _options) {
|
|
4635
4924
|
const store = this.getStore(repositoryId);
|
|
4636
4925
|
let entitiesImported = 0;
|
|
4637
4926
|
let relationshipsImported = 0;
|