@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.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 (spec.steps && spec.steps.length > maxSteps) {
816
- errors.push(`steps length ${spec.steps.length} exceeds maximum ${maxSteps}`);
817
- }
818
- if (spec.steps) {
819
- for (let i = 0; i < spec.steps.length; i++) {
820
- const step = spec.steps[i];
821
- if (!step.direction || !["out", "in", "both"].includes(step.direction)) {
822
- errors.push(`steps[${i}].direction must be 'out', 'in', or 'both'`);
823
- }
824
- if (step.repeat) {
825
- if (step.repeat.maxDepth === void 0 || step.repeat.maxDepth <= 0) {
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 spec.steps) {
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 && spec.steps) {
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 < spec.steps.length; i++) {
858
- const step = spec.steps[i];
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
- for (const step of spec.steps) {
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
- for (let i = 0; i < spec.steps.length; i++) {
1095
- const step = spec.steps[i];
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 = spec.steps.map((_, i) => `r${i}`);
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: spec.limit ?? 50,
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: spec.limit ?? 50,
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 spec.steps) {
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 entities = paged.map((entry) => projectEntity(entry.entity, detailLevel));
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: [projectEntity(entry.entity, detailLevel)],
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.2.0" : "0.1.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 — set up repo then pipe chunks through importBulk */
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 now = (/* @__PURE__ */ new Date()).toISOString();
3592
- await this.storage.createRepository({
3593
- repositoryId: target.repositoryId,
3594
- type: target.config.type,
3595
- label: target.config.label,
3596
- description: target.config.description,
3597
- governanceConfig: target.config.governance ?? { mode: "open" },
3598
- createdAt: now,
3599
- createdBy: this.actorId
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.deleteRepository(repositoryId);
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.globalEventBus.emit("export:started", { repositoryId });
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;