@powersync/service-module-mongodb-storage 0.0.0-dev-20260803144812 → 0.0.0-dev-20260909133214
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 +83 -7
- package/dist/storage/MongoBucketStorage.d.ts +31 -3
- package/dist/storage/MongoBucketStorage.js +365 -206
- package/dist/storage/MongoBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoBucketBatch.d.ts +15 -4
- package/dist/storage/implementation/MongoBucketBatch.js +35 -9
- 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 +3 -1
- 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/MongoWriteCheckpointAPI.d.ts +9 -7
- package/dist/storage/implementation/MongoWriteCheckpointAPI.js +7 -28
- 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 +27 -0
- package/dist/storage/implementation/common/PersistedBatch.js +51 -10
- package/dist/storage/implementation/common/PersistedBatch.js.map +1 -1
- package/dist/storage/implementation/db.d.ts +4 -1
- package/dist/storage/implementation/db.js +14 -1
- package/dist/storage/implementation/db.js.map +1 -1
- package/dist/storage/implementation/models.d.ts +35 -0
- package/dist/storage/implementation/models.js.map +1 -1
- package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +6 -0
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js +80 -84
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -1
- package/dist/storage/implementation/v1/MongoCompactorV1.d.ts +36 -5
- package/dist/storage/implementation/v1/MongoCompactorV1.js +229 -8
- 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/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 +8 -1
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js +120 -39
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +77 -25
- package/dist/storage/implementation/v3/MongoCompactorV3.js +585 -141
- 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 +92 -32
- 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 +2 -0
- package/dist/storage/implementation/v3/PersistedBatchV3.js +53 -14
- 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 +89 -5
- 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 +8 -4
- 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 +3 -5
- package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js +10 -2
- package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js.map +1 -1
- package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.d.ts +37 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.js +167 -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 +177 -23
- package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js.map +1 -1
- package/dist/storage/implementation/v3/source-table-utils.d.ts +15 -7
- package/dist/storage/implementation/v3/source-table-utils.js +49 -57
- 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 +23 -1
- package/dist/types/types.js +16 -7
- package/dist/types/types.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 +495 -268
- package/src/storage/implementation/MongoBucketBatch.ts +54 -14
- 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 +3 -1
- package/src/storage/implementation/MongoSyncBucketStorage.ts +114 -40
- package/src/storage/implementation/MongoSyncRulesLock.ts +22 -9
- package/src/storage/implementation/MongoWriteCheckpointAPI.ts +12 -44
- package/src/storage/implementation/SyncRuleStateUpdate.ts +13 -6
- package/src/storage/implementation/common/MongoSyncBucketStorageCheckpoint.ts +24 -2
- package/src/storage/implementation/common/PersistedBatch.ts +64 -12
- package/src/storage/implementation/db.ts +21 -2
- package/src/storage/implementation/models.ts +38 -0
- package/src/storage/implementation/v1/MongoBucketBatchV1.ts +107 -96
- package/src/storage/implementation/v1/MongoCompactorV1.ts +303 -19
- package/src/storage/implementation/v1/MongoParameterCompactorV1.ts +25 -9
- package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +87 -27
- 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 +159 -42
- package/src/storage/implementation/v3/MongoCompactorV3.ts +744 -190
- 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 +112 -42
- package/src/storage/implementation/v3/MongoWriteCheckpointAPIV3.ts +111 -0
- package/src/storage/implementation/v3/PersistedBatchV3.ts +58 -15
- 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 +96 -3
- package/src/storage/implementation/v3/object-storage/BucketDataObjectStorage.ts +16 -8
- package/src/storage/implementation/v3/object-storage/ObjectStorage.ts +20 -5
- package/src/storage/implementation/v3/object-storage/ObjectStorageLifecycle.ts +15 -4
- package/src/storage/implementation/v3/object-storage/ObjectStorageUsage.ts +230 -0
- package/src/storage/implementation/v3/object-storage/S3ObjectStorage.ts +257 -27
- package/src/storage/implementation/v3/source-table-utils.ts +82 -76
- package/src/storage/storage-index.ts +2 -0
- package/src/types/types.ts +24 -7
- 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/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 +255 -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 +714 -244
- 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 +243 -1
- package/test/src/storage_s3_writing.test.ts +1 -0
- package/test/src/storage_sync.test.ts +365 -41
- package/test/src/util.ts +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -65,6 +65,11 @@ export interface PersistedBatchOptions {
|
|
|
65
65
|
logger?: Logger;
|
|
66
66
|
objectStorage?: ObjectStorage;
|
|
67
67
|
inlineThresholdBytes?: number;
|
|
68
|
+
/**
|
|
69
|
+
* Aborts in-flight object storage uploads when replication stops.
|
|
70
|
+
*/
|
|
71
|
+
signal?: AbortSignal;
|
|
72
|
+
objectStorageUsageWriterId?: string;
|
|
68
73
|
}
|
|
69
74
|
|
|
70
75
|
/**
|
|
@@ -81,6 +86,8 @@ export abstract class PersistedBatch {
|
|
|
81
86
|
|
|
82
87
|
protected readonly objectStorage?: ObjectStorage;
|
|
83
88
|
protected readonly inlineThresholdBytes: number = DEFAULT_INLINE_THRESHOLD_BYTES;
|
|
89
|
+
protected readonly signal?: AbortSignal;
|
|
90
|
+
protected readonly objectStorageUsageWriterId?: string;
|
|
84
91
|
|
|
85
92
|
/**
|
|
86
93
|
* For debug logging only.
|
|
@@ -102,6 +109,8 @@ export abstract class PersistedBatch {
|
|
|
102
109
|
this.currentSize = writtenSize;
|
|
103
110
|
this.logger = options?.logger ?? defaultLogger;
|
|
104
111
|
this.objectStorage = options?.objectStorage;
|
|
112
|
+
this.signal = options?.signal;
|
|
113
|
+
this.objectStorageUsageWriterId = options?.objectStorageUsageWriterId;
|
|
105
114
|
if (options?.inlineThresholdBytes != null) {
|
|
106
115
|
this.inlineThresholdBytes = options.inlineThresholdBytes;
|
|
107
116
|
}
|
|
@@ -124,7 +133,8 @@ export abstract class PersistedBatch {
|
|
|
124
133
|
remaining_buckets.set(currentBucketKey(mapped), mapped);
|
|
125
134
|
}
|
|
126
135
|
|
|
127
|
-
const
|
|
136
|
+
const subkey = this.persistedSubkey(options.table.id, options.sourceKey);
|
|
137
|
+
const dchecksum = BigInt(utils.hashDelete(subkey));
|
|
128
138
|
|
|
129
139
|
for (const evaluated of options.evaluated) {
|
|
130
140
|
const definitionId = this.getBucketDefinitionId(evaluated.source);
|
|
@@ -159,6 +169,7 @@ export abstract class PersistedBatch {
|
|
|
159
169
|
bucket: evaluated.bucket,
|
|
160
170
|
sourceTableId: options.table.id,
|
|
161
171
|
sourceKey: options.sourceKey,
|
|
172
|
+
subkey,
|
|
162
173
|
table: evaluated.table,
|
|
163
174
|
rowId: evaluated.id,
|
|
164
175
|
checksum: BigInt(checksum),
|
|
@@ -181,6 +192,7 @@ export abstract class PersistedBatch {
|
|
|
181
192
|
op_id,
|
|
182
193
|
sourceTableId: options.table.id,
|
|
183
194
|
sourceKey: options.sourceKey,
|
|
195
|
+
subkey,
|
|
184
196
|
table: bucket.table,
|
|
185
197
|
rowId: bucket.id,
|
|
186
198
|
checksum: dchecksum
|
|
@@ -217,6 +229,14 @@ export abstract class PersistedBatch {
|
|
|
217
229
|
protected abstract checkDefinitionId(definitionId: BucketDefinitionId | null): BucketDefinitionId;
|
|
218
230
|
protected abstract getBucketDefinitionId(bucketSource: BucketDataSource): BucketDefinitionId;
|
|
219
231
|
|
|
232
|
+
/**
|
|
233
|
+
* New bucket operations persist protocol subkeys during replication. Older
|
|
234
|
+
* V1 documents may not have one, so sync reads retain a fallback.
|
|
235
|
+
*/
|
|
236
|
+
protected persistedSubkey(table: storage.SourceTableId, key: storage.ReplicaId): string {
|
|
237
|
+
return replicaIdToSubkey(table, key);
|
|
238
|
+
}
|
|
239
|
+
|
|
220
240
|
protected get bucketDataCount(): number {
|
|
221
241
|
return this.bucketData.length;
|
|
222
242
|
}
|
|
@@ -234,17 +254,45 @@ export abstract class PersistedBatch {
|
|
|
234
254
|
bucket,
|
|
235
255
|
lastOp: op_id,
|
|
236
256
|
incrementCount: 1,
|
|
237
|
-
incrementBytes: bytes
|
|
257
|
+
incrementBytes: bytes,
|
|
258
|
+
incrementChunks: 0
|
|
238
259
|
});
|
|
239
260
|
}
|
|
240
261
|
}
|
|
241
262
|
|
|
263
|
+
/**
|
|
264
|
+
* V3 persists operations in chunks. Keep this separate from incrementBucket:
|
|
265
|
+
* operation counts are known while evaluating rows, while chunk counts and
|
|
266
|
+
* exact persisted bytes are only known after the writer has chunked a flush.
|
|
267
|
+
*/
|
|
268
|
+
protected incrementBucketPersistedChunk(definitionId: BucketDefinitionId, bucket: string, bytes: number) {
|
|
269
|
+
const key = `${definitionId ?? ''}:${bucket}`;
|
|
270
|
+
const existingState = this.bucketStates.get(key);
|
|
271
|
+
if (existingState != null) {
|
|
272
|
+
existingState.incrementChunks += 1;
|
|
273
|
+
existingState.incrementBytes += bytes;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* V3's compactor calculates byte deltas from persisted chunk metadata.
|
|
279
|
+
* Replace the writer's per-operation estimate with those exact sizes before
|
|
280
|
+
* flushing the corresponding bucket state.
|
|
281
|
+
*/
|
|
282
|
+
protected resetBucketPersistedBytes(definitionId: BucketDefinitionId, bucket: string) {
|
|
283
|
+
const state = this.bucketStates.get(`${definitionId ?? ''}:${bucket}`);
|
|
284
|
+
if (state != null) {
|
|
285
|
+
state.incrementBytes = 0;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
242
289
|
protected addBucketDataPut(options: {
|
|
243
290
|
op_id: InternalOpId;
|
|
244
291
|
bucketKey: BucketKey;
|
|
245
292
|
bucket: string;
|
|
246
293
|
sourceTableId: storage.SourceTable['id'];
|
|
247
294
|
sourceKey: storage.ReplicaId;
|
|
295
|
+
subkey: string;
|
|
248
296
|
table: string;
|
|
249
297
|
rowId: string;
|
|
250
298
|
checksum: bigint;
|
|
@@ -256,6 +304,7 @@ export abstract class PersistedBatch {
|
|
|
256
304
|
op: 'PUT',
|
|
257
305
|
source_table: mongoTableId(options.sourceTableId),
|
|
258
306
|
source_key: options.sourceKey,
|
|
307
|
+
subkey: options.subkey,
|
|
259
308
|
table: options.table,
|
|
260
309
|
row_id: options.rowId,
|
|
261
310
|
checksum: options.checksum,
|
|
@@ -268,6 +317,7 @@ export abstract class PersistedBatch {
|
|
|
268
317
|
bucketKey: BucketKey;
|
|
269
318
|
sourceTableId: storage.SourceTable['id'];
|
|
270
319
|
sourceKey: storage.ReplicaId;
|
|
320
|
+
subkey: string;
|
|
271
321
|
table: string;
|
|
272
322
|
rowId: string;
|
|
273
323
|
checksum: bigint;
|
|
@@ -278,6 +328,7 @@ export abstract class PersistedBatch {
|
|
|
278
328
|
op: 'REMOVE',
|
|
279
329
|
source_table: mongoTableId(options.sourceTableId),
|
|
280
330
|
source_key: options.sourceKey,
|
|
331
|
+
subkey: options.subkey,
|
|
281
332
|
table: options.table,
|
|
282
333
|
row_id: options.rowId,
|
|
283
334
|
checksum: options.checksum,
|
|
@@ -321,32 +372,32 @@ export abstract class PersistedBatch {
|
|
|
321
372
|
const replicationLag = Math.round((Date.now() - options.oldestUncommittedChange.getTime()) / 1000);
|
|
322
373
|
|
|
323
374
|
this.logger.info(
|
|
324
|
-
`Flushed ${this.bucketDataCount}
|
|
375
|
+
`Flushed: ${this.bucketDataCount} ops, ${this.bucketParameters.length} index entries, ${
|
|
325
376
|
this.currentDataCount
|
|
326
|
-
}
|
|
377
|
+
} records. ${Math.round(this.currentSize / 1024)}kb in ${duration}ms. Last op_id: ${this.debugLastOpId}. Replication lag: ${replicationLag}s`,
|
|
327
378
|
{
|
|
328
379
|
flushed: {
|
|
329
380
|
duration: duration,
|
|
330
381
|
size: this.currentSize,
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
382
|
+
bucket_ops_count: this.bucketDataCount,
|
|
383
|
+
parameter_indexes_count: this.bucketParameters.length,
|
|
384
|
+
source_records_count: this.currentDataCount,
|
|
334
385
|
replication_lag_seconds: replicationLag
|
|
335
386
|
}
|
|
336
387
|
}
|
|
337
388
|
);
|
|
338
389
|
} else {
|
|
339
390
|
this.logger.info(
|
|
340
|
-
`Flushed ${this.bucketDataCount}
|
|
391
|
+
`Flushed: ${this.bucketDataCount} ops, ${this.bucketParameters.length} index entries, ${
|
|
341
392
|
this.currentDataCount
|
|
342
|
-
}
|
|
393
|
+
} records. ${Math.round(this.currentSize / 1024)}kb in ${duration}ms. Last op_id: ${this.debugLastOpId}`,
|
|
343
394
|
{
|
|
344
395
|
flushed: {
|
|
345
396
|
duration: duration,
|
|
346
397
|
size: this.currentSize,
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
398
|
+
bucket_ops_count: this.bucketDataCount,
|
|
399
|
+
parameter_indexes_count: this.bucketParameters.length,
|
|
400
|
+
source_records_count: this.currentDataCount
|
|
350
401
|
}
|
|
351
402
|
}
|
|
352
403
|
);
|
|
@@ -377,4 +428,5 @@ export interface BucketStateUpdate {
|
|
|
377
428
|
lastOp: InternalOpId;
|
|
378
429
|
incrementCount: number;
|
|
379
430
|
incrementBytes: number;
|
|
431
|
+
incrementChunks: number;
|
|
380
432
|
}
|
|
@@ -24,8 +24,9 @@ import {
|
|
|
24
24
|
CurrentDataDocument
|
|
25
25
|
} from './v1/models.js';
|
|
26
26
|
import { VersionedPowerSyncMongoV1 } from './v1/VersionedPowerSyncMongoV1.js';
|
|
27
|
-
import { BucketDataDocumentV3 } from './v3/models.js';
|
|
28
|
-
import {
|
|
27
|
+
import { BucketDataDocumentV3, CustomCheckpointRequestDocumentV3 } from './v3/models.js';
|
|
28
|
+
import type { CustomCheckpointRequestCollectionSpecifier } from './v3/VersionedPowerSyncMongoV3.js';
|
|
29
|
+
import { OBJECT_STORAGE_USAGE_COLLECTION, VersionedPowerSyncMongoV3 } from './v3/VersionedPowerSyncMongoV3.js';
|
|
29
30
|
|
|
30
31
|
export interface PowerSyncMongoOptions {
|
|
31
32
|
/**
|
|
@@ -137,6 +138,20 @@ export class PowerSyncMongo {
|
|
|
137
138
|
return `source_table_${replicationStreamId}`;
|
|
138
139
|
}
|
|
139
140
|
|
|
141
|
+
customCheckpointRequestCollectionName(options: CustomCheckpointRequestCollectionSpecifier) {
|
|
142
|
+
return `custom_checkpoint_requests_${options.replicationStreamId}_${options.eventId}`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async listCustomCheckpointRequestCollections(
|
|
146
|
+
replicationStreamId?: number
|
|
147
|
+
): Promise<mongo.Collection<CustomCheckpointRequestDocumentV3>[]> {
|
|
148
|
+
const prefix =
|
|
149
|
+
replicationStreamId == null
|
|
150
|
+
? 'custom_checkpoint_requests_'
|
|
151
|
+
: `custom_checkpoint_requests_${replicationStreamId}_`;
|
|
152
|
+
return this.collectionsByPrefix(prefix);
|
|
153
|
+
}
|
|
154
|
+
|
|
140
155
|
async listSourceTableCollections(
|
|
141
156
|
replicationStreamId?: number
|
|
142
157
|
): Promise<mongo.Collection<CommonSourceTableDocument>[]> {
|
|
@@ -178,6 +193,10 @@ export class PowerSyncMongo {
|
|
|
178
193
|
await this.locks.deleteMany({});
|
|
179
194
|
await this.bucket_state.deleteMany({});
|
|
180
195
|
await this.custom_write_checkpoints.deleteMany({});
|
|
196
|
+
await this.db.collection(OBJECT_STORAGE_USAGE_COLLECTION).deleteMany({});
|
|
197
|
+
for (const collection of await this.listCustomCheckpointRequestCollections()) {
|
|
198
|
+
await collection.drop();
|
|
199
|
+
}
|
|
181
200
|
}
|
|
182
201
|
|
|
183
202
|
/**
|
|
@@ -74,6 +74,12 @@ export interface BucketDataProperties {
|
|
|
74
74
|
op: OpType;
|
|
75
75
|
source_table?: bson.ObjectId;
|
|
76
76
|
source_key?: ReplicaId;
|
|
77
|
+
/**
|
|
78
|
+
* Stable identifier for the source record in the client protocol.
|
|
79
|
+
*
|
|
80
|
+
* This is derived from (source_table, source_key), so persistence is optional but preferred.
|
|
81
|
+
*/
|
|
82
|
+
subkey?: string;
|
|
77
83
|
table?: string;
|
|
78
84
|
row_id?: string;
|
|
79
85
|
checksum: bigint;
|
|
@@ -110,6 +116,10 @@ export interface SourceTableDocument {
|
|
|
110
116
|
replica_id_columns2: { name: string; type_oid?: number; type?: string }[] | undefined;
|
|
111
117
|
snapshot_done: boolean | undefined;
|
|
112
118
|
snapshot_status: SourceTableDocumentSnapshotStatus | undefined;
|
|
119
|
+
/**
|
|
120
|
+
* Source-specific metadata. Absent for legacy records.
|
|
121
|
+
*/
|
|
122
|
+
source_metadata?: storage.JsonValue;
|
|
113
123
|
}
|
|
114
124
|
|
|
115
125
|
export interface SourceTableDocumentSnapshotStatus {
|
|
@@ -211,6 +221,34 @@ export interface SyncRuleDocumentBase {
|
|
|
211
221
|
} | null;
|
|
212
222
|
|
|
213
223
|
storage_version?: number;
|
|
224
|
+
|
|
225
|
+
/** Optional operator-supplied label identifying the sync config version. */
|
|
226
|
+
version_label?: string | null;
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Incremental parameter compaction state for the replication stream.
|
|
230
|
+
*
|
|
231
|
+
* Operation ids are allocated across all parameter indexes of a stream, so this is
|
|
232
|
+
* stream-level state, shared by all sync configs of the stream.
|
|
233
|
+
*/
|
|
234
|
+
parameter_compaction?: {
|
|
235
|
+
/**
|
|
236
|
+
* The exclusive operation-id boundary through which every parameter index in this stream has
|
|
237
|
+
* been compacted.
|
|
238
|
+
*/
|
|
239
|
+
compacted_before: InternalOpId;
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Parameter entries below this boundary may no longer be available for checkpoint change
|
|
243
|
+
* detection, since compaction may have deleted them.
|
|
244
|
+
*
|
|
245
|
+
* This is advanced before the first delete of a compaction pass, while
|
|
246
|
+
* {@link compacted_before} is only advanced after every delete of the pass completed.
|
|
247
|
+
* A checkpoint transition starting below this boundary must conservatively invalidate all
|
|
248
|
+
* parameter buckets, since the individual changes may no longer be available.
|
|
249
|
+
*/
|
|
250
|
+
checkpoint_changes_invalid_before?: InternalOpId;
|
|
251
|
+
};
|
|
214
252
|
}
|
|
215
253
|
|
|
216
254
|
export interface SyncRuleCheckpointFields {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { mongo } from '@powersync/lib-service-mongodb';
|
|
1
2
|
import { ReplicationAssertionError } from '@powersync/lib-services-framework';
|
|
2
3
|
import { ColumnDescriptor, InternalOpId, SourceTable, storage } from '@powersync/service-core';
|
|
4
|
+
import { HydratedSyncConfig } from '@powersync/service-sync-rules';
|
|
3
5
|
import * as bson from 'bson';
|
|
4
6
|
import { mongoTableId } from '../../../utils/util.js';
|
|
5
7
|
import { calculateCheckpointState } from '../CheckpointState.js';
|
|
@@ -9,7 +11,7 @@ import { SourceRecordStore } from '../common/SourceRecordStore.js';
|
|
|
9
11
|
import { PersistedBatchV1 } from './PersistedBatchV1.js';
|
|
10
12
|
import { SourceRecordStoreV1 } from './SourceRecordStoreV1.js';
|
|
11
13
|
import { VersionedPowerSyncMongoV1 } from './VersionedPowerSyncMongoV1.js';
|
|
12
|
-
import { SyncRuleDocumentV1 } from './models.js';
|
|
14
|
+
import { SourceTableDocumentV1, SyncRuleDocumentV1 } from './models.js';
|
|
13
15
|
|
|
14
16
|
export class MongoBucketBatchV1 extends MongoBucketBatch {
|
|
15
17
|
declare readonly db: VersionedPowerSyncMongoV1;
|
|
@@ -49,8 +51,39 @@ export class MongoBucketBatchV1 extends MongoBucketBatch {
|
|
|
49
51
|
// No-op for V1: source records live in a shared collection.
|
|
50
52
|
}
|
|
51
53
|
|
|
54
|
+
protected override async batchCreateCustomWriteCheckpoints(
|
|
55
|
+
session: mongo.ClientSession,
|
|
56
|
+
opId: InternalOpId
|
|
57
|
+
): Promise<void> {
|
|
58
|
+
await this.db.custom_write_checkpoints.bulkWrite(
|
|
59
|
+
this.write_checkpoint_batch.map((checkpointOptions) => {
|
|
60
|
+
const set: Record<string, unknown> = {
|
|
61
|
+
checkpoint: checkpointOptions.checkpoint,
|
|
62
|
+
sync_rules_id: checkpointOptions.sync_rules_id,
|
|
63
|
+
op_id: opId
|
|
64
|
+
};
|
|
65
|
+
if (checkpointOptions.checkpoint_requested_at != null) {
|
|
66
|
+
set.checkpoint_requested_at = checkpointOptions.checkpoint_requested_at;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
updateOne: {
|
|
71
|
+
filter: { user_id: checkpointOptions.user_id, sync_rules_id: checkpointOptions.sync_rules_id },
|
|
72
|
+
update: {
|
|
73
|
+
$set: set,
|
|
74
|
+
...(checkpointOptions.checkpoint_requested_at == null ? { $unset: { checkpoint_requested_at: 1 } } : {})
|
|
75
|
+
},
|
|
76
|
+
upsert: true
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}),
|
|
80
|
+
{ session }
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
52
84
|
async resolveTables(options: storage.ResolveTablesOptions): Promise<storage.ResolveTablesResult> {
|
|
53
85
|
const syncRules = options.parsedSyncConfig?.hydratedSyncConfig ?? this.sync_rules;
|
|
86
|
+
const reconcile = options.reconcileSourceTables ?? storage.defaultSourceTableReconciler;
|
|
54
87
|
const { connection_id, source } = options;
|
|
55
88
|
const { schema, name, objectId, replicaIdColumns, connectionTag, sendsCompleteRows } = source;
|
|
56
89
|
|
|
@@ -63,20 +96,38 @@ export class MongoBucketBatchV1 extends MongoBucketBatch {
|
|
|
63
96
|
let result: storage.ResolveTablesResult | null = null;
|
|
64
97
|
await this.db.client.withSession(async (session) => {
|
|
65
98
|
const col = this.db.sourceTablesV1(this.replicationStreamId);
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
schema_name: schema,
|
|
70
|
-
table_name: name,
|
|
71
|
-
replica_id_columns2: normalizedReplicaIdColumns
|
|
72
|
-
};
|
|
99
|
+
|
|
100
|
+
// Find records that overlap by name or relation id.
|
|
101
|
+
const orClauses: Record<string, unknown>[] = [{ schema_name: schema, table_name: name }];
|
|
73
102
|
if (objectId != null) {
|
|
74
|
-
|
|
103
|
+
orClauses.push({ relation_id: objectId });
|
|
75
104
|
}
|
|
105
|
+
const candidateDocs = await col
|
|
106
|
+
.find({ group_id: this.replicationStreamId, connection_id, $or: orClauses }, { session })
|
|
107
|
+
.toArray();
|
|
76
108
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
109
|
+
const candidateTables = candidateDocs.map((doc) =>
|
|
110
|
+
this.hydrateSourceTable(doc, connectionTag, syncRules, sendsCompleteRows)
|
|
111
|
+
);
|
|
112
|
+
const candidates = candidateTables.map((table) => table.clone());
|
|
113
|
+
const resolution = await reconcile({ source, candidates });
|
|
114
|
+
storage.validateSourceTableCandidateResolution(candidates, resolution);
|
|
115
|
+
|
|
116
|
+
for (const { id, sourceMetadata } of storage.diffSourceTableUpdates(candidateTables, resolution)) {
|
|
117
|
+
await col.updateOne({ _id: mongoTableId(id) }, { $set: { source_metadata: sourceMetadata } }, { session });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const materializedResolution = storage.materializeSourceTableResolution(candidateTables, resolution);
|
|
121
|
+
const compatibleTables = materializedResolution.compatibleTables;
|
|
122
|
+
|
|
123
|
+
// V1 keeps one record, preferring one that has already been snapshotted.
|
|
124
|
+
const reuse = compatibleTables.find((table) => table.snapshotComplete) ?? compatibleTables[0] ?? null;
|
|
125
|
+
|
|
126
|
+
let sourceTable: storage.SourceTable;
|
|
127
|
+
if (reuse != null) {
|
|
128
|
+
sourceTable = reuse;
|
|
129
|
+
} else {
|
|
130
|
+
const doc: SourceTableDocumentV1 = {
|
|
80
131
|
_id: options.idGenerator ? (options.idGenerator() as bson.ObjectId) : new bson.ObjectId(),
|
|
81
132
|
group_id: this.replicationStreamId,
|
|
82
133
|
connection_id,
|
|
@@ -86,69 +137,18 @@ export class MongoBucketBatchV1 extends MongoBucketBatch {
|
|
|
86
137
|
replica_id_columns: null,
|
|
87
138
|
replica_id_columns2: normalizedReplicaIdColumns,
|
|
88
139
|
snapshot_done: false,
|
|
89
|
-
snapshot_status: undefined
|
|
140
|
+
snapshot_status: undefined,
|
|
141
|
+
source_metadata: resolution.newTableValues.sourceMetadata
|
|
90
142
|
};
|
|
91
143
|
await col.insertOne(doc, { session });
|
|
144
|
+
sourceTable = this.hydrateSourceTable(doc, connectionTag, syncRules, sendsCompleteRows);
|
|
92
145
|
}
|
|
93
146
|
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
snapshotComplete: doc.snapshot_done ?? true,
|
|
100
|
-
...syncRules.getMatchingSources(source)
|
|
101
|
-
});
|
|
102
|
-
sourceTable.syncEvent = syncRules.tableTriggersEvent(source);
|
|
103
|
-
sourceTable.syncData = sourceTable.bucketDataSources.length > 0;
|
|
104
|
-
sourceTable.syncParameters = sourceTable.parameterLookupSources.length > 0;
|
|
105
|
-
sourceTable.storeCurrentData = sendsCompleteRows !== true;
|
|
106
|
-
sourceTable.snapshotStatus =
|
|
107
|
-
doc.snapshot_status == null
|
|
108
|
-
? undefined
|
|
109
|
-
: {
|
|
110
|
-
lastKey: doc.snapshot_status.last_key?.buffer ?? null,
|
|
111
|
-
totalEstimatedCount: doc.snapshot_status.total_estimated_count,
|
|
112
|
-
replicatedCount: doc.snapshot_status.replicated_count
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
const truncateFilter = [{ schema_name: schema, table_name: name }] as any[];
|
|
116
|
-
if (objectId != null) {
|
|
117
|
-
truncateFilter.push({ relation_id: objectId });
|
|
118
|
-
}
|
|
119
|
-
const truncate = await col
|
|
120
|
-
.find(
|
|
121
|
-
{
|
|
122
|
-
group_id: this.replicationStreamId,
|
|
123
|
-
connection_id,
|
|
124
|
-
_id: { $ne: doc._id },
|
|
125
|
-
$or: truncateFilter
|
|
126
|
-
},
|
|
127
|
-
{ session }
|
|
128
|
-
)
|
|
129
|
-
.toArray();
|
|
130
|
-
const dropTables = truncate.map((dropDoc) => {
|
|
131
|
-
const ref = {
|
|
132
|
-
connectionTag,
|
|
133
|
-
schema: dropDoc.schema_name,
|
|
134
|
-
name: dropDoc.table_name
|
|
135
|
-
};
|
|
136
|
-
const table = new storage.SourceTable({
|
|
137
|
-
id: dropDoc._id,
|
|
138
|
-
ref,
|
|
139
|
-
objectId: dropDoc.relation_id,
|
|
140
|
-
replicaIdColumns:
|
|
141
|
-
dropDoc.replica_id_columns2?.map(
|
|
142
|
-
(c) => ({ name: c.name, typeId: c.type_oid, type: c.type }) satisfies ColumnDescriptor
|
|
143
|
-
) ?? [],
|
|
144
|
-
snapshotComplete: dropDoc.snapshot_done ?? true,
|
|
145
|
-
...syncRules.getMatchingSources(ref)
|
|
146
|
-
});
|
|
147
|
-
table.syncEvent = syncRules.tableTriggersEvent(ref);
|
|
148
|
-
table.syncData = table.bucketDataSources.length > 0;
|
|
149
|
-
table.syncParameters = table.parameterLookupSources.length > 0;
|
|
150
|
-
return table;
|
|
151
|
-
});
|
|
147
|
+
const dropTables = [
|
|
148
|
+
...materializedResolution.incompatibleTables,
|
|
149
|
+
// V1 only keeps one SourceTable per physical table.
|
|
150
|
+
...compatibleTables.filter((table) => !storage.sourceTableIdEquals(table.id, sourceTable.id))
|
|
151
|
+
];
|
|
152
152
|
|
|
153
153
|
result = { tables: [sourceTable], dropTables };
|
|
154
154
|
});
|
|
@@ -156,38 +156,33 @@ export class MongoBucketBatchV1 extends MongoBucketBatch {
|
|
|
156
156
|
return result!;
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const ref = {
|
|
172
|
-
connectionTag: table.ref.connectionTag,
|
|
173
|
-
schema: doc.schema_name,
|
|
174
|
-
name: doc.table_name
|
|
175
|
-
};
|
|
176
|
-
const sourceTable = new storage.SourceTable({
|
|
159
|
+
/**
|
|
160
|
+
* Build a SourceTable from its persisted v1 record.
|
|
161
|
+
*/
|
|
162
|
+
private hydrateSourceTable(
|
|
163
|
+
doc: SourceTableDocumentV1,
|
|
164
|
+
connectionTag: string,
|
|
165
|
+
syncRules: HydratedSyncConfig,
|
|
166
|
+
sendsCompleteRows: boolean | undefined
|
|
167
|
+
): storage.SourceTable {
|
|
168
|
+
const ref = { connectionTag, schema: doc.schema_name, name: doc.table_name };
|
|
169
|
+
const table = new storage.SourceTable({
|
|
177
170
|
id: doc._id,
|
|
178
171
|
ref,
|
|
179
172
|
objectId: doc.relation_id,
|
|
180
173
|
replicaIdColumns:
|
|
181
174
|
doc.replica_id_columns2?.map(
|
|
182
|
-
(
|
|
175
|
+
(column) => ({ name: column.name, typeId: column.type_oid, type: column.type }) satisfies ColumnDescriptor
|
|
183
176
|
) ?? [],
|
|
184
177
|
snapshotComplete: doc.snapshot_done ?? true,
|
|
185
|
-
|
|
178
|
+
sourceMetadata: doc.source_metadata ?? null,
|
|
179
|
+
...syncRules.getMatchingSources(ref)
|
|
186
180
|
});
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
181
|
+
table.syncEvent = syncRules.tableTriggersEvent(ref);
|
|
182
|
+
table.syncData = table.bucketDataSources.length > 0;
|
|
183
|
+
table.syncParameters = table.parameterLookupSources.length > 0;
|
|
184
|
+
table.storeCurrentData = sendsCompleteRows !== true;
|
|
185
|
+
table.snapshotStatus =
|
|
191
186
|
doc.snapshot_status == null
|
|
192
187
|
? undefined
|
|
193
188
|
: {
|
|
@@ -195,7 +190,23 @@ export class MongoBucketBatchV1 extends MongoBucketBatch {
|
|
|
195
190
|
totalEstimatedCount: doc.snapshot_status.total_estimated_count,
|
|
196
191
|
replicatedCount: doc.snapshot_status.replicated_count
|
|
197
192
|
};
|
|
198
|
-
return
|
|
193
|
+
return table;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async getSourceTableStatus(table: storage.SourceTable): Promise<storage.SourceTable | null> {
|
|
197
|
+
const doc = await this.db.sourceTablesV1(this.replicationStreamId).findOne(
|
|
198
|
+
{
|
|
199
|
+
group_id: this.replicationStreamId,
|
|
200
|
+
_id: mongoTableId(table.id)
|
|
201
|
+
},
|
|
202
|
+
{ session: this.session }
|
|
203
|
+
);
|
|
204
|
+
if (doc == null) {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Use the default storeCurrentData value when reading outside resolution.
|
|
209
|
+
return this.hydrateSourceTable(doc, table.ref.connectionTag, this.sync_rules, undefined);
|
|
199
210
|
}
|
|
200
211
|
|
|
201
212
|
async commit(lsn: string, options?: storage.BucketBatchCommitOptions): Promise<storage.CheckpointResult> {
|