@powersync/service-module-mongodb-storage 0.20.0 → 0.22.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 (192) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/dist/storage/MongoBucketStorage.d.ts +36 -3
  3. package/dist/storage/MongoBucketStorage.js +372 -207
  4. package/dist/storage/MongoBucketStorage.js.map +1 -1
  5. package/dist/storage/implementation/MongoBucketBatch.d.ts +17 -5
  6. package/dist/storage/implementation/MongoBucketBatch.js +44 -17
  7. package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
  8. package/dist/storage/implementation/MongoCompactor.d.ts +19 -79
  9. package/dist/storage/implementation/MongoCompactor.js +27 -238
  10. package/dist/storage/implementation/MongoCompactor.js.map +1 -1
  11. package/dist/storage/implementation/MongoParameterCompactor.d.ts +83 -10
  12. package/dist/storage/implementation/MongoParameterCompactor.js +309 -96
  13. package/dist/storage/implementation/MongoParameterCompactor.js.map +1 -1
  14. package/dist/storage/implementation/MongoParsedSyncConfigSet.d.ts +9 -1
  15. package/dist/storage/implementation/MongoParsedSyncConfigSet.js +29 -0
  16. package/dist/storage/implementation/MongoParsedSyncConfigSet.js.map +1 -1
  17. package/dist/storage/implementation/MongoPersistedSyncConfigContent.js +4 -2
  18. package/dist/storage/implementation/MongoPersistedSyncConfigContent.js.map +1 -1
  19. package/dist/storage/implementation/MongoStorageProvider.js +7 -3
  20. package/dist/storage/implementation/MongoStorageProvider.js.map +1 -1
  21. package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +27 -11
  22. package/dist/storage/implementation/MongoSyncBucketStorage.js +78 -31
  23. package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
  24. package/dist/storage/implementation/MongoSyncRulesLock.js +21 -9
  25. package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -1
  26. package/dist/storage/implementation/MongoWriteBatch.d.ts +49 -0
  27. package/dist/storage/implementation/MongoWriteBatch.js +149 -0
  28. package/dist/storage/implementation/MongoWriteBatch.js.map +1 -0
  29. package/dist/storage/implementation/MongoWriteCheckpointAPI.d.ts +9 -7
  30. package/dist/storage/implementation/MongoWriteCheckpointAPI.js +9 -30
  31. package/dist/storage/implementation/MongoWriteCheckpointAPI.js.map +1 -1
  32. package/dist/storage/implementation/SyncRuleStateUpdate.d.ts +11 -5
  33. package/dist/storage/implementation/SyncRuleStateUpdate.js +14 -6
  34. package/dist/storage/implementation/SyncRuleStateUpdate.js.map +1 -1
  35. package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.d.ts +22 -2
  36. package/dist/storage/implementation/common/PersistedBatch.d.ts +32 -4
  37. package/dist/storage/implementation/common/PersistedBatch.js +49 -6
  38. package/dist/storage/implementation/common/PersistedBatch.js.map +1 -1
  39. package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.d.ts +3 -0
  40. package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js +3 -0
  41. package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js.map +1 -1
  42. package/dist/storage/implementation/db.d.ts +9 -1
  43. package/dist/storage/implementation/db.js +27 -1
  44. package/dist/storage/implementation/db.js.map +1 -1
  45. package/dist/storage/implementation/models.d.ts +31 -0
  46. package/dist/storage/implementation/models.js.map +1 -1
  47. package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +2 -0
  48. package/dist/storage/implementation/v1/MongoBucketBatchV1.js +33 -4
  49. package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -1
  50. package/dist/storage/implementation/v1/MongoCompactorV1.d.ts +38 -6
  51. package/dist/storage/implementation/v1/MongoCompactorV1.js +247 -20
  52. package/dist/storage/implementation/v1/MongoCompactorV1.js.map +1 -1
  53. package/dist/storage/implementation/v1/MongoParameterCompactorV1.d.ts +16 -2
  54. package/dist/storage/implementation/v1/MongoParameterCompactorV1.js +20 -9
  55. package/dist/storage/implementation/v1/MongoParameterCompactorV1.js.map +1 -1
  56. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.d.ts +18 -11
  57. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +65 -16
  58. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js.map +1 -1
  59. package/dist/storage/implementation/v1/PersistedBatchV1.d.ts +7 -5
  60. package/dist/storage/implementation/v1/PersistedBatchV1.js +22 -28
  61. package/dist/storage/implementation/v1/PersistedBatchV1.js.map +1 -1
  62. package/dist/storage/implementation/v1/models.js +1 -0
  63. package/dist/storage/implementation/v1/models.js.map +1 -1
  64. package/dist/storage/implementation/v3/CompactionLease.d.ts +50 -0
  65. package/dist/storage/implementation/v3/CompactionLease.js +131 -0
  66. package/dist/storage/implementation/v3/CompactionLease.js.map +1 -0
  67. package/dist/storage/implementation/v3/MongoBucketBatchV3.d.ts +10 -2
  68. package/dist/storage/implementation/v3/MongoBucketBatchV3.js +100 -26
  69. package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -1
  70. package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +82 -25
  71. package/dist/storage/implementation/v3/MongoCompactorV3.js +698 -159
  72. package/dist/storage/implementation/v3/MongoCompactorV3.js.map +1 -1
  73. package/dist/storage/implementation/v3/MongoParameterCompactorV3.d.ts +18 -0
  74. package/dist/storage/implementation/v3/MongoParameterCompactorV3.js +23 -0
  75. package/dist/storage/implementation/v3/MongoParameterCompactorV3.js.map +1 -0
  76. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.d.ts +2 -15
  77. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js +60 -92
  78. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js.map +1 -1
  79. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.d.ts +19 -9
  80. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +91 -31
  81. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -1
  82. package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.d.ts +26 -0
  83. package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.js +82 -0
  84. package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.js.map +1 -0
  85. package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +14 -8
  86. package/dist/storage/implementation/v3/PersistedBatchV3.js +76 -42
  87. package/dist/storage/implementation/v3/PersistedBatchV3.js.map +1 -1
  88. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.d.ts +12 -2
  89. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js +42 -3
  90. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js.map +1 -1
  91. package/dist/storage/implementation/v3/bucket-format.js +2 -0
  92. package/dist/storage/implementation/v3/bucket-format.js.map +1 -1
  93. package/dist/storage/implementation/v3/compact-utils.d.ts +107 -0
  94. package/dist/storage/implementation/v3/compact-utils.js +216 -0
  95. package/dist/storage/implementation/v3/compact-utils.js.map +1 -0
  96. package/dist/storage/implementation/v3/compaction-constants.d.ts +5 -0
  97. package/dist/storage/implementation/v3/compaction-constants.js +6 -0
  98. package/dist/storage/implementation/v3/compaction-constants.js.map +1 -0
  99. package/dist/storage/implementation/v3/models.d.ts +84 -4
  100. package/dist/storage/implementation/v3/models.js.map +1 -1
  101. package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.d.ts +7 -8
  102. package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js +16 -5
  103. package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js.map +1 -1
  104. package/dist/storage/implementation/v3/object-storage/ObjectStorage.d.ts +14 -11
  105. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.d.ts +6 -8
  106. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js +17 -11
  107. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js.map +1 -1
  108. package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.d.ts +38 -0
  109. package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.js +164 -0
  110. package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.js.map +1 -0
  111. package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.d.ts +37 -12
  112. package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js +197 -25
  113. package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js.map +1 -1
  114. package/dist/storage/implementation/v3/source-table-utils.d.ts +6 -6
  115. package/dist/storage/implementation/v3/source-table-utils.js +30 -37
  116. package/dist/storage/implementation/v3/source-table-utils.js.map +1 -1
  117. package/dist/storage/storage-index.d.ts +2 -1
  118. package/dist/storage/storage-index.js +1 -0
  119. package/dist/storage/storage-index.js.map +1 -1
  120. package/dist/types/types.d.ts +28 -1
  121. package/dist/types/types.js +28 -7
  122. package/dist/types/types.js.map +1 -1
  123. package/dist/utils/test-utils.js +1 -0
  124. package/dist/utils/test-utils.js.map +1 -1
  125. package/dist/utils/util.js +3 -1
  126. package/dist/utils/util.js.map +1 -1
  127. package/package.json +10 -9
  128. package/src/storage/MongoBucketStorage.ts +507 -269
  129. package/src/storage/implementation/MongoBucketBatch.ts +67 -24
  130. package/src/storage/implementation/MongoCompactor.ts +35 -346
  131. package/src/storage/implementation/MongoParameterCompactor.ts +388 -102
  132. package/src/storage/implementation/MongoParsedSyncConfigSet.ts +34 -0
  133. package/src/storage/implementation/MongoPersistedSyncConfigContent.ts +4 -2
  134. package/src/storage/implementation/MongoStorageProvider.ts +15 -3
  135. package/src/storage/implementation/MongoSyncBucketStorage.ts +114 -40
  136. package/src/storage/implementation/MongoSyncRulesLock.ts +22 -9
  137. package/src/storage/implementation/MongoWriteBatch.ts +172 -0
  138. package/src/storage/implementation/MongoWriteCheckpointAPI.ts +16 -46
  139. package/src/storage/implementation/SyncRuleStateUpdate.ts +13 -6
  140. package/src/storage/implementation/common/MongoSyncBucketStorageCheckpoint.ts +24 -2
  141. package/src/storage/implementation/common/PersistedBatch.ts +66 -10
  142. package/src/storage/implementation/common/VersionedPowerSyncMongoBase.ts +4 -0
  143. package/src/storage/implementation/db.ts +35 -2
  144. package/src/storage/implementation/models.ts +34 -0
  145. package/src/storage/implementation/v1/MongoBucketBatchV1.ts +46 -6
  146. package/src/storage/implementation/v1/MongoCompactorV1.ts +327 -40
  147. package/src/storage/implementation/v1/MongoParameterCompactorV1.ts +25 -9
  148. package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +87 -27
  149. package/src/storage/implementation/v1/PersistedBatchV1.ts +25 -30
  150. package/src/storage/implementation/v1/models.ts +1 -0
  151. package/src/storage/implementation/v3/CompactionLease.ts +152 -0
  152. package/src/storage/implementation/v3/MongoBucketBatchV3.ts +138 -34
  153. package/src/storage/implementation/v3/MongoCompactorV3.ts +958 -225
  154. package/src/storage/implementation/v3/MongoParameterCompactorV3.ts +34 -0
  155. package/src/storage/implementation/v3/MongoStoppedSyncConfigCleanup.ts +100 -109
  156. package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +111 -41
  157. package/src/storage/implementation/v3/MongoWriteCheckpointAPIV3.ts +111 -0
  158. package/src/storage/implementation/v3/PersistedBatchV3.ts +93 -48
  159. package/src/storage/implementation/v3/VersionedPowerSyncMongoV3.ts +55 -4
  160. package/src/storage/implementation/v3/bucket-format.ts +2 -0
  161. package/src/storage/implementation/v3/compact-utils.ts +320 -0
  162. package/src/storage/implementation/v3/compaction-constants.ts +5 -0
  163. package/src/storage/implementation/v3/models.ts +91 -3
  164. package/src/storage/implementation/v3/object-storage/BucketDataObjectStorage.ts +24 -9
  165. package/src/storage/implementation/v3/object-storage/ObjectStorage.ts +20 -5
  166. package/src/storage/implementation/v3/object-storage/ObjectStorageLifecycle.ts +26 -14
  167. package/src/storage/implementation/v3/object-storage/ObjectStorageUsage.ts +229 -0
  168. package/src/storage/implementation/v3/object-storage/S3ObjectStorage.ts +279 -29
  169. package/src/storage/implementation/v3/source-table-utils.ts +39 -41
  170. package/src/storage/storage-index.ts +2 -0
  171. package/src/types/types.ts +42 -7
  172. package/src/utils/test-utils.ts +1 -0
  173. package/src/utils/util.ts +3 -1
  174. package/test/src/__snapshots__/storage.test.ts.snap +25 -0
  175. package/test/src/__snapshots__/storage_sync.test.ts.snap +605 -16
  176. package/test/src/chunk_compaction_config.test.ts +38 -0
  177. package/test/src/cleanup-stopped-sync-configs.test.ts +40 -13
  178. package/test/src/compact-utils.test.ts +226 -0
  179. package/test/src/helpers/MemoryObjectStorage.ts +14 -6
  180. package/test/src/object_storage_usage.test.ts +267 -0
  181. package/test/src/parameter_compacting_v1.test.ts +120 -0
  182. package/test/src/parameter_compacting_v3.test.ts +192 -0
  183. package/test/src/parameter_compaction_fence.test.ts +276 -0
  184. package/test/src/storage.test.ts +273 -22
  185. package/test/src/storage_compacting.test.ts +968 -246
  186. package/test/src/storage_s3_checksums.test.ts +3 -4
  187. package/test/src/storage_s3_compaction_lifecycle.test.ts +138 -12
  188. package/test/src/storage_s3_reading.test.ts +290 -2
  189. package/test/src/storage_s3_writing.test.ts +1 -0
  190. package/test/src/storage_sync.test.ts +336 -41
  191. package/test/src/util.ts +1 -1
  192. package/tsconfig.tsbuildinfo +1 -1
