@utaba/deep-memory 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{StorageProvider-DR9HLYHJ.d.ts → StorageProvider-CSbaIY6X.d.cts} +8 -3
- package/dist/{StorageProvider-C3tKhfuw.d.cts → StorageProvider-DqW-zpd0.d.ts} +8 -3
- package/dist/index.cjs +324 -50
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +27 -8
- package/dist/index.d.ts +27 -8
- package/dist/index.js +324 -50
- package/dist/index.js.map +1 -1
- package/dist/{portability-DdjsCB07.d.cts → portability-1KBMVBGJ.d.cts} +19 -1
- package/dist/{portability-DdjsCB07.d.ts → portability-1KBMVBGJ.d.ts} +19 -1
- package/dist/providers/index.d.cts +9 -3
- package/dist/providers/index.d.ts +9 -3
- package/dist/testing/conformance.cjs +22 -0
- package/dist/testing/conformance.cjs.map +1 -1
- package/dist/testing/conformance.d.cts +2 -2
- package/dist/testing/conformance.d.ts +2 -2
- package/dist/testing/conformance.js +22 -0
- package/dist/testing/conformance.js.map +1 -1
- package/dist/{traversal-9JdiqjZ9.d.ts → traversal-B4SUysM-.d.ts} +98 -11
- package/dist/{traversal-BB_yERbf.d.cts → traversal-DQynd-to.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
|
@@ -850,6 +850,7 @@ var DEFAULT_MAX_LIMIT = 200;
|
|
|
850
850
|
var DEFAULT_FALLBACK_MAX_DEPTH = 10;
|
|
851
851
|
function validateTraversalSpec(spec, vocabulary, capabilities) {
|
|
852
852
|
const errors = [];
|
|
853
|
+
const steps = spec.steps ?? [];
|
|
853
854
|
if (!spec.start) {
|
|
854
855
|
errors.push("start is required");
|
|
855
856
|
} else {
|
|
@@ -863,37 +864,45 @@ function validateTraversalSpec(spec, vocabulary, capabilities) {
|
|
|
863
864
|
errors.push("start.entityType without entityId requires limit on the spec to prevent full type scans");
|
|
864
865
|
}
|
|
865
866
|
}
|
|
866
|
-
if (!spec.steps || spec.steps.length === 0) {
|
|
867
|
-
errors.push("steps must contain at least one step");
|
|
868
|
-
}
|
|
869
867
|
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
|
-
}
|
|
868
|
+
if (steps.length > maxSteps) {
|
|
869
|
+
errors.push(`steps length ${steps.length} exceeds maximum ${maxSteps}`);
|
|
870
|
+
}
|
|
871
|
+
for (let i = 0; i < steps.length; i++) {
|
|
872
|
+
const step = steps[i];
|
|
873
|
+
if (!step.direction || !["out", "in", "both"].includes(step.direction)) {
|
|
874
|
+
errors.push(`steps[${i}].direction must be 'out', 'in', or 'both'`);
|
|
875
|
+
}
|
|
876
|
+
if (step.repeat) {
|
|
877
|
+
if (step.repeat.maxDepth === void 0 || step.repeat.maxDepth <= 0) {
|
|
878
|
+
errors.push(`steps[${i}].repeat.maxDepth must be a positive number`);
|
|
883
879
|
}
|
|
884
880
|
}
|
|
881
|
+
}
|
|
882
|
+
if (steps.length > 0) {
|
|
885
883
|
const maxProviderDepth = capabilities?.maxTraversalDepth ?? DEFAULT_FALLBACK_MAX_DEPTH;
|
|
886
884
|
let totalDepth = 0;
|
|
887
|
-
for (const step of
|
|
885
|
+
for (const step of steps) {
|
|
888
886
|
totalDepth += step.repeat ? step.repeat.maxDepth : 1;
|
|
889
887
|
}
|
|
890
888
|
if (totalDepth > maxProviderDepth) {
|
|
891
889
|
errors.push(`total potential depth ${totalDepth} exceeds provider maximum ${maxProviderDepth}`);
|
|
892
890
|
}
|
|
893
891
|
}
|
|
892
|
+
if (spec.returnMode === "path" && steps.length === 0) {
|
|
893
|
+
errors.push("returnMode 'path' requires at least one step");
|
|
894
|
+
}
|
|
894
895
|
if (spec.returnMode && !["terminal", "path", "all"].includes(spec.returnMode)) {
|
|
895
896
|
errors.push(`returnMode must be 'terminal', 'path', or 'all'`);
|
|
896
897
|
}
|
|
898
|
+
if (spec.projection) {
|
|
899
|
+
if (!spec.projection.properties || spec.projection.properties.length === 0) {
|
|
900
|
+
errors.push("projection.properties must contain at least one property name");
|
|
901
|
+
}
|
|
902
|
+
if (spec.projection.mode && !["count", "values"].includes(spec.projection.mode)) {
|
|
903
|
+
errors.push("projection.mode must be 'count' or 'values'");
|
|
904
|
+
}
|
|
905
|
+
}
|
|
897
906
|
if (spec.limit !== void 0) {
|
|
898
907
|
if (spec.limit < 1 || spec.limit > DEFAULT_MAX_LIMIT) {
|
|
899
908
|
errors.push(`limit must be between 1 and ${DEFAULT_MAX_LIMIT}`);
|
|
@@ -902,15 +911,15 @@ function validateTraversalSpec(spec, vocabulary, capabilities) {
|
|
|
902
911
|
if (spec.offset !== void 0 && spec.offset < 0) {
|
|
903
912
|
errors.push("offset must be >= 0");
|
|
904
913
|
}
|
|
905
|
-
if (vocabulary
|
|
914
|
+
if (vocabulary) {
|
|
906
915
|
const entityTypeSet = new Set(vocabulary.entityTypes.map((et) => et.type));
|
|
907
916
|
const relationshipTypeSet = new Set(vocabulary.relationshipTypes.map((rt) => rt.type));
|
|
908
917
|
const unknownTypes = [];
|
|
909
918
|
if (spec.start?.entityType && !entityTypeSet.has(spec.start.entityType)) {
|
|
910
919
|
unknownTypes.push(`entity type "${spec.start.entityType}"`);
|
|
911
920
|
}
|
|
912
|
-
for (let i = 0; i <
|
|
913
|
-
const step =
|
|
921
|
+
for (let i = 0; i < steps.length; i++) {
|
|
922
|
+
const step = steps[i];
|
|
914
923
|
if (step.relationshipTypes) {
|
|
915
924
|
for (const rt of step.relationshipTypes) {
|
|
916
925
|
if (!relationshipTypeSet.has(rt)) {
|
|
@@ -964,7 +973,8 @@ var GremlinCompiler = class {
|
|
|
964
973
|
parts.push(compilePropertyFilter(f, nextParam));
|
|
965
974
|
}
|
|
966
975
|
}
|
|
967
|
-
|
|
976
|
+
const steps = spec.steps ?? [];
|
|
977
|
+
for (const step of steps) {
|
|
968
978
|
if (step.repeat) {
|
|
969
979
|
parts.push(compileRepeatStep(step, nextParam));
|
|
970
980
|
estimatedFanOut *= step.repeat.maxDepth * DEFAULT_ESTIMATED_FANOUT_PER_HOP;
|
|
@@ -1146,8 +1156,9 @@ var CypherCompiler = class {
|
|
|
1146
1156
|
}
|
|
1147
1157
|
}
|
|
1148
1158
|
let lastNode = startNode;
|
|
1149
|
-
|
|
1150
|
-
|
|
1159
|
+
const steps = spec.steps ?? [];
|
|
1160
|
+
for (let i = 0; i < steps.length; i++) {
|
|
1161
|
+
const step = steps[i];
|
|
1151
1162
|
const targetNode = `n${nodeIndex++}`;
|
|
1152
1163
|
const relAlias = `r${i}`;
|
|
1153
1164
|
const relTypes = step.relationshipTypes?.length ? step.relationshipTypes.join("|") : "";
|
|
@@ -1189,7 +1200,7 @@ WHERE ${whereClauses.join(" AND ")}` : "";
|
|
|
1189
1200
|
let returnClause;
|
|
1190
1201
|
if (spec.returnMode === "path") {
|
|
1191
1202
|
const allNodes = Array.from({ length: nodeIndex }, (_, i) => `n${i}`);
|
|
1192
|
-
const allRels =
|
|
1203
|
+
const allRels = steps.map((_, i) => `r${i}`);
|
|
1193
1204
|
returnClause = `RETURN ${[...allNodes, ...allRels].join(", ")}`;
|
|
1194
1205
|
} else if (spec.returnMode === "all") {
|
|
1195
1206
|
const allNodes = Array.from({ length: nodeIndex }, (_, i) => `n${i}`);
|
|
@@ -1270,6 +1281,7 @@ function matchesSingleFilter(properties, filter) {
|
|
|
1270
1281
|
|
|
1271
1282
|
// src/relationships/FallbackTraversalExecutor.ts
|
|
1272
1283
|
var MAX_STORAGE_CALLS = 500;
|
|
1284
|
+
var MAX_ENTITY_SCAN = 1e4;
|
|
1273
1285
|
var REL_FETCH_LIMIT = 1e3;
|
|
1274
1286
|
async function executeFallbackTraversal(repositoryId, storage, spec) {
|
|
1275
1287
|
const startTime = Date.now();
|
|
@@ -1279,6 +1291,9 @@ async function executeFallbackTraversal(repositoryId, storage, spec) {
|
|
|
1279
1291
|
throw new Error(`Fallback traversal exceeded ${MAX_STORAGE_CALLS} storage calls. Consider using a GraphTraversalProvider for large traversals.`);
|
|
1280
1292
|
}
|
|
1281
1293
|
};
|
|
1294
|
+
const isVertexQuery = !spec.steps || spec.steps.length === 0;
|
|
1295
|
+
const needsFullScan = isVertexQuery || spec.projection !== void 0;
|
|
1296
|
+
const fetchLimit = needsFullScan ? MAX_ENTITY_SCAN : spec.limit ?? 50;
|
|
1282
1297
|
let frontier = [];
|
|
1283
1298
|
if (spec.start.entityId) {
|
|
1284
1299
|
const entity = await resolveEntity(repositoryId, storage, spec.start.entityId);
|
|
@@ -1291,7 +1306,7 @@ async function executeFallbackTraversal(repositoryId, storage, spec) {
|
|
|
1291
1306
|
} else if (spec.start.entityType) {
|
|
1292
1307
|
const result = await storage.findEntities(repositoryId, {
|
|
1293
1308
|
entityTypes: [spec.start.entityType],
|
|
1294
|
-
limit:
|
|
1309
|
+
limit: fetchLimit,
|
|
1295
1310
|
offset: 0
|
|
1296
1311
|
});
|
|
1297
1312
|
storageCalls++;
|
|
@@ -1302,7 +1317,7 @@ async function executeFallbackTraversal(repositoryId, storage, spec) {
|
|
|
1302
1317
|
}));
|
|
1303
1318
|
} else if (spec.start.filter && spec.start.filter.length > 0) {
|
|
1304
1319
|
const result = await storage.findEntities(repositoryId, {
|
|
1305
|
-
limit:
|
|
1320
|
+
limit: fetchLimit,
|
|
1306
1321
|
offset: 0
|
|
1307
1322
|
});
|
|
1308
1323
|
storageCalls++;
|
|
@@ -1317,8 +1332,9 @@ async function executeFallbackTraversal(repositoryId, storage, spec) {
|
|
|
1317
1332
|
(entry) => matchesPropertyFilters(entry.entity.properties, spec.start.filter)
|
|
1318
1333
|
);
|
|
1319
1334
|
}
|
|
1335
|
+
const steps = spec.steps ?? [];
|
|
1320
1336
|
const allCollected = spec.returnMode === "all" ? [...frontier] : [];
|
|
1321
|
-
for (const step of
|
|
1337
|
+
for (const step of steps) {
|
|
1322
1338
|
checkCallLimit();
|
|
1323
1339
|
if (step.repeat) {
|
|
1324
1340
|
const result = await executeRepeatStep(
|
|
@@ -1371,10 +1387,89 @@ async function executeFallbackTraversal(repositoryId, storage, spec) {
|
|
|
1371
1387
|
}
|
|
1372
1388
|
const total = resultEntries.length;
|
|
1373
1389
|
const paged = resultEntries.slice(offset, offset + limit);
|
|
1374
|
-
const
|
|
1390
|
+
const suppressEntities = spec.projection !== void 0 && spec.projection.includeEntities !== true;
|
|
1391
|
+
let entities;
|
|
1392
|
+
if (suppressEntities) {
|
|
1393
|
+
entities = [];
|
|
1394
|
+
} else {
|
|
1395
|
+
entities = paged.map((entry) => {
|
|
1396
|
+
const projected = projectEntity(entry.entity, detailLevel);
|
|
1397
|
+
if (!spec.includeProvenance) {
|
|
1398
|
+
delete projected["provenance"];
|
|
1399
|
+
}
|
|
1400
|
+
return projected;
|
|
1401
|
+
});
|
|
1402
|
+
if (spec.includeRelationshipSummary && entities.length > 0) {
|
|
1403
|
+
const summaryResults = await Promise.all(
|
|
1404
|
+
paged.map(async (entry) => {
|
|
1405
|
+
storageCalls++;
|
|
1406
|
+
const result = await storage.getEntityRelationships(repositoryId, entry.entity.id, {
|
|
1407
|
+
direction: "both",
|
|
1408
|
+
limit: 1e4
|
|
1409
|
+
});
|
|
1410
|
+
const summary = { outbound: {}, inbound: {} };
|
|
1411
|
+
for (const rel of result.items) {
|
|
1412
|
+
if (rel.sourceEntityId === entry.entity.id) {
|
|
1413
|
+
summary.outbound[rel.relationshipType] = (summary.outbound[rel.relationshipType] ?? 0) + 1;
|
|
1414
|
+
}
|
|
1415
|
+
if (rel.targetEntityId === entry.entity.id) {
|
|
1416
|
+
summary.inbound[rel.relationshipType] = (summary.inbound[rel.relationshipType] ?? 0) + 1;
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
return summary;
|
|
1420
|
+
})
|
|
1421
|
+
);
|
|
1422
|
+
entities = entities.map((e, i) => ({ ...e, relationshipSummary: summaryResults[i] }));
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
let aggregations;
|
|
1426
|
+
if (spec.projection) {
|
|
1427
|
+
const propNames = spec.projection.properties;
|
|
1428
|
+
const mode = spec.projection.mode ?? "values";
|
|
1429
|
+
const distinct = spec.projection.distinct ?? false;
|
|
1430
|
+
const sourceEntries = dedup ? (() => {
|
|
1431
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1432
|
+
return (spec.returnMode === "all" ? allCollected : frontier).filter((e) => {
|
|
1433
|
+
if (seen.has(e.entity.id)) return false;
|
|
1434
|
+
seen.add(e.entity.id);
|
|
1435
|
+
return true;
|
|
1436
|
+
});
|
|
1437
|
+
})() : spec.returnMode === "all" ? allCollected : frontier;
|
|
1438
|
+
if (mode === "count" || distinct) {
|
|
1439
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1440
|
+
for (const entry of sourceEntries) {
|
|
1441
|
+
const vals = {};
|
|
1442
|
+
for (const prop of propNames) {
|
|
1443
|
+
vals[prop] = entry.entity.properties[prop] ?? null;
|
|
1444
|
+
}
|
|
1445
|
+
const key = JSON.stringify(vals);
|
|
1446
|
+
const existing = groups.get(key);
|
|
1447
|
+
if (existing) {
|
|
1448
|
+
existing.count++;
|
|
1449
|
+
} else {
|
|
1450
|
+
groups.set(key, { values: vals, count: 1 });
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
aggregations = Array.from(groups.values()).map((g) => ({
|
|
1454
|
+
values: g.values,
|
|
1455
|
+
...mode === "count" ? { count: g.count } : {}
|
|
1456
|
+
}));
|
|
1457
|
+
if (mode === "count") {
|
|
1458
|
+
aggregations.sort((a, b) => (b.count ?? 0) - (a.count ?? 0));
|
|
1459
|
+
}
|
|
1460
|
+
} else {
|
|
1461
|
+
aggregations = sourceEntries.map((entry) => {
|
|
1462
|
+
const vals = {};
|
|
1463
|
+
for (const prop of propNames) {
|
|
1464
|
+
vals[prop] = entry.entity.properties[prop] ?? null;
|
|
1465
|
+
}
|
|
1466
|
+
return { values: vals };
|
|
1467
|
+
});
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1375
1470
|
let relationships;
|
|
1376
1471
|
let paths;
|
|
1377
|
-
if (spec.returnMode === "path" || spec.returnMode === "all") {
|
|
1472
|
+
if (!suppressEntities && (spec.returnMode === "path" || spec.returnMode === "all")) {
|
|
1378
1473
|
const relMap = /* @__PURE__ */ new Map();
|
|
1379
1474
|
for (const entry of paged) {
|
|
1380
1475
|
for (const rel of entry.relationshipPath) {
|
|
@@ -1392,10 +1487,14 @@ async function executeFallbackTraversal(repositoryId, storage, spec) {
|
|
|
1392
1487
|
}
|
|
1393
1488
|
relationships = Array.from(relMap.values());
|
|
1394
1489
|
}
|
|
1395
|
-
if (spec.returnMode === "path") {
|
|
1490
|
+
if (!suppressEntities && spec.returnMode === "path") {
|
|
1396
1491
|
paths = paged.map((entry) => ({
|
|
1397
1492
|
length: entry.entityPath.length - 1,
|
|
1398
|
-
entities: [
|
|
1493
|
+
entities: [(() => {
|
|
1494
|
+
const e = projectEntity(entry.entity, detailLevel);
|
|
1495
|
+
if (!spec.includeProvenance) delete e["provenance"];
|
|
1496
|
+
return e;
|
|
1497
|
+
})()],
|
|
1399
1498
|
relationships: entry.relationshipPath.map((rel) => ({
|
|
1400
1499
|
id: rel.id,
|
|
1401
1500
|
type: rel.relationshipType,
|
|
@@ -1411,6 +1510,7 @@ async function executeFallbackTraversal(repositoryId, storage, spec) {
|
|
|
1411
1510
|
entities,
|
|
1412
1511
|
relationships,
|
|
1413
1512
|
paths,
|
|
1513
|
+
aggregations,
|
|
1414
1514
|
total,
|
|
1415
1515
|
returned: paged.length,
|
|
1416
1516
|
hasMore: offset + limit < total,
|
|
@@ -2245,6 +2345,58 @@ var MemoryRepository = class {
|
|
|
2245
2345
|
async getStats() {
|
|
2246
2346
|
return this.storage.getRepositoryStats(this.repositoryId);
|
|
2247
2347
|
}
|
|
2348
|
+
// ─── Bulk Operations ──────────────────────────────────────────────
|
|
2349
|
+
/** Delete all entities and relationships in this repository, preserving the repository and vocabulary */
|
|
2350
|
+
async deleteAllContents() {
|
|
2351
|
+
return this.storage.deleteAllContents(this.repositoryId);
|
|
2352
|
+
}
|
|
2353
|
+
/**
|
|
2354
|
+
* Returns a static markdown guide describing the recommended query strategy
|
|
2355
|
+
* for AI agents and other consumers of the repository.
|
|
2356
|
+
*/
|
|
2357
|
+
getQueryGuide() {
|
|
2358
|
+
return `## Query Strategy Guide
|
|
2359
|
+
|
|
2360
|
+
### Step 1 \u2014 Discover before you traverse
|
|
2361
|
+
|
|
2362
|
+
Before following relationships from an entity, check that it actually has relationships.
|
|
2363
|
+
\`memory_query_graph\` includes \`relationshipSummary\` on every returned entity by default
|
|
2364
|
+
(outbound and inbound counts by type). Inspect this before traversing \u2014 entities with
|
|
2365
|
+
zero outbound counts for the relationship type you need have no connections to follow.
|
|
2366
|
+
To skip summaries and reduce response size, set \`includeRelationshipSummary: false\`.
|
|
2367
|
+
|
|
2368
|
+
### Step 2 \u2014 Use projection for aggregation
|
|
2369
|
+
|
|
2370
|
+
To understand what data exists (e.g. all distinct values of a property, or counts by
|
|
2371
|
+
category), use \`memory_query_graph\` with \`projection\` instead of fetching full entities:
|
|
2372
|
+
\`{ start: { entityType: "..." }, projection: { properties: ["propName"], distinct: true }, limit: 200 }\`
|
|
2373
|
+
|
|
2374
|
+
This returns only the aggregated values \u2014 no entity objects \u2014 keeping responses lightweight.
|
|
2375
|
+
|
|
2376
|
+
### Step 3 \u2014 Traverse efficiently
|
|
2377
|
+
|
|
2378
|
+
- **Omit relationshipTypes** in a traversal step to follow all relationship types at once,
|
|
2379
|
+
rather than making separate calls per type.
|
|
2380
|
+
- **Use multi-step traversals** for multi-hop patterns. A two-step query
|
|
2381
|
+
(e.g. Equipment \u2192 Component \u2192 MaintenanceProcedure) is one call, not two sequential calls.
|
|
2382
|
+
- **Use returnMode "all"** when you need intermediate entities along the path, not just
|
|
2383
|
+
the terminal nodes.
|
|
2384
|
+
|
|
2385
|
+
### Step 4 \u2014 Use semantic search for known-unknowns
|
|
2386
|
+
|
|
2387
|
+
\`memory_search_by_concept\` is best for finding specific entities when you know roughly
|
|
2388
|
+
what you're looking for but not the exact label or type. It is not efficient for broad
|
|
2389
|
+
discovery \u2014 use projection or find_entities for that.
|
|
2390
|
+
|
|
2391
|
+
### Common anti-patterns to avoid
|
|
2392
|
+
|
|
2393
|
+
- **Don't traverse from entities with zero relationship counts.** The \`relationshipSummary\` on each entity tells you what is connected before you traverse.
|
|
2394
|
+
- **Don't split queries by relationship type.** One step with no type filter is better
|
|
2395
|
+
than three separate single-type calls.
|
|
2396
|
+
- **Don't use sequential single-hop traversals** when a multi-step query achieves the
|
|
2397
|
+
same result in one call.
|
|
2398
|
+
- **Don't fetch full entities when you only need property values.** Use projection.`;
|
|
2399
|
+
}
|
|
2248
2400
|
// ─── Events ────────────────────────────────────────────────────────
|
|
2249
2401
|
on(event, handler) {
|
|
2250
2402
|
return this.eventBus.on(event, handler);
|
|
@@ -3240,7 +3392,7 @@ var EventBus = class {
|
|
|
3240
3392
|
};
|
|
3241
3393
|
|
|
3242
3394
|
// src/portability/RepositoryExporter.ts
|
|
3243
|
-
var LIBRARY_VERSION = true ? "0.
|
|
3395
|
+
var LIBRARY_VERSION = true ? "0.3.0" : "0.1.0";
|
|
3244
3396
|
var RepositoryExporter = class {
|
|
3245
3397
|
storage;
|
|
3246
3398
|
provenance;
|
|
@@ -3583,13 +3735,22 @@ function describeChange(change) {
|
|
|
3583
3735
|
}
|
|
3584
3736
|
|
|
3585
3737
|
// src/portability/RepositoryImporter.ts
|
|
3738
|
+
function estimateChunkCount(totalEntities, totalRelationships) {
|
|
3739
|
+
const ESTIMATED_CHUNK_SIZE = 100;
|
|
3740
|
+
return Math.max(
|
|
3741
|
+
1,
|
|
3742
|
+
Math.ceil(totalEntities / ESTIMATED_CHUNK_SIZE) + Math.ceil(totalRelationships / ESTIMATED_CHUNK_SIZE)
|
|
3743
|
+
);
|
|
3744
|
+
}
|
|
3586
3745
|
var RepositoryImporter = class {
|
|
3587
3746
|
storage;
|
|
3588
3747
|
actorId;
|
|
3748
|
+
onProgress;
|
|
3589
3749
|
migrationEngine = new MigrationEngine();
|
|
3590
3750
|
constructor(config) {
|
|
3591
3751
|
this.storage = config.storage;
|
|
3592
3752
|
this.actorId = config.actorId;
|
|
3753
|
+
this.onProgress = config.onProgress;
|
|
3593
3754
|
}
|
|
3594
3755
|
/**
|
|
3595
3756
|
* Import an export archive according to the provided options.
|
|
@@ -3640,26 +3801,34 @@ var RepositoryImporter = class {
|
|
|
3640
3801
|
return this.importStreamMerge(header, chunks, options, warnings);
|
|
3641
3802
|
}
|
|
3642
3803
|
}
|
|
3643
|
-
/** Streaming create mode —
|
|
3804
|
+
/** Streaming create mode — fast bulk inserts with no existence checks */
|
|
3644
3805
|
async importStreamCreate(header, chunks, options, warnings) {
|
|
3645
3806
|
const target = options.target;
|
|
3646
|
-
const
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3807
|
+
const existing = await this.storage.getRepository(target.repositoryId);
|
|
3808
|
+
if (!existing) {
|
|
3809
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3810
|
+
await this.storage.createRepository({
|
|
3811
|
+
repositoryId: target.repositoryId,
|
|
3812
|
+
type: target.config.type,
|
|
3813
|
+
label: target.config.label,
|
|
3814
|
+
description: target.config.description,
|
|
3815
|
+
governanceConfig: target.config.governance ?? { mode: "open" },
|
|
3816
|
+
createdAt: now,
|
|
3817
|
+
createdBy: this.actorId
|
|
3818
|
+
});
|
|
3819
|
+
}
|
|
3656
3820
|
await this.storage.saveVocabulary(target.repositoryId, header.vocabulary);
|
|
3821
|
+
const totalEntities = header.manifest?.statistics?.entityCount ?? 0;
|
|
3822
|
+
const totalRelationships = header.manifest?.statistics?.relationshipCount ?? 0;
|
|
3823
|
+
const totalChunks = estimateChunkCount(totalEntities, totalRelationships);
|
|
3657
3824
|
let entitiesImported = 0;
|
|
3658
3825
|
let relationshipsImported = 0;
|
|
3826
|
+
let chunksCompleted = 0;
|
|
3659
3827
|
for await (const chunk of chunks) {
|
|
3660
|
-
const bulkResult = await this.storage.importBulk(target.repositoryId, [chunk]);
|
|
3828
|
+
const bulkResult = await this.storage.importBulk(target.repositoryId, [chunk], { skipExistenceCheck: true });
|
|
3661
3829
|
entitiesImported += bulkResult.entitiesImported;
|
|
3662
3830
|
relationshipsImported += bulkResult.relationshipsImported;
|
|
3831
|
+
chunksCompleted++;
|
|
3663
3832
|
for (const e of bulkResult.errors) {
|
|
3664
3833
|
warnings.push({
|
|
3665
3834
|
code: "import_error",
|
|
@@ -3667,6 +3836,15 @@ var RepositoryImporter = class {
|
|
|
3667
3836
|
id: e.item
|
|
3668
3837
|
});
|
|
3669
3838
|
}
|
|
3839
|
+
await this.onProgress?.({
|
|
3840
|
+
repositoryId: target.repositoryId,
|
|
3841
|
+
entitiesImported,
|
|
3842
|
+
relationshipsImported,
|
|
3843
|
+
totalEntities,
|
|
3844
|
+
totalRelationships,
|
|
3845
|
+
chunksCompleted,
|
|
3846
|
+
totalChunks
|
|
3847
|
+
});
|
|
3670
3848
|
}
|
|
3671
3849
|
return {
|
|
3672
3850
|
success: true,
|
|
@@ -3714,11 +3892,15 @@ var RepositoryImporter = class {
|
|
|
3714
3892
|
if (migrationResult.mergedVocabulary && migrationResult.mergedVocabulary !== targetVocabulary) {
|
|
3715
3893
|
await this.storage.saveVocabulary(repositoryId, migrationResult.mergedVocabulary);
|
|
3716
3894
|
}
|
|
3895
|
+
const totalEntities = header.manifest?.statistics?.entityCount ?? 0;
|
|
3896
|
+
const totalRelationships = header.manifest?.statistics?.relationshipCount ?? 0;
|
|
3897
|
+
const totalChunks = estimateChunkCount(totalEntities, totalRelationships);
|
|
3717
3898
|
const entityConflict = options.entityConflict ?? "skip";
|
|
3718
3899
|
let entitiesImported = 0;
|
|
3719
3900
|
let entitiesSkipped = 0;
|
|
3720
3901
|
let relationshipsImported = 0;
|
|
3721
3902
|
let relationshipsSkipped = 0;
|
|
3903
|
+
let chunksCompleted = 0;
|
|
3722
3904
|
for await (const chunk of chunks) {
|
|
3723
3905
|
if (chunk.entities) {
|
|
3724
3906
|
for (const entity of chunk.entities) {
|
|
@@ -3800,6 +3982,16 @@ var RepositoryImporter = class {
|
|
|
3800
3982
|
}
|
|
3801
3983
|
}
|
|
3802
3984
|
}
|
|
3985
|
+
chunksCompleted++;
|
|
3986
|
+
await this.onProgress?.({
|
|
3987
|
+
repositoryId,
|
|
3988
|
+
entitiesImported,
|
|
3989
|
+
relationshipsImported,
|
|
3990
|
+
totalEntities,
|
|
3991
|
+
totalRelationships,
|
|
3992
|
+
chunksCompleted,
|
|
3993
|
+
totalChunks
|
|
3994
|
+
});
|
|
3803
3995
|
}
|
|
3804
3996
|
const vocabExtensions = migrationResult.warnings.filter(
|
|
3805
3997
|
(w) => w.code === "entity_type_added" || w.code === "relationship_type_added"
|
|
@@ -4024,12 +4216,53 @@ var DeepMemory = class {
|
|
|
4024
4216
|
async deleteRepository(repositoryId) {
|
|
4025
4217
|
await this.ensureInitialised();
|
|
4026
4218
|
this.validateRepositoryId(repositoryId);
|
|
4027
|
-
await this.storage.
|
|
4219
|
+
const stats = await this.storage.getRepositoryStats(repositoryId);
|
|
4220
|
+
await this.globalEventBus.emit("delete:started", {
|
|
4221
|
+
repositoryId,
|
|
4222
|
+
totalEntities: stats.entityCount,
|
|
4223
|
+
totalRelationships: stats.relationshipCount
|
|
4224
|
+
});
|
|
4225
|
+
await this.storage.deleteRepository(
|
|
4226
|
+
repositoryId,
|
|
4227
|
+
(progress) => this.globalEventBus.emit("delete:progress", { repositoryId, ...progress })
|
|
4228
|
+
);
|
|
4229
|
+
await this.globalEventBus.emit("delete:completed", {
|
|
4230
|
+
repositoryId,
|
|
4231
|
+
entitiesDeleted: stats.entityCount,
|
|
4232
|
+
relationshipsDeleted: stats.relationshipCount
|
|
4233
|
+
});
|
|
4028
4234
|
await this.globalEventBus.emit("repository:deleted", { repositoryId });
|
|
4029
4235
|
}
|
|
4236
|
+
/** Delete all entities and relationships in a repository, preserving the repository and vocabulary */
|
|
4237
|
+
async deleteAllContents(repositoryId) {
|
|
4238
|
+
await this.ensureInitialised();
|
|
4239
|
+
this.validateRepositoryId(repositoryId);
|
|
4240
|
+
const stats = await this.storage.getRepositoryStats(repositoryId);
|
|
4241
|
+
await this.globalEventBus.emit("delete:started", {
|
|
4242
|
+
repositoryId,
|
|
4243
|
+
totalEntities: stats.entityCount,
|
|
4244
|
+
totalRelationships: stats.relationshipCount
|
|
4245
|
+
});
|
|
4246
|
+
const result = await this.storage.deleteAllContents(
|
|
4247
|
+
repositoryId,
|
|
4248
|
+
(progress) => this.globalEventBus.emit("delete:progress", { repositoryId, ...progress })
|
|
4249
|
+
);
|
|
4250
|
+
await this.globalEventBus.emit("delete:completed", {
|
|
4251
|
+
repositoryId,
|
|
4252
|
+
entitiesDeleted: result.deletedEntities,
|
|
4253
|
+
relationshipsDeleted: result.deletedRelationships
|
|
4254
|
+
});
|
|
4255
|
+
return result;
|
|
4256
|
+
}
|
|
4030
4257
|
/** Export a repository to a portable archive */
|
|
4031
4258
|
async exportRepository(repositoryId, options) {
|
|
4032
4259
|
await this.ensureInitialised();
|
|
4260
|
+
const stats = await this.storage.getRepositoryStats(repositoryId);
|
|
4261
|
+
await this.globalEventBus.emit("export:started", {
|
|
4262
|
+
repositoryId,
|
|
4263
|
+
totalEntities: stats.entityCount,
|
|
4264
|
+
totalRelationships: stats.relationshipCount
|
|
4265
|
+
});
|
|
4033
4266
|
const exporter = new RepositoryExporter({
|
|
4034
4267
|
storage: this.storage,
|
|
4035
4268
|
provenance: this.provenance.getContext(),
|
|
@@ -4046,9 +4279,11 @@ var DeepMemory = class {
|
|
|
4046
4279
|
/** Import a repository from a portable archive */
|
|
4047
4280
|
async importRepository(archive, options) {
|
|
4048
4281
|
await this.ensureInitialised();
|
|
4282
|
+
await this.globalEventBus.emit("import:started", { repositoryId: options.target.repositoryId });
|
|
4049
4283
|
const importer = new RepositoryImporter({
|
|
4050
4284
|
storage: this.storage,
|
|
4051
|
-
actorId: this.provenance.getContext().actorId
|
|
4285
|
+
actorId: this.provenance.getContext().actorId,
|
|
4286
|
+
onProgress: (progress) => this.globalEventBus.emit("import:progress", progress)
|
|
4052
4287
|
});
|
|
4053
4288
|
const result = await importer.import(archive, options);
|
|
4054
4289
|
if (result.success) {
|
|
@@ -4077,15 +4312,44 @@ var DeepMemory = class {
|
|
|
4077
4312
|
provenance: this.provenance.getContext(),
|
|
4078
4313
|
legal: options?.legal
|
|
4079
4314
|
});
|
|
4080
|
-
await this.
|
|
4315
|
+
const stats = await this.storage.getRepositoryStats(repositoryId);
|
|
4316
|
+
const totalEntities = stats.entityCount;
|
|
4317
|
+
const totalRelationships = stats.relationshipCount;
|
|
4318
|
+
const estimatedChunkSize = 100;
|
|
4319
|
+
const totalChunks = Math.max(
|
|
4320
|
+
1,
|
|
4321
|
+
Math.ceil(totalEntities / estimatedChunkSize) + Math.ceil(totalRelationships / estimatedChunkSize)
|
|
4322
|
+
);
|
|
4323
|
+
await this.globalEventBus.emit("export:started", { repositoryId, totalEntities, totalRelationships });
|
|
4081
4324
|
let entityCount = 0;
|
|
4082
4325
|
let relationshipCount = 0;
|
|
4326
|
+
let chunksCompleted = 0;
|
|
4083
4327
|
for await (const item of exporter.exportStream(repositoryId)) {
|
|
4084
4328
|
yield item;
|
|
4085
4329
|
if (item.type === "entities") {
|
|
4086
4330
|
entityCount += item.data.length;
|
|
4331
|
+
chunksCompleted++;
|
|
4332
|
+
await this.globalEventBus.emit("export:progress", {
|
|
4333
|
+
repositoryId,
|
|
4334
|
+
entitiesExported: entityCount,
|
|
4335
|
+
relationshipsExported: relationshipCount,
|
|
4336
|
+
totalEntities,
|
|
4337
|
+
totalRelationships,
|
|
4338
|
+
chunksCompleted,
|
|
4339
|
+
totalChunks
|
|
4340
|
+
});
|
|
4087
4341
|
} else if (item.type === "relationships") {
|
|
4088
4342
|
relationshipCount += item.data.length;
|
|
4343
|
+
chunksCompleted++;
|
|
4344
|
+
await this.globalEventBus.emit("export:progress", {
|
|
4345
|
+
repositoryId,
|
|
4346
|
+
entitiesExported: entityCount,
|
|
4347
|
+
relationshipsExported: relationshipCount,
|
|
4348
|
+
totalEntities,
|
|
4349
|
+
totalRelationships,
|
|
4350
|
+
chunksCompleted,
|
|
4351
|
+
totalChunks
|
|
4352
|
+
});
|
|
4089
4353
|
}
|
|
4090
4354
|
}
|
|
4091
4355
|
await this.globalEventBus.emit("export:completed", {
|
|
@@ -4102,7 +4366,8 @@ var DeepMemory = class {
|
|
|
4102
4366
|
await this.ensureInitialised();
|
|
4103
4367
|
const importer = new RepositoryImporter({
|
|
4104
4368
|
storage: this.storage,
|
|
4105
|
-
actorId: this.provenance.getContext().actorId
|
|
4369
|
+
actorId: this.provenance.getContext().actorId,
|
|
4370
|
+
onProgress: (progress) => this.globalEventBus.emit("import:progress", progress)
|
|
4106
4371
|
});
|
|
4107
4372
|
await this.globalEventBus.emit("import:started", { repositoryId: options.target.repositoryId });
|
|
4108
4373
|
const result = await importer.importStream(header, chunks, options);
|
|
@@ -4226,12 +4491,21 @@ var InMemoryStorageProvider = class {
|
|
|
4226
4491
|
if (updates.metadata !== void 0) repo.metadata = { ...repo.metadata, ...updates.metadata };
|
|
4227
4492
|
return repo;
|
|
4228
4493
|
}
|
|
4229
|
-
async deleteRepository(repositoryId) {
|
|
4494
|
+
async deleteRepository(repositoryId, _onProgress) {
|
|
4230
4495
|
if (!this.stores.has(repositoryId)) {
|
|
4231
4496
|
throw new RepositoryNotFoundError(repositoryId);
|
|
4232
4497
|
}
|
|
4233
4498
|
this.stores.delete(repositoryId);
|
|
4234
4499
|
}
|
|
4500
|
+
async deleteAllContents(repositoryId, _onProgress) {
|
|
4501
|
+
const store = this.getStore(repositoryId);
|
|
4502
|
+
const deletedEntities = store.entities.size;
|
|
4503
|
+
const deletedRelationships = store.relationships.size;
|
|
4504
|
+
store.entities.clear();
|
|
4505
|
+
store.slugIndex.clear();
|
|
4506
|
+
store.relationships.clear();
|
|
4507
|
+
return { deletedEntities, deletedRelationships };
|
|
4508
|
+
}
|
|
4235
4509
|
async getRepositoryStats(repositoryId) {
|
|
4236
4510
|
const store = this.getStore(repositoryId);
|
|
4237
4511
|
const entityTypeBreakdown = {};
|
|
@@ -4686,7 +4960,7 @@ var InMemoryStorageProvider = class {
|
|
|
4686
4960
|
yield { type: "relationships", data: [], sequence: 0, isLast: true };
|
|
4687
4961
|
}
|
|
4688
4962
|
}
|
|
4689
|
-
async importBulk(repositoryId, data) {
|
|
4963
|
+
async importBulk(repositoryId, data, _options) {
|
|
4690
4964
|
const store = this.getStore(repositoryId);
|
|
4691
4965
|
let entitiesImported = 0;
|
|
4692
4966
|
let relationshipsImported = 0;
|