@powersync/service-core 0.8.8 → 0.9.0

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 (372) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/api/RouteAPI.d.ts +67 -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 +170 -158
  10. package/dist/api/diagnostics.js.map +1 -1
  11. package/dist/api/schema.d.ts +3 -5
  12. package/dist/api/schema.js +14 -80
  13. package/dist/api/schema.js.map +1 -1
  14. package/dist/auth/CachedKeyCollector.js.map +1 -1
  15. package/dist/auth/KeySpec.js.map +1 -1
  16. package/dist/auth/KeyStore.d.ts +7 -4
  17. package/dist/auth/KeyStore.js +1 -1
  18. package/dist/auth/KeyStore.js.map +1 -1
  19. package/dist/auth/LeakyBucket.js.map +1 -1
  20. package/dist/auth/RemoteJWKSCollector.d.ts +0 -2
  21. package/dist/auth/RemoteJWKSCollector.js.map +1 -1
  22. package/dist/auth/auth-index.d.ts +0 -1
  23. package/dist/auth/auth-index.js +0 -1
  24. package/dist/auth/auth-index.js.map +1 -1
  25. package/dist/db/mongo.js +5 -3
  26. package/dist/db/mongo.js.map +1 -1
  27. package/dist/entry/cli-entry.js +3 -2
  28. package/dist/entry/cli-entry.js.map +1 -1
  29. package/dist/entry/commands/compact-action.js +90 -14
  30. package/dist/entry/commands/compact-action.js.map +1 -1
  31. package/dist/entry/commands/migrate-action.js +4 -5
  32. package/dist/entry/commands/migrate-action.js.map +1 -1
  33. package/dist/entry/commands/teardown-action.js +2 -2
  34. package/dist/entry/commands/teardown-action.js.map +1 -1
  35. package/dist/index.d.ts +4 -2
  36. package/dist/index.js +4 -2
  37. package/dist/index.js.map +1 -1
  38. package/dist/locks/MongoLocks.js.map +1 -1
  39. package/dist/metrics/Metrics.d.ts +2 -2
  40. package/dist/metrics/Metrics.js +5 -13
  41. package/dist/metrics/Metrics.js.map +1 -1
  42. package/dist/migrations/db/migrations/1684951997326-init.d.ts +2 -2
  43. package/dist/migrations/db/migrations/1684951997326-init.js +4 -2
  44. package/dist/migrations/db/migrations/1684951997326-init.js.map +1 -1
  45. package/dist/migrations/db/migrations/1702295701188-sync-rule-state.d.ts +2 -2
  46. package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js +4 -2
  47. package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js.map +1 -1
  48. package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.d.ts +2 -2
  49. package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.js +4 -2
  50. package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.js.map +1 -1
  51. package/dist/migrations/db/migrations/1727099539247-custom-write-checkpoint-index.d.ts +3 -0
  52. package/dist/migrations/db/migrations/1727099539247-custom-write-checkpoint-index.js +31 -0
  53. package/dist/migrations/db/migrations/1727099539247-custom-write-checkpoint-index.js.map +1 -0
  54. package/dist/migrations/executor.js.map +1 -1
  55. package/dist/migrations/migrations.d.ts +8 -0
  56. package/dist/migrations/migrations.js +19 -7
  57. package/dist/migrations/migrations.js.map +1 -1
  58. package/dist/migrations/store/migration-store.js.map +1 -1
  59. package/dist/modules/AbstractModule.d.ts +26 -0
  60. package/dist/modules/AbstractModule.js +11 -0
  61. package/dist/modules/AbstractModule.js.map +1 -0
  62. package/dist/modules/ModuleManager.d.ts +11 -0
  63. package/dist/modules/ModuleManager.js +32 -0
  64. package/dist/modules/ModuleManager.js.map +1 -0
  65. package/dist/modules/modules-index.d.ts +2 -0
  66. package/dist/modules/modules-index.js +3 -0
  67. package/dist/modules/modules-index.js.map +1 -0
  68. package/dist/replication/AbstractReplicationJob.d.ts +37 -0
  69. package/dist/replication/AbstractReplicationJob.js +51 -0
  70. package/dist/replication/AbstractReplicationJob.js.map +1 -0
  71. package/dist/replication/AbstractReplicator.d.ts +53 -0
  72. package/dist/replication/AbstractReplicator.js +250 -0
  73. package/dist/replication/AbstractReplicator.js.map +1 -0
  74. package/dist/replication/ErrorRateLimiter.d.ts +0 -10
  75. package/dist/replication/ErrorRateLimiter.js +1 -42
  76. package/dist/replication/ErrorRateLimiter.js.map +1 -1
  77. package/dist/replication/ReplicationEngine.d.ts +18 -0
  78. package/dist/replication/ReplicationEngine.js +41 -0
  79. package/dist/replication/ReplicationEngine.js.map +1 -0
  80. package/dist/replication/ReplicationModule.d.ts +51 -0
  81. package/dist/replication/ReplicationModule.js +68 -0
  82. package/dist/replication/ReplicationModule.js.map +1 -0
  83. package/dist/replication/replication-index.d.ts +4 -6
  84. package/dist/replication/replication-index.js +4 -6
  85. package/dist/replication/replication-index.js.map +1 -1
  86. package/dist/routes/RouterEngine.d.ts +42 -0
  87. package/dist/routes/RouterEngine.js +80 -0
  88. package/dist/routes/RouterEngine.js.map +1 -0
  89. package/dist/routes/auth.d.ts +2 -2
  90. package/dist/routes/auth.js +11 -11
  91. package/dist/routes/auth.js.map +1 -1
  92. package/dist/routes/configure-fastify.d.ts +37 -23
  93. package/dist/routes/configure-fastify.js +18 -18
  94. package/dist/routes/configure-fastify.js.map +1 -1
  95. package/dist/routes/configure-rsocket.d.ts +3 -4
  96. package/dist/routes/configure-rsocket.js +7 -4
  97. package/dist/routes/configure-rsocket.js.map +1 -1
  98. package/dist/routes/endpoints/admin.d.ts +30 -0
  99. package/dist/routes/endpoints/admin.js +46 -67
  100. package/dist/routes/endpoints/admin.js.map +1 -1
  101. package/dist/routes/endpoints/checkpointing.js +103 -15
  102. package/dist/routes/endpoints/checkpointing.js.map +1 -1
  103. package/dist/routes/endpoints/socket-route.js +8 -6
  104. package/dist/routes/endpoints/socket-route.js.map +1 -1
  105. package/dist/routes/endpoints/sync-rules.d.ts +1 -1
  106. package/dist/routes/endpoints/sync-rules.js +32 -23
  107. package/dist/routes/endpoints/sync-rules.js.map +1 -1
  108. package/dist/routes/endpoints/sync-stream.d.ts +0 -1
  109. package/dist/routes/endpoints/sync-stream.js +8 -8
  110. package/dist/routes/endpoints/sync-stream.js.map +1 -1
  111. package/dist/routes/hooks.js.map +1 -1
  112. package/dist/routes/route-register.js.map +1 -1
  113. package/dist/routes/router.d.ts +9 -2
  114. package/dist/routes/router.js.map +1 -1
  115. package/dist/routes/routes-index.d.ts +1 -0
  116. package/dist/routes/routes-index.js +1 -0
  117. package/dist/routes/routes-index.js.map +1 -1
  118. package/dist/runner/teardown.js +109 -76
  119. package/dist/runner/teardown.js.map +1 -1
  120. package/dist/storage/BucketStorage.d.ts +86 -36
  121. package/dist/storage/BucketStorage.js +6 -10
  122. package/dist/storage/BucketStorage.js.map +1 -1
  123. package/dist/storage/ChecksumCache.js.map +1 -1
  124. package/dist/storage/MongoBucketStorage.d.ts +7 -11
  125. package/dist/storage/MongoBucketStorage.js +48 -41
  126. package/dist/storage/MongoBucketStorage.js.map +1 -1
  127. package/dist/storage/ReplicationEventPayload.d.ts +14 -0
  128. package/dist/storage/ReplicationEventPayload.js +2 -0
  129. package/dist/storage/ReplicationEventPayload.js.map +1 -0
  130. package/dist/storage/SourceEntity.d.ts +20 -0
  131. package/dist/storage/SourceEntity.js +2 -0
  132. package/dist/storage/SourceEntity.js.map +1 -0
  133. package/dist/storage/SourceTable.d.ts +12 -5
  134. package/dist/storage/SourceTable.js +12 -5
  135. package/dist/storage/SourceTable.js.map +1 -1
  136. package/dist/storage/StorageEngine.d.ts +28 -0
  137. package/dist/storage/StorageEngine.js +45 -0
  138. package/dist/storage/StorageEngine.js.map +1 -0
  139. package/dist/storage/StorageProvider.d.ts +21 -0
  140. package/dist/storage/StorageProvider.js +2 -0
  141. package/dist/storage/StorageProvider.js.map +1 -0
  142. package/dist/storage/WriteCheckpointAPI.d.ts +74 -0
  143. package/dist/storage/WriteCheckpointAPI.js +16 -0
  144. package/dist/storage/WriteCheckpointAPI.js.map +1 -0
  145. package/dist/storage/mongo/MongoBucketBatch.d.ts +24 -5
  146. package/dist/storage/mongo/MongoBucketBatch.js +119 -62
  147. package/dist/storage/mongo/MongoBucketBatch.js.map +1 -1
  148. package/dist/storage/mongo/MongoCompactor.js +20 -3
  149. package/dist/storage/mongo/MongoCompactor.js.map +1 -1
  150. package/dist/storage/mongo/MongoIdSequence.js.map +1 -1
  151. package/dist/storage/mongo/MongoPersistedSyncRulesContent.d.ts +2 -2
  152. package/dist/storage/mongo/MongoPersistedSyncRulesContent.js +2 -2
  153. package/dist/storage/mongo/MongoPersistedSyncRulesContent.js.map +1 -1
  154. package/dist/storage/mongo/MongoStorageProvider.d.ts +5 -0
  155. package/dist/storage/mongo/MongoStorageProvider.js +26 -0
  156. package/dist/storage/mongo/MongoStorageProvider.js.map +1 -0
  157. package/dist/storage/mongo/MongoSyncBucketStorage.d.ts +18 -10
  158. package/dist/storage/mongo/MongoSyncBucketStorage.js +140 -25
  159. package/dist/storage/mongo/MongoSyncBucketStorage.js.map +1 -1
  160. package/dist/storage/mongo/MongoSyncRulesLock.js +1 -1
  161. package/dist/storage/mongo/MongoSyncRulesLock.js.map +1 -1
  162. package/dist/storage/mongo/MongoWriteCheckpointAPI.d.ts +20 -0
  163. package/dist/storage/mongo/MongoWriteCheckpointAPI.js +103 -0
  164. package/dist/storage/mongo/MongoWriteCheckpointAPI.js.map +1 -0
  165. package/dist/storage/mongo/OperationBatch.d.ts +13 -4
  166. package/dist/storage/mongo/OperationBatch.js +25 -7
  167. package/dist/storage/mongo/OperationBatch.js.map +1 -1
  168. package/dist/storage/mongo/PersistedBatch.d.ts +3 -3
  169. package/dist/storage/mongo/PersistedBatch.js +2 -2
  170. package/dist/storage/mongo/PersistedBatch.js.map +1 -1
  171. package/dist/storage/mongo/config.d.ts +19 -0
  172. package/dist/storage/mongo/config.js +26 -0
  173. package/dist/storage/mongo/config.js.map +1 -0
  174. package/dist/storage/mongo/db.d.ts +3 -2
  175. package/dist/storage/mongo/db.js +1 -0
  176. package/dist/storage/mongo/db.js.map +1 -1
  177. package/dist/storage/mongo/models.d.ts +20 -5
  178. package/dist/storage/mongo/models.js.map +1 -1
  179. package/dist/storage/mongo/util.d.ts +12 -1
  180. package/dist/storage/mongo/util.js +50 -2
  181. package/dist/storage/mongo/util.js.map +1 -1
  182. package/dist/storage/storage-index.d.ts +8 -2
  183. package/dist/storage/storage-index.js +8 -2
  184. package/dist/storage/storage-index.js.map +1 -1
  185. package/dist/sync/BroadcastIterable.d.ts +0 -1
  186. package/dist/sync/BroadcastIterable.js.map +1 -1
  187. package/dist/sync/LastValueSink.d.ts +0 -1
  188. package/dist/sync/LastValueSink.js.map +1 -1
  189. package/dist/sync/merge.d.ts +0 -1
  190. package/dist/sync/merge.js.map +1 -1
  191. package/dist/sync/safeRace.js.map +1 -1
  192. package/dist/sync/sync.d.ts +1 -1
  193. package/dist/sync/sync.js +5 -5
  194. package/dist/sync/sync.js.map +1 -1
  195. package/dist/sync/util.d.ts +0 -2
  196. package/dist/sync/util.js.map +1 -1
  197. package/dist/system/ServiceContext.d.ts +37 -0
  198. package/dist/system/ServiceContext.js +48 -0
  199. package/dist/system/ServiceContext.js.map +1 -0
  200. package/dist/system/system-index.d.ts +1 -1
  201. package/dist/system/system-index.js +1 -1
  202. package/dist/system/system-index.js.map +1 -1
  203. package/dist/util/Mutex.js.map +1 -1
  204. package/dist/util/config/collectors/config-collector.js.map +1 -1
  205. package/dist/util/config/collectors/impl/base64-config-collector.js.map +1 -1
  206. package/dist/util/config/collectors/impl/filesystem-config-collector.js.map +1 -1
  207. package/dist/util/config/compound-config-collector.d.ts +9 -2
  208. package/dist/util/config/compound-config-collector.js +16 -24
  209. package/dist/util/config/compound-config-collector.js.map +1 -1
  210. package/dist/util/config/sync-rules/impl/base64-sync-rules-collector.js.map +1 -1
  211. package/dist/util/config/sync-rules/impl/filesystem-sync-rules-collector.js.map +1 -1
  212. package/dist/util/config/sync-rules/impl/inline-sync-rules-collector.js.map +1 -1
  213. package/dist/util/config/sync-rules/sync-rules-provider.d.ts +9 -0
  214. package/dist/util/config/sync-rules/sync-rules-provider.js +15 -0
  215. package/dist/util/config/sync-rules/sync-rules-provider.js.map +1 -0
  216. package/dist/util/config/types.d.ts +7 -4
  217. package/dist/util/config/types.js.map +1 -1
  218. package/dist/util/config.d.ts +3 -4
  219. package/dist/util/config.js +5 -20
  220. package/dist/util/config.js.map +1 -1
  221. package/dist/util/memory-tracking.js.map +1 -1
  222. package/dist/util/secs.js.map +1 -1
  223. package/dist/util/util-index.d.ts +3 -6
  224. package/dist/util/util-index.js +3 -6
  225. package/dist/util/util-index.js.map +1 -1
  226. package/dist/util/utils.d.ts +10 -7
  227. package/dist/util/utils.js +36 -25
  228. package/dist/util/utils.js.map +1 -1
  229. package/package.json +8 -12
  230. package/src/api/RouteAPI.ts +78 -0
  231. package/src/api/api-index.ts +1 -0
  232. package/src/api/diagnostics.ts +18 -70
  233. package/src/api/schema.ts +18 -90
  234. package/src/auth/KeyStore.ts +9 -6
  235. package/src/auth/RemoteJWKSCollector.ts +4 -1
  236. package/src/auth/auth-index.ts +0 -1
  237. package/src/db/mongo.ts +5 -3
  238. package/src/entry/cli-entry.ts +3 -2
  239. package/src/entry/commands/compact-action.ts +24 -12
  240. package/src/entry/commands/migrate-action.ts +5 -8
  241. package/src/entry/commands/teardown-action.ts +2 -2
  242. package/src/index.ts +5 -2
  243. package/src/metrics/Metrics.ts +6 -16
  244. package/src/migrations/db/migrations/1684951997326-init.ts +9 -4
  245. package/src/migrations/db/migrations/1702295701188-sync-rule-state.ts +7 -4
  246. package/src/migrations/db/migrations/1711543888062-write-checkpoint-index.ts +6 -4
  247. package/src/migrations/db/migrations/1727099539247-custom-write-checkpoint-index.ts +37 -0
  248. package/src/migrations/migrations.ts +24 -8
  249. package/src/modules/AbstractModule.ts +37 -0
  250. package/src/modules/ModuleManager.ts +34 -0
  251. package/src/modules/modules-index.ts +2 -0
  252. package/src/replication/AbstractReplicationJob.ts +79 -0
  253. package/src/replication/AbstractReplicator.ts +228 -0
  254. package/src/replication/ErrorRateLimiter.ts +0 -44
  255. package/src/replication/ReplicationEngine.ts +43 -0
  256. package/src/replication/ReplicationModule.ts +122 -0
  257. package/src/replication/replication-index.ts +4 -6
  258. package/src/routes/RouterEngine.ts +120 -0
  259. package/src/routes/auth.ts +21 -12
  260. package/src/routes/configure-fastify.ts +26 -27
  261. package/src/routes/configure-rsocket.ts +13 -8
  262. package/src/routes/endpoints/admin.ts +72 -76
  263. package/src/routes/endpoints/checkpointing.ts +51 -11
  264. package/src/routes/endpoints/socket-route.ts +10 -6
  265. package/src/routes/endpoints/sync-rules.ts +41 -25
  266. package/src/routes/endpoints/sync-stream.ts +8 -8
  267. package/src/routes/router.ts +8 -3
  268. package/src/routes/routes-index.ts +1 -0
  269. package/src/runner/teardown.ts +50 -88
  270. package/src/storage/BucketStorage.ts +103 -41
  271. package/src/storage/MongoBucketStorage.ts +65 -53
  272. package/src/storage/ReplicationEventPayload.ts +16 -0
  273. package/src/storage/SourceEntity.ts +22 -0
  274. package/src/storage/SourceTable.ts +14 -7
  275. package/src/storage/StorageEngine.ts +62 -0
  276. package/src/storage/StorageProvider.ts +27 -0
  277. package/src/storage/WriteCheckpointAPI.ts +85 -0
  278. package/src/storage/mongo/MongoBucketBatch.ts +164 -84
  279. package/src/storage/mongo/MongoCompactor.ts +25 -4
  280. package/src/storage/mongo/MongoPersistedSyncRulesContent.ts +7 -4
  281. package/src/storage/mongo/MongoStorageProvider.ts +31 -0
  282. package/src/storage/mongo/MongoSyncBucketStorage.ts +118 -41
  283. package/src/storage/mongo/MongoSyncRulesLock.ts +7 -3
  284. package/src/storage/mongo/MongoWriteCheckpointAPI.ts +151 -0
  285. package/src/storage/mongo/OperationBatch.ts +28 -12
  286. package/src/storage/mongo/PersistedBatch.ts +10 -6
  287. package/src/storage/mongo/config.ts +40 -0
  288. package/src/storage/mongo/db.ts +4 -1
  289. package/src/storage/mongo/models.ts +21 -5
  290. package/src/storage/mongo/util.ts +48 -3
  291. package/src/storage/storage-index.ts +8 -2
  292. package/src/sync/sync.ts +7 -4
  293. package/src/sync/util.ts +0 -1
  294. package/src/system/ServiceContext.ts +68 -0
  295. package/src/system/system-index.ts +1 -1
  296. package/src/util/config/compound-config-collector.ts +31 -31
  297. package/src/util/config/sync-rules/sync-rules-provider.ts +18 -0
  298. package/src/util/config/types.ts +7 -5
  299. package/src/util/config.ts +6 -23
  300. package/src/util/util-index.ts +3 -6
  301. package/src/util/utils.ts +48 -41
  302. package/test/src/__snapshots__/sync.test.ts.snap +14 -14
  303. package/test/src/auth.test.ts +7 -7
  304. package/test/src/broadcast_iterable.test.ts +1 -1
  305. package/test/src/compacting.test.ts +50 -40
  306. package/test/src/data_storage.test.ts +382 -202
  307. package/test/src/env.ts +1 -3
  308. package/test/src/merge_iterable.test.ts +1 -6
  309. package/test/src/routes/probes.integration.test.ts +34 -30
  310. package/test/src/setup.ts +1 -1
  311. package/test/src/stream_utils.ts +42 -0
  312. package/test/src/sync.test.ts +115 -39
  313. package/test/src/util.ts +48 -51
  314. package/test/tsconfig.json +1 -1
  315. package/tsconfig.tsbuildinfo +1 -1
  316. package/vitest.config.ts +7 -1
  317. package/dist/auth/SupabaseKeyCollector.d.ts +0 -22
  318. package/dist/auth/SupabaseKeyCollector.js +0 -61
  319. package/dist/auth/SupabaseKeyCollector.js.map +0 -1
  320. package/dist/replication/PgRelation.d.ts +0 -16
  321. package/dist/replication/PgRelation.js +0 -26
  322. package/dist/replication/PgRelation.js.map +0 -1
  323. package/dist/replication/WalConnection.d.ts +0 -34
  324. package/dist/replication/WalConnection.js +0 -190
  325. package/dist/replication/WalConnection.js.map +0 -1
  326. package/dist/replication/WalStream.d.ts +0 -57
  327. package/dist/replication/WalStream.js +0 -519
  328. package/dist/replication/WalStream.js.map +0 -1
  329. package/dist/replication/WalStreamManager.d.ts +0 -30
  330. package/dist/replication/WalStreamManager.js +0 -198
  331. package/dist/replication/WalStreamManager.js.map +0 -1
  332. package/dist/replication/WalStreamRunner.d.ts +0 -38
  333. package/dist/replication/WalStreamRunner.js +0 -155
  334. package/dist/replication/WalStreamRunner.js.map +0 -1
  335. package/dist/replication/util.d.ts +0 -9
  336. package/dist/replication/util.js +0 -62
  337. package/dist/replication/util.js.map +0 -1
  338. package/dist/system/CorePowerSyncSystem.d.ts +0 -23
  339. package/dist/system/CorePowerSyncSystem.js +0 -52
  340. package/dist/system/CorePowerSyncSystem.js.map +0 -1
  341. package/dist/util/PgManager.d.ts +0 -24
  342. package/dist/util/PgManager.js +0 -55
  343. package/dist/util/PgManager.js.map +0 -1
  344. package/dist/util/migration_lib.d.ts +0 -11
  345. package/dist/util/migration_lib.js +0 -64
  346. package/dist/util/migration_lib.js.map +0 -1
  347. package/dist/util/pgwire_utils.d.ts +0 -24
  348. package/dist/util/pgwire_utils.js +0 -117
  349. package/dist/util/pgwire_utils.js.map +0 -1
  350. package/dist/util/populate_test_data.d.ts +0 -8
  351. package/dist/util/populate_test_data.js +0 -65
  352. package/dist/util/populate_test_data.js.map +0 -1
  353. package/src/auth/SupabaseKeyCollector.ts +0 -67
  354. package/src/replication/PgRelation.ts +0 -42
  355. package/src/replication/WalConnection.ts +0 -227
  356. package/src/replication/WalStream.ts +0 -631
  357. package/src/replication/WalStreamManager.ts +0 -213
  358. package/src/replication/WalStreamRunner.ts +0 -180
  359. package/src/replication/util.ts +0 -76
  360. package/src/system/CorePowerSyncSystem.ts +0 -64
  361. package/src/util/PgManager.ts +0 -64
  362. package/src/util/migration_lib.ts +0 -79
  363. package/src/util/pgwire_utils.ts +0 -139
  364. package/src/util/populate_test_data.ts +0 -78
  365. package/test/src/__snapshots__/pg_test.test.ts.snap +0 -256
  366. package/test/src/large_batch.test.ts +0 -194
  367. package/test/src/pg_test.test.ts +0 -450
  368. package/test/src/schema_changes.test.ts +0 -545
  369. package/test/src/slow_tests.test.ts +0 -338
  370. package/test/src/validation.test.ts +0 -63
  371. package/test/src/wal_stream.test.ts +0 -319
  372. package/test/src/wal_stream_utils.ts +0 -156
