@powersync/service-module-mongodb-storage 0.22.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/storage/MongoBucketStorage.d.ts +5 -1
  3. package/dist/storage/MongoBucketStorage.js +30 -6
  4. package/dist/storage/MongoBucketStorage.js.map +1 -1
  5. package/dist/storage/implementation/MongoBucketBatch.d.ts +23 -10
  6. package/dist/storage/implementation/MongoBucketBatch.js +132 -88
  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/MongoSyncBucketStorage.d.ts +5 -0
  17. package/dist/storage/implementation/MongoSyncBucketStorage.js +17 -0
  18. package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
  19. package/dist/storage/implementation/MongoSyncRulesLock.d.ts +26 -2
  20. package/dist/storage/implementation/MongoSyncRulesLock.js +38 -1
  21. package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -1
  22. package/dist/storage/implementation/db.js +1 -0
  23. package/dist/storage/implementation/db.js.map +1 -1
  24. package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +5 -0
  25. package/dist/storage/implementation/v1/MongoBucketBatchV1.js +136 -133
  26. package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -1
  27. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +1 -1
  28. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js.map +1 -1
  29. package/dist/storage/implementation/v3/MongoBucketBatchV3.d.ts +5 -1
  30. package/dist/storage/implementation/v3/MongoBucketBatchV3.js +131 -148
  31. package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -1
  32. package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +4 -3
  33. package/dist/storage/implementation/v3/MongoCompactorV3.js +14 -10
  34. package/dist/storage/implementation/v3/MongoCompactorV3.js.map +1 -1
  35. package/dist/storage/implementation/v3/compact-utils.d.ts +3 -2
  36. package/dist/storage/implementation/v3/compact-utils.js +12 -5
  37. package/dist/storage/implementation/v3/compact-utils.js.map +1 -1
  38. package/dist/storage/implementation/v3/models.d.ts +1 -1
  39. package/dist/utils/test-utils.d.ts +2 -1
  40. package/dist/utils/test-utils.js +4 -3
  41. package/dist/utils/test-utils.js.map +1 -1
  42. package/package.json +7 -7
  43. package/src/storage/MongoBucketStorage.ts +35 -7
  44. package/src/storage/implementation/MongoBucketBatch.ts +201 -110
  45. package/src/storage/implementation/MongoCompactor.ts +2 -0
  46. package/src/storage/implementation/MongoIdSequence.ts +21 -1
  47. package/src/storage/implementation/MongoOpIdAllocator.ts +105 -0
  48. package/src/storage/implementation/MongoSyncBucketStorage.ts +19 -0
  49. package/src/storage/implementation/MongoSyncRulesLock.ts +56 -3
  50. package/src/storage/implementation/db.ts +1 -0
  51. package/src/storage/implementation/models.ts +1 -0
  52. package/src/storage/implementation/v1/MongoBucketBatchV1.ts +150 -152
  53. package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +1 -1
  54. package/src/storage/implementation/v3/MongoBucketBatchV3.ts +167 -194
  55. package/src/storage/implementation/v3/MongoCompactorV3.ts +19 -22
  56. package/src/storage/implementation/v3/compact-utils.ts +16 -7
  57. package/src/storage/implementation/v3/models.ts +1 -1
  58. package/src/utils/test-utils.ts +5 -3
  59. package/test/src/__snapshots__/storage.test.ts.snap +102 -2
  60. package/test/src/__snapshots__/storage_compacting.test.ts.snap +2 -2
  61. package/test/src/__snapshots__/storage_sync.test.ts.snap +12 -12
  62. package/test/src/cleanup-stopped-sync-configs.test.ts +19 -13
  63. package/test/src/compact-utils.test.ts +33 -8
  64. package/test/src/concurrent_writers.test.ts +599 -0
  65. package/test/src/helpers/concurrentWriter.mjs +29 -0
  66. package/test/src/helpers/s3TestFactory.ts +12 -12
  67. package/test/src/object_storage_usage.test.ts +2 -1
  68. package/test/src/parameter_compacting_v1.test.ts +2 -2
  69. package/test/src/parameter_compacting_v3.test.ts +2 -2
  70. package/test/src/parameter_compaction_fence.test.ts +3 -3
  71. package/test/src/storage.test.ts +32 -33
  72. package/test/src/storage_compacting.test.ts +147 -21
  73. package/test/src/storage_object_storage_inline_threshold.test.ts +3 -3
  74. package/test/src/storage_s3_checksums.test.ts +4 -4
  75. package/test/src/storage_s3_compaction_lifecycle.test.ts +8 -8
  76. package/test/src/storage_s3_reading.test.ts +4 -4
  77. package/test/src/storage_s3_writing.test.ts +3 -3
  78. package/test/src/storage_sync.test.ts +52 -46
  79. package/test/src/storeCurrentData.test.ts +5 -5
  80. package/tsconfig.tsbuildinfo +1 -1
