@powersync/service-core 0.0.0-dev-20240718134716 → 0.0.0-dev-20240918082156

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 (352) hide show
  1. package/CHANGELOG.md +89 -6
  2. package/dist/api/RouteAPI.d.ts +68 -0
  3. package/dist/api/RouteAPI.js +2 -0
  4. package/dist/api/RouteAPI.js.map +1 -0
  5. package/dist/api/api-index.d.ts +1 -0
  6. package/dist/api/api-index.js +1 -0
  7. package/dist/api/api-index.js.map +1 -1
  8. package/dist/api/diagnostics.d.ts +4 -4
  9. package/dist/api/diagnostics.js +11 -65
  10. package/dist/api/diagnostics.js.map +1 -1
  11. package/dist/api/schema.d.ts +3 -5
  12. package/dist/api/schema.js +9 -79
  13. package/dist/api/schema.js.map +1 -1
  14. package/dist/auth/KeyStore.d.ts +7 -4
  15. package/dist/auth/KeyStore.js +1 -1
  16. package/dist/auth/KeyStore.js.map +1 -1
  17. package/dist/auth/auth-index.d.ts +0 -1
  18. package/dist/auth/auth-index.js +0 -1
  19. package/dist/auth/auth-index.js.map +1 -1
  20. package/dist/entry/cli-entry.js +4 -2
  21. package/dist/entry/cli-entry.js.map +1 -1
  22. package/dist/entry/commands/compact-action.d.ts +2 -0
  23. package/dist/entry/commands/compact-action.js +52 -0
  24. package/dist/entry/commands/compact-action.js.map +1 -0
  25. package/dist/entry/commands/migrate-action.js +4 -5
  26. package/dist/entry/commands/migrate-action.js.map +1 -1
  27. package/dist/entry/commands/teardown-action.js +2 -2
  28. package/dist/entry/commands/teardown-action.js.map +1 -1
  29. package/dist/entry/entry-index.d.ts +1 -0
  30. package/dist/entry/entry-index.js +1 -0
  31. package/dist/entry/entry-index.js.map +1 -1
  32. package/dist/index.d.ts +4 -2
  33. package/dist/index.js +4 -2
  34. package/dist/index.js.map +1 -1
  35. package/dist/metrics/Metrics.d.ts +6 -5
  36. package/dist/metrics/Metrics.js +53 -10
  37. package/dist/metrics/Metrics.js.map +1 -1
  38. package/dist/migrations/db/migrations/1684951997326-init.d.ts +2 -2
  39. package/dist/migrations/db/migrations/1684951997326-init.js +4 -2
  40. package/dist/migrations/db/migrations/1684951997326-init.js.map +1 -1
  41. package/dist/migrations/db/migrations/1702295701188-sync-rule-state.d.ts +2 -2
  42. package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js +4 -2
  43. package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js.map +1 -1
  44. package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.d.ts +2 -2
  45. package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.js +4 -2
  46. package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.js.map +1 -1
  47. package/dist/migrations/migrations.d.ts +8 -0
  48. package/dist/migrations/migrations.js +19 -7
  49. package/dist/migrations/migrations.js.map +1 -1
  50. package/dist/modules/AbstractModule.d.ts +26 -0
  51. package/dist/modules/AbstractModule.js +11 -0
  52. package/dist/modules/AbstractModule.js.map +1 -0
  53. package/dist/modules/ModuleManager.d.ts +11 -0
  54. package/dist/modules/ModuleManager.js +32 -0
  55. package/dist/modules/ModuleManager.js.map +1 -0
  56. package/dist/modules/modules-index.d.ts +2 -0
  57. package/dist/modules/modules-index.js +3 -0
  58. package/dist/modules/modules-index.js.map +1 -0
  59. package/dist/replication/AbstractReplicationJob.d.ts +38 -0
  60. package/dist/replication/AbstractReplicationJob.js +51 -0
  61. package/dist/replication/AbstractReplicationJob.js.map +1 -0
  62. package/dist/replication/AbstractReplicator.d.ts +53 -0
  63. package/dist/replication/AbstractReplicator.js +187 -0
  64. package/dist/replication/AbstractReplicator.js.map +1 -0
  65. package/dist/replication/ErrorRateLimiter.d.ts +0 -9
  66. package/dist/replication/ErrorRateLimiter.js +1 -42
  67. package/dist/replication/ErrorRateLimiter.js.map +1 -1
  68. package/dist/replication/ReplicationEngine.d.ts +18 -0
  69. package/dist/replication/ReplicationEngine.js +41 -0
  70. package/dist/replication/ReplicationEngine.js.map +1 -0
  71. package/dist/replication/ReplicationModule.d.ts +39 -0
  72. package/dist/replication/ReplicationModule.js +65 -0
  73. package/dist/replication/ReplicationModule.js.map +1 -0
  74. package/dist/replication/replication-index.d.ts +4 -6
  75. package/dist/replication/replication-index.js +4 -6
  76. package/dist/replication/replication-index.js.map +1 -1
  77. package/dist/routes/RouterEngine.d.ts +42 -0
  78. package/dist/routes/RouterEngine.js +80 -0
  79. package/dist/routes/RouterEngine.js.map +1 -0
  80. package/dist/routes/auth.d.ts +2 -2
  81. package/dist/routes/auth.js +11 -11
  82. package/dist/routes/auth.js.map +1 -1
  83. package/dist/routes/configure-fastify.d.ts +737 -0
  84. package/dist/routes/configure-fastify.js +57 -0
  85. package/dist/routes/configure-fastify.js.map +1 -0
  86. package/dist/routes/configure-rsocket.d.ts +13 -0
  87. package/dist/routes/configure-rsocket.js +47 -0
  88. package/dist/routes/configure-rsocket.js.map +1 -0
  89. package/dist/routes/endpoints/admin.d.ts +0 -34
  90. package/dist/routes/endpoints/admin.js +48 -89
  91. package/dist/routes/endpoints/admin.js.map +1 -1
  92. package/dist/routes/endpoints/checkpointing.d.ts +56 -16
  93. package/dist/routes/endpoints/checkpointing.js +33 -12
  94. package/dist/routes/endpoints/checkpointing.js.map +1 -1
  95. package/dist/routes/endpoints/route-endpoints-index.d.ts +0 -1
  96. package/dist/routes/endpoints/route-endpoints-index.js +0 -1
  97. package/dist/routes/endpoints/route-endpoints-index.js.map +1 -1
  98. package/dist/routes/endpoints/socket-route.js +46 -39
  99. package/dist/routes/endpoints/socket-route.js.map +1 -1
  100. package/dist/routes/endpoints/sync-rules.d.ts +1 -1
  101. package/dist/routes/endpoints/sync-rules.js +32 -23
  102. package/dist/routes/endpoints/sync-rules.js.map +1 -1
  103. package/dist/routes/endpoints/sync-stream.d.ts +10 -0
  104. package/dist/routes/endpoints/sync-stream.js +17 -13
  105. package/dist/routes/endpoints/sync-stream.js.map +1 -1
  106. package/dist/routes/route-register.d.ts +1 -1
  107. package/dist/routes/route-register.js +1 -1
  108. package/dist/routes/route-register.js.map +1 -1
  109. package/dist/routes/router-socket.d.ts +5 -4
  110. package/dist/routes/router-socket.js +2 -1
  111. package/dist/routes/router-socket.js.map +1 -1
  112. package/dist/routes/router.d.ts +7 -2
  113. package/dist/routes/router.js.map +1 -1
  114. package/dist/routes/routes-index.d.ts +3 -0
  115. package/dist/routes/routes-index.js +3 -0
  116. package/dist/routes/routes-index.js.map +1 -1
  117. package/dist/runner/teardown.js +47 -76
  118. package/dist/runner/teardown.js.map +1 -1
  119. package/dist/storage/BucketStorage.d.ts +61 -20
  120. package/dist/storage/BucketStorage.js +0 -10
  121. package/dist/storage/BucketStorage.js.map +1 -1
  122. package/dist/storage/MongoBucketStorage.d.ts +4 -4
  123. package/dist/storage/MongoBucketStorage.js +19 -24
  124. package/dist/storage/MongoBucketStorage.js.map +1 -1
  125. package/dist/storage/SourceEntity.d.ts +20 -0
  126. package/dist/storage/SourceEntity.js +2 -0
  127. package/dist/storage/SourceEntity.js.map +1 -0
  128. package/dist/storage/SourceTable.d.ts +4 -5
  129. package/dist/storage/SourceTable.js +3 -4
  130. package/dist/storage/SourceTable.js.map +1 -1
  131. package/dist/storage/StorageEngine.d.ts +24 -0
  132. package/dist/storage/StorageEngine.js +43 -0
  133. package/dist/storage/StorageEngine.js.map +1 -0
  134. package/dist/storage/StorageProvider.d.ts +21 -0
  135. package/dist/storage/StorageProvider.js +2 -0
  136. package/dist/storage/StorageProvider.js.map +1 -0
  137. package/dist/storage/mongo/MongoBucketBatch.d.ts +1 -1
  138. package/dist/storage/mongo/MongoBucketBatch.js +6 -7
  139. package/dist/storage/mongo/MongoBucketBatch.js.map +1 -1
  140. package/dist/storage/mongo/MongoCompactor.d.ts +40 -0
  141. package/dist/storage/mongo/MongoCompactor.js +293 -0
  142. package/dist/storage/mongo/MongoCompactor.js.map +1 -0
  143. package/dist/storage/mongo/MongoPersistedSyncRulesContent.d.ts +2 -2
  144. package/dist/storage/mongo/MongoPersistedSyncRulesContent.js +2 -2
  145. package/dist/storage/mongo/MongoPersistedSyncRulesContent.js.map +1 -1
  146. package/dist/storage/mongo/MongoStorageProvider.d.ts +5 -0
  147. package/dist/storage/mongo/MongoStorageProvider.js +26 -0
  148. package/dist/storage/mongo/MongoStorageProvider.js.map +1 -0
  149. package/dist/storage/mongo/MongoSyncBucketStorage.d.ts +9 -7
  150. package/dist/storage/mongo/MongoSyncBucketStorage.js +43 -28
  151. package/dist/storage/mongo/MongoSyncBucketStorage.js.map +1 -1
  152. package/dist/storage/mongo/MongoSyncRulesLock.js +1 -1
  153. package/dist/storage/mongo/MongoSyncRulesLock.js.map +1 -1
  154. package/dist/storage/mongo/OperationBatch.d.ts +7 -3
  155. package/dist/storage/mongo/OperationBatch.js +16 -7
  156. package/dist/storage/mongo/OperationBatch.js.map +1 -1
  157. package/dist/storage/mongo/PersistedBatch.d.ts +3 -3
  158. package/dist/storage/mongo/PersistedBatch.js +2 -2
  159. package/dist/storage/mongo/PersistedBatch.js.map +1 -1
  160. package/dist/storage/mongo/models.d.ts +17 -7
  161. package/dist/storage/mongo/models.js.map +1 -1
  162. package/dist/storage/mongo/util.d.ts +14 -0
  163. package/dist/storage/mongo/util.js +70 -0
  164. package/dist/storage/mongo/util.js.map +1 -1
  165. package/dist/storage/storage-index.d.ts +5 -2
  166. package/dist/storage/storage-index.js +5 -2
  167. package/dist/storage/storage-index.js.map +1 -1
  168. package/dist/sync/RequestTracker.js +2 -3
  169. package/dist/sync/RequestTracker.js.map +1 -1
  170. package/dist/sync/sync-index.d.ts +1 -0
  171. package/dist/sync/sync-index.js +1 -0
  172. package/dist/sync/sync-index.js.map +1 -1
  173. package/dist/sync/sync.d.ts +2 -1
  174. package/dist/sync/sync.js +56 -17
  175. package/dist/sync/sync.js.map +1 -1
  176. package/dist/system/ServiceContext.d.ts +37 -0
  177. package/dist/system/ServiceContext.js +48 -0
  178. package/dist/system/ServiceContext.js.map +1 -0
  179. package/dist/system/system-index.d.ts +1 -1
  180. package/dist/system/system-index.js +1 -1
  181. package/dist/system/system-index.js.map +1 -1
  182. package/dist/util/config/collectors/config-collector.d.ts +12 -0
  183. package/dist/util/config/collectors/config-collector.js +43 -0
  184. package/dist/util/config/collectors/config-collector.js.map +1 -1
  185. package/dist/util/config/compound-config-collector.d.ts +10 -29
  186. package/dist/util/config/compound-config-collector.js +28 -84
  187. package/dist/util/config/compound-config-collector.js.map +1 -1
  188. package/dist/util/config/sync-rules/sync-rules-provider.d.ts +9 -0
  189. package/dist/util/config/sync-rules/sync-rules-provider.js +15 -0
  190. package/dist/util/config/sync-rules/sync-rules-provider.js.map +1 -0
  191. package/dist/util/config/types.d.ts +6 -4
  192. package/dist/util/config/types.js.map +1 -1
  193. package/dist/util/config.d.ts +3 -4
  194. package/dist/util/config.js +5 -20
  195. package/dist/util/config.js.map +1 -1
  196. package/dist/util/protocol-types.d.ts +4 -0
  197. package/dist/util/protocol-types.js +5 -1
  198. package/dist/util/protocol-types.js.map +1 -1
  199. package/dist/util/util-index.d.ts +3 -6
  200. package/dist/util/util-index.js +3 -6
  201. package/dist/util/util-index.js.map +1 -1
  202. package/dist/util/utils.d.ts +10 -6
  203. package/dist/util/utils.js +45 -25
  204. package/dist/util/utils.js.map +1 -1
  205. package/package.json +7 -7
  206. package/src/api/RouteAPI.ts +78 -0
  207. package/src/api/api-index.ts +1 -0
  208. package/src/api/diagnostics.ts +16 -71
  209. package/src/api/schema.ts +13 -89
  210. package/src/auth/KeyStore.ts +9 -6
  211. package/src/auth/auth-index.ts +0 -1
  212. package/src/entry/cli-entry.ts +4 -2
  213. package/src/entry/commands/compact-action.ts +57 -0
  214. package/src/entry/commands/migrate-action.ts +5 -8
  215. package/src/entry/commands/teardown-action.ts +2 -2
  216. package/src/entry/entry-index.ts +1 -0
  217. package/src/index.ts +5 -2
  218. package/src/metrics/Metrics.ts +70 -15
  219. package/src/migrations/db/migrations/1684951997326-init.ts +9 -4
  220. package/src/migrations/db/migrations/1702295701188-sync-rule-state.ts +7 -4
  221. package/src/migrations/db/migrations/1711543888062-write-checkpoint-index.ts +6 -4
  222. package/src/migrations/migrations.ts +24 -8
  223. package/src/modules/AbstractModule.ts +37 -0
  224. package/src/modules/ModuleManager.ts +34 -0
  225. package/src/modules/modules-index.ts +2 -0
  226. package/src/replication/AbstractReplicationJob.ts +79 -0
  227. package/src/replication/AbstractReplicator.ts +227 -0
  228. package/src/replication/ErrorRateLimiter.ts +0 -44
  229. package/src/replication/ReplicationEngine.ts +43 -0
  230. package/src/replication/ReplicationModule.ts +101 -0
  231. package/src/replication/replication-index.ts +4 -6
  232. package/src/routes/RouterEngine.ts +120 -0
  233. package/src/routes/auth.ts +21 -12
  234. package/src/routes/configure-fastify.ts +101 -0
  235. package/src/routes/configure-rsocket.ts +60 -0
  236. package/src/routes/endpoints/admin.ts +74 -100
  237. package/src/routes/endpoints/checkpointing.ts +46 -12
  238. package/src/routes/endpoints/route-endpoints-index.ts +0 -1
  239. package/src/routes/endpoints/socket-route.ts +50 -42
  240. package/src/routes/endpoints/sync-rules.ts +41 -25
  241. package/src/routes/endpoints/sync-stream.ts +17 -13
  242. package/src/routes/route-register.ts +2 -2
  243. package/src/routes/router-socket.ts +6 -5
  244. package/src/routes/router.ts +7 -2
  245. package/src/routes/routes-index.ts +3 -0
  246. package/src/runner/teardown.ts +50 -88
  247. package/src/storage/BucketStorage.ts +74 -26
  248. package/src/storage/MongoBucketStorage.ts +23 -26
  249. package/src/storage/SourceEntity.ts +22 -0
  250. package/src/storage/SourceTable.ts +4 -6
  251. package/src/storage/StorageEngine.ts +55 -0
  252. package/src/storage/StorageProvider.ts +27 -0
  253. package/src/storage/mongo/MongoBucketBatch.ts +8 -8
  254. package/src/storage/mongo/MongoCompactor.ts +372 -0
  255. package/src/storage/mongo/MongoPersistedSyncRulesContent.ts +3 -3
  256. package/src/storage/mongo/MongoStorageProvider.ts +31 -0
  257. package/src/storage/mongo/MongoSyncBucketStorage.ts +64 -34
  258. package/src/storage/mongo/MongoSyncRulesLock.ts +1 -1
  259. package/src/storage/mongo/OperationBatch.ts +18 -11
  260. package/src/storage/mongo/PersistedBatch.ts +6 -5
  261. package/src/storage/mongo/models.ts +17 -7
  262. package/src/storage/mongo/util.ts +71 -1
  263. package/src/storage/storage-index.ts +5 -2
  264. package/src/sync/RequestTracker.ts +3 -3
  265. package/src/sync/sync-index.ts +1 -0
  266. package/src/sync/sync.ts +66 -17
  267. package/src/system/ServiceContext.ts +68 -0
  268. package/src/system/system-index.ts +1 -1
  269. package/src/util/config/collectors/config-collector.ts +48 -0
  270. package/src/util/config/compound-config-collector.ts +45 -110
  271. package/src/util/config/sync-rules/sync-rules-provider.ts +18 -0
  272. package/src/util/config/types.ts +6 -5
  273. package/src/util/config.ts +6 -23
  274. package/src/util/protocol-types.ts +6 -1
  275. package/src/util/util-index.ts +3 -6
  276. package/src/util/utils.ts +55 -39
  277. package/test/src/__snapshots__/sync.test.ts.snap +90 -5
  278. package/test/src/auth.test.ts +7 -7
  279. package/test/src/broadcast_iterable.test.ts +1 -1
  280. package/test/src/bucket_validation.test.ts +142 -0
  281. package/test/src/bucket_validation.ts +116 -0
  282. package/test/src/checksum_cache.test.ts +3 -3
  283. package/test/src/compacting.test.ts +216 -0
  284. package/test/src/data_storage.test.ts +275 -204
  285. package/test/src/env.ts +1 -3
  286. package/test/src/merge_iterable.test.ts +1 -6
  287. package/test/src/setup.ts +1 -1
  288. package/test/src/stream_utils.ts +42 -0
  289. package/test/src/sync.test.ts +209 -48
  290. package/test/src/util.ts +110 -55
  291. package/test/tsconfig.json +1 -1
  292. package/tsconfig.tsbuildinfo +1 -1
  293. package/dist/auth/SupabaseKeyCollector.d.ts +0 -22
  294. package/dist/auth/SupabaseKeyCollector.js +0 -61
  295. package/dist/auth/SupabaseKeyCollector.js.map +0 -1
  296. package/dist/replication/PgRelation.d.ts +0 -16
  297. package/dist/replication/PgRelation.js +0 -26
  298. package/dist/replication/PgRelation.js.map +0 -1
  299. package/dist/replication/WalConnection.d.ts +0 -34
  300. package/dist/replication/WalConnection.js +0 -190
  301. package/dist/replication/WalConnection.js.map +0 -1
  302. package/dist/replication/WalStream.d.ts +0 -57
  303. package/dist/replication/WalStream.js +0 -517
  304. package/dist/replication/WalStream.js.map +0 -1
  305. package/dist/replication/WalStreamManager.d.ts +0 -30
  306. package/dist/replication/WalStreamManager.js +0 -198
  307. package/dist/replication/WalStreamManager.js.map +0 -1
  308. package/dist/replication/WalStreamRunner.d.ts +0 -38
  309. package/dist/replication/WalStreamRunner.js +0 -155
  310. package/dist/replication/WalStreamRunner.js.map +0 -1
  311. package/dist/replication/util.d.ts +0 -9
  312. package/dist/replication/util.js +0 -62
  313. package/dist/replication/util.js.map +0 -1
  314. package/dist/routes/endpoints/dev.d.ts +0 -312
  315. package/dist/routes/endpoints/dev.js +0 -172
  316. package/dist/routes/endpoints/dev.js.map +0 -1
  317. package/dist/system/CorePowerSyncSystem.d.ts +0 -23
  318. package/dist/system/CorePowerSyncSystem.js +0 -52
  319. package/dist/system/CorePowerSyncSystem.js.map +0 -1
  320. package/dist/util/PgManager.d.ts +0 -24
  321. package/dist/util/PgManager.js +0 -55
  322. package/dist/util/PgManager.js.map +0 -1
  323. package/dist/util/migration_lib.d.ts +0 -11
  324. package/dist/util/migration_lib.js +0 -64
  325. package/dist/util/migration_lib.js.map +0 -1
  326. package/dist/util/pgwire_utils.d.ts +0 -24
  327. package/dist/util/pgwire_utils.js +0 -117
  328. package/dist/util/pgwire_utils.js.map +0 -1
  329. package/dist/util/populate_test_data.d.ts +0 -8
  330. package/dist/util/populate_test_data.js +0 -65
  331. package/dist/util/populate_test_data.js.map +0 -1
  332. package/src/auth/SupabaseKeyCollector.ts +0 -67
  333. package/src/replication/PgRelation.ts +0 -42
  334. package/src/replication/WalConnection.ts +0 -227
  335. package/src/replication/WalStream.ts +0 -628
  336. package/src/replication/WalStreamManager.ts +0 -213
  337. package/src/replication/WalStreamRunner.ts +0 -180
  338. package/src/replication/util.ts +0 -76
  339. package/src/routes/endpoints/dev.ts +0 -199
  340. package/src/system/CorePowerSyncSystem.ts +0 -64
  341. package/src/util/PgManager.ts +0 -64
  342. package/src/util/migration_lib.ts +0 -79
  343. package/src/util/pgwire_utils.ts +0 -139
  344. package/src/util/populate_test_data.ts +0 -78
  345. package/test/src/__snapshots__/pg_test.test.ts.snap +0 -256
  346. package/test/src/large_batch.test.ts +0 -194
  347. package/test/src/pg_test.test.ts +0 -450
  348. package/test/src/schema_changes.test.ts +0 -545
  349. package/test/src/slow_tests.test.ts +0 -296
  350. package/test/src/validation.test.ts +0 -63
  351. package/test/src/wal_stream.test.ts +0 -314
  352. package/test/src/wal_stream_utils.ts +0 -147
