@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
@@ -0,0 +1,142 @@
1
+ import { OplogEntry } from '@/util/protocol-types.js';
2
+ import { describe, expect, test } from 'vitest';
3
+ import { reduceBucket, validateBucket } from './bucket_validation.js';
4
+
5
+ // This tests the reduceBucket function.
6
+ // While this function is not used directly in the service implementation,
7
+ // it is an important part of validating consistency in other tests.
8
+ describe('bucket validation', () => {
9
+ const ops1: OplogEntry[] = [
10
+ {
11
+ op_id: '1',
12
+ op: 'PUT',
13
+ object_type: 'test',
14
+ object_id: 't1',
15
+ checksum: 2634521662,
16
+ subkey: '6544e3899293153fa7b38331/117ab485-4b42-58a2-ab32-0053a22c3423',
17
+ data: '{"id":"t1"}'
18
+ },
19
+ {
20
+ op_id: '2',
21
+ op: 'PUT',
22
+ object_type: 'test',
23
+ object_id: 't2',
24
+ checksum: 4243212114,
25
+ subkey: '6544e3899293153fa7b38331/ec27c691-b47a-5d92-927a-9944feb89eee',
26
+ data: '{"id":"t2"}'
27
+ },
28
+ {
29
+ op_id: '3',
30
+ op: 'REMOVE',
31
+ object_type: 'test',
32
+ object_id: 't1',
33
+ checksum: 4228978084,
34
+ subkey: '6544e3899293153fa7b38331/117ab485-4b42-58a2-ab32-0053a22c3423',
35
+ data: null
36
+ },
37
+ {
38
+ op_id: '4',
39
+ op: 'PUT',
40
+ object_type: 'test',
41
+ object_id: 't2',
42
+ checksum: 4243212114,
43
+ subkey: '6544e3899293153fa7b38331/ec27c691-b47a-5d92-927a-9944feb89eee',
44
+ data: '{"id":"t2"}'
45
+ }
46
+ ];
47
+
48
+ test('reduce 1', () => {
49
+ expect(reduceBucket(ops1)).toEqual([
50
+ {
51
+ checksum: -1778190028,
52
+ op: 'CLEAR',
53
+ op_id: '0'
54
+ },
55
+ {
56
+ checksum: 4243212114,
57
+ data: '{"id":"t2"}',
58
+ object_id: 't2',
59
+ object_type: 'test',
60
+ op: 'PUT',
61
+ op_id: '4',
62
+ subkey: '6544e3899293153fa7b38331/ec27c691-b47a-5d92-927a-9944feb89eee'
63
+ }
64
+ ]);
65
+
66
+ expect(reduceBucket(reduceBucket(ops1))).toEqual([
67
+ {
68
+ checksum: -1778190028,
69
+ op: 'CLEAR',
70
+ op_id: '0'
71
+ },
72
+ {
73
+ checksum: 4243212114,
74
+ data: '{"id":"t2"}',
75
+ object_id: 't2',
76
+ object_type: 'test',
77
+ op: 'PUT',
78
+ op_id: '4',
79
+ subkey: '6544e3899293153fa7b38331/ec27c691-b47a-5d92-927a-9944feb89eee'
80
+ }
81
+ ]);
82
+
83
+ validateBucket(ops1);
84
+ });
85
+
86
+ test('reduce 2', () => {
87
+ const bucket: OplogEntry[] = [
88
+ ...ops1,
89
+
90
+ {
91
+ checksum: 93784613,
92
+ op: 'CLEAR',
93
+ op_id: '5'
94
+ },
95
+ {
96
+ checksum: 5133378,
97
+ data: '{"id":"t3"}',
98
+ object_id: 't3',
99
+ object_type: 'test',
100
+ op: 'PUT',
101
+ op_id: '11',
102
+ subkey: '6544e3899293153fa7b38333/ec27c691-b47a-5d92-927a-9944feb89eee'
103
+ }
104
+ ];
105
+
106
+ expect(reduceBucket(bucket)).toEqual([
107
+ {
108
+ checksum: 93784613,
109
+ op: 'CLEAR',
110
+ op_id: '0'
111
+ },
112
+ {
113
+ checksum: 5133378,
114
+ data: '{"id":"t3"}',
115
+ object_id: 't3',
116
+ object_type: 'test',
117
+ op: 'PUT',
118
+ op_id: '11',
119
+ subkey: '6544e3899293153fa7b38333/ec27c691-b47a-5d92-927a-9944feb89eee'
120
+ }
121
+ ]);
122
+
123
+ expect(reduceBucket(reduceBucket(bucket))).toEqual([
124
+ {
125
+ checksum: 93784613,
126
+ op: 'CLEAR',
127
+ op_id: '0'
128
+ },
129
+ {
130
+ checksum: 5133378,
131
+ data: '{"id":"t3"}',
132
+ object_id: 't3',
133
+ object_type: 'test',
134
+ op: 'PUT',
135
+ op_id: '11',
136
+ subkey: '6544e3899293153fa7b38333/ec27c691-b47a-5d92-927a-9944feb89eee'
137
+ }
138
+ ]);
139
+
140
+ validateBucket(bucket);
141
+ });
142
+ });
@@ -0,0 +1,116 @@
1
+ import { OplogEntry } from '@/util/protocol-types.js';
2
+ import { addChecksums } from '@/util/utils.js';
3
+ import { expect } from 'vitest';
4
+
5
+ /**
6
+ * Reduce a bucket to the final state as stored on the client.
7
+ *
8
+ * This keeps the final state for each row as a PUT operation.
9
+ *
10
+ * All other operations are replaced with a single CLEAR operation,
11
+ * summing their checksums, and using a 0 as an op_id.
12
+ *
13
+ * This is the function $r(B)$, as described in /docs/bucket-properties.md.
14
+ */
15
+ export function reduceBucket(operations: OplogEntry[]) {
16
+ let rowState = new Map<string, OplogEntry>();
17
+ let otherChecksum = 0;
18
+
19
+ for (let op of operations) {
20
+ const key = rowKey(op);
21
+ if (op.op == 'PUT') {
22
+ const existing = rowState.get(key);
23
+ if (existing) {
24
+ otherChecksum = addChecksums(otherChecksum, existing.checksum as number);
25
+ }
26
+ rowState.set(key, op);
27
+ } else if (op.op == 'REMOVE') {
28
+ const existing = rowState.get(key);
29
+ if (existing) {
30
+ otherChecksum = addChecksums(otherChecksum, existing.checksum as number);
31
+ }
32
+ rowState.delete(key);
33
+ otherChecksum = addChecksums(otherChecksum, op.checksum as number);
34
+ } else if (op.op == 'CLEAR') {
35
+ rowState.clear();
36
+ otherChecksum = op.checksum as number;
37
+ } else if (op.op == 'MOVE') {
38
+ otherChecksum = addChecksums(otherChecksum, op.checksum as number);
39
+ } else {
40
+ throw new Error(`Unknown operation ${op.op}`);
41
+ }
42
+ }
43
+
44
+ const puts = [...rowState.values()].sort((a, b) => {
45
+ return Number(BigInt(a.op_id) - BigInt(b.op_id));
46
+ });
47
+
48
+ let finalState: OplogEntry[] = [
49
+ // Special operation to indiciate the checksum remainder
50
+ { op_id: '0', op: 'CLEAR', checksum: otherChecksum },
51
+ ...puts
52
+ ];
53
+
54
+ return finalState;
55
+ }
56
+
57
+ function rowKey(entry: OplogEntry) {
58
+ return `${entry.object_type}/${entry.object_id}/${entry.subkey}`;
59
+ }
60
+
61
+ /**
62
+ * Validate this property, as described in /docs/bucket-properties.md:
63
+ *
64
+ * $r(B_{[..id_n]}) = r(r(B_{[..id_i]}) \cup B_{[id_{i+1}..id_n]}) \;\forall\; i \in [1..n]$
65
+ *
66
+ * We test that a client syncing the entire bucket in one go (left side of the equation),
67
+ * ends up with the same result as another client syncing up to operation id_i, then sync
68
+ * the rest.
69
+ */
70
+ export function validateBucket(bucket: OplogEntry[]) {
71
+ const r1 = reduceBucket(bucket);
72
+ for (let i = 0; i <= bucket.length; i++) {
73
+ const r2 = reduceBucket(bucket.slice(0, i + 1));
74
+ const b3 = bucket.slice(i + 1);
75
+ const r3 = r2.concat(b3);
76
+ const r4 = reduceBucket(r3);
77
+ expect(r4).toEqual(r1);
78
+ }
79
+
80
+ // This is the same check, just implemented differently
81
+ validateCompactedBucket(bucket, bucket);
82
+ }
83
+
84
+ /**
85
+ * Validate these properties for a bucket $B$ and its compacted version $B'$,:
86
+ * as described in /docs/bucket-properties.md:
87
+ *
88
+ * 1. $r(B) = r(B')$
89
+ * 2. $r(B_{[..c]}) = r(r(B_{[..c_i]}) \cup B'_{[c_i+1..c]}) \;\forall\; c_i \in B$
90
+ *
91
+ * The first one is that the result of syncing the original bucket is the same as
92
+ * syncing the compacted bucket.
93
+ *
94
+ * The second property is that result of syncing the entire original bucket, is the same
95
+ * as syncing any partial version of that (up to op $c_i$), and then continue syncing
96
+ * using the compacted bucket.
97
+ */
98
+ export function validateCompactedBucket(bucket: OplogEntry[], compacted: OplogEntry[]) {
99
+ // r(B_{[..c]})
100
+ const r1 = reduceBucket(bucket);
101
+ // r(B) = r(B')
102
+ expect(reduceBucket(compacted)).toEqual(r1);
103
+
104
+ for (let i = 0; i < bucket.length; i++) {
105
+ // r(B_{[..c_i]})
106
+ const r2 = reduceBucket(bucket.slice(0, i + 1));
107
+ const c_i = BigInt(bucket[i].op_id);
108
+ // B'_{[c_i+1..c]}
109
+ const b3 = compacted.filter((op) => BigInt(op.op_id) > c_i);
110
+ // r(B_{[..c_i]}) \cup B'_{[c_i+1..c]}
111
+ const r3 = r2.concat(b3);
112
+ // r(r(B_{[..c_i]}) \cup B'_{[c_i+1..c]})
113
+ const r4 = reduceBucket(r3);
114
+ expect(r4).toEqual(r1);
115
+ }
116
+ }
@@ -1,8 +1,8 @@
1
- import { describe, expect, it } from 'vitest';
1
+ import { ChecksumCache, FetchChecksums, FetchPartialBucketChecksum } from '@/storage/ChecksumCache.js';
2
2
  import { BucketChecksum, OpId } from '@/util/protocol-types.js';
