@powersync/service-core 0.0.0-dev-20240718134716 → 0.0.0-dev-20240918082156

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 (352) hide show
  1. package/CHANGELOG.md +89 -6
  2. package/dist/api/RouteAPI.d.ts +68 -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 +11 -65
  10. package/dist/api/diagnostics.js.map +1 -1
  11. package/dist/api/schema.d.ts +3 -5
  12. package/dist/api/schema.js +9 -79
  13. package/dist/api/schema.js.map +1 -1
  14. package/dist/auth/KeyStore.d.ts +7 -4
  15. package/dist/auth/KeyStore.js +1 -1
  16. package/dist/auth/KeyStore.js.map +1 -1
  17. package/dist/auth/auth-index.d.ts +0 -1
  18. package/dist/auth/auth-index.js +0 -1
  19. package/dist/auth/auth-index.js.map +1 -1
  20. package/dist/entry/cli-entry.js +4 -2
  21. package/dist/entry/cli-entry.js.map +1 -1
  22. package/dist/entry/commands/compact-action.d.ts +2 -0
  23. package/dist/entry/commands/compact-action.js +52 -0
  24. package/dist/entry/commands/compact-action.js.map +1 -0
  25. package/dist/entry/commands/migrate-action.js +4 -5
  26. package/dist/entry/commands/migrate-action.js.map +1 -1
  27. package/dist/entry/commands/teardown-action.js +2 -2
  28. package/dist/entry/commands/teardown-action.js.map +1 -1
  29. package/dist/entry/entry-index.d.ts +1 -0
  30. package/dist/entry/entry-index.js +1 -0
  31. package/dist/entry/entry-index.js.map +1 -1
  32. package/dist/index.d.ts +4 -2
  33. package/dist/index.js +4 -2
  34. package/dist/index.js.map +1 -1
  35. package/dist/metrics/Metrics.d.ts +6 -5
  36. package/dist/metrics/Metrics.js +53 -10
  37. package/dist/metrics/Metrics.js.map +1 -1
  38. package/dist/migrations/db/migrations/1684951997326-init.d.ts +2 -2
  39. package/dist/migrations/db/migrations/1684951997326-init.js +4 -2
  40. package/dist/migrations/db/migrations/1684951997326-init.js.map +1 -1
  41. package/dist/migrations/db/migrations/1702295701188-sync-rule-state.d.ts +2 -2
  42. package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js +4 -2
  43. package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js.map +1 -1
  44. package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.d.ts +2 -2
  45. package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.js +4 -2
  46. package/dist/migrations/db/migrations/1711543888062-write-checkpoint-index.js.map +1 -1
  47. package/dist/migrations/migrations.d.ts +8 -0
  48. package/dist/migrations/migrations.js +19 -7
  49. package/dist/migrations/migrations.js.map +1 -1
  50. package/dist/modules/AbstractModule.d.ts +26 -0
  51. package/dist/modules/AbstractModule.js +11 -0
  52. package/dist/modules/AbstractModule.js.map +1 -0
  53. package/dist/modules/ModuleManager.d.ts +11 -0
  54. package/dist/modules/ModuleManager.js +32 -0
  55. package/dist/modules/ModuleManager.js.map +1 -0
  56. package/dist/modules/modules-index.d.ts +2 -0
  57. package/dist/modules/modules-index.js +3 -0
  58. package/dist/modules/modules-index.js.map +1 -0
  59. package/dist/replication/AbstractReplicationJob.d.ts +38 -0
  60. package/dist/replication/AbstractReplicationJob.js +51 -0
  61. package/dist/replication/AbstractReplicationJob.js.map +1 -0
  62. package/dist/replication/AbstractReplicator.d.ts +53 -0
  63. package/dist/replication/AbstractReplicator.js +187 -0
  64. package/dist/replication/AbstractReplicator.js.map +1 -0
  65. package/dist/replication/ErrorRateLimiter.d.ts +0 -9
  66. package/dist/replication/ErrorRateLimiter.js +1 -42
  67. package/dist/replication/ErrorRateLimiter.js.map +1 -1
  68. package/dist/replication/ReplicationEngine.d.ts +18 -0
  69. package/dist/replication/ReplicationEngine.js +41 -0
  70. package/dist/replication/ReplicationEngine.js.map +1 -0
  71. package/dist/replication/ReplicationModule.d.ts +39 -0
  72. package/dist/replication/ReplicationModule.js +65 -0
  73. package/dist/replication/ReplicationModule.js.map +1 -0
  74. package/dist/replication/replication-index.d.ts +4 -6
  75. package/dist/replication/replication-index.js +4 -6
  76. package/dist/replication/replication-index.js.map +1 -1
  77. package/dist/routes/RouterEngine.d.ts +42 -0
  78. package/dist/routes/RouterEngine.js +80 -0
  79. package/dist/routes/RouterEngine.js.map +1 -0
  80. package/dist/routes/auth.d.ts +2 -2
  81. package/dist/routes/auth.js +11 -11
  82. package/dist/routes/auth.js.map +1 -1
  83. package/dist/routes/configure-fastify.d.ts +737 -0
  84. package/dist/routes/configure-fastify.js +57 -0
  85. package/dist/routes/configure-fastify.js.map +1 -0
  86. package/dist/routes/configure-rsocket.d.ts +13 -0
  87. package/dist/routes/configure-rsocket.js +47 -0
  88. package/dist/routes/configure-rsocket.js.map +1 -0
  89. package/dist/routes/endpoints/admin.d.ts +0 -34
  90. package/dist/routes/endpoints/admin.js +48 -89
  91. package/dist/routes/endpoints/admin.js.map +1 -1
  92. package/dist/routes/endpoints/checkpointing.d.ts +56 -16
  93. package/dist/routes/endpoints/checkpointing.js +33 -12
  94. package/dist/routes/endpoints/checkpointing.js.map +1 -1
  95. package/dist/routes/endpoints/route-endpoints-index.d.ts +0 -1
  96. package/dist/routes/endpoints/route-endpoints-index.js +0 -1
  97. package/dist/routes/endpoints/route-endpoints-index.js.map +1 -1
  98. package/dist/routes/endpoints/socket-route.js +46 -39
  99. package/dist/routes/endpoints/socket-route.js.map +1 -1
  100. package/dist/routes/endpoints/sync-rules.d.ts +1 -1
  101. package/dist/routes/endpoints/sync-rules.js +32 -23
  102. package/dist/routes/endpoints/sync-rules.js.map +1 -1
  103. package/dist/routes/endpoints/sync-stream.d.ts +10 -0
  104. package/dist/routes/endpoints/sync-stream.js +17 -13
  105. package/dist/routes/endpoints/sync-stream.js.map +1 -1
  106. package/dist/routes/route-register.d.ts +1 -1
  107. package/dist/routes/route-register.js +1 -1
  108. package/dist/routes/route-register.js.map +1 -1
  109. package/dist/routes/router-socket.d.ts +5 -4
  110. package/dist/routes/router-socket.js +2 -1
  111. package/dist/routes/router-socket.js.map +1 -1
  112. package/dist/routes/router.d.ts +7 -2
  113. package/dist/routes/router.js.map +1 -1
  114. package/dist/routes/routes-index.d.ts +3 -0
  115. package/dist/routes/routes-index.js +3 -0
  116. package/dist/routes/routes-index.js.map +1 -1
  117. package/dist/runner/teardown.js +47 -76
  118. package/dist/runner/teardown.js.map +1 -1
  119. package/dist/storage/BucketStorage.d.ts +61 -20
  120. package/dist/storage/BucketStorage.js +0 -10
  121. package/dist/storage/BucketStorage.js.map +1 -1
  122. package/dist/storage/MongoBucketStorage.d.ts +4 -4
  123. package/dist/storage/MongoBucketStorage.js +19 -24
  124. package/dist/storage/MongoBucketStorage.js.map +1 -1
  125. package/dist/storage/SourceEntity.d.ts +20 -0
  126. package/dist/storage/SourceEntity.js +2 -0
  127. package/dist/storage/SourceEntity.js.map +1 -0
  128. package/dist/storage/SourceTable.d.ts +4 -5
  129. package/dist/storage/SourceTable.js +3 -4
  130. package/dist/storage/SourceTable.js.map +1 -1
  131. package/dist/storage/StorageEngine.d.ts +24 -0
  132. package/dist/storage/StorageEngine.js +43 -0
  133. package/dist/storage/StorageEngine.js.map +1 -0
  134. package/dist/storage/StorageProvider.d.ts +21 -0
  135. package/dist/storage/StorageProvider.js +2 -0
  136. package/dist/storage/StorageProvider.js.map +1 -0
  137. package/dist/storage/mongo/MongoBucketBatch.d.ts +1 -1
  138. package/dist/storage/mongo/MongoBucketBatch.js +6 -7
  139. package/dist/storage/mongo/MongoBucketBatch.js.map +1 -1
  140. package/dist/storage/mongo/MongoCompactor.d.ts +40 -0
  141. package/dist/storage/mongo/MongoCompactor.js +293 -0
  142. package/dist/storage/mongo/MongoCompactor.js.map +1 -0
  143. package/dist/storage/mongo/MongoPersistedSyncRulesContent.d.ts +2 -2
  144. package/dist/storage/mongo/MongoPersistedSyncRulesContent.js +2 -2
  145. package/dist/storage/mongo/MongoPersistedSyncRulesContent.js.map +1 -1
  146. package/dist/storage/mongo/MongoStorageProvider.d.ts +5 -0
  147. package/dist/storage/mongo/MongoStorageProvider.js +26 -0
  148. package/dist/storage/mongo/MongoStorageProvider.js.map +1 -0
  149. package/dist/storage/mongo/MongoSyncBucketStorage.d.ts +9 -7
  150. package/dist/storage/mongo/MongoSyncBucketStorage.js +43 -28
  151. package/dist/storage/mongo/MongoSyncBucketStorage.js.map +1 -1
  152. package/dist/storage/mongo/MongoSyncRulesLock.js +1 -1
  153. package/dist/storage/mongo/MongoSyncRulesLock.js.map +1 -1
  154. package/dist/storage/mongo/OperationBatch.d.ts +7 -3
  155. package/dist/storage/mongo/OperationBatch.js +16 -7
  156. package/dist/storage/mongo/OperationBatch.js.map +1 -1
  157. package/dist/storage/mongo/PersistedBatch.d.ts +3 -3
  158. package/dist/storage/mongo/PersistedBatch.js +2 -2
  159. package/dist/storage/mongo/PersistedBatch.js.map +1 -1
  160. package/dist/storage/mongo/models.d.ts +17 -7
  161. package/dist/storage/mongo/models.js.map +1 -1
  162. package/dist/storage/mongo/util.d.ts +14 -0
  163. package/dist/storage/mongo/util.js +70 -0
  164. package/dist/storage/mongo/util.js.map +1 -1
  165. package/dist/storage/storage-index.d.ts +5 -2
  166. package/dist/storage/storage-index.js +5 -2
  167. package/dist/storage/storage-index.js.map +1 -1
  168. package/dist/sync/RequestTracker.js +2 -3
  169. package/dist/sync/RequestTracker.js.map +1 -1
  170. package/dist/sync/sync-index.d.ts +1 -0
  171. package/dist/sync/sync-index.js +1 -0
  172. package/dist/sync/sync-index.js.map +1 -1
  173. package/dist/sync/sync.d.ts +2 -1
  174. package/dist/sync/sync.js +56 -17
  175. package/dist/sync/sync.js.map +1 -1
  176. package/dist/system/ServiceContext.d.ts +37 -0
  177. package/dist/system/ServiceContext.js +48 -0
  178. package/dist/system/ServiceContext.js.map +1 -0
  179. package/dist/system/system-index.d.ts +1 -1
  180. package/dist/system/system-index.js +1 -1
  181. package/dist/system/system-index.js.map +1 -1
  182. package/dist/util/config/collectors/config-collector.d.ts +12 -0
  183. package/dist/util/config/collectors/config-collector.js +43 -0
  184. package/dist/util/config/collectors/config-collector.js.map +1 -1
  185. package/dist/util/config/compound-config-collector.d.ts +10 -29
  186. package/dist/util/config/compound-config-collector.js +28 -84
  187. package/dist/util/config/compound-config-collector.js.map +1 -1
  188. package/dist/util/config/sync-rules/sync-rules-provider.d.ts +9 -0
  189. package/dist/util/config/sync-rules/sync-rules-provider.js +15 -0
  190. package/dist/util/config/sync-rules/sync-rules-provider.js.map +1 -0
  191. package/dist/util/config/types.d.ts +6 -4
  192. package/dist/util/config/types.js.map +1 -1
  193. package/dist/util/config.d.ts +3 -4
  194. package/dist/util/config.js +5 -20
  195. package/dist/util/config.js.map +1 -1
  196. package/dist/util/protocol-types.d.ts +4 -0
  197. package/dist/util/protocol-types.js +5 -1
  198. package/dist/util/protocol-types.js.map +1 -1
  199. package/dist/util/util-index.d.ts +3 -6
  200. package/dist/util/util-index.js +3 -6
  201. package/dist/util/util-index.js.map +1 -1
  202. package/dist/util/utils.d.ts +10 -6
  203. package/dist/util/utils.js +45 -25
  204. package/dist/util/utils.js.map +1 -1
  205. package/package.json +7 -7
  206. package/src/api/RouteAPI.ts +78 -0
  207. package/src/api/api-index.ts +1 -0
  208. package/src/api/diagnostics.ts +16 -71
  209. package/src/api/schema.ts +13 -89
  210. package/src/auth/KeyStore.ts +9 -6
  211. package/src/auth/auth-index.ts +0 -1
  212. package/src/entry/cli-entry.ts +4 -2
  213. package/src/entry/commands/compact-action.ts +57 -0
  214. package/src/entry/commands/migrate-action.ts +5 -8
  215. package/src/entry/commands/teardown-action.ts +2 -2
  216. package/src/entry/entry-index.ts +1 -0
  217. package/src/index.ts +5 -2
  218. package/src/metrics/Metrics.ts +70 -15
  219. package/src/migrations/db/migrations/1684951997326-init.ts +9 -4
  220. package/src/migrations/db/migrations/1702295701188-sync-rule-state.ts +7 -4
  221. package/src/migrations/db/migrations/1711543888062-write-checkpoint-index.ts +6 -4
  222. package/src/migrations/migrations.ts +24 -8
  223. package/src/modules/AbstractModule.ts +37 -0
  224. package/src/modules/ModuleManager.ts +34 -0
  225. package/src/modules/modules-index.ts +2 -0
  226. package/src/replication/AbstractReplicationJob.ts +79 -0
  227. package/src/replication/AbstractReplicator.ts +227 -0
  228. package/src/replication/ErrorRateLimiter.ts +0 -44
  229. package/src/replication/ReplicationEngine.ts +43 -0
  230. package/src/replication/ReplicationModule.ts +101 -0
  231. package/src/replication/replication-index.ts +4 -6
  232. package/src/routes/RouterEngine.ts +120 -0
  233. package/src/routes/auth.ts +21 -12
  234. package/src/routes/configure-fastify.ts +101 -0
  235. package/src/routes/configure-rsocket.ts +60 -0
  236. package/src/routes/endpoints/admin.ts +74 -100
  237. package/src/routes/endpoints/checkpointing.ts +46 -12
  238. package/src/routes/endpoints/route-endpoints-index.ts +0 -1
  239. package/src/routes/endpoints/socket-route.ts +50 -42
  240. package/src/routes/endpoints/sync-rules.ts +41 -25
  241. package/src/routes/endpoints/sync-stream.ts +17 -13
  242. package/src/routes/route-register.ts +2 -2
  243. package/src/routes/router-socket.ts +6 -5
  244. package/src/routes/router.ts +7 -2
  245. package/src/routes/routes-index.ts +3 -0
  246. package/src/runner/teardown.ts +50 -88
  247. package/src/storage/BucketStorage.ts +74 -26
  248. package/src/storage/MongoBucketStorage.ts +23 -26
  249. package/src/storage/SourceEntity.ts +22 -0
  250. package/src/storage/SourceTable.ts +4 -6
  251. package/src/storage/StorageEngine.ts +55 -0
  252. package/src/storage/StorageProvider.ts +27 -0
  253. package/src/storage/mongo/MongoBucketBatch.ts +8 -8
  254. package/src/storage/mongo/MongoCompactor.ts +372 -0
  255. package/src/storage/mongo/MongoPersistedSyncRulesContent.ts +3 -3
  256. package/src/storage/mongo/MongoStorageProvider.ts +31 -0
  257. package/src/storage/mongo/MongoSyncBucketStorage.ts +64 -34
  258. package/src/storage/mongo/MongoSyncRulesLock.ts +1 -1
  259. package/src/storage/mongo/OperationBatch.ts +18 -11
  260. package/src/storage/mongo/PersistedBatch.ts +6 -5
  261. package/src/storage/mongo/models.ts +17 -7
  262. package/src/storage/mongo/util.ts +71 -1
  263. package/src/storage/storage-index.ts +5 -2
  264. package/src/sync/RequestTracker.ts +3 -3
  265. package/src/sync/sync-index.ts +1 -0
  266. package/src/sync/sync.ts +66 -17
  267. package/src/system/ServiceContext.ts +68 -0
  268. package/src/system/system-index.ts +1 -1
  269. package/src/util/config/collectors/config-collector.ts +48 -0
  270. package/src/util/config/compound-config-collector.ts +45 -110
  271. package/src/util/config/sync-rules/sync-rules-provider.ts +18 -0
  272. package/src/util/config/types.ts +6 -5
  273. package/src/util/config.ts +6 -23
  274. package/src/util/protocol-types.ts +6 -1
  275. package/src/util/util-index.ts +3 -6
  276. package/src/util/utils.ts +55 -39
  277. package/test/src/__snapshots__/sync.test.ts.snap +90 -5
  278. package/test/src/auth.test.ts +7 -7
  279. package/test/src/broadcast_iterable.test.ts +1 -1
  280. package/test/src/bucket_validation.test.ts +142 -0
  281. package/test/src/bucket_validation.ts +116 -0
  282. package/test/src/checksum_cache.test.ts +3 -3
  283. package/test/src/compacting.test.ts +216 -0
  284. package/test/src/data_storage.test.ts +275 -204
  285. package/test/src/env.ts +1 -3
  286. package/test/src/merge_iterable.test.ts +1 -6
  287. package/test/src/setup.ts +1 -1
  288. package/test/src/stream_utils.ts +42 -0
  289. package/test/src/sync.test.ts +209 -48
  290. package/test/src/util.ts +110 -55
  291. package/test/tsconfig.json +1 -1
  292. package/tsconfig.tsbuildinfo +1 -1
  293. package/dist/auth/SupabaseKeyCollector.d.ts +0 -22
  294. package/dist/auth/SupabaseKeyCollector.js +0 -61
  295. package/dist/auth/SupabaseKeyCollector.js.map +0 -1
  296. package/dist/replication/PgRelation.d.ts +0 -16
  297. package/dist/replication/PgRelation.js +0 -26
  298. package/dist/replication/PgRelation.js.map +0 -1
  299. package/dist/replication/WalConnection.d.ts +0 -34
  300. package/dist/replication/WalConnection.js +0 -190
  301. package/dist/replication/WalConnection.js.map +0 -1
  302. package/dist/replication/WalStream.d.ts +0 -57
  303. package/dist/replication/WalStream.js +0 -517
  304. package/dist/replication/WalStream.js.map +0 -1
  305. package/dist/replication/WalStreamManager.d.ts +0 -30
  306. package/dist/replication/WalStreamManager.js +0 -198
  307. package/dist/replication/WalStreamManager.js.map +0 -1
  308. package/dist/replication/WalStreamRunner.d.ts +0 -38
  309. package/dist/replication/WalStreamRunner.js +0 -155
  310. package/dist/replication/WalStreamRunner.js.map +0 -1
  311. package/dist/replication/util.d.ts +0 -9
  312. package/dist/replication/util.js +0 -62
  313. package/dist/replication/util.js.map +0 -1
  314. package/dist/routes/endpoints/dev.d.ts +0 -312
  315. package/dist/routes/endpoints/dev.js +0 -172
  316. package/dist/routes/endpoints/dev.js.map +0 -1
  317. package/dist/system/CorePowerSyncSystem.d.ts +0 -23
  318. package/dist/system/CorePowerSyncSystem.js +0 -52
  319. package/dist/system/CorePowerSyncSystem.js.map +0 -1
  320. package/dist/util/PgManager.d.ts +0 -24
  321. package/dist/util/PgManager.js +0 -55
  322. package/dist/util/PgManager.js.map +0 -1
  323. package/dist/util/migration_lib.d.ts +0 -11
  324. package/dist/util/migration_lib.js +0 -64
  325. package/dist/util/migration_lib.js.map +0 -1
  326. package/dist/util/pgwire_utils.d.ts +0 -24
  327. package/dist/util/pgwire_utils.js +0 -117
  328. package/dist/util/pgwire_utils.js.map +0 -1
  329. package/dist/util/populate_test_data.d.ts +0 -8
  330. package/dist/util/populate_test_data.js +0 -65
  331. package/dist/util/populate_test_data.js.map +0 -1
  332. package/src/auth/SupabaseKeyCollector.ts +0 -67
  333. package/src/replication/PgRelation.ts +0 -42
  334. package/src/replication/WalConnection.ts +0 -227
  335. package/src/replication/WalStream.ts +0 -628
  336. package/src/replication/WalStreamManager.ts +0 -213
  337. package/src/replication/WalStreamRunner.ts +0 -180
  338. package/src/replication/util.ts +0 -76
  339. package/src/routes/endpoints/dev.ts +0 -199
  340. package/src/system/CorePowerSyncSystem.ts +0 -64
  341. package/src/util/PgManager.ts +0 -64
  342. package/src/util/migration_lib.ts +0 -79
  343. package/src/util/pgwire_utils.ts +0 -139
  344. package/src/util/populate_test_data.ts +0 -78
  345. package/test/src/__snapshots__/pg_test.test.ts.snap +0 -256
  346. package/test/src/large_batch.test.ts +0 -194
  347. package/test/src/pg_test.test.ts +0 -450
  348. package/test/src/schema_changes.test.ts +0 -545
  349. package/test/src/slow_tests.test.ts +0 -296
  350. package/test/src/validation.test.ts +0 -63
  351. package/test/src/wal_stream.test.ts +0 -314
  352. package/test/src/wal_stream_utils.ts +0 -147