@@ -0,0 +1,372 @@
1
+ import { logger } from '@powersync/lib-services-framework';
2
+ import { AnyBulkWriteOperation, MaxKey, MinKey } from 'mongodb';
3
+ import { addChecksums } from '../../util/utils.js';
4
+ import { PowerSyncMongo } from './db.js';
5
+ import { BucketDataDocument, BucketDataKey } from './models.js';
6
+ import { CompactOptions } from '../BucketStorage.js';
7
+ import { cacheKey } from './OperationBatch.js';
8
+
9
+ interface CurrentBucketState {
10
+ /** Bucket name */
11
+ bucket: string;
12
+ /**
13
+ * Rows seen in the bucket, with the last op_id of each.
14
+ */
15
+ seen: Map<string, bigint>;
16
+ /**
17
+ * Estimated memory usage of the seen Map.
18
+ */
19
+ trackingSize: number;
20
+
21
+ /**
22
+ * Last (lowest) seen op_id that is not a PUT.
23
+ */
24
+ lastNotPut: bigint | null;
25
+
26
+ /**
27
+ * Number of REMOVE/MOVE operations seen since lastNotPut.
28
+ */
29
+ opsSincePut: number;
30
+ }
31
+
32
+ /**
33
+ * Additional options, primarily for testing.
34
+ */
35
+ export interface MongoCompactOptions extends CompactOptions {
36
+ /** Minimum of 2 */
37
+ clearBatchLimit?: number;
38
+ /** Minimum of 1 */
39
+ moveBatchLimit?: number;
40
+ /** Minimum of 1 */
41
+ moveBatchQueryLimit?: number;
42
+ }
43
+
44
+ const DEFAULT_CLEAR_BATCH_LIMIT = 5000;
45
+ const DEFAULT_MOVE_BATCH_LIMIT = 2000;
46
+ const DEFAULT_MOVE_BATCH_QUERY_LIMIT = 10_000;
47
+
48
+ /** This default is primarily for tests. */
49
+ const DEFAULT_MEMORY_LIMIT_MB = 64;
50
+
51
+ export class MongoCompactor {
52
+ private updates: AnyBulkWriteOperation<BucketDataDocument>[] = [];
53
+
54
+ private idLimitBytes: number;
55
+ private moveBatchLimit: number;
56
+ private moveBatchQueryLimit: number;
57
+ private clearBatchLimit: number;
58
+ private maxOpId: bigint | undefined;
59
+ private buckets: string[] | undefined;
60
+
61
+ constructor(private db: PowerSyncMongo, private group_id: number, options?: MongoCompactOptions) {
62
+ this.idLimitBytes = (options?.memoryLimitMB ?? DEFAULT_MEMORY_LIMIT_MB) * 1024 * 1024;
63
+ this.moveBatchLimit = options?.moveBatchLimit ?? DEFAULT_MOVE_BATCH_LIMIT;
64
+ this.moveBatchQueryLimit = options?.moveBatchQueryLimit ?? DEFAULT_MOVE_BATCH_QUERY_LIMIT;
65
+ this.clearBatchLimit = options?.clearBatchLimit ?? DEFAULT_CLEAR_BATCH_LIMIT;
66
+ this.maxOpId = options?.maxOpId;
67
+ this.buckets = options?.compactBuckets;
68
+ }
69
+
70
+ /**
71
+ * Compact buckets by converting operations into MOVE and/or CLEAR operations.
72
+ *
73
+ * See /docs/compacting-operations.md for details.
74
+ */
75
+ async compact() {
76
+ if (this.buckets) {
77
+ for (let bucket of this.buckets) {
78
+ // We can make this more efficient later on by iterating
79
+ // through the buckets in a single query.
80
+ // That makes batching more tricky, so we leave for later.
81
+ await this.compactInternal(bucket);
82
+ }
83
+ } else {
84
+ await this.compactInternal(undefined);
85
+ }
86
+ }
87
+
88
+ async compactInternal(bucket: string | undefined) {
89
+ const idLimitBytes = this.idLimitBytes;
90
+
91
+ let currentState: CurrentBucketState | null = null;
92
+
93
+ // Constant lower bound
94
+ const lowerBound: BucketDataKey = {
95
+ g: this.group_id,
96
+ b: bucket ?? (new MinKey() as any),
97
+ o: new MinKey() as any
98
+ };
99
+
100
+ // Upper bound is adjusted for each batch
101
+ let upperBound: BucketDataKey = {
102
+ g: this.group_id,
103
+ b: bucket ?? (new MaxKey() as any),
104
+ o: new MaxKey() as any
105
+ };
106
+
107
+ while (true) {
108
+ // Query one batch at a time, to avoid cursor timeouts
109
+ const batch = await this.db.bucket_data
110
+ .find(
111
+ {
112
+ _id: {
113
+ $gte: lowerBound,
114
+ $lt: upperBound
115
+ }
116
+ },
117
+ {
118
+ projection: {
119
+ _id: 1,
120
+ op: 1,
121
+ table: 1,
122
+ row_id: 1,
123
+ source_table: 1,
124
+ source_key: 1
125
+ },
126
+ limit: this.moveBatchQueryLimit,
127
+ sort: { _id: -1 },
128
+ singleBatch: true
129
+ }
130
+ )
131
+ .toArray();
132
+
133
+ if (batch.length == 0) {
134
+ // We've reached the end
135
+ break;
136
+ }
137
+
138
+ // Set upperBound for the next batch
139
+ upperBound = batch[batch.length - 1]._id;
140
+
141
+ for (let doc of batch) {
142
+ if (currentState == null || doc._id.b != currentState.bucket) {
143
+ if (currentState != null && currentState.lastNotPut != null && currentState.opsSincePut >= 1) {
144
+ // Important to flush before clearBucket()
145
+ await this.flush();
146
+ logger.info(
147
+ `Inserting CLEAR at ${this.group_id}:${currentState.bucket}:${currentState.lastNotPut} to remove ${currentState.opsSincePut} operations`
148
+ );
149
+
150
+ const bucket = currentState.bucket;
151
+ const clearOp = currentState.lastNotPut;
152
+ // Free memory before clearing bucket
153
+ currentState = null;
154
+ await this.clearBucket(bucket, clearOp);
155
+ }
156
+ currentState = {
157
+ bucket: doc._id.b,
158
+ seen: new Map(),
159
+ trackingSize: 0,
160
+ lastNotPut: null,
161
+ opsSincePut: 0
162
+ };
163
+ }
164
+
165
+ if (this.maxOpId != null && doc._id.o > this.maxOpId) {
166
+ continue;
167
+ }
168
+
169
+ let isPersistentPut = doc.op == 'PUT';
170
+
171
+ if (doc.op == 'REMOVE' || doc.op == 'PUT') {
172
+ const key = `${doc.table}/${doc.row_id}/${cacheKey(doc.source_table!, doc.source_key!)}`;
173
+ const targetOp = currentState.seen.get(key);
174
+ if (targetOp) {
175
+ // Will convert to MOVE, so don't count as PUT
176
+ isPersistentPut = false;
177
+
178
+ this.updates.push({
179
+ updateOne: {
180
+ filter: {
181
+ _id: doc._id
182
+ },
183
+ update: {
184
+ $set: {
185
+ op: 'MOVE',
186
+ target_op: targetOp
187
+ },
188
+ $unset: {
189
+ source_table: 1,
190
+ source_key: 1,
191
+ table: 1,
192
+ row_id: 1,
193
+ data: 1
194
+ }
195
+ }
196
+ }
197
+ });
198
+ } else {
199
+ if (currentState.trackingSize >= idLimitBytes) {
200
+ // Reached memory limit.
201
+ // Keep the highest seen values in this case.
202
+ } else {
203
+ // flatstr reduces the memory usage by flattening the string
204
+ currentState.seen.set(flatstr(key), doc._id.o);
205
+ // length + 16 for the string
206
+ // 24 for the bigint
207
+ // 50 for map overhead
208
+ // 50 for additional overhead
209
+ currentState.trackingSize += key.length + 140;
210
+ }
211
+ }
212
+ }
213
+
214
+ if (isPersistentPut) {
215
+ currentState.lastNotPut = null;
216
+ currentState.opsSincePut = 0;
217
+ } else if (doc.op != 'CLEAR') {
218
+ if (currentState.lastNotPut == null) {
219
+ currentState.lastNotPut = doc._id.o;
220
+ }
221
+ currentState.opsSincePut += 1;
222
+ }
223
+
224
+ if (this.updates.length >= this.moveBatchLimit) {
225
+ await this.flush();
226
+ }
227
+ }
228
+ }
229
+
230
+ await this.flush();
231
+ currentState?.seen.clear();
232
+ if (currentState?.lastNotPut != null && currentState?.opsSincePut > 1) {
233
+ logger.info(
234
+ `Inserting CLEAR at ${this.group_id}:${currentState.bucket}:${currentState.lastNotPut} to remove ${currentState.opsSincePut} operations`
235
+ );
236
+ const bucket = currentState.bucket;
237
+ const clearOp = currentState.lastNotPut;
238
+ // Free memory before clearing bucket
239
+ currentState = null;
240
+ await this.clearBucket(bucket, clearOp);
241
+ }
242
+ }
243
+
244
+ private async flush() {
245
+ if (this.updates.length > 0) {
246
+ logger.info(`Compacting ${this.updates.length} ops`);
247
+ await this.db.bucket_data.bulkWrite(this.updates, {
248
+ // Order is not important.
249
+ // Since checksums are not affected, these operations can happen in any order,
250
+ // and it's fine if the operations are partially applied.
251
+ // Each individual operation is atomic.
252
+ ordered: false
253
+ });
254
+ this.updates = [];
255
+ }
256
+ }
257
+
258
+ /**
259
+ * Perform a CLEAR compact for a bucket.
260
+ *
261
+ * @param bucket bucket name
262
+ * @param op op_id of the last non-PUT operation, which will be converted to CLEAR.
263
+ */
264
+ private async clearBucket(bucket: string, op: bigint) {
265
+ const opFilter = {
266
+ _id: {
267
+ $gte: {
268
+ g: this.group_id,
269
+ b: bucket,
270
+ o: new MinKey() as any
271
+ },
272
+ $lte: {
273
+ g: this.group_id,
274
+ b: bucket,
275
+ o: op
276
+ }
277
+ }
278
+ };
279
+
280
+ const session = this.db.client.startSession();
281
+ try {
282
+ let done = false;
283
+ while (!done) {
284
+ // Do the CLEAR operation in batches, with each batch a separate transaction.
285
+ // The state after each batch is fully consistent.
286
+ // We need a transaction per batch to make sure checksums stay consistent.
287
+ await session.withTransaction(
288
+ async () => {
289
+ const query = this.db.bucket_data.find(opFilter, {
290
+ session,
291
+ sort: { _id: 1 },
292
+ projection: {
293
+ _id: 1,
294
+ op: 1,
295
+ checksum: 1,
296
+ target_op: 1
297
+ },
298
+ limit: this.clearBatchLimit
299
+ });
300
+ let checksum = 0;
301
+ let lastOpId: BucketDataKey | null = null;
302
+ let targetOp: bigint | null = null;
303
+ let gotAnOp = false;
304
+ for await (let op of query.stream()) {
305
+ if (op.op == 'MOVE' || op.op == 'REMOVE' || op.op == 'CLEAR') {
306
+ checksum = addChecksums(checksum, op.checksum);
307
+ lastOpId = op._id;
308
+ if (op.op != 'CLEAR') {
309
+ gotAnOp = true;
310
+ }
311
+ if (op.target_op != null) {
312
+ if (targetOp == null || op.target_op > targetOp) {
313
+ targetOp = op.target_op;
314
+ }
315
+ }
316
+ } else {
317
+ throw new Error(`Unexpected ${op.op} operation at ${op._id.g}:${op._id.b}:${op._id.o}`);
318
+ }
319
+ }
320
+ if (!gotAnOp) {
321
+ done = true;
322
+ return;
323
+ }
324
+
325
+ logger.info(`Flushing CLEAR at ${lastOpId?.o}`);
326
+ await this.db.bucket_data.deleteMany(
327
+ {
328
+ _id: {
329
+ $gte: {
330
+ g: this.group_id,
331
+ b: bucket,
332
+ o: new MinKey() as any
333
+ },
334
+ $lte: lastOpId!
335
+ }
336
+ },
337
+ { session }
338
+ );
339
+
340
+ await this.db.bucket_data.insertOne(
341
+ {
342
+ _id: lastOpId!,
343
+ op: 'CLEAR',
344
+ checksum: checksum,
345
+ data: null,
346
+ target_op: targetOp
347
+ },
348
+ { session }
349
+ );
350
+ },
351
+ {
352
+ writeConcern: { w: 'majority' },
353
+ readConcern: { level: 'snapshot' }
354
+ }
355
+ );
356
+ }
357
+ } finally {
358
+ await session.endSession();
359
+ }
360
+ }
361
+ }
362
+
363
+ /**
364
+ * Flattens string to reduce memory usage (around 320 bytes -> 120 bytes),
365
+ * at the cost of some upfront CPU usage.
366
+ *
367
+ * From: https://github.com/davidmarkclements/flatstr/issues/8
368
+ */
369
+ function flatstr(s: string) {
370
+ s.match(/\n/g);
371
+ return s;
372
+ }
@@ -1,7 +1,7 @@
1
1
  import { SqlSyncRules } from '@powersync/service-sync-rules';
