@powersync/service-module-mongodb-storage 0.20.0 → 0.22.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 +62 -0
- package/dist/storage/MongoBucketStorage.d.ts +36 -3
- package/dist/storage/MongoBucketStorage.js +372 -207
- package/dist/storage/MongoBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoBucketBatch.d.ts +17 -5
- package/dist/storage/implementation/MongoBucketBatch.js +44 -17
- package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
- package/dist/storage/implementation/MongoCompactor.d.ts +19 -79
- package/dist/storage/implementation/MongoCompactor.js +27 -238
- package/dist/storage/implementation/MongoCompactor.js.map +1 -1
- package/dist/storage/implementation/MongoParameterCompactor.d.ts +83 -10
- package/dist/storage/implementation/MongoParameterCompactor.js +309 -96
- package/dist/storage/implementation/MongoParameterCompactor.js.map +1 -1
- package/dist/storage/implementation/MongoParsedSyncConfigSet.d.ts +9 -1
- package/dist/storage/implementation/MongoParsedSyncConfigSet.js +29 -0
- package/dist/storage/implementation/MongoParsedSyncConfigSet.js.map +1 -1
- package/dist/storage/implementation/MongoPersistedSyncConfigContent.js +4 -2
- package/dist/storage/implementation/MongoPersistedSyncConfigContent.js.map +1 -1
- package/dist/storage/implementation/MongoStorageProvider.js +7 -3
- package/dist/storage/implementation/MongoStorageProvider.js.map +1 -1
- package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +27 -11
- package/dist/storage/implementation/MongoSyncBucketStorage.js +78 -31
- package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoSyncRulesLock.js +21 -9
- package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -1
- package/dist/storage/implementation/MongoWriteBatch.d.ts +49 -0
- package/dist/storage/implementation/MongoWriteBatch.js +149 -0
- package/dist/storage/implementation/MongoWriteBatch.js.map +1 -0
- package/dist/storage/implementation/MongoWriteCheckpointAPI.d.ts +9 -7
- package/dist/storage/implementation/MongoWriteCheckpointAPI.js +9 -30
- package/dist/storage/implementation/MongoWriteCheckpointAPI.js.map +1 -1
- package/dist/storage/implementation/SyncRuleStateUpdate.d.ts +11 -5
- package/dist/storage/implementation/SyncRuleStateUpdate.js +14 -6
- package/dist/storage/implementation/SyncRuleStateUpdate.js.map +1 -1
- package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.d.ts +22 -2
- package/dist/storage/implementation/common/PersistedBatch.d.ts +32 -4
- package/dist/storage/implementation/common/PersistedBatch.js +49 -6
- package/dist/storage/implementation/common/PersistedBatch.js.map +1 -1
- package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.d.ts +3 -0
- package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js +3 -0
- package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js.map +1 -1
- package/dist/storage/implementation/db.d.ts +9 -1
- package/dist/storage/implementation/db.js +27 -1
- package/dist/storage/implementation/db.js.map +1 -1
- package/dist/storage/implementation/models.d.ts +31 -0
- package/dist/storage/implementation/models.js.map +1 -1
- package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +2 -0
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js +33 -4
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -1
- package/dist/storage/implementation/v1/MongoCompactorV1.d.ts +38 -6
- package/dist/storage/implementation/v1/MongoCompactorV1.js +247 -20
- package/dist/storage/implementation/v1/MongoCompactorV1.js.map +1 -1
- package/dist/storage/implementation/v1/MongoParameterCompactorV1.d.ts +16 -2
- package/dist/storage/implementation/v1/MongoParameterCompactorV1.js +20 -9
- package/dist/storage/implementation/v1/MongoParameterCompactorV1.js.map +1 -1
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.d.ts +18 -11
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +65 -16
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js.map +1 -1
- package/dist/storage/implementation/v1/PersistedBatchV1.d.ts +7 -5
- package/dist/storage/implementation/v1/PersistedBatchV1.js +22 -28
- package/dist/storage/implementation/v1/PersistedBatchV1.js.map +1 -1
- package/dist/storage/implementation/v1/models.js +1 -0
- package/dist/storage/implementation/v1/models.js.map +1 -1
- package/dist/storage/implementation/v3/CompactionLease.d.ts +50 -0
- package/dist/storage/implementation/v3/CompactionLease.js +131 -0
- package/dist/storage/implementation/v3/CompactionLease.js.map +1 -0
- package/dist/storage/implementation/v3/MongoBucketBatchV3.d.ts +10 -2
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js +100 -26
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +82 -25
- package/dist/storage/implementation/v3/MongoCompactorV3.js +698 -159
- package/dist/storage/implementation/v3/MongoCompactorV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.d.ts +18 -0
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.js +23 -0
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.js.map +1 -0
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.d.ts +2 -15
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js +60 -92
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js.map +1 -1
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.d.ts +19 -9
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +91 -31
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.d.ts +26 -0
- package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.js +82 -0
- package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.js.map +1 -0
- package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +14 -8
- package/dist/storage/implementation/v3/PersistedBatchV3.js +76 -42
- package/dist/storage/implementation/v3/PersistedBatchV3.js.map +1 -1
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.d.ts +12 -2
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js +42 -3
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js.map +1 -1
- package/dist/storage/implementation/v3/bucket-format.js +2 -0
- package/dist/storage/implementation/v3/bucket-format.js.map +1 -1
- package/dist/storage/implementation/v3/compact-utils.d.ts +107 -0
- package/dist/storage/implementation/v3/compact-utils.js +216 -0
- package/dist/storage/implementation/v3/compact-utils.js.map +1 -0
- package/dist/storage/implementation/v3/compaction-constants.d.ts +5 -0
- package/dist/storage/implementation/v3/compaction-constants.js +6 -0
- package/dist/storage/implementation/v3/compaction-constants.js.map +1 -0
- package/dist/storage/implementation/v3/models.d.ts +84 -4
- package/dist/storage/implementation/v3/models.js.map +1 -1
- package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.d.ts +7 -8
- package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js +16 -5
- package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js.map +1 -1
- package/dist/storage/implementation/v3/object-storage/ObjectStorage.d.ts +14 -11
- package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.d.ts +6 -8
- package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js +17 -11
- package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js.map +1 -1
- package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.d.ts +38 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.js +164 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.js.map +1 -0
- package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.d.ts +37 -12
- package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js +197 -25
- package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js.map +1 -1
- package/dist/storage/implementation/v3/source-table-utils.d.ts +6 -6
- package/dist/storage/implementation/v3/source-table-utils.js +30 -37
- package/dist/storage/implementation/v3/source-table-utils.js.map +1 -1
- package/dist/storage/storage-index.d.ts +2 -1
- package/dist/storage/storage-index.js +1 -0
- package/dist/storage/storage-index.js.map +1 -1
- package/dist/types/types.d.ts +28 -1
- package/dist/types/types.js +28 -7
- package/dist/types/types.js.map +1 -1
- package/dist/utils/test-utils.js +1 -0
- package/dist/utils/test-utils.js.map +1 -1
- package/dist/utils/util.js +3 -1
- package/dist/utils/util.js.map +1 -1
- package/package.json +10 -9
- package/src/storage/MongoBucketStorage.ts +507 -269
- package/src/storage/implementation/MongoBucketBatch.ts +67 -24
- package/src/storage/implementation/MongoCompactor.ts +35 -346
- package/src/storage/implementation/MongoParameterCompactor.ts +388 -102
- package/src/storage/implementation/MongoParsedSyncConfigSet.ts +34 -0
- package/src/storage/implementation/MongoPersistedSyncConfigContent.ts +4 -2
- package/src/storage/implementation/MongoStorageProvider.ts +15 -3
- package/src/storage/implementation/MongoSyncBucketStorage.ts +114 -40
- package/src/storage/implementation/MongoSyncRulesLock.ts +22 -9
- package/src/storage/implementation/MongoWriteBatch.ts +172 -0
- package/src/storage/implementation/MongoWriteCheckpointAPI.ts +16 -46
- package/src/storage/implementation/SyncRuleStateUpdate.ts +13 -6
- package/src/storage/implementation/common/MongoSyncBucketStorageCheckpoint.ts +24 -2
- package/src/storage/implementation/common/PersistedBatch.ts +66 -10
- package/src/storage/implementation/common/VersionedPowerSyncMongoBase.ts +4 -0
- package/src/storage/implementation/db.ts +35 -2
- package/src/storage/implementation/models.ts +34 -0
- package/src/storage/implementation/v1/MongoBucketBatchV1.ts +46 -6
- package/src/storage/implementation/v1/MongoCompactorV1.ts +327 -40
- package/src/storage/implementation/v1/MongoParameterCompactorV1.ts +25 -9
- package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +87 -27
- package/src/storage/implementation/v1/PersistedBatchV1.ts +25 -30
- package/src/storage/implementation/v1/models.ts +1 -0
- package/src/storage/implementation/v3/CompactionLease.ts +152 -0
- package/src/storage/implementation/v3/MongoBucketBatchV3.ts +138 -34
- package/src/storage/implementation/v3/MongoCompactorV3.ts +958 -225
- package/src/storage/implementation/v3/MongoParameterCompactorV3.ts +34 -0
- package/src/storage/implementation/v3/MongoStoppedSyncConfigCleanup.ts +100 -109
- package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +111 -41
- package/src/storage/implementation/v3/MongoWriteCheckpointAPIV3.ts +111 -0
- package/src/storage/implementation/v3/PersistedBatchV3.ts +93 -48
- package/src/storage/implementation/v3/VersionedPowerSyncMongoV3.ts +55 -4
- package/src/storage/implementation/v3/bucket-format.ts +2 -0
- package/src/storage/implementation/v3/compact-utils.ts +320 -0
- package/src/storage/implementation/v3/compaction-constants.ts +5 -0
- package/src/storage/implementation/v3/models.ts +91 -3
- package/src/storage/implementation/v3/object-storage/BucketDataObjectStorage.ts +24 -9
- package/src/storage/implementation/v3/object-storage/ObjectStorage.ts +20 -5
- package/src/storage/implementation/v3/object-storage/ObjectStorageLifecycle.ts +26 -14
- package/src/storage/implementation/v3/object-storage/ObjectStorageUsage.ts +229 -0
- package/src/storage/implementation/v3/object-storage/S3ObjectStorage.ts +279 -29
- package/src/storage/implementation/v3/source-table-utils.ts +39 -41
- package/src/storage/storage-index.ts +2 -0
- package/src/types/types.ts +42 -7
- package/src/utils/test-utils.ts +1 -0
- package/src/utils/util.ts +3 -1
- package/test/src/__snapshots__/storage.test.ts.snap +25 -0
- package/test/src/__snapshots__/storage_sync.test.ts.snap +605 -16
- package/test/src/chunk_compaction_config.test.ts +38 -0
- package/test/src/cleanup-stopped-sync-configs.test.ts +40 -13
- package/test/src/compact-utils.test.ts +226 -0
- package/test/src/helpers/MemoryObjectStorage.ts +14 -6
- package/test/src/object_storage_usage.test.ts +267 -0
- package/test/src/parameter_compacting_v1.test.ts +120 -0
- package/test/src/parameter_compacting_v3.test.ts +192 -0
- package/test/src/parameter_compaction_fence.test.ts +276 -0
- package/test/src/storage.test.ts +273 -22
- package/test/src/storage_compacting.test.ts +968 -246
- package/test/src/storage_s3_checksums.test.ts +3 -4
- package/test/src/storage_s3_compaction_lifecycle.test.ts +138 -12
- package/test/src/storage_s3_reading.test.ts +290 -2
- package/test/src/storage_s3_writing.test.ts +1 -0
- package/test/src/storage_sync.test.ts +336 -41
- package/test/src/util.ts +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -6,6 +6,7 @@ import * as bson from 'bson';
|
|
|
6
6
|
import { BucketDataSource, BucketDefinitionId } from '@powersync/service-sync-rules';
|
|
7
7
|
import { mongoTableId } from '../../../utils/util.js';
|
|
8
8
|
import { EMPTY_DATA } from '../MongoBucketBatchShared.js';
|
|
9
|
+
import { MongoWriteBatch } from '../MongoWriteBatch.js';
|
|
9
10
|
import {
|
|
10
11
|
BucketStateUpdate,
|
|
11
12
|
PersistedBatch,
|
|
@@ -25,7 +26,9 @@ import {
|
|
|
25
26
|
export class PersistedBatchV1 extends PersistedBatch {
|
|
26
27
|
declare protected readonly db: VersionedPowerSyncMongoV1;
|
|
27
28
|
|
|
28
|
-
|
|
29
|
+
// Each upsert supplies the complete source-record state. Only its final
|
|
30
|
+
// state matters within a flush; bucket and parameter history is kept separately.
|
|
31
|
+
currentData = new Map<string, mongo.AnyBulkWriteOperation<CurrentDataDocument>>();
|
|
29
32
|
|
|
30
33
|
protected checkDefinitionId(_definitionId: BucketDefinitionId | null): BucketDefinitionId {
|
|
31
34
|
// V1 storage doesn't persist the id, and we don't use it.
|
|
@@ -94,7 +97,7 @@ export class PersistedBatchV1 extends PersistedBatch {
|
|
|
94
97
|
}
|
|
95
98
|
|
|
96
99
|
hardDeleteCurrentData(sourceTableId: bson.ObjectId, replicaId: storage.ReplicaId) {
|
|
97
|
-
this.currentData.
|
|
100
|
+
this.currentData.set(this.currentDataKey(sourceTableId, replicaId), {
|
|
98
101
|
deleteOne: {
|
|
99
102
|
filter: { _id: this.currentDataId(sourceTableId, replicaId) }
|
|
100
103
|
}
|
|
@@ -124,7 +127,7 @@ export class PersistedBatchV1 extends PersistedBatch {
|
|
|
124
127
|
return lookup.lookup;
|
|
125
128
|
});
|
|
126
129
|
|
|
127
|
-
this.currentData.
|
|
130
|
+
this.currentData.set(this.currentDataKey(values.sourceTableId, values.replicaId), {
|
|
128
131
|
updateOne: {
|
|
129
132
|
filter: { _id: this.currentDataId(values.sourceTableId, values.replicaId) },
|
|
130
133
|
update: {
|
|
@@ -141,57 +144,45 @@ export class PersistedBatchV1 extends PersistedBatch {
|
|
|
141
144
|
}
|
|
142
145
|
|
|
143
146
|
protected get currentDataCount() {
|
|
144
|
-
return this.currentData.
|
|
147
|
+
return this.currentData.size;
|
|
145
148
|
}
|
|
146
149
|
|
|
147
|
-
protected async
|
|
148
|
-
|
|
150
|
+
protected async queueBucketData(writes: MongoWriteBatch) {
|
|
151
|
+
writes.bulkWriteUnordered(
|
|
152
|
+
this.db.bucketDataV1,
|
|
149
153
|
this.bucketData.map((document) => ({
|
|
150
154
|
insertOne: {
|
|
151
155
|
document: serializeBucketDataV1(document)
|
|
152
156
|
}
|
|
153
|
-
}))
|
|
154
|
-
{
|
|
155
|
-
session,
|
|
156
|
-
ordered: false
|
|
157
|
-
}
|
|
157
|
+
}))
|
|
158
158
|
);
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
protected
|
|
162
|
-
|
|
161
|
+
protected queueBucketParameters(writes: MongoWriteBatch): void {
|
|
162
|
+
writes.bulkWriteUnordered(
|
|
163
|
+
this.db.parameterIndexV1,
|
|
163
164
|
this.bucketParameters.map((document) => ({
|
|
164
165
|
insertOne: {
|
|
165
166
|
document: taggedBucketParameterDocumentToV1(document)
|
|
166
167
|
}
|
|
167
|
-
}))
|
|
168
|
-
{
|
|
169
|
-
session,
|
|
170
|
-
ordered: false
|
|
171
|
-
}
|
|
168
|
+
}))
|
|
172
169
|
);
|
|
173
170
|
}
|
|
174
171
|
|
|
175
|
-
protected
|
|
176
|
-
if (this.currentData.
|
|
172
|
+
protected queueCurrentData(writes: MongoWriteBatch): void {
|
|
173
|
+
if (this.currentData.size == 0) {
|
|
177
174
|
return;
|
|
178
175
|
}
|
|
179
176
|
|
|
180
|
-
|
|
181
|
-
session,
|
|
182
|
-
ordered: true
|
|
183
|
-
});
|
|
177
|
+
writes.bulkWriteUnordered(this.db.sourceRecordsV1, [...this.currentData.values()]);
|
|
184
178
|
}
|
|
185
179
|
|
|
186
|
-
protected
|
|
187
|
-
|
|
188
|
-
session,
|
|
189
|
-
ordered: false
|
|
190
|
-
});
|
|
180
|
+
protected queueBucketStates(writes: MongoWriteBatch): void {
|
|
181
|
+
writes.bulkWriteUnordered(this.db.bucketStateV1, this.getBucketStateUpdates());
|
|
191
182
|
}
|
|
192
183
|
|
|
193
184
|
protected resetCurrentData() {
|
|
194
|
-
this.currentData
|
|
185
|
+
this.currentData.clear();
|
|
195
186
|
}
|
|
196
187
|
|
|
197
188
|
private getBucketStateUpdates(): mongo.AnyBulkWriteOperation<BucketStateDocumentV1>[] {
|
|
@@ -219,6 +210,10 @@ export class PersistedBatchV1 extends PersistedBatch {
|
|
|
219
210
|
});
|
|
220
211
|
}
|
|
221
212
|
|
|
213
|
+
private currentDataKey(sourceTableId: bson.ObjectId, replicaId: storage.ReplicaId): string {
|
|
214
|
+
return Buffer.from(bson.serialize({ t: sourceTableId, k: replicaId })).toString('base64');
|
|
215
|
+
}
|
|
216
|
+
|
|
222
217
|
private currentDataId(sourceTableId: bson.ObjectId, replicaId: storage.ReplicaId): SourceKey {
|
|
223
218
|
return {
|
|
224
219
|
g: this.group_id,
|
|
@@ -48,6 +48,7 @@ export function serializeBucketDataV1(document: BucketDataDoc): BucketDataDocume
|
|
|
48
48
|
op: document.op,
|
|
49
49
|
source_table: document.source_table,
|
|
50
50
|
source_key: document.source_key,
|
|
51
|
+
subkey: document.subkey,
|
|
51
52
|
table: document.table,
|
|
52
53
|
row_id: document.row_id,
|
|
53
54
|
checksum: document.checksum,
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { mongo } from '@powersync/lib-service-mongodb';
|
|
2
|
+
import { InternalOpId } from '@powersync/service-core';
|
|
3
|
+
import { CompactionLeaseLostError } from '../MongoCompactor.js';
|
|
4
|
+
import { BucketStateDocumentV3 } from './models.js';
|
|
5
|
+
|
|
6
|
+
const LEASE_RENEW_INTERVAL_MS = 60 * 1000;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Filters for buckets that have no lease or an expired lease.
|
|
10
|
+
*/
|
|
11
|
+
export const AVAILABLE_LEASE_EXPR = {
|
|
12
|
+
// $$NOW is evaluated by MongoDB, avoiding lease expiry races due
|
|
13
|
+
// to clocks on separate compact workers.
|
|
14
|
+
$expr: {
|
|
15
|
+
$or: [{ $eq: [{ $type: '$compact_lease' }, 'missing'] }, { $lte: ['$compact_lease.expires_at', '$$NOW'] }]
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Owns one V3 bucket-compaction lease, including its server-time renewal and
|
|
21
|
+
* the owner-fenced operations which release it.
|
|
22
|
+
*
|
|
23
|
+
* This is intended as a way to reduce redundant work if multiple jobs attempt to
|
|
24
|
+
* compact the same bucket concurrently, but it's not an absolute safety guarantee.
|
|
25
|
+
* The individual operations must still be designed to be safe with concurrent compacting.
|
|
26
|
+
*/
|
|
27
|
+
export class CompactionLease implements AsyncDisposable {
|
|
28
|
+
readonly startedAt: Date;
|
|
29
|
+
readonly lastOp: InternalOpId;
|
|
30
|
+
|
|
31
|
+
private timer: NodeJS.Timeout | undefined;
|
|
32
|
+
private renewalInFlight = false;
|
|
33
|
+
private renewalError: unknown;
|
|
34
|
+
private finalizing = false;
|
|
35
|
+
private finished = false;
|
|
36
|
+
|
|
37
|
+
private constructor(
|
|
38
|
+
private readonly collection: mongo.Collection<BucketStateDocumentV3>,
|
|
39
|
+
readonly state: BucketStateDocumentV3,
|
|
40
|
+
readonly id: mongo.ObjectId,
|
|
41
|
+
private readonly durationMs: number
|
|
42
|
+
) {
|
|
43
|
+
this.startedAt = new Date(state.compact_lease!.expires_at.getTime() - durationMs);
|
|
44
|
+
this.lastOp = state.last_op;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
static async claim(
|
|
48
|
+
collection: mongo.Collection<BucketStateDocumentV3>,
|
|
49
|
+
filter: mongo.Filter<BucketStateDocumentV3>,
|
|
50
|
+
sort: mongo.Sort | undefined,
|
|
51
|
+
durationMs: number
|
|
52
|
+
): Promise<CompactionLease | null> {
|
|
53
|
+
const id = new mongo.ObjectId();
|
|
54
|
+
const state = await collection.findOneAndUpdate(
|
|
55
|
+
{
|
|
56
|
+
$and: [filter, AVAILABLE_LEASE_EXPR]
|
|
57
|
+
},
|
|
58
|
+
[
|
|
59
|
+
{
|
|
60
|
+
$set: {
|
|
61
|
+
compact_lease: {
|
|
62
|
+
id,
|
|
63
|
+
expires_at: { $dateAdd: { startDate: '$$NOW', unit: 'millisecond', amount: durationMs } }
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
{ sort, returnDocument: 'after' }
|
|
69
|
+
);
|
|
70
|
+
return state == null ? null : new CompactionLease(collection, state, id, durationMs);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
startRenewal() {
|
|
74
|
+
const interval = Math.max(1, Math.min(LEASE_RENEW_INTERVAL_MS, Math.floor(this.durationMs / 2)));
|
|
75
|
+
this.timer = setInterval(() => {
|
|
76
|
+
if (this.renewalInFlight) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
this.renewalInFlight = true;
|
|
80
|
+
void this.renew()
|
|
81
|
+
.catch((error) => {
|
|
82
|
+
this.renewalError = error;
|
|
83
|
+
})
|
|
84
|
+
.finally(() => {
|
|
85
|
+
this.renewalInFlight = false;
|
|
86
|
+
});
|
|
87
|
+
}, interval);
|
|
88
|
+
this.timer.unref();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async throwIfLost() {
|
|
92
|
+
if (this.renewalError != null) {
|
|
93
|
+
throw this.renewalError;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async reschedule(nextCompactCheck: mongo.Document) {
|
|
98
|
+
await this.finish([{ $set: { next_compact_check: nextCompactCheck } }, { $unset: 'compact_lease' }]);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async finalize(update: mongo.Document) {
|
|
102
|
+
await this.finish([{ $set: update }, { $unset: 'compact_lease' }]);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async [Symbol.asyncDispose]() {
|
|
106
|
+
if (this.timer != null) {
|
|
107
|
+
clearInterval(this.timer);
|
|
108
|
+
this.timer = undefined;
|
|
109
|
+
}
|
|
110
|
+
if (this.finished) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
this.finalizing = true;
|
|
114
|
+
await this.collection.updateOne(this.filter, { $unset: { compact_lease: '' } });
|
|
115
|
+
this.finished = true;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private get filter(): mongo.Filter<BucketStateDocumentV3> {
|
|
119
|
+
return { _id: this.state._id, 'compact_lease.id': this.id };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private async renew() {
|
|
123
|
+
const result = await this.collection.updateOne(this.filter, [
|
|
124
|
+
{
|
|
125
|
+
$set: {
|
|
126
|
+
'compact_lease.expires_at': {
|
|
127
|
+
$dateAdd: { startDate: '$$NOW', unit: 'millisecond', amount: this.durationMs }
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
]);
|
|
132
|
+
// A finalization in progress owns the authoritative fenced result.
|
|
133
|
+
if (result.matchedCount != 1 && !this.finalizing) {
|
|
134
|
+
throw new CompactionLeaseLostError(`Lost compaction lease for bucket ${this.state._id.b}`);
|
|
135
|
+
}
|
|
136
|
+
// A successful renewal confirms that a transient failure has passed. A
|
|
137
|
+
// lease-loss error remains sticky: it means another worker may own it.
|
|
138
|
+
if (!(this.renewalError instanceof CompactionLeaseLostError)) {
|
|
139
|
+
this.renewalError = undefined;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private async finish(update: mongo.Document[]) {
|
|
144
|
+
await this.throwIfLost();
|
|
145
|
+
this.finalizing = true;
|
|
146
|
+
const result = await this.collection.updateOne(this.filter, update);
|
|
147
|
+
if (result.matchedCount != 1) {
|
|
148
|
+
throw new CompactionLeaseLostError(`Lost compaction lease for bucket ${this.state._id.b}`);
|
|
149
|
+
}
|
|
150
|
+
this.finished = true;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
import * as lib_mongo from '@powersync/lib-service-mongodb';
|
|
2
|
-
import { ReplicationAssertionError } from '@powersync/lib-services-framework';
|
|
3
|
-
import { storage } from '@powersync/service-core';
|
|
2
|
+
import { ReplicationAssertionError, ServiceAssertionError } from '@powersync/lib-services-framework';
|
|
3
|
+
import { InternalOpId, storage } from '@powersync/service-core';
|
|
4
|
+
import { EventDefinitionId } from '@powersync/service-sync-rules';
|
|
4
5
|
import * as bson from 'bson';
|
|
5
6
|
import { mongoTableId } from '../../../utils/util.js';
|
|
6
7
|
import { canCheckpointState } from '../CheckpointState.js';
|
|
7
8
|
import { MongoBucketBatch, MongoBucketBatchOptions } from '../MongoBucketBatch.js';
|
|
8
9
|
import { MongoParsedSyncConfigSet } from '../MongoParsedSyncConfigSet.js';
|
|
9
|
-
import {
|
|
10
|
+
import { MongoWriteBatch } from '../MongoWriteBatch.js';
|
|
11
|
+
import { stopReplicationStreamPipeline } from '../SyncRuleStateUpdate.js';
|
|
10
12
|
import { PersistedBatch } from '../common/PersistedBatch.js';
|
|
11
13
|
import { SourceRecordStore } from '../common/SourceRecordStore.js';
|
|
12
14
|
import { PersistedBatchV3 } from './PersistedBatchV3.js';
|
|
13
15
|
import { SourceRecordStoreV3 } from './SourceRecordStoreV3.js';
|
|
14
16
|
import { VersionedPowerSyncMongoV3 } from './VersionedPowerSyncMongoV3.js';
|
|
15
|
-
import { ReplicationStreamDocumentV3, SourceTableDocumentV3 } from './models.js';
|
|
17
|
+
import { CustomCheckpointRequestDocumentV3, ReplicationStreamDocumentV3, SourceTableDocumentV3 } from './models.js';
|
|
16
18
|
import {
|
|
17
19
|
createNewSourceTable,
|
|
18
|
-
designateEventCarrier,
|
|
19
20
|
overlappingSourceTableFilter,
|
|
20
21
|
planSourceTableReconciliation,
|
|
21
22
|
sourceTableDesiredResolution,
|
|
@@ -27,6 +28,10 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
27
28
|
declare readonly db: VersionedPowerSyncMongoV3;
|
|
28
29
|
|
|
29
30
|
private readonly store: SourceRecordStore;
|
|
31
|
+
/** Avoid repeating idempotent createIndexes calls for every checkpoint flush. */
|
|
32
|
+
private readonly initializedCustomCheckpointEventIds = new Set<EventDefinitionId>();
|
|
33
|
+
// Used only to validate supplied checkpoint ids; it does not initialize collections.
|
|
34
|
+
private readonly knownEventIds: ReadonlySet<EventDefinitionId>;
|
|
30
35
|
private readonly syncConfigIds: bson.ObjectId[];
|
|
31
36
|
private needsActivationV3 = true;
|
|
32
37
|
private lastWaitingLogThrottledV3 = 0;
|
|
@@ -38,6 +43,7 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
38
43
|
throw new ReplicationAssertionError('Missing sync config id for v3 batch');
|
|
39
44
|
}
|
|
40
45
|
this.syncConfigIds = syncConfigIds;
|
|
46
|
+
this.knownEventIds = new Set(this.mapping.allEventDefinitionIds());
|
|
41
47
|
this.store = new SourceRecordStoreV3(this.db, this.replicationStreamId, this.mapping);
|
|
42
48
|
}
|
|
43
49
|
|
|
@@ -45,7 +51,9 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
45
51
|
return new PersistedBatchV3(this.db, this.replicationStreamId, this.mapping, writtenSize, {
|
|
46
52
|
logger: this.logger,
|
|
47
53
|
objectStorage: this.options.objectStorage,
|
|
48
|
-
inlineThresholdBytes: this.options.inlineThresholdBytes
|
|
54
|
+
inlineThresholdBytes: this.options.inlineThresholdBytes,
|
|
55
|
+
signal: this.options.signal,
|
|
56
|
+
objectStorageUsageWriterId: this.objectStorageUsageWriterId
|
|
49
57
|
});
|
|
50
58
|
}
|
|
51
59
|
|
|
@@ -53,14 +61,12 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
53
61
|
return this.store;
|
|
54
62
|
}
|
|
55
63
|
|
|
56
|
-
protected override
|
|
57
|
-
session: lib_mongo.mongo.ClientSession,
|
|
58
|
-
lastOp: bigint
|
|
59
|
-
): Promise<void> {
|
|
64
|
+
protected override onReplicationTransactionFlush(writes: MongoWriteBatch, lastOp: bigint): void {
|
|
60
65
|
// Durably advance the stream-level head of persisted ops within the flush transaction.
|
|
61
66
|
// This ensures a checkpoint created later (even by an empty commit, or by a freshly-appended
|
|
62
67
|
// config that replicates nothing) covers all ops persisted before a potential crash.
|
|
63
|
-
|
|
68
|
+
writes.updateOne(
|
|
69
|
+
this.db.sync_rules,
|
|
64
70
|
{
|
|
65
71
|
_id: this.replicationStreamId
|
|
66
72
|
},
|
|
@@ -68,8 +74,7 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
68
74
|
$max: {
|
|
69
75
|
last_persisted_op: lastOp
|
|
70
76
|
}
|
|
71
|
-
}
|
|
72
|
-
{ session }
|
|
77
|
+
}
|
|
73
78
|
);
|
|
74
79
|
}
|
|
75
80
|
|
|
@@ -90,7 +95,8 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
90
95
|
this.syncConfigIds.map((id) => id.toHexString()),
|
|
91
96
|
table.ref,
|
|
92
97
|
bucketDataSourceIds,
|
|
93
|
-
parameterLookupSourceIds
|
|
98
|
+
parameterLookupSourceIds,
|
|
99
|
+
[...table.eventDefinitionIds!]
|
|
94
100
|
)
|
|
95
101
|
);
|
|
96
102
|
}
|
|
@@ -132,7 +138,7 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
132
138
|
.sourceRecords(this.replicationStreamId, mongoTableId(table.id))
|
|
133
139
|
.drop()
|
|
134
140
|
.catch((error) => {
|
|
135
|
-
if (lib_mongo.
|
|
141
|
+
if (lib_mongo.isMongoNamespaceNotFoundError(error)) {
|
|
136
142
|
return;
|
|
137
143
|
}
|
|
138
144
|
throw error;
|
|
@@ -146,6 +152,7 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
146
152
|
const parsedOverride = options.parsedSyncConfig as MongoParsedSyncConfigSet | undefined;
|
|
147
153
|
const syncConfig = parsedOverride?.hydratedSyncConfig ?? this.sync_rules;
|
|
148
154
|
const mapping = parsedOverride?.mapping ?? this.mapping;
|
|
155
|
+
const eventById = (parsedOverride ?? this.options.parsedSyncConfig).eventById;
|
|
149
156
|
|
|
150
157
|
const { connection_id, source } = options;
|
|
151
158
|
const reconcile = options.reconcileSourceTables ?? storage.defaultSourceTableReconciler;
|
|
@@ -173,7 +180,7 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
173
180
|
.toArray();
|
|
174
181
|
|
|
175
182
|
const candidateTables = candidateDocs.map((doc) =>
|
|
176
|
-
sourceTableFromDocument(doc, source.connectionTag, syncConfig, mapping)
|
|
183
|
+
sourceTableFromDocument(doc, source.connectionTag, syncConfig, mapping, eventById)
|
|
177
184
|
);
|
|
178
185
|
const candidates = candidateTables.map((table) => table.clone());
|
|
179
186
|
const resolution = await reconcile({ source, candidates });
|
|
@@ -191,7 +198,7 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
191
198
|
storeCurrentData: source.sendsCompleteRows !== true,
|
|
192
199
|
syncConfig,
|
|
193
200
|
mapping,
|
|
194
|
-
desired: sourceTableDesiredResolution(syncConfig, source, mapping),
|
|
201
|
+
desired: sourceTableDesiredResolution(syncConfig, source, mapping, eventById),
|
|
195
202
|
sourceCompatibleTables: resolution.compatibleTables,
|
|
196
203
|
newTableSourceMetadata: resolution.newTableValues.sourceMetadata
|
|
197
204
|
};
|
|
@@ -209,7 +216,8 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
209
216
|
{
|
|
210
217
|
$set: {
|
|
211
218
|
bucket_data_source_ids: update.memberships.bucketDataSourceIds,
|
|
212
|
-
parameter_lookup_source_ids: update.memberships.parameterLookupSourceIds
|
|
219
|
+
parameter_lookup_source_ids: update.memberships.parameterLookupSourceIds,
|
|
220
|
+
event_definition_ids: update.memberships.eventDefinitionIds
|
|
213
221
|
}
|
|
214
222
|
},
|
|
215
223
|
{ session }
|
|
@@ -227,12 +235,11 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
227
235
|
plan.tables.push(table);
|
|
228
236
|
}
|
|
229
237
|
|
|
230
|
-
// If memberships are split across multiple source tables, only one may fire events.
|
|
231
|
-
designateEventCarrier(plan.tables, context.desired.triggersEvent);
|
|
232
|
-
|
|
233
238
|
result = {
|
|
234
239
|
tables: plan.tables,
|
|
235
|
-
dropTables: plan.dropDocs.map((doc) =>
|
|
240
|
+
dropTables: plan.dropDocs.map((doc) =>
|
|
241
|
+
sourceTableFromDocument(doc, context.connectionTag, syncConfig, mapping, eventById)
|
|
242
|
+
)
|
|
236
243
|
};
|
|
237
244
|
});
|
|
238
245
|
|
|
@@ -247,12 +254,13 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
247
254
|
return null;
|
|
248
255
|
}
|
|
249
256
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
257
|
+
return sourceTableFromDocument(
|
|
258
|
+
doc,
|
|
259
|
+
table.ref.connectionTag,
|
|
260
|
+
this.sync_rules,
|
|
261
|
+
this.mapping,
|
|
262
|
+
this.options.parsedSyncConfig.eventById
|
|
263
|
+
);
|
|
256
264
|
}
|
|
257
265
|
|
|
258
266
|
async commit(lsn: string, options?: storage.BucketBatchCommitOptions): Promise<storage.CheckpointResult> {
|
|
@@ -519,7 +527,7 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
519
527
|
_id: { $ne: this.replicationStreamId },
|
|
520
528
|
state: { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] }
|
|
521
529
|
},
|
|
522
|
-
|
|
530
|
+
stopReplicationStreamPipeline(),
|
|
523
531
|
{ session }
|
|
524
532
|
);
|
|
525
533
|
activated = true;
|
|
@@ -642,7 +650,9 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
642
650
|
const syncConfigIds = this.relevantSyncConfigIdsForTables(tables);
|
|
643
651
|
|
|
644
652
|
await this.withTransaction(async () => {
|
|
645
|
-
|
|
653
|
+
const writes = this.db.createWriteBatch(session, { ordered: false });
|
|
654
|
+
writes.updateMany(
|
|
655
|
+
this.db.sourceTables(this.replicationStreamId),
|
|
646
656
|
{ _id: { $in: ids } },
|
|
647
657
|
{
|
|
648
658
|
$set: {
|
|
@@ -651,12 +661,12 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
651
661
|
$unset: {
|
|
652
662
|
snapshot_status: 1
|
|
653
663
|
}
|
|
654
|
-
}
|
|
655
|
-
{ session }
|
|
664
|
+
}
|
|
656
665
|
);
|
|
657
666
|
|
|
658
667
|
if (no_checkpoint_before_lsn != null && syncConfigIds.length > 0) {
|
|
659
|
-
|
|
668
|
+
writes.updateOne(
|
|
669
|
+
this.db.sync_rules,
|
|
660
670
|
{
|
|
661
671
|
_id: this.replicationStreamId,
|
|
662
672
|
'sync_configs._id': { $in: syncConfigIds }
|
|
@@ -670,12 +680,12 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
670
680
|
}
|
|
671
681
|
},
|
|
672
682
|
{
|
|
673
|
-
session: this.session,
|
|
674
683
|
// Only set for sync configs that use this table
|
|
675
684
|
arrayFilters: [{ 'config._id': { $in: syncConfigIds } }]
|
|
676
685
|
}
|
|
677
686
|
);
|
|
678
687
|
}
|
|
688
|
+
await writes.execute();
|
|
679
689
|
});
|
|
680
690
|
return tables.map((table) => {
|
|
681
691
|
const copy = table.clone();
|
|
@@ -683,4 +693,98 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
683
693
|
return copy;
|
|
684
694
|
});
|
|
685
695
|
}
|
|
696
|
+
|
|
697
|
+
protected override async batchCreateCustomWriteCheckpoints(
|
|
698
|
+
session: lib_mongo.mongo.ClientSession,
|
|
699
|
+
opId: InternalOpId
|
|
700
|
+
): Promise<void> {
|
|
701
|
+
if (this.write_checkpoint_batch.length == 0) {
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
const checkpointsByEvent = Map.groupBy(this.write_checkpoint_batch, (checkpoint) =>
|
|
706
|
+
this.validateCustomCheckpointEventId(checkpoint.event_id)
|
|
707
|
+
);
|
|
708
|
+
|
|
709
|
+
const writes = this.db.createWriteBatch(session, { ordered: false });
|
|
710
|
+
for (const [eventId, checkpoints] of checkpointsByEvent) {
|
|
711
|
+
this.batchCreateEventCustomWriteCheckpoints(writes, opId, eventId, checkpoints);
|
|
712
|
+
}
|
|
713
|
+
await writes.execute();
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
protected override async prepareCustomWriteCheckpoints(): Promise<void> {
|
|
717
|
+
// Most events never produce custom checkpoints, so create collections lazily
|
|
718
|
+
// only for the event ids present in this batch. This hook runs before the
|
|
719
|
+
// replication transaction because MongoDB cannot create indexes within it.
|
|
720
|
+
const eventIds = new Set(
|
|
721
|
+
this.write_checkpoint_batch.map((checkpoint) => this.validateCustomCheckpointEventId(checkpoint.event_id))
|
|
722
|
+
);
|
|
723
|
+
for (const eventId of eventIds) {
|
|
724
|
+
if (this.initializedCustomCheckpointEventIds.has(eventId)) {
|
|
725
|
+
continue;
|
|
726
|
+
}
|
|
727
|
+
await this.db.initializeCustomCheckpointRequestsCollection({
|
|
728
|
+
replicationStreamId: this.replicationStreamId,
|
|
729
|
+
eventId
|
|
730
|
+
});
|
|
731
|
+
this.initializedCustomCheckpointEventIds.add(eventId);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
private validateCustomCheckpointEventId(eventId: EventDefinitionId | undefined): EventDefinitionId {
|
|
736
|
+
if (eventId == null) {
|
|
737
|
+
throw new ServiceAssertionError('V3 custom checkpoints require an event definition id');
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
if (!this.knownEventIds.has(eventId)) {
|
|
741
|
+
throw new ServiceAssertionError(`Unknown custom checkpoint event definition ${eventId}`);
|
|
742
|
+
}
|
|
743
|
+
return eventId;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
private batchCreateEventCustomWriteCheckpoints(
|
|
747
|
+
writes: MongoWriteBatch,
|
|
748
|
+
opId: InternalOpId,
|
|
749
|
+
eventId: EventDefinitionId,
|
|
750
|
+
checkpoints: storage.CustomWriteCheckpointOptions[]
|
|
751
|
+
): void {
|
|
752
|
+
// A repeated user within an event replaces the complete checkpoint state.
|
|
753
|
+
const uniqueCheckpoints = new Map(checkpoints.map((checkpoint) => [checkpoint.user_id, checkpoint]));
|
|
754
|
+
writes.bulkWriteUnordered(
|
|
755
|
+
this.db.customCheckpointRequests({
|
|
756
|
+
eventId,
|
|
757
|
+
replicationStreamId: this.replicationStreamId
|
|
758
|
+
}),
|
|
759
|
+
[...uniqueCheckpoints.values()].map((checkpointOptions) => {
|
|
760
|
+
const set: Partial<CustomCheckpointRequestDocumentV3> = {
|
|
761
|
+
user_id: checkpointOptions.user_id,
|
|
762
|
+
checkpoint: checkpointOptions.checkpoint,
|
|
763
|
+
op_id: opId
|
|
764
|
+
};
|
|
765
|
+
if (checkpointOptions.checkpoint_requested_at != null) {
|
|
766
|
+
set.checkpoint_requested_at = checkpointOptions.checkpoint_requested_at;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
return {
|
|
770
|
+
updateOne: {
|
|
771
|
+
filter: {
|
|
772
|
+
user_id: checkpointOptions.user_id
|
|
773
|
+
},
|
|
774
|
+
update: {
|
|
775
|
+
$set: set,
|
|
776
|
+
...(checkpointOptions.checkpoint_requested_at == null
|
|
777
|
+
? {
|
|
778
|
+
$unset: {
|
|
779
|
+
checkpoint_requested_at: 1
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
: {})
|
|
783
|
+
},
|
|
784
|
+
upsert: true
|
|
785
|
+
}
|
|
786
|
+
};
|
|
787
|
+
})
|
|
788
|
+
);
|
|
789
|
+
}
|
|
686
790
|
}
|