@@ -3,6 +3,8 @@ import { mongo } from '@powersync/lib-service-mongodb';
3
3
  import { ServiceAssertionError } from '@powersync/lib-services-framework';
4
4
  import {
5
5
  CheckpointChanges,
6
+ CompactInitialReplicationOptions,
7
+ CompactInitialReplicationResults,
6
8
  GetCheckpointChangesOptions,
7
9
  InternalOpId,
8
10
  internalToExternalOpId,
@@ -18,12 +20,20 @@ import * as bson from 'bson';
18
20
  import { mapOpEntry, readSingleBatch, setSessionSnapshotTime } from '../../../utils/util.js';
19
21
  import { MongoBucketStorage } from '../../MongoBucketStorage.js';
20
22
  import { BucketDataDoc } from '../common/BucketDataDoc.js';
21
- import { MongoSyncBucketStorageCheckpoint } from '../common/MongoSyncBucketStorageCheckpoint.js';
23
+ import {
24
+ MongoGetCheckpointChangesOptions,
25
+ MongoSyncBucketStorageCheckpoint
26
+ } from '../common/MongoSyncBucketStorageCheckpoint.js';
22
27
  import { MongoChecksums } from '../MongoChecksums.js';
23
28
  import { MongoCompactOptions, MongoCompactor } from '../MongoCompactor.js';
24
29
  import { MongoParameterCompactor } from '../MongoParameterCompactor.js';
25
30
  import { MongoPersistedReplicationStream } from '../MongoPersistedReplicationStream.js';
26
- import { MongoSyncBucketStorage, MongoSyncBucketStorageOptions } from '../MongoSyncBucketStorage.js';
31
+ import {
32
+ MongoCheckpointState,
33
+ MongoSyncBucketStorage,
34
+ MongoSyncBucketStorageOptions
35
+ } from '../MongoSyncBucketStorage.js';
36
+ import { MongoCheckpointAPIOptions } from '../MongoWriteCheckpointAPI.js';
27
37
  import { loadBucketDataDocument, maxOpId } from './bucket-format.js';
28
38
  import {
29
39
  BucketDataDocumentV3,
@@ -36,10 +46,13 @@ import {
36
46
  import { MongoBucketBatchV3 } from './MongoBucketBatchV3.js';
37
47
  import { MongoChecksumsV3 } from './MongoChecksumsV3.js';
38
48
  import { MongoCompactorV3 } from './MongoCompactorV3.js';
49
+ import { MongoParameterCompactorV3 } from './MongoParameterCompactorV3.js';
39
50
  import { MongoStoppedSyncConfigCleanup } from './MongoStoppedSyncConfigCleanup.js';
51
+ import { MongoWriteCheckpointAPIV3 } from './MongoWriteCheckpointAPIV3.js';
40
52
  import { hydrateBucketDataDocuments } from './object-storage/BucketDataObjectStorage.js';
41
53
  import { ObjectStorage } from './object-storage/ObjectStorage.js';
42
54
  import { ObjectStorageLifecycle } from './object-storage/ObjectStorageLifecycle.js';
55
+ import { ObjectStorageUsage } from './object-storage/ObjectStorageUsage.js';
43
56
  import { VersionedPowerSyncMongoV3 } from './VersionedPowerSyncMongoV3.js';
44
57
 
45
58
  export interface MongoSyncBucketStorageContextV3 {
@@ -188,21 +201,39 @@ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
188
201
  });
189
202
  }
190
203
 
204
+ protected override createWriteCheckpointAPI(options: MongoCheckpointAPIOptions): MongoWriteCheckpointAPIV3 {
205
+ return new MongoWriteCheckpointAPIV3({
206
+ ...options,
207
+ db: this.db,
208
+ syncConfigMapping: () => this.singleSyncConfigMapping()
209
+ });
210
+ }
211
+
191
212
  createMongoCompactor(options: MongoCompactOptions): MongoCompactor {
192
213
  return new MongoCompactorV3(this, this.db, options);
193
214
  }
194
215
 
216
+ override async compactInitialReplication(
217
+ options: CompactInitialReplicationOptions
218
+ ): Promise<CompactInitialReplicationResults> {
219
+ this.logger.info(`Compacting chunks after initial replication...`);
220
+ const start = Date.now();
221
+ const maxOpId = options.maxOpId ?? (await this.fetchPersistedOpHead()) ?? undefined;
222
+ const compactedBuckets = await this.createMongoCompactor({
223
+ ...options,
224
+ maxOpId,
225
+ compactChunksOnly: true,
226
+ logger: this.logger
227
+ }).compact();
228
+ this.logger.info(`Compacted chunks after initial replication in ${(Date.now() - start) / 1000}s`);
229
+ return { buckets: compactedBuckets };
230
+ }
231
+
195
232
  protected createMongoParameterCompactor(
196
233
  checkpoint: InternalOpId,
197
234
  options: storage.CompactOptions
198
235
  ): MongoParameterCompactor {
199
- return new MongoParameterCompactor(this.db, this.replicationStreamId, checkpoint, options, () =>
200
- this.db
201
- .listParameterIndexCollections(this.replicationStreamId)
202
- .then((collections) =>
203
- collections.map((c) => c.collection as unknown as lib_mongo.mongo.Collection<lib_mongo.mongo.Document>)
204
- )
205
- );
236
+ return new MongoParameterCompactorV3(this.db, this.replicationStreamId, checkpoint, options);
206
237
  }
207
238
 
208
239
  protected async fetchPersistedOpHead(): Promise<InternalOpId | null> {
@@ -214,13 +245,14 @@ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
214
245
  }
215
246
 
216
247
  protected async createWriterImpl(options: storage.CreateWriterOptions): Promise<storage.BucketStorageBatch> {
248
+ const batchOptions = this.writerBatchOptions(options);
217
249
  const doc = await this.syncRulesCollection.findOne(
218
250
  { _id: this.replicationStreamId },
219
251
  { projection: { resume_lsn: 1 } }
220
252
  );
221
253
 
222
254
  return new MongoBucketBatchV3({
223
- ...this.writerBatchOptions(options),
255
+ ...batchOptions,
224
256
  // The stream-level replication position - per-config checkpoint LSNs are consistency
225
257
  // markers and do not affect where replication resumes.
226
258
  resumeFromLsn: doc?.resume_lsn ?? null,
@@ -228,16 +260,15 @@ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
228
260
  });
229
261
  }
230
262
 
231
- protected async fetchCheckpointState(
232
- session: mongo.ClientSession
233
- ): Promise<{ checkpoint: bigint; lsn: string | null } | null> {
263
+ protected async fetchCheckpointState(session: mongo.ClientSession): Promise<MongoCheckpointState | null> {
234
264
  const doc = await this.syncRulesCollection.findOne(
235
265
  this.syncConfigMatch({
236
266
  state: { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] }
237
267
  }),
238
268
  {
239
269
  session,
240
- projection: this.syncConfigProjection()
270
+ // The invalidation fence must be read in the same snapshot as the checkpoint.
271
+ projection: this.syncConfigProjection({ 'parameter_compaction.checkpoint_changes_invalid_before': 1 })
241
272
  }
242
273
  );
243
274
  // Checkpoints are served from the single active config. A PROCESSING config in the same
@@ -258,7 +289,10 @@ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
258
289
  }
259
290
  return {
260
291
  checkpoint: syncConfig.last_checkpoint ?? 0n,
261
- lsn: syncConfig.last_checkpoint_lsn ?? null
292
+ lsn: syncConfig.last_checkpoint_lsn ?? null,
293
+ // Stream-level state: shared by all sync configs. Defaults to 0n for streams that have
294
+ // never been compacted.
295
+ parameterChangesInvalidBefore: doc?.parameter_compaction?.checkpoint_changes_invalid_before ?? 0n
262
296
  };
263
297
  }
