@powersync/service-core 1.20.0 → 1.20.1

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 (45) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/routes/endpoints/admin.js +1 -0
  3. package/dist/routes/endpoints/admin.js.map +1 -1
  4. package/dist/routes/endpoints/sync-stream.js +6 -1
  5. package/dist/routes/endpoints/sync-stream.js.map +1 -1
  6. package/dist/storage/BucketStorageBatch.d.ts +21 -8
  7. package/dist/storage/BucketStorageBatch.js.map +1 -1
  8. package/dist/storage/BucketStorageFactory.d.ts +5 -0
  9. package/dist/storage/ChecksumCache.d.ts +5 -2
  10. package/dist/storage/ChecksumCache.js +8 -4
  11. package/dist/storage/ChecksumCache.js.map +1 -1
  12. package/dist/storage/PersistedSyncRulesContent.d.ts +6 -2
  13. package/dist/storage/PersistedSyncRulesContent.js +2 -1
  14. package/dist/storage/PersistedSyncRulesContent.js.map +1 -1
  15. package/dist/storage/SourceTable.d.ts +7 -2
  16. package/dist/storage/SourceTable.js.map +1 -1
  17. package/dist/storage/StorageVersionConfig.d.ts +33 -0
  18. package/dist/storage/StorageVersionConfig.js +39 -6
  19. package/dist/storage/StorageVersionConfig.js.map +1 -1
  20. package/dist/storage/SyncRulesBucketStorage.d.ts +12 -3
  21. package/dist/storage/SyncRulesBucketStorage.js.map +1 -1
  22. package/dist/sync/BucketChecksumState.d.ts +3 -3
  23. package/dist/sync/BucketChecksumState.js +12 -42
  24. package/dist/sync/BucketChecksumState.js.map +1 -1
  25. package/dist/sync/sync.js.map +1 -1
  26. package/dist/sync/util.d.ts +1 -0
  27. package/dist/sync/util.js +10 -0
  28. package/dist/sync/util.js.map +1 -1
  29. package/package.json +4 -4
  30. package/src/routes/endpoints/admin.ts +1 -0
  31. package/src/routes/endpoints/sync-stream.ts +6 -1
  32. package/src/storage/BucketStorageBatch.ts +23 -9
  33. package/src/storage/BucketStorageFactory.ts +6 -0
  34. package/src/storage/ChecksumCache.ts +14 -6
  35. package/src/storage/PersistedSyncRulesContent.ts +7 -2
  36. package/src/storage/SourceTable.ts +7 -1
  37. package/src/storage/StorageVersionConfig.ts +54 -6
  38. package/src/storage/SyncRulesBucketStorage.ts +18 -3
  39. package/src/sync/BucketChecksumState.ts +18 -49
  40. package/src/sync/sync.ts +9 -3
  41. package/src/sync/util.ts +10 -0
  42. package/test/src/checksum_cache.test.ts +102 -57
  43. package/test/src/sync/BucketChecksumState.test.ts +53 -21
  44. package/test/src/utils.ts +9 -0
  45. package/tsconfig.tsbuildinfo +1 -1
@@ -14,15 +14,43 @@ import {
14
14
  } from '@/index.js';
15
15
  import { JSONBig } from '@powersync/service-jsonbig';
16
16
  import {
17
+ ParameterIndexLookupCreator,
17
18
  RequestJwtPayload,
18
19
  ScopedParameterLookup,
19
20
  SqliteJsonRow,
21
+ SqliteRow,
20
22
  SqlSyncRules,
23
+ TablePattern,
24
+ SourceTableInterface,
21
25
  versionedHydrationState
22
26
  } from '@powersync/service-sync-rules';
27
+ import { ParameterLookupScope } from '@powersync/service-sync-rules/src/HydrationState.js';
23
28
  import { beforeEach, describe, expect, test } from 'vitest';
24
29
 
