@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,24 +1,34 @@
1
1
  import { mongo } from '@powersync/lib-service-mongodb';
2
+ import { bson, InternalOpId } from '@powersync/service-core';
2
3
  import { MongoParameterCompactor } from '../MongoParameterCompactor.js';
3
- import { VersionedPowerSyncMongoV3 } from './VersionedPowerSyncMongoV3.js';
4
+ import type { VersionedPowerSyncMongoV3 } from './VersionedPowerSyncMongoV3.js';
4
5
 
6
+ /**
7
+ * Incrementally compacts V3 parameter indexes using the stream operation sequence as a work
8
+ * cursor. The cursor is advanced only after every parameter index has completed the same range.
9
+ */
5
10
  export class MongoParameterCompactorV3 extends MongoParameterCompactor {
6
11
  declare protected readonly db: VersionedPowerSyncMongoV3;
7
12
 
8
13
  protected async getCollections(): Promise<mongo.Collection<mongo.Document>[]> {
9
- const collections = await this.db.listParameterIndexCollectionsV3(this.group_id);
10
- return collections.map((collection) => collection.collection as unknown as mongo.Collection<mongo.Document>);
14
+ const collections = await this.db.listParameterIndexCollections(this.replicationStreamId);
15
+ return collections.map(({ collection }) => collection as unknown as mongo.Collection<mongo.Document>);
11
16
  }
12
17
 
13
- protected collectionFilter(): mongo.Document {
14
- return {};
18
+ protected shouldCompactDocument(_doc: { _id: bigint; key: mongo.Document }): boolean {
19
+ return true;
15
20
  }
16
21
 
17
- protected deleteFilter(doc: mongo.Document): mongo.Document {
22
+ /** Uses the `{ lookup: 1, key: 1, _id: -1 }` `lookup_op_id` index. */
23
+ protected leadingHistoryDeleteFilter(
24
+ lookup: bson.Binary,
25
+ keys: mongo.Document[],
26
+ before: InternalOpId
27
+ ): mongo.Document {
18
28
  return {
19
- lookup: doc.lookup,
20
- _id: { $lte: doc._id },
21
- key: doc.key
29
+ lookup,
30
+ key: { $in: keys },
31
+ _id: { $lt: before }
22
32
  };
23
33
  }
24
34
  }
@@ -0,0 +1,473 @@
1
+ import * as lib_mongo from '@powersync/lib-service-mongodb';
2
+ import { Logger, ReplicationAbortedError } from '@powersync/lib-services-framework';
3
+ import { SingleSyncConfigBucketDefinitionMapping, storage } from '@powersync/service-core';
4
+ import { BucketDefinitionId, EventDefinitionId, ParameterIndexId } from '@powersync/service-sync-rules';
5
+ import * as bson from 'bson';
6
+ import { clearCollectionInIdRanges, idPrefixFilter } from '../../../utils/util.js';
7
+ import { VersionedPowerSyncMongoV3 } from './VersionedPowerSyncMongoV3.js';
8
+ import {
9
+ BucketStateDocumentV3,
10
+ ReplicationStreamDocumentV3,
11
+ SourceTableDocumentV3,
12
+ SyncConfigDefinition
13
+ } from './models.js';
14
+ import { ObjectStorage } from './object-storage/ObjectStorage.js';
15
+ import { ObjectStorageLifecycle } from './object-storage/ObjectStorageLifecycle.js';
16
+ import { ObjectStorageUsage } from './object-storage/ObjectStorageUsage.js';
17
+
18
+ type SyncConfigState = ReplicationStreamDocumentV3['sync_configs'][number];
19
+
20
+ const LIVE_STATES = new Set<storage.SyncRuleState>([
21
+ storage.SyncRuleState.ACTIVE,
22
+ storage.SyncRuleState.PROCESSING,
23
+ storage.SyncRuleState.ERRORED
24
+ ]);
25
+
26
+ const EMPTY_RESULT: storage.CleanupStoppedSyncConfigsResult = {
27
+ stoppedSyncConfigsRemoved: 0,
28
+ bucketDataCollectionsDropped: 0,
29
+ parameterIndexCollectionsDropped: 0,
30
+ bucketStateDocumentsDeleted: 0,
31
+ sourceRecordCollectionsDropped: 0,
32
+ sourceTablesUpdated: 0,
33
+ sourceTablesDeleted: 0
34
+ };
35
+
36
+ export interface MongoStoppedSyncConfigCleanupOptions extends storage.CleanupStoppedSyncConfigsOptions {
37
+ replicationStreamId: number;
38
+ db: VersionedPowerSyncMongoV3;
39
+ logger: Logger;
40
+ clearBatchThrottleRate: number;
41
+ objectStorage?: ObjectStorage;
42
+ }
43
+
44
+ export class MongoStoppedSyncConfigCleanup {
45
+ private readonly db: VersionedPowerSyncMongoV3;
46
+ private readonly replicationStreamId: number;
47
+ private readonly signal: AbortSignal | undefined;
48
+ private readonly logger: Logger;
49
+ private readonly objectStorage: ObjectStorage | undefined;
50
+ private readonly clearBatchThrottleRate: number;
51
+
52
+ constructor(options: MongoStoppedSyncConfigCleanupOptions) {
53
+ this.db = options.db;
54
+ this.replicationStreamId = options.replicationStreamId;
55
+ this.signal = options.signal;
56
+ this.logger = options.logger;
57
+ this.objectStorage = options.objectStorage;
58
+ this.clearBatchThrottleRate = options.clearBatchThrottleRate;
59
+ }
60
+
61
+ async run(): Promise<storage.CleanupStoppedSyncConfigsResult> {
62
+ this.throwIfAborted();
63
+
64
+ const doc = await this.db.sync_rules.findOne<ReplicationStreamDocumentV3>(
65
+ { _id: this.replicationStreamId },
66
+ { projection: { sync_configs: 1 } }
67
+ );
68
+ if (doc == null || doc.sync_configs.length == 0) {
69
+ return { ...EMPTY_RESULT };
70
+ }
71
+
72
+ const stoppedStates = doc.sync_configs.filter((config) => config.state == storage.SyncRuleState.STOP);
73
+ if (stoppedStates.length == 0) {
74
+ return { ...EMPTY_RESULT };
75
+ }
76
+
77
+ const liveStates = doc.sync_configs.filter((config) => LIVE_STATES.has(config.state));
78
+ const configDocs = await this.loadSyncConfigDefinitions([...stoppedStates, ...liveStates]);
79
+ const stoppedIds = new Set(stoppedStates.map((state) => state._id.toHexString()));
80
+ const liveIds = new Set(liveStates.map((state) => state._id.toHexString()));
81
+ const stoppedConfigDocs = configDocs.filter((config) => stoppedIds.has(config._id.toHexString()));
82
+ const liveConfigDocs = configDocs.filter((config) => liveIds.has(config._id.toHexString()));
83
+
84
+ const stoppedStorageIds = this.storageIdsFor(stoppedConfigDocs);
85
+ const liveStorageIds = this.storageIdsFor(liveConfigDocs);
86
+ const unusedBucketDefinitionIds = difference(
87
+ stoppedStorageIds.bucketDefinitionIds,
88
+ liveStorageIds.bucketDefinitionIds
89
+ );
90
+ const unusedParameterIndexIds = difference(stoppedStorageIds.parameterIndexIds, liveStorageIds.parameterIndexIds);
91
+ const unusedEventDefinitionIds = difference(
92
+ stoppedStorageIds.eventDefinitionIds,
93
+ liveStorageIds.eventDefinitionIds
94
+ );
95
+
96
+ const result: storage.CleanupStoppedSyncConfigsResult = {
97
+ ...EMPTY_RESULT
98
+ };
99
+
100
+ if (
101
+ unusedBucketDefinitionIds.length > 0 ||
102
+ unusedParameterIndexIds.length > 0 ||
103
+ unusedEventDefinitionIds.length > 0
104
+ ) {
105
+ await this.cleanupSourceTableMemberships(
106
+ unusedBucketDefinitionIds,
107
+ unusedParameterIndexIds,
108
+ unusedEventDefinitionIds,
109
+ result
110
+ );
111
+ await this.dropCustomCheckpointRequestCollections(unusedEventDefinitionIds);
112
+ result.bucketDataCollectionsDropped = await this.dropBucketDataCollections(unusedBucketDefinitionIds);
113
+ result.bucketStateDocumentsDeleted = await this.deleteBucketStateDocuments(unusedBucketDefinitionIds);
114
+ result.parameterIndexCollectionsDropped = await this.dropParameterIndexCollections(unusedParameterIndexIds);
115
+ }
116
+
117
+ result.stoppedSyncConfigsRemoved = await this.pruneStoppedSyncConfigStates(stoppedStates);
118
+
119
+ if (result.stoppedSyncConfigsRemoved > 0) {
120
+ this.logger.info(
121
+ `Cleaned up ${result.stoppedSyncConfigsRemoved} stopped sync config${result.stoppedSyncConfigsRemoved == 1 ? '' : 's'}`,
122
+ result
123
+ );
124
+ }
125
+ return result;
126
+ }
127
+
128
+ private async loadSyncConfigDefinitions(states: SyncConfigState[]): Promise<SyncConfigDefinition[]> {
129
+ const ids = [...new Map(states.map((state) => [state._id.toHexString(), state._id])).values()];
130
+ if (ids.length == 0) {
131
+ return [];
132
+ }
133
+ return this.db.syncConfigDefinitions.find({ _id: { $in: ids } }).toArray();
134
+ }
135
+
136
+ private storageIdsFor(configs: SyncConfigDefinition[]) {
137
+ const mappings = configs.map((config) =>
138
+ SingleSyncConfigBucketDefinitionMapping.fromPersistedMapping(config.rule_mapping)
139
+ );
140
+ return {
141
+ bucketDefinitionIds: [...new Set(mappings.flatMap((mapping) => mapping.allBucketDefinitionIds()))],
142
+ parameterIndexIds: [...new Set(mappings.flatMap((mapping) => mapping.allParameterIndexIds()))],
143
+ eventDefinitionIds: [...new Set(mappings.flatMap((mapping) => mapping.allEventDefinitionIds()))]
144
+ };
145
+ }
146
+
147
+ private async cleanupSourceTableMemberships(
148
+ unusedBucketDefinitionIds: BucketDefinitionId[],
149
+ unusedParameterIndexIds: ParameterIndexId[],
150
+ unusedEventDefinitionIds: EventDefinitionId[],
151
+ result: storage.CleanupStoppedSyncConfigsResult
152
+ ) {
153
+ const update: Record<string, unknown> = {};
154
+ if (unusedBucketDefinitionIds.length > 0) {
155
+ update.bucket_data_source_ids = { $in: unusedBucketDefinitionIds };
156
+ }
157
+ if (unusedParameterIndexIds.length > 0) {
158
+ update.parameter_lookup_source_ids = { $in: unusedParameterIndexIds };
159
+ }
160
+ if (unusedEventDefinitionIds.length > 0) {
161
+ update.event_definition_ids = { $in: unusedEventDefinitionIds };
162
+ }
163
+
164
+ // Keep obsolete membership ids as the durable cleanup marker until each source table is
165
+ // either deleted or retained. If interrupted after dropping a source_records collection,
166
+ // the next run can still rediscover the source table from these obsolete ids and retry.
167
+ const filter = this.sourceTableMembershipFilter(
168
+ unusedBucketDefinitionIds,
169
+ unusedParameterIndexIds,
170
+ unusedEventDefinitionIds
171
+ );
172
+ const candidateSourceTables = await this.db
173
+ .sourceTables(this.replicationStreamId)
174
+ .find(filter, {
175
+ projection: {
176
+ _id: 1,
177
+ bucket_data_source_ids: 1,
178
+ parameter_lookup_source_ids: 1,
179
+ event_definition_ids: 1,
180
+ schema_name: 1,
181
+ table_name: 1
182
+ }
183
+ })
184
+ .toArray();
185
+ if (candidateSourceTables.length == 0) {
186
+ return;
187
+ }
188
+ const deletableSourceTables = candidateSourceTables.filter((sourceTable) =>
189
+ this.membershipsBecomeEmpty(
190
+ sourceTable,
191
+ unusedBucketDefinitionIds,
192
+ unusedParameterIndexIds,
193
+ unusedEventDefinitionIds
194
+ )
195
+ );
196
+ const retainedSourceTables = candidateSourceTables.filter(
197
+ (sourceTable) => !deletableSourceTables.some((deletable) => deletable._id.equals(sourceTable._id))
198
+ );
199
+ const retainedSourceTableIds = retainedSourceTables.map((sourceTable) => sourceTable._id);
200
+
201
+ await this.deleteSourceTables(
202
+ deletableSourceTables.map((table) => table._id),
203
+ (ids) =>
204
+ this.deletableSourceTableFilter(
205
+ ids,
206
+ unusedBucketDefinitionIds,
207
+ unusedParameterIndexIds,
208
+ unusedEventDefinitionIds
209
+ ),
210
+ result
211
+ );
212
+
213
+ // A retained source table whose data and parameter memberships become empty is event-only.
214
+ // Event-only save() never reads or writes current_data, so its source_records collection is now
215
+ // dead weight. Drop it before the $pull below, so the obsolete membership ids remain as a recovery
216
+ // marker if interrupted. Existing source-table docs only ever shrink their memberships, so this
217
+ // table cannot resume data sync on the same doc and need current_data again.
218
+ const becomingEventOnlySourceTableIds = retainedSourceTables
219
+ .filter((sourceTable) =>
220
+ this.dataMembershipsBecomeEmpty(sourceTable, unusedBucketDefinitionIds, unusedParameterIndexIds)
221
+ )
222
+ .map((sourceTable) => sourceTable._id);
223
+ for (const sourceTableId of becomingEventOnlySourceTableIds) {
224
+ this.throwIfAborted();
225
+ await this.dropCollection(this.db.sourceRecords(this.replicationStreamId, sourceTableId));
226
+ result.sourceRecordCollectionsDropped += 1;
227
+ }
228
+
229
+ if (retainedSourceTableIds.length > 0) {
230
+ this.throwIfAborted();
231
+ const updateResult = await this.db.sourceTables(this.replicationStreamId).updateMany(
232
+ { _id: { $in: retainedSourceTableIds } },
233
+ {
234
+ $pull: update
235
+ },
236
+ { maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS }
237
+ );
238
+ result.sourceTablesUpdated += updateResult.modifiedCount;
239
+ }
240
+ }
241
+
242
+ /**
243
+ * Drop the given source tables and their source_records collections.
244
+ *
245
+ * `refetchFilter` re-applies the deletability predicate against the latest persisted state, so
246
+ * a source table that became live through another writer after planning is not deleted. The
247
+ * sourceRecords collection is dropped before the related sourceTables row, so an interruption
248
+ * can be recovered on the next run.
249
+ */
250
+ private async deleteSourceTables(
251
+ deletableSourceTableIds: bson.ObjectId[],
252
+ refetchFilter: (ids: bson.ObjectId[]) => Record<string, unknown>,
253
+ result: storage.CleanupStoppedSyncConfigsResult
254
+ ) {
255
+ if (deletableSourceTableIds.length == 0) {
256
+ return;
257
+ }
258
+ const sourceTablesToDelete = await this.db
259
+ .sourceTables(this.replicationStreamId)
260
+ .find(refetchFilter(deletableSourceTableIds), { projection: { _id: 1 } })
261
+ .toArray();
262
+
263
+ for (const sourceTable of sourceTablesToDelete) {
264
+ this.throwIfAborted();
265
+ await this.dropCollection(this.db.sourceRecords(this.replicationStreamId, sourceTable._id));
266
+ result.sourceRecordCollectionsDropped += 1;
267
+ }
268
+
269
+ if (sourceTablesToDelete.length > 0) {
270
+ this.throwIfAborted();
271
+ const deleteResult = await this.db
272
+ .sourceTables(this.replicationStreamId)
273
+ .deleteMany(refetchFilter(sourceTablesToDelete.map((table) => table._id)), {
274
+ maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS
275
+ });
276
+ result.sourceTablesDeleted += deleteResult.deletedCount;
277
+ }
278
+ }
279
+
280
+ private membershipsBecomeEmpty(
281
+ sourceTable: Pick<
282
+ SourceTableDocumentV3,
283
+ 'bucket_data_source_ids' | 'parameter_lookup_source_ids' | 'event_definition_ids'
284
+ >,
285
+ unusedBucketDefinitionIds: BucketDefinitionId[],
286
+ unusedParameterIndexIds: ParameterIndexId[],
287
+ unusedEventDefinitionIds: EventDefinitionId[]
288
+ ): boolean {
289
+ const unusedBucketDefinitionIdSet = new Set(unusedBucketDefinitionIds);
290
+ const unusedParameterIndexIdSet = new Set(unusedParameterIndexIds);
291
+ const unusedEventDefinitionIdSet = new Set(unusedEventDefinitionIds);
292
+ return (
293
+ sourceTable.bucket_data_source_ids.every((id) => unusedBucketDefinitionIdSet.has(id)) &&
294
+ sourceTable.parameter_lookup_source_ids.every((id) => unusedParameterIndexIdSet.has(id)) &&
295
+ sourceTable.event_definition_ids.every((id) => unusedEventDefinitionIdSet.has(id))
296
+ );
297
+ }
298
+
299
+ private dataMembershipsBecomeEmpty(
300
+ sourceTable: Pick<SourceTableDocumentV3, 'bucket_data_source_ids' | 'parameter_lookup_source_ids'>,
301
+ unusedBucketDefinitionIds: BucketDefinitionId[],
302
+ unusedParameterIndexIds: ParameterIndexId[]
303
+ ): boolean {
304
+ const unusedBucketDefinitionIdSet = new Set(unusedBucketDefinitionIds);
305
+ const unusedParameterIndexIdSet = new Set(unusedParameterIndexIds);
306
+ return (
307
+ sourceTable.bucket_data_source_ids.every((id) => unusedBucketDefinitionIdSet.has(id)) &&
308
+ sourceTable.parameter_lookup_source_ids.every((id) => unusedParameterIndexIdSet.has(id))
309
+ );
310
+ }
311
+
312
+ private deletableSourceTableFilter(
313
+ ids: bson.ObjectId[],
314
+ unusedBucketDefinitionIds: BucketDefinitionId[],
315
+ unusedParameterIndexIds: ParameterIndexId[],
316
+ unusedEventDefinitionIds: EventDefinitionId[]
317
+ ): Record<string, unknown> {
318
+ return {
319
+ _id: { $in: ids },
320
+ bucket_data_source_ids: { $not: { $elemMatch: { $nin: unusedBucketDefinitionIds } } },
321
+ parameter_lookup_source_ids: { $not: { $elemMatch: { $nin: unusedParameterIndexIds } } },
322
+ event_definition_ids: { $not: { $elemMatch: { $nin: unusedEventDefinitionIds } } }
323
+ };
324
+ }
325
+
326
+ private sourceTableMembershipFilter(
327
+ bucketDefinitionIds: BucketDefinitionId[],
328
+ parameterIndexIds: ParameterIndexId[],
329
+ eventDefinitionIds: EventDefinitionId[]
330
+ ): Partial<SourceTableDocumentV3> | Record<string, unknown> {
331
+ const clauses: Record<string, unknown>[] = [];
332
+ if (bucketDefinitionIds.length > 0) {
333
+ clauses.push({ bucket_data_source_ids: { $in: bucketDefinitionIds } });
334
+ }
335
+ if (parameterIndexIds.length > 0) {
336
+ clauses.push({ parameter_lookup_source_ids: { $in: parameterIndexIds } });
337
+ }
338
+ if (eventDefinitionIds.length > 0) {
339
+ clauses.push({ event_definition_ids: { $in: eventDefinitionIds } });
340
+ }
341
+ if (clauses.length == 0) {
342
+ return { _id: { $exists: false } };
343
+ }
344
+ return { $or: clauses };
345
+ }
346
+
347
+ private async dropCustomCheckpointRequestCollections(eventDefinitionIds: EventDefinitionId[]) {
348
+ for (const eventId of eventDefinitionIds) {
349
+ this.throwIfAborted();
350
+ await this.dropCollection(
351
+ this.db.customCheckpointRequests({
352
+ replicationStreamId: this.replicationStreamId,
353
+ eventId
354
+ })
355
+ );
356
+ }
357
+ }
358
+
359
+ private async dropBucketDataCollections(definitionIds: BucketDefinitionId[]): Promise<number> {
360
+ const lifecycle = this.objectStorage
361
+ ? new ObjectStorageLifecycle(this.db, this.replicationStreamId, this.objectStorage)
362
+ : null;
363
+ for (const definitionId of definitionIds) {
364
+ this.throwIfAborted();
365
+ await this.dropCollection(this.db.bucketData(this.replicationStreamId, definitionId));
366
+ }
367
+ if (definitionIds.length > 0 && this.objectStorage != null) {
368
+ const usage = new ObjectStorageUsage(this.db, this.replicationStreamId);
369
+ await this.db.client.withSession((session) =>
370
+ session.withTransaction(async () => {
371
+ for (const definitionId of definitionIds) {
372
+ await usage.removeDefinition(definitionId, session);
373
+ }
374
+ })
375
+ );
376
+ }
377
+ for (const definitionId of definitionIds) {
378
+ this.throwIfAborted();
379
+ await lifecycle?.deletePrefix(lifecycle.definitionPrefix(definitionId), { signal: this.signal });
380
+ }
381
+ return definitionIds.length;
382
+ }
383
+
384
+ /**
385
+ * Delete bucket_state documents for the unused bucket definitions.
386
+ *
387
+ * The bucket_state collection is shared across all bucket definitions of the
388
+ * stream (keyed by `_id`, an ordered `{ d, b }` compound), so we delete the individual documents.
389
+ *
390
+ * There can be hundreds of millions of buckets, so we delete per definition using a range filter
391
+ * on `_id` (which uses the `_id` index) rather than a slow `_id.d` field scan, and retry on
392
+ * MaxTimeMSExpired so each call makes progress in batches.
393
+ */
394
+ private async deleteBucketStateDocuments(definitionIds: BucketDefinitionId[]): Promise<number> {
395
+ const collection = this.db.bucketState(this.replicationStreamId);
396
+ let deletedCount = 0;
397
+ for (const definitionId of definitionIds) {
398
+ this.throwIfAborted();
399
+ deletedCount += await clearCollectionInIdRanges(
400
+ this.logger,
401
+ 'bucket state',
402
+ collection,
403
+ {
404
+ _id: idPrefixFilter<BucketStateDocumentV3['_id']>({ d: definitionId }, ['b'])
405
+ },
406
+ this.signal,
407
+ this.clearBatchThrottleRate
408
+ );
409
+ }
410
+ return deletedCount;
411
+ }
412
+
413
+ private async dropParameterIndexCollections(indexIds: ParameterIndexId[]): Promise<number> {
414
+ for (const indexId of indexIds) {
415
+ this.throwIfAborted();
416
+ await this.dropCollection(this.db.parameterIndex(this.replicationStreamId, indexId));
417
+ }
418
+ return indexIds.length;
419
+ }
420
+
421
+ private async dropCollection<T extends lib_mongo.mongo.Document>(
422
+ collection: lib_mongo.mongo.Collection<T>
423
+ ): Promise<void> {
424
+ await collection.drop({ maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS }).catch((error) => {
425
+ if (lib_mongo.isMongoNamespaceNotFoundError(error)) {
426
+ return;
427
+ }
428
+ throw error;
429
+ });
430
+ }
431
+
432
+ private async pruneStoppedSyncConfigStates(stoppedStates: SyncConfigState[]): Promise<number> {
433
+ this.throwIfAborted();
434
+ const stoppedIds = stoppedStates.map((state) => state._id);
435
+ const result = await this.db.sync_rules.updateOne(
436
+ {
437
+ _id: this.replicationStreamId,
438
+ sync_configs: {
439
+ $not: {
440
+ $elemMatch: {
441
+ _id: { $in: stoppedIds },
442
+ state: { $ne: storage.SyncRuleState.STOP }
443
+ }
444
+ }
445
+ }
446
+ },
447
+ {
448
+ $pull: {
449
+ sync_configs: {
450
+ _id: { $in: stoppedIds },
451
+ state: storage.SyncRuleState.STOP
452
+ }
453
+ }
454
+ } as bson.Document
455
+ );
456
+ if (result.modifiedCount == 0 && stoppedIds.length > 0) {
457
+ this.logger.warn(`Skipped pruning stopped sync configs`);
458
+ return 0;
459
+ }
460
+ return stoppedStates.length;
461
+ }
462
+
463
+ private throwIfAborted() {
464
+ if (this.signal?.aborted) {
465
+ throw new ReplicationAbortedError('Aborted stopped sync config cleanup', this.signal.reason);
466
+ }
467
+ }
468
+ }
469
+
470
+ function difference<T>(left: T[], right: T[]): T[] {
471
+ const rightSet = new Set(right);
472
+ return left.filter((value) => !rightSet.has(value));
473
+ }