@powersync/service-core-tests 0.15.5 → 0.17.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.
Files changed (35) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/dist/test-utils/AbstractStreamTestContext.d.ts +52 -0
  3. package/dist/test-utils/AbstractStreamTestContext.js +147 -0
  4. package/dist/test-utils/AbstractStreamTestContext.js.map +1 -0
  5. package/dist/test-utils/StorageDataHelpers.d.ts +3 -3
  6. package/dist/test-utils/StorageDataHelpers.js.map +1 -1
  7. package/dist/test-utils/general-utils.d.ts +13 -8
  8. package/dist/test-utils/general-utils.js +39 -25
  9. package/dist/test-utils/general-utils.js.map +1 -1
  10. package/dist/test-utils/test-utils-index.d.ts +1 -0
  11. package/dist/test-utils/test-utils-index.js +1 -0
  12. package/dist/test-utils/test-utils-index.js.map +1 -1
  13. package/dist/tests/register-compacting-tests.js +15 -8
  14. package/dist/tests/register-compacting-tests.js.map +1 -1
  15. package/dist/tests/register-data-storage-data-tests.js +104 -75
  16. package/dist/tests/register-data-storage-data-tests.js.map +1 -1
  17. package/dist/tests/register-data-storage-parameter-tests.js +48 -47
  18. package/dist/tests/register-data-storage-parameter-tests.js.map +1 -1
  19. package/dist/tests/register-sync-tests.js +6 -3
  20. package/dist/tests/register-sync-tests.js.map +1 -1
  21. package/dist/tests/util.d.ts +3 -4
  22. package/dist/tests/util.js +8 -5
  23. package/dist/tests/util.js.map +1 -1
  24. package/package.json +5 -5
  25. package/src/test-utils/AbstractStreamTestContext.ts +183 -0
  26. package/src/test-utils/StorageDataHelpers.ts +4 -4
  27. package/src/test-utils/general-utils.ts +52 -34
  28. package/src/test-utils/test-utils-index.ts +1 -0
  29. package/src/tests/register-compacting-tests.ts +15 -8
  30. package/src/tests/register-data-storage-data-tests.ts +130 -77
  31. package/src/tests/register-data-storage-parameter-tests.ts +62 -47
  32. package/src/tests/register-sync-tests.ts +6 -3
  33. package/src/tests/util.ts +14 -11
  34. package/tsconfig.json +4 -4
  35. package/tsconfig.tsbuildinfo +1 -1
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  InternalOpId,
3
3
  OplogEntry,
4
- PersistedSyncRules,
5
- PersistedSyncRulesContent,
4
+ ParsedSyncConfigSet,
5
+ PersistedSyncConfigContent,
6
6
  SyncRulesBucketStorage
7
7
  } from '@powersync/service-core';
8
8
  import { bucketRequest } from './general-utils.js';
@@ -10,9 +10,9 @@ import { fromAsync } from './stream_utils.js';
10
10
 