25
30
  describe('BucketChecksumState', () => {
31
+ const LOOKUP_SOURCE: ParameterIndexLookupCreator = {
32
+ get defaultLookupScope(): ParameterLookupScope {
33
+ return {
34
+ lookupName: 'lookup',
35
+ queryId: '0',
36
+ source: LOOKUP_SOURCE
37
+ };
38
+ },
39
+ getSourceTables(): Set<TablePattern> {
40
+ return new Set();
41
+ },
42
+ evaluateParameterRow(_sourceTable: SourceTableInterface, _row: SqliteRow) {
43
+ return [];
44
+ },
45
+ tableSyncsParameters(_table: SourceTableInterface): boolean {
46
+ return false;
47
+ }
48
+ };
49
+
50
+ function lookupScope(lookupName: string, queryId: string): ParameterLookupScope {
51
+ return { lookupName, queryId, source: LOOKUP_SOURCE };
52
+ }
53
+
26
54
  // Single global[] bucket.
27
55
  // We don't care about data in these tests
28
56
  const SYNC_RULES_GLOBAL = SqlSyncRules.fromYaml(
@@ -67,6 +95,10 @@ bucket_definitions:
67
95
  const syncRequest: StreamingSyncRequest = {};
68
96
  const tokenPayload = new JwtPayload({ sub: '' });
69
97
 
98
+ function bucketStarts(requests: { bucket: string; start: InternalOpId }[]) {
99
+ return new Map(requests.map((request) => [request.bucket, request.start]));
100
+ }
101
+
70
102
  test('global bucket with update', async () => {
71
103
  const storage = new MockBucketChecksumStateStorage();
72
104
  // Set intial state
@@ -94,14 +126,14 @@ bucket_definitions:
94
126
  streams: [{ name: 'global', is_default: true, errors: [] }]
95
127
  }
96
128
  });
97
- expect(line.bucketsToFetch).toEqual([
129
+ expect(line.bucketsToFetch).toMatchObject([
98
130
  {
99
131
  bucket: '1#global[]',
100
132
  priority: 3
101
133
  }
102
134
  ]);
103
135
  // This is the bucket data to be fetched
104
- expect(line.getFilteredBucketPositions()).toEqual(new Map([['1#global[]', 0n]]));
136
+ expect(bucketStarts(line.getFilteredBucketPositions())).toEqual(new Map([['1#global[]', 0n]]));
105
137
 
106
138
  // This similuates the bucket data being sent
107
139
  line.advance();
@@ -132,7 +164,7 @@ bucket_definitions:
132
164
  write_checkpoint: undefined
133
165
  }
134
166
  });
135
- expect(line2.getFilteredBucketPositions()).toEqual(new Map([['1#global[]', 1n]]));
167
+ expect(bucketStarts(line2.getFilteredBucketPositions())).toEqual(new Map([['1#global[]', 1n]]));
136
168
  });
137
169
 
138
170
  test('global bucket with initial state', async () => {
@@ -166,14 +198,14 @@ bucket_definitions:
166
198
  streams: [{ name: 'global', is_default: true, errors: [] }]
167
199
  }
168
200
  });
169
- expect(line.bucketsToFetch).toEqual([
201
+ expect(line.bucketsToFetch).toMatchObject([
170
202
  {
171
203
  bucket: '1#global[]',
172
204
  priority: 3
173
205
  }
174
206
  ]);
175
207
  // This is the main difference between this and the previous test
176
- expect(line.getFilteredBucketPositions()).toEqual(new Map([['1#global[]', 1n]]));
208
+ expect(bucketStarts(line.getFilteredBucketPositions())).toEqual(new Map([['1#global[]', 1n]]));
177
209
  });
178
210
 
179
211
  test('multiple static buckets', async () => {
@@ -206,7 +238,7 @@ bucket_definitions:
206
238
  streams: [{ name: 'global', is_default: true, errors: [] }]
207
239
  }
208
240
  });
209
- expect(line.bucketsToFetch).toEqual([
241
+ expect(line.bucketsToFetch).toMatchObject([
210
242
  {
211
243
  bucket: '2#global[1]',
212
244
  priority: 3
@@ -274,13 +306,13 @@ bucket_definitions:
274
306
  streams: [{ name: 'global', is_default: true, errors: [] }]
275
307
  }
276
308
  });
277
- expect(line.bucketsToFetch).toEqual([
309
+ expect(line.bucketsToFetch).toMatchObject([
278
310
  {
279
311
  bucket: '1#global[]',
280
312
  priority: 3
281
313
  }
282
314
  ]);
283
- expect(line.getFilteredBucketPositions()).toEqual(new Map([['1#global[]', 0n]]));
315
+ expect(bucketStarts(line.getFilteredBucketPositions())).toEqual(new Map([['1#global[]', 0n]]));
284
316
  });
285
317
 
286
318
  test('invalidating individual bucket', async () => {
@@ -337,7 +369,7 @@ bucket_definitions:
337
369
  write_checkpoint: undefined
338
370
  }
339
371
  });
340
- expect(line2.bucketsToFetch).toEqual([{ bucket: '2#global[1]', priority: 3 }]);
372
+ expect(line2.bucketsToFetch).toMatchObject([{ bucket: '2#global[1]', priority: 3 }]);
341
373
  });
342
374
 
343
375
  test('invalidating all buckets', async () => {
@@ -387,7 +419,7 @@ bucket_definitions:
387
419
  write_checkpoint: undefined
388
420
  }
389
421
  });
390
- expect(line2.bucketsToFetch).toEqual([
422
+ expect(line2.bucketsToFetch).toMatchObject([
391
423
  { bucket: '2#global[1]', priority: 3 },
392
424
  { bucket: '2#global[2]', priority: 3 }
393
425
  ]);
@@ -424,7 +456,7 @@ bucket_definitions:
424
456
  streams: [{ name: 'global', is_default: true, errors: [] }]
425
457
  }
426
458
  });
