@powersync/service-module-mongodb-storage 0.17.0 → 0.18.1
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 +27 -0
- package/dist/storage/MongoBucketStorage.d.ts +34 -16
- package/dist/storage/MongoBucketStorage.js +219 -70
- package/dist/storage/MongoBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoBucketBatch.d.ts +42 -23
- package/dist/storage/implementation/MongoBucketBatch.js +61 -44
- package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
- package/dist/storage/implementation/MongoChecksums.d.ts +10 -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 +2 -0
- 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 +123 -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 +41 -23
- package/dist/storage/implementation/MongoSyncBucketStorage.js +51 -43
- package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoSyncRulesLock.d.ts +1 -1
- package/dist/storage/implementation/MongoSyncRulesLock.js +3 -3
- package/dist/storage/implementation/MongoSyncRulesLock.js.map +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 +1 -2
- package/dist/storage/implementation/common/PersistedBatch.js +5 -0
- package/dist/storage/implementation/common/PersistedBatch.js.map +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.js +9 -14
- package/dist/storage/implementation/db.js.map +1 -1
- package/dist/storage/implementation/models.d.ts +4 -13
- package/dist/storage/implementation/models.js.map +1 -1
- package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +9 -1
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js +42 -26
- 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/MongoSyncBucketStorageV1.d.ts +15 -17
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +47 -45
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.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 +10 -1
- package/dist/storage/implementation/v3/MongoBucketBatchV3.d.ts +8 -4
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js +286 -259
- 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 +19 -2
- 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 +40 -28
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +232 -116
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -1
- package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +1 -1
- 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 +13 -12
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js +29 -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 +64 -21
- 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/storage/MongoBucketStorage.ts +334 -87
- package/src/storage/implementation/MongoBucketBatch.ts +86 -58
- package/src/storage/implementation/MongoChecksums.ts +6 -138
- package/src/storage/implementation/MongoCompactor.ts +19 -9
- 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 +154 -0
- package/src/storage/implementation/MongoStorageProvider.ts +5 -2
- package/src/storage/implementation/MongoSyncBucketStorage.ts +70 -56
- package/src/storage/implementation/MongoSyncRulesLock.ts +7 -4
- package/src/storage/implementation/common/MongoSyncBucketStorageCheckpoint.ts +7 -0
- package/src/storage/implementation/common/PersistedBatch.ts +7 -3
- package/src/storage/implementation/common/VersionedPowerSyncMongoBase.ts +1 -1
- package/src/storage/implementation/createMongoSyncBucketStorage.ts +21 -7
- package/src/storage/implementation/db.ts +11 -19
- package/src/storage/implementation/models.ts +4 -14
- package/src/storage/implementation/v1/MongoBucketBatchV1.ts +55 -34
- package/src/storage/implementation/v1/MongoChecksumsV1.ts +137 -4
- package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +70 -64
- package/src/storage/implementation/v1/VersionedPowerSyncMongoV1.ts +11 -1
- package/src/storage/implementation/v1/models.ts +11 -1
- package/src/storage/implementation/v3/MongoBucketBatchV3.ts +358 -314
- package/src/storage/implementation/v3/MongoChecksumsV3.ts +262 -26
- package/src/storage/implementation/v3/MongoCompactorV3.ts +646 -29
- package/src/storage/implementation/v3/MongoStoppedSyncConfigCleanup.ts +480 -0
- package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +315 -157
- package/src/storage/implementation/v3/PersistedBatchV3.ts +62 -36
- 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 +41 -36
- 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 +93 -55
- 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 +1077 -68
- package/test/src/storeCurrentData.test.ts +17 -7
- 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 -15
- 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 -67
- package/dist/storage/implementation/MongoPersistedSyncRules.js.map +0 -1
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.d.ts +0 -29
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.js +0 -76
- 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 -4
- 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 -75
- package/src/storage/implementation/MongoPersistedSyncRules.ts +0 -82
- package/src/storage/implementation/MongoPersistedSyncRulesContent.ts +0 -101
- 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 -11
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
ServiceError
|
|
13
13
|
} from '@powersync/lib-services-framework';
|
|
14
14
|
import {
|
|
15
|
+
BucketDefinitionMapping,
|
|
15
16
|
BucketStorageMarkRecordUnavailable,
|
|
16
17
|
deserializeBson,
|
|
17
18
|
InternalOpId,
|
|
@@ -23,12 +24,12 @@ import {
|
|
|
23
24
|
} from '@powersync/service-core';
|
|
24
25
|
import * as timers from 'node:timers/promises';
|
|
25
26
|
import { mongoTableId } from '../../utils/util.js';
|
|
26
|
-
import { BucketDefinitionMapping } from './BucketDefinitionMapping.js';
|
|
27
27
|
import { PersistedBatch } from './common/PersistedBatch.js';
|
|
28
28
|
import { LoadedSourceRecord, SourceRecordStore } from './common/SourceRecordStore.js';
|
|
29
29
|
import type { VersionedPowerSyncMongo } from './db.js';
|
|
30
30
|
import { MAX_ROW_SIZE } from './MongoBucketBatchShared.js';
|
|
31
31
|
import { MongoIdSequence } from './MongoIdSequence.js';
|
|
32
|
+
import { MongoParsedSyncConfigSet } from './MongoParsedSyncConfigSet.js';
|
|
32
33
|
import { batchCreateCustomWriteCheckpoints } from './MongoWriteCheckpointAPI.js';
|
|
33
34
|
import { OperationBatch, RecordOperation } from './OperationBatch.js';
|
|
34
35
|
|
|
@@ -41,15 +42,25 @@ const replicationMutex = new utils.Mutex();
|
|
|
41
42
|
|
|
42
43
|
export interface MongoBucketBatchOptions {
|
|
43
44
|
db: VersionedPowerSyncMongo;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
/**
|
|
46
|
+
* The parsed sync config set for this batch.
|
|
47
|
+
*
|
|
48
|
+
* The batch derives both the hydrated sync rules and the bucket definition mapping from
|
|
49
|
+
* this single set, so they always come from the same parse. Do not add separate
|
|
50
|
+
* syncRules/mapping options - pairing values from different parses is exactly the bug
|
|
51
|
+
* this shape prevents.
|
|
52
|
+
*/
|
|
53
|
+
parsedSyncConfig: MongoParsedSyncConfigSet;
|
|
54
|
+
replicationStreamId: number;
|
|
55
|
+
replicationStreamName: string;
|
|
56
|
+
syncConfigIds?: bson.ObjectId[];
|
|
57
|
+
/**
|
|
58
|
+
* Seeds the in-memory persisted-op tracking for v1 storage. Not used by v3 storage, which
|
|
59
|
+
* tracks the persisted-op head durably on the replication stream document instead.
|
|
60
|
+
*/
|
|
61
|
+
keepaliveOp?: InternalOpId | null;
|
|
50
62
|
resumeFromLsn: string | null;
|
|
51
63
|
storeCurrentData: boolean;
|
|
52
|
-
mapping: BucketDefinitionMapping;
|
|
53
64
|
/**
|
|
54
65
|
* Set to true for initial replication.
|
|
55
66
|
*/
|
|
@@ -70,13 +81,13 @@ export abstract class MongoBucketBatch
|
|
|
70
81
|
protected logger: Logger;
|
|
71
82
|
|
|
72
83
|
private readonly client: mongo.MongoClient;
|
|
73
|
-
|
|
84
|
+
readonly db: VersionedPowerSyncMongo;
|
|
74
85
|
public readonly session: mongo.ClientSession;
|
|
75
86
|
protected readonly sync_rules: HydratedSyncConfig;
|
|
76
87
|
|
|
77
|
-
protected readonly
|
|
88
|
+
protected readonly replicationStreamId: number;
|
|
78
89
|
|
|
79
|
-
private readonly
|
|
90
|
+
private readonly replicationStreamName: string;
|
|
80
91
|
/**
|
|
81
92
|
* Source-level setting for whether raw row data should be stored in current_data.
|
|
82
93
|
*
|
|
@@ -98,26 +109,15 @@ export abstract class MongoBucketBatch
|
|
|
98
109
|
|
|
99
110
|
private tracer: PerformanceTracer<'storage' | 'evaluate'>;
|
|
100
111
|
|
|
101
|
-
/**
|
|
102
|
-
* Last LSN received associated with a checkpoint.
|
|
103
|
-
*
|
|
104
|
-
* This could be either:
|
|
105
|
-
* 1. A commit LSN.
|
|
106
|
-
* 2. A keepalive message LSN.
|
|
107
|
-
*/
|
|
108
|
-
protected last_checkpoint_lsn: string | null = null;
|
|
109
|
-
|
|
110
|
-
protected persisted_op: InternalOpId | null = null;
|
|
111
|
-
|
|
112
112
|
/**
|
|
113
113
|
* Last written op, if any. This may not reflect a consistent checkpoint.
|
|
114
114
|
*/
|
|
115
115
|
public last_flushed_op: InternalOpId | null = null;
|
|
116
116
|
|
|
117
117
|
/**
|
|
118
|
-
*
|
|
118
|
+
* LSN to resume replication from.
|
|
119
119
|
*
|
|
120
|
-
*
|
|
120
|
+
* This is typically the last commit LSN, but there are cases where it differs:
|
|
121
121
|
* 1. After an initial snapshot, we don't have a consistent commit yet, but need to resume from the snapshot LSN.
|
|
122
122
|
* 2. If "no_checkpoint_before_lsn" is set far in advance, it may take a while to reach that point. We
|
|
123
123
|
* may want to resume at incremental points before that.
|
|
@@ -132,34 +132,28 @@ export abstract class MongoBucketBatch
|
|
|
132
132
|
this.options = options;
|
|
133
133
|
this.client = options.db.client;
|
|
134
134
|
this.db = options.db;
|
|
135
|
-
this.
|
|
136
|
-
this.last_checkpoint_lsn = options.lastCheckpointLsn;
|
|
135
|
+
this.replicationStreamId = options.replicationStreamId;
|
|
137
136
|
this.resumeFromLsn = options.resumeFromLsn;
|
|
138
137
|
this.session = this.client.startSession();
|
|
139
|
-
this.
|
|
140
|
-
this.sync_rules = options.
|
|
138
|
+
this.replicationStreamName = options.replicationStreamName;
|
|
139
|
+
this.sync_rules = options.parsedSyncConfig.hydratedSyncConfig;
|
|
141
140
|
this.storeCurrentData = options.storeCurrentData;
|
|
142
|
-
this.mapping = options.mapping;
|
|
141
|
+
this.mapping = options.parsedSyncConfig.mapping;
|
|
143
142
|
this.skipExistingRows = options.skipExistingRows;
|
|
144
143
|
this.markRecordUnavailable = options.markRecordUnavailable;
|
|
145
144
|
this.hooks = options.hooks;
|
|
146
145
|
this.batch = new OperationBatch();
|
|
147
146
|
|
|
148
|
-
this.persisted_op = options.keepaliveOp ?? null;
|
|
149
147
|
this.tracer = options.tracer ?? new PerformanceTracer('MongoDB storage');
|
|
150
148
|
}
|
|
151
149
|
|
|
152
150
|
addCustomWriteCheckpoint(checkpoint: storage.BatchedCustomWriteCheckpointOptions): void {
|
|
153
151
|
this.write_checkpoint_batch.push({
|
|
154
152
|
...checkpoint,
|
|
155
|
-
sync_rules_id: this.
|
|
153
|
+
sync_rules_id: this.replicationStreamId
|
|
156
154
|
});
|
|
157
155
|
}
|
|
158
156
|
|
|
159
|
-
get lastCheckpointLsn() {
|
|
160
|
-
return this.last_checkpoint_lsn;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
157
|
abstract resolveTables(options: storage.ResolveTablesOptions): Promise<storage.ResolveTablesResult>;
|
|
164
158
|
|
|
165
159
|
protected abstract createPersistedBatch(writtenSize: number): PersistedBatch;
|
|
@@ -230,7 +224,7 @@ export abstract class MongoBucketBatch
|
|
|
230
224
|
throw new ReplicationAssertionError('Unexpected last_op == null');
|
|
231
225
|
}
|
|
232
226
|
|
|
233
|
-
this.
|
|
227
|
+
this.recordPersistedOp(last_op);
|
|
234
228
|
this.last_flushed_op = last_op;
|
|
235
229
|
await this.hooks?.afterBatchFlush?.(this);
|
|
236
230
|
return { flushed_op: last_op };
|
|
@@ -462,7 +456,7 @@ export abstract class MongoBucketBatch
|
|
|
462
456
|
{
|
|
463
457
|
level: errors.ErrorSeverity.WARNING,
|
|
464
458
|
metadata: {
|
|
465
|
-
replication_slot: this.
|
|
459
|
+
replication_slot: this.replicationStreamName,
|
|
466
460
|
table: record.sourceTable.qualifiedName
|
|
467
461
|
}
|
|
468
462
|
}
|
|
@@ -521,7 +515,7 @@ export abstract class MongoBucketBatch
|
|
|
521
515
|
{
|
|
522
516
|
level: errors.ErrorSeverity.WARNING,
|
|
523
517
|
metadata: {
|
|
524
|
-
replication_slot: this.
|
|
518
|
+
replication_slot: this.replicationStreamName,
|
|
525
519
|
table: record.sourceTable.qualifiedName
|
|
526
520
|
}
|
|
527
521
|
}
|
|
@@ -556,7 +550,7 @@ export abstract class MongoBucketBatch
|
|
|
556
550
|
{
|
|
557
551
|
level: errors.ErrorSeverity.WARNING,
|
|
558
552
|
metadata: {
|
|
559
|
-
replication_slot: this.
|
|
553
|
+
replication_slot: this.replicationStreamName,
|
|
560
554
|
table: record.sourceTable.qualifiedName
|
|
561
555
|
}
|
|
562
556
|
}
|
|
@@ -691,7 +685,7 @@ export abstract class MongoBucketBatch
|
|
|
691
685
|
|
|
692
686
|
await this.db.sync_rules.updateOne(
|
|
693
687
|
{
|
|
694
|
-
_id: this.
|
|
688
|
+
_id: this.replicationStreamId
|
|
695
689
|
},
|
|
696
690
|
{
|
|
697
691
|
$set: {
|
|
@@ -700,12 +694,41 @@ export abstract class MongoBucketBatch
|
|
|
700
694
|
},
|
|
701
695
|
{ session }
|
|
702
696
|
);
|
|
697
|
+
|
|
698
|
+
// Allow subclasses to persist additional flush-time state in the same transaction
|
|
699
|
+
// (e.g. v3 advances the stream-level last_persisted_op).
|
|
700
|
+
await this.onReplicationTransactionFlush(session, opSeq.last());
|
|
703
701
|
// We don't notify checkpoint here - we don't make any checkpoint updates directly
|
|
704
702
|
});
|
|
705
703
|
}
|
|
706
704
|
|
|
705
|
+
/**
|
|
706
|
+
* Hook called inside the replication flush transaction, after ops have been persisted.
|
|
707
|
+
*
|
|
708
|
+
* The base implementation does nothing; v3 storage overrides this to `$max` the stream-level
|
|
709
|
+
* `last_persisted_op` durably within the same transaction.
|
|
710
|
+
*/
|
|
711
|
+
protected async onReplicationTransactionFlush(_session: mongo.ClientSession, _lastOp: InternalOpId): Promise<void> {
|
|
712
|
+
// No-op by default (v1 behaviour unchanged).
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Called after a replication transaction has successfully committed, with the last persisted op id.
|
|
717
|
+
*
|
|
718
|
+
* v1 storage tracks this in memory to fold into the next checkpoint. v3 storage does not need it:
|
|
719
|
+
* the stream-level `last_persisted_op` is already advanced durably by
|
|
720
|
+
* {@link onReplicationTransactionFlush} within the same transaction, and checkpoints read it
|
|
721
|
+
* from the document.
|
|
722
|
+
*
|
|
723
|
+
* Keep calls to this adjacent to {@link withReplicationTransaction} usage - both must observe
|
|
724
|
+
* every path that persists ops.
|
|
725
|
+
*/
|
|
726
|
+
protected recordPersistedOp(_lastOp: InternalOpId): void {
|
|
727
|
+
// No-op by default.
|
|
728
|
+
}
|
|
729
|
+
|
|
707
730
|
async [Symbol.asyncDispose]() {
|
|
708
|
-
if (this.batch
|
|
731
|
+
if (this.batch?.hasData() || this.write_checkpoint_batch.length > 0) {
|
|
709
732
|
// We don't error here, since:
|
|
710
733
|
// 1. In error states, this is expected (we can't distinguish between disposing after success or error).
|
|
711
734
|
// 2. SuppressedError is messy to deal with.
|
|
@@ -722,19 +745,24 @@ export abstract class MongoBucketBatch
|
|
|
722
745
|
async save(record: storage.SaveOptions): Promise<storage.FlushedResult | null> {
|
|
723
746
|
const { after, before, sourceTable, tag } = record;
|
|
724
747
|
const storeCurrentData = this.storeCurrentData && sourceTable.storeCurrentData;
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
748
|
+
// syncEvent is the per-table designation from resolveTables. With v3 storage, multiple
|
|
749
|
+
// SourceTables can exist for the same ref, with a row change saved once per table -
|
|
750
|
+
// only the designated event carrier may fire events, so each event fires once per row.
|
|
751
|
+
if (sourceTable.syncEvent) {
|
|
752
|
+
for (const event of this.getTableEvents(sourceTable)) {
|
|
753
|
+
this.iterateListeners((cb) =>
|
|
754
|
+
cb.replicationEvent?.({
|
|
755
|
+
batch: this,
|
|
756
|
+
table: sourceTable,
|
|
757
|
+
data: {
|
|
758
|
+
op: tag,
|
|
759
|
+
after: after && utils.isCompleteRow(storeCurrentData, after) ? after : undefined,
|
|
760
|
+
before: before && utils.isCompleteRow(storeCurrentData, before) ? before : undefined
|
|
761
|
+
},
|
|
762
|
+
event
|
|
763
|
+
})
|
|
764
|
+
);
|
|
765
|
+
}
|
|
738
766
|
}
|
|
739
767
|
|
|
740
768
|
/**
|
|
@@ -767,7 +795,7 @@ export abstract class MongoBucketBatch
|
|
|
767
795
|
|
|
768
796
|
await this.withTransaction(async () => {
|
|
769
797
|
for (let table of sourceTables) {
|
|
770
|
-
await this.db.commonSourceTables(this.
|
|
798
|
+
await this.db.commonSourceTables(this.replicationStreamId).deleteOne({ _id: mongoTableId(table.id) });
|
|
771
799
|
}
|
|
772
800
|
});
|
|
773
801
|
|
|
@@ -784,7 +812,7 @@ export abstract class MongoBucketBatch
|
|
|
784
812
|
}
|
|
785
813
|
|
|
786
814
|
if (last_op) {
|
|
787
|
-
this.
|
|
815
|
+
this.recordPersistedOp(last_op);
|
|
788
816
|
return {
|
|
789
817
|
flushed_op: last_op
|
|
790
818
|
};
|
|
@@ -853,7 +881,7 @@ export abstract class MongoBucketBatch
|
|
|
853
881
|
copy.snapshotStatus = snapshotStatus;
|
|
854
882
|
|
|
855
883
|
await this.withTransaction(async () => {
|
|
856
|
-
await this.db.commonSourceTables(this.
|
|
884
|
+
await this.db.commonSourceTables(this.replicationStreamId).updateOne(
|
|
857
885
|
{ _id: mongoTableId(table.id) },
|
|
858
886
|
{
|
|
859
887
|
$set: {
|
|
@@ -879,7 +907,7 @@ export abstract class MongoBucketBatch
|
|
|
879
907
|
|
|
880
908
|
await this.db.sync_rules.updateOne(
|
|
881
909
|
{
|
|
882
|
-
_id: this.
|
|
910
|
+
_id: this.replicationStreamId
|
|
883
911
|
},
|
|
884
912
|
{
|
|
885
913
|
$set: {
|
|
@@ -7,19 +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 * as lib_mongo from '@powersync/lib-service-mongodb';
|
|
18
16
|
import { BucketDefinitionId } from '@powersync/service-sync-rules';
|
|
19
|
-
import {
|
|
20
|
-
import { BucketDataDocumentBase, StorageConfig } from './models.js';
|
|
17
|
+
import { StorageConfig } from './models.js';
|
|
21
18
|
|
|
22
|
-
export interface
|
|
19
|
+
export interface FetchPartialBucketChecksumByDefinition {
|
|
23
20
|
bucket: string;
|
|
24
21
|
definitionId: BucketDefinitionId;
|
|
25
22
|
start?: InternalOpId;
|
|
@@ -47,15 +44,14 @@ export interface MongoChecksumOptions {
|
|
|
47
44
|
operationBatchLimit?: number;
|
|
48
45
|
|
|
49
46
|
storageConfig: StorageConfig;
|
|
50
|
-
mapping?: BucketDefinitionMapping;
|
|
51
47
|
}
|
|
52
48
|
|
|
53
49
|
const DEFAULT_BUCKET_BATCH_LIMIT = 200;
|
|
54
|
-
const DEFAULT_OPERATION_BATCH_LIMIT = 50_000;
|
|
50
|
+
export const DEFAULT_OPERATION_BATCH_LIMIT = 50_000;
|
|
55
51
|
|
|
56
52
|
export abstract class MongoChecksums {
|
|
57
53
|
private _cache: ChecksumCache | undefined;
|
|
58
|
-
|
|
54
|
+
protected readonly storageConfig: StorageConfig;
|
|
59
55
|
|
|
60
56
|
constructor(
|
|
61
57
|
protected readonly db: VersionedPowerSyncMongo,
|
|
@@ -178,138 +174,10 @@ export abstract class MongoChecksums {
|
|
|
178
174
|
protected abstract fetchPreStates(
|
|
179
175
|
batch: FetchPartialBucketChecksum[]
|
|
180
176
|
): Promise<Map<string, { opId: InternalOpId; checksum: BucketChecksum }>>;
|
|
181
|
-
|
|
182
|
-
protected async computePartialChecksumsForCollection<
|
|
183
|
-
TRequest extends FetchPartialBucketChecksumByBucket,
|
|
184
|
-
TBucketDataDocument extends BucketDataDocumentBase
|
|
185
|
-
>(
|
|
186
|
-
batch: TRequest[],
|
|
187
|
-
collection: lib_mongo.mongo.Collection<TBucketDataDocument>,
|
|
188
|
-
createFilter: (request: TRequest) => any
|
|
189
|
-
): Promise<PartialChecksumMap> {
|
|
190
|
-
const batchLimit = this.options?.operationBatchLimit ?? DEFAULT_OPERATION_BATCH_LIMIT;
|
|
191
|
-
|
|
192
|
-
// Map requests by bucket. We adjust this as we get partial results.
|
|
193
|
-
let requests = new Map<string, TRequest>();
|
|
194
|
-
for (let request of batch) {
|
|
195
|
-
requests.set(request.bucket, request);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
const partialChecksums = new Map<string, PartialOrFullChecksum>();
|
|
199
|
-
|
|
200
|
-
while (requests.size > 0) {
|
|
201
|
-
const filters = Array.from(requests.values(), createFilter);
|
|
202
|
-
|
|
203
|
-
// Historically, checksum may be stored as 'int' or 'double'.
|
|
204
|
-
// More recently, this should be a 'long'.
|
|
205
|
-
// $toLong ensures that we always sum it as a long, avoiding inaccuracies in the calculations.
|
|
206
|
-
const checksumLong = this.storageConfig.longChecksums ? '$checksum' : { $toLong: '$checksum' };
|
|
207
|
-
|
|
208
|
-
// Aggregate over a max of `batchLimit` operations at a time.
|
|
209
|
-
// Let's say we have 3 buckets (A, B, C), each with 10 operations, and our batch limit is 12.
|
|
210
|
-
// Then we'll do three batches:
|
|
211
|
-
// 1. Query: A[1-end], B[1-end], C[1-end]
|
|
212
|
-
// Returns: A[1-10], B[1-2]
|
|
213
|
-
// 2. Query: B[3-end], C[1-end]
|
|
214
|
-
// Returns: B[3-10], C[1-4]
|
|
215
|
-
// 3. Query: C[5-end]
|
|
216
|
-
// Returns: C[5-10]
|
|
217
|
-
const aggregate = await collection
|
|
218
|
-
.aggregate(
|
|
219
|
-
[
|
|
220
|
-
{
|
|
221
|
-
$match: {
|
|
222
|
-
$or: filters
|
|
223
|
-
}
|
|
224
|
-
},
|
|
225
|
-
// sort and limit _before_ grouping
|
|
226
|
-
{ $sort: { _id: 1 } },
|
|
227
|
-
{ $limit: batchLimit },
|
|
228
|
-
{
|
|
229
|
-
$group: {
|
|
230
|
-
_id: '$_id.b',
|
|
231
|
-
checksum_total: { $sum: checksumLong },
|
|
232
|
-
count: { $sum: 1 },
|
|
233
|
-
has_clear_op: {
|
|
234
|
-
$max: {
|
|
235
|
-
$cond: [{ $eq: ['$op', 'CLEAR'] }, 1, 0]
|
|
236
|
-
}
|
|
237
|
-
},
|
|
238
|
-
last_op: { $max: '$_id.o' }
|
|
239
|
-
}
|
|
240
|
-
},
|
|
241
|
-
// Sort the aggregated results (100 max, so should be fast).
|
|
242
|
-
// This is important to identify which buckets we have partial data for.
|
|
243
|
-
{ $sort: { _id: 1 } }
|
|
244
|
-
],
|
|
245
|
-
{ session: undefined, readConcern: 'snapshot', maxTimeMS: lib_mongo.MONGO_CHECKSUM_TIMEOUT_MS }
|
|
246
|
-
)
|
|
247
|
-
.toArray()
|
|
248
|
-
.catch((e) => {
|
|
249
|
-
throw lib_mongo.mapQueryError(e, 'while reading checksums');
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
let batchCount = 0;
|
|
253
|
-
let limitReached = false;
|
|
254
|
-
for (let doc of aggregate) {
|
|
255
|
-
const bucket = doc._id;
|
|
256
|
-
const checksum = checksumFromAggregate(doc);
|
|
257
|
-
|
|
258
|
-
const existing = partialChecksums.get(bucket);
|
|
259
|
-
if (existing != null) {
|
|
260
|
-
partialChecksums.set(bucket, addPartialChecksums(bucket, existing, checksum));
|
|
261
|
-
} else {
|
|
262
|
-
partialChecksums.set(bucket, checksum);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
batchCount += doc.count;
|
|
266
|
-
if (batchCount == batchLimit) {
|
|
267
|
-
// Limit reached. Request more in the next batch.
|
|
268
|
-
// Note that this only affects the _last_ bucket in a batch.
|
|
269
|
-
limitReached = true;
|
|
270
|
-
const req = requests.get(bucket);
|
|
271
|
-
requests.set(bucket, {
|
|
272
|
-
...req!,
|
|
273
|
-
start: doc.last_op
|
|
274
|
-
});
|
|
275
|
-
} else {
|
|
276
|
-
// All done for this bucket
|
|
277
|
-
requests.delete(bucket);
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
if (!limitReached) {
|
|
281
|
-
break;
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
return new Map<string, PartialOrFullChecksum>(
|
|
286
|
-
batch.map((request) => {
|
|
287
|
-
const bucket = request.bucket;
|
|
288
|
-
// Could be null if we got no data
|
|
289
|
-
let partialChecksum = partialChecksums.get(bucket);
|
|
290
|
-
if (partialChecksum == null) {
|
|
291
|
-
partialChecksum = {
|
|
292
|
-
bucket,
|
|
293
|
-
partialCount: 0,
|
|
294
|
-
partialChecksum: 0
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
if (request.start == null && isPartialChecksum(partialChecksum)) {
|
|
298
|
-
partialChecksum = {
|
|
299
|
-
bucket,
|
|
300
|
-
count: partialChecksum.partialCount,
|
|
301
|
-
checksum: partialChecksum.partialChecksum
|
|
302
|
-
};
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
return [bucket, partialChecksum];
|
|
306
|
-
})
|
|
307
|
-
);
|
|
308
|
-
}
|
|
309
177
|
}
|
|
310
178
|
|
|
311
179
|
export function emptyChecksumForRequest(
|
|
312
|
-
request: Pick<FetchPartialBucketChecksum |
|
|
180
|
+
request: Pick<FetchPartialBucketChecksum | FetchPartialBucketChecksumByDefinition, 'bucket' | 'start'>
|
|
313
181
|
): PartialOrFullChecksum {
|
|
314
182
|
if (request.start == null) {
|
|
315
183
|
return { bucket: request.bucket, count: 0, checksum: 0 };
|
|
@@ -320,7 +188,7 @@ export function emptyChecksumForRequest(
|
|
|
320
188
|
/**
|
|
321
189
|
* Convert output of the $group stage into a checksum.
|
|
322
190
|
*/
|
|
323
|
-
function checksumFromAggregate(doc: bson.Document): PartialOrFullChecksum {
|
|
191
|
+
export function checksumFromAggregate(doc: bson.Document): PartialOrFullChecksum {
|
|
324
192
|
const partialChecksum = Number(BigInt(doc.checksum_total) & 0xffffffffn) & 0xffffffff;
|
|
325
193
|
const bucket = doc._id;
|
|
326
194
|
|
|
@@ -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
|
+
}
|