@powersync/service-module-mongodb-storage 0.15.4 → 0.17.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 (202) hide show
  1. package/CHANGELOG.md +69 -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 +2 -2
  5. package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js.map +1 -1
  6. package/dist/storage/MongoBucketStorage.d.ts +8 -6
  7. package/dist/storage/MongoBucketStorage.js +153 -66
  8. package/dist/storage/MongoBucketStorage.js.map +1 -1
  9. package/dist/storage/implementation/BucketDefinitionMapping.d.ts +15 -0
  10. package/dist/storage/implementation/BucketDefinitionMapping.js +58 -0
  11. package/dist/storage/implementation/BucketDefinitionMapping.js.map +1 -0
  12. package/dist/storage/implementation/CheckpointState.d.ts +20 -0
  13. package/dist/storage/implementation/CheckpointState.js +31 -0
  14. package/dist/storage/implementation/CheckpointState.js.map +1 -0
  15. package/dist/storage/implementation/MongoBucketBatch.d.ts +48 -35
  16. package/dist/storage/implementation/MongoBucketBatch.js +118 -379
  17. package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
  18. package/dist/storage/implementation/MongoBucketBatchShared.d.ts +5 -0
  19. package/dist/storage/implementation/MongoBucketBatchShared.js +8 -0
  20. package/dist/storage/implementation/MongoBucketBatchShared.js.map +1 -0
  21. package/dist/storage/implementation/MongoChecksums.d.ts +29 -17
  22. package/dist/storage/implementation/MongoChecksums.js +13 -72
  23. package/dist/storage/implementation/MongoChecksums.js.map +1 -1
  24. package/dist/storage/implementation/MongoCompactor.d.ts +98 -58
  25. package/dist/storage/implementation/MongoCompactor.js +229 -296
  26. package/dist/storage/implementation/MongoCompactor.js.map +1 -1
  27. package/dist/storage/implementation/MongoParameterCompactor.d.ts +11 -6
  28. package/dist/storage/implementation/MongoParameterCompactor.js +11 -8
  29. package/dist/storage/implementation/MongoParameterCompactor.js.map +1 -1
  30. package/dist/storage/implementation/MongoPersistedSyncRules.d.ts +14 -0
  31. package/dist/storage/implementation/MongoPersistedSyncRules.js +67 -0
  32. package/dist/storage/implementation/MongoPersistedSyncRules.js.map +1 -0
  33. package/dist/storage/implementation/MongoPersistedSyncRulesContent.d.ts +22 -5
  34. package/dist/storage/implementation/MongoPersistedSyncRulesContent.js +56 -13
  35. package/dist/storage/implementation/MongoPersistedSyncRulesContent.js.map +1 -1
  36. package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +61 -32
  37. package/dist/storage/implementation/MongoSyncBucketStorage.js +85 -523
  38. package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
  39. package/dist/storage/implementation/MongoSyncRulesLock.d.ts +10 -4
  40. package/dist/storage/implementation/MongoSyncRulesLock.js +19 -13
  41. package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -1
  42. package/dist/storage/implementation/MongoWriteCheckpointAPI.js +1 -1
  43. package/dist/storage/implementation/MongoWriteCheckpointAPI.js.map +1 -1
  44. package/dist/storage/implementation/OperationBatch.js +1 -1
  45. package/dist/storage/implementation/SyncRuleStateUpdate.d.ts +14 -0
  46. package/dist/storage/implementation/SyncRuleStateUpdate.js +36 -0
  47. package/dist/storage/implementation/SyncRuleStateUpdate.js.map +1 -0
  48. package/dist/storage/implementation/common/BucketDataDoc.d.ts +35 -0
  49. package/dist/storage/implementation/common/BucketDataDoc.js +2 -0
  50. package/dist/storage/implementation/common/BucketDataDoc.js.map +1 -0
  51. package/dist/storage/implementation/common/MongoSyncBucketStorageContext.d.ts +13 -0
  52. package/dist/storage/implementation/common/MongoSyncBucketStorageContext.js +2 -0
  53. package/dist/storage/implementation/common/MongoSyncBucketStorageContext.js.map +1 -0
  54. package/dist/storage/implementation/common/PersistedBatch.d.ts +108 -0
  55. package/dist/storage/implementation/common/PersistedBatch.js +237 -0
  56. package/dist/storage/implementation/common/PersistedBatch.js.map +1 -0
  57. package/dist/storage/implementation/common/SingleBucketStore.d.ts +54 -0
  58. package/dist/storage/implementation/common/SingleBucketStore.js +3 -0
  59. package/dist/storage/implementation/common/SingleBucketStore.js.map +1 -0
  60. package/dist/storage/implementation/common/SourceRecordStore.d.ts +35 -0
  61. package/dist/storage/implementation/common/SourceRecordStore.js +2 -0
  62. package/dist/storage/implementation/common/SourceRecordStore.js.map +1 -0
  63. package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.d.ts +27 -0
  64. package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js +57 -0
  65. package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js.map +1 -0
  66. package/dist/storage/implementation/createMongoSyncBucketStorage.d.ts +7 -0
  67. package/dist/storage/implementation/createMongoSyncBucketStorage.js +9 -0
  68. package/dist/storage/implementation/createMongoSyncBucketStorage.js.map +1 -0
  69. package/dist/storage/implementation/db.d.ts +41 -36
  70. package/dist/storage/implementation/db.js +77 -99
  71. package/dist/storage/implementation/db.js.map +1 -1
  72. package/dist/storage/implementation/models.d.ts +79 -66
  73. package/dist/storage/implementation/models.js +20 -1
  74. package/dist/storage/implementation/models.js.map +1 -1
  75. package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +27 -0
  76. package/dist/storage/implementation/v1/MongoBucketBatchV1.js +407 -0
  77. package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -0
  78. package/dist/storage/implementation/v1/MongoChecksumsV1.d.ts +12 -0
  79. package/dist/storage/implementation/v1/MongoChecksumsV1.js +56 -0
  80. package/dist/storage/implementation/v1/MongoChecksumsV1.js.map +1 -0
  81. package/dist/storage/implementation/v1/MongoCompactorV1.d.ts +23 -0
  82. package/dist/storage/implementation/v1/MongoCompactorV1.js +52 -0
  83. package/dist/storage/implementation/v1/MongoCompactorV1.js.map +1 -0
  84. package/dist/storage/implementation/v1/MongoParameterCompactorV1.d.ts +9 -0
  85. package/dist/storage/implementation/v1/MongoParameterCompactorV1.js +20 -0
  86. package/dist/storage/implementation/v1/MongoParameterCompactorV1.js.map +1 -0
  87. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.d.ts +50 -0
  88. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +354 -0
  89. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js.map +1 -0
  90. package/dist/storage/implementation/v1/PersistedBatchV1.d.ts +25 -0
  91. package/dist/storage/implementation/v1/PersistedBatchV1.js +183 -0
  92. package/dist/storage/implementation/v1/PersistedBatchV1.js.map +1 -0
  93. package/dist/storage/implementation/v1/SingleBucketStoreV1.d.ts +18 -0
  94. package/dist/storage/implementation/v1/SingleBucketStoreV1.js +57 -0
  95. package/dist/storage/implementation/v1/SingleBucketStoreV1.js.map +1 -0
  96. package/dist/storage/implementation/v1/SourceRecordStoreV1.d.ts +19 -0
  97. package/dist/storage/implementation/v1/SourceRecordStoreV1.js +105 -0
  98. package/dist/storage/implementation/v1/SourceRecordStoreV1.js.map +1 -0
  99. package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.d.ts +12 -0
  100. package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.js +20 -0
  101. package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.js.map +1 -0
  102. package/dist/storage/implementation/v1/models.d.ts +45 -0
  103. package/dist/storage/implementation/v1/models.js +37 -0
  104. package/dist/storage/implementation/v1/models.js.map +1 -0
  105. package/dist/storage/implementation/v3/MongoBucketBatchV3.d.ts +30 -0
  106. package/dist/storage/implementation/v3/MongoBucketBatchV3.js +463 -0
  107. package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -0
  108. package/dist/storage/implementation/v3/MongoChecksumsV3.d.ts +15 -0
  109. package/dist/storage/implementation/v3/MongoChecksumsV3.js +84 -0
  110. package/dist/storage/implementation/v3/MongoChecksumsV3.js.map +1 -0
  111. package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +23 -0
  112. package/dist/storage/implementation/v3/MongoCompactorV3.js +68 -0
  113. package/dist/storage/implementation/v3/MongoCompactorV3.js.map +1 -0
  114. package/dist/storage/implementation/v3/MongoParameterCompactorV3.d.ts +9 -0
  115. package/dist/storage/implementation/v3/MongoParameterCompactorV3.js +18 -0
  116. package/dist/storage/implementation/v3/MongoParameterCompactorV3.js.map +1 -0
  117. package/dist/storage/implementation/v3/MongoParameterLookupV3.d.ts +4 -0
  118. package/dist/storage/implementation/v3/MongoParameterLookupV3.js +9 -0
  119. package/dist/storage/implementation/v3/MongoParameterLookupV3.js.map +1 -0
  120. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.d.ts +63 -0
  121. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +508 -0
  122. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -0
  123. package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +28 -0
  124. package/dist/storage/implementation/v3/PersistedBatchV3.js +259 -0
  125. package/dist/storage/implementation/v3/PersistedBatchV3.js.map +1 -0
  126. package/dist/storage/implementation/v3/SingleBucketStoreV3.d.ts +18 -0
  127. package/dist/storage/implementation/v3/SingleBucketStoreV3.js +48 -0
  128. package/dist/storage/implementation/v3/SingleBucketStoreV3.js.map +1 -0
  129. package/dist/storage/implementation/v3/SourceRecordStoreV3.d.ts +22 -0
  130. package/dist/storage/implementation/v3/SourceRecordStoreV3.js +164 -0
  131. package/dist/storage/implementation/v3/SourceRecordStoreV3.js.map +1 -0
  132. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.d.ts +22 -0
  133. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js +74 -0
  134. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js.map +1 -0
  135. package/dist/storage/implementation/v3/models.d.ts +101 -0
  136. package/dist/storage/implementation/v3/models.js +34 -0
  137. package/dist/storage/implementation/v3/models.js.map +1 -0
  138. package/dist/storage/storage-index.d.ts +6 -3
  139. package/dist/storage/storage-index.js +6 -3
  140. package/dist/storage/storage-index.js.map +1 -1
  141. package/dist/utils/util.d.ts +10 -3
  142. package/dist/utils/util.js +24 -3
  143. package/dist/utils/util.js.map +1 -1
  144. package/package.json +9 -9
  145. package/src/migrations/db/migrations/1688556755264-initial-sync-rules.ts +1 -1
  146. package/src/migrations/db/migrations/1702295701188-sync-rule-state.ts +7 -7
  147. package/src/storage/MongoBucketStorage.ts +254 -99
  148. package/src/storage/implementation/BucketDefinitionMapping.ts +75 -0
  149. package/src/storage/implementation/CheckpointState.ts +59 -0
  150. package/src/storage/implementation/MongoBucketBatch.ts +182 -490
  151. package/src/storage/implementation/MongoBucketBatchShared.ts +11 -0
  152. package/src/storage/implementation/MongoChecksums.ts +53 -75
  153. package/src/storage/implementation/MongoCompactor.ts +374 -404
  154. package/src/storage/implementation/MongoParameterCompactor.ts +37 -24
  155. package/src/storage/implementation/MongoPersistedSyncRules.ts +82 -0
  156. package/src/storage/implementation/MongoPersistedSyncRulesContent.ts +78 -16
  157. package/src/storage/implementation/MongoSyncBucketStorage.ts +179 -628
  158. package/src/storage/implementation/MongoSyncRulesLock.ts +20 -16
  159. package/src/storage/implementation/MongoWriteCheckpointAPI.ts +3 -1
  160. package/src/storage/implementation/OperationBatch.ts +1 -1
  161. package/src/storage/implementation/SyncRuleStateUpdate.ts +38 -0
  162. package/src/storage/implementation/common/BucketDataDoc.ts +37 -0
  163. package/src/storage/implementation/common/MongoSyncBucketStorageContext.ts +15 -0
  164. package/src/storage/implementation/common/PersistedBatch.ts +364 -0
  165. package/src/storage/implementation/common/SingleBucketStore.ts +63 -0
  166. package/src/storage/implementation/common/SourceRecordStore.ts +48 -0
  167. package/src/storage/implementation/common/VersionedPowerSyncMongoBase.ts +80 -0
  168. package/src/storage/implementation/createMongoSyncBucketStorage.ts +25 -0
  169. package/src/storage/implementation/db.ts +110 -131
  170. package/src/storage/implementation/models.ts +102 -79
  171. package/src/storage/implementation/v1/MongoBucketBatchV1.ts +509 -0
  172. package/src/storage/implementation/v1/MongoChecksumsV1.ts +75 -0
  173. package/src/storage/implementation/v1/MongoCompactorV1.ts +93 -0
  174. package/src/storage/implementation/v1/MongoParameterCompactorV1.ts +26 -0
  175. package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +543 -0
  176. package/src/storage/implementation/v1/PersistedBatchV1.ts +229 -0
  177. package/src/storage/implementation/v1/SingleBucketStoreV1.ts +74 -0
  178. package/src/storage/implementation/v1/SourceRecordStoreV1.ts +156 -0
  179. package/src/storage/implementation/v1/VersionedPowerSyncMongoV1.ts +28 -0
  180. package/src/storage/implementation/v1/models.ts +99 -0
  181. package/src/storage/implementation/v3/MongoBucketBatchV3.ts +607 -0
  182. package/src/storage/implementation/v3/MongoChecksumsV3.ts +120 -0
  183. package/src/storage/implementation/v3/MongoCompactorV3.ts +107 -0
  184. package/src/storage/implementation/v3/MongoParameterCompactorV3.ts +24 -0
  185. package/src/storage/implementation/v3/MongoParameterLookupV3.ts +11 -0
  186. package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +678 -0
  187. package/src/storage/implementation/v3/PersistedBatchV3.ts +317 -0
  188. package/src/storage/implementation/v3/SingleBucketStoreV3.ts +68 -0
  189. package/src/storage/implementation/v3/SourceRecordStoreV3.ts +226 -0
  190. package/src/storage/implementation/v3/VersionedPowerSyncMongoV3.ts +117 -0
  191. package/src/storage/implementation/v3/models.ts +164 -0
  192. package/src/storage/storage-index.ts +6 -3
  193. package/src/utils/util.ts +34 -5
  194. package/test/src/storage_compacting.test.ts +57 -29
  195. package/test/src/storage_sync.test.ts +767 -5
  196. package/test/src/storeCurrentData.test.ts +211 -0
  197. package/test/tsconfig.json +0 -1
  198. package/tsconfig.tsbuildinfo +1 -1
  199. package/dist/storage/implementation/PersistedBatch.d.ts +0 -71
  200. package/dist/storage/implementation/PersistedBatch.js +0 -354
  201. package/dist/storage/implementation/PersistedBatch.js.map +0 -1
  202. package/src/storage/implementation/PersistedBatch.ts +0 -432