427
- expect(line.bucketsToFetch).toEqual([
459
+ expect(line.bucketsToFetch).toMatchObject([
428
460
  {
429
461
  bucket: '2#global[1]',
430
462
  priority: 3
@@ -436,7 +468,7 @@ bucket_definitions:
436
468
  ]);
437
469
 
438
470
  // This is the bucket data to be fetched
439
- expect(line.getFilteredBucketPositions()).toEqual(
471
+ expect(bucketStarts(line.getFilteredBucketPositions())).toEqual(
440
472
  new Map([
441
473
  ['2#global[1]', 0n],
442
474
  ['2#global[2]', 0n]
@@ -477,7 +509,7 @@ bucket_definitions:
477
509
  }
478
510
  });
479
511
  // This should contain both buckets, even though only one changed.
480
- expect(line2.bucketsToFetch).toEqual([
512
+ expect(line2.bucketsToFetch).toMatchObject([
481
513
  {
482
514
  bucket: '2#global[1]',
483
515
  priority: 3
@@ -488,7 +520,7 @@ bucket_definitions:
488
520
  }
489
521
  ]);
490
522
 
491
- expect(line2.getFilteredBucketPositions()).toEqual(
523
+ expect(bucketStarts(line2.getFilteredBucketPositions())).toEqual(
492
524
  new Map([
493
525
  ['2#global[1]', 3n],
494
526
  ['2#global[2]', 1n]
@@ -513,7 +545,7 @@ bucket_definitions:
513
545
 
514
546
  const line = (await state.buildNextCheckpointLine({
515
547
  base: storage.makeCheckpoint(1n, (lookups) => {
516
- expect(lookups).toEqual([ScopedParameterLookup.direct({ lookupName: 'by_project', queryId: '1' }, ['u1'])]);
548
+ expect(lookups).toEqual([ScopedParameterLookup.direct(lookupScope('by_project', '1'), ['u1'])]);
517
549
  return [{ id: 1 }, { id: 2 }];
518
550
  }),
519
551
  writeCheckpoint: null,
@@ -548,7 +580,7 @@ bucket_definitions:
548
580
  write_checkpoint: undefined
549
581
  }
550
582
  });
551
- expect(line.bucketsToFetch).toEqual([
583
+ expect(line.bucketsToFetch).toMatchObject([
552
584
  {
553
585
  bucket: '3#by_project[1]',
554
586
  priority: 3
@@ -560,7 +592,7 @@ bucket_definitions:
560
592
  ]);
561
593
  line.advance();
562
594
  // This is the bucket data to be fetched
563
- expect(line.getFilteredBucketPositions()).toEqual(
595
+ expect(bucketStarts(line.getFilteredBucketPositions())).toEqual(
564
596
  new Map([
565
597
  ['3#by_project[1]', 0n],
566
598
  ['3#by_project[2]', 0n]
@@ -574,7 +606,7 @@ bucket_definitions:
574
606
  // Now we get a new line
575
607
  const line2 = (await state.buildNextCheckpointLine({
576
608
  base: storage.makeCheckpoint(2n, (lookups) => {
577
- expect(lookups).toEqual([ScopedParameterLookup.direct({ lookupName: 'by_project', queryId: '1' }, ['u1'])]);
609
+ expect(lookups).toEqual([ScopedParameterLookup.direct(lookupScope('by_project', '1'), ['u1'])]);
578
610
  return [{ id: 1 }, { id: 2 }, { id: 3 }];
579
611
  }),
580
612
  writeCheckpoint: null,
@@ -602,7 +634,7 @@ bucket_definitions:
602
634
  write_checkpoint: undefined
603
635
  }
604
636
  });
605
- expect(line2.getFilteredBucketPositions()).toEqual(new Map([['3#by_project[3]', 0n]]));
637
+ expect(bucketStarts(line2.getFilteredBucketPositions())).toEqual(new Map([['3#by_project[3]', 0n]]));
606
638
  });
607
639
 
608
640
  describe('streams', () => {
@@ -1044,9 +1076,9 @@ class MockBucketChecksumStateStorage implements BucketChecksumStateStorage {
1044
1076
  this.filter?.({ invalidate: true });
1045
1077
  }
1046
1078
 
1047
- async getChecksums(checkpoint: InternalOpId, buckets: string[]): Promise<ChecksumMap> {
1079
+ async getChecksums(_checkpoint: InternalOpId, buckets: { bucket: string }[]): Promise<ChecksumMap> {
1048
1080
  return new Map<string, BucketChecksum>(
1049
- buckets.map((bucket) => {
1081
+ buckets.map(({ bucket }) => {
1050
1082
  const checksum = this.state.get(bucket);
1051
1083
  return [
1052
1084
  bucket,
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Removes the source property from an object.
3
+ *
4
+ * This is for tests where we don't care about this value, and it adds a lot of noise in the output.
5
+ */
6
+ export function removeSource<T extends { source?: any }>(obj: T): Omit<T, 'source'> {
7
+ const { source, ...rest } = obj;
8
+ return rest;
9
+ }