@powersync/service-module-mongodb-storage 0.0.0-dev-20260909133214 → 0.0.0-dev-20260929095716

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 (140) hide show
  1. package/CHANGELOG.md +52 -6
  2. package/dist/storage/MongoBucketStorage.d.ts +11 -2
  3. package/dist/storage/MongoBucketStorage.js +43 -13
  4. package/dist/storage/MongoBucketStorage.js.map +1 -1
  5. package/dist/storage/implementation/MongoBucketBatch.d.ts +24 -10
  6. package/dist/storage/implementation/MongoBucketBatch.js +134 -89
  7. package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
  8. package/dist/storage/implementation/MongoCompactor.d.ts +2 -0
  9. package/dist/storage/implementation/MongoCompactor.js.map +1 -1
  10. package/dist/storage/implementation/MongoIdSequence.d.ts +10 -1
  11. package/dist/storage/implementation/MongoIdSequence.js +14 -1
  12. package/dist/storage/implementation/MongoIdSequence.js.map +1 -1
  13. package/dist/storage/implementation/MongoOpIdAllocator.d.ts +27 -0
  14. package/dist/storage/implementation/MongoOpIdAllocator.js +97 -0
  15. package/dist/storage/implementation/MongoOpIdAllocator.js.map +1 -0
  16. package/dist/storage/implementation/MongoParsedSyncConfigSet.js +6 -2
  17. package/dist/storage/implementation/MongoParsedSyncConfigSet.js.map +1 -1
  18. package/dist/storage/implementation/MongoPersistedReplicationStream.d.ts +2 -1
  19. package/dist/storage/implementation/MongoPersistedReplicationStream.js +7 -4
  20. package/dist/storage/implementation/MongoPersistedReplicationStream.js.map +1 -1
  21. package/dist/storage/implementation/MongoPersistedSyncConfigContent.d.ts +3 -3
  22. package/dist/storage/implementation/MongoPersistedSyncConfigContent.js +6 -6
  23. package/dist/storage/implementation/MongoPersistedSyncConfigContent.js.map +1 -1
  24. package/dist/storage/implementation/MongoStorageProvider.js +5 -3
  25. package/dist/storage/implementation/MongoStorageProvider.js.map +1 -1
  26. package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +5 -0
  27. package/dist/storage/implementation/MongoSyncBucketStorage.js +17 -0
  28. package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
  29. package/dist/storage/implementation/MongoSyncRulesLock.d.ts +26 -2
  30. package/dist/storage/implementation/MongoSyncRulesLock.js +38 -1
  31. package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -1
  32. package/dist/storage/implementation/MongoWriteBatch.d.ts +49 -0
  33. package/dist/storage/implementation/MongoWriteBatch.js +149 -0
  34. package/dist/storage/implementation/MongoWriteBatch.js.map +1 -0
  35. package/dist/storage/implementation/MongoWriteCheckpointAPI.js +2 -2
  36. package/dist/storage/implementation/MongoWriteCheckpointAPI.js.map +1 -1
  37. package/dist/storage/implementation/common/PersistedBatch.d.ts +5 -4
  38. package/dist/storage/implementation/common/PersistedBatch.js +6 -4
  39. package/dist/storage/implementation/common/PersistedBatch.js.map +1 -1
  40. package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.d.ts +3 -0
  41. package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js +3 -0
  42. package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js.map +1 -1
  43. package/dist/storage/implementation/db.d.ts +5 -0
  44. package/dist/storage/implementation/db.js +14 -0
  45. package/dist/storage/implementation/db.js.map +1 -1
  46. package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +5 -0
  47. package/dist/storage/implementation/v1/MongoBucketBatchV1.js +149 -139
  48. package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -1
  49. package/dist/storage/implementation/v1/MongoCompactorV1.d.ts +2 -1
  50. package/dist/storage/implementation/v1/MongoCompactorV1.js +23 -17
  51. package/dist/storage/implementation/v1/MongoCompactorV1.js.map +1 -1
  52. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +1 -1
  53. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js.map +1 -1
  54. package/dist/storage/implementation/v1/PersistedBatchV1.d.ts +7 -5
  55. package/dist/storage/implementation/v1/PersistedBatchV1.js +22 -28
  56. package/dist/storage/implementation/v1/PersistedBatchV1.js.map +1 -1
  57. package/dist/storage/implementation/v3/MongoBucketBatchV3.d.ts +6 -1
  58. package/dist/storage/implementation/v3/MongoBucketBatchV3.js +148 -162
  59. package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -1
  60. package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +10 -4
  61. package/dist/storage/implementation/v3/MongoCompactorV3.js +175 -76
  62. package/dist/storage/implementation/v3/MongoCompactorV3.js.map +1 -1
  63. package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +12 -8
  64. package/dist/storage/implementation/v3/PersistedBatchV3.js +25 -32
  65. package/dist/storage/implementation/v3/PersistedBatchV3.js.map +1 -1
  66. package/dist/storage/implementation/v3/compact-utils.d.ts +3 -2
  67. package/dist/storage/implementation/v3/compact-utils.js +12 -5
  68. package/dist/storage/implementation/v3/compact-utils.js.map +1 -1
  69. package/dist/storage/implementation/v3/models.d.ts +1 -1
  70. package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js +8 -1
  71. package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js.map +1 -1
  72. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.d.ts +3 -3
  73. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js +7 -9
  74. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js.map +1 -1
  75. package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.d.ts +3 -2
  76. package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.js +4 -7
  77. package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.js.map +1 -1
  78. package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js +21 -3
  79. package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js.map +1 -1
  80. package/dist/types/types.d.ts +5 -0
  81. package/dist/types/types.js +12 -0
  82. package/dist/types/types.js.map +1 -1
  83. package/dist/utils/test-utils.d.ts +4 -2
  84. package/dist/utils/test-utils.js +7 -4
  85. package/dist/utils/test-utils.js.map +1 -1
  86. package/package.json +6 -6
  87. package/src/storage/MongoBucketStorage.ts +54 -13
  88. package/src/storage/implementation/MongoBucketBatch.ts +205 -111
  89. package/src/storage/implementation/MongoCompactor.ts +2 -0
  90. package/src/storage/implementation/MongoIdSequence.ts +21 -1
  91. package/src/storage/implementation/MongoOpIdAllocator.ts +105 -0
  92. package/src/storage/implementation/MongoParsedSyncConfigSet.ts +12 -1
  93. package/src/storage/implementation/MongoPersistedReplicationStream.ts +12 -4
  94. package/src/storage/implementation/MongoPersistedSyncConfigContent.ts +48 -31
  95. package/src/storage/implementation/MongoStorageProvider.ts +13 -3
  96. package/src/storage/implementation/MongoSyncBucketStorage.ts +19 -0
  97. package/src/storage/implementation/MongoSyncRulesLock.ts +56 -3
  98. package/src/storage/implementation/MongoWriteBatch.ts +172 -0
  99. package/src/storage/implementation/MongoWriteCheckpointAPI.ts +4 -2
  100. package/src/storage/implementation/common/PersistedBatch.ts +12 -8
  101. package/src/storage/implementation/common/VersionedPowerSyncMongoBase.ts +4 -0
  102. package/src/storage/implementation/db.ts +15 -0
  103. package/src/storage/implementation/models.ts +1 -0
  104. package/src/storage/implementation/v1/MongoBucketBatchV1.ts +167 -160
  105. package/src/storage/implementation/v1/MongoCompactorV1.ts +29 -26
  106. package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +1 -1
  107. package/src/storage/implementation/v1/PersistedBatchV1.ts +25 -30
  108. package/src/storage/implementation/v3/MongoBucketBatchV3.ts +217 -242
  109. package/src/storage/implementation/v3/MongoCompactorV3.ts +284 -108
  110. package/src/storage/implementation/v3/PersistedBatchV3.ts +36 -36
  111. package/src/storage/implementation/v3/compact-utils.ts +16 -7
  112. package/src/storage/implementation/v3/models.ts +1 -1
  113. package/src/storage/implementation/v3/object-storage/BucketDataObjectStorage.ts +8 -1
  114. package/src/storage/implementation/v3/object-storage/ObjectStorageLifecycle.ts +11 -10
  115. package/src/storage/implementation/v3/object-storage/ObjectStorageUsage.ts +8 -9
  116. package/src/storage/implementation/v3/object-storage/S3ObjectStorage.ts +23 -3
  117. package/src/types/types.ts +18 -0
  118. package/src/utils/test-utils.ts +9 -5
  119. package/test/src/__snapshots__/storage_sync.test.ts.snap +12 -12
  120. package/test/src/chunk_compaction_config.test.ts +38 -0
  121. package/test/src/cleanup-stopped-sync-configs.test.ts +19 -13
  122. package/test/src/compact-utils.test.ts +33 -8
  123. package/test/src/concurrent_writers.test.ts +599 -0
  124. package/test/src/connection_config.test.ts +159 -0
  125. package/test/src/helpers/concurrentWriter.mjs +29 -0
  126. package/test/src/helpers/s3TestFactory.ts +12 -12
  127. package/test/src/object_storage_usage.test.ts +18 -5
  128. package/test/src/parameter_compacting_v1.test.ts +2 -2
  129. package/test/src/parameter_compacting_v3.test.ts +2 -2
  130. package/test/src/parameter_compaction_fence.test.ts +3 -3
  131. package/test/src/storage.test.ts +15 -10
  132. package/test/src/storage_compacting.test.ts +397 -23
  133. package/test/src/storage_object_storage_inline_threshold.test.ts +3 -3
  134. package/test/src/storage_s3_checksums.test.ts +4 -4
  135. package/test/src/storage_s3_compaction_lifecycle.test.ts +8 -8
  136. package/test/src/storage_s3_reading.test.ts +51 -5
  137. package/test/src/storage_s3_writing.test.ts +3 -3
  138. package/test/src/storage_sync.test.ts +55 -47
  139. package/test/src/storeCurrentData.test.ts +5 -5
  140. package/tsconfig.tsbuildinfo +1 -1
