@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
@@ -5,6 +5,8 @@ import { ErrorCode, Logger, ServiceError } from '@powersync/lib-services-framewo
5
5
  import { storage } from '@powersync/service-core';
6
6
  import { VersionedPowerSyncMongo } from './db.js';
7
7
 
8
+ const LOCK_DURATION_MS = 60 * 1000;
9
+
8
10
  /**
9
11
  * Manages a lock on a replication stream document, so that only one process
10
12
  * processes that replication stream at a time.
@@ -17,20 +19,25 @@ export class MongoSyncRulesLock implements storage.ReplicationLock {
17
19
  */
18
20
  static async createLock(
19
21
  db: VersionedPowerSyncMongo,
20
- sync_rules: storage.PersistedSyncRulesContent,
22
+ sync_rules: storage.PersistedReplicationStream,
21
23
  session?: mongo.ClientSession
22
24
  ): Promise<MongoSyncRulesLock> {
23
25
  const lockId = crypto.randomBytes(8).toString('hex');
24
26
  const doc = await db.sync_rules.findOneAndUpdate(
25
- { _id: sync_rules.id, $or: [{ lock: null }, { 'lock.expires_at': { $lt: new Date() } }] },
26
27
  {
27
- $set: {
28
- lock: {
29
- id: lockId,
30
- expires_at: new Date(Date.now() + 60 * 1000)
28
+ _id: sync_rules.replicationStreamId,
29
+ $or: [{ lock: null }, { $expr: { $lt: ['$lock.expires_at', '$$NOW'] } }]
30
+ },
31
+ [
32
+ {
33
+ $set: {
34
+ lock: {
35
+ id: lockId,
36
+ expires_at: { $dateAdd: { startDate: '$$NOW', unit: 'millisecond', amount: LOCK_DURATION_MS } }
37
+ }
31
38
  }
32
39
  }
33
- },
40
+ ],
34
41
  {
35
42
  projection: { lock: 1 },
36
43
  returnDocument: 'before',
@@ -40,7 +47,10 @@ export class MongoSyncRulesLock implements storage.ReplicationLock {
40
47
 
41
48
  if (doc == null) {
42
49
  // Query the existing lock to get the expiration time (best effort - it may have been released in the meantime).
43
- const heldLock = await db.sync_rules.findOne({ _id: sync_rules.id }, { projection: { lock: 1 }, session });
50
+ const heldLock = await db.sync_rules.findOne(
51
+ { _id: sync_rules.replicationStreamId },
52
+ { projection: { lock: 1 }, session }
53
+ );
44
54
  if (heldLock?.lock?.expires_at) {
45
55
  throw new ServiceError(
46
56
  ErrorCode.PSYNC_S1003,
@@ -51,7 +61,7 @@ export class MongoSyncRulesLock implements storage.ReplicationLock {
51
61
  }
52
62
  }
53
63
  sync_rules.logger.info(`Locked replication stream for processing`);
54
- return new MongoSyncRulesLock(db, sync_rules.id, lockId, sync_rules.logger);
64
+ return new MongoSyncRulesLock(db, sync_rules.replicationStreamId, lockId, sync_rules.logger);
55
65
  }
56
66
 
57
67
  constructor(
@@ -93,9 +103,15 @@ export class MongoSyncRulesLock implements storage.ReplicationLock {
93
103
  _id: this.sync_rules_id,
94
104
  'lock.id': this.lock_id
95
105
  },
96
- {
97
- $set: { 'lock.expires_at': new Date(Date.now() + 60 * 1000) }
98
- },
106
+ [
107
+ {
108
+ $set: {
109
+ 'lock.expires_at': {
110
+ $dateAdd: { startDate: '$$NOW', unit: 'millisecond', amount: LOCK_DURATION_MS }
111
+ }
112
+ }
113
+ }
114
+ ],
99
115
  { returnDocument: 'after' }
100
116
  );
101
117
  if (result == null) {
@@ -1,55 +1,223 @@
1
- import { mongo } from '@powersync/lib-service-mongodb';
2
1
  import * as framework from '@powersync/lib-services-framework';
3
- import { GetCheckpointChangesOptions, InternalOpId, storage } from '@powersync/service-core';
2
+ import { GetCheckpointChangesOptions, storage } from '@powersync/service-core';
4
3
  import { VersionedPowerSyncMongo } from './db.js';
5
4
 
6
5
  export type MongoCheckpointAPIOptions = {
7
6
  db: VersionedPowerSyncMongo;
8
- mode: storage.WriteCheckpointMode;
9
- sync_rules_id: number;
7
+ writeCheckpointMode: storage.WriteCheckpointModeConfig;
8
+ replicationStreamId: number;
10
9
  };
11
10
 
12
11
  export class MongoWriteCheckpointAPI implements storage.WriteCheckpointAPI {
13
12
  readonly db: VersionedPowerSyncMongo;
13
+ readonly replicationStreamId: number;
14
+
14
15
  private _mode: storage.WriteCheckpointMode;
15
16
 
16
17
  constructor(options: MongoCheckpointAPIOptions) {
17
18
  this.db = options.db;
18
- this._mode = options.mode;
19
+ this._mode = options.writeCheckpointMode.mode;
20
+ this.replicationStreamId = options.replicationStreamId;
19
21
  }
20
22
 
21
23
  get writeCheckpointMode() {
22
24
  return this._mode;
23
25
  }
24
26
 
25
- setWriteCheckpointMode(mode: storage.WriteCheckpointMode): void {
26
- this._mode = mode;
27
+ setWriteCheckpointMode(config: storage.WriteCheckpointModeConfig): void {
28
+ this._mode = config.mode;
27
29
  }
28
30
 
29
- async createManagedWriteCheckpoint(checkpoint: storage.ManagedWriteCheckpointOptions): Promise<bigint> {
31
+ async createManagedWriteCheckpoints(
32
+ checkpoints: storage.ManagedWriteCheckpointOptions[]
33
+ ): Promise<storage.CreateManagedWriteCheckpointsResult> {
30
34
  if (this.writeCheckpointMode !== storage.WriteCheckpointMode.MANAGED) {
31
35
  throw new framework.ServiceAssertionError(
32
36
  `Attempting to create a managed Write Checkpoint when the current Write Checkpoint mode is set to "${this.writeCheckpointMode}"`
33
37
  );
34
38
  }
35
39
 
36
- const { user_id, heads: lsns } = checkpoint;
37
- const doc = await this.db.write_checkpoints.findOneAndUpdate(
38
- {
39
- user_id: user_id
40
- },
41
- {
42
- $set: {
43
- lsns,
44
- processed_at_lsn: null
40
+ const uniqueCheckpoints = storage.uniqueManagedWriteCheckpoints(checkpoints);
41
+ if (uniqueCheckpoints.length == 0) {
42
+ return { writeCheckpoints: new Map(), shouldAdvance: false };
43
+ }
44
+
45
+ let shouldAdvance = false;
46
+ const writeCheckpoints = new Map<string, bigint>();
47
+ const generatedCheckpoints = uniqueCheckpoints.filter((checkpoint) => checkpoint.checkpoint_request_id == null);
48
+ const suppliedCheckpoints = uniqueCheckpoints.filter((checkpoint) => checkpoint.checkpoint_request_id != null);
49
+
50
+ if (generatedCheckpoints.length == 1) {
51
+ // For the common case of a single checkpoint, we can do this in a single request.
52
+ const { user_id, heads: lsns } = generatedCheckpoints[0];
53
+ const doc = await this.db.write_checkpoints.findOneAndUpdate(
54
+ {
55
+ user_id
45
56
  },
46
- $inc: {
47
- client_id: 1n
48
- }
49
- },
50
- { upsert: true, returnDocument: 'after' }
57
+ {
58
+ $set: {
59
+ lsns,
60
+ processed_at_lsn: null
61
+ },
62
+ // Unset rather than set to null, to keep the document out of the
63
+ // partial checkpoint_requested_at index used for retention cleanup.
64
+ $unset: {
65
+ checkpoint_requested_at: 1
66
+ },
67
+ $inc: {
68
+ client_id: 1n
69
+ }
70
+ },
71
+ { upsert: true, returnDocument: 'after' }
72
+ );
73
+
74
+ // Generated checkpoints always reset processed_at_lsn, so they are pending
75
+ // and the source marker must be forced.
76
+ shouldAdvance = true;
77
+ writeCheckpoints.set(doc!.user_id, doc!.client_id);
78
+ } else if (generatedCheckpoints.length > 1) {
79
+ // For more than one generated checkpoint, this gives a constant 2 requests.
80
+ await this.db.write_checkpoints.bulkWrite(
81
+ generatedCheckpoints.map(({ user_id, heads: lsns }) => ({
82
+ updateOne: {
83
+ filter: { user_id },
84
+ update: {
85
+ $set: {
86
+ lsns,
87
+ processed_at_lsn: null
88
+ },
89
+ $unset: {
90
+ checkpoint_requested_at: 1
91
+ },
92
+ $inc: {
93
+ client_id: 1n
94
+ }
95
+ },
96
+ upsert: true
97
+ }
98
+ }))
99
+ );
100
+
101
+ const userIds = generatedCheckpoints.map((checkpoint) => checkpoint.user_id);
102
+ const docs = await this.db.write_checkpoints
103
+ .find(
104
+ {
105
+ user_id: { $in: userIds }
106
+ },
107
+ {
108
+ projection: {
109
+ user_id: 1,
110
+ client_id: 1
111
+ }
112
+ }
113
+ )
114
+ .toArray();
115
+
116
+ shouldAdvance = true;
117
+ for (const doc of docs) {
118
+ writeCheckpoints.set(doc.user_id, doc.client_id);
119
+ }
120
+ }
121
+
122
+ if (suppliedCheckpoints.length > 0) {
123
+ const suppliedResult = await this.createSuppliedManagedWriteCheckpoints(suppliedCheckpoints);
124
+ shouldAdvance ||= suppliedResult.shouldAdvance;
125
+ for (const [userId, writeCheckpoint] of suppliedResult.writeCheckpoints) {
126
+ writeCheckpoints.set(userId, writeCheckpoint);
127
+ }
128
+ }
129
+
130
+ return { writeCheckpoints, shouldAdvance };
131
+ }
132
+
133
+ private async createSuppliedManagedWriteCheckpoints(checkpoints: storage.ManagedWriteCheckpointOptions[]) {
134
+ const checkpointRequestedAt = new Date();
135
+
136
+ await this.db.write_checkpoints.bulkWrite(
137
+ checkpoints.map((checkpoint) => {
138
+ const { user_id, heads: lsns } = checkpoint;
139
+ const checkpointRequestId = checkpoint.checkpoint_request_id!;
140
+
141
+ // Supplied request ids are monotonic. updateMany preserves the existing
142
+ // upsert behavior for first checkpoint requests, while also advancing
143
+ // every duplicate row for the user_id that is lower than this request id.
144
+ const shouldApplyRequestId = {
145
+ $or: [
146
+ { $eq: [{ $ifNull: ['$client_id', null] }, null] },
147
+ { $lt: ['$client_id', { $literal: checkpointRequestId }] }
148
+ ]
149
+ };
150
+ // Equal retries keep their original source head but refresh retention.
151
+ const shouldRefreshRequest = {
152
+ $or: [
153
+ { $eq: [{ $ifNull: ['$client_id', null] }, null] },
154
+ { $lte: ['$client_id', { $literal: checkpointRequestId }] }
155
+ ]
156
+ };
157
+
158
+ return {
159
+ updateMany: {
160
+ filter: { user_id },
161
+ update: [
162
+ {
163
+ $set: {
164
+ user_id,
165
+ client_id: {
166
+ $cond: [shouldApplyRequestId, { $literal: checkpointRequestId }, '$client_id']
167
+ },
168
+ lsns: {
169
+ $cond: [shouldApplyRequestId, { $literal: lsns }, '$lsns']
170
+ },
171
+ processed_at_lsn: {
172
+ $cond: [shouldApplyRequestId, null, '$processed_at_lsn']
173
+ },
174
+ checkpoint_requested_at: {
175
+ $cond: [shouldRefreshRequest, { $literal: checkpointRequestedAt }, '$checkpoint_requested_at']
176
+ }
177
+ }
178
+ }
179
+ ],
180
+ upsert: true
181
+ }
182
+ };
183
+ })
51
184
  );
52
- return doc!.client_id;
185
+
186
+ // Fetch the final ids separately so stale requests can still return the
187
+ // checkpoint currently stored. If duplicate rows already exist for a
188
+ // user_id, use the highest stored id instead of whichever document MongoDB
189
+ // happens to return first.
190
+ const userIds = checkpoints.map((checkpoint) => checkpoint.user_id);
191
+ const docs = await this.db.write_checkpoints
192
+ .find(
193
+ {
194
+ user_id: { $in: userIds }
195
+ },
196
+ {
197
+ projection: {
198
+ user_id: 1,
199
+ client_id: 1,
200
+ processed_at_lsn: 1
201
+ }
202
+ }
203
+ )
204
+ .toArray();
205
+ const maxDocsByUser = new Map<string, (typeof docs)[number]>();
206
+ for (const doc of docs) {
207
+ const existing = maxDocsByUser.get(doc.user_id);
208
+ if (
209
+ existing == null ||
210
+ doc.client_id > existing.client_id ||
211
+ (doc.client_id == existing.client_id && existing.processed_at_lsn != null && doc.processed_at_lsn == null)
212
+ ) {
213
+ maxDocsByUser.set(doc.user_id, doc);
214
+ }
215
+ }
216
+
217
+ return {
218
+ writeCheckpoints: new Map([...maxDocsByUser].map(([userId, doc]) => [userId, doc.client_id])),
219
+ shouldAdvance: [...maxDocsByUser.values()].some((doc) => doc.processed_at_lsn == null)
220
+ };
53
221
  }
54
222
 
55
223
  async lastWriteCheckpoint(filters: storage.LastWriteCheckpointFilters): Promise<bigint | null> {
@@ -81,6 +249,7 @@ export class MongoWriteCheckpointAPI implements storage.WriteCheckpointAPI {
81
249
  }
82
250
 
83
251
  protected async lastCustomWriteCheckpoint(filters: storage.CustomWriteCheckpointFilters) {
252
+ // Legacy V1/V2 implementation.
84
253
  const { user_id, sync_rules_id } = filters;
85
254
  const lastWriteCheckpoint = await this.db.custom_write_checkpoints.findOne({
86
255
  user_id,
@@ -135,8 +304,9 @@ export class MongoWriteCheckpointAPI implements storage.WriteCheckpointAPI {
135
304
  };
136
305
  }
137
306
 
138
- private async getCustomWriteCheckpointChanges(options: GetCheckpointChangesOptions) {
307
+ protected async getCustomWriteCheckpointChanges(options: GetCheckpointChangesOptions) {
139
308
  const limit = 1000;
309
+ // Legacy V1/V2 implementation.
140
310
  const changes = await this.db.custom_write_checkpoints
141
311
  .find(
142
312
  {
@@ -166,31 +336,3 @@ export class MongoWriteCheckpointAPI implements storage.WriteCheckpointAPI {
166
336
  };
167
337
  }
168
338
  }
169
-
170
- export async function batchCreateCustomWriteCheckpoints(
171
- db: VersionedPowerSyncMongo,
172
- session: mongo.ClientSession,
173
- checkpoints: storage.CustomWriteCheckpointOptions[],
174
- opId: InternalOpId
175
- ): Promise<void> {
176
- if (checkpoints.length == 0) {
177
- return;
178
- }
179
-
180
- await db.custom_write_checkpoints.bulkWrite(
181
- checkpoints.map((checkpointOptions) => ({
182
- updateOne: {
183
- filter: { user_id: checkpointOptions.user_id, sync_rules_id: checkpointOptions.sync_rules_id },
184
- update: {
185
- $set: {
186
- checkpoint: checkpointOptions.checkpoint,
187
- sync_rules_id: checkpointOptions.sync_rules_id,
188
- op_id: opId
189
- }
190
- },
191
- upsert: true
192
- }
193
- })),
194
- { session }
195
- );
196
- }
@@ -2,21 +2,28 @@ import { mongo } from '@powersync/lib-service-mongodb';
2
2
  import { storage } from '@powersync/service-core';
3
3
 
4
4
  /**
5
- * Update pipeline to update replication stream status, covering all storage versions.
5
+ * Update pipeline to stop a deploying replication stream, covering all storage versions.
6
6
  *
7
7
  * Roughly equivalent to:
8
8
  * $set: {
9
- * state: state,
10
- * 'sync_configs.$[].state': state
9
+ * state: STOP,
10
+ * 'sync_configs.$[].state': STOP
11
11
  * }
12
12
  *
13
13
  * The difference is that this also handles v1 storage cases, where `sync_configs` is not present.
14
+ *
15
+ * This can be used for specific use cases:
16
+ * 1. Stop an existing PROCESSING stream, when it is being replaced by a new PROCESSING one.
17
+ * 2. Stop an existing ACTIVE or ERRORED stream, when it is being replaced by a new ACTIVE one.
18
+ *
19
+ * Do not use this when an ACTIVE stream transitions to ERRORED, and is being replaced by a new PROCESSING one.
20
+ * In that case, we need to maintain the ERRORED state, which this pipeline does not cover.
14
21
  */
15
- export function syncRuleStateUpdatePipeline(state: storage.SyncRuleState): mongo.Document[] {
22
+ export function stopReplicationStreamPipeline(): mongo.Document[] {
16
23
  return [
17
24
  {
18
25
  $set: {
19
- state,
26
+ state: storage.SyncRuleState.STOP,
20
27
  sync_configs: {
21
28
  $cond: [
22
29
  { $isArray: '$sync_configs' },
@@ -25,7 +32,7 @@ export function syncRuleStateUpdatePipeline(state: storage.SyncRuleState): mongo
25
32
  input: '$sync_configs',
26
33
  as: 'config',
27
34
  in: {
28
- $mergeObjects: ['$$config', { state }]
35
+ $mergeObjects: ['$$config', { state: storage.SyncRuleState.STOP }]
29
36
  }
30
37
  }
31
38
  },
@@ -0,0 +1,31 @@
1
+ import { mongo } from '@powersync/lib-service-mongodb';
2
+ import { InternalOpId, ReplicationCheckpoint } from '@powersync/service-core';
3
+ import * as bson from 'bson';
4
+
5
+ export interface MongoSyncBucketStorageCheckpoint extends ReplicationCheckpoint {
6
+ checkpoint: InternalOpId;
7
+ snapshotTime: bson.Timestamp;
8
+ clusterTime: mongo.ClusterTime;
9
+
10
+ /**
11
+ * The stream's `parameter_compaction.checkpoint_changes_invalid_before` boundary, read in the
12
+ * same snapshot as {@link checkpoint} and {@link snapshotTime}.
13
+ *
14
+ * Parameter compaction may have deleted parameter entries below this boundary, so checkpoint
15
+ * change detection cannot enumerate individual lookup changes from a checkpoint below it.
16
+ *
17
+ * 0n for streams that have never been compacted.
18
+ */
19
+ parameterChangesInvalidBefore: InternalOpId;
20
+ }
21
+
22
+ /**
23
+ * MongoDB-specific version of GetCheckpointChangesOptions.
24
+ *
25
+ * The next checkpoint carries the snapshot and invalidation boundary that change detection must
26
+ * be evaluated against. Only `checkpoint` is used from the previous one.
27
+ */
28
+ export interface MongoGetCheckpointChangesOptions {
29
+ lastCheckpoint: ReplicationCheckpoint;
30
+ nextCheckpoint: MongoSyncBucketStorageCheckpoint;
31
+ }
@@ -3,14 +3,14 @@ import { BucketDataSource, BucketDefinitionId, EvaluatedParameters, EvaluatedRow
3
3
  import * as bson from 'bson';
4
4
 
5
5
  import { logger as defaultLogger, Logger } from '@powersync/lib-services-framework';
6
- import { InternalOpId, storage, utils } from '@powersync/service-core';
6
+ import { BucketDefinitionMapping, InternalOpId, storage, utils } from '@powersync/service-core';
7
7
  import { JSONBig } from '@powersync/service-jsonbig';
8
8
  import { mongoTableId, replicaIdToSubkey } from '../../../utils/util.js';
9
- import { BucketDefinitionMapping } from '../BucketDefinitionMapping.js';
10
9
  import { currentBucketKey, MAX_ROW_SIZE } from '../MongoBucketBatchShared.js';
11
10
  import { MongoIdSequence } from '../MongoIdSequence.js';
12
11
  import type { VersionedPowerSyncMongo } from '../db.js';
13
12
  import { TaggedBucketParameterDocument } from '../models.js';
13
+ import { ObjectStorage } from '../v3/object-storage/ObjectStorage.js';
14
14
  import { BucketDataDoc, BucketKey } from './BucketDataDoc.js';
15
15
  import { SourceRecordBucketState, SourceRecordLookupState } from './SourceRecordStore.js';
16
16
 
@@ -35,6 +35,8 @@ const MAX_TRANSACTION_BATCH_SIZE = 30_000_000;
35
35
  */
36
36
  const MAX_TRANSACTION_DOC_COUNT = 2_000;
37
37
 
38
+ export const DEFAULT_INLINE_THRESHOLD_BYTES = 16 * 1024;
39
+
38
40
  export interface SaveBucketDataOptions {
39
41
  op_seq: MongoIdSequence;
40
42
  sourceKey: storage.ReplicaId;
@@ -61,6 +63,13 @@ export interface UpsertCurrentDataOptions {
61
63
 
62
64
  export interface PersistedBatchOptions {
63
65
  logger?: Logger;
66
+ objectStorage?: ObjectStorage;
67
+ inlineThresholdBytes?: number;
68
+ /**
69
+ * Aborts in-flight object storage uploads when replication stops.
70
+ */
71
+ signal?: AbortSignal;
72
+ objectStorageUsageWriterId?: string;
64
73
  }
65
74
 
66
75
  /**
@@ -75,6 +84,11 @@ export abstract class PersistedBatch {
75
84
  bucketParameters: TaggedBucketParameterDocument[] = [];
76
85
  bucketStates: Map<string, BucketStateUpdate> = new Map();
77
86
 
87
+ protected readonly objectStorage?: ObjectStorage;
88
+ protected readonly inlineThresholdBytes: number = DEFAULT_INLINE_THRESHOLD_BYTES;
89
+ protected readonly signal?: AbortSignal;
90
+ protected readonly objectStorageUsageWriterId?: string;
91
+
78
92
  /**
79
93
  * For debug logging only.
80
94
  */
@@ -94,10 +108,16 @@ export abstract class PersistedBatch {
94
108
  ) {
95
109
  this.currentSize = writtenSize;
96
110
  this.logger = options?.logger ?? defaultLogger;
111
+ this.objectStorage = options?.objectStorage;
112
+ this.signal = options?.signal;
113
+ this.objectStorageUsageWriterId = options?.objectStorageUsageWriterId;
114
+ if (options?.inlineThresholdBytes != null) {
115
+ this.inlineThresholdBytes = options.inlineThresholdBytes;
116
+ }
97
117
  }
98
118
 
99
119
  saveBucketData(options: SaveBucketDataOptions) {
100
- const remaining_buckets = new Map<string, SaveBucketDataOptions['before_buckets'][number]>();
120
+ const remaining_buckets = new Map<string, SourceRecordBucketState>();
101
121
  for (let bucket of options.before_buckets) {
102
122
  const mapped: SourceRecordBucketState = {
103
123
  bucket: bucket.bucket,
@@ -105,10 +125,16 @@ export abstract class PersistedBatch {
105
125
  id: bucket.id,
106
126
  table: bucket.table
107
127
  };
128
+ if (options.table.bucketDataSourceIds != null && !options.table.bucketDataSourceIds.has(mapped.definitionId!)) {
129
+ // The bucket definition is not active anymore.
130
+ // We don't cleanup these references upfront, but do need to ignore them after the definition is removed.
131
+ continue;
132
+ }
108
133
  remaining_buckets.set(currentBucketKey(mapped), mapped);
109
134
  }
110
135
 
111
- const dchecksum = BigInt(utils.hashDelete(replicaIdToSubkey(options.table.id, options.sourceKey)));
136
+ const subkey = this.persistedSubkey(options.table.id, options.sourceKey);
137
+ const dchecksum = BigInt(utils.hashDelete(subkey));
112
138
 
113
139
  for (const evaluated of options.evaluated) {
114
140
  const definitionId = this.getBucketDefinitionId(evaluated.source);
@@ -143,6 +169,7 @@ export abstract class PersistedBatch {
143
169
  bucket: evaluated.bucket,
144
170
  sourceTableId: options.table.id,
145
171
  sourceKey: options.sourceKey,
172
+ subkey,
146
173
  table: evaluated.table,
147
174
  rowId: evaluated.id,
148
175
  checksum: BigInt(checksum),
@@ -165,6 +192,7 @@ export abstract class PersistedBatch {
165
192
  op_id,
166
193
  sourceTableId: options.table.id,
167
194
  sourceKey: options.sourceKey,
195
+ subkey,
168
196
  table: bucket.table,
169
197
  rowId: bucket.id,
170
198
  checksum: dchecksum
@@ -201,6 +229,14 @@ export abstract class PersistedBatch {
201
229
  protected abstract checkDefinitionId(definitionId: BucketDefinitionId | null): BucketDefinitionId;
202
230
  protected abstract getBucketDefinitionId(bucketSource: BucketDataSource): BucketDefinitionId;
203
231
 
232
+ /**
233
+ * New bucket operations persist protocol subkeys during replication. Older
234
+ * V1 documents may not have one, so sync reads retain a fallback.
235
+ */
236
+ protected persistedSubkey(table: storage.SourceTableId, key: storage.ReplicaId): string {
237
+ return replicaIdToSubkey(table, key);
238
+ }
239
+
204
240
  protected get bucketDataCount(): number {
205
241
  return this.bucketData.length;
206
242
  }
@@ -218,17 +254,45 @@ export abstract class PersistedBatch {
218
254
  bucket,
219
255
  lastOp: op_id,
220
256
  incrementCount: 1,
221
- incrementBytes: bytes
257
+ incrementBytes: bytes,
258
+ incrementChunks: 0
222
259
  });
223
260
  }
224
261
  }
225
262
 
263
+ /**
264
+ * V3 persists operations in chunks. Keep this separate from incrementBucket:
265
+ * operation counts are known while evaluating rows, while chunk counts and
266
+ * exact persisted bytes are only known after the writer has chunked a flush.
267
+ */
268
+ protected incrementBucketPersistedChunk(definitionId: BucketDefinitionId, bucket: string, bytes: number) {
269
+ const key = `${definitionId ?? ''}:${bucket}`;
270
+ const existingState = this.bucketStates.get(key);
271
+ if (existingState != null) {
272
+ existingState.incrementChunks += 1;
273
+ existingState.incrementBytes += bytes;
274
+ }
275
+ }
276
+
277
+ /**
278
+ * V3's compactor calculates byte deltas from persisted chunk metadata.
279
+ * Replace the writer's per-operation estimate with those exact sizes before
280
+ * flushing the corresponding bucket state.
281
+ */
282
+ protected resetBucketPersistedBytes(definitionId: BucketDefinitionId, bucket: string) {
283
+ const state = this.bucketStates.get(`${definitionId ?? ''}:${bucket}`);
284
+ if (state != null) {
285
+ state.incrementBytes = 0;
286
+ }
287
+ }
288
+
226
289
  protected addBucketDataPut(options: {
227
290
  op_id: InternalOpId;
228
291
  bucketKey: BucketKey;
229
292
  bucket: string;
230
293
  sourceTableId: storage.SourceTable['id'];
231
294
  sourceKey: storage.ReplicaId;
295
+ subkey: string;
232
296
  table: string;
233
297
  rowId: string;
234
298
  checksum: bigint;
@@ -240,6 +304,7 @@ export abstract class PersistedBatch {
240
304
  op: 'PUT',
241
305
  source_table: mongoTableId(options.sourceTableId),
242
306
  source_key: options.sourceKey,
307
+ subkey: options.subkey,
243
308
  table: options.table,
244
309
  row_id: options.rowId,
245
310
  checksum: options.checksum,
@@ -252,6 +317,7 @@ export abstract class PersistedBatch {
252
317
  bucketKey: BucketKey;
253
318
  sourceTableId: storage.SourceTable['id'];
254
319
  sourceKey: storage.ReplicaId;
320
+ subkey: string;
255
321
  table: string;
256
322
  rowId: string;
257
323
  checksum: bigint;
@@ -262,6 +328,7 @@ export abstract class PersistedBatch {
262
328
  op: 'REMOVE',
263
329
  source_table: mongoTableId(options.sourceTableId),
264
330
  source_key: options.sourceKey,
331
+ subkey: options.subkey,
265
332
  table: options.table,
266
333
  row_id: options.rowId,
267
334
  checksum: options.checksum,
@@ -305,32 +372,32 @@ export abstract class PersistedBatch {
305
372
  const replicationLag = Math.round((Date.now() - options.oldestUncommittedChange.getTime()) / 1000);
306
373
 
307
374
  this.logger.info(
308
- `Flushed ${this.bucketDataCount} + ${this.bucketParameters.length} + ${
375
+ `Flushed: ${this.bucketDataCount} ops, ${this.bucketParameters.length} index entries, ${
309
376
  this.currentDataCount
310
- } updates, ${Math.round(this.currentSize / 1024)}kb in ${duration}ms. Last op_id: ${this.debugLastOpId}. Replication lag: ${replicationLag}s`,
377
+ } records. ${Math.round(this.currentSize / 1024)}kb in ${duration}ms. Last op_id: ${this.debugLastOpId}. Replication lag: ${replicationLag}s`,
311
378
  {
312
379
  flushed: {
313
380
  duration: duration,
314
381
  size: this.currentSize,
315
- bucket_data_count: this.bucketDataCount,
316
- parameter_data_count: this.bucketParameters.length,
317
- current_data_count: this.currentDataCount,
382
+ bucket_ops_count: this.bucketDataCount,
383
+ parameter_indexes_count: this.bucketParameters.length,
384
+ source_records_count: this.currentDataCount,
318
385
  replication_lag_seconds: replicationLag
319
386
  }
320
387
  }
321
388
  );
322
389
  } else {
323
390
  this.logger.info(
324
- `Flushed ${this.bucketDataCount} + ${this.bucketParameters.length} + ${
391
+ `Flushed: ${this.bucketDataCount} ops, ${this.bucketParameters.length} index entries, ${
325
392
  this.currentDataCount
326
- } updates, ${Math.round(this.currentSize / 1024)}kb in ${duration}ms. Last op_id: ${this.debugLastOpId}`,
393
+ } records. ${Math.round(this.currentSize / 1024)}kb in ${duration}ms. Last op_id: ${this.debugLastOpId}`,
327
394
  {
328
395
  flushed: {
329
396
  duration: duration,
330
397
  size: this.currentSize,
331
- bucket_data_count: this.bucketDataCount,
332
- parameter_data_count: this.bucketParameters.length,
333
- current_data_count: this.currentDataCount
398
+ bucket_ops_count: this.bucketDataCount,
399
+ parameter_indexes_count: this.bucketParameters.length,
400
+ source_records_count: this.currentDataCount
334
401
  }
335
402
  }
336
403
  );
@@ -361,4 +428,5 @@ export interface BucketStateUpdate {
361
428
  lastOp: InternalOpId;
362
429
  incrementCount: number;
363
430
  incrementBytes: number;
431
+ incrementChunks: number;
364
432
  }