@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/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 (spec.steps && spec.steps.length > maxSteps) {
871
- errors.push(`steps length ${spec.steps.length} exceeds maximum ${maxSteps}`);
872
- }
873
- if (spec.steps) {
874
- for (let i = 0; i < spec.steps.length; i++) {
875
- const step = spec.steps[i];
876
- if (!step.direction || !["out", "in", "both"].includes(step.direction)) {
877
- errors.push(`steps[${i}].direction must be 'out', 'in', or 'both'`);
878
- }
879
- if (step.repeat) {
880
- if (step.repeat.maxDepth === void 0 || step.repeat.maxDepth <= 0) {
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 spec.steps) {
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 && spec.steps) {
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 < spec.steps.length; i++) {
913
- const step = spec.steps[i];
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
- for (const step of spec.steps) {
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
- for (let i = 0; i < spec.steps.length; i++) {
1150
- const step = spec.steps[i];
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 = spec.steps.map((_, i) => `r${i}`);
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: spec.limit ?? 50,
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: spec.limit ?? 50,
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 spec.steps) {
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 entities = paged.map((entry) => projectEntity(entry.entity, detailLevel));
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: [projectEntity(entry.entity, detailLevel)],
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.2.0" : "0.1.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 — set up repo then pipe chunks through importBulk */
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 now = (/* @__PURE__ */ new Date()).toISOString();
3647
- await this.storage.createRepository({
3648
- repositoryId: target.repositoryId,
3649
- type: target.config.type,
3650
- label: target.config.label,
3651
- description: target.config.description,
3652
- governanceConfig: target.config.governance ?? { mode: "open" },
3653
- createdAt: now,
3654
- createdBy: this.actorId
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.deleteRepository(repositoryId);
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.globalEventBus.emit("export:started", { repositoryId });
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;