@@ -7,6 +7,7 @@ import { CompactionLease } from '@module/storage/implementation/v3/CompactionLea
7
7
  import { BucketDataDocumentV3 } from '@module/storage/implementation/v3/models.js';
8
8
  import { ObjectStorageError } from '@module/storage/implementation/v3/object-storage/ObjectStorage.js';
9
9
  import { VersionedPowerSyncMongoV3 } from '@module/storage/implementation/v3/VersionedPowerSyncMongoV3.js';
10
+ import { mongoTestStorageFactoryGenerator } from '@module/utils/test-utils.js';
10
11
  import { replicaIdToSubkey } from '@module/utils/util.js';
11
12
  import { logger as defaultLogger } from '@powersync/lib-services-framework';
12
13
  import {
@@ -16,9 +17,11 @@ import {
16
17
  SyncRulesBucketStorage,
17
18
  updateSyncRulesFromYaml
18
19
  } from '@powersync/service-core';
19
- import { bucketRequest, compactActive, register, test_utils } from '@powersync/service-core-tests';
20
+ import { bucketRequest, compactActive, getTestStorage, register, test_utils } from '@powersync/service-core-tests';
20
21
  import * as bson from 'bson';
21
22
  import { describe, expect, test, vi } from 'vitest';
23
+ import { env } from './env.js';
24
+ import { MemoryObjectStorage } from './helpers/MemoryObjectStorage.js';
22
25
  import { INITIALIZED_MONGO_STORAGE_FACTORY, TEST_STORAGE_VERSIONS } from './util.js';
23
26
 
24
27
  function makeOp(
@@ -103,7 +106,7 @@ bucket_definitions:
103
106
  { storageVersion }
104
107
  )
105
108
  );
106
- const bucketStorage = factory.getInstance(syncRules);
109
+ const bucketStorage = await getTestStorage(factory, syncRules);
107
110
  const syncRulesContent = syncRules.syncConfigContent[0];
108
111
  const { checkpoint } = await populate(bucketStorage, 1);
109
112
 
@@ -161,15 +164,16 @@ bucket_definitions:
161
164
  { storageVersion }
162
165
  )
163
166
  );
164
- const bucketStorage = factory.getInstance(syncRules);
167
+ const bucketStorage = await getTestStorage(factory, syncRules);
165
168
  const syncRulesContent = syncRules.syncConfigContent[0];
166
169
 
