@powersync/service-module-mongodb-storage 0.16.0 → 0.18.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 +53 -0
- package/dist/storage/MongoBucketStorage.d.ts +35 -15
- package/dist/storage/MongoBucketStorage.js +309 -86
- package/dist/storage/MongoBucketStorage.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 +69 -39
- package/dist/storage/implementation/MongoBucketBatch.js +104 -313
- package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
- package/dist/storage/implementation/MongoChecksums.d.ts +11 -9
- package/dist/storage/implementation/MongoChecksums.js +3 -111
- package/dist/storage/implementation/MongoChecksums.js.map +1 -1
- package/dist/storage/implementation/MongoCompactor.d.ts +3 -1
- package/dist/storage/implementation/MongoCompactor.js +18 -9
- package/dist/storage/implementation/MongoCompactor.js.map +1 -1
- package/dist/storage/implementation/MongoHydrationState.d.ts +11 -0
- package/dist/storage/implementation/MongoHydrationState.js +49 -0
- package/dist/storage/implementation/MongoHydrationState.js.map +1 -0
- package/dist/storage/implementation/MongoParameterCompactor.d.ts +6 -5
- package/dist/storage/implementation/MongoParameterCompactor.js +24 -1
- package/dist/storage/implementation/MongoParameterCompactor.js.map +1 -1
- package/dist/storage/implementation/MongoParsedSyncConfigSet.d.ts +13 -0
- package/dist/storage/implementation/MongoParsedSyncConfigSet.js +64 -0
- package/dist/storage/implementation/MongoParsedSyncConfigSet.js.map +1 -0
- package/dist/storage/implementation/MongoPersistedReplicationStream.d.ts +36 -0
- package/dist/storage/implementation/MongoPersistedReplicationStream.js +90 -0
- package/dist/storage/implementation/MongoPersistedReplicationStream.js.map +1 -0
- package/dist/storage/implementation/MongoPersistedSyncConfigContent.d.ts +25 -0
- package/dist/storage/implementation/MongoPersistedSyncConfigContent.js +121 -0
- package/dist/storage/implementation/MongoPersistedSyncConfigContent.js.map +1 -0
- package/dist/storage/implementation/MongoStorageProvider.js +5 -2
- package/dist/storage/implementation/MongoStorageProvider.js.map +1 -1
- package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +50 -21
- package/dist/storage/implementation/MongoSyncBucketStorage.js +62 -199
- package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoSyncRulesLock.d.ts +5 -1
- package/dist/storage/implementation/MongoSyncRulesLock.js +9 -5
- 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/MongoSyncBucketStorageCheckpoint.d.ts +6 -0
- package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.js +2 -0
- package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.js.map +1 -0
- package/dist/storage/implementation/common/PersistedBatch.d.ts +2 -3
- package/dist/storage/implementation/common/PersistedBatch.js +5 -0
- package/dist/storage/implementation/common/PersistedBatch.js.map +1 -1
- package/dist/storage/implementation/common/SourceRecordStore.d.ts +1 -2
- package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.d.ts +1 -1
- package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js +1 -1
- package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js.map +1 -1
- package/dist/storage/implementation/createMongoSyncBucketStorage.d.ts +2 -2
- package/dist/storage/implementation/createMongoSyncBucketStorage.js +4 -4
- package/dist/storage/implementation/createMongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/implementation/db.d.ts +10 -2
- package/dist/storage/implementation/db.js +9 -14
- package/dist/storage/implementation/db.js.map +1 -1
- package/dist/storage/implementation/models.d.ts +25 -50
- package/dist/storage/implementation/models.js.map +1 -1
- package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +23 -1
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js +403 -2
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -1
- package/dist/storage/implementation/v1/MongoChecksumsV1.js +114 -3
- package/dist/storage/implementation/v1/MongoChecksumsV1.js.map +1 -1
- package/dist/storage/implementation/v1/MongoCompactorV1.d.ts +1 -1
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.d.ts +22 -15
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +100 -27
- 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/VersionedPowerSyncMongoV1.d.ts +2 -1
- package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.js +3 -0
- package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.js.map +1 -1
- package/dist/storage/implementation/v1/models.d.ts +21 -1
- package/dist/storage/implementation/v1/models.js.map +1 -1
- package/dist/storage/implementation/v3/MongoBucketBatchV3.d.ts +21 -0
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js +459 -3
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoChecksumsV3.d.ts +27 -7
- package/dist/storage/implementation/v3/MongoChecksumsV3.js +202 -18
- package/dist/storage/implementation/v3/MongoChecksumsV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +20 -3
- package/dist/storage/implementation/v3/MongoCompactorV3.js +511 -22
- package/dist/storage/implementation/v3/MongoCompactorV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.d.ts +62 -0
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js +350 -0
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js.map +1 -0
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.d.ts +50 -16
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +292 -75
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -1
- package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +2 -3
- package/dist/storage/implementation/v3/PersistedBatchV3.js +43 -20
- package/dist/storage/implementation/v3/PersistedBatchV3.js.map +1 -1
- package/dist/storage/implementation/v3/SingleBucketStoreV3.js +52 -16
- package/dist/storage/implementation/v3/SingleBucketStoreV3.js.map +1 -1
- package/dist/storage/implementation/v3/SourceRecordStoreV3.d.ts +3 -3
- package/dist/storage/implementation/v3/SourceRecordStoreV3.js +13 -11
- package/dist/storage/implementation/v3/SourceRecordStoreV3.js.map +1 -1
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.d.ts +16 -14
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js +32 -27
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js.map +1 -1
- package/dist/storage/implementation/v3/bucket-format.d.ts +4 -0
- package/dist/storage/implementation/v3/bucket-format.js +58 -0
- package/dist/storage/implementation/v3/bucket-format.js.map +1 -0
- package/dist/storage/implementation/v3/chunking.d.ts +10 -0
- package/dist/storage/implementation/v3/chunking.js +28 -0
- package/dist/storage/implementation/v3/chunking.js.map +1 -0
- package/dist/storage/implementation/v3/models.d.ts +111 -10
- package/dist/storage/implementation/v3/models.js +11 -28
- package/dist/storage/implementation/v3/models.js.map +1 -1
- package/dist/storage/implementation/v3/source-table-utils.d.ts +55 -0
- package/dist/storage/implementation/v3/source-table-utils.js +214 -0
- package/dist/storage/implementation/v3/source-table-utils.js.map +1 -0
- package/dist/storage/storage-index.d.ts +4 -4
- package/dist/storage/storage-index.js +4 -4
- package/dist/storage/storage-index.js.map +1 -1
- package/dist/types/types.d.ts +2 -2
- package/dist/utils/test-utils.d.ts +1 -2
- package/dist/utils/test-utils.js +5 -1
- package/dist/utils/test-utils.js.map +1 -1
- package/dist/utils/util.d.ts +1 -1
- package/dist/utils/util.js +3 -2
- package/dist/utils/util.js.map +1 -1
- package/package.json +9 -19
- package/src/migrations/db/migrations/1702295701188-sync-rule-state.ts +1 -1
- package/src/storage/MongoBucketStorage.ts +464 -95
- package/src/storage/implementation/CheckpointState.ts +59 -0
- package/src/storage/implementation/MongoBucketBatch.ts +159 -405
- package/src/storage/implementation/MongoChecksums.ts +7 -138
- package/src/storage/implementation/MongoCompactor.ts +20 -10
- package/src/storage/implementation/MongoHydrationState.ts +61 -0
- package/src/storage/implementation/MongoParameterCompactor.ts +24 -5
- package/src/storage/implementation/MongoParsedSyncConfigSet.ts +88 -0
- package/src/storage/implementation/MongoPersistedReplicationStream.ts +118 -0
- package/src/storage/implementation/MongoPersistedSyncConfigContent.ts +152 -0
- package/src/storage/implementation/MongoStorageProvider.ts +5 -2
- package/src/storage/implementation/MongoSyncBucketStorage.ts +89 -250
- package/src/storage/implementation/MongoSyncRulesLock.ts +14 -5
- package/src/storage/implementation/SyncRuleStateUpdate.ts +38 -0
- package/src/storage/implementation/common/BucketDataDoc.ts +1 -1
- package/src/storage/implementation/common/MongoSyncBucketStorageCheckpoint.ts +7 -0
- package/src/storage/implementation/common/PersistedBatch.ts +8 -4
- package/src/storage/implementation/common/SourceRecordStore.ts +1 -2
- package/src/storage/implementation/common/VersionedPowerSyncMongoBase.ts +1 -1
- package/src/storage/implementation/createMongoSyncBucketStorage.ts +21 -7
- package/src/storage/implementation/db.ts +16 -21
- package/src/storage/implementation/models.ts +28 -61
- package/src/storage/implementation/v1/MongoBucketBatchV1.ts +503 -5
- package/src/storage/implementation/v1/MongoChecksumsV1.ts +137 -4
- package/src/storage/implementation/v1/MongoCompactorV1.ts +1 -1
- package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +150 -49
- package/src/storage/implementation/v1/PersistedBatchV1.ts +1 -2
- package/src/storage/implementation/v1/VersionedPowerSyncMongoV1.ts +11 -1
- package/src/storage/implementation/v1/models.ts +25 -0
- package/src/storage/implementation/v3/MongoBucketBatchV3.ts +611 -4
- package/src/storage/implementation/v3/MongoChecksumsV3.ts +262 -26
- package/src/storage/implementation/v3/MongoCompactorV3.ts +647 -30
- package/src/storage/implementation/v3/MongoStoppedSyncConfigCleanup.ts +480 -0
- package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +397 -111
- package/src/storage/implementation/v3/PersistedBatchV3.ts +63 -38
- package/src/storage/implementation/v3/SingleBucketStoreV3.ts +59 -19
- package/src/storage/implementation/v3/SourceRecordStoreV3.ts +12 -12
- package/src/storage/implementation/v3/VersionedPowerSyncMongoV3.ts +48 -38
- package/src/storage/implementation/v3/bucket-format.ts +70 -0
- package/src/storage/implementation/v3/chunking.ts +38 -0
- package/src/storage/implementation/v3/models.ts +150 -44
- package/src/storage/implementation/v3/source-table-utils.ts +392 -0
- package/src/storage/storage-index.ts +12 -4
- package/src/utils/test-utils.ts +6 -3
- package/src/utils/util.ts +3 -2
- package/test/src/__snapshots__/storage.test.ts.snap +0 -75
- package/test/src/__snapshots__/storage_sync.test.ts.snap +48 -48
- package/test/src/cleanup-stopped-sync-configs.test.ts +648 -0
- package/test/src/storage.test.ts +11 -11
- package/test/src/storage_compacting.test.ts +1956 -6
- package/test/src/storage_sync.test.ts +1429 -42
- package/test/src/storeCurrentData.test.ts +221 -0
- package/test/src/util.ts +2 -1
- package/tsconfig.json +1 -4
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/storage/implementation/BucketDefinitionMapping.d.ts +0 -17
- package/dist/storage/implementation/BucketDefinitionMapping.js +0 -58
- package/dist/storage/implementation/BucketDefinitionMapping.js.map +0 -1
- package/dist/storage/implementation/MongoPersistedSyncRules.d.ts +0 -14
- package/dist/storage/implementation/MongoPersistedSyncRules.js +0 -64
- package/dist/storage/implementation/MongoPersistedSyncRules.js.map +0 -1
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.d.ts +0 -15
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.js +0 -42
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.js.map +0 -1
- package/dist/storage/implementation/common/MongoSyncBucketStorageContext.d.ts +0 -13
- package/dist/storage/implementation/common/MongoSyncBucketStorageContext.js +0 -2
- package/dist/storage/implementation/common/MongoSyncBucketStorageContext.js.map +0 -1
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.d.ts +0 -9
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.js +0 -18
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.js.map +0 -1
- package/dist/storage/implementation/v3/MongoParameterLookupV3.d.ts +0 -5
- package/dist/storage/implementation/v3/MongoParameterLookupV3.js +0 -9
- package/dist/storage/implementation/v3/MongoParameterLookupV3.js.map +0 -1
- package/src/storage/implementation/BucketDefinitionMapping.ts +0 -72
- package/src/storage/implementation/MongoPersistedSyncRules.ts +0 -76
- package/src/storage/implementation/MongoPersistedSyncRulesContent.ts +0 -56
- package/src/storage/implementation/common/MongoSyncBucketStorageContext.ts +0 -15
- package/src/storage/implementation/v3/MongoParameterCompactorV3.ts +0 -24
- package/src/storage/implementation/v3/MongoParameterLookupV3.ts +0 -12
|
@@ -7,18 +7,16 @@ import {
|
|
|
7
7
|
ChecksumMap,
|
|
8
8
|
FetchPartialBucketChecksum,
|
|
9
9
|
InternalOpId,
|
|
10
|
-
isPartialChecksum,
|
|
11
10
|
PartialChecksum,
|
|
12
11
|
PartialChecksumMap,
|
|
13
12
|
PartialOrFullChecksum
|
|
14
13
|
} from '@powersync/service-core';
|
|
15
14
|
import type { VersionedPowerSyncMongo } from './db.js';
|
|
16
15
|
|
|
17
|
-
import
|
|
18
|
-
import {
|
|
19
|
-
import { BucketDataDocumentBase, StorageConfig } from './models.js';
|
|
16
|
+
import { BucketDefinitionId } from '@powersync/service-sync-rules';
|
|
17
|
+
import { StorageConfig } from './models.js';
|
|
20
18
|
|
|
21
|
-
export interface
|
|
19
|
+
export interface FetchPartialBucketChecksumByDefinition {
|
|
22
20
|
bucket: string;
|
|
23
21
|
definitionId: BucketDefinitionId;
|
|
24
22
|
start?: InternalOpId;
|
|
@@ -46,15 +44,14 @@ export interface MongoChecksumOptions {
|
|
|
46
44
|
operationBatchLimit?: number;
|
|
47
45
|
|
|
48
46
|
storageConfig: StorageConfig;
|
|
49
|
-
mapping?: BucketDefinitionMapping;
|
|
50
47
|
}
|
|
51
48
|
|
|
52
49
|
const DEFAULT_BUCKET_BATCH_LIMIT = 200;
|
|
53
|
-
const DEFAULT_OPERATION_BATCH_LIMIT = 50_000;
|
|
50
|
+
export const DEFAULT_OPERATION_BATCH_LIMIT = 50_000;
|
|
54
51
|
|
|
55
52
|
export abstract class MongoChecksums {
|
|
56
53
|
private _cache: ChecksumCache | undefined;
|
|
57
|
-
|
|
54
|
+
protected readonly storageConfig: StorageConfig;
|
|
58
55
|
|
|
59
56
|
constructor(
|
|
60
57
|
protected readonly db: VersionedPowerSyncMongo,
|
|
@@ -177,138 +174,10 @@ export abstract class MongoChecksums {
|
|
|
177
174
|
protected abstract fetchPreStates(
|
|
178
175
|
batch: FetchPartialBucketChecksum[]
|
|
179
176
|
): Promise<Map<string, { opId: InternalOpId; checksum: BucketChecksum }>>;
|
|
180
|
-
|
|
181
|
-
protected async computePartialChecksumsForCollection<
|
|
182
|
-
TRequest extends FetchPartialBucketChecksumByBucket,
|
|
183
|
-
TBucketDataDocument extends BucketDataDocumentBase
|
|
184
|
-
>(
|
|
185
|
-
batch: TRequest[],
|
|
186
|
-
collection: lib_mongo.mongo.Collection<TBucketDataDocument>,
|
|
187
|
-
createFilter: (request: TRequest) => any
|
|
188
|
-
): Promise<PartialChecksumMap> {
|
|
189
|
-
const batchLimit = this.options?.operationBatchLimit ?? DEFAULT_OPERATION_BATCH_LIMIT;
|
|
190
|
-
|
|
191
|
-
// Map requests by bucket. We adjust this as we get partial results.
|
|
192
|
-
let requests = new Map<string, TRequest>();
|
|
193
|
-
for (let request of batch) {
|
|
194
|
-
requests.set(request.bucket, request);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
const partialChecksums = new Map<string, PartialOrFullChecksum>();
|
|
198
|
-
|
|
199
|
-
while (requests.size > 0) {
|
|
200
|
-
const filters = Array.from(requests.values(), createFilter);
|
|
201
|
-
|
|
202
|
-
// Historically, checksum may be stored as 'int' or 'double'.
|
|
203
|
-
// More recently, this should be a 'long'.
|
|
204
|
-
// $toLong ensures that we always sum it as a long, avoiding inaccuracies in the calculations.
|
|
205
|
-
const checksumLong = this.storageConfig.longChecksums ? '$checksum' : { $toLong: '$checksum' };
|
|
206
|
-
|
|
207
|
-
// Aggregate over a max of `batchLimit` operations at a time.
|
|
208
|
-
// Let's say we have 3 buckets (A, B, C), each with 10 operations, and our batch limit is 12.
|
|
209
|
-
// Then we'll do three batches:
|
|
210
|
-
// 1. Query: A[1-end], B[1-end], C[1-end]
|
|
211
|
-
// Returns: A[1-10], B[1-2]
|
|
212
|
-
// 2. Query: B[3-end], C[1-end]
|
|
213
|
-
// Returns: B[3-10], C[1-4]
|
|
214
|
-
// 3. Query: C[5-end]
|
|
215
|
-
// Returns: C[5-10]
|
|
216
|
-
const aggregate = await collection
|
|
217
|
-
.aggregate(
|
|
218
|
-
[
|
|
219
|
-
{
|
|
220
|
-
$match: {
|
|
221
|
-
$or: filters
|
|
222
|
-
}
|
|
223
|
-
},
|
|
224
|
-
// sort and limit _before_ grouping
|
|
225
|
-
{ $sort: { _id: 1 } },
|
|
226
|
-
{ $limit: batchLimit },
|
|
227
|
-
{
|
|
228
|
-
$group: {
|
|
229
|
-
_id: '$_id.b',
|
|
230
|
-
checksum_total: { $sum: checksumLong },
|
|
231
|
-
count: { $sum: 1 },
|
|
232
|
-
has_clear_op: {
|
|
233
|
-
$max: {
|
|
234
|
-
$cond: [{ $eq: ['$op', 'CLEAR'] }, 1, 0]
|
|
235
|
-
}
|
|
236
|
-
},
|
|
237
|
-
last_op: { $max: '$_id.o' }
|
|
238
|
-
}
|
|
239
|
-
},
|
|
240
|
-
// Sort the aggregated results (100 max, so should be fast).
|
|
241
|
-
// This is important to identify which buckets we have partial data for.
|
|
242
|
-
{ $sort: { _id: 1 } }
|
|
243
|
-
],
|
|
244
|
-
{ session: undefined, readConcern: 'snapshot', maxTimeMS: lib_mongo.MONGO_CHECKSUM_TIMEOUT_MS }
|
|
245
|
-
)
|
|
246
|
-
.toArray()
|
|
247
|
-
.catch((e) => {
|
|
248
|
-
throw lib_mongo.mapQueryError(e, 'while reading checksums');
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
let batchCount = 0;
|
|
252
|
-
let limitReached = false;
|
|
253
|
-
for (let doc of aggregate) {
|
|
254
|
-
const bucket = doc._id;
|
|
255
|
-
const checksum = checksumFromAggregate(doc);
|
|
256
|
-
|
|
257
|
-
const existing = partialChecksums.get(bucket);
|
|
258
|
-
if (existing != null) {
|
|
259
|
-
partialChecksums.set(bucket, addPartialChecksums(bucket, existing, checksum));
|
|
260
|
-
} else {
|
|
261
|
-
partialChecksums.set(bucket, checksum);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
batchCount += doc.count;
|
|
265
|
-
if (batchCount == batchLimit) {
|
|
266
|
-
// Limit reached. Request more in the next batch.
|
|
267
|
-
// Note that this only affects the _last_ bucket in a batch.
|
|
268
|
-
limitReached = true;
|
|
269
|
-
const req = requests.get(bucket);
|
|
270
|
-
requests.set(bucket, {
|
|
271
|
-
...req!,
|
|
272
|
-
start: doc.last_op
|
|
273
|
-
});
|
|
274
|
-
} else {
|
|
275
|
-
// All done for this bucket
|
|
276
|
-
requests.delete(bucket);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
if (!limitReached) {
|
|
280
|
-
break;
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
return new Map<string, PartialOrFullChecksum>(
|
|
285
|
-
batch.map((request) => {
|
|
286
|
-
const bucket = request.bucket;
|
|
287
|
-
// Could be null if we got no data
|
|
288
|
-
let partialChecksum = partialChecksums.get(bucket);
|
|
289
|
-
if (partialChecksum == null) {
|
|
290
|
-
partialChecksum = {
|
|
291
|
-
bucket,
|
|
292
|
-
partialCount: 0,
|
|
293
|
-
partialChecksum: 0
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
if (request.start == null && isPartialChecksum(partialChecksum)) {
|
|
297
|
-
partialChecksum = {
|
|
298
|
-
bucket,
|
|
299
|
-
count: partialChecksum.partialCount,
|
|
300
|
-
checksum: partialChecksum.partialChecksum
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
return [bucket, partialChecksum];
|
|
305
|
-
})
|
|
306
|
-
);
|
|
307
|
-
}
|
|
308
177
|
}
|
|
309
178
|
|
|
310
179
|
export function emptyChecksumForRequest(
|
|
311
|
-
request: Pick<FetchPartialBucketChecksum |
|
|
180
|
+
request: Pick<FetchPartialBucketChecksum | FetchPartialBucketChecksumByDefinition, 'bucket' | 'start'>
|
|
312
181
|
): PartialOrFullChecksum {
|
|
313
182
|
if (request.start == null) {
|
|
314
183
|
return { bucket: request.bucket, count: 0, checksum: 0 };
|
|
@@ -319,7 +188,7 @@ export function emptyChecksumForRequest(
|
|
|
319
188
|
/**
|
|
320
189
|
* Convert output of the $group stage into a checksum.
|
|
321
190
|
*/
|
|
322
|
-
function checksumFromAggregate(doc: bson.Document): PartialOrFullChecksum {
|
|
191
|
+
export function checksumFromAggregate(doc: bson.Document): PartialOrFullChecksum {
|
|
323
192
|
const partialChecksum = Number(BigInt(doc.checksum_total) & 0xffffffffn) & 0xffffffff;
|
|
324
193
|
const bucket = doc._id;
|
|
325
194
|
|
|
@@ -13,8 +13,8 @@ import {
|
|
|
13
13
|
storage,
|
|
14
14
|
utils
|
|
15
15
|
} from '@powersync/service-core';
|
|
16
|
+
import { BucketDefinitionId } from '@powersync/service-sync-rules';
|
|
16
17
|
|
|
17
|
-
import { BucketDefinitionId } from './BucketDefinitionMapping.js';
|
|
18
18
|
import { BucketDataDoc, BucketKey } from './common/BucketDataDoc.js';
|
|
19
19
|
import { BucketDataDocumentGeneric, SingleBucketStore } from './common/SingleBucketStore.js';
|
|
20
20
|
import type { VersionedPowerSyncMongo } from './db.js';
|
|
@@ -63,6 +63,7 @@ export interface MongoCompactOptions extends storage.CompactOptions {}
|
|
|
63
63
|
const DEFAULT_CLEAR_BATCH_LIMIT = 5000;
|
|
64
64
|
const DEFAULT_MOVE_BATCH_LIMIT = 2000;
|
|
65
65
|
const DEFAULT_MOVE_BATCH_QUERY_LIMIT = 10_000;
|
|
66
|
+
const DEFAULT_MOVE_BATCH_BYTE_LIMIT = 16 * 1024 * 1024;
|
|
66
67
|
const DEFAULT_MIN_BUCKET_CHANGES = 10;
|
|
67
68
|
const DEFAULT_MIN_CHANGE_RATIO = 0.1;
|
|
68
69
|
const DIRTY_BUCKET_SCAN_BATCH_SIZE = 2_000;
|
|
@@ -83,6 +84,7 @@ export abstract class MongoCompactor {
|
|
|
83
84
|
protected readonly idLimitBytes: number;
|
|
84
85
|
protected readonly moveBatchLimit: number;
|
|
85
86
|
protected readonly moveBatchQueryLimit: number;
|
|
87
|
+
protected readonly moveBatchByteLimit: number;
|
|
86
88
|
protected readonly clearBatchLimit: number;
|
|
87
89
|
protected readonly minBucketChanges: number;
|
|
88
90
|
protected readonly minChangeRatio: number;
|
|
@@ -98,11 +100,15 @@ export abstract class MongoCompactor {
|
|
|
98
100
|
protected readonly db: VersionedPowerSyncMongo,
|
|
99
101
|
options: MongoCompactOptions
|
|
100
102
|
) {
|
|
101
|
-
this.group_id = storage.
|
|
103
|
+
this.group_id = storage.replicationStreamId;
|
|
102
104
|
this.idLimitBytes = (options.memoryLimitMB ?? DEFAULT_MEMORY_LIMIT_MB) * 1024 * 1024;
|
|
103
105
|
this.moveBatchLimit = options.moveBatchLimit ?? DEFAULT_MOVE_BATCH_LIMIT;
|
|
104
106
|
this.moveBatchQueryLimit = options.moveBatchQueryLimit ?? DEFAULT_MOVE_BATCH_QUERY_LIMIT;
|
|
107
|
+
this.moveBatchByteLimit = options.moveBatchByteLimit ?? DEFAULT_MOVE_BATCH_BYTE_LIMIT;
|
|
105
108
|
this.clearBatchLimit = options.clearBatchLimit ?? DEFAULT_CLEAR_BATCH_LIMIT;
|
|
109
|
+
if (this.clearBatchLimit < 2) {
|
|
110
|
+
throw new ReplicationAssertionError('clearBatchLimit must be >= 2');
|
|
111
|
+
}
|
|
106
112
|
this.minBucketChanges = options.minBucketChanges ?? DEFAULT_MIN_BUCKET_CHANGES;
|
|
107
113
|
this.minChangeRatio = options.minChangeRatio ?? DEFAULT_MIN_CHANGE_RATIO;
|
|
108
114
|
this.maxOpId = options.maxOpId ?? 0n;
|
|
@@ -133,6 +139,7 @@ export abstract class MongoCompactor {
|
|
|
133
139
|
*/
|
|
134
140
|
async populateChecksums(options: { minBucketChanges: number }): Promise<PopulateChecksumCacheResults> {
|
|
135
141
|
let count = 0;
|
|
142
|
+
// Paginate through dirty buckets in batches until no more buckets meet the criteria.
|
|
136
143
|
while (true) {
|
|
137
144
|
this.signal?.throwIfAborted();
|
|
138
145
|
const buckets = await this.dirtyBucketBatchForChecksums(options);
|
|
@@ -172,6 +179,7 @@ export abstract class MongoCompactor {
|
|
|
172
179
|
},
|
|
173
180
|
getDefinitionId: (state: TCollectionBucketState) => BucketDefinitionId | null
|
|
174
181
|
): AsyncGenerator<DirtyBucket[]> {
|
|
182
|
+
// Paginate through the bucket state collection using cursor-based scanning.
|
|
175
183
|
while (true) {
|
|
176
184
|
// To avoid timeouts from too many buckets not meeting the minBucketChanges criteria, use an aggregation pipeline
|
|
177
185
|
// to scan a fixed batch of buckets at a time, but only return buckets that meet the criteria.
|
|
@@ -304,6 +312,7 @@ export abstract class MongoCompactor {
|
|
|
304
312
|
*/
|
|
305
313
|
protected async compactSingleBucketRetried(bucket: string, definitionId: BucketDefinitionId | null = null) {
|
|
306
314
|
let retryCount = 0;
|
|
315
|
+
// Retry with exponential backoff up to 3 times on MongoDB errors.
|
|
307
316
|
while (true) {
|
|
308
317
|
try {
|
|
309
318
|
await this.compactSingleBucket(bucket, definitionId);
|
|
@@ -343,6 +352,7 @@ export abstract class MongoCompactor {
|
|
|
343
352
|
// Upper bound is adjusted for each batch.
|
|
344
353
|
let upperBound = bucketContext.maxId;
|
|
345
354
|
|
|
355
|
+
// Paginate through bucket data in batches to avoid cursor timeouts.
|
|
346
356
|
while (true) {
|
|
347
357
|
this.signal?.throwIfAborted();
|
|
348
358
|
|
|
@@ -353,11 +363,7 @@ export abstract class MongoCompactor {
|
|
|
353
363
|
_id: {
|
|
354
364
|
$gte: lowerBound,
|
|
355
365
|
$lt: upperBound
|
|
356
|
-
}
|
|
357
|
-
// Workaround for a clustered collection bug where the $lt operator may include upperBound.
|
|
358
|
-
// Technically only needed for storage V3.
|
|
359
|
-
// https://jira.mongodb.org/browse/SERVER-121822
|
|
360
|
-
'_id.o': { $lt: upperBound.o }
|
|
366
|
+
}
|
|
361
367
|
}
|
|
362
368
|
},
|
|
363
369
|
{ $sort: { _id: -1 } },
|
|
@@ -488,13 +494,13 @@ export abstract class MongoCompactor {
|
|
|
488
494
|
await this.flush(bucketContext);
|
|
489
495
|
}
|
|
490
496
|
|
|
491
|
-
protected
|
|
497
|
+
protected collectBucketStateUpdates(state: CurrentBucketState): mongo.AnyBulkWriteOperation<BucketStateDocumentBase> {
|
|
492
498
|
if (state.opCount < 0) {
|
|
493
499
|
throw new ServiceAssertionError(
|
|
494
500
|
`Invalid opCount: ${state.opCount} checksum ${state.checksum} opsSincePut: ${state.opsSincePut} maxOpId: ${this.maxOpId}`
|
|
495
501
|
);
|
|
496
502
|
}
|
|
497
|
-
|
|
503
|
+
return {
|
|
498
504
|
updateOne: {
|
|
499
505
|
filter: this.bucketStateFilter(state.bucket, state.definitionId),
|
|
500
506
|
update: {
|
|
@@ -517,7 +523,11 @@ export abstract class MongoCompactor {
|
|
|
517
523
|
// We don't create new ones here, to avoid issues with the unique index on bucket_updates.
|
|
518
524
|
upsert: false
|
|
519
525
|
}
|
|
520
|
-
}
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
protected updateBucketChecksums(state: CurrentBucketState) {
|
|
530
|
+
this.bucketStateUpdates.push(this.collectBucketStateUpdates(state));
|
|
521
531
|
}
|
|
522
532
|
|
|
523
533
|
protected async flush(col: SingleBucketStore) {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { ServiceAssertionError } from '@powersync/lib-services-framework';
|
|
2
|
+
import { SyncConfigWithRequiredMapping } from '@powersync/service-core';
|
|
3
|
+
import {
|
|
4
|
+
BucketDataScope,
|
|
5
|
+
BucketDataSource,
|
|
6
|
+
HydrationState,
|
|
7
|
+
ParameterIndexLookupCreator,
|
|
8
|
+
ParameterLookupScope
|
|
9
|
+
} from '@powersync/service-sync-rules';
|
|
10
|
+
|
|
11
|
+
export class MongoHydrationState implements HydrationState {
|
|
12
|
+
private bucketDataSourceSyncConfig = new WeakMap<BucketDataSource, SyncConfigWithRequiredMapping>();
|
|
13
|
+
private parameterIndexLookupSyncConfig = new WeakMap<ParameterIndexLookupCreator, SyncConfigWithRequiredMapping>();
|
|
14
|
+
|
|
15
|
+
constructor(
|
|
16
|
+
readonly syncConfigs: SyncConfigWithRequiredMapping[],
|
|
17
|
+
private readonly replicationStreamId: number
|
|
18
|
+
) {
|
|
19
|
+
for (let syncConfig of syncConfigs) {
|
|
20
|
+
for (let source of syncConfig.syncConfig.config.bucketDataSources) {
|
|
21
|
+
this.bucketDataSourceSyncConfig.set(source, syncConfig);
|
|
22
|
+
}
|
|
23
|
+
for (let source of syncConfig.syncConfig.config.bucketParameterLookupSources) {
|
|
24
|
+
this.parameterIndexLookupSyncConfig.set(source, syncConfig);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
getBucketSourceScope(source: BucketDataSource): BucketDataScope {
|
|
30
|
+
const syncConfig = this.bucketDataSourceSyncConfig.get(source);
|
|
31
|
+
if (syncConfig == null) {
|
|
32
|
+
throw new ServiceAssertionError(`No sync config found for bucket data source ${source.uniqueName}`);
|
|
33
|
+
}
|
|
34
|
+
const mapping = syncConfig.mapping;
|
|
35
|
+
const defId = mapping.bucketSourceId(source);
|
|
36
|
+
// Currently uniqueName is constant for a BucketDataSource within a replication stream. This may change in the
|
|
37
|
+
// future, in which case we need to reconsider the bucketPrefix here.
|
|
38
|
+
// However, the definition may change without changing the name, so we include the defId in the bucketPrefix.
|
|
39
|
+
// defId is not unique across replication streams, so we include the replicationStreamId as well.
|
|
40
|
+
return {
|
|
41
|
+
bucketPrefix: `${source.uniqueName}.${this.replicationStreamId.toString(16)}.${defId}`,
|
|
42
|
+
source
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
getParameterIndexLookupScope(source: ParameterIndexLookupCreator): ParameterLookupScope {
|
|
47
|
+
const syncConfig = this.parameterIndexLookupSyncConfig.get(source);
|
|
48
|
+
if (syncConfig == null) {
|
|
49
|
+
throw new ServiceAssertionError(
|
|
50
|
+
`No sync config found for parameter index lookup source ${source.sourceId.lookupName}#${source.sourceId.queryId}`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
const mapping = syncConfig.mapping;
|
|
54
|
+
const defId = mapping.parameterLookupId(source);
|
|
55
|
+
return {
|
|
56
|
+
lookupName: defId,
|
|
57
|
+
queryId: '',
|
|
58
|
+
source
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -18,12 +18,13 @@ type ParameterCompactionReadDocument = {
|
|
|
18
18
|
*
|
|
19
19
|
* For background, see the `/docs/parameters-lookups.md` file.
|
|
20
20
|
*/
|
|
21
|
-
export
|
|
21
|
+
export class MongoParameterCompactor {
|
|
22
22
|
constructor(
|
|
23
23
|
protected readonly db: VersionedPowerSyncMongo,
|
|
24
24
|
protected readonly group_id: number,
|
|
25
25
|
protected readonly checkpoint: InternalOpId,
|
|
26
|
-
protected readonly options: CompactOptions
|
|
26
|
+
protected readonly options: CompactOptions,
|
|
27
|
+
protected readonly getCollectionsCb?: () => Promise<mongo.Collection<mongo.Document>[]>
|
|
27
28
|
) {}
|
|
28
29
|
|
|
29
30
|
async compact() {
|
|
@@ -33,11 +34,27 @@ export abstract class MongoParameterCompactor {
|
|
|
33
34
|
}
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
protected
|
|
37
|
+
protected async getCollections(): Promise<mongo.Collection<mongo.Document>[]> {
|
|
38
|
+
if (this.getCollectionsCb == null) {
|
|
39
|
+
throw new Error('getCollections callback not provided');
|
|
40
|
+
}
|
|
41
|
+
const collections = await this.getCollectionsCb();
|
|
42
|
+
// Cast from the version-specific collection type to the generic Document type
|
|
43
|
+
// used by the parameter compactor base class.
|
|
44
|
+
return collections.map((collection) => collection as unknown as mongo.Collection<mongo.Document>);
|
|
45
|
+
}
|
|
37
46
|
|
|
38
|
-
protected
|
|
47
|
+
protected collectionFilter(): mongo.Document {
|
|
48
|
+
return {};
|
|
49
|
+
}
|
|
39
50
|
|
|
40
|
-
protected
|
|
51
|
+
protected deleteFilter(doc: mongo.Document): mongo.Document {
|
|
52
|
+
return {
|
|
53
|
+
lookup: doc.lookup,
|
|
54
|
+
_id: { $lte: doc._id },
|
|
55
|
+
key: doc.key
|
|
56
|
+
};
|
|
57
|
+
}
|
|
41
58
|
|
|
42
59
|
protected async compactCollection(collection: mongo.Collection<mongo.Document>) {
|
|
43
60
|
// This is the currently-active checkpoint.
|
|
@@ -68,6 +85,7 @@ export abstract class MongoParameterCompactor {
|
|
|
68
85
|
|
|
69
86
|
const flush = async (force: boolean) => {
|
|
70
87
|
if (removeIds.length >= 1000 || (force && removeIds.length > 0)) {
|
|
88
|
+
// MongoDB Filter<T> doesn't fully match our dynamic delete filter shape here.
|
|
71
89
|
const results = await collection.deleteMany({ _id: { $in: removeIds } } as any);
|
|
72
90
|
logger.info(`Removed ${results.deletedCount} (${removeIds.length}) superseded parameter entries`);
|
|
73
91
|
removeIds = [];
|
|
@@ -81,6 +99,7 @@ export abstract class MongoParameterCompactor {
|
|
|
81
99
|
};
|
|
82
100
|
|
|
83
101
|
while (await cursor.hasNext()) {
|
|
102
|
+
// readBufferedDocuments returns a generic type; we know the shape from our projection.
|
|
84
103
|
const batch = cursor.readBufferedDocuments() as unknown as ParameterCompactionReadDocument[];
|
|
85
104
|
checkedEntries += batch.length;
|
|
86
105
|
const now = Date.now();
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import * as sqlite from 'node:sqlite';
|
|
2
|
+
|
|
3
|
+
import { ServiceAssertionError } from '@powersync/lib-services-framework';
|
|
4
|
+
import {
|
|
5
|
+
BucketDefinitionMapping,
|
|
6
|
+
MultiSyncConfigBucketDefinitionMapping,
|
|
7
|
+
SingleSyncConfigBucketDefinitionMapping,
|
|
8
|
+
storage,
|
|
9
|
+
SyncConfigWithMapping,
|
|
10
|
+
SyncConfigWithRequiredMapping
|
|
11
|
+
} from '@powersync/service-core';
|
|
12
|
+
import {
|
|
13
|
+
CompatibilityOption,
|
|
14
|
+
DEFAULT_HYDRATION_STATE,
|
|
15
|
+
HydratedSyncConfig,
|
|
16
|
+
HydrationState,
|
|
17
|
+
nodeSqlite,
|
|
18
|
+
SyncConfigWithErrors,
|
|
19
|
+
versionedHydrationState
|
|
20
|
+
} from '@powersync/service-sync-rules';
|
|
21
|
+
import { StorageConfig } from './models.js';
|
|
22
|
+
import { MongoHydrationState } from './MongoHydrationState.js';
|
|
23
|
+
|
|
24
|
+
export class MongoParsedSyncConfigSet implements storage.ParsedSyncConfigSet {
|
|
25
|
+
public readonly hydrationState: HydrationState;
|
|
26
|
+
public readonly syncConfigs: SyncConfigWithErrors[];
|
|
27
|
+
public readonly replicationStreamName: string;
|
|
28
|
+
public readonly mapping: BucketDefinitionMapping;
|
|
29
|
+
|
|
30
|
+
constructor(
|
|
31
|
+
public readonly replicationStreamId: number,
|
|
32
|
+
storageConfig: StorageConfig,
|
|
33
|
+
slotName: string,
|
|
34
|
+
syncConfigs: SyncConfigWithMapping[]
|
|
35
|
+
) {
|
|
36
|
+
this.replicationStreamName = slotName;
|
|
37
|
+
this.syncConfigs = syncConfigs.map((config) => config.syncConfig);
|
|
38
|
+
if (this.syncConfigs.length == 0) {
|
|
39
|
+
throw new ServiceAssertionError(`At least one sync config is required`);
|
|
40
|
+
}
|
|
41
|
+
const [firstConfig] = this.syncConfigs;
|
|
42
|
+
const compatibility = firstConfig.config.compatibility;
|
|
43
|
+
for (const config of this.syncConfigs) {
|
|
44
|
+
if (config.config.compatibility.equals(compatibility)) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
throw new ServiceAssertionError(
|
|
48
|
+
`All sync configs in a replication stream must use the same compatibility options`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (storageConfig.incrementalReprocessing) {
|
|
53
|
+
if (syncConfigs.some((c) => c.mapping == null)) {
|
|
54
|
+
throw new ServiceAssertionError(`mapping is required for v3 storage`);
|
|
55
|
+
}
|
|
56
|
+
const mappedConfigs = syncConfigs as SyncConfigWithRequiredMapping[];
|
|
57
|
+
this.hydrationState = new MongoHydrationState(mappedConfigs, this.replicationStreamId);
|
|
58
|
+
this.mapping = new MultiSyncConfigBucketDefinitionMapping(mappedConfigs);
|
|
59
|
+
} else if (!compatibility.isEnabled(CompatibilityOption.versionedBucketIds) && !storageConfig.versionedBuckets) {
|
|
60
|
+
const [syncConfig] = syncConfigs;
|
|
61
|
+
if (syncConfigs.length != 1 || syncConfig == null) {
|
|
62
|
+
throw new ServiceAssertionError(`Non-incremental storage requires exactly one sync config`);
|
|
63
|
+
}
|
|
64
|
+
this.hydrationState = DEFAULT_HYDRATION_STATE;
|
|
65
|
+
this.mapping = syncConfig.mapping ?? new SingleSyncConfigBucketDefinitionMapping();
|
|
66
|
+
} else {
|
|
67
|
+
const [syncConfig] = syncConfigs;
|
|
68
|
+
if (syncConfigs.length != 1 || syncConfig == null) {
|
|
69
|
+
throw new ServiceAssertionError(`Non-incremental storage requires exactly one sync config`);
|
|
70
|
+
}
|
|
71
|
+
this.hydrationState = versionedHydrationState(this.replicationStreamId);
|
|
72
|
+
this.mapping = syncConfig.mapping ?? new SingleSyncConfigBucketDefinitionMapping();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
#hydratedSyncConfig: HydratedSyncConfig | undefined;
|
|
77
|
+
|
|
78
|
+
get hydratedSyncConfig(): HydratedSyncConfig {
|
|
79
|
+
this.#hydratedSyncConfig ??= new HydratedSyncConfig({
|
|
80
|
+
definitions: this.syncConfigs.map((config) => config.config),
|
|
81
|
+
createParams: {
|
|
82
|
+
hydrationState: this.hydrationState,
|
|
83
|
+
sqlite: nodeSqlite(sqlite)
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
return this.#hydratedSyncConfig;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { mongo } from '@powersync/lib-service-mongodb';
|
|
2
|
+
import { ServiceAssertionError } from '@powersync/lib-services-framework';
|
|
3
|
+
import { ReplicationStreamStorageIds, SingleSyncConfigBucketDefinitionMapping, storage } from '@powersync/service-core';
|
|
4
|
+
import * as bson from 'bson';
|
|
5
|
+
import { ReplicationStreamDocumentV3, SyncConfigDefinition } from '../storage-index.js';
|
|
6
|
+
import { PowerSyncMongo } from './db.js';
|
|
7
|
+
import { getMongoStorageConfig } from './models.js';
|
|
8
|
+
import { MongoParsedSyncConfigSet } from './MongoParsedSyncConfigSet.js';
|
|
9
|
+
import {
|
|
10
|
+
MongoPersistedSyncConfigContentBase,
|
|
11
|
+
MongoPersistedSyncConfigContentV1,
|
|
12
|
+
MongoPersistedSyncConfigContentV3
|
|
13
|
+
} from './MongoPersistedSyncConfigContent.js';
|
|
14
|
+
import { MongoSyncRulesLock } from './MongoSyncRulesLock.js';
|
|
15
|
+
import { SyncRuleDocumentV1 } from './v1/models.js';
|
|
16
|
+
|
|
17
|
+
export class MongoPersistedReplicationStream extends storage.PersistedReplicationStream {
|
|
18
|
+
public current_lock: MongoSyncRulesLock | null = null;
|
|
19
|
+
public readonly syncConfigContent: readonly MongoPersistedSyncConfigContentBase[];
|
|
20
|
+
|
|
21
|
+
constructor(
|
|
22
|
+
private readonly db: PowerSyncMongo,
|
|
23
|
+
private readonly doc: SyncRuleDocumentV1 | ReplicationStreamDocumentV3,
|
|
24
|
+
private readonly configs: SyncConfigDefinition[] = []
|
|
25
|
+
) {
|
|
26
|
+
const storageVersion = doc.storage_version ?? storage.LEGACY_STORAGE_VERSION;
|
|
27
|
+
const replicationJobId =
|
|
28
|
+
configs.length == 0
|
|
29
|
+
? String(doc._id)
|
|
30
|
+
: `${doc._id}:${configs
|
|
31
|
+
.map((config) => config._id.toHexString())
|
|
32
|
+
.sort()
|
|
33
|
+
.join(',')}`;
|
|
34
|
+
|
|
35
|
+
super({
|
|
36
|
+
replicationStreamId: doc._id,
|
|
37
|
+
replicationStreamName: doc.slot_name ?? `powersync_${doc._id}`,
|
|
38
|
+
state: doc.state,
|
|
39
|
+
storageVersion,
|
|
40
|
+
replicationJobId
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
this.syncConfigContent = this.createSyncConfigContent();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
getStorageConfig() {
|
|
47
|
+
return getMongoStorageConfig(this.storageVersion);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private createSyncConfigContent(): MongoPersistedSyncConfigContentBase[] {
|
|
51
|
+
if (this.getStorageConfig().incrementalReprocessing) {
|
|
52
|
+
if (this.configs.length == 0) {
|
|
53
|
+
throw new ServiceAssertionError(`Cannot create v3 storage without sync config definitions`);
|
|
54
|
+
}
|
|
55
|
+
return this.configs.map(
|
|
56
|
+
(config) => new MongoPersistedSyncConfigContentV3(this.db, this.doc as ReplicationStreamDocumentV3, config)
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return [new MongoPersistedSyncConfigContentV1(this.db, this.doc as SyncRuleDocumentV1)];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
get syncConfigIds(): bson.ObjectId[] {
|
|
64
|
+
return this.configs.map((config) => config._id);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
#storageIds: ReplicationStreamStorageIds | undefined;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Persisted storage ids for all sync configs in this replication stream.
|
|
71
|
+
*
|
|
72
|
+
* Derived from the persisted rule_mapping documents - requires no parsed sync configs.
|
|
73
|
+
*/
|
|
74
|
+
get storageIds(): ReplicationStreamStorageIds {
|
|
75
|
+
this.#storageIds ??= new ReplicationStreamStorageIds(this.syncConfigContent.map((content) => content.mapping));
|
|
76
|
+
return this.#storageIds;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Parse the sync configs for this replication stream.
|
|
81
|
+
*
|
|
82
|
+
* This creates a fresh parse on every call. Do not call this from operational paths -
|
|
83
|
+
* use {@link MongoSyncBucketStorage.getParsedSyncConfigSet} instead, which returns a
|
|
84
|
+
* canonical instance so that parsed source objects and mappings stay associated.
|
|
85
|
+
*/
|
|
86
|
+
parsed(options: storage.ParseSyncConfigOptions): MongoParsedSyncConfigSet {
|
|
87
|
+
const storageConfig = this.getStorageConfig();
|
|
88
|
+
if (!storageConfig.incrementalReprocessing) {
|
|
89
|
+
return this.syncConfigContent[0].parsed(options);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const syncConfigs = this.configs.map((config) => {
|
|
93
|
+
return {
|
|
94
|
+
syncConfigId: config._id.toHexString(),
|
|
95
|
+
syncConfig: storage.parsePersistedSyncConfigContent({
|
|
96
|
+
content: config.content,
|
|
97
|
+
compiledPlan: config.serialized_plan ?? null,
|
|
98
|
+
storageVersion: this.storageVersion,
|
|
99
|
+
parseOptions: options
|
|
100
|
+
}),
|
|
101
|
+
mapping: SingleSyncConfigBucketDefinitionMapping.fromPersistedMapping(config.rule_mapping)
|
|
102
|
+
};
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
return new MongoParsedSyncConfigSet(
|
|
106
|
+
this.replicationStreamId,
|
|
107
|
+
storageConfig,
|
|
108
|
+
this.replicationStreamName,
|
|
109
|
+
syncConfigs
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async lock(session?: mongo.ClientSession) {
|
|
114
|
+
const lock = await MongoSyncRulesLock.createLock(this.db.versioned(this.getStorageConfig()), this, session);
|
|
115
|
+
this.current_lock = lock;
|
|
116
|
+
return lock;
|
|
117
|
+
}
|
|
118
|
+
}
|