@@ -1,4 +1,4 @@
1
- import { GetIntanceOptions, storage } from '@powersync/service-core';
1
+ import { GetIntanceOptions, LEGACY_STORAGE_VERSION, storage } from '@powersync/service-core';
2
2
 
3
3
  import { DO_NOT_LOG, ErrorCode, ServiceError } from '@powersync/lib-services-framework';
4
4
  import { v4 as uuid } from 'uuid';
@@ -6,15 +6,25 @@ import { v4 as uuid } from 'uuid';
6
6
  import * as lib_mongo from '@powersync/lib-service-mongodb';
7
7
  import { mongo } from '@powersync/lib-service-mongodb';
8
8
 
9
+ import { ObjectId } from 'bson';
9
10
  import { generateSlotName } from '../utils/util.js';
11
+ import { BucketDefinitionMapping } from './implementation/BucketDefinitionMapping.js';
12
+ import type { MongoSyncBucketStorage } from './implementation/createMongoSyncBucketStorage.js';
13
+ import { createMongoSyncBucketStorage } from './implementation/createMongoSyncBucketStorage.js';
10
14
  import { PowerSyncMongo } from './implementation/db.js';
11
- import { getMongoStorageConfig, SyncRuleDocument } from './implementation/models.js';
15
+ import { getMongoStorageConfig, StorageConfig, SyncRuleDocumentBase } from './implementation/models.js';
12
16
  import { MongoChecksumOptions } from './implementation/MongoChecksums.js';
