@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,213 @@
1
+ import * as pgwire from '@powersync/service-jpgwire';
2
+ import { hrtime } from 'node:process';
3
+
4
+ import * as storage from '../storage/storage-index.js';
5
+ import * as util from '../util/util-index.js';
6
+
7
+ import { DefaultErrorRateLimiter } from './ErrorRateLimiter.js';
8
+ import { WalStreamRunner } from './WalStreamRunner.js';
9
+ import { CorePowerSyncSystem } from '../system/CorePowerSyncSystem.js';
10
+ import { container, logger } from '@powersync/lib-services-framework';
11
+
12
+ // 5 minutes
13
+ const PING_INTERVAL = 1_000_000_000n * 300n;
14
+
15
+ export class WalStreamManager {
16
+ private streams = new Map<number, WalStreamRunner>();
17
+
18
+ private system: CorePowerSyncSystem;
19
+
20
+ private stopped = false;
21
+
22
+ // First ping is only after 5 minutes, not when starting
23
+ private lastPing = hrtime.bigint();
24
+
25
+ private storage: storage.BucketStorageFactory;
26
+
27
+ /**
28
+ * This limits the effect of retries when there is a persistent issue.
29
+ */
30
+ private rateLimiter = new DefaultErrorRateLimiter();
31
+
32
+ constructor(system: CorePowerSyncSystem) {
33
+ this.system = system;
34
+ this.storage = system.storage;
35
+ }
36
+
37
+ start() {
38
+ this.runLoop().catch((e) => {
39
+ logger.error(`Fatal WalStream error`, e);
40
+ container.reporter.captureException(e);
41
+ setTimeout(() => {
42
+ process.exit(1);
43
+ }, 1000);
44
+ });
45
+ }
46
+
47
+ async stop() {
48
+ this.stopped = true;
49
+ let promises: Promise<void>[] = [];
50
+ for (let stream of this.streams.values()) {
51
+ promises.push(stream.stop());
52
+ }
53
+ await Promise.all(promises);
54
+ }
55
+
56
+ private async runLoop() {
57
+ const configured_sync_rules = await util.loadSyncRules(this.system.config);
58
+ let configured_lock: storage.ReplicationLock | undefined = undefined;
59
+ if (configured_sync_rules != null) {
60
+ logger.info('Loading sync rules from configuration');
61
+ try {
62
+ // Configure new sync rules, if it has changed.
63
+ // In that case, also immediately take out a lock, so that another process doesn't start replication on it.
64
+ const { updated, persisted_sync_rules, lock } = await this.storage.configureSyncRules(configured_sync_rules!, {
65
+ lock: true
66
+ });
67
+ if (lock) {
68
+ configured_lock = lock;
69
+ }
70
+ } catch (e) {
71
+ // Log, but continue with previous sync rules
72
+ logger.error(`Failed to load sync rules from configuration`, e);
73
+ }
74
+ } else {
75
+ logger.info('No sync rules configured - configure via API');
76
+ }
77
+ while (!this.stopped) {
78
+ await container.probes.touch();
79
+ try {
80
+ const pool = this.system.pgwire_pool;
81
+ if (pool) {
82
+ await this.refresh({ configured_lock });
83
+ // The lock is only valid on the first refresh.
84
+ configured_lock = undefined;
85
+
86
+ // TODO: Ping on all connections when we have multiple
87
+ // Perhaps WalStreamRunner would be a better place to do pings?
88
+ // We don't ping while in error retry back-off, to avoid having too
89
+ // many authentication failures.
90
+ if (this.rateLimiter.mayPing()) {
91
+ await this.ping(pool);
92
+ }
93
+ }
94
+ } catch (e) {
95
+ logger.error(`Failed to refresh wal streams`, e);
96
+ }
97
+ await new Promise((resolve) => setTimeout(resolve, 5000));
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Postgres on RDS writes performs a WAL checkpoint every 5 minutes by default, which creates a new 64MB file.
103
+ *
104
+ * The old WAL files are only deleted once no replication slot still references it.
105
+ *
106
+ * Unfortunately, when there are no changes to the db, the database creates new WAL files without the replication slot
107
+ * advancing**.
108
+ *
109
+ * As a workaround, we write a new message every couple of minutes, to make sure that the replication slot advances.
110
+ *
111
+ * **This may be a bug in pgwire or how we're using it.
112
+ */
113
+ private async ping(db: pgwire.PgClient) {
114
+ const now = hrtime.bigint();
115
+ if (now - this.lastPing >= PING_INTERVAL) {
116
+ try {
117
+ await db.query(`SELECT * FROM pg_logical_emit_message(false, 'powersync', 'ping')`);
118
+ } catch (e) {
119
+ logger.warn(`Failed to ping`, e);
120
+ }
121
+ this.lastPing = now;
122
+ }
123
+ }
124
+
125
+ private async refresh(options?: { configured_lock?: storage.ReplicationLock }) {
126
+ if (this.stopped) {
127
+ return;
128
+ }
129
+
130
+ let configured_lock = options?.configured_lock;
131
+
132
+ const existingStreams = new Map<number, WalStreamRunner>(this.streams.entries());
133
+ const replicating = await this.storage.getReplicatingSyncRules();
134
+ const newStreams = new Map<number, WalStreamRunner>();
135
+ for (let syncRules of replicating) {
136
+ const existing = existingStreams.get(syncRules.id);
137
+ if (existing && !existing.stopped) {
138
+ // No change
139
+ existingStreams.delete(syncRules.id);
140
+ newStreams.set(syncRules.id, existing);
141
+ } else if (existing && existing.stopped) {
142
+ // Stopped (e.g. fatal error, slot rename).
143
+ // Remove from the list. Next refresh call will restart the stream.
144
+ existingStreams.delete(syncRules.id);
145
+ } else {
146
+ // New (or resume after restart)
147
+ try {
148
+ let lock: storage.ReplicationLock;
149
+ if (configured_lock?.sync_rules_id == syncRules.id) {
150
+ lock = configured_lock;
151
+ } else {
152
+ lock = await syncRules.lock();
153
+ }
154
+ const parsed = syncRules.parsed();
155
+ const storage = this.storage.getInstance(parsed);
156
+ const stream = new WalStreamRunner({
157
+ factory: this.storage,
158
+ storage: storage,
159
+ source_db: this.system.config.connection!,
160
+ lock,
161
+ rateLimiter: this.rateLimiter
162
+ });
163
+ newStreams.set(syncRules.id, stream);
164
+ stream.start();
165
+ } catch (e) {
166
+ // Could be a sync rules parse error,
167
+ // for example from stricter validation that was added.
168
+ // This will be retried every couple of seconds.
169
+ // When new (valid) sync rules are deployed and processed, this one be disabled.
170
+ logger.error(`Failed to start replication for ${syncRules.slot_name}`, e);
171
+ }
172
+ }
173
+ }
174
+
175
+ this.streams = newStreams;
176
+
177
+ // TODO: Should this termination be happening in the "background" instead?
178
+ // That becomes tricky to manage
179
+
180
+ for (let stream of existingStreams.values()) {
181
+ // Old - stop and remove.
182
+ try {
183
+ await stream.terminate();
184
+ } catch (e) {
185
+ // This will be retried
186
+ logger.warn(`Failed to terminate ${stream.slot_name}`, e);
187
+ }
188
+ }
189
+
190
+ // Sync rules stopped previously or by a different process.
191
+ const stopped = await this.storage.getStoppedSyncRules();
192
+ for (let syncRules of stopped) {
193
+ try {
194
+ const lock = await syncRules.lock();
195
+ try {
196
+ const parsed = syncRules.parsed();
197
+ const storage = this.storage.getInstance(parsed);
198
+ const stream = new WalStreamRunner({
199
+ factory: this.storage,
200
+ storage: storage,
201
+ source_db: this.system.config.connection!,
202
+ lock
203
+ });
204
+ await stream.terminate();
205
+ } finally {
206
+ await lock.release();
207
+ }
208
+ } catch (e) {
209
+ logger.warn(`Failed to terminate ${syncRules.slot_name}`, e);
210
+ }
211
+ }
212
+ }
213
+ }
@@ -0,0 +1,180 @@
1
+ import * as pgwire from '@powersync/service-jpgwire';
2
+
3
+ import * as storage from '../storage/storage-index.js';
4
+ import * as util from '../util/util-index.js';
5
+
6
+ import { ErrorRateLimiter } from './ErrorRateLimiter.js';
7
+ import { MissingReplicationSlotError, WalStream } from './WalStream.js';
8
+ import { ResolvedConnection } from '../util/config/types.js';
9
+ import { container, logger } from '@powersync/lib-services-framework';
10
+
11
+ export interface WalStreamRunnerOptions {
12
+ factory: storage.BucketStorageFactory;
13
+ storage: storage.SyncRulesBucketStorage;
14
+ source_db: ResolvedConnection;
15
+ lock: storage.ReplicationLock;
16
+ rateLimiter?: ErrorRateLimiter;
17
+ }
18
+
19
+ export class WalStreamRunner {
20
+ private abortController = new AbortController();
21
+
22
+ private runPromise?: Promise<void>;
23
+
24
+ private connections: util.PgManager | null = null;
25
+
26
+ private rateLimiter?: ErrorRateLimiter;
27
+
28
+ constructor(public options: WalStreamRunnerOptions) {
29
+ this.rateLimiter = options.rateLimiter;
30
+ }
31
+
32
+ start() {
33
+ this.runPromise = this.run();
34
+ }
35
+
36
+ get slot_name() {
37
+ return this.options.storage.slot_name;
38
+ }
39
+
40
+ get stopped() {
41
+ return this.abortController.signal.aborted;
42
+ }
43
+
44
+ async run() {
45
+ try {
46
+ await this.replicateLoop();
47
+ } catch (e) {
48
+ // Fatal exception
49
+ container.reporter.captureException(e, {
50
+ metadata: {
51
+ replication_slot: this.slot_name
52
+ }
53
+ });
54
+ logger.error(`Replication failed on ${this.slot_name}`, e);
55
+
56
+ if (e instanceof MissingReplicationSlotError) {
57
+ // This stops replication on this slot, and creates a new slot
58
+ await this.options.storage.factory.slotRemoved(this.slot_name);
59
+ }
60
+ } finally {
61
+ this.abortController.abort();
62
+ }
63
+ await this.options.lock.release();
64
+ }
65
+
66
+ async replicateLoop() {
67
+ while (!this.stopped) {
68
+ await this.replicateOnce();
69
+
70
+ if (!this.stopped) {
71
+ await new Promise((resolve) => setTimeout(resolve, 5000));
72
+ }
73
+ }
74
+ }
75
+
76
+ async replicateOnce() {
77
+ // New connections on every iteration (every error with retry),
78
+ // otherwise we risk repeating errors related to the connection,
79
+ // such as caused by cached PG schemas.
80
+ let connections = new util.PgManager(this.options.source_db, {
81
+ // Pool connections are only used intermittently.
82
+ idleTimeout: 30_000,
83
+ maxSize: 2
84
+ });
85
+ this.connections = connections;
86
+ try {
87
+ await this.rateLimiter?.waitUntilAllowed({ signal: this.abortController.signal });
88
+ if (this.stopped) {
89
+ return;
90
+ }
91
+ const stream = new WalStream({
92
+ abort_signal: this.abortController.signal,
93
+ factory: this.options.factory,
94
+ storage: this.options.storage,
95
+ connections
96
+ });
97
+ await stream.replicate();
98
+ } catch (e) {
99
+ logger.error(`Replication error`, e);
100
+ if (e.cause != null) {
101
+ // Example:
102
+ // PgError.conn_ended: Unable to do postgres query on ended connection
103
+ // at PgConnection.stream (file:///.../powersync/node_modules/.pnpm/github.com+kagis+pgwire@f1cb95f9a0f42a612bb5a6b67bb2eb793fc5fc87/node_modules/pgwire/mod.js:315:13)
104
+ // at stream.next (<anonymous>)
105
+ // at PgResult.fromStream (file:///.../powersync/node_modules/.pnpm/github.com+kagis+pgwire@f1cb95f9a0f42a612bb5a6b67bb2eb793fc5fc87/node_modules/pgwire/mod.js:1174:22)
106
+ // at PgConnection.query (file:///.../powersync/node_modules/.pnpm/github.com+kagis+pgwire@f1cb95f9a0f42a612bb5a6b67bb2eb793fc5fc87/node_modules/pgwire/mod.js:311:21)
107
+ // at WalStream.startInitialReplication (file:///.../powersync/powersync-service/lib/replication/WalStream.js:266:22)
108
+ // ...
109
+ // cause: TypeError: match is not iterable
110
+ // at timestamptzToSqlite (file:///.../powersync/packages/jpgwire/dist/util.js:140:50)
111
+ // at PgType.decode (file:///.../powersync/packages/jpgwire/dist/pgwire_types.js:25:24)
112
+ // at PgConnection._recvDataRow (file:///.../powersync/packages/jpgwire/dist/util.js:88:22)
113
+ // at PgConnection._recvMessages (file:///.../powersync/node_modules/.pnpm/github.com+kagis+pgwire@f1cb95f9a0f42a612bb5a6b67bb2eb793fc5fc87/node_modules/pgwire/mod.js:656:30)
114
+ // at PgConnection._ioloopAttempt (file:///.../powersync/node_modules/.pnpm/github.com+kagis+pgwire@f1cb95f9a0f42a612bb5a6b67bb2eb793fc5fc87/node_modules/pgwire/mod.js:563:20)
115
+ // at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
116
+ // at async PgConnection._ioloop (file:///.../powersync/node_modules/.pnpm/github.com+kagis+pgwire@f1cb95f9a0f42a612bb5a6b67bb2eb793fc5fc87/node_modules/pgwire/mod.js:517:14),
117
+ // [Symbol(pg.ErrorCode)]: 'conn_ended',
118
+ // [Symbol(pg.ErrorResponse)]: undefined
119
+ // }
120
+ // Without this additional log, the cause would not be visible in the logs.
121
+ logger.error(`cause`, e.cause);
122
+ }
123
+ if (e instanceof MissingReplicationSlotError) {
124
+ throw e;
125
+ } else {
126
+ // Report the error if relevant, before retrying
127
+ container.reporter.captureException(e, {
128
+ metadata: {
129
+ replication_slot: this.slot_name
130
+ }
131
+ });
132
+ // This sets the retry delay
133
+ this.rateLimiter?.reportError(e);
134
+ }
135
+ } finally {
136
+ this.connections = null;
137
+ if (connections != null) {
138
+ await connections.end();
139
+ }
140
+ }
141
+ }
142
+
143
+ /**
144
+ * This will also release the lock if start() was called earlier.
145
+ */
146
+ async stop(options?: { force?: boolean }) {
147
+ logger.info(`${this.slot_name} Stopping replication`);
148
+ // End gracefully
149
+ this.abortController.abort();
150
+
151
+ if (options?.force) {
152
+ // destroy() is more forceful.
153
+ await this.connections?.destroy();
154
+ }
155
+ await this.runPromise;
156
+ }
157
+
158
+ /**
159
+ * Terminate this replication stream. This drops the replication slot and deletes the replication data.
160
+ *
161
+ * Stops replication if needed.
162
+ */
163
+ async terminate(options?: { force?: boolean }) {
164
+ logger.info(`${this.slot_name} Terminating replication`);
165
+ await this.stop(options);
166
+
167
+ const slotName = this.slot_name;
168
+ const db = await pgwire.connectPgWire(this.options.source_db, { type: 'standard' });
169
+ try {
170
+ await db.query({
171
+ statement: 'SELECT pg_drop_replication_slot(slot_name) FROM pg_replication_slots WHERE slot_name = $1',
172
+ params: [{ type: 'varchar', value: slotName }]
173
+ });
174
+ } finally {
175
+ await db.end();
176
+ }
177
+
178
+ await this.options.storage.terminate();
179
+ }
180
+ }
@@ -0,0 +1,7 @@
1
+ export * from './ErrorRateLimiter.js';
2
+ export * from './PgRelation.js';
3
+ export * from './util.js';
4
+ export * from './WalConnection.js';
5
+ export * from './WalStream.js';
6
+ export * from './WalStreamManager.js';
7
+ export * from './WalStreamRunner.js';
@@ -0,0 +1,76 @@
1
+ import * as pgwire from '@powersync/service-jpgwire';
2
+
3
+ import * as util from '../util/util-index.js';
4
+ import { ReplicationColumn, ReplicationIdentity } from './PgRelation.js';
5
+
6
+ export interface ReplicaIdentityResult {
7
+ columns: ReplicationColumn[];
8
+ replicationIdentity: ReplicationIdentity;
9
+ }
10
+
11
+ export async function getPrimaryKeyColumns(
12
+ db: pgwire.PgClient,
13
+ relationId: number,
14
+ mode: 'primary' | 'replident'
15
+ ): Promise<ReplicationColumn[]> {
16
+ const indexFlag = mode == 'primary' ? `i.indisprimary` : `i.indisreplident`;
17
+ const attrRows = await util.retriedQuery(db, {
18
+ statement: `SELECT a.attname as name, a.atttypid as typeid, a.attnum as attnum
19
+ FROM pg_index i
20
+ JOIN pg_attribute a
21
+ ON a.attrelid = i.indrelid
22
+ AND a.attnum = ANY (i.indkey)
23
+ WHERE i.indrelid = $1::oid
24
+ AND ${indexFlag}
25
+ AND a.attnum > 0
26
+ ORDER BY a.attnum`,
27
+ params: [{ value: relationId, type: 'int4' }]
28
+ });
29
+
30
+ return attrRows.rows.map((row) => {
31
+ return { name: row[0] as string, typeOid: row[1] as number };
32
+ });
33
+ }
34
+
35
+ export async function getAllColumns(db: pgwire.PgClient, relationId: number): Promise<ReplicationColumn[]> {
36
+ const attrRows = await util.retriedQuery(db, {
37
+ statement: `SELECT a.attname as name, a.atttypid as typeid, a.attnum as attnum
38
+ FROM pg_attribute a
39
+ WHERE a.attrelid = $1::oid
40
+ AND attnum > 0
41
+ ORDER BY a.attnum`,
42
+ params: [{ type: 'varchar', value: relationId }]
43
+ });
44
+ return attrRows.rows.map((row) => {
45
+ return { name: row[0] as string, typeOid: row[1] as number };
46
+ });
47
+ }
48
+
49
+ export async function getReplicationIdentityColumns(
50
+ db: pgwire.PgClient,
51
+ relationId: number
52
+ ): Promise<ReplicaIdentityResult> {
53
+ const rows = await util.retriedQuery(db, {
54
+ statement: `SELECT CASE relreplident
55
+ WHEN 'd' THEN 'default'
56
+ WHEN 'n' THEN 'nothing'
57
+ WHEN 'f' THEN 'full'
58
+ WHEN 'i' THEN 'index'
59
+ END AS replica_identity
60
+ FROM pg_class
61
+ WHERE oid = $1::oid LIMIT 1`,
62
+ params: [{ type: 'int8', value: relationId }]
63
+ });
64
+ const idType: string = rows.rows[0]?.[0];
65
+ if (idType == 'nothing' || idType == null) {
66
+ return { replicationIdentity: 'nothing', columns: [] };
67
+ } else if (idType == 'full') {
68
+ return { replicationIdentity: 'full', columns: await getAllColumns(db, relationId) };
69
+ } else if (idType == 'default') {
70
+ return { replicationIdentity: 'default', columns: await getPrimaryKeyColumns(db, relationId, 'primary') };
71
+ } else if (idType == 'index') {
72
+ return { replicationIdentity: 'index', columns: await getPrimaryKeyColumns(db, relationId, 'replident') };
73
+ } else {
74
+ return { replicationIdentity: 'nothing', columns: [] };
75
+ }
76
+ }
@@ -0,0 +1,215 @@
1
+ import * as jose from 'jose';
2
+
3
+ import * as auth from '../auth/auth-index.js';
4
+ import * as util from '../util/util-index.js';
5
+ import { BasicRouterRequest, Context, RequestEndpointHandlerPayload } from './router.js';
6
+ import { CorePowerSyncSystem } from '../system/CorePowerSyncSystem.js';
7
+
8
+ export function endpoint(req: BasicRouterRequest) {
9
+ const protocol = req.headers['x-forwarded-proto'] ?? req.protocol;
10
+ const host = req.hostname;
11
+ return `${protocol}://${host}`;
12
+ }
13
+
14
+ function devAudience(req: BasicRouterRequest): string {
15
+ return `${endpoint(req)}/dev`;
16
+ }
17
+
18
+ /**
19
+ * @deprecated
20
+ *
21
+ * Will be replaced by temporary tokens issued by PowerSync Management service.
22
+ */
23
+ export async function issueDevToken(req: BasicRouterRequest, user_id: string, config: util.ResolvedPowerSyncConfig) {
24
+ const iss = devAudience(req);
25
+ const aud = devAudience(req);
26
+
27
+ const key = config.dev.dev_key;
28
+ if (key == null) {
29
+ throw new Error('Auth disabled');
30
+ }
31
+
32
+ return await new jose.SignJWT({})
33
+ .setProtectedHeader({ alg: key.source.alg!, kid: key.kid })
34
+ .setSubject(user_id)
35
+ .setIssuedAt()
36
+ .setIssuer(iss)
37
+ .setAudience(aud)
38
+ .setExpirationTime('30d')
39
+ .sign(key.key);
40
+ }
41
+
42
+ /** @deprecated */
43
+ export async function issueLegacyDevToken(
44
+ req: BasicRouterRequest,
45
+ user_id: string,
46
+ config: util.ResolvedPowerSyncConfig
47
+ ) {
48
+ const iss = devAudience(req);
49
+ const aud = config.jwt_audiences[0];
50
+
51
+ const key = config.dev.dev_key;
52
+ if (key == null || aud == null) {
53
+ throw new Error('Auth disabled');
54
+ }
55
+
56
+ return await new jose.SignJWT({})
57
+ .setProtectedHeader({ alg: key.source.alg!, kid: key.kid })
58
+ .setSubject(user_id)
59
+ .setIssuedAt()
60
+ .setIssuer(iss)
61
+ .setAudience(aud)
62
+ .setExpirationTime('60m')
63
+ .sign(key.key);
64
+ }
65
+
66
+ export async function issuePowerSyncToken(
67
+ req: BasicRouterRequest,
68
+ user_id: string,
69
+ config: util.ResolvedPowerSyncConfig
70
+ ) {
71
+ const iss = devAudience(req);
72
+ const aud = config.jwt_audiences[0];
73
+ const key = config.dev.dev_key;
74
+ if (key == null || aud == null) {
75
+ throw new Error('Auth disabled');
76
+ }
77
+
78
+ const jwt = await new jose.SignJWT({})
79
+ .setProtectedHeader({ alg: key.source.alg!, kid: key.kid })
80
+ .setSubject(user_id)
81
+ .setIssuedAt()
82
+ .setIssuer(iss)
83
+ .setAudience(aud)
84
+ .setExpirationTime('5m')
85
+ .sign(key.key);
86
+ return jwt;
87
+ }
88
+
89
+ export function getTokenFromHeader(authHeader: string = ''): string | null {
90
+ const tokenMatch = /^(Token|Bearer) (\S+)$/.exec(authHeader);
91
+ if (!tokenMatch) {
92
+ return null;
93
+ }
94
+ const token = tokenMatch[2];
95
+ return token ?? null;
96
+ }
97
+
98
+ export const authUser = async (payload: RequestEndpointHandlerPayload) => {
99
+ return authorizeUser(payload.context, payload.request.headers.authorization as string);
100
+ };
101
+
102
+ export async function authorizeUser(context: Context, authHeader: string = '') {
103
+ const token = getTokenFromHeader(authHeader);
104
+ if (token == null) {
105
+ return {
106
+ authorized: false,
107
+ errors: ['Authentication required']
108
+ };
109
+ }
110
+
111
+ const { context: tokenContext, errors } = await generateContext(context.system, token);
112
+
113
+ if (!tokenContext) {
114
+ return {
115
+ authorized: false,
116
+ errors
117
+ };
118
+ }
119
+
120
+ Object.assign(context, tokenContext);
121
+ return { authorized: true };
122
+ }
123
+
124
+ export async function generateContext(system: CorePowerSyncSystem, token: string) {
125
+ const config = system.config;
126
+
127
+ let tokenPayload: auth.JwtPayload;
128
+ try {
129
+ const maxAge = config.token_max_expiration;
130
+ tokenPayload = await system.client_keystore.verifyJwt(token, {
131
+ defaultAudiences: config.jwt_audiences,
132
+ maxAge: maxAge
133
+ });
134
+ return {
135
+ context: {
136
+ user_id: tokenPayload.sub,
137
+ token_payload: tokenPayload
138
+ }
139
+ };
140
+ } catch (err) {
141
+ return {
142
+ context: null,
143
+ errors: [err.message]
144
+ };
145
+ }
146
+ }
147
+
148
+ /**
149
+ * @deprecated
150
+ */
151
+ export const authDevUser = async (payload: RequestEndpointHandlerPayload) => {
152
+ const context = payload.context;
153
+ const token = getTokenFromHeader(payload.request.headers.authorization as string);
154
+ if (!context.system.config.dev.demo_auth) {
155
+ return {
156
+ authorized: false,
157
+ errors: ['Authentication disabled']
158
+ };
159
+ }
160
+ if (token == null) {
161
+ return {
162
+ authorized: false,
163
+ errors: ['Authentication required']
164
+ };
165
+ }
166
+
167
+ // Different from the configured audience.
168
+ // Should also not be changed by keys
169
+ const audience = [devAudience(payload.request)];
170
+
171
+ let tokenPayload: auth.JwtPayload;
172
+ try {
173
+ tokenPayload = await context.system.dev_client_keystore.verifyJwt(token, {
174
+ defaultAudiences: audience,
175
+ maxAge: '31d'
176
+ });
177
+ } catch (err) {
178
+ return {
179
+ authorized: false,
180
+ errors: [err.message]
181
+ };
182
+ }
183
+
184
+ payload.context.user_id = tokenPayload.sub;
185
+ return { authorized: true };
186
+ };
187
+
188
+ export const authApi = (payload: RequestEndpointHandlerPayload) => {
189
+ const context = payload.context;
190
+ const api_keys = context.system.config.api_tokens;
191
+ if (api_keys.length == 0) {
192
+ return {
193
+ authorized: false,
194
+ errors: ['Authentication disabled']
195
+ };
196
+ }
197
+ const auth = (payload.request.headers.authorization as string) ?? '';
198
+
199
+ const tokenMatch = /^(Token|Bearer) (\S+)$/.exec(auth);
200
+ if (!tokenMatch) {
201
+ return {
202
+ authorized: false,
203
+ errors: ['Authentication required']
204
+ };
205
+ }
206
+ const token = tokenMatch[2];
207
+ if (api_keys.includes(token)) {
208
+ return { authorized: true };
209
+ } else {
210
+ return {
211
+ authorized: false,
212
+ errors: ['Authentication failed']
213
+ };
214
+ }
215
+ };