@powersync/service-module-mongodb-storage 0.19.0 → 0.21.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 +64 -0
- 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 +43 -2
- 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 +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 +2 -0
- package/dist/storage/implementation/v3/PersistedBatchV3.js +52 -11
- 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 +54 -2
- 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 +111 -41
- package/src/storage/implementation/v3/MongoWriteCheckpointAPIV3.ts +111 -0
- package/src/storage/implementation/v3/PersistedBatchV3.ts +57 -12
- 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
|
@@ -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,11 +115,65 @@ 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,
|
|
120
146
|
tableIdStrings: storageConfig.tableIdStrings
|
|
121
147
|
});
|
|
148
|
+
|
|
149
|
+
test('updates source metadata on an existing resolved table', async () => {
|
|
150
|
+
await using factory = await storageConfig.factory();
|
|
151
|
+
const syncRules = await factory.updateSyncRules(updateSyncRulesFromYaml(MINIMAL_SYNC_RULES, { storageVersion }));
|
|
152
|
+
const bucketStorage = factory.getInstance(syncRules);
|
|
153
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
154
|
+
const source = sourceDescriptor('test');
|
|
155
|
+
|
|
156
|
+
const initial = await writer.resolveTables({ connection_id: 1, source });
|
|
157
|
+
expect(initial.tables[0].sourceMetadata).toBeNull();
|
|
158
|
+
|
|
159
|
+
const sourceMetadata = { captureTableObjectId: 42 };
|
|
160
|
+
const updated = await writer.resolveTables({
|
|
161
|
+
connection_id: 1,
|
|
162
|
+
source,
|
|
163
|
+
reconcileSourceTables: ({ candidates }) => ({
|
|
164
|
+
compatibleTables: candidates.map((candidate) => candidate.withSourceMetadata(sourceMetadata)),
|
|
165
|
+
incompatibleTables: [],
|
|
166
|
+
newTableValues: { sourceMetadata }
|
|
167
|
+
})
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
expect(updated.tables.map((table) => table.id.toString())).toEqual(
|
|
171
|
+
initial.tables.map((table) => table.id.toString())
|
|
172
|
+
);
|
|
173
|
+
expect(updated.tables[0].sourceMetadata).toEqual(sourceMetadata);
|
|
174
|
+
expect((await writer.getSourceTableStatus(updated.tables[0]))?.sourceMetadata).toEqual(sourceMetadata);
|
|
175
|
+
});
|
|
176
|
+
|
|
122
177
|
// The split of returned results can vary depending on storage drivers
|
|
123
178
|
test('large batch (2)', async () => {
|
|
124
179
|
// Test syncing a batch of data that is small in count,
|
|
@@ -504,6 +559,7 @@ function registerSyncStorageTests(storageConfig: storage.TestStorageConfig, stor
|
|
|
504
559
|
const source = sourceDescriptor('memberships', { objectId: 'memberships-relation' });
|
|
505
560
|
const dataOnlyTableId = new bson.ObjectId('6544e3899293153fa7b38348');
|
|
506
561
|
const addedParameterTableId = new bson.ObjectId('6544e3899293153fa7b38349');
|
|
562
|
+
const addedEventTableId = new bson.ObjectId('6544e3899293153fa7b3834a');
|
|
507
563
|
|
|
508
564
|
const dataOnly = await writer.resolveTables({
|
|
509
565
|
connection_id: 1,
|
|
@@ -561,15 +617,13 @@ function registerSyncStorageTests(storageConfig: storage.TestStorageConfig, stor
|
|
|
561
617
|
const eventOnly = await writer.resolveTables({
|
|
562
618
|
connection_id: 1,
|
|
563
619
|
source,
|
|
564
|
-
idGenerator: () =>
|
|
565
|
-
throw new Error('resolve should reuse existing v3 source table');
|
|
566
|
-
},
|
|
620
|
+
idGenerator: () => addedEventTableId,
|
|
567
621
|
parsedSyncConfig: eventOnlyRules
|
|
568
622
|
});
|
|
569
623
|
|
|
570
|
-
//
|
|
624
|
+
// A newly-added event gets its own source table so that its existing rows are snapshotted.
|
|
571
625
|
expect(eventOnly.tables).toHaveLength(1);
|
|
572
|
-
expect(
|
|
626
|
+
expect(eventOnly.tables[0].id).toEqual(addedEventTableId);
|
|
573
627
|
expect(eventOnly.dropTables.map((table) => table.id)).toEqual([]);
|
|
574
628
|
expect(eventOnly.tables[0].bucketDataSources).toHaveLength(0);
|
|
575
629
|
expect(eventOnly.tables[0].parameterLookupSources).toHaveLength(0);
|
|
@@ -578,13 +632,12 @@ function registerSyncStorageTests(storageConfig: storage.TestStorageConfig, stor
|
|
|
578
632
|
expect(eventOnly.tables[0].syncEvent).toBe(true);
|
|
579
633
|
});
|
|
580
634
|
|
|
581
|
-
test.runIf(storageVersion >= 3)(
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
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 = `
|
|
588
641
|
bucket_definitions:
|
|
589
642
|
by_owner:
|
|
590
643
|
parameters:
|
|
@@ -597,7 +650,7 @@ function registerSyncStorageTests(storageConfig: storage.TestStorageConfig, stor
|
|
|
597
650
|
payloads:
|
|
598
651
|
- SELECT id, owner_id FROM memberships
|
|
599
652
|
`;
|
|
600
|
-
|
|
653
|
+
const fullEventYaml = `
|
|
601
654
|
bucket_definitions:
|
|
602
655
|
by_owner:
|
|
603
656
|
parameters:
|
|
@@ -611,42 +664,201 @@ function registerSyncStorageTests(storageConfig: storage.TestStorageConfig, stor
|
|
|
611
664
|
- SELECT id, owner_id FROM memberships
|
|
612
665
|
`;
|
|
613
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
|
+
|
|
614
735
|
await using factory = await storageConfig.factory();
|
|
615
|
-
const
|
|
616
|
-
const
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
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
|
+
);
|
|
621
786
|
|
|
622
|
-
|
|
787
|
+
test.runIf(storageVersion >= 3)('creates snapshot work for a changed event definition', async () => {
|
|
788
|
+
const yaml = (active: boolean) => `
|
|
789
|
+
config:
|
|
790
|
+
edition: 3
|
|
791
|
+
|
|
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({
|
|
623
813
|
connection_id: 1,
|
|
624
814
|
source,
|
|
625
|
-
idGenerator: objectIdGenerator('
|
|
626
|
-
parsedSyncConfig: dataOnlyRules
|
|
815
|
+
idGenerator: objectIdGenerator('6544e3899293153fa7b38352')
|
|
627
816
|
});
|
|
628
|
-
|
|
629
|
-
|
|
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');
|
|
630
821
|
|
|
631
|
-
|
|
632
|
-
|
|
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({
|
|
633
827
|
connection_id: 1,
|
|
634
828
|
source,
|
|
635
|
-
idGenerator: objectIdGenerator('
|
|
636
|
-
parsedSyncConfig: fullRules
|
|
829
|
+
idGenerator: objectIdGenerator('6544e3899293153fa7b38353')
|
|
637
830
|
});
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
const
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
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();
|
|
648
860
|
}
|
|
649
|
-
);
|
|
861
|
+
});
|
|
650
862
|
|
|
651
863
|
test.runIf(storageVersion >= 3)('uses v3 mongodb model shapes', async () => {
|
|
652
864
|
await using factory = await storageConfig.factory();
|
|
@@ -1130,6 +1342,100 @@ streams:
|
|
|
1130
1342
|
}
|
|
1131
1343
|
);
|
|
1132
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
|
+
|
|
1133
1439
|
test.runIf(storageVersion >= 3)(
|
|
1134
1440
|
'appended compatible config adopts the stream checkpoint instead of regressing to 0',
|
|
1135
1441
|
async () => {
|
|
@@ -1595,6 +1901,22 @@ streams:
|
|
|
1595
1901
|
expect(metricsAfter.replication_size_bytes).toBeGreaterThan(metricsBefore.replication_size_bytes);
|
|
1596
1902
|
});
|
|
1597
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
|
+
|
|
1598
1920
|
test.runIf(storageVersion >= 3)(
|
|
1599
1921
|
'loads parameter checkpoint changes across all v3 parameter index collections',
|
|
1600
1922
|
async () => {
|
|
@@ -1647,7 +1969,7 @@ streams:
|
|
|
1647
1969
|
}
|
|
1648
1970
|
);
|
|
1649
1971
|
|
|
1650
|
-
test.runIf(storageVersion
|
|
1972
|
+
test.runIf(storageVersion >= 3)('cleans pending deletes only for tracked v3 source tables', async () => {
|
|
1651
1973
|
await using factory = await storageConfig.factory();
|
|
1652
1974
|
const syncRules = await factory.updateSyncRules(
|
|
1653
1975
|
updateSyncRulesFromYaml(
|
|
@@ -1680,6 +2002,7 @@ streams:
|
|
|
1680
2002
|
snapshot_status: undefined,
|
|
1681
2003
|
bucket_data_source_ids: [],
|
|
1682
2004
|
parameter_lookup_source_ids: [],
|
|
2005
|
+
event_definition_ids: [],
|
|
1683
2006
|
latest_pending_delete: 9n
|
|
1684
2007
|
},
|
|
1685
2008
|
{
|
|
@@ -1693,6 +2016,7 @@ streams:
|
|
|
1693
2016
|
snapshot_status: undefined,
|
|
1694
2017
|
bucket_data_source_ids: [],
|
|
1695
2018
|
parameter_lookup_source_ids: [],
|
|
2019
|
+
event_definition_ids: [],
|
|
1696
2020
|
latest_pending_delete: 12n
|
|
1697
2021
|
}
|
|
1698
2022
|
]);
|
|
@@ -1781,7 +2105,7 @@ describe('sync - mongodb', () => {
|
|
|
1781
2105
|
describe(`storage v${storageVersion}`, () => {
|
|
1782
2106
|
registerSyncStorageTests(INITIALIZED_MONGO_STORAGE_FACTORY, storageVersion);
|
|
1783
2107
|
|
|
1784
|
-
describe.runIf(storageVersion
|
|
2108
|
+
describe.runIf(storageVersion >= 3)('V3 read filtering boundaries', () => {
|
|
1785
2109
|
async function setupFilteringTest() {
|
|
1786
2110
|
await using factory = await INITIALIZED_MONGO_STORAGE_FACTORY.factory();
|
|
1787
2111
|
const syncRules = await factory.updateSyncRules(
|
package/test/src/util.ts
CHANGED