mindoodb 0.0.10 → 0.0.11

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.
Files changed (43) hide show
  1. package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.d.ts +1 -0
  2. package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.d.ts.map +1 -1
  3. package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.js +10 -0
  4. package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.js.map +1 -1
  5. package/dist/appendonlystores/network/HttpTransport.d.ts +1 -0
  6. package/dist/appendonlystores/network/HttpTransport.d.ts.map +1 -1
  7. package/dist/appendonlystores/network/HttpTransport.js +4 -3
  8. package/dist/appendonlystores/network/HttpTransport.js.map +1 -1
  9. package/dist/core/BaseMindooDB.d.ts +30 -0
  10. package/dist/core/BaseMindooDB.d.ts.map +1 -1
  11. package/dist/core/BaseMindooDB.js +155 -38
  12. package/dist/core/BaseMindooDB.js.map +1 -1
  13. package/dist/core/BaseMindooTenant.d.ts +0 -1
  14. package/dist/core/BaseMindooTenant.d.ts.map +1 -1
  15. package/dist/core/BaseMindooTenant.js +3 -13
  16. package/dist/core/BaseMindooTenant.js.map +1 -1
  17. package/dist/core/BaseMindooTenantDirectory.d.ts +1 -0
  18. package/dist/core/BaseMindooTenantDirectory.d.ts.map +1 -1
  19. package/dist/core/BaseMindooTenantDirectory.js +24 -8
  20. package/dist/core/BaseMindooTenantDirectory.js.map +1 -1
  21. package/dist/core/appendonlystores/AttachmentReadPlanner.d.ts.map +1 -1
  22. package/dist/core/appendonlystores/AttachmentReadPlanner.js +1 -1
  23. package/dist/core/appendonlystores/AttachmentReadPlanner.js.map +1 -1
  24. package/dist/core/types.d.ts +17 -8
  25. package/dist/core/types.d.ts.map +1 -1
  26. package/dist/core/types.js.map +1 -1
  27. package/dist/node/server/MindooDBServer.d.ts +104 -0
  28. package/dist/node/server/MindooDBServer.d.ts.map +1 -1
  29. package/dist/node/server/MindooDBServer.js +192 -32
  30. package/dist/node/server/MindooDBServer.js.map +1 -1
  31. package/dist/node/server/TenantManager.d.ts +2 -0
  32. package/dist/node/server/TenantManager.d.ts.map +1 -1
  33. package/dist/node/server/TenantManager.js +21 -12
  34. package/dist/node/server/TenantManager.js.map +1 -1
  35. package/dist/node/server/config.d.ts.map +1 -1
  36. package/dist/node/server/config.js +4 -0
  37. package/dist/node/server/config.js.map +1 -1
  38. package/dist/node/server/server.js +2 -2
  39. package/dist/node/server/server.js.map +1 -1
  40. package/dist/node/server/types.d.ts +1 -0
  41. package/dist/node/server/types.d.ts.map +1 -1
  42. package/dist/node/server/types.js.map +1 -1
  43. package/package.json +1 -1
@@ -18,6 +18,7 @@ const logging_1 = require("./logging");
18
18
  * Default chunk size for attachments: 256KB
19
19
  */
20
20
  const DEFAULT_CHUNK_SIZE_BYTES = 256 * 1024;
21
+ const DEFAULT_ATTACHMENT_STREAM_BATCH_SIZE = 4;
21
22
  const DEFAULT_SNAPSHOT_MIN_CHANGES = 100;
22
23
  const DEFAULT_SNAPSHOT_COOLDOWN_MS = 10 * 60 * 1000;
23
24
  const DEFAULT_MAX_CACHED_DOCS = 128;
@@ -778,6 +779,98 @@ class BaseMindooDB {
778
779
  }
779
780
  return missingIds;
780
781
  }
