@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
|
@@ -4,66 +4,76 @@ import { InternalOpId, storage } from '@powersync/service-core';
|
|
|
4
4
|
import { BucketDataSource, BucketDefinitionId } from '@powersync/service-sync-rules';
|
|
5
5
|
import * as bson from 'bson';
|
|
6
6
|
import { mongoTableId } from '../../../utils/util.js';
|
|
7
|
+
import { BucketDataDoc } from '../common/BucketDataDoc.js';
|
|
7
8
|
import {
|
|
8
9
|
BucketStateUpdate,
|
|
9
10
|
PersistedBatch,
|
|
10
11
|
SaveParameterDataOptions,
|
|
11
12
|
UpsertCurrentDataOptions
|
|
12
13
|
} from '../common/PersistedBatch.js';
|
|
13
|
-
import {
|
|
14
|
+
import { SourceRecordLookupState } from '../common/SourceRecordStore.js';
|
|
15
|
+
import { serializeBucketData } from './bucket-format.js';
|
|
16
|
+
import { chunkBucketData } from './chunking.js';
|
|
14
17
|
import {
|
|
15
|
-
|
|
18
|
+
BucketDataDocumentV3,
|
|
16
19
|
BucketStateDocumentV3,
|
|
17
20
|
CurrentDataDocumentV3,
|
|
18
|
-
|
|
21
|
+
serializeParameterLookup,
|
|
19
22
|
SourceTableDocumentV3,
|
|
20
|
-
|
|
23
|
+
taggedBucketParameterDocumentToTagged
|
|
21
24
|
} from './models.js';
|
|
22
|
-
import { serializeParameterLookupV3 } from './MongoParameterLookupV3.js';
|
|
23
25
|
import { VersionedPowerSyncMongoV3 } from './VersionedPowerSyncMongoV3.js';
|
|
24
26
|
|
|
25
27
|
export class PersistedBatchV3 extends PersistedBatch {
|
|
26
|
-
declare protected readonly db: VersionedPowerSyncMongoV3;
|
|
27
|
-
|
|
28
28
|
currentData: { sourceTableId: bson.ObjectId; operation: mongo.AnyBulkWriteOperation<CurrentDataDocumentV3> }[] = [];
|
|
29
29
|
sourceTablePendingDeletes = new Map<string, InternalOpId>();
|
|
30
30
|
|
|
31
|
-
protected
|
|
31
|
+
declare protected readonly db: VersionedPowerSyncMongoV3;
|
|
32
|
+
|
|
33
|
+
// Abstract override from PersistedBatch (V3-specific error message)
|
|
34
|
+
|
|
35
|
+
protected override checkDefinitionId(definitionId: BucketDefinitionId | null): BucketDefinitionId {
|
|
32
36
|
if (definitionId == null) {
|
|
33
|
-
// This is required for V3 storage.
|
|
34
37
|
throw new ReplicationAssertionError('Expected v3 bucket when incrementalReprocessing is enabled');
|
|
35
38
|
}
|
|
36
39
|
return definitionId;
|
|
37
40
|
}
|
|
38
41
|
|
|
42
|
+
// Concrete implementations from PersistedBatchShared
|
|
43
|
+
|
|
39
44
|
protected getBucketDefinitionId(bucketSource: BucketDataSource): BucketDefinitionId {
|
|
40
45
|
return this.mapping.bucketSourceId(bucketSource);
|
|
41
46
|
}
|
|
42
47
|
|
|
43
48
|
saveParameterData(data: SaveParameterDataOptions) {
|
|
44
49
|
const { sourceTable, sourceKey, evaluated } = data;
|
|
45
|
-
const remaining_lookups = new Map<string,
|
|
50
|
+
const remaining_lookups = new Map<string, SourceRecordLookupState>();
|
|
46
51
|
|
|
47
52
|
for (let lookup of data.existing_lookups) {
|
|
48
53
|
if (lookup.indexId == null) {
|
|
49
|
-
throw new ReplicationAssertionError('Expected
|
|
54
|
+
throw new ReplicationAssertionError('Expected lookup when incrementalReprocessing is enabled');
|
|
55
|
+
}
|
|
56
|
+
if (sourceTable.parameterLookupSourceIds != null && !sourceTable.parameterLookupSourceIds.has(lookup.indexId)) {
|
|
57
|
+
// The parameter index is not active anymore.
|
|
58
|
+
// We don't cleanup these references upfront, but do need to ignore them after the definition is removed.
|
|
59
|
+
continue;
|
|
50
60
|
}
|
|
51
61
|
remaining_lookups.set(`${lookup.indexId}.${lookup.lookup.toString('base64')}`, lookup);
|
|
52
62
|
}
|
|
53
63
|
|
|
54
64
|
for (let result of evaluated) {
|
|
55
65
|
const sourceDefinitionId = this.mapping.parameterLookupId(result.lookup.source);
|
|
56
|
-
const binLookup =
|
|
66
|
+
const binLookup = serializeParameterLookup(result.lookup);
|
|
57
67
|
remaining_lookups.delete(`${sourceDefinitionId}.${binLookup.toString('base64')}`);
|
|
58
68
|
|
|
59
69
|
const op_id = data.op_seq.next();
|
|
60
70
|
this.debugLastOpId = op_id;
|
|
61
|
-
const values
|
|
71
|
+
const values = {
|
|
62
72
|
_id: op_id,
|
|
63
73
|
key: {
|
|
64
74
|
t: mongoTableId(sourceTable.id),
|
|
65
75
|
k: sourceKey
|
|
66
|
-
}
|
|
76
|
+
},
|
|
67
77
|
lookup: binLookup,
|
|
68
78
|
bucket_parameters: result.bucketParameters
|
|
69
79
|
};
|
|
@@ -80,14 +90,14 @@ export class PersistedBatchV3 extends PersistedBatch {
|
|
|
80
90
|
this.debugLastOpId = op_id;
|
|
81
91
|
const indexId = lookup.indexId;
|
|
82
92
|
if (indexId == null) {
|
|
83
|
-
throw new ReplicationAssertionError('Expected
|
|
93
|
+
throw new ReplicationAssertionError('Expected lookup when incrementalReprocessing is enabled');
|
|
84
94
|
}
|
|
85
|
-
const values
|
|
95
|
+
const values = {
|
|
86
96
|
_id: op_id,
|
|
87
97
|
key: {
|
|
88
98
|
t: mongoTableId(sourceTable.id),
|
|
89
99
|
k: sourceKey
|
|
90
|
-
}
|
|
100
|
+
},
|
|
91
101
|
lookup: lookup.lookup,
|
|
92
102
|
bucket_parameters: []
|
|
93
103
|
};
|
|
@@ -125,8 +135,8 @@ export class PersistedBatchV3 extends PersistedBatch {
|
|
|
125
135
|
update: {
|
|
126
136
|
$set: {
|
|
127
137
|
data: null,
|
|
128
|
-
buckets: []
|
|
129
|
-
lookups: []
|
|
138
|
+
buckets: [],
|
|
139
|
+
lookups: [],
|
|
130
140
|
pending_delete: checkpointGreaterThan
|
|
131
141
|
}
|
|
132
142
|
},
|
|
@@ -146,7 +156,7 @@ export class PersistedBatchV3 extends PersistedBatch {
|
|
|
146
156
|
upsertCurrentData(values: UpsertCurrentDataOptions) {
|
|
147
157
|
const buckets = values.buckets.map((bucket) => {
|
|
148
158
|
if (bucket.definitionId == null) {
|
|
149
|
-
throw new ReplicationAssertionError('Expected
|
|
159
|
+
throw new ReplicationAssertionError('Expected bucket when incrementalReprocessing is enabled');
|
|
150
160
|
}
|
|
151
161
|
return {
|
|
152
162
|
def: bucket.definitionId,
|
|
@@ -157,7 +167,7 @@ export class PersistedBatchV3 extends PersistedBatch {
|
|
|
157
167
|
});
|
|
158
168
|
const lookups = values.lookups.map((lookup) => {
|
|
159
169
|
if (lookup.indexId == null) {
|
|
160
|
-
throw new ReplicationAssertionError('Expected
|
|
170
|
+
throw new ReplicationAssertionError('Expected lookup when incrementalReprocessing is enabled');
|
|
161
171
|
}
|
|
162
172
|
return {
|
|
163
173
|
i: lookup.indexId,
|
|
@@ -189,8 +199,10 @@ export class PersistedBatchV3 extends PersistedBatch {
|
|
|
189
199
|
return this.currentData.length;
|
|
190
200
|
}
|
|
191
201
|
|
|
202
|
+
// Flush methods
|
|
203
|
+
|
|
192
204
|
protected async flushBucketData(session: mongo.ClientSession) {
|
|
193
|
-
const operationsByDefinition = new Map<BucketDefinitionId,
|
|
205
|
+
const operationsByDefinition = new Map<BucketDefinitionId, BucketDataDoc[]>();
|
|
194
206
|
for (const document of this.bucketData) {
|
|
195
207
|
const existing = operationsByDefinition.get(document.bucketKey.definitionId) ?? [];
|
|
196
208
|
existing.push(document);
|
|
@@ -198,17 +210,31 @@ export class PersistedBatchV3 extends PersistedBatch {
|
|
|
198
210
|
}
|
|
199
211
|
|
|
200
212
|
for (const [definitionId, documents] of operationsByDefinition.entries()) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
213
|
+
const operationsByBucket = new Map<string, BucketDataDoc[]>();
|
|
214
|
+
for (const document of documents) {
|
|
215
|
+
const existing = operationsByBucket.get(document.bucketKey.bucket) ?? [];
|
|
216
|
+
existing.push(document);
|
|
217
|
+
operationsByBucket.set(document.bucketKey.bucket, existing);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const inserts: mongo.AnyBulkWriteOperation<BucketDataDocumentV3>[] = [];
|
|
221
|
+
for (const [bucket, ops] of operationsByBucket.entries()) {
|
|
222
|
+
const chunks = chunkBucketData(ops);
|
|
223
|
+
for (const chunk of chunks) {
|
|
224
|
+
inserts.push({
|
|
225
|
+
insertOne: {
|
|
226
|
+
document: serializeBucketData(bucket, chunk)
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (inserts.length > 0) {
|
|
233
|
+
await this.db.bucketData(this.group_id, definitionId).bulkWrite(inserts, {
|
|
208
234
|
session,
|
|
209
235
|
ordered: false
|
|
210
|
-
}
|
|
211
|
-
|
|
236
|
+
});
|
|
237
|
+
}
|
|
212
238
|
}
|
|
213
239
|
}
|
|
214
240
|
|
|
@@ -221,10 +247,10 @@ export class PersistedBatchV3 extends PersistedBatch {
|
|
|
221
247
|
}
|
|
222
248
|
|
|
223
249
|
for (const [indexId, documents] of operationsByIndex.entries()) {
|
|
224
|
-
await this.db.
|
|
250
|
+
await this.db.parameterIndex(this.group_id, indexId).bulkWrite(
|
|
225
251
|
documents.map((document) => ({
|
|
226
252
|
insertOne: {
|
|
227
|
-
document:
|
|
253
|
+
document: taggedBucketParameterDocumentToTagged(document)
|
|
228
254
|
}
|
|
229
255
|
})),
|
|
230
256
|
{
|
|
@@ -260,12 +286,12 @@ export class PersistedBatchV3 extends PersistedBatch {
|
|
|
260
286
|
});
|
|
261
287
|
|
|
262
288
|
if (sourceTableUpdates.length > 0) {
|
|
263
|
-
await this.db.
|
|
289
|
+
await this.db.sourceTables(this.group_id).bulkWrite(sourceTableUpdates, { session, ordered: false });
|
|
264
290
|
}
|
|
265
291
|
|
|
266
292
|
for (const operations of operationsBySourceTable.values()) {
|
|
267
293
|
const sourceTableId = operations[0]!.sourceTableId;
|
|
268
|
-
await this.db.
|
|
294
|
+
await this.db.sourceRecords(this.group_id, sourceTableId).bulkWrite(
|
|
269
295
|
operations.map((entry) => entry.operation),
|
|
270
296
|
{
|
|
271
297
|
session,
|
|
@@ -276,7 +302,7 @@ export class PersistedBatchV3 extends PersistedBatch {
|
|
|
276
302
|
}
|
|
277
303
|
|
|
278
304
|
protected async flushBucketStates(session: mongo.ClientSession) {
|
|
279
|
-
await this.db.
|
|
305
|
+
await this.db.bucketState(this.group_id).bulkWrite(this.getBucketStateUpdates(), {
|
|
280
306
|
session,
|
|
281
307
|
ordered: false
|
|
282
308
|
});
|
|
@@ -6,9 +6,38 @@ import {
|
|
|
6
6
|
BucketDataDocumentGenericId,
|
|
7
7
|
SingleBucketStore
|
|
8
8
|
} from '../common/SingleBucketStore.js';
|
|
9
|
-
import { BucketDataProperties } from '../models.js';
|
|
9
|
+
import { BucketDataKey, BucketDataProperties } from '../models.js';
|
|
10
|
+
import { loadBucketDataDocument, serializeBucketData } from './bucket-format.js';
|
|
11
|
+
import { BucketDataDocumentV3 } from './models.js';
|
|
10
12
|
import { VersionedPowerSyncMongoV3 } from './VersionedPowerSyncMongoV3.js';
|
|
11
|
-
|
|
13
|
+
|
|
14
|
+
function extractPartialDocumentFields(doc: unknown): { _id: { o: bigint }; [key: string]: unknown } {
|
|
15
|
+
if (typeof doc !== 'object' || doc === null) {
|
|
16
|
+
throw new Error('Invalid partial document: expected object');
|
|
17
|
+
}
|
|
18
|
+
const d = doc as Record<string, unknown>;
|
|
19
|
+
const id = d._id;
|
|
20
|
+
if (typeof id !== 'object' || id === null || !('o' in id)) {
|
|
21
|
+
throw new Error('Invalid partial document: missing _id.o');
|
|
22
|
+
}
|
|
23
|
+
return d as { _id: { o: bigint }; [key: string]: unknown };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// MongoDB's MinKey/MaxKey are special sentinel values that don't match the bigint type
|
|
27
|
+
// for _id.o in BucketDataDocumentGenericId, so we need an explicit cast.
|
|
28
|
+
function minKeyForBucket(bucket: string): BucketDataDocumentGenericId {
|
|
29
|
+
return {
|
|
30
|
+
b: bucket,
|
|
31
|
+
o: new mongo.MinKey()
|
|
32
|
+
} as unknown as BucketDataDocumentGenericId;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function maxKeyForBucket(bucket: string): BucketDataDocumentGenericId {
|
|
36
|
+
return {
|
|
37
|
+
b: bucket,
|
|
38
|
+
o: new mongo.MaxKey()
|
|
39
|
+
} as unknown as BucketDataDocumentGenericId;
|
|
40
|
+
}
|
|
12
41
|
|
|
13
42
|
export class SingleBucketStoreV3 implements SingleBucketStore {
|
|
14
43
|
public readonly collection: mongo.Collection<BucketDataDocumentGeneric>;
|
|
@@ -17,52 +46,63 @@ export class SingleBucketStoreV3 implements SingleBucketStore {
|
|
|
17
46
|
private db: VersionedPowerSyncMongoV3,
|
|
18
47
|
public readonly key: BucketKey
|
|
19
48
|
) {
|
|
20
|
-
|
|
49
|
+
// Cast from the version-specific collection type to the generic interface
|
|
50
|
+
// used across storage versions.
|
|
51
|
+
this.collection = db.bucketData(
|
|
21
52
|
key.replicationStreamId,
|
|
22
53
|
key.definitionId
|
|
23
54
|
) as unknown as mongo.Collection<BucketDataDocumentGeneric>;
|
|
24
55
|
}
|
|
25
56
|
|
|
26
57
|
docId(o: InternalOpId): BucketDataDocumentGenericId {
|
|
27
|
-
// `satisfies
|
|
58
|
+
// `satisfies BucketDataKey` checks that we use the correct type for V3 storage
|
|
28
59
|
// `as BucketDataDocumentGenericId` does a cast to get the interface virtual type
|
|
29
60
|
return {
|
|
30
61
|
b: this.key.bucket,
|
|
31
62
|
o
|
|
32
|
-
} satisfies
|
|
63
|
+
} satisfies BucketDataKey as BucketDataDocumentGenericId;
|
|
33
64
|
}
|
|
34
65
|
|
|
35
66
|
get minId(): BucketDataDocumentGenericId {
|
|
36
|
-
return
|
|
37
|
-
b: this.key.bucket,
|
|
38
|
-
o: new mongo.MinKey()
|
|
39
|
-
} as any; // No way to properly type this
|
|
67
|
+
return minKeyForBucket(this.key.bucket);
|
|
40
68
|
}
|
|
41
69
|
|
|
42
70
|
get maxId(): BucketDataDocumentGenericId {
|
|
43
|
-
return
|
|
44
|
-
b: this.key.bucket,
|
|
45
|
-
o: new mongo.MaxKey()
|
|
46
|
-
} as any; // No way to properly type this
|
|
71
|
+
return maxKeyForBucket(this.key.bucket);
|
|
47
72
|
}
|
|
48
73
|
|
|
49
74
|
toPersistedDocument(source: Omit<BucketDataDoc, 'bucketKey'>): BucketDataDocumentGeneric {
|
|
50
|
-
return
|
|
75
|
+
return serializeBucketData(this.key.bucket, [
|
|
76
|
+
{ bucketKey: this.key, ...source }
|
|
77
|
+
]) as unknown as BucketDataDocumentGeneric;
|
|
51
78
|
}
|
|
52
79
|
|
|
53
80
|
fromPersistedDocument(doc: BucketDataDocumentGeneric): BucketDataDoc {
|
|
54
|
-
|
|
81
|
+
const generator = loadBucketDataDocument(this.key, doc as unknown as BucketDataDocumentV3);
|
|
82
|
+
const first = generator.next();
|
|
83
|
+
if (first.done) {
|
|
84
|
+
throw new Error('Empty ops array in BucketDataDocumentV3');
|
|
85
|
+
}
|
|
86
|
+
return first.value;
|
|
55
87
|
}
|
|
56
88
|
|
|
57
89
|
fromPartialPersistedDocument<T extends keyof BucketDataProperties>(
|
|
58
90
|
doc: Pick<BucketDataDocumentGeneric, '_id' | T>
|
|
59
91
|
): Pick<BucketDataDoc, 'bucketKey' | 'o' | T> {
|
|
60
|
-
const document = doc as Pick<BucketDataDocumentV3, '_id' |
|
|
61
|
-
const
|
|
92
|
+
const document = doc as unknown as Pick<BucketDataDocumentV3, '_id' | 'ops'>;
|
|
93
|
+
const op = document.ops?.[0];
|
|
94
|
+
if (op == null) {
|
|
95
|
+
const fields = extractPartialDocumentFields(doc);
|
|
96
|
+
const { _id, ...rest } = fields;
|
|
97
|
+
return {
|
|
98
|
+
bucketKey: this.key,
|
|
99
|
+
o: _id.o,
|
|
100
|
+
...rest
|
|
101
|
+
} as Pick<BucketDataDoc, 'bucketKey' | 'o' | T>;
|
|
102
|
+
}
|
|
62
103
|
return {
|
|
63
104
|
bucketKey: this.key,
|
|
64
|
-
|
|
65
|
-
...rest
|
|
105
|
+
...op
|
|
66
106
|
} as Pick<BucketDataDoc, 'bucketKey' | 'o' | T>;
|
|
67
107
|
}
|
|
68
108
|
}
|
|
@@ -1,21 +1,19 @@
|
|
|
1
1
|
import * as lib_mongo from '@powersync/lib-service-mongodb';
|
|
2
2
|
import { mongo } from '@powersync/lib-service-mongodb';
|
|
3
3
|
import { Logger } from '@powersync/lib-services-framework';
|
|
4
|
-
import { storage } from '@powersync/service-core';
|
|
4
|
+
import { BucketDefinitionMapping, storage } from '@powersync/service-core';
|
|
5
5
|
import { EvaluatedParameters, EvaluatedRow } from '@powersync/service-sync-rules';
|
|
6
6
|
import * as bson from 'bson';
|
|
7
7
|
import { retryOnMongoMaxTimeMSExpired } from '../../../utils/util.js';
|
|
8
|
-
import { BucketDefinitionMapping } from '../BucketDefinitionMapping.js';
|
|
9
8
|
import { cacheKey } from '../OperationBatch.js';
|
|
10
9
|
import { LoadedSourceRecord, SourceRecordLookupEntry, SourceRecordStore } from '../common/SourceRecordStore.js';
|
|
11
|
-
import { serializeParameterLookupV3 } from './MongoParameterLookupV3.js';
|
|
12
10
|
import { VersionedPowerSyncMongoV3 } from './VersionedPowerSyncMongoV3.js';
|
|
13
|
-
import { CurrentDataDocumentV3, SourceTableDocumentV3 } from './models.js';
|
|
11
|
+
import { CurrentDataDocumentV3, serializeParameterLookup, SourceTableDocumentV3 } from './models.js';
|
|
14
12
|
|
|
15
13
|
export class SourceRecordStoreV3 implements SourceRecordStore {
|
|
16
14
|
constructor(
|
|
17
15
|
private readonly db: VersionedPowerSyncMongoV3,
|
|
18
|
-
private readonly
|
|
16
|
+
private readonly replicationStreamId: number,
|
|
19
17
|
private readonly mapping: BucketDefinitionMapping
|
|
20
18
|
) {}
|
|
21
19
|
|
|
@@ -31,7 +29,7 @@ export class SourceRecordStoreV3 implements SourceRecordStore {
|
|
|
31
29
|
mapParameterLookups(paramEvaluated: EvaluatedParameters[]): LoadedSourceRecord['lookups'] {
|
|
32
30
|
return paramEvaluated.map((entry) => ({
|
|
33
31
|
indexId: this.mapping.parameterLookupId(entry.lookup.source),
|
|
34
|
-
lookup:
|
|
32
|
+
lookup: serializeParameterLookup(entry.lookup)
|
|
35
33
|
}));
|
|
36
34
|
}
|
|
37
35
|
|
|
@@ -67,7 +65,7 @@ export class SourceRecordStoreV3 implements SourceRecordStore {
|
|
|
67
65
|
_id: { $in: replicaIds as any[] }
|
|
68
66
|
} as unknown as mongo.Filter<CurrentDataDocumentV3>;
|
|
69
67
|
const sizeCursor: mongo.AggregationCursor<CurrentDataDocumentV3 & { size: number }> = this.db
|
|
70
|
-
.
|
|
68
|
+
.sourceRecords(this.replicationStreamId, sourceTableId)
|
|
71
69
|
.aggregate(
|
|
72
70
|
[
|
|
73
71
|
{
|
|
@@ -100,7 +98,9 @@ export class SourceRecordStoreV3 implements SourceRecordStore {
|
|
|
100
98
|
const filter = {
|
|
101
99
|
_id: { $in: replicaIds as any[] }
|
|
102
100
|
} as unknown as mongo.Filter<CurrentDataDocumentV3>;
|
|
103
|
-
const cursor = this.db
|
|
101
|
+
const cursor = this.db
|
|
102
|
+
.sourceRecords(this.replicationStreamId, sourceTableId)
|
|
103
|
+
.find(filter, { session, projection });
|
|
104
104
|
for await (const doc of cursor.stream()) {
|
|
105
105
|
const loaded = this.createLoadedDocument(
|
|
106
106
|
sourceTableId,
|
|
@@ -120,7 +120,7 @@ export class SourceRecordStoreV3 implements SourceRecordStore {
|
|
|
120
120
|
sourceTableId: bson.ObjectId,
|
|
121
121
|
limit: number
|
|
122
122
|
): Promise<LoadedSourceRecord[]> {
|
|
123
|
-
const cursor = this.db.
|
|
123
|
+
const cursor = this.db.sourceRecords(this.replicationStreamId, sourceTableId).find(
|
|
124
124
|
{
|
|
125
125
|
pending_delete: { $exists: false }
|
|
126
126
|
},
|
|
@@ -145,7 +145,7 @@ export class SourceRecordStoreV3 implements SourceRecordStore {
|
|
|
145
145
|
// ones have dirty deletes in source_tables.
|
|
146
146
|
|
|
147
147
|
const dirtySourceTables = await this.db
|
|
148
|
-
.
|
|
148
|
+
.sourceTables(this.replicationStreamId)
|
|
149
149
|
.find(
|
|
150
150
|
{
|
|
151
151
|
latest_pending_delete: { $exists: true }
|
|
@@ -159,7 +159,7 @@ export class SourceRecordStoreV3 implements SourceRecordStore {
|
|
|
159
159
|
let deletedCount = 0;
|
|
160
160
|
const sourceTableUpdates: mongo.AnyBulkWriteOperation<SourceTableDocumentV3>[] = [];
|
|
161
161
|
for (const sourceTable of dirtySourceTables) {
|
|
162
|
-
const collection = this.db.
|
|
162
|
+
const collection = this.db.sourceRecords(this.replicationStreamId, sourceTable._id);
|
|
163
163
|
const result = await this.deletePendingDeletes(collection, sourceTable._id, lastCheckpoint, logger);
|
|
164
164
|
deletedCount += result.deletedCount;
|
|
165
165
|
|
|
@@ -182,7 +182,7 @@ export class SourceRecordStoreV3 implements SourceRecordStore {
|
|
|
182
182
|
}
|
|
183
183
|
|
|
184
184
|
if (sourceTableUpdates.length > 0) {
|
|
185
|
-
await this.db.
|
|
185
|
+
await this.db.sourceTables(this.replicationStreamId).bulkWrite(sourceTableUpdates, { ordered: false });
|
|
186
186
|
}
|
|
187
187
|
if (deletedCount > 0) {
|
|
188
188
|
logger.info(`Cleaned up ${deletedCount} pending delete current_data records for checkpoint ${lastCheckpoint}`);
|
|
@@ -12,53 +12,80 @@ import {
|
|
|
12
12
|
} from './models.js';
|
|
13
13
|
|
|
14
14
|
export class VersionedPowerSyncMongoV3 extends BaseVersionedPowerSyncMongo {
|
|
15
|
+
constructor(
|
|
16
|
+
upstream: ConstructorParameters<typeof BaseVersionedPowerSyncMongo>[0],
|
|
17
|
+
storageConfig: ConstructorParameters<typeof BaseVersionedPowerSyncMongo>[1]
|
|
18
|
+
) {
|
|
19
|
+
super(upstream, storageConfig);
|
|
20
|
+
}
|
|
21
|
+
|
|
15
22
|
get syncConfigDefinitions() {
|
|
16
23
|
return this.db.collection<SyncConfigDefinition>('sync_config');
|
|
17
24
|
}
|
|
18
25
|
|
|
19
|
-
|
|
26
|
+
sourceRecords(replicationStreamId: number, sourceTableId: mongo.ObjectId): mongo.Collection<CurrentDataDocumentV3> {
|
|
20
27
|
const collectionName = this.sourceRecordsCollectionName(replicationStreamId, sourceTableId);
|
|
21
28
|
return this.db.collection<CurrentDataDocumentV3>(collectionName);
|
|
22
29
|
}
|
|
23
30
|
|
|
24
|
-
async
|
|
31
|
+
async listSourceRecordCollections(replicationStreamId: number): Promise<mongo.Collection<CurrentDataDocumentV3>[]> {
|
|
25
32
|
return this.listCollectionsByPrefix<CurrentDataDocumentV3>(`source_records_${replicationStreamId}_`);
|
|
26
33
|
}
|
|
27
34
|
|
|
28
|
-
async initializeSourceRecordsCollection(
|
|
29
|
-
|
|
35
|
+
async initializeSourceRecordsCollection(
|
|
36
|
+
replicationStreamId: number,
|
|
37
|
+
sourceTableId: mongo.ObjectId,
|
|
38
|
+
session?: mongo.ClientSession
|
|
39
|
+
) {
|
|
40
|
+
await this.sourceRecords(replicationStreamId, sourceTableId).createIndex(
|
|
30
41
|
{
|
|
31
42
|
pending_delete: 1
|
|
32
43
|
},
|
|
33
44
|
{
|
|
34
45
|
partialFilterExpression: { pending_delete: { $exists: true } },
|
|
35
|
-
name: 'pending_delete'
|
|
46
|
+
name: 'pending_delete',
|
|
47
|
+
session
|
|
36
48
|
}
|
|
37
49
|
);
|
|
38
50
|
}
|
|
39
51
|
|
|
40
52
|
commonSourceTables(replicationStreamId: number): mongo.Collection<CommonSourceTableDocument> {
|
|
41
|
-
return this.
|
|
53
|
+
return this.sourceTables(replicationStreamId) as any as mongo.Collection<CommonSourceTableDocument>;
|
|
42
54
|
}
|
|
43
55
|
|
|
44
|
-
|
|
56
|
+
bucketState(replicationStreamId: number): mongo.Collection<BucketStateDocumentV3> {
|
|
45
57
|
return this.db.collection(`bucket_state_${replicationStreamId}`);
|
|
46
58
|
}
|
|
47
59
|
|
|
48
|
-
|
|
49
|
-
replicationStreamId: number,
|
|
50
|
-
indexId: ParameterIndexId
|
|
51
|
-
): mongo.Collection<BucketParameterDocumentV3> {
|
|
60
|
+
parameterIndex(replicationStreamId: number, indexId: ParameterIndexId): mongo.Collection<BucketParameterDocumentV3> {
|
|
52
61
|
return this.db.collection(`parameter_index_${replicationStreamId}_${indexId}`);
|
|
53
62
|
}
|
|
54
63
|
|
|
55
|
-
|
|
64
|
+
sourceTables(replicationStreamId: number): mongo.Collection<SourceTableDocumentV3> {
|
|
56
65
|
return this.db.collection<SourceTableDocumentV3>(this.sourceTableCollectionName(replicationStreamId));
|
|
57
66
|
}
|
|
58
67
|
|
|
68
|
+
bucketData(replicationStreamId: number, definitionId: BucketDefinitionId): mongo.Collection<BucketDataDocumentV3> {
|
|
69
|
+
return this.db.collection<BucketDataDocumentV3>(`bucket_data_${replicationStreamId}_${definitionId}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
listBucketDataCollections(replicationStreamId: number) {
|
|
73
|
+
return this.listCollectionsByPrefix(`bucket_data_${replicationStreamId}_`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async listParameterIndexCollections(replicationStreamId: number) {
|
|
77
|
+
const prefix = `parameter_index_${replicationStreamId}_`;
|
|
78
|
+
const collections = await this.listCollectionsByPrefix(prefix);
|
|
79
|
+
|
|
80
|
+
return collections.map((collection) => ({
|
|
81
|
+
collection,
|
|
82
|
+
indexId: collection.collectionName.slice(prefix.length)
|
|
83
|
+
}));
|
|
84
|
+
}
|
|
85
|
+
|
|
59
86
|
async initializeStreamStorage(replicationStreamId: number) {
|
|
60
|
-
const sourceTables = this.
|
|
61
|
-
const bucketState = this.
|
|
87
|
+
const sourceTables = this.sourceTables(replicationStreamId);
|
|
88
|
+
const bucketState = this.bucketState(replicationStreamId);
|
|
62
89
|
await sourceTables.createIndex(
|
|
63
90
|
{
|
|
64
91
|
connection_id: 1,
|
|
@@ -92,26 +119,4 @@ export class VersionedPowerSyncMongoV3 extends BaseVersionedPowerSyncMongo {
|
|
|
92
119
|
{ name: 'dirty_count' }
|
|
93
120
|
);
|
|
94
121
|
}
|
|
95
|
-
|
|
96
|
-
bucketDataV3(replicationStreamId: number, definitionId: BucketDefinitionId) {
|
|
97
|
-
return this.db.collection<BucketDataDocumentV3>(`bucket_data_${replicationStreamId}_${definitionId}`);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
listBucketDataCollectionsV3(replicationStreamId: number) {
|
|
101
|
-
return this.upstream.listBucketDataCollectionsV3(replicationStreamId);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
async listParameterIndexCollectionsV3(
|
|
105
|
-
replicationStreamId: number
|
|
106
|
-
): Promise<{ collection: mongo.Collection<BucketParameterDocumentV3>; indexId: ParameterIndexId }[]> {
|
|
107
|
-
const prefix = `parameter_index_${replicationStreamId}_`;
|
|
108
|
-
const collections = await this.db.listCollections({ name: new RegExp(`^${prefix}`) }, { nameOnly: true }).toArray();
|
|
109
|
-
|
|
110
|
-
return collections
|
|
111
|
-
.filter((collection) => collection.name.startsWith(prefix))
|
|
112
|
-
.map((collection) => ({
|
|
113
|
-
collection: this.db.collection<BucketParameterDocumentV3>(collection.name),
|
|
114
|
-
indexId: collection.name.slice(prefix.length)
|
|
115
|
-
}));
|
|
116
|
-
}
|
|
117
122
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { BucketDataDoc, BucketKey } from '../common/BucketDataDoc.js';
|
|
2
|
+
import { BucketDataDocumentV3, BucketOperation } from './models.js';
|
|
3
|
+
|
|
4
|
+
export function serializeBucketData(bucket: string, operations: BucketDataDoc[]): BucketDataDocumentV3 {
|
|
5
|
+
const minOp = operations[0].o;
|
|
6
|
+
const maxOp = operations[operations.length - 1].o;
|
|
7
|
+
|
|
8
|
+
let totalChecksum = 0n;
|
|
9
|
+
let totalSize = 0;
|
|
10
|
+
let maxTargetOp: bigint | null = null;
|
|
11
|
+
|
|
12
|
+
const ops: BucketOperation[] = operations.map((op) => {
|
|
13
|
+
totalChecksum += op.checksum;
|
|
14
|
+
totalSize += op.data?.length ?? 0;
|
|
15
|
+
|
|
16
|
+
if (op.target_op != null && (maxTargetOp == null || op.target_op > maxTargetOp)) {
|
|
17
|
+
maxTargetOp = op.target_op;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
o: op.o,
|
|
22
|
+
op: op.op,
|
|
23
|
+
source_table: op.source_table,
|
|
24
|
+
source_key: op.source_key,
|
|
25
|
+
table: op.table,
|
|
26
|
+
row_id: op.row_id,
|
|
27
|
+
checksum: op.checksum,
|
|
28
|
+
data: op.data
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
_id: {
|
|
34
|
+
b: bucket,
|
|
35
|
+
o: maxOp
|
|
36
|
+
},
|
|
37
|
+
min_op: minOp,
|
|
38
|
+
checksum: totalChecksum,
|
|
39
|
+
count: operations.length,
|
|
40
|
+
size: totalSize,
|
|
41
|
+
target_op: maxTargetOp,
|
|
42
|
+
ops
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function* loadBucketDataDocument(
|
|
47
|
+
context: Pick<BucketKey, 'replicationStreamId' | 'definitionId'>,
|
|
48
|
+
doc: BucketDataDocumentV3
|
|
49
|
+
): Generator<BucketDataDoc> {
|
|
50
|
+
const { _id, ops } = doc;
|
|
51
|
+
const bucketKey = {
|
|
52
|
+
...context,
|
|
53
|
+
bucket: _id.b
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
for (const op of ops) {
|
|
57
|
+
yield {
|
|
58
|
+
bucketKey,
|
|
59
|
+
o: op.o,
|
|
60
|
+
op: op.op,
|
|
61
|
+
source_table: op.source_table,
|
|
62
|
+
source_key: op.source_key,
|
|
63
|
+
table: op.table,
|
|
64
|
+
row_id: op.row_id,
|
|
65
|
+
checksum: op.checksum,
|
|
66
|
+
data: op.data,
|
|
67
|
+
target_op: doc.target_op ?? null
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { BucketDataDoc } from '../common/BucketDataDoc.js';
|
|
2
|
+
|
|
3
|
+
export const DEFAULT_MAX_DOC_SIZE_BYTES = 1024 * 1024; // 1MB
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Split an array of bucket data operations into chunks where each chunk's
|
|
7
|
+
* total data size stays close to `maxDocSizeBytes`.
|
|
8
|
+
*
|
|
9
|
+
* The threshold is a target, not a hard ceiling — a single operation whose
|
|
10
|
+
* `data` field exceeds the limit is placed in its own chunk and is NOT split.
|
|
11
|
+
*/
|
|
12
|
+
export function chunkBucketData(
|
|
13
|
+
operations: BucketDataDoc[],
|
|
14
|
+
maxDocSizeBytes = DEFAULT_MAX_DOC_SIZE_BYTES
|
|
15
|
+
): BucketDataDoc[][] {
|
|
16
|
+
const chunks: BucketDataDoc[][] = [];
|
|
17
|
+
let currentChunk: BucketDataDoc[] = [];
|
|
18
|
+
let currentSize = 0;
|
|
19
|
+
|
|
20
|
+
for (const op of operations) {
|
|
21
|
+
const opSize = op.data?.length ?? 0;
|
|
22
|
+
|
|
23
|
+
if (currentSize + opSize > maxDocSizeBytes && currentChunk.length > 0) {
|
|
24
|
+
chunks.push(currentChunk);
|
|
25
|
+
currentChunk = [];
|
|
26
|
+
currentSize = 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
currentChunk.push(op);
|
|
30
|
+
currentSize += opSize;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (currentChunk.length > 0) {
|
|
34
|
+
chunks.push(currentChunk);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return chunks;
|
|
38
|
+
}
|