@powersync/service-module-mongodb-storage 0.15.3 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +54 -0
- package/dist/migrations/db/migrations/1688556755264-initial-sync-rules.js +1 -1
- package/dist/migrations/db/migrations/1688556755264-initial-sync-rules.js.map +1 -1
- package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js +3 -3
- package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js.map +1 -1
- package/dist/migrations/db/migrations/1770213298299-storage-version.js.map +1 -1
- package/dist/storage/MongoBucketStorage.d.ts +5 -3
- package/dist/storage/MongoBucketStorage.js +50 -36
- package/dist/storage/MongoBucketStorage.js.map +1 -1
- package/dist/storage/MongoReportStorage.js.map +1 -1
- package/dist/storage/implementation/BucketDefinitionMapping.d.ts +17 -0
- package/dist/storage/implementation/BucketDefinitionMapping.js +58 -0
- package/dist/storage/implementation/BucketDefinitionMapping.js.map +1 -0
- package/dist/storage/implementation/MongoBucketBatch.d.ts +16 -14
- package/dist/storage/implementation/MongoBucketBatch.js +80 -115
- package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
- package/dist/storage/implementation/MongoBucketBatchShared.d.ts +5 -0
- package/dist/storage/implementation/MongoBucketBatchShared.js +8 -0
- package/dist/storage/implementation/MongoBucketBatchShared.js.map +1 -0
- package/dist/storage/implementation/MongoChecksums.d.ts +28 -17
- package/dist/storage/implementation/MongoChecksums.js +13 -72
- package/dist/storage/implementation/MongoChecksums.js.map +1 -1
- package/dist/storage/implementation/MongoCompactor.d.ts +98 -58
- package/dist/storage/implementation/MongoCompactor.js +229 -296
- package/dist/storage/implementation/MongoCompactor.js.map +1 -1
- package/dist/storage/implementation/MongoParameterCompactor.d.ts +11 -6
- package/dist/storage/implementation/MongoParameterCompactor.js +11 -8
- package/dist/storage/implementation/MongoParameterCompactor.js.map +1 -1
- package/dist/storage/implementation/MongoPersistedSyncRules.d.ts +14 -0
- package/dist/storage/implementation/MongoPersistedSyncRules.js +64 -0
- package/dist/storage/implementation/MongoPersistedSyncRules.js.map +1 -0
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.d.ts +3 -0
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.js +9 -0
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.js.map +1 -1
- package/dist/storage/implementation/MongoStorageProvider.js +1 -1
- package/dist/storage/implementation/MongoStorageProvider.js.map +1 -1
- package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +49 -30
- package/dist/storage/implementation/MongoSyncBucketStorage.js +96 -388
- package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoSyncRulesLock.d.ts +5 -3
- package/dist/storage/implementation/MongoSyncRulesLock.js +12 -10
- package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -1
- package/dist/storage/implementation/MongoWriteCheckpointAPI.js +1 -1
- package/dist/storage/implementation/MongoWriteCheckpointAPI.js.map +1 -1
- package/dist/storage/implementation/OperationBatch.js +1 -1
- package/dist/storage/implementation/common/BucketDataDoc.d.ts +35 -0
- package/dist/storage/implementation/common/BucketDataDoc.js +2 -0
- package/dist/storage/implementation/common/BucketDataDoc.js.map +1 -0
- package/dist/storage/implementation/common/MongoSyncBucketStorageContext.d.ts +13 -0
- package/dist/storage/implementation/common/MongoSyncBucketStorageContext.js +2 -0
- package/dist/storage/implementation/common/MongoSyncBucketStorageContext.js.map +1 -0
- package/dist/storage/implementation/common/PersistedBatch.d.ts +108 -0
- package/dist/storage/implementation/common/PersistedBatch.js +237 -0
- package/dist/storage/implementation/common/PersistedBatch.js.map +1 -0
- package/dist/storage/implementation/common/SingleBucketStore.d.ts +54 -0
- package/dist/storage/implementation/common/SingleBucketStore.js +3 -0
- package/dist/storage/implementation/common/SingleBucketStore.js.map +1 -0
- package/dist/storage/implementation/common/SourceRecordStore.d.ts +36 -0
- package/dist/storage/implementation/common/SourceRecordStore.js +2 -0
- package/dist/storage/implementation/common/SourceRecordStore.js.map +1 -0
- package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.d.ts +27 -0
- package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js +57 -0
- package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js.map +1 -0
- package/dist/storage/implementation/createMongoSyncBucketStorage.d.ts +7 -0
- package/dist/storage/implementation/createMongoSyncBucketStorage.js +9 -0
- package/dist/storage/implementation/createMongoSyncBucketStorage.js.map +1 -0
- package/dist/storage/implementation/db.d.ts +34 -34
- package/dist/storage/implementation/db.js +78 -98
- package/dist/storage/implementation/db.js.map +1 -1
- package/dist/storage/implementation/models.d.ts +63 -34
- package/dist/storage/implementation/models.js +21 -2
- package/dist/storage/implementation/models.js.map +1 -1
- package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +13 -0
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js +22 -0
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -0
- package/dist/storage/implementation/v1/MongoChecksumsV1.d.ts +12 -0
- package/dist/storage/implementation/v1/MongoChecksumsV1.js +56 -0
- package/dist/storage/implementation/v1/MongoChecksumsV1.js.map +1 -0
- package/dist/storage/implementation/v1/MongoCompactorV1.d.ts +23 -0
- package/dist/storage/implementation/v1/MongoCompactorV1.js +52 -0
- package/dist/storage/implementation/v1/MongoCompactorV1.js.map +1 -0
- package/dist/storage/implementation/v1/MongoParameterCompactorV1.d.ts +9 -0
- package/dist/storage/implementation/v1/MongoParameterCompactorV1.js +20 -0
- package/dist/storage/implementation/v1/MongoParameterCompactorV1.js.map +1 -0
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.d.ts +41 -0
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +283 -0
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js.map +1 -0
- package/dist/storage/implementation/v1/PersistedBatchV1.d.ts +26 -0
- package/dist/storage/implementation/v1/PersistedBatchV1.js +183 -0
- package/dist/storage/implementation/v1/PersistedBatchV1.js.map +1 -0
- package/dist/storage/implementation/v1/SingleBucketStoreV1.d.ts +18 -0
- package/dist/storage/implementation/v1/SingleBucketStoreV1.js +57 -0
- package/dist/storage/implementation/v1/SingleBucketStoreV1.js.map +1 -0
- package/dist/storage/implementation/v1/SourceRecordStoreV1.d.ts +19 -0
- package/dist/storage/implementation/v1/SourceRecordStoreV1.js +105 -0
- package/dist/storage/implementation/v1/SourceRecordStoreV1.js.map +1 -0
- package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.d.ts +12 -0
- package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.js +20 -0
- package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.js.map +1 -0
- package/dist/storage/implementation/v1/models.d.ts +34 -0
- package/dist/storage/implementation/v1/models.js +37 -0
- package/dist/storage/implementation/v1/models.js.map +1 -0
- package/dist/storage/implementation/v3/MongoBucketBatchV3.d.ts +13 -0
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js +34 -0
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -0
- package/dist/storage/implementation/v3/MongoChecksumsV3.d.ts +15 -0
- package/dist/storage/implementation/v3/MongoChecksumsV3.js +84 -0
- package/dist/storage/implementation/v3/MongoChecksumsV3.js.map +1 -0
- package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +23 -0
- package/dist/storage/implementation/v3/MongoCompactorV3.js +68 -0
- package/dist/storage/implementation/v3/MongoCompactorV3.js.map +1 -0
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.d.ts +9 -0
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.js +18 -0
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.js.map +1 -0
- package/dist/storage/implementation/v3/MongoParameterLookupV3.d.ts +5 -0
- package/dist/storage/implementation/v3/MongoParameterLookupV3.js +9 -0
- package/dist/storage/implementation/v3/MongoParameterLookupV3.js.map +1 -0
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.d.ts +41 -0
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +407 -0
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -0
- package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +29 -0
- package/dist/storage/implementation/v3/PersistedBatchV3.js +259 -0
- package/dist/storage/implementation/v3/PersistedBatchV3.js.map +1 -0
- package/dist/storage/implementation/v3/SingleBucketStoreV3.d.ts +18 -0
- package/dist/storage/implementation/v3/SingleBucketStoreV3.js +48 -0
- package/dist/storage/implementation/v3/SingleBucketStoreV3.js.map +1 -0
- package/dist/storage/implementation/v3/SourceRecordStoreV3.d.ts +22 -0
- package/dist/storage/implementation/v3/SourceRecordStoreV3.js +164 -0
- package/dist/storage/implementation/v3/SourceRecordStoreV3.js.map +1 -0
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.d.ts +21 -0
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js +71 -0
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js.map +1 -0
- package/dist/storage/implementation/v3/models.d.ts +43 -0
- package/dist/storage/implementation/v3/models.js +34 -0
- package/dist/storage/implementation/v3/models.js.map +1 -0
- package/dist/storage/storage-index.d.ts +8 -5
- package/dist/storage/storage-index.js +8 -5
- package/dist/storage/storage-index.js.map +1 -1
- package/dist/utils/util.d.ts +11 -4
- package/dist/utils/util.js +25 -4
- package/dist/utils/util.js.map +1 -1
- package/package.json +9 -9
- package/src/migrations/db/migrations/1688556755264-initial-sync-rules.ts +1 -1
- package/src/migrations/db/migrations/1702295701188-sync-rule-state.ts +7 -7
- package/src/migrations/db/migrations/1770213298299-storage-version.ts +1 -1
- package/src/storage/MongoBucketStorage.ts +97 -62
- package/src/storage/MongoReportStorage.ts +2 -2
- package/src/storage/implementation/BucketDefinitionMapping.ts +72 -0
- package/src/storage/implementation/MongoBucketBatch.ts +110 -144
- package/src/storage/implementation/MongoBucketBatchShared.ts +11 -0
- package/src/storage/implementation/MongoChecksums.ts +53 -76
- package/src/storage/implementation/MongoCompactor.ts +374 -404
- package/src/storage/implementation/MongoParameterCompactor.ts +37 -24
- package/src/storage/implementation/MongoPersistedSyncRules.ts +76 -0
- package/src/storage/implementation/MongoPersistedSyncRulesContent.ts +18 -1
- package/src/storage/implementation/MongoStorageProvider.ts +1 -1
- package/src/storage/implementation/MongoSyncBucketStorage.ts +190 -457
- package/src/storage/implementation/MongoSyncRulesLock.ts +12 -14
- package/src/storage/implementation/MongoWriteCheckpointAPI.ts +4 -2
- package/src/storage/implementation/OperationBatch.ts +1 -1
- package/src/storage/implementation/common/BucketDataDoc.ts +37 -0
- package/src/storage/implementation/common/MongoSyncBucketStorageContext.ts +15 -0
- package/src/storage/implementation/common/PersistedBatch.ts +364 -0
- package/src/storage/implementation/common/SingleBucketStore.ts +63 -0
- package/src/storage/implementation/common/SourceRecordStore.ts +49 -0
- package/src/storage/implementation/common/VersionedPowerSyncMongoBase.ts +80 -0
- package/src/storage/implementation/createMongoSyncBucketStorage.ts +25 -0
- package/src/storage/implementation/db.ts +107 -128
- package/src/storage/implementation/models.ts +84 -38
- package/src/storage/implementation/v1/MongoBucketBatchV1.ts +32 -0
- package/src/storage/implementation/v1/MongoChecksumsV1.ts +75 -0
- package/src/storage/implementation/v1/MongoCompactorV1.ts +93 -0
- package/src/storage/implementation/v1/MongoParameterCompactorV1.ts +26 -0
- package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +448 -0
- package/src/storage/implementation/v1/PersistedBatchV1.ts +230 -0
- package/src/storage/implementation/v1/SingleBucketStoreV1.ts +74 -0
- package/src/storage/implementation/v1/SourceRecordStoreV1.ts +156 -0
- package/src/storage/implementation/v1/VersionedPowerSyncMongoV1.ts +28 -0
- package/src/storage/implementation/v1/models.ts +84 -0
- package/src/storage/implementation/v3/MongoBucketBatchV3.ts +44 -0
- package/src/storage/implementation/v3/MongoChecksumsV3.ts +120 -0
- package/src/storage/implementation/v3/MongoCompactorV3.ts +107 -0
- package/src/storage/implementation/v3/MongoParameterCompactorV3.ts +24 -0
- package/src/storage/implementation/v3/MongoParameterLookupV3.ts +12 -0
- package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +550 -0
- package/src/storage/implementation/v3/PersistedBatchV3.ts +318 -0
- package/src/storage/implementation/v3/SingleBucketStoreV3.ts +68 -0
- package/src/storage/implementation/v3/SourceRecordStoreV3.ts +226 -0
- package/src/storage/implementation/v3/VersionedPowerSyncMongoV3.ts +112 -0
- package/src/storage/implementation/v3/models.ts +96 -0
- package/src/storage/storage-index.ts +8 -5
- package/src/utils/util.ts +36 -7
- package/test/src/__snapshots__/storage_sync.test.ts.snap +282 -0
- package/test/src/connection-report-storage.test.ts +3 -3
- package/test/src/setup.ts +1 -1
- package/test/src/storage.test.ts +2 -2
- package/test/src/storage_compacting.test.ts +57 -29
- package/test/src/storage_sync.test.ts +351 -5
- package/test/tsconfig.json +0 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/storage/implementation/PersistedBatch.d.ts +0 -71
- package/dist/storage/implementation/PersistedBatch.js +0 -354
- package/dist/storage/implementation/PersistedBatch.js.map +0 -1
- package/src/storage/implementation/PersistedBatch.ts +0 -432
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { mongo } from '@powersync/lib-service-mongodb';
|
|
2
|
+
import { ReplicationAssertionError } from '@powersync/lib-services-framework';
|
|
3
|
+
import { InternalOpId, storage } from '@powersync/service-core';
|
|
4
|
+
import { BucketDataSource } from '@powersync/service-sync-rules';
|
|
5
|
+
import * as bson from 'bson';
|
|
6
|
+
import { mongoTableId } from '../../../utils/util.js';
|
|
7
|
+
import { BucketDefinitionId } from '../BucketDefinitionMapping.js';
|
|
8
|
+
import {
|
|
9
|
+
BucketStateUpdate,
|
|
10
|
+
PersistedBatch,
|
|
11
|
+
SaveParameterDataOptions,
|
|
12
|
+
UpsertCurrentDataOptions
|
|
13
|
+
} from '../common/PersistedBatch.js';
|
|
14
|
+
import { SourceTableKey } from '../models.js';
|
|
15
|
+
import {
|
|
16
|
+
BucketParameterDocumentV3,
|
|
17
|
+
BucketStateDocumentV3,
|
|
18
|
+
CurrentDataDocumentV3,
|
|
19
|
+
serializeBucketDataV3,
|
|
20
|
+
SourceTableDocumentV3,
|
|
21
|
+
taggedBucketParameterDocumentToV3
|
|
22
|
+
} from './models.js';
|
|
23
|
+
import { serializeParameterLookupV3 } from './MongoParameterLookupV3.js';
|
|
24
|
+
import { VersionedPowerSyncMongoV3 } from './VersionedPowerSyncMongoV3.js';
|
|
25
|
+
|
|
26
|
+
export class PersistedBatchV3 extends PersistedBatch {
|
|
27
|
+
declare protected readonly db: VersionedPowerSyncMongoV3;
|
|
28
|
+
|
|
29
|
+
currentData: { sourceTableId: bson.ObjectId; operation: mongo.AnyBulkWriteOperation<CurrentDataDocumentV3> }[] = [];
|
|
30
|
+
sourceTablePendingDeletes = new Map<string, InternalOpId>();
|
|
31
|
+
|
|
32
|
+
protected checkDefinitionId(definitionId: BucketDefinitionId | null): BucketDefinitionId {
|
|
33
|
+
if (definitionId == null) {
|
|
34
|
+
// This is required for V3 storage.
|
|
35
|
+
throw new ReplicationAssertionError('Expected v3 bucket when incrementalReprocessing is enabled');
|
|
36
|
+
}
|
|
37
|
+
return definitionId;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
protected getBucketDefinitionId(bucketSource: BucketDataSource): BucketDefinitionId {
|
|
41
|
+
return this.mapping.bucketSourceId(bucketSource);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
saveParameterData(data: SaveParameterDataOptions) {
|
|
45
|
+
const { sourceTable, sourceKey, evaluated } = data;
|
|
46
|
+
const remaining_lookups = new Map<string, SaveParameterDataOptions['existing_lookups'][number]>();
|
|
47
|
+
|
|
48
|
+
for (let lookup of data.existing_lookups) {
|
|
49
|
+
if (lookup.indexId == null) {
|
|
50
|
+
throw new ReplicationAssertionError('Expected v3 lookup when incrementalReprocessing is enabled');
|
|
51
|
+
}
|
|
52
|
+
remaining_lookups.set(`${lookup.indexId}.${lookup.lookup.toString('base64')}`, lookup);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
for (let result of evaluated) {
|
|
56
|
+
const sourceDefinitionId = this.mapping.parameterLookupId(result.lookup.source);
|
|
57
|
+
const binLookup = serializeParameterLookupV3(result.lookup);
|
|
58
|
+
remaining_lookups.delete(`${sourceDefinitionId}.${binLookup.toString('base64')}`);
|
|
59
|
+
|
|
60
|
+
const op_id = data.op_seq.next();
|
|
61
|
+
this.debugLastOpId = op_id;
|
|
62
|
+
const values: BucketParameterDocumentV3 = {
|
|
63
|
+
_id: op_id,
|
|
64
|
+
key: {
|
|
65
|
+
t: mongoTableId(sourceTable.id),
|
|
66
|
+
k: sourceKey
|
|
67
|
+
} satisfies SourceTableKey,
|
|
68
|
+
lookup: binLookup,
|
|
69
|
+
bucket_parameters: result.bucketParameters
|
|
70
|
+
};
|
|
71
|
+
this.bucketParameters.push({
|
|
72
|
+
...values,
|
|
73
|
+
index: sourceDefinitionId
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
this.currentSize += 200;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
for (let lookup of remaining_lookups.values()) {
|
|
80
|
+
const op_id = data.op_seq.next();
|
|
81
|
+
this.debugLastOpId = op_id;
|
|
82
|
+
const indexId = lookup.indexId;
|
|
83
|
+
if (indexId == null) {
|
|
84
|
+
throw new ReplicationAssertionError('Expected v3 lookup when incrementalReprocessing is enabled');
|
|
85
|
+
}
|
|
86
|
+
const values: BucketParameterDocumentV3 = {
|
|
87
|
+
_id: op_id,
|
|
88
|
+
key: {
|
|
89
|
+
t: mongoTableId(sourceTable.id),
|
|
90
|
+
k: sourceKey
|
|
91
|
+
} satisfies SourceTableKey,
|
|
92
|
+
lookup: lookup.lookup,
|
|
93
|
+
bucket_parameters: []
|
|
94
|
+
};
|
|
95
|
+
this.bucketParameters.push({
|
|
96
|
+
...values,
|
|
97
|
+
index: indexId
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
this.currentSize += 200;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
hardDeleteCurrentData(sourceTableId: bson.ObjectId, replicaId: storage.ReplicaId) {
|
|
105
|
+
this.currentData.push({
|
|
106
|
+
sourceTableId,
|
|
107
|
+
operation: {
|
|
108
|
+
deleteOne: {
|
|
109
|
+
filter: { _id: replicaId }
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
this.currentSize += 50;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
softDeleteCurrentData(
|
|
117
|
+
sourceTableId: bson.ObjectId,
|
|
118
|
+
replicaId: storage.ReplicaId,
|
|
119
|
+
checkpointGreaterThan: InternalOpId
|
|
120
|
+
) {
|
|
121
|
+
this.currentData.push({
|
|
122
|
+
sourceTableId,
|
|
123
|
+
operation: {
|
|
124
|
+
updateOne: {
|
|
125
|
+
filter: { _id: replicaId },
|
|
126
|
+
update: {
|
|
127
|
+
$set: {
|
|
128
|
+
data: null,
|
|
129
|
+
buckets: [] as CurrentDataDocumentV3['buckets'],
|
|
130
|
+
lookups: [] as CurrentDataDocumentV3['lookups'],
|
|
131
|
+
pending_delete: checkpointGreaterThan
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
upsert: true
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
const sourceTableKey = sourceTableId.toHexString();
|
|
139
|
+
const existingPendingDelete = this.sourceTablePendingDeletes.get(sourceTableKey);
|
|
140
|
+
if (existingPendingDelete == null || checkpointGreaterThan > existingPendingDelete) {
|
|
141
|
+
this.sourceTablePendingDeletes.set(sourceTableKey, checkpointGreaterThan);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
this.currentSize += 50;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
upsertCurrentData(values: UpsertCurrentDataOptions) {
|
|
148
|
+
const buckets = values.buckets.map((bucket) => {
|
|
149
|
+
if (bucket.definitionId == null) {
|
|
150
|
+
throw new ReplicationAssertionError('Expected v3 bucket when incrementalReprocessing is enabled');
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
def: bucket.definitionId,
|
|
154
|
+
bucket: bucket.bucket,
|
|
155
|
+
table: bucket.table,
|
|
156
|
+
id: bucket.id
|
|
157
|
+
};
|
|
158
|
+
});
|
|
159
|
+
const lookups = values.lookups.map((lookup) => {
|
|
160
|
+
if (lookup.indexId == null) {
|
|
161
|
+
throw new ReplicationAssertionError('Expected v3 lookup when incrementalReprocessing is enabled');
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
i: lookup.indexId,
|
|
165
|
+
l: lookup.lookup
|
|
166
|
+
};
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
this.currentData.push({
|
|
170
|
+
sourceTableId: values.sourceTableId,
|
|
171
|
+
operation: {
|
|
172
|
+
updateOne: {
|
|
173
|
+
filter: { _id: values.replicaId },
|
|
174
|
+
update: {
|
|
175
|
+
$set: {
|
|
176
|
+
data: values.data,
|
|
177
|
+
buckets,
|
|
178
|
+
lookups
|
|
179
|
+
},
|
|
180
|
+
$unset: { pending_delete: 1 }
|
|
181
|
+
},
|
|
182
|
+
upsert: true
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
this.currentSize += (values.data?.length() ?? 0) + 100;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
protected get currentDataCount() {
|
|
190
|
+
return this.currentData.length;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
protected async flushBucketData(session: mongo.ClientSession) {
|
|
194
|
+
const operationsByDefinition = new Map<BucketDefinitionId, typeof this.bucketData>();
|
|
195
|
+
for (const document of this.bucketData) {
|
|
196
|
+
const existing = operationsByDefinition.get(document.bucketKey.definitionId) ?? [];
|
|
197
|
+
existing.push(document);
|
|
198
|
+
operationsByDefinition.set(document.bucketKey.definitionId, existing);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
for (const [definitionId, documents] of operationsByDefinition.entries()) {
|
|
202
|
+
await this.db.bucketDataV3(this.group_id, definitionId).bulkWrite(
|
|
203
|
+
documents.map((document) => ({
|
|
204
|
+
insertOne: {
|
|
205
|
+
document: serializeBucketDataV3(document)
|
|
206
|
+
}
|
|
207
|
+
})),
|
|
208
|
+
{
|
|
209
|
+
session,
|
|
210
|
+
ordered: false
|
|
211
|
+
}
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
protected async flushBucketParameters(session: mongo.ClientSession) {
|
|
217
|
+
const operationsByIndex = new Map<string, typeof this.bucketParameters>();
|
|
218
|
+
for (const document of this.bucketParameters) {
|
|
219
|
+
const existing = operationsByIndex.get(document.index) ?? [];
|
|
220
|
+
existing.push(document);
|
|
221
|
+
operationsByIndex.set(document.index, existing);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
for (const [indexId, documents] of operationsByIndex.entries()) {
|
|
225
|
+
await this.db.parameterIndexV3(this.group_id, indexId).bulkWrite(
|
|
226
|
+
documents.map((document) => ({
|
|
227
|
+
insertOne: {
|
|
228
|
+
document: taggedBucketParameterDocumentToV3(document)
|
|
229
|
+
}
|
|
230
|
+
})),
|
|
231
|
+
{
|
|
232
|
+
session,
|
|
233
|
+
ordered: false
|
|
234
|
+
}
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
protected async flushCurrentData(session: mongo.ClientSession) {
|
|
240
|
+
const operationsBySourceTable = new Map<string, typeof this.currentData>();
|
|
241
|
+
for (const operation of this.currentData) {
|
|
242
|
+
const sourceTableId = operation.sourceTableId.toHexString();
|
|
243
|
+
const existing = operationsBySourceTable.get(sourceTableId) ?? [];
|
|
244
|
+
existing.push(operation);
|
|
245
|
+
operationsBySourceTable.set(sourceTableId, existing);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const sourceTableUpdates: mongo.AnyBulkWriteOperation<SourceTableDocumentV3>[] = [
|
|
249
|
+
...this.sourceTablePendingDeletes.entries()
|
|
250
|
+
].map(([key, value]) => {
|
|
251
|
+
return {
|
|
252
|
+
updateOne: {
|
|
253
|
+
filter: { _id: new bson.ObjectId(key) },
|
|
254
|
+
update: {
|
|
255
|
+
$max: {
|
|
256
|
+
latest_pending_delete: value
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
if (sourceTableUpdates.length > 0) {
|
|
264
|
+
await this.db.sourceTablesV3(this.group_id).bulkWrite(sourceTableUpdates, { session, ordered: false });
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
for (const operations of operationsBySourceTable.values()) {
|
|
268
|
+
const sourceTableId = operations[0]!.sourceTableId;
|
|
269
|
+
await this.db.sourceRecordsV3(this.group_id, sourceTableId).bulkWrite(
|
|
270
|
+
operations.map((entry) => entry.operation),
|
|
271
|
+
{
|
|
272
|
+
session,
|
|
273
|
+
ordered: true
|
|
274
|
+
}
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
protected async flushBucketStates(session: mongo.ClientSession) {
|
|
280
|
+
await this.db.bucketStateV3(this.group_id).bulkWrite(this.getBucketStateUpdates(), {
|
|
281
|
+
session,
|
|
282
|
+
ordered: false
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
protected resetCurrentData() {
|
|
287
|
+
this.currentData = [];
|
|
288
|
+
this.sourceTablePendingDeletes.clear();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
private getBucketStateUpdates(): mongo.AnyBulkWriteOperation<BucketStateDocumentV3>[] {
|
|
292
|
+
return Array.from(this.bucketStates.values()).map((state: BucketStateUpdate) => {
|
|
293
|
+
if (state.definitionId == null) {
|
|
294
|
+
throw new ReplicationAssertionError('Expected bucket definition id when incrementalReprocessing is enabled');
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
updateOne: {
|
|
298
|
+
filter: {
|
|
299
|
+
_id: {
|
|
300
|
+
d: state.definitionId,
|
|
301
|
+
b: state.bucket
|
|
302
|
+
}
|
|
303
|
+
},
|
|
304
|
+
update: {
|
|
305
|
+
$set: {
|
|
306
|
+
last_op: state.lastOp
|
|
307
|
+
},
|
|
308
|
+
$inc: {
|
|
309
|
+
'estimate_since_compact.count': state.incrementCount,
|
|
310
|
+
'estimate_since_compact.bytes': state.incrementBytes
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
upsert: true
|
|
314
|
+
}
|
|
315
|
+
} satisfies mongo.AnyBulkWriteOperation<BucketStateDocumentV3>;
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { mongo } from '@powersync/lib-service-mongodb';
|
|
2
|
+
import { InternalOpId } from '@powersync/service-core';
|
|
3
|
+
import { BucketDataDoc, BucketKey } from '../common/BucketDataDoc.js';
|
|
4
|
+
import {
|
|
5
|
+
BucketDataDocumentGeneric,
|
|
6
|
+
BucketDataDocumentGenericId,
|
|
7
|
+
SingleBucketStore
|
|
8
|
+
} from '../common/SingleBucketStore.js';
|
|
9
|
+
import { BucketDataProperties } from '../models.js';
|
|
10
|
+
import { VersionedPowerSyncMongoV3 } from './VersionedPowerSyncMongoV3.js';
|
|
11
|
+
import { BucketDataDocumentV3, BucketDataKeyV3, loadBucketDataDocumentV3, serializeBucketDataV3 } from './models.js';
|
|
12
|
+
|
|
13
|
+
export class SingleBucketStoreV3 implements SingleBucketStore {
|
|
14
|
+
public readonly collection: mongo.Collection<BucketDataDocumentGeneric>;
|
|
15
|
+
|
|
16
|
+
constructor(
|
|
17
|
+
private db: VersionedPowerSyncMongoV3,
|
|
18
|
+
public readonly key: BucketKey
|
|
19
|
+
) {
|
|
20
|
+
this.collection = db.bucketDataV3(
|
|
21
|
+
key.replicationStreamId,
|
|
22
|
+
key.definitionId
|
|
23
|
+
) as unknown as mongo.Collection<BucketDataDocumentGeneric>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
docId(o: InternalOpId): BucketDataDocumentGenericId {
|
|
27
|
+
// `satisfies BucketDataKeyV3` checks that we use the correct type for V3 storage
|
|
28
|
+
// `as BucketDataDocumentGenericId` does a cast to get the interface virtual type
|
|
29
|
+
return {
|
|
30
|
+
b: this.key.bucket,
|
|
31
|
+
o
|
|
32
|
+
} satisfies BucketDataKeyV3 as BucketDataDocumentGenericId;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get minId(): BucketDataDocumentGenericId {
|
|
36
|
+
return {
|
|
37
|
+
b: this.key.bucket,
|
|
38
|
+
o: new mongo.MinKey()
|
|
39
|
+
} as any; // No way to properly type this
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
get maxId(): BucketDataDocumentGenericId {
|
|
43
|
+
return {
|
|
44
|
+
b: this.key.bucket,
|
|
45
|
+
o: new mongo.MaxKey()
|
|
46
|
+
} as any; // No way to properly type this
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
toPersistedDocument(source: Omit<BucketDataDoc, 'bucketKey'>): BucketDataDocumentGeneric {
|
|
50
|
+
return serializeBucketDataV3({ bucketKey: this.key, ...source }) as BucketDataDocumentGeneric;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
fromPersistedDocument(doc: BucketDataDocumentGeneric): BucketDataDoc {
|
|
54
|
+
return loadBucketDataDocumentV3(this.key, doc as BucketDataDocumentV3);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
fromPartialPersistedDocument<T extends keyof BucketDataProperties>(
|
|
58
|
+
doc: Pick<BucketDataDocumentGeneric, '_id' | T>
|
|
59
|
+
): Pick<BucketDataDoc, 'bucketKey' | 'o' | T> {
|
|
60
|
+
const document = doc as Pick<BucketDataDocumentV3, '_id' | T>;
|
|
61
|
+
const { _id, ...rest } = document;
|
|
62
|
+
return {
|
|
63
|
+
bucketKey: this.key,
|
|
64
|
+
o: _id.o,
|
|
65
|
+
...rest
|
|
66
|
+
} as Pick<BucketDataDoc, 'bucketKey' | 'o' | T>;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import * as lib_mongo from '@powersync/lib-service-mongodb';
|
|
2
|
+
import { mongo } from '@powersync/lib-service-mongodb';
|
|
3
|
+
import { Logger } from '@powersync/lib-services-framework';
|
|
4
|
+
import { storage } from '@powersync/service-core';
|
|
5
|
+
import { EvaluatedParameters, EvaluatedRow } from '@powersync/service-sync-rules';
|
|
6
|
+
import * as bson from 'bson';
|
|
7
|
+
import { retryOnMongoMaxTimeMSExpired } from '../../../utils/util.js';
|
|
8
|
+
import { BucketDefinitionMapping } from '../BucketDefinitionMapping.js';
|
|
9
|
+
import { cacheKey } from '../OperationBatch.js';
|
|
10
|
+
import { LoadedSourceRecord, SourceRecordLookupEntry, SourceRecordStore } from '../common/SourceRecordStore.js';
|
|
11
|
+
import { serializeParameterLookupV3 } from './MongoParameterLookupV3.js';
|
|
12
|
+
import { VersionedPowerSyncMongoV3 } from './VersionedPowerSyncMongoV3.js';
|
|
13
|
+
import { CurrentDataDocumentV3, SourceTableDocumentV3 } from './models.js';
|
|
14
|
+
|
|
15
|
+
export class SourceRecordStoreV3 implements SourceRecordStore {
|
|
16
|
+
constructor(
|
|
17
|
+
private readonly db: VersionedPowerSyncMongoV3,
|
|
18
|
+
private readonly groupId: number,
|
|
19
|
+
private readonly mapping: BucketDefinitionMapping
|
|
20
|
+
) {}
|
|
21
|
+
|
|
22
|
+
mapEvaluatedBuckets(evaluated: EvaluatedRow[]): LoadedSourceRecord['buckets'] {
|
|
23
|
+
return evaluated.map((entry) => ({
|
|
24
|
+
definitionId: this.mapping.bucketSourceId(entry.source),
|
|
25
|
+
bucket: entry.bucket,
|
|
26
|
+
table: entry.table,
|
|
27
|
+
id: entry.id
|
|
28
|
+
}));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
mapParameterLookups(paramEvaluated: EvaluatedParameters[]): LoadedSourceRecord['lookups'] {
|
|
32
|
+
return paramEvaluated.map((entry) => ({
|
|
33
|
+
indexId: this.mapping.parameterLookupId(entry.lookup.source),
|
|
34
|
+
lookup: serializeParameterLookupV3(entry.lookup)
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private createLoadedDocument(
|
|
39
|
+
sourceTableId: bson.ObjectId,
|
|
40
|
+
id: storage.ReplicaId,
|
|
41
|
+
data: bson.Binary | null,
|
|
42
|
+
buckets: CurrentDataDocumentV3['buckets'],
|
|
43
|
+
lookups: CurrentDataDocumentV3['lookups']
|
|
44
|
+
): LoadedSourceRecord {
|
|
45
|
+
return {
|
|
46
|
+
sourceTableId,
|
|
47
|
+
replicaId: id,
|
|
48
|
+
data,
|
|
49
|
+
buckets: buckets.map((bucket) => ({
|
|
50
|
+
definitionId: bucket.def,
|
|
51
|
+
bucket: bucket.bucket,
|
|
52
|
+
table: bucket.table,
|
|
53
|
+
id: bucket.id
|
|
54
|
+
})),
|
|
55
|
+
lookups: lookups.map((lookup) => ({
|
|
56
|
+
indexId: lookup.i,
|
|
57
|
+
lookup: lookup.l
|
|
58
|
+
})),
|
|
59
|
+
cacheKey: cacheKey(sourceTableId, id)
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async loadSizes(session: mongo.ClientSession, entries: SourceRecordLookupEntry[]): Promise<Map<string, number>> {
|
|
64
|
+
const sizes = new Map<string, number>();
|
|
65
|
+
for (const [sourceTableId, replicaIds] of this.groupEntries(entries)) {
|
|
66
|
+
const filter = {
|
|
67
|
+
_id: { $in: replicaIds as any[] }
|
|
68
|
+
} as unknown as mongo.Filter<CurrentDataDocumentV3>;
|
|
69
|
+
const sizeCursor: mongo.AggregationCursor<CurrentDataDocumentV3 & { size: number }> = this.db
|
|
70
|
+
.sourceRecordsV3(this.groupId, sourceTableId)
|
|
71
|
+
.aggregate(
|
|
72
|
+
[
|
|
73
|
+
{
|
|
74
|
+
$match: filter
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
$project: {
|
|
78
|
+
_id: 1,
|
|
79
|
+
size: { $bsonSize: '$$ROOT' }
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
],
|
|
83
|
+
{ session }
|
|
84
|
+
);
|
|
85
|
+
for await (const doc of sizeCursor.stream()) {
|
|
86
|
+
sizes.set(cacheKey(sourceTableId, doc._id), doc.size);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return sizes;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async loadDocuments(
|
|
93
|
+
session: mongo.ClientSession,
|
|
94
|
+
entries: SourceRecordLookupEntry[],
|
|
95
|
+
idsOnly: boolean
|
|
96
|
+
): Promise<Map<string, LoadedSourceRecord>> {
|
|
97
|
+
const documents = new Map<string, LoadedSourceRecord>();
|
|
98
|
+
const projection = idsOnly ? { _id: 1 } : undefined;
|
|
99
|
+
for (const [sourceTableId, replicaIds] of this.groupEntries(entries)) {
|
|
100
|
+
const filter = {
|
|
101
|
+
_id: { $in: replicaIds as any[] }
|
|
102
|
+
} as unknown as mongo.Filter<CurrentDataDocumentV3>;
|
|
103
|
+
const cursor = this.db.sourceRecordsV3(this.groupId, sourceTableId).find(filter, { session, projection });
|
|
104
|
+
for await (const doc of cursor.stream()) {
|
|
105
|
+
const loaded = this.createLoadedDocument(
|
|
106
|
+
sourceTableId,
|
|
107
|
+
doc._id,
|
|
108
|
+
idsOnly ? null : doc.data,
|
|
109
|
+
idsOnly ? [] : doc.buckets,
|
|
110
|
+
idsOnly ? [] : doc.lookups
|
|
111
|
+
);
|
|
112
|
+
documents.set(loaded.cacheKey, loaded);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return documents;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async loadTruncateBatch(
|
|
119
|
+
session: mongo.ClientSession,
|
|
120
|
+
sourceTableId: bson.ObjectId,
|
|
121
|
+
limit: number
|
|
122
|
+
): Promise<LoadedSourceRecord[]> {
|
|
123
|
+
const cursor = this.db.sourceRecordsV3(this.groupId, sourceTableId).find(
|
|
124
|
+
{
|
|
125
|
+
pending_delete: { $exists: false }
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
projection: {
|
|
129
|
+
_id: 1,
|
|
130
|
+
buckets: 1,
|
|
131
|
+
lookups: 1
|
|
132
|
+
},
|
|
133
|
+
limit,
|
|
134
|
+
session
|
|
135
|
+
}
|
|
136
|
+
);
|
|
137
|
+
return (await cursor.toArray()).map((doc) =>
|
|
138
|
+
this.createLoadedDocument(sourceTableId, doc._id, null, doc.buckets, doc.lookups)
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async postCommitCleanup(lastCheckpoint: bigint, logger: Logger): Promise<void> {
|
|
143
|
+
// This cleans up soft deletes in source_records collections.
|
|
144
|
+
// Since there may be a lot (100+) of these collections in some cases, we track which
|
|
145
|
+
// ones have dirty deletes in source_tables.
|
|
146
|
+
|
|
147
|
+
const dirtySourceTables = await this.db
|
|
148
|
+
.sourceTablesV3(this.groupId)
|
|
149
|
+
.find(
|
|
150
|
+
{
|
|
151
|
+
latest_pending_delete: { $exists: true }
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
projection: { _id: 1, latest_pending_delete: 1 }
|
|
155
|
+
}
|
|
156
|
+
)
|
|
157
|
+
.toArray();
|
|
158
|
+
|
|
159
|
+
let deletedCount = 0;
|
|
160
|
+
const sourceTableUpdates: mongo.AnyBulkWriteOperation<SourceTableDocumentV3>[] = [];
|
|
161
|
+
for (const sourceTable of dirtySourceTables) {
|
|
162
|
+
const collection = this.db.sourceRecordsV3(this.groupId, sourceTable._id);
|
|
163
|
+
const result = await this.deletePendingDeletes(collection, sourceTable._id, lastCheckpoint, logger);
|
|
164
|
+
deletedCount += result.deletedCount;
|
|
165
|
+
|
|
166
|
+
if (sourceTable.latest_pending_delete != null && sourceTable.latest_pending_delete <= lastCheckpoint) {
|
|
167
|
+
sourceTableUpdates.push({
|
|
168
|
+
updateOne: {
|
|
169
|
+
filter: {
|
|
170
|
+
_id: sourceTable._id,
|
|
171
|
+
// If the source table received more writes in the meantime, this will filter it out
|
|
172
|
+
latest_pending_delete: sourceTable.latest_pending_delete
|
|
173
|
+
},
|
|
174
|
+
update: {
|
|
175
|
+
$unset: {
|
|
176
|
+
latest_pending_delete: 1
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (sourceTableUpdates.length > 0) {
|
|
185
|
+
await this.db.sourceTablesV3(this.groupId).bulkWrite(sourceTableUpdates, { ordered: false });
|
|
186
|
+
}
|
|
187
|
+
if (deletedCount > 0) {
|
|
188
|
+
logger.info(`Cleaned up ${deletedCount} pending delete current_data records for checkpoint ${lastCheckpoint}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private async deletePendingDeletes(
|
|
193
|
+
collection: mongo.Collection<CurrentDataDocumentV3>,
|
|
194
|
+
sourceTableId: bson.ObjectId,
|
|
195
|
+
lastCheckpoint: bigint,
|
|
196
|
+
logger: Logger
|
|
197
|
+
) {
|
|
198
|
+
return retryOnMongoMaxTimeMSExpired(
|
|
199
|
+
() =>
|
|
200
|
+
collection.deleteMany(
|
|
201
|
+
{
|
|
202
|
+
pending_delete: { $exists: true, $lte: lastCheckpoint }
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS
|
|
206
|
+
}
|
|
207
|
+
),
|
|
208
|
+
{
|
|
209
|
+
retryDelayMs: lib_mongo.db.MONGO_OPERATION_TIMEOUT_MS / 5,
|
|
210
|
+
onRetry: (n: number) => {
|
|
211
|
+
logger.warn(`Cleared batch ${n} of pending deletes for source table ${sourceTableId}, continuing...`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
private groupEntries(entries: SourceRecordLookupEntry[]): Map<bson.ObjectId, storage.ReplicaId[]> {
|
|
218
|
+
const grouped = new Map<bson.ObjectId, storage.ReplicaId[]>();
|
|
219
|
+
for (const entry of entries) {
|
|
220
|
+
const existing = grouped.get(entry.sourceTableId) ?? [];
|
|
221
|
+
existing.push(entry.replicaId);
|
|
222
|
+
grouped.set(entry.sourceTableId, existing);
|
|
223
|
+
}
|
|
224
|
+
return grouped;
|
|
225
|
+
}
|
|
226
|
+
}
|