@powersync/service-module-mongodb-storage 0.0.0-dev-20260602073133 → 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 +177 -5
- package/dist/migrations/db/migrations/1782950400000-checkpoint-requested-at-index.d.ts +3 -0
- package/dist/migrations/db/migrations/1782950400000-checkpoint-requested-at-index.js +41 -0
- package/dist/migrations/db/migrations/1782950400000-checkpoint-requested-at-index.js.map +1 -0
- package/dist/storage/MongoBucketStorage.d.ts +71 -18
- package/dist/storage/MongoBucketStorage.js +516 -202
- package/dist/storage/MongoBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoBucketBatch.d.ts +71 -29
- package/dist/storage/implementation/MongoBucketBatch.js +140 -66
- package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
- package/dist/storage/implementation/MongoChecksums.d.ts +41 -13
- package/dist/storage/implementation/MongoChecksums.js +36 -121
- package/dist/storage/implementation/MongoChecksums.js.map +1 -1
- package/dist/storage/implementation/MongoCompactor.d.ts +28 -90
- package/dist/storage/implementation/MongoCompactor.js +87 -481
- 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 +82 -8
- package/dist/storage/implementation/MongoParameterCompactor.js +312 -76
- package/dist/storage/implementation/MongoParameterCompactor.js.map +1 -1
- package/dist/storage/implementation/MongoParsedSyncConfigSet.d.ts +21 -0
- package/dist/storage/implementation/MongoParsedSyncConfigSet.js +93 -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 +125 -0
- package/dist/storage/implementation/MongoPersistedSyncConfigContent.js.map +1 -0
- package/dist/storage/implementation/MongoStorageProvider.js +41 -4
- package/dist/storage/implementation/MongoStorageProvider.js.map +1 -1
- package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +81 -37
- package/dist/storage/implementation/MongoSyncBucketStorage.js +159 -86
- package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoSyncRulesLock.d.ts +1 -1
- package/dist/storage/implementation/MongoSyncRulesLock.js +23 -11
- package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -1
- package/dist/storage/implementation/MongoWriteCheckpointAPI.d.ts +11 -8
- package/dist/storage/implementation/MongoWriteCheckpointAPI.js +159 -33
- 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 +28 -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 +34 -2
- package/dist/storage/implementation/common/PersistedBatch.js +63 -10
- 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.d.ts +4 -1
- package/dist/storage/implementation/db.js +31 -16
- package/dist/storage/implementation/db.js.map +1 -1
- package/dist/storage/implementation/models.d.ts +55 -13
- package/dist/storage/implementation/models.js.map +1 -1
- package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +15 -1
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js +135 -107
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -1
- package/dist/storage/implementation/v1/MongoChecksumsV1.d.ts +4 -4
- package/dist/storage/implementation/v1/MongoChecksumsV1.js +119 -7
- package/dist/storage/implementation/v1/MongoChecksumsV1.js.map +1 -1
- package/dist/storage/implementation/v1/MongoCompactorV1.d.ts +47 -7
- package/dist/storage/implementation/v1/MongoCompactorV1.js +478 -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 +33 -27
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +146 -73
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js.map +1 -1
- package/dist/storage/implementation/v1/SingleBucketStoreV1.d.ts +9 -10
- package/dist/storage/implementation/v1/SingleBucketStoreV1.js +2 -8
- package/dist/storage/implementation/v1/SingleBucketStoreV1.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/v1/models.js +1 -0
- package/dist/storage/implementation/v1/models.js.map +1 -1
- package/dist/storage/implementation/v3/BucketDataContextV3.d.ts +13 -0
- package/dist/storage/implementation/v3/BucketDataContextV3.js +23 -0
- package/dist/storage/implementation/v3/BucketDataContextV3.js.map +1 -0
- 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 +16 -5
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js +393 -262
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoChecksumsV3.d.ts +30 -9
- package/dist/storage/implementation/v3/MongoChecksumsV3.js +161 -20
- package/dist/storage/implementation/v3/MongoChecksumsV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +108 -18
- package/dist/storage/implementation/v3/MongoCompactorV3.js +1115 -47
- package/dist/storage/implementation/v3/MongoCompactorV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.d.ts +12 -3
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.js +13 -8
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.d.ts +54 -0
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js +319 -0
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js.map +1 -0
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.d.ts +61 -36
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +353 -161
- 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 +8 -3
- package/dist/storage/implementation/v3/PersistedBatchV3.js +131 -29
- package/dist/storage/implementation/v3/PersistedBatchV3.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 +26 -14
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js +73 -29
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js.map +1 -1
- package/dist/storage/implementation/v3/bucket-format.d.ts +13 -0
- package/dist/storage/implementation/v3/bucket-format.js +79 -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 +30 -0
- package/dist/storage/implementation/v3/chunking.js.map +1 -0
- 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 +187 -23
- package/dist/storage/implementation/v3/models.js +11 -28
- package/dist/storage/implementation/v3/models.js.map +1 -1
- package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.d.ts +20 -0
- package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js +48 -0
- package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js.map +1 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorage.d.ts +33 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorage.js +12 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorage.js.map +1 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.d.ts +68 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js +154 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js.map +1 -0
- 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 +67 -0
- package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js +439 -0
- package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js.map +1 -0
- package/dist/storage/implementation/v3/source-table-utils.d.ts +63 -0
- package/dist/storage/implementation/v3/source-table-utils.js +206 -0
- package/dist/storage/implementation/v3/source-table-utils.js.map +1 -0
- package/dist/storage/storage-index.d.ts +5 -4
- package/dist/storage/storage-index.js +5 -4
- package/dist/storage/storage-index.js.map +1 -1
- package/dist/types/types.d.ts +71 -3
- package/dist/types/types.js +58 -2
- package/dist/types/types.js.map +1 -1
- package/dist/utils/test-utils.d.ts +1 -2
- package/dist/utils/test-utils.js +7 -1
- package/dist/utils/test-utils.js.map +1 -1
- package/dist/utils/util.d.ts +9 -2
- package/dist/utils/util.js +131 -5
- package/dist/utils/util.js.map +1 -1
- package/package.json +13 -19
- package/src/migrations/db/migrations/1782950400000-checkpoint-requested-at-index.ts +55 -0
- package/src/storage/MongoBucketStorage.ts +728 -242
- package/src/storage/implementation/MongoBucketBatch.ts +196 -91
- package/src/storage/implementation/MongoChecksums.ts +93 -153
- package/src/storage/implementation/MongoCompactor.ts +106 -631
- package/src/storage/implementation/MongoHydrationState.ts +61 -0
- package/src/storage/implementation/MongoParameterCompactor.ts +392 -87
- package/src/storage/implementation/MongoParsedSyncConfigSet.ts +122 -0
- package/src/storage/implementation/MongoPersistedReplicationStream.ts +118 -0
- package/src/storage/implementation/MongoPersistedSyncConfigContent.ts +156 -0
- package/src/storage/implementation/MongoStorageProvider.ts +48 -4
- package/src/storage/implementation/MongoSyncBucketStorage.ts +226 -118
- package/src/storage/implementation/MongoSyncRulesLock.ts +28 -12
- package/src/storage/implementation/MongoWriteCheckpointAPI.ts +194 -52
- package/src/storage/implementation/SyncRuleStateUpdate.ts +13 -6
- package/src/storage/implementation/common/MongoSyncBucketStorageCheckpoint.ts +31 -0
- package/src/storage/implementation/common/PersistedBatch.ts +83 -15
- package/src/storage/implementation/common/VersionedPowerSyncMongoBase.ts +1 -1
- package/src/storage/implementation/createMongoSyncBucketStorage.ts +21 -7
- package/src/storage/implementation/db.ts +40 -22
- package/src/storage/implementation/models.ts +58 -14
- package/src/storage/implementation/v1/MongoBucketBatchV1.ts +174 -125
- package/src/storage/implementation/v1/MongoChecksumsV1.ts +154 -12
- package/src/storage/implementation/v1/MongoCompactorV1.ts +594 -25
- package/src/storage/implementation/v1/MongoParameterCompactorV1.ts +25 -9
- package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +226 -129
- package/src/storage/implementation/v1/SingleBucketStoreV1.ts +18 -27
- package/src/storage/implementation/v1/VersionedPowerSyncMongoV1.ts +11 -1
- package/src/storage/implementation/v1/models.ts +12 -1
- package/src/storage/implementation/v3/BucketDataContextV3.ts +32 -0
- package/src/storage/implementation/v3/CompactionLease.ts +152 -0
- package/src/storage/implementation/v3/MongoBucketBatchV3.ts +502 -324
- package/src/storage/implementation/v3/MongoChecksumsV3.ts +250 -32
- package/src/storage/implementation/v3/MongoCompactorV3.ts +1439 -70
- package/src/storage/implementation/v3/MongoParameterCompactorV3.ts +19 -9
- package/src/storage/implementation/v3/MongoStoppedSyncConfigCleanup.ts +473 -0
- package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +453 -211
- package/src/storage/implementation/v3/MongoWriteCheckpointAPIV3.ts +111 -0
- package/src/storage/implementation/v3/PersistedBatchV3.ts +169 -45
- package/src/storage/implementation/v3/SourceRecordStoreV3.ts +12 -12
- package/src/storage/implementation/v3/VersionedPowerSyncMongoV3.ts +99 -38
- package/src/storage/implementation/v3/bucket-format.ts +99 -0
- package/src/storage/implementation/v3/chunking.ts +40 -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 +228 -58
- package/src/storage/implementation/v3/object-storage/BucketDataObjectStorage.ts +62 -0
- package/src/storage/implementation/v3/object-storage/ObjectStorage.ts +44 -0
- package/src/storage/implementation/v3/object-storage/ObjectStorageLifecycle.ts +182 -0
- package/src/storage/implementation/v3/object-storage/ObjectStorageUsage.ts +230 -0
- package/src/storage/implementation/v3/object-storage/S3ObjectStorage.ts +573 -0
- package/src/storage/implementation/v3/source-table-utils.ts +398 -0
- package/src/storage/storage-index.ts +14 -4
- package/src/types/types.ts +76 -2
- package/src/utils/test-utils.ts +8 -3
- package/src/utils/util.ts +188 -7
- package/test/src/__snapshots__/storage.test.ts.snap +4 -54
- package/test/src/__snapshots__/storage_sync.test.ts.snap +652 -645
- package/test/src/cleanup-stopped-sync-configs.test.ts +698 -0
- package/test/src/compact-utils.test.ts +226 -0
- package/test/src/helpers/MemoryObjectStorage.ts +61 -0
- package/test/src/helpers/s3TestFactory.ts +69 -0
- package/test/src/object_storage_lifecycle.test.ts +59 -0
- 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/setup.ts +6 -1
- package/test/src/storage.test.ts +478 -13
- package/test/src/storage_compacting.test.ts +2516 -107
- package/test/src/storage_object_storage_inline_threshold.test.ts +116 -0
- package/test/src/storage_s3_checksums.test.ts +190 -0
- package/test/src/storage_s3_compaction_lifecycle.test.ts +543 -0
- package/test/src/storage_s3_reading.test.ts +768 -0
- package/test/src/storage_s3_writing.test.ts +136 -0
- package/test/src/storage_sync.test.ts +1507 -76
- package/test/src/storeCurrentData.test.ts +227 -0
- package/test/src/util.ts +3 -2
- 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/common/SingleBucketStore.d.ts +0 -54
- package/dist/storage/implementation/common/SingleBucketStore.js +0 -3
- package/dist/storage/implementation/common/SingleBucketStore.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/dist/storage/implementation/v3/SingleBucketStoreV3.d.ts +0 -18
- package/dist/storage/implementation/v3/SingleBucketStoreV3.js +0 -48
- package/dist/storage/implementation/v3/SingleBucketStoreV3.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/common/SingleBucketStore.ts +0 -63
- package/src/storage/implementation/v3/MongoParameterLookupV3.ts +0 -11
- package/src/storage/implementation/v3/SingleBucketStoreV3.ts +0 -68
package/test/src/storage.test.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { mongoTestStorageFactoryGenerator } from '@module/utils/test-utils.js';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { mongo } from '@powersync/lib-service-mongodb';
|
|
3
|
+
import { storage, updateSyncRulesFromYaml } from '@powersync/service-core';
|
|
4
|
+
import { compactActive, register, test_utils } from '@powersync/service-core-tests';
|
|
5
|
+
import { describe, expect, test } from 'vitest';
|
|
6
|
+
import { MongoSyncBucketStorage } from '../../src/storage/implementation/createMongoSyncBucketStorage.js';
|
|
7
|
+
import { VersionedPowerSyncMongoV3 } from '../../src/storage/implementation/v3/VersionedPowerSyncMongoV3.js';
|
|
4
8
|
import { env } from './env.js';
|
|
5
9
|
import { INITIALIZED_MONGO_STORAGE_FACTORY, TEST_STORAGE_VERSIONS } from './util.js';
|
|
6
10
|
|
|
@@ -9,10 +13,475 @@ for (let storageVersion of TEST_STORAGE_VERSIONS) {
|
|
|
9
13
|
register.registerDataStorageParameterTests({ ...INITIALIZED_MONGO_STORAGE_FACTORY, storageVersion }));
|
|
10
14
|
|
|
11
15
|
describe(`Mongo Sync Bucket Storage - Data - v${storageVersion}`, () =>
|
|
12
|
-
register.registerDataStorageDataTests({
|
|
16
|
+
register.registerDataStorageDataTests({
|
|
17
|
+
...INITIALIZED_MONGO_STORAGE_FACTORY,
|
|
18
|
+
storageVersion,
|
|
19
|
+
compressedBucketStorage: storageVersion >= 3
|
|
20
|
+
}));
|
|
13
21
|
|
|
14
22
|
describe(`Mongo Sync Bucket Storage - Checkpoints - v${storageVersion}`, () =>
|
|
15
23
|
register.registerDataStorageCheckpointTests({ ...INITIALIZED_MONGO_STORAGE_FACTORY, storageVersion }));
|
|
24
|
+
|
|
25
|
+
describe(`Mongo Sync Bucket Storage - write checkpoint metadata - v${storageVersion}`, () => {
|
|
26
|
+
test('uses checkpoint_requested_at as the client-requested checkpoint marker', async () => {
|
|
27
|
+
await using factory = await INITIALIZED_MONGO_STORAGE_FACTORY.factory();
|
|
28
|
+
const syncRules = await factory.updateSyncRules(
|
|
29
|
+
updateSyncRulesFromYaml(
|
|
30
|
+
`
|
|
31
|
+
bucket_definitions:
|
|
32
|
+
global:
|
|
33
|
+
data: []
|
|
34
|
+
`,
|
|
35
|
+
{ storageVersion }
|
|
36
|
+
)
|
|
37
|
+
);
|
|
38
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
39
|
+
|
|
40
|
+
// user1 has no existing row, so this covers updateMany with upsert.
|
|
41
|
+
// The initial request stores checkpoint id 42 at source head 5/0.
|
|
42
|
+
const requestedResult = await bucketStorage.createManagedWriteCheckpoints([
|
|
43
|
+
{ user_id: 'user1', heads: { '1': '5/0' }, checkpoint_request_id: 42n }
|
|
44
|
+
]);
|
|
45
|
+
expect(requestedResult.writeCheckpoints.get('user1')).toEqual(42n);
|
|
46
|
+
expect(requestedResult.shouldAdvance).toBe(true);
|
|
47
|
+
|
|
48
|
+
const requested = await factory.db.write_checkpoints.findOne({ user_id: 'user1' });
|
|
49
|
+
expect(requested?.client_id).toEqual(42n);
|
|
50
|
+
expect(requested?.lsns).toEqual({ '1': '5/0' });
|
|
51
|
+
expect(requested?.processed_at_lsn).toBeNull();
|
|
52
|
+
expect(requested?.checkpoint_requested_at).toBeInstanceOf(Date);
|
|
53
|
+
|
|
54
|
+
// Request id 41 is lower than the stored id 42, so the entire stale
|
|
55
|
+
// request is ignored, including its newer 6/0 source head.
|
|
56
|
+
await bucketStorage.createManagedWriteCheckpoints([
|
|
57
|
+
{ user_id: 'user1', heads: { '1': '6/0' }, checkpoint_request_id: 41n }
|
|
58
|
+
]);
|
|
59
|
+
const stale = await factory.db.write_checkpoints.findOne({ user_id: 'user1' });
|
|
60
|
+
expect(stale?.checkpoint_requested_at).toEqual(requested?.checkpoint_requested_at);
|
|
61
|
+
|
|
62
|
+
const expiredRequestedAt = new Date('2024-01-01T00:00:00.000Z');
|
|
63
|
+
await factory.db.write_checkpoints.updateOne(
|
|
64
|
+
{ user_id: 'user1' },
|
|
65
|
+
{ $set: { checkpoint_requested_at: expiredRequestedAt } }
|
|
66
|
+
);
|
|
67
|
+
// Although the previous incoming request was 41, the stored id is still
|
|
68
|
+
// 42. This is therefore an equal-id retry of the original request.
|
|
69
|
+
await bucketStorage.createManagedWriteCheckpoints([
|
|
70
|
+
{ user_id: 'user1', heads: { '1': '6/0' }, checkpoint_request_id: 42n }
|
|
71
|
+
]);
|
|
72
|
+
const retried = await factory.db.write_checkpoints.findOne({ user_id: 'user1' });
|
|
73
|
+
// Retrying the current id refreshes its retention timestamp without
|
|
74
|
+
// replacing the original source head or resetting its processed state.
|
|
75
|
+
expect(retried?.checkpoint_requested_at).toBeInstanceOf(Date);
|
|
76
|
+
expect(retried!.checkpoint_requested_at!.getTime()).toBeGreaterThan(expiredRequestedAt.getTime());
|
|
77
|
+
expect(retried?.lsns).toEqual({ '1': '5/0' });
|
|
78
|
+
expect(retried?.processed_at_lsn).toBeNull();
|
|
79
|
+
|
|
80
|
+
await factory.db.write_checkpoints.updateOne(
|
|
81
|
+
{ user_id: 'user1' },
|
|
82
|
+
{ $set: { checkpoint_requested_at: expiredRequestedAt } }
|
|
83
|
+
);
|
|
84
|
+
await bucketStorage.createManagedWriteCheckpoints([
|
|
85
|
+
{ user_id: 'user1', heads: { '1': '6/0' }, checkpoint_request_id: 43n }
|
|
86
|
+
]);
|
|
87
|
+
const advanced = await factory.db.write_checkpoints.findOne({ user_id: 'user1' });
|
|
88
|
+
// A greater id refreshes retention and advances the stored checkpoint.
|
|
89
|
+
expect(advanced?.checkpoint_requested_at).toBeInstanceOf(Date);
|
|
90
|
+
expect(advanced!.checkpoint_requested_at!.getTime()).toBeGreaterThan(expiredRequestedAt.getTime());
|
|
91
|
+
expect(advanced?.client_id).toEqual(43n);
|
|
92
|
+
expect(advanced?.lsns).toEqual({ '1': '6/0' });
|
|
93
|
+
|
|
94
|
+
await bucketStorage.createManagedWriteCheckpoints([{ user_id: 'user1', heads: { '1': '7/0' } }]);
|
|
95
|
+
const generated = await factory.db.write_checkpoints.findOne({ user_id: 'user1' });
|
|
96
|
+
// Generated checkpoints unset the field, keeping the document out of the
|
|
97
|
+
// partial checkpoint_requested_at index.
|
|
98
|
+
expect(generated).not.toBeNull();
|
|
99
|
+
expect(generated?.checkpoint_requested_at).toBeUndefined();
|
|
100
|
+
|
|
101
|
+
await bucketStorage.createManagedWriteCheckpoints([
|
|
102
|
+
{ user_id: 'user2', heads: { '1': '8/0' }, checkpoint_request_id: 50n }
|
|
103
|
+
]);
|
|
104
|
+
await factory.db.write_checkpoints.updateOne(
|
|
105
|
+
{ user_id: 'user2' },
|
|
106
|
+
{ $set: { checkpoint_requested_at: new Date('2024-01-01T00:00:00.000Z') } }
|
|
107
|
+
);
|
|
108
|
+
await bucketStorage.compact({
|
|
109
|
+
compactBuckets: [],
|
|
110
|
+
deleteCheckpointRequestsBefore: new Date('2024-02-01T00:00:00.000Z')
|
|
111
|
+
});
|
|
112
|
+
// The request is expired but still pending, so compaction must retain it.
|
|
113
|
+
await expect(factory.db.write_checkpoints.findOne({ user_id: 'user2' })).resolves.not.toBeNull();
|
|
114
|
+
|
|
115
|
+
// Advancing replication to the request head sets processed_at_lsn.
|
|
116
|
+
await using managedWriter = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
117
|
+
await managedWriter.markAllSnapshotDone('1/1');
|
|
118
|
+
await managedWriter.keepalive('8/0');
|
|
119
|
+
|
|
120
|
+
await compactActive(factory, {
|
|
121
|
+
compactBuckets: [],
|
|
122
|
+
deleteCheckpointRequestsBefore: new Date('2024-02-01T00:00:00.000Z')
|
|
123
|
+
});
|
|
124
|
+
// The request is now both expired and processed, so it can be removed.
|
|
125
|
+
await expect(factory.db.write_checkpoints.findOne({ user_id: 'user2' })).resolves.toBeNull();
|
|
126
|
+
|
|
127
|
+
if (storageVersion < 3) {
|
|
128
|
+
bucketStorage.setWriteCheckpointMode({
|
|
129
|
+
mode: storage.WriteCheckpointMode.CUSTOM
|
|
130
|
+
});
|
|
131
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
132
|
+
await writer.markAllSnapshotDone('1/1');
|
|
133
|
+
const customCheckpointRequestedAt = new Date('2024-01-01T00:00:00.000Z');
|
|
134
|
+
writer.addCustomWriteCheckpoint({
|
|
135
|
+
user_id: 'custom1',
|
|
136
|
+
checkpoint: 51n,
|
|
137
|
+
checkpoint_requested_at: customCheckpointRequestedAt
|
|
138
|
+
});
|
|
139
|
+
await writer.flush();
|
|
140
|
+
const customRequested = await factory.db.custom_write_checkpoints.findOne({ user_id: 'custom1' });
|
|
141
|
+
expect(customRequested?.checkpoint_requested_at).toEqual(customCheckpointRequestedAt);
|
|
142
|
+
|
|
143
|
+
writer.addCustomWriteCheckpoint({
|
|
144
|
+
user_id: 'custom1',
|
|
145
|
+
checkpoint: 52n
|
|
146
|
+
});
|
|
147
|
+
await writer.flush();
|
|
148
|
+
const customGenerated = await factory.db.custom_write_checkpoints.findOne({ user_id: 'custom1' });
|
|
149
|
+
expect(customGenerated).not.toBeNull();
|
|
150
|
+
expect(customGenerated?.checkpoint_requested_at).toBeUndefined();
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test.runIf(storageVersion >= 3)(
|
|
155
|
+
'stores and streams checkpoints from different event definitions independently',
|
|
156
|
+
async (context) => {
|
|
157
|
+
await using factory = await INITIALIZED_MONGO_STORAGE_FACTORY.factory();
|
|
158
|
+
const syncRules = await factory.updateSyncRules(
|
|
159
|
+
updateSyncRulesFromYaml(
|
|
160
|
+
`
|
|
161
|
+
config:
|
|
162
|
+
edition: 3
|
|
163
|
+
|
|
164
|
+
streams:
|
|
165
|
+
global:
|
|
166
|
+
query: SELECT * FROM checkpoints
|
|
167
|
+
|
|
168
|
+
event_definitions:
|
|
169
|
+
checkpoint_a:
|
|
170
|
+
payloads:
|
|
171
|
+
- SELECT id FROM checkpoints WHERE kind = 'a'
|
|
172
|
+
checkpoint_b:
|
|
173
|
+
payloads:
|
|
174
|
+
- SELECT id FROM checkpoints WHERE kind = 'b'
|
|
175
|
+
unrelated_event:
|
|
176
|
+
payloads:
|
|
177
|
+
- SELECT id FROM checkpoints WHERE kind = 'unrelated'
|
|
178
|
+
`,
|
|
179
|
+
{ storageVersion }
|
|
180
|
+
)
|
|
181
|
+
);
|
|
182
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
183
|
+
const db = bucketStorage.db as VersionedPowerSyncMongoV3;
|
|
184
|
+
const activeSyncConfig = bucketStorage.getParsedSyncRules({ defaultSchema: 'public' });
|
|
185
|
+
const eventA = activeSyncConfig.eventDescriptors.find((event) => event.name == 'checkpoint_a')!;
|
|
186
|
+
const eventB = activeSyncConfig.eventDescriptors.find((event) => event.name == 'checkpoint_b')!;
|
|
187
|
+
const unrelatedEvent = activeSyncConfig.eventDescriptors.find((event) => event.name == 'unrelated_event')!;
|
|
188
|
+
const mapping = syncRules.syncConfigContent[0].mapping;
|
|
189
|
+
const eventAId = mapping.eventId(eventA);
|
|
190
|
+
const eventBId = mapping.eventId(eventB);
|
|
191
|
+
const unrelatedEventId = mapping.eventId(unrelatedEvent);
|
|
192
|
+
expect(() =>
|
|
193
|
+
bucketStorage.setWriteCheckpointMode({
|
|
194
|
+
mode: storage.WriteCheckpointMode.CUSTOM
|
|
195
|
+
})
|
|
196
|
+
).toThrow('eventName');
|
|
197
|
+
bucketStorage.setWriteCheckpointMode({
|
|
198
|
+
mode: storage.WriteCheckpointMode.CUSTOM,
|
|
199
|
+
eventName: 'unknown-event'
|
|
200
|
+
});
|
|
201
|
+
await expect(bucketStorage.lastWriteCheckpoint({ user_id: 'user1' })).rejects.toThrow(
|
|
202
|
+
'No mapping found for event definition unknown-event'
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
bucketStorage.setWriteCheckpointMode({
|
|
206
|
+
mode: storage.WriteCheckpointMode.CUSTOM,
|
|
207
|
+
eventName: eventA.name
|
|
208
|
+
});
|
|
209
|
+
// Reads before the first checkpoint must behave like an empty result;
|
|
210
|
+
// they must not require the lazily-created collection to exist.
|
|
211
|
+
await expect(bucketStorage.lastWriteCheckpoint({ user_id: 'user1' })).resolves.toBeNull();
|
|
212
|
+
|
|
213
|
+
await using invalidWriter = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
214
|
+
invalidWriter.addCustomWriteCheckpoint({ user_id: 'invalid', checkpoint: 1n });
|
|
215
|
+
await expect(invalidWriter.flush()).rejects.toThrow('require an event definition id');
|
|
216
|
+
await expect(db.listCustomCheckpointRequestCollections(syncRules.replicationStreamId)).resolves.toEqual([]);
|
|
217
|
+
|
|
218
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
219
|
+
await writer.markAllSnapshotDone('1/1');
|
|
220
|
+
const abortController = new AbortController();
|
|
221
|
+
context.onTestFinished(() => abortController.abort());
|
|
222
|
+
const iter = bucketStorage
|
|
223
|
+
.watchCheckpointChanges({
|
|
224
|
+
user_id: 'user1',
|
|
225
|
+
signal: abortController.signal
|
|
226
|
+
})
|
|
227
|
+
[Symbol.asyncIterator]();
|
|
228
|
+
|
|
229
|
+
writer.addCustomWriteCheckpoint({ user_id: 'user1', checkpoint: 5n, event_id: eventAId });
|
|
230
|
+
writer.addCustomWriteCheckpoint({ user_id: 'user1', checkpoint: 8n, event_id: eventBId });
|
|
231
|
+
await writer.flush();
|
|
232
|
+
await writer.keepalive('5/0');
|
|
233
|
+
|
|
234
|
+
const eventACollection = db.customCheckpointRequests({
|
|
235
|
+
replicationStreamId: syncRules.replicationStreamId,
|
|
236
|
+
eventId: eventAId
|
|
237
|
+
});
|
|
238
|
+
const eventBCollection = db.customCheckpointRequests({
|
|
239
|
+
replicationStreamId: syncRules.replicationStreamId,
|
|
240
|
+
eventId: eventBId
|
|
241
|
+
});
|
|
242
|
+
await expect(eventACollection.findOne({ user_id: 'user1' })).resolves.toMatchObject({ checkpoint: 5n });
|
|
243
|
+
await expect(eventBCollection.findOne({ user_id: 'user1' })).resolves.toMatchObject({ checkpoint: 8n });
|
|
244
|
+
const checkpointCollectionNames = (
|
|
245
|
+
await db.listCustomCheckpointRequestCollections(syncRules.replicationStreamId)
|
|
246
|
+
)
|
|
247
|
+
.map((collection) => collection.collectionName)
|
|
248
|
+
.sort();
|
|
249
|
+
expect(checkpointCollectionNames).toEqual(
|
|
250
|
+
[eventACollection.collectionName, eventBCollection.collectionName].sort()
|
|
251
|
+
);
|
|
252
|
+
expect(checkpointCollectionNames).not.toContain(
|
|
253
|
+
db.customCheckpointRequests({
|
|
254
|
+
replicationStreamId: syncRules.replicationStreamId,
|
|
255
|
+
eventId: unrelatedEventId
|
|
256
|
+
}).collectionName
|
|
257
|
+
);
|
|
258
|
+
await expect(eventACollection.indexExists(['user_unique', 'op_id', 'checkpoint_requested_at'])).resolves.toBe(
|
|
259
|
+
true
|
|
260
|
+
);
|
|
261
|
+
await expect(bucketStorage.lastWriteCheckpoint({ user_id: 'user1' })).resolves.toEqual(5n);
|
|
262
|
+
await expect(iter.next()).resolves.toMatchObject({
|
|
263
|
+
done: false,
|
|
264
|
+
value: { writeCheckpoint: 5n }
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
writer.addCustomWriteCheckpoint({ user_id: 'user1', checkpoint: 9n, event_id: eventBId });
|
|
268
|
+
await writer.flush();
|
|
269
|
+
await writer.keepalive('6/0');
|
|
270
|
+
// The processing event's record does not advance clients still served by
|
|
271
|
+
// the active event definition.
|
|
272
|
+
await expect(iter.next()).resolves.toMatchObject({
|
|
273
|
+
done: false,
|
|
274
|
+
value: { writeCheckpoint: 5n }
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
bucketStorage.setWriteCheckpointMode({
|
|
278
|
+
mode: storage.WriteCheckpointMode.CUSTOM,
|
|
279
|
+
eventName: eventB.name
|
|
280
|
+
});
|
|
281
|
+
await expect(bucketStorage.lastWriteCheckpoint({ user_id: 'user1' })).resolves.toEqual(9n);
|
|
282
|
+
|
|
283
|
+
writer.addCustomWriteCheckpoint({
|
|
284
|
+
user_id: 'temporary',
|
|
285
|
+
checkpoint: 10n,
|
|
286
|
+
event_id: eventBId,
|
|
287
|
+
checkpoint_requested_at: new Date('2024-01-01T00:00:00.000Z')
|
|
288
|
+
});
|
|
289
|
+
await writer.flush();
|
|
290
|
+
await compactActive(factory, {
|
|
291
|
+
compactBuckets: [],
|
|
292
|
+
deleteCheckpointRequestsBefore: new Date('2024-01-02T00:00:00.000Z')
|
|
293
|
+
});
|
|
294
|
+
await expect(eventBCollection.findOne({ user_id: 'temporary' })).resolves.toBeNull();
|
|
295
|
+
|
|
296
|
+
// A fresh storage instance has an empty initialization cache, just like
|
|
297
|
+
// one created after a service restart. Recreating the existing indexes
|
|
298
|
+
// must be idempotent before another checkpoint is written.
|
|
299
|
+
const freshBucketStorage = factory.getInstance(syncRules);
|
|
300
|
+
await using freshWriter = await freshBucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
301
|
+
freshWriter.addCustomWriteCheckpoint({
|
|
302
|
+
user_id: 'after-restart',
|
|
303
|
+
checkpoint: 11n,
|
|
304
|
+
event_id: eventAId
|
|
305
|
+
});
|
|
306
|
+
await freshWriter.flush();
|
|
307
|
+
await expect(eventACollection.findOne({ user_id: 'after-restart' })).resolves.toMatchObject({
|
|
308
|
+
checkpoint: 11n
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
await bucketStorage.clear();
|
|
312
|
+
await expect(db.listCustomCheckpointRequestCollections(syncRules.replicationStreamId)).resolves.toEqual([]);
|
|
313
|
+
}
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
test.runIf(storageVersion >= 3)(
|
|
317
|
+
'resolves custom checkpoints through the active event mapping across deployments',
|
|
318
|
+
async () => {
|
|
319
|
+
await using factory = await INITIALIZED_MONGO_STORAGE_FACTORY.factory();
|
|
320
|
+
const syncConfigYaml = (predicate: string, includeExtraStream: boolean) => `
|
|
321
|
+
config:
|
|
322
|
+
edition: 3
|
|
323
|
+
|
|
324
|
+
streams:
|
|
325
|
+
global:
|
|
326
|
+
query: SELECT * FROM checkpoints
|
|
327
|
+
${includeExtraStream ? ' todos:\n query: SELECT * FROM todos' : ''}
|
|
328
|
+
|
|
329
|
+
event_definitions:
|
|
330
|
+
write_checkpoints:
|
|
331
|
+
payloads:
|
|
332
|
+
- SELECT user_id, checkpoint FROM checkpoints WHERE ${predicate}
|
|
333
|
+
`;
|
|
334
|
+
|
|
335
|
+
const first = await factory.updateSyncRules(
|
|
336
|
+
updateSyncRulesFromYaml(syncConfigYaml("kind = 'write'", false), { storageVersion })
|
|
337
|
+
);
|
|
338
|
+
const firstEventId = first.syncConfigContent[0].mapping.eventDefinitionIdByName('write_checkpoints');
|
|
339
|
+
const firstStorage = factory.getInstance(first);
|
|
340
|
+
await using firstWriter = await firstStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
341
|
+
firstWriter.addCustomWriteCheckpoint({ user_id: 'user1', checkpoint: 5n, event_id: firstEventId });
|
|
342
|
+
await firstWriter.markAllSnapshotDone('1/1');
|
|
343
|
+
await firstWriter.commit('1/1');
|
|
344
|
+
|
|
345
|
+
const unchanged = await factory.updateSyncRules(
|
|
346
|
+
updateSyncRulesFromYaml(syncConfigYaml("kind = 'write'", true), { storageVersion })
|
|
347
|
+
);
|
|
348
|
+
const unchangedEventId = unchanged.syncConfigContent[1].mapping.eventDefinitionIdByName('write_checkpoints');
|
|
349
|
+
expect(unchangedEventId).toBe(firstEventId);
|
|
350
|
+
|
|
351
|
+
await using unchangedWriter = await factory.getInstance(unchanged).createWriter(test_utils.BATCH_OPTIONS);
|
|
352
|
+
await unchangedWriter.markAllSnapshotDone('2/1');
|
|
353
|
+
await unchangedWriter.commit('2/1');
|
|
354
|
+
|
|
355
|
+
const unchangedActiveStorage = (await factory.getActiveSyncConfig())!.storage as MongoSyncBucketStorage;
|
|
356
|
+
unchangedActiveStorage.setWriteCheckpointMode({
|
|
357
|
+
mode: storage.WriteCheckpointMode.CUSTOM,
|
|
358
|
+
eventName: 'write_checkpoints'
|
|
359
|
+
});
|
|
360
|
+
await expect(
|
|
361
|
+
unchangedActiveStorage.lastWriteCheckpoint({
|
|
362
|
+
user_id: 'user1'
|
|
363
|
+
})
|
|
364
|
+
).resolves.toBe(5n);
|
|
365
|
+
|
|
366
|
+
const changed = await factory.updateSyncRules(
|
|
367
|
+
updateSyncRulesFromYaml(syncConfigYaml("kind = 'processed'", true), { storageVersion })
|
|
368
|
+
);
|
|
369
|
+
const changedEventId = changed.syncConfigContent[1].mapping.eventDefinitionIdByName('write_checkpoints');
|
|
370
|
+
expect(changedEventId).not.toBe(unchangedEventId);
|
|
371
|
+
|
|
372
|
+
await using changedWriter = await factory.getInstance(changed).createWriter(test_utils.BATCH_OPTIONS);
|
|
373
|
+
changedWriter.addCustomWriteCheckpoint({ user_id: 'user1', checkpoint: 9n, event_id: changedEventId });
|
|
374
|
+
await changedWriter.flush();
|
|
375
|
+
|
|
376
|
+
// Until activation, reads still use the previous config's assigned id and collection.
|
|
377
|
+
await expect(
|
|
378
|
+
unchangedActiveStorage.lastWriteCheckpoint({
|
|
379
|
+
user_id: 'user1'
|
|
380
|
+
})
|
|
381
|
+
).resolves.toBe(5n);
|
|
382
|
+
|
|
383
|
+
await changedWriter.markAllSnapshotDone('3/1');
|
|
384
|
+
await changedWriter.commit('3/1');
|
|
385
|
+
|
|
386
|
+
const changedActiveStorage = (await factory.getActiveSyncConfig())!.storage as MongoSyncBucketStorage;
|
|
387
|
+
changedActiveStorage.setWriteCheckpointMode({
|
|
388
|
+
mode: storage.WriteCheckpointMode.CUSTOM,
|
|
389
|
+
eventName: 'write_checkpoints'
|
|
390
|
+
});
|
|
391
|
+
await expect(
|
|
392
|
+
changedActiveStorage.lastWriteCheckpoint({
|
|
393
|
+
user_id: 'user1'
|
|
394
|
+
})
|
|
395
|
+
).resolves.toBe(9n);
|
|
396
|
+
}
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* It's extremely rare (but technically possible) to have duplicate write checkpoint records
|
|
401
|
+
* for a full user_id. This is normally not an issue for checkpoints requested from `write-checkpoint2.json`,
|
|
402
|
+
* but it can be for requests in the `sync/checkpoint-request` flow.
|
|
403
|
+
* That flow seeds the client with the current latest state (if available). We need to ensure that the client
|
|
404
|
+
* does NOT start at a lower bound if there are duplicate records.
|
|
405
|
+
*/
|
|
406
|
+
test('updates duplicate managed rows on supplied checkpoint requests', async () => {
|
|
407
|
+
await using factory = await INITIALIZED_MONGO_STORAGE_FACTORY.factory();
|
|
408
|
+
const syncRules = await factory.updateSyncRules(
|
|
409
|
+
updateSyncRulesFromYaml(
|
|
410
|
+
`
|
|
411
|
+
bucket_definitions:
|
|
412
|
+
global:
|
|
413
|
+
data: []
|
|
414
|
+
`,
|
|
415
|
+
{ storageVersion }
|
|
416
|
+
)
|
|
417
|
+
);
|
|
418
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
419
|
+
|
|
420
|
+
// Replicate an extremely rare possibility where there are multiple records
|
|
421
|
+
await factory.db.write_checkpoints.insertMany([
|
|
422
|
+
{
|
|
423
|
+
_id: new mongo.ObjectId('000000000000000000000001'),
|
|
424
|
+
user_id: 'user1',
|
|
425
|
+
client_id: 50n,
|
|
426
|
+
lsns: { '1': '50/0' },
|
|
427
|
+
processed_at_lsn: '50/0'
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
_id: new mongo.ObjectId('000000000000000000000002'),
|
|
431
|
+
user_id: 'user1',
|
|
432
|
+
client_id: 42n,
|
|
433
|
+
lsns: { '1': '42/0' },
|
|
434
|
+
processed_at_lsn: null,
|
|
435
|
+
checkpoint_requested_at: new Date('2024-01-01T00:00:00.000Z')
|
|
436
|
+
}
|
|
437
|
+
]);
|
|
438
|
+
|
|
439
|
+
// Simulate a client without a sequence state trying to seed its state.
|
|
440
|
+
const seeded = await bucketStorage.createManagedWriteCheckpoints([
|
|
441
|
+
{ user_id: 'user1', heads: { '1': '1/0' }, checkpoint_request_id: 1n }
|
|
442
|
+
]);
|
|
443
|
+
|
|
444
|
+
// The result should be the current max state.
|
|
445
|
+
const seededCheckpoint = seeded.writeCheckpoints.get('user1')!;
|
|
446
|
+
expect(seededCheckpoint).toEqual(50n);
|
|
447
|
+
expect(seeded.shouldAdvance).toBe(false);
|
|
448
|
+
|
|
449
|
+
// Simulate a client using the above state for its next checkpoint request.
|
|
450
|
+
const nextCheckpoint = seededCheckpoint + 1n;
|
|
451
|
+
const incremented = await bucketStorage.createManagedWriteCheckpoints([
|
|
452
|
+
{ user_id: 'user1', heads: { '1': `${nextCheckpoint}/0` }, checkpoint_request_id: nextCheckpoint }
|
|
453
|
+
]);
|
|
454
|
+
|
|
455
|
+
// This should be accepted by the service.
|
|
456
|
+
expect(incremented.writeCheckpoints.get('user1')).toEqual(nextCheckpoint);
|
|
457
|
+
expect(incremented.shouldAdvance).toBe(true);
|
|
458
|
+
|
|
459
|
+
// The service should now reconcile all duplicates to the latest state.
|
|
460
|
+
// Duplicates can now also automatically be deleted when they expire.
|
|
461
|
+
const docs = await factory.db.write_checkpoints.find({ user_id: 'user1' }, { sort: { _id: 1 } }).toArray();
|
|
462
|
+
expect(
|
|
463
|
+
docs.map((doc) => ({
|
|
464
|
+
client_id: doc.client_id,
|
|
465
|
+
lsn: doc.lsns['1'],
|
|
466
|
+
processed_at_lsn: doc.processed_at_lsn,
|
|
467
|
+
is_checkpoint_request: doc.checkpoint_requested_at != null
|
|
468
|
+
}))
|
|
469
|
+
).toEqual([
|
|
470
|
+
{
|
|
471
|
+
client_id: nextCheckpoint,
|
|
472
|
+
lsn: '51/0',
|
|
473
|
+
processed_at_lsn: null,
|
|
474
|
+
is_checkpoint_request: true
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
client_id: nextCheckpoint,
|
|
478
|
+
lsn: '51/0',
|
|
479
|
+
processed_at_lsn: null,
|
|
480
|
+
is_checkpoint_request: true
|
|
481
|
+
}
|
|
482
|
+
]);
|
|
483
|
+
});
|
|
484
|
+
});
|
|
16
485
|
}
|
|
17
486
|
|
|
18
487
|
describe('Sync Bucket Validation', register.registerBucketValidationTests);
|
|
@@ -22,11 +491,9 @@ describe('Mongo Sync Bucket Storage - split operations', () =>
|
|
|
22
491
|
mongoTestStorageFactoryGenerator({
|
|
23
492
|
url: env.MONGO_TEST_URL,
|
|
24
493
|
isCI: env.CI,
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
operationBatchLimit: 1
|
|
29
|
-
}
|
|
494
|
+
checksumOptions: {
|
|
495
|
+
bucketBatchLimit: 100,
|
|
496
|
+
operationBatchLimit: 1
|
|
30
497
|
}
|
|
31
498
|
})
|
|
32
499
|
));
|
|
@@ -36,11 +503,9 @@ describe('Mongo Sync Bucket Storage - split buckets', () =>
|
|
|
36
503
|
mongoTestStorageFactoryGenerator({
|
|
37
504
|
url: env.MONGO_TEST_URL,
|
|
38
505
|
isCI: env.CI,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
operationBatchLimit: 100
|
|
43
|
-
}
|
|
506
|
+
checksumOptions: {
|
|
507
|
+
bucketBatchLimit: 1,
|
|
508
|
+
operationBatchLimit: 100
|
|
44
509
|
}
|
|
45
510
|
})
|
|
46
511
|
));
|