@powersync/service-module-mongodb-storage 0.16.0 → 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.
- package/CHANGELOG.md +34 -0
- package/dist/storage/MongoBucketStorage.d.ts +6 -4
- package/dist/storage/MongoBucketStorage.js +110 -36
- package/dist/storage/MongoBucketStorage.js.map +1 -1
- package/dist/storage/implementation/BucketDefinitionMapping.d.ts +4 -6
- package/dist/storage/implementation/BucketDefinitionMapping.js +3 -3
- package/dist/storage/implementation/BucketDefinitionMapping.js.map +1 -1
- package/dist/storage/implementation/CheckpointState.d.ts +20 -0
- package/dist/storage/implementation/CheckpointState.js +31 -0
- package/dist/storage/implementation/CheckpointState.js.map +1 -0
- package/dist/storage/implementation/MongoBucketBatch.d.ts +33 -22
- package/dist/storage/implementation/MongoBucketBatch.js +45 -271
- package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
- package/dist/storage/implementation/MongoChecksums.d.ts +2 -1
- package/dist/storage/implementation/MongoChecksums.js.map +1 -1
- package/dist/storage/implementation/MongoCompactor.d.ts +1 -1
- package/dist/storage/implementation/MongoPersistedSyncRules.d.ts +4 -4
- package/dist/storage/implementation/MongoPersistedSyncRules.js +11 -8
- package/dist/storage/implementation/MongoPersistedSyncRules.js.map +1 -1
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.d.ts +19 -5
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.js +53 -19
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.js.map +1 -1
- package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +21 -10
- package/dist/storage/implementation/MongoSyncBucketStorage.js +18 -163
- package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoSyncRulesLock.d.ts +5 -1
- package/dist/storage/implementation/MongoSyncRulesLock.js +7 -3
- package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -1
- package/dist/storage/implementation/SyncRuleStateUpdate.d.ts +14 -0
- package/dist/storage/implementation/SyncRuleStateUpdate.js +36 -0
- package/dist/storage/implementation/SyncRuleStateUpdate.js.map +1 -0
- package/dist/storage/implementation/common/BucketDataDoc.d.ts +1 -1
- package/dist/storage/implementation/common/PersistedBatch.d.ts +2 -2
- package/dist/storage/implementation/common/SourceRecordStore.d.ts +1 -2
- package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.d.ts +1 -1
- package/dist/storage/implementation/createMongoSyncBucketStorage.d.ts +2 -2
- package/dist/storage/implementation/createMongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/implementation/db.d.ts +10 -2
- package/dist/storage/implementation/db.js.map +1 -1
- package/dist/storage/implementation/models.d.ts +31 -47
- package/dist/storage/implementation/models.js.map +1 -1
- package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +15 -1
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js +385 -0
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -1
- package/dist/storage/implementation/v1/MongoCompactorV1.d.ts +1 -1
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.d.ts +16 -7
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +77 -6
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js.map +1 -1
- package/dist/storage/implementation/v1/PersistedBatchV1.d.ts +1 -2
- package/dist/storage/implementation/v1/PersistedBatchV1.js.map +1 -1
- package/dist/storage/implementation/v1/models.d.ts +12 -1
- package/dist/storage/implementation/v1/models.js.map +1 -1
- package/dist/storage/implementation/v3/MongoBucketBatchV3.d.ts +17 -0
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js +429 -0
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +1 -1
- package/dist/storage/implementation/v3/MongoParameterLookupV3.d.ts +1 -2
- package/dist/storage/implementation/v3/MongoParameterLookupV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.d.ts +29 -7
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +117 -16
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -1
- package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +1 -2
- package/dist/storage/implementation/v3/PersistedBatchV3.js.map +1 -1
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.d.ts +3 -2
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js +3 -0
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js.map +1 -1
- package/dist/storage/implementation/v3/models.d.ts +61 -3
- package/dist/storage/implementation/v3/models.js.map +1 -1
- package/package.json +6 -6
- package/src/migrations/db/migrations/1702295701188-sync-rule-state.ts +1 -1
- package/src/storage/MongoBucketStorage.ts +166 -44
- package/src/storage/implementation/BucketDefinitionMapping.ts +12 -9
- package/src/storage/implementation/CheckpointState.ts +59 -0
- package/src/storage/implementation/MongoBucketBatch.ts +81 -355
- package/src/storage/implementation/MongoChecksums.ts +2 -1
- package/src/storage/implementation/MongoCompactor.ts +1 -1
- package/src/storage/implementation/MongoPersistedSyncRules.ts +13 -7
- package/src/storage/implementation/MongoPersistedSyncRulesContent.ts +69 -24
- package/src/storage/implementation/MongoSyncBucketStorage.ts +40 -215
- package/src/storage/implementation/MongoSyncRulesLock.ts +9 -3
- package/src/storage/implementation/SyncRuleStateUpdate.ts +38 -0
- package/src/storage/implementation/common/BucketDataDoc.ts +1 -1
- package/src/storage/implementation/common/PersistedBatch.ts +2 -2
- package/src/storage/implementation/common/SourceRecordStore.ts +1 -2
- package/src/storage/implementation/createMongoSyncBucketStorage.ts +2 -2
- package/src/storage/implementation/db.ts +5 -2
- package/src/storage/implementation/models.ts +35 -58
- package/src/storage/implementation/v1/MongoBucketBatchV1.ts +478 -1
- package/src/storage/implementation/v1/MongoCompactorV1.ts +1 -1
- package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +111 -16
- package/src/storage/implementation/v1/PersistedBatchV1.ts +1 -2
- package/src/storage/implementation/v1/models.ts +15 -0
- package/src/storage/implementation/v3/MongoBucketBatchV3.ts +564 -1
- package/src/storage/implementation/v3/MongoCompactorV3.ts +1 -1
- package/src/storage/implementation/v3/MongoParameterLookupV3.ts +1 -2
- package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +150 -22
- package/src/storage/implementation/v3/PersistedBatchV3.ts +1 -2
- package/src/storage/implementation/v3/VersionedPowerSyncMongoV3.ts +7 -2
- package/src/storage/implementation/v3/models.ts +70 -2
- package/test/src/storage_sync.test.ts +422 -6
- package/test/src/storeCurrentData.test.ts +211 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -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,14 +6,22 @@ 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';
|
|
10
11
|
import { BucketDefinitionMapping } from './implementation/BucketDefinitionMapping.js';
|
|
11
12
|
import type { MongoSyncBucketStorage } from './implementation/createMongoSyncBucketStorage.js';
|
|
12
13
|
import { createMongoSyncBucketStorage } from './implementation/createMongoSyncBucketStorage.js';
|
|
13
14
|
import { PowerSyncMongo } from './implementation/db.js';
|
|
14
|
-
import { getMongoStorageConfig,
|
|
15
|
+
import { getMongoStorageConfig, StorageConfig, SyncRuleDocumentBase } from './implementation/models.js';
|
|
15
16
|
import { MongoChecksumOptions } from './implementation/MongoChecksums.js';
|
|
16
|
-
import {
|
|
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';
|
|
17
25
|
|
|
18
26
|
export interface MongoBucketStorageOptions {
|
|
19
27
|
checksumOptions?: Omit<MongoChecksumOptions, 'storageConfig' | 'mapping'>;
|
|
@@ -54,11 +62,11 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
54
62
|
if ((typeof id as any) == 'bigint') {
|
|
55
63
|
id = Number(id);
|
|
56
64
|
}
|
|
57
|
-
const storageConfig = (syncRules as
|
|
65
|
+
const storageConfig = (syncRules as MongoPersistedSyncRulesContentV1).getStorageConfig();
|
|
58
66
|
const storage = createMongoSyncBucketStorage(
|
|
59
67
|
this,
|
|
60
68
|
id,
|
|
61
|
-
syncRules as
|
|
69
|
+
syncRules as MongoPersistedSyncRulesContentV1,
|
|
62
70
|
slot_name,
|
|
63
71
|
undefined,
|
|
64
72
|
{
|
|
@@ -110,11 +118,7 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
110
118
|
_id: next.id,
|
|
111
119
|
state: storage.SyncRuleState.PROCESSING
|
|
112
120
|
},
|
|
113
|
-
|
|
114
|
-
$set: {
|
|
115
|
-
state: storage.SyncRuleState.STOP
|
|
116
|
-
}
|
|
117
|
-
}
|
|
121
|
+
syncRuleStateUpdatePipeline(storage.SyncRuleState.STOP)
|
|
118
122
|
);
|
|
119
123
|
await this.db.notifyCheckpoint();
|
|
120
124
|
} else if (next == null && active?.id == sync_rules_group_id) {
|
|
@@ -130,11 +134,7 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
130
134
|
_id: active.id,
|
|
131
135
|
state: storage.SyncRuleState.ACTIVE
|
|
132
136
|
},
|
|
133
|
-
|
|
134
|
-
$set: {
|
|
135
|
-
state: storage.SyncRuleState.ERRORED
|
|
136
|
-
}
|
|
137
|
-
}
|
|
137
|
+
syncRuleStateUpdatePipeline(storage.SyncRuleState.ERRORED)
|
|
138
138
|
);
|
|
139
139
|
await this.db.notifyCheckpoint();
|
|
140
140
|
} else if (next != null && active?.id == sync_rules_group_id) {
|
|
@@ -145,30 +145,122 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
145
145
|
_id: active.id,
|
|
146
146
|
state: storage.SyncRuleState.ACTIVE
|
|
147
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(
|
|
148
167
|
{
|
|
149
|
-
|
|
150
|
-
|
|
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
|
+
},
|
|
178
|
+
{
|
|
179
|
+
$inc: {
|
|
180
|
+
op_id: 1n
|
|
151
181
|
}
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
upsert: true,
|
|
185
|
+
returnDocument: 'after',
|
|
186
|
+
session
|
|
152
187
|
}
|
|
153
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 });
|
|
154
230
|
await this.db.notifyCheckpoint();
|
|
155
|
-
|
|
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!;
|
|
156
239
|
}
|
|
157
240
|
|
|
158
|
-
async updateSyncRules(
|
|
241
|
+
async updateSyncRules(
|
|
242
|
+
options: storage.UpdateSyncRulesOptions
|
|
243
|
+
): Promise<MongoPersistedSyncRulesContentV1 | MongoPersistedSyncRulesContentV3> {
|
|
159
244
|
const storageVersion =
|
|
160
245
|
options.storageVersion ?? options.config.parsed.config.storageVersion ?? storage.CURRENT_STORAGE_VERSION;
|
|
246
|
+
|
|
161
247
|
const storageConfig = getMongoStorageConfig(storageVersion);
|
|
248
|
+
if (storageConfig.incrementalReprocessing) {
|
|
249
|
+
return this.updateSyncRulesV3(options, storageVersion, storageConfig);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
let rules: MongoPersistedSyncRulesContentV1 | undefined = undefined;
|
|
162
253
|
|
|
163
|
-
|
|
254
|
+
const session = this.session;
|
|
164
255
|
|
|
165
|
-
await
|
|
256
|
+
await session.withTransaction(async () => {
|
|
166
257
|
// Only have a single replication stream with PROCESSING.
|
|
167
258
|
await this.db.sync_rules.updateMany(
|
|
168
259
|
{
|
|
169
260
|
state: storage.SyncRuleState.PROCESSING
|
|
170
261
|
},
|
|
171
|
-
|
|
262
|
+
syncRuleStateUpdatePipeline(storage.SyncRuleState.STOP),
|
|
263
|
+
{ session }
|
|
172
264
|
);
|
|
173
265
|
|
|
174
266
|
const id_doc = await this.db.op_id_sequence.findOneAndUpdate(
|
|
@@ -182,14 +274,15 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
182
274
|
},
|
|
183
275
|
{
|
|
184
276
|
upsert: true,
|
|
185
|
-
returnDocument: 'after'
|
|
277
|
+
returnDocument: 'after',
|
|
278
|
+
session
|
|
186
279
|
}
|
|
187
280
|
);
|
|
188
281
|
|
|
189
282
|
const id = Number(id_doc!.op_id);
|
|
190
283
|
const slot_name = generateSlotName(this.slot_name_prefix, id);
|
|
191
284
|
|
|
192
|
-
const doc:
|
|
285
|
+
const doc: SyncRuleDocumentV1 = {
|
|
193
286
|
_id: id,
|
|
194
287
|
storage_version: storageVersion,
|
|
195
288
|
content: options.config.yaml,
|
|
@@ -207,47 +300,68 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
207
300
|
last_fatal_error_ts: null,
|
|
208
301
|
last_keepalive_ts: null
|
|
209
302
|
};
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
doc.rule_mapping = BucketDefinitionMapping.fromParsedSyncRules(parsed).serialize();
|
|
213
|
-
}
|
|
214
|
-
await this.db.sync_rules.insertOne(doc);
|
|
303
|
+
|
|
304
|
+
await this.db.sync_rules.insertOne(doc, { session });
|
|
215
305
|
await this.db.notifyCheckpoint();
|
|
216
|
-
rules = new
|
|
306
|
+
rules = new MongoPersistedSyncRulesContentV1(this.db, doc);
|
|
217
307
|
if (options.lock) {
|
|
218
|
-
|
|
308
|
+
// The lock is persisted on rules.current_lock
|
|
309
|
+
await rules.lock(session);
|
|
219
310
|
}
|
|
220
311
|
});
|
|
221
312
|
|
|
222
313
|
return rules!;
|
|
223
314
|
}
|
|
224
315
|
|
|
225
|
-
async getActiveSyncRulesContent(): Promise<
|
|
316
|
+
async getActiveSyncRulesContent(): Promise<
|
|
317
|
+
MongoPersistedSyncRulesContentV1 | MongoPersistedSyncRulesContentV3 | null
|
|
318
|
+
> {
|
|
226
319
|
const doc = await this.db.sync_rules.findOne(
|
|
227
320
|
{
|
|
228
321
|
state: { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] }
|
|
229
322
|
},
|
|
230
323
|
{ sort: { _id: -1 }, limit: 1 }
|
|
231
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[]) {
|
|
232
330
|
if (doc == null) {
|
|
233
331
|
return null;
|
|
234
332
|
}
|
|
333
|
+
const storageConfig = getMongoStorageConfig(doc.storage_version ?? LEGACY_STORAGE_VERSION);
|
|
235
334
|
|
|
236
|
-
|
|
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
|
+
}
|
|
341
|
+
|
|
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);
|
|
237
354
|
}
|
|
238
355
|
|
|
239
|
-
async getNextSyncRulesContent(): Promise<
|
|
356
|
+
async getNextSyncRulesContent(): Promise<MongoPersistedSyncRulesContentV1 | MongoPersistedSyncRulesContentV3 | null> {
|
|
240
357
|
const doc = await this.db.sync_rules.findOne(
|
|
241
358
|
{
|
|
242
359
|
state: storage.SyncRuleState.PROCESSING
|
|
243
360
|
},
|
|
244
361
|
{ sort: { _id: -1 }, limit: 1 }
|
|
245
362
|
);
|
|
246
|
-
if (doc == null) {
|
|
247
|
-
return null;
|
|
248
|
-
}
|
|
249
363
|
|
|
250
|
-
return
|
|
364
|
+
return this.getSyncRulesContent(doc, [storage.SyncRuleState.PROCESSING]);
|
|
251
365
|
}
|
|
252
366
|
|
|
253
367
|
async getReplicatingSyncRules(): Promise<storage.PersistedSyncRulesContent[]> {
|
|
@@ -257,9 +371,13 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
257
371
|
})
|
|
258
372
|
.toArray();
|
|
259
373
|
|
|
260
|
-
return
|
|
261
|
-
|
|
262
|
-
|
|
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);
|
|
263
381
|
}
|
|
264
382
|
|
|
265
383
|
async getStoppedSyncRules(): Promise<storage.PersistedSyncRulesContent[]> {
|
|
@@ -269,9 +387,13 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
269
387
|
})
|
|
270
388
|
.toArray();
|
|
271
389
|
|
|
272
|
-
return
|
|
273
|
-
|
|
274
|
-
|
|
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);
|
|
275
397
|
}
|
|
276
398
|
|
|
277
399
|
async getActiveStorage(): Promise<MongoSyncBucketStorage | null> {
|
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
import { ServiceAssertionError } from '@powersync/lib-services-framework';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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';
|
|
7
10
|
|
|
8
11
|
export class BucketDefinitionMapping {
|
|
9
|
-
static
|
|
12
|
+
static fromSyncConfig(doc: Pick<SyncConfigDefinition, 'rule_mapping'>): BucketDefinitionMapping {
|
|
10
13
|
return new BucketDefinitionMapping(doc.rule_mapping?.definitions ?? {}, doc.rule_mapping?.parameter_indexes ?? {});
|
|
11
14
|
}
|
|
12
15
|
|
|
13
16
|
static fromParsedSyncRules(syncRules: SyncConfigWithErrors): BucketDefinitionMapping {
|
|
14
17
|
const definitionNames = syncRules.config.bucketDataSources.map((source) => source.uniqueName).sort();
|
|
15
18
|
const parameterKeys = syncRules.config.bucketParameterLookupSources
|
|
16
|
-
.map((source) => `${source.
|
|
19
|
+
.map((source) => `${source.sourceId.lookupName}#${source.sourceId.queryId}`)
|
|
17
20
|
.sort();
|
|
18
21
|
|
|
19
22
|
const definitions: Record<string, BucketDefinitionId> = {};
|
|
@@ -51,7 +54,7 @@ export class BucketDefinitionMapping {
|
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
parameterLookupId(source: ParameterIndexLookupCreator): ParameterIndexId {
|
|
54
|
-
const key = this.parameterLookupKey(source.
|
|
57
|
+
const key = this.parameterLookupKey(source.sourceId.lookupName, source.sourceId.queryId);
|
|
55
58
|
const defId = this.parameterLookupMapping[key];
|
|
56
59
|
if (defId == null) {
|
|
57
60
|
throw new ServiceAssertionError(`No mapping found for parameter lookup source ${key}`);
|
|
@@ -63,7 +66,7 @@ export class BucketDefinitionMapping {
|
|
|
63
66
|
return `${lookupName}#${queryId}`;
|
|
64
67
|
}
|
|
65
68
|
|
|
66
|
-
serialize():
|
|
69
|
+
serialize(): SyncConfigDefinition['rule_mapping'] {
|
|
67
70
|
return {
|
|
68
71
|
definitions: { ...this.definitions },
|
|
69
72
|
parameter_indexes: { ...this.parameterLookupMapping }
|
|
@@ -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
|
+
}
|