@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.cjs
CHANGED
|
@@ -332,6 +332,12 @@ var EntityManager = class {
|
|
|
332
332
|
this.storage = storage;
|
|
333
333
|
this.embedding = embedding;
|
|
334
334
|
}
|
|
335
|
+
repositoryId;
|
|
336
|
+
vocabularyEngine;
|
|
337
|
+
provenanceTracker;
|
|
338
|
+
eventBus;
|
|
339
|
+
storage;
|
|
340
|
+
embedding;
|
|
335
341
|
/** Create one or more entities with vocabulary validation, ID generation, provenance, and events */
|
|
336
342
|
async create(inputs) {
|
|
337
343
|
const results = [];
|
|
@@ -695,7 +701,7 @@ function jaroWinklerSimilarity(a, b, scalingFactor = 0.1) {
|
|
|
695
701
|
}
|
|
696
702
|
return jaro + commonPrefix * Math.min(scalingFactor, 0.25) * (1 - jaro);
|
|
697
703
|
}
|
|
698
|
-
function
|
|
704
|
+
function normalizeTypeName(name) {
|
|
699
705
|
return name.toLowerCase().trim().replace(/[-\s.]+/g, "_").replace(/[^a-z0-9_]/g, "");
|
|
700
706
|
}
|
|
701
707
|
function toScreamingSnakeCase(name) {
|
|
@@ -711,6 +717,11 @@ var RelationshipManager = class {
|
|
|
711
717
|
this.eventBus = eventBus;
|
|
712
718
|
this.storage = storage;
|
|
713
719
|
}
|
|
720
|
+
repositoryId;
|
|
721
|
+
vocabularyEngine;
|
|
722
|
+
provenanceTracker;
|
|
723
|
+
eventBus;
|
|
724
|
+
storage;
|
|
714
725
|
/** Create one or more relationships with vocabulary validation, provenance, and events */
|
|
715
726
|
async create(inputs) {
|
|
716
727
|
const results = [];
|
|
@@ -850,6 +861,7 @@ var DEFAULT_MAX_LIMIT = 200;
|
|
|
850
861
|
var DEFAULT_FALLBACK_MAX_DEPTH = 10;
|
|
851
862
|
function validateTraversalSpec(spec, vocabulary, capabilities) {
|
|
852
863
|
const errors = [];
|
|
864
|
+
const steps = spec.steps ?? [];
|
|
853
865
|
if (!spec.start) {
|
|
854
866
|
errors.push("start is required");
|
|
855
867
|
} else {
|
|
@@ -863,37 +875,45 @@ function validateTraversalSpec(spec, vocabulary, capabilities) {
|
|
|
863
875
|
errors.push("start.entityType without entityId requires limit on the spec to prevent full type scans");
|
|
864
876
|
}
|
|
865
877
|
}
|
|
866
|
-
if (!spec.steps || spec.steps.length === 0) {
|
|
867
|
-
errors.push("steps must contain at least one step");
|
|
868
|
-
}
|
|
869
878
|
const maxSteps = capabilities?.maxTraversalDepth ?? DEFAULT_MAX_STEPS;
|
|
870
|
-
if (
|
|
871
|
-
errors.push(`steps length ${
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
if (step.repeat) {
|
|
880
|
-
|
|
881
|
-
errors.push(`steps[${i}].repeat.maxDepth must be a positive number`);
|
|
882
|
-
}
|
|
879
|
+
if (steps.length > maxSteps) {
|
|
880
|
+
errors.push(`steps length ${steps.length} exceeds maximum ${maxSteps}`);
|
|
881
|
+
}
|
|
882
|
+
for (let i = 0; i < steps.length; i++) {
|
|
883
|
+
const step = steps[i];
|
|
884
|
+
if (!step.direction || !["out", "in", "both"].includes(step.direction)) {
|
|
885
|
+
errors.push(`steps[${i}].direction must be 'out', 'in', or 'both'`);
|
|
886
|
+
}
|
|
887
|
+
if (step.repeat) {
|
|
888
|
+
if (step.repeat.maxDepth === void 0 || step.repeat.maxDepth <= 0) {
|
|
889
|
+
errors.push(`steps[${i}].repeat.maxDepth must be a positive number`);
|
|
883
890
|
}
|
|
884
891
|
}
|
|
892
|
+
}
|
|
893
|
+
if (steps.length > 0) {
|
|
885
894
|
const maxProviderDepth = capabilities?.maxTraversalDepth ?? DEFAULT_FALLBACK_MAX_DEPTH;
|
|
886
895
|
let totalDepth = 0;
|
|
887
|
-
for (const step of
|
|
896
|
+
for (const step of steps) {
|
|
888
897
|
totalDepth += step.repeat ? step.repeat.maxDepth : 1;
|
|
889
898
|
}
|
|
890
899
|
if (totalDepth > maxProviderDepth) {
|
|
891
900
|
errors.push(`total potential depth ${totalDepth} exceeds provider maximum ${maxProviderDepth}`);
|
|
892
901
|
}
|
|
893
902
|
}
|
|
903
|
+
if (spec.returnMode === "path" && steps.length === 0) {
|
|
904
|
+
errors.push("returnMode 'path' requires at least one step");
|
|
905
|
+
}
|
|
894
906
|
if (spec.returnMode && !["terminal", "path", "all"].includes(spec.returnMode)) {
|
|
895
907
|
errors.push(`returnMode must be 'terminal', 'path', or 'all'`);
|
|
896
908
|
}
|
|
909
|
+
if (spec.projection) {
|
|
910
|
+
if (!spec.projection.properties || spec.projection.properties.length === 0) {
|
|
911
|
+
errors.push("projection.properties must contain at least one property name");
|
|
912
|
+
}
|
|
913
|
+
if (spec.projection.mode && !["count", "values"].includes(spec.projection.mode)) {
|
|
914
|
+
errors.push("projection.mode must be 'count' or 'values'");
|
|
915
|
+
}
|
|
916
|
+
}
|
|
897
917
|
if (spec.limit !== void 0) {
|
|
898
918
|
if (spec.limit < 1 || spec.limit > DEFAULT_MAX_LIMIT) {
|
|
899
919
|
errors.push(`limit must be between 1 and ${DEFAULT_MAX_LIMIT}`);
|
|
@@ -902,15 +922,15 @@ function validateTraversalSpec(spec, vocabulary, capabilities) {
|
|
|
902
922
|
if (spec.offset !== void 0 && spec.offset < 0) {
|
|
903
923
|
errors.push("offset must be >= 0");
|
|
904
924
|
}
|
|
905
|
-
if (vocabulary
|
|
925
|
+
if (vocabulary) {
|
|
906
926
|
const entityTypeSet = new Set(vocabulary.entityTypes.map((et) => et.type));
|
|
907
927
|
const relationshipTypeSet = new Set(vocabulary.relationshipTypes.map((rt) => rt.type));
|
|
908
928
|
const unknownTypes = [];
|
|
909
929
|
if (spec.start?.entityType && !entityTypeSet.has(spec.start.entityType)) {
|
|
910
930
|
unknownTypes.push(`entity type "${spec.start.entityType}"`);
|
|
911
931
|
}
|
|
912
|
-
for (let i = 0; i <
|
|
913
|
-
const step =
|
|
932
|
+
for (let i = 0; i < steps.length; i++) {
|
|
933
|
+
const step = steps[i];
|
|
914
934
|
if (step.relationshipTypes) {
|
|
915
935
|
for (const rt of step.relationshipTypes) {
|
|
916
936
|
if (!relationshipTypeSet.has(rt)) {
|
|
@@ -964,7 +984,8 @@ var GremlinCompiler = class {
|
|
|
964
984
|
parts.push(compilePropertyFilter(f, nextParam));
|
|
965
985
|
}
|
|
966
986
|
}
|
|
967
|
-
|
|
987
|
+
const steps = spec.steps ?? [];
|
|
988
|
+
for (const step of steps) {
|
|
968
989
|
if (step.repeat) {
|
|
969
990
|
parts.push(compileRepeatStep(step, nextParam));
|
|
970
991
|
estimatedFanOut *= step.repeat.maxDepth * DEFAULT_ESTIMATED_FANOUT_PER_HOP;
|
|
@@ -1146,8 +1167,9 @@ var CypherCompiler = class {
|
|
|
1146
1167
|
}
|
|
1147
1168
|
}
|
|
1148
1169
|
let lastNode = startNode;
|
|
1149
|
-
|
|
1150
|
-
|
|
1170
|
+
const steps = spec.steps ?? [];
|
|
1171
|
+
for (let i = 0; i < steps.length; i++) {
|
|
1172
|
+
const step = steps[i];
|
|
1151
1173
|
const targetNode = `n${nodeIndex++}`;
|
|
1152
1174
|
const relAlias = `r${i}`;
|
|
1153
1175
|
const relTypes = step.relationshipTypes?.length ? step.relationshipTypes.join("|") : "";
|
|
@@ -1189,7 +1211,7 @@ WHERE ${whereClauses.join(" AND ")}` : "";
|
|
|
1189
1211
|
let returnClause;
|
|
1190
1212
|
if (spec.returnMode === "path") {
|
|
1191
1213
|
const allNodes = Array.from({ length: nodeIndex }, (_, i) => `n${i}`);
|
|
1192
|
-
const allRels =
|
|
1214
|
+
const allRels = steps.map((_, i) => `r${i}`);
|
|
1193
1215
|
returnClause = `RETURN ${[...allNodes, ...allRels].join(", ")}`;
|
|
1194
1216
|
} else if (spec.returnMode === "all") {
|
|
1195
1217
|
const allNodes = Array.from({ length: nodeIndex }, (_, i) => `n${i}`);
|
|
@@ -1270,6 +1292,7 @@ function matchesSingleFilter(properties, filter) {
|
|
|
1270
1292
|
|
|
1271
1293
|
// src/relationships/FallbackTraversalExecutor.ts
|
|
1272
1294
|
var MAX_STORAGE_CALLS = 500;
|
|
1295
|
+
var MAX_ENTITY_SCAN = 1e4;
|
|
1273
1296
|
var REL_FETCH_LIMIT = 1e3;
|
|
1274
1297
|
async function executeFallbackTraversal(repositoryId, storage, spec) {
|
|
1275
1298
|
const startTime = Date.now();
|
|
@@ -1279,6 +1302,9 @@ async function executeFallbackTraversal(repositoryId, storage, spec) {
|
|
|
1279
1302
|
throw new Error(`Fallback traversal exceeded ${MAX_STORAGE_CALLS} storage calls. Consider using a GraphTraversalProvider for large traversals.`);
|
|
1280
1303
|
}
|
|
1281
1304
|
};
|
|
1305
|
+
const isVertexQuery = !spec.steps || spec.steps.length === 0;
|
|
1306
|
+
const needsFullScan = isVertexQuery || spec.projection !== void 0;
|
|
1307
|
+
const fetchLimit = needsFullScan ? MAX_ENTITY_SCAN : spec.limit ?? 50;
|
|
1282
1308
|
let frontier = [];
|
|
1283
1309
|
if (spec.start.entityId) {
|
|
1284
1310
|
const entity = await resolveEntity(repositoryId, storage, spec.start.entityId);
|
|
@@ -1291,7 +1317,7 @@ async function executeFallbackTraversal(repositoryId, storage, spec) {
|
|
|
1291
1317
|
} else if (spec.start.entityType) {
|
|
1292
1318
|
const result = await storage.findEntities(repositoryId, {
|
|
1293
1319
|
entityTypes: [spec.start.entityType],
|
|
1294
|
-
limit:
|
|
1320
|
+
limit: fetchLimit,
|
|
1295
1321
|
offset: 0
|
|
1296
1322
|
});
|
|
1297
1323
|
storageCalls++;
|
|
@@ -1302,7 +1328,7 @@ async function executeFallbackTraversal(repositoryId, storage, spec) {
|
|
|
1302
1328
|
}));
|
|
1303
1329
|
} else if (spec.start.filter && spec.start.filter.length > 0) {
|
|
1304
1330
|
const result = await storage.findEntities(repositoryId, {
|
|
1305
|
-
limit:
|
|
1331
|
+
limit: fetchLimit,
|
|
1306
1332
|
offset: 0
|
|
1307
1333
|
});
|
|
1308
1334
|
storageCalls++;
|
|
@@ -1317,8 +1343,9 @@ async function executeFallbackTraversal(repositoryId, storage, spec) {
|
|
|
1317
1343
|
(entry) => matchesPropertyFilters(entry.entity.properties, spec.start.filter)
|
|
1318
1344
|
);
|
|
1319
1345
|
}
|
|
1346
|
+
const steps = spec.steps ?? [];
|
|
1320
1347
|
const allCollected = spec.returnMode === "all" ? [...frontier] : [];
|
|
1321
|
-
for (const step of
|
|
1348
|
+
for (const step of steps) {
|
|
1322
1349
|
checkCallLimit();
|
|
1323
1350
|
if (step.repeat) {
|
|
1324
1351
|
const result = await executeRepeatStep(
|
|
@@ -1371,10 +1398,89 @@ async function executeFallbackTraversal(repositoryId, storage, spec) {
|
|
|
1371
1398
|
}
|
|
1372
1399
|
const total = resultEntries.length;
|
|
1373
1400
|
const paged = resultEntries.slice(offset, offset + limit);
|
|
1374
|
-
const
|
|
1401
|
+
const suppressEntities = spec.projection !== void 0 && spec.projection.includeEntities !== true;
|
|
1402
|
+
let entities;
|
|
1403
|
+
if (suppressEntities) {
|
|
1404
|
+
entities = [];
|
|
1405
|
+
} else {
|
|
1406
|
+
entities = paged.map((entry) => {
|
|
1407
|
+
const projected = projectEntity(entry.entity, detailLevel);
|
|
1408
|
+
if (!spec.includeProvenance) {
|
|
1409
|
+
delete projected["provenance"];
|
|
1410
|
+
}
|
|
1411
|
+
return projected;
|
|
1412
|
+
});
|
|
1413
|
+
if (spec.includeRelationshipSummary && entities.length > 0) {
|
|
1414
|
+
const summaryResults = await Promise.all(
|
|
1415
|
+
paged.map(async (entry) => {
|
|
1416
|
+
storageCalls++;
|
|
1417
|
+
const result = await storage.getEntityRelationships(repositoryId, entry.entity.id, {
|
|
1418
|
+
direction: "both",
|
|
1419
|
+
limit: 1e4
|
|
1420
|
+
});
|
|
1421
|
+
const summary = { outbound: {}, inbound: {} };
|
|
1422
|
+
for (const rel of result.items) {
|
|
1423
|
+
if (rel.sourceEntityId === entry.entity.id) {
|
|
1424
|
+
summary.outbound[rel.relationshipType] = (summary.outbound[rel.relationshipType] ?? 0) + 1;
|
|
1425
|
+
}
|
|
1426
|
+
if (rel.targetEntityId === entry.entity.id) {
|
|
1427
|
+
summary.inbound[rel.relationshipType] = (summary.inbound[rel.relationshipType] ?? 0) + 1;
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
return summary;
|
|
1431
|
+
})
|
|
1432
|
+
);
|
|
1433
|
+
entities = entities.map((e, i) => ({ ...e, relationshipSummary: summaryResults[i] }));
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
let aggregations;
|
|
1437
|
+
if (spec.projection) {
|
|
1438
|
+
const propNames = spec.projection.properties;
|
|
1439
|
+
const mode = spec.projection.mode ?? "values";
|
|
1440
|
+
const distinct = spec.projection.distinct ?? false;
|
|
1441
|
+
const sourceEntries = dedup ? (() => {
|
|
1442
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1443
|
+
return (spec.returnMode === "all" ? allCollected : frontier).filter((e) => {
|
|
1444
|
+
if (seen.has(e.entity.id)) return false;
|
|
1445
|
+
seen.add(e.entity.id);
|
|
1446
|
+
return true;
|
|
1447
|
+
});
|
|
1448
|
+
})() : spec.returnMode === "all" ? allCollected : frontier;
|
|
1449
|
+
if (mode === "count" || distinct) {
|
|
1450
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1451
|
+
for (const entry of sourceEntries) {
|
|
1452
|
+
const vals = {};
|
|
1453
|
+
for (const prop of propNames) {
|
|
1454
|
+
vals[prop] = entry.entity.properties[prop] ?? null;
|
|
1455
|
+
}
|
|
1456
|
+
const key = JSON.stringify(vals);
|
|
1457
|
+
const existing = groups.get(key);
|
|
1458
|
+
if (existing) {
|
|
1459
|
+
existing.count++;
|
|
1460
|
+
} else {
|
|
1461
|
+
groups.set(key, { values: vals, count: 1 });
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
aggregations = Array.from(groups.values()).map((g) => ({
|
|
1465
|
+
values: g.values,
|
|
1466
|
+
...mode === "count" ? { count: g.count } : {}
|
|
1467
|
+
}));
|
|
1468
|
+
if (mode === "count") {
|
|
1469
|
+
aggregations.sort((a, b) => (b.count ?? 0) - (a.count ?? 0));
|
|
1470
|
+
}
|
|
1471
|
+
} else {
|
|
1472
|
+
aggregations = sourceEntries.map((entry) => {
|
|
1473
|
+
const vals = {};
|
|
1474
|
+
for (const prop of propNames) {
|
|
1475
|
+
vals[prop] = entry.entity.properties[prop] ?? null;
|
|
1476
|
+
}
|
|
1477
|
+
return { values: vals };
|
|
1478
|
+
});
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1375
1481
|
let relationships;
|
|
1376
1482
|
let paths;
|
|
1377
|
-
if (spec.returnMode === "path" || spec.returnMode === "all") {
|
|
1483
|
+
if (!suppressEntities && (spec.returnMode === "path" || spec.returnMode === "all")) {
|
|
1378
1484
|
const relMap = /* @__PURE__ */ new Map();
|
|
1379
1485
|
for (const entry of paged) {
|
|
1380
1486
|
for (const rel of entry.relationshipPath) {
|
|
@@ -1392,10 +1498,14 @@ async function executeFallbackTraversal(repositoryId, storage, spec) {
|
|
|
1392
1498
|
}
|
|
1393
1499
|
relationships = Array.from(relMap.values());
|
|
1394
1500
|
}
|
|
1395
|
-
if (spec.returnMode === "path") {
|
|
1501
|
+
if (!suppressEntities && spec.returnMode === "path") {
|
|
1396
1502
|
paths = paged.map((entry) => ({
|
|
1397
1503
|
length: entry.entityPath.length - 1,
|
|
1398
|
-
entities: [
|
|
1504
|
+
entities: [(() => {
|
|
1505
|
+
const e = projectEntity(entry.entity, detailLevel);
|
|
1506
|
+
if (!spec.includeProvenance) delete e["provenance"];
|
|
1507
|
+
return e;
|
|
1508
|
+
})()],
|
|
1399
1509
|
relationships: entry.relationshipPath.map((rel) => ({
|
|
1400
1510
|
id: rel.id,
|
|
1401
1511
|
type: rel.relationshipType,
|
|
@@ -1411,6 +1521,7 @@ async function executeFallbackTraversal(repositoryId, storage, spec) {
|
|
|
1411
1521
|
entities,
|
|
1412
1522
|
relationships,
|
|
1413
1523
|
paths,
|
|
1524
|
+
aggregations,
|
|
1414
1525
|
total,
|
|
1415
1526
|
returned: paged.length,
|
|
1416
1527
|
hasMore: offset + limit < total,
|
|
@@ -1545,8 +1656,12 @@ var GraphTraversal = class {
|
|
|
1545
1656
|
this.graphTraversalProvider = graphTraversalProvider;
|
|
1546
1657
|
this.vocabularyEngine = vocabularyEngine;
|
|
1547
1658
|
}
|
|
1548
|
-
|
|
1549
|
-
|
|
1659
|
+
repositoryId;
|
|
1660
|
+
storage;
|
|
1661
|
+
graphTraversalProvider;
|
|
1662
|
+
vocabularyEngine;
|
|
1663
|
+
/** BFS neighborhood exploration from a center entity */
|
|
1664
|
+
async exploreNeighborhood(entityId, options) {
|
|
1550
1665
|
const storageOptions = {
|
|
1551
1666
|
depth: options?.depth ?? 1,
|
|
1552
1667
|
relationshipTypes: options?.relationshipTypes,
|
|
@@ -1556,13 +1671,13 @@ var GraphTraversal = class {
|
|
|
1556
1671
|
offsetPerType: options?.offsetPerType ?? 0,
|
|
1557
1672
|
relationshipPropertyFilters: options?.relationshipPropertyFilters
|
|
1558
1673
|
};
|
|
1559
|
-
const storageResult = await this.storage.
|
|
1674
|
+
const storageResult = await this.storage.exploreNeighborhood(
|
|
1560
1675
|
this.repositoryId,
|
|
1561
1676
|
entityId,
|
|
1562
1677
|
storageOptions
|
|
1563
1678
|
);
|
|
1564
|
-
const
|
|
1565
|
-
if (!
|
|
1679
|
+
const centerEntity = await this.storage.getEntity(this.repositoryId, entityId);
|
|
1680
|
+
if (!centerEntity) {
|
|
1566
1681
|
throw new EntityNotFoundError(entityId);
|
|
1567
1682
|
}
|
|
1568
1683
|
const detailLevel = options?.detailLevel ?? "summary";
|
|
@@ -1586,11 +1701,11 @@ var GraphTraversal = class {
|
|
|
1586
1701
|
return layer;
|
|
1587
1702
|
});
|
|
1588
1703
|
return {
|
|
1589
|
-
|
|
1590
|
-
id:
|
|
1591
|
-
slug:
|
|
1592
|
-
entityType:
|
|
1593
|
-
label:
|
|
1704
|
+
center: {
|
|
1705
|
+
id: centerEntity.id,
|
|
1706
|
+
slug: centerEntity.slug,
|
|
1707
|
+
entityType: centerEntity.entityType,
|
|
1708
|
+
label: centerEntity.label
|
|
1594
1709
|
},
|
|
1595
1710
|
layers,
|
|
1596
1711
|
statistics: {
|
|
@@ -2144,8 +2259,8 @@ var MemoryRepository = class {
|
|
|
2144
2259
|
};
|
|
2145
2260
|
}
|
|
2146
2261
|
// ─── Graph Traversal ───────────────────────────────────────────────
|
|
2147
|
-
async
|
|
2148
|
-
return this.graphTraversal.
|
|
2262
|
+
async exploreNeighborhood(entityId, options) {
|
|
2263
|
+
return this.graphTraversal.exploreNeighborhood(entityId, options);
|
|
2149
2264
|
}
|
|
2150
2265
|
async findPaths(sourceId, targetId, options) {
|
|
2151
2266
|
return this.graphTraversal.findPaths(sourceId, targetId, options);
|
|
@@ -2175,7 +2290,7 @@ var MemoryRepository = class {
|
|
|
2175
2290
|
storageOptions
|
|
2176
2291
|
);
|
|
2177
2292
|
const entity = await this.storage.getEntity(this.repositoryId, entityId);
|
|
2178
|
-
const
|
|
2293
|
+
const centerRef = {
|
|
2179
2294
|
id: entityId,
|
|
2180
2295
|
slug: entity?.slug ?? entityId,
|
|
2181
2296
|
label: entity?.label ?? entityId
|
|
@@ -2221,7 +2336,7 @@ var MemoryRepository = class {
|
|
|
2221
2336
|
timestamp: e.timestamp,
|
|
2222
2337
|
eventType: e.eventType,
|
|
2223
2338
|
description: `${rel.type} relationship created ${direction} ${otherRef.label}`,
|
|
2224
|
-
relatedEntities: [
|
|
2339
|
+
relatedEntities: [centerRef, otherRef],
|
|
2225
2340
|
relationship: relDetail
|
|
2226
2341
|
};
|
|
2227
2342
|
}
|
|
@@ -2229,13 +2344,13 @@ var MemoryRepository = class {
|
|
|
2229
2344
|
return {
|
|
2230
2345
|
timestamp: e.timestamp,
|
|
2231
2346
|
eventType: e.eventType,
|
|
2232
|
-
description: `${
|
|
2233
|
-
relatedEntities: [
|
|
2347
|
+
description: `${centerRef.label} ${action}`,
|
|
2348
|
+
relatedEntities: [centerRef]
|
|
2234
2349
|
};
|
|
2235
2350
|
});
|
|
2236
2351
|
return {
|
|
2237
2352
|
id: entityId,
|
|
2238
|
-
slug:
|
|
2353
|
+
slug: centerRef.slug,
|
|
2239
2354
|
totalEvents: storageResult.total,
|
|
2240
2355
|
returned: events.length,
|
|
2241
2356
|
events
|
|
@@ -2245,6 +2360,58 @@ var MemoryRepository = class {
|
|
|
2245
2360
|
async getStats() {
|
|
2246
2361
|
return this.storage.getRepositoryStats(this.repositoryId);
|
|
2247
2362
|
}
|
|
2363
|
+
// ─── Bulk Operations ──────────────────────────────────────────────
|
|
2364
|
+
/** Delete all entities and relationships in this repository, preserving the repository and vocabulary */
|
|
2365
|
+
async deleteAllContents() {
|
|
2366
|
+
return this.storage.deleteAllContents(this.repositoryId);
|
|
2367
|
+
}
|
|
2368
|
+
/**
|
|
2369
|
+
* Returns a static markdown guide describing the recommended query strategy
|
|
2370
|
+
* for AI agents and other consumers of the repository.
|
|
2371
|
+
*/
|
|
2372
|
+
getQueryGuide() {
|
|
2373
|
+
return `## Query Strategy Guide
|
|
2374
|
+
|
|
2375
|
+
### Step 1 \u2014 Discover before you traverse
|
|
2376
|
+
|
|
2377
|
+
Before following relationships from an entity, check that it actually has relationships.
|
|
2378
|
+
\`memory_query_graph\` includes \`relationshipSummary\` on every returned entity by default
|
|
2379
|
+
(outbound and inbound counts by type). Inspect this before traversing \u2014 entities with
|
|
2380
|
+
zero outbound counts for the relationship type you need have no connections to follow.
|
|
2381
|
+
To skip summaries and reduce response size, set \`includeRelationshipSummary: false\`.
|
|
2382
|
+
|
|
2383
|
+
### Step 2 \u2014 Use projection for aggregation
|
|
2384
|
+
|
|
2385
|
+
To understand what data exists (e.g. all distinct values of a property, or counts by
|
|
2386
|
+
category), use \`memory_query_graph\` with \`projection\` instead of fetching full entities:
|
|
2387
|
+
\`{ start: { entityType: "..." }, projection: { properties: ["propName"], distinct: true }, limit: 200 }\`
|
|
2388
|
+
|
|
2389
|
+
This returns only the aggregated values \u2014 no entity objects \u2014 keeping responses lightweight.
|
|
2390
|
+
|
|
2391
|
+
### Step 3 \u2014 Traverse efficiently
|
|
2392
|
+
|
|
2393
|
+
- **Omit relationshipTypes** in a traversal step to follow all relationship types at once,
|
|
2394
|
+
rather than making separate calls per type.
|
|
2395
|
+
- **Use multi-step traversals** for multi-hop patterns. A two-step query
|
|
2396
|
+
(e.g. Equipment \u2192 Component \u2192 MaintenanceProcedure) is one call, not two sequential calls.
|
|
2397
|
+
- **Use returnMode "all"** when you need intermediate entities along the path, not just
|
|
2398
|
+
the terminal nodes.
|
|
2399
|
+
|
|
2400
|
+
### Step 4 \u2014 Use semantic search for known-unknowns
|
|
2401
|
+
|
|
2402
|
+
\`memory_search_by_concept\` is best for finding specific entities when you know roughly
|
|
2403
|
+
what you're looking for but not the exact label or type. It is not efficient for broad
|
|
2404
|
+
discovery \u2014 use projection or find_entities for that.
|
|
2405
|
+
|
|
2406
|
+
### Common anti-patterns to avoid
|
|
2407
|
+
|
|
2408
|
+
- **Don't traverse from entities with zero relationship counts.** The \`relationshipSummary\` on each entity tells you what is connected before you traverse.
|
|
2409
|
+
- **Don't split queries by relationship type.** One step with no type filter is better
|
|
2410
|
+
than three separate single-type calls.
|
|
2411
|
+
- **Don't use sequential single-hop traversals** when a multi-step query achieves the
|
|
2412
|
+
same result in one call.
|
|
2413
|
+
- **Don't fetch full entities when you only need property values.** Use projection.`;
|
|
2414
|
+
}
|
|
2248
2415
|
// ─── Events ────────────────────────────────────────────────────────
|
|
2249
2416
|
on(event, handler) {
|
|
2250
2417
|
return this.eventBus.on(event, handler);
|
|
@@ -2266,14 +2433,14 @@ var SemanticDeduplicator = class {
|
|
|
2266
2433
|
/**
|
|
2267
2434
|
* Check whether a proposed type name is a duplicate of an existing type.
|
|
2268
2435
|
* Uses embedding similarity if an EmbeddingProvider is available, otherwise falls back
|
|
2269
|
-
* to Jaro-Winkler string similarity on
|
|
2436
|
+
* to Jaro-Winkler string similarity on normalized type names.
|
|
2270
2437
|
*/
|
|
2271
2438
|
async checkDuplicate(proposedType, proposedDescription, existingTypes) {
|
|
2272
2439
|
if (existingTypes.length === 0) {
|
|
2273
2440
|
return { isDuplicate: false, matches: [] };
|
|
2274
2441
|
}
|
|
2275
|
-
const normProposed =
|
|
2276
|
-
const exactMatch = existingTypes.find((t) =>
|
|
2442
|
+
const normProposed = normalizeTypeName(proposedType);
|
|
2443
|
+
const exactMatch = existingTypes.find((t) => normalizeTypeName(t.type) === normProposed);
|
|
2277
2444
|
if (exactMatch) {
|
|
2278
2445
|
return {
|
|
2279
2446
|
isDuplicate: true,
|
|
@@ -2309,10 +2476,10 @@ var SemanticDeduplicator = class {
|
|
|
2309
2476
|
};
|
|
2310
2477
|
}
|
|
2311
2478
|
checkWithStringSimilarity(proposedType, existingTypes) {
|
|
2312
|
-
const normProposed =
|
|
2479
|
+
const normProposed = normalizeTypeName(proposedType);
|
|
2313
2480
|
const matches = [];
|
|
2314
2481
|
for (const existing of existingTypes) {
|
|
2315
|
-
const normExisting =
|
|
2482
|
+
const normExisting = normalizeTypeName(existing.type);
|
|
2316
2483
|
const similarity = jaroWinklerSimilarity(normProposed, normExisting);
|
|
2317
2484
|
if (similarity >= this.threshold) {
|
|
2318
2485
|
matches.push({
|
|
@@ -2473,7 +2640,7 @@ function canPropose(governanceConfig, _proposal) {
|
|
|
2473
2640
|
case "locked":
|
|
2474
2641
|
return {
|
|
2475
2642
|
allowed: false,
|
|
2476
|
-
reason: "Vocabulary is locked. Only
|
|
2643
|
+
reason: "Vocabulary is locked. Only organization admins can modify the vocabulary via the admin API."
|
|
2477
2644
|
};
|
|
2478
2645
|
case "managed":
|
|
2479
2646
|
case "open":
|
|
@@ -2913,9 +3080,9 @@ function validateEntityUpdate(input, entityTypeDef) {
|
|
|
2913
3080
|
return ok();
|
|
2914
3081
|
}
|
|
2915
3082
|
function validateRelationship(input, vocabulary, sourceEntityType, targetEntityType) {
|
|
2916
|
-
const
|
|
3083
|
+
const normalizedType = toScreamingSnakeCase(input.relationshipType);
|
|
2917
3084
|
const relTypeDef = vocabulary.relationshipTypes.find(
|
|
2918
|
-
(rt) => rt.type ===
|
|
3085
|
+
(rt) => rt.type === normalizedType
|
|
2919
3086
|
);
|
|
2920
3087
|
if (!relTypeDef) {
|
|
2921
3088
|
const closest = findClosestType(input.relationshipType, vocabulary.relationshipTypes);
|
|
@@ -3240,7 +3407,7 @@ var EventBus = class {
|
|
|
3240
3407
|
};
|
|
3241
3408
|
|
|
3242
3409
|
// src/portability/RepositoryExporter.ts
|
|
3243
|
-
var LIBRARY_VERSION = true ? "0.
|
|
3410
|
+
var LIBRARY_VERSION = true ? "0.4.0" : "0.1.0";
|
|
3244
3411
|
var RepositoryExporter = class {
|
|
3245
3412
|
storage;
|
|
3246
3413
|
provenance;
|
|
@@ -3456,7 +3623,7 @@ function diffArrays(from, to) {
|
|
|
3456
3623
|
// src/portability/MigrationEngine.ts
|
|
3457
3624
|
var MigrationEngine = class {
|
|
3458
3625
|
/**
|
|
3459
|
-
*
|
|
3626
|
+
* Analyze and migrate vocabulary differences between source and target.
|
|
3460
3627
|
*
|
|
3461
3628
|
* @param sourceVocabulary - vocabulary from the export archive
|
|
3462
3629
|
* @param targetVocabulary - vocabulary in the target repository
|
|
@@ -3583,13 +3750,22 @@ function describeChange(change) {
|
|
|
3583
3750
|
}
|
|
3584
3751
|
|
|
3585
3752
|
// src/portability/RepositoryImporter.ts
|
|
3753
|
+
function estimateChunkCount(totalEntities, totalRelationships) {
|
|
3754
|
+
const ESTIMATED_CHUNK_SIZE = 100;
|
|
3755
|
+
return Math.max(
|
|
3756
|
+
1,
|
|
3757
|
+
Math.ceil(totalEntities / ESTIMATED_CHUNK_SIZE) + Math.ceil(totalRelationships / ESTIMATED_CHUNK_SIZE)
|
|
3758
|
+
);
|
|
3759
|
+
}
|
|
3586
3760
|
var RepositoryImporter = class {
|
|
3587
3761
|
storage;
|
|
3588
3762
|
actorId;
|
|
3763
|
+
onProgress;
|
|
3589
3764
|
migrationEngine = new MigrationEngine();
|
|
3590
3765
|
constructor(config) {
|
|
3591
3766
|
this.storage = config.storage;
|
|
3592
3767
|
this.actorId = config.actorId;
|
|
3768
|
+
this.onProgress = config.onProgress;
|
|
3593
3769
|
}
|
|
3594
3770
|
/**
|
|
3595
3771
|
* Import an export archive according to the provided options.
|
|
@@ -3640,26 +3816,34 @@ var RepositoryImporter = class {
|
|
|
3640
3816
|
return this.importStreamMerge(header, chunks, options, warnings);
|
|
3641
3817
|
}
|
|
3642
3818
|
}
|
|
3643
|
-
/** Streaming create mode —
|
|
3819
|
+
/** Streaming create mode — fast bulk inserts with no existence checks */
|
|
3644
3820
|
async importStreamCreate(header, chunks, options, warnings) {
|
|
3645
3821
|
const target = options.target;
|
|
3646
|
-
const
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3822
|
+
const existing = await this.storage.getRepository(target.repositoryId);
|
|
3823
|
+
if (!existing) {
|
|
3824
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3825
|
+
await this.storage.createRepository({
|
|
3826
|
+
repositoryId: target.repositoryId,
|
|
3827
|
+
type: target.config.type,
|
|
3828
|
+
label: target.config.label,
|
|
3829
|
+
description: target.config.description,
|
|
3830
|
+
governanceConfig: target.config.governance ?? { mode: "open" },
|
|
3831
|
+
createdAt: now,
|
|
3832
|
+
createdBy: this.actorId
|
|
3833
|
+
});
|
|
3834
|
+
}
|
|
3656
3835
|
await this.storage.saveVocabulary(target.repositoryId, header.vocabulary);
|
|
3836
|
+
const totalEntities = header.manifest?.statistics?.entityCount ?? 0;
|
|
3837
|
+
const totalRelationships = header.manifest?.statistics?.relationshipCount ?? 0;
|
|
3838
|
+
const totalChunks = estimateChunkCount(totalEntities, totalRelationships);
|
|
3657
3839
|
let entitiesImported = 0;
|
|
3658
3840
|
let relationshipsImported = 0;
|
|
3841
|
+
let chunksCompleted = 0;
|
|
3659
3842
|
for await (const chunk of chunks) {
|
|
3660
|
-
const bulkResult = await this.storage.importBulk(target.repositoryId, [chunk]);
|
|
3843
|
+
const bulkResult = await this.storage.importBulk(target.repositoryId, [chunk], { skipExistenceCheck: true });
|
|
3661
3844
|
entitiesImported += bulkResult.entitiesImported;
|
|
3662
3845
|
relationshipsImported += bulkResult.relationshipsImported;
|
|
3846
|
+
chunksCompleted++;
|
|
3663
3847
|
for (const e of bulkResult.errors) {
|
|
3664
3848
|
warnings.push({
|
|
3665
3849
|
code: "import_error",
|
|
@@ -3667,6 +3851,15 @@ var RepositoryImporter = class {
|
|
|
3667
3851
|
id: e.item
|
|
3668
3852
|
});
|
|
3669
3853
|
}
|
|
3854
|
+
await this.onProgress?.({
|
|
3855
|
+
repositoryId: target.repositoryId,
|
|
3856
|
+
entitiesImported,
|
|
3857
|
+
relationshipsImported,
|
|
3858
|
+
totalEntities,
|
|
3859
|
+
totalRelationships,
|
|
3860
|
+
chunksCompleted,
|
|
3861
|
+
totalChunks
|
|
3862
|
+
});
|
|
3670
3863
|
}
|
|
3671
3864
|
return {
|
|
3672
3865
|
success: true,
|
|
@@ -3714,11 +3907,15 @@ var RepositoryImporter = class {
|
|
|
3714
3907
|
if (migrationResult.mergedVocabulary && migrationResult.mergedVocabulary !== targetVocabulary) {
|
|
3715
3908
|
await this.storage.saveVocabulary(repositoryId, migrationResult.mergedVocabulary);
|
|
3716
3909
|
}
|
|
3910
|
+
const totalEntities = header.manifest?.statistics?.entityCount ?? 0;
|
|
3911
|
+
const totalRelationships = header.manifest?.statistics?.relationshipCount ?? 0;
|
|
3912
|
+
const totalChunks = estimateChunkCount(totalEntities, totalRelationships);
|
|
3717
3913
|
const entityConflict = options.entityConflict ?? "skip";
|
|
3718
3914
|
let entitiesImported = 0;
|
|
3719
3915
|
let entitiesSkipped = 0;
|
|
3720
3916
|
let relationshipsImported = 0;
|
|
3721
3917
|
let relationshipsSkipped = 0;
|
|
3918
|
+
let chunksCompleted = 0;
|
|
3722
3919
|
for await (const chunk of chunks) {
|
|
3723
3920
|
if (chunk.entities) {
|
|
3724
3921
|
for (const entity of chunk.entities) {
|
|
@@ -3800,6 +3997,16 @@ var RepositoryImporter = class {
|
|
|
3800
3997
|
}
|
|
3801
3998
|
}
|
|
3802
3999
|
}
|
|
4000
|
+
chunksCompleted++;
|
|
4001
|
+
await this.onProgress?.({
|
|
4002
|
+
repositoryId,
|
|
4003
|
+
entitiesImported,
|
|
4004
|
+
relationshipsImported,
|
|
4005
|
+
totalEntities,
|
|
4006
|
+
totalRelationships,
|
|
4007
|
+
chunksCompleted,
|
|
4008
|
+
totalChunks
|
|
4009
|
+
});
|
|
3803
4010
|
}
|
|
3804
4011
|
const vocabExtensions = migrationResult.warnings.filter(
|
|
3805
4012
|
(w) => w.code === "entity_type_added" || w.code === "relationship_type_added"
|
|
@@ -3856,7 +4063,7 @@ var DeepMemory = class {
|
|
|
3856
4063
|
graphTraversalProvider;
|
|
3857
4064
|
provenance;
|
|
3858
4065
|
globalEventBus;
|
|
3859
|
-
|
|
4066
|
+
initialized = false;
|
|
3860
4067
|
constructor(config) {
|
|
3861
4068
|
this.storage = config.storage;
|
|
3862
4069
|
this.search = config.search;
|
|
@@ -3866,16 +4073,16 @@ var DeepMemory = class {
|
|
|
3866
4073
|
this.provenance = new ProvenanceTracker(config.provenance);
|
|
3867
4074
|
this.globalEventBus = new EventBus(config.provenance);
|
|
3868
4075
|
}
|
|
3869
|
-
/**
|
|
3870
|
-
async
|
|
3871
|
-
if (!this.
|
|
3872
|
-
if (this.storage.
|
|
3873
|
-
await this.storage.
|
|
4076
|
+
/** Initialize the storage provider and optional providers (call once before use) */
|
|
4077
|
+
async ensureInitialized() {
|
|
4078
|
+
if (!this.initialized) {
|
|
4079
|
+
if (this.storage.initialize) {
|
|
4080
|
+
await this.storage.initialize();
|
|
3874
4081
|
}
|
|
3875
|
-
if (this.graphTraversalProvider?.
|
|
3876
|
-
await this.graphTraversalProvider.
|
|
4082
|
+
if (this.graphTraversalProvider?.initialize) {
|
|
4083
|
+
await this.graphTraversalProvider.initialize();
|
|
3877
4084
|
}
|
|
3878
|
-
this.
|
|
4085
|
+
this.initialized = true;
|
|
3879
4086
|
}
|
|
3880
4087
|
}
|
|
3881
4088
|
/** Create or migrate the storage schema. Call once at deployment / startup, not per-request. */
|
|
@@ -3887,15 +4094,15 @@ var DeepMemory = class {
|
|
|
3887
4094
|
schemaVersion: 0
|
|
3888
4095
|
};
|
|
3889
4096
|
try {
|
|
3890
|
-
await this.
|
|
4097
|
+
await this.ensureInitialized();
|
|
3891
4098
|
} catch {
|
|
3892
4099
|
}
|
|
3893
4100
|
let result = noOpResult;
|
|
3894
4101
|
if (this.storage.ensureSchema) {
|
|
3895
4102
|
result = await this.storage.ensureSchema();
|
|
3896
4103
|
}
|
|
3897
|
-
if (!this.
|
|
3898
|
-
await this.
|
|
4104
|
+
if (!this.initialized) {
|
|
4105
|
+
await this.ensureInitialized();
|
|
3899
4106
|
}
|
|
3900
4107
|
return result;
|
|
3901
4108
|
}
|
|
@@ -3910,7 +4117,7 @@ var DeepMemory = class {
|
|
|
3910
4117
|
}
|
|
3911
4118
|
/** Create a new memory repository */
|
|
3912
4119
|
async createRepository(config) {
|
|
3913
|
-
await this.
|
|
4120
|
+
await this.ensureInitialized();
|
|
3914
4121
|
const repositoryId = config.repositoryId ?? generateId();
|
|
3915
4122
|
this.validateRepositoryId(repositoryId);
|
|
3916
4123
|
const context = this.provenance.getContext();
|
|
@@ -3967,7 +4174,7 @@ var DeepMemory = class {
|
|
|
3967
4174
|
}
|
|
3968
4175
|
/** Open an existing repository */
|
|
3969
4176
|
async openRepository(repositoryId) {
|
|
3970
|
-
await this.
|
|
4177
|
+
await this.ensureInitialized();
|
|
3971
4178
|
this.validateRepositoryId(repositoryId);
|
|
3972
4179
|
const storedRepo = await this.storage.getRepository(repositoryId);
|
|
3973
4180
|
if (!storedRepo) {
|
|
@@ -3996,7 +4203,7 @@ var DeepMemory = class {
|
|
|
3996
4203
|
}
|
|
3997
4204
|
/** List all repositories */
|
|
3998
4205
|
async listRepositories(filter) {
|
|
3999
|
-
await this.
|
|
4206
|
+
await this.ensureInitialized();
|
|
4000
4207
|
const result = await this.storage.listRepositories(filter);
|
|
4001
4208
|
return {
|
|
4002
4209
|
items: result.items.map((stored) => ({
|
|
@@ -4014,7 +4221,7 @@ var DeepMemory = class {
|
|
|
4014
4221
|
}
|
|
4015
4222
|
/** Update repository metadata and settings */
|
|
4016
4223
|
async updateRepository(repositoryId, updates) {
|
|
4017
|
-
await this.
|
|
4224
|
+
await this.ensureInitialized();
|
|
4018
4225
|
this.validateRepositoryId(repositoryId);
|
|
4019
4226
|
const updated = await this.storage.updateRepository(repositoryId, updates);
|
|
4020
4227
|
await this.globalEventBus.emit("repository:updated", { repositoryId });
|
|
@@ -4022,14 +4229,55 @@ var DeepMemory = class {
|
|
|
4022
4229
|
}
|
|
4023
4230
|
/** Delete a repository */
|
|
4024
4231
|
async deleteRepository(repositoryId) {
|
|
4025
|
-
await this.
|
|
4232
|
+
await this.ensureInitialized();
|
|
4026
4233
|
this.validateRepositoryId(repositoryId);
|
|
4027
|
-
await this.storage.
|
|
4234
|
+
const stats = await this.storage.getRepositoryStats(repositoryId);
|
|
4235
|
+
await this.globalEventBus.emit("delete:started", {
|
|
4236
|
+
repositoryId,
|
|
4237
|
+
totalEntities: stats.entityCount,
|
|
4238
|
+
totalRelationships: stats.relationshipCount
|
|
4239
|
+
});
|
|
4240
|
+
await this.storage.deleteRepository(
|
|
4241
|
+
repositoryId,
|
|
4242
|
+
(progress) => this.globalEventBus.emit("delete:progress", { repositoryId, ...progress })
|
|
4243
|
+
);
|
|
4244
|
+
await this.globalEventBus.emit("delete:completed", {
|
|
4245
|
+
repositoryId,
|
|
4246
|
+
entitiesDeleted: stats.entityCount,
|
|
4247
|
+
relationshipsDeleted: stats.relationshipCount
|
|
4248
|
+
});
|
|
4028
4249
|
await this.globalEventBus.emit("repository:deleted", { repositoryId });
|
|
4029
4250
|
}
|
|
4251
|
+
/** Delete all entities and relationships in a repository, preserving the repository and vocabulary */
|
|
4252
|
+
async deleteAllContents(repositoryId) {
|
|
4253
|
+
await this.ensureInitialized();
|
|
4254
|
+
this.validateRepositoryId(repositoryId);
|
|
4255
|
+
const stats = await this.storage.getRepositoryStats(repositoryId);
|
|
4256
|
+
await this.globalEventBus.emit("delete:started", {
|
|
4257
|
+
repositoryId,
|
|
4258
|
+
totalEntities: stats.entityCount,
|
|
4259
|
+
totalRelationships: stats.relationshipCount
|
|
4260
|
+
});
|
|
4261
|
+
const result = await this.storage.deleteAllContents(
|
|
4262
|
+
repositoryId,
|
|
4263
|
+
(progress) => this.globalEventBus.emit("delete:progress", { repositoryId, ...progress })
|
|
4264
|
+
);
|
|
4265
|
+
await this.globalEventBus.emit("delete:completed", {
|
|
4266
|
+
repositoryId,
|
|
4267
|
+
entitiesDeleted: result.deletedEntities,
|
|
4268
|
+
relationshipsDeleted: result.deletedRelationships
|
|
4269
|
+
});
|
|
4270
|
+
return result;
|
|
4271
|
+
}
|
|
4030
4272
|
/** Export a repository to a portable archive */
|
|
4031
4273
|
async exportRepository(repositoryId, options) {
|
|
4032
|
-
await this.
|
|
4274
|
+
await this.ensureInitialized();
|
|
4275
|
+
const stats = await this.storage.getRepositoryStats(repositoryId);
|
|
4276
|
+
await this.globalEventBus.emit("export:started", {
|
|
4277
|
+
repositoryId,
|
|
4278
|
+
totalEntities: stats.entityCount,
|
|
4279
|
+
totalRelationships: stats.relationshipCount
|
|
4280
|
+
});
|
|
4033
4281
|
const exporter = new RepositoryExporter({
|
|
4034
4282
|
storage: this.storage,
|
|
4035
4283
|
provenance: this.provenance.getContext(),
|
|
@@ -4045,10 +4293,12 @@ var DeepMemory = class {
|
|
|
4045
4293
|
}
|
|
4046
4294
|
/** Import a repository from a portable archive */
|
|
4047
4295
|
async importRepository(archive, options) {
|
|
4048
|
-
await this.
|
|
4296
|
+
await this.ensureInitialized();
|
|
4297
|
+
await this.globalEventBus.emit("import:started", { repositoryId: options.target.repositoryId });
|
|
4049
4298
|
const importer = new RepositoryImporter({
|
|
4050
4299
|
storage: this.storage,
|
|
4051
|
-
actorId: this.provenance.getContext().actorId
|
|
4300
|
+
actorId: this.provenance.getContext().actorId,
|
|
4301
|
+
onProgress: (progress) => this.globalEventBus.emit("import:progress", progress)
|
|
4052
4302
|
});
|
|
4053
4303
|
const result = await importer.import(archive, options);
|
|
4054
4304
|
if (result.success) {
|
|
@@ -4071,21 +4321,50 @@ var DeepMemory = class {
|
|
|
4071
4321
|
* Use for large repositories to avoid loading everything into memory.
|
|
4072
4322
|
*/
|
|
4073
4323
|
async *exportRepositoryStream(repositoryId, options) {
|
|
4074
|
-
await this.
|
|
4324
|
+
await this.ensureInitialized();
|
|
4075
4325
|
const exporter = new RepositoryExporter({
|
|
4076
4326
|
storage: this.storage,
|
|
4077
4327
|
provenance: this.provenance.getContext(),
|
|
4078
4328
|
legal: options?.legal
|
|
4079
4329
|
});
|
|
4080
|
-
await this.
|
|
4330
|
+
const stats = await this.storage.getRepositoryStats(repositoryId);
|
|
4331
|
+
const totalEntities = stats.entityCount;
|
|
4332
|
+
const totalRelationships = stats.relationshipCount;
|
|
4333
|
+
const estimatedChunkSize = 100;
|
|
4334
|
+
const totalChunks = Math.max(
|
|
4335
|
+
1,
|
|
4336
|
+
Math.ceil(totalEntities / estimatedChunkSize) + Math.ceil(totalRelationships / estimatedChunkSize)
|
|
4337
|
+
);
|
|
4338
|
+
await this.globalEventBus.emit("export:started", { repositoryId, totalEntities, totalRelationships });
|
|
4081
4339
|
let entityCount = 0;
|
|
4082
4340
|
let relationshipCount = 0;
|
|
4341
|
+
let chunksCompleted = 0;
|
|
4083
4342
|
for await (const item of exporter.exportStream(repositoryId)) {
|
|
4084
4343
|
yield item;
|
|
4085
4344
|
if (item.type === "entities") {
|
|
4086
4345
|
entityCount += item.data.length;
|
|
4346
|
+
chunksCompleted++;
|
|
4347
|
+
await this.globalEventBus.emit("export:progress", {
|
|
4348
|
+
repositoryId,
|
|
4349
|
+
entitiesExported: entityCount,
|
|
4350
|
+
relationshipsExported: relationshipCount,
|
|
4351
|
+
totalEntities,
|
|
4352
|
+
totalRelationships,
|
|
4353
|
+
chunksCompleted,
|
|
4354
|
+
totalChunks
|
|
4355
|
+
});
|
|
4087
4356
|
} else if (item.type === "relationships") {
|
|
4088
4357
|
relationshipCount += item.data.length;
|
|
4358
|
+
chunksCompleted++;
|
|
4359
|
+
await this.globalEventBus.emit("export:progress", {
|
|
4360
|
+
repositoryId,
|
|
4361
|
+
entitiesExported: entityCount,
|
|
4362
|
+
relationshipsExported: relationshipCount,
|
|
4363
|
+
totalEntities,
|
|
4364
|
+
totalRelationships,
|
|
4365
|
+
chunksCompleted,
|
|
4366
|
+
totalChunks
|
|
4367
|
+
});
|
|
4089
4368
|
}
|
|
4090
4369
|
}
|
|
4091
4370
|
await this.globalEventBus.emit("export:completed", {
|
|
@@ -4099,10 +4378,11 @@ var DeepMemory = class {
|
|
|
4099
4378
|
* Use for large repositories to avoid loading everything into memory.
|
|
4100
4379
|
*/
|
|
4101
4380
|
async importRepositoryStream(header, chunks, options) {
|
|
4102
|
-
await this.
|
|
4381
|
+
await this.ensureInitialized();
|
|
4103
4382
|
const importer = new RepositoryImporter({
|
|
4104
4383
|
storage: this.storage,
|
|
4105
|
-
actorId: this.provenance.getContext().actorId
|
|
4384
|
+
actorId: this.provenance.getContext().actorId,
|
|
4385
|
+
onProgress: (progress) => this.globalEventBus.emit("import:progress", progress)
|
|
4106
4386
|
});
|
|
4107
4387
|
await this.globalEventBus.emit("import:started", { repositoryId: options.target.repositoryId });
|
|
4108
4388
|
const result = await importer.importStream(header, chunks, options);
|
|
@@ -4138,7 +4418,7 @@ var DeepMemory = class {
|
|
|
4138
4418
|
if (this.storage.dispose) {
|
|
4139
4419
|
await this.storage.dispose();
|
|
4140
4420
|
}
|
|
4141
|
-
this.
|
|
4421
|
+
this.initialized = false;
|
|
4142
4422
|
}
|
|
4143
4423
|
};
|
|
4144
4424
|
|
|
@@ -4153,7 +4433,7 @@ var InMemoryStorageProvider = class {
|
|
|
4153
4433
|
return store;
|
|
4154
4434
|
}
|
|
4155
4435
|
// ─── Lifecycle ─────────────────────────────────────────────────────
|
|
4156
|
-
async
|
|
4436
|
+
async initialize() {
|
|
4157
4437
|
}
|
|
4158
4438
|
async dispose() {
|
|
4159
4439
|
this.stores.clear();
|
|
@@ -4226,12 +4506,21 @@ var InMemoryStorageProvider = class {
|
|
|
4226
4506
|
if (updates.metadata !== void 0) repo.metadata = { ...repo.metadata, ...updates.metadata };
|
|
4227
4507
|
return repo;
|
|
4228
4508
|
}
|
|
4229
|
-
async deleteRepository(repositoryId) {
|
|
4509
|
+
async deleteRepository(repositoryId, _onProgress) {
|
|
4230
4510
|
if (!this.stores.has(repositoryId)) {
|
|
4231
4511
|
throw new RepositoryNotFoundError(repositoryId);
|
|
4232
4512
|
}
|
|
4233
4513
|
this.stores.delete(repositoryId);
|
|
4234
4514
|
}
|
|
4515
|
+
async deleteAllContents(repositoryId, _onProgress) {
|
|
4516
|
+
const store = this.getStore(repositoryId);
|
|
4517
|
+
const deletedEntities = store.entities.size;
|
|
4518
|
+
const deletedRelationships = store.relationships.size;
|
|
4519
|
+
store.entities.clear();
|
|
4520
|
+
store.slugIndex.clear();
|
|
4521
|
+
store.relationships.clear();
|
|
4522
|
+
return { deletedEntities, deletedRelationships };
|
|
4523
|
+
}
|
|
4235
4524
|
async getRepositoryStats(repositoryId) {
|
|
4236
4525
|
const store = this.getStore(repositoryId);
|
|
4237
4526
|
const entityTypeBreakdown = {};
|
|
@@ -4461,7 +4750,7 @@ var InMemoryStorageProvider = class {
|
|
|
4461
4750
|
return { deletedRelationships };
|
|
4462
4751
|
}
|
|
4463
4752
|
// ─── Graph Traversal ───────────────────────────────────────────────
|
|
4464
|
-
async
|
|
4753
|
+
async exploreNeighborhood(repositoryId, entityId, options) {
|
|
4465
4754
|
const store = this.getStore(repositoryId);
|
|
4466
4755
|
if (!store.entities.has(entityId)) {
|
|
4467
4756
|
throw new EntityNotFoundError(entityId);
|
|
@@ -4527,7 +4816,7 @@ var InMemoryStorageProvider = class {
|
|
|
4527
4816
|
if (nextFrontier.size === 0) break;
|
|
4528
4817
|
}
|
|
4529
4818
|
return {
|
|
4530
|
-
|
|
4819
|
+
centerId: entityId,
|
|
4531
4820
|
layers
|
|
4532
4821
|
};
|
|
4533
4822
|
}
|
|
@@ -4686,7 +4975,7 @@ var InMemoryStorageProvider = class {
|
|
|
4686
4975
|
yield { type: "relationships", data: [], sequence: 0, isLast: true };
|
|
4687
4976
|
}
|
|
4688
4977
|
}
|
|
4689
|
-
async importBulk(repositoryId, data) {
|
|
4978
|
+
async importBulk(repositoryId, data, _options) {
|
|
4690
4979
|
const store = this.getStore(repositoryId);
|
|
4691
4980
|
let entitiesImported = 0;
|
|
4692
4981
|
let relationshipsImported = 0;
|