167
170
  await populate(bucketStorage, 2);
168
171
  const { checkpoint } = await bucketStorage.getCheckpoint();
169
172
 
170
- // V3's initial lite pass processes every bucket with no prior compact state.
173
+ // Force V3's initial lite pass to process these small buckets for cache verification.
171
174
  // Earlier storage versions use the default minimum-change threshold.
172
175
  const result0 = await bucketStorage.compactInitialReplication({
176
+ forceChunkCompaction: true,
173
177
  maxOpId: checkpoint
174
178
  });
175
179
  expect(result0.buckets).toEqual(storageVersion >= storage.STORAGE_VERSION_3 ? 2 : 0);
@@ -177,6 +181,7 @@ bucket_definitions:
177
181
  // For V1/V2, lower the threshold to populate the checksum cache. V3 has
178
182
  // already updated its compacted state, so another initial pass is a no-op.
179
183
  const result1 = await bucketStorage.compactInitialReplication({
184
+ forceChunkCompaction: true,
180
185
  maxOpId: checkpoint,
181
186
  minBucketChanges: 1
182
187
  });
@@ -184,6 +189,7 @@ bucket_definitions:
184
189
 
185
190
  // Repeating it stays a no-op.
186
191
  const result2 = await bucketStorage.compactInitialReplication({
192
+ forceChunkCompaction: true,
187
193
  maxOpId: checkpoint,
188
194
  minBucketChanges: 1
189
195
  });
@@ -211,6 +217,7 @@ bucket_definitions:
211
217
  .createMongoCompactor({
212
218
  maxOpId: checkpoint,
213
219
  compactChunksOnly: true,
220
+ forceChunkCompaction: true,
214
221
  minCompactChunkIntervalMs: 1
215
222
  })
216
223
  .compact();
@@ -220,7 +227,10 @@ bucket_definitions:
220
227
 
221
228
  test('v3 repeated initial chunk compaction reschedules overdue full work beyond the current run', async () => {
222
229
  const { bucketStorage, checkpoint } = await setup(storage.STORAGE_VERSION_3);
223
- const firstResult = await bucketStorage.compactInitialReplication({ maxOpId: checkpoint });
230
+ const firstResult = await bucketStorage.compactInitialReplication({
231
+ forceChunkCompaction: true,
232
+ maxOpId: checkpoint
233
+ });
224
234
  expect(firstResult.buckets).toBe(2);
225
235
 
226
236
  const bucketStateCollection = (bucketStorage.db as VersionedPowerSyncMongoV3).bucketState(
@@ -240,6 +250,7 @@ bucket_definitions:
240
250
  .aggregate<{ now: Date }>([{ $documents: [{}] }, { $project: { _id: 0, now: '$$NOW' } }])
241
251
  .toArray();
242
252
  const result = await bucketStorage.compactInitialReplication({
253
+ forceChunkCompaction: true,
243
254
  maxOpId: checkpoint,
244
255
  signal: AbortSignal.timeout(2_000)
245
256
  });
@@ -345,7 +356,7 @@ bucket_definitions:
345
356
  { storageVersion: 3 }
346
357
  )
347
358
  );
348
- const bucketStorage = factory.getInstance(syncRules) as MongoSyncBucketStorage;
359
+ const bucketStorage = (await getTestStorage(factory, syncRules)) as MongoSyncBucketStorage;
349
360
  const db = bucketStorage.db as VersionedPowerSyncMongoV3;
350
361
  const mapping = syncRules.syncConfigContent[0].mapping;
351
362
  const definitionId = mapping.allBucketDefinitionIds()[0];
@@ -385,6 +396,91 @@ bucket_definitions:
385
396
  });
386
397
  }
387
398
 