@@ -1,8 +1,9 @@
1
- import * as bson from 'bson';
2
1
  import { ToastableSqliteRow } from '@powersync/service-sync-rules';
2
+ import * as bson from 'bson';
3
3
 
4
- import * as util from '../../util/util-index.js';
5
4
  import { SaveOptions } from '../BucketStorage.js';
5
+ import { isUUID } from './util.js';
6
+ import { ReplicaId } from './models.js';
6
7
 
7
8
  /**
8
9
  * Maximum number of operations in a batch.
@@ -63,18 +64,15 @@ export class OperationBatch {
63
64
  }
64
65
 
65
66
  export class RecordOperation {
66
- public readonly afterId: bson.UUID | null;
67
- public readonly beforeId: bson.UUID;
67
+ public readonly afterId: ReplicaId | null;
68
+ public readonly beforeId: ReplicaId;
68
69
  public readonly internalBeforeKey: string;
69
70
  public readonly internalAfterKey: string | null;
70
71
  public readonly estimatedSize: number;
71
72
 
72
73
  constructor(public readonly record: SaveOptions) {
73
- const after = record.after;
74
- const afterId = after ? util.getUuidReplicaIdentityBson(after, record.sourceTable.replicaIdColumns!) : null;
75
- const beforeId = record.before
76
- ? util.getUuidReplicaIdentityBson(record.before, record.sourceTable.replicaIdColumns!)
77
- : afterId!;
74
+ const afterId = record.afterReplicaId ?? null;
75
+ const beforeId = record.beforeReplicaId ?? record.afterReplicaId;
78
76
  this.afterId = afterId;
79
77
  this.beforeId = beforeId;
80
78
  this.internalBeforeKey = cacheKey(record.sourceTable.id, beforeId);
@@ -84,8 +82,17 @@ export class RecordOperation {
84
82
  }
85
83
  }
86
84
 
87
- export function cacheKey(table: bson.ObjectId, id: bson.UUID) {
88
- return `${table.toHexString()}.${id.toHexString()}`;
85
+ /**
86
+ * In-memory cache key - must not be persisted.
87
+ */
88
+ export function cacheKey(table: bson.ObjectId, id: ReplicaId) {
89
+ if (isUUID(id)) {
90
+ return `${table.toHexString()}.${id.toHexString()}`;
91
+ } else if (typeof id == 'string') {
92
+ return `${table.toHexString()}.${id}`;
93
+ } else {
94
+ return `${table.toHexString()}.${(bson.serialize({ id: id }) as Buffer).toString('base64')}`;
95
+ }
89
96
  }
