@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
@@ -1,5 +1,5 @@
1
1
  import { mongo } from '@powersync/lib-service-mongodb';
2
- import { BucketDefinitionId, ParameterIndexId } from '@powersync/service-sync-rules';
2
+ import { BucketDefinitionId, EventDefinitionId, ParameterIndexId } from '@powersync/service-sync-rules';
3
3
  import { BaseVersionedPowerSyncMongo } from '../common/VersionedPowerSyncMongoBase.js';
4
4
  import { CommonSourceTableDocument } from '../models.js';
5
5
  import {
@@ -7,58 +7,134 @@ import {
7
7
  BucketParameterDocumentV3,
8
8
  BucketStateDocumentV3,
9
9
  CurrentDataDocumentV3,
10
+ CustomCheckpointRequestDocumentV3,
11
+ ObjectStorageDeletionMarker,
12
+ ObjectStorageUsageDocument,
10
13
  SourceTableDocumentV3,
11
14
  SyncConfigDefinition
12
15
  } from './models.js';
13
16
 
17
+ export const OBJECT_STORAGE_USAGE_COLLECTION = 'object_storage_usage';
18
+ export type CustomCheckpointRequestCollectionSpecifier = {
19
+ replicationStreamId: number;
20
+ eventId: EventDefinitionId;
21
+ };
22
+
14
23
  export class VersionedPowerSyncMongoV3 extends BaseVersionedPowerSyncMongo {
24
+ constructor(
25
+ upstream: ConstructorParameters<typeof BaseVersionedPowerSyncMongo>[0],
26
+ storageConfig: ConstructorParameters<typeof BaseVersionedPowerSyncMongo>[1]
27
+ ) {
28
+ super(upstream, storageConfig);
29
+ }
30
+
15
31
  get syncConfigDefinitions() {
16
32
  return this.db.collection<SyncConfigDefinition>('sync_config');
17
33
  }
18
34
 
19
- sourceRecordsV3(replicationStreamId: number, sourceTableId: mongo.ObjectId): mongo.Collection<CurrentDataDocumentV3> {
35
+ sourceRecords(replicationStreamId: number, sourceTableId: mongo.ObjectId): mongo.Collection<CurrentDataDocumentV3> {
20
36
  const collectionName = this.sourceRecordsCollectionName(replicationStreamId, sourceTableId);
21
37
  return this.db.collection<CurrentDataDocumentV3>(collectionName);
22
38
  }
23
39
 
24
- async listSourceRecordCollectionsV3(replicationStreamId: number): Promise<mongo.Collection<CurrentDataDocumentV3>[]> {
40
+ async listSourceRecordCollections(replicationStreamId: number): Promise<mongo.Collection<CurrentDataDocumentV3>[]> {
25
41
  return this.listCollectionsByPrefix<CurrentDataDocumentV3>(`source_records_${replicationStreamId}_`);
26
42
  }
27
43
 
28
- async initializeSourceRecordsCollection(replicationStreamId: number, sourceTableId: mongo.ObjectId) {
29
- await this.sourceRecordsV3(replicationStreamId, sourceTableId).createIndex(
44
+ async initializeSourceRecordsCollection(
45
+ replicationStreamId: number,
46
+ sourceTableId: mongo.ObjectId,
47
+ session?: mongo.ClientSession
48
+ ) {
49
+ await this.sourceRecords(replicationStreamId, sourceTableId).createIndex(
30
50
  {
31
51
  pending_delete: 1
32
52
  },
33
53
  {
34
54
  partialFilterExpression: { pending_delete: { $exists: true } },
35
- name: 'pending_delete'
55
+ name: 'pending_delete',
56
+ session
36
57
  }
37
58
  );
38
59
  }
39
60
 
40
61
  commonSourceTables(replicationStreamId: number): mongo.Collection<CommonSourceTableDocument> {
41
- return this.sourceTablesV3(replicationStreamId) as mongo.Collection<CommonSourceTableDocument>;
62
+ return this.sourceTables(replicationStreamId) as any as mongo.Collection<CommonSourceTableDocument>;
42
63
  }
43
64
 
44
- bucketStateV3(replicationStreamId: number): mongo.Collection<BucketStateDocumentV3> {
65
+ bucketState(replicationStreamId: number): mongo.Collection<BucketStateDocumentV3> {
45
66
  return this.db.collection(`bucket_state_${replicationStreamId}`);
46
67
  }
47
68
 
48
- parameterIndexV3(
49
- replicationStreamId: number,
50
- indexId: ParameterIndexId
51
- ): mongo.Collection<BucketParameterDocumentV3> {
69
+ parameterIndex(replicationStreamId: number, indexId: ParameterIndexId): mongo.Collection<BucketParameterDocumentV3> {
52
70
  return this.db.collection(`parameter_index_${replicationStreamId}_${indexId}`);
53
71
  }
54
72
 
55
- sourceTablesV3(replicationStreamId: number): mongo.Collection<SourceTableDocumentV3> {
73
+ sourceTables(replicationStreamId: number): mongo.Collection<SourceTableDocumentV3> {
56
74
  return this.db.collection<SourceTableDocumentV3>(this.sourceTableCollectionName(replicationStreamId));
57
75
  }
58
76
 
77
+ bucketData(replicationStreamId: number, definitionId: BucketDefinitionId): mongo.Collection<BucketDataDocumentV3> {
78
+ return this.db.collection<BucketDataDocumentV3>(`bucket_data_${replicationStreamId}_${definitionId}`);
79
+ }
80
+
81
+ pendingObjectStorageDeletes(replicationStreamId: number): mongo.Collection<ObjectStorageDeletionMarker> {
82
+ return this.db.collection<ObjectStorageDeletionMarker>(`pending_object_storage_deletes_${replicationStreamId}`);
83
+ }
84
+
85
+ get objectStorageUsage(): mongo.Collection<ObjectStorageUsageDocument> {
86
+ return this.db.collection<ObjectStorageUsageDocument>(OBJECT_STORAGE_USAGE_COLLECTION);
87
+ }
88
+
89
+ customCheckpointRequests(options: CustomCheckpointRequestCollectionSpecifier) {
90
+ const collectionName = this.customCheckpointRequestCollectionName(options);
91
+ return this.db.collection<CustomCheckpointRequestDocumentV3>(collectionName);
92
+ }
93
+
94
+ async initializeCustomCheckpointRequestsCollection(options: CustomCheckpointRequestCollectionSpecifier) {
95
+ // createIndexes also creates the collection on first use. Callers invoke
96
+ // this lazily before the transaction which writes the first checkpoint.
97
+ await this.customCheckpointRequests(options).createIndexes([
98
+ {
99
+ key: { user_id: 1 },
100
+ name: 'user_unique',
101
+ unique: true
102
+ },
103
+ {
104
+ // Find checkpoint changes over an operation-id range.
105
+ key: { op_id: 1 },
106
+ name: 'op_id'
107
+ },
108
+ {
109
+ // Limit retention scans to temporary client-requested checkpoints.
110
+ key: { checkpoint_requested_at: 1 },
111
+ name: 'checkpoint_requested_at',
112
+ partialFilterExpression: { checkpoint_requested_at: { $exists: true } }
113
+ }
114
+ ]);
115
+ }
116
+
117
+ listCustomCheckpointRequestCollections(replicationStreamId: number) {
118
+ return this.upstream.listCustomCheckpointRequestCollections(replicationStreamId);
119
+ }
120
+
121
+ listBucketDataCollections(replicationStreamId: number) {
122
+ return this.listCollectionsByPrefix(`bucket_data_${replicationStreamId}_`);
123
+ }
124
+
125
+ async listParameterIndexCollections(replicationStreamId: number) {
126
+ const prefix = `parameter_index_${replicationStreamId}_`;
127
+ const collections = await this.listCollectionsByPrefix(prefix);
128
+
129
+ return collections.map((collection) => ({
130
+ collection,
131
+ indexId: collection.collectionName.slice(prefix.length)
132
+ }));
133
+ }
134
+
59
135
  async initializeStreamStorage(replicationStreamId: number) {
60
- const sourceTables = this.sourceTablesV3(replicationStreamId);
61
- const bucketState = this.bucketStateV3(replicationStreamId);
136
+ const sourceTables = this.sourceTables(replicationStreamId);
137
+ const bucketState = this.bucketState(replicationStreamId);
62
138
  await sourceTables.createIndex(
63
139
  {
64
140
  connection_id: 1,
@@ -85,33 +161,18 @@ export class VersionedPowerSyncMongoV3 extends BaseVersionedPowerSyncMongo {
85
161
  },
86
162
  { name: 'bucket_updates', unique: true }
87
163
  );
164
+ // V3 workers claim only buckets that have reached their scheduled check
165
+ // time, keeping scheduled scans proportional to pending work.
88
166
  await bucketState.createIndex(
167
+ { next_compact_check: 1 },
89
168
  {
90
- 'estimate_since_compact.count': -1
91
- },
92
- { name: 'dirty_count' }
169
+ name: 'next_compact_check',
170
+ partialFilterExpression: { next_compact_check: { $exists: true } }
171
+ }
93
172
  );
94
173
  }
95
174
 
96
- bucketDataV3(replicationStreamId: number, definitionId: BucketDefinitionId) {
97
- return this.db.collection<BucketDataDocumentV3>(`bucket_data_${replicationStreamId}_${definitionId}`);
98
- }
99
-
100
- listBucketDataCollectionsV3(replicationStreamId: number) {
101
- return this.upstream.listBucketDataCollectionsV3(replicationStreamId);
102
- }
103
-
104
- async listParameterIndexCollectionsV3(
105
- replicationStreamId: number
106
- ): Promise<{ collection: mongo.Collection<BucketParameterDocumentV3>; indexId: ParameterIndexId }[]> {
107
- const prefix = `parameter_index_${replicationStreamId}_`;
108
- const collections = await this.db.listCollections({ name: new RegExp(`^${prefix}`) }, { nameOnly: true }).toArray();
109
-
110
- return collections
111
- .filter((collection) => collection.name.startsWith(prefix))
112
- .map((collection) => ({
113
- collection: this.db.collection<BucketParameterDocumentV3>(collection.name),
114
- indexId: collection.name.slice(prefix.length)
115
- }));
175
+ customCheckpointRequestCollectionName(options: CustomCheckpointRequestCollectionSpecifier) {
176
+ return this.upstream.customCheckpointRequestCollectionName(options);
116
177
  }
117
178
  }
@@ -0,0 +1,99 @@
1
+ import { ServiceAssertionError } from '@powersync/lib-services-framework';
2
+ import { bson, InternalOpId } from '@powersync/service-core';
3
+ import { BucketDataDoc, BucketKey } from '../common/BucketDataDoc.js';
4
+ import { BucketDataDocumentV3, BucketOperation } from './models.js';
5
+
6
+ /**
7
+ * Serialize a non-empty list of operations in strictly ascending `o` order.
8
+ * Preserving that order establishes the {@link BucketDataDocumentV3} range
9
+ * invariants used by metadata-only queries.
10
+ */
11
+ export function serializeBucketData(
12
+ bucket: string,
13
+ operations: BucketDataDoc[],
14
+ options?: { targetOp?: InternalOpId | null }
15
+ ): BucketDataDocumentV3 {
16
+ const minOp = operations[0].o;
17
+ const maxOp = operations[operations.length - 1].o;
18
+
19
+ let totalChecksum = 0n;
20
+ let maxTargetOp: InternalOpId | null = options?.targetOp ?? null;
21
+ let hasClearOp = false;
22
+
23
+ const ops: BucketOperation[] = operations.map((op) => {
24
+ totalChecksum += op.checksum;
25
+
26
+ if (op.op == 'CLEAR') {
27
+ hasClearOp = true;
28
+ }
29
+
30
+ return {
31
+ o: op.o,
32
+ op: op.op,
33
+ source_table: op.source_table,
34
+ source_key: op.source_key,
35
+ subkey: op.subkey,
36
+ table: op.table,
37
+ row_id: op.row_id,
38
+ checksum: op.checksum,
39
+ data: op.data
40
+ };
41
+ });
42
+
43
+ const size = bson.calculateObjectSize(ops);
44
+
45
+ return {
46
+ _id: {
47
+ b: bucket,
48
+ o: maxOp
49
+ },
50
+ min_op: minOp,
51
+ checksum: totalChecksum,
52
+ count: operations.length,
53
+ size,
54
+ target_op: maxTargetOp,
55
+ ...(hasClearOp ? { has_clear_op: true as const } : {}),
56
+ ops
57
+ };
58
+ }
59
+
60
+ export function* loadBucketDataDocument(
61
+ context: Pick<BucketKey, 'replicationStreamId' | 'definitionId'>,
62
+ doc: BucketDataDocumentV3
63
+ ): Generator<BucketDataDoc> {
64
+ const { _id, ops } = doc;
65
+ if (!ops) {
66
+ throw new ServiceAssertionError(
67
+ `Missing ops array on BucketDataDocumentV3 at _id.o=${_id.o}. Callers must patch doc.ops from S3 before calling this function.`
68
+ );
69
+ }
70
+ const bucketKey = {
71
+ ...context,
72
+ bucket: _id.b
73
+ };
74
+
75
+ for (const op of ops) {
76
+ yield {
77
+ bucketKey,
78
+ o: op.o,
79
+ op: op.op,
80
+ source_table: op.source_table,
81
+ source_key: op.source_key,
82
+ subkey: op.subkey,
83
+ table: op.table,
84
+ row_id: op.row_id,
85
+ checksum: op.checksum,
86
+ data: op.data
87
+ };
88
+ }
89
+ }
90
+
91
+ export function maxOpId(a: InternalOpId | null | undefined, b: InternalOpId | null | undefined): InternalOpId | null {
92
+ if (a == null) {
93
+ return b ?? null;
94
+ }
95
+ if (b == null) {
96
+ return a ?? null;
97
+ }
98
+ return a > b ? a : b;
99
+ }
@@ -0,0 +1,40 @@
1
+ import { bson } from '@powersync/service-core';
2
+ import { BucketDataDoc } from '../common/BucketDataDoc.js';
3
+
4
+ export const DEFAULT_MAX_DOC_SIZE_BYTES = 1024 * 1024; // 1MB
5
+
6
+ /**
7
+ * Split an array of bucket data operations into chunks where each chunk's
8
+ * total data size stays close to `maxDocSizeBytes`.
9
+ *
10
+ * The threshold is a target, not a hard ceiling — a single operation whose
11
+ * `data` field exceeds the limit is placed in its own chunk and is NOT split.
12
+ */
13
+ export function chunkBucketData(
14
+ operations: BucketDataDoc[],
15
+ maxDocSizeBytes = DEFAULT_MAX_DOC_SIZE_BYTES
16
+ ): BucketDataDoc[][] {
17
+ const chunks: BucketDataDoc[][] = [];
18
+ let currentChunk: BucketDataDoc[] = [];
19
+ let currentSize = 0;
20
+
21
+ for (const op of operations) {
22
+ // Add 5 to account for array index up to 4 digits (9999) plus null byte.
23
+ const opSize = bson.calculateObjectSize(op) + 5;
24
+
25
+ if (currentSize + opSize > maxDocSizeBytes && currentChunk.length > 0) {
26
+ chunks.push(currentChunk);
27
+ currentChunk = [];
28
+ currentSize = 0;
29
+ }
30
+
31
+ currentChunk.push(op);
32
+ currentSize += opSize;
33
+ }
34
+
35
+ if (currentChunk.length > 0) {
36
+ chunks.push(currentChunk);
37
+ }
38
+
39
+ return chunks;
40
+ }
@@ -0,0 +1,320 @@
1
+ import { mongo } from '@powersync/lib-service-mongodb';
2
+ import { ReplicationAssertionError } from '@powersync/lib-services-framework';
3
+ import { addChecksums, InternalOpId } from '@powersync/service-core';
4
+ import { BucketDataDoc } from '../common/BucketDataDoc.js';
5
+ import { AVAILABLE_LEASE_EXPR, CompactionLease } from './CompactionLease.js';
6
+ import { BucketDataDocumentV3, BucketStateDocumentV3 } from './models.js';
7
+
8
+ /**
9
+ * Perform chunk compaction if at least this many chunks have been added since
10
+ * the latest compaction.
11
+ *
12
+ * A lower value increases compaction frequency and can increase chunk rewrites
13
+ * and object-storage operations. A higher value leaves more small chunks for
14
+ * longer, which can hurt sync performance.
15
+ */
16
+ const MERGE_CHUNKS_THRESHOLD = 8;
17
+
18
+ export const FULL_COMPACT_RESCHEDULE_MARGIN_MS = 60 * 1000;
19
+
20
+ export interface PendingCompactionGroup {
21
+ /**
22
+ * Input documents are ordered from oldest to newest, matching `ops`.
23
+ * Keeping the inputs intact lets unchanged singletons retain their object.
24
+ */
25
+ inputs: BucketDataDocumentV3[];
26
+ ops: BucketDataDoc[];
27
+ changed: boolean;
28
+ targetOp: InternalOpId | null;
29
+ }
30
+
31
+ export interface CompactIntervalConfig {
32
+ readonly minCompactChunkIntervalMs: number;
33
+ readonly minCompactFullIntervalMs: number;
34
+ readonly maxCompactFullIntervalMs: number;
35
+ }
36
+ export interface CompactTargetConfig {
37
+ readonly maxOpIdCap: InternalOpId | undefined;
38
+ }
39
+
40
+ export enum CompactionKind {
41
+ Full = 'full',
42
+ Chunks = 'chunks'
43
+ }
44
+
45
+ export interface CompactionDecision {
46
+ kind: CompactionKind | null;
47
+ nextCompactCheck: mongo.Document;
48
+ }
49
+
50
+ export interface ScheduledCompactionOptions {
51
+ /** Process checks scheduled this far after the captured job start. */
52
+ dueAheadMs?: number;
53
+ /** Used by initial replication, which must not run a full compact. */
54
+ forceKind?: CompactionKind;
55
+ }
56
+
57
+ export class CompactionContext {
58
+ constructor(
59
+ readonly lease: CompactionLease,
60
+ readonly kind: CompactionKind,
61
+ readonly decision: CompactionDecision,
62
+ readonly rescheduleNotBefore: Date | undefined,
63
+ readonly targetOp: InternalOpId
64
+ ) {}
65
+
66
+ get state() {
67
+ return this.lease.state;
68
+ }
69
+
70
+ get startedAt() {
71
+ return this.lease.startedAt;
72
+ }
73
+
74
+ get lastOp() {
75
+ return this.lease.lastOp;
76
+ }
77
+ }
78
+
79
+ export interface BucketStats {
80
+ count: number;
81
+ bytes: bigint;
82
+ chunks: number;
83
+ }
84
+
85
+ /** Bucket stats read from bucket-data documents, including their checksum. */
86
+ export interface BucketStatsWithChecksum extends BucketStats {
87
+ checksum: number;
88
+ }
89
+
90
+ export interface CompactionResult {
91
+ /** Metadata cached at compacted_state.op_id. */
92
+ compactedState: BucketStatsWithChecksum;
93
+ /** Complete bucket metadata through the op head captured at claim time. */
94
+ bucketStats: BucketStats;
95
+ }
96
+
97
+ export function bucketStats(state: BucketStateDocumentV3): BucketStats {
98
+ return {
99
+ count: state.bucket_stats.count,
100
+ bytes: state.bucket_stats.bytes,
101
+ chunks: state.bucket_stats.chunks
102
+ };
103
+ }
104
+
105
+ export function emptyBucketStats(): BucketStatsWithChecksum {
106
+ return { count: 0, bytes: 0n, chunks: 0, checksum: 0 };
107
+ }
108
+
109
+ export function statsForDocument(
110
+ document: Pick<BucketDataDocumentV3, 'count' | 'size' | 'checksum'>
111
+ ): BucketStatsWithChecksum {
112
+ return {
113
+ count: document.count,
114
+ bytes: BigInt(document.size),
115
+ chunks: 1,
116
+ checksum: addChecksums(0, Number(document.checksum))
117
+ };
118
+ }
119
+
120
+ export function statsForDocuments(
121
+ documents: Iterable<Pick<BucketDataDocumentV3, 'count' | 'size' | 'checksum'>>
122
+ ): BucketStatsWithChecksum {
123
+ let result = emptyBucketStats();
124
+ for (const document of documents) {
125
+ result = combineAdjacentStats(result, statsForDocument(document));
126
+ }
127
+ return result;
128
+ }
129
+
130
+ /** A scheduled bucket always has writes awaiting a full compact. */
131
+ export function firstUncompactedWrite(state: BucketStateDocumentV3): Date {
132
+ if (state.first_uncompacted_write == null) {
133
+ throw new ReplicationAssertionError(`Scheduled V3 bucket ${state._id.b} has no first uncompacted write`);
134
+ }
135
+ return state.first_uncompacted_write;
136
+ }
137
+
138
+ export function combineChunkStats(
139
+ previous: NonNullable<BucketStateDocumentV3['compacted_state']>,
140
+ compactedTail: BucketStatsWithChecksum,
141
+ overlappingCompactedChunk: BucketStatsWithChecksum
142
+ ): BucketStatsWithChecksum {
143
+ return {
144
+ count: previous.count - overlappingCompactedChunk.count + compactedTail.count,
145
+ bytes: previous.bytes - overlappingCompactedChunk.bytes + compactedTail.bytes,
146
+ chunks: previous.chunks - 1 + compactedTail.chunks,
147
+ checksum: addChecksums(
148
+ addChecksums(Number(previous.checksum), -overlappingCompactedChunk.checksum),
149
+ compactedTail.checksum
150
+ )
151
+ };
152
+ }
153
+
154
+ export function combineAdjacentStats(
155
+ first: BucketStatsWithChecksum,
156
+ second: BucketStatsWithChecksum
157
+ ): BucketStatsWithChecksum {
158
+ return {
159
+ count: first.count + second.count,
160
+ bytes: first.bytes + second.bytes,
161
+ chunks: first.chunks + second.chunks,
162
+ checksum: addChecksums(first.checksum, second.checksum)
163
+ };
164
+ }
165
+
166
+ export function applyCompactionDelta(
167
+ total: BucketStats,
168
+ before: BucketStatsWithChecksum,
169
+ after: BucketStatsWithChecksum
170
+ ): BucketStats {
171
+ return {
172
+ count: total.count - before.count + after.count,
173
+ bytes: total.bytes - before.bytes + after.bytes,
174
+ chunks: total.chunks - before.chunks + after.chunks
175
+ };
176
+ }
177
+
178
+ /** Replace one stored range in an accumulated statistics snapshot. */
179
+ export function applyStatsReplacement(
180
+ total: BucketStatsWithChecksum,
181
+ before: BucketStatsWithChecksum,
182
+ after: BucketStatsWithChecksum
183
+ ): BucketStatsWithChecksum {
184
+ return {
185
+ ...applyCompactionDelta(total, before, after),
186
+ checksum: addChecksums(addChecksums(total.checksum, -before.checksum), after.checksum)
187
+ };
188
+ }
189
+
190
+ /**
191
+ * This checks that the bucket state hasn't changed by a concurrent write since we checked.
192
+ *
193
+ * If it has changed, we'll re-check in the next batch.
194
+ */
195
+ export function unclaimedSnapshotFilter(state: BucketStateDocumentV3): mongo.Filter<BucketStateDocumentV3> {
196
+ return {
197
+ _id: state._id,
198
+ last_op: state.last_op,
199
+ next_compact_check: state.next_compact_check,
200
+ first_uncompacted_write: state.first_uncompacted_write ?? { $exists: false },
201
+ bucket_stats: state.bucket_stats,
202
+ compacted_state: state.compacted_state ?? { $exists: false },
203
+ last_full_compact: state.last_full_compact ?? { $exists: false },
204
+ ...AVAILABLE_LEASE_EXPR
205
+ };
206
+ }
207
+
208
+ /**
209
+ * Calculate the earliest full compaction time from the first uncompacted
210
+ * write, bounded by the maximum retention interval.
211
+ */
212
+ export function fullCompactionCheckAt(state: BucketStateDocumentV3, config: CompactIntervalConfig): Date {
213
+ const firstWrite = firstUncompactedWrite(state);
214
+ const stats = bucketStats(state);
215
+ const lastFull = state.last_full_compact;
216
+
217
+ // The number of operations since the last full compact.
218
+ // We may make this more specific in the future, to track new updates and deletes only, ignoring
219
+ // full new inserts, but that requires more granular tracking when replicating.
220
+ const uncompactedCount = lastFull == null ? stats.count : Math.max(0, stats.count - lastFull.count);
221
+ const compactedRows = lastFull?.puts ?? 0;
222
+
223
+ // If no full compact has ever been performed: ratio = 1, compact after minCompactFullIntervalMs.
224
+ // If every row has been updated or deleted exactly once since the last full compact: ratio = 0.5, compact after minCompactFullIntervalMs * 2.
225
+ // If 10% of rows has been updated since the last full compact, compact after minCompactFullIntervalMs * 11.
226
+ // If every row has been updated multiple times, the ratio tends closer to 1 again.
227
+ const ratio = uncompactedCount == 0 ? 0 : uncompactedCount / (compactedRows + uncompactedCount);
228
+ const fullIntervalMs = ratio == 0 ? config.maxCompactFullIntervalMs : config.minCompactFullIntervalMs / ratio;
229
+ return new Date(firstWrite.getTime() + Math.min(fullIntervalMs, config.maxCompactFullIntervalMs));
230
+ }
231
+
232
+ export function chooseCompactionKind(
233
+ state: BucketStateDocumentV3,
234
+ now: Date,
235
+ config: CompactIntervalConfig
236
+ ): CompactionDecision {
237
+ // For chunk compaction, we consider the number of chunks added.
238
+ // Right now, we trigger a compact if the interval has passed and at least a threshold of chunks were added.
239
+ // A future policy could also use bytes per chunk or records per chunk to
240
+ // decide when to compact.
241
+ const fullCheckAt = fullCompactionCheckAt(state, config);
242
+ // Schedule a little late so a worker using a slightly earlier clock does
243
+ // not wake before the exact full-compaction condition is true.
244
+ const fullCheckWithMargin = new Date(fullCheckAt.getTime() + FULL_COMPACT_RESCHEDULE_MARGIN_MS);
245
+ const compacted = state.compacted_state;
246
+ const chunksSinceCompact = Math.max(0, state.bucket_stats.chunks - (compacted?.chunks ?? 0));
247
+ const shouldCompactChunks = chunksSinceCompact >= MERGE_CHUNKS_THRESHOLD;
248
+ const canCheckChunks =
249
+ compacted == null || now.getTime() - compacted.at.getTime() >= config.minCompactChunkIntervalMs;
250
+ // Too few new chunks cannot make chunk compaction eligible. Do not poll this
251
+ // bucket at the chunk-compaction interval; only wake it for its full-compact check.
252
+ const nextCompactCheck: mongo.Document = !shouldCompactChunks
253
+ ? fullCheckWithMargin
254
+ : {
255
+ $min: [
256
+ fullCheckWithMargin,
257
+ { $dateAdd: { startDate: '$$NOW', unit: 'millisecond', amount: config.minCompactChunkIntervalMs } }
258
+ ]
259
+ };
260
+ let kind: CompactionKind | null = null;
261
+ if (now >= fullCheckAt) {
262
+ kind = CompactionKind.Full;
263
+ } else if (canCheckChunks && shouldCompactChunks) {
264
+ kind = CompactionKind.Chunks;
265
+ }
266
+ return {
267
+ kind,
268
+ nextCompactCheck
269
+ };
270
+ }
271
+
272
+ export function forcedCompactionKind(
273
+ state: BucketStateDocumentV3,
274
+ forceKind: CompactionKind | undefined,
275
+ config: CompactTargetConfig
276
+ ): CompactionKind | null {
277
+ if (forceKind == null) {
278
+ return null;
279
+ }
280
+ const maxOpId = config.maxOpIdCap == null || state.last_op < config.maxOpIdCap ? state.last_op : config.maxOpIdCap;
281
+ return state.compacted_state == null || state.compacted_state.op_id < maxOpId ? forceKind : null;
282
+ }
283
+
284
+ /**
285
+ * Read one bounded prefix from a compaction cursor.
286
+ *
287
+ * The document that would cross the byte limit is deliberately not returned:
288
+ * pagination resumes past the last returned `_id`, so that document remains
289
+ * eligible for the next query. The first document is always accepted to
290
+ * ensure progress when a single document exceeds the configured byte limit.
291
+ *
292
+ * `hasMore` is conservative when the document limit is reached. An extra empty
293
+ * query is preferable to exhausting the cursor just to determine whether the
294
+ * limited MongoDB query contained another document.
295
+ */
296
+ export async function readCompactionBatch(
297
+ cursor: mongo.AggregationCursor<BucketDataDocumentV3>,
298
+ options: { byteLimit: number; documentLimit: number }
299
+ ): Promise<{ documents: BucketDataDocumentV3[]; hasMore: boolean }> {
300
+ const documents: BucketDataDocumentV3[] = [];
301
+ let cumulativeBytes = 0;
302
+
303
+ try {
304
+ for await (const document of cursor) {
305
+ if (documents.length > 0 && cumulativeBytes + document.size > options.byteLimit) {
306
+ return { documents, hasMore: true };
307
+ }
308
+
309
+ documents.push(document);
310
+ cumulativeBytes += document.size;
311
+
312
+ if (documents.length >= options.documentLimit) {
313
+ return { documents, hasMore: true };
314
+ }
315
+ }
316
+ return { documents, hasMore: false };
317
+ } finally {
318
+ await cursor.close();
319
+ }
320
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Writers use this delay before scheduling their first chunk compaction.
3
+ * Initial replication includes the same delay when it picks up that work.
4
+ */
5
+ export const DEFAULT_MIN_COMPACT_CHUNK_INTERVAL_MS = 5 * 60 * 1000;