@powersync/service-module-mongodb-storage 0.18.2 → 0.19.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 (143) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/dist/migrations/db/migrations/1782950400000-checkpoint-requested-at-index.d.ts +3 -0
  3. package/dist/migrations/db/migrations/1782950400000-checkpoint-requested-at-index.js +41 -0
  4. package/dist/migrations/db/migrations/1782950400000-checkpoint-requested-at-index.js.map +1 -0
  5. package/dist/storage/MongoBucketStorage.d.ts +5 -1
  6. package/dist/storage/MongoBucketStorage.js +5 -1
  7. package/dist/storage/MongoBucketStorage.js.map +1 -1
  8. package/dist/storage/implementation/MongoBucketBatch.d.ts +3 -0
  9. package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
  10. package/dist/storage/implementation/MongoChecksums.d.ts +2 -0
  11. package/dist/storage/implementation/MongoChecksums.js +3 -2
  12. package/dist/storage/implementation/MongoChecksums.js.map +1 -1
  13. package/dist/storage/implementation/MongoCompactor.d.ts +15 -19
  14. package/dist/storage/implementation/MongoCompactor.js +68 -260
  15. package/dist/storage/implementation/MongoCompactor.js.map +1 -1
  16. package/dist/storage/implementation/MongoStorageProvider.js +26 -3
  17. package/dist/storage/implementation/MongoStorageProvider.js.map +1 -1
  18. package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +12 -5
  19. package/dist/storage/implementation/MongoSyncBucketStorage.js +25 -14
  20. package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
  21. package/dist/storage/implementation/MongoWriteCheckpointAPI.d.ts +2 -1
  22. package/dist/storage/implementation/MongoWriteCheckpointAPI.js +146 -34
  23. package/dist/storage/implementation/MongoWriteCheckpointAPI.js.map +1 -1
  24. package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.d.ts +2 -0
  25. package/dist/storage/implementation/common/PersistedBatch.d.ts +6 -0
  26. package/dist/storage/implementation/common/PersistedBatch.js +15 -8
  27. package/dist/storage/implementation/common/PersistedBatch.js.map +1 -1
  28. package/dist/storage/implementation/models.d.ts +16 -0
  29. package/dist/storage/implementation/models.js.map +1 -1
  30. package/dist/storage/implementation/v1/MongoCompactorV1.d.ts +11 -2
  31. package/dist/storage/implementation/v1/MongoCompactorV1.js +250 -1
  32. package/dist/storage/implementation/v1/MongoCompactorV1.js.map +1 -1
  33. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +20 -11
  34. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js.map +1 -1
  35. package/dist/storage/implementation/v1/SingleBucketStoreV1.d.ts +9 -10
  36. package/dist/storage/implementation/v1/SingleBucketStoreV1.js +2 -8
  37. package/dist/storage/implementation/v1/SingleBucketStoreV1.js.map +1 -1
  38. package/dist/storage/implementation/v3/BucketDataContextV3.d.ts +13 -0
  39. package/dist/storage/implementation/v3/BucketDataContextV3.js +23 -0
  40. package/dist/storage/implementation/v3/BucketDataContextV3.js.map +1 -0
  41. package/dist/storage/implementation/v3/MongoBucketBatchV3.js +3 -1
  42. package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -1
  43. package/dist/storage/implementation/v3/MongoChecksumsV3.d.ts +1 -0
  44. package/dist/storage/implementation/v3/MongoChecksumsV3.js +59 -103
  45. package/dist/storage/implementation/v3/MongoChecksumsV3.js.map +1 -1
  46. package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +23 -2
  47. package/dist/storage/implementation/v3/MongoCompactorV3.js +354 -219
  48. package/dist/storage/implementation/v3/MongoCompactorV3.js.map +1 -1
  49. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.d.ts +5 -0
  50. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js +12 -11
  51. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js.map +1 -1
  52. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.d.ts +5 -3
  53. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +109 -106
  54. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -1
  55. package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +5 -2
  56. package/dist/storage/implementation/v3/PersistedBatchV3.js +53 -15
  57. package/dist/storage/implementation/v3/PersistedBatchV3.js.map +1 -1
  58. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.d.ts +2 -1
  59. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js +3 -0
  60. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js.map +1 -1
  61. package/dist/storage/implementation/v3/bucket-format.d.ts +10 -1
  62. package/dist/storage/implementation/v3/bucket-format.js +28 -9
  63. package/dist/storage/implementation/v3/bucket-format.js.map +1 -1
  64. package/dist/storage/implementation/v3/chunking.js +3 -1
  65. package/dist/storage/implementation/v3/chunking.js.map +1 -1
  66. package/dist/storage/implementation/v3/models.d.ts +38 -1
  67. package/dist/storage/implementation/v3/models.js.map +1 -1
  68. package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.d.ts +21 -0
  69. package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js +44 -0
  70. package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js.map +1 -0
  71. package/dist/storage/implementation/v3/object-storage/ObjectStorage.d.ts +30 -0
  72. package/dist/storage/implementation/v3/object-storage/ObjectStorage.js +12 -0
  73. package/dist/storage/implementation/v3/object-storage/ObjectStorage.js.map +1 -0
  74. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.d.ts +70 -0
  75. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js +146 -0
  76. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js.map +1 -0
  77. package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.d.ts +42 -0
  78. package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js +285 -0
  79. package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js.map +1 -0
  80. package/dist/types/types.d.ts +35 -0
  81. package/dist/types/types.js +35 -1
  82. package/dist/types/types.js.map +1 -1
  83. package/dist/utils/test-utils.js +3 -1
  84. package/dist/utils/test-utils.js.map +1 -1
  85. package/dist/utils/util.d.ts +8 -1
  86. package/dist/utils/util.js +125 -2
  87. package/dist/utils/util.js.map +1 -1
  88. package/package.json +10 -7
  89. package/src/migrations/db/migrations/1782950400000-checkpoint-requested-at-index.ts +55 -0
  90. package/src/storage/MongoBucketStorage.ts +10 -2
  91. package/src/storage/implementation/MongoBucketBatch.ts +4 -0
  92. package/src/storage/implementation/MongoChecksums.ts +5 -2
  93. package/src/storage/implementation/MongoCompactor.ts +90 -314
  94. package/src/storage/implementation/MongoStorageProvider.ts +32 -3
  95. package/src/storage/implementation/MongoSyncBucketStorage.ts +34 -24
  96. package/src/storage/implementation/MongoWriteCheckpointAPI.ts +164 -37
  97. package/src/storage/implementation/common/MongoSyncBucketStorageCheckpoint.ts +2 -0
  98. package/src/storage/implementation/common/PersistedBatch.ts +22 -10
  99. package/src/storage/implementation/models.ts +16 -0
  100. package/src/storage/implementation/v1/MongoCompactorV1.ts +292 -8
  101. package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +54 -40
  102. package/src/storage/implementation/v1/SingleBucketStoreV1.ts +18 -27
  103. package/src/storage/implementation/v3/BucketDataContextV3.ts +32 -0
  104. package/src/storage/implementation/v3/MongoBucketBatchV3.ts +3 -1
  105. package/src/storage/implementation/v3/MongoChecksumsV3.ts +75 -105
  106. package/src/storage/implementation/v3/MongoCompactorV3.ts +456 -258
  107. package/src/storage/implementation/v3/MongoStoppedSyncConfigCleanup.ts +21 -19
  108. package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +123 -126
  109. package/src/storage/implementation/v3/PersistedBatchV3.ts +69 -16
  110. package/src/storage/implementation/v3/VersionedPowerSyncMongoV3.ts +5 -0
  111. package/src/storage/implementation/v3/bucket-format.ts +36 -9
  112. package/src/storage/implementation/v3/chunking.ts +3 -1
  113. package/src/storage/implementation/v3/models.ts +40 -1
  114. package/src/storage/implementation/v3/object-storage/BucketDataObjectStorage.ts +54 -0
  115. package/src/storage/implementation/v3/object-storage/ObjectStorage.ts +29 -0
  116. package/src/storage/implementation/v3/object-storage/ObjectStorageLifecycle.ts +171 -0
  117. package/src/storage/implementation/v3/object-storage/S3ObjectStorage.ts +343 -0
  118. package/src/types/types.ts +42 -1
  119. package/src/utils/test-utils.ts +3 -1
  120. package/src/utils/util.ts +182 -4
  121. package/test/src/__snapshots__/storage_sync.test.ts.snap +1 -583
  122. package/test/src/cleanup-stopped-sync-configs.test.ts +24 -1
  123. package/test/src/helpers/MemoryObjectStorage.ts +53 -0
  124. package/test/src/helpers/s3TestFactory.ts +69 -0
  125. package/test/src/object_storage_lifecycle.test.ts +59 -0
  126. package/test/src/setup.ts +6 -1
  127. package/test/src/storage.test.ts +216 -2
  128. package/test/src/storage_compacting.test.ts +176 -187
  129. package/test/src/storage_object_storage_inline_threshold.test.ts +116 -0
  130. package/test/src/storage_s3_checksums.test.ts +191 -0
  131. package/test/src/storage_s3_compaction_lifecycle.test.ts +417 -0
  132. package/test/src/storage_s3_reading.test.ts +526 -0
  133. package/test/src/storage_s3_writing.test.ts +135 -0
  134. package/test/src/storage_sync.test.ts +95 -5
  135. package/tsconfig.tsbuildinfo +1 -1
  136. package/dist/storage/implementation/common/SingleBucketStore.d.ts +0 -54
  137. package/dist/storage/implementation/common/SingleBucketStore.js +0 -3
  138. package/dist/storage/implementation/common/SingleBucketStore.js.map +0 -1
  139. package/dist/storage/implementation/v3/SingleBucketStoreV3.d.ts +0 -18
  140. package/dist/storage/implementation/v3/SingleBucketStoreV3.js +0 -84
  141. package/dist/storage/implementation/v3/SingleBucketStoreV3.js.map +0 -1
  142. package/src/storage/implementation/common/SingleBucketStore.ts +0 -63
  143. package/src/storage/implementation/v3/SingleBucketStoreV3.ts +0 -108
