@powersync/service-core 0.8.8 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (376) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/dist/api/RouteAPI.d.ts +67 -0
  3. package/dist/api/RouteAPI.js +2 -0
  4. package/dist/api/RouteAPI.js.map +1 -0
  5. package/dist/api/api-index.d.ts +1 -0
  6. package/dist/api/api-index.js +1 -0
  7. package/dist/api/api-index.js.map +1 -1
  8. package/dist/api/diagnostics.d.ts +4 -4
  9. package/dist/api/diagnostics.js +170 -158
  10. package/dist/api/diagnostics.js.map +1 -1
  11. package/dist/api/schema.d.ts +3 -5
  12. package/dist/api/schema.js +14 -80
  13. package/dist/api/schema.js.map +1 -1
  14. package/dist/auth/CachedKeyCollector.js.map +1 -1
  15. package/dist/auth/KeySpec.js.map +1 -1
  16. package/dist/auth/KeyStore.d.ts +7 -4
  17. package/dist/auth/KeyStore.js +1 -1
  18. package/dist/auth/KeyStore.js.map +1 -1
  19. package/dist/auth/LeakyBucket.js.map +1 -1
  20. package/dist/auth/RemoteJWKSCollector.d.ts +0 -2
  21. package/dist/auth/RemoteJWKSCollector.js.map +1 -1
  22. package/dist/auth/StaticSupabaseKeyCollector.d.ts +19 -0
  23. package/dist/auth/StaticSupabaseKeyCollector.js +28 -0
  24. package/dist/auth/StaticSupabaseKeyCollector.js.map +1 -0
  25. package/dist/auth/auth-index.d.ts +1 -1
  26. package/dist/auth/auth-index.js +1 -1
  27. package/dist/auth/auth-index.js.map +1 -1
  28. package/dist/db/mongo.js +5 -3
  29. package/dist/db/mongo.js.map +1 -1
  30. package/dist/entry/cli-entry.js +3 -2
  31. package/dist/entry/cli-entry.js.map +1 -1
  32. package/dist/entry/commands/compact-action.js +90 -14
  33. package/dist/entry/commands/compact-action.js.map +1 -1
  34. package/dist/entry/commands/migrate-action.js +4 -5
  35. package/dist/entry/commands/migrate-action.js.map +1 -1
  36. package/dist/entry/commands/teardown-action.js +2 -2
  37. package/dist/entry/commands/teardown-action.js.map +1 -1
  38. package/dist/index.d.ts +4 -2
  39. package/dist/index.js +4 -2
  40. package/dist/index.js.map +1 -1
  41. package/dist/locks/MongoLocks.js.map +1 -1
  42. package/dist/metrics/Metrics.d.ts +2 -2
  43. package/dist/metrics/Metrics.js +5 -13
  44. package/dist/metrics/Metrics.js.map +1 -1
  45. package/dist/migrations/db/migrations/1684951997326-init.d.ts +2 -2
  46. package/dist/migrations/db/migrations/1684951997326-init.js +4 -2
  47. package/dist/migrations/db/migrations/1684951997326-init.js.map +1 -1
  48. package/dist/migrations/db/migrations/1702295701188-sync-rule-state.d.ts +2 -2
  49. package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js +4 -2
  50. package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js.map +1 -1
  51. package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.d.ts +2 -2
  52. package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.js +4 -2
  53. package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.js.map +1 -1
  54. package/dist/migrations/db/migrations/1727099539247-custom-write-checkpoint-index.d.ts +3 -0
  55. package/dist/migrations/db/migrations/1727099539247-custom-write-checkpoint-index.js +31 -0
  56. package/dist/migrations/db/migrations/1727099539247-custom-write-checkpoint-index.js.map +1 -0
  57. package/dist/migrations/executor.js.map +1 -1
  58. package/dist/migrations/migrations.d.ts +8 -0
  59. package/dist/migrations/migrations.js +19 -7
  60. package/dist/migrations/migrations.js.map +1 -1
  61. package/dist/migrations/store/migration-store.js.map +1 -1
  62. package/dist/modules/AbstractModule.d.ts +26 -0
  63. package/dist/modules/AbstractModule.js +11 -0
  64. package/dist/modules/AbstractModule.js.map +1 -0
  65. package/dist/modules/ModuleManager.d.ts +11 -0
  66. package/dist/modules/ModuleManager.js +32 -0
  67. package/dist/modules/ModuleManager.js.map +1 -0
  68. package/dist/modules/modules-index.d.ts +2 -0
  69. package/dist/modules/modules-index.js +3 -0
  70. package/dist/modules/modules-index.js.map +1 -0
  71. package/dist/replication/AbstractReplicationJob.d.ts +37 -0
  72. package/dist/replication/AbstractReplicationJob.js +51 -0
  73. package/dist/replication/AbstractReplicationJob.js.map +1 -0
  74. package/dist/replication/AbstractReplicator.d.ts +53 -0
  75. package/dist/replication/AbstractReplicator.js +250 -0
  76. package/dist/replication/AbstractReplicator.js.map +1 -0
  77. package/dist/replication/ErrorRateLimiter.d.ts +0 -10
  78. package/dist/replication/ErrorRateLimiter.js +1 -42
  79. package/dist/replication/ErrorRateLimiter.js.map +1 -1
  80. package/dist/replication/ReplicationEngine.d.ts +18 -0
  81. package/dist/replication/ReplicationEngine.js +41 -0
  82. package/dist/replication/ReplicationEngine.js.map +1 -0
  83. package/dist/replication/ReplicationModule.d.ts +51 -0
  84. package/dist/replication/ReplicationModule.js +68 -0
  85. package/dist/replication/ReplicationModule.js.map +1 -0
  86. package/dist/replication/replication-index.d.ts +4 -6
  87. package/dist/replication/replication-index.js +4 -6
  88. package/dist/replication/replication-index.js.map +1 -1
  89. package/dist/routes/RouterEngine.d.ts +42 -0
  90. package/dist/routes/RouterEngine.js +80 -0
  91. package/dist/routes/RouterEngine.js.map +1 -0
  92. package/dist/routes/auth.d.ts +2 -2
  93. package/dist/routes/auth.js +11 -11
  94. package/dist/routes/auth.js.map +1 -1
  95. package/dist/routes/configure-fastify.d.ts +37 -23
  96. package/dist/routes/configure-fastify.js +18 -18
  97. package/dist/routes/configure-fastify.js.map +1 -1
  98. package/dist/routes/configure-rsocket.d.ts +3 -4
  99. package/dist/routes/configure-rsocket.js +7 -4
  100. package/dist/routes/configure-rsocket.js.map +1 -1
  101. package/dist/routes/endpoints/admin.d.ts +30 -0
  102. package/dist/routes/endpoints/admin.js +46 -67
  103. package/dist/routes/endpoints/admin.js.map +1 -1
  104. package/dist/routes/endpoints/checkpointing.js +103 -15
  105. package/dist/routes/endpoints/checkpointing.js.map +1 -1
  106. package/dist/routes/endpoints/socket-route.js +8 -6
  107. package/dist/routes/endpoints/socket-route.js.map +1 -1
  108. package/dist/routes/endpoints/sync-rules.d.ts +1 -1
  109. package/dist/routes/endpoints/sync-rules.js +32 -23
  110. package/dist/routes/endpoints/sync-rules.js.map +1 -1
  111. package/dist/routes/endpoints/sync-stream.d.ts +0 -1
  112. package/dist/routes/endpoints/sync-stream.js +8 -8
  113. package/dist/routes/endpoints/sync-stream.js.map +1 -1
  114. package/dist/routes/hooks.js.map +1 -1
  115. package/dist/routes/route-register.js.map +1 -1
  116. package/dist/routes/router.d.ts +9 -2
  117. package/dist/routes/router.js.map +1 -1
  118. package/dist/routes/routes-index.d.ts +1 -0
  119. package/dist/routes/routes-index.js +1 -0
  120. package/dist/routes/routes-index.js.map +1 -1
  121. package/dist/runner/teardown.js +109 -76
  122. package/dist/runner/teardown.js.map +1 -1
  123. package/dist/storage/BucketStorage.d.ts +86 -36
  124. package/dist/storage/BucketStorage.js +6 -10
  125. package/dist/storage/BucketStorage.js.map +1 -1
  126. package/dist/storage/ChecksumCache.js.map +1 -1
  127. package/dist/storage/MongoBucketStorage.d.ts +7 -11
  128. package/dist/storage/MongoBucketStorage.js +48 -41
  129. package/dist/storage/MongoBucketStorage.js.map +1 -1
  130. package/dist/storage/ReplicationEventPayload.d.ts +14 -0
  131. package/dist/storage/ReplicationEventPayload.js +2 -0
  132. package/dist/storage/ReplicationEventPayload.js.map +1 -0
  133. package/dist/storage/SourceEntity.d.ts +20 -0
  134. package/dist/storage/SourceEntity.js +2 -0
  135. package/dist/storage/SourceEntity.js.map +1 -0
  136. package/dist/storage/SourceTable.d.ts +12 -5
  137. package/dist/storage/SourceTable.js +12 -5
  138. package/dist/storage/SourceTable.js.map +1 -1
  139. package/dist/storage/StorageEngine.d.ts +28 -0
  140. package/dist/storage/StorageEngine.js +45 -0
  141. package/dist/storage/StorageEngine.js.map +1 -0
  142. package/dist/storage/StorageProvider.d.ts +21 -0
  143. package/dist/storage/StorageProvider.js +2 -0
  144. package/dist/storage/StorageProvider.js.map +1 -0
  145. package/dist/storage/WriteCheckpointAPI.d.ts +74 -0
  146. package/dist/storage/WriteCheckpointAPI.js +16 -0
  147. package/dist/storage/WriteCheckpointAPI.js.map +1 -0
  148. package/dist/storage/mongo/MongoBucketBatch.d.ts +24 -5
  149. package/dist/storage/mongo/MongoBucketBatch.js +119 -62
  150. package/dist/storage/mongo/MongoBucketBatch.js.map +1 -1
  151. package/dist/storage/mongo/MongoCompactor.js +20 -3
  152. package/dist/storage/mongo/MongoCompactor.js.map +1 -1
  153. package/dist/storage/mongo/MongoIdSequence.js.map +1 -1
  154. package/dist/storage/mongo/MongoPersistedSyncRulesContent.d.ts +2 -2
  155. package/dist/storage/mongo/MongoPersistedSyncRulesContent.js +2 -2
  156. package/dist/storage/mongo/MongoPersistedSyncRulesContent.js.map +1 -1
  157. package/dist/storage/mongo/MongoStorageProvider.d.ts +5 -0
  158. package/dist/storage/mongo/MongoStorageProvider.js +26 -0
  159. package/dist/storage/mongo/MongoStorageProvider.js.map +1 -0
  160. package/dist/storage/mongo/MongoSyncBucketStorage.d.ts +18 -10
  161. package/dist/storage/mongo/MongoSyncBucketStorage.js +140 -25
  162. package/dist/storage/mongo/MongoSyncBucketStorage.js.map +1 -1
  163. package/dist/storage/mongo/MongoSyncRulesLock.js +1 -1
  164. package/dist/storage/mongo/MongoSyncRulesLock.js.map +1 -1
  165. package/dist/storage/mongo/MongoWriteCheckpointAPI.d.ts +20 -0
  166. package/dist/storage/mongo/MongoWriteCheckpointAPI.js +103 -0
  167. package/dist/storage/mongo/MongoWriteCheckpointAPI.js.map +1 -0
  168. package/dist/storage/mongo/OperationBatch.d.ts +13 -4
  169. package/dist/storage/mongo/OperationBatch.js +25 -7
  170. package/dist/storage/mongo/OperationBatch.js.map +1 -1
  171. package/dist/storage/mongo/PersistedBatch.d.ts +3 -3
  172. package/dist/storage/mongo/PersistedBatch.js +2 -2
  173. package/dist/storage/mongo/PersistedBatch.js.map +1 -1
  174. package/dist/storage/mongo/config.d.ts +19 -0
  175. package/dist/storage/mongo/config.js +26 -0
  176. package/dist/storage/mongo/config.js.map +1 -0
  177. package/dist/storage/mongo/db.d.ts +3 -2
  178. package/dist/storage/mongo/db.js +1 -0
  179. package/dist/storage/mongo/db.js.map +1 -1
  180. package/dist/storage/mongo/models.d.ts +20 -5
  181. package/dist/storage/mongo/models.js.map +1 -1
  182. package/dist/storage/mongo/util.d.ts +12 -1
  183. package/dist/storage/mongo/util.js +50 -2
  184. package/dist/storage/mongo/util.js.map +1 -1
  185. package/dist/storage/storage-index.d.ts +8 -2
  186. package/dist/storage/storage-index.js +8 -2
  187. package/dist/storage/storage-index.js.map +1 -1
  188. package/dist/sync/BroadcastIterable.d.ts +0 -1
  189. package/dist/sync/BroadcastIterable.js.map +1 -1
  190. package/dist/sync/LastValueSink.d.ts +0 -1
  191. package/dist/sync/LastValueSink.js.map +1 -1
  192. package/dist/sync/merge.d.ts +0 -1
  193. package/dist/sync/merge.js.map +1 -1
  194. package/dist/sync/safeRace.js.map +1 -1
  195. package/dist/sync/sync.d.ts +1 -1
  196. package/dist/sync/sync.js +5 -5
  197. package/dist/sync/sync.js.map +1 -1
  198. package/dist/sync/util.d.ts +0 -2
  199. package/dist/sync/util.js.map +1 -1
  200. package/dist/system/ServiceContext.d.ts +37 -0
  201. package/dist/system/ServiceContext.js +48 -0
  202. package/dist/system/ServiceContext.js.map +1 -0
  203. package/dist/system/system-index.d.ts +1 -1
  204. package/dist/system/system-index.js +1 -1
  205. package/dist/system/system-index.js.map +1 -1
  206. package/dist/util/Mutex.js.map +1 -1
  207. package/dist/util/config/collectors/config-collector.js.map +1 -1
  208. package/dist/util/config/collectors/impl/base64-config-collector.js.map +1 -1
  209. package/dist/util/config/collectors/impl/filesystem-config-collector.js.map +1 -1
  210. package/dist/util/config/compound-config-collector.d.ts +9 -2
  211. package/dist/util/config/compound-config-collector.js +31 -23
  212. package/dist/util/config/compound-config-collector.js.map +1 -1
  213. package/dist/util/config/sync-rules/impl/base64-sync-rules-collector.js.map +1 -1
  214. package/dist/util/config/sync-rules/impl/filesystem-sync-rules-collector.js.map +1 -1
  215. package/dist/util/config/sync-rules/impl/inline-sync-rules-collector.js.map +1 -1
  216. package/dist/util/config/sync-rules/sync-rules-provider.d.ts +9 -0
  217. package/dist/util/config/sync-rules/sync-rules-provider.js +15 -0
  218. package/dist/util/config/sync-rules/sync-rules-provider.js.map +1 -0
  219. package/dist/util/config/types.d.ts +7 -4
  220. package/dist/util/config/types.js.map +1 -1
  221. package/dist/util/config.d.ts +3 -4
  222. package/dist/util/config.js +5 -20
  223. package/dist/util/config.js.map +1 -1
  224. package/dist/util/memory-tracking.js.map +1 -1
  225. package/dist/util/secs.js.map +1 -1
  226. package/dist/util/util-index.d.ts +3 -6
  227. package/dist/util/util-index.js +3 -6
  228. package/dist/util/util-index.js.map +1 -1
  229. package/dist/util/utils.d.ts +10 -7
  230. package/dist/util/utils.js +36 -25
  231. package/dist/util/utils.js.map +1 -1
  232. package/package.json +8 -12
  233. package/src/api/RouteAPI.ts +78 -0
  234. package/src/api/api-index.ts +1 -0
  235. package/src/api/diagnostics.ts +18 -70
  236. package/src/api/schema.ts +18 -90
  237. package/src/auth/KeyStore.ts +9 -6
  238. package/src/auth/RemoteJWKSCollector.ts +4 -1
  239. package/src/auth/StaticSupabaseKeyCollector.ts +31 -0
  240. package/src/auth/auth-index.ts +1 -1
  241. package/src/db/mongo.ts +5 -3
  242. package/src/entry/cli-entry.ts +3 -2
  243. package/src/entry/commands/compact-action.ts +24 -12
  244. package/src/entry/commands/migrate-action.ts +5 -8
  245. package/src/entry/commands/teardown-action.ts +2 -2
  246. package/src/index.ts +5 -2
  247. package/src/metrics/Metrics.ts +6 -16
  248. package/src/migrations/db/migrations/1684951997326-init.ts +9 -4
  249. package/src/migrations/db/migrations/1702295701188-sync-rule-state.ts +7 -4
  250. package/src/migrations/db/migrations/1711543888062-write-checkpoint-index.ts +6 -4
  251. package/src/migrations/db/migrations/1727099539247-custom-write-checkpoint-index.ts +37 -0
  252. package/src/migrations/migrations.ts +24 -8
  253. package/src/modules/AbstractModule.ts +37 -0
  254. package/src/modules/ModuleManager.ts +34 -0
  255. package/src/modules/modules-index.ts +2 -0
  256. package/src/replication/AbstractReplicationJob.ts +79 -0
  257. package/src/replication/AbstractReplicator.ts +228 -0
  258. package/src/replication/ErrorRateLimiter.ts +0 -44
  259. package/src/replication/ReplicationEngine.ts +43 -0
  260. package/src/replication/ReplicationModule.ts +122 -0
  261. package/src/replication/replication-index.ts +4 -6
  262. package/src/routes/RouterEngine.ts +120 -0
  263. package/src/routes/auth.ts +21 -12
  264. package/src/routes/configure-fastify.ts +26 -27
  265. package/src/routes/configure-rsocket.ts +13 -8
  266. package/src/routes/endpoints/admin.ts +72 -76
  267. package/src/routes/endpoints/checkpointing.ts +51 -11
  268. package/src/routes/endpoints/socket-route.ts +10 -6
  269. package/src/routes/endpoints/sync-rules.ts +41 -25
  270. package/src/routes/endpoints/sync-stream.ts +8 -8
  271. package/src/routes/router.ts +8 -3
  272. package/src/routes/routes-index.ts +1 -0
  273. package/src/runner/teardown.ts +50 -88
  274. package/src/storage/BucketStorage.ts +103 -41
  275. package/src/storage/MongoBucketStorage.ts +65 -53
  276. package/src/storage/ReplicationEventPayload.ts +16 -0
  277. package/src/storage/SourceEntity.ts +22 -0
  278. package/src/storage/SourceTable.ts +14 -7
  279. package/src/storage/StorageEngine.ts +62 -0
  280. package/src/storage/StorageProvider.ts +27 -0
  281. package/src/storage/WriteCheckpointAPI.ts +85 -0
  282. package/src/storage/mongo/MongoBucketBatch.ts +164 -84
  283. package/src/storage/mongo/MongoCompactor.ts +25 -4
  284. package/src/storage/mongo/MongoPersistedSyncRulesContent.ts +7 -4
  285. package/src/storage/mongo/MongoStorageProvider.ts +31 -0
  286. package/src/storage/mongo/MongoSyncBucketStorage.ts +118 -41
  287. package/src/storage/mongo/MongoSyncRulesLock.ts +7 -3
  288. package/src/storage/mongo/MongoWriteCheckpointAPI.ts +151 -0
  289. package/src/storage/mongo/OperationBatch.ts +28 -12
  290. package/src/storage/mongo/PersistedBatch.ts +10 -6
  291. package/src/storage/mongo/config.ts +40 -0
  292. package/src/storage/mongo/db.ts +4 -1
  293. package/src/storage/mongo/models.ts +21 -5
  294. package/src/storage/mongo/util.ts +48 -3
  295. package/src/storage/storage-index.ts +8 -2
  296. package/src/sync/sync.ts +7 -4
  297. package/src/sync/util.ts +0 -1
  298. package/src/system/ServiceContext.ts +68 -0
  299. package/src/system/system-index.ts +1 -1
  300. package/src/util/config/compound-config-collector.ts +48 -30
  301. package/src/util/config/sync-rules/sync-rules-provider.ts +18 -0
  302. package/src/util/config/types.ts +7 -5
  303. package/src/util/config.ts +6 -23
  304. package/src/util/util-index.ts +3 -6
  305. package/src/util/utils.ts +48 -41
  306. package/test/src/__snapshots__/sync.test.ts.snap +14 -14
  307. package/test/src/auth.test.ts +7 -7
  308. package/test/src/broadcast_iterable.test.ts +1 -1
  309. package/test/src/compacting.test.ts +50 -40
  310. package/test/src/data_storage.test.ts +382 -202
  311. package/test/src/env.ts +1 -3
  312. package/test/src/merge_iterable.test.ts +1 -6
  313. package/test/src/routes/probes.integration.test.ts +34 -30
  314. package/test/src/setup.ts +1 -1
  315. package/test/src/stream_utils.ts +42 -0
  316. package/test/src/sync.test.ts +115 -39
  317. package/test/src/util.ts +48 -51
  318. package/test/tsconfig.json +1 -1
  319. package/tsconfig.tsbuildinfo +1 -1
  320. package/vitest.config.ts +7 -1
  321. package/dist/auth/SupabaseKeyCollector.d.ts +0 -22
  322. package/dist/auth/SupabaseKeyCollector.js +0 -61
  323. package/dist/auth/SupabaseKeyCollector.js.map +0 -1
  324. package/dist/replication/PgRelation.d.ts +0 -16
  325. package/dist/replication/PgRelation.js +0 -26
  326. package/dist/replication/PgRelation.js.map +0 -1
  327. package/dist/replication/WalConnection.d.ts +0 -34
  328. package/dist/replication/WalConnection.js +0 -190
  329. package/dist/replication/WalConnection.js.map +0 -1
  330. package/dist/replication/WalStream.d.ts +0 -57
  331. package/dist/replication/WalStream.js +0 -519
  332. package/dist/replication/WalStream.js.map +0 -1
  333. package/dist/replication/WalStreamManager.d.ts +0 -30
  334. package/dist/replication/WalStreamManager.js +0 -198
  335. package/dist/replication/WalStreamManager.js.map +0 -1
  336. package/dist/replication/WalStreamRunner.d.ts +0 -38
  337. package/dist/replication/WalStreamRunner.js +0 -155
  338. package/dist/replication/WalStreamRunner.js.map +0 -1
  339. package/dist/replication/util.d.ts +0 -9
  340. package/dist/replication/util.js +0 -62
  341. package/dist/replication/util.js.map +0 -1
  342. package/dist/system/CorePowerSyncSystem.d.ts +0 -23
  343. package/dist/system/CorePowerSyncSystem.js +0 -52
  344. package/dist/system/CorePowerSyncSystem.js.map +0 -1
  345. package/dist/util/PgManager.d.ts +0 -24
  346. package/dist/util/PgManager.js +0 -55
  347. package/dist/util/PgManager.js.map +0 -1
  348. package/dist/util/migration_lib.d.ts +0 -11
  349. package/dist/util/migration_lib.js +0 -64
  350. package/dist/util/migration_lib.js.map +0 -1
  351. package/dist/util/pgwire_utils.d.ts +0 -24
  352. package/dist/util/pgwire_utils.js +0 -117
  353. package/dist/util/pgwire_utils.js.map +0 -1
  354. package/dist/util/populate_test_data.d.ts +0 -8
  355. package/dist/util/populate_test_data.js +0 -65
  356. package/dist/util/populate_test_data.js.map +0 -1
  357. package/src/auth/SupabaseKeyCollector.ts +0 -67
  358. package/src/replication/PgRelation.ts +0 -42
  359. package/src/replication/WalConnection.ts +0 -227
  360. package/src/replication/WalStream.ts +0 -631
  361. package/src/replication/WalStreamManager.ts +0 -213
  362. package/src/replication/WalStreamRunner.ts +0 -180
  363. package/src/replication/util.ts +0 -76
  364. package/src/system/CorePowerSyncSystem.ts +0 -64
  365. package/src/util/PgManager.ts +0 -64
  366. package/src/util/migration_lib.ts +0 -79
  367. package/src/util/pgwire_utils.ts +0 -139
  368. package/src/util/populate_test_data.ts +0 -78
  369. package/test/src/__snapshots__/pg_test.test.ts.snap +0 -256
  370. package/test/src/large_batch.test.ts +0 -194
  371. package/test/src/pg_test.test.ts +0 -450
  372. package/test/src/schema_changes.test.ts +0 -545
  373. package/test/src/slow_tests.test.ts +0 -338
  374. package/test/src/validation.test.ts +0 -63
  375. package/test/src/wal_stream.test.ts +0 -319
  376. package/test/src/wal_stream_utils.ts +0 -156