90
97
 
91
98
  /**
@@ -13,9 +13,10 @@ import {
13
13
  BucketParameterDocument,
14
14
  CurrentBucket,
15
15
  CurrentDataDocument,
16
- SourceKey
16
+ SourceKey,
17
+ ReplicaId
17
18
  } from './models.js';
18
- import { serializeLookup } from './util.js';
19
+ import { replicaIdToSubkey, serializeLookup } from './util.js';
19
20
  import { logger } from '@powersync/lib-services-framework';
20
21
 
21
22
  /**
@@ -59,7 +60,7 @@ export class PersistedBatch {
59
60
 
60
61
  saveBucketData(options: {
61
62
  op_seq: MongoIdSequence;
62
- sourceKey: bson.UUID;
63
+ sourceKey: ReplicaId;
63
64
  table: SourceTable;
64
65
  evaluated: EvaluatedRow[];
65
66
  before_buckets: CurrentBucket[];
@@ -70,7 +71,7 @@ export class PersistedBatch {
70
71
  remaining_buckets.set(key, b);
71
72
  }
72
73
 
73
- const dchecksum = util.hashDelete(`${options.table.id}/${options.sourceKey}`);
74
+ const dchecksum = util.hashDelete(replicaIdToSubkey(options.table.id, options.sourceKey));
74
75
 
75
76
  for (let k of options.evaluated) {
76
77
  const key = currentBucketKey(k);
@@ -134,7 +135,7 @@ export class PersistedBatch {
134
135
 
135
136
  saveParameterData(data: {
136
137
  op_seq: MongoIdSequence;
137
- sourceKey: bson.UUID;
138
+ sourceKey: ReplicaId;
138
139
  sourceTable: SourceTable;
139
140
  evaluated: EvaluatedParameters[];
140
141
  existing_lookups: bson.Binary[];
@@ -1,13 +1,22 @@
1
1
  import * as bson from 'bson';
2
2
  import { SqliteJsonValue } from '@powersync/service-sync-rules';
3
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;
12
+
4
13
  export interface SourceKey {
5
14
  /** group_id */