2
2
  import * as mongo from 'mongodb';
3
3
 
4
- import { PersistedSyncRulesContent } from '../BucketStorage.js';
4
+ import { ParseSyncRulesOptions, PersistedSyncRulesContent } from '../BucketStorage.js';
5
5
  import { MongoPersistedSyncRules } from './MongoPersistedSyncRules.js';
6
6
  import { MongoSyncRulesLock } from './MongoSyncRulesLock.js';
7
7
  import { PowerSyncMongo } from './db.js';
@@ -30,10 +30,10 @@ export class MongoPersistedSyncRulesContent implements PersistedSyncRulesContent
30
30
  this.last_keepalive_ts = doc.last_keepalive_ts;
31
31
  }
32
32
 
33
- parsed() {
33
+ parsed(options: ParseSyncRulesOptions) {
34
34
  return new MongoPersistedSyncRules(
35
35
  this.id,
36
- SqlSyncRules.fromYaml(this.sync_rules_content),
36
+ SqlSyncRules.fromYaml(this.sync_rules_content, options),
37
37
  this.last_checkpoint_lsn,
38
38
  this.slot_name
39
39
  );
@@ -0,0 +1,31 @@
1
+ import * as db from '../../db/db-index.js';
2
+ import { MongoBucketStorage } from '../MongoBucketStorage.js';
3
+ import { BucketStorageProvider, ActiveStorage, GetStorageOptions } from '../StorageProvider.js';
4
+ import { PowerSyncMongo } from './db.js';
5
+ import { logger } from '@powersync/lib-services-framework';
6
+
7
+ export class MongoStorageProvider implements BucketStorageProvider {
8
+ get type() {
9
+ return 'mongodb';
10
+ }
11
+
12
+ async getStorage(options: GetStorageOptions): Promise<ActiveStorage> {
13
+ const { resolvedConfig } = options;
14
+
15
+ const client = db.mongo.createMongoClient(resolvedConfig.storage);
16
+
17
+ const database = new PowerSyncMongo(client, { database: resolvedConfig.storage.database });
18
+
19
+ return {
20
+ storage: new MongoBucketStorage(database, {
21
+ // TODO currently need the entire resolved config due to this
22
+ slot_name_prefix: resolvedConfig.slot_name_prefix
23
+ }),
24
+ shutDown: () => client.close(),
25
+ tearDown: () => {
26
+ logger.info(`Tearing down storage: ${database.db.namespace}...`);
27
+ return database.db.dropDatabase();
28
+ }
29
+ } satisfies ActiveStorage;
30
+ }
31
+ }
@@ -3,26 +3,33 @@ import * as bson from 'bson';
3
3
  import * as mongo from 'mongodb';
4
4
 
5
5
  import * as db from '../../db/db-index.js';
6
- import * as replication from '../../replication/WalStream.js';
7
6
  import * as util from '../../util/util-index.js';
8
7
  import {
9
8
  BucketDataBatchOptions,
10
9
  BucketStorageBatch,
10
+ CompactOptions,
11
11
  DEFAULT_DOCUMENT_BATCH_LIMIT,
12
12
  DEFAULT_DOCUMENT_CHUNK_LIMIT_BYTES,
13
13
  FlushedResult,
14
+ ParseSyncRulesOptions,
15
+ PersistedSyncRules,
16
+ PersistedSyncRulesContent,
14
17
  ResolveTableOptions,
15
18
  ResolveTableResult,
19
+ StartBatchOptions,
20
+ SyncBucketDataBatch,
16
21
  SyncRulesBucketStorage,
17
- SyncRuleStatus
22
+ SyncRuleStatus,
23
+ TerminateOptions
18
24
  } from '../BucketStorage.js';
25
+ import { ChecksumCache, FetchPartialBucketChecksum } from '../ChecksumCache.js';
19
26
  import { MongoBucketStorage } from '../MongoBucketStorage.js';
20
27
  import { SourceTable } from '../SourceTable.js';
21
28
  import { PowerSyncMongo } from './db.js';
22
29
  import { BucketDataDocument, BucketDataKey, SourceKey, SyncRuleState } from './models.js';
23
30
  import { MongoBucketBatch } from './MongoBucketBatch.js';
24
- import { BSON_DESERIALIZE_OPTIONS, idPrefixFilter, readSingleBatch, serializeLookup } from './util.js';
25
- import { ChecksumCache, FetchPartialBucketChecksum } from '../ChecksumCache.js';
31
+ import { MongoCompactor } from './MongoCompactor.js';
32
+ import { BSON_DESERIALIZE_OPTIONS, idPrefixFilter, mapOpEntry, readSingleBatch, serializeLookup } from './util.js';
26
33
 
27
34
  export class MongoSyncBucketStorage implements SyncRulesBucketStorage {
28
35
  private readonly db: PowerSyncMongo;
@@ -32,29 +39,38 @@ export class MongoSyncBucketStorage implements SyncRulesBucketStorage {
32
39
  }
33
40
  });
34
41
 
42
+ private parsedSyncRulesCache: SqlSyncRules | undefined;
43
+
35
44
  constructor(
36
45
  public readonly factory: MongoBucketStorage,
37
46
  public readonly group_id: number,
38
- public readonly sync_rules: SqlSyncRules,
47
+ private readonly sync_rules: PersistedSyncRulesContent,
39
48
  public readonly slot_name: string
40
49
  ) {
41
50
  this.db = factory.db;
42
51
  }
43
52
 
53
+ getParsedSyncRules(options: ParseSyncRulesOptions): SqlSyncRules {
54
+ this.parsedSyncRulesCache ??= this.sync_rules.parsed(options).sync_rules;
55
+ return this.parsedSyncRulesCache;
56
+ }
57
+
44
58
  async getCheckpoint() {
45
59
  const doc = await this.db.sync_rules.findOne(
46
60
  { _id: this.group_id },
47
61
  {
48
- projection: { last_checkpoint: 1, last_checkpoint_lsn: 1 }
62
+ projection: { last_checkpoint: 1 }
49
63
  }
50
64
  );
51
65
  return {
52
- checkpoint: util.timestampToOpId(doc?.last_checkpoint ?? 0n),
53
- lsn: doc?.last_checkpoint_lsn ?? replication.ZERO_LSN
66
+ checkpoint: util.timestampToOpId(doc?.last_checkpoint ?? 0n)
54
67
  };
55
68
  }
56
69
 
57
- async startBatch(options: {}, callback: (batch: BucketStorageBatch) => Promise<void>): Promise<FlushedResult | null> {
70
+ async startBatch(
71
+ options: StartBatchOptions,
72
+ callback: (batch: BucketStorageBatch) => Promise<void>
73
+ ): Promise<FlushedResult | null> {
58
74
  const doc = await this.db.sync_rules.findOne(
59
75
  {
60
76
  _id: this.group_id
@@ -65,11 +81,11 @@ export class MongoSyncBucketStorage implements SyncRulesBucketStorage {
65
81
 
66
82
  const batch = new MongoBucketBatch(
67
83
  this.db,
68
- this.sync_rules,
84
+ this.sync_rules.parsed(options).sync_rules,
69
85
  this.group_id,
70
86
  this.slot_name,
71
87
  checkpoint_lsn,
72
- doc?.no_checkpoint_before ?? null
88
+ doc?.no_checkpoint_before ?? options.zeroLSN
73
89
  );
74
90
  try {
75
91
  await callback(batch);
@@ -87,11 +103,15 @@ export class MongoSyncBucketStorage implements SyncRulesBucketStorage {
87
103
  }
88
104
 
89
105
  async resolveTable(options: ResolveTableOptions): Promise<ResolveTableResult> {
90
- const { group_id, connection_id, connection_tag, relation } = options;
106
+ const { group_id, connection_id, connection_tag, entity_descriptor } = options;
91
107
 
92
- const { schema, name: table, relationId, replicationColumns } = relation;
108
+ const { schema, name: table, objectId, replicationColumns } = entity_descriptor;
93
109
 
94
- const columns = replicationColumns.map((column) => ({ name: column.name, type_oid: column.typeOid }));
110
+ const columns = replicationColumns.map((column) => ({
111
+ name: column.name,
112
+ type: column.type,
113
+ type_oid: column.typeId
114
+ }));
95
115
  let result: ResolveTableResult | null = null;
96
116
  await this.db.client.withSession(async (session) => {
97
117
  const col = this.db.source_tables;
@@ -99,7 +119,7 @@ export class MongoSyncBucketStorage implements SyncRulesBucketStorage {
99
119
  {
100
120
  group_id: group_id,
101
121
  connection_id: connection_id,
102
- relation_id: relationId,
122
+ relation_id: objectId,
103
123
  schema_name: schema,
104
124
  table_name: table,
105
125
  replica_id_columns2: columns
@@ -111,7 +131,7 @@ export class MongoSyncBucketStorage implements SyncRulesBucketStorage {
111
131
  _id: new bson.ObjectId(),
112
132
  group_id: group_id,
113
133
  connection_id: connection_id,
114
- relation_id: relationId,
134
+ relation_id: objectId,
115
135
  schema_name: schema,
116
136
  table_name: table,
117
137
  replica_id_columns: null,
@@ -124,7 +144,7 @@ export class MongoSyncBucketStorage implements SyncRulesBucketStorage {
124
144
  const sourceTable = new SourceTable(
125
145
  doc._id,
126
146
  connection_tag,
127
- relationId,
147
+ objectId,
128
148
  schema,
129
149
  table,
130
150
  replicationColumns,
@@ -139,7 +159,7 @@ export class MongoSyncBucketStorage implements SyncRulesBucketStorage {
139
159
  group_id: group_id,
140
160
  connection_id: connection_id,
141
161
  _id: { $ne: doc._id },
142
- $or: [{ relation_id: relationId }, { schema_name: schema, table_name: table }]
162
+ $or: [{ relation_id: objectId }, { schema_name: schema, table_name: table }]
143
163
  },
144
164
  { session }
145
165
  )
@@ -154,7 +174,7 @@ export class MongoSyncBucketStorage implements SyncRulesBucketStorage {
154
174
  doc.relation_id ?? 0,
155
175
  doc.schema_name,
156
176
  doc.table_name,
157
- doc.replica_id_columns2?.map((c) => ({ name: c.name, typeOid: c.type_oid })) ?? [],
177
+ doc.replica_id_columns2?.map((c) => ({ name: c.name, typeOid: c.type_oid, type: c.type })) ?? [],
158
178
  doc.snapshot_done ?? true
159
179
  )
160
180
  )
@@ -201,7 +221,7 @@ export class MongoSyncBucketStorage implements SyncRulesBucketStorage {
201
221
  checkpoint: util.OpId,
202
222
  dataBuckets: Map<string, string>,
203
223
  options?: BucketDataBatchOptions
204
- ): AsyncIterable<util.SyncBucketData> {
224
+ ): AsyncIterable<SyncBucketDataBatch> {
205
225
  if (dataBuckets.size == 0) {
206
226
  return;
207
227
  }
@@ -267,6 +287,7 @@ export class MongoSyncBucketStorage implements SyncRulesBucketStorage {
267
287
 
268
288
  let batchSize = 0;
269
289
  let currentBatch: util.SyncBucketData | null = null;
290
+ let targetOp: bigint | null = null;
270
291
 
271
292
  // Ordered by _id, meaning buckets are grouped together
272
293
  for (let rawData of data) {
@@ -284,7 +305,8 @@ export class MongoSyncBucketStorage implements SyncRulesBucketStorage {
284
305
  start = currentBatch.after;
285
306
  currentBatch = null;
286
307
  batchSize = 0;
287
- yield yieldBatch;
308
+ yield { batch: yieldBatch, targetOp: targetOp };
309
+ targetOp = null;
288
310
  }
289
311
 
290
312
  start ??= dataBuckets.get(bucket);
@@ -298,17 +320,18 @@ export class MongoSyncBucketStorage implements SyncRulesBucketStorage {
298
320
  data: [],
299
321
  next_after: start
300
322
  };
323
+ targetOp = null;
324
+ }
325
+
326
+ const entry = mapOpEntry(row);
327
+
328
+ if (row.target_op != null) {
329
+ // MOVE, CLEAR
330
+ if (targetOp == null || row.target_op > targetOp) {
331
+ targetOp = row.target_op;
332
+ }
301
333
  }
302
334
 
303
- const entry: util.OplogEntry = {
304
- op_id: util.timestampToOpId(row._id.o),
305
- op: row.op,
306
- object_type: row.table,
307
- object_id: row.row_id,
308
- checksum: Number(row.checksum),
309
- subkey: `${row.source_table}/${row.source_key.toHexString()}`,
310
- data: row.data
311
- };
312
335
  currentBatch.data.push(entry);
313
336
  currentBatch.next_after = entry.op_id;
314
337
 
@@ -318,7 +341,8 @@ export class MongoSyncBucketStorage implements SyncRulesBucketStorage {
318
341
  if (currentBatch != null) {
319
342
  const yieldBatch = currentBatch;
320
343
  currentBatch = null;
321
- yield yieldBatch;
344
+ yield { batch: yieldBatch, targetOp: targetOp };
345
+ targetOp = null;
322
346
  }
323
347
  }
324
348
 
@@ -379,9 +403,11 @@ export class MongoSyncBucketStorage implements SyncRulesBucketStorage {
379
403
  );
380
404
  }
381
405
 
382
- async terminate() {
383
- await this.clear();
384
-
406
+ async terminate(options?: TerminateOptions) {
407
+ // Default is to clear the storage except when explicitly requested not to.
408
+ if (!options || options?.clearStorage) {
409
+ await this.clear();
410
+ }
385
411
  await this.db.sync_rules.updateOne(
386
412
  {
387
413
  _id: this.group_id
@@ -530,4 +556,8 @@ export class MongoSyncBucketStorage implements SyncRulesBucketStorage {
530
556
  }
531
557
  );
532
558
  }
559
+
560
+ async compact(options?: CompactOptions) {
561
+ return new MongoCompactor(this.db, this.group_id, options).compact();
562
+ }
533
563
  }
@@ -30,7 +30,7 @@ export class MongoSyncRulesLock implements ReplicationLock {
30
30
  );
31
31
 
32
32
  if (doc == null) {
33
- throw new Error(`Replication slot ${sync_rules.slot_name} is locked by another process`);
33
+ throw new Error(`Sync rules: ${sync_rules.id} have been locked by another process for replication.`);
34
34
  }
35
35
  return new MongoSyncRulesLock(db, sync_rules.id, lockId);
36
36
  }