264
298
 
@@ -311,7 +345,8 @@ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
311
345
  },
312
346
  $unset: {
313
347
  resume_lsn: 1,
314
- last_persisted_op: 1
348
+ last_persisted_op: 1,
349
+ parameter_compaction: 1
315
350
  }
316
351
  },
317
352
  {
@@ -376,6 +411,8 @@ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
376
411
  for (const collection of await this.db.listBucketDataCollections(this.replicationStreamId)) {
377
412
  await collection.drop();
378
413
  }
414
+ const usage = new ObjectStorageUsage(this.db, this.replicationStreamId);
415
+ await this.db.client.withSession((session) => session.withTransaction(() => usage.removeStream(session)));
379
416
  if (this.objectStorage) {
380
417
  const lifecycle = new ObjectStorageLifecycle(this.db, this.replicationStreamId, this.objectStorage);
381
418
  await lifecycle.deletePrefix(lifecycle.streamPrefix(), { signal });
@@ -384,7 +421,7 @@ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
384
421
  .pendingObjectStorageDeletes(this.replicationStreamId)
385
422
  .drop({ maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS })
386
423
  .catch((error) => {
387
- if (lib_mongo.isMongoServerError(error) && error.codeName === 'NamespaceNotFound') {
424
+ if (lib_mongo.isMongoNamespaceNotFoundError(error)) {
388
425
  return;
389
426
  }
390
427
  throw error;
@@ -408,7 +445,7 @@ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
408
445
  .bucketState(this.replicationStreamId)
409
446
  .drop({ maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS })
410
447
  .catch((error) => {
411
- if (lib_mongo.isMongoServerError(error) && error.codeName === 'NamespaceNotFound') {
448
+ if (lib_mongo.isMongoNamespaceNotFoundError(error)) {
412
449
  return;
413
450
  }
414
451
  throw error;
@@ -420,11 +457,23 @@ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
420
457
  .sourceTables(this.replicationStreamId)
421
458
  .drop({ maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS })
422
459
  .catch((error) => {
460
+ if (lib_mongo.isMongoNamespaceNotFoundError(error)) {
461
+ return;
462
+ }
463
+ throw error;
464
+ });
465
+ }
466
+
467
+ protected override async clearCustomCheckpointRequests(signal?: AbortSignal): Promise<void> {
468
+ for (const collection of await this.db.listCustomCheckpointRequestCollections(this.replicationStreamId)) {
469
+ signal?.throwIfAborted();
470
+ await collection.drop({ maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS }).catch((error) => {
423
471
  if (lib_mongo.isMongoServerError(error) && error.codeName === 'NamespaceNotFound') {
424
472
  return;
425
473
  }
426
474
  throw error;
427
475
  });
476
+ }
428
477
  }
429
478
 
430
479
  async cleanupStoppedSyncConfigs(
@@ -449,7 +498,7 @@ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
449
498
  }
450
499
 
451
500
  protected getParameterBucketChangesImpl(
452
- options: GetCheckpointChangesOptions
501
+ options: MongoGetCheckpointChangesOptions
453
502
  ): Promise<Pick<CheckpointChanges, 'updatedParameterLookups' | 'invalidateParameterBuckets'>> {
454
503
  return getParameterBucketChangesV3(this.versionContext, options);
455
504
  }
@@ -647,7 +696,7 @@ export async function* getBucketDataBatchV3(
647
696
  // Hydrate any operations from object storage.
648
697
  // In the future we can do this in a more pipelined fashion, but for now we hydrate
649
698
  // the entire batch at once.
650
- await hydrateBucketDataDocuments(docs, ctx.objectStorage, { signal: options?.signal });
699
+ await hydrateBucketDataDocuments(docs, ctx.objectStorage, { signal: options?.signal, tracer: options?.tracer });
651
700
 
652
701
  let currentChunkSizeBytes = 0;
653
702
  let currentChunk: utils.SyncBucketData | null = null;
@@ -784,9 +833,17 @@ export async function getDataBucketChangesV3(
784
833
  };
785
834
  }
786
835
 
836
+ /**
837
+ * Query the parameter entries changed between the two checkpoints, to determine which parameter
838
+ * lookups need to be re-evaluated.
839
+ *
840
+ * This runs at the next checkpoint's snapshot, so it still sees entries that parameter compaction
841
+ * deleted after that snapshot. Compaction that deleted entries before the snapshot is covered by
842
+ * the invalidation fence, checked before we get here.
843
+ */
787
844
  export async function getParameterBucketChangesV3(
788
845
  ctx: MongoSyncBucketStorageContextV3,
789
- options: GetCheckpointChangesOptions
846
+ options: MongoGetCheckpointChangesOptions
790
847
  ): Promise<Pick<CheckpointChanges, 'updatedParameterLookups' | 'invalidateParameterBuckets'>> {
791
848
  const limit = 1000;
792
849
  const indexIds = ctx.mapping.allParameterIndexIds();
@@ -816,28 +873,41 @@ export async function getParameterBucketChangesV3(
816
873
  }
817
874
  ];
818
875
  const [firstCollection, ...remainingCollections] = collections;
819
- const parameterUpdates = await firstCollection.collection
820
- .aggregate<{ lookup: bson.Binary; indexId: string }>(
821
- [
822
- ...pipelineForCollection(firstCollection.indexId),
823
- ...remainingCollections.map((collection) => {
824
- return {
825
- $unionWith: {
826
- coll: collection.collection.collectionName,
827
- pipeline: pipelineForCollection(collection.indexId)
828
- }
829
- };
830
- }),
876
+ const parameterUpdates = await ctx.db.client.withSession({ snapshot: true }, async (session) => {
877
+ setSessionSnapshotTime(session, options.nextCheckpoint.snapshotTime);
878
+ return await firstCollection.collection
879
+ .aggregate<{ lookup: bson.Binary; indexId: string }>(
880
+ [
881
+ ...pipelineForCollection(firstCollection.indexId),
882
+ ...remainingCollections.map((collection) => {
883
+ return {
884
+ $unionWith: {
885
+ coll: collection.collection.collectionName,
886
+ pipeline: pipelineForCollection(collection.indexId)
887
+ }
888
+ };
889
+ }),
890
+ {
891
+ $limit: limit + 1
892
+ }
893
+ ],
831
894
  {
832
- $limit: limit + 1
895
+ session,
896
+ readConcern: 'snapshot',
897
+ batchSize: limit + 2,
898
+ maxTimeMS: lib_mongo.db.MONGO_OPERATION_TIMEOUT_MS
833
899
  }
834
- ],
835
- {
836
- batchSize: limit + 2,
837
- maxTimeMS: lib_mongo.db.MONGO_OPERATION_TIMEOUT_MS
838
- }
839
- )
840
- .toArray();
900
+ )
901
+ .toArray()
902
+ .catch((e) => {
903
+ // Includes the case where the checkpoint snapshot has expired. Degrading to
904
+ // invalidateParameterBuckets would be safe in itself - it reads nothing - but it gains
905
+ // nothing: the caller responds to that by re-evaluating the parameter queries at this
906
+ // same snapshot, which fails too. The checkpoint has to be refetched instead, which is
907
+ // what the existing sync retry behavior does.
908
+ throw lib_mongo.mapQueryError(e, 'while querying parameter changes');
909
+ });
910
+ });
841
911
 
842
912
  const invalidateParameterUpdates = parameterUpdates.length > limit;
843
913
 
@@ -0,0 +1,111 @@
1
+ import { ServiceAssertionError } from '@powersync/lib-services-framework';
2
+ import {
3
+ CustomWriteCheckpointFilters,
4
+ GetCheckpointChangesOptions,
5
+ SingleSyncConfigBucketDefinitionMapping,
6
+ storage
7
+ } from '@powersync/service-core';
8
+ import { EventDefinitionId } from '@powersync/service-sync-rules';
9
+ import { MongoCheckpointAPIOptions, MongoWriteCheckpointAPI } from '../MongoWriteCheckpointAPI.js';
10
+ import { VersionedPowerSyncMongoV3 } from './VersionedPowerSyncMongoV3.js';
11
+
12
+ export type MongoCheckpointAPIV3Options = Omit<MongoCheckpointAPIOptions, 'db'> & {
13
+ db: VersionedPowerSyncMongoV3;
14
+ syncConfigMapping: () => SingleSyncConfigBucketDefinitionMapping;
15
+ };
16
+
17
+ export class MongoWriteCheckpointAPIV3 extends MongoWriteCheckpointAPI {
18
+ declare db: VersionedPowerSyncMongoV3;
19
+
20
+ // Supplied via the constructor or the setWriteCheckpointMode setter.
21
+ private _customWriteCheckpointEventName: string | undefined;
22
+ private readonly syncConfigMapping: () => SingleSyncConfigBucketDefinitionMapping;
23
+
24
+ constructor(options: MongoCheckpointAPIV3Options) {
25
+ super(options);
26
+ this.syncConfigMapping = options.syncConfigMapping;
27
+ // Enforce the CUSTOM-requires-event-name invariant on construction too, not only setWriteCheckpointMode.
28
+ this._customWriteCheckpointEventName = this.requireCustomEventName(options.writeCheckpointMode);
29
+ }
30
+
31
+ protected resolveEventId(): EventDefinitionId {
32
+ const eventName = this._customWriteCheckpointEventName;
33
+ if (!eventName) {
34
+ throw new ServiceAssertionError(`No eventName has been supplied via setWriteCheckpointMode.`);
35
+ }
36
+ return this.syncConfigMapping().eventDefinitionIdByName(eventName);
37
+ }
38
+
39
+ protected override async lastCustomWriteCheckpoint(filters: CustomWriteCheckpointFilters): Promise<bigint | null> {
40
+ const { user_id } = filters;
41
+ const eventId = this.resolveEventId();
42
+
43
+ const lastWriteCheckpoint = await this.db
44
+ .customCheckpointRequests({
45
+ eventId,
46
+ replicationStreamId: this.replicationStreamId
47
+ })
48
+ .findOne({
49
+ user_id
50
+ });
51
+ return lastWriteCheckpoint?.checkpoint ?? null;
52
+ }
53
+
54
+ override setWriteCheckpointMode(config: storage.WriteCheckpointModeConfig): void {
55
+ this._customWriteCheckpointEventName = this.requireCustomEventName(config);
56
+ super.setWriteCheckpointMode(config);
57
+ }
58
+
59
+ /**
60
+ * Validates and returns the event name for the given mode config. Shared by the constructor and
61
+ * {@link setWriteCheckpointMode} so both paths enforce that CUSTOM mode always names its checkpoint event.
62
+ */
63
+ private requireCustomEventName(config: storage.WriteCheckpointModeConfig): string | undefined {
64
+ if (config.mode != storage.WriteCheckpointMode.CUSTOM) {
65
+ return undefined;
66
+ }
67
+ if (!config.eventName) {
68
+ throw new ServiceAssertionError(`V3 incremental reprocessing requires an eventName to be supplied.`);
69
+ }
70
+ return config.eventName;
71
+ }
72
+
73
+ protected override async getCustomWriteCheckpointChanges(
74
+ options: GetCheckpointChangesOptions
75
+ ): Promise<{ invalidateWriteCheckpoints: boolean; updatedWriteCheckpoints: Map<string, bigint> }> {
76
+ const eventId = this.resolveEventId();
77
+
78
+ const limit = 1000;
79
+ const changes = await this.db
80
+ .customCheckpointRequests({
81
+ replicationStreamId: this.replicationStreamId,
82
+ eventId
83
+ })
84
+ .find(
85
+ {
86
+ op_id: { $gt: options.lastCheckpoint.checkpoint, $lte: options.nextCheckpoint.checkpoint }
87
+ },
88
+ {
89
+ limit: limit + 1,
90
+ // batchSize is 1 more than limit to auto-close the cursor.
91
+ // See https://github.com/mongodb/node-mongodb-native/pull/4580
92
+ batchSize: limit + 2,
93
+ singleBatch: true
94
+ }
95
+ )
96
+ .toArray();
97
+ const invalidate = changes.length > limit;
98
+
99
+ const updatedWriteCheckpoints = new Map<string, bigint>();
100
+ if (!invalidate) {
101
+ for (let c of changes) {
102
+ updatedWriteCheckpoints.set(c.user_id, c.checkpoint);
103
+ }
104
+ }
105
+
106
+ return {
107
+ invalidateWriteCheckpoints: invalidate,
108
+ updatedWriteCheckpoints
109
+ };
110
+ }
111
+ }