399
+ test.each([1, 7])(
400
+ 'initial compaction bulk-reschedules buckets with %s chunks without reading data',
401
+ async (chunks) => {
402
+ const { bucketStorage, collection, bucketStateCollection, ctx, sourceTableId, db } = await setupV3Storage();
403
+ const documents = Array.from({ length: chunks }, (_, index) =>
404
+ serializeBucketData(BUCKET, [makeOp(index + 1, String(index), 'data', ctx, sourceTableId)])
405
+ );
406
+ await insertDocs(collection, documents);
407
+ await bucketStateCollection.insertOne({
408
+ _id: { d: ctx.definitionId, b: BUCKET },
409
+ last_op: BigInt(chunks),
410
+ next_compact_check: new Date(0),
411
+ // Even overdue full compaction must not force a small initial chunk merge.
412
+ first_uncompacted_write: new Date(0),
413
+ bucket_stats: { count: chunks, bytes: BigInt(documents.reduce((sum, doc) => sum + doc.size, 0)), chunks }
414
+ });
415
+ const claim = vi.spyOn(CompactionLease, 'claim');
416
+ const data = vi.spyOn(db, 'bucketData');
417
+ try {
418
+ expect(await bucketStorage.compactInitialReplication({ maxOpId: BigInt(chunks) })).toEqual({ buckets: 0 });
419
+ expect(claim).not.toHaveBeenCalled();
420
+ expect(data).not.toHaveBeenCalled();
421
+ const state = await bucketStateCollection.findOne({ _id: { d: ctx.definitionId, b: BUCKET } });
422
+ expect(state?.compacted_state).toBeUndefined();
423
+ expect(state?.compact_lease).toBeUndefined();
424
+ expect(state?.last_full_compact).toBeUndefined();
425
+ expect(state!.next_compact_check!.getTime()).toBeGreaterThan(Date.now());
426
+ expect(await collection.find().toArray()).toEqual(documents);
427
+ } finally {
428
+ claim.mockRestore();
429
+ data.mockRestore();
430
+ }
431
+ }
432
+ );
433
+
434
+ test('initial compaction rechecks the chunk threshold after claiming', async () => {
435
+ const { bucketStorage, collection, bucketStateCollection, ctx, sourceTableId, db } = await setupV3Storage();
436
+ const documents = Array.from({ length: 8 }, (_, index) =>
437
+ serializeBucketData(BUCKET, [makeOp(index + 1, String(index), 'data', ctx, sourceTableId)])
438
+ );
439
+ await insertDocs(collection, documents);
440
+ const bytes = BigInt(documents.reduce((sum, doc) => sum + doc.size, 0));
441
+ await bucketStateCollection.insertOne({
442
+ _id: { d: ctx.definitionId, b: BUCKET },
443
+ last_op: 8n,
444
+ next_compact_check: new Date(0),
445
+ first_uncompacted_write: new Date(),
446
+ bucket_stats: { count: 8, bytes, chunks: 8 }
447
+ });
448
+ const originalClaim = CompactionLease.claim.bind(CompactionLease);
449
+ const claim = vi.spyOn(CompactionLease, 'claim').mockImplementationOnce(async (...args) => {
450
+ // Another compactor finishes before we acquire the lease, leaving one
451
+ // new chunk beyond its cached prefix instead of the eight we selected.
452
+ await bucketStateCollection.updateOne(
453
+ { _id: { d: ctx.definitionId, b: BUCKET } },
454
+ {
455
+ $set: {
456
+ compacted_state: {
457
+ op_id: 7n,
458
+ checksum: 196n,
459
+ count: 7,
460
+ bytes: bytes - BigInt(documents[7].size),
461
+ chunks: 7,
462
+ at: new Date()
463
+ }
464
+ }
465
+ }
466
+ );
467
+ return originalClaim(...args);
468
+ });
469
+ const data = vi.spyOn(db, 'bucketData');
470
+ try {
471
+ expect(await bucketStorage.compactInitialReplication({ maxOpId: 8n })).toEqual({ buckets: 0 });
472
+ expect(claim).toHaveBeenCalledTimes(1);
473
+ expect(data).not.toHaveBeenCalled();
474
+ const state = await bucketStateCollection.findOne({ _id: { d: ctx.definitionId, b: BUCKET } });
475
+ expect(state?.compacted_state?.op_id).toBe(7n);
476
+ expect(state?.compact_lease).toBeUndefined();
477
+ expect(state!.next_compact_check!.getTime()).toBeGreaterThan(Date.now());
478
+ } finally {
479
+ claim.mockRestore();
480
+ data.mockRestore();
481
+ }
482
+ });
483
+
388
484
  test('a successful lease renewal clears a transient renewal error', async () => {
389
485
  const { bucketStateCollection, ctx } = await setupV3Storage();
390
486
  await insertBucketState(bucketStateCollection, ctx.definitionId, 1n);
@@ -446,7 +542,7 @@ bucket_definitions:
446
542
  );
447
543
 
448
544
  await (bucketStorage as MongoSyncBucketStorage)
449
- .createMongoCompactor({ maxOpId: 2n, compactChunksOnly: true })
545
+ .createMongoCompactor({ maxOpId: 2n, compactChunksOnly: true, forceChunkCompaction: true })
450
546
  .compact();
451
547
 
452
548
  const [badState, goodState] = await Promise.all([
@@ -460,7 +556,7 @@ bucket_definitions:
460
556
 
461
557
  test('aborting scheduled compaction does not reschedule the remaining batch', async () => {
462
558
  const { bucketStorage, collection, bucketStateCollection, ctx, sourceTableId } = await setupV3Storage();
463
- const buckets = ['first[]', 'second[]', 'third[]'];
559
+ const buckets = Array.from({ length: 8 }, (_, index) => `bucket${index}[]`);
464
560
  const documents = buckets.map((bucket) =>
465
561
  serializeBucketData(bucket, [makeOp(2, bucket, bucket, { ...ctx, bucket }, sourceTableId)])
466
562
  );
@@ -490,6 +586,7 @@ bucket_definitions:
490
586
  .createMongoCompactor({
491
587
  maxOpId: 2n,
492
588
  compactChunksOnly: true,
589
+ forceChunkCompaction: true,
493
590
  signal: abortController.signal,
494
591
  logger: testLogger
495
592
  })
@@ -499,8 +596,11 @@ bucket_definitions:
499
596
  const states = await bucketStateCollection.find({ '_id.b': { $in: buckets } }).toArray();
500
597
  const completed = states.filter((state) => state.compacted_state != null);
501
598
  const remaining = states.filter((state) => state.compacted_state == null);
502
- expect(completed).toHaveLength(1);
503
- expect(remaining).toHaveLength(2);
599
+ // Already-started buckets may finish after abort, but the queued remainder
600
+ // must not be claimed or rescheduled.
601
+ expect(completed.length).toBeGreaterThanOrEqual(1);
602
+ expect(completed.length).toBeLessThanOrEqual(2);
603
+ expect(remaining.length).toBeGreaterThanOrEqual(6);
504
604
  for (const state of remaining) {
505
605
  expect(state.next_compact_check).toEqual(new Date(0));
506
606
  expect(state.compact_lease).toBeUndefined();
@@ -890,7 +990,7 @@ bucket_definitions:
890
990
  { storageVersion: 3 }
891
991
  )
892
992
  );
893
- const bucketStorage = factory.getInstance(syncRules) as MongoSyncBucketStorage;
993
+ const bucketStorage = (await getTestStorage(factory, syncRules)) as MongoSyncBucketStorage;
894
994
  const db = bucketStorage.db as VersionedPowerSyncMongoV3;
895
995
  const mapping = syncRules.syncConfigContent[0].mapping;
896
996
  const definitionId = mapping.allBucketDefinitionIds()[0];
@@ -1067,7 +1167,7 @@ bucket_definitions:
1067
1167
  { storageVersion: 3 }
1068
1168
  )
1069
1169
  );
1070
- const bucketStorage = factory.getInstance(syncRules) as MongoSyncBucketStorage;
1170
+ const bucketStorage = (await getTestStorage(factory, syncRules)) as MongoSyncBucketStorage;
1071
1171
  const db = bucketStorage.db as VersionedPowerSyncMongoV3;
1072
1172
  const mapping = syncRules.syncConfigContent[0].mapping;
1073
1173
  const definitionId = mapping.allBucketDefinitionIds()[0];
@@ -1460,7 +1560,7 @@ bucket_definitions:
1460
1560
  { storageVersion: 3 }
1461
1561
  )
1462
1562
  );
1463
- const bucketStorage = factory.getInstance(syncRules) as MongoSyncBucketStorage;
1563
+ const bucketStorage = (await getTestStorage(factory, syncRules)) as MongoSyncBucketStorage;
1464
1564
  const db = bucketStorage.db as VersionedPowerSyncMongoV3;
