@powersync/service-core 0.0.0-dev-20240620165206

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 (454) hide show
  1. package/.probes/.gitkeep +0 -0
  2. package/CHANGELOG.md +82 -0
  3. package/LICENSE +67 -0
  4. package/README.md +3 -0
  5. package/dist/api/api-index.d.ts +2 -0
  6. package/dist/api/api-index.js +3 -0
  7. package/dist/api/api-index.js.map +1 -0
  8. package/dist/api/diagnostics.d.ts +21 -0
  9. package/dist/api/diagnostics.js +183 -0
  10. package/dist/api/diagnostics.js.map +1 -0
  11. package/dist/api/schema.d.ts +5 -0
  12. package/dist/api/schema.js +88 -0
  13. package/dist/api/schema.js.map +1 -0
  14. package/dist/auth/CachedKeyCollector.d.ts +46 -0
  15. package/dist/auth/CachedKeyCollector.js +116 -0
  16. package/dist/auth/CachedKeyCollector.js.map +1 -0
  17. package/dist/auth/CompoundKeyCollector.d.ts +8 -0
  18. package/dist/auth/CompoundKeyCollector.js +23 -0
  19. package/dist/auth/CompoundKeyCollector.js.map +1 -0
  20. package/dist/auth/JwtPayload.d.ts +10 -0
  21. package/dist/auth/JwtPayload.js +2 -0
  22. package/dist/auth/JwtPayload.js.map +1 -0
  23. package/dist/auth/KeyCollector.d.ts +24 -0
  24. package/dist/auth/KeyCollector.js +2 -0
  25. package/dist/auth/KeyCollector.js.map +1 -0
  26. package/dist/auth/KeySpec.d.ts +26 -0
  27. package/dist/auth/KeySpec.js +49 -0
  28. package/dist/auth/KeySpec.js.map +1 -0
  29. package/dist/auth/KeyStore.d.ts +39 -0
  30. package/dist/auth/KeyStore.js +131 -0
  31. package/dist/auth/KeyStore.js.map +1 -0
  32. package/dist/auth/LeakyBucket.d.ts +39 -0
  33. package/dist/auth/LeakyBucket.js +57 -0
  34. package/dist/auth/LeakyBucket.js.map +1 -0
  35. package/dist/auth/RemoteJWKSCollector.d.ts +24 -0
  36. package/dist/auth/RemoteJWKSCollector.js +106 -0
  37. package/dist/auth/RemoteJWKSCollector.js.map +1 -0
  38. package/dist/auth/StaticKeyCollector.d.ts +14 -0
  39. package/dist/auth/StaticKeyCollector.js +19 -0
  40. package/dist/auth/StaticKeyCollector.js.map +1 -0
  41. package/dist/auth/SupabaseKeyCollector.d.ts +22 -0
  42. package/dist/auth/SupabaseKeyCollector.js +61 -0
  43. package/dist/auth/SupabaseKeyCollector.js.map +1 -0
  44. package/dist/auth/auth-index.d.ts +10 -0
  45. package/dist/auth/auth-index.js +11 -0
  46. package/dist/auth/auth-index.js.map +1 -0
  47. package/dist/db/db-index.d.ts +1 -0
  48. package/dist/db/db-index.js +2 -0
  49. package/dist/db/db-index.js.map +1 -0
  50. package/dist/db/mongo.d.ts +29 -0
  51. package/dist/db/mongo.js +65 -0
  52. package/dist/db/mongo.js.map +1 -0
  53. package/dist/entry/cli-entry.d.ts +15 -0
  54. package/dist/entry/cli-entry.js +36 -0
  55. package/dist/entry/cli-entry.js.map +1 -0
  56. package/dist/entry/commands/config-command.d.ts +10 -0
  57. package/dist/entry/commands/config-command.js +21 -0
  58. package/dist/entry/commands/config-command.js.map +1 -0
  59. package/dist/entry/commands/migrate-action.d.ts +2 -0
  60. package/dist/entry/commands/migrate-action.js +18 -0
  61. package/dist/entry/commands/migrate-action.js.map +1 -0
  62. package/dist/entry/commands/start-action.d.ts +3 -0
  63. package/dist/entry/commands/start-action.js +15 -0
  64. package/dist/entry/commands/start-action.js.map +1 -0
  65. package/dist/entry/commands/teardown-action.d.ts +2 -0
  66. package/dist/entry/commands/teardown-action.js +17 -0
  67. package/dist/entry/commands/teardown-action.js.map +1 -0
  68. package/dist/entry/entry-index.d.ts +5 -0
  69. package/dist/entry/entry-index.js +6 -0
  70. package/dist/entry/entry-index.js.map +1 -0
  71. package/dist/index.d.ts +25 -0
  72. package/dist/index.js +28 -0
  73. package/dist/index.js.map +1 -0
  74. package/dist/locks/LockManager.d.ts +10 -0
  75. package/dist/locks/LockManager.js +7 -0
  76. package/dist/locks/LockManager.js.map +1 -0
  77. package/dist/locks/MongoLocks.d.ts +36 -0
  78. package/dist/locks/MongoLocks.js +81 -0
  79. package/dist/locks/MongoLocks.js.map +1 -0
  80. package/dist/locks/locks-index.d.ts +2 -0
  81. package/dist/locks/locks-index.js +3 -0
  82. package/dist/locks/locks-index.js.map +1 -0
  83. package/dist/metrics/Metrics.d.ts +30 -0
  84. package/dist/metrics/Metrics.js +176 -0
  85. package/dist/metrics/Metrics.js.map +1 -0
  86. package/dist/migrations/db/migrations/1684951997326-init.d.ts +3 -0
  87. package/dist/migrations/db/migrations/1684951997326-init.js +31 -0
  88. package/dist/migrations/db/migrations/1684951997326-init.js.map +1 -0
  89. package/dist/migrations/db/migrations/1688556755264-initial-sync-rules.d.ts +2 -0
  90. package/dist/migrations/db/migrations/1688556755264-initial-sync-rules.js +5 -0
  91. package/dist/migrations/db/migrations/1688556755264-initial-sync-rules.js.map +1 -0
  92. package/dist/migrations/db/migrations/1702295701188-sync-rule-state.d.ts +3 -0
  93. package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js +54 -0
  94. package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js.map +1 -0
  95. package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.d.ts +3 -0
  96. package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.js +27 -0
  97. package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.js.map +1 -0
  98. package/dist/migrations/definitions.d.ts +18 -0
  99. package/dist/migrations/definitions.js +6 -0
  100. package/dist/migrations/definitions.js.map +1 -0
  101. package/dist/migrations/executor.d.ts +16 -0
  102. package/dist/migrations/executor.js +64 -0
  103. package/dist/migrations/executor.js.map +1 -0
  104. package/dist/migrations/migrations-index.d.ts +3 -0
  105. package/dist/migrations/migrations-index.js +4 -0
  106. package/dist/migrations/migrations-index.js.map +1 -0
  107. package/dist/migrations/migrations.d.ts +10 -0
  108. package/dist/migrations/migrations.js +90 -0
  109. package/dist/migrations/migrations.js.map +1 -0
  110. package/dist/migrations/store/migration-store.d.ts +11 -0
  111. package/dist/migrations/store/migration-store.js +46 -0
  112. package/dist/migrations/store/migration-store.js.map +1 -0
  113. package/dist/replication/ErrorRateLimiter.d.ts +17 -0
  114. package/dist/replication/ErrorRateLimiter.js +43 -0
  115. package/dist/replication/ErrorRateLimiter.js.map +1 -0
  116. package/dist/replication/PgRelation.d.ts +16 -0
  117. package/dist/replication/PgRelation.js +26 -0
  118. package/dist/replication/PgRelation.js.map +1 -0
  119. package/dist/replication/WalConnection.d.ts +34 -0
  120. package/dist/replication/WalConnection.js +190 -0
  121. package/dist/replication/WalConnection.js.map +1 -0
  122. package/dist/replication/WalStream.d.ts +57 -0
  123. package/dist/replication/WalStream.js +515 -0
  124. package/dist/replication/WalStream.js.map +1 -0
  125. package/dist/replication/WalStreamManager.d.ts +30 -0
  126. package/dist/replication/WalStreamManager.js +198 -0
  127. package/dist/replication/WalStreamManager.js.map +1 -0
  128. package/dist/replication/WalStreamRunner.d.ts +38 -0
  129. package/dist/replication/WalStreamRunner.js +155 -0
  130. package/dist/replication/WalStreamRunner.js.map +1 -0
  131. package/dist/replication/replication-index.d.ts +7 -0
  132. package/dist/replication/replication-index.js +8 -0
  133. package/dist/replication/replication-index.js.map +1 -0
  134. package/dist/replication/util.d.ts +9 -0
  135. package/dist/replication/util.js +62 -0
  136. package/dist/replication/util.js.map +1 -0
  137. package/dist/routes/auth.d.ts +56 -0
  138. package/dist/routes/auth.js +182 -0
  139. package/dist/routes/auth.js.map +1 -0
  140. package/dist/routes/endpoints/admin.d.ts +1011 -0
  141. package/dist/routes/endpoints/admin.js +207 -0
  142. package/dist/routes/endpoints/admin.js.map +1 -0
  143. package/dist/routes/endpoints/checkpointing.d.ts +76 -0
  144. package/dist/routes/endpoints/checkpointing.js +36 -0
  145. package/dist/routes/endpoints/checkpointing.js.map +1 -0
  146. package/dist/routes/endpoints/dev.d.ts +312 -0
  147. package/dist/routes/endpoints/dev.js +172 -0
  148. package/dist/routes/endpoints/dev.js.map +1 -0
  149. package/dist/routes/endpoints/route-endpoints-index.d.ts +6 -0
  150. package/dist/routes/endpoints/route-endpoints-index.js +7 -0
  151. package/dist/routes/endpoints/route-endpoints-index.js.map +1 -0
  152. package/dist/routes/endpoints/socket-route.d.ts +2 -0
  153. package/dist/routes/endpoints/socket-route.js +119 -0
  154. package/dist/routes/endpoints/socket-route.js.map +1 -0
  155. package/dist/routes/endpoints/sync-rules.d.ts +174 -0
  156. package/dist/routes/endpoints/sync-rules.js +202 -0
  157. package/dist/routes/endpoints/sync-rules.js.map +1 -0
  158. package/dist/routes/endpoints/sync-stream.d.ts +132 -0
  159. package/dist/routes/endpoints/sync-stream.js +83 -0
  160. package/dist/routes/endpoints/sync-stream.js.map +1 -0
  161. package/dist/routes/hooks.d.ts +10 -0
  162. package/dist/routes/hooks.js +32 -0
  163. package/dist/routes/hooks.js.map +1 -0
  164. package/dist/routes/route-register.d.ts +10 -0
  165. package/dist/routes/route-register.js +87 -0
  166. package/dist/routes/route-register.js.map +1 -0
  167. package/dist/routes/router-socket.d.ts +10 -0
  168. package/dist/routes/router-socket.js +5 -0
  169. package/dist/routes/router-socket.js.map +1 -0
  170. package/dist/routes/router.d.ts +26 -0
  171. package/dist/routes/router.js +7 -0
  172. package/dist/routes/router.js.map +1 -0
  173. package/dist/routes/routes-index.d.ts +6 -0
  174. package/dist/routes/routes-index.js +7 -0
  175. package/dist/routes/routes-index.js.map +1 -0
  176. package/dist/runner/teardown.d.ts +2 -0
  177. package/dist/runner/teardown.js +94 -0
  178. package/dist/runner/teardown.js.map +1 -0
  179. package/dist/storage/BucketStorage.d.ts +307 -0
  180. package/dist/storage/BucketStorage.js +25 -0
  181. package/dist/storage/BucketStorage.js.map +1 -0
  182. package/dist/storage/ChecksumCache.d.ts +50 -0
  183. package/dist/storage/ChecksumCache.js +234 -0
  184. package/dist/storage/ChecksumCache.js.map +1 -0
  185. package/dist/storage/MongoBucketStorage.d.ts +52 -0
  186. package/dist/storage/MongoBucketStorage.js +409 -0
  187. package/dist/storage/MongoBucketStorage.js.map +1 -0
  188. package/dist/storage/SourceTable.d.ts +39 -0
  189. package/dist/storage/SourceTable.js +50 -0
  190. package/dist/storage/SourceTable.js.map +1 -0
  191. package/dist/storage/mongo/MongoBucketBatch.d.ts +48 -0
  192. package/dist/storage/mongo/MongoBucketBatch.js +581 -0
  193. package/dist/storage/mongo/MongoBucketBatch.js.map +1 -0
  194. package/dist/storage/mongo/MongoIdSequence.d.ts +12 -0
  195. package/dist/storage/mongo/MongoIdSequence.js +21 -0
  196. package/dist/storage/mongo/MongoIdSequence.js.map +1 -0
  197. package/dist/storage/mongo/MongoPersistedSyncRules.d.ts +9 -0
  198. package/dist/storage/mongo/MongoPersistedSyncRules.js +9 -0
  199. package/dist/storage/mongo/MongoPersistedSyncRules.js.map +1 -0
  200. package/dist/storage/mongo/MongoPersistedSyncRulesContent.d.ts +20 -0
  201. package/dist/storage/mongo/MongoPersistedSyncRulesContent.js +26 -0
  202. package/dist/storage/mongo/MongoPersistedSyncRulesContent.js.map +1 -0
  203. package/dist/storage/mongo/MongoSyncBucketStorage.d.ts +29 -0
  204. package/dist/storage/mongo/MongoSyncBucketStorage.js +391 -0
  205. package/dist/storage/mongo/MongoSyncBucketStorage.js.map +1 -0
  206. package/dist/storage/mongo/MongoSyncRulesLock.d.ts +16 -0
  207. package/dist/storage/mongo/MongoSyncRulesLock.js +65 -0
  208. package/dist/storage/mongo/MongoSyncRulesLock.js.map +1 -0
  209. package/dist/storage/mongo/OperationBatch.d.ts +26 -0
  210. package/dist/storage/mongo/OperationBatch.js +101 -0
  211. package/dist/storage/mongo/OperationBatch.js.map +1 -0
  212. package/dist/storage/mongo/PersistedBatch.d.ts +46 -0
  213. package/dist/storage/mongo/PersistedBatch.js +213 -0
  214. package/dist/storage/mongo/PersistedBatch.js.map +1 -0
  215. package/dist/storage/mongo/db.d.ts +26 -0
  216. package/dist/storage/mongo/db.js +35 -0
  217. package/dist/storage/mongo/db.js.map +1 -0
  218. package/dist/storage/mongo/models.d.ts +140 -0
  219. package/dist/storage/mongo/models.js +27 -0
  220. package/dist/storage/mongo/models.js.map +1 -0
  221. package/dist/storage/mongo/util.d.ts +26 -0
  222. package/dist/storage/mongo/util.js +81 -0
  223. package/dist/storage/mongo/util.js.map +1 -0
  224. package/dist/storage/storage-index.d.ts +14 -0
  225. package/dist/storage/storage-index.js +15 -0
  226. package/dist/storage/storage-index.js.map +1 -0
  227. package/dist/sync/BroadcastIterable.d.ts +38 -0
  228. package/dist/sync/BroadcastIterable.js +153 -0
  229. package/dist/sync/BroadcastIterable.js.map +1 -0
  230. package/dist/sync/LastValueSink.d.ts +25 -0
  231. package/dist/sync/LastValueSink.js +84 -0
  232. package/dist/sync/LastValueSink.js.map +1 -0
  233. package/dist/sync/merge.d.ts +39 -0
  234. package/dist/sync/merge.js +175 -0
  235. package/dist/sync/merge.js.map +1 -0
  236. package/dist/sync/safeRace.d.ts +1 -0
  237. package/dist/sync/safeRace.js +91 -0
  238. package/dist/sync/safeRace.js.map +1 -0
  239. package/dist/sync/sync-index.d.ts +6 -0
  240. package/dist/sync/sync-index.js +7 -0
  241. package/dist/sync/sync-index.js.map +1 -0
  242. package/dist/sync/sync.d.ts +18 -0
  243. package/dist/sync/sync.js +259 -0
  244. package/dist/sync/sync.js.map +1 -0
  245. package/dist/sync/util.d.ts +26 -0
  246. package/dist/sync/util.js +73 -0
  247. package/dist/sync/util.js.map +1 -0
  248. package/dist/system/CorePowerSyncSystem.d.ts +23 -0
  249. package/dist/system/CorePowerSyncSystem.js +52 -0
  250. package/dist/system/CorePowerSyncSystem.js.map +1 -0
  251. package/dist/system/system-index.d.ts +1 -0
  252. package/dist/system/system-index.js +2 -0
  253. package/dist/system/system-index.js.map +1 -0
  254. package/dist/util/Mutex.d.ts +47 -0
  255. package/dist/util/Mutex.js +132 -0
  256. package/dist/util/Mutex.js.map +1 -0
  257. package/dist/util/PgManager.d.ts +24 -0
  258. package/dist/util/PgManager.js +55 -0
  259. package/dist/util/PgManager.js.map +1 -0
  260. package/dist/util/alerting.d.ts +2 -0
  261. package/dist/util/alerting.js +8 -0
  262. package/dist/util/alerting.js.map +1 -0
  263. package/dist/util/config/collectors/config-collector.d.ts +29 -0
  264. package/dist/util/config/collectors/config-collector.js +116 -0
  265. package/dist/util/config/collectors/config-collector.js.map +1 -0
  266. package/dist/util/config/collectors/impl/base64-config-collector.d.ts +6 -0
  267. package/dist/util/config/collectors/impl/base64-config-collector.js +15 -0
  268. package/dist/util/config/collectors/impl/base64-config-collector.js.map +1 -0
  269. package/dist/util/config/collectors/impl/fallback-config-collector.d.ts +11 -0
  270. package/dist/util/config/collectors/impl/fallback-config-collector.js +19 -0
  271. package/dist/util/config/collectors/impl/fallback-config-collector.js.map +1 -0
  272. package/dist/util/config/collectors/impl/filesystem-config-collector.d.ts +6 -0
  273. package/dist/util/config/collectors/impl/filesystem-config-collector.js +37 -0
  274. package/dist/util/config/collectors/impl/filesystem-config-collector.js.map +1 -0
  275. package/dist/util/config/compound-config-collector.d.ts +32 -0
  276. package/dist/util/config/compound-config-collector.js +130 -0
  277. package/dist/util/config/compound-config-collector.js.map +1 -0
  278. package/dist/util/config/sync-rules/impl/base64-sync-rules-collector.d.ts +7 -0
  279. package/dist/util/config/sync-rules/impl/base64-sync-rules-collector.js +17 -0
  280. package/dist/util/config/sync-rules/impl/base64-sync-rules-collector.js.map +1 -0
  281. package/dist/util/config/sync-rules/impl/filesystem-sync-rules-collector.d.ts +7 -0
  282. package/dist/util/config/sync-rules/impl/filesystem-sync-rules-collector.js +21 -0
  283. package/dist/util/config/sync-rules/impl/filesystem-sync-rules-collector.js.map +1 -0
  284. package/dist/util/config/sync-rules/impl/inline-sync-rules-collector.d.ts +7 -0
  285. package/dist/util/config/sync-rules/impl/inline-sync-rules-collector.js +17 -0
  286. package/dist/util/config/sync-rules/impl/inline-sync-rules-collector.js.map +1 -0
  287. package/dist/util/config/sync-rules/sync-collector.d.ts +6 -0
  288. package/dist/util/config/sync-rules/sync-collector.js +3 -0
  289. package/dist/util/config/sync-rules/sync-collector.js.map +1 -0
  290. package/dist/util/config/types.d.ts +57 -0
  291. package/dist/util/config/types.js +7 -0
  292. package/dist/util/config/types.js.map +1 -0
  293. package/dist/util/config.d.ts +7 -0
  294. package/dist/util/config.js +35 -0
  295. package/dist/util/config.js.map +1 -0
  296. package/dist/util/env.d.ts +9 -0
  297. package/dist/util/env.js +26 -0
  298. package/dist/util/env.js.map +1 -0
  299. package/dist/util/memory-tracking.d.ts +7 -0
  300. package/dist/util/memory-tracking.js +58 -0
  301. package/dist/util/memory-tracking.js.map +1 -0
  302. package/dist/util/migration_lib.d.ts +11 -0
  303. package/dist/util/migration_lib.js +64 -0
  304. package/dist/util/migration_lib.js.map +1 -0
  305. package/dist/util/pgwire_utils.d.ts +24 -0
  306. package/dist/util/pgwire_utils.js +117 -0
  307. package/dist/util/pgwire_utils.js.map +1 -0
  308. package/dist/util/populate_test_data.d.ts +8 -0
  309. package/dist/util/populate_test_data.js +65 -0
  310. package/dist/util/populate_test_data.js.map +1 -0
  311. package/dist/util/protocol-types.d.ts +182 -0
  312. package/dist/util/protocol-types.js +42 -0
  313. package/dist/util/protocol-types.js.map +1 -0
  314. package/dist/util/secs.d.ts +2 -0
  315. package/dist/util/secs.js +49 -0
  316. package/dist/util/secs.js.map +1 -0
  317. package/dist/util/util-index.d.ts +22 -0
  318. package/dist/util/util-index.js +23 -0
  319. package/dist/util/util-index.js.map +1 -0
  320. package/dist/util/utils.d.ts +17 -0
  321. package/dist/util/utils.js +92 -0
  322. package/dist/util/utils.js.map +1 -0
  323. package/package.json +59 -0
  324. package/src/api/api-index.ts +2 -0
  325. package/src/api/diagnostics.ts +221 -0
  326. package/src/api/schema.ts +99 -0
  327. package/src/auth/CachedKeyCollector.ts +132 -0
  328. package/src/auth/CompoundKeyCollector.ts +33 -0
  329. package/src/auth/JwtPayload.ts +11 -0
  330. package/src/auth/KeyCollector.ts +27 -0
  331. package/src/auth/KeySpec.ts +67 -0
  332. package/src/auth/KeyStore.ts +156 -0
  333. package/src/auth/LeakyBucket.ts +66 -0
  334. package/src/auth/RemoteJWKSCollector.ts +130 -0
  335. package/src/auth/StaticKeyCollector.ts +21 -0
  336. package/src/auth/SupabaseKeyCollector.ts +67 -0
  337. package/src/auth/auth-index.ts +10 -0
  338. package/src/db/db-index.ts +1 -0
  339. package/src/db/mongo.ts +72 -0
  340. package/src/entry/cli-entry.ts +40 -0
  341. package/src/entry/commands/config-command.ts +36 -0
  342. package/src/entry/commands/migrate-action.ts +25 -0
  343. package/src/entry/commands/start-action.ts +24 -0
  344. package/src/entry/commands/teardown-action.ts +23 -0
  345. package/src/entry/entry-index.ts +5 -0
  346. package/src/index.ts +40 -0
  347. package/src/locks/LockManager.ts +16 -0
  348. package/src/locks/MongoLocks.ts +142 -0
  349. package/src/locks/locks-index.ts +2 -0
  350. package/src/metrics/Metrics.ts +265 -0
  351. package/src/migrations/db/migrations/1684951997326-init.ts +33 -0
  352. package/src/migrations/db/migrations/1688556755264-initial-sync-rules.ts +5 -0
  353. package/src/migrations/db/migrations/1702295701188-sync-rule-state.ts +99 -0
  354. package/src/migrations/db/migrations/1711543888062-write-checkpoint-index.ts +32 -0
  355. package/src/migrations/definitions.ts +21 -0
  356. package/src/migrations/executor.ts +87 -0
  357. package/src/migrations/migrations-index.ts +3 -0
  358. package/src/migrations/migrations.ts +118 -0
  359. package/src/migrations/store/migration-store.ts +63 -0
  360. package/src/replication/ErrorRateLimiter.ts +50 -0
  361. package/src/replication/PgRelation.ts +42 -0
  362. package/src/replication/WalConnection.ts +227 -0
  363. package/src/replication/WalStream.ts +624 -0
  364. package/src/replication/WalStreamManager.ts +213 -0
  365. package/src/replication/WalStreamRunner.ts +180 -0
  366. package/src/replication/replication-index.ts +7 -0
  367. package/src/replication/util.ts +76 -0
  368. package/src/routes/auth.ts +215 -0
  369. package/src/routes/endpoints/admin.ts +237 -0
  370. package/src/routes/endpoints/checkpointing.ts +41 -0
  371. package/src/routes/endpoints/dev.ts +199 -0
  372. package/src/routes/endpoints/route-endpoints-index.ts +6 -0
  373. package/src/routes/endpoints/socket-route.ts +135 -0
  374. package/src/routes/endpoints/sync-rules.ts +227 -0
  375. package/src/routes/endpoints/sync-stream.ts +101 -0
  376. package/src/routes/hooks.ts +46 -0
  377. package/src/routes/route-register.ts +104 -0
  378. package/src/routes/router-socket.ts +13 -0
  379. package/src/routes/router.ts +46 -0
  380. package/src/routes/routes-index.ts +6 -0
  381. package/src/runner/teardown.ts +108 -0
  382. package/src/storage/BucketStorage.ts +396 -0
  383. package/src/storage/ChecksumCache.ts +294 -0
  384. package/src/storage/MongoBucketStorage.ts +519 -0
  385. package/src/storage/SourceTable.ts +60 -0
  386. package/src/storage/mongo/MongoBucketBatch.ts +752 -0
  387. package/src/storage/mongo/MongoIdSequence.ts +24 -0
  388. package/src/storage/mongo/MongoPersistedSyncRules.ts +16 -0
  389. package/src/storage/mongo/MongoPersistedSyncRulesContent.ts +47 -0
  390. package/src/storage/mongo/MongoSyncBucketStorage.ts +533 -0
  391. package/src/storage/mongo/MongoSyncRulesLock.ts +81 -0
  392. package/src/storage/mongo/OperationBatch.ts +115 -0
  393. package/src/storage/mongo/PersistedBatch.ts +268 -0
  394. package/src/storage/mongo/db.ts +73 -0
  395. package/src/storage/mongo/models.ts +162 -0
  396. package/src/storage/mongo/util.ts +88 -0
  397. package/src/storage/storage-index.ts +15 -0
  398. package/src/sync/BroadcastIterable.ts +161 -0
  399. package/src/sync/LastValueSink.ts +100 -0
  400. package/src/sync/merge.ts +200 -0
  401. package/src/sync/safeRace.ts +99 -0
  402. package/src/sync/sync-index.ts +6 -0
  403. package/src/sync/sync.ts +319 -0
  404. package/src/sync/util.ts +98 -0
  405. package/src/system/CorePowerSyncSystem.ts +64 -0
  406. package/src/system/system-index.ts +1 -0
  407. package/src/util/Mutex.ts +159 -0
  408. package/src/util/PgManager.ts +64 -0
  409. package/src/util/alerting.ts +9 -0
  410. package/src/util/config/collectors/config-collector.ts +143 -0
  411. package/src/util/config/collectors/impl/base64-config-collector.ts +18 -0
  412. package/src/util/config/collectors/impl/fallback-config-collector.ts +22 -0
  413. package/src/util/config/collectors/impl/filesystem-config-collector.ts +43 -0
  414. package/src/util/config/compound-config-collector.ts +176 -0
  415. package/src/util/config/sync-rules/impl/base64-sync-rules-collector.ts +21 -0
  416. package/src/util/config/sync-rules/impl/filesystem-sync-rules-collector.ts +26 -0
  417. package/src/util/config/sync-rules/impl/inline-sync-rules-collector.ts +21 -0
  418. package/src/util/config/sync-rules/sync-collector.ts +8 -0
  419. package/src/util/config/types.ts +66 -0
  420. package/src/util/config.ts +39 -0
  421. package/src/util/env.ts +30 -0
  422. package/src/util/memory-tracking.ts +67 -0
  423. package/src/util/migration_lib.ts +79 -0
  424. package/src/util/pgwire_utils.ts +139 -0
  425. package/src/util/populate_test_data.ts +78 -0
  426. package/src/util/protocol-types.ts +228 -0
  427. package/src/util/secs.ts +54 -0
  428. package/src/util/util-index.ts +25 -0
  429. package/src/util/utils.ts +122 -0
  430. package/test/src/__snapshots__/pg_test.test.ts.snap +256 -0
  431. package/test/src/__snapshots__/sync.test.ts.snap +247 -0
  432. package/test/src/auth.test.ts +342 -0
  433. package/test/src/broadcast_iterable.test.ts +156 -0
  434. package/test/src/checksum_cache.test.ts +436 -0
  435. package/test/src/data_storage.test.ts +1176 -0
  436. package/test/src/env.ts +8 -0
  437. package/test/src/large_batch.test.ts +194 -0
  438. package/test/src/merge_iterable.test.ts +355 -0
  439. package/test/src/pg_test.test.ts +450 -0
  440. package/test/src/schema_changes.test.ts +545 -0
  441. package/test/src/setup.ts +7 -0
  442. package/test/src/slow_tests.test.ts +257 -0
  443. package/test/src/sql_functions.test.ts +254 -0
  444. package/test/src/sql_operators.test.ts +132 -0
  445. package/test/src/sync.test.ts +293 -0
  446. package/test/src/sync_rules.test.ts +1053 -0
  447. package/test/src/util.ts +76 -0
  448. package/test/src/validation.test.ts +63 -0
  449. package/test/src/wal_stream.test.ts +319 -0
  450. package/test/src/wal_stream_utils.ts +147 -0
  451. package/test/tsconfig.json +20 -0
  452. package/tsconfig.json +31 -0
  453. package/tsconfig.tsbuildinfo +1 -0
  454. package/vitest.config.ts +9 -0
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Manages op_id or similar sequence in memory.
3
+ *
4
+ * This is typically used within a transaction, with the last value persisted
5
+ * at the end of the transaction.
6
+ */
7
+ export class MongoIdSequence {
8
+ private _last: bigint;
9
+
10
+ constructor(last: bigint) {
11
+ if (typeof last != 'bigint') {
12
+ throw new Error(`BigInt required, got ${last} ${typeof last}`);
13
+ }
14
+ this._last = last;
15
+ }
16
+
17
+ next() {
18
+ return ++this._last;
19
+ }
20
+
21
+ last() {
22
+ return this._last;
23
+ }
24
+ }
@@ -0,0 +1,16 @@
1
+ import { SqlSyncRules } from '@powersync/service-sync-rules';
2
+
3
+ import { PersistedSyncRules } from '../BucketStorage.js';
4
+
5
+ export class MongoPersistedSyncRules implements PersistedSyncRules {
6
+ public readonly slot_name: string;
7
+
8
+ constructor(
9
+ public readonly id: number,
10
+ public readonly sync_rules: SqlSyncRules,
11
+ public readonly checkpoint_lsn: string | null,
12
+ slot_name: string | null
13
+ ) {
14
+ this.slot_name = slot_name ?? `powersync_${id}`;
15
+ }
16
+ }
@@ -0,0 +1,47 @@
1
+ import { SqlSyncRules } from '@powersync/service-sync-rules';
2
+ import * as mongo from 'mongodb';
3
+
4
+ import { PersistedSyncRulesContent } from '../BucketStorage.js';
5
+ import { MongoPersistedSyncRules } from './MongoPersistedSyncRules.js';
6
+ import { MongoSyncRulesLock } from './MongoSyncRulesLock.js';
7
+ import { PowerSyncMongo } from './db.js';
8
+ import { SyncRuleDocument } from './models.js';
9
+
10
+ export class MongoPersistedSyncRulesContent implements PersistedSyncRulesContent {
11
+ public readonly slot_name: string;
12
+
13
+ public readonly id: number;
14
+ public readonly sync_rules_content: string;
15
+ public readonly last_checkpoint_lsn: string | null;
16
+ public readonly last_fatal_error: string | null;
17
+ public readonly last_keepalive_ts: Date | null;
18
+ public readonly last_checkpoint_ts: Date | null;
19
+
20
+ public current_lock: MongoSyncRulesLock | null = null;
21
+
22
+ constructor(private db: PowerSyncMongo, doc: mongo.WithId<SyncRuleDocument>) {
23
+ this.id = doc._id;
24
+ this.sync_rules_content = doc.content;
25
+ this.last_checkpoint_lsn = doc.last_checkpoint_lsn;
26
+ // Handle legacy values
27
+ this.slot_name = doc.slot_name ?? `powersync_${this.id}`;
28
+ this.last_fatal_error = doc.last_fatal_error;
29
+ this.last_checkpoint_ts = doc.last_checkpoint_ts;
30
+ this.last_keepalive_ts = doc.last_keepalive_ts;
31
+ }
32
+
33
+ parsed() {
34
+ return new MongoPersistedSyncRules(
35
+ this.id,
36
+ SqlSyncRules.fromYaml(this.sync_rules_content),
37
+ this.last_checkpoint_lsn,
38
+ this.slot_name
39
+ );
40
+ }
41
+
42
+ async lock() {
43
+ const lock = await MongoSyncRulesLock.createLock(this.db, this);
44
+ this.current_lock = lock;
45
+ return lock;
46
+ }
47
+ }
@@ -0,0 +1,533 @@
1
+ import { SqliteJsonRow, SqliteJsonValue, SqlSyncRules } from '@powersync/service-sync-rules';
2
+ import * as bson from 'bson';
3
+ import * as mongo from 'mongodb';
4
+
5
+ import * as db from '../../db/db-index.js';
6
+ import * as replication from '../../replication/WalStream.js';
7
+ import * as util from '../../util/util-index.js';
8
+ import {
9
+ BucketDataBatchOptions,
10
+ BucketStorageBatch,
11
+ DEFAULT_DOCUMENT_BATCH_LIMIT,
12
+ DEFAULT_DOCUMENT_CHUNK_LIMIT_BYTES,
13
+ FlushedResult,
14
+ ResolveTableOptions,
15
+ ResolveTableResult,
16
+ SyncRulesBucketStorage,
17
+ SyncRuleStatus
18
+ } from '../BucketStorage.js';
19
+ import { MongoBucketStorage } from '../MongoBucketStorage.js';
20
+ import { SourceTable } from '../SourceTable.js';
21
+ import { PowerSyncMongo } from './db.js';
22
+ import { BucketDataDocument, BucketDataKey, SourceKey, SyncRuleState } from './models.js';
23
+ import { MongoBucketBatch } from './MongoBucketBatch.js';
24
+ import { BSON_DESERIALIZE_OPTIONS, idPrefixFilter, readSingleBatch, serializeLookup } from './util.js';
25
+ import { ChecksumCache, FetchPartialBucketChecksum } from '../ChecksumCache.js';
26
+
27
+ export class MongoSyncBucketStorage implements SyncRulesBucketStorage {
28
+ private readonly db: PowerSyncMongo;
29
+ private checksumCache = new ChecksumCache({
30
+ fetchChecksums: (batch) => {
31
+ return this.getChecksumsInternal(batch);
32
+ }
33
+ });
34
+
35
+ constructor(
36
+ public readonly factory: MongoBucketStorage,
37
+ public readonly group_id: number,
38
+ public readonly sync_rules: SqlSyncRules,
39
+ public readonly slot_name: string
40
+ ) {
41
+ this.db = factory.db;
42
+ }
43
+
44
+ async getCheckpoint() {
45
+ const doc = await this.db.sync_rules.findOne(
46
+ { _id: this.group_id },
47
+ {
48
+ projection: { last_checkpoint: 1, last_checkpoint_lsn: 1 }
49
+ }
50
+ );
51
+ return {
52
+ checkpoint: util.timestampToOpId(doc?.last_checkpoint ?? 0n),
53
+ lsn: doc?.last_checkpoint_lsn ?? replication.ZERO_LSN
54
+ };
55
+ }
56
+
57
+ async startBatch(options: {}, callback: (batch: BucketStorageBatch) => Promise<void>): Promise<FlushedResult | null> {
58
+ const doc = await this.db.sync_rules.findOne(
59
+ {
60
+ _id: this.group_id
61
+ },
62
+ { projection: { last_checkpoint_lsn: 1, no_checkpoint_before: 1 } }
63
+ );
64
+ const checkpoint_lsn = doc?.last_checkpoint_lsn ?? null;
65
+
66
+ const batch = new MongoBucketBatch(
67
+ this.db,
68
+ this.sync_rules,
69
+ this.group_id,
70
+ this.slot_name,
71
+ checkpoint_lsn,
72
+ doc?.no_checkpoint_before ?? null
73
+ );
74
+ try {
75
+ await callback(batch);
76
+ await batch.flush();
77
+ await batch.abort();
78
+ if (batch.last_flushed_op) {
79
+ return { flushed_op: String(batch.last_flushed_op) };
80
+ } else {
81
+ return null;
82
+ }
83
+ } catch (e) {
84
+ await batch.abort();
85
+ throw e;
86
+ }
87
+ }
88
+
89
+ async resolveTable(options: ResolveTableOptions): Promise<ResolveTableResult> {
90
+ const { group_id, connection_id, connection_tag, relation } = options;
91
+
92
+ const { schema, name: table, relationId, replicationColumns } = relation;
93
+
94
+ const columns = replicationColumns.map((column) => ({ name: column.name, type_oid: column.typeOid }));
95
+ let result: ResolveTableResult | null = null;
96
+ await this.db.client.withSession(async (session) => {
97
+ const col = this.db.source_tables;
98
+ let doc = await col.findOne(
99
+ {
100
+ group_id: group_id,
101
+ connection_id: connection_id,
102
+ relation_id: relationId,
103
+ schema_name: schema,
104
+ table_name: table,
105
+ replica_id_columns2: columns
106
+ },
107
+ { session }
108
+ );
109
+ if (doc == null) {
110
+ doc = {
111
+ _id: new bson.ObjectId(),
112
+ group_id: group_id,
113
+ connection_id: connection_id,
114
+ relation_id: relationId,
115
+ schema_name: schema,
116
+ table_name: table,
117
+ replica_id_columns: null,
118
+ replica_id_columns2: columns,
119
+ snapshot_done: false
120
+ };
121
+
122
+ await col.insertOne(doc, { session });
123
+ }
124
+ const sourceTable = new SourceTable(
125
+ doc._id,
126
+ connection_tag,
127
+ relationId,
128
+ schema,
129
+ table,
130
+ replicationColumns,
131
+ doc.snapshot_done ?? true
132
+ );
133
+ sourceTable.syncData = options.sync_rules.tableSyncsData(sourceTable);
134
+ sourceTable.syncParameters = options.sync_rules.tableSyncsParameters(sourceTable);
135
+
136
+ const truncate = await col
137
+ .find(
138
+ {
139
+ group_id: group_id,
140
+ connection_id: connection_id,
141
+ _id: { $ne: doc._id },
142
+ $or: [{ relation_id: relationId }, { schema_name: schema, table_name: table }]
143
+ },
144
+ { session }
145
+ )
146
+ .toArray();
147
+ result = {
148
+ table: sourceTable,
149
+ dropTables: truncate.map(
150
+ (doc) =>
151
+ new SourceTable(
152
+ doc._id,
153
+ connection_tag,
154
+ doc.relation_id ?? 0,
155
+ doc.schema_name,
156
+ doc.table_name,
157
+ doc.replica_id_columns2?.map((c) => ({ name: c.name, typeOid: c.type_oid })) ?? [],
158
+ doc.snapshot_done ?? true
159
+ )
160
+ )
161
+ };
162
+ });
163
+ return result!;
164
+ }
165
+
166
+ async getParameterSets(checkpoint: util.OpId, lookups: SqliteJsonValue[][]): Promise<SqliteJsonRow[]> {
167
+ const lookupFilter = lookups.map((lookup) => {
168
+ return serializeLookup(lookup);
169
+ });
170
+ const rows = await this.db.bucket_parameters
171
+ .aggregate([
172
+ {
173
+ $match: {
174
+ 'key.g': this.group_id,
175
+ lookup: { $in: lookupFilter },
176
+ _id: { $lte: BigInt(checkpoint) }
177
+ }
178
+ },
179
+ {
180
+ $sort: {
181
+ _id: -1
182
+ }
183
+ },
184
+ {
185
+ $group: {
186
+ _id: '$key',
187
+ bucket_parameters: {
188
+ $first: '$bucket_parameters'
189
+ }
190
+ }
191
+ }
192
+ ])
193
+ .toArray();
194
+ const groupedParameters = rows.map((row) => {
195
+ return row.bucket_parameters;
196
+ });
197
+ return groupedParameters.flat();
198
+ }
199
+
200
+ async *getBucketDataBatch(
201
+ checkpoint: util.OpId,
202
+ dataBuckets: Map<string, string>,
203
+ options?: BucketDataBatchOptions
204
+ ): AsyncIterable<util.SyncBucketData> {
205
+ if (dataBuckets.size == 0) {
206
+ return;
207
+ }
208
+ let filters: mongo.Filter<BucketDataDocument>[] = [];
209
+
210
+ const end = checkpoint ? BigInt(checkpoint) : new bson.MaxKey();
211
+ for (let [name, start] of dataBuckets.entries()) {
212
+ filters.push({
213
+ _id: {
214
+ $gt: {
215
+ g: this.group_id,
216
+ b: name,
217
+ o: BigInt(start)
218
+ },
219
+ $lte: {
220
+ g: this.group_id,
221
+ b: name,
222
+ o: end as any
223
+ }
224
+ }
225
+ });
226
+ }
227
+
228
+ const limit = options?.limit ?? DEFAULT_DOCUMENT_BATCH_LIMIT;
229
+ const sizeLimit = options?.chunkLimitBytes ?? DEFAULT_DOCUMENT_CHUNK_LIMIT_BYTES;
230
+
231
+ const cursor = this.db.bucket_data.find(
232
+ {
233
+ $or: filters
234
+ },
235
+ {
236
+ session: undefined,
237
+ sort: { _id: 1 },
238
+ limit: limit,
239
+ // Increase batch size above the default 101, so that we can fill an entire batch in
240
+ // one go.
241
+ batchSize: limit,
242
+ // Raw mode is returns an array of Buffer instead of parsed documents.
243
+ // We use it so that:
244
+ // 1. We can calculate the document size accurately without serializing again.
245
+ // 2. We can delay parsing the results until it's needed.
246
+ // We manually use bson.deserialize below
247
+ raw: true,
248
+
249
+ // Since we're using raw: true and parsing ourselves later, we don't need bigint
250
+ // support here.
251
+ // Disabling due to https://jira.mongodb.org/browse/NODE-6165, and the fact that this
252
+ // is one of our most common queries.
253
+ useBigInt64: false
254
+ }
255
+ ) as unknown as mongo.FindCursor<Buffer>;
256
+
257
+ // We want to limit results to a single batch to avoid high memory usage.
258
+ // This approach uses MongoDB's batch limits to limit the data here, which limits
259
+ // to the lower of the batch count and size limits.
260
+ // This is similar to using `singleBatch: true` in the find options, but allows
261
+ // detecting "hasMore".
262
+ let { data, hasMore } = await readSingleBatch(cursor);
263
+ if (data.length == limit) {
264
+ // Limit reached - could have more data, despite the cursor being drained.
265
+ hasMore = true;
266
+ }
267
+
268
+ let batchSize = 0;
269
+ let currentBatch: util.SyncBucketData | null = null;
270
+
271
+ // Ordered by _id, meaning buckets are grouped together
272
+ for (let rawData of data) {
273
+ const row = bson.deserialize(rawData, BSON_DESERIALIZE_OPTIONS) as BucketDataDocument;
274
+ const bucket = row._id.b;
275
+
276
+ if (currentBatch == null || currentBatch.bucket != bucket || batchSize >= sizeLimit) {
277
+ let start: string | undefined = undefined;
278
+ if (currentBatch != null) {
279
+ if (currentBatch.bucket == bucket) {
280
+ currentBatch.has_more = true;
281
+ }
282
+
283
+ const yieldBatch = currentBatch;
284
+ start = currentBatch.after;
285
+ currentBatch = null;
286
+ batchSize = 0;
287
+ yield yieldBatch;
288
+ }
289
+
290
+ start ??= dataBuckets.get(bucket);
291
+ if (start == null) {
292
+ throw new Error(`data for unexpected bucket: ${bucket}`);
293
+ }
294
+ currentBatch = {
295
+ bucket,
296
+ after: start,
297
+ has_more: hasMore,
298
+ data: [],
299
+ next_after: start
300
+ };
301
+ }
302
+
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
+ currentBatch.data.push(entry);
313
+ currentBatch.next_after = entry.op_id;
314
+
315
+ batchSize += rawData.byteLength;
316
+ }
317
+
318
+ if (currentBatch != null) {
319
+ const yieldBatch = currentBatch;
320
+ currentBatch = null;
321
+ yield yieldBatch;
322
+ }
323
+ }
324
+
325
+ async getChecksums(checkpoint: util.OpId, buckets: string[]): Promise<util.ChecksumMap> {
326
+ return this.checksumCache.getChecksumMap(checkpoint, buckets);
327
+ }
328
+
329
+ private async getChecksumsInternal(batch: FetchPartialBucketChecksum[]): Promise<util.ChecksumMap> {
330
+ if (batch.length == 0) {
331
+ return new Map();
332
+ }
333
+
334
+ const filters: any[] = [];
335
+ for (let request of batch) {
336
+ filters.push({
337
+ _id: {
338
+ $gt: {
339
+ g: this.group_id,
340
+ b: request.bucket,
341
+ o: request.start ? BigInt(request.start) : new bson.MinKey()
342
+ },
343
+ $lte: {
344
+ g: this.group_id,
345
+ b: request.bucket,
346
+ o: BigInt(request.end)
347
+ }
348
+ }
349
+ });
350
+ }
351
+
352
+ const aggregate = await this.db.bucket_data
353
+ .aggregate(
354
+ [
355
+ {
356
+ $match: {
357
+ $or: filters
358
+ }
359
+ },
360
+ {
361
+ $group: { _id: '$_id.b', checksum_total: { $sum: '$checksum' }, count: { $sum: 1 } }
362
+ }
363
+ ],
364
+ { session: undefined }
365
+ )
366
+ .toArray();
367
+
368
+ return new Map<string, util.BucketChecksum>(
369
+ aggregate.map((doc) => {
370
+ return [
371
+ doc._id,
372
+ {
373
+ bucket: doc._id,
374
+ count: doc.count,
375
+ checksum: Number(BigInt(doc.checksum_total) & 0xffffffffn) & 0xffffffff
376
+ } satisfies util.BucketChecksum
377
+ ];
378
+ })
379
+ );
380
+ }
381
+
382
+ async terminate() {
383
+ await this.clear();
384
+
385
+ await this.db.sync_rules.updateOne(
386
+ {
387
+ _id: this.group_id
388
+ },
389
+ {
390
+ $set: {
391
+ state: SyncRuleState.TERMINATED,
392
+ persisted_lsn: null,
393
+ snapshot_done: false
394
+ }
395
+ }
396
+ );
397
+ }
398
+
399
+ async getStatus(): Promise<SyncRuleStatus> {
400
+ const doc = await this.db.sync_rules.findOne(
401
+ {
402
+ _id: this.group_id
403
+ },
404
+ {
405
+ projection: {
406
+ snapshot_done: 1,
407
+ last_checkpoint_lsn: 1,
408
+ state: 1
409
+ }
410
+ }
411
+ );
412
+ if (doc == null) {
413
+ throw new Error('Cannot find sync rules status');
414
+ }
415
+
416
+ return {
417
+ snapshot_done: doc.snapshot_done,
418
+ active: doc.state == 'ACTIVE',
419
+ checkpoint_lsn: doc.last_checkpoint_lsn
420
+ };
421
+ }
422
+
423
+ async clear(): Promise<void> {
424
+ // Individual operations here may time out with the maxTimeMS option.
425
+ // It is expected to still make progress, and continue on the next try.
426
+
427
+ // TODO: Transactional?
428
+ await this.db.sync_rules.updateOne(
429
+ {
430
+ _id: this.group_id
431
+ },
432
+ {
433
+ $set: {
434
+ snapshot_done: false,
435
+ persisted_lsn: null,
436
+ last_checkpoint_lsn: null,
437
+ last_checkpoint: null,
438
+ no_checkpoint_before: null
439
+ }
440
+ },
441
+ { maxTimeMS: db.mongo.MONGO_OPERATION_TIMEOUT_MS }
442
+ );
443
+ await this.db.bucket_data.deleteMany(
444
+ {
445
+ _id: idPrefixFilter<BucketDataKey>({ g: this.group_id }, ['b', 'o'])
446
+ },
447
+ { maxTimeMS: db.mongo.MONGO_OPERATION_TIMEOUT_MS }
448
+ );
449
+ await this.db.bucket_parameters.deleteMany(
450
+ {
451
+ key: idPrefixFilter<SourceKey>({ g: this.group_id }, ['t', 'k'])
452
+ },
453
+ { maxTimeMS: db.mongo.MONGO_OPERATION_TIMEOUT_MS }
454
+ );
455
+
456
+ await this.db.current_data.deleteMany(
457
+ {
458
+ _id: idPrefixFilter<SourceKey>({ g: this.group_id }, ['t', 'k'])
459
+ },
460
+ { maxTimeMS: db.mongo.MONGO_OPERATION_TIMEOUT_MS }
461
+ );
462
+
463
+ await this.db.source_tables.deleteMany(
464
+ {
465
+ group_id: this.group_id
466
+ },
467
+ { maxTimeMS: db.mongo.MONGO_OPERATION_TIMEOUT_MS }
468
+ );
469
+ }
470
+
471
+ async setSnapshotDone(lsn: string): Promise<void> {
472
+ await this.db.sync_rules.updateOne(
473
+ {
474
+ _id: this.group_id
475
+ },
476
+ {
477
+ $set: {
478
+ snapshot_done: true,
479
+ persisted_lsn: lsn,
480
+ last_checkpoint_ts: new Date()
481
+ }
482
+ }
483
+ );
484
+ }
485
+
486
+ async autoActivate(): Promise<void> {
487
+ await this.db.client.withSession(async (session) => {
488
+ await session.withTransaction(async () => {
489
+ const doc = await this.db.sync_rules.findOne({ _id: this.group_id }, { session });
490
+ if (doc && doc.state == 'PROCESSING') {
491
+ await this.db.sync_rules.updateOne(
492
+ {
493
+ _id: this.group_id
494
+ },
495
+ {
496
+ $set: {
497
+ state: SyncRuleState.ACTIVE
498
+ }
499
+ },
500
+ { session }
501
+ );
502
+
503
+ await this.db.sync_rules.updateMany(
504
+ {
505
+ _id: { $ne: this.group_id },
506
+ state: SyncRuleState.ACTIVE
507
+ },
508
+ {
509
+ $set: {
510
+ state: SyncRuleState.STOP
511
+ }
512
+ },
513
+ { session }
514
+ );
515
+ }
516
+ });
517
+ });
518
+ }
519
+
520
+ async reportError(e: any): Promise<void> {
521
+ const message = String(e.message ?? 'Replication failure');
522
+ await this.db.sync_rules.updateOne(
523
+ {
524
+ _id: this.group_id
525
+ },
526
+ {
527
+ $set: {
528
+ last_fatal_error: message
529
+ }
530
+ }
531
+ );
532
+ }
533
+ }
@@ -0,0 +1,81 @@
1
+ import crypto from 'crypto';
2
+
3
+ import { PersistedSyncRulesContent, ReplicationLock } from '../BucketStorage.js';
4
+ import { PowerSyncMongo } from './db.js';
5
+ import { logger } from '@powersync/lib-services-framework';
6
+
7
+ /**
8
+ * Manages a lock on a sync rules document, so that only one process
9
+ * replicates those sync rules at a time.
10
+ */
11
+ export class MongoSyncRulesLock implements ReplicationLock {
12
+ private readonly refreshInterval: NodeJS.Timer;
13
+
14
+ static async createLock(db: PowerSyncMongo, sync_rules: PersistedSyncRulesContent): Promise<MongoSyncRulesLock> {
15
+ const lockId = crypto.randomBytes(8).toString('hex');
16
+ const doc = await db.sync_rules.findOneAndUpdate(
17
+ { _id: sync_rules.id, $or: [{ lock: null }, { 'lock.expires_at': { $lt: new Date() } }] },
18
+ {
19
+ $set: {
20
+ lock: {
21
+ id: lockId,
22
+ expires_at: new Date(Date.now() + 60 * 1000)
23
+ }
24
+ }
25
+ },
26
+ {
27
+ projection: { lock: 1 },
28
+ returnDocument: 'before'
29
+ }
30
+ );
31
+
32
+ if (doc == null) {
33
+ throw new Error(`Replication slot ${sync_rules.slot_name} is locked by another process`);
34
+ }
35
+ return new MongoSyncRulesLock(db, sync_rules.id, lockId);
36
+ }
37
+
38
+ constructor(private db: PowerSyncMongo, public sync_rules_id: number, private lock_id: string) {
39
+ this.refreshInterval = setInterval(async () => {
40
+ try {
41
+ await this.refresh();
42
+ } catch (e) {
43
+ logger.error('Failed to refresh lock', e);
44
+ clearInterval(this.refreshInterval);
45
+ }
46
+ }, 30_130);
47
+ }
48
+
49
+ async release(): Promise<void> {
50
+ clearInterval(this.refreshInterval);
51
+ const result = await this.db.sync_rules.updateOne(
52
+ {
53
+ _id: this.sync_rules_id,
54
+ 'lock.id': this.lock_id
55
+ },
56
+ {
57
+ $unset: { lock: 1 }
58
+ }
59
+ );
60
+ if (result.modifiedCount == 0) {
61
+ // Log and ignore
62
+ logger.warn(`Lock already released: ${this.sync_rules_id}/${this.lock_id}`);
63
+ }
64
+ }
65
+
66
+ private async refresh(): Promise<void> {
67
+ const result = await this.db.sync_rules.findOneAndUpdate(
68
+ {
69
+ _id: this.sync_rules_id,
70
+ 'lock.id': this.lock_id
71
+ },
72
+ {
73
+ $set: { 'lock.expires_at': new Date(Date.now() + 60 * 1000) }
74
+ },
75
+ { returnDocument: 'after' }
76
+ );
77
+ if (result == null) {
78
+ throw new Error(`Lock not held anymore: ${this.sync_rules_id}/${this.lock_id}`);
79
+ }
80
+ }
81
+ }