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

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 (275) hide show
  1. package/CHANGELOG.md +177 -5
  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 +71 -18
  6. package/dist/storage/MongoBucketStorage.js +516 -202
  7. package/dist/storage/MongoBucketStorage.js.map +1 -1
  8. package/dist/storage/implementation/MongoBucketBatch.d.ts +71 -29
  9. package/dist/storage/implementation/MongoBucketBatch.js +140 -66
  10. package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
  11. package/dist/storage/implementation/MongoChecksums.d.ts +41 -13
  12. package/dist/storage/implementation/MongoChecksums.js +36 -121
  13. package/dist/storage/implementation/MongoChecksums.js.map +1 -1
  14. package/dist/storage/implementation/MongoCompactor.d.ts +28 -90
  15. package/dist/storage/implementation/MongoCompactor.js +87 -481
  16. package/dist/storage/implementation/MongoCompactor.js.map +1 -1
  17. package/dist/storage/implementation/MongoHydrationState.d.ts +11 -0
  18. package/dist/storage/implementation/MongoHydrationState.js +49 -0
  19. package/dist/storage/implementation/MongoHydrationState.js.map +1 -0
  20. package/dist/storage/implementation/MongoParameterCompactor.d.ts +82 -8
  21. package/dist/storage/implementation/MongoParameterCompactor.js +312 -76
  22. package/dist/storage/implementation/MongoParameterCompactor.js.map +1 -1
  23. package/dist/storage/implementation/MongoParsedSyncConfigSet.d.ts +21 -0
  24. package/dist/storage/implementation/MongoParsedSyncConfigSet.js +93 -0
  25. package/dist/storage/implementation/MongoParsedSyncConfigSet.js.map +1 -0
  26. package/dist/storage/implementation/MongoPersistedReplicationStream.d.ts +36 -0
  27. package/dist/storage/implementation/MongoPersistedReplicationStream.js +90 -0
  28. package/dist/storage/implementation/MongoPersistedReplicationStream.js.map +1 -0
  29. package/dist/storage/implementation/MongoPersistedSyncConfigContent.d.ts +25 -0
  30. package/dist/storage/implementation/MongoPersistedSyncConfigContent.js +125 -0
  31. package/dist/storage/implementation/MongoPersistedSyncConfigContent.js.map +1 -0
  32. package/dist/storage/implementation/MongoStorageProvider.js +41 -4
  33. package/dist/storage/implementation/MongoStorageProvider.js.map +1 -1
  34. package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +81 -37
  35. package/dist/storage/implementation/MongoSyncBucketStorage.js +159 -86
  36. package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
  37. package/dist/storage/implementation/MongoSyncRulesLock.d.ts +1 -1
  38. package/dist/storage/implementation/MongoSyncRulesLock.js +23 -11
  39. package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -1
  40. package/dist/storage/implementation/MongoWriteCheckpointAPI.d.ts +11 -8
  41. package/dist/storage/implementation/MongoWriteCheckpointAPI.js +159 -33
  42. package/dist/storage/implementation/MongoWriteCheckpointAPI.js.map +1 -1
  43. package/dist/storage/implementation/SyncRuleStateUpdate.d.ts +11 -5
  44. package/dist/storage/implementation/SyncRuleStateUpdate.js +14 -6
  45. package/dist/storage/implementation/SyncRuleStateUpdate.js.map +1 -1
  46. package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.d.ts +28 -0
  47. package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.js +2 -0
  48. package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.js.map +1 -0
  49. package/dist/storage/implementation/common/PersistedBatch.d.ts +34 -2
  50. package/dist/storage/implementation/common/PersistedBatch.js +63 -10
  51. package/dist/storage/implementation/common/PersistedBatch.js.map +1 -1
  52. package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js +1 -1
  53. package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js.map +1 -1
  54. package/dist/storage/implementation/createMongoSyncBucketStorage.d.ts +2 -2
  55. package/dist/storage/implementation/createMongoSyncBucketStorage.js +4 -4
  56. package/dist/storage/implementation/createMongoSyncBucketStorage.js.map +1 -1
  57. package/dist/storage/implementation/db.d.ts +4 -1
  58. package/dist/storage/implementation/db.js +31 -16
  59. package/dist/storage/implementation/db.js.map +1 -1
  60. package/dist/storage/implementation/models.d.ts +55 -13
  61. package/dist/storage/implementation/models.js.map +1 -1
  62. package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +15 -1
  63. package/dist/storage/implementation/v1/MongoBucketBatchV1.js +135 -107
  64. package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -1
  65. package/dist/storage/implementation/v1/MongoChecksumsV1.d.ts +4 -4
  66. package/dist/storage/implementation/v1/MongoChecksumsV1.js +119 -7
  67. package/dist/storage/implementation/v1/MongoChecksumsV1.js.map +1 -1
  68. package/dist/storage/implementation/v1/MongoCompactorV1.d.ts +47 -7
  69. package/dist/storage/implementation/v1/MongoCompactorV1.js +478 -8
  70. package/dist/storage/implementation/v1/MongoCompactorV1.js.map +1 -1
  71. package/dist/storage/implementation/v1/MongoParameterCompactorV1.d.ts +16 -2
  72. package/dist/storage/implementation/v1/MongoParameterCompactorV1.js +20 -9
  73. package/dist/storage/implementation/v1/MongoParameterCompactorV1.js.map +1 -1
  74. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.d.ts +33 -27
  75. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +146 -73
  76. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js.map +1 -1
  77. package/dist/storage/implementation/v1/SingleBucketStoreV1.d.ts +9 -10
  78. package/dist/storage/implementation/v1/SingleBucketStoreV1.js +2 -8
  79. package/dist/storage/implementation/v1/SingleBucketStoreV1.js.map +1 -1
  80. package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.d.ts +2 -1
  81. package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.js +3 -0
  82. package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.js.map +1 -1
  83. package/dist/storage/implementation/v1/models.d.ts +10 -1
  84. package/dist/storage/implementation/v1/models.js +1 -0
  85. package/dist/storage/implementation/v1/models.js.map +1 -1
  86. package/dist/storage/implementation/v3/BucketDataContextV3.d.ts +13 -0
  87. package/dist/storage/implementation/v3/BucketDataContextV3.js +23 -0
  88. package/dist/storage/implementation/v3/BucketDataContextV3.js.map +1 -0
  89. package/dist/storage/implementation/v3/CompactionLease.d.ts +50 -0
  90. package/dist/storage/implementation/v3/CompactionLease.js +131 -0
  91. package/dist/storage/implementation/v3/CompactionLease.js.map +1 -0
  92. package/dist/storage/implementation/v3/MongoBucketBatchV3.d.ts +16 -5
  93. package/dist/storage/implementation/v3/MongoBucketBatchV3.js +393 -262
  94. package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -1
  95. package/dist/storage/implementation/v3/MongoChecksumsV3.d.ts +30 -9
  96. package/dist/storage/implementation/v3/MongoChecksumsV3.js +161 -20
  97. package/dist/storage/implementation/v3/MongoChecksumsV3.js.map +1 -1
  98. package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +108 -18
  99. package/dist/storage/implementation/v3/MongoCompactorV3.js +1115 -47
  100. package/dist/storage/implementation/v3/MongoCompactorV3.js.map +1 -1
  101. package/dist/storage/implementation/v3/MongoParameterCompactorV3.d.ts +12 -3
  102. package/dist/storage/implementation/v3/MongoParameterCompactorV3.js +13 -8
  103. package/dist/storage/implementation/v3/MongoParameterCompactorV3.js.map +1 -1
  104. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.d.ts +54 -0
  105. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js +319 -0
  106. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js.map +1 -0
  107. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.d.ts +61 -36
  108. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +353 -161
  109. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -1
  110. package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.d.ts +26 -0
  111. package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.js +82 -0
  112. package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.js.map +1 -0
  113. package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +8 -3
  114. package/dist/storage/implementation/v3/PersistedBatchV3.js +131 -29
  115. package/dist/storage/implementation/v3/PersistedBatchV3.js.map +1 -1
  116. package/dist/storage/implementation/v3/SourceRecordStoreV3.d.ts +3 -3
  117. package/dist/storage/implementation/v3/SourceRecordStoreV3.js +13 -11
  118. package/dist/storage/implementation/v3/SourceRecordStoreV3.js.map +1 -1
  119. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.d.ts +26 -14
  120. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js +73 -29
  121. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js.map +1 -1
  122. package/dist/storage/implementation/v3/bucket-format.d.ts +13 -0
  123. package/dist/storage/implementation/v3/bucket-format.js +79 -0
  124. package/dist/storage/implementation/v3/bucket-format.js.map +1 -0
  125. package/dist/storage/implementation/v3/chunking.d.ts +10 -0
  126. package/dist/storage/implementation/v3/chunking.js +30 -0
  127. package/dist/storage/implementation/v3/chunking.js.map +1 -0
  128. package/dist/storage/implementation/v3/compact-utils.d.ts +107 -0
  129. package/dist/storage/implementation/v3/compact-utils.js +216 -0
  130. package/dist/storage/implementation/v3/compact-utils.js.map +1 -0
  131. package/dist/storage/implementation/v3/compaction-constants.d.ts +5 -0
  132. package/dist/storage/implementation/v3/compaction-constants.js +6 -0
  133. package/dist/storage/implementation/v3/compaction-constants.js.map +1 -0
  134. package/dist/storage/implementation/v3/models.d.ts +187 -23
  135. package/dist/storage/implementation/v3/models.js +11 -28
  136. package/dist/storage/implementation/v3/models.js.map +1 -1
  137. package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.d.ts +20 -0
  138. package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js +48 -0
  139. package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js.map +1 -0
  140. package/dist/storage/implementation/v3/object-storage/ObjectStorage.d.ts +33 -0
  141. package/dist/storage/implementation/v3/object-storage/ObjectStorage.js +12 -0
  142. package/dist/storage/implementation/v3/object-storage/ObjectStorage.js.map +1 -0
  143. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.d.ts +68 -0
  144. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js +154 -0
  145. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js.map +1 -0
  146. package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.d.ts +37 -0
  147. package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.js +167 -0
  148. package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.js.map +1 -0
  149. package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.d.ts +67 -0
  150. package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js +439 -0
  151. package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js.map +1 -0
  152. package/dist/storage/implementation/v3/source-table-utils.d.ts +63 -0
  153. package/dist/storage/implementation/v3/source-table-utils.js +206 -0
  154. package/dist/storage/implementation/v3/source-table-utils.js.map +1 -0
  155. package/dist/storage/storage-index.d.ts +5 -4
  156. package/dist/storage/storage-index.js +5 -4
  157. package/dist/storage/storage-index.js.map +1 -1
  158. package/dist/types/types.d.ts +71 -3
  159. package/dist/types/types.js +58 -2
  160. package/dist/types/types.js.map +1 -1
  161. package/dist/utils/test-utils.d.ts +1 -2
  162. package/dist/utils/test-utils.js +7 -1
  163. package/dist/utils/test-utils.js.map +1 -1
  164. package/dist/utils/util.d.ts +9 -2
  165. package/dist/utils/util.js +131 -5
  166. package/dist/utils/util.js.map +1 -1
  167. package/package.json +13 -19
  168. package/src/migrations/db/migrations/1782950400000-checkpoint-requested-at-index.ts +55 -0
  169. package/src/storage/MongoBucketStorage.ts +728 -242
  170. package/src/storage/implementation/MongoBucketBatch.ts +196 -91
  171. package/src/storage/implementation/MongoChecksums.ts +93 -153
  172. package/src/storage/implementation/MongoCompactor.ts +106 -631
  173. package/src/storage/implementation/MongoHydrationState.ts +61 -0
  174. package/src/storage/implementation/MongoParameterCompactor.ts +392 -87
  175. package/src/storage/implementation/MongoParsedSyncConfigSet.ts +122 -0
  176. package/src/storage/implementation/MongoPersistedReplicationStream.ts +118 -0
  177. package/src/storage/implementation/MongoPersistedSyncConfigContent.ts +156 -0
  178. package/src/storage/implementation/MongoStorageProvider.ts +48 -4
  179. package/src/storage/implementation/MongoSyncBucketStorage.ts +226 -118
  180. package/src/storage/implementation/MongoSyncRulesLock.ts +28 -12
  181. package/src/storage/implementation/MongoWriteCheckpointAPI.ts +194 -52
  182. package/src/storage/implementation/SyncRuleStateUpdate.ts +13 -6
  183. package/src/storage/implementation/common/MongoSyncBucketStorageCheckpoint.ts +31 -0
  184. package/src/storage/implementation/common/PersistedBatch.ts +83 -15
  185. package/src/storage/implementation/common/VersionedPowerSyncMongoBase.ts +1 -1
  186. package/src/storage/implementation/createMongoSyncBucketStorage.ts +21 -7
  187. package/src/storage/implementation/db.ts +40 -22
  188. package/src/storage/implementation/models.ts +58 -14
  189. package/src/storage/implementation/v1/MongoBucketBatchV1.ts +174 -125
  190. package/src/storage/implementation/v1/MongoChecksumsV1.ts +154 -12
  191. package/src/storage/implementation/v1/MongoCompactorV1.ts +594 -25
  192. package/src/storage/implementation/v1/MongoParameterCompactorV1.ts +25 -9
  193. package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +226 -129
  194. package/src/storage/implementation/v1/SingleBucketStoreV1.ts +18 -27
  195. package/src/storage/implementation/v1/VersionedPowerSyncMongoV1.ts +11 -1
  196. package/src/storage/implementation/v1/models.ts +12 -1
  197. package/src/storage/implementation/v3/BucketDataContextV3.ts +32 -0
  198. package/src/storage/implementation/v3/CompactionLease.ts +152 -0
  199. package/src/storage/implementation/v3/MongoBucketBatchV3.ts +502 -324
  200. package/src/storage/implementation/v3/MongoChecksumsV3.ts +250 -32
  201. package/src/storage/implementation/v3/MongoCompactorV3.ts +1439 -70
  202. package/src/storage/implementation/v3/MongoParameterCompactorV3.ts +19 -9
  203. package/src/storage/implementation/v3/MongoStoppedSyncConfigCleanup.ts +473 -0
  204. package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +453 -211
  205. package/src/storage/implementation/v3/MongoWriteCheckpointAPIV3.ts +111 -0
  206. package/src/storage/implementation/v3/PersistedBatchV3.ts +169 -45
  207. package/src/storage/implementation/v3/SourceRecordStoreV3.ts +12 -12
  208. package/src/storage/implementation/v3/VersionedPowerSyncMongoV3.ts +99 -38
  209. package/src/storage/implementation/v3/bucket-format.ts +99 -0
  210. package/src/storage/implementation/v3/chunking.ts +40 -0
  211. package/src/storage/implementation/v3/compact-utils.ts +320 -0
  212. package/src/storage/implementation/v3/compaction-constants.ts +5 -0
  213. package/src/storage/implementation/v3/models.ts +228 -58
  214. package/src/storage/implementation/v3/object-storage/BucketDataObjectStorage.ts +62 -0
  215. package/src/storage/implementation/v3/object-storage/ObjectStorage.ts +44 -0
  216. package/src/storage/implementation/v3/object-storage/ObjectStorageLifecycle.ts +182 -0
  217. package/src/storage/implementation/v3/object-storage/ObjectStorageUsage.ts +230 -0
  218. package/src/storage/implementation/v3/object-storage/S3ObjectStorage.ts +573 -0
  219. package/src/storage/implementation/v3/source-table-utils.ts +398 -0
  220. package/src/storage/storage-index.ts +14 -4
  221. package/src/types/types.ts +76 -2
  222. package/src/utils/test-utils.ts +8 -3
  223. package/src/utils/util.ts +188 -7
  224. package/test/src/__snapshots__/storage.test.ts.snap +4 -54
  225. package/test/src/__snapshots__/storage_sync.test.ts.snap +652 -645
  226. package/test/src/cleanup-stopped-sync-configs.test.ts +698 -0
  227. package/test/src/compact-utils.test.ts +226 -0
  228. package/test/src/helpers/MemoryObjectStorage.ts +61 -0
  229. package/test/src/helpers/s3TestFactory.ts +69 -0
  230. package/test/src/object_storage_lifecycle.test.ts +59 -0
  231. package/test/src/object_storage_usage.test.ts +255 -0
  232. package/test/src/parameter_compacting_v1.test.ts +120 -0
  233. package/test/src/parameter_compacting_v3.test.ts +192 -0
  234. package/test/src/parameter_compaction_fence.test.ts +276 -0
  235. package/test/src/setup.ts +6 -1
  236. package/test/src/storage.test.ts +478 -13
  237. package/test/src/storage_compacting.test.ts +2516 -107
  238. package/test/src/storage_object_storage_inline_threshold.test.ts +116 -0
  239. package/test/src/storage_s3_checksums.test.ts +190 -0
  240. package/test/src/storage_s3_compaction_lifecycle.test.ts +543 -0
  241. package/test/src/storage_s3_reading.test.ts +768 -0
  242. package/test/src/storage_s3_writing.test.ts +136 -0
  243. package/test/src/storage_sync.test.ts +1507 -76
  244. package/test/src/storeCurrentData.test.ts +227 -0
  245. package/test/src/util.ts +3 -2
  246. package/tsconfig.json +1 -4
  247. package/tsconfig.tsbuildinfo +1 -1
  248. package/dist/storage/implementation/BucketDefinitionMapping.d.ts +0 -15
  249. package/dist/storage/implementation/BucketDefinitionMapping.js +0 -58
  250. package/dist/storage/implementation/BucketDefinitionMapping.js.map +0 -1
  251. package/dist/storage/implementation/MongoPersistedSyncRules.d.ts +0 -14
  252. package/dist/storage/implementation/MongoPersistedSyncRules.js +0 -67
  253. package/dist/storage/implementation/MongoPersistedSyncRules.js.map +0 -1
  254. package/dist/storage/implementation/MongoPersistedSyncRulesContent.d.ts +0 -29
  255. package/dist/storage/implementation/MongoPersistedSyncRulesContent.js +0 -76
  256. package/dist/storage/implementation/MongoPersistedSyncRulesContent.js.map +0 -1
  257. package/dist/storage/implementation/common/MongoSyncBucketStorageContext.d.ts +0 -13
  258. package/dist/storage/implementation/common/MongoSyncBucketStorageContext.js +0 -2
  259. package/dist/storage/implementation/common/MongoSyncBucketStorageContext.js.map +0 -1
  260. package/dist/storage/implementation/common/SingleBucketStore.d.ts +0 -54
  261. package/dist/storage/implementation/common/SingleBucketStore.js +0 -3
  262. package/dist/storage/implementation/common/SingleBucketStore.js.map +0 -1
  263. package/dist/storage/implementation/v3/MongoParameterLookupV3.d.ts +0 -4
  264. package/dist/storage/implementation/v3/MongoParameterLookupV3.js +0 -9
  265. package/dist/storage/implementation/v3/MongoParameterLookupV3.js.map +0 -1
  266. package/dist/storage/implementation/v3/SingleBucketStoreV3.d.ts +0 -18
  267. package/dist/storage/implementation/v3/SingleBucketStoreV3.js +0 -48
  268. package/dist/storage/implementation/v3/SingleBucketStoreV3.js.map +0 -1
  269. package/src/storage/implementation/BucketDefinitionMapping.ts +0 -75
  270. package/src/storage/implementation/MongoPersistedSyncRules.ts +0 -82
  271. package/src/storage/implementation/MongoPersistedSyncRulesContent.ts +0 -101
  272. package/src/storage/implementation/common/MongoSyncBucketStorageContext.ts +0 -15
  273. package/src/storage/implementation/common/SingleBucketStore.ts +0 -63
  274. package/src/storage/implementation/v3/MongoParameterLookupV3.ts +0 -11
  275. package/src/storage/implementation/v3/SingleBucketStoreV3.ts +0 -68
