document-dataply 0.0.14-alpha.1 → 0.0.14-alpha.2

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/cjs/index.js CHANGED
@@ -82,6 +82,8 @@ var require_cjs = __commonJS({
82
82
  IndexPageManager: () => IndexPageManager,
83
83
  InvertedWeakMap: () => InvertedWeakMap,
84
84
  LRUMap: () => LRUMap2,
85
+ Logger: () => Logger2,
86
+ LoggerManager: () => LoggerManager,
85
87
  MVCCStrategy: () => MVCCStrategy2,
86
88
  MVCCTransaction: () => MVCCTransaction2,
87
89
  MetadataPageManager: () => MetadataPageManager,
@@ -476,11 +478,7 @@ var require_cjs = __commonJS({
476
478
  */
477
479
  rollback() {
478
480
  const { created, updated, deleted } = this.getResultEntries();
479
- this.writeBuffer.clear();
480
- this.deleteBuffer.clear();
481
- this.createdKeys.clear();
482
- this.deletedValues.clear();
483
- this.originallyExisted.clear();
481
+ this._cleanupAll();
484
482
  this.committed = true;
485
483
  if (this.root !== this) {
486
484
  this.root.activeTransactions.delete(this);
@@ -516,6 +514,19 @@ var require_cjs = __commonJS({
516
514
  }
517
515
  return Array.from(conflicts);
518
516
  }
517
+ /**
518
+ * Cleans up all buffers and history.
519
+ * This method is called by the commit method.
520
+ */
521
+ _cleanupAll() {
522
+ this.writeBuffer.clear();
523
+ this.deleteBuffer.clear();
524
+ this.createdKeys.clear();
525
+ this.deletedValues.clear();
526
+ this.originallyExisted.clear();
527
+ this.keyVersions.clear();
528
+ this.bufferHistory.clear();
529
+ }
519
530
  /**
520
531
  * Cleans up both deletedCache and versionIndex based on minActiveVersion.
521
532
  * Root transactions call this after commit to reclaim memory.
@@ -707,6 +718,7 @@ var require_cjs = __commonJS({
707
718
  if (this.parent) {
708
719
  const failure = this.parent._merge(this);
709
720
  if (failure) {
721
+ this.rollback();
710
722
  return {
711
723
  label,
712
724
  success: false,
@@ -717,11 +729,13 @@ var require_cjs = __commonJS({
717
729
  deleted
718
730
  };
719
731
  }
732
+ this._cleanupAll();
720
733
  this.committed = true;
721
734
  } else {
722
735
  if (this.writeBuffer.size > 0 || this.deleteBuffer.size > 0) {
723
736
  const failure = this._merge(this);
724
737
  if (failure) {
738
+ this.rollback();
725
739
  return {
726
740
  label,
727
741
  success: false,
@@ -732,13 +746,7 @@ var require_cjs = __commonJS({
732
746
  deleted: []
733
747
  };
734
748
  }
735
- this.writeBuffer.clear();
736
- this.deleteBuffer.clear();
737
- this.createdKeys.clear();
738
- this.deletedValues.clear();
739
- this.originallyExisted.clear();
740
- this.keyVersions.clear();
741
- this.bufferHistory.clear();
749
+ this._cleanupAll();
742
750
  this.localVersion = 0;
743
751
  this.snapshotVersion = this.version;
744
752
  }
@@ -1372,6 +1380,7 @@ var require_cjs = __commonJS({
1372
1380
  if (this.parent) {
1373
1381
  const failure = await this.parent._merge(this);
1374
1382
  if (failure) {
1383
+ this.rollback();
1375
1384
  return {
1376
1385
  label,
1377
1386
  success: false,
@@ -1382,11 +1391,13 @@ var require_cjs = __commonJS({
1382
1391
  deleted
1383
1392
  };
1384
1393
  }
1394
+ this._cleanupAll();
1385
1395
  this.committed = true;
1386
1396
  } else {
1387
1397
  if (this.writeBuffer.size > 0 || this.deleteBuffer.size > 0) {
1388
1398
  const failure = await this._merge(this);
1389
1399
  if (failure) {
1400
+ this.rollback();
1390
1401
  return {
1391
1402
  label,
1392
1403
  success: false,
@@ -1397,13 +1408,7 @@ var require_cjs = __commonJS({
1397
1408
  deleted: []
1398
1409
  };
1399
1410
  }
1400
- this.writeBuffer.clear();
1401
- this.deleteBuffer.clear();
1402
- this.createdKeys.clear();
1403
- this.deletedValues.clear();
1404
- this.originallyExisted.clear();
1405
- this.keyVersions.clear();
1406
- this.bufferHistory.clear();
1411
+ this._cleanupAll();
1407
1412
  this.localVersion = 0;
1408
1413
  this.snapshotVersion = this.version;
1409
1414
  }
@@ -2958,8 +2963,12 @@ var require_cjs = __commonJS({
2958
2963
  result = this.rootTx.commit(label);
2959
2964
  if (result.success) {
2960
2965
  this.rootTx.rootId = this.rootId;
2966
+ } else {
2967
+ this.mvcc.rollback();
2961
2968
  }
2962
2969
  }
2970
+ } else {
2971
+ this.mvcc.rollback();
2963
2972
  }
2964
2973
  return result;
2965
2974
  }
@@ -4160,8 +4169,12 @@ var require_cjs = __commonJS({
4160
4169
  result = await this.rootTx.commit(label);
4161
4170
  if (result.success) {
4162
4171
  this.rootTx.rootId = this.rootId;
4172
+ } else {
4173
+ this.mvcc.rollback();
4163
4174
  }
4164
4175
  }
4176
+ } else {
4177
+ this.mvcc.rollback();
4165
4178
  }
4166
4179
  return result;
4167
4180
  }
@@ -5426,11 +5439,7 @@ var require_cjs = __commonJS({
5426
5439
  */
5427
5440
  rollback() {
5428
5441
  const { created, updated, deleted } = this.getResultEntries();
5429
- this.writeBuffer.clear();
5430
- this.deleteBuffer.clear();
5431
- this.createdKeys.clear();
5432
- this.deletedValues.clear();
5433
- this.originallyExisted.clear();
5442
+ this._cleanupAll();
5434
5443
  this.committed = true;
5435
5444
  if (this.root !== this) {
5436
5445
  this.root.activeTransactions.delete(this);
@@ -5466,6 +5475,19 @@ var require_cjs = __commonJS({
5466
5475
  }
5467
5476
  return Array.from(conflicts);
5468
5477
  }
5478
+ /**
5479
+ * Cleans up all buffers and history.
5480
+ * This method is called by the commit method.
5481
+ */
5482
+ _cleanupAll() {
5483
+ this.writeBuffer.clear();
5484
+ this.deleteBuffer.clear();
5485
+ this.createdKeys.clear();
5486
+ this.deletedValues.clear();
5487
+ this.originallyExisted.clear();
5488
+ this.keyVersions.clear();
5489
+ this.bufferHistory.clear();
5490
+ }
5469
5491
  /**
5470
5492
  * Cleans up both deletedCache and versionIndex based on minActiveVersion.
5471
5493
  * Root transactions call this after commit to reclaim memory.
@@ -5657,6 +5679,7 @@ var require_cjs = __commonJS({
5657
5679
  if (this.parent) {
5658
5680
  const failure = this.parent._merge(this);
5659
5681
  if (failure) {
5682
+ this.rollback();
5660
5683
  return {
5661
5684
  label,
5662
5685
  success: false,
@@ -5667,11 +5690,13 @@ var require_cjs = __commonJS({
5667
5690
  deleted
5668
5691
  };
5669
5692
  }
5693
+ this._cleanupAll();
5670
5694
  this.committed = true;
5671
5695
  } else {
5672
5696
  if (this.writeBuffer.size > 0 || this.deleteBuffer.size > 0) {
5673
5697
  const failure = this._merge(this);
5674
5698
  if (failure) {
5699
+ this.rollback();
5675
5700
  return {
5676
5701
  label,
5677
5702
  success: false,
@@ -5682,13 +5707,7 @@ var require_cjs = __commonJS({
5682
5707
  deleted: []
5683
5708
  };
5684
5709
  }
5685
- this.writeBuffer.clear();
5686
- this.deleteBuffer.clear();
5687
- this.createdKeys.clear();
5688
- this.deletedValues.clear();
5689
- this.originallyExisted.clear();
5690
- this.keyVersions.clear();
5691
- this.bufferHistory.clear();
5710
+ this._cleanupAll();
5692
5711
  this.localVersion = 0;
5693
5712
  this.snapshotVersion = this.version;
5694
5713
  }
@@ -6322,6 +6341,7 @@ var require_cjs = __commonJS({
6322
6341
  if (this.parent) {
6323
6342
  const failure = await this.parent._merge(this);
6324
6343
  if (failure) {
6344
+ this.rollback();
6325
6345
  return {
6326
6346
  label,
6327
6347
  success: false,
@@ -6332,11 +6352,13 @@ var require_cjs = __commonJS({
6332
6352
  deleted
6333
6353
  };
6334
6354
  }
6355
+ this._cleanupAll();
6335
6356
  this.committed = true;
6336
6357
  } else {
6337
6358
  if (this.writeBuffer.size > 0 || this.deleteBuffer.size > 0) {
6338
6359
  const failure = await this._merge(this);
6339
6360
  if (failure) {
6361
+ this.rollback();
6340
6362
  return {
6341
6363
  label,
6342
6364
  success: false,
@@ -6347,13 +6369,7 @@ var require_cjs = __commonJS({
6347
6369
  deleted: []
6348
6370
  };
6349
6371
  }
6350
- this.writeBuffer.clear();
6351
- this.deleteBuffer.clear();
6352
- this.createdKeys.clear();
6353
- this.deletedValues.clear();
6354
- this.originallyExisted.clear();
6355
- this.keyVersions.clear();
6356
- this.bufferHistory.clear();
6372
+ this._cleanupAll();
6357
6373
  this.localVersion = 0;
6358
6374
  this.snapshotVersion = this.version;
6359
6375
  }
@@ -9967,10 +9983,10 @@ var require_cjs = __commonJS({
9967
9983
  if (shouldTriggerCheckpoint) {
9968
9984
  await this.pfs.checkpoint();
9969
9985
  }
9986
+ } finally {
9970
9987
  this.dirtyPages.clear();
9971
9988
  this.undoPages.clear();
9972
9989
  this.releaseAllLocks();
9973
- } finally {
9974
9990
  if (this._writeLockRelease) {
9975
9991
  this._writeLockRelease();
9976
9992
  this._writeLockRelease = null;
@@ -10053,10 +10069,10 @@ var require_cjs = __commonJS({
10053
10069
  return level >= this.level;
10054
10070
  }
10055
10071
  create(moduleName) {
10056
- return new Logger(this, moduleName);
10072
+ return new Logger2(this, moduleName);
10057
10073
  }
10058
10074
  };
10059
- var Logger = class {
10075
+ var Logger2 = class {
10060
10076
  constructor(parent, moduleName) {
10061
10077
  this.parent = parent;
10062
10078
  this.moduleName = moduleName;
@@ -11699,6 +11715,71 @@ var DocumentSerializeStrategyAsync = class extends import_dataply2.SerializeStra
11699
11715
  }
11700
11716
  };
11701
11717
 
11718
+ // src/utils/eventLoopManager.ts
11719
+ function yieldEventLoop() {
11720
+ return new Promise(setImmediate);
11721
+ }
11722
+ var DeadlineChunker = class {
11723
+ /**
11724
+ * 이벤트 루프를 막을 최대 허용 시간
11725
+ */
11726
+ targetMs;
11727
+ /**
11728
+ * 현재 chunk size
11729
+ */
11730
+ chunkSize;
11731
+ /**
11732
+ * Exponential Weighted Moving Average
11733
+ */
11734
+ ewmaMs;
11735
+ /**
11736
+ * EWMA 평활화 계수
11737
+ */
11738
+ alpha;
11739
+ constructor(startChunkSize = 0, targetMs = 5, alpha = 0.5) {
11740
+ this.chunkSize = startChunkSize;
11741
+ this.targetMs = targetMs;
11742
+ this.alpha = alpha;
11743
+ this.ewmaMs = null;
11744
+ }
11745
+ /**
11746
+ * EWMA 평활화 계수를 사용하여 평균 처리 시간을 업데이트합니다.
11747
+ */
11748
+ updateEstimate(elapsed, count) {
11749
+ const msPerItem = elapsed / count;
11750
+ this.ewmaMs = this.ewmaMs === null ? msPerItem : this.alpha * msPerItem + (1 - this.alpha) * this.ewmaMs;
11751
+ }
11752
+ /**
11753
+ * 현재 chunk size를 업데이트합니다.
11754
+ */
11755
+ nextChunkSize() {
11756
+ if (!this.ewmaMs || this.ewmaMs === 0) return this.chunkSize;
11757
+ const next = Math.floor(this.targetMs / this.ewmaMs);
11758
+ return Math.max(1, next);
11759
+ }
11760
+ /**
11761
+ * 주어진 items를 chunk로 분할하여 처리합니다.
11762
+ */
11763
+ async processInChunks(items, processFn) {
11764
+ let i = 0;
11765
+ let len = items.length;
11766
+ if (this.chunkSize === 0) {
11767
+ this.chunkSize = Math.floor(items.length / 100 * 5);
11768
+ }
11769
+ while (i < len) {
11770
+ const chunk = items.slice(i, i + this.chunkSize);
11771
+ const count = chunk.length;
11772
+ const start = performance.now();
11773
+ await processFn(chunk);
11774
+ const elapsed = performance.now() - start;
11775
+ this.updateEstimate(elapsed, count);
11776
+ this.chunkSize = this.nextChunkSize();
11777
+ i += count;
11778
+ await yieldEventLoop();
11779
+ }
11780
+ }
11781
+ };
11782
+
11702
11783
  // src/core/IndexManager.ts
11703
11784
  var IndexManager = class {
11704
11785
  constructor(api, logger) {
@@ -11878,7 +11959,7 @@ var IndexManager = class {
11878
11959
  this.indices = metadata.indices;
11879
11960
  this.registeredIndices.delete(name);
11880
11961
  const fields = this.getFieldsFromConfig(config);
11881
- for (let i = 0; i < fields.length; i++) {
11962
+ for (let i = 0, len = fields.length; i < len; i++) {
11882
11963
  const field = fields[i];
11883
11964
  const indexNames = this.fieldToIndices.get(field);
11884
11965
  if (indexNames) {
@@ -11972,6 +12053,7 @@ var IndexManager = class {
11972
12053
  await treeTx.rollback();
11973
12054
  throw err;
11974
12055
  }
12056
+ await yieldEventLoop();
11975
12057
  }
11976
12058
  this.pendingBackfillFields = [];
11977
12059
  return backfilledCount;
@@ -12043,7 +12125,9 @@ var IndexManager = class {
12043
12125
  indexName
12044
12126
  ),
12045
12127
  this.api.comparator,
12046
- { capacity: perIndexCapacity }
12128
+ {
12129
+ capacity: perIndexCapacity
12130
+ }
12047
12131
  );
12048
12132
  await tree.init();
12049
12133
  this.trees.set(indexName, tree);
@@ -12061,6 +12145,7 @@ var IndexManager = class {
12061
12145
  await treeTx.rollback();
12062
12146
  throw err;
12063
12147
  }
12148
+ await yieldEventLoop();
12064
12149
  }
12065
12150
  }
12066
12151
  return docCount;
@@ -12171,77 +12256,15 @@ async function catchPromise(promise) {
12171
12256
  return promise.then((res) => [void 0, res]).catch((reason) => [reason]);
12172
12257
  }
12173
12258
 
12174
- // src/utils/DeadlineChunker.ts
12175
- var DeadlineChunker = class {
12176
- /**
12177
- * 이벤트 루프를 막을 최대 허용 시간
12178
- */
12179
- targetMs;
12180
- /**
12181
- * 현재 chunk size
12182
- */
12183
- chunkSize;
12184
- /**
12185
- * Exponential Weighted Moving Average
12186
- */
12187
- ewmaMs;
12188
- /**
12189
- * EWMA 평활화 계수
12190
- */
12191
- alpha;
12192
- constructor(startChunkSize = 0, targetMs = 5, alpha = 0.5) {
12193
- this.chunkSize = startChunkSize;
12194
- this.targetMs = targetMs;
12195
- this.alpha = alpha;
12196
- this.ewmaMs = null;
12197
- }
12198
- /**
12199
- * EWMA 평활화 계수를 사용하여 평균 처리 시간을 업데이트합니다.
12200
- */
12201
- updateEstimate(elapsed, count) {
12202
- const msPerItem = elapsed / count;
12203
- this.ewmaMs = this.ewmaMs === null ? msPerItem : this.alpha * msPerItem + (1 - this.alpha) * this.ewmaMs;
12204
- }
12205
- /**
12206
- * 현재 chunk size를 업데이트합니다.
12207
- */
12208
- nextChunkSize() {
12209
- if (!this.ewmaMs || this.ewmaMs === 0) return this.chunkSize;
12210
- const next = Math.floor(this.targetMs / this.ewmaMs);
12211
- return Math.max(1, next);
12212
- }
12213
- /**
12214
- * 주어진 items를 chunk로 분할하여 처리합니다.
12215
- */
12216
- async processInChunks(items, processFn) {
12217
- let i = 0;
12218
- let len = items.length;
12219
- if (this.chunkSize === 0) {
12220
- this.chunkSize = Math.floor(items.length / 100 * 5);
12221
- }
12222
- while (i < len) {
12223
- const chunk = items.slice(i, i + this.chunkSize);
12224
- const count = chunk.length;
12225
- const start = performance.now();
12226
- await processFn(chunk);
12227
- const elapsed = performance.now() - start;
12228
- this.updateEstimate(elapsed, count);
12229
- this.chunkSize = this.nextChunkSize();
12230
- i += count;
12231
- await new Promise(setImmediate);
12232
- }
12233
- }
12234
- };
12235
-
12236
12259
  // src/core/MutationManager.ts
12237
12260
  var MutationManager = class {
12238
12261
  constructor(api, logger) {
12239
12262
  this.api = api;
12240
12263
  this.logger = logger;
12241
12264
  }
12242
- async isTreeEmpty(tree) {
12265
+ async isTreeEmpty(tree, tx) {
12243
12266
  try {
12244
- const root = await tree.getNode(tree.rootId);
12267
+ const root = await tree.getNode(tree.rootId, tx);
12245
12268
  return root.leaf && root.values.length === 0;
12246
12269
  } catch {
12247
12270
  return true;
@@ -12288,16 +12311,19 @@ var MutationManager = class {
12288
12311
  }
12289
12312
  }
12290
12313
  await this.api.analysisManager.notifyInsert([flattenDocument], tx2);
12314
+ this.logger.debug(`Inserted single document with ID: ${dataplyDocument._id}`);
12291
12315
  return dataplyDocument._id;
12292
12316
  }, tx);
12293
12317
  }
12294
12318
  async insertBatchDocuments(documents, tx) {
12295
12319
  return this.api.runWithDefaultWrite(async (tx2) => {
12320
+ this.logger.debug(`Batch inserting ${documents.length} documents`);
12296
12321
  const metadata = await this.api.getDocumentInnerMetadata(tx2);
12297
- const startId = metadata.lastId + 1;
12322
+ let startId = metadata.lastId + 1;
12298
12323
  metadata.lastId += documents.length;
12299
12324
  await this.api.updateDocumentInnerMetadata(metadata, tx2);
12300
- const ids = [];
12325
+ const ids = new Float64Array(documents.length);
12326
+ const pks = new Float64Array(documents.length);
12301
12327
  const dataplyDocuments = [];
12302
12328
  const flattenedData = [];
12303
12329
  for (let i = 0, len = documents.length; i < len; i++) {
@@ -12309,17 +12335,15 @@ var MutationManager = class {
12309
12335
  dataplyDocuments.push(stringified);
12310
12336
  const flattenDocument = this.api.flattenDocument(dataplyDocument);
12311
12337
  flattenedData.push({ pk: -1, data: flattenDocument });
12312
- ids.push(id);
12338
+ ids[i] = id;
12313
12339
  }
12314
- const pks = [];
12315
- const documentChunker = new DeadlineChunker(1e4);
12316
- await documentChunker.processInChunks(dataplyDocuments, async (chunk) => {
12317
- const res = await this.api.insertBatch(chunk, true, tx2);
12318
- pks.push(...res);
12319
- });
12320
- for (let i = 0, len = pks.length; i < len; i++) {
12321
- flattenedData[i].pk = pks[i];
12340
+ const res = await this.api.insertBatch(dataplyDocuments, true, tx2);
12341
+ for (let i = 0, len = res.length; i < len; i++) {
12342
+ const index = i;
12343
+ pks[index] = res[i];
12344
+ flattenedData[index].pk = res[i];
12322
12345
  }
12346
+ await yieldEventLoop();
12323
12347
  for (const [indexName, config] of this.api.indexManager.registeredIndices) {
12324
12348
  const tree = this.api.trees.get(indexName);
12325
12349
  if (!tree) continue;
@@ -12346,42 +12370,39 @@ var MutationManager = class {
12346
12370
  batchInsertData.push([item.pk, { k: item.pk, v: indexVal }]);
12347
12371
  }
12348
12372
  }
12349
- const isEmptyTree = await this.isTreeEmpty(tree);
12373
+ const isEmptyTree = await this.isTreeEmpty(tree, tx2);
12350
12374
  if (isEmptyTree) {
12351
12375
  const [error] = await catchPromise(treeTx.bulkLoad(batchInsertData));
12352
12376
  if (error) {
12353
12377
  throw error;
12354
12378
  }
12355
12379
  } else {
12356
- const initMaxSize = 5e4;
12357
- const initChunkSize = Math.min(
12358
- initMaxSize,
12359
- Math.max(initMaxSize, Math.floor(batchInsertData.length / 100 * 5))
12360
- );
12361
- const chunker = new DeadlineChunker(initChunkSize);
12362
- await chunker.processInChunks(batchInsertData, async (chunk) => {
12363
- const [error] = await catchPromise(treeTx.batchInsert(chunk));
12364
- if (error) {
12365
- throw error;
12366
- }
12367
- });
12380
+ const [error] = await catchPromise(treeTx.batchInsert(batchInsertData));
12381
+ if (error) {
12382
+ throw error;
12383
+ }
12368
12384
  }
12369
- const res = await treeTx.commit();
12370
- if (!res.success) {
12385
+ const res2 = await treeTx.commit();
12386
+ if (!res2.success) {
12371
12387
  await treeTx.rollback();
12372
- throw res.error;
12388
+ this.logger.error(`Failed to commit batch insert for index ${indexName}: ${res2.error}`);
12389
+ throw res2.error;
12373
12390
  }
12391
+ await yieldEventLoop();
12374
12392
  }
12375
12393
  const flatDocs = [];
12376
12394
  for (let i = 0, len = flattenedData.length; i < len; i++) {
12377
12395
  flatDocs.push(flattenedData[i].data);
12378
12396
  }
12379
12397
  await this.api.analysisManager.notifyInsert(flatDocs, tx2);
12380
- return ids;
12398
+ await yieldEventLoop();
12399
+ this.logger.debug(`Successfully batch inserted ${documents.length} documents`);
12400
+ return Array.from(ids);
12381
12401
  }, tx);
12382
12402
  }
12383
12403
  async updateInternal(query, computeUpdatedDoc, tx) {
12384
12404
  const pks = await this.api.queryManager.getKeys(query);
12405
+ this.logger.debug(`Found ${pks.length} documents to update`);
12385
12406
  let updatedCount = 0;
12386
12407
  const updatePairs = [];
12387
12408
  const treeTxs = /* @__PURE__ */ new Map();
@@ -12430,9 +12451,11 @@ var MutationManager = class {
12430
12451
  await treeTx.batchInsert([[pk, { k: pk, v: newIndexVal }]]);
12431
12452
  }
12432
12453
  }
12454
+ await yieldEventLoop();
12433
12455
  }
12434
12456
  updatePairs.push({ oldDocument: oldFlatDoc, newDocument: newFlatDoc });
12435
12457
  await this.api.update(pk, JSON.stringify(updatedDoc), tx);
12458
+ await yieldEventLoop();
12436
12459
  updatedCount++;
12437
12460
  }
12438
12461
  for (const [indexName, treeTx] of treeTxs) {
@@ -12441,10 +12464,14 @@ var MutationManager = class {
12441
12464
  for (const rollbackTx of treeTxs.values()) {
12442
12465
  rollbackTx.rollback();
12443
12466
  }
12467
+ await yieldEventLoop();
12468
+ this.logger.error(`Failed to commit update for index ${indexName}: ${result.error}`);
12444
12469
  throw result.error;
12445
12470
  }
12446
12471
  }
12447
12472
  await this.api.analysisManager.notifyUpdate(updatePairs, tx);
12473
+ await yieldEventLoop();
12474
+ this.logger.debug(`Successfully updated ${updatedCount} documents`);
12448
12475
  return updatedCount;
12449
12476
  }
12450
12477
  async fullUpdate(query, newRecord, tx) {
@@ -12468,6 +12495,7 @@ var MutationManager = class {
12468
12495
  async deleteDocuments(query, tx) {
12469
12496
  return this.api.runWithDefaultWrite(async (tx2) => {
12470
12497
  const pks = await this.api.queryManager.getKeys(query);
12498
+ this.logger.debug(`Found ${pks.length} documents to delete`);
12471
12499
  let deletedCount = 0;
12472
12500
  const deletedFlatDocs = [];
12473
12501
  for (let i = 0, len = pks.length; i < len; i++) {
@@ -12492,12 +12520,16 @@ var MutationManager = class {
12492
12520
  if (indexVal === void 0) continue;
12493
12521
  await tree.delete(pk, { k: pk, v: indexVal });
12494
12522
  }
12523
+ await yieldEventLoop();
12495
12524
  }
12496
12525
  deletedFlatDocs.push(flatDoc);
12497
12526
  await this.api.delete(pk, true, tx2);
12527
+ await yieldEventLoop();
12498
12528
  deletedCount++;
12499
12529
  }
12500
12530
  await this.api.analysisManager.notifyDelete(deletedFlatDocs, tx2);
12531
+ await yieldEventLoop();
12532
+ this.logger.debug(`Successfully deleted ${deletedCount} documents`);
12501
12533
  return deletedCount;
12502
12534
  }, tx);
12503
12535
  }
@@ -13701,6 +13733,22 @@ var DocumentDataplyAPI = class extends import_dataply4.DataplyAPI {
13701
13733
  get indexedFields() {
13702
13734
  return this.indexManager.indexedFields;
13703
13735
  }
13736
+ /**
13737
+ * Method for reliably processing large batches (chunks) of data.
13738
+ * Processes data in fragments to prevent blocking the event loop while handling large volumes of data.
13739
+ * @param items The items to process
13740
+ * @param callback The callback to process each chunk
13741
+ * @param options The options for the chunk splitter
13742
+ * @param options.firstChunkSize The size of the first chunk. Subsequent chunk sizes are determined based on this value and the processing time. If not specified or set to 0, 5% of the total data is used as the initial value.
13743
+ * @param options.alpha A value that determines the weight given to recent processing times. Higher values weigh recent times more heavily. Lower values are recommended for stability, but excessively low values may impact performance. Default is 0.5.
13744
+ * @returns The processed items
13745
+ */
13746
+ processInChunks(items, callback, options) {
13747
+ this.logger.debug(`Processing ${items.length} items in chunks`);
13748
+ const firstChunkSize = options?.firstChunkSize ?? 0;
13749
+ const alpha = options?.alpha ?? 0.5;
13750
+ return new DeadlineChunker(firstChunkSize, alpha).processInChunks(items, callback);
13751
+ }
13704
13752
  /**
13705
13753
  * Register an index.
13706
13754
  * @param name The name of the index
@@ -13950,6 +13998,19 @@ var DocumentDataply = class _DocumentDataply {
13950
13998
  constructor(file, options) {
13951
13999
  this.api = new DocumentDataplyAPI(file, options ?? {});
13952
14000
  }
14001
+ /**
14002
+ * Method for reliably processing large batches (chunks) of data.
14003
+ * Processes data in fragments to prevent blocking the event loop while handling large volumes of data.
14004
+ * @param items The items to process
14005
+ * @param callback The callback to process each chunk
14006
+ * @param options The options for the chunk splitter
14007
+ * @param options.firstChunkSize The size of the first chunk. Subsequent chunk sizes are determined based on this value and the processing time. If not specified or set to 0, 5% of the total data is used as the initial value.
14008
+ * @param options.alpha A value that determines the weight given to recent processing times. Higher values weigh recent times more heavily. Lower values are recommended for stability, but excessively low values may impact performance. Default is 0.5.
14009
+ * @returns The processed items
14010
+ */
14011
+ processInChunks(items, callback, options) {
14012
+ return this.api.processInChunks(items, callback, options);
14013
+ }
13953
14014
  /**
13954
14015
  * Create a named index on the database.
13955
14016
  * Can be called before or after init().
@@ -1,7 +1,7 @@
1
1
  import type { AnalysisHeader, DocumentJSON, FlattenedDocumentJSON } from '../types';
2
2
  import type { DocumentDataplyAPI } from './documentAPI';
3
3
  import type { AnalysisProvider } from './AnalysisProvider';
4
- import { Transaction } from 'dataply';
4
+ import { Transaction, Logger } from 'dataply';
5
5
  export declare class AnalysisManager<T extends DocumentJSON> {
6
6
  private api;
7
7
  readonly sampleSize: number;
@@ -9,7 +9,7 @@ export declare class AnalysisManager<T extends DocumentJSON> {
9
9
  private providers;
10
10
  private cron;
11
11
  private flushing;
12
- constructor(api: DocumentDataplyAPI<T>, schedule: string, sampleSize: number, logger: any);
12
+ constructor(api: DocumentDataplyAPI<T>, schedule: string, sampleSize: number, logger: Logger);
13
13
  /**
14
14
  * Stop the background analysis cron job.
15
15
  */
@@ -1,6 +1,6 @@
1
1
  import type { DataplyTreeValue, DocumentDataplyInnerMetadata, Primitive, CreateIndexOption, IndexMetaConfig, FTSConfig, DocumentJSON, FlattenedDocumentJSON } from '../types';
2
2
  import type { DocumentDataplyAPI } from './documentAPI';
3
- import { BPTreeAsync, Transaction } from 'dataply';
3
+ import { BPTreeAsync, Transaction, Logger } from 'dataply';
4
4
  export declare class IndexManager<T extends DocumentJSON> {
5
5
  private api;
6
6
  private logger;
@@ -23,7 +23,7 @@ export declare class IndexManager<T extends DocumentJSON> {
23
23
  */
24
24
  fieldToIndices: Map<string, string[]>;
25
25
  pendingBackfillFields: string[];
26
- constructor(api: DocumentDataplyAPI<T>, logger: any);
26
+ constructor(api: DocumentDataplyAPI<T>, logger: Logger);
27
27
  /**
28
28
  * Validate and apply indices from DB metadata and pending indices.
29
29
  * Called during database initialization.
@@ -1,10 +1,10 @@
1
1
  import type { DocumentDataplyMetadata, DocumentDataplyInnerMetadata, DocumentJSON } from '../types';
2
2
  import type { DocumentDataplyAPI } from './documentAPI';
3
- import { Transaction } from 'dataply';
3
+ import { Transaction, Logger } from 'dataply';
4
4
  export declare class MetadataManager<T extends DocumentJSON> {
5
5
  private api;
6
6
  private logger;
7
- constructor(api: DocumentDataplyAPI<T>, logger: any);
7
+ constructor(api: DocumentDataplyAPI<T>, logger: Logger);
8
8
  getDocumentMetadata(tx: Transaction): Promise<DocumentDataplyMetadata>;
9
9
  getDocumentInnerMetadata(tx: Transaction): Promise<DocumentDataplyInnerMetadata>;
10
10
  updateDocumentInnerMetadata(metadata: DocumentDataplyInnerMetadata, tx: Transaction): Promise<void>;
@@ -1,10 +1,10 @@
1
1
  import type { DocumentJSON, DataplyDocument, DocumentDataplyQuery } from '../types';
2
2
  import type { DocumentDataplyAPI } from './documentAPI';
3
- import { Transaction } from 'dataply';
3
+ import { Transaction, Logger } from 'dataply';
4
4
  export declare class MutationManager<T extends DocumentJSON> {
5
5
  private api;
6
6
  private logger;
7
- constructor(api: DocumentDataplyAPI<T>, logger: any);
7
+ constructor(api: DocumentDataplyAPI<T>, logger: Logger);
8
8
  private isTreeEmpty;
9
9
  private insertDocumentInternal;
10
10
  insertSingleDocument(document: T, tx?: Transaction): Promise<number>;
@@ -1,13 +1,13 @@
1
1
  import type { DataplyTreeValue, DocumentDataplyQuery, DocumentDataplyQueryOptions, DataplyDocument, Primitive, DocumentJSON } from '../types';
2
2
  import type { DocumentDataplyAPI } from './documentAPI';
3
3
  import type { Optimizer } from './Optimizer';
4
- import { BPTreeAsync } from 'dataply';
4
+ import { BPTreeAsync, Logger } from 'dataply';
5
5
  export declare class QueryManager<T extends DocumentJSON> {
6
6
  private api;
7
7
  private optimizer;
8
8
  private logger;
9
9
  private readonly operatorConverters;
10
- constructor(api: DocumentDataplyAPI<T>, optimizer: Optimizer<T>, logger: any);
10
+ constructor(api: DocumentDataplyAPI<T>, optimizer: Optimizer<T>, logger: Logger);
11
11
  /**
12
12
  * Transforms a query object into a verbose query object
13
13
  */
@@ -30,6 +30,20 @@ export declare class DocumentDataply<T extends DocumentJSON> {
30
30
  private static Open;
31
31
  protected readonly api: DocumentDataplyAPI<T>;
32
32
  protected constructor(file: string, options?: DocumentDataplyOptions);
33
+ /**
34
+ * Method for reliably processing large batches (chunks) of data.
35
+ * Processes data in fragments to prevent blocking the event loop while handling large volumes of data.
36
+ * @param items The items to process
37
+ * @param callback The callback to process each chunk
38
+ * @param options The options for the chunk splitter
39
+ * @param options.firstChunkSize The size of the first chunk. Subsequent chunk sizes are determined based on this value and the processing time. If not specified or set to 0, 5% of the total data is used as the initial value.
40
+ * @param options.alpha A value that determines the weight given to recent processing times. Higher values weigh recent times more heavily. Lower values are recommended for stability, but excessively low values may impact performance. Default is 0.5.
41
+ * @returns The processed items
42
+ */
43
+ processInChunks<T>(items: T[], callback: (chunk: T[]) => Promise<void>, options?: {
44
+ firstChunkSize?: number;
45
+ alpha?: number;
46
+ }): Promise<void>;
33
47
  /**
34
48
  * Create a named index on the database.
35
49
  * Can be called before or after init().
@@ -31,6 +31,20 @@ export declare class DocumentDataplyAPI<T extends DocumentJSON> extends DataplyA
31
31
  };
32
32
  get trees(): Map<string, import("dataply").BPTreeAsync<string | number, import("../types").DataplyTreeValue<import("../types").Primitive>>>;
33
33
  get indexedFields(): Set<string>;
34
+ /**
35
+ * Method for reliably processing large batches (chunks) of data.
36
+ * Processes data in fragments to prevent blocking the event loop while handling large volumes of data.
37
+ * @param items The items to process
38
+ * @param callback The callback to process each chunk
39
+ * @param options The options for the chunk splitter
40
+ * @param options.firstChunkSize The size of the first chunk. Subsequent chunk sizes are determined based on this value and the processing time. If not specified or set to 0, 5% of the total data is used as the initial value.
41
+ * @param options.alpha A value that determines the weight given to recent processing times. Higher values weigh recent times more heavily. Lower values are recommended for stability, but excessively low values may impact performance. Default is 0.5.
42
+ * @returns The processed items
43
+ */
44
+ processInChunks<T>(items: T[], callback: (chunk: T[]) => Promise<void>, options?: {
45
+ firstChunkSize?: number;
46
+ alpha?: number;
47
+ }): Promise<void>;
34
48
  /**
35
49
  * Register an index.
36
50
  * @param name The name of the index
@@ -1,3 +1,4 @@
1
+ export declare function yieldEventLoop(): Promise<unknown>;
1
2
  /**
2
3
  * 이벤트 루프를 막지 않고 대량의 데이터를 처리하기 위한 유틸리티 클래스
3
4
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "document-dataply",
3
- "version": "0.0.14-alpha.1",
3
+ "version": "0.0.14-alpha.2",
4
4
  "description": "Simple and powerful JSON document database supporting complex queries and flexible indexing policies.",
5
5
  "license": "MIT",
6
6
  "author": "izure <admin@izure.org>",
@@ -43,7 +43,7 @@
43
43
  ],
44
44
  "dependencies": {
45
45
  "croner": "^10.0.1",
46
- "dataply": "^0.0.26-alpha.2"
46
+ "dataply": "^0.0.26-alpha.4"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@types/jest": "^30.0.0",