13
- import { MongoPersistedSyncRulesContent } from './implementation/MongoPersistedSyncRulesContent.js';
14
- import { MongoSyncBucketStorage } from './implementation/MongoSyncBucketStorage.js';
17
+ import {
18
+ MongoPersistedSyncRulesContentV1,
19
+ MongoPersistedSyncRulesContentV3
20
+ } from './implementation/MongoPersistedSyncRulesContent.js';
21
+ import { syncRuleStateUpdatePipeline } from './implementation/SyncRuleStateUpdate.js';
22
+ import { SyncRuleDocumentV1 } from './implementation/v1/models.js';
23
+ import { VersionedPowerSyncMongoV3 } from './implementation/v3/VersionedPowerSyncMongoV3.js';
24
+ import { ReplicationStreamDocumentV3, SyncConfigDefinition } from './storage-index.js';
15
25
 
16
26
  export interface MongoBucketStorageOptions {
17
- checksumOptions?: Omit<MongoChecksumOptions, 'storageConfig'>;
27
+ checksumOptions?: Omit<MongoChecksumOptions, 'storageConfig' | 'mapping'>;
18
28
  }
19
29
 
20
30
  export class MongoBucketStorage extends storage.BucketStorageFactory {
@@ -52,11 +62,11 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
52
62
  if ((typeof id as any) == 'bigint') {
53
63
  id = Number(id);
54
64
  }
55
- const storageConfig = (syncRules as MongoPersistedSyncRulesContent).getStorageConfig();
56
- const storage = new MongoSyncBucketStorage(
65
+ const storageConfig = (syncRules as MongoPersistedSyncRulesContentV1).getStorageConfig();
66
+ const storage = createMongoSyncBucketStorage(
57
67
  this,
58
68
  id,
59
- syncRules as MongoPersistedSyncRulesContent,
69
+ syncRules as MongoPersistedSyncRulesContentV1,
60
70
  slot_name,
61
71
  undefined,
62
72
  {
@@ -100,7 +110,7 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
100
110
  const active = await this.getActiveSyncRulesContent();
101
111
 
102
112
  if (next != null && next.id == sync_rules_group_id) {
103
- // We need to redo the "next" sync rules
113
+ // We need to redo the "next" replication stream
104
114
  await this.updateSyncRules(next.asUpdateOptions());
105
115
  // Pro-actively stop replicating
106
116
  await this.db.sync_rules.updateOne(
@@ -108,15 +118,11 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
108
118
  _id: next.id,
109
119
  state: storage.SyncRuleState.PROCESSING
110
120
  },
111
- {
112
- $set: {
113
- state: storage.SyncRuleState.STOP
114
- }
115
- }
121
+ syncRuleStateUpdatePipeline(storage.SyncRuleState.STOP)
116
122
  );
117
123
  await this.db.notifyCheckpoint();
118
124
  } else if (next == null && active?.id == sync_rules_group_id) {
119
- // Slot removed for "active" sync rules, while there is no "next" one.
125
+ // Slot removed for "active" replication stream, while there is no "next" one.
120
126
  await this.updateSyncRules(active.asUpdateOptions());
121
127
 
122
128
  // In this case we keep the old one as active for clients, so that that existing clients
@@ -128,45 +134,133 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
128
134
  _id: active.id,
129
135
  state: storage.SyncRuleState.ACTIVE
130
136
  },
131
- {
132
- $set: {
133
- state: storage.SyncRuleState.ERRORED
134
- }
135
- }
137
+ syncRuleStateUpdatePipeline(storage.SyncRuleState.ERRORED)
136
138
  );
137
139
  await this.db.notifyCheckpoint();
138
140
  } else if (next != null && active?.id == sync_rules_group_id) {
139
- // Already have next sync rules, but need to stop replicating the active one.
141
+ // Already have next replication stream, but need to stop replicating the active one.
140
142
 
141
143
  await this.db.sync_rules.updateOne(
142
144
  {
143
145
  _id: active.id,
144
146
  state: storage.SyncRuleState.ACTIVE
145
147
  },
148
+ syncRuleStateUpdatePipeline(storage.SyncRuleState.ERRORED)
149
+ );
150
+ await this.db.notifyCheckpoint();
151
+ }
152
+ }
153
+
154
+ private async updateSyncRulesV3(
155
+ options: storage.UpdateSyncRulesOptions,
156
+ storageVersion: number,
157
+ storageConfig: StorageConfig
158
+ ): Promise<MongoPersistedSyncRulesContentV3> {
159
+ let rules: MongoPersistedSyncRulesContentV3 | undefined = undefined;
160
+ const versioned = this.db.versioned(storageConfig) as VersionedPowerSyncMongoV3;
161
+
162
+ const session = this.session;
163
+
164
+ await session.withTransaction(async () => {
165
+ // Only have a single replication stream with PROCESSING.
166
+ await this.db.sync_rules.updateMany(
167
+ {
168
+ state: storage.SyncRuleState.PROCESSING
169
+ },
170
+ syncRuleStateUpdatePipeline(storage.SyncRuleState.STOP),
171
+ { session }
172
+ );
173
+
174
+ const id_doc = await this.db.op_id_sequence.findOneAndUpdate(
175
+ {
176
+ _id: 'sync_rules'
177
+ },
146
178
  {
147
- $set: {
148
- state: storage.SyncRuleState.ERRORED
179
+ $inc: {
180
+ op_id: 1n
149
181
  }
182
+ },
183
+ {
184
+ upsert: true,
185
+ returnDocument: 'after',
186
+ session
150
187
  }
151
188
  );
189
+
190
+ const id = Number(id_doc!.op_id);
191
+ const slot_name = generateSlotName(this.slot_name_prefix, id);
192
+
193
+ const mapping = BucketDefinitionMapping.fromParsedSyncRules(options.config.parsed);
194
+
195
+ const syncConfigDoc: SyncConfigDefinition = {
196
+ _id: new ObjectId(),
197
+ replication_stream_id: id,
198
+ created_at: new Date(),
199
+ storage_version: storageVersion,
200
+ content: options.config.yaml,
201
+ serialized_plan: options.config.plan,
202
+ rule_mapping: mapping.serialize()
203
+ };
204
+ await versioned.syncConfigDefinitions.insertOne(syncConfigDoc, { session });
205
+
206
+ const doc: ReplicationStreamDocumentV3 = {
207
+ _id: id,
208
+ storage_version: storageVersion,
209
+ sync_configs: [
210
+ {
211
+ _id: syncConfigDoc._id,
212
+ state: storage.SyncRuleState.PROCESSING,
213
+ keepalive_op: null,
214
+ last_checkpoint: null,
215
+ last_checkpoint_lsn: null,
216
+ no_checkpoint_before: null,
217
+ snapshot_done: false
218
+ }
219
+ ],
220
+ snapshot_lsn: undefined,
221
+ state: storage.SyncRuleState.PROCESSING,
222
+ slot_name: slot_name,
223
+ last_checkpoint_ts: null,
224
+ last_fatal_error: null,
225
+ last_fatal_error_ts: null,
226
+ last_keepalive_ts: null
227
+ };
228
+
229
+ await this.db.sync_rules.insertOne(doc, { session });
152
230
  await this.db.notifyCheckpoint();
153
- }
231
+ rules = new MongoPersistedSyncRulesContentV3(this.db, doc, syncConfigDoc);
232
+ if (options.lock) {
233
+ // The lock is persisted on rules.current_lock
234
+ await rules.lock(session);
235
+ }
236
+ });
237
+
238
+ return rules!;
154
239
  }
155
240
 
156
- async updateSyncRules(options: storage.UpdateSyncRulesOptions): Promise<MongoPersistedSyncRulesContent> {
157
- const storageVersion = options.storageVersion ?? storage.CURRENT_STORAGE_VERSION;
241
+ async updateSyncRules(
242
+ options: storage.UpdateSyncRulesOptions
243
+ ): Promise<MongoPersistedSyncRulesContentV1 | MongoPersistedSyncRulesContentV3> {
244
+ const storageVersion =
245
+ options.storageVersion ?? options.config.parsed.config.storageVersion ?? storage.CURRENT_STORAGE_VERSION;
246
+
158
247
  const storageConfig = getMongoStorageConfig(storageVersion);
159
- await this.db.initializeStorageVersion(storageConfig);
248
+ if (storageConfig.incrementalReprocessing) {
249
+ return this.updateSyncRulesV3(options, storageVersion, storageConfig);
250
+ }
251
+
252
+ let rules: MongoPersistedSyncRulesContentV1 | undefined = undefined;
160
253
 
161
- let rules: MongoPersistedSyncRulesContent | undefined = undefined;
254
+ const session = this.session;
162
255
 
163
- await this.session.withTransaction(async () => {
164
- // Only have a single set of sync rules with PROCESSING.
256
+ await session.withTransaction(async () => {
257
+ // Only have a single replication stream with PROCESSING.
165
258
  await this.db.sync_rules.updateMany(
166
259
  {
167
260
  state: storage.SyncRuleState.PROCESSING
168
261
  },
169
- { $set: { state: storage.SyncRuleState.STOP } }
262
+ syncRuleStateUpdatePipeline(storage.SyncRuleState.STOP),
263
+ { session }
170
264
  );
171
265
 
172
266
  const id_doc = await this.db.op_id_sequence.findOneAndUpdate(
@@ -180,14 +274,15 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
180
274
  },
181
275
  {
182
276
  upsert: true,
183
- returnDocument: 'after'
277
+ returnDocument: 'after',
278
+ session
184
279
  }
185
280
  );
186
281
 
187
282
  const id = Number(id_doc!.op_id);
188
283
  const slot_name = generateSlotName(this.slot_name_prefix, id);
189
284
 
190
- const doc: SyncRuleDocument = {
285
+ const doc: SyncRuleDocumentV1 = {
191
286
  _id: id,
192
287
  storage_version: storageVersion,
193
288
  content: options.config.yaml,
@@ -205,43 +300,68 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
205
300
  last_fatal_error_ts: null,
206
301
  last_keepalive_ts: null
207
302
  };
208
- await this.db.sync_rules.insertOne(doc);
303
+
304
+ await this.db.sync_rules.insertOne(doc, { session });
209
305
  await this.db.notifyCheckpoint();
210
- rules = new MongoPersistedSyncRulesContent(this.db, doc);
306
+ rules = new MongoPersistedSyncRulesContentV1(this.db, doc);
211
307
  if (options.lock) {
212
- const lock = await rules.lock();
308
+ // The lock is persisted on rules.current_lock
309
+ await rules.lock(session);
213
310
  }
214
311
  });
215
312
 
216
313
  return rules!;
217
314
  }
218
315
 
219
- async getActiveSyncRulesContent(): Promise<MongoPersistedSyncRulesContent | null> {
316
+ async getActiveSyncRulesContent(): Promise<
317
+ MongoPersistedSyncRulesContentV1 | MongoPersistedSyncRulesContentV3 | null
318
+ > {
220
319
  const doc = await this.db.sync_rules.findOne(
221
320
  {
222
321
  state: { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] }
223
322
  },
224
323
  { sort: { _id: -1 }, limit: 1 }
225
324
  );
325
+
326
+ return this.getSyncRulesContent(doc, [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED]);
327
+ }
328
+
329
+ private async getSyncRulesContent(doc: SyncRuleDocumentBase | null, stateFilter: storage.SyncRuleState[]) {
226
330
  if (doc == null) {
227
331
  return null;
228
332
  }
333
+ const storageConfig = getMongoStorageConfig(doc.storage_version ?? LEGACY_STORAGE_VERSION);
334
+
335
+ if (storageConfig.incrementalReprocessing) {
336
+ const v3 = doc as ReplicationStreamDocumentV3;
337
+ const active = v3.sync_configs.find((c) => stateFilter.includes(c.state));
338
+ if (active == null) {
339
+ return null;
340
+ }
229
341
 
230
- return new MongoPersistedSyncRulesContent(this.db, doc);
342
+ // TODO: cache the config. It could specifically help for the main replication loop
343
+ // that checks for active replication streams.
344
+ // It is not a major bottleneck though, since it only runs once every couple of seconds at most.
345
+ const db = this.db.versioned(storageConfig) as VersionedPowerSyncMongoV3;
346
+ const syncConfigDoc = await db.syncConfigDefinitions.findOne({ _id: active._id });
347
+ if (syncConfigDoc == null) {
348
+ return null;
349
+ }
350
+ return new MongoPersistedSyncRulesContentV3(this.db, v3, syncConfigDoc);
351
+ }
352
+
353
+ return new MongoPersistedSyncRulesContentV1(this.db, doc as SyncRuleDocumentV1);
231
354
  }
232
355
 
233
- async getNextSyncRulesContent(): Promise<MongoPersistedSyncRulesContent | null> {
356
+ async getNextSyncRulesContent(): Promise<MongoPersistedSyncRulesContentV1 | MongoPersistedSyncRulesContentV3 | null> {
234
357
  const doc = await this.db.sync_rules.findOne(
235
358
  {
236
359
  state: storage.SyncRuleState.PROCESSING
237
360
  },
238
361
  { sort: { _id: -1 }, limit: 1 }
239
362
  );
240
- if (doc == null) {
241
- return null;
242
- }
243
363
 
244
- return new MongoPersistedSyncRulesContent(this.db, doc);
364
+ return this.getSyncRulesContent(doc, [storage.SyncRuleState.PROCESSING]);
245
365
  }
246
366
 
247
367
  async getReplicatingSyncRules(): Promise<storage.PersistedSyncRulesContent[]> {
@@ -251,9 +371,13 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
251
371
  })
252
372
  .toArray();
253
373
 
254
- return docs.map((doc) => {
255
- return new MongoPersistedSyncRulesContent(this.db, doc);
256
- });
374
+ return (
375
+ await Promise.all(
376
+ docs.map((doc) => {
377
+ return this.getSyncRulesContent(doc, [storage.SyncRuleState.PROCESSING, storage.SyncRuleState.ACTIVE]);
378
+ })
379
+ )
380
+ ).filter((r) => r != null);
257
381
  }
258
382
 
259
383
  async getStoppedSyncRules(): Promise<storage.PersistedSyncRulesContent[]> {
@@ -263,9 +387,13 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
263
387
  })
264
388
  .toArray();
265
389
 
266
- return docs.map((doc) => {
267
- return new MongoPersistedSyncRulesContent(this.db, doc);
268
- });
390
+ return (
391
+ await Promise.all(
392
+ docs.map((doc) => {
393
+ return this.getSyncRulesContent(doc, [storage.SyncRuleState.STOP]);
394
+ })
395
+ )
396
+ ).filter((d) => d != null);
269
397
  }
270
398
 
271
399
  async getActiveStorage(): Promise<MongoSyncBucketStorage | null> {
@@ -296,63 +424,90 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
296
424
  }
297
425
  };
298
426
 
299
- const active_sync_rules = await this.getActiveSyncRules({ defaultSchema: 'public' });
300
- if (active_sync_rules == null) {
301
- return {
302
- operations_size_bytes: 0,
303
- parameters_size_bytes: 0,
304
- replication_size_bytes: 0
305
- };
306
- }
307
- const operations_aggregate = await this.db.bucket_data
427
+ // For now, we get storage metrics over all v1 and v3 collections.
428
+ // In the future, we may split these metrics to report separately for active replication streams versus processing streams.
308
429
 
309
- .aggregate([
310
- {
311
- $collStats: {
312
- storageStats: {}
313
- }
314
- }
315
- ])
316
- .toArray()
317
- .catch(ignoreNotExisting);
430
+ const aggregateStaticCollection = async <T extends mongo.Document>(collection: mongo.Collection<T>) => {
431
+ // We check whether the collection exists before getting the statistics. This avoids repeated
432
+ // errors in the MongoDB logs if the collection hasn't been created yet.
433
+ const exists =
434
+ (await this.db.db.listCollections({ name: collection.collectionName }, { nameOnly: true }).toArray()).length >
435
+ 0;
436
+ if (!exists) {
437
+ return [{ storageStats: { size: 0 } }];
438
+ }
318
439
 
319
- const parameters_aggregate = await this.db.bucket_parameters
320
- .aggregate([
321
- {
322
- $collStats: {
323
- storageStats: {}
440
+ return collection
441
+ .aggregate([
442
+ {
443
+ $collStats: {
444
+ storageStats: {}
445
+ }
324
446
  }
325
- }
326
- ])
327
- .toArray()
328
- .catch(ignoreNotExisting);
447
+ ])
448
+ .toArray()
449
+ .catch(ignoreNotExisting);
450
+ };
329
451
 
330
- const v1_replication_aggregate = await this.db.current_data
331
- .aggregate([
332
- {
333
- $collStats: {
334
- storageStats: {}
335
- }
336
- }
337
- ])
338
- .toArray()
339
- .catch(ignoreNotExisting);
452
+ const operations_aggregate = await aggregateStaticCollection(this.db.bucket_data);
453
+ const v3_operation_aggregates = await Promise.all(
454
+ (await this.db.listBucketDataCollectionsV3()).map((collection) =>
455
+ collection
456
+ .aggregate([
457
+ {
458
+ $collStats: {
459
+ storageStats: {}
460
+ }
461
+ }
462
+ ])
463
+ .toArray()
464
+ .catch(ignoreNotExisting)
465
+ )
466
+ );
340
467
 
341
- const v3_replication_aggregate = await this.db.v3_current_data
342
- .aggregate([
343
- {
344
- $collStats: {
345
- storageStats: {}
346
- }
347
- }
348
- ])
349
- .toArray()
350
- .catch(ignoreNotExisting);
468
+ const parameters_aggregate = await aggregateStaticCollection(this.db.bucket_parameters);
469
+
470
+ const v3_parameter_aggregates = await Promise.all(
471
+ (await this.db.listAllParameterIndexCollectionsV3()).map((collection) =>
472
+ collection
473
+ .aggregate([
474
+ {
475
+ $collStats: {
476
+ storageStats: {}
477
+ }
478
+ }
479
+ ])
480
+ .toArray()
481
+ .catch(ignoreNotExisting)
482
+ )
483
+ );
484
+
485
+ const v1_source_record_aggregate = await aggregateStaticCollection(this.db.current_data);
486
+
487
+ const source_record_aggregates = await Promise.all(
488
+ (await this.db.listAllSourceRecordCollectionsV3()).map((collection) =>
489
+ collection
490
+ .aggregate([
491
+ {
492
+ $collStats: {
493
+ storageStats: {}
494
+ }
495
+ }
496
+ ])
497
+ .toArray()
498
+ .catch(ignoreNotExisting)
499
+ )
500
+ );
351
501
  return {
352
- operations_size_bytes: Number(operations_aggregate[0].storageStats.size),
353
- parameters_size_bytes: Number(parameters_aggregate[0].storageStats.size),
502
+ operations_size_bytes:
503
+ Number(operations_aggregate[0].storageStats.size) +
504
+ v3_operation_aggregates.reduce((total, aggregate) => total + Number(aggregate[0].storageStats.size), 0),
505
+ parameters_size_bytes:
506
+ Number(parameters_aggregate[0].storageStats.size) +
507
+ v3_parameter_aggregates.reduce((total, aggregate) => total + Number(aggregate[0].storageStats.size), 0),
354
508
  replication_size_bytes:
355
- Number(v1_replication_aggregate[0].storageStats.size) + Number(v3_replication_aggregate[0].storageStats.size)
509
+ Number(v1_source_record_aggregate[0]?.storageStats?.size ?? 0) +
510
+ source_record_aggregates.reduce((total, aggregate) => total + Number(aggregate[0]?.storageStats?.size ?? 0), 0)
356
511
  };
357
512
  }
358
513
 
@@ -0,0 +1,75 @@
1
+ import { ServiceAssertionError } from '@powersync/lib-services-framework';
2
+ import {
3
+ BucketDataSource,
4
+ BucketDefinitionId,
5
+ ParameterIndexId,
6
+ ParameterIndexLookupCreator,
7
+ SyncConfigWithErrors
8
+ } from '@powersync/service-sync-rules';
9
+ import { SyncConfigDefinition } from '../storage-index.js';
10
+
11
+ export class BucketDefinitionMapping {
12
+ static fromSyncConfig(doc: Pick<SyncConfigDefinition, 'rule_mapping'>): BucketDefinitionMapping {
13
+ return new BucketDefinitionMapping(doc.rule_mapping?.definitions ?? {}, doc.rule_mapping?.parameter_indexes ?? {});
14
+ }
15
+
16
+ static fromParsedSyncRules(syncRules: SyncConfigWithErrors): BucketDefinitionMapping {
17
+ const definitionNames = syncRules.config.bucketDataSources.map((source) => source.uniqueName).sort();
18
+ const parameterKeys = syncRules.config.bucketParameterLookupSources
19
+ .map((source) => `${source.sourceId.lookupName}#${source.sourceId.queryId}`)
20
+ .sort();
21
+
22
+ const definitions: Record<string, BucketDefinitionId> = {};
23
+ const parameterLookups: Record<string, ParameterIndexId> = {};
24
+
25
+ for (const [index, uniqueName] of definitionNames.entries()) {
26
+ definitions[uniqueName] = (index + 1).toString(16);
27
+ }
28
+ for (const [index, key] of parameterKeys.entries()) {
29
+ parameterLookups[key] = (index + 1).toString(16);
30
+ }
31
+
32
+ return new BucketDefinitionMapping(definitions, parameterLookups);
33
+ }
34
+
35
+ constructor(
36
+ private definitions: Record<string, BucketDefinitionId> = {},
37
+ private parameterLookupMapping: Record<string, ParameterIndexId> = {}
38
+ ) {}
39
+
40
+ bucketSourceId(source: BucketDataSource): BucketDefinitionId {
41
+ const defId = this.definitions[source.uniqueName];
42
+ if (defId == null) {
43
+ throw new ServiceAssertionError(`No mapping found for bucket source ${source.uniqueName}`);
44
+ }
45
+ return defId;
46
+ }
47
+
48
+ allBucketDefinitionIds(): BucketDefinitionId[] {
49
+ return Object.values(this.definitions);
50
+ }
51
+
52
+ allParameterIndexIds(): ParameterIndexId[] {
53
+ return Object.values(this.parameterLookupMapping);
54
+ }
55
+
56
+ parameterLookupId(source: ParameterIndexLookupCreator): ParameterIndexId {
57
+ const key = this.parameterLookupKey(source.sourceId.lookupName, source.sourceId.queryId);
58
+ const defId = this.parameterLookupMapping[key];
59
+ if (defId == null) {
60
+ throw new ServiceAssertionError(`No mapping found for parameter lookup source ${key}`);
61
+ }
62
+ return defId;
63
+ }
64
+
65
+ private parameterLookupKey(lookupName: string, queryId: string) {
66
+ return `${lookupName}#${queryId}`;
67
+ }
68
+
69
+ serialize(): SyncConfigDefinition['rule_mapping'] {
70
+ return {
71
+ definitions: { ...this.definitions },
72
+ parameter_indexes: { ...this.parameterLookupMapping }
73
+ };
74
+ }
75
+ }
@@ -0,0 +1,59 @@
1
+ export interface CheckpointStateInput {
2
+ lsn: string;
3
+ snapshotDone: boolean;
4
+ lastCheckpointLsn: string | null;
5
+ noCheckpointBefore: string | null;
6
+ keepaliveOp: bigint | null;
7
+ lastCheckpoint: bigint | null;
8
+ persistedOp: bigint | null;
9
+ createEmptyCheckpoints: boolean;
10
+ }
11
+
12
+ export interface CheckpointStateResult {
13
+ canCheckpoint: boolean;
14
+ checkpointBlocked: boolean;
15
+ checkpointCreated: boolean;
16
+ notEmpty: boolean;
17
+ newKeepaliveOp: bigint | null;
18
+ newLastCheckpoint: bigint | null;
19
+ }
20
+
21
+ function maxOpId(...values: (bigint | null | undefined)[]): bigint {
22
+ let max = 0n;
23
+ for (const value of values) {
24
+ if (value != null && value > max) {
25
+ max = value;
26
+ }
27
+ }
28
+ return max;
29
+ }
30
+
31
+ export function canCheckpointState(
32
+ lsn: string,
33
+ state: Pick<CheckpointStateInput, 'snapshotDone' | 'lastCheckpointLsn' | 'noCheckpointBefore'>
34
+ ): boolean {
35
+ return (
36
+ state.snapshotDone === true &&
37
+ (state.lastCheckpointLsn == null || state.lastCheckpointLsn <= lsn) &&
38
+ (state.noCheckpointBefore == null || state.noCheckpointBefore <= lsn)
39
+ );
40
+ }
41
+
42
+ export function calculateCheckpointState(input: CheckpointStateInput): CheckpointStateResult {
43
+ const canCheckpoint = canCheckpointState(input.lsn, input);
44
+ const newKeepaliveOp = canCheckpoint ? null : maxOpId(input.keepaliveOp, input.persistedOp);
45
+ const newLastCheckpoint = canCheckpoint
46
+ ? maxOpId(input.lastCheckpoint, input.persistedOp, input.keepaliveOp)
47
+ : input.lastCheckpoint;
48
+ const notEmpty =
49
+ input.createEmptyCheckpoints || input.keepaliveOp !== newKeepaliveOp || input.lastCheckpoint !== newLastCheckpoint;
50
+
51
+ return {
52
+ canCheckpoint,
53
+ checkpointBlocked: !canCheckpoint,
54
+ checkpointCreated: canCheckpoint && notEmpty,
55
+ notEmpty,
56
+ newKeepaliveOp,
57
+ newLastCheckpoint
58
+ };
59
+ }