@@ -1,7 +1,9 @@
1
1
  import * as lib_mongo from '@powersync/lib-service-mongodb';
2
+ import { ServiceAssertionError } from '@powersync/lib-services-framework';
2
3
  import {
3
4
  bson,
4
5
  BucketChecksum,
6
+ CheckpointChecksumInvalidatedError,
5
7
  FetchPartialBucketChecksum,
6
8
  InternalOpId,
7
9
  isPartialChecksum,
@@ -179,9 +181,42 @@ export class MongoChecksumsV3 extends MongoChecksums implements DefinitionChecks
179
181
  requests.set(request.bucket, request);
180
182
  }
181
183
 
182
- const pipeline = this.buildPartialChecksumPipeline(requests);
183
- const aggregate = await collection
184
- .aggregate(pipeline, {
184
+ const aggregate = await this.aggregatePartialChecksums(collection, requests, context);
185
+ const { checksums, startStraddledBuckets } = this.normalizePartialChecksumResults(batch, aggregate);
186
+
187
+ if (startStraddledBuckets.size == 0) {
188
+ return checksums;
189
+ }
190
+
191
+ // A cached base ends inside a compaction-produced document. Its checksum cannot
192
+ // be safely combined with the document-level checksum, so recalculate this
193
+ // bucket from the beginning. A full result replaces the cached base upstream.
194
+ const fullRequests = batch
195
+ .filter((request) => startStraddledBuckets.has(request.bucket))
196
+ .map((request) => ({ ...request, start: undefined }));
197
+ const fullRequestMap = new Map(fullRequests.map((request) => [request.bucket, request]));
198
+ const fullAggregate = await this.aggregatePartialChecksums(collection, fullRequestMap, context);
199
+ const fullResults = this.normalizePartialChecksumResults(fullRequests, fullAggregate);
200
+
201
+ if (fullResults.startStraddledBuckets.size > 0) {
202
+ throw new ServiceAssertionError(
203
+ `Unexpected start-boundary straddle while recalculating bucket(s) ${[...fullResults.startStraddledBuckets].join(', ')}`
204
+ );
205
+ }
206
+ for (const [bucket, checksum] of fullResults.checksums) {
207
+ checksums.set(bucket, checksum);
208
+ }
209
+
210
+ return checksums;
211
+ }
212
+
213
+ private async aggregatePartialChecksums(
214
+ collection: lib_mongo.mongo.Collection<BucketDataDocumentV3>,
215
+ requests: Map<string, FetchPartialBucketChecksumByBucket>,
216
+ context: MongoChecksumSessionContext
217
+ ): Promise<bson.Document[]> {
218
+ return collection
219
+ .aggregate(this.buildPartialChecksumPipeline(requests), {
185
220
  ...context.readOptions,
186
221
  maxTimeMS: lib_mongo.MONGO_CHECKSUM_TIMEOUT_MS
187
222
  })
@@ -189,8 +224,6 @@ export class MongoChecksumsV3 extends MongoChecksums implements DefinitionChecks
189
224
  .catch((e) => {
190
225
  throw lib_mongo.mapQueryError(e, 'while reading checksums');
191
226
  });
192
-
193
- return this.normalizePartialChecksumResults(batch, aggregate);
194
227
  }
195
228
 
196
229
  private buildPartialChecksumPipeline(requests: Map<string, FetchPartialBucketChecksumByBucket>): bson.Document[] {
@@ -203,128 +236,64 @@ export class MongoChecksumsV3 extends MongoChecksums implements DefinitionChecks
203
236
  $or: filters
204
237
  }
205
238
  },