6
15
  g: number;
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 {
@@ -42,12 +51,13 @@ export interface BucketParameterDocument {
42
51
  export interface BucketDataDocument {
43
52
  _id: BucketDataKey;
44
53
  op: OpType;
45
- source_table: bson.ObjectId;
46
- source_key: bson.UUID;
47
- table: string;
48
- row_id: string;
54
+ source_table?: bson.ObjectId;
55
+ source_key?: ReplicaId;
56
+ table?: string;
57
+ row_id?: string;
49
58
  checksum: number;
50
59
  data: string | null;
60
+ target_op?: bigint | null;
51
61
  }
52
62
 
53
63
  export type OpType = 'PUT' | 'REMOVE' | 'MOVE' | 'CLEAR';
@@ -56,11 +66,11 @@ export interface SourceTableDocument {
56
66
  _id: bson.ObjectId;
57
67
  group_id: number;
58
68
  connection_id: number;
59
- relation_id: number | undefined;
69
+ relation_id: number | string | undefined;
60
70
  schema_name: string;
61
71
  table_name: string;
62
72
  replica_id_columns: string[] | null;
63
- replica_id_columns2: { name: string; type_oid: number }[] | undefined;
73
+ replica_id_columns2: { name: string; type_oid?: number; type?: string }[] | undefined;
64
74
  snapshot_done: boolean | undefined;
65
75
  }
66
76
 
@@ -1,7 +1,11 @@
1
1
  import { SqliteJsonValue } from '@powersync/service-sync-rules';
2
2
  import * as bson from 'bson';
3
- import * as mongo from 'mongodb';
4
3
  import * as crypto from 'crypto';
4
+ import * as mongo from 'mongodb';
5
+ import * as uuid from 'uuid';
6
+ import { OplogEntry } from '../../util/protocol-types.js';
7
+ import { ID_NAMESPACE, timestampToOpId } from '../../util/utils.js';
8
+ import { BucketDataDocument, ReplicaId } from './models.js';
5
9
 
6
10
  /**
7
11
  * Lookup serialization must be number-agnostic. I.e. normalize numbers, instead of preserving numbers.
@@ -86,3 +90,69 @@ export const BSON_DESERIALIZE_OPTIONS: bson.DeserializeOptions = {
86
90
  // use bigint instead of Long
87
91
  useBigInt64: true
88
92
  };
93
+
94
+ export function mapOpEntry(row: BucketDataDocument): OplogEntry {
95
+ if (row.op == 'PUT' || row.op == 'REMOVE') {
96
+ return {
97
+ op_id: timestampToOpId(row._id.o),
98
+ op: row.op,
99
+ object_type: row.table,
100
+ object_id: row.row_id,
101
+ checksum: Number(row.checksum),
102
+ subkey: replicaIdToSubkey(row.source_table!, row.source_key!),
103
+ data: row.data
104
+ };
105
+ } else {
106
+ // MOVE, CLEAR
107
+
108
+ return {
109
+ op_id: timestampToOpId(row._id.o),
110
+ op: row.op,
111
+ checksum: Number(row.checksum)
112
+ };
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,6 +1,8 @@
1
- export * from './SourceTable.js';
2
- export * from './MongoBucketStorage.js';
3
1
  export * from './BucketStorage.js';
2
+ export * from './MongoBucketStorage.js';
3
+ export * from './SourceEntity.js';
4
+ export * from './SourceTable.js';
5
+ export * from './StorageEngine.js';
4
6
 
5
7
  export * from './mongo/db.js';
6
8
  export * from './mongo/models.js';
@@ -8,6 +10,7 @@ export * from './mongo/MongoBucketBatch.js';
8
10
  export * from './mongo/MongoIdSequence.js';
9
11
  export * from './mongo/MongoPersistedSyncRules.js';
10
12
  export * from './mongo/MongoPersistedSyncRulesContent.js';
13
+ export * from './mongo/MongoStorageProvider.js';
11
14
  export * from './mongo/MongoSyncBucketStorage.js';
12
15
  export * from './mongo/MongoSyncRulesLock.js';
13
16
  export * from './mongo/OperationBatch.js';
@@ -1,4 +1,3 @@
1
- import { container } from '@powersync/lib-services-framework';
2
1
  import { Metrics } from '../metrics/Metrics.js';
3
2
 
4
3
  /**
@@ -10,12 +9,13 @@ export class RequestTracker {
10
9
 
11
10
  addOperationsSynced(operations: number) {
12
11
  this.operationsSynced += operations;
13
- container.getImplementation(Metrics).operations_synced_total.add(operations);
12
+
13
+ Metrics.getInstance().operations_synced_total.add(operations);
14
14
  }
15
15
 
16
16
  addDataSynced(bytes: number) {
17
17
  this.dataSyncedBytes += bytes;
18
18
 
19
- container.getImplementation(Metrics).data_synced_bytes.add(bytes);
19
+ Metrics.getInstance().data_synced_bytes.add(bytes);
20
20
  }
21
21
  }
@@ -1,6 +1,7 @@
1
1
  export * from './BroadcastIterable.js';
2
2
  export * from './LastValueSink.js';
3
3
  export * from './merge.js';
4
+ export * from './RequestTracker.js';
4
5
  export * from './safeRace.js';
5
6
  export * from './sync.js';
6
7
  export * from './util.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
- import { Semaphore } from 'async-mutex';
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';
@@ -9,20 +10,33 @@ import * as util from '../util/util-index.js';
9
10
 
10
11
  import { logger } from '@powersync/lib-services-framework';
11
12
  import { mergeAsyncIterables } from './merge.js';
12
- import { TokenStreamOptions, tokenStream } from './util.js';
13
13
  import { RequestTracker } from './RequestTracker.js';
14
+ import { TokenStreamOptions, tokenStream } from './util.js';
14
15
 
15
16
  /**
16
17
  * Maximum number of connections actively fetching data.
17
18
  */
18
19
  const MAX_ACTIVE_CONNECTIONS = 10;
19
- const syncSemaphore = new Semaphore(MAX_ACTIVE_CONNECTIONS);
20
+
21
+ /**
22
+ * Maximum duration to wait for the mutex to become available.
23
+ *
24
+ * This gives an explicit error if there are mutex issues, rather than just hanging.
25
+ */
26
+ const MUTEX_ACQUIRE_TIMEOUT = 30_000;
27
+
28
+ const syncSemaphore = withTimeout(
29
+ new Semaphore(MAX_ACTIVE_CONNECTIONS),
30
+ MUTEX_ACQUIRE_TIMEOUT,
31
+ new Error(`Timeout while waiting for data`)
32
+ );
20
33
 
21
34
  export interface SyncStreamParameters {
22
35
  storage: storage.BucketStorageFactory;
23
36
  params: util.StreamingSyncRequest;
24
37
  syncParams: RequestParameters;
25
38
  token: auth.JwtPayload;
39
+ parseOptions: storage.ParseSyncRulesOptions;
26
40
  /**
27
41
  * If this signal is aborted, the stream response ends as soon as possible, without error.
28
42
  */
@@ -35,7 +49,7 @@ export interface SyncStreamParameters {
35
49
  export async function* streamResponse(
36
50
  options: SyncStreamParameters
37
51
  ): AsyncIterable<util.StreamingSyncLine | string | null> {
38
- const { storage, params, syncParams, token, tokenStreamOptions, tracker, signal } = options;
52
+ const { storage, params, syncParams, token, tokenStreamOptions, tracker, signal, parseOptions } = options;
39
53
  // We also need to be able to abort, so we create our own controller.
40
54
  const controller = new AbortController();
41
55
  if (signal) {
@@ -51,7 +65,7 @@ export async function* streamResponse(
51
65
  }
52
66
  }
53
67
  const ki = tokenStream(token, controller.signal, tokenStreamOptions);
54
- const stream = streamResponseInner(storage, params, syncParams, tracker, controller.signal);
68
+ const stream = streamResponseInner(storage, params, syncParams, tracker, parseOptions, controller.signal);
55
69
  // Merge the two streams, and abort as soon as one of the streams end.
56
70
  const merged = mergeAsyncIterables([stream, ki], controller.signal);
57
71
 
@@ -75,6 +89,7 @@ async function* streamResponseInner(
75
89
  params: util.StreamingSyncRequest,
76
90
  syncParams: RequestParameters,
77
91
  tracker: RequestTracker,
92
+ parseOptions: storage.ParseSyncRulesOptions,
78
93
  signal: AbortSignal
79
94
  ): AsyncGenerator<util.StreamingSyncLine | string | null> {
80
95
  // Bucket state of bucket id -> op_id.
@@ -92,7 +107,8 @@ async function* streamResponseInner(
92
107
  }
93
108
  }
94
109
 
95
- const stream = storage.watchWriteCheckpoint(syncParams.token_parameters.user_id as string, signal);
110
+ const checkpointUserId = util.checkpointUserId(syncParams.token_parameters.user_id as string, params.client_id);
111
+ const stream = storage.watchWriteCheckpoint(checkpointUserId, signal);
96
112
  for await (const next of stream) {
97
113
  const { base, writeCheckpoint } = next;
98
114
  const checkpoint = base.checkpoint;
@@ -102,9 +118,9 @@ async function* streamResponseInner(
102
118
  // Sync rules deleted in the meantime - try again with the next checkpoint.
103
119
  continue;
104
120
  }
105
- const sync_rules = storage.sync_rules;
121
+ const syncRules = storage.getParsedSyncRules(parseOptions);
106
122
 
107
- const allBuckets = await sync_rules.queryBucketIds({
123
+ const allBuckets = await syncRules.queryBucketIds({
108
124
  getParameterSets(lookups) {
109
125
  return storage.getParameterSets(checkpoint, lookups);
110
126
  },
@@ -195,7 +211,8 @@ async function* streamResponseInner(
195
211
  raw_data,
196
212
  binary_data,
197
213
  signal,
198
- tracker
214
+ tracker,
215
+ user_id: syncParams.user_id
199
216
  });
200
217
 
201
218
  await new Promise((resolve) => setTimeout(resolve, 10));
@@ -212,6 +229,7 @@ interface BucketDataRequest {
212
229
  binary_data: boolean | undefined;
213
230
  tracker: RequestTracker;
214
231
  signal: AbortSignal;
232
+ user_id?: string;
215
233
  }
216
234
 
217
235
  async function* bucketDataInBatches(request: BucketDataRequest) {
@@ -257,8 +275,22 @@ interface BucketDataBatchResult {
257
275
  async function* bucketDataBatch(request: BucketDataRequest): AsyncGenerator<BucketDataBatchResult, void> {
258
276
  const { storage, checkpoint, bucketsToFetch, dataBuckets, raw_data, binary_data, tracker, signal } = request;
259
277
 
260
- const [_, release] = await syncSemaphore.acquire();
278
+ const checkpointOp = BigInt(checkpoint);
279
+ let checkpointInvalidated = false;
280
+
281
+ if (syncSemaphore.isLocked()) {
282
+ logger.info('Sync concurrency limit reached, waiting for lock', { user_id: request.user_id });
283
+ }
284
+ const [value, release] = await syncSemaphore.acquire();
261
285
  try {
286
+ if (value <= 3) {
287
+ // This can be noisy, so we only log when we get close to the
288
+ // concurrency limit.
289
+ logger.info(`Got sync lock. Slots available: ${value - 1}`, {
290
+ user_id: request.user_id,
291
+ sync_data_slots: value - 1
292
+ });
293
+ }
262
294
  // Optimization: Only fetch buckets for which the checksums have changed since the last checkpoint
263
295
  // For the first batch, this will be all buckets.
264
296
  const filteredBuckets = new Map(bucketsToFetch.map((bucket) => [bucket, dataBuckets.get(bucket)!]));
@@ -266,13 +298,16 @@ async function* bucketDataBatch(request: BucketDataRequest): AsyncGenerator<Buck
266
298
 
267
299
  let has_more = false;
268
300
 
269
- for await (let r of data) {
301
+ for await (let { batch: r, targetOp } of data) {
270
302
  if (signal.aborted) {
271
303
  return;
272
304
  }
273
305
  if (r.has_more) {
274
306
  has_more = true;
275
307
  }
308
+ if (targetOp != null && targetOp > checkpointOp) {
309
+ checkpointInvalidated = true;
310
+ }
276
311
  if (r.data.length == 0) {
277
312
  continue;
278
313
  }
@@ -308,14 +343,28 @@ async function* bucketDataBatch(request: BucketDataRequest): AsyncGenerator<Buck
308
343
  }
309
344
 
310
345
  if (!has_more) {
311
- const line: util.StreamingSyncCheckpointComplete = {
312
- checkpoint_complete: {
313
- last_op_id: checkpoint
314
- }
315
- };
316
- yield { data: line, done: true };
346
+ if (checkpointInvalidated) {
347
+ // Checkpoint invalidated by a CLEAR or MOVE op.
348
+ // Don't send the checkpoint_complete line in this case.
349
+ // More data should be available immediately for a new checkpoint.
350
+ yield { data: null, done: true };
351
+ } else {
352
+ const line: util.StreamingSyncCheckpointComplete = {
353
+ checkpoint_complete: {
354
+ last_op_id: checkpoint
355
+ }
356
+ };
357
+ yield { data: line, done: true };
358
+ }
317
359
  }
318
360
  } finally {
361
+ if (value <= 3) {
362
+ // This can be noisy, so we only log when we get close to the
363
+ // concurrency limit.
364
+ logger.info(`Releasing sync lock`, {
365
+ user_id: request.user_id
366
+ });
367
+ }
319
368
  release();
320
369
  }
321
370
  }
@@ -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,6 +1,8 @@
1
+ import * as t from 'ts-codec';
1
2
  import * as yaml from 'yaml';
2
3
 
3
4
  import { configFile } from '@powersync/service-types';
5
+ import { schema } from '@powersync/lib-services-framework';
4
6
 
5
7
  import { RunnerConfig } from '../types.js';
6
8
 
@@ -21,6 +23,13 @@ export enum ConfigFileFormat {
21
23
  */
22
24
  const YAML_ENV_PREFIX = 'PS_';
23
25
 
26
+ // ts-codec itself doesn't give great validation errors, so we use json schema for that
27
+ const configSchemaValidator = schema
28
+ .parseJSONSchema(
29
+ t.generateJSONSchema(configFile.powerSyncConfig, { allowAdditional: true, parsers: [configFile.portParser] })
30
+ )
31
+ .validator();
32
+
24
33
  export abstract class ConfigCollector {
25
34
  abstract get name(): string;
26
35
 
@@ -30,6 +39,45 @@ export abstract class ConfigCollector {
30
39
  */
31
40
  abstract collectSerialized(runnerConfig: RunnerConfig): Promise<configFile.SerializedPowerSyncConfig | null>;
32
41
 
42
+ /**
43
+ * Collects the PowerSyncConfig settings.
44
+ * Validates and decodes the config.
45
+ * @returns null if this collector cannot provide a config
46
+ */
47
+ async collect(runner_config: RunnerConfig): Promise<configFile.PowerSyncConfig | null> {
48
+ const serialized = await this.collectSerialized(runner_config);
49
+ if (!serialized) {
50
+ return null;
51
+ }
52
+
53
+ /**
54
+ * After this point a serialized config has been found. Any failures to decode or validate
55
+ * will result in a hard stop.
56
+ */
57
+ const decoded = this.decode(serialized);
58
+ this.validate(decoded);
59
+ return decoded;
60
+ }
61
+
62
+ /**
63
+ * Validates input config
64
+ * ts-codec itself doesn't give great validation errors, so we use json schema for that
65
+ */
66
+ validate(config: configFile.PowerSyncConfig) {
67
+ const valid = configSchemaValidator.validate(config);
68
+ if (!valid.valid) {
69
+ throw new Error(`Failed to validate PowerSync config: ${valid.errors.join(', ')}`);
70
+ }
71
+ }
72
+
73
+ decode(encoded: configFile.SerializedPowerSyncConfig): configFile.PowerSyncConfig {
74
+ try {
75
+ return configFile.powerSyncConfig.decode(encoded);
76
+ } catch (ex) {
77
+ throw new Error(`Failed to decode PowerSync config: ${ex}`);
78
+ }
79
+ }
80
+
33
81
  protected parseContent(content: string, contentType?: ConfigFileFormat) {
34
82
  switch (contentType) {
35
83
  case ConfigFileFormat.YAML: