@powersync/service-module-mongodb-storage 0.15.3 → 0.16.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 (204) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/dist/migrations/db/migrations/1688556755264-initial-sync-rules.js +1 -1
  3. package/dist/migrations/db/migrations/1688556755264-initial-sync-rules.js.map +1 -1
  4. package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js +3 -3
  5. package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js.map +1 -1
  6. package/dist/migrations/db/migrations/1770213298299-storage-version.js.map +1 -1
  7. package/dist/storage/MongoBucketStorage.d.ts +5 -3
  8. package/dist/storage/MongoBucketStorage.js +50 -36
  9. package/dist/storage/MongoBucketStorage.js.map +1 -1
  10. package/dist/storage/MongoReportStorage.js.map +1 -1
  11. package/dist/storage/implementation/BucketDefinitionMapping.d.ts +17 -0
  12. package/dist/storage/implementation/BucketDefinitionMapping.js +58 -0
  13. package/dist/storage/implementation/BucketDefinitionMapping.js.map +1 -0
  14. package/dist/storage/implementation/MongoBucketBatch.d.ts +16 -14
  15. package/dist/storage/implementation/MongoBucketBatch.js +80 -115
  16. package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
  17. package/dist/storage/implementation/MongoBucketBatchShared.d.ts +5 -0
  18. package/dist/storage/implementation/MongoBucketBatchShared.js +8 -0
  19. package/dist/storage/implementation/MongoBucketBatchShared.js.map +1 -0
  20. package/dist/storage/implementation/MongoChecksums.d.ts +28 -17
  21. package/dist/storage/implementation/MongoChecksums.js +13 -72
  22. package/dist/storage/implementation/MongoChecksums.js.map +1 -1
  23. package/dist/storage/implementation/MongoCompactor.d.ts +98 -58
  24. package/dist/storage/implementation/MongoCompactor.js +229 -296
  25. package/dist/storage/implementation/MongoCompactor.js.map +1 -1
  26. package/dist/storage/implementation/MongoParameterCompactor.d.ts +11 -6
  27. package/dist/storage/implementation/MongoParameterCompactor.js +11 -8
  28. package/dist/storage/implementation/MongoParameterCompactor.js.map +1 -1
  29. package/dist/storage/implementation/MongoPersistedSyncRules.d.ts +14 -0
  30. package/dist/storage/implementation/MongoPersistedSyncRules.js +64 -0
  31. package/dist/storage/implementation/MongoPersistedSyncRules.js.map +1 -0
  32. package/dist/storage/implementation/MongoPersistedSyncRulesContent.d.ts +3 -0
  33. package/dist/storage/implementation/MongoPersistedSyncRulesContent.js +9 -0
  34. package/dist/storage/implementation/MongoPersistedSyncRulesContent.js.map +1 -1
  35. package/dist/storage/implementation/MongoStorageProvider.js +1 -1
  36. package/dist/storage/implementation/MongoStorageProvider.js.map +1 -1
  37. package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +49 -30
  38. package/dist/storage/implementation/MongoSyncBucketStorage.js +96 -388
  39. package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
  40. package/dist/storage/implementation/MongoSyncRulesLock.d.ts +5 -3
  41. package/dist/storage/implementation/MongoSyncRulesLock.js +12 -10
  42. package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -1
  43. package/dist/storage/implementation/MongoWriteCheckpointAPI.js +1 -1
  44. package/dist/storage/implementation/MongoWriteCheckpointAPI.js.map +1 -1
  45. package/dist/storage/implementation/OperationBatch.js +1 -1
  46. package/dist/storage/implementation/common/BucketDataDoc.d.ts +35 -0
  47. package/dist/storage/implementation/common/BucketDataDoc.js +2 -0
  48. package/dist/storage/implementation/common/BucketDataDoc.js.map +1 -0
  49. package/dist/storage/implementation/common/MongoSyncBucketStorageContext.d.ts +13 -0
  50. package/dist/storage/implementation/common/MongoSyncBucketStorageContext.js +2 -0
  51. package/dist/storage/implementation/common/MongoSyncBucketStorageContext.js.map +1 -0
  52. package/dist/storage/implementation/common/PersistedBatch.d.ts +108 -0
  53. package/dist/storage/implementation/common/PersistedBatch.js +237 -0
  54. package/dist/storage/implementation/common/PersistedBatch.js.map +1 -0
  55. package/dist/storage/implementation/common/SingleBucketStore.d.ts +54 -0
  56. package/dist/storage/implementation/common/SingleBucketStore.js +3 -0
  57. package/dist/storage/implementation/common/SingleBucketStore.js.map +1 -0
  58. package/dist/storage/implementation/common/SourceRecordStore.d.ts +36 -0
  59. package/dist/storage/implementation/common/SourceRecordStore.js +2 -0
  60. package/dist/storage/implementation/common/SourceRecordStore.js.map +1 -0
  61. package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.d.ts +27 -0
  62. package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js +57 -0
  63. package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js.map +1 -0
  64. package/dist/storage/implementation/createMongoSyncBucketStorage.d.ts +7 -0
  65. package/dist/storage/implementation/createMongoSyncBucketStorage.js +9 -0
  66. package/dist/storage/implementation/createMongoSyncBucketStorage.js.map +1 -0
  67. package/dist/storage/implementation/db.d.ts +34 -34
  68. package/dist/storage/implementation/db.js +78 -98
  69. package/dist/storage/implementation/db.js.map +1 -1
  70. package/dist/storage/implementation/models.d.ts +63 -34
  71. package/dist/storage/implementation/models.js +21 -2
  72. package/dist/storage/implementation/models.js.map +1 -1
  73. package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +13 -0
  74. package/dist/storage/implementation/v1/MongoBucketBatchV1.js +22 -0
  75. package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -0
  76. package/dist/storage/implementation/v1/MongoChecksumsV1.d.ts +12 -0
  77. package/dist/storage/implementation/v1/MongoChecksumsV1.js +56 -0
  78. package/dist/storage/implementation/v1/MongoChecksumsV1.js.map +1 -0
  79. package/dist/storage/implementation/v1/MongoCompactorV1.d.ts +23 -0
  80. package/dist/storage/implementation/v1/MongoCompactorV1.js +52 -0
  81. package/dist/storage/implementation/v1/MongoCompactorV1.js.map +1 -0
  82. package/dist/storage/implementation/v1/MongoParameterCompactorV1.d.ts +9 -0
  83. package/dist/storage/implementation/v1/MongoParameterCompactorV1.js +20 -0
  84. package/dist/storage/implementation/v1/MongoParameterCompactorV1.js.map +1 -0
  85. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.d.ts +41 -0
  86. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +283 -0
  87. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js.map +1 -0
  88. package/dist/storage/implementation/v1/PersistedBatchV1.d.ts +26 -0
  89. package/dist/storage/implementation/v1/PersistedBatchV1.js +183 -0
  90. package/dist/storage/implementation/v1/PersistedBatchV1.js.map +1 -0
  91. package/dist/storage/implementation/v1/SingleBucketStoreV1.d.ts +18 -0
  92. package/dist/storage/implementation/v1/SingleBucketStoreV1.js +57 -0
  93. package/dist/storage/implementation/v1/SingleBucketStoreV1.js.map +1 -0
  94. package/dist/storage/implementation/v1/SourceRecordStoreV1.d.ts +19 -0
  95. package/dist/storage/implementation/v1/SourceRecordStoreV1.js +105 -0
  96. package/dist/storage/implementation/v1/SourceRecordStoreV1.js.map +1 -0
  97. package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.d.ts +12 -0
  98. package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.js +20 -0
  99. package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.js.map +1 -0
  100. package/dist/storage/implementation/v1/models.d.ts +34 -0
  101. package/dist/storage/implementation/v1/models.js +37 -0
  102. package/dist/storage/implementation/v1/models.js.map +1 -0
  103. package/dist/storage/implementation/v3/MongoBucketBatchV3.d.ts +13 -0
  104. package/dist/storage/implementation/v3/MongoBucketBatchV3.js +34 -0
  105. package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -0
  106. package/dist/storage/implementation/v3/MongoChecksumsV3.d.ts +15 -0
  107. package/dist/storage/implementation/v3/MongoChecksumsV3.js +84 -0
  108. package/dist/storage/implementation/v3/MongoChecksumsV3.js.map +1 -0
  109. package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +23 -0
  110. package/dist/storage/implementation/v3/MongoCompactorV3.js +68 -0
  111. package/dist/storage/implementation/v3/MongoCompactorV3.js.map +1 -0
  112. package/dist/storage/implementation/v3/MongoParameterCompactorV3.d.ts +9 -0
  113. package/dist/storage/implementation/v3/MongoParameterCompactorV3.js +18 -0
  114. package/dist/storage/implementation/v3/MongoParameterCompactorV3.js.map +1 -0
  115. package/dist/storage/implementation/v3/MongoParameterLookupV3.d.ts +5 -0
  116. package/dist/storage/implementation/v3/MongoParameterLookupV3.js +9 -0
  117. package/dist/storage/implementation/v3/MongoParameterLookupV3.js.map +1 -0
  118. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.d.ts +41 -0
  119. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +407 -0
  120. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -0
  121. package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +29 -0
  122. package/dist/storage/implementation/v3/PersistedBatchV3.js +259 -0
  123. package/dist/storage/implementation/v3/PersistedBatchV3.js.map +1 -0
  124. package/dist/storage/implementation/v3/SingleBucketStoreV3.d.ts +18 -0
  125. package/dist/storage/implementation/v3/SingleBucketStoreV3.js +48 -0
  126. package/dist/storage/implementation/v3/SingleBucketStoreV3.js.map +1 -0
  127. package/dist/storage/implementation/v3/SourceRecordStoreV3.d.ts +22 -0
  128. package/dist/storage/implementation/v3/SourceRecordStoreV3.js +164 -0
  129. package/dist/storage/implementation/v3/SourceRecordStoreV3.js.map +1 -0
  130. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.d.ts +21 -0
  131. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js +71 -0
  132. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js.map +1 -0
  133. package/dist/storage/implementation/v3/models.d.ts +43 -0
  134. package/dist/storage/implementation/v3/models.js +34 -0
  135. package/dist/storage/implementation/v3/models.js.map +1 -0
  136. package/dist/storage/storage-index.d.ts +8 -5
  137. package/dist/storage/storage-index.js +8 -5
  138. package/dist/storage/storage-index.js.map +1 -1
  139. package/dist/utils/util.d.ts +11 -4
  140. package/dist/utils/util.js +25 -4
  141. package/dist/utils/util.js.map +1 -1
  142. package/package.json +9 -9
  143. package/src/migrations/db/migrations/1688556755264-initial-sync-rules.ts +1 -1
  144. package/src/migrations/db/migrations/1702295701188-sync-rule-state.ts +7 -7
  145. package/src/migrations/db/migrations/1770213298299-storage-version.ts +1 -1
  146. package/src/storage/MongoBucketStorage.ts +97 -62
  147. package/src/storage/MongoReportStorage.ts +2 -2
  148. package/src/storage/implementation/BucketDefinitionMapping.ts +72 -0
  149. package/src/storage/implementation/MongoBucketBatch.ts +110 -144
  150. package/src/storage/implementation/MongoBucketBatchShared.ts +11 -0
  151. package/src/storage/implementation/MongoChecksums.ts +53 -76
  152. package/src/storage/implementation/MongoCompactor.ts +374 -404
  153. package/src/storage/implementation/MongoParameterCompactor.ts +37 -24
  154. package/src/storage/implementation/MongoPersistedSyncRules.ts +76 -0
  155. package/src/storage/implementation/MongoPersistedSyncRulesContent.ts +18 -1
  156. package/src/storage/implementation/MongoStorageProvider.ts +1 -1
  157. package/src/storage/implementation/MongoSyncBucketStorage.ts +190 -457
  158. package/src/storage/implementation/MongoSyncRulesLock.ts +12 -14
  159. package/src/storage/implementation/MongoWriteCheckpointAPI.ts +4 -2
  160. package/src/storage/implementation/OperationBatch.ts +1 -1
  161. package/src/storage/implementation/common/BucketDataDoc.ts +37 -0
  162. package/src/storage/implementation/common/MongoSyncBucketStorageContext.ts +15 -0
  163. package/src/storage/implementation/common/PersistedBatch.ts +364 -0
  164. package/src/storage/implementation/common/SingleBucketStore.ts +63 -0
  165. package/src/storage/implementation/common/SourceRecordStore.ts +49 -0
  166. package/src/storage/implementation/common/VersionedPowerSyncMongoBase.ts +80 -0
  167. package/src/storage/implementation/createMongoSyncBucketStorage.ts +25 -0
  168. package/src/storage/implementation/db.ts +107 -128
  169. package/src/storage/implementation/models.ts +84 -38
  170. package/src/storage/implementation/v1/MongoBucketBatchV1.ts +32 -0
  171. package/src/storage/implementation/v1/MongoChecksumsV1.ts +75 -0
  172. package/src/storage/implementation/v1/MongoCompactorV1.ts +93 -0
  173. package/src/storage/implementation/v1/MongoParameterCompactorV1.ts +26 -0
  174. package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +448 -0
  175. package/src/storage/implementation/v1/PersistedBatchV1.ts +230 -0
  176. package/src/storage/implementation/v1/SingleBucketStoreV1.ts +74 -0
  177. package/src/storage/implementation/v1/SourceRecordStoreV1.ts +156 -0
  178. package/src/storage/implementation/v1/VersionedPowerSyncMongoV1.ts +28 -0
  179. package/src/storage/implementation/v1/models.ts +84 -0
  180. package/src/storage/implementation/v3/MongoBucketBatchV3.ts +44 -0
  181. package/src/storage/implementation/v3/MongoChecksumsV3.ts +120 -0
  182. package/src/storage/implementation/v3/MongoCompactorV3.ts +107 -0
  183. package/src/storage/implementation/v3/MongoParameterCompactorV3.ts +24 -0
  184. package/src/storage/implementation/v3/MongoParameterLookupV3.ts +12 -0
  185. package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +550 -0
  186. package/src/storage/implementation/v3/PersistedBatchV3.ts +318 -0
  187. package/src/storage/implementation/v3/SingleBucketStoreV3.ts +68 -0
  188. package/src/storage/implementation/v3/SourceRecordStoreV3.ts +226 -0
  189. package/src/storage/implementation/v3/VersionedPowerSyncMongoV3.ts +112 -0
  190. package/src/storage/implementation/v3/models.ts +96 -0
  191. package/src/storage/storage-index.ts +8 -5
  192. package/src/utils/util.ts +36 -7
  193. package/test/src/__snapshots__/storage_sync.test.ts.snap +282 -0
  194. package/test/src/connection-report-storage.test.ts +3 -3
  195. package/test/src/setup.ts +1 -1
  196. package/test/src/storage.test.ts +2 -2
  197. package/test/src/storage_compacting.test.ts +57 -29
  198. package/test/src/storage_sync.test.ts +351 -5
  199. package/test/tsconfig.json +0 -1
  200. package/tsconfig.tsbuildinfo +1 -1
  201. package/dist/storage/implementation/PersistedBatch.d.ts +0 -71
  202. package/dist/storage/implementation/PersistedBatch.js +0 -354
  203. package/dist/storage/implementation/PersistedBatch.js.map +0 -1
  204. package/src/storage/implementation/PersistedBatch.ts +0 -432