@@ -7,12 +7,13 @@ import { PowerSyncMongo } from '../storage/implementation/db.js';
7
7
  export type MongoTestStorageOptions = {
8
8
  url: string;
9
9
  isCI: boolean;
10
+ monitorCommands?: boolean;
10
11
  } & Omit<MongoBucketStorageOptions, 'replicationStreamNamePrefix'>;
11
12
 
12
13
  export function mongoTestStorageFactoryGenerator(factoryOptions: MongoTestStorageOptions) {
13
14
  return {
14
15
  factory: async (options?: TestStorageOptions) => {
15
- const db = connectMongoForTests(factoryOptions.url, factoryOptions.isCI);
16
+ const db = connectMongoForTests(factoryOptions.url, factoryOptions.isCI, factoryOptions.monitorCommands);
16
17
 
17
18
  // None of the tests insert data into this collection, so it was never created
18
19
  if (!(await db.db.listCollections({ name: db.bucket_parameters.collectionName }).hasNext())) {
@@ -41,7 +42,7 @@ export function mongoTestStorageFactoryGenerator(factoryOptions: MongoTestStorag
41
42
 
42
43
  export function mongoTestReportStorageFactoryGenerator(factoryOptions: MongoTestStorageOptions) {
43
44
  return async (options?: TestStorageOptions) => {
44
- const db = connectMongoForTests(factoryOptions.url, factoryOptions.isCI);
45
+ const db = connectMongoForTests(factoryOptions.url, factoryOptions.isCI, factoryOptions.monitorCommands);
45
46
 
46
47
  await db.createConnectionReportingCollection();
47
48
 
@@ -53,10 +54,11 @@ export function mongoTestReportStorageFactoryGenerator(factoryOptions: MongoTest
53
54
  };
54
55
  }
55
56
 
56
- export const connectMongoForTests = (url: string, isCI: boolean) => {
57
+ export const connectMongoForTests = (url: string, isCI: boolean, monitorCommands = false) => {
57
58
  // Short timeout for tests, to fail fast when the server is not available.
58
59
  // Slightly longer timeouts for CI, to avoid arbitrary test failures
59
60
  const client = new mongo.MongoClient(url, {
61
+ monitorCommands,
60
62
  connectTimeoutMS: isCI ? 15_000 : 5_000,
61
63
  socketTimeoutMS: isCI ? 15_000 : 5_000,
62
64
  serverSelectionTimeoutMS: isCI ? 15_000 : 2_500
@@ -100,7 +100,7 @@ exports[`Mongo Sync Bucket Storage - Data - v4 > (insert, delete, insert), (dele
100
100
  ]
101
101
  `;
102
102
 
103
- exports[`Mongo Sync Bucket Storage - split buckets > (insert, delete, insert), (delete) 1`] = `
103
+ exports[`Mongo Sync Bucket Storage - split buckets - v1 > (insert, delete, insert), (delete) 1`] = `
104
104
  [
105
105
  {
106
106
  "checksum": 2871785649,
@@ -125,7 +125,107 @@ exports[`Mongo Sync Bucket Storage - split buckets > (insert, delete, insert), (
125
125
  ]
126
126
  `;
127
127
 
128
- exports[`Mongo Sync Bucket Storage - split operations > (insert, delete, insert), (delete) 1`] = `
128
+ exports[`Mongo Sync Bucket Storage - split buckets - v2 > (insert, delete, insert), (delete) 1`] = `
129
+ [
130
+ {
131
+ "checksum": 2871785649,
132
+ "object_id": "test1",
133
+ "op": "PUT",
134
+ },
135
+ {
136
+ "checksum": 2872534815,
137
+ "object_id": "test1",
138
+ "op": "REMOVE",
139
+ },
140
+ {
141
+ "checksum": 2871785649,
142
+ "object_id": "test1",
143
+ "op": "PUT",
144
+ },
145
+ {
146
+ "checksum": 2872534815,
147
+ "object_id": "test1",
148
+ "op": "REMOVE",
149
+ },
150
+ ]
151
+ `;
152
+
153
+ exports[`Mongo Sync Bucket Storage - split buckets - v4 > (insert, delete, insert), (delete) 1`] = `
154
+ [
155
+ {
156
+ "checksum": 2871785649,
157
+ "object_id": "test1",
158
+ "op": "PUT",
159
+ },
160
+ {
161
+ "checksum": 2872534815,
162
+ "object_id": "test1",
163
+ "op": "REMOVE",
164
+ },
165
+ {
166
+ "checksum": 2871785649,
167
+ "object_id": "test1",
168
+ "op": "PUT",
169
+ },
170
+ {
171
+ "checksum": 2872534815,
172
+ "object_id": "test1",
173
+ "op": "REMOVE",
174
+ },
175
+ ]
176
+ `;
177
+
178
+ exports[`Mongo Sync Bucket Storage - split operations - v1 > (insert, delete, insert), (delete) 1`] = `
179
+ [
180
+ {
181
+ "checksum": 2871785649,
182
+ "object_id": "test1",
183
+ "op": "PUT",
184
+ },
185
+ {
186
+ "checksum": 2872534815,
187
+ "object_id": "test1",
188
+ "op": "REMOVE",
189
+ },
190
+ {
191
+ "checksum": 2871785649,
192
+ "object_id": "test1",
193
+ "op": "PUT",
194
+ },
195
+ {
196
+ "checksum": 2872534815,
197
+ "object_id": "test1",
198
+ "op": "REMOVE",
199
+ },
200
+ ]
201
+ `;
202
+
203
+ exports[`Mongo Sync Bucket Storage - split operations - v2 > (insert, delete, insert), (delete) 1`] = `
204
+ [
205
+ {
206
+ "checksum": 2871785649,
207
+ "object_id": "test1",
208
+ "op": "PUT",
209
+ },
210
+ {
211
+ "checksum": 2872534815,
212
+ "object_id": "test1",
213
+ "op": "REMOVE",
214
+ },
215
+ {
216
+ "checksum": 2871785649,
217
+ "object_id": "test1",
218
+ "op": "PUT",
219
+ },
220
+ {
221
+ "checksum": 2872534815,
222
+ "object_id": "test1",
223
+ "op": "REMOVE",
224
+ },
225
+ ]
226
+ `;
227
+
228
+ exports[`Mongo Sync Bucket Storage - split operations - v4 > (insert, delete, insert), (delete) 1`] = `
129
229
  [
130
230
  {
131
231
  "checksum": 2871785649,
@@ -2,7 +2,7 @@
2
2
 
3
3
  exports[`Mongo Sync Bucket Storage Compact > partial checksums after compacting (2) 1`] = `
4
4
  {
5
- "bucket": "1#global[]",
5
+ "bucket": "global.1.1[]",
6
6
  "checksum": -1481659821,
7
7
  "count": 1,
8
8
  }
@@ -10,7 +10,7 @@ exports[`Mongo Sync Bucket Storage Compact > partial checksums after compacting
10
10
 
11
11
  exports[`Mongo Sync Bucket Storage Compact > partial checksums after compacting 1`] = `
12
12
  {
13
- "bucket": "1#global[]",
13
+ "bucket": "global.1.1[]",
14
14
  "checksum": 1874612650,
15
15
  "count": 4,
16
16
  }
@@ -270,7 +270,7 @@ exports[`sync - mongodb > storage v1 > encodes sync rules id in buckets for stre
270
270
  ],
271
271
  },
272
272
  ],
273
- "last_op_id": "2",
273
+ "last_op_id": "65537",
274
274
  "streams": [
275
275
  {
276
276
  "errors": [],
@@ -292,17 +292,17 @@ exports[`sync - mongodb > storage v1 > encodes sync rules id in buckets for stre
292
292
  "object_id": "t1",
293
293
  "object_type": "test",
294
294
  "op": "PUT",
295
- "op_id": "2",
295
+ "op_id": "65537",
296
296
  "subkey": "bfe6a7fc-1a36-5a95-877f-518ff63ecb56",
297
297
  },
298
298
  ],
299
299
  "has_more": false,
300
- "next_after": "2",
300
+ "next_after": "65537",
301
301
  },
302
302
  },
303
303
  {
304
304
  "checkpoint_complete": {
305
- "last_op_id": "2",
305
+ "last_op_id": "65537",
306
306
  },
307
307
  },
308
308
  ]
@@ -1265,7 +1265,7 @@ exports[`sync - mongodb > storage v2 > encodes sync rules id in buckets for stre
1265
1265
  ],
1266
1266
  },
1267
1267
  ],
1268
- "last_op_id": "2",
1268
+ "last_op_id": "65537",
1269
1269
  "streams": [
1270
1270
  {
1271
1271
  "errors": [],
@@ -1287,17 +1287,17 @@ exports[`sync - mongodb > storage v2 > encodes sync rules id in buckets for stre
1287
1287
  "object_id": "t1",
1288
1288
  "object_type": "test",
1289
1289
  "op": "PUT",
1290
- "op_id": "2",
1290
+ "op_id": "65537",
1291
1291
  "subkey": "bfe6a7fc-1a36-5a95-877f-518ff63ecb56",
1292
1292
  },
1293
1293
  ],
1294
1294
  "has_more": false,
1295
- "next_after": "2",
1295
+ "next_after": "65537",
1296
1296
  },
1297
1297
  },
1298
1298
  {
1299
1299
  "checkpoint_complete": {
1300
- "last_op_id": "2",
1300
+ "last_op_id": "65537",
1301
1301
  },
1302
1302
  },
1303
1303
  ]
@@ -2849,7 +2849,7 @@ exports[`sync - mongodb > storage v4 > encodes sync rules id in buckets for stre
2849
2849
  ],
2850
2850
  },
2851
2851
  ],
2852
- "last_op_id": "2",
2852
+ "last_op_id": "65537",
2853
2853
  "streams": [
2854
2854
  {
2855
2855
  "errors": [],
@@ -2871,17 +2871,17 @@ exports[`sync - mongodb > storage v4 > encodes sync rules id in buckets for stre
2871
2871
  "object_id": "t1",
2872
2872
  "object_type": "test",
2873
2873
  "op": "PUT",
2874
- "op_id": "2",
2874
+ "op_id": "65537",
2875
2875
  "subkey": "bfe6a7fc-1a36-5a95-877f-518ff63ecb56",
2876
2876
  },
2877
2877
  ],
2878
2878
  "has_more": false,
2879
- "next_after": "2",
2879
+ "next_after": "65537",
2880
2880
  },
2881
2881
  },
2882
2882
  {
2883
2883
  "checkpoint_complete": {
2884
- "last_op_id": "2",
2884
+ "last_op_id": "65537",
2885
2885
  },
2886
2886
  },
2887
2887
  ]
@@ -2,7 +2,7 @@ import { MongoSyncBucketStorageV3 } from '@module/storage/implementation/v3/Mong
2
2
  import { ObjectStorageLifecycle } from '@module/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js';
3
3
  import { mongoTestStorageFactoryGenerator } from '@module/utils/test-utils.js';
4
4
  import { storage, updateSyncRulesFromYaml } from '@powersync/service-core';
5
- import { test_utils } from '@powersync/service-core-tests';
5
+ import { getTestStorage, test_utils } from '@powersync/service-core-tests';
6
6
  import * as bson from 'bson';
7
7
  import { describe, expect, test } from 'vitest';
8
8
  import { VersionedPowerSyncMongoV3 } from '../../src/storage/implementation/v3/VersionedPowerSyncMongoV3.js';
@@ -78,7 +78,7 @@ streams:
78
78
  { storageVersion: 3 }
79
79
  )
80
80
  );
81
- const firstStorage = factory.getInstance(first) as MongoSyncBucketStorageV3;
81
+ const firstStorage = (await getTestStorage(factory, first)) as MongoSyncBucketStorageV3;
82
82
  const db = firstStorage.db as VersionedPowerSyncMongoV3;
83
83
  await using firstWriter = await firstStorage.createWriter(test_utils.BATCH_OPTIONS);
84
84
 
@@ -155,7 +155,7 @@ streams:
155
155
  );
156
156
  expect(second.replicationStreamId).toBe(first.replicationStreamId);
157
157
  const replicatingStreams = await factory.getReplicatingReplicationStreams();
158
- const secondStorage = factory.getInstance(replicatingStreams[0]) as MongoSyncBucketStorageV3;
158
+ const secondStorage = (await getTestStorage(factory, replicatingStreams[0])) as MongoSyncBucketStorageV3;
159
159
  await using secondWriter = await secondStorage.createWriter(test_utils.BATCH_OPTIONS);
160
160
  await secondWriter.markAllSnapshotDone('2/1');
161
161
  await secondWriter.commit('2/1');
@@ -218,7 +218,7 @@ streams:
218
218
  { storageVersion: 3 }
219
219
  )
220
220
  );
221
- const firstStorage = factory.getInstance(first) as MongoSyncBucketStorageV3;
221
+ const firstStorage = (await getTestStorage(factory, first)) as MongoSyncBucketStorageV3;
222
222
  const db = firstStorage.db as VersionedPowerSyncMongoV3;
223
223
  await using firstWriter = await firstStorage.createWriter(test_utils.BATCH_OPTIONS);
224
224
  const resolved = await firstWriter.resolveTables({
@@ -266,7 +266,7 @@ streams:
266
266
  expect(secondDefinitionId).toBe(projectDefinitionId);
267
267
 
268
268
  const replicatingStreams = await factory.getReplicatingReplicationStreams();
269
- const secondStorage = factory.getInstance(replicatingStreams[0]) as MongoSyncBucketStorageV3;
269
+ const secondStorage = (await getTestStorage(factory, replicatingStreams[0])) as MongoSyncBucketStorageV3;
270
270
  await using secondWriter = await secondStorage.createWriter(test_utils.BATCH_OPTIONS);
271
271
  await secondWriter.markAllSnapshotDone('2/1');
272
272
  await secondWriter.commit('2/1');
@@ -289,7 +289,10 @@ streams:
289
289
  expect(await collectionExists(db, ownerBucketDataCollection)).toBe(false);
290
290
  expect(await collectionExists(db, sourceRecordsCollection)).toBe(true);
291
291
 
292
- const activeStorage = (await factory.getActiveSyncConfig())!.storage as MongoSyncBucketStorageV3;
292
+ const activeStorage = (await getTestStorage(
293
+ factory,
294
+ (await factory.getActiveSyncConfig())!.replicationStream
295
+ )) as MongoSyncBucketStorageV3;
293
296
  await using activeWriter = await activeStorage.createWriter(test_utils.BATCH_OPTIONS);
294
297
  const activeTable = (
295
298
  await activeWriter.resolveTables({
@@ -343,7 +346,7 @@ event_definitions:
343
346
  { storageVersion: 3 }
344
347
  )
345
348
  );
346
- const firstStorage = factory.getInstance(first) as MongoSyncBucketStorageV3;
349
+ const firstStorage = (await getTestStorage(factory, first)) as MongoSyncBucketStorageV3;
347
350
  const db = firstStorage.db as VersionedPowerSyncMongoV3;
348
351
  await using firstWriter = await firstStorage.createWriter(test_utils.BATCH_OPTIONS);
349
352
  const resolved = await firstWriter.resolveTables({
@@ -402,7 +405,7 @@ event_definitions:
402
405
  expect(second.replicationStreamId).toBe(first.replicationStreamId);
403
406
 
404
407
  const replicatingStreams = await factory.getReplicatingReplicationStreams();
405
- const secondStorage = factory.getInstance(replicatingStreams[0]) as MongoSyncBucketStorageV3;
408
+ const secondStorage = (await getTestStorage(factory, replicatingStreams[0])) as MongoSyncBucketStorageV3;
406
409
  await using secondWriter = await secondStorage.createWriter(test_utils.BATCH_OPTIONS);
407
410
  await secondWriter.markAllSnapshotDone('2/1');
408
411
  await secondWriter.commit('2/1');
@@ -466,7 +469,7 @@ event_definitions:
466
469
  { storageVersion: 3 }
467
470
  )
468
471
  );
469
- const firstStorage = factory.getInstance(first) as MongoSyncBucketStorageV3;
472
+ const firstStorage = (await getTestStorage(factory, first)) as MongoSyncBucketStorageV3;
470
473
  const db = firstStorage.db as VersionedPowerSyncMongoV3;
471
474
  await using firstWriter = await firstStorage.createWriter(test_utils.BATCH_OPTIONS);
472
475
 
@@ -538,7 +541,7 @@ streams:
538
541
  expect(second.replicationStreamId).toBe(first.replicationStreamId);
539
542
 
540
543
  const replicatingStreams = await factory.getReplicatingReplicationStreams();
541
- const secondStorage = factory.getInstance(replicatingStreams[0]) as MongoSyncBucketStorageV3;
544
+ const secondStorage = (await getTestStorage(factory, replicatingStreams[0])) as MongoSyncBucketStorageV3;
542
545
  await using secondWriter = await secondStorage.createWriter(test_utils.BATCH_OPTIONS);
543
546
  await secondWriter.markAllSnapshotDone('2/1');
544
547
  await secondWriter.commit('2/1');
@@ -599,7 +602,7 @@ streams:
599
602
  { storageVersion: 3 }
600
603
  )
601
604
  );
602
- const firstStorage = factory.getInstance(first) as MongoSyncBucketStorageV3;
605
+ const firstStorage = (await getTestStorage(factory, first)) as MongoSyncBucketStorageV3;
603
606
  const db = firstStorage.db as VersionedPowerSyncMongoV3;
604
607
  await using firstWriter = await firstStorage.createWriter(test_utils.BATCH_OPTIONS);
605
608
  const resolved = await firstWriter.resolveTables({
@@ -652,7 +655,7 @@ streams:
652
655
  expect(secondIndexId).toBe(roleIndexId);
653
656
 
654
657
  const replicatingStreams = await factory.getReplicatingReplicationStreams();
655
- const secondStorage = factory.getInstance(replicatingStreams[0]) as MongoSyncBucketStorageV3;
658
+ const secondStorage = (await getTestStorage(factory, replicatingStreams[0])) as MongoSyncBucketStorageV3;
656
659
  await using secondWriter = await secondStorage.createWriter(test_utils.BATCH_OPTIONS);
657
660
  await secondWriter.markAllSnapshotDone('2/1');
658
661
  await secondWriter.commit('2/1');
@@ -664,7 +667,10 @@ streams:
664
667
  expect(await collectionExists(db, orgParameterIndexCollection)).toBe(false);
665
668
  expect(await collectionExists(db, sourceRecordsCollection)).toBe(true);
666
669
 
667
- const activeStorage = (await factory.getActiveSyncConfig())!.storage as MongoSyncBucketStorageV3;
670
+ const activeStorage = (await getTestStorage(
671
+ factory,
672
+ (await factory.getActiveSyncConfig())!.replicationStream
673
+ )) as MongoSyncBucketStorageV3;
668
674
  await using activeWriter = await activeStorage.createWriter(test_utils.BATCH_OPTIONS);
669
675
  const activeTable = (
670
676
  await activeWriter.resolveTables({
@@ -3,12 +3,12 @@ import {
3
3
  applyStatsReplacement,
4
4
  bucketStats,
5
5
  chooseCompactionKind,
6
+ chooseRequestedCompactionKind,
6
7
  combineAdjacentStats,
7
8
  combineChunkStats,
8
9
  CompactIntervalConfig,
9
10
  CompactionKind,
10
11
  emptyBucketStats,
11
- forcedCompactionKind,
12
12
  fullCompactionCheckAt,
13
13
  statsForDocument,
14
14
  statsForDocuments
@@ -130,44 +130,69 @@ describe('V3 compact utilities', () => {
130
130
  test.each([
131
131
  {
132
132
  name: 'does not force an unspecified kind',
133
- forceKind: undefined,
133
+ requestedKind: undefined,
134
134
  compactedOpId: undefined,
135
135
  maxOpIdCap: undefined,
136
136
  expected: null
137
137
  },
138
138
  {
139
139
  name: 'forces work without previous compacted state',
140
- forceKind: CompactionKind.Chunks,
140
+ requestedKind: CompactionKind.Chunks,
141
141
  compactedOpId: undefined,
142
142
  maxOpIdCap: undefined,
143
143
  expected: CompactionKind.Chunks
144
144
  },
145
145
  {
146
146
  name: 'skips work already compacted through the cap',
147
- forceKind: CompactionKind.Chunks,
147
+ requestedKind: CompactionKind.Chunks,
148
148
  compactedOpId: 5n,
149
149
  maxOpIdCap: 5n,
150
150
  expected: null
151
151
  },
152
152
  {
153
153
  name: 'skips work already compacted through the bucket head',
154
- forceKind: CompactionKind.Chunks,
154
+ requestedKind: CompactionKind.Chunks,
155
155
  compactedOpId: 10n,
156
156
  maxOpIdCap: undefined,
157
157
  expected: null
158
158
  },
159
159
  {
160
160
  name: 'forces work beyond the compacted state',
161
- forceKind: CompactionKind.Chunks,
161
+ requestedKind: CompactionKind.Chunks,
162
162
  compactedOpId: 5n,
163
163
  maxOpIdCap: 6n,
164
164
  expected: CompactionKind.Chunks
165
165
  }
166
- ])('$name', ({ forceKind, compactedOpId, maxOpIdCap, expected }) => {
166
+ ])('$name', ({ requestedKind, compactedOpId, maxOpIdCap, expected }) => {
167
167
  const state = bucketState({
168
+ bucket_stats: { count: 10, bytes: 100n, chunks: 9 },
168
169
  compacted_state: compactedOpId == null ? undefined : compactedState({ op_id: compactedOpId })
169
170
  });
170
- expect(forcedCompactionKind(state, forceKind, { maxOpIdCap })).toBe(expected);
171
+ expect(chooseRequestedCompactionKind(state, requestedKind, { maxOpIdCap })).toBe(expected);
172
+ });
173
+
174
+ test.each([undefined, compactedState({ at: new Date() })])(
175
+ 'initial chunk compaction requires eight new chunks, regardless of the interval: %o',
176
+ (compacted) => {
177
+ for (const newChunks of [1, 7, 8]) {
178
+ const state = bucketState({
179
+ bucket_stats: { count: 10, bytes: 100n, chunks: (compacted?.chunks ?? 0) + newChunks },
180
+ compacted_state: compacted
181
+ });
182
+ expect(chooseRequestedCompactionKind(state, CompactionKind.Chunks, { maxOpIdCap: undefined })).toBe(
183
+ newChunks < 8 ? null : CompactionKind.Chunks
184
+ );
185
+ }
186
+ }
187
+ );
188
+
189
+ test('the testing override bypasses the chunk threshold but preserves the compaction watermark', () => {
190
+ const state = bucketState();
191
+ const config = { maxOpIdCap: undefined, forceChunkCompaction: true };
192
+ expect(chooseRequestedCompactionKind(state, CompactionKind.Chunks, config)).toBe(CompactionKind.Chunks);
193
+ expect(chooseRequestedCompactionKind(state, undefined, config)).toBeNull();
194
+ state.compacted_state = compactedState({ op_id: state.last_op });
195
+ expect(chooseRequestedCompactionKind(state, CompactionKind.Chunks, config)).toBeNull();
171
196
  });
172
197
 
173
198
  test('derives and combines bucket statistics', () => {