@@ -4,6 +4,7 @@ import { addChecksums } from '../../util/utils.js';
4
4
  import { PowerSyncMongo } from './db.js';
5
5
  import { BucketDataDocument, BucketDataKey } from './models.js';
6
6
  import { CompactOptions } from '../BucketStorage.js';
7
+ import { cacheKey } from './OperationBatch.js';
7
8
 
8
9
  interface CurrentBucketState {
9
10
  /** Bucket name */
@@ -57,7 +58,11 @@ export class MongoCompactor {
57
58
  private maxOpId: bigint | undefined;
58
59
  private buckets: string[] | undefined;
59
60
 
60
- constructor(private db: PowerSyncMongo, private group_id: number, options?: MongoCompactOptions) {
61
+ constructor(
62
+ private db: PowerSyncMongo,
63
+ private group_id: number,
64
+ options?: MongoCompactOptions
65
+ ) {
61
66
  this.idLimitBytes = (options?.memoryLimitMB ?? DEFAULT_MEMORY_LIMIT_MB) * 1024 * 1024;
62
67
  this.moveBatchLimit = options?.moveBatchLimit ?? DEFAULT_MOVE_BATCH_LIMIT;
63
68
  this.moveBatchQueryLimit = options?.moveBatchQueryLimit ?? DEFAULT_MOVE_BATCH_QUERY_LIMIT;
@@ -89,17 +94,33 @@ export class MongoCompactor {
89
94
 
90
95
  let currentState: CurrentBucketState | null = null;
91
96
 
97
+ let bucketLower: string | MinKey;
98
+ let bucketUpper: string | MaxKey;
99
+
100
+ if (bucket == null) {
101
+ bucketLower = new MinKey();
102
+ bucketUpper = new MaxKey();
103
+ } else if (bucket.includes('[')) {
104
+ // Exact bucket name
105
+ bucketLower = bucket;
106
+ bucketUpper = bucket;
107
+ } else {
108
+ // Bucket definition name
109
+ bucketLower = `${bucket}[`;
110
+ bucketUpper = `${bucket}[\uFFFF`;
111
+ }
112
+
92
113
  // Constant lower bound
93
114
  const lowerBound: BucketDataKey = {
94
115
  g: this.group_id,
95
- b: bucket ?? (new MinKey() as any),
116
+ b: bucketLower as string,
96
117
  o: new MinKey() as any
97
118
  };
98
119
 
99
120
  // Upper bound is adjusted for each batch
100
121
  let upperBound: BucketDataKey = {
101
122
  g: this.group_id,
102
- b: bucket ?? (new MaxKey() as any),
123
+ b: bucketUpper as string,
103
124
  o: new MaxKey() as any
104
125
  };
105
126
 
@@ -168,7 +189,7 @@ export class MongoCompactor {
168
189
  let isPersistentPut = doc.op == 'PUT';
169
190
 
170
191
  if (doc.op == 'REMOVE' || doc.op == 'PUT') {
171
- const key = `${doc.table}/${doc.row_id}/${doc.source_table}/${doc.source_key?.toHexString()}`;
192
+ const key = `${doc.table}/${doc.row_id}/${cacheKey(doc.source_table!, doc.source_key!)}`;
172
193
  const targetOp = currentState.seen.get(key);
173
194
  if (targetOp) {
174
195
  // Will convert to MOVE, so don't count as PUT
@@ -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';
@@ -19,7 +19,10 @@ export class MongoPersistedSyncRulesContent implements PersistedSyncRulesContent
19
19
 
20
20
  public current_lock: MongoSyncRulesLock | null = null;
21
21
 
22
- constructor(private db: PowerSyncMongo, doc: mongo.WithId<SyncRuleDocument>) {
22
+ constructor(
23
+ private db: PowerSyncMongo,
24
+ doc: mongo.WithId<SyncRuleDocument>
25
+ ) {
23
26
  this.id = doc._id;
24
27
  this.sync_rules_content = doc.content;
25
28
  this.last_checkpoint_lsn = doc.last_checkpoint_lsn;
@@ -30,10 +33,10 @@ export class MongoPersistedSyncRulesContent implements PersistedSyncRulesContent
30
33
  this.last_keepalive_ts = doc.last_keepalive_ts;
31
34
  }
32
35
 
33
- parsed() {
36
+ parsed(options: ParseSyncRulesOptions) {
34
37
  return new MongoPersistedSyncRules(
35
38
  this.id,
36
- SqlSyncRules.fromYaml(this.sync_rules_content),
39
+ SqlSyncRules.fromYaml(this.sync_rules_content, options),
37
40
  this.last_checkpoint_lsn,
38
41
  this.slot_name
39
42
  );
@@ -0,0 +1,31 @@
1
+ import { logger } from '@powersync/lib-services-framework';
2
+ import * as db from '../../db/db-index.js';
3
+ import { MongoBucketStorage } from '../MongoBucketStorage.js';
4
+ import { ActiveStorage, BucketStorageProvider, GetStorageOptions } from '../StorageProvider.js';
5
+ import { PowerSyncMongo } from './db.js';
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
+ }
@@ -2,8 +2,9 @@ import { SqliteJsonRow, SqliteJsonValue, SqlSyncRules } from '@powersync/service
2
2
  import * as bson from 'bson';
3
3
  import * as mongo from 'mongodb';
4
4
 
5
+ import { DisposableObserver, logger } from '@powersync/lib-services-framework';
6
+ import * as timers from 'timers/promises';
5
7
  import * as db from '../../db/db-index.js';
6
- import * as replication from '../../replication/WalStream.js';
7
8
  import * as util from '../../util/util-index.js';
8
9
  import {
9
10
  BucketDataBatchOptions,
@@ -12,24 +13,39 @@ import {
12
13
  DEFAULT_DOCUMENT_BATCH_LIMIT,
13
14
  DEFAULT_DOCUMENT_CHUNK_LIMIT_BYTES,
14
15
  FlushedResult,
16
+ ParseSyncRulesOptions,
17
+ PersistedSyncRulesContent,
18
+ ReplicationCheckpoint,
15
19
  ResolveTableOptions,
16
20
  ResolveTableResult,
21
+ StartBatchOptions,
17
22
  SyncBucketDataBatch,
18
23
  SyncRulesBucketStorage,
19
- SyncRuleStatus
24
+ SyncRulesBucketStorageListener,
25
+ SyncRuleStatus,
26
+ TerminateOptions
20
27
  } from '../BucketStorage.js';
21
28
  import { ChecksumCache, FetchPartialBucketChecksum, PartialChecksum, PartialChecksumMap } from '../ChecksumCache.js';
22
29
  import { MongoBucketStorage } from '../MongoBucketStorage.js';
23
30
  import { SourceTable } from '../SourceTable.js';
31
+ import {
32
+ BatchedCustomWriteCheckpointOptions,
33
+ ManagedWriteCheckpointOptions,
34
+ SyncStorageLastWriteCheckpointFilters,
35
+ WriteCheckpointAPI,
36
+ WriteCheckpointMode
37
+ } from '../WriteCheckpointAPI.js';
24
38
  import { PowerSyncMongo } from './db.js';
25
39
  import { BucketDataDocument, BucketDataKey, SourceKey, SyncRuleState } from './models.js';
26
40
  import { MongoBucketBatch } from './MongoBucketBatch.js';
27
41
  import { MongoCompactor } from './MongoCompactor.js';
42
+ import { MongoWriteCheckpointAPI } from './MongoWriteCheckpointAPI.js';
28
43
  import { BSON_DESERIALIZE_OPTIONS, idPrefixFilter, mapOpEntry, readSingleBatch, serializeLookup } from './util.js';
29
- import { logger } from '@powersync/lib-services-framework';
30
- import * as timers from 'timers/promises';
31
44
 
32
- export class MongoSyncBucketStorage implements SyncRulesBucketStorage {
45
+ export class MongoSyncBucketStorage
46
+ extends DisposableObserver<SyncRulesBucketStorageListener>
47
+ implements SyncRulesBucketStorage
48
+ {
33
49
  private readonly db: PowerSyncMongo;
34
50
  private checksumCache = new ChecksumCache({
35
51
  fetchChecksums: (batch) => {
@@ -37,16 +53,70 @@ export class MongoSyncBucketStorage implements SyncRulesBucketStorage {
37
53
  }
38
54
  });
39
55
 
56
+ private parsedSyncRulesCache: { parsed: SqlSyncRules; options: ParseSyncRulesOptions } | undefined;
57
+ private writeCheckpointAPI: WriteCheckpointAPI;
58
+
40
59
  constructor(
41
60
  public readonly factory: MongoBucketStorage,
42
61
  public readonly group_id: number,
43
- public readonly sync_rules: SqlSyncRules,
44
- public readonly slot_name: string
62
+ private readonly sync_rules: PersistedSyncRulesContent,
63
+ public readonly slot_name: string,
64
+ writeCheckpointMode: WriteCheckpointMode = WriteCheckpointMode.MANAGED
45
65
  ) {
66
+ super();
46
67
  this.db = factory.db;
68
+ this.writeCheckpointAPI = new MongoWriteCheckpointAPI({
69
+ db: this.db,
70
+ mode: writeCheckpointMode
71
+ });
72
+ }
73
+
74
+ get writeCheckpointMode() {
75
+ return this.writeCheckpointAPI.writeCheckpointMode;
76
+ }
77
+
78
+ setWriteCheckpointMode(mode: WriteCheckpointMode): void {
79
+ this.writeCheckpointAPI.setWriteCheckpointMode(mode);
80
+ }
81
+
82
+ batchCreateCustomWriteCheckpoints(checkpoints: BatchedCustomWriteCheckpointOptions[]): Promise<void> {
83
+ return this.writeCheckpointAPI.batchCreateCustomWriteCheckpoints(
84
+ checkpoints.map((checkpoint) => ({ ...checkpoint, sync_rules_id: this.group_id }))
85
+ );
86
+ }
87
+
88
+ createCustomWriteCheckpoint(checkpoint: BatchedCustomWriteCheckpointOptions): Promise<bigint> {
89
+ return this.writeCheckpointAPI.createCustomWriteCheckpoint({
90
+ ...checkpoint,
91
+ sync_rules_id: this.group_id
92
+ });
47
93
  }
48
94
 
49
- async getCheckpoint() {
95
+ createManagedWriteCheckpoint(checkpoint: ManagedWriteCheckpointOptions): Promise<bigint> {
96
+ return this.writeCheckpointAPI.createManagedWriteCheckpoint(checkpoint);
97
+ }
98
+
99
+ lastWriteCheckpoint(filters: SyncStorageLastWriteCheckpointFilters): Promise<bigint | null> {
100
+ return this.writeCheckpointAPI.lastWriteCheckpoint({
101
+ ...filters,
102
+ sync_rules_id: this.group_id
103
+ });
104
+ }
105
+
106
+ getParsedSyncRules(options: ParseSyncRulesOptions): SqlSyncRules {
107
+ const { parsed, options: cachedOptions } = this.parsedSyncRulesCache ?? {};
108
+ /**
109
+ * Check if the cached sync rules, if present, had the same options.
110
+ * Parse sync rules if the options are different or if there is no cached value.
111
+ */
112
+ if (!parsed || options.defaultSchema != cachedOptions?.defaultSchema) {
113
+ this.parsedSyncRulesCache = { parsed: this.sync_rules.parsed(options).sync_rules, options };
114
+ }
115
+
116
+ return this.parsedSyncRulesCache!.parsed;
117
+ }
118
+
119
+ async getCheckpoint(): Promise<ReplicationCheckpoint> {
50
120
  const doc = await this.db.sync_rules.findOne(
51
121
  { _id: this.group_id },
52
122
  {
@@ -55,11 +125,14 @@ export class MongoSyncBucketStorage implements SyncRulesBucketStorage {
55
125
  );
56
126
  return {
57
127
  checkpoint: util.timestampToOpId(doc?.last_checkpoint ?? 0n),
58
- lsn: doc?.last_checkpoint_lsn ?? replication.ZERO_LSN
128
+ lsn: doc?.last_checkpoint_lsn ?? null
59
129
  };
60
130
  }
61
131
 
62
- async startBatch(options: {}, callback: (batch: BucketStorageBatch) => Promise<void>): Promise<FlushedResult | null> {
132
+ async startBatch(
133
+ options: StartBatchOptions,
134
+ callback: (batch: BucketStorageBatch) => Promise<void>
135
+ ): Promise<FlushedResult | null> {
63
136
  const doc = await this.db.sync_rules.findOne(
64
137
  {
65
138
  _id: this.group_id
@@ -68,35 +141,36 @@ export class MongoSyncBucketStorage implements SyncRulesBucketStorage {
68
141
  );
69
142
  const checkpoint_lsn = doc?.last_checkpoint_lsn ?? null;
70
143
 
71
- const batch = new MongoBucketBatch(
72
- this.db,
73
- this.sync_rules,
74
- this.group_id,
75
- this.slot_name,
76
- checkpoint_lsn,
77
- doc?.no_checkpoint_before ?? null
78
- );
79
- try {
80
- await callback(batch);
81
- await batch.flush();
82
- await batch.abort();
83
- if (batch.last_flushed_op) {
84
- return { flushed_op: String(batch.last_flushed_op) };
85
- } else {
86
- return null;
87
- }
88
- } catch (e) {
89
- await batch.abort();
90
- throw e;
144
+ await using batch = new MongoBucketBatch({
145
+ db: this.db,
146
+ syncRules: this.sync_rules.parsed(options).sync_rules,
147
+ groupId: this.group_id,
148
+ slotName: this.slot_name,
149
+ lastCheckpointLsn: checkpoint_lsn,
150
+ noCheckpointBeforeLsn: doc?.no_checkpoint_before ?? options.zeroLSN,
151
+ storeCurrentData: options.storeCurrentData
152
+ });
153
+ this.iterateListeners((cb) => cb.batchStarted?.(batch));
154
+
155
+ await callback(batch);
156
+ await batch.flush();
157
+ if (batch.last_flushed_op) {
158
+ return { flushed_op: String(batch.last_flushed_op) };
159
+ } else {
160
+ return null;
91
161
  }
92
162
  }
93
163
 
94
164
  async resolveTable(options: ResolveTableOptions): Promise<ResolveTableResult> {
95
- const { group_id, connection_id, connection_tag, relation } = options;
165
+ const { group_id, connection_id, connection_tag, entity_descriptor } = options;
96
166
 
97
- const { schema, name: table, relationId, replicationColumns } = relation;
167
+ const { schema, name: table, objectId, replicationColumns } = entity_descriptor;
98
168
 
99
- const columns = replicationColumns.map((column) => ({ name: column.name, type_oid: column.typeOid }));
169
+ const columns = replicationColumns.map((column) => ({
170
+ name: column.name,
171
+ type: column.type,
172
+ type_oid: column.typeId
173
+ }));
100
174
  let result: ResolveTableResult | null = null;
101
175
  await this.db.client.withSession(async (session) => {
102
176
  const col = this.db.source_tables;
@@ -104,7 +178,7 @@ export class MongoSyncBucketStorage implements SyncRulesBucketStorage {
104
178
  {
105
179
  group_id: group_id,
106
180
  connection_id: connection_id,
107
- relation_id: relationId,
181
+ relation_id: objectId,
108
182
  schema_name: schema,
109
183
  table_name: table,
110
184
  replica_id_columns2: columns
@@ -116,7 +190,7 @@ export class MongoSyncBucketStorage implements SyncRulesBucketStorage {
116
190
  _id: new bson.ObjectId(),
117
191
  group_id: group_id,
118
192
  connection_id: connection_id,
119
- relation_id: relationId,
193
+ relation_id: objectId,
120
194
  schema_name: schema,
121
195
  table_name: table,
122
196
  replica_id_columns: null,
@@ -129,12 +203,13 @@ export class MongoSyncBucketStorage implements SyncRulesBucketStorage {
129
203
  const sourceTable = new SourceTable(
130
204
  doc._id,
131
205
  connection_tag,
132
- relationId,
206
+ objectId,
133
207
  schema,
134
208
  table,
135
209
  replicationColumns,
136
210
  doc.snapshot_done ?? true
137
211
  );
212
+ sourceTable.syncEvent = options.sync_rules.tableTriggersEvent(sourceTable);
138
213
  sourceTable.syncData = options.sync_rules.tableSyncsData(sourceTable);
139
214
  sourceTable.syncParameters = options.sync_rules.tableSyncsParameters(sourceTable);
140
215
 
@@ -144,7 +219,7 @@ export class MongoSyncBucketStorage implements SyncRulesBucketStorage {
144
219
  group_id: group_id,
145
220
  connection_id: connection_id,
146
221
  _id: { $ne: doc._id },
147
- $or: [{ relation_id: relationId }, { schema_name: schema, table_name: table }]
222
+ $or: [{ relation_id: objectId }, { schema_name: schema, table_name: table }]
148
223
  },
149
224
  { session }
150
225
  )
@@ -159,7 +234,7 @@ export class MongoSyncBucketStorage implements SyncRulesBucketStorage {
159
234
  doc.relation_id ?? 0,
160
235
  doc.schema_name,
161
236
  doc.table_name,
162
- doc.replica_id_columns2?.map((c) => ({ name: c.name, typeOid: c.type_oid })) ?? [],
237
+ doc.replica_id_columns2?.map((c) => ({ name: c.name, typeOid: c.type_oid, type: c.type })) ?? [],
163
238
  doc.snapshot_done ?? true
164
239
  )
165
240
  )
@@ -398,9 +473,11 @@ export class MongoSyncBucketStorage implements SyncRulesBucketStorage {
398
473
  );
399
474
  }
400
475
 
401
- async terminate() {
402
- await this.clear();
403
-
476
+ async terminate(options?: TerminateOptions) {
477
+ // Default is to clear the storage except when explicitly requested not to.
478
+ if (!options || options?.clearStorage) {
479
+ await this.clear();
480
+ }
404
481
  await this.db.sync_rules.updateOne(
405
482
  {
406
483
  _id: this.group_id
@@ -9,7 +9,7 @@ import { logger } from '@powersync/lib-services-framework';
9
9
  * replicates those sync rules at a time.
10
10
  */
11
11
  export class MongoSyncRulesLock implements ReplicationLock {
12
- private readonly refreshInterval: NodeJS.Timer;
12
+ private readonly refreshInterval: NodeJS.Timeout;
13
13
 
14
14
  static async createLock(db: PowerSyncMongo, sync_rules: PersistedSyncRulesContent): Promise<MongoSyncRulesLock> {
15
15
  const lockId = crypto.randomBytes(8).toString('hex');
@@ -30,12 +30,16 @@ 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
  }
37
37
 
38
- constructor(private db: PowerSyncMongo, public sync_rules_id: number, private lock_id: string) {
38
+ constructor(
39
+ private db: PowerSyncMongo,
40
+ public sync_rules_id: number,
41
+ private lock_id: string
42
+ ) {
39
43
  this.refreshInterval = setInterval(async () => {
40
44
  try {
41
45
  await this.refresh();
@@ -0,0 +1,151 @@
1
+ import * as framework from '@powersync/lib-services-framework';
2
+ import {
3
+ CustomWriteCheckpointFilters,
4
+ CustomWriteCheckpointOptions,
5
+ LastWriteCheckpointFilters,
6
+ ManagedWriteCheckpointFilters,
7
+ ManagedWriteCheckpointOptions,
8
+ WriteCheckpointAPI,
9
+ WriteCheckpointMode
10
+ } from '../WriteCheckpointAPI.js';
11
+ import { PowerSyncMongo } from './db.js';
12
+
13
+ export type MongoCheckpointAPIOptions = {
14
+ db: PowerSyncMongo;
15
+ mode: WriteCheckpointMode;
16
+ };
17
+
18
+ export class MongoWriteCheckpointAPI implements WriteCheckpointAPI {
19
+ readonly db: PowerSyncMongo;
20
+ private _mode: WriteCheckpointMode;
21
+
22
+ constructor(options: MongoCheckpointAPIOptions) {
23
+ this.db = options.db;
24
+ this._mode = options.mode;
25
+ }
26
+
27
+ get writeCheckpointMode() {
28
+ return this._mode;
29
+ }
30
+
31
+ setWriteCheckpointMode(mode: WriteCheckpointMode): void {
32
+ this._mode = mode;
33
+ }
34
+
35
+ async batchCreateCustomWriteCheckpoints(checkpoints: CustomWriteCheckpointOptions[]): Promise<void> {
36
+ return batchCreateCustomWriteCheckpoints(this.db, checkpoints);
37
+ }
38
+
39
+ async createCustomWriteCheckpoint(options: CustomWriteCheckpointOptions): Promise<bigint> {
40
+ if (this.writeCheckpointMode !== WriteCheckpointMode.CUSTOM) {
41
+ throw new framework.errors.ValidationError(
42
+ `Creating a custom Write Checkpoint when the current Write Checkpoint mode is set to "${this.writeCheckpointMode}"`
43
+ );
44
+ }
45
+
46
+ const { checkpoint, user_id, sync_rules_id } = options;
47
+ const doc = await this.db.custom_write_checkpoints.findOneAndUpdate(
48
+ {
49
+ user_id: user_id,
50
+ sync_rules_id
51
+ },
52
+ {
53
+ $set: {
54
+ checkpoint
55
+ }
56
+ },
57
+ { upsert: true, returnDocument: 'after' }
58
+ );
59
+ return doc!.checkpoint;
60
+ }
61
+
62
+ async createManagedWriteCheckpoint(checkpoint: ManagedWriteCheckpointOptions): Promise<bigint> {
63
+ if (this.writeCheckpointMode !== WriteCheckpointMode.MANAGED) {
64
+ throw new framework.errors.ValidationError(
65
+ `Attempting to create a managed Write Checkpoint when the current Write Checkpoint mode is set to "${this.writeCheckpointMode}"`
66
+ );
67
+ }
68
+
69
+ const { user_id, heads: lsns } = checkpoint;
70
+ const doc = await this.db.write_checkpoints.findOneAndUpdate(
71
+ {
72
+ user_id: user_id
73
+ },
74
+ {
75
+ $set: {
76
+ lsns
77
+ },
78
+ $inc: {
79
+ client_id: 1n
80
+ }
81
+ },
82
+ { upsert: true, returnDocument: 'after' }
83
+ );
84
+ return doc!.client_id;
85
+ }
86
+
87
+ async lastWriteCheckpoint(filters: LastWriteCheckpointFilters): Promise<bigint | null> {
88
+ switch (this.writeCheckpointMode) {
89
+ case WriteCheckpointMode.CUSTOM:
90
+ if (false == 'sync_rules_id' in filters) {
91
+ throw new framework.errors.ValidationError(`Sync rules ID is required for custom Write Checkpoint filtering`);
92
+ }
93
+ return this.lastCustomWriteCheckpoint(filters);
94
+ case WriteCheckpointMode.MANAGED:
95
+ if (false == 'heads' in filters) {
96
+ throw new framework.errors.ValidationError(
97
+ `Replication HEAD is required for managed Write Checkpoint filtering`
98
+ );
99
+ }
100
+ return this.lastManagedWriteCheckpoint(filters);
101
+ }
102
+ }
103
+
104
+ protected async lastCustomWriteCheckpoint(filters: CustomWriteCheckpointFilters) {
105
+ const { user_id, sync_rules_id } = filters;
106
+ const lastWriteCheckpoint = await this.db.custom_write_checkpoints.findOne({
107
+ user_id,
108
+ sync_rules_id
109
+ });
110
+ return lastWriteCheckpoint?.checkpoint ?? null;
111
+ }
112
+
113
+ protected async lastManagedWriteCheckpoint(filters: ManagedWriteCheckpointFilters) {
114
+ const { user_id, heads } = filters;
115
+ // TODO: support multiple heads when we need to support multiple connections
116
+ const lsn = heads['1'];
117
+ if (lsn == null) {
118
+ // Can happen if we haven't replicated anything yet.
119
+ return null;
120
+ }
121
+ const lastWriteCheckpoint = await this.db.write_checkpoints.findOne({
122
+ user_id: user_id,
123
+ 'lsns.1': { $lte: lsn }
124
+ });
125
+ return lastWriteCheckpoint?.client_id ?? null;
126
+ }
127
+ }
128
+
129
+ export async function batchCreateCustomWriteCheckpoints(
130
+ db: PowerSyncMongo,
131
+ checkpoints: CustomWriteCheckpointOptions[]
132
+ ): Promise<void> {
133
+ if (!checkpoints.length) {
134
+ return;
135
+ }
136
+
137
+ await db.custom_write_checkpoints.bulkWrite(
138
+ checkpoints.map((checkpointOptions) => ({
139
+ updateOne: {
140
+ filter: { user_id: checkpointOptions.user_id, sync_rules_id: checkpointOptions.sync_rules_id },
141
+ update: {
142
+ $set: {
143
+ checkpoint: checkpointOptions.checkpoint,
144
+ sync_rules_id: checkpointOptions.sync_rules_id
145
+ }
146
+ },
147
+ upsert: true
148
+ }
149
+ }))
150
+ );
151
+ }
@@ -1,8 +1,9 @@
1
- import * as bson from 'bson';
2
1
  import { ToastableSqliteRow } from '@powersync/service-sync-rules';
2
+ import * as bson from 'bson';
3
3
 
4
- import * as util from '../../util/util-index.js';
5
4
  import { SaveOptions } from '../BucketStorage.js';
5
+ import { isUUID } from './util.js';
6
+ import { ReplicaId } from './models.js';
6
7
 
7
8
  /**
8
9
  * Maximum number of operations in a batch.
@@ -42,7 +43,16 @@ export class OperationBatch {
42
43
  return this.batch.length >= MAX_BATCH_COUNT || this.currentSize > MAX_RECORD_BATCH_SIZE;
43
44
  }
44
45
 
45
- *batched(sizes: Map<string, number>): Generator<RecordOperation[]> {
46
+ /**
47
+ *
48
+ * @param sizes Map of source key to estimated size of the current_data document, or undefined if current_data is not persisted.
49
+ *
50
+ */
51
+ *batched(sizes: Map<string, number> | undefined): Generator<RecordOperation[]> {
52
+ if (sizes == null) {
53
+ yield this.batch;
54
+ return;
55
+ }
46
56
  let currentBatch: RecordOperation[] = [];
47
57
  let currentBatchSize = 0;
48
58
  for (let op of this.batch) {
@@ -63,18 +73,15 @@ export class OperationBatch {
63
73
  }
64
74
 
65
75
  export class RecordOperation {
66
- public readonly afterId: bson.UUID | null;
67
- public readonly beforeId: bson.UUID;
76
+ public readonly afterId: ReplicaId | null;
77
+ public readonly beforeId: ReplicaId;
68
78
  public readonly internalBeforeKey: string;
69
79
  public readonly internalAfterKey: string | null;
70
80
  public readonly estimatedSize: number;
71
81
 
72
82
  constructor(public readonly record: SaveOptions) {
73
- const after = record.after;
74
- const afterId = after ? util.getUuidReplicaIdentityBson(after, record.sourceTable.replicaIdColumns!) : null;
75
- const beforeId = record.before
76
- ? util.getUuidReplicaIdentityBson(record.before, record.sourceTable.replicaIdColumns!)
77
- : afterId!;
83
+ const afterId = record.afterReplicaId ?? null;
84
+ const beforeId = record.beforeReplicaId ?? record.afterReplicaId;
78
85
  this.afterId = afterId;
79
86
  this.beforeId = beforeId;
80
87
  this.internalBeforeKey = cacheKey(record.sourceTable.id, beforeId);
@@ -84,8 +91,17 @@ export class RecordOperation {
84
91
  }
85
92
  }
86
93
 
87
- export function cacheKey(table: bson.ObjectId, id: bson.UUID) {
88
- return `${table.toHexString()}.${id.toHexString()}`;
94
+ /**
95
+ * In-memory cache key - must not be persisted.
96
+ */
97
+ export function cacheKey(table: bson.ObjectId, id: ReplicaId) {
98
+ if (isUUID(id)) {
99
+ return `${table.toHexString()}.${id.toHexString()}`;
100
+ } else if (typeof id == 'string') {
101
+ return `${table.toHexString()}.${id}`;
102
+ } else {
103
+ return `${table.toHexString()}.${(bson.serialize({ id: id }) as Buffer).toString('base64')}`;
104
+ }
89
105
  }
90
106
 
91
107
  /**