@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.js
CHANGED
|
@@ -795,6 +795,7 @@ var DEFAULT_MAX_LIMIT = 200;
|
|
|
795
795
|
var DEFAULT_FALLBACK_MAX_DEPTH = 10;
|
|
796
796
|
function validateTraversalSpec(spec, vocabulary, capabilities) {
|
|
797
797
|
const errors = [];
|
|
798
|
+
const steps = spec.steps ?? [];
|
|
798
799
|
if (!spec.start) {
|
|
799
800
|
errors.push("start is required");
|
|
800
801
|
} else {
|
|
@@ -808,37 +809,45 @@ function validateTraversalSpec(spec, vocabulary, capabilities) {
|
|
|
808
809
|
errors.push("start.entityType without entityId requires limit on the spec to prevent full type scans");
|
|
809
810
|
}
|
|
810
811
|
}
|
|
811
|
-
if (!spec.steps || spec.steps.length === 0) {
|
|
812
|
-
errors.push("steps must contain at least one step");
|
|
813
|
-
}
|
|
814
812
|
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
|
-
}
|
|
813
|
+
if (steps.length > maxSteps) {
|
|
814
|
+
errors.push(`steps length ${steps.length} exceeds maximum ${maxSteps}`);
|
|
815
|
+
}
|
|
816
|
+
for (let i = 0; i < steps.length; i++) {
|
|
817
|
+
const step = steps[i];
|
|
818
|
+
if (!step.direction || !["out", "in", "both"].includes(step.direction)) {
|
|
819
|
+
errors.push(`steps[${i}].direction must be 'out', 'in', or 'both'`);
|
|
820
|
+
}
|
|
821
|
+
if (step.repeat) {
|
|
822
|
+
if (step.repeat.maxDepth === void 0 || step.repeat.maxDepth <= 0) {
|
|
823
|
+
errors.push(`steps[${i}].repeat.maxDepth must be a positive number`);
|
|
828
824
|
}
|
|
829
825
|
}
|
|
826
|
+
}
|
|
827
|
+
if (steps.length > 0) {
|
|
830
828
|
const maxProviderDepth = capabilities?.maxTraversalDepth ?? DEFAULT_FALLBACK_MAX_DEPTH;
|
|
831
829
|
let totalDepth = 0;
|
|
832
|
-
for (const step of
|
|
830
|
+
for (const step of steps) {
|
|
833
831
|
totalDepth += step.repeat ? step.repeat.maxDepth : 1;
|
|
834
832
|
}
|
|
835
833
|
if (totalDepth > maxProviderDepth) {
|
|
836
834
|
errors.push(`total potential depth ${totalDepth} exceeds provider maximum ${maxProviderDepth}`);
|
|
837
835
|
}
|
|
838
836
|
}
|
|
837
|
+
if (spec.returnMode === "path" && steps.length === 0) {
|
|
838
|
+
errors.push("returnMode 'path' requires at least one step");
|
|
839
|
+
}
|
|
839
840
|
if (spec.returnMode && !["terminal", "path", "all"].includes(spec.returnMode)) {
|
|
840
841
|
errors.push(`returnMode must be 'terminal', 'path', or 'all'`);
|
|
841
842
|
}
|
|
843
|
+
if (spec.projection) {
|
|
844
|
+
if (!spec.projection.properties || spec.projection.properties.length === 0) {
|
|
845
|
+
errors.push("projection.properties must contain at least one property name");
|
|
846
|
+
}
|
|
847
|
+
if (spec.projection.mode && !["count", "values"].includes(spec.projection.mode)) {
|
|
848
|
+
errors.push("projection.mode must be 'count' or 'values'");
|
|
849
|
+
}
|
|
850
|
+
}
|
|
842
851
|
if (spec.limit !== void 0) {
|
|
843
852
|
if (spec.limit < 1 || spec.limit > DEFAULT_MAX_LIMIT) {
|
|
844
853
|
errors.push(`limit must be between 1 and ${DEFAULT_MAX_LIMIT}`);
|
|
@@ -847,15 +856,15 @@ function validateTraversalSpec(spec, vocabulary, capabilities) {
|
|
|
847
856
|
if (spec.offset !== void 0 && spec.offset < 0) {
|
|
848
857
|
errors.push("offset must be >= 0");
|
|
849
858
|
}
|
|
850
|
-
if (vocabulary
|
|
859
|
+
if (vocabulary) {
|
|
851
860
|
const entityTypeSet = new Set(vocabulary.entityTypes.map((et) => et.type));
|
|
852
861
|
const relationshipTypeSet = new Set(vocabulary.relationshipTypes.map((rt) => rt.type));
|
|
853
862
|
const unknownTypes = [];
|
|
854
863
|
if (spec.start?.entityType && !entityTypeSet.has(spec.start.entityType)) {
|
|
855
864
|
unknownTypes.push(`entity type "${spec.start.entityType}"`);
|
|
856
865
|
}
|
|
857
|
-
for (let i = 0; i <
|
|
858
|
-
const step =
|
|
866
|
+
for (let i = 0; i < steps.length; i++) {
|
|
867
|
+
const step = steps[i];
|
|
859
868
|
if (step.relationshipTypes) {
|
|
860
869
|
for (const rt of step.relationshipTypes) {
|
|
861
870
|
if (!relationshipTypeSet.has(rt)) {
|
|
@@ -909,7 +918,8 @@ var GremlinCompiler = class {
|
|
|
909
918
|
parts.push(compilePropertyFilter(f, nextParam));
|
|
910
919
|
}
|
|
911
920
|
}
|
|
912
|
-
|
|
921
|
+
const steps = spec.steps ?? [];
|
|
922
|
+
for (const step of steps) {
|
|
913
923
|
if (step.repeat) {
|
|
914
924
|
parts.push(compileRepeatStep(step, nextParam));
|
|
915
925
|
estimatedFanOut *= step.repeat.maxDepth * DEFAULT_ESTIMATED_FANOUT_PER_HOP;
|
|
@@ -1091,8 +1101,9 @@ var CypherCompiler = class {
|
|
|
1091
1101
|
}
|
|
1092
1102
|
}
|
|
1093
1103
|
let lastNode = startNode;
|
|
1094
|
-
|
|
1095
|
-
|
|
1104
|
+
const steps = spec.steps ?? [];
|
|
1105
|
+
for (let i = 0; i < steps.length; i++) {
|
|
1106
|
+
const step = steps[i];
|
|
1096
1107
|
const targetNode = `n${nodeIndex++}`;
|
|
1097
1108
|
const relAlias = `r${i}`;
|
|
1098
1109
|
const relTypes = step.relationshipTypes?.length ? step.relationshipTypes.join("|") : "";
|
|
@@ -1134,7 +1145,7 @@ WHERE ${whereClauses.join(" AND ")}` : "";
|
|
|
1134
1145
|
let returnClause;
|
|
1135
1146
|
if (spec.returnMode === "path") {
|
|
1136
1147
|
const allNodes = Array.from({ length: nodeIndex }, (_, i) => `n${i}`);
|
|
1137
|
-
const allRels =
|
|
1148
|
+
const allRels = steps.map((_, i) => `r${i}`);
|
|
1138
1149
|
returnClause = `RETURN ${[...allNodes, ...allRels].join(", ")}`;
|
|
1139
1150
|
} else if (spec.returnMode === "all") {
|
|
1140
1151
|
const allNodes = Array.from({ length: nodeIndex }, (_, i) => `n${i}`);
|
|
@@ -1215,6 +1226,7 @@ function matchesSingleFilter(properties, filter) {
|
|
|
1215
1226
|
|
|
1216
1227
|
// src/relationships/FallbackTraversalExecutor.ts
|
|
1217
1228
|
var MAX_STORAGE_CALLS = 500;
|
|
1229
|
+
var MAX_ENTITY_SCAN = 1e4;
|
|
1218
1230
|
var REL_FETCH_LIMIT = 1e3;
|
|
1219
1231
|
async function executeFallbackTraversal(repositoryId, storage, spec) {
|
|
1220
1232
|
const startTime = Date.now();
|
|
@@ -1224,6 +1236,9 @@ async function executeFallbackTraversal(repositoryId, storage, spec) {
|
|
|
1224
1236
|
throw new Error(`Fallback traversal exceeded ${MAX_STORAGE_CALLS} storage calls. Consider using a GraphTraversalProvider for large traversals.`);
|
|
1225
1237
|
}
|
|
1226
1238
|
};
|
|
1239
|
+
const isVertexQuery = !spec.steps || spec.steps.length === 0;
|
|
1240
|
+
const needsFullScan = isVertexQuery || spec.projection !== void 0;
|
|
1241
|
+
const fetchLimit = needsFullScan ? MAX_ENTITY_SCAN : spec.limit ?? 50;
|
|
1227
1242
|
let frontier = [];
|
|
1228
1243
|
if (spec.start.entityId) {
|
|
1229
1244
|
const entity = await resolveEntity(repositoryId, storage, spec.start.entityId);
|
|
@@ -1236,7 +1251,7 @@ async function executeFallbackTraversal(repositoryId, storage, spec) {
|
|
|
1236
1251
|
} else if (spec.start.entityType) {
|
|
1237
1252
|
const result = await storage.findEntities(repositoryId, {
|
|
1238
1253
|
entityTypes: [spec.start.entityType],
|
|
1239
|
-
limit:
|
|
1254
|
+
limit: fetchLimit,
|
|
1240
1255
|
offset: 0
|
|
1241
1256
|
});
|
|
1242
1257
|
storageCalls++;
|
|
@@ -1247,7 +1262,7 @@ async function executeFallbackTraversal(repositoryId, storage, spec) {
|
|
|
1247
1262
|
}));
|
|
1248
1263
|
} else if (spec.start.filter && spec.start.filter.length > 0) {
|
|
1249
1264
|
const result = await storage.findEntities(repositoryId, {
|
|
1250
|
-
limit:
|
|
1265
|
+
limit: fetchLimit,
|
|
1251
1266
|
offset: 0
|
|
1252
1267
|
});
|
|
1253
1268
|
storageCalls++;
|
|
@@ -1262,8 +1277,9 @@ async function executeFallbackTraversal(repositoryId, storage, spec) {
|
|
|
1262
1277
|
(entry) => matchesPropertyFilters(entry.entity.properties, spec.start.filter)
|
|
1263
1278
|
);
|
|
1264
1279
|
}
|
|
1280
|
+
const steps = spec.steps ?? [];
|
|
1265
1281
|
const allCollected = spec.returnMode === "all" ? [...frontier] : [];
|
|
1266
|
-
for (const step of
|
|
1282
|
+
for (const step of steps) {
|
|
1267
1283
|
checkCallLimit();
|
|
1268
1284
|
if (step.repeat) {
|
|
1269
1285
|
const result = await executeRepeatStep(
|
|
@@ -1316,10 +1332,89 @@ async function executeFallbackTraversal(repositoryId, storage, spec) {
|
|
|
1316
1332
|
}
|
|
1317
1333
|
const total = resultEntries.length;
|
|
1318
1334
|
const paged = resultEntries.slice(offset, offset + limit);
|
|
1319
|
-
const
|
|
1335
|
+
const suppressEntities = spec.projection !== void 0 && spec.projection.includeEntities !== true;
|
|
1336
|
+
let entities;
|
|
1337
|
+
if (suppressEntities) {
|
|
1338
|
+
entities = [];
|
|
1339
|
+
} else {
|
|
1340
|
+
entities = paged.map((entry) => {
|
|
1341
|
+
const projected = projectEntity(entry.entity, detailLevel);
|
|
1342
|
+
if (!spec.includeProvenance) {
|
|
1343
|
+
delete projected["provenance"];
|
|
1344
|
+
}
|
|
1345
|
+
return projected;
|
|
1346
|
+
});
|
|
1347
|
+
if (spec.includeRelationshipSummary && entities.length > 0) {
|
|
1348
|
+
const summaryResults = await Promise.all(
|
|
1349
|
+
paged.map(async (entry) => {
|
|
1350
|
+
storageCalls++;
|
|
1351
|
+
const result = await storage.getEntityRelationships(repositoryId, entry.entity.id, {
|
|
1352
|
+
direction: "both",
|
|
1353
|
+
limit: 1e4
|
|
1354
|
+
});
|
|
1355
|
+
const summary = { outbound: {}, inbound: {} };
|
|
1356
|
+
for (const rel of result.items) {
|
|
1357
|
+
if (rel.sourceEntityId === entry.entity.id) {
|
|
1358
|
+
summary.outbound[rel.relationshipType] = (summary.outbound[rel.relationshipType] ?? 0) + 1;
|
|
1359
|
+
}
|
|
1360
|
+
if (rel.targetEntityId === entry.entity.id) {
|
|
1361
|
+
summary.inbound[rel.relationshipType] = (summary.inbound[rel.relationshipType] ?? 0) + 1;
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
return summary;
|
|
1365
|
+
})
|
|
1366
|
+
);
|
|
1367
|
+
entities = entities.map((e, i) => ({ ...e, relationshipSummary: summaryResults[i] }));
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
let aggregations;
|
|
1371
|
+
if (spec.projection) {
|
|
1372
|
+
const propNames = spec.projection.properties;
|
|
1373
|
+
const mode = spec.projection.mode ?? "values";
|
|
1374
|
+
const distinct = spec.projection.distinct ?? false;
|
|
1375
|
+
const sourceEntries = dedup ? (() => {
|
|
1376
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1377
|
+
return (spec.returnMode === "all" ? allCollected : frontier).filter((e) => {
|
|
1378
|
+
if (seen.has(e.entity.id)) return false;
|
|
1379
|
+
seen.add(e.entity.id);
|
|
1380
|
+
return true;
|
|
1381
|
+
});
|
|
1382
|
+
})() : spec.returnMode === "all" ? allCollected : frontier;
|
|
1383
|
+
if (mode === "count" || distinct) {
|
|
1384
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1385
|
+
for (const entry of sourceEntries) {
|
|
1386
|
+
const vals = {};
|
|
1387
|
+
for (const prop of propNames) {
|
|
1388
|
+
vals[prop] = entry.entity.properties[prop] ?? null;
|
|
1389
|
+
}
|
|
1390
|
+
const key = JSON.stringify(vals);
|
|
1391
|
+
const existing = groups.get(key);
|
|
1392
|
+
if (existing) {
|
|
1393
|
+
existing.count++;
|
|
1394
|
+
} else {
|
|
1395
|
+
groups.set(key, { values: vals, count: 1 });
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
aggregations = Array.from(groups.values()).map((g) => ({
|
|
1399
|
+
values: g.values,
|
|
1400
|
+
...mode === "count" ? { count: g.count } : {}
|
|
1401
|
+
}));
|
|
1402
|
+
if (mode === "count") {
|
|
1403
|
+
aggregations.sort((a, b) => (b.count ?? 0) - (a.count ?? 0));
|
|
1404
|
+
}
|
|
1405
|
+
} else {
|
|
1406
|
+
aggregations = sourceEntries.map((entry) => {
|
|
1407
|
+
const vals = {};
|
|
1408
|
+
for (const prop of propNames) {
|
|
1409
|
+
vals[prop] = entry.entity.properties[prop] ?? null;
|
|
1410
|
+
}
|
|
1411
|
+
return { values: vals };
|
|
1412
|
+
});
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1320
1415
|
let relationships;
|
|
1321
1416
|
let paths;
|
|
1322
|
-
if (spec.returnMode === "path" || spec.returnMode === "all") {
|
|
1417
|
+
if (!suppressEntities && (spec.returnMode === "path" || spec.returnMode === "all")) {
|
|
1323
1418
|
const relMap = /* @__PURE__ */ new Map();
|
|
1324
1419
|
for (const entry of paged) {
|
|
1325
1420
|
for (const rel of entry.relationshipPath) {
|
|
@@ -1337,10 +1432,14 @@ async function executeFallbackTraversal(repositoryId, storage, spec) {
|
|
|
1337
1432
|
}
|
|
1338
1433
|
relationships = Array.from(relMap.values());
|
|
1339
1434
|
}
|
|
1340
|
-
if (spec.returnMode === "path") {
|
|
1435
|
+
if (!suppressEntities && spec.returnMode === "path") {
|
|
1341
1436
|
paths = paged.map((entry) => ({
|
|
1342
1437
|
length: entry.entityPath.length - 1,
|
|
1343
|
-
entities: [
|
|
1438
|
+
entities: [(() => {
|
|
1439
|
+
const e = projectEntity(entry.entity, detailLevel);
|
|
1440
|
+
if (!spec.includeProvenance) delete e["provenance"];
|
|
1441
|
+
return e;
|
|
1442
|
+
})()],
|
|
1344
1443
|
relationships: entry.relationshipPath.map((rel) => ({
|
|
1345
1444
|
id: rel.id,
|
|
1346
1445
|
type: rel.relationshipType,
|
|
@@ -1356,6 +1455,7 @@ async function executeFallbackTraversal(repositoryId, storage, spec) {
|
|
|
1356
1455
|
entities,
|
|
1357
1456
|
relationships,
|
|
1358
1457
|
paths,
|
|
1458
|
+
aggregations,
|
|
1359
1459
|
total,
|
|
1360
1460
|
returned: paged.length,
|
|
1361
1461
|
hasMore: offset + limit < total,
|
|
@@ -2190,6 +2290,58 @@ var MemoryRepository = class {
|
|
|
2190
2290
|
async getStats() {
|
|
2191
2291
|
return this.storage.getRepositoryStats(this.repositoryId);
|
|
2192
2292
|
}
|
|
2293
|
+
// ─── Bulk Operations ──────────────────────────────────────────────
|
|
2294
|
+
/** Delete all entities and relationships in this repository, preserving the repository and vocabulary */
|
|
2295
|
+
async deleteAllContents() {
|
|
2296
|
+
return this.storage.deleteAllContents(this.repositoryId);
|
|
2297
|
+
}
|
|
2298
|
+
/**
|
|
2299
|
+
* Returns a static markdown guide describing the recommended query strategy
|
|
2300
|
+
* for AI agents and other consumers of the repository.
|
|
2301
|
+
*/
|
|
2302
|
+
getQueryGuide() {
|
|
2303
|
+
return `## Query Strategy Guide
|
|
2304
|
+
|
|
2305
|
+
### Step 1 \u2014 Discover before you traverse
|
|
2306
|
+
|
|
2307
|
+
Before following relationships from an entity, check that it actually has relationships.
|
|
2308
|
+
\`memory_query_graph\` includes \`relationshipSummary\` on every returned entity by default
|
|
2309
|
+
(outbound and inbound counts by type). Inspect this before traversing \u2014 entities with
|
|
2310
|
+
zero outbound counts for the relationship type you need have no connections to follow.
|
|
2311
|
+
To skip summaries and reduce response size, set \`includeRelationshipSummary: false\`.
|
|
2312
|
+
|
|
2313
|
+
### Step 2 \u2014 Use projection for aggregation
|
|
2314
|
+
|
|
2315
|
+
To understand what data exists (e.g. all distinct values of a property, or counts by
|
|
2316
|
+
category), use \`memory_query_graph\` with \`projection\` instead of fetching full entities:
|
|
2317
|
+
\`{ start: { entityType: "..." }, projection: { properties: ["propName"], distinct: true }, limit: 200 }\`
|
|
2318
|
+
|
|
2319
|
+
This returns only the aggregated values \u2014 no entity objects \u2014 keeping responses lightweight.
|
|
2320
|
+
|
|
2321
|
+
### Step 3 \u2014 Traverse efficiently
|
|
2322
|
+
|
|
2323
|
+
- **Omit relationshipTypes** in a traversal step to follow all relationship types at once,
|
|
2324
|
+
rather than making separate calls per type.
|
|
2325
|
+
- **Use multi-step traversals** for multi-hop patterns. A two-step query
|
|
2326
|
+
(e.g. Equipment \u2192 Component \u2192 MaintenanceProcedure) is one call, not two sequential calls.
|
|
2327
|
+
- **Use returnMode "all"** when you need intermediate entities along the path, not just
|
|
2328
|
+
the terminal nodes.
|
|
2329
|
+
|
|
2330
|
+
### Step 4 \u2014 Use semantic search for known-unknowns
|
|
2331
|
+
|
|
2332
|
+
\`memory_search_by_concept\` is best for finding specific entities when you know roughly
|
|
2333
|
+
what you're looking for but not the exact label or type. It is not efficient for broad
|
|
2334
|
+
discovery \u2014 use projection or find_entities for that.
|
|
2335
|
+
|
|
2336
|
+
### Common anti-patterns to avoid
|
|
2337
|
+
|
|
2338
|
+
- **Don't traverse from entities with zero relationship counts.** The \`relationshipSummary\` on each entity tells you what is connected before you traverse.
|
|
2339
|
+
- **Don't split queries by relationship type.** One step with no type filter is better
|
|
2340
|
+
than three separate single-type calls.
|
|
2341
|
+
- **Don't use sequential single-hop traversals** when a multi-step query achieves the
|
|
2342
|
+
same result in one call.
|
|
2343
|
+
- **Don't fetch full entities when you only need property values.** Use projection.`;
|
|
2344
|
+
}
|
|
2193
2345
|
// ─── Events ────────────────────────────────────────────────────────
|
|
2194
2346
|
on(event, handler) {
|
|
2195
2347
|
return this.eventBus.on(event, handler);
|
|
@@ -3185,7 +3337,7 @@ var EventBus = class {
|
|
|
3185
3337
|
};
|
|
3186
3338
|
|
|
3187
3339
|
// src/portability/RepositoryExporter.ts
|
|
3188
|
-
var LIBRARY_VERSION = true ? "0.
|
|
3340
|
+
var LIBRARY_VERSION = true ? "0.3.0" : "0.1.0";
|
|
3189
3341
|
var RepositoryExporter = class {
|
|
3190
3342
|
storage;
|
|
3191
3343
|
provenance;
|
|
@@ -3528,13 +3680,22 @@ function describeChange(change) {
|
|
|
3528
3680
|
}
|
|
3529
3681
|
|
|
3530
3682
|
// src/portability/RepositoryImporter.ts
|
|
3683
|
+
function estimateChunkCount(totalEntities, totalRelationships) {
|
|
3684
|
+
const ESTIMATED_CHUNK_SIZE = 100;
|
|
3685
|
+
return Math.max(
|
|
3686
|
+
1,
|
|
3687
|
+
Math.ceil(totalEntities / ESTIMATED_CHUNK_SIZE) + Math.ceil(totalRelationships / ESTIMATED_CHUNK_SIZE)
|
|
3688
|
+
);
|
|
3689
|
+
}
|
|
3531
3690
|
var RepositoryImporter = class {
|
|
3532
3691
|
storage;
|
|
3533
3692
|
actorId;
|
|
3693
|
+
onProgress;
|
|
3534
3694
|
migrationEngine = new MigrationEngine();
|
|
3535
3695
|
constructor(config) {
|
|
3536
3696
|
this.storage = config.storage;
|
|
3537
3697
|
this.actorId = config.actorId;
|
|
3698
|
+
this.onProgress = config.onProgress;
|
|
3538
3699
|
}
|
|
3539
3700
|
/**
|
|
3540
3701
|
* Import an export archive according to the provided options.
|
|
@@ -3585,26 +3746,34 @@ var RepositoryImporter = class {
|
|
|
3585
3746
|
return this.importStreamMerge(header, chunks, options, warnings);
|
|
3586
3747
|
}
|
|
3587
3748
|
}
|
|
3588
|
-
/** Streaming create mode —
|
|
3749
|
+
/** Streaming create mode — fast bulk inserts with no existence checks */
|
|
3589
3750
|
async importStreamCreate(header, chunks, options, warnings) {
|
|
3590
3751
|
const target = options.target;
|
|
3591
|
-
const
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3752
|
+
const existing = await this.storage.getRepository(target.repositoryId);
|
|
3753
|
+
if (!existing) {
|
|
3754
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3755
|
+
await this.storage.createRepository({
|
|
3756
|
+
repositoryId: target.repositoryId,
|
|
3757
|
+
type: target.config.type,
|
|
3758
|
+
label: target.config.label,
|
|
3759
|
+
description: target.config.description,
|
|
3760
|
+
governanceConfig: target.config.governance ?? { mode: "open" },
|
|
3761
|
+
createdAt: now,
|
|
3762
|
+
createdBy: this.actorId
|
|
3763
|
+
});
|
|
3764
|
+
}
|
|
3601
3765
|
await this.storage.saveVocabulary(target.repositoryId, header.vocabulary);
|
|
3766
|
+
const totalEntities = header.manifest?.statistics?.entityCount ?? 0;
|
|
3767
|
+
const totalRelationships = header.manifest?.statistics?.relationshipCount ?? 0;
|
|
3768
|
+
const totalChunks = estimateChunkCount(totalEntities, totalRelationships);
|
|
3602
3769
|
let entitiesImported = 0;
|
|
3603
3770
|
let relationshipsImported = 0;
|
|
3771
|
+
let chunksCompleted = 0;
|
|
3604
3772
|
for await (const chunk of chunks) {
|
|
3605
|
-
const bulkResult = await this.storage.importBulk(target.repositoryId, [chunk]);
|
|
3773
|
+
const bulkResult = await this.storage.importBulk(target.repositoryId, [chunk], { skipExistenceCheck: true });
|
|
3606
3774
|
entitiesImported += bulkResult.entitiesImported;
|
|
3607
3775
|
relationshipsImported += bulkResult.relationshipsImported;
|
|
3776
|
+
chunksCompleted++;
|
|
3608
3777
|
for (const e of bulkResult.errors) {
|
|
3609
3778
|
warnings.push({
|
|
3610
3779
|
code: "import_error",
|
|
@@ -3612,6 +3781,15 @@ var RepositoryImporter = class {
|
|
|
3612
3781
|
id: e.item
|
|
3613
3782
|
});
|
|
3614
3783
|
}
|
|
3784
|
+
await this.onProgress?.({
|
|
3785
|
+
repositoryId: target.repositoryId,
|
|
3786
|
+
entitiesImported,
|
|
3787
|
+
relationshipsImported,
|
|
3788
|
+
totalEntities,
|
|
3789
|
+
totalRelationships,
|
|
3790
|
+
chunksCompleted,
|
|
3791
|
+
totalChunks
|
|
3792
|
+
});
|
|
3615
3793
|
}
|
|
3616
3794
|
return {
|
|
3617
3795
|
success: true,
|
|
@@ -3659,11 +3837,15 @@ var RepositoryImporter = class {
|
|
|
3659
3837
|
if (migrationResult.mergedVocabulary && migrationResult.mergedVocabulary !== targetVocabulary) {
|
|
3660
3838
|
await this.storage.saveVocabulary(repositoryId, migrationResult.mergedVocabulary);
|
|
3661
3839
|
}
|
|
3840
|
+
const totalEntities = header.manifest?.statistics?.entityCount ?? 0;
|
|
3841
|
+
const totalRelationships = header.manifest?.statistics?.relationshipCount ?? 0;
|
|
3842
|
+
const totalChunks = estimateChunkCount(totalEntities, totalRelationships);
|
|
3662
3843
|
const entityConflict = options.entityConflict ?? "skip";
|
|
3663
3844
|
let entitiesImported = 0;
|
|
3664
3845
|
let entitiesSkipped = 0;
|
|
3665
3846
|
let relationshipsImported = 0;
|
|
3666
3847
|
let relationshipsSkipped = 0;
|
|
3848
|
+
let chunksCompleted = 0;
|
|
3667
3849
|
for await (const chunk of chunks) {
|
|
3668
3850
|
if (chunk.entities) {
|
|
3669
3851
|
for (const entity of chunk.entities) {
|
|
@@ -3745,6 +3927,16 @@ var RepositoryImporter = class {
|
|
|
3745
3927
|
}
|
|
3746
3928
|
}
|
|
3747
3929
|
}
|
|
3930
|
+
chunksCompleted++;
|
|
3931
|
+
await this.onProgress?.({
|
|
3932
|
+
repositoryId,
|
|
3933
|
+
entitiesImported,
|
|
3934
|
+
relationshipsImported,
|
|
3935
|
+
totalEntities,
|
|
3936
|
+
totalRelationships,
|
|
3937
|
+
chunksCompleted,
|
|
3938
|
+
totalChunks
|
|
3939
|
+
});
|
|
3748
3940
|
}
|
|
3749
3941
|
const vocabExtensions = migrationResult.warnings.filter(
|
|
3750
3942
|
(w) => w.code === "entity_type_added" || w.code === "relationship_type_added"
|
|
@@ -3969,12 +4161,53 @@ var DeepMemory = class {
|
|
|
3969
4161
|
async deleteRepository(repositoryId) {
|
|
3970
4162
|
await this.ensureInitialised();
|
|
3971
4163
|
this.validateRepositoryId(repositoryId);
|
|
3972
|
-
await this.storage.
|
|
4164
|
+
const stats = await this.storage.getRepositoryStats(repositoryId);
|
|
4165
|
+
await this.globalEventBus.emit("delete:started", {
|
|
4166
|
+
repositoryId,
|
|
4167
|
+
totalEntities: stats.entityCount,
|
|
4168
|
+
totalRelationships: stats.relationshipCount
|
|
4169
|
+
});
|
|
4170
|
+
await this.storage.deleteRepository(
|
|
4171
|
+
repositoryId,
|
|
4172
|
+
(progress) => this.globalEventBus.emit("delete:progress", { repositoryId, ...progress })
|
|
4173
|
+
);
|
|
4174
|
+
await this.globalEventBus.emit("delete:completed", {
|
|
4175
|
+
repositoryId,
|
|
4176
|
+
entitiesDeleted: stats.entityCount,
|
|
4177
|
+
relationshipsDeleted: stats.relationshipCount
|
|
4178
|
+
});
|
|
3973
4179
|
await this.globalEventBus.emit("repository:deleted", { repositoryId });
|
|
3974
4180
|
}
|
|
4181
|
+
/** Delete all entities and relationships in a repository, preserving the repository and vocabulary */
|
|
4182
|
+
async deleteAllContents(repositoryId) {
|
|
4183
|
+
await this.ensureInitialised();
|
|
4184
|
+
this.validateRepositoryId(repositoryId);
|
|
4185
|
+
const stats = await this.storage.getRepositoryStats(repositoryId);
|
|
4186
|
+
await this.globalEventBus.emit("delete:started", {
|
|
4187
|
+
repositoryId,
|
|
4188
|
+
totalEntities: stats.entityCount,
|
|
4189
|
+
totalRelationships: stats.relationshipCount
|
|
4190
|
+
});
|
|
4191
|
+
const result = await this.storage.deleteAllContents(
|
|
4192
|
+
repositoryId,
|
|
4193
|
+
(progress) => this.globalEventBus.emit("delete:progress", { repositoryId, ...progress })
|
|
4194
|
+
);
|
|
4195
|
+
await this.globalEventBus.emit("delete:completed", {
|
|
4196
|
+
repositoryId,
|
|
4197
|
+
entitiesDeleted: result.deletedEntities,
|
|
4198
|
+
relationshipsDeleted: result.deletedRelationships
|
|
4199
|
+
});
|
|
4200
|
+
return result;
|
|
4201
|
+
}
|
|
3975
4202
|
/** Export a repository to a portable archive */
|
|
3976
4203
|
async exportRepository(repositoryId, options) {
|
|
3977
4204
|
await this.ensureInitialised();
|
|
4205
|
+
const stats = await this.storage.getRepositoryStats(repositoryId);
|
|
4206
|
+
await this.globalEventBus.emit("export:started", {
|
|
4207
|
+
repositoryId,
|
|
4208
|
+
totalEntities: stats.entityCount,
|
|
4209
|
+
totalRelationships: stats.relationshipCount
|
|
4210
|
+
});
|
|
3978
4211
|
const exporter = new RepositoryExporter({
|
|
3979
4212
|
storage: this.storage,
|
|
3980
4213
|
provenance: this.provenance.getContext(),
|
|
@@ -3991,9 +4224,11 @@ var DeepMemory = class {
|
|
|
3991
4224
|
/** Import a repository from a portable archive */
|
|
3992
4225
|
async importRepository(archive, options) {
|
|
3993
4226
|
await this.ensureInitialised();
|
|
4227
|
+
await this.globalEventBus.emit("import:started", { repositoryId: options.target.repositoryId });
|
|
3994
4228
|
const importer = new RepositoryImporter({
|
|
3995
4229
|
storage: this.storage,
|
|
3996
|
-
actorId: this.provenance.getContext().actorId
|
|
4230
|
+
actorId: this.provenance.getContext().actorId,
|
|
4231
|
+
onProgress: (progress) => this.globalEventBus.emit("import:progress", progress)
|
|
3997
4232
|
});
|
|
3998
4233
|
const result = await importer.import(archive, options);
|
|
3999
4234
|
if (result.success) {
|
|
@@ -4022,15 +4257,44 @@ var DeepMemory = class {
|
|
|
4022
4257
|
provenance: this.provenance.getContext(),
|
|
4023
4258
|
legal: options?.legal
|
|
4024
4259
|
});
|
|
4025
|
-
await this.
|
|
4260
|
+
const stats = await this.storage.getRepositoryStats(repositoryId);
|
|
4261
|
+
const totalEntities = stats.entityCount;
|
|
4262
|
+
const totalRelationships = stats.relationshipCount;
|
|
4263
|
+
const estimatedChunkSize = 100;
|
|
4264
|
+
const totalChunks = Math.max(
|
|
4265
|
+
1,
|
|
4266
|
+
Math.ceil(totalEntities / estimatedChunkSize) + Math.ceil(totalRelationships / estimatedChunkSize)
|
|
4267
|
+
);
|
|
4268
|
+
await this.globalEventBus.emit("export:started", { repositoryId, totalEntities, totalRelationships });
|
|
4026
4269
|
let entityCount = 0;
|
|
4027
4270
|
let relationshipCount = 0;
|
|
4271
|
+
let chunksCompleted = 0;
|
|
4028
4272
|
for await (const item of exporter.exportStream(repositoryId)) {
|
|
4029
4273
|
yield item;
|
|
4030
4274
|
if (item.type === "entities") {
|
|
4031
4275
|
entityCount += item.data.length;
|
|
4276
|
+
chunksCompleted++;
|
|
4277
|
+
await this.globalEventBus.emit("export:progress", {
|
|
4278
|
+
repositoryId,
|
|
4279
|
+
entitiesExported: entityCount,
|
|
4280
|
+
relationshipsExported: relationshipCount,
|
|
4281
|
+
totalEntities,
|
|
4282
|
+
totalRelationships,
|
|
4283
|
+
chunksCompleted,
|
|
4284
|
+
totalChunks
|
|
4285
|
+
});
|
|
4032
4286
|
} else if (item.type === "relationships") {
|
|
4033
4287
|
relationshipCount += item.data.length;
|
|
4288
|
+
chunksCompleted++;
|
|
4289
|
+
await this.globalEventBus.emit("export:progress", {
|
|
4290
|
+
repositoryId,
|
|
4291
|
+
entitiesExported: entityCount,
|
|
4292
|
+
relationshipsExported: relationshipCount,
|
|
4293
|
+
totalEntities,
|
|
4294
|
+
totalRelationships,
|
|
4295
|
+
chunksCompleted,
|
|
4296
|
+
totalChunks
|
|
4297
|
+
});
|
|
4034
4298
|
}
|
|
4035
4299
|
}
|
|
4036
4300
|
await this.globalEventBus.emit("export:completed", {
|
|
@@ -4047,7 +4311,8 @@ var DeepMemory = class {
|
|
|
4047
4311
|
await this.ensureInitialised();
|
|
4048
4312
|
const importer = new RepositoryImporter({
|
|
4049
4313
|
storage: this.storage,
|
|
4050
|
-
actorId: this.provenance.getContext().actorId
|
|
4314
|
+
actorId: this.provenance.getContext().actorId,
|
|
4315
|
+
onProgress: (progress) => this.globalEventBus.emit("import:progress", progress)
|
|
4051
4316
|
});
|
|
4052
4317
|
await this.globalEventBus.emit("import:started", { repositoryId: options.target.repositoryId });
|
|
4053
4318
|
const result = await importer.importStream(header, chunks, options);
|
|
@@ -4171,12 +4436,21 @@ var InMemoryStorageProvider = class {
|
|
|
4171
4436
|
if (updates.metadata !== void 0) repo.metadata = { ...repo.metadata, ...updates.metadata };
|
|
4172
4437
|
return repo;
|
|
4173
4438
|
}
|
|
4174
|
-
async deleteRepository(repositoryId) {
|
|
4439
|
+
async deleteRepository(repositoryId, _onProgress) {
|
|
4175
4440
|
if (!this.stores.has(repositoryId)) {
|
|
4176
4441
|
throw new RepositoryNotFoundError(repositoryId);
|
|
4177
4442
|
}
|
|
4178
4443
|
this.stores.delete(repositoryId);
|
|
4179
4444
|
}
|
|
4445
|
+
async deleteAllContents(repositoryId, _onProgress) {
|
|
4446
|
+
const store = this.getStore(repositoryId);
|
|
4447
|
+
const deletedEntities = store.entities.size;
|
|
4448
|
+
const deletedRelationships = store.relationships.size;
|
|
4449
|
+
store.entities.clear();
|
|
4450
|
+
store.slugIndex.clear();
|
|
4451
|
+
store.relationships.clear();
|
|
4452
|
+
return { deletedEntities, deletedRelationships };
|
|
4453
|
+
}
|
|
4180
4454
|
async getRepositoryStats(repositoryId) {
|
|
4181
4455
|
const store = this.getStore(repositoryId);
|
|
4182
4456
|
const entityTypeBreakdown = {};
|
|
@@ -4631,7 +4905,7 @@ var InMemoryStorageProvider = class {
|
|
|
4631
4905
|
yield { type: "relationships", data: [], sequence: 0, isLast: true };
|
|
4632
4906
|
}
|
|
4633
4907
|
}
|
|
4634
|
-
async importBulk(repositoryId, data) {
|
|
4908
|
+
async importBulk(repositoryId, data, _options) {
|
|
4635
4909
|
const store = this.getStore(repositoryId);
|
|
4636
4910
|
let entitiesImported = 0;
|
|
4637
4911
|
let relationshipsImported = 0;
|