@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
|
@@ -16,6 +16,7 @@ import { MongoPersistedSyncConfigContentV3 } from '../../src/storage/implementat
|
|
|
16
16
|
import { BucketDataDoc, BucketKey } from '../../src/storage/implementation/common/BucketDataDoc.js';
|
|
17
17
|
import { MongoSyncBucketStorage } from '../../src/storage/implementation/createMongoSyncBucketStorage.js';
|
|
18
18
|
import { getMongoStorageConfig } from '../../src/storage/implementation/models.js';
|
|
19
|
+
import { loadBucketDataDocumentV1, serializeBucketDataV1 } from '../../src/storage/implementation/v1/models.js';
|
|
19
20
|
import { SourceRecordStoreV3 } from '../../src/storage/implementation/v3/SourceRecordStoreV3.js';
|
|
20
21
|
import type { VersionedPowerSyncMongoV3 } from '../../src/storage/implementation/v3/VersionedPowerSyncMongoV3.js';
|
|
21
22
|
import { serializeBucketData } from '../../src/storage/implementation/v3/bucket-format.js';
|
|
@@ -114,6 +115,31 @@ function bucketDefinitionId(bucket: string) {
|
|
|
114
115
|
return parseInt(match[1], 16);
|
|
115
116
|
}
|
|
116
117
|
|
|
118
|
+
test('V1 bucket data retains an optional persisted subkey', () => {
|
|
119
|
+
const sourceTable = new bson.ObjectId();
|
|
120
|
+
const document = serializeBucketDataV1({
|
|
121
|
+
bucketKey: {
|
|
122
|
+
replicationStreamId: 1,
|
|
123
|
+
definitionId: '0',
|
|
124
|
+
bucket: 'global[]'
|
|
125
|
+
},
|
|
126
|
+
o: 1n,
|
|
127
|
+
op: 'PUT',
|
|
128
|
+
source_table: sourceTable,
|
|
129
|
+
source_key: 'source-key',
|
|
130
|
+
subkey: 'persisted-subkey',
|
|
131
|
+
table: 'items',
|
|
132
|
+
row_id: 'item-1',
|
|
133
|
+
checksum: 1n,
|
|
134
|
+
data: '{"id":"item-1"}'
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
expect(loadBucketDataDocumentV1(document).subkey).toBe('persisted-subkey');
|
|
138
|
+
|
|
139
|
+
const { subkey: _subkey, ...legacyDocument } = document;
|
|
140
|
+
expect(loadBucketDataDocumentV1(legacyDocument).subkey).toBeUndefined();
|
|
141
|
+
});
|
|
142
|
+
|
|
117
143
|
function registerSyncStorageTests(storageConfig: storage.TestStorageConfig, storageVersion: number) {
|
|
118
144
|
register.registerSyncTests(storageConfig.factory, {
|
|
119
145
|
storageVersion,
|
|
@@ -533,6 +559,7 @@ function registerSyncStorageTests(storageConfig: storage.TestStorageConfig, stor
|
|
|
533
559
|
const source = sourceDescriptor('memberships', { objectId: 'memberships-relation' });
|
|
534
560
|
const dataOnlyTableId = new bson.ObjectId('6544e3899293153fa7b38348');
|
|
535
561
|
const addedParameterTableId = new bson.ObjectId('6544e3899293153fa7b38349');
|
|
562
|
+
const addedEventTableId = new bson.ObjectId('6544e3899293153fa7b3834a');
|
|
536
563
|
|
|
537
564
|
const dataOnly = await writer.resolveTables({
|
|
538
565
|
connection_id: 1,
|
|
@@ -590,15 +617,13 @@ function registerSyncStorageTests(storageConfig: storage.TestStorageConfig, stor
|
|
|
590
617
|
const eventOnly = await writer.resolveTables({
|
|
591
618
|
connection_id: 1,
|
|
592
619
|
source,
|
|
593
|
-
idGenerator: () =>
|
|
594
|
-
throw new Error('resolve should reuse existing v3 source table');
|
|
595
|
-
},
|
|
620
|
+
idGenerator: () => addedEventTableId,
|
|
596
621
|
parsedSyncConfig: eventOnlyRules
|
|
597
622
|
});
|
|
598
623
|
|
|
599
|
-
//
|
|
624
|
+
// A newly-added event gets its own source table so that its existing rows are snapshotted.
|
|
600
625
|
expect(eventOnly.tables).toHaveLength(1);
|
|
601
|
-
expect(
|
|
626
|
+
expect(eventOnly.tables[0].id).toEqual(addedEventTableId);
|
|
602
627
|
expect(eventOnly.dropTables.map((table) => table.id)).toEqual([]);
|
|
603
628
|
expect(eventOnly.tables[0].bucketDataSources).toHaveLength(0);
|
|
604
629
|
expect(eventOnly.tables[0].parameterLookupSources).toHaveLength(0);
|
|
@@ -607,13 +632,12 @@ function registerSyncStorageTests(storageConfig: storage.TestStorageConfig, stor
|
|
|
607
632
|
expect(eventOnly.tables[0].syncEvent).toBe(true);
|
|
608
633
|
});
|
|
609
634
|
|
|
610
|
-
test.runIf(storageVersion >= 3)(
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
const dataOnlyEventYaml = `
|
|
635
|
+
test.runIf(storageVersion >= 3)('resolveTables assigns an event definition to one split source table', async () => {
|
|
636
|
+
// When memberships are split over multiple SourceTables for the same ref, a row change
|
|
637
|
+
// is saved once per table. Each event-definition id must belong to only one of those tables,
|
|
638
|
+
// otherwise that definition would be evaluated more than once for the same row change.
|
|
639
|
+
// Other tables may independently own and evaluate different event definitions.
|
|
640
|
+
const dataOnlyEventYaml = `
|
|
617
641
|
bucket_definitions:
|
|
618
642
|
by_owner:
|
|
619
643
|
parameters:
|
|
@@ -626,7 +650,7 @@ function registerSyncStorageTests(storageConfig: storage.TestStorageConfig, stor
|
|
|
626
650
|
payloads:
|
|
627
651
|
- SELECT id, owner_id FROM memberships
|
|
628
652
|
`;
|
|
629
|
-
|
|
653
|
+
const fullEventYaml = `
|
|
630
654
|
bucket_definitions:
|
|
631
655
|
by_owner:
|
|
632
656
|
parameters:
|
|
@@ -640,42 +664,201 @@ function registerSyncStorageTests(storageConfig: storage.TestStorageConfig, stor
|
|
|
640
664
|
- SELECT id, owner_id FROM memberships
|
|
641
665
|
`;
|
|
642
666
|
|
|
667
|
+
await using factory = await storageConfig.factory();
|
|
668
|
+
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(dataOnlyEventYaml, { storageVersion }));
|
|
669
|
+
const bucketStorage = factory.getInstance(syncRules) as MongoSyncBucketStorage;
|
|
670
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
671
|
+
const dataOnlyRules = parsedSyncConfigSetFor(dataOnlyEventYaml, storageVersion);
|
|
672
|
+
const fullRules = parsedSyncConfigSetFor(fullEventYaml, storageVersion);
|
|
673
|
+
const source = sourceDescriptor('memberships', { objectId: 'memberships-relation' });
|
|
674
|
+
|
|
675
|
+
const initial = await writer.resolveTables({
|
|
676
|
+
connection_id: 1,
|
|
677
|
+
source,
|
|
678
|
+
idGenerator: objectIdGenerator('6544e3899293153fa7b3834b'),
|
|
679
|
+
parsedSyncConfig: dataOnlyRules
|
|
680
|
+
});
|
|
681
|
+
expect(initial.tables).toHaveLength(1);
|
|
682
|
+
expect(initial.tables[0].syncEvent).toBe(true);
|
|
683
|
+
|
|
684
|
+
// Adding the table-based parameter lookup creates a second SourceTable for the same ref.
|
|
685
|
+
const split = await writer.resolveTables({
|
|
686
|
+
connection_id: 1,
|
|
687
|
+
source,
|
|
688
|
+
idGenerator: objectIdGenerator('6544e3899293153fa7b3834c'),
|
|
689
|
+
parsedSyncConfig: fullRules
|
|
690
|
+
});
|
|
691
|
+
expect(split.tables).toHaveLength(2);
|
|
692
|
+
// Both tables match the event by ref, but this event id belongs to only one of them.
|
|
693
|
+
const eventOwners = split.tables.filter((table) => table.syncEvent);
|
|
694
|
+
expect(eventOwners).toHaveLength(1);
|
|
695
|
+
|
|
696
|
+
// getSourceTableStatus rehydrates the persisted event memberships rather than recomputing
|
|
697
|
+
// them from the ref, so refreshing the other table does not assign this event to it.
|
|
698
|
+
const nonOwner = split.tables.find((table) => !table.syncEvent)!;
|
|
699
|
+
const refreshed = await writer.getSourceTableStatus(nonOwner);
|
|
700
|
+
expect(refreshed!.syncEvent).toBe(false);
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
test.runIf(storageVersion >= 3)(
|
|
704
|
+
'reuses an unchanged event definition without resnapshotting or duplicate evaluation',
|
|
705
|
+
async () => {
|
|
706
|
+
const firstYaml = `
|
|
707
|
+
config:
|
|
708
|
+
edition: 3
|
|
709
|
+
|
|
710
|
+
streams:
|
|
711
|
+
by_owner:
|
|
712
|
+
query: SELECT * FROM todos WHERE owner_id = subscription.parameter('owner_id')
|
|
713
|
+
|
|
714
|
+
event_definitions:
|
|
715
|
+
write_checkpoints:
|
|
716
|
+
payloads:
|
|
717
|
+
- SELECT user_id, checkpoint FROM checkpoints WHERE active = true AND checkpoint > 0
|
|
718
|
+
`;
|
|
719
|
+
const secondYaml = `
|
|
720
|
+
config:
|
|
721
|
+
edition: 3
|
|
722
|
+
|
|
723
|
+
streams:
|
|
724
|
+
by_owner:
|
|
725
|
+
query: SELECT * FROM todos WHERE owner_id = subscription.parameter('owner_id')
|
|
726
|
+
by_project:
|
|
727
|
+
query: SELECT * FROM todos WHERE project_id = subscription.parameter('project_id')
|
|
728
|
+
|
|
729
|
+
event_definitions:
|
|
730
|
+
write_checkpoints:
|
|
731
|
+
payloads:
|
|
732
|
+
- select "user_id", "checkpoint" from "checkpoints" where "active" = true and "checkpoint" > 0
|
|
733
|
+
`;
|
|
734
|
+
|
|
643
735
|
await using factory = await storageConfig.factory();
|
|
644
|
-
const
|
|
645
|
-
const
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
736
|
+
const emittedEvents: { name: string; eventId: string | undefined }[] = [];
|
|
737
|
+
const disposeListener = factory.registerListener({
|
|
738
|
+
replicationEvent: ({ event, event_id }) => emittedEvents.push({ name: event.name, eventId: event_id })
|
|
739
|
+
});
|
|
740
|
+
try {
|
|
741
|
+
const first = await factory.updateSyncRules(updateSyncRulesFromYaml(firstYaml, { storageVersion }));
|
|
742
|
+
const firstStorage = factory.getInstance(first) as MongoSyncBucketStorage;
|
|
743
|
+
await using firstWriter = await firstStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
744
|
+
const source = sourceDescriptor('checkpoints', { objectId: 'checkpoints-relation' });
|
|
745
|
+
const firstResolved = await firstWriter.resolveTables({
|
|
746
|
+
connection_id: 1,
|
|
747
|
+
source,
|
|
748
|
+
idGenerator: objectIdGenerator('6544e3899293153fa7b38351')
|
|
749
|
+
});
|
|
750
|
+
const eventId = [...firstResolved.tables[0].eventDefinitionIds!][0];
|
|
751
|
+
expect(eventId).toBeDefined();
|
|
752
|
+
await firstWriter.markTableSnapshotDone(firstResolved.tables, '1/1');
|
|
753
|
+
await firstWriter.markAllSnapshotDone('1/1');
|
|
754
|
+
await firstWriter.commit('1/1');
|
|
755
|
+
|
|
756
|
+
const second = await factory.updateSyncRules(updateSyncRulesFromYaml(secondYaml, { storageVersion }));
|
|
757
|
+
expect(second.replicationStreamId).toBe(first.replicationStreamId);
|
|
758
|
+
const secondStorage = factory.getInstance(second) as MongoSyncBucketStorage;
|
|
759
|
+
await using secondWriter = await secondStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
760
|
+
const resolved = await secondWriter.resolveTables({
|
|
761
|
+
connection_id: 1,
|
|
762
|
+
source,
|
|
763
|
+
idGenerator: () => {
|
|
764
|
+
throw new Error('unchanged event definition should reuse the snapshotted source table');
|
|
765
|
+
}
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
expect(resolved.tables).toHaveLength(1);
|
|
769
|
+
expect(resolved.tables[0].id).toEqual(firstResolved.tables[0].id);
|
|
770
|
+
expect(resolved.tables[0].snapshotComplete).toBe(true);
|
|
771
|
+
expect([...resolved.tables[0].eventDefinitionIds!]).toEqual([eventId]);
|
|
772
|
+
|
|
773
|
+
emittedEvents.length = 0;
|
|
774
|
+
await secondWriter.save({
|
|
775
|
+
sourceTable: resolved.tables[0],
|
|
776
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
777
|
+
after: { id: 'checkpoint-1', user_id: 'user-1', checkpoint: 1n, active: 1 },
|
|
778
|
+
afterReplicaId: test_utils.rid('checkpoint-1')
|
|
779
|
+
});
|
|
780
|
+
expect(emittedEvents).toEqual([{ name: 'write_checkpoints', eventId }]);
|
|
781
|
+
} finally {
|
|
782
|
+
disposeListener();
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
);
|
|
786
|
+
|
|
787
|
+
test.runIf(storageVersion >= 3)('creates snapshot work for a changed event definition', async () => {
|
|
788
|
+
const yaml = (active: boolean) => `
|
|
789
|
+
config:
|
|
790
|
+
edition: 3
|
|
650
791
|
|
|
651
|
-
|
|
792
|
+
streams:
|
|
793
|
+
by_owner:
|
|
794
|
+
query: SELECT * FROM todos WHERE owner_id = subscription.parameter('owner_id')
|
|
795
|
+
|
|
796
|
+
event_definitions:
|
|
797
|
+
write_checkpoints:
|
|
798
|
+
payloads:
|
|
799
|
+
- SELECT user_id, checkpoint FROM checkpoints WHERE active = ${active}
|
|
800
|
+
`;
|
|
801
|
+
|
|
802
|
+
await using factory = await storageConfig.factory();
|
|
803
|
+
const emittedEvents: { name: string; eventId: string | undefined }[] = [];
|
|
804
|
+
const disposeListener = factory.registerListener({
|
|
805
|
+
replicationEvent: ({ event, event_id }) => emittedEvents.push({ name: event.name, eventId: event_id })
|
|
806
|
+
});
|
|
807
|
+
try {
|
|
808
|
+
const first = await factory.updateSyncRules(updateSyncRulesFromYaml(yaml(true), { storageVersion }));
|
|
809
|
+
const firstStorage = factory.getInstance(first) as MongoSyncBucketStorage;
|
|
810
|
+
await using firstWriter = await firstStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
811
|
+
const source = sourceDescriptor('checkpoints', { objectId: 'checkpoints-relation' });
|
|
812
|
+
const firstResolved = await firstWriter.resolveTables({
|
|
652
813
|
connection_id: 1,
|
|
653
814
|
source,
|
|
654
|
-
idGenerator: objectIdGenerator('
|
|
655
|
-
parsedSyncConfig: dataOnlyRules
|
|
815
|
+
idGenerator: objectIdGenerator('6544e3899293153fa7b38352')
|
|
656
816
|
});
|
|
657
|
-
|
|
658
|
-
|
|
817
|
+
const oldEventId = [...firstResolved.tables[0].eventDefinitionIds!][0];
|
|
818
|
+
await firstWriter.markTableSnapshotDone(firstResolved.tables, '1/1');
|
|
819
|
+
await firstWriter.markAllSnapshotDone('1/1');
|
|
820
|
+
await firstWriter.commit('1/1');
|
|
659
821
|
|
|
660
|
-
|
|
661
|
-
|
|
822
|
+
const second = await factory.updateSyncRules(updateSyncRulesFromYaml(yaml(false), { storageVersion }));
|
|
823
|
+
expect(second.replicationStreamId).toBe(first.replicationStreamId);
|
|
824
|
+
const secondStorage = factory.getInstance(second) as MongoSyncBucketStorage;
|
|
825
|
+
await using secondWriter = await secondStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
826
|
+
const resolved = await secondWriter.resolveTables({
|
|
662
827
|
connection_id: 1,
|
|
663
828
|
source,
|
|
664
|
-
idGenerator: objectIdGenerator('
|
|
665
|
-
parsedSyncConfig: fullRules
|
|
829
|
+
idGenerator: objectIdGenerator('6544e3899293153fa7b38353')
|
|
666
830
|
});
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
const
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
expect(
|
|
831
|
+
|
|
832
|
+
expect(resolved.tables).toHaveLength(2);
|
|
833
|
+
const oldTable = resolved.tables.find((table) => table.eventDefinitionIds?.has(oldEventId))!;
|
|
834
|
+
const newTable = resolved.tables.find((table) => !table.eventDefinitionIds?.has(oldEventId))!;
|
|
835
|
+
const newEventId = [...newTable.eventDefinitionIds!][0];
|
|
836
|
+
expect(newEventId).toBeDefined();
|
|
837
|
+
expect(newEventId).not.toBe(oldEventId);
|
|
838
|
+
expect(oldTable.snapshotComplete).toBe(true);
|
|
839
|
+
expect(newTable.snapshotComplete).toBe(false);
|
|
840
|
+
expect(newTable.bucketDataSources).toEqual([]);
|
|
841
|
+
expect(newTable.parameterLookupSources).toEqual([]);
|
|
842
|
+
|
|
843
|
+
await expect(secondWriter.markSnapshotDone('2/1')).rejects.toThrow(
|
|
844
|
+
'Cannot mark snapshot done while source tables still require snapshotting'
|
|
845
|
+
);
|
|
846
|
+
|
|
847
|
+
emittedEvents.length = 0;
|
|
848
|
+
await secondWriter.save({
|
|
849
|
+
sourceTable: newTable,
|
|
850
|
+
tag: storage.SaveOperationTag.INSERT,
|
|
851
|
+
after: { id: 'checkpoint-2', user_id: 'user-2', checkpoint: 2n, active: 0 },
|
|
852
|
+
afterReplicaId: test_utils.rid('checkpoint-2')
|
|
853
|
+
});
|
|
854
|
+
expect(emittedEvents).toEqual([{ name: 'write_checkpoints', eventId: newEventId }]);
|
|
855
|
+
|
|
856
|
+
await secondWriter.markTableSnapshotDone([newTable], '2/1');
|
|
857
|
+
await secondWriter.markSnapshotDone('2/1');
|
|
858
|
+
} finally {
|
|
859
|
+
disposeListener();
|
|
677
860
|
}
|
|
678
|
-
);
|
|
861
|
+
});
|
|
679
862
|
|
|
680
863
|
test.runIf(storageVersion >= 3)('uses v3 mongodb model shapes', async () => {
|
|
681
864
|
await using factory = await storageConfig.factory();
|
|
@@ -1159,6 +1342,100 @@ streams:
|
|
|
1159
1342
|
}
|
|
1160
1343
|
);
|
|
1161
1344
|
|
|
1345
|
+
test.runIf(storageVersion >= 3)(
|
|
1346
|
+
'restart creates a replacement stream instead of appending to the active stream',
|
|
1347
|
+
async () => {
|
|
1348
|
+
await using factory = await storageConfig.factory();
|
|
1349
|
+
|
|
1350
|
+
const active = await factory.updateSyncRules(
|
|
1351
|
+
updateSyncRulesFromYaml(
|
|
1352
|
+
`
|
|
1353
|
+
config:
|
|
1354
|
+
edition: 3
|
|
1355
|
+
|
|
1356
|
+
streams:
|
|
1357
|
+
by_owner:
|
|
1358
|
+
query: SELECT * FROM todos WHERE owner_id = subscription.parameter('owner_id')
|
|
1359
|
+
`,
|
|
1360
|
+
{ storageVersion }
|
|
1361
|
+
)
|
|
1362
|
+
);
|
|
1363
|
+
const activeStorage = factory.getInstance(active) as MongoSyncBucketStorage;
|
|
1364
|
+
await using activeWriter = await activeStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
1365
|
+
await activeWriter.markAllSnapshotDone('1/1');
|
|
1366
|
+
await activeWriter.commit('1/1');
|
|
1367
|
+
|
|
1368
|
+
await factory.restartReplication(active.replicationStreamId);
|
|
1369
|
+
|
|
1370
|
+
const replicatingStreams = await factory.getReplicatingReplicationStreams();
|
|
1371
|
+
expect(replicatingStreams).toHaveLength(1);
|
|
1372
|
+
expect(replicatingStreams[0].replicationStreamId).not.toEqual(active.replicationStreamId);
|
|
1373
|
+
expect(replicatingStreams[0].state).toBe(storage.SyncRuleState.PROCESSING);
|
|
1374
|
+
|
|
1375
|
+
const oldStream = (await (factory as MongoBucketStorage).db.sync_rules.findOne({
|
|
1376
|
+
_id: active.replicationStreamId
|
|
1377
|
+
})) as ReplicationStreamDocumentV3;
|
|
1378
|
+
expect(oldStream.state).toBe(storage.SyncRuleState.ERRORED);
|
|
1379
|
+
expect(oldStream.sync_configs.map((config) => config.state)).toEqual([storage.SyncRuleState.ERRORED]);
|
|
1380
|
+
}
|
|
1381
|
+
);
|
|
1382
|
+
|
|
1383
|
+
test.runIf(storageVersion >= 3)('restart errors an active stream with an embedded deploying config', async () => {
|
|
1384
|
+
await using factory = await storageConfig.factory();
|
|
1385
|
+
|
|
1386
|
+
const active = await factory.updateSyncRules(
|
|
1387
|
+
updateSyncRulesFromYaml(
|
|
1388
|
+
`
|
|
1389
|
+
config:
|
|
1390
|
+
edition: 3
|
|
1391
|
+
|
|
1392
|
+
streams:
|
|
1393
|
+
by_owner:
|
|
1394
|
+
query: SELECT * FROM todos WHERE owner_id = subscription.parameter('owner_id')
|
|
1395
|
+
`,
|
|
1396
|
+
{ storageVersion }
|
|
1397
|
+
)
|
|
1398
|
+
);
|
|
1399
|
+
const activeStorage = factory.getInstance(active) as MongoSyncBucketStorage;
|
|
1400
|
+
await using activeWriter = await activeStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
1401
|
+
await activeWriter.markAllSnapshotDone('1/1');
|
|
1402
|
+
await activeWriter.commit('1/1');
|
|
1403
|
+
|
|
1404
|
+
const deploying = await factory.updateSyncRules(
|
|
1405
|
+
updateSyncRulesFromYaml(
|
|
1406
|
+
`
|
|
1407
|
+
config:
|
|
1408
|
+
edition: 3
|
|
1409
|
+
|
|
1410
|
+
streams:
|
|
1411
|
+
by_project:
|
|
1412
|
+
query: SELECT * FROM todos WHERE project_id = subscription.parameter('project_id')
|
|
1413
|
+
`,
|
|
1414
|
+
{ storageVersion }
|
|
1415
|
+
)
|
|
1416
|
+
);
|
|
1417
|
+
expect(deploying.replicationStreamId).toEqual(active.replicationStreamId);
|
|
1418
|
+
|
|
1419
|
+
await factory.restartReplication(active.replicationStreamId);
|
|
1420
|
+
|
|
1421
|
+
const replicatingStreams = await factory.getReplicatingReplicationStreams();
|
|
1422
|
+
expect(replicatingStreams).toHaveLength(1);
|
|
1423
|
+
expect(replicatingStreams[0].replicationStreamId).not.toEqual(active.replicationStreamId);
|
|
1424
|
+
|
|
1425
|
+
const oldStream = (await (factory as MongoBucketStorage).db.sync_rules.findOne({
|
|
1426
|
+
_id: active.replicationStreamId
|
|
1427
|
+
})) as ReplicationStreamDocumentV3;
|
|
1428
|
+
expect(oldStream.state).toBe(storage.SyncRuleState.ERRORED);
|
|
1429
|
+
expect(oldStream.sync_configs.map((config) => config.state)).toEqual([
|
|
1430
|
+
storage.SyncRuleState.ERRORED,
|
|
1431
|
+
storage.SyncRuleState.STOP
|
|
1432
|
+
]);
|
|
1433
|
+
const oldActive = await factory.getActiveSyncConfig();
|
|
1434
|
+
expect(oldActive).not.toBeNull();
|
|
1435
|
+
expect(oldActive!.replicationStream.syncConfigContent).toHaveLength(1);
|
|
1436
|
+
await expect(oldActive!.storage.getCheckpoint()).resolves.toBeDefined();
|
|
1437
|
+
});
|
|
1438
|
+
|
|
1162
1439
|
test.runIf(storageVersion >= 3)(
|
|
1163
1440
|
'appended compatible config adopts the stream checkpoint instead of regressing to 0',
|
|
1164
1441
|
async () => {
|
|
@@ -1624,6 +1901,22 @@ streams:
|
|
|
1624
1901
|
expect(metricsAfter.replication_size_bytes).toBeGreaterThan(metricsBefore.replication_size_bytes);
|
|
1625
1902
|
});
|
|
1626
1903
|
|
|
1904
|
+
test.runIf(storageVersion >= 3)('storage metrics include the sync config version label', async () => {
|
|
1905
|
+
await using factory = await storageConfig.factory();
|
|
1906
|
+
const syncRules = await factory.updateSyncRules(
|
|
1907
|
+
updateSyncRulesFromYaml(MINIMAL_SYNC_RULES, { storageVersion, version_label: 'metrics-test' })
|
|
1908
|
+
);
|
|
1909
|
+
|
|
1910
|
+
const metrics = await factory.getStorageMetrics();
|
|
1911
|
+
|
|
1912
|
+
expect(metrics.sync_config_metrics).toContainEqual(
|
|
1913
|
+
expect.objectContaining({
|
|
1914
|
+
sync_config_id: syncRules.syncConfigContent[0].syncConfigId,
|
|
1915
|
+
version_label: 'metrics-test'
|
|
1916
|
+
})
|
|
1917
|
+
);
|
|
1918
|
+
});
|
|
1919
|
+
|
|
1627
1920
|
test.runIf(storageVersion >= 3)(
|
|
1628
1921
|
'loads parameter checkpoint changes across all v3 parameter index collections',
|
|
1629
1922
|
async () => {
|
|
@@ -1676,7 +1969,7 @@ streams:
|
|
|
1676
1969
|
}
|
|
1677
1970
|
);
|
|
1678
1971
|
|
|
1679
|
-
test.runIf(storageVersion
|
|
1972
|
+
test.runIf(storageVersion >= 3)('cleans pending deletes only for tracked v3 source tables', async () => {
|
|
1680
1973
|
await using factory = await storageConfig.factory();
|
|
1681
1974
|
const syncRules = await factory.updateSyncRules(
|
|
1682
1975
|
updateSyncRulesFromYaml(
|
|
@@ -1709,6 +2002,7 @@ streams:
|
|
|
1709
2002
|
snapshot_status: undefined,
|
|
1710
2003
|
bucket_data_source_ids: [],
|
|
1711
2004
|
parameter_lookup_source_ids: [],
|
|
2005
|
+
event_definition_ids: [],
|
|
1712
2006
|
latest_pending_delete: 9n
|
|
1713
2007
|
},
|
|
1714
2008
|
{
|
|
@@ -1722,6 +2016,7 @@ streams:
|
|
|
1722
2016
|
snapshot_status: undefined,
|
|
1723
2017
|
bucket_data_source_ids: [],
|
|
1724
2018
|
parameter_lookup_source_ids: [],
|
|
2019
|
+
event_definition_ids: [],
|
|
1725
2020
|
latest_pending_delete: 12n
|
|
1726
2021
|
}
|
|
1727
2022
|
]);
|
|
@@ -1810,7 +2105,7 @@ describe('sync - mongodb', () => {
|
|
|
1810
2105
|
describe(`storage v${storageVersion}`, () => {
|
|
1811
2106
|
registerSyncStorageTests(INITIALIZED_MONGO_STORAGE_FACTORY, storageVersion);
|
|
1812
2107
|
|
|
1813
|
-
describe.runIf(storageVersion
|
|
2108
|
+
describe.runIf(storageVersion >= 3)('V3 read filtering boundaries', () => {
|
|
1814
2109
|
async function setupFilteringTest() {
|
|
1815
2110
|
await using factory = await INITIALIZED_MONGO_STORAGE_FACTORY.factory();
|
|
1816
2111
|
const syncRules = await factory.updateSyncRules(
|
package/test/src/util.ts
CHANGED