206
- {
207
- $addFields: {
208
- bucket_start: {
209
- $switch: {
210
- branches: Array.from(requests.entries()).map(([bucket, req]) => ({
211
- case: { $eq: ['$_id.b', bucket] },
212
- then: req.start ?? new bson.MinKey()
213
- })),
214
- default: new bson.MinKey()
215
- }
216
- },
217
- bucket_end: {
218
- $switch: {
219
- branches: Array.from(requests.entries()).map(([bucket, req]) => ({
220
- case: { $eq: ['$_id.b', bucket] },
221
- then: req.end
222
- })),
223
- default: new bson.MaxKey()
224
- }
225
- }
226
- }
227
- },
228
- // Determine if document is fully included within [start, end] range
239
+ // Only document-level metadata is used below. Bucket-data operations may
240
+ // be offloaded, and checksum queries must never inspect the ops array.
229
241
  {
230
242
  $project: {
231
243
  _id: 1,
232
244
  min_op: 1,
233
245
  checksum: 1,
234
246
  count: 1,
235
- ops: 1,
236
- bucket_start: 1,
237
- bucket_end: 1,
238
- is_fully_included: {
239
- $and: [{ $gt: ['$min_op', '$bucket_start'] }, { $lte: ['$_id.o', '$bucket_end'] }]
240
- }
241
- }
242
- },
243
- // Compute included checksum, count, and clear op detection
244
- {
245
- $project: {
246
- _id: 1,
247
- checksum_total: {
248
- $cond: {
249
- if: '$is_fully_included',
250
- then: '$checksum',
251
- else: {
252
- $sum: {
253
- $map: {
254
- input: {
255
- $filter: {
256
- input: '$ops',
257
- cond: {
258
- $and: [{ $gt: ['$$this.o', '$bucket_start'] }, { $lte: ['$$this.o', '$bucket_end'] }]
259
- }
260
- }
261
- },
262
- in: '$$this.checksum'
263
- }
264
- }
265
- }
266
- }
267
- },
268
- count_total: {
269
- $cond: {
270
- if: '$is_fully_included',
271
- then: '$count',
272
- else: {
273
- $size: {
274
- $filter: {
275
- input: '$ops',
276
- cond: {
277
- $and: [{ $gt: ['$$this.o', '$bucket_start'] }, { $lte: ['$$this.o', '$bucket_end'] }]
278
- }
279
- }
280
- }
281
- }
282
- }
283
- },
284
- has_clear_op: {
285
- $max: {
286
- $map: {
287
- input: {
288
- $filter: {
289
- input: '$ops',
290
- cond: {
291
- $and: [{ $gt: ['$$this.o', '$bucket_start'] }, { $lte: ['$$this.o', '$bucket_end'] }]
292
- }
293
- }
294
- },
295
- in: { $cond: [{ $eq: ['$$this.op', 'CLEAR'] }, 1, 0] }
296
- }
297
- }
298
- },
299
- last_op: { $max: '$_id.o' }
247
+ has_clear_op: 1
300
248
  }
301
249
  },