782
+ /**
783
+ * Determine how many entry IDs to fetch per `getEntries` call during sync.
784
+ *
785
+ * This is intentionally separate from the metadata scan `pageSize` so that
786
+ * scanning can page through large ID lists quickly while the heavier
787
+ * payload downloads use a smaller batch to keep progress responsive and
788
+ * cancellation timely.
789
+ *
790
+ * Priority: explicit option > attachment default (100) > pageSize fallback.
791
+ */
792
+ resolveTransferBatchSize(options) {
793
+ if (options?.transferBatchSize && options.transferBatchSize > 0) {
794
+ return options.transferBatchSize;
795
+ }
796
+ if (options?.storeKind === types_1.StoreKind.attachments) {
797
+ return 100;
798
+ }
799
+ return options?.pageSize ?? 1000;
800
+ }
801
+ /**
802
+ * Transfer a set of entry IDs from source to target in fixed-size batches,
803
+ * emitting progress and checking for cancellation between each batch.
804
+ *
805
+ * Callers (cursor-scan path and legacy path) collect the IDs that need
806
+ * transferring, then delegate to this method instead of issuing one
807
+ * monolithic `getEntries`. This gives three benefits:
808
+ *
809
+ * 1. The UI receives frequent progress updates with batch metadata so long
810
+ * transfers no longer look frozen.
811
+ * 2. Cancellation can interrupt work between batches rather than waiting
812
+ * for one large HTTP response to complete.
813
+ * 3. Each server-side `getEntries` + RSA encryption unit is smaller,
814
+ * reducing the risk of socket timeouts on large payloads.
815
+ *
816
+ * Returns partial progress on cancellation so callers can report how much
817
+ * was actually transferred before the abort.
818
+ */
819
+ async transferEntriesInBatches(sourceStore, targetStore, entryIds, options, state) {
820
+ if (entryIds.length === 0) {
821
+ return { transferred: state.transferred, cancelled: false };
822
+ }
823
+ const onProgress = options?.onProgress;
824
+ const signal = options?.signal;
825
+ const transferBatchSize = this.resolveTransferBatchSize(options);
826
+ const totalTransferBatches = Math.max(1, Math.ceil(entryIds.length / transferBatchSize));
827
+ let transferred = state.transferred;
828
+ for (let offset = 0; offset < entryIds.length; offset += transferBatchSize) {
829
+ if (signal?.aborted) {
830
+ return { transferred, cancelled: true };
831
+ }
832
+ const currentTransferBatch = Math.floor(offset / transferBatchSize) + 1;
833
+ const batchIds = entryIds.slice(offset, offset + transferBatchSize);
834
+ const pageSummary = state.currentPage ? `page ${state.currentPage}, ` : "";
835
+ onProgress?.({
836
+ phase: "transferring",
837
+ message: `Transferring batch ${currentTransferBatch}/${totalTransferBatches} (${batchIds.length} entries, ${pageSummary}scanned ${state.scanned})...`,
838
+ transferredEntries: transferred,
839
+ scannedEntries: state.scanned,
840
+ totalSourceEntries: state.totalSourceEntries,
841
+ currentPage: state.currentPage,
842
+ currentTransferBatch,
843
+ totalTransferBatches,
844
+ transferBatchSize,
845
+ });
846
+ try {
847
+ const batchEntries = await sourceStore.getEntries(batchIds);
848
+ if (signal?.aborted) {
849
+ return { transferred, cancelled: true };
850
+ }
851
+ await targetStore.putEntries(batchEntries);
852
+ transferred += batchEntries.length;
853
+ }
854
+ catch (error) {
855
+ if (signal?.aborted) {
856
+ return { transferred, cancelled: true };
857
+ }
858
+ throw error;
859
+ }
860
+ onProgress?.({
861
+ phase: "transferring",
862
+ message: `Transferred ${transferred} entries after batch ${currentTransferBatch}/${totalTransferBatches}`,
863
+ transferredEntries: transferred,
864
+ scannedEntries: state.scanned,
865
+ totalSourceEntries: state.totalSourceEntries,
866
+ currentPage: state.currentPage,
867
+ currentTransferBatch,
868
+ totalTransferBatches,
869
+ transferBatchSize,
870
+ });
871
+ }
872
+ return { transferred, cancelled: false };
873
+ }
781
874
  async syncEntriesFromStore(sourceStore, targetStore, options) {
782
875
  const signal = options?.signal;
783
876
  BaseMindooDB.setSyncAbortSignalOnStore(sourceStore, signal);
@@ -843,20 +936,16 @@ class BaseMindooDB {
843
936
  return { transferred, scanned, cancelled: true };
844
937
  }
845
938
  if (missingIds.length > 0) {
846
- onProgress?.({
847
- phase: 'transferring',
848
- message: `Transferring ${missingIds.length} entries (page ${currentPage}, scanned ${scanned})...`,
849
- transferredEntries: transferred,
850
- scannedEntries: scanned,
939
+ const transferResult = await this.transferEntriesInBatches(sourceStore, targetStore, missingIds, options, {
940
+ transferred,
941
+ scanned,
851
942
  totalSourceEntries: totalSourceEstimate,
852
943
  currentPage,
853
944
  });
854
- const missingEntries = await sourceStore.getEntries(missingIds);
855
- if (signal?.aborted) {
945
+ transferred = transferResult.transferred;
946
+ if (transferResult.cancelled) {
856
947
  return { transferred, scanned, cancelled: true };
857
948
  }
858
- await targetStore.putEntries(missingEntries);
859
- transferred += missingEntries.length;
860
949
  }
861
950
  }
862
951
  onProgress?.({
@@ -891,21 +980,28 @@ class BaseMindooDB {
891
980
  onProgress?.({
892
981
  phase: 'transferring',
893
982
  message: `Transferring ${sourceNewMetadata.length} entries...`,
894
- transferredEntries: 0,
983
+ transferredEntries: transferred,
895
984
  scannedEntries: sourceNewMetadata.length,
896
985
  totalSourceEntries: sourceNewMetadata.length,
897
986
  });
898
- const sourceEntries = await sourceStore.getEntries(sourceNewMetadata.map((m) => m.id));
899
- await targetStore.putEntries(sourceEntries);
987
+ const transferResult = await this.transferEntriesInBatches(sourceStore, targetStore, sourceNewMetadata.map((m) => m.id), options, {
988
+ transferred,
989
+ scanned: sourceNewMetadata.length,
990
+ totalSourceEntries: sourceNewMetadata.length,
991
+ });
992
+ transferred = transferResult.transferred;
993
+ if (transferResult.cancelled) {
994
+ return { transferred, scanned: sourceNewMetadata.length, cancelled: true };
995
+ }
900
996
  onProgress?.({
901
997
  phase: 'transferring',
902
- message: `Transferred ${sourceEntries.length} entries`,
903
- transferredEntries: sourceEntries.length,
998
+ message: `Transferred ${transferred} entries`,
999
+ transferredEntries: transferred,
904
1000
  scannedEntries: sourceNewMetadata.length,
905
1001
  totalSourceEntries: sourceNewMetadata.length,
906
1002
  });
907
1003
  return {
908
- transferred: sourceEntries.length,
1004
+ transferred,
909
1005
  scanned: sourceNewMetadata.length,
910
1006
  cancelled: false,
911
1007
  };
@@ -3199,10 +3295,10 @@ class BaseMindooDB {
3199
3295
  /**
3200
3296
  * Get an attachment reference by ID from a document's _attachments array.
3201
3297
  */
3202
- getAttachmentRefInternal(docId, attachmentId) {
3203
- const internalDoc = this.getCachedDocument(docId);
3298
+ async getAttachmentRefInternal(docId, attachmentId) {
3299
+ const internalDoc = this.getCachedDocument(docId) ?? await this.loadDocumentInternal(docId);
3204
3300
  if (!internalDoc) {
3205
- throw new Error(`Document ${docId} not found in cache`);
3301
+ throw new Error(`Document ${docId} not found while reading attachment ${attachmentId}`);
3206
3302
  }
3207
3303
  const payload = internalDoc.doc;
3208
3304
  const attachments = payload._attachments || [];
@@ -3250,7 +3346,7 @@ class BaseMindooDB {
3250
3346
  */
3251
3347
  async getAttachmentInternal(docId, attachmentId) {
3252
3348
  this.logger.debug(`Getting attachment ${attachmentId} from document ${docId}`);
3253
- const ref = this.getAttachmentRefInternal(docId, attachmentId);
3349
+ const ref = await this.getAttachmentRefInternal(docId, attachmentId);
3254
3350
  const store = this.getEffectiveAttachmentStore();
3255
3351
  // Resolve dependency chain to get all chunk IDs in order (oldest first)
3256
3352
  const chunkIds = await store.resolveDependencies(ref.lastChunkId, { includeStart: true });
@@ -3283,7 +3379,7 @@ class BaseMindooDB {
3283
3379
  if (startByte < 0 || endByte <= startByte) {
3284
3380
  throw new Error(`Invalid byte range: [${startByte}, ${endByte})`);
3285
3381
  }
3286
- const ref = this.getAttachmentRefInternal(docId, attachmentId);
3382
+ const ref = await this.getAttachmentRefInternal(docId, attachmentId);
3287
3383
  const store = this.getEffectiveAttachmentStore();
3288
3384
  const readPlan = await this.planAttachmentRead(store, ref, startByte, endByte);
3289
3385
  const neededChunkIds = readPlan.chunkPlans.map((chunkPlan) => chunkPlan.id);
@@ -3315,7 +3411,7 @@ class BaseMindooDB {
3315
3411
  */
3316
3412
  async *streamAttachmentInternal(docId, attachmentId, startOffset) {
3317
3413
  this.logger.debug(`Streaming attachment ${attachmentId} from offset ${startOffset}`);
3318
- const ref = this.getAttachmentRefInternal(docId, attachmentId);
3414
+ const ref = await this.getAttachmentRefInternal(docId, attachmentId);
3319
3415
  if (startOffset < 0 || startOffset > ref.size) {
3320
3416
  throw new Error(`Invalid stream offset ${startOffset} for attachment size ${ref.size}`);
3321
3417
  }
@@ -3324,20 +3420,25 @@ class BaseMindooDB {
3324
3420
  }
3325
3421
  const store = this.getEffectiveAttachmentStore();
3326
3422
  const readPlan = await this.planAttachmentRead(store, ref, startOffset, ref.size);
3327
- // Stream chunks starting from startChunkIndex
3328
- for (let i = 0; i < readPlan.chunkPlans.length; i++) {
3329
- const chunkPlan = readPlan.chunkPlans[i];
3330
- const [chunk] = await store.getEntries([chunkPlan.id]);
3331
- if (!chunk) {
3332
- throw new Error(`Attachment chunk ${chunkPlan.id} not found in store`);
3333
- }
3334
- const plaintext = await this.decryptAttachmentChunk(chunk);
3335
- // For first chunk, skip bytes before startOffset
3336
- if (i === 0 && readPlan.offsetInFirstChunk > 0) {
3337
- yield plaintext.slice(readPlan.offsetInFirstChunk);
3338
- }
3339
- else {
3340
- yield plaintext;
3423
+ for (let i = 0; i < readPlan.chunkPlans.length; i += DEFAULT_ATTACHMENT_STREAM_BATCH_SIZE) {
3424
+ const chunkPlansBatch = readPlan.chunkPlans.slice(i, i + DEFAULT_ATTACHMENT_STREAM_BATCH_SIZE);
3425
+ const batchIds = chunkPlansBatch.map((chunkPlan) => chunkPlan.id);
3426
+ const chunks = await store.getEntries(batchIds);
3427
+ const chunkById = new Map(chunks.map((chunk) => [chunk.id, chunk]));
3428
+ for (let batchIndex = 0; batchIndex < chunkPlansBatch.length; batchIndex++) {
3429
+ const chunkPlan = chunkPlansBatch[batchIndex];
3430
+ const chunk = chunkById.get(chunkPlan.id);
3431
+ if (!chunk) {
3432
+ throw new Error(`Attachment chunk ${chunkPlan.id} not found in store. The document metadata exists, but the attachment payload may not be synced locally yet.`);
3433
+ }
3434
+ const plaintext = await this.decryptAttachmentChunk(chunk);
3435
+ // For first chunk, skip bytes before startOffset
3436
+ if (i === 0 && batchIndex === 0 && readPlan.offsetInFirstChunk > 0) {
3437
+ yield plaintext.slice(readPlan.offsetInFirstChunk);
3438
+ }
3439
+ else {
3440
+ yield plaintext;
3441
+ }
3341
3442
  }
3342
3443
  }
3343
3444
  this.logger.debug(`Finished streaming attachment ${attachmentId}`);
@@ -3643,19 +3744,35 @@ class BaseMindooDB {
3643
3744
  phase: 'processing',
3644
3745
  message: `Processing ${syncResult.transferred} new entries...`,
3645
3746
  transferredEntries: syncResult.transferred,
3646
- scannedEntries: syncResult.transferred,
3747
+ scannedEntries: syncResult.scanned,
3647
3748
  });
3648
3749
  // Sync the local store to process the new entries
3649
3750
  // This will update the index, cache, and processedEntryIds
3751
+ if (options?.signal?.aborted) {
3752
+ this.logger.info(`Pull cancelled before local processing after transferring ${syncResult.transferred} entries`);
3753
+ return {
3754
+ transferredEntries: syncResult.transferred,
3755
+ scannedEntries: syncResult.scanned,
3756
+ cancelled: true,
3757
+ };
3758
+ }
3650
3759
  if (storeKind === types_1.StoreKind.docs) {
3651
3760
  await this.syncStoreChanges();
3652
3761
  }
3762
+ if (options?.signal?.aborted) {
3763
+ this.logger.info(`Pull cancelled after local processing for ${storeKind} entries`);
3764
+ return {
3765
+ transferredEntries: syncResult.transferred,
3766
+ scannedEntries: syncResult.scanned,
3767
+ cancelled: true,
3768
+ };
3769
+ }
3653
3770
  this.logger.info(`Pull complete, synced ${syncResult.transferred} entries`);
3654
3771
  options?.onProgress?.({
3655
3772
  phase: 'complete',
3656
3773
  message: `Pull complete: ${syncResult.transferred} ${storeKind} entries synced`,
3657
3774
  transferredEntries: syncResult.transferred,
3658
- scannedEntries: syncResult.transferred,
3775
+ scannedEntries: syncResult.scanned,
3659
3776
  });
3660
3777
  return {
3661
3778
  transferredEntries: syncResult.transferred,
@@ -3712,7 +3829,7 @@ class BaseMindooDB {
3712
3829
  phase: 'complete',
3713
3830
  message: `Push complete: ${syncResult.transferred} entries transferred`,
3714
3831
  transferredEntries: syncResult.transferred,
3715
- scannedEntries: syncResult.transferred,
3832
+ scannedEntries: syncResult.scanned,
3716
3833
  });
3717
3834
  return {
3718
3835
  transferredEntries: syncResult.transferred,