@@ -1,7 +1,7 @@
1
+ import { VersionedPowerSyncMongoV3 } from '@module/storage/implementation/v3/VersionedPowerSyncMongoV3.js';
1
2
  import { storage, SyncRulesBucketStorage, updateSyncRulesFromYaml } from '@powersync/service-core';
2
3
  import { bucketRequest, register, test_utils } from '@powersync/service-core-tests';
3
4
  import { describe, expect, test } from 'vitest';
4
- import { MongoCompactor } from '../../src/storage/implementation/MongoCompactor.js';
5
5
  import { INITIALIZED_MONGO_STORAGE_FACTORY } from './util.js';
6
6
 
7
7
  describe('Mongo Sync Bucket Storage Compact', () => {
@@ -64,9 +64,16 @@ bucket_definitions:
64
64
 
65
65
  test('full compact', async () => {
66
66
  const { bucketStorage, checkpoint, factory, syncRules } = await setup();
67
+ const storageDb = bucketStorage.db;
67
68
 
68
69
  // Simulate bucket_state from old version not being available
69
- await factory.db.bucket_state.deleteMany({});
70
+ if (storageDb.storageConfig.incrementalReprocessing) {
71
+ // This should actually never happen on V3, but we test this anyway.
72
+ // Can remove this if it causes issues in the future.
73
+ await (storageDb as VersionedPowerSyncMongoV3).bucketStateV3(bucketStorage.group_id).deleteMany({});
74
+ } else {
75
+ await factory.db.bucket_state.deleteMany({});
76
+ }
70
77
 
71
78
  await bucketStorage.compact({
72
79
  clearBatchLimit: 200,
@@ -95,10 +102,10 @@ bucket_definitions:
95
102
  });
96
103
 
97
104
  test('populatePersistentChecksumCache', async () => {
98
- // Populate old sync rules version
105
+ // Populate old replication stream
99
106
  const { factory } = await setup();
100
107
 
101
- // Now populate another version (bucket definition name changed)
108
+ // Now populate another replication stream (bucket definition name changed)
102
109
  const syncRules = await factory.updateSyncRules(
103
110
  updateSyncRulesFromYaml(`
104
111
  bucket_definitions:
@@ -108,6 +115,7 @@ bucket_definitions:
108
115
  `)
109
116
  );
110
117
  const bucketStorage = factory.getInstance(syncRules);
118
+ const storageDb = (bucketStorage as any).db;
111
119
 
112
120
  await populate(bucketStorage, 2);
113
121
  const { checkpoint } = await bucketStorage.getCheckpoint();
@@ -158,35 +166,54 @@ bucket_definitions:
158
166
  `)
159
167
  );
160
168
  const bucketStorage = factory.getInstance(syncRules);
169
+ const storageDb = bucketStorage.db;
161
170
 
162
171
  // This simulates bucket_state created using bigint bytes.
163
172
  // This typically happens when buckets get very large (> 2GiB). We don't want to create that much
164
173
  // data in the tests, so we directly insert the bucket_state here.
165
- await factory.db.bucket_state.insertOne({
166
- _id: {
167
- g: bucketStorage.group_id,
168
- b: 'global[]'
169
- },
170
- last_op: 5n,
171
- compacted_state: {
172
- op_id: 3n,
173
- count: 3,
174
- checksum: 0n,
175
- bytes: 7n
176
- },
177
- estimate_since_compact: {
178
- count: 2,
179
- bytes: 5n
180
- }
181
- });
182
-
183
- // This test uses a couple of internal APIs of the compactor - there is no simple way
184
- // to test this using the current public APIs.
185
- const compactor = new MongoCompactor(bucketStorage, (bucketStorage as any).db, {
186
- maxOpId: 5n
187
- });
188
-
189
- const dirtyBuckets = (compactor as any).dirtyBucketBatches({
174
+ if (storageDb.storageConfig.incrementalReprocessing) {
175
+ const bucketStateCollection = (storageDb as VersionedPowerSyncMongoV3).bucketStateV3(bucketStorage.group_id);
176
+ await bucketStateCollection.insertOne({
177
+ _id: {
178
+ d: '1',
179
+ b: 'global[]'
180
+ },
181
+ last_op: 5n,
182
+ compacted_state: {
183
+ op_id: 3n,
184
+ count: 3,
185
+ checksum: 0n,
186
+ bytes: 7n
187
+ },
188
+ estimate_since_compact: {
189
+ count: 2,
190
+ bytes: 5n
191
+ }
192
+ });
193
+ } else {
194
+ await factory.db.bucket_state.insertOne({
195
+ _id: {
196
+ g: bucketStorage.group_id,
197
+ b: 'global[]'
198
+ },
199
+ last_op: 5n,
200
+ compacted_state: {
201
+ op_id: 3n,
202
+ count: 3,
203
+ checksum: 0n,
204
+ bytes: 7n
205
+ },
206
+ estimate_since_compact: {
207
+ count: 2,
208
+ bytes: 5n
209
+ }
210
+ });
211
+ }
212
+
213
+ // This test uses a couple of "internal" APIs of the compactor.
214
+ const compactor = bucketStorage.createMongoCompactor({ maxOpId: 5n });
215
+
216
+ const dirtyBuckets = compactor.dirtyBucketBatches({
190
217
  minBucketChanges: 1,
191
218
  minChangeRatio: 0.39
192
219
  });
@@ -205,6 +232,7 @@ bucket_definitions:
205
232
  expect(checksumBuckets).toEqual([
206
233
  {
207
234
  bucket: 'global[]',
235
+ definitionId: storageDb.storageConfig.incrementalReprocessing ? '1' : null,
208
236
  estimatedCount: 5
209
237
  }
210
238
  ]);
@@ -1,6 +1,14 @@
1
- import { storage, updateSyncRulesFromYaml } from '@powersync/service-core';
1
+ import { deserializeParameterLookup, JwtPayload, storage, updateSyncRulesFromYaml } from '@powersync/service-core';
2
2
  import { bucketRequest, register, test_utils } from '@powersync/service-core-tests';
3
+ import { RequestParameters } from '@powersync/service-sync-rules';
4
+ import * as bson from 'bson';
3
5
  import { describe, expect, test } from 'vitest';
6
+ import { MongoBucketStorage } from '../../src/storage/MongoBucketStorage.js';
7
+ import { MongoSyncBucketStorage } from '../../src/storage/implementation/createMongoSyncBucketStorage.js';
8
+ import { SyncRuleDocument } from '../../src/storage/implementation/models.js';
9
+ import { SourceRecordStoreV3 } from '../../src/storage/implementation/v3/SourceRecordStoreV3.js';
10
+ import type { VersionedPowerSyncMongoV3 } from '../../src/storage/implementation/v3/VersionedPowerSyncMongoV3.js';
11
+ import { CurrentBucketV3 } from '../../src/storage/implementation/v3/models.js';
4
12
  import { INITIALIZED_MONGO_STORAGE_FACTORY, TEST_STORAGE_VERSIONS } from './util.js';
5
13
 
6
14
  function registerSyncStorageTests(storageConfig: storage.TestStorageConfig, storageVersion: number) {
@@ -126,12 +134,350 @@ function registerSyncStorageTests(storageConfig: storage.TestStorageConfig, stor
126
134
 
127
135
  // Test that the checksum type is correct.
128
136
  // Specifically, test that it never persisted as double.
129
- const mongoFactory = factory as any;
130
- const checksumTypes = await mongoFactory.db.bucket_data
131
- .aggregate([{ $group: { _id: { $type: '$checksum' }, count: { $sum: 1 } } }])
132
- .toArray();
137
+ const mongoFactory = factory as MongoBucketStorage;
138
+ const checksumTypes =
139
+ storageVersion >= 3
140
+ ? (
141
+ await Promise.all(
142
+ (
143
+ await mongoFactory.db.db
144
+ .listCollections({ name: new RegExp(`^bucket_data_${syncRules.id}_`) }, { nameOnly: true })
145
+ .toArray()
146
+ ).map((collection: { name: string }) =>
147
+ mongoFactory.db.db
148
+ .collection(collection.name)
149
+ .aggregate([{ $group: { _id: { $type: '$checksum' }, count: { $sum: 1 } } }])
150
+ .toArray()
151
+ )
152
+ )
153
+ ).flat()
154
+ : await mongoFactory.db.bucket_data
155
+ .aggregate([{ $group: { _id: { $type: '$checksum' }, count: { $sum: 1 } } }])
156
+ .toArray();
133
157
  expect(checksumTypes).toEqual([{ _id: 'long', count: 4 }]);
134
158
  });
159
+
160
+ test.runIf(storageVersion >= 3)('uses v3 mongodb model shapes', async () => {
161
+ await using factory = await storageConfig.factory();
162
+ const syncRules = await factory.updateSyncRules(
163
+ updateSyncRulesFromYaml(
164
+ `
165
+ bucket_definitions:
166
+ global:
167
+ parameters:
168
+ - SELECT owner_id FROM test WHERE id = token_parameters.test
169
+ data:
170
+ - SELECT id, description, owner_id FROM test WHERE id = bucket.owner_id
171
+ `,
172
+ { storageVersion }
173
+ )
174
+ );
175
+ const bucketStorage = factory.getInstance(syncRules);
176
+ const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncRules();
177
+ await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
178
+ const sourceTable = await test_utils.resolveTestTable(writer, 'test', ['id'], INITIALIZED_MONGO_STORAGE_FACTORY);
179
+
180
+ await writer.save({
181
+ sourceTable,
182
+ tag: storage.SaveOperationTag.INSERT,
183
+ after: {
184
+ id: 'shape-check',
185
+ description: 'shape',
186
+ owner_id: 'user-1'
187
+ },
188
+ afterReplicaId: test_utils.rid('shape-check')
189
+ });
190
+ await writer.markAllSnapshotDone('1/1');
191
+ await writer.commit('1/1');
192
+
193
+ const checkpoint = await bucketStorage.getCheckpoint();
194
+ const parameters = new RequestParameters(new JwtPayload({ sub: 'u1', parameters: { test: 'shape-check' } }), {});
195
+ const querier = sync_rules.getBucketParameterQuerier(test_utils.querierOptions(parameters)).querier;
196
+ const buckets = await querier.queryDynamicBucketDescriptions({
197
+ async getParameterSets(lookups) {
198
+ expect(lookups.map((l) => l.indexKey)).toEqual([['shape-check']]);
199
+ expect(lookups[0].indexId).toEqual('1');
200
+
201
+ const parameter_sets = await checkpoint.getParameterSets(lookups, 1000);
202
+ expect(parameter_sets).toEqual([{ lookup: lookups[0], rows: [{ owner_id: 'user-1' }] }]);
203
+ return parameter_sets;
204
+ }
205
+ });
206
+ expect(buckets.map((b) => b.bucket)).toEqual([bucketRequest(syncRules, 'global["user-1"]').bucket]);
207
+
208
+ const mongoFactory = factory as MongoBucketStorage;
209
+ const db = (bucketStorage as MongoSyncBucketStorage).db as VersionedPowerSyncMongoV3;
210
+ const currentDataCollections = await db.listSourceRecordCollectionsV3(syncRules.id);
211
+ const currentData = await currentDataCollections[0]?.findOne({});
212
+ const firstBucket: CurrentBucketV3 | undefined = currentData?.buckets[0] as CurrentBucketV3 | undefined;
213
+ expect(firstBucket?.def).toMatch(/^[0-9a-f]+$/);
214
+
215
+ const bucketCollections = await mongoFactory.db.db
216
+ .listCollections({ name: new RegExp(`^bucket_data_${syncRules.id}_`) }, { nameOnly: true })
217
+ .toArray();
218
+ expect(
219
+ bucketCollections.some((collection) => collection.name === `bucket_data_${syncRules.id}_${firstBucket?.def}`)
220
+ ).toBe(true);
221
+
222
+ const syncRule = await mongoFactory.db.sync_rules.findOne({ _id: syncRules.id });
223
+ const ruleMapping: SyncRuleDocument['rule_mapping'] | undefined = syncRule?.rule_mapping;
224
+ expect(Object.keys(ruleMapping?.definitions ?? {})).not.toHaveLength(0);
225
+
226
+ const parameterIndexId = Object.values(ruleMapping?.parameter_indexes ?? {})[0] as string | undefined;
227
+ expect(parameterIndexId).toBeDefined();
228
+ const parameterEntry = await db.parameterIndexV3(syncRules.id, parameterIndexId!).findOne({});
229
+ expect(deserializeParameterLookup(parameterEntry!.lookup)).toEqual(['shape-check']);
230
+ });
231
+
232
+ test.runIf(storageVersion < 3)('uses a single current_data collection for v1 source records', async () => {
233
+ await using factory = await storageConfig.factory();
234
+ const syncRules = await factory.updateSyncRules(
235
+ updateSyncRulesFromYaml(
236
+ `
237
+ bucket_definitions:
238
+ global:
239
+ data:
240
+ - SELECT id, description FROM test
241
+ `,
242
+ { storageVersion }
243
+ )
244
+ );
245
+ const bucketStorage = factory.getInstance(syncRules);
246
+
247
+ await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
248
+ const sourceTable = await test_utils.resolveTestTable(writer, 'test', ['id'], INITIALIZED_MONGO_STORAGE_FACTORY);
249
+
250
+ await writer.save({
251
+ sourceTable,
252
+ tag: storage.SaveOperationTag.INSERT,
253
+ after: {
254
+ id: 'shape-check',
255
+ description: 'shape'
256
+ },
257
+ afterReplicaId: test_utils.rid('shape-check')
258
+ });
259
+ await writer.markAllSnapshotDone('1/1');
260
+ await writer.commit('1/1');
261
+
262
+ const mongoFactory = factory as MongoBucketStorage;
263
+ expect(await mongoFactory.db.current_data.countDocuments({ '_id.g': syncRules.id })).toBe(1);
264
+
265
+ const sourceRecordCollections = await mongoFactory.db.db
266
+ .listCollections({ name: new RegExp(`^source_records_${syncRules.id}_`) }, { nameOnly: true })
267
+ .toArray();
268
+ expect(sourceRecordCollections).toEqual([]);
269
+ });
270
+
271
+ test.runIf(storageVersion < 3)('clear removes v1 current_data rows', async () => {
272
+ await using factory = await storageConfig.factory();
273
+ const syncRules = await factory.updateSyncRules(
274
+ updateSyncRulesFromYaml(
275
+ `
276
+ bucket_definitions:
277
+ global:
278
+ data:
279
+ - SELECT id, description FROM test
280
+ `,
281
+ { storageVersion }
282
+ )
283
+ );
284
+ const bucketStorage = factory.getInstance(syncRules);
285
+
286
+ await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
287
+ const sourceTable = await test_utils.resolveTestTable(writer, 'test', ['id'], INITIALIZED_MONGO_STORAGE_FACTORY);
288
+
289
+ await writer.save({
290
+ sourceTable,
291
+ tag: storage.SaveOperationTag.INSERT,
292
+ after: {
293
+ id: 'clear-check',
294
+ description: 'shape'
295
+ },
296
+ afterReplicaId: test_utils.rid('clear-check')
297
+ });
298
+ await writer.markAllSnapshotDone('1/1');
299
+ await writer.commit('1/1');
300
+
301
+ const mongoFactory = factory as MongoBucketStorage;
302
+ expect(await mongoFactory.db.current_data.countDocuments({ '_id.g': syncRules.id })).toBe(1);
303
+
304
+ await bucketStorage.clear();
305
+
306
+ expect(await mongoFactory.db.current_data.countDocuments({ '_id.g': syncRules.id })).toBe(0);
307
+ });
308
+
309
+ test.runIf(storageVersion < 3)('storage metrics include v1 current_data', async () => {
310
+ await using factory = await storageConfig.factory();
311
+ const syncRules = await factory.updateSyncRules(
312
+ updateSyncRulesFromYaml(
313
+ `
314
+ bucket_definitions:
315
+ global:
316
+ data:
317
+ - SELECT id, description FROM test
318
+ `,
319
+ { storageVersion }
320
+ )
321
+ );
322
+ const bucketStorage = factory.getInstance(syncRules);
323
+ const metricsBefore = await factory.getStorageMetrics();
324
+
325
+ await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
326
+ const sourceTable = await test_utils.resolveTestTable(writer, 'test', ['id'], INITIALIZED_MONGO_STORAGE_FACTORY);
327
+
328
+ await writer.save({
329
+ sourceTable,
330
+ tag: storage.SaveOperationTag.INSERT,
331
+ after: {
332
+ id: 'metric-check',
333
+ description: 'shape'
334
+ },
335
+ afterReplicaId: test_utils.rid('metric-check')
336
+ });
337
+ await writer.markAllSnapshotDone('1/1');
338
+ await writer.commit('1/1');
339
+
340
+ const mongoFactory = factory as MongoBucketStorage;
341
+ expect(await mongoFactory.db.current_data.countDocuments({ '_id.g': syncRules.id })).toBe(1);
342
+
343
+ const metricsAfter = await factory.getStorageMetrics();
344
+ expect(metricsAfter.replication_size_bytes).toBeGreaterThan(metricsBefore.replication_size_bytes);
345
+ });
346
+
347
+ test.runIf(storageVersion >= 3)(
348
+ 'loads parameter checkpoint changes across all v3 parameter index collections',
349
+ async () => {
350
+ await using factory = await storageConfig.factory();
351
+ const syncRules = await factory.updateSyncRules(
352
+ updateSyncRulesFromYaml(
353
+ `
354
+ bucket_definitions:
355
+ by_owner:
356
+ parameters:
357
+ - SELECT owner_id FROM test WHERE id = token_parameters.owner_lookup
358
+ data:
359
+ - SELECT id, owner_id FROM test WHERE owner_id = bucket.owner_id
360
+ by_category:
361
+ parameters:
362
+ - SELECT category_id FROM test WHERE id = token_parameters.category_lookup
363
+ data:
364
+ - SELECT id, category_id FROM test WHERE category_id = bucket.category_id
365
+ `,
366
+ { storageVersion }
367
+ )
368
+ );
369
+ const bucketStorage = factory.getInstance(syncRules) as MongoSyncBucketStorage;
370
+ const previousCheckpoint = await bucketStorage.getCheckpoint();
371
+
372
+ await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
373
+ const sourceTable = await test_utils.resolveTestTable(writer, 'test', ['id'], INITIALIZED_MONGO_STORAGE_FACTORY);
374
+
375
+ await writer.save({
376
+ sourceTable,
377
+ tag: storage.SaveOperationTag.INSERT,
378
+ after: {
379
+ id: 'shape-check',
380
+ owner_id: 'user-1',
381
+ category_id: 'cat-1'
382
+ },
383
+ afterReplicaId: test_utils.rid('shape-check')
384
+ });
385
+ await writer.markAllSnapshotDone('1/1');
386
+ await writer.commit('1/1');
387
+
388
+ const nextCheckpoint = await bucketStorage.getCheckpoint();
389
+ const changes = await bucketStorage.getCheckpointChanges({
390
+ lastCheckpoint: previousCheckpoint,
391
+ nextCheckpoint
392
+ });
393
+
394
+ expect(changes.invalidateParameterBuckets).toBe(false);
395
+ expect(changes.updatedParameterLookups).toEqual(new Set(['["1","","shape-check"]', '["2","","shape-check"]']));
396
+ }
397
+ );
398
+
399
+ test.runIf(storageVersion >= 3)('cleans pending deletes only for tracked v3 source tables', async () => {
400
+ await using factory = await storageConfig.factory();
401
+ const syncRules = await factory.updateSyncRules(
402
+ updateSyncRulesFromYaml(
403
+ `
404
+ bucket_definitions:
405
+ global:
406
+ data:
407
+ - SELECT id, description FROM test
408
+ `,
409
+ { storageVersion }
410
+ )
411
+ );
412
+
413
+ const mongoFactory = factory as MongoBucketStorage;
414
+ const bucketStorage = mongoFactory.getInstance(syncRules) as any;
415
+ const db = bucketStorage.db;
416
+ await db.initializeStreamStorage(syncRules.id);
417
+
418
+ const sourceTableA = new bson.ObjectId();
419
+ const sourceTableB = new bson.ObjectId();
420
+ await db.sourceTablesV3(syncRules.id).insertMany([
421
+ {
422
+ _id: sourceTableA,
423
+ connection_id: 1,
424
+ relation_id: 'a',
425
+ schema_name: 'public',
426
+ table_name: 'table_a',
427
+ replica_id_columns: null,
428
+ replica_id_columns2: [],
429
+ snapshot_done: true,
430
+ snapshot_status: undefined,
431
+ bucket_data_source_ids: [],
432
+ parameter_lookup_source_ids: [],
433
+ latest_pending_delete: 9n
434
+ },
435
+ {
436
+ _id: sourceTableB,
437
+ connection_id: 1,
438
+ relation_id: 'b',
439
+ schema_name: 'public',
440
+ table_name: 'table_b',
441
+ replica_id_columns: null,
442
+ replica_id_columns2: [],
443
+ snapshot_done: true,
444
+ snapshot_status: undefined,
445
+ bucket_data_source_ids: [],
446
+ parameter_lookup_source_ids: [],
447
+ latest_pending_delete: 12n
448
+ }
449
+ ]);
450
+
451
+ await db.sourceRecordsV3(syncRules.id, sourceTableA).insertMany([
452
+ { _id: 'deleted-1', data: null, buckets: [], lookups: [], pending_delete: 5n },
453
+ { _id: 'deleted-2', data: null, buckets: [], lookups: [], pending_delete: 9n },
454
+ { _id: 'active', data: null, buckets: [], lookups: [] }
455
+ ]);
456
+ await db
457
+ .sourceRecordsV3(syncRules.id, sourceTableB)
458
+ .insertMany([{ _id: 'later-delete', data: null, buckets: [], lookups: [], pending_delete: 12n }]);
459
+
460
+ const store = new SourceRecordStoreV3(db, syncRules.id, bucketStorage.sync_rules.mapping);
461
+ const logger = { info() {} } as any;
462
+
463
+ await store.postCommitCleanup(6n, logger);
464
+
465
+ expect(await db.sourceRecordsV3(syncRules.id, sourceTableA).countDocuments({ pending_delete: 5n })).toBe(0);
466
+ expect(await db.sourceRecordsV3(syncRules.id, sourceTableA).countDocuments({ pending_delete: 9n })).toBe(1);
467
+ expect(await db.sourceRecordsV3(syncRules.id, sourceTableB).countDocuments({ pending_delete: 12n })).toBe(1);
468
+ expect((await db.sourceTablesV3(syncRules.id).findOne({ _id: sourceTableA }))?.latest_pending_delete).toBe(9n);
469
+ expect((await db.sourceTablesV3(syncRules.id).findOne({ _id: sourceTableB }))?.latest_pending_delete).toBe(12n);
470
+
471
+ await store.postCommitCleanup(10n, logger);
472
+
473
+ expect(
474
+ await db.sourceRecordsV3(syncRules.id, sourceTableA).countDocuments({ pending_delete: { $exists: true } })
475
+ ).toBe(0);
476
+ expect(
477
+ (await db.sourceTablesV3(syncRules.id).findOne({ _id: sourceTableA }))?.latest_pending_delete
478
+ ).toBeUndefined();
479
+ expect((await db.sourceTablesV3(syncRules.id).findOne({ _id: sourceTableB }))?.latest_pending_delete).toBe(12n);
480
+ });
135
481
  }
136
482
 
137
483
  describe('sync - mongodb', () => {
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "extends": "../../../tsconfig.tests.json",
3
3
  "compilerOptions": {
4
- "baseUrl": "./",
5
4
  "paths": {
6
5
  "@/*": ["../../../packages/service-core/src/*"],
7
6
  "@module/*": ["../src/*"],