302
- // Group by bucket
250
+ // Aggregate document metadata per bucket. A CLEAR makes the result a full
251
+ // checksum that replaces any cached prefix; storage guarantees that
252
+ // documents preceding the CLEAR have already been removed.
303
253
  {
304
254
  $group: {
305
255
  _id: '$_id.b',
306
- checksum_total: { $sum: '$checksum_total' },
307
- count: { $sum: '$count_total' },
308
- has_clear_op: { $max: '$has_clear_op' },
309
- last_op: { $max: '$last_op' }
256
+ checksum_total: { $sum: '$checksum' },
257
+ count: { $sum: '$count' },
258
+ has_clear_op: {
259
+ $max: { $cond: ['$has_clear_op', 1, 0] }
260
+ },
261
+ min_op: { $min: '$min_op' },
262
+ max_op: { $max: '$_id.o' }
310
263
  }
311
- },
312
- // $sort results
313
- { $sort: { _id: 1 } }
264
+ }
314
265
  ];
315
266
  }
316
267
 
317
268
  private normalizePartialChecksumResults(
318
269
  batch: FetchPartialBucketChecksumByBucket[],
319
270
  aggregate: bson.Document[]
320
- ): PartialChecksumMap {
271
+ ): { checksums: PartialChecksumMap; startStraddledBuckets: Set<string> } {
272
+ const requests = new Map(batch.map((request) => [request.bucket, request]));
273
+ const startStraddledBuckets = new Set<string>();
321
274
  const partialChecksums = new Map<string, PartialOrFullChecksum>();
322
- for (let doc of aggregate) {
323
- const bucket = doc._id;
275
+
276
+ for (const doc of aggregate) {
277
+ const bucket = doc._id as string;
278
+ const request = requests.get(bucket)!;
279
+
280
+ // A maximum beyond end means a matched document straddles the checkpoint.
281
+ // Its document-level checksum includes later operations, so this checkpoint
282
+ // cannot be calculated from document metadata.
283
+ if (doc.max_op > request.end) {
284
+ throw new CheckpointChecksumInvalidatedError(request.end, bucket);
285
+ }
286
+ // The _id filter excludes documents ending at or before start. A minimum
287
+ // at or below start therefore means a matched document straddles it.
288
+ if (request.start != null && doc.min_op <= request.start) {
289
+ startStraddledBuckets.add(bucket);
290
+ continue;
291
+ }
292
+
324
293
  partialChecksums.set(bucket, checksumFromAggregate(doc));
325
294
  }
326
295
 
327
- return new Map<string, PartialOrFullChecksum>(
296
+ const checksums = new Map<string, PartialOrFullChecksum>(
328
297
  batch.map((request) => {
329
298
  const bucket = request.bucket;
330
299
  let partialChecksum = partialChecksums.get(bucket);
@@ -346,6 +315,7 @@ export class MongoChecksumsV3 extends MongoChecksums implements DefinitionChecks
346
315
  return [bucket, partialChecksum];
347
316
  })
348
317
  );
318
+ return { checksums, startStraddledBuckets };
349
319
  }
350
320
  }
351
321