@utaba/deep-memory 0.14.0 → 0.16.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
@@ -165,6 +165,18 @@ var OperationCancelledError = class extends DeepMemoryError {
165
165
  this.reason = reason;
166
166
  }
167
167
  };
168
+ var OperationAbortedError = class extends DeepMemoryError {
169
+ operation;
170
+ constructor(operation) {
171
+ super(
172
+ "OPERATION_ABORTED",
173
+ `${operation} aborted by caller`,
174
+ `The caller's AbortSignal was triggered. Partial state from completed batches has been retained.`
175
+ );
176
+ this.name = "OperationAbortedError";
177
+ this.operation = operation;
178
+ }
179
+ };
168
180
  var EmbeddingProviderRequiredError = class extends DeepMemoryError {
169
181
  constructor() {
170
182
  super(
@@ -821,10 +833,15 @@ var EntityManager = class {
821
833
  attempt++;
822
834
  if (attempt > maxRetries) {
823
835
  const errorMsg = err instanceof Error ? err.message : String(err);
836
+ const failureMessage = `embedBatch failed after ${maxRetries} retries: ${errorMsg}`;
837
+ const failed = entries.map(([id]) => ({ entityId: id, error: failureMessage }));
838
+ for (const { entityId, error } of failed) {
839
+ await options?.onItemFailed?.(entityId, error);
840
+ }
824
841
  return {
825
842
  processed: 0,
826
843
  failed: entries.length,
827
- errors: entries.map(([id]) => ({ entityId: id, error: `embedBatch failed after ${maxRetries} retries: ${errorMsg}` })),
844
+ errors: failed,
828
845
  modelId: this.embedding.modelId(),
829
846
  dimensions: this.embedding.dimensions()
830
847
  };
@@ -847,7 +864,9 @@ var EntityManager = class {
847
864
  });
848
865
  processed++;
849
866
  } catch (err) {
850
- errors.push({ entityId: id, error: err instanceof Error ? err.message : String(err) });
867
+ const errorMessage = err instanceof Error ? err.message : String(err);
868
+ errors.push({ entityId: id, error: errorMessage });
869
+ await options?.onItemFailed?.(id, errorMessage);
851
870
  }
852
871
  }
853
872
  return {
@@ -872,6 +891,7 @@ var EntityManager = class {
872
891
  }
873
892
  const batchSize = options?.batchSize ?? 50;
874
893
  const errorThreshold = options?.errorThresholdToAbort;
894
+ const signal = options?.signal;
875
895
  const firstPage = await this.storage.findEntities(this.repositoryId, { limit: 1, offset: 0 });
876
896
  const total = firstPage.total;
877
897
  let totalProcessed = 0;
@@ -879,6 +899,9 @@ var EntityManager = class {
879
899
  const allErrors = [];
880
900
  let offset = 0;
881
901
  while (offset < total) {
902
+ if (signal?.aborted) {
903
+ throw new OperationAbortedError("reembedAll");
904
+ }
882
905
  const page = await this.storage.findEntities(this.repositoryId, {
883
906
  limit: batchSize,
884
907
  offset
@@ -886,17 +909,21 @@ var EntityManager = class {
886
909
  if (page.items.length === 0) break;
887
910
  const ids = page.items.map((e) => e.id);
888
911
  const result = await this.reembedEntities(ids, {
889
- maxRetries: options?.maxRetries
912
+ maxRetries: options?.maxRetries,
913
+ onItemFailed: options?.onItemFailed
890
914
  });
891
915
  totalProcessed += result.processed;
892
916
  totalFailed += result.failed;
893
917
  allErrors.push(...result.errors);
894
- options?.onProgress?.(totalProcessed, total, totalFailed);
918
+ await options?.onProgress?.(totalProcessed, total, totalFailed);
895
919
  if (errorThreshold !== void 0 && totalFailed >= errorThreshold) {
896
920
  allErrors.push({ entityId: "", error: `Aborted: error threshold of ${errorThreshold} reached (${totalFailed} failures)` });
897
921
  break;
898
922
  }
899
923
  offset += page.items.length;
924
+ if (signal?.aborted) {
925
+ throw new OperationAbortedError("reembedAll");
926
+ }
900
927
  const delayMs = options?.delayBetweenBatchesMs ?? 0;
901
928
  if (delayMs > 0 && offset < total) {
902
929
  await new Promise((resolve) => {
@@ -2263,7 +2290,13 @@ var MemoryRepository = class {
2263
2290
  * the repository's embedding configuration is updated to the new values before
2264
2291
  * re-embedding, so all subsequent entity writes use the new model/dimensionality.
2265
2292
  *
2266
- * Processes in batches with optional rate limiting. Emits reembed:* events for progress tracking.
2293
+ * Processes in batches with optional rate limiting.
2294
+ *
2295
+ * Progress is delivered via the optional `onProgress` callback. Most callers
2296
+ * should use {@link DeepMemory.reembedAll} instead, which wires this callback
2297
+ * to the global event bus so `reembed:*` events are reachable via
2298
+ * `DeepMemory.on()`.
2299
+ *
2267
2300
  * Updates repository metadata with the new model ID and dimensions on completion.
2268
2301
  */
2269
2302
  async reembedAll(options) {
@@ -2298,46 +2331,26 @@ var MemoryRepository = class {
2298
2331
  }
2299
2332
  });
2300
2333
  }
2301
- const stats = await this.getStats();
2302
- await this.eventBus.emit("reembed:started", {
2303
- repositoryId: this.repositoryId,
2304
- totalEntities: stats.entityCount
2334
+ const result = await this.entityManager.reembedAll({
2335
+ batchSize: options?.batchSize,
2336
+ maxRetries: options?.maxRetries,
2337
+ errorThresholdToAbort: options?.errorThresholdToAbort,
2338
+ delayBetweenBatchesMs: options?.delayBetweenBatchesMs,
2339
+ signal: options?.signal,
2340
+ onProgress: async (processed, total, failed) => {
2341
+ await options?.onProgress?.({ processed, totalEntities: total, failed });
2342
+ },
2343
+ onItemFailed: async (entityId, error) => {
2344
+ await options?.onItemFailed?.({ entityId, error });
2345
+ }
2305
2346
  });
2306
- try {
2307
- const result = await this.entityManager.reembedAll({
2308
- batchSize: options?.batchSize,
2309
- maxRetries: options?.maxRetries,
2310
- errorThresholdToAbort: options?.errorThresholdToAbort,
2311
- delayBetweenBatchesMs: options?.delayBetweenBatchesMs,
2312
- onProgress: async (processed, total, failed) => {
2313
- await this.eventBus.emit("reembed:progress", {
2314
- repositoryId: this.repositoryId,
2315
- processed,
2316
- totalEntities: total,
2317
- failed
2318
- });
2319
- }
2320
- });
2321
- await this.storage.updateRepository(this.repositoryId, {
2322
- metadata: {
2323
- embeddingModelId: result.modelId,
2324
- embeddingDimensions: result.dimensions
2325
- }
2326
- });
2327
- await this.eventBus.emit("reembed:completed", {
2328
- repositoryId: this.repositoryId,
2329
- processed: result.processed,
2330
- failed: result.failed,
2331
- modelId: result.modelId
2332
- });
2333
- return result;
2334
- } catch (err) {
2335
- await this.eventBus.emit("reembed:failed", {
2336
- repositoryId: this.repositoryId,
2337
- error: err instanceof Error ? err.message : String(err)
2338
- });
2339
- throw err;
2340
- }
2347
+ await this.storage.updateRepository(this.repositoryId, {
2348
+ metadata: {
2349
+ embeddingModelId: result.modelId,
2350
+ embeddingDimensions: result.dimensions
2351
+ }
2352
+ });
2353
+ return result;
2341
2354
  }
2342
2355
  // ─── Relationships ─────────────────────────────────────────────────
2343
2356
  async createRelationships(inputs) {
@@ -3451,7 +3464,7 @@ var EventBus = class {
3451
3464
  };
3452
3465
 
3453
3466
  // src/portability/RepositoryExporter.ts
3454
- var LIBRARY_VERSION = true ? "0.14.0" : "0.1.0";
3467
+ var LIBRARY_VERSION = true ? "0.16.0" : "0.1.0";
3455
3468
  var RepositoryExporter = class {
3456
3469
  storage;
3457
3470
  provenance;
@@ -3805,11 +3818,20 @@ var RepositoryImporter = class {
3805
3818
  storage;
3806
3819
  actorId;
3807
3820
  onProgress;
3821
+ onItemFailed;
3822
+ signal;
3808
3823
  migrationEngine = new MigrationEngine();
3809
3824
  constructor(config) {
3810
3825
  this.storage = config.storage;
3811
3826
  this.actorId = config.actorId;
3812
3827
  this.onProgress = config.onProgress;
3828
+ this.onItemFailed = config.onItemFailed;
3829
+ this.signal = config.signal;
3830
+ }
3831
+ throwIfAborted() {
3832
+ if (this.signal?.aborted) {
3833
+ throw new OperationAbortedError("import");
3834
+ }
3813
3835
  }
3814
3836
  /**
3815
3837
  * Import an export archive according to the provided options.
@@ -3885,6 +3907,7 @@ var RepositoryImporter = class {
3885
3907
  let chunksCompleted = 0;
3886
3908
  const adaptiveConcurrencyHandle = options.bulk?.adaptiveConcurrencyHandle ?? {};
3887
3909
  for await (const chunk of chunks) {
3910
+ this.throwIfAborted();
3888
3911
  const bulkResult = await this.storage.importBulk(target.repositoryId, [chunk], {
3889
3912
  ...options.bulk,
3890
3913
  skipExistenceCheck: true,
@@ -3899,6 +3922,8 @@ var RepositoryImporter = class {
3899
3922
  message: `Failed to import ${e.item}: ${e.error}`,
3900
3923
  id: e.item
3901
3924
  });
3925
+ const itemType = chunk.entities?.some((x) => x.id === e.item) ? "entity" : chunk.relationships?.some((x) => x.id === e.item) ? "relationship" : "entity";
3926
+ await this.onItemFailed?.({ itemId: e.item, itemType, error: e.error });
3902
3927
  }
3903
3928
  await this.onProgress?.({
3904
3929
  repositoryId: target.repositoryId,
@@ -3966,6 +3991,7 @@ var RepositoryImporter = class {
3966
3991
  let relationshipsSkipped = 0;
3967
3992
  let chunksCompleted = 0;
3968
3993
  for await (const chunk of chunks) {
3994
+ this.throwIfAborted();
3969
3995
  if (chunk.entities) {
3970
3996
  for (const entity of chunk.entities) {
3971
3997
  let existing = await this.storage.getEntity(repositoryId, entity.id);
@@ -4034,11 +4060,13 @@ var RepositoryImporter = class {
4034
4060
  const targetExists = await this.storage.getEntity(repositoryId, rel.targetEntityId);
4035
4061
  if (!sourceExists || !targetExists) {
4036
4062
  relationshipsSkipped++;
4063
+ const errorMsg = `source or target entity missing`;
4037
4064
  warnings.push({
4038
4065
  code: "relationship_orphaned",
4039
- message: `Relationship "${rel.id}" skipped \u2014 source or target entity missing`,
4066
+ message: `Relationship "${rel.id}" skipped \u2014 ${errorMsg}`,
4040
4067
  relationshipId: rel.id
4041
4068
  });
4069
+ await this.onItemFailed?.({ itemId: rel.id, itemType: "relationship", error: errorMsg });
4042
4070
  } else {
4043
4071
  try {
4044
4072
  await this.storage.createRelationship(repositoryId, rel);
@@ -4384,22 +4412,37 @@ var DeepMemory = class {
4384
4412
  const importer = new RepositoryImporter({
4385
4413
  storage: this.storage,
4386
4414
  actorId: this.provenance.getContext().actorId,
4387
- onProgress: (progress) => this.globalEventBus.emit("import:progress", progress)
4415
+ onProgress: (progress) => this.globalEventBus.emit("import:progress", progress),
4416
+ onItemFailed: (failure) => this.globalEventBus.emit("import:item-failed", {
4417
+ repositoryId: options.target.repositoryId,
4418
+ itemId: failure.itemId,
4419
+ itemType: failure.itemType,
4420
+ error: failure.error
4421
+ }),
4422
+ signal: options.signal
4388
4423
  });
4389
- const result = await importer.import(archive, options);
4390
- if (result.success) {
4391
- await this.globalEventBus.emit("import:completed", {
4392
- repositoryId: result.repositoryId,
4393
- entitiesImported: result.statistics.entitiesImported,
4394
- relationshipsImported: result.statistics.relationshipsImported
4395
- });
4396
- } else {
4424
+ try {
4425
+ const result = await importer.import(archive, options);
4426
+ if (result.success) {
4427
+ await this.globalEventBus.emit("import:completed", {
4428
+ repositoryId: result.repositoryId,
4429
+ entitiesImported: result.statistics.entitiesImported,
4430
+ relationshipsImported: result.statistics.relationshipsImported
4431
+ });
4432
+ } else {
4433
+ await this.globalEventBus.emit("import:failed", {
4434
+ repositoryId: result.repositoryId,
4435
+ error: result.warnings.map((w) => w.message).join("; ")
4436
+ });
4437
+ }
4438
+ return result;
4439
+ } catch (err) {
4397
4440
  await this.globalEventBus.emit("import:failed", {
4398
- repositoryId: result.repositoryId,
4399
- error: result.warnings.map((w) => w.message).join("; ")
4441
+ repositoryId: options.target.repositoryId,
4442
+ error: err instanceof Error ? err.message : String(err)
4400
4443
  });
4444
+ throw err;
4401
4445
  }
4402
- return result;
4403
4446
  }
4404
4447
  /**
4405
4448
  * Stream a repository export as an async generator.
@@ -4468,23 +4511,88 @@ var DeepMemory = class {
4468
4511
  const importer = new RepositoryImporter({
4469
4512
  storage: this.storage,
4470
4513
  actorId: this.provenance.getContext().actorId,
4471
- onProgress: (progress) => this.globalEventBus.emit("import:progress", progress)
4514
+ onProgress: (progress) => this.globalEventBus.emit("import:progress", progress),
4515
+ onItemFailed: (failure) => this.globalEventBus.emit("import:item-failed", {
4516
+ repositoryId: options.target.repositoryId,
4517
+ itemId: failure.itemId,
4518
+ itemType: failure.itemType,
4519
+ error: failure.error
4520
+ }),
4521
+ signal: options.signal
4472
4522
  });
4473
4523
  await this.globalEventBus.emit("import:started", { repositoryId: options.target.repositoryId });
4474
- const result = await importer.importStream(header, chunks, options);
4475
- if (result.success) {
4476
- await this.globalEventBus.emit("import:completed", {
4477
- repositoryId: result.repositoryId,
4478
- entitiesImported: result.statistics.entitiesImported,
4479
- relationshipsImported: result.statistics.relationshipsImported
4480
- });
4481
- } else {
4524
+ try {
4525
+ const result = await importer.importStream(header, chunks, options);
4526
+ if (result.success) {
4527
+ await this.globalEventBus.emit("import:completed", {
4528
+ repositoryId: result.repositoryId,
4529
+ entitiesImported: result.statistics.entitiesImported,
4530
+ relationshipsImported: result.statistics.relationshipsImported
4531
+ });
4532
+ } else {
4533
+ await this.globalEventBus.emit("import:failed", {
4534
+ repositoryId: result.repositoryId,
4535
+ error: result.warnings.map((w) => w.message).join("; ")
4536
+ });
4537
+ }
4538
+ return result;
4539
+ } catch (err) {
4482
4540
  await this.globalEventBus.emit("import:failed", {
4483
- repositoryId: result.repositoryId,
4484
- error: result.warnings.map((w) => w.message).join("; ")
4541
+ repositoryId: options.target.repositoryId,
4542
+ error: err instanceof Error ? err.message : String(err)
4485
4543
  });
4544
+ throw err;
4545
+ }
4546
+ }
4547
+ /**
4548
+ * Re-embed all entities in a repository, emitting `reembed:*` events on the
4549
+ * global event bus (reachable via {@link DeepMemory.on}).
4550
+ *
4551
+ * Mirrors the lifecycle of {@link importRepositoryStream}: emits
4552
+ * `reembed:started` up front, `reembed:progress` after every batch,
4553
+ * and either `reembed:completed` or `reembed:failed` at the end.
4554
+ *
4555
+ * `options.signal` is honoured at batch boundaries — any vectors already
4556
+ * written by completed batches are retained, and `reembed:failed` is emitted
4557
+ * with the abort error message.
4558
+ */
4559
+ async reembedAll(repositoryId, options) {
4560
+ await this.ensureInitialized();
4561
+ const repo = await this.openRepository(repositoryId);
4562
+ const stats = await repo.getStats();
4563
+ await this.globalEventBus.emit("reembed:started", {
4564
+ repositoryId,
4565
+ totalEntities: stats.entityCount
4566
+ });
4567
+ try {
4568
+ const result = await repo.reembedAll({
4569
+ ...options,
4570
+ onProgress: (progress) => this.globalEventBus.emit("reembed:progress", {
4571
+ repositoryId,
4572
+ processed: progress.processed,
4573
+ totalEntities: progress.totalEntities,
4574
+ failed: progress.failed
4575
+ }),
4576
+ onItemFailed: (failure) => this.globalEventBus.emit("reembed:item-failed", {
4577
+ repositoryId,
4578
+ entityId: failure.entityId,
4579
+ error: failure.error
4580
+ })
4581
+ });
4582
+ await this.globalEventBus.emit("reembed:completed", {
4583
+ repositoryId,
4584
+ processed: result.processed,
4585
+ failed: result.failed,
4586
+ modelId: result.modelId
4587
+ });
4588
+ return result;
4589
+ } catch (err) {
4590
+ await this.globalEventBus.emit("reembed:failed", {
4591
+ repositoryId,
4592
+ error: err instanceof Error ? err.message : String(err)
4593
+ });
4594
+ throw err;
4486
4595
  }
4487
- return result;
4488
4596
  }
4489
4597
  /** Subscribe to global events */
4490
4598
  on(event, handler) {
@@ -5585,6 +5693,7 @@ export {
5585
5693
  InvalidInputError,
5586
5694
  MemoryRepository,
5587
5695
  NoOpEmbeddingProvider,
5696
+ OperationAbortedError,
5588
5697
  OperationCancelledError,
5589
5698
  ProviderError,
5590
5699
  RelationshipConstraintError,