@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,156 @@
1
+ import * as jose from 'jose';
2
+ import secs from '../util/secs.js';
3
+ import { KeyOptions, KeySpec, SUPPORTED_ALGORITHMS } from './KeySpec.js';
4
+ import { KeyCollector } from './KeyCollector.js';
5
+ import { JwtPayload } from './JwtPayload.js';
6
+ import { logger } from '@powersync/lib-services-framework';
7
+
8
+ /**
9
+ * KeyStore to get keys and verify tokens.
10
+ *
11
+ *
12
+ * Similar to micro_auth's KeyStore, but with different caching and error handling.
13
+ *
14
+ * We generally assume that:
15
+ * 1. If we have a key kid matching a JWT kid, that is the correct key.
16
+ * We don't look for other keys, even if there are algorithm or other issues.
17
+ * 2. Otherwise, iterate through "wildcard" keys and look for a matching signature.
18
+ * Wildcard keys are any key defined without a kid.
19
+ *
20
+ * # Security considerations
21
+ *
22
+ * Some places for security holes:
23
+ * 1. Signature verification not done correctly: We rely on jose.jwtVerify() to do this correctly.
24
+ * 2. Using a key that has been revoked - see CachedKeyCollector's refresh strategy.
25
+ * 3. Using a key for the wrong purpose (e.g. key.use != 'sig'). Checked in RemoteJWKSCollector.
26
+ * 4. Not checking all attributes, e.g. a JWT trusted by the global firebase key, but has the wrong aud. Correct aud must be configured.
27
+ * 5. Using the incorrect algorithm, e.g. 'none', or using public key as a shared key.
28
+ * We check the algorithm for each JWT against the matching key's configured algorithm or algorithm family.
29
+ *
30
+ * # Errors
31
+ *
32
+ * If we have a matching kid, we can generally get a detailed error (e.g. signature verification failed, invalid algorithm, etc).
33
+ * If we don't have a matching kid, we'll generally just get an error "Could not find an appropriate key...".
34
+ */
35
+ export class KeyStore {
36
+ private collector: KeyCollector;
37
+
38
+ constructor(collector: KeyCollector) {
39
+ this.collector = collector;
40
+ }
41
+
42
+ async verifyJwt(token: string, options: { defaultAudiences: string[]; maxAge: string }): Promise<JwtPayload> {
43
+ const { result, keyOptions } = await this.verifyInternal(token, {
44
+ // audience is not checked here, since we vary the allowed audience based on the key
45
+ // audience: options.defaultAudiences,
46
+ clockTolerance: 60,
47
+ // More specific algorithm checking is done when selecting the key to use.
48
+ algorithms: SUPPORTED_ALGORITHMS,
49
+ requiredClaims: ['aud', 'sub', 'iat', 'exp']
50
+ });
51
+
52
+ let audiences = options.defaultAudiences;
53
+ if (keyOptions.requiresAudience) {
54
+ // Replace the audience, don't add
55
+ audiences = keyOptions.requiresAudience;
56
+ }
57
+
58
+ const tokenPayload = result.payload;
59
+
60
+ let aud = tokenPayload.aud!;
61
+ if (!Array.isArray(aud)) {
62
+ aud = [aud];
63
+ }
64
+ if (
65
+ !aud.some((a) => {
66
+ return audiences.includes(a);
67
+ })
68
+ ) {
69
+ throw new jose.errors.JWTClaimValidationFailed('unexpected "aud" claim value', 'aud', 'check_failed');
70
+ }
71
+
72
+ const tokenDuration = tokenPayload.exp! - tokenPayload.iat!;
73
+
74
+ // Implement our own maxAge validation, that rejects the token immediately if expiration
75
+ // is too far into the future.
76
+ const maxAge = keyOptions.maxLifetimeSeconds ?? secs(options.maxAge);
77
+ if (tokenDuration > maxAge) {
78
+ throw new jose.errors.JWTInvalid(`Token must expire in a maximum of ${maxAge} seconds, got ${tokenDuration}`);
79
+ }
80
+
81
+ const parameters = tokenPayload.parameters;
82
+ if (parameters != null && (Array.isArray(parameters) || typeof parameters != 'object')) {
83
+ throw new jose.errors.JWTInvalid('parameters must be an object');
84
+ }
85
+
86
+ return {
87
+ ...(tokenPayload as any),
88
+ parameters: {
89
+ user_id: tokenPayload.sub,
90
+ ...parameters
91
+ }
92
+ };
93
+ }
94
+
95
+ private async verifyInternal(token: string, options: jose.JWTVerifyOptions) {
96
+ let keyOptions: KeyOptions | undefined = undefined;
97
+ const result = await jose.jwtVerify(
98
+ token,
99
+ async (header) => {
100
+ let key = await this.getCachedKey(token, header);
101
+ keyOptions = key.options;
102
+ return key.key;
103
+ },
104
+ options
105
+ );
106
+ return { result, keyOptions: keyOptions! };
107
+ }
108
+
109
+ private async getCachedKey(token: string, header: jose.JWTHeaderParameters): Promise<KeySpec> {
110
+ const kid = header.kid;
111
+ const { keys, errors } = await this.collector.getKeys();
112
+ if (kid) {
113
+ // key has kid: JWK with exact kid, or JWK without kid
114
+ // key without kid: JWK without kid only
115
+ for (let key of keys) {
116
+ if (key.kid == kid) {
117
+ if (!key.matchesAlgorithm(header.alg)) {
118
+ throw new jose.errors.JOSEAlgNotAllowed(`Unexpected token algorithm ${header.alg}`);
119
+ }
120
+ return key;
121
+ }
122
+ }
123
+ }
124
+
125
+ for (let key of keys) {
126
+ // Checks signature and algorithm
127
+ if (key.kid != null) {
128
+ // Not a wildcard key
129
+ continue;
130
+ }
131
+ if (!key.matchesAlgorithm(header.alg)) {
132
+ continue;
133
+ }
134
+
135
+ if (await key.isValidSignature(token)) {
136
+ return key;
137
+ }
138
+ }
139
+
140
+ if (errors.length > 0) {
141
+ throw errors[0];
142
+ } else {
143
+ // No key found
144
+ // Trigger refresh of the keys - might be ready by the next request.
145
+ this.collector.noKeyFound?.().catch((e) => {
146
+ // Typically this error would be stored on the collector.
147
+ // This is just a last resort error handling.
148
+ logger.error(`Failed to refresh keys`, e);
149
+ });
150
+
151
+ throw new jose.errors.JOSEError(
152
+ 'Could not find an appropriate key in the keystore. The key is missing or no key matched the token KID'
153
+ );
154
+ }
155
+ }
156
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Variation on a leaky bucket rate limiter.
3
+ *
4
+ * The base is a leaky bucket:
5
+ * * The bucket is filled at a certain rate
6
+ * * The bucket has a max capacity
7
+ *
8
+ * This gives an initial burst capacity, then continues
9
+ * at the refill rate when the burst capacity is depleted.
10
+ *
11
+ * This variation introduces an additional quadratic delay
12
+ * during the initial burst, which prevents the burst capacity
13
+ * from being consumed immediately. The steady-state rate
14
+ * is still the same.
15
+ *
16
+ *
17
+ * Steady state rate: x requests / ms.
18
+ * Steady state period: 1 / rate
19
+ *
20
+ * capacity: number of requests before steady state
21
+ *
22
+ * capacity == max_capacity: no delay between requests
23
+ * capacity = 0: delay = Steady state period
24
+ *
25
+ * variable_delay = (max_capacity - capacity)^2 / max_capacity^2 * period
26
+ */
27
+ export class LeakyBucket {
28
+ private capacity: number;
29
+ private lastRequest: number;
30
+ private maxCapacity: number;
31
+ private periodMs: number;
32
+
33
+ public lastGrantedRequest: number;
34
+
35
+ constructor(options: { maxCapacity: number; periodMs: number }) {
36
+ this.capacity = options.maxCapacity;
37
+ this.maxCapacity = options.maxCapacity;
38
+ this.periodMs = options.periodMs;
39
+ this.lastRequest = 0;
40
+ this.lastGrantedRequest = 0;
41
+ }
42
+
43
+ allowed(): boolean {
44
+ const now = Date.now();
45
+ const elapsed = now - this.lastRequest;
46
+ const leaked = elapsed / this.periodMs;
47
+ this.capacity = Math.min(this.capacity + leaked, this.maxCapacity);
48
+ this.lastRequest = now;
49
+
50
+ const capacityUsed = this.maxCapacity - this.capacity;
51
+ const variableDelay = ((capacityUsed * capacityUsed) / (this.maxCapacity * this.maxCapacity)) * this.periodMs;
52
+
53
+ if (this.capacity >= 1 && variableDelay <= now - this.lastGrantedRequest) {
54
+ this.capacity -= 1;
55
+ this.lastGrantedRequest = now;
56
+ return true;
57
+ } else {
58
+ return false;
59
+ }
60
+ }
61
+
62
+ reset() {
63
+ this.capacity = this.maxCapacity;
64
+ this.lastGrantedRequest = 0;
65
+ }
66
+ }
@@ -0,0 +1,130 @@
1
+ import * as https from 'https';
2
+ import * as http from 'http';
3
+ import * as dns from 'dns/promises';
4
+ import ip from 'ipaddr.js';
5
+ import * as jose from 'jose';
6
+ import * as net from 'net';
7
+ import fetch from 'node-fetch';
8
+
9
+ import { KeySpec } from './KeySpec.js';
10
+ import { KeyCollector, KeyResult } from './KeyCollector.js';
11
+
12
+ export type RemoteJWKSCollectorOptions = {
13
+ /**
14
+ * Blocks IP Ranges from the BLOCKED_IP_RANGES array
15
+ */
16
+ block_local_ip?: boolean;
17
+ };
18
+
19
+ /**
20
+ * Set of keys fetched from JWKS URI.
21
+ */
22
+ export class RemoteJWKSCollector implements KeyCollector {
23
+ private url: URL;
24
+
25
+ constructor(url: string, protected options?: RemoteJWKSCollectorOptions) {
26
+ try {
27
+ this.url = new URL(url);
28
+ } catch (e) {
29
+ throw new Error(`Invalid jwks_uri: ${url}`);
30
+ }
31
+
32
+ // We do support http here for self-hosting use cases.
33
+ // Management service restricts this to https for hosted versions.
34
+ if (this.url.protocol != 'https:' && this.url.protocol != 'http:') {
35
+ throw new Error(`Only http(s) is supported for jwks_uri, got: ${url}`);
36
+ }
37
+ }
38
+
39
+ async getKeys(): Promise<KeyResult> {
40
+ const abortController = new AbortController();
41
+ const timeout = setTimeout(() => {
42
+ abortController.abort();
43
+ }, 30_000);
44
+
45
+ const res = await fetch(this.url, {
46
+ method: 'GET',
47
+ headers: {
48
+ Accept: 'application/json'
49
+ },
50
+ signal: abortController.signal,
51
+ agent: await this.resolveAgent()
52
+ });
53
+
54
+ if (!res.ok) {
55
+ throw new jose.errors.JWKSInvalid(`JWKS request failed with ${res.statusText}`);
56
+ }
57
+
58
+ const data = (await res.json()) as any;
59
+
60
+ clearTimeout(timeout);
61
+
62
+ // https://github.com/panva/jose/blob/358e864a0cccf1e0f9928a959f91f18f3f06a7de/src/jwks/local.ts#L36
63
+ if (
64
+ data.keys == null ||
65
+ !Array.isArray(data.keys) ||
66
+ !(data.keys as any[]).every((key) => typeof key == 'object' && !Array.isArray(key))
67
+ ) {
68
+ return { keys: [], errors: [new jose.errors.JWKSInvalid(`No keys in found in JWKS response`)] };
69
+ }
70
+
71
+ let keys: KeySpec[] = [];
72
+ for (let keyData of data.keys) {
73
+ if (keyData.kty != 'RSA') {
74
+ // Only RSA public keys supported here
75
+ continue;
76
+ }
77
+
78
+ if (typeof keyData.use == 'string') {
79
+ if (keyData.use != 'sig') {
80
+ continue;
81
+ }
82
+ }
83
+ if (Array.isArray(keyData.key_ops)) {
84
+ if (!keyData.key_ops.includes('verify')) {
85
+ continue;
86
+ }
87
+ }
88
+
89
+ const key = await KeySpec.importKey(keyData);
90
+ keys.push(key);
91
+ }
92
+
93
+ return { keys: keys, errors: [] };
94
+ }
95
+
96
+ /**
97
+ * Resolve IP, and check that it is in an allowed range.
98
+ */
99
+ async resolveAgent(): Promise<http.Agent | https.Agent> {
100
+ const hostname = this.url.hostname;
101
+ let resolved_ip: string;
102
+ if (net.isIPv6(hostname)) {
103
+ throw new Error('IPv6 not supported yet');
104
+ } else if (net.isIPv4(hostname)) {
105
+ // All good
106
+ resolved_ip = hostname;
107
+ } else {
108
+ resolved_ip = (await dns.resolve4(hostname))[0];
109
+ }
110
+
111
+ const parsed = ip.parse(resolved_ip);
112
+ if (parsed.kind() != 'ipv4' || (this.options?.block_local_ip && parsed.range() !== 'unicast')) {
113
+ // Do not connect to any reserved IPs, including loopback and private ranges
114
+ throw new Error(`IPs in this range are not supported: ${resolved_ip}`);
115
+ }
116
+
117
+ const options = {
118
+ // This is the host that the agent connects to
119
+ host: resolved_ip
120
+ };
121
+
122
+ switch (this.url.protocol) {
123
+ case 'http:':
124
+ return new http.Agent(options);
125
+ case 'https:':
126
+ return new https.Agent(options);
127
+ }
128
+ throw new Error('http or or https is required for protocol');
129
+ }
130
+ }
@@ -0,0 +1,21 @@
1
+ import * as jose from 'jose';
2
+ import { KeySpec } from './KeySpec.js';
3
+ import { KeyCollector, KeyResult } from './KeyCollector.js';
4
+
5
+ /**
6
+ * Set of static keys.
7
+ *
8
+ * A key can be added both with and without a kid, in case wildcard matching is desired.
9
+ */
10
+ export class StaticKeyCollector implements KeyCollector {
11
+ static async importKeys(keys: jose.JWK[]) {
12
+ const parsedKeys = await Promise.all(keys.map((key) => KeySpec.importKey(key)));
13
+ return new StaticKeyCollector(parsedKeys);
14
+ }
15
+
16
+ constructor(private keys: KeySpec[]) {}
17
+
18
+ async getKeys(): Promise<KeyResult> {
19
+ return { keys: this.keys, errors: [] };
20
+ }
21
+ }
@@ -0,0 +1,67 @@
1
+ import * as jose from 'jose';
2
+ import * as pgwire from '@powersync/service-jpgwire';
3
+ import { connectPgWirePool, pgwireRows } from '@powersync/service-jpgwire';
4
+ import { KeyCollector } from './KeyCollector.js';
5
+ import { KeyOptions, KeySpec } from './KeySpec.js';
6
+ import { retriedQuery } from '../util/pgwire_utils.js';
7
+ import { ResolvedConnection } from '../util/config/types.js';
8
+
9
+ /**
10
+ * Fetches key from the Supabase database.
11
+ *
12
+ * Unfortunately, despite the JWTs containing a kid, we have no way to lookup that kid
13
+ * before receiving a valid token.
14
+ */
15
+ export class SupabaseKeyCollector implements KeyCollector {
16
+ private pool: pgwire.PgClient;
17
+
18
+ private keyOptions: KeyOptions = {
19
+ requiresAudience: ['authenticated'],
20
+ maxLifetimeSeconds: 86400 * 7 + 1200 // 1 week + 20 minutes margin
21
+ };
22
+
23
+ constructor(connection: ResolvedConnection) {
24
+ this.pool = connectPgWirePool(connection, {
25
+ // To avoid overloading the source database with open connections,
26
+ // limit to a single connection, and close the connection shortly
27
+ // after using it.
28
+ idleTimeout: 5_000,
29
+ maxSize: 1
30
+ });
31
+ }
32
+
33
+ async getKeys() {
34
+ let row: { jwt_secret: string };
35
+ try {
36
+ const rows = pgwireRows(
37
+ await retriedQuery(this.pool, `SELECT current_setting('app.settings.jwt_secret') as jwt_secret`)
38
+ );
39
+ row = rows[0] as any;
40
+ } catch (e) {
41
+ if (e.message?.includes('unrecognized configuration parameter')) {
42
+ throw new jose.errors.JOSEError(`Generate a new JWT secret on Supabase. Cause: ${e.message}`);
43
+ } else {
44
+ throw e;
45
+ }
46
+ }
47
+ const secret = row?.jwt_secret as string | undefined;
48
+ if (secret == null) {
49
+ return {
50
+ keys: [],
51
+ errors: [new jose.errors.JWKSNoMatchingKey()]
52
+ };
53
+ } else {
54
+ const key: jose.JWK = {
55
+ kty: 'oct',
56
+ alg: 'HS256',
57
+ // While the secret is valid base64, the base64-encoded form is the secret value.
58
+ k: Buffer.from(secret, 'utf8').toString('base64url')
59
+ };
60
+ const imported = await KeySpec.importKey(key, this.keyOptions);
61
+ return {
62
+ keys: [imported],
63
+ errors: []
64
+ };
65
+ }
66
+ }
67
+ }
@@ -0,0 +1,10 @@
1
+ export * from './CachedKeyCollector.js';
2
+ export * from './CompoundKeyCollector.js';
3
+ export * from './JwtPayload.js';
4
+ export * from './KeyCollector.js';
5
+ export * from './KeySpec.js';
6
+ export * from './KeyStore.js';
7
+ export * from './LeakyBucket.js';
8
+ export * from './RemoteJWKSCollector.js';
9
+ export * from './StaticKeyCollector.js';
10
+ export * from './SupabaseKeyCollector.js';
@@ -0,0 +1 @@
1
+ export * as mongo from './mongo.js';
@@ -0,0 +1,72 @@
1
+ import * as mongo from 'mongodb';
2
+ import * as timers from 'timers/promises';
3
+
4
+ import { configFile } from '@powersync/service-types';
5
+
6
+ /**
7
+ * Time for new connection to timeout.
8
+ */
9
+ export const MONGO_CONNECT_TIMEOUT_MS = 10_000;
10
+
11
+ /**
12
+ * Time for individual requests to timeout the socket.
13
+ */
14
+ export const MONGO_SOCKET_TIMEOUT_MS = 60_000;
15
+
16
+ /**
17
+ * Time for individual requests to timeout the operation.
18
+ *
19
+ * This is time spent on the cursor, not total time.
20
+ *
21
+ * Must be less than MONGO_SOCKET_TIMEOUT_MS to ensure proper error handling.
22
+ */
23
+ export const MONGO_OPERATION_TIMEOUT_MS = 30_000;
24
+
25
+ export function createMongoClient(config: configFile.PowerSyncConfig['storage']) {
26
+ return new mongo.MongoClient(config.uri, {
27
+ auth: {
28
+ username: config.username,
29
+ password: config.password
30
+ },
31
+ // Time for connection to timeout
32
+ connectTimeoutMS: MONGO_CONNECT_TIMEOUT_MS,
33
+ // Time for individual requests to timeout
34
+ socketTimeoutMS: MONGO_SOCKET_TIMEOUT_MS,
35
+ // How long to wait for new primary selection
36
+ serverSelectionTimeoutMS: 30_000,
37
+
38
+ // Avoid too many connections:
39
+ // 1. It can overwhelm the source database.
40
+ // 2. Processing too many queries in parallel can cause the process to run out of memory.
41
+ maxPoolSize: 8,
42
+
43
+ maxConnecting: 3,
44
+ maxIdleTimeMS: 60_000
45
+ });
46
+ }
47
+
48
+ /**
49
+ * Wait up to a minute for authentication errors to resolve.
50
+ *
51
+ * There can be a delay between an Atlas user being created, and that user being
52
+ * available on the database cluster. This works around it.
53
+ *
54
+ * This is specifically relevant for migrations and teardown - other parts of the stack
55
+ * can generate handle these failures and just retry or restart.
56
+ */
57
+ export async function waitForAuth(db: mongo.Db) {
58
+ const start = Date.now();
59
+ while (Date.now() - start < 60_000) {
60
+ try {
61
+ await db.command({ ping: 1 });
62
+ // Success
63
+ break;
64
+ } catch (e) {
65
+ if (e.codeName == 'AuthenticationFailed') {
66
+ await timers.setTimeout(1_000);
67
+ continue;
68
+ }
69
+ throw e;
70
+ }
71
+ }
72
+ }
@@ -0,0 +1,40 @@
1
+ import { Command } from 'commander';
2
+
3
+ import * as utils from '../util/util-index.js';
4
+ import { registerMigrationAction } from './commands/migrate-action.js';
5
+ import { registerTearDownAction } from './commands/teardown-action.js';
6
+ import { registerStartAction } from './entry-index.js';
7
+ import { logger } from '@powersync/lib-services-framework';
8
+
9
+ /**
10
+ * Generates a Commander program which serves as the entry point
11
+ * for the PowerSync service.
12
+ * This registers standard actions for teardown and migrations.
13
+ * Optionally registers the start command handlers.
14
+ */
15
+ export function generateEntryProgram(startHandlers?: Record<utils.ServiceRunner, utils.Runner>) {
16
+ const entryProgram = new Command();
17
+ entryProgram.name('powersync-runner').description('CLI to initiate a PowerSync service runner');
18
+
19
+ registerTearDownAction(entryProgram);
20
+ registerMigrationAction(entryProgram);
21
+
22
+ if (startHandlers) {
23
+ registerStartAction(entryProgram, startHandlers);
24
+ }
25
+
26
+ return {
27
+ program: entryProgram,
28
+ /**
29
+ * Executes the main program. Ends the NodeJS process if an exception was caught.
30
+ */
31
+ execute: async function runProgram() {
32
+ try {
33
+ await entryProgram.parseAsync();
34
+ } catch (e) {
35
+ logger.error('Fatal error', e);
36
+ process.exit(1);
37
+ }
38
+ }
39
+ };
40
+ }
@@ -0,0 +1,36 @@
1
+ import { Command } from 'commander';
2
+
3
+ import * as util from '../../util/util-index.js';
4
+
5
+ /**
6
+ * Wraps a Command with the standard config options
7
+ */
8
+ export function wrapConfigCommand(command: Command) {
9
+ return command
10
+ .option(
11
+ `-c, --config-path [path]`,
12
+ 'Path (inside container) to YAML config file. Defaults to process.env.POWERSYNC_CONFIG_PATH',
13
+ util.env.POWERSYNC_CONFIG_PATH
14
+ )
15
+ .option(
16
+ `-c64, --config-base64 [base64]`,
17
+ 'Base64 encoded YAML or JSON config file. Defaults to process.env.POWERSYNC_CONFIG_B64',
18
+ util.env.POWERSYNC_CONFIG_B64
19
+ )
20
+ .option(
21
+ `-sync64, --sync-base64 [base64]`,
22
+ 'Base64 encoded YAML Sync Rules. Defaults to process.env.POWERSYNC_SYNC_RULES_B64',
23
+ util.env.POWERSYNC_SYNC_RULES_B64
24
+ );
25
+ }
26
+
27
+ /**
28
+ * Extracts runner configuration params from Command options.
29
+ */
30
+ export function extractRunnerOptions(options: any): util.RunnerConfig {
31
+ return {
32
+ config_path: options.configPath,
33
+ config_base64: options.configBase64,
34
+ sync_rules_base64: options.syncBase64
35
+ };
36
+ }
@@ -0,0 +1,25 @@
1
+ import { Command } from 'commander';
2
+
3
+ import { extractRunnerOptions, wrapConfigCommand } from './config-command.js';
4
+ import { migrate } from '../../migrations/migrations.js';
5
+ import { Direction } from '../../migrations/definitions.js';
6
+
7
+ const COMMAND_NAME = 'migrate';
8
+
9
+ export function registerMigrationAction(program: Command) {
10
+ const migrationCommand = program.command(COMMAND_NAME);
11
+
12
+ wrapConfigCommand(migrationCommand);
13
+
14
+ return migrationCommand
15
+ .description('Run migrations')
16
+ .argument('<direction>', 'Migration direction. `up` or `down`')
17
+ .action(async (direction: Direction, options) => {
18
+ const runnerConfig = extractRunnerOptions(options);
19
+
20
+ await migrate({
21
+ direction,
22
+ runner_config: runnerConfig
23
+ });
24
+ });
25
+ }
@@ -0,0 +1,24 @@
1
+ import { Command } from 'commander';
2
+
3
+ import * as utils from '../../util/util-index.js';
4
+ import { extractRunnerOptions, wrapConfigCommand } from './config-command.js';
5
+
6
+ const COMMAND_NAME = 'start';
7
+
8
+ export function registerStartAction(program: Command, handlers: Record<utils.ServiceRunner, utils.Runner>) {
9
+ const startCommand = program.command(COMMAND_NAME);
10
+
11
+ wrapConfigCommand(startCommand);
12
+
13
+ return startCommand
14
+ .description('Starts a PowerSync service runner.')
15
+ .option(
16
+ `-r, --runner-type [${Object.values(utils.ServiceRunner).join('|')}]`,
17
+ 'Type of runner to start. Defaults to unified runner.',
18
+ utils.env.PS_RUNNER_TYPE
19
+ )
20
+ .action(async (options) => {
21
+ const runner = handlers[options.runnerType as utils.ServiceRunner];
22
+ await runner(extractRunnerOptions(options));
23
+ });
24
+ }
@@ -0,0 +1,23 @@
1
+ import { Command } from 'commander';
2
+
3
+ import { extractRunnerOptions, wrapConfigCommand } from './config-command.js';
4
+ import { teardown } from '../../runner/teardown.js';
5
+
6
+ const COMMAND_NAME = 'teardown';
7
+
8
+ export function registerTearDownAction(program: Command) {
9
+ const teardownCommand = program.command(COMMAND_NAME);
10
+
11
+ wrapConfigCommand(teardownCommand);
12
+
13
+ return teardownCommand
14
+ .argument('[ack]', 'Type `TEARDOWN` to confirm teardown should occur')
15
+ .description('Terminate all replicating sync rules, deleting the replication slots')
16
+ .action(async (ack, options) => {
17
+ if (ack !== 'TEARDOWN') {
18
+ throw new Error('TEARDOWN was not acknowledged.');
19
+ }
20
+
21
+ await teardown(extractRunnerOptions(options));
22
+ });
23
+ }
@@ -0,0 +1,5 @@
1
+ export * from './cli-entry.js';
2
+ export * from './commands/config-command.js';
3
+ export * from './commands/migrate-action.js';
4
+ export * from './commands/start-action.js';
5
+ export * from './commands/teardown-action.js';