@@ -1,5 +1,14 @@
1
- import * as bson from 'bson';
2
1
  import { SqliteJsonValue } from '@powersync/service-sync-rules';
2
+ import * as bson from 'bson';
3
+
4
+ /**
5
+ * Replica id uniquely identifying a row on the source database.
6
+ *
7
+ * Can be any value serializable to BSON.
8
+ *
9
+ * If the value is an entire document, the data serialized to a v5 UUID may be a good choice here.
10
+ */
11
+ export type ReplicaId = bson.UUID | bson.Document | any;
3
12
 
4
13
  export interface SourceKey {
5
14
  /** group_id */
@@ -7,7 +16,7 @@ export interface SourceKey {
7
16
  /** source table id */
8
17
  t: bson.ObjectId;
9
18
  /** source key */
10
- k: bson.UUID;
19
+ k: ReplicaId;
11
20
  }
12
21
 
13
22
  export interface BucketDataKey {
@@ -43,7 +52,7 @@ export interface BucketDataDocument {
43
52
  _id: BucketDataKey;
44
53
  op: OpType;
45
54
  source_table?: bson.ObjectId;
46
- source_key?: bson.UUID;
55
+ source_key?: ReplicaId;
47
56
  table?: string;
48
57
  row_id?: string;
49
58
  checksum: number;
@@ -57,11 +66,11 @@ export interface SourceTableDocument {
57
66
  _id: bson.ObjectId;
58
67
  group_id: number;
59
68
  connection_id: number;
60
- relation_id: number | undefined;
69
+ relation_id: number | string | undefined;
61
70
  schema_name: string;
62
71
  table_name: string;
63
72
  replica_id_columns: string[] | null;
64
- replica_id_columns2: { name: string; type_oid: number }[] | undefined;
73
+ replica_id_columns2: { name: string; type_oid?: number; type?: string }[] | undefined;
65
74
  snapshot_done: boolean | undefined;
66
75
  }
67
76
 
@@ -150,6 +159,13 @@ export interface SyncRuleDocument {
150
159
  content: string;
151
160
  }
152
161
 
162
+ export interface CustomWriteCheckpointDocument {
163
+ _id: bson.ObjectId;
164
+ user_id: string;
165
+ checkpoint: bigint;
166
+ sync_rules_id: number;
167
+ }
168
+
153
169
  export interface WriteCheckpointDocument {
154
170
  _id: bson.ObjectId;
155
171
  user_id: string;
@@ -2,9 +2,10 @@ import { SqliteJsonValue } from '@powersync/service-sync-rules';
2
2
  import * as bson from 'bson';
3
3
  import * as crypto from 'crypto';
4
4
  import * as mongo from 'mongodb';
5
+ import * as uuid from 'uuid';
5
6
  import { OplogEntry } from '../../util/protocol-types.js';
6
- import { timestampToOpId } from '../../util/utils.js';
7
- import { BucketDataDocument } from './models.js';
7
+ import { ID_NAMESPACE, timestampToOpId } from '../../util/utils.js';
8
+ import { BucketDataDocument, ReplicaId } from './models.js';
8
9
 
9
10
  /**
10
11
  * Lookup serialization must be number-agnostic. I.e. normalize numbers, instead of preserving numbers.
@@ -98,7 +99,7 @@ export function mapOpEntry(row: BucketDataDocument): OplogEntry {
98
99
  object_type: row.table,
99
100
  object_id: row.row_id,
100
101
  checksum: Number(row.checksum),
101
- subkey: `${row.source_table}/${row.source_key!.toHexString()}`,
102
+ subkey: replicaIdToSubkey(row.source_table!, row.source_key!),
102
103
  data: row.data
103
104
  };
104
105
  } else {
@@ -111,3 +112,47 @@ export function mapOpEntry(row: BucketDataDocument): OplogEntry {
111
112
  };
112
113
  }
113
114
  }
115
+
116
+ /**
117
+ * Returns true if two ReplicaId values are the same (serializes to the same BSON value).
118
+ */
119
+ export function replicaIdEquals(a: ReplicaId, b: ReplicaId) {
120
+ if (a === b) {
121
+ return true;
122
+ } else if (typeof a == 'string' && typeof b == 'string') {
123
+ return a == b;
124
+ } else if (isUUID(a) && isUUID(b)) {
125
+ return a.equals(b);
126
+ } else if (a == null && b == null) {
127
+ return true;
128
+ } else if (a != null || b != null) {
129
+ return false;
130
+ } else {
131
+ // There are many possible primitive values, this covers them all
132
+ return (bson.serialize({ id: a }) as Buffer).equals(bson.serialize({ id: b }));
133
+ }
134
+ }
135
+
136
+ export function replicaIdToSubkey(table: bson.ObjectId, id: ReplicaId): string {
137
+ if (isUUID(id)) {
138
+ // Special case for UUID for backwards-compatiblity
139
+ return `${table.toHexString()}/${id.toHexString()}`;
140
+ } else {
141
+ // Hashed UUID from the table and id
142
+ const repr = bson.serialize({ table, id });
143
+ return uuid.v5(repr, ID_NAMESPACE);
144
+ }
145
+ }
146
+
147
+ /**
148
+ * True if this is a bson.UUID.
149
+ *
150
+ * Works even with multiple copies of the bson package.
151
+ */
152
+ export function isUUID(value: any): value is bson.UUID {
153
+ if (value == null || typeof value != 'object') {
154
+ return false;
155
+ }
156
+ const uuid = value as bson.UUID;
157
+ return uuid._bsontype == 'Binary' && uuid.sub_type == bson.Binary.SUBTYPE_UUID;
158
+ }
@@ -1,15 +1,21 @@
1
- export * from './SourceTable.js';
2
- export * from './MongoBucketStorage.js';
3
1
  export * from './BucketStorage.js';
2
+ export * from './MongoBucketStorage.js';
3
+ export * from './ReplicationEventPayload.js';
4
+ export * from './SourceEntity.js';
5
+ export * from './SourceTable.js';
6
+ export * from './StorageEngine.js';
4
7
 
8
+ export * from './mongo/config.js';
5
9
  export * from './mongo/db.js';
6
10
  export * from './mongo/models.js';
7
11
  export * from './mongo/MongoBucketBatch.js';
8
12
  export * from './mongo/MongoIdSequence.js';
9
13
  export * from './mongo/MongoPersistedSyncRules.js';
10
14
  export * from './mongo/MongoPersistedSyncRulesContent.js';
15
+ export * from './mongo/MongoStorageProvider.js';
11
16
  export * from './mongo/MongoSyncBucketStorage.js';
12
17
  export * from './mongo/MongoSyncRulesLock.js';
13
18
  export * from './mongo/OperationBatch.js';
14
19
  export * from './mongo/PersistedBatch.js';
15
20
  export * from './mongo/util.js';
21
+ export * from './WriteCheckpointAPI.js';
package/src/sync/sync.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { JSONBig, JsonContainer } from '@powersync/service-jsonbig';
2
2
  import { RequestParameters } from '@powersync/service-sync-rules';
3
3
  import { Semaphore, withTimeout } from 'async-mutex';
4
+
4
5
  import { AbortError } from 'ix/aborterror.js';
5
6
 
6
7
  import * as auth from '../auth/auth-index.js';
@@ -35,6 +36,7 @@ export interface SyncStreamParameters {
35
36
  params: util.StreamingSyncRequest;
36
37
  syncParams: RequestParameters;
37
38
  token: auth.JwtPayload;
39
+ parseOptions: storage.ParseSyncRulesOptions;
38
40
  /**
39
41
  * If this signal is aborted, the stream response ends as soon as possible, without error.
40
42
  */
@@ -47,7 +49,7 @@ export interface SyncStreamParameters {
47
49
  export async function* streamResponse(
48
50
  options: SyncStreamParameters
49
51
  ): AsyncIterable<util.StreamingSyncLine | string | null> {
50
- const { storage, params, syncParams, token, tokenStreamOptions, tracker, signal } = options;
52
+ const { storage, params, syncParams, token, tokenStreamOptions, tracker, signal, parseOptions } = options;
51
53
  // We also need to be able to abort, so we create our own controller.
52
54
  const controller = new AbortController();
53
55
  if (signal) {
@@ -63,7 +65,7 @@ export async function* streamResponse(
63
65
  }
64
66
  }
65
67
  const ki = tokenStream(token, controller.signal, tokenStreamOptions);
66
- const stream = streamResponseInner(storage, params, syncParams, tracker, controller.signal);
68
+ const stream = streamResponseInner(storage, params, syncParams, tracker, parseOptions, controller.signal);
67
69
  // Merge the two streams, and abort as soon as one of the streams end.
68
70
  const merged = mergeAsyncIterables([stream, ki], controller.signal);
69
71
 
@@ -87,6 +89,7 @@ async function* streamResponseInner(
87
89
  params: util.StreamingSyncRequest,
88
90
  syncParams: RequestParameters,
89
91
  tracker: RequestTracker,
92
+ parseOptions: storage.ParseSyncRulesOptions,
90
93
  signal: AbortSignal
91
94
  ): AsyncGenerator<util.StreamingSyncLine | string | null> {
92
95
  // Bucket state of bucket id -> op_id.
@@ -115,9 +118,9 @@ async function* streamResponseInner(
115
118
  // Sync rules deleted in the meantime - try again with the next checkpoint.
116
119
  continue;
117
120
  }
118
- const sync_rules = storage.sync_rules;
121
+ const syncRules = storage.getParsedSyncRules(parseOptions);
119
122
 
120
- const allBuckets = await sync_rules.queryBucketIds({
123
+ const allBuckets = await syncRules.queryBucketIds({
121
124
  getParameterSets(lookups) {
122
125
  return storage.getParameterSets(checkpoint, lookups);
123
126
  },
package/src/sync/util.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import * as timers from 'timers/promises';
2
2
 
3
3
  import * as util from '../util/util-index.js';
4
- import { Metrics } from '../metrics/Metrics.js';
5
4
  import { RequestTracker } from './RequestTracker.js';
6
5
 
7
6
  export type TokenStreamOptions = {
@@ -0,0 +1,68 @@
1
+ import { LifeCycledSystem, ServiceIdentifier, container } from '@powersync/lib-services-framework';
2
+
3
+ import * as metrics from '../metrics/Metrics.js';
4
+ import * as replication from '../replication/replication-index.js';
5
+ import * as routes from '../routes/routes-index.js';
6
+ import * as storage from '../storage/storage-index.js';
7
+ import * as utils from '../util/util-index.js';
8
+
9
+ export interface ServiceContext {
10
+ configuration: utils.ResolvedPowerSyncConfig;
11
+ lifeCycleEngine: LifeCycledSystem;
12
+ metrics: metrics.Metrics | null;
13
+ replicationEngine: replication.ReplicationEngine | null;
14
+ routerEngine: routes.RouterEngine | null;
15
+ storageEngine: storage.StorageEngine;
16
+ }
17
+
18
+ /**
19
+ * Context which allows for registering and getting implementations
20
+ * of various service engines.
21
+ * This controls registering, initializing and the lifecycle of various services.
22
+ */
23
+ export class ServiceContextContainer implements ServiceContext {
24
+ lifeCycleEngine: LifeCycledSystem;
25
+ storageEngine: storage.StorageEngine;
26
+
27
+ constructor(public configuration: utils.ResolvedPowerSyncConfig) {
28
+ this.lifeCycleEngine = new LifeCycledSystem();
29
+
30
+ this.storageEngine = new storage.StorageEngine({
31
+ configuration
32
+ });
33
+ this.lifeCycleEngine.withLifecycle(this.storageEngine, {
34
+ start: (storageEngine) => storageEngine.start(),
35
+ stop: (storageEngine) => storageEngine.shutDown()
36
+ });
37
+
38
+ // Mongo storage is available as an option by default TODO: Consider moving this to a Mongo Storage Module
39
+ this.storageEngine.registerProvider(new storage.MongoStorageProvider());
40
+ }
41
+
42
+ get replicationEngine(): replication.ReplicationEngine | null {
43
+ return container.getOptional(replication.ReplicationEngine);
44
+ }
45
+
46
+ get routerEngine(): routes.RouterEngine | null {
47
+ return container.getOptional(routes.RouterEngine);
48
+ }
49
+
50
+ get metrics(): metrics.Metrics | null {
51
+ return container.getOptional(metrics.Metrics);
52
+ }
53
+
54
+ /**
55
+ * Allows for registering core and generic implementations of services/helpers.
56
+ * This uses the framework container under the hood.
57
+ */
58
+ register<T>(identifier: ServiceIdentifier<T>, implementation: T) {
59
+ container.register(identifier, implementation);
60
+ }
61
+
62
+ /**
63
+ * Gets the implementation of an identifiable service.
64
+ */
65
+ get<T>(identifier: ServiceIdentifier<T>) {
66
+ return container.getImplementation(identifier);
67
+ }
68
+ }
@@ -1 +1 @@
1
- export * from './CorePowerSyncSystem.js';
1
+ export * from './ServiceContext.js';
@@ -1,17 +1,15 @@
1
- import { configFile, normalizeConnection } from '@powersync/service-types';
2
- import { ConfigCollector } from './collectors/config-collector.js';
3
- import { ResolvedConnection, ResolvedPowerSyncConfig, RunnerConfig, SyncRulesConfig } from './types.js';
1
+ import { logger } from '@powersync/lib-services-framework';
2
+ import { configFile } from '@powersync/service-types';
4
3
  import * as auth from '../../auth/auth-index.js';
5
- import { SyncRulesCollector } from './sync-rules/sync-collector.js';
4
+ import { ConfigCollector } from './collectors/config-collector.js';
6
5
  import { Base64ConfigCollector } from './collectors/impl/base64-config-collector.js';
6
+ import { FallbackConfigCollector } from './collectors/impl/fallback-config-collector.js';
7
7
  import { FileSystemConfigCollector } from './collectors/impl/filesystem-config-collector.js';
8
8
  import { Base64SyncRulesCollector } from './sync-rules/impl/base64-sync-rules-collector.js';
9
- import { InlineSyncRulesCollector } from './sync-rules/impl/inline-sync-rules-collector.js';
10
9
  import { FileSystemSyncRulesCollector } from './sync-rules/impl/filesystem-sync-rules-collector.js';
11
- import { FallbackConfigCollector } from './collectors/impl/fallback-config-collector.js';
12
- import { logger } from '@powersync/lib-services-framework';
13
-
14
- const POWERSYNC_DEV_KID = 'powersync-dev';
10
+ import { InlineSyncRulesCollector } from './sync-rules/impl/inline-sync-rules-collector.js';
11
+ import { SyncRulesCollector } from './sync-rules/sync-collector.js';
12
+ import { ResolvedPowerSyncConfig, RunnerConfig, SyncRulesConfig } from './types.js';
15
13
 
16
14
  export type CompoundConfigCollectorOptions = {
17
15
  /**
@@ -28,6 +26,17 @@ export type CompoundConfigCollectorOptions = {
28
26
  syncRulesCollectors: SyncRulesCollector[];
29
27
  };
30
28
 
29
+ export type ConfigCollectedEvent = {
30
+ base_config: configFile.PowerSyncConfig;
31
+ resolved_config: ResolvedPowerSyncConfig;
32
+ };
33
+
34
+ export type ConfigCollectorListener = {
35
+ configCollected?: (event: ConfigCollectedEvent) => Promise<void>;
36
+ };
37
+
38
+ const POWERSYNC_DEV_KID = 'powersync-dev';
39
+
31
40
  const DEFAULT_COLLECTOR_OPTIONS: CompoundConfigCollectorOptions = {
32
41
  configCollectors: [new Base64ConfigCollector(), new FileSystemConfigCollector(), new FallbackConfigCollector()],
33
42
  syncRulesCollectors: [
@@ -43,34 +52,38 @@ export class CompoundConfigCollector {
43
52
  /**
44
53
  * Collects and resolves base config
45
54
  */
46
- async collectConfig(runner_config: RunnerConfig = {}): Promise<ResolvedPowerSyncConfig> {
47
- const baseConfig = await this.collectBaseConfig(runner_config);
55
+ async collectConfig(runnerConfig: RunnerConfig = {}): Promise<ResolvedPowerSyncConfig> {
56
+ const baseConfig = await this.collectBaseConfig(runnerConfig);
48
57
 
49
- const connections = baseConfig.replication?.connections ?? [];
50
- if (connections.length > 1) {
51
- throw new Error('Only a single replication connection is supported currently');
58
+ const dataSources = baseConfig.replication?.connections ?? [];
59
+ if (dataSources.length > 1) {
60
+ throw new Error('Only a single replication data source is supported currently');
52
61
  }
53
62
 
54
- const mapped = connections.map((c) => {
55
- const conf: ResolvedConnection = {
56
- type: 'postgresql' as const,
57
- ...normalizeConnection(c),
58
- debug_api: c.debug_api ?? false
59
- };
60
-
61
- return conf;
62
- });
63
-
64
63
  const collectors = new auth.CompoundKeyCollector();
65
64
  const keyStore = new auth.KeyStore(collectors);
66
65
 
67
66
  const inputKeys = baseConfig.client_auth?.jwks?.keys ?? [];
68
67
  const staticCollector = await auth.StaticKeyCollector.importKeys(inputKeys);
69
-
70
68
  collectors.add(staticCollector);
71
69
 
72
- if (baseConfig.client_auth?.supabase && mapped.length > 0) {
73
- collectors.add(new auth.CachedKeyCollector(new auth.SupabaseKeyCollector(mapped[0])));
70
+ if (baseConfig.client_auth?.supabase && baseConfig.client_auth?.supabase_jwt_secret != null) {
71
+ // This replaces the old SupabaseKeyCollector, with a statically-configured key.
72
+ // You can get the same effect with manual HS256 key configuration, but this
73
+ // makes the config simpler.
74
+ // We also a custom audience ("authenticated"), increased max lifetime (1 week),
75
+ // and auto base64-url-encode the key.
76
+ collectors.add(
77
+ await auth.StaticSupabaseKeyCollector.importKeys([
78
+ {
79
+ kty: 'oct',
80
+ alg: 'HS256',
81
+ // In this case, the key is not base64-encoded yet.
82
+ k: Buffer.from(baseConfig.client_auth.supabase_jwt_secret, 'utf8').toString('base64url'),
83
+ kid: undefined // Wildcard kid - any kid can match
84
+ }
85
+ ])
86
+ );
74
87
  }
75
88
 
76
89
  let jwks_uris = baseConfig.client_auth?.jwks_uri ?? [];
@@ -93,12 +106,13 @@ export class CompoundConfigCollector {
93
106
  devKey = await auth.KeySpec.importKey(baseDevKey);
94
107
  }
95
108
 
96
- const sync_rules = await this.collectSyncRules(baseConfig, runner_config);
109
+ const sync_rules = await this.collectSyncRules(baseConfig, runnerConfig);
97
110
 
98
111
  let jwt_audiences: string[] = baseConfig.client_auth?.audience ?? [];
99
112
 
100
113
  let config: ResolvedPowerSyncConfig = {
101
- connection: mapped[0],
114
+ base_config: baseConfig,
115
+ connections: baseConfig.replication?.connections || [],
102
116
  storage: baseConfig.storage,
103
117
  client_keystore: keyStore,
104
118
  // Dev tokens only use the static keys, no external key sources
@@ -124,8 +138,12 @@ export class CompoundConfigCollector {
124
138
  internal_service_endpoint:
125
139
  baseConfig.telemetry?.internal_service_endpoint ?? 'https://pulse.journeyapps.com/v1/metrics'
126
140
  },
127
- slot_name_prefix: connections[0]?.slot_name_prefix ?? 'powersync_'
141
+ // TODO maybe move this out of the connection or something
142
+ // slot_name_prefix: connections[0]?.slot_name_prefix ?? 'powersync_'
143
+ slot_name_prefix: 'powersync_',
144
+ parameters: baseConfig.parameters ?? {}
128
145
  };
146
+
129
147
  return config;
130
148
  }
131
149
 
@@ -0,0 +1,18 @@
1
+ import { SyncRulesConfig } from '../types.js';
2
+ import fs from 'fs/promises';
3
+
4
+ export interface SyncRulesProvider {
5
+ get(): Promise<string | undefined>;
6
+ }
7
+
8
+ export class ConfigurationFileSyncRulesProvider implements SyncRulesProvider {
9
+ constructor(private config: SyncRulesConfig) {}
10
+
11
+ async get(): Promise<string | undefined> {
12
+ if (this.config.content) {
13
+ return this.config.content;
14
+ } else if (this.config.path) {
15
+ return await fs.readFile(this.config.path, 'utf-8');
16
+ }
17
+ }
18
+ }
@@ -1,4 +1,6 @@
1
- import { NormalizedPostgresConnection, configFile } from '@powersync/service-types';
1
+ import { configFile } from '@powersync/service-types';
2
+ import { PowerSyncConfig } from '@powersync/service-types/src/config/PowerSyncConfig.js';
3
+ import { CompoundKeyCollector } from '../../auth/CompoundKeyCollector.js';
2
4
  import { KeySpec } from '../../auth/KeySpec.js';
3
5
  import { KeyStore } from '../../auth/KeyStore.js';
4
6
 
@@ -20,8 +22,6 @@ export type MigrationContext = {
20
22
 
21
23
  export type Runner = (config: RunnerConfig) => Promise<void>;
22
24
 
23
- export type ResolvedConnection = configFile.PostgresConnection & NormalizedPostgresConnection;
24
-
25
25
  export type SyncRulesConfig = {
26
26
  present: boolean;
27
27
  content?: string;
@@ -29,7 +29,8 @@ export type SyncRulesConfig = {
29
29
  };
30
30
 
31
31
  export type ResolvedPowerSyncConfig = {
32
- connection?: ResolvedConnection;
32
+ base_config: PowerSyncConfig;
33
+ connections?: configFile.DataSourceConfig[];
33
34
  storage: configFile.StorageConfig;
34
35
  dev: {
35
36
  demo_auth: boolean;
@@ -41,7 +42,7 @@ export type ResolvedPowerSyncConfig = {
41
42
  */
42
43
  dev_key?: KeySpec;
43
44
  };
44
- client_keystore: KeyStore;
45
+ client_keystore: KeyStore<CompoundKeyCollector>;
45
46
  /**
46
47
  * Keystore for development tokens.
47
48
  */
@@ -63,4 +64,5 @@ export type ResolvedPowerSyncConfig = {
63
64
 
64
65
  /** Prefix for postgres replication slot names. May eventually be connection-specific. */
65
66
  slot_name_prefix: string;
67
+ parameters: Record<string, number | string | boolean | null>;
66
68
  };
@@ -1,31 +1,14 @@
1
1
  import * as fs from 'fs/promises';
2
- import { baseUri } from '@powersync/service-types';
3
2
 
4
- import { ResolvedConnection, ResolvedPowerSyncConfig, RunnerConfig } from './config/types.js';
5
- import { CompoundConfigCollector } from './config/compound-config-collector.js';
3
+ import { container } from '@powersync/lib-services-framework';
4
+ import { ResolvedPowerSyncConfig, RunnerConfig } from './config/types.js';
5
+ import { CompoundConfigCollector } from './util-index.js';
6
6
 
7
7
  /**
8
- * Build a single URI from full postgres credentials.
8
+ * Loads the resolved config using the registered config collector
9
9
  */
10
- export function buildDemoPgUri(options: ResolvedConnection): string {
11
- if (!options.debug_api) {
12
- throw new Error('Not supported');
13
- }
14
-
15
- const uri = new URL(baseUri(options));
16
- uri.username = options.username;
17
- uri.password = options.password;
18
- if (options.sslmode != 'disable') {
19
- // verify-full is tricky to actually use on a client, since they won't have the cert
20
- // Just use "require" by default
21
- // uri.searchParams.set('sslmode', options.sslmode);
22
- uri.searchParams.set('sslmode', 'require');
23
- }
24
- return uri.toString();
25
- }
26
-
27
- export function loadConfig(runnerConfig: RunnerConfig = {}) {
28
- const collector = new CompoundConfigCollector();
10
+ export async function loadConfig(runnerConfig: RunnerConfig) {
11
+ const collector = container.getImplementation(CompoundConfigCollector);
29
12
  return collector.collectConfig(runnerConfig);
30
13
  }
31
14
 
@@ -1,25 +1,22 @@
1
1
  export * from './alerting.js';
2
2
  export * from './env.js';
3
3
  export * from './memory-tracking.js';
4
- export * from './migration_lib.js';
5
4
  export * from './Mutex.js';
6
- export * from './PgManager.js';
7
- export * from './pgwire_utils.js';
8
- export * from './populate_test_data.js';
9
5
  export * from './protocol-types.js';
10
6
  export * from './secs.js';
11
7
  export * from './utils.js';
12
8
 
13
9
  export * from './config.js';
14
- export * from './config/types.js';
15
10
  export * from './config/compound-config-collector.js';
11
+ export * from './config/types.js';
16
12
 
17
13
  export * from './config/collectors/config-collector.js';
18
14
  export * from './config/collectors/impl/base64-config-collector.js';
19
15
  export * from './config/collectors/impl/fallback-config-collector.js';
20
16
  export * from './config/collectors/impl/filesystem-config-collector.js';
21
17
 
22
- export * from './config/sync-rules/sync-collector.js';
23
18
  export * from './config/sync-rules/impl/base64-sync-rules-collector.js';
24
19
  export * from './config/sync-rules/impl/filesystem-sync-rules-collector.js';
25
20
  export * from './config/sync-rules/impl/inline-sync-rules-collector.js';
21
+ export * from './config/sync-rules/sync-collector.js';
22
+ export * from './config/sync-rules/sync-rules-provider.js';
package/src/util/utils.ts CHANGED
@@ -1,15 +1,21 @@
1
+ import * as sync_rules from '@powersync/service-sync-rules';
2
+ import * as bson from 'bson';
1
3
  import crypto from 'crypto';
4
+ import * as uuid from 'uuid';
5
+ import { BucketChecksum, OpId } from './protocol-types.js';
2
6
 
3
- import { logger } from '@powersync/lib-services-framework';
4
- import * as pgwire from '@powersync/service-jpgwire';
5
- import { pgwireRows } from '@powersync/service-jpgwire';
6
- import { PartialChecksum } from '../storage/ChecksumCache.js';
7
7
  import * as storage from '../storage/storage-index.js';
8
- import { retriedQuery } from './pgwire_utils.js';
9
- import { BucketChecksum, OpId } from './protocol-types.js';
8
+
9
+ import { PartialChecksum } from '../storage/ChecksumCache.js';
10
10
 
11
11
  export type ChecksumMap = Map<string, BucketChecksum>;
12
12
 
13
+ export const ID_NAMESPACE = 'a396dd91-09fc-4017-a28d-3df722f651e9';
14
+
15
+ export function escapeIdentifier(identifier: string) {
16
+ return `"${identifier.replace(/"/g, '""').replace(/\./g, '"."')}"`;
17
+ }
18
+
13
19
  export function hashData(type: string, id: string, data: string): number {
14
20
  const hash = crypto.createHash('sha256');
15
21
  hash.update(`put.${type}.${id}.${data}`);
@@ -83,49 +89,50 @@ export function addBucketChecksums(a: BucketChecksum, b: PartialChecksum | null)
83
89
  }
84
90
  }
85
91
 
86
- export async function getClientCheckpoint(
87
- db: pgwire.PgClient,
88
- bucketStorage: storage.BucketStorageFactory,
89
- options?: { timeout?: number }
90
- ): Promise<OpId> {
91
- const start = Date.now();
92
+ function getRawReplicaIdentity(
93
+ tuple: sync_rules.ToastableSqliteRow,
94
+ columns: storage.ColumnDescriptor[]
95
+ ): Record<string, any> {
96
+ let result: Record<string, any> = {};
97
+ for (let column of columns) {
98
+ const name = column.name;
99
+ result[name] = tuple[name];
100
+ }
101
+ return result;
102
+ }
92
103
 
93
- const [{ lsn }] = pgwireRows(await db.query(`SELECT pg_logical_emit_message(false, 'powersync', 'ping') as lsn`));
104
+ export function getUuidReplicaIdentityBson(
105
+ tuple: sync_rules.ToastableSqliteRow,
106
+ columns: storage.ColumnDescriptor[]
107
+ ): bson.UUID {
108
+ if (columns.length == 0) {
109
+ // REPLICA IDENTITY NOTHING - generate random id
110
+ return new bson.UUID(uuid.v4());
111
+ }
112
+ const rawIdentity = getRawReplicaIdentity(tuple, columns);
94
113
 
95
- // This old API needs a persisted checkpoint id.
96
- // Since we don't use LSNs anymore, the only way to get that is to wait.
114
+ return uuidForRowBson(rawIdentity);
115
+ }
97
116
 
98
- const timeout = options?.timeout ?? 50_000;
117
+ export function uuidForRowBson(row: sync_rules.SqliteRow): bson.UUID {
118
+ // Important: This must not change, since it will affect how ids are generated.
119
+ // Use BSON so that it's a well-defined format without encoding ambiguities.
120
+ const repr = bson.serialize(row);
121
+ const buffer = Buffer.alloc(16);
122
+ return new bson.UUID(uuid.v5(repr, ID_NAMESPACE, buffer));
123
+ }
99
124
 
100
- logger.info(`Waiting for LSN checkpoint: ${lsn}`);
101
- while (Date.now() - start < timeout) {
102
- const cp = await bucketStorage.getActiveCheckpoint();
103
- if (!cp.hasSyncRules()) {
104
- throw new Error('No sync rules available');
105
- }
106
- if (cp.lsn >= lsn) {
107
- logger.info(`Got write checkpoint: ${lsn} : ${cp.checkpoint}`);
108
- return cp.checkpoint;
125
+ export function hasToastedValues(row: sync_rules.ToastableSqliteRow) {
126
+ for (let key in row) {
127
+ if (typeof row[key] == 'undefined') {
128
+ return true;
109
129
  }
110
-
111
- await new Promise((resolve) => setTimeout(resolve, 30));
112
130
  }
113
-
114
- throw new Error('Timeout while waiting for checkpoint');
131
+ return false;
115
132
  }
116
133
 
117
- export async function createWriteCheckpoint(
118
- db: pgwire.PgClient,
119
- bucketStorage: storage.BucketStorageFactory,
120
- user_id: string
121
- ): Promise<bigint> {
122
- const [{ lsn }] = pgwireRows(
123
- await retriedQuery(db, `SELECT pg_logical_emit_message(false, 'powersync', 'ping') as lsn`)
124
- );
125
-
126
- const id = await bucketStorage.createWriteCheckpoint(user_id, { '1': lsn });
127
- logger.info(`Write checkpoint 2: ${JSON.stringify({ lsn, id: String(id) })}`);
128
- return id;
134
+ export function isCompleteRow(row: sync_rules.ToastableSqliteRow): row is sync_rules.SqliteRow {
135
+ return !hasToastedValues(row);
129
136
  }
130
137
 
131
138
  export function checkpointUserId(user_id: string | undefined, client_id: string | undefined) {