@powersync/service-core 0.0.2

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