@@ -2,6 +2,7 @@ import * as lib_mongo from '@powersync/lib-service-mongodb';
2
2
  import { mongo } from '@powersync/lib-service-mongodb';
3
3
  import {
4
4
  BaseObserver,
5
+ logger as defaultLogger,
5
6
  DO_NOT_LOG,
6
7
  Logger,
7
8
  ReplicationAbortedError,
@@ -11,13 +12,15 @@ import {
11
12
  BroadcastIterable,
12
13
  CHECKPOINT_INVALIDATE_ALL,
13
14
  CheckpointChanges,
15
+ CompactInitialReplicationOptions,
16
+ CompactInitialReplicationResults,
14
17
  GetCheckpointChangesOptions,
15
18
  InternalOpId,
16
19
  mergeAsyncIterables,
17
- PopulateChecksumCacheOptions,
18
- PopulateChecksumCacheResults,
19
20
  ReplicationCheckpoint,
21
+ ReplicationStreamStorageIds,
20
22
  storage,
23
+ SyncRuleState,
21
24
  utils,
22
25
  WatchWriteCheckpointOptions
23
26
  } from '@powersync/service-core';
@@ -25,21 +28,42 @@ import { HydratedSyncConfig, ParameterLookupRows, ScopedParameterLookup } from '
25
28
  import * as bson from 'bson';
26
29
  import { LRUCache } from 'lru-cache';
27
30
  import * as timers from 'timers/promises';
28
- import { retryOnMongoMaxTimeMSExpired } from '../../utils/util.js';
31
+ import { DEFAULT_CLEAR_BATCH_THROTTLE_RATE } from '../../types/types.js';
29
32
  import { MongoBucketStorage } from '../MongoBucketStorage.js';
30
- import { MongoSyncBucketStorageContext } from './common/MongoSyncBucketStorageContext.js';
33
+ import {
34
+ MongoGetCheckpointChangesOptions,
35
+ MongoSyncBucketStorageCheckpoint
36
+ } from './common/MongoSyncBucketStorageCheckpoint.js';
37
+ import { DEFAULT_INLINE_THRESHOLD_BYTES } from './common/PersistedBatch.js';
31
38
  import type { VersionedPowerSyncMongo } from './db.js';
32
39
  import { StorageConfig } from './models.js';
33
40
  import { MongoBucketBatchOptions } from './MongoBucketBatch.js';
34
41
  import { MongoChecksumOptions, MongoChecksums } from './MongoChecksums.js';
35
42
  import { MongoCompactOptions, MongoCompactor } from './MongoCompactor.js';
36
43
  import { MongoParameterCompactor } from './MongoParameterCompactor.js';
37
- import { MongoPersistedSyncRulesContentV1 } from './MongoPersistedSyncRulesContent.js';
38
- import { MongoWriteCheckpointAPI } from './MongoWriteCheckpointAPI.js';
44
+ import { MongoParsedSyncConfigSet } from './MongoParsedSyncConfigSet.js';
45
+ import { MongoPersistedReplicationStream } from './MongoPersistedReplicationStream.js';
46
+ import { MongoCheckpointAPIOptions, MongoWriteCheckpointAPI } from './MongoWriteCheckpointAPI.js';
47
+ import { ObjectStorage } from './v3/object-storage/ObjectStorage.js';
39
48
 
40
49
  export interface MongoSyncBucketStorageOptions {
41
- checksumOptions?: Omit<MongoChecksumOptions, 'storageConfig' | 'mapping'>;
50
+ checksumOptions?: Omit<MongoChecksumOptions, 'storageConfig'>;
51
+ readPreference?: mongo.ReadPreference;
52
+ checksumCacheTtlMs?: number;
53
+ clearBatchThrottleRate?: number;
42
54
  storageConfig: StorageConfig;
55
+ objectStorage?: ObjectStorage;
56
+ inlineThresholdBytes?: number;
57
+ }
58
+
59
+ /**
60
+ * The stream state read for a checkpoint. All fields must come from the same snapshot.
61
+ */
62
+ export interface MongoCheckpointState {
63
+ checkpoint: InternalOpId;
64
+ lsn: string | null;
65
+ /** See {@link MongoSyncBucketStorageCheckpoint.parameterChangesInvalidBefore}. */
66
+ parameterChangesInvalidBefore: InternalOpId;
43
67
  }
44
68
 
45
69
  interface InternalCheckpointChanges extends CheckpointChanges {
@@ -47,13 +71,6 @@ interface InternalCheckpointChanges extends CheckpointChanges {
47
71
  invalidateWriteCheckpoints: boolean;
48
72
  }
49
73
 
50
- interface WriterSyncState {
51
- lastCheckpointLsn: string | null;
52
- resumeFromLsn: string | null;
53
- keepaliveOp: InternalOpId | null;
54
- syncConfigId?: bson.ObjectId | null;
55
- }
56
-
57
74
  /**
58
75
  * Only keep checkpoints around for a minute, before fetching a fresh one.
59
76
  *
@@ -70,34 +87,68 @@ export abstract class MongoSyncBucketStorage
70
87
  implements storage.SyncRulesBucketStorage
71
88
  {
72
89
  readonly db: VersionedPowerSyncMongo;
90
+
73
91
  [DO_NOT_LOG] = true;
74
92
 
75
93
  readonly checksums: MongoChecksums;
76
94
 
77
- private parsedSyncRulesCache: { parsed: HydratedSyncConfig; options: storage.ParseSyncRulesOptions } | undefined;
95
+ readonly objectStorage?: ObjectStorage;
96
+ readonly inlineThresholdBytes: number;
97
+
98
+ /**
99
+ * Canonical parsed sync config sets, keyed by defaultSchema.
100
+ *
101
+ * Entries are never evicted: each parse options value maps to exactly one parsed set for
102
+ * the lifetime of this storage instance, so parsed source objects and mappings always
103
+ * stay associated.
104
+ */
105
+ private readonly parsedSyncConfigSets = new Map<string, MongoParsedSyncConfigSet>();
78
106
  private writeCheckpointAPI: MongoWriteCheckpointAPI;
79
107
  public readonly logger: Logger;
80
108
  public readonly storageConfig: StorageConfig;
109
+ public readonly readPreference: mongo.ReadPreference | undefined;
110
+ public readonly clearBatchThrottleRate: number;
81
111
  #storageInitialized = false;
82
112
 
83
113
  constructor(
84
114
  public readonly factory: MongoBucketStorage,
85
- public readonly group_id: number,
86
- protected readonly sync_rules: MongoPersistedSyncRulesContentV1,
87
- public readonly slot_name: string,
115
+ public readonly replicationStreamId: number,
116
+ public readonly replicationStream: MongoPersistedReplicationStream,
117
+ public readonly replicationStreamName: string,
88
118
  writeCheckpointMode: storage.WriteCheckpointMode | undefined,
89
119
  options: MongoSyncBucketStorageOptions
90
120
  ) {
91
121
  super();
92
122
  this.storageConfig = options.storageConfig;
123
+ this.objectStorage = options.objectStorage;
124
+ // Keep small chunks inline in MongoDB rather than offloading them to S3.
125
+ // Configurable via object_storage.inline_threshold_bytes.
126
+ this.inlineThresholdBytes = options.inlineThresholdBytes ?? DEFAULT_INLINE_THRESHOLD_BYTES;
127
+ this.readPreference = options.readPreference;
128
+ this.clearBatchThrottleRate = options.clearBatchThrottleRate ?? DEFAULT_CLEAR_BATCH_THROTTLE_RATE;
93
129
  this.db = factory.db.versioned(this.storageConfig);
94
130
  this.checksums = this.createMongoChecksums(options);
95
- this.writeCheckpointAPI = new MongoWriteCheckpointAPI({
131
+ this.writeCheckpointAPI = this.createWriteCheckpointAPI({
96
132
  db: this.db,
97
- mode: writeCheckpointMode ?? storage.WriteCheckpointMode.MANAGED,
98
- sync_rules_id: group_id
133
+ writeCheckpointMode: { mode: writeCheckpointMode ?? storage.WriteCheckpointMode.MANAGED },
134
+ replicationStreamId
99
135
  });
100
- this.logger = sync_rules.logger;
136
+
137
+ // Include both replication stream name and sync config version labels in log prefix.
138
+ // Examples:
139
+ // No sync config with a label: [stream]
140
+ // All sync configs have a label: [stream][v5,v6]
141
+ // Mixed: [stream][?,v6]
142
+ const hasVersionLabel = replicationStream.syncConfigContent.some((config) => config.version_label != null);
143
+ const versionLabels = hasVersionLabel
144
+ ? replicationStream.syncConfigContent.map((config) => config.version_label ?? '?')
145
+ : [];
146
+ this.logger =
147
+ versionLabels.length == 0
148
+ ? replicationStream.logger
149
+ : defaultLogger.child({
150
+ prefix: `[${replicationStreamName}][${versionLabels.join(',')}] `
151
+ });
101
152
  }
102
153
 
103
154
  /**
@@ -108,58 +159,66 @@ export abstract class MongoSyncBucketStorage
108
159
  abstract createMongoCompactor(options: MongoCompactOptions): MongoCompactor;
109
160
 
110
161
  protected abstract createMongoChecksums(options: MongoSyncBucketStorageOptions): MongoChecksums;
162
+ protected createWriteCheckpointAPI(options: MongoCheckpointAPIOptions): MongoWriteCheckpointAPI {
163
+ return new MongoWriteCheckpointAPI(options);
164
+ }
165
+
111
166
  protected abstract createMongoParameterCompactor(
112
167
  checkpoint: InternalOpId,
113
168
  options: storage.CompactOptions
114
169
  ): MongoParameterCompactor;
115
170
 
116
- get writeCheckpointMode() {
117
- return this.writeCheckpointAPI.writeCheckpointMode;
171
+ /** MongoDB parameter compaction uses a persisted operation-id cursor. */
172
+ public supportsIncrementalParameterCompaction(): boolean {
173
+ return true;
118
174
  }
119
175
 
120
- get mapping() {
121
- return this.sync_rules.mapping;
176
+ get writeCheckpointMode() {
177
+ return this.writeCheckpointAPI.writeCheckpointMode;
122
178
  }
123
179
 
124
- protected get versionContext(): MongoSyncBucketStorageContext {
125
- return {
126
- db: this.db,
127
- group_id: this.group_id,
128
- mapping: this.mapping
129
- };
180
+ /**
181
+ * Persisted storage ids of all sync configs in this replication stream. Parse-free.
182
+ */
183
+ get storageIds(): ReplicationStreamStorageIds {
184
+ return this.replicationStream.storageIds;
130
185
  }
131
186
 
132
- setWriteCheckpointMode(mode: storage.WriteCheckpointMode): void {
133
- this.writeCheckpointAPI.setWriteCheckpointMode(mode);
187
+ setWriteCheckpointMode(config: storage.WriteCheckpointModeConfig): void {
188
+ this.writeCheckpointAPI.setWriteCheckpointMode(config);
134
189
  }
135
190
 
136
- createManagedWriteCheckpoint(checkpoint: storage.ManagedWriteCheckpointOptions): Promise<bigint> {
137
- return this.writeCheckpointAPI.createManagedWriteCheckpoint(checkpoint);
191
+ createManagedWriteCheckpoints(
192
+ checkpoints: storage.ManagedWriteCheckpointOptions[]
193
+ ): Promise<storage.CreateManagedWriteCheckpointsResult> {
194
+ return this.writeCheckpointAPI.createManagedWriteCheckpoints(checkpoints);
138
195
  }
139
196
 
140
197
  lastWriteCheckpoint(filters: storage.SyncStorageLastWriteCheckpointFilters): Promise<bigint | null> {
141
198
  return this.writeCheckpointAPI.lastWriteCheckpoint({
142
199
  ...filters,
143
- sync_rules_id: this.group_id
200
+ sync_rules_id: this.replicationStreamId
144
201
  });
145
202
  }
146
203
 
147
- getParsedSyncRules(options: storage.ParseSyncRulesOptions): HydratedSyncConfig {
148
- const { parsed, options: cachedOptions } = this.parsedSyncRulesCache ?? {};
149
- if (!parsed || options.defaultSchema != cachedOptions?.defaultSchema) {
150
- this.parsedSyncRulesCache = { parsed: this.sync_rules.parsed(options).hydratedSyncConfig(), options };
204
+ getParsedSyncConfigSet(options: storage.ParseSyncConfigOptions): MongoParsedSyncConfigSet {
205
+ let parsed = this.parsedSyncConfigSets.get(options.defaultSchema);
206
+ if (parsed == null) {
207
+ parsed = this.replicationStream.parsed(options);
208
+ this.parsedSyncConfigSets.set(options.defaultSchema, parsed);
151
209
  }
210
+ return parsed;
211
+ }
152
212
 
153
- return this.parsedSyncRulesCache!.parsed;
213
+ getParsedSyncRules(options: storage.ParseSyncConfigOptions): HydratedSyncConfig {
214
+ return this.getParsedSyncConfigSet(options).hydratedSyncConfig;
154
215
  }
155
216
 
156
217
  async getCheckpoint(): Promise<storage.ReplicationCheckpoint> {
157
218
  return (await this.getCheckpointInternal()) ?? new EmptyReplicationCheckpoint();
158
219
  }
159
220
 
160
- protected abstract fetchCheckpointState(
161
- session: mongo.ClientSession
162
- ): Promise<{ checkpoint: bigint; lsn: string | null } | null>;
221
+ protected abstract fetchCheckpointState(session: mongo.ClientSession): Promise<MongoCheckpointState | null>;
163
222
 
164
223
  async getCheckpointInternal(): Promise<storage.ReplicationCheckpoint | null> {
165
224
  return await this.db.client.withSession({ snapshot: true }, async (session) => {
@@ -169,10 +228,21 @@ export abstract class MongoSyncBucketStorage
169
228
  }
170
229
 
171
230
  const snapshotTime = (session as any).snapshotTime as bson.Timestamp | undefined;
231
+ const clusterTime = session.clusterTime;
172
232
  if (snapshotTime == null) {
173
233
  throw new ServiceAssertionError('Missing snapshotTime in getCheckpoint()');
174
234
  }
175
- return new MongoReplicationCheckpoint(this, state.checkpoint, state.lsn, snapshotTime);
235
+ if (clusterTime == null) {
236
+ throw new ServiceAssertionError('Missing clusterTime in getCheckpoint()');
237
+ }
238
+ return new MongoReplicationCheckpoint(
239
+ this,
240
+ state.checkpoint,
241
+ state.lsn,
242
+ snapshotTime,
243
+ clusterTime,
244
+ state.parameterChangesInvalidBefore
245
+ );
176
246
  });
177
247
  }
178
248
 
@@ -183,37 +253,43 @@ export abstract class MongoSyncBucketStorage
183
253
  return;
184
254
  }
185
255
 
186
- await this.db.initializeStreamStorage(this.group_id);
256
+ await this.db.initializeStreamStorage(this.replicationStreamId);
187
257
  await this.initializeVersionStorage();
188
258
  this.#storageInitialized = true;
189
259
  }
190
260
 
191
- protected abstract createWriterImpl(batchOptions: MongoBucketBatchOptions): storage.BucketStorageBatch;
192
- protected abstract getWriterSyncState(): Promise<WriterSyncState>;
193
-
194
- async createWriter(options: storage.CreateWriterOptions): Promise<storage.BucketStorageBatch> {
195
- await this.initializeStorage();
196
-
197
- const state = await this.getWriterSyncState();
261
+ /**
262
+ * Create the version-specific writer. Implementations fetch their own resume state
263
+ * (e.g. resume LSN, v1 keepalive op) and construct the batch from
264
+ * {@link writerBatchOptions} plus the version-specific fields.
265
+ */
266
+ protected abstract createWriterImpl(options: storage.CreateWriterOptions): Promise<storage.BucketStorageBatch>;
198
267
 
199
- const batchOptions: MongoBucketBatchOptions = {
268
+ /**
269
+ * The version-independent part of the batch options.
270
+ */
271
+ protected writerBatchOptions(options: storage.CreateWriterOptions): Omit<MongoBucketBatchOptions, 'resumeFromLsn'> {
272
+ return {
200
273
  logger: options.logger ?? this.logger,
201
274
  db: this.db,
202
- syncRules: this.sync_rules.parsed(options).hydratedSyncConfig(),
203
- mapping: this.sync_rules.mapping,
204
- groupId: this.group_id,
205
- slotName: this.slot_name,
206
- lastCheckpointLsn: state.lastCheckpointLsn,
207
- resumeFromLsn: state.resumeFromLsn,
208
- keepaliveOp: state.keepaliveOp,
275
+ parsedSyncConfig: this.getParsedSyncConfigSet(options),
276
+ replicationStreamId: this.replicationStreamId,
277
+ replicationStreamName: this.replicationStreamName,
209
278
  storeCurrentData: options.storeCurrentData,
210
279
  skipExistingRows: options.skipExistingRows ?? false,
211
280
  markRecordUnavailable: options.markRecordUnavailable,
212
281
  hooks: options.hooks,
213
- syncConfigId: state.syncConfigId,
214
- tracer: options.tracer
282
+ tracer: options.tracer,
283
+ signal: options.signal,
284
+ objectStorage: this.objectStorage,
285
+ inlineThresholdBytes: this.inlineThresholdBytes
215
286
  };
216
- const writer = this.createWriterImpl(batchOptions);
287
+ }
288
+
289
+ async createWriter(options: storage.CreateWriterOptions): Promise<storage.BucketStorageBatch> {
290
+ await this.initializeStorage();
291
+
292
+ const writer = await this.createWriterImpl(options);
217
293
  this.iterateListeners((cb) => cb.batchStarted?.(writer));
218
294
  return writer;
219
295
  }
@@ -243,24 +319,31 @@ export abstract class MongoSyncBucketStorage
243
319
  }
244
320
 
245
321
  protected abstract getBucketDataBatchImpl(
246
- checkpoint: utils.InternalOpId,
322
+ checkpoint: MongoReplicationCheckpoint,
247
323
  dataBuckets: storage.BucketDataRequest[],
248
324
  options?: storage.BucketDataBatchOptions
249
- ): AsyncIterable<storage.SyncBucketDataChunk>;
325
+ ): AsyncIterable<storage.SyncBucketDataChunk | storage.SyncBucketDataBatchEnd>;
250
326
 
251
327
  async *getBucketDataBatch(
252
- checkpoint: utils.InternalOpId,
328
+ checkpoint: storage.ReplicationCheckpoint,
253
329
  dataBuckets: storage.BucketDataRequest[],
254
330
  options?: storage.BucketDataBatchOptions
255
- ): AsyncIterable<storage.SyncBucketDataChunk> {
256
- yield* this.getBucketDataBatchImpl(checkpoint, dataBuckets, options);
331
+ ): AsyncIterable<storage.SyncBucketDataChunk | storage.SyncBucketDataBatchEnd> {
332
+ yield* this.getBucketDataBatchImpl(checkpoint as MongoReplicationCheckpoint, dataBuckets, options);
257
333
  }
258
334
 
259
335
  async getChecksums(
260
- checkpoint: utils.InternalOpId,
261
- buckets: storage.BucketChecksumRequest[]
336
+ checkpoint: storage.ReplicationCheckpoint,
337
+ buckets: storage.BucketChecksumRequest[],
338
+ options?: storage.BucketChecksumOptions
262
339
  ): Promise<utils.ChecksumMap> {
263
- return this.checksums.getChecksums(checkpoint, buckets);
340
+ const mongoCheckpoint = checkpoint as MongoReplicationCheckpoint;
341
+ const snapshotTime = mongoCheckpoint.snapshotTime; // May be undefined in tests
342
+ return this.checksums.getChecksums(checkpoint.checkpoint, buckets, {
343
+ snapshotTime,
344
+ clusterTime: mongoCheckpoint.clusterTime,
345
+ readPreference: options?.requestHint == 'bulk' ? this.readPreference : undefined
346
+ });
264
347
  }
265
348
 
266
349
  clearChecksumCache() {
@@ -277,9 +360,9 @@ export abstract class MongoSyncBucketStorage
277
360
  await this.db.notifyCheckpoint();
278
361
  }
279
362
 
280
- protected abstract getStatusImpl(): Promise<storage.SyncRuleStatus>;
363
+ protected abstract getStatusImpl(): Promise<storage.ReplicationStreamStatus>;
281
364
 
282
- async getStatus(): Promise<storage.SyncRuleStatus> {
365
+ async getStatus(): Promise<storage.ReplicationStreamStatus> {
283
366
  return this.getStatusImpl();
284
367
  }
285
368
 
@@ -292,6 +375,7 @@ export abstract class MongoSyncBucketStorage
292
375
  protected abstract clearBucketState(signal?: AbortSignal): Promise<void>;
293
376
 
294
377
  protected abstract clearSourceTables(signal?: AbortSignal): Promise<void>;
378
+ protected async clearCustomCheckpointRequests(_signal?: AbortSignal): Promise<void> {}
295
379
  protected abstract clearSyncRuleState(): Promise<void>;
296
380
 
297
381
  async clear(options?: storage.ClearStorageOptions): Promise<void> {
@@ -308,32 +392,16 @@ export abstract class MongoSyncBucketStorage
308
392
  await this.clearSourceRecords(signal);
309
393
  await this.clearBucketState(signal);
310
394
  await this.clearSourceTables(signal);
395
+ await this.clearCustomCheckpointRequests(signal);
311
396
 
312
397
  this.#storageInitialized = false;
313
398
  }
314
399
 
315
- protected async clearDeleteMany(
316
- label: string,
317
- operation: () => Promise<mongo.DeleteResult>,
318
- signal?: AbortSignal
319
- ): Promise<void> {
320
- await retryOnMongoMaxTimeMSExpired(operation, {
321
- signal,
322
- abortMessage: 'Aborted clearing data',
323
- retryDelayMs: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS / 5,
324
- onRetry: () => {
325
- this.logger.info(
326
- `Cleared batch of ${label} in ${lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS}ms, continuing...`
327
- );
328
- }
329
- });
330
- }
331
-
332
400
  async reportError(e: any): Promise<void> {
333
401
  const message = String(e.message ?? 'Replication failure');
334
402
  await this.db.sync_rules.updateOne(
335
403
  {
336
- _id: this.group_id
404
+ _id: this.replicationStreamId
337
405
  },
338
406
  {
339
407
  $set: {
@@ -347,32 +415,29 @@ export abstract class MongoSyncBucketStorage
347
415
  async compact(options?: storage.CompactOptions) {
348
416
  let maxOpId = options?.maxOpId;
349
417
  if (maxOpId == null) {
418
+ // For PROCESSING streams, this will be undefined.
350
419
  const checkpoint = await this.getCheckpointInternal();
351
420
  maxOpId = checkpoint?.checkpoint ?? undefined;
352
421
  }
353
422
  await this.createMongoCompactor({ ...options, maxOpId, logger: this.logger }).compact();
354
423
 
355
- if (maxOpId != null && options?.compactParameterData) {
356
- await this.createMongoParameterCompactor(maxOpId, options).compact();
424
+ if (maxOpId != null && options?.compactParameterData && this.replicationStream.state == SyncRuleState.ACTIVE) {
425
+ // Use the stream-scoped logger, matching bucket compaction above.
426
+ await this.createMongoParameterCompactor(maxOpId, { ...options, logger: this.logger }).compact();
357
427
  }
358
428
  }
359
429
 
360
- async populatePersistentChecksumCache(options: PopulateChecksumCacheOptions): Promise<PopulateChecksumCacheResults> {
361
- this.logger.info(`Populating persistent checksum cache...`);
362
- const start = Date.now();
363
- const compactor = this.createMongoCompactor({
364
- ...options,
365
- memoryLimitMB: 0,
366
- logger: this.logger
367
- });
430
+ abstract compactInitialReplication(
431
+ options: CompactInitialReplicationOptions
432
+ ): Promise<CompactInitialReplicationResults>;
368
433
 
369
- const result = await compactor.populateChecksums({
370
- minBucketChanges: options.minBucketChanges ?? 10
371
- });
372
- const duration = Date.now() - start;
373
- this.logger.info(`Populated persistent checksum cache in ${(duration / 1000).toFixed(1)}s`);
374
- return result;
375
- }
434
+ /**
435
+ * The highest op id persisted for this stream, whether or not covered by a checkpoint.
436
+ *
437
+ * Used as the default `maxOpId` for {@link compactInitialReplication}, which runs after
438
+ * initial replication but before the first checkpoint exists.
439
+ */
440
+ protected abstract fetchPersistedOpHead(): Promise<InternalOpId | null>;
376
441
 
377
442
  private async *watchActiveCheckpoint(signal: AbortSignal): AsyncIterable<ReplicationCheckpoint> {
378
443
  if (signal.aborted) {
@@ -413,7 +478,7 @@ export abstract class MongoSyncBucketStorage
413
478
  for await (const nextCheckpoint of iter) {
414
479
  if (nextCheckpoint.lsn != null && !queriedInitialWriteCheckpoint) {
415
480
  writeCheckpoint = await this.writeCheckpointAPI.lastWriteCheckpoint({
416
- sync_rules_id: this.group_id,
481
+ sync_rules_id: this.replicationStreamId,
417
482
  user_id: options.user_id,
418
483
  heads: {
419
484
  '1': nextCheckpoint.lsn
@@ -446,7 +511,7 @@ export abstract class MongoSyncBucketStorage
446
511
  let updatedWriteCheckpoint = updates.updatedWriteCheckpoints.get(options.user_id) ?? null;
447
512
  if (updates.invalidateWriteCheckpoints) {
448
513
  updatedWriteCheckpoint = await this.writeCheckpointAPI.lastWriteCheckpoint({
449
- sync_rules_id: this.group_id,
514
+ sync_rules_id: this.replicationStreamId,
450
515
  user_id: options.user_id,
451
516
  heads: {
452
517
  '1': nextCheckpoint.lsn!
@@ -550,13 +615,31 @@ export abstract class MongoSyncBucketStorage
550
615
  }
551
616
 
552
617
  protected abstract getParameterBucketChangesImpl(
553
- options: GetCheckpointChangesOptions
618
+ options: MongoGetCheckpointChangesOptions
554
619
  ): Promise<Pick<CheckpointChanges, 'updatedParameterLookups' | 'invalidateParameterBuckets'>>;
555
620
 
556
621
  private async getParameterBucketChanges(
557
622
  options: GetCheckpointChangesOptions
558
623
  ): Promise<Pick<CheckpointChanges, 'updatedParameterLookups' | 'invalidateParameterBuckets'>> {
559
- return this.getParameterBucketChangesImpl(options);
624
+ const nextCheckpoint = requireMongoCheckpoint(options.nextCheckpoint);
625
+ if (options.lastCheckpoint.checkpoint < nextCheckpoint.parameterChangesInvalidBefore) {
626
+ // Parameter compaction may have deleted parameter entries in the range we'd have to query
627
+ // to find the individual changed lookups. Invalidate all parameter buckets instead.
628
+ //
629
+ // The fence is committed before the first delete of a compaction pass, and captured in the
630
+ // same snapshot as the checkpoint, so a checkpoint that could miss a deleted entry always
631
+ // observes the fence as well.
632
+ return {
633
+ invalidateParameterBuckets: true,
634
+ updatedParameterLookups: new Set<string>()
635
+ };
636
+ }
637
+ // The query below runs at the checkpoint snapshot, which still sees entries deleted by a
638
+ // compaction pass that started after the snapshot.
639
+ return this.getParameterBucketChangesImpl({
640
+ lastCheckpoint: options.lastCheckpoint,
641
+ nextCheckpoint
642
+ });
560
643
  }
561
644
 
562
645
  private checkpointChangesCache = new LRUCache<
@@ -566,6 +649,9 @@ export abstract class MongoSyncBucketStorage
566
649
  >({
567
650
  max: 50,
568
651
  maxSize: 12 * 1024 * 1024,
652
+ // When we have more fetches than the cache size, complete the fetches instead
653
+ // of failing with Error('evicted').
654
+ ignoreFetchAbort: true,
569
655
  sizeCalculation: (value: InternalCheckpointChanges) => {
570
656
  const paramSize = [...value.updatedParameterLookups].reduce<number>((a, b) => a + b.length, 0);
571
657
  const bucketSize = [...value.updatedDataBuckets].reduce<number>((a, b) => a + b.length, 0);
@@ -578,7 +664,10 @@ export abstract class MongoSyncBucketStorage
578
664
  });
579
665
 
580
666
  async getCheckpointChanges(options: GetCheckpointChangesOptions): Promise<InternalCheckpointChanges> {
581
- const key = `${options.lastCheckpoint.checkpoint}_${options.lastCheckpoint.lsn}__${options.nextCheckpoint.checkpoint}_${options.nextCheckpoint.lsn}`;
667
+ // The invalidation fence is part of the identity: the same checkpoint pair read before and
668
+ // after a compaction pass produces different results (specific lookups vs. invalidate-all).
669
+ const fence = requireMongoCheckpoint(options.nextCheckpoint).parameterChangesInvalidBefore;
670
+ const key = `${options.lastCheckpoint.checkpoint}_${options.lastCheckpoint.lsn}__${options.nextCheckpoint.checkpoint}_${options.nextCheckpoint.lsn}_${fence}`;
582
671
  const result = await this.checkpointChangesCache.fetch(key, { context: { options } });
583
672
  return result!;
584
673
  }
@@ -596,14 +685,29 @@ export abstract class MongoSyncBucketStorage
596
685
  }
597
686
  }
598
687
 
599
- class MongoReplicationCheckpoint implements ReplicationCheckpoint {
688
+ /**
689
+ * We don't support any other constructions of ReplicationCheckpoint.
690
+ */
691
+ function requireMongoCheckpoint(checkpoint: ReplicationCheckpoint): MongoReplicationCheckpoint {
692
+ if (!(checkpoint instanceof MongoReplicationCheckpoint)) {
693
+ throw new ServiceAssertionError(
694
+ `Checkpoint changes require a checkpoint from getCheckpointInternal(), got ${checkpoint.constructor.name}`
695
+ );
696
+ }
697
+ return checkpoint;
698
+ }
699
+
700
+ class MongoReplicationCheckpoint implements MongoSyncBucketStorageCheckpoint {
600
701
  #storage: MongoSyncBucketStorage;
601
702
 
602
703
  constructor(
603
704
  storage: MongoSyncBucketStorage,
604
705
  public readonly checkpoint: InternalOpId,
605
706
  public readonly lsn: string | null,
606
- public snapshotTime: mongo.Timestamp
707
+ public snapshotTime: mongo.Timestamp,
708
+ public clusterTime: mongo.ClusterTime,
709
+ /** Captured in the same snapshot as the checkpoint. */
710
+ public readonly parameterChangesInvalidBefore: InternalOpId
607
711
  ) {
608
712
  this.#storage = storage;
609
713
  }
@@ -613,11 +717,15 @@ class MongoReplicationCheckpoint implements ReplicationCheckpoint {
613
717
  }
614
718
  }
615
719
 
720
+ /**
721
+ * Used when no checkpoint has been persisted yet. This has no snapshot or invalidation fence, so
722
+ * it cannot be used for checkpoint change detection - see {@link requireMongoCheckpoint}.
723
+ */
616
724
  class EmptyReplicationCheckpoint implements ReplicationCheckpoint {
617
725
  readonly checkpoint: InternalOpId = 0n;
618
726
  readonly lsn: string | null = null;
619
727
 
620
- async getParameterSets(_lookups: ScopedParameterLookup[]): Promise<ParameterLookupRows[]> {
728
+ async getParameterSets(_lookups: ScopedParameterLookup[], _limit: number): Promise<ParameterLookupRows[]> {
621
729
  return [];
622
730
  }
623
731
  }