1465
1565
  const mapping = syncRules.syncConfigContent[0].mapping;
1466
1566
  const definitionId = mapping.allBucketDefinitionIds()[0];
@@ -1637,8 +1737,8 @@ bucket_definitions:
1637
1737
  describe('Streaming compactor', () => {
1638
1738
  const BUCKET = 'global[]';
1639
1739
 
1640
- async function setupV3() {
1641
- await using factory = await INITIALIZED_MONGO_STORAGE_FACTORY.factory();
1740
+ async function setupV3(factoryGenerator = INITIALIZED_MONGO_STORAGE_FACTORY) {
1741
+ await using factory = await factoryGenerator.factory();
1642
1742
  const syncRules = await factory.updateSyncRules(
1643
1743
  updateSyncRulesFromYaml(
1644
1744
  `
@@ -1649,7 +1749,7 @@ bucket_definitions:
1649
1749
  { storageVersion: 3 }
1650
1750
  )
1651
1751
  );
1652
- const bucketStorage = factory.getInstance(syncRules) as MongoSyncBucketStorage;
1752
+ const bucketStorage = (await getTestStorage(factory, syncRules)) as MongoSyncBucketStorage;
1653
1753
  const db = bucketStorage.db as VersionedPowerSyncMongoV3;
1654
1754
  const definitionId = bucketStorage.storageIds.bucketDefinitionIds[0];
1655
1755
  const collection = db.bucketData(bucketStorage.replicationStreamId, definitionId);
@@ -1746,6 +1846,29 @@ bucket_definitions:
1746
1846
  expect(state?.bucket_stats).toEqual({ count: 4, bytes, chunks: documents.length });
1747
1847
  }
1748
1848
 
1849
+ test('initial compaction merges eight chunks immediately without full compaction', async () => {
1850
+ const { bucketStorage, collection, bucketStateCollection, ctx, sourceTableId } = await setupV3();
1851
+ const inputs = Array.from({ length: 8 }, (_, index) =>
1852
+ serializeBucketData(BUCKET, [makeOp(index + 1, 'A', 'data', ctx, sourceTableId)])
1853
+ );
1854
+ await insertDocs(collection, inputs);
1855
+ await bucketStateCollection.insertOne({
1856
+ _id: { d: ctx.definitionId, b: BUCKET },
1857
+ last_op: 8n,
1858
+ next_compact_check: new Date(),
1859
+ first_uncompacted_write: new Date(0),
1860
+ bucket_stats: { count: 8, bytes: BigInt(inputs.reduce((sum, doc) => sum + doc.size, 0)), chunks: 8 }
1861
+ });
1862
+
1863
+ expect(await bucketStorage.compactInitialReplication({ maxOpId: 8n })).toEqual({ buckets: 1 });
1864
+ const documents = await collection.find({ '_id.b': BUCKET }).toArray();
1865
+ expect(documents).toHaveLength(1);
1866
+ expect(documents[0].ops!.map((op) => op.o)).toEqual([1n, 2n, 3n, 4n, 5n, 6n, 7n, 8n]);
1867
+ const state = await bucketStateCollection.findOne({ _id: { d: ctx.definitionId, b: BUCKET } });
1868
+ expect(state?.compacted_state).toMatchObject({ op_id: 8n, count: 8, checksum: 252n });
1869
+ expect(state?.last_full_compact).toBeUndefined();
1870
+ });
1871
+
1749
1872
  test('initial compaction merges small chunks and refreshes bucket metadata', async () => {
1750
1873
  const { bucketStorage, collection, bucketStateCollection, ctx, sourceTableId } = await setupV3();
1751
1874
  await insertDocs(collection, [
@@ -1754,7 +1877,7 @@ bucket_definitions:
1754
1877
  ]);
1755
1878
  await insertBucketState(bucketStateCollection, ctx.definitionId, 4n);
1756
1879
 
1757
- const result = await bucketStorage.compactInitialReplication({ maxOpId: 4n });
1880
+ const result = await bucketStorage.compactInitialReplication({ forceChunkCompaction: true, maxOpId: 4n });
1758
1881
 
1759
1882
  expect(result).toEqual({ buckets: 1 });
1760
1883
  const documents = await collection.find({ '_id.b': BUCKET }).sort({ '_id.o': 1 }).toArray();
@@ -1769,6 +1892,254 @@ bucket_definitions:
1769
1892
  });
1770
1893
  });
1771
1894
 
1895
+ test.each([
1896
+ { payloadSize: 100, workers: undefined, abort: false },
1897
+ { payloadSize: 450_000, workers: undefined, abort: false },
1898
+ { payloadSize: 450_000, workers: 4, abort: false },
1899
+ { payloadSize: 100, workers: 1, abort: false },
1900
+ { payloadSize: 450_000, workers: 4, abort: true }
1901
+ ])('chunk compaction shares configured workers across runs: %o', async ({ payloadSize, workers, abort }) => {
1902
+ const concurrency = workers ?? 4;
1903
+ const objectStorage = new MemoryObjectStorage();
1904
+ const generator = mongoTestStorageFactoryGenerator({
1905
+ url: env.MONGO_TEST_URL,
1906
+ isCI: env.CI,
1907
+ objectStorage,
1908
+ chunkCompactionConcurrency: workers,
1909
+ inlineThresholdBytes: 0
1910
+ });
1911
+ const { bucketStorage, syncRules, collection, bucketStateCollection, ctx, sourceTableId, db } =
1912
+ await setupV3(generator);
1913
+ const bucketCount = 12;
1914
+ for (let i = 0; i < bucketCount; i++) {
1915
+ const bucket = `global[${i}]`;
1916
+ const documents = [1, 2].map((id) =>
1917
+ serializeBucketData(bucket, [makeOp(id, String(id), 'x'.repeat(payloadSize), ctx, sourceTableId)])
1918
+ );
1919
+ await collection.insertMany(documents);
1920
+ await bucketStateCollection.insertOne({
1921
+ _id: { d: ctx.definitionId, b: bucket },
1922
+ last_op: 2n,
1923
+ next_compact_check: new Date(0),
1924
+ first_uncompacted_write: new Date(0),
1925
+ bucket_stats: {
1926
+ count: 2,
1927
+ bytes: BigInt(documents.reduce((sum, doc) => sum + doc.size, 0)),
1928
+ chunks: 2
1929
+ }
1930
+ });
1931
+ }
1932
+
1933
+ const gate = Promise.withResolvers<void>();
1934
+ const originalPut = objectStorage.put.bind(objectStorage);
1935
+ let uploads = 0;
1936
+ const put = vi.spyOn(objectStorage, 'put').mockImplementation(async (...args) => {
1937
+ uploads++;
1938
+ await gate.promise;
1939
+ await originalPut(...args);
1940
+ });
1941
+ const controller = new AbortController();
1942
+ let settled = false;
1943
+ const runs = Promise.allSettled(
1944
+ Array.from({ length: 2 }, () =>
1945
+ bucketStorage.factory
1946
+ .getInstance(syncRules)
1947
+ .compactInitialReplication({ forceChunkCompaction: true, maxOpId: 2n, signal: controller.signal })
1948
+ )
1949
+ ).finally(() => {
1950
+ settled = true;
1951
+ });
1952
+ try {
1953
+ await vi.waitFor(() => expect(uploads).toBe(concurrency));
1954
+ // Let other ready workers reach admission; the held uploads must prevent
1955
+ // any additional payloads from reaching S3, across both compactor instances.
1956
+ await new Promise((resolve) => setTimeout(resolve, 50));
1957
+ expect(uploads).toBe(concurrency);
1958
+ expect(await bucketStateCollection.countDocuments({ compact_lease: { $exists: true } })).toBeLessThanOrEqual(
1959
+ concurrency
1960
+ );
1961
+ expect(await collection.countDocuments()).toBe(bucketCount * 2);
1962
+ if (abort) {
1963
+ controller.abort();
1964
+ await new Promise((resolve) => setTimeout(resolve, 50));
1965
+ expect(settled).toBe(false);
1966
+ }
1967
+ } finally {
1968
+ gate.resolve();
1969
+ await runs;
1970
+ put.mockRestore();
1971
+ }
1972
+
1973
+ const results = await runs;
1974
+ if (abort) {
1975
+ expect(results.every((result) => result.status === 'rejected')).toBe(true);
1976
+ expect(await bucketStateCollection.countDocuments({ compact_lease: { $exists: true } })).toBe(0);
1977
+ // Cancellation must return all worker slots.
1978
+ expect(await bucketStorage.compactInitialReplication({ forceChunkCompaction: true, maxOpId: 2n })).toEqual({
1979
+ buckets: bucketCount
1980
+ });
1981
+ } else {
1982
+ expect(results.every((result) => result.status === 'fulfilled')).toBe(true);
1983
+ expect(results.reduce((sum, result) => sum + (result.status === 'fulfilled' ? result.value.buckets : 0), 0)).toBe(
1984
+ bucketCount
1985
+ );
1986
+ }
1987
+
1988
+ const documents = await collection.find().toArray();
1989
+ expect(documents).toHaveLength(bucketCount);
1990
+ expect(documents.every((doc) => doc.count === 2 && doc.checksum === 21n && doc.storage_ref != null)).toBe(true);
1991
+ expect(
1992
+ await bucketStateCollection.countDocuments({ 'compacted_state.op_id': 2n, compact_lease: { $exists: false } })
1993
+ ).toBe(bucketCount);
1994
+ const usage = await db.objectStorageUsage.find({ '_id.g': ctx.replicationStreamId }).toArray();
1995
+ const activeBytes = usage.reduce((sum, doc) => sum + BigInt(doc.definitions[ctx.definitionId] ?? 0), 0n);
1996
+ expect(activeBytes).toBe(documents.reduce((sum, doc) => sum + BigInt(doc.storage_ref!.file_size), 0n));
1997
+ });
1998
+
1999
+ test.each([2, 3])('failed chunk worker stops queued work and drains siblings (%s workers)', async (workerCount) => {
2000
+ const { bucketStorage } = await setupV3();
2001
+ const compactor = bucketStorage.createMongoCompactor({}) as any;
2002
+ const failure = new Error('worker failed');
2003
+ const failing = Promise.withResolvers<void>();
2004
+ const draining = Promise.withResolvers<void>();
2005
+ const started: number[] = [];
2006
+ let settled = false;
2007
+ // The factory has two slots. With three workers, one also waits for a slot
2008
+ // when its sibling fails; it must release that slot without starting work.
2009
+ const run = compactor
2010
+ .runChunkCompactionWorkers(
2011
+ [0, 1, 2, 3, 4],
2012
+ Array.from({ length: workerCount }, () => compactor.objectStorageUsage),
2013
+ async (bucket: number) => {
2014
+ started.push(bucket);
2015
+ if (bucket === 0) await failing.promise;
2016
+ if (bucket === 1) await draining.promise;
2017
+ }
2018
+ )
2019
+ .then(
2020
+ () => undefined,
2021
+ (error: unknown) => error
2022
+ )
2023
+ .finally(() => {
2024
+ settled = true;
2025
+ });
2026
+ try {
2027
+ await vi.waitFor(() => expect(started).toEqual([0, 1]));
2028
+ failing.reject(failure);
2029
+ await new Promise<void>((resolve) => setImmediate(resolve));
2030
+ expect(started).toEqual([0, 1]);
2031
+ expect(settled).toBe(false);
2032
+ } finally {
2033
+ failing.resolve();
2034
+ draining.resolve();
2035
+ await run;
2036
+ }
2037
+ expect(await run).toBe(failure);
2038
+ expect(started).toEqual([0, 1]);
2039
+ expect(bucketStorage.factory.chunkCompactionSlots.getValue()).toBe(2);
2040
+ });
2041
+
2042
+ test('unforced chunk workers drain before full compaction and reclassify aged buckets on the next scan', async () => {
2043
+ const { bucketStorage, collection, bucketStateCollection, ctx, sourceTableId } = await setupV3();
2044
+ const buckets = ['chunk1[]', 'chunk2[]', 'promoted[]', 'full1[]', 'full2[]'];
2045
+ for (const bucket of buckets) {
2046
+ const documents = Array.from({ length: 8 }, (_, index) =>
2047
+ serializeBucketData(bucket, [makeOp(index + 1, String(index), 'data', { ...ctx, bucket }, sourceTableId)])
2048
+ );
2049
+ await collection.insertMany(documents);
2050
+ await bucketStateCollection.insertOne({
2051
+ _id: { d: ctx.definitionId, b: bucket },
2052
+ last_op: 8n,
2053
+ next_compact_check: new Date(0),
2054
+ first_uncompacted_write: bucket.startsWith('full') ? new Date(0) : new Date(),
2055
+ bucket_stats: { count: 8, bytes: BigInt(documents.reduce((sum, doc) => sum + doc.size, 0)), chunks: 8 }
2056
+ });
2057
+ }
2058
+
2059
+ const originalClaim = CompactionLease.claim.bind(CompactionLease);
2060
+ const jobStartedAt = new Date();
2061
+ let serverTime = jobStartedAt;
2062
+ let promoted = false;
2063
+ const claim = vi.spyOn(CompactionLease, 'claim').mockImplementation(async (...args) => {
2064
+ if (!promoted && (args[1]._id as { b: string })?.b === 'promoted[]') {
2065
+ promoted = true;
2066
+ // Advance the server clock past the full-compaction interval without
2067
+ // changing bucket state. A fixed scan timestamp would retry forever.
2068
+ serverTime = new Date(jobStartedAt.getTime() + 3 * 60 * 60 * 1000);
2069
+ }
2070
+ const lease = await originalClaim(...args);
2071
+ if (lease != null) {
2072
+ Object.defineProperty(lease, 'startedAt', { value: serverTime });
2073
+ }
2074
+ return lease;
2075
+ });
2076
+
2077
+ const compactor = bucketStorage.createMongoCompactor({ maxOpId: 8n });
2078
+ const internal = compactor as any;
2079
+ let timeReads = 0;
2080
+ const clock = vi.spyOn(internal, 'readCompactionTime').mockImplementation(async () => {
2081
+ if (++timeReads > 5) {
2082
+ throw new Error('Compaction did not advance after the bucket aged');
2083
+ }
2084
+ return serverTime;
2085
+ });
2086
+ const scan = vi.spyOn(internal, 'findScheduledBucketBatch');
2087
+ const originalChunks = internal.compactSingleBucketChunks.bind(compactor);
2088
+ const originalFull = internal.compactSingleBucketFully.bind(compactor);
2089
+ const gate = Promise.withResolvers<void>();
2090
+ let activeChunks = 0;
2091
+ let activeFull = 0;
2092
+ let maxActiveFull = 0;
2093
+ const fullBuckets: string[] = [];
2094
+ const chunks = vi.spyOn(internal, 'compactSingleBucketChunks').mockImplementation(async (...args) => {
2095
+ activeChunks++;
2096
+ try {
2097
+ await gate.promise;
2098
+ return await originalChunks(...args);
2099
+ } finally {
2100
+ activeChunks--;
2101
+ }
2102
+ });
2103
+ const full = vi.spyOn(internal, 'compactSingleBucketFully').mockImplementation(async (...args) => {
2104
+ expect(activeChunks).toBe(0);
2105
+ activeFull++;
2106
+ maxActiveFull = Math.max(maxActiveFull, activeFull);
2107
+ fullBuckets.push((args[0] as any).state._id.b);
2108
+ try {
2109
+ return await originalFull(...args);
2110
+ } finally {
2111
+ activeFull--;
2112
+ }
2113
+ });
2114
+ const run = compactor.compact();
2115
+ try {
2116
+ await vi.waitFor(() => expect(activeChunks).toBe(2));
2117
+ expect(full).not.toHaveBeenCalled();
2118
+ } finally {
2119
+ gate.resolve();
2120
+ try {
2121
+ await run;
2122
+ expect(timeReads).toBe(3); // Job start, first batch, and promoted bucket's batch.
2123
+ expect(scan).toHaveBeenCalledTimes(3); // Includes the final empty scan.
2124
+ for (const [cutoff] of scan.mock.calls) {
2125
+ expect(cutoff).toEqual(jobStartedAt);
2126
+ }
2127
+ } finally {
2128
+ clock.mockRestore();
2129
+ scan.mockRestore();
2130
+ claim.mockRestore();
2131
+ chunks.mockRestore();
2132
+ full.mockRestore();
2133
+ }
2134
+ }
2135
+ expect(await run).toBe(5);
2136
+ expect(promoted).toBe(true);
2137
+ expect(maxActiveFull).toBe(1);
2138
+ expect(fullBuckets.sort()).toEqual(['full1[]', 'full2[]', 'promoted[]']);
2139
+ expect(await bucketStateCollection.countDocuments({ compact_lease: { $exists: true } })).toBe(0);
2140
+ expect(await collection.countDocuments()).toBe(5);
2141
+ });
2142
+
1772
2143
  test('capped chunk compaction repairs stale bucket stats after a committed first merge', async () => {
1773
2144
  const { bucketStorage, collection, bucketStateCollection, ctx, sourceTableId } = await setupV3();
1774
2145
  const operations = [
@@ -1796,7 +2167,7 @@ bucket_definitions:
1796
2167
  }
1797
2168
  });
1798
2169
 
1799
- const result = await bucketStorage.compactInitialReplication({ maxOpId: 4n });
2170
+ const result = await bucketStorage.compactInitialReplication({ forceChunkCompaction: true, maxOpId: 4n });
1800
2171
 
1801
2172
  expect(result).toEqual({ buckets: 1 });
1802
2173
  const expectedStats = {
@@ -1915,9 +2286,9 @@ bucket_definitions:
1915
2286
  bucket_stats: { count: 4, bytes: 200n, chunks: 4 }
1916
2287
  });
1917
2288
 
1918
- await expect(bucketStorage.createMongoCompactor({ maxOpId: 4n, compactChunksOnly: true }).compact()).resolves.toBe(
1919
- 1
1920
- );
2289
+ await expect(
2290
+ bucketStorage.createMongoCompactor({ maxOpId: 4n, compactChunksOnly: true, forceChunkCompaction: true }).compact()
2291
+ ).resolves.toBe(1);
1921
2292
 
1922
2293
  const currentDocuments = await collection.find({ '_id.b': BUCKET }).sort({ '_id.o': 1 }).toArray();
1923
2294
  expect(currentDocuments.flatMap((document) => document.ops!.map((op) => op.o))).toEqual([1n, 3n, 4n]);
@@ -1937,7 +2308,8 @@ bucket_definitions:
1937
2308
  const { bucketStorage, collection, bucketStateCollection, ctx } = await setupCompactedTail();
1938
2309
  const failedCompactor = bucketStorage.createMongoCompactor({
1939
2310
  maxOpId: 4n,
1940
- compactChunksOnly: true
2311
+ compactChunksOnly: true,
2312
+ forceChunkCompaction: true
1941
2313
  });
1942
2314
  const originalFlush = (failedCompactor as any).flushCompactionGroup.bind(failedCompactor);
1943
2315
  const flushSpy = vi
@@ -1958,7 +2330,9 @@ bucket_definitions:
1958
2330
  { $set: { next_compact_check: new Date(0) } }
1959
2331
  );
1960
2332
 
1961
- const result = await bucketStorage.createMongoCompactor({ maxOpId: 4n, compactChunksOnly: true }).compact();
2333
+ const result = await bucketStorage
2334
+ .createMongoCompactor({ maxOpId: 4n, compactChunksOnly: true, forceChunkCompaction: true })
2335
+ .compact();
1962
2336
 
1963
2337
  expect(result).toBe(1);
1964
2338
  await expectRecoveredCompactedTail(collection, bucketStateCollection, ctx.definitionId);
@@ -1,6 +1,6 @@
1
1
  import { mongoTestStorageFactoryGenerator } from '@module/utils/test-utils.js';
2
2
  import { storage, updateSyncRulesFromYaml } from '@powersync/service-core';
3
- import { bucketRequest, test_utils } from '@powersync/service-core-tests';
3
+ import { bucketRequest, getTestStorage, test_utils } from '@powersync/service-core-tests';
4
4
  import { describe, expect, test } from 'vitest';
5
5
  import { DEFAULT_INLINE_THRESHOLD_BYTES } from '../../src/storage/implementation/common/PersistedBatch.js';
6
6
  import { MongoSyncBucketStorage } from '../../src/storage/implementation/createMongoSyncBucketStorage.js';
@@ -31,7 +31,7 @@ describe('Object storage inline threshold', () => {
31
31
  const { memoryStorage, factoryGen } = s3Factory();
32
32
  await using factory = await factoryGen.factory();
33
33
  const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(SYNC_RULES_YAML, { storageVersion: 3 }));
34
- const bucketStorage = factory.getInstance(syncRules) as MongoSyncBucketStorage;
34
+ const bucketStorage = (await getTestStorage(factory, syncRules)) as MongoSyncBucketStorage;
35
35
  expect(bucketStorage.inlineThresholdBytes).toBe(DEFAULT_INLINE_THRESHOLD_BYTES);
36
36
 
37
37
  await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
@@ -77,7 +77,7 @@ describe('Object storage inline threshold', () => {
77
77
  const { memoryStorage, factoryGen } = s3Factory(256);
78
78
  await using factory = await factoryGen.factory();
79
79
  const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(SYNC_RULES_YAML, { storageVersion: 3 }));
80
- const bucketStorage = factory.getInstance(syncRules) as MongoSyncBucketStorage;
80
+ const bucketStorage = (await getTestStorage(factory, syncRules)) as MongoSyncBucketStorage;
81
81
  expect(bucketStorage.inlineThresholdBytes).toBe(256);
82
82
 
83
83
  await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
@@ -4,7 +4,7 @@ import {
4
4
  storage,
5
5
  updateSyncRulesFromYaml
6
6
  } from '@powersync/service-core';
7
- import { bucketRequest, compactActive, test_utils } from '@powersync/service-core-tests';
7
+ import { bucketRequest, compactActive, getTestStorage, test_utils } from '@powersync/service-core-tests';
8
8
  import { describe, expect, test } from 'vitest';
9
9
  import { MongoSyncBucketStorage } from '../../src/storage/implementation/createMongoSyncBucketStorage.js';
10
10
  import { VersionedPowerSyncMongoV3 } from '../../src/storage/implementation/v3/VersionedPowerSyncMongoV3.js';
@@ -28,7 +28,7 @@ describe('V3 checksums with S3 object storage', () => {
28
28
  const { factoryGen } = s3Factory();
29
29
  await using factory = await factoryGen.factory();
30
30
  const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(SYNC_RULES_YAML, { storageVersion: 3 }));
31
- const bucketStorage = factory.getInstance(syncRules) as MongoSyncBucketStorage;
31
+ const bucketStorage = (await getTestStorage(factory, syncRules)) as MongoSyncBucketStorage;
32
32
  const db = bucketStorage.db as VersionedPowerSyncMongoV3;
33
33
 
34
34
  const request = bucketRequest(syncRules.syncConfigContent[0], 'global[]', 0n);
@@ -75,7 +75,7 @@ describe('V3 checksums with S3 object storage', () => {
75
75
  const { factoryGen } = s3Factory();
76
76
  await using factory = await factoryGen.factory();
77
77
  const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(SYNC_RULES_YAML, { storageVersion: 3 }));
78
- const bucketStorage = factory.getInstance(syncRules) as MongoSyncBucketStorage;
78
+ const bucketStorage = (await getTestStorage(factory, syncRules)) as MongoSyncBucketStorage;
79
79
  const db = bucketStorage.db as VersionedPowerSyncMongoV3;
80
80
 
81
81
  const request = bucketRequest(syncRules.syncConfigContent[0], 'global[]', 0n);
@@ -114,7 +114,7 @@ describe('V3 checksums with S3 object storage', () => {
114
114
  const { factoryGen } = s3Factory();
115
115
  await using factory = await factoryGen.factory();
116
116
  const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(SYNC_RULES_YAML, { storageVersion: 3 }));
117
- const bucketStorage = factory.getInstance(syncRules) as MongoSyncBucketStorage;
117
+ const bucketStorage = (await getTestStorage(factory, syncRules)) as MongoSyncBucketStorage;
118
118
  const db = bucketStorage.db as VersionedPowerSyncMongoV3;
119
119
 
120
120
  const request = bucketRequest(syncRules.syncConfigContent[0], 'global[]', 0n);