11
11
  export class StorageDataHelpers {
12
12
  storage: SyncRulesBucketStorage;
13
- syncRules: PersistedSyncRulesContent | PersistedSyncRules;
13
+ syncRules: PersistedSyncConfigContent | ParsedSyncConfigSet;
14
14
 
15
- constructor(storage: SyncRulesBucketStorage, syncRules: PersistedSyncRulesContent | PersistedSyncRules) {
15
+ constructor(storage: SyncRulesBucketStorage, syncRules: PersistedSyncConfigContent | ParsedSyncConfigSet) {
16
16
  this.storage = storage;
17
17
  this.syncRules = syncRules;
18
18
  }
@@ -4,7 +4,7 @@ import * as bson from 'bson';
4
4
 
5
5
  export const ZERO_LSN = '0/0';
6
6
 
7
- export const PARSE_OPTIONS: storage.ParseSyncRulesOptions = {
7
+ export const PARSE_OPTIONS: storage.ParseSyncConfigOptions = {
8
8
  defaultSchema: 'public'
9
9
  };
10
10
 
@@ -14,50 +14,65 @@ export const BATCH_OPTIONS: storage.CreateWriterOptions = {
14
14
  storeCurrentData: true
15
15
  };
16
16
 
17
- export function makeTestTable(
18
- name: string,
19
- replicaIdColumns?: string[] | undefined,
20
- options?: { tableIdStrings: boolean }
21
- ) {
22
- const relId = utils.hashData('table', name, (replicaIdColumns ?? ['id']).join(','));
23
- const id =
24
- options?.tableIdStrings == false ? new bson.ObjectId('6544e3899293153fa7b38331') : '6544e3899293153fa7b38331';
25
- return new storage.SourceTable({
26
- id: id,
27
- connectionTag: storage.SourceTable.DEFAULT_TAG,
28
- objectId: relId,
29
- schema: 'public',
30
- name: name,
31
- replicaIdColumns: (replicaIdColumns ?? ['id']).map((column) => ({ name: column, type: 'VARCHAR', typeId: 25 })),
32
- snapshotComplete: true
33
- });
34
- }
35
17
  /**
36
- * With incremental reprocessing, we need actual test tables, resolved via the writer.
18
+ * Deploy a sync config and return both the replication stream (for {@link storage.BucketStorageFactory.getInstance})
19
+ * and the deployed config content (for parsing / bucket requests).
37
20
  *
38
- * This prepares for it.
21
+ * Replication streams and sync config content are separate concerns since one stream can hold multiple configs.
22
+ */
23
+ export async function deploySyncRules(
24
+ factory: storage.BucketStorageFactory,
25
+ options: storage.UpdateSyncRulesOptions
26
+ ): Promise<{ stream: storage.PersistedReplicationStream; content: storage.PersistedSyncConfigContent }> {
27
+ const stream = await factory.updateSyncRules(options);
28
+ const content = stream.syncConfigContent[0];
29
+ return { stream, content };
30
+ }
31
+
32
+ /**
33
+ * With newer storage versions, we need actual test tables, resolved via the writer.
39
34
  */
40
35
  export async function resolveTestTable(
41
- _writer: storage.BucketStorageBatch,
36
+ writer: storage.BucketStorageBatch,
42
37
  name: string,
43
38
  replicaIdColumns: string[] | undefined,
44
39
  options: { tableIdStrings: boolean },
45
40
  idIndex: number = 1
46
41
  ) {
42
+ void idIndex;
43
+ void options;
44
+
47
45
  const relId = utils.hashData('table', name, (replicaIdColumns ?? ['id']).join(','));
48
- // Generate unique ids per test table (if idIndex is specified), without completely
49
- // breaking all the existing tests.
46
+ // Semi-hardcoded id for tests, to get consistent output.
47
+ // If the same test uses multiple tables, pass idIndex to get different ids.
50
48
  const idString = '6544e3899293153fa7b383' + (30 + idIndex).toString().padStart(2, '0');
51
49
  const id = options.tableIdStrings == false ? new bson.ObjectId(idString) : idString;
52
- return new storage.SourceTable({
53
- id: id,
50
+ let didGenerateId = false;
51
+
52
+ const source: storage.SourceEntityDescriptor = {
54
53
  connectionTag: storage.SourceTable.DEFAULT_TAG,
55
54
  objectId: relId,
56
55
  schema: 'public',
57
56
  name: name,
58
- replicaIdColumns: (replicaIdColumns ?? ['id']).map((column) => ({ name: column, type: 'VARCHAR', typeId: 25 })),
59
- snapshotComplete: true
57
+ replicaIdColumns: (replicaIdColumns ?? ['id']).map((column) => ({ name: column, type: 'VARCHAR', typeId: 25 }))
58
+ };
59
+ const resolved = await writer.resolveTables({
60
+ connection_id: 1,
61
+ source,
62
+ idGenerator: () => {
63
+ if (didGenerateId) {
64
+ throw new Error('idGenerator called multiple times - not supported in tests');
65
+ }
66
+ didGenerateId = true;
67
+ return id;
68
+ }
60
69
  });
70
+
71
+ const table = resolved.tables[0];
72
+ if (table == null) {
73
+ throw new Error(`Failed to resolve test table ${source.schema}.${source.name}`);
74
+ }
75
+ return table;
61
76
  }
62
77
 
63
78
  export function getBatchData(
@@ -78,9 +93,9 @@ export function getBatchData(
78
93
  }
79
94
 
80
95
  function isParsedSyncRules(
81
- syncRules: storage.PersistedSyncRulesContent | storage.PersistedSyncRules
82
- ): syncRules is storage.PersistedSyncRules {
83
- return (syncRules as storage.PersistedSyncRules).sync_rules !== undefined;
96
+ syncRules: storage.PersistedSyncConfigContent | storage.ParsedSyncConfigSet
97
+ ): syncRules is storage.ParsedSyncConfigSet {
98
+ return (syncRules as storage.ParsedSyncConfigSet).syncConfigs !== undefined;
84
99
  }
85
100
 
86
101
  /**
@@ -88,7 +103,7 @@ function isParsedSyncRules(
88
103
  * This converts a bucket name like "global[]" into the actual bucket name, for use in tests.
89
104
  */
90
105
  export function bucketRequest(
91
- syncRules: storage.PersistedSyncRulesContent | storage.PersistedSyncRules,
106
+ syncRules: storage.PersistedSyncConfigContent | storage.ParsedSyncConfigSet,
92
107
  bucket: string,
93
108
  start?: InternalOpId | string | number
94
109
  ): BucketDataRequest {
@@ -97,10 +112,13 @@ export function bucketRequest(
97
112
  const parameterStart = bucket.indexOf('[');
98
113
  const definitionName = bucket.substring(0, parameterStart);
99
114
  const parameters = bucket.substring(parameterStart);
100
- const source = parsed.sync_rules.config.bucketDataSources.find((b) => b.uniqueName === definitionName);
115
+ const availableSources = parsed.syncConfigs.flatMap((config) => config.config.bucketDataSources);
116
+ const source = availableSources.find((b) => b.uniqueName === definitionName);
101
117
 
102
118
  if (source == null) {
103
- throw new Error(`Failed to find global bucket ${bucket}`);
119
+ throw new Error(
120
+ `Failed to find global bucket ${bucket}. Available: ${availableSources.map((s) => s.uniqueName).join(',')}`
121
+ );
104
122
  }
105
123
  const bucketName = hydrationState.getBucketSourceScope(source).bucketPrefix + parameters;
106
124
  return {
@@ -1,3 +1,4 @@
1
+ export * from './AbstractStreamTestContext.js';
1
2
  export * from './bucket-validation.js';
2
3
  export * from './general-utils.js';
3
4
  export * from './MetricsHelper.js';
@@ -17,6 +17,7 @@ bucket_definitions:
17
17
  `)
18
18
  );
19
19
  const bucketStorage = factory.getInstance(syncRules);
20
+ const syncRulesContent = syncRules.syncConfigContent[0];
20
21
 
21
22
  await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
22
23
  const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
@@ -53,7 +54,7 @@ bucket_definitions:
53
54
 
54
55
  const checkpoint = writer.last_flushed_op!;
55
56
 
56
- const request = bucketRequest(syncRules, 'global[]');
57
+ const request = bucketRequest(syncRulesContent, 'global[]');
57
58
 
58
59
  const batchBefore = await test_utils.oneFromAsync(bucketStorage.getBucketDataBatch(checkpoint, [request]));
59
60
  const dataBefore = batchBefore.chunkData.data;
@@ -124,6 +125,7 @@ bucket_definitions:
124
125
  `)
125
126
  );
126
127
  const bucketStorage = factory.getInstance(syncRules);
128
+ const syncRulesContent = syncRules.syncConfigContent[0];
127
129
 
128
130
  await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
129
131
  const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
@@ -168,7 +170,7 @@ bucket_definitions:
168
170
  await writer.flush();
169
171
 
170
172
  const checkpoint = writer.last_flushed_op!;
171
- const request = bucketRequest(syncRules, 'global[]');
173
+ const request = bucketRequest(syncRulesContent, 'global[]');
172
174
 
173
175
  const batchBefore = await test_utils.oneFromAsync(bucketStorage.getBucketDataBatch(checkpoint, [request]));
174
176
  const dataBefore = batchBefore.chunkData.data;
@@ -240,6 +242,7 @@ bucket_definitions:
240
242
  `)
241
243
  );
242
244
  const bucketStorage = factory.getInstance(syncRules);
245
+ const syncRulesContent = syncRules.syncConfigContent[0];
243
246
 
244
247
  await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
245
248
  const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
@@ -275,7 +278,7 @@ bucket_definitions:
275
278
  await writer.flush();
276
279
 
277
280
  const checkpoint1 = writer.last_flushed_op!;
278
- const request = bucketRequest(syncRules, 'global[]');
281
+ const request = bucketRequest(syncRulesContent, 'global[]');
279
282
  await using writer2 = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
280
283
  const testTable2 = await test_utils.resolveTestTable(writer2, 'test', ['id'], config);
281
284
  await writer2.save({
@@ -328,6 +331,7 @@ bucket_definitions:
328
331
  - select * from test where b = bucket.b`)
329
332
  );
330
333
  const bucketStorage = factory.getInstance(syncRules);
334
+ const syncRulesContent = syncRules.syncConfigContent[0];
331
335
 
332
336
  await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
333
337
  const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
@@ -413,7 +417,7 @@ bucket_definitions:
413
417
  const batchAfter = await test_utils.fromAsync(
414
418
  bucketStorage.getBucketDataBatch(
415
419
  checkpoint,
416
- bucketRequestMap(syncRules, [
420
+ bucketRequestMap(syncRulesContent, [
417
421
  ['grouped["b1"]', 0n],
418
422
  ['grouped["b2"]', 0n]
419
423
  ])
@@ -458,6 +462,7 @@ bucket_definitions:
458
462
  `)
459
463
  );
460
464
  const bucketStorage = factory.getInstance(syncRules);
465
+ const syncRulesContent = syncRules.syncConfigContent[0];
461
466
 
462
467
  await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
463
468
  const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
@@ -513,7 +518,7 @@ bucket_definitions:
513
518
  await writer2.commit('2/1');
514
519
  await writer2.flush();
515
520
  const checkpoint2 = writer2.last_flushed_op!;
516
- const request = bucketRequest(syncRules, 'global[]');
521
+ const request = bucketRequest(syncRulesContent, 'global[]');
517
522
  await bucketStorage.clearChecksumCache();
518
523
  const checksumAfter = await bucketStorage.getChecksums(checkpoint2, [request]);
519
524
  const globalChecksum = checksumAfter.get(request.bucket);
@@ -536,6 +541,7 @@ bucket_definitions:
536
541
  `)
537
542
  );
538
543
  const bucketStorage = factory.getInstance(syncRules);
544
+ const syncRulesContent = syncRules.syncConfigContent[0];
539
545
 
540
546
  await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
541
547
  const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
@@ -562,7 +568,7 @@ bucket_definitions:
562
568
  await writer.flush();
563
569
 
564
570
  // Get checksums here just to populate the cache
565
- await bucketStorage.getChecksums(writer.last_flushed_op!, bucketRequests(syncRules, ['global[]']));
571
+ await bucketStorage.getChecksums(writer.last_flushed_op!, bucketRequests(syncRulesContent, ['global[]']));
566
572
  await using writer2 = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
567
573
  const testTable2 = await test_utils.resolveTestTable(writer2, 'test', ['id'], config);
568
574
  await writer2.save({
@@ -585,7 +591,7 @@ bucket_definitions:
585
591
  });
586
592
 
587
593
  const checkpoint2 = writer2.last_flushed_op!;
588
- const request = bucketRequest(syncRules, 'global[]');
594
+ const request = bucketRequest(syncRulesContent, 'global[]');
589
595
  // Check that the checksum was correctly updated with the clear operation after having a cached checksum
590
596
  const checksumAfter = await bucketStorage.getChecksums(checkpoint2, [request]);
591
597
  const globalChecksum = checksumAfter.get(request.bucket);
@@ -607,6 +613,7 @@ bucket_definitions:
607
613
  `)
608
614
  );
609
615
  const bucketStorage = factory.getInstance(syncRules);
616
+ const syncRulesContent = syncRules.syncConfigContent[0];
610
617
 
611
618
  await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
612
619
  const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
@@ -646,7 +653,7 @@ bucket_definitions:
646
653
  });
647
654
 
648
655
  const batchAfterDefaultCompact = await test_utils.oneFromAsync(
649
- bucketStorage.getBucketDataBatch(checkpoint2, bucketRequestMap(syncRules, [['global[]', 0n]]))
656
+ bucketStorage.getBucketDataBatch(checkpoint2, bucketRequestMap(syncRulesContent, [['global[]', 0n]]))
650
657
  );
651
658
 
652
659
  // Operation 1 should remain a PUT because op_id=2 is above the default maxOpId checkpoint.