3
- import * as crypto from 'node:crypto';
4
3
  import { addBucketChecksums } from '@/util/util-index.js';
5
- import { ChecksumCache, FetchChecksums, FetchPartialBucketChecksum } from '@/storage/ChecksumCache.js';
4
+ import * as crypto from 'node:crypto';
5
+ import { describe, expect, it } from 'vitest';
6
6
 
7
7
  /**
8
8
  * Create a deterministic BucketChecksum based on the bucket name and checkpoint for testing purposes.
@@ -0,0 +1,216 @@
1
+ import { MongoCompactOptions } from '@/storage/mongo/MongoCompactor.js';
2
+ import { SqlSyncRules } from '@powersync/service-sync-rules';
3
+ import { describe, expect, test } from 'vitest';
4
+ import { validateCompactedBucket } from './bucket_validation.js';
5
+ import { oneFromAsync } from './stream_utils.js';
6
+ import { BATCH_OPTIONS, makeTestTable, MONGO_STORAGE_FACTORY, rid, testRules, ZERO_LSN } from './util.js';
7
+ import { ParseSyncRulesOptions, PersistedSyncRulesContent, StartBatchOptions } from '@/storage/BucketStorage.js';
8
+ import { getUuidReplicaIdentityBson } from '@/util/util-index.js';
9
+
10
+ const TEST_TABLE = makeTestTable('test', ['id']);
11
+
12
+ // Test with the default options - large batch sizes
13
+ describe('compacting buckets - default options', () => compactTests({}));
14
+
15
+ // Also test with the miniumum batch sizes, forcing usage of multiple batches internally
16
+ describe('compacting buckets - batched', () =>
17
+ compactTests({ clearBatchLimit: 2, moveBatchLimit: 1, moveBatchQueryLimit: 1 }));
18
+
19
+ function compactTests(compactOptions: MongoCompactOptions) {
20
+ const factory = MONGO_STORAGE_FACTORY;
21
+
22
+ test('compacting (1)', async () => {
23
+ const sync_rules = testRules(`
24
+ bucket_definitions:
25
+ global:
26
+ data: [select * from test]
27
+ `);
28
+
29
+ const storage = (await factory()).getInstance(sync_rules);
30
+
31
+ const result = await storage.startBatch(BATCH_OPTIONS, async (batch) => {
32
+ await batch.save({
33
+ sourceTable: TEST_TABLE,
34
+ tag: 'insert',
35
+ after: {
36
+ id: 't1'
37
+ },
38
+ afterReplicaId: rid('t1')
39
+ });
40
+
41
+ await batch.save({
42
+ sourceTable: TEST_TABLE,
43
+ tag: 'insert',
44
+ after: {
45
+ id: 't2'
46
+ },
47
+ afterReplicaId: rid('t2')
48
+ });
49
+
50
+ await batch.save({
51
+ sourceTable: TEST_TABLE,
52
+ tag: 'update',
53
+ after: {
54
+ id: 't2'
55
+ },
56
+ afterReplicaId: rid('t2')
57
+ });
58
+ });
59
+
60
+ const checkpoint = result!.flushed_op;
61
+
62
+ const batchBefore = await oneFromAsync(storage.getBucketDataBatch(checkpoint, new Map([['global[]', '0']])));
63
+ const dataBefore = batchBefore.batch.data;
64
+
65
+ expect(dataBefore).toMatchObject([
66
+ {
67
+ checksum: 2634521662,
68
+ object_id: 't1',
69
+ op: 'PUT',
70
+ op_id: '1'
71
+ },
72
+ {
73
+ checksum: 4243212114,
74
+ object_id: 't2',
75
+ op: 'PUT',
76
+ op_id: '2'
77
+ },
78
+ {
79
+ checksum: 4243212114,
80
+ object_id: 't2',
81
+ op: 'PUT',
82
+ op_id: '3'
83
+ }
84
+ ]);
85
+
86
+ await storage.compact(compactOptions);
87
+
88
+ const batchAfter = await oneFromAsync(storage.getBucketDataBatch(checkpoint, new Map([['global[]', '0']])));
89
+ const dataAfter = batchAfter.batch.data;
90
+
91
+ expect(batchAfter.targetOp).toEqual(3n);
92
+ expect(dataAfter).toMatchObject([
93
+ {
94
+ checksum: 2634521662,
95
+ object_id: 't1',
96
+ op: 'PUT',
97
+ op_id: '1'
98
+ },
99
+ {
100
+ checksum: 4243212114,
101
+ op: 'MOVE',
102
+ op_id: '2'
103
+ },
104
+ {
105
+ checksum: 4243212114,
106
+ object_id: 't2',
107
+ op: 'PUT',
108
+ op_id: '3'
109
+ }
110
+ ]);
111
+
112
+ validateCompactedBucket(dataBefore, dataAfter);
113
+ });
114
+
115
+ test('compacting (2)', async () => {
116
+ const sync_rules = testRules(`
117
+ bucket_definitions:
118
+ global:
119
+ data: [select * from test]
120
+ `);
121
+
122
+ const storage = (await factory()).getInstance(sync_rules);
123
+
124
+ const result = await storage.startBatch(BATCH_OPTIONS, async (batch) => {
125
+ await batch.save({
126
+ sourceTable: TEST_TABLE,
127
+ tag: 'insert',
128
+ after: {
129
+ id: 't1'
130
+ },
131
+ afterReplicaId: rid('t1')
132
+ });
133
+
134
+ await batch.save({
135
+ sourceTable: TEST_TABLE,
136
+ tag: 'insert',
137
+ after: {
138
+ id: 't2'
139
+ },
140
+ afterReplicaId: rid('t2')
141
+ });
142
+
143
+ await batch.save({
144
+ sourceTable: TEST_TABLE,
145
+ tag: 'delete',
146
+ before: {
147
+ id: 't1'
148
+ },
149
+ beforeReplicaId: rid('t1')
150
+ });
151
+
152
+ await batch.save({
153
+ sourceTable: TEST_TABLE,
154
+ tag: 'update',
155
+ after: {
156
+ id: 't2'
157
+ },
158
+ afterReplicaId: rid('t2')
159
+ });
160
+ });
161
+
162
+ const checkpoint = result!.flushed_op;
163
+
164
+ const batchBefore = await oneFromAsync(storage.getBucketDataBatch(checkpoint, new Map([['global[]', '0']])));
165
+ const dataBefore = batchBefore.batch.data;
166
+
167
+ expect(dataBefore).toMatchObject([
168
+ {
169
+ checksum: 2634521662,
170
+ object_id: 't1',
171
+ op: 'PUT',
172
+ op_id: '1'
173
+ },
174
+ {
175
+ checksum: 4243212114,
176
+ object_id: 't2',
177
+ op: 'PUT',
178
+ op_id: '2'
179
+ },
180
+ {
181
+ checksum: 4228978084,
182
+ object_id: 't1',
183
+ op: 'REMOVE',
184
+ op_id: '3'
185
+ },
186
+ {
187
+ checksum: 4243212114,
188
+ object_id: 't2',
189
+ op: 'PUT',
190
+ op_id: '4'
191
+ }
192
+ ]);
193
+
194
+ await storage.compact(compactOptions);
195
+
196
+ const batchAfter = await oneFromAsync(storage.getBucketDataBatch(checkpoint, new Map([['global[]', '0']])));
197
+ const dataAfter = batchAfter.batch.data;
198
+
199
+ expect(batchAfter.targetOp).toEqual(4n);
200
+ expect(dataAfter).toMatchObject([
201
+ {
202
+ checksum: -1778190028,
203
+ op: 'CLEAR',
204
+ op_id: '3'
205
+ },
206
+ {
207
+ checksum: 4243212114,
208
+ object_id: 't2',
209
+ op: 'PUT',
210
+ op_id: '4'
211
+ }
212
+ ]);
213
+
214
+ validateCompactedBucket(dataBefore, dataAfter);
215
+ });
216
+ }