@powersync/service-core 1.14.0 → 1.15.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 (71) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/LICENSE +3 -3
  3. package/dist/api/api-metrics.js +5 -0
  4. package/dist/api/api-metrics.js.map +1 -1
  5. package/dist/api/diagnostics.js +1 -1
  6. package/dist/api/diagnostics.js.map +1 -1
  7. package/dist/metrics/open-telemetry/util.d.ts +0 -3
  8. package/dist/metrics/open-telemetry/util.js +18 -13
  9. package/dist/metrics/open-telemetry/util.js.map +1 -1
  10. package/dist/routes/compression.d.ts +19 -0
  11. package/dist/routes/compression.js +70 -0
  12. package/dist/routes/compression.js.map +1 -0
  13. package/dist/routes/configure-fastify.d.ts +40 -5
  14. package/dist/routes/endpoints/socket-route.js +24 -9
  15. package/dist/routes/endpoints/socket-route.js.map +1 -1
  16. package/dist/routes/endpoints/sync-rules.js +1 -27
  17. package/dist/routes/endpoints/sync-rules.js.map +1 -1
  18. package/dist/routes/endpoints/sync-stream.d.ts +80 -10
  19. package/dist/routes/endpoints/sync-stream.js +17 -12
  20. package/dist/routes/endpoints/sync-stream.js.map +1 -1
  21. package/dist/storage/BucketStorage.d.ts +1 -1
  22. package/dist/storage/BucketStorage.js.map +1 -1
  23. package/dist/storage/BucketStorageBatch.d.ts +4 -4
  24. package/dist/storage/BucketStorageBatch.js.map +1 -1
  25. package/dist/storage/ChecksumCache.d.ts +4 -19
  26. package/dist/storage/ChecksumCache.js +4 -0
  27. package/dist/storage/ChecksumCache.js.map +1 -1
  28. package/dist/storage/ReplicationEventPayload.d.ts +2 -2
  29. package/dist/storage/SyncRulesBucketStorage.d.ts +9 -0
  30. package/dist/storage/SyncRulesBucketStorage.js.map +1 -1
  31. package/dist/sync/BucketChecksumState.d.ts +40 -10
  32. package/dist/sync/BucketChecksumState.js +154 -18
  33. package/dist/sync/BucketChecksumState.js.map +1 -1
  34. package/dist/sync/RequestTracker.d.ts +7 -1
  35. package/dist/sync/RequestTracker.js +22 -2
  36. package/dist/sync/RequestTracker.js.map +1 -1
  37. package/dist/sync/sync.d.ts +3 -3
  38. package/dist/sync/sync.js +23 -42
  39. package/dist/sync/sync.js.map +1 -1
  40. package/dist/sync/util.js +1 -1
  41. package/dist/sync/util.js.map +1 -1
  42. package/dist/util/protocol-types.d.ts +153 -9
  43. package/dist/util/protocol-types.js +41 -6
  44. package/dist/util/protocol-types.js.map +1 -1
  45. package/dist/util/utils.d.ts +18 -3
  46. package/dist/util/utils.js +39 -10
  47. package/dist/util/utils.js.map +1 -1
  48. package/package.json +14 -14
  49. package/src/api/api-metrics.ts +6 -0
  50. package/src/api/diagnostics.ts +1 -1
  51. package/src/metrics/open-telemetry/util.ts +22 -21
  52. package/src/routes/compression.ts +75 -0
  53. package/src/routes/endpoints/socket-route.ts +24 -9
  54. package/src/routes/endpoints/sync-rules.ts +1 -28
  55. package/src/routes/endpoints/sync-stream.ts +18 -15
  56. package/src/storage/BucketStorage.ts +2 -2
  57. package/src/storage/BucketStorageBatch.ts +10 -4
  58. package/src/storage/ChecksumCache.ts +8 -22
  59. package/src/storage/ReplicationEventPayload.ts +2 -2
  60. package/src/storage/SyncRulesBucketStorage.ts +12 -0
  61. package/src/sync/BucketChecksumState.ts +192 -29
  62. package/src/sync/RequestTracker.ts +27 -2
  63. package/src/sync/sync.ts +53 -51
  64. package/src/sync/util.ts +1 -1
  65. package/src/util/protocol-types.ts +138 -10
  66. package/src/util/utils.ts +64 -13
  67. package/test/src/checksum_cache.test.ts +6 -8
  68. package/test/src/routes/mocks.ts +59 -0
  69. package/test/src/routes/stream.test.ts +84 -0
  70. package/test/src/sync/BucketChecksumState.test.ts +340 -42
  71. package/tsconfig.tsbuildinfo +1 -1
@@ -1,17 +1,25 @@
1
1
  import {
2
2
  BucketChecksum,
3
3
  BucketChecksumState,
4
+ BucketChecksumStateOptions,
4
5
  BucketChecksumStateStorage,
5
6
  CHECKPOINT_INVALIDATE_ALL,
6
7
  ChecksumMap,
7
8
  InternalOpId,
8
9
  ReplicationCheckpoint,
10
+ StreamingSyncRequest,
9
11
  SyncContext,
10
12
  WatchFilterEvent
11
13
  } from '@/index.js';
12
14
  import { JSONBig } from '@powersync/service-jsonbig';
13
- import { ParameterLookup, RequestParameters, SqliteJsonRow, SqlSyncRules } from '@powersync/service-sync-rules';
14
- import { describe, expect, test } from 'vitest';
15
+ import {
16
+ SqliteJsonRow,
17
+ ParameterLookup,
18
+ SqlSyncRules,
19
+ RequestJwtPayload,
20
+ BucketSource
21
+ } from '@powersync/service-sync-rules';
22
+ import { describe, expect, test, beforeEach } from 'vitest';
15
23
 
16
24
  describe('BucketChecksumState', () => {
17
25
  // Single global[] bucket.
@@ -55,6 +63,9 @@ bucket_definitions:
55
63
  maxDataFetchConcurrency: 10
56
64
  });
57
65
 
66
+ const syncRequest: StreamingSyncRequest = {};
67
+ const tokenPayload: RequestJwtPayload = { sub: '' };
68
+
58
69
  test('global bucket with update', async () => {
59
70
  const storage = new MockBucketChecksumStateStorage();
60
71
  // Set intial state
@@ -62,8 +73,12 @@ bucket_definitions:
62
73
 
63
74
  const state = new BucketChecksumState({
64
75
  syncContext,
65
- syncParams: new RequestParameters({ sub: '' }, {}),
66
- syncRules: SYNC_RULES_GLOBAL,
76
+ syncRequest,
77
+ tokenPayload,
78
+ syncRules: {
79
+ syncRules: SYNC_RULES_GLOBAL,
80
+ version: 1
81
+ },
67
82
  bucketStorage: storage
68
83
  });
69
84
 
@@ -75,9 +90,10 @@ bucket_definitions:
75
90
  line.advance();
76
91
  expect(line.checkpointLine).toEqual({
77
92
  checkpoint: {
78
- buckets: [{ bucket: 'global[]', checksum: 1, count: 1, priority: 3 }],
93
+ buckets: [{ bucket: 'global[]', checksum: 1, count: 1, priority: 3, subscriptions: [{ default: 0 }] }],
79
94
  last_op_id: '1',
80
- write_checkpoint: undefined
95
+ write_checkpoint: undefined,
96
+ streams: [{ name: 'global', is_default: true, errors: [] }]
81
97
  }
82
98
  });
83
99
  expect(line.bucketsToFetch).toEqual([
@@ -111,7 +127,7 @@ bucket_definitions:
111
127
  expect(line2.checkpointLine).toEqual({
112
128
  checkpoint_diff: {
113
129
  removed_buckets: [],
114
- updated_buckets: [{ bucket: 'global[]', checksum: 2, count: 2, priority: 3 }],
130
+ updated_buckets: [{ bucket: 'global[]', checksum: 2, count: 2, priority: 3, subscriptions: [{ default: 0 }] }],
115
131
  last_op_id: '2',
116
132
  write_checkpoint: undefined
117
133
  }
@@ -129,10 +145,13 @@ bucket_definitions:
129
145
 
130
146
  const state = new BucketChecksumState({
131
147
  syncContext,
148
+ tokenPayload,
132
149
  // Client sets the initial state here
133
- initialBucketPositions: [{ name: 'global[]', after: 1n }],
134
- syncParams: new RequestParameters({ sub: '' }, {}),
135
- syncRules: SYNC_RULES_GLOBAL,
150
+ syncRequest: { buckets: [{ name: 'global[]', after: '1' }] },
151
+ syncRules: {
152
+ syncRules: SYNC_RULES_GLOBAL,
153
+ version: 1
154
+ },
136
155
  bucketStorage: storage
137
156
  });
138
157
 
@@ -144,9 +163,10 @@ bucket_definitions:
144
163
  line.advance();
145
164
  expect(line.checkpointLine).toEqual({
146
165
  checkpoint: {
147
- buckets: [{ bucket: 'global[]', checksum: 1, count: 1, priority: 3 }],
166
+ buckets: [{ bucket: 'global[]', checksum: 1, count: 1, priority: 3, subscriptions: [{ default: 0 }] }],
148
167
  last_op_id: '1',
149
- write_checkpoint: undefined
168
+ write_checkpoint: undefined,
169
+ streams: [{ name: 'global', is_default: true, errors: [] }]
150
170
  }
151
171
  });
152
172
  expect(line.bucketsToFetch).toEqual([
@@ -167,8 +187,12 @@ bucket_definitions:
167
187
 
168
188
  const state = new BucketChecksumState({
169
189
  syncContext,
170
- syncParams: new RequestParameters({ sub: '' }, {}),
171
- syncRules: SYNC_RULES_GLOBAL_TWO,
190
+ tokenPayload,
191
+ syncRequest,
192
+ syncRules: {
193
+ syncRules: SYNC_RULES_GLOBAL_TWO,
194
+ version: 2
195
+ },
172
196
  bucketStorage: storage
173
197
  });
174
198
 
@@ -180,11 +204,12 @@ bucket_definitions:
180
204
  expect(line.checkpointLine).toEqual({
181
205
  checkpoint: {
182
206
  buckets: [
183
- { bucket: 'global[1]', checksum: 1, count: 1, priority: 3 },
184
- { bucket: 'global[2]', checksum: 1, count: 1, priority: 3 }
207
+ { bucket: 'global[1]', checksum: 1, count: 1, priority: 3, subscriptions: [{ default: 0 }] },
208
+ { bucket: 'global[2]', checksum: 1, count: 1, priority: 3, subscriptions: [{ default: 0 }] }
185
209
  ],
186
210
  last_op_id: '1',
187
- write_checkpoint: undefined
211
+ write_checkpoint: undefined,
212
+ streams: [{ name: 'global', is_default: true, errors: [] }]
188
213
  }
189
214
  });
190
215
  expect(line.bucketsToFetch).toEqual([
@@ -215,8 +240,8 @@ bucket_definitions:
215
240
  checkpoint_diff: {
216
241
  removed_buckets: [],
217
242
  updated_buckets: [
218
- { bucket: 'global[1]', checksum: 2, count: 2, priority: 3 },
219
- { bucket: 'global[2]', checksum: 2, count: 2, priority: 3 }
243
+ { bucket: 'global[1]', checksum: 2, count: 2, priority: 3, subscriptions: [{ default: 0 }] },
244
+ { bucket: 'global[2]', checksum: 2, count: 2, priority: 3, subscriptions: [{ default: 0 }] }
220
245
  ],
221
246
  last_op_id: '2',
222
247
  write_checkpoint: undefined
@@ -232,10 +257,13 @@ bucket_definitions:
232
257
 
233
258
  const state = new BucketChecksumState({
234
259
  syncContext,
260
+ tokenPayload,
235
261
  // Client sets the initial state here
236
- initialBucketPositions: [{ name: 'something_here[]', after: 1n }],
237
- syncParams: new RequestParameters({ sub: '' }, {}),
238
- syncRules: SYNC_RULES_GLOBAL,
262
+ syncRequest: { buckets: [{ name: 'something_here[]', after: '1' }] },
263
+ syncRules: {
264
+ syncRules: SYNC_RULES_GLOBAL,
265
+ version: 1
266
+ },
239
267
  bucketStorage: storage
240
268
  });
241
269
 
@@ -249,9 +277,10 @@ bucket_definitions:
249
277
  line.advance();
250
278
  expect(line.checkpointLine).toEqual({
251
279
  checkpoint: {
252
- buckets: [{ bucket: 'global[]', checksum: 1, count: 1, priority: 3 }],
280
+ buckets: [{ bucket: 'global[]', checksum: 1, count: 1, priority: 3, subscriptions: [{ default: 0 }] }],
253
281
  last_op_id: '1',
254
- write_checkpoint: undefined
282
+ write_checkpoint: undefined,
283
+ streams: [{ name: 'global', is_default: true, errors: [] }]
255
284
  }
256
285
  });
257
286
  expect(line.bucketsToFetch).toEqual([
@@ -273,8 +302,12 @@ bucket_definitions:
273
302
 
274
303
  const state = new BucketChecksumState({
275
304
  syncContext,
276
- syncParams: new RequestParameters({ sub: '' }, {}),
277
- syncRules: SYNC_RULES_GLOBAL_TWO,
305
+ tokenPayload,
306
+ syncRequest,
307
+ syncRules: {
308
+ syncRules: SYNC_RULES_GLOBAL_TWO,
309
+ version: 1
310
+ },
278
311
  bucketStorage: storage
279
312
  });
280
313
 
@@ -310,7 +343,7 @@ bucket_definitions:
310
343
  removed_buckets: [],
311
344
  updated_buckets: [
312
345
  // This does not include global[2], since it was not invalidated.
313
- { bucket: 'global[1]', checksum: 2, count: 2, priority: 3 }
346
+ { bucket: 'global[1]', checksum: 2, count: 2, priority: 3, subscriptions: [{ default: 0 }] }
314
347
  ],
315
348
  last_op_id: '2',
316
349
  write_checkpoint: undefined
@@ -325,8 +358,12 @@ bucket_definitions:
325
358
 
326
359
  const state = new BucketChecksumState({
327
360
  syncContext,
328
- syncParams: new RequestParameters({ sub: '' }, {}),
329
- syncRules: SYNC_RULES_GLOBAL_TWO,
361
+ tokenPayload,
362
+ syncRequest,
363
+ syncRules: {
364
+ syncRules: SYNC_RULES_GLOBAL_TWO,
365
+ version: 2
366
+ },
330
367
  bucketStorage: storage
331
368
  });
332
369
 
@@ -358,8 +395,8 @@ bucket_definitions:
358
395
  checkpoint_diff: {
359
396
  removed_buckets: [],
360
397
  updated_buckets: [
361
- { bucket: 'global[1]', checksum: 2, count: 2, priority: 3 },
362
- { bucket: 'global[2]', checksum: 2, count: 2, priority: 3 }
398
+ { bucket: 'global[1]', checksum: 2, count: 2, priority: 3, subscriptions: [{ default: 0 }] },
399
+ { bucket: 'global[2]', checksum: 2, count: 2, priority: 3, subscriptions: [{ default: 0 }] }
363
400
  ],
364
401
  last_op_id: '2',
365
402
  write_checkpoint: undefined
@@ -379,8 +416,12 @@ bucket_definitions:
379
416
 
380
417
  const state = new BucketChecksumState({
381
418
  syncContext,
382
- syncParams: new RequestParameters({ sub: '' }, {}),
383
- syncRules: SYNC_RULES_GLOBAL_TWO,
419
+ tokenPayload,
420
+ syncRequest,
421
+ syncRules: {
422
+ syncRules: SYNC_RULES_GLOBAL_TWO,
423
+ version: 2
424
+ },
384
425
  bucketStorage: storage
385
426
  });
386
427
 
@@ -393,11 +434,12 @@ bucket_definitions:
393
434
  expect(line.checkpointLine).toEqual({
394
435
  checkpoint: {
395
436
  buckets: [
396
- { bucket: 'global[1]', checksum: 3, count: 3, priority: 3 },
397
- { bucket: 'global[2]', checksum: 3, count: 3, priority: 3 }
437
+ { bucket: 'global[1]', checksum: 3, count: 3, priority: 3, subscriptions: [{ default: 0 }] },
438
+ { bucket: 'global[2]', checksum: 3, count: 3, priority: 3, subscriptions: [{ default: 0 }] }
398
439
  ],
399
440
  last_op_id: '3',
400
- write_checkpoint: undefined
441
+ write_checkpoint: undefined,
442
+ streams: [{ name: 'global', is_default: true, errors: [] }]
401
443
  }
402
444
  });
403
445
  expect(line.bucketsToFetch).toEqual([
@@ -444,7 +486,8 @@ bucket_definitions:
444
486
  bucket: 'global[1]',
445
487
  checksum: 4,
446
488
  count: 4,
447
- priority: 3
489
+ priority: 3,
490
+ subscriptions: [{ default: 0 }]
448
491
  }
449
492
  ],
450
493
  last_op_id: '4',
@@ -480,8 +523,12 @@ bucket_definitions:
480
523
 
481
524
  const state = new BucketChecksumState({
482
525
  syncContext,
483
- syncParams: new RequestParameters({ sub: 'u1' }, {}),
484
- syncRules: SYNC_RULES_DYNAMIC,
526
+ tokenPayload: { sub: 'u1' },
527
+ syncRequest,
528
+ syncRules: {
529
+ syncRules: SYNC_RULES_DYNAMIC,
530
+ version: 1
531
+ },
485
532
  bucketStorage: storage
486
533
  });
487
534
 
@@ -496,10 +543,29 @@ bucket_definitions:
496
543
  expect(line.checkpointLine).toEqual({
497
544
  checkpoint: {
498
545
  buckets: [
499
- { bucket: 'by_project[1]', checksum: 1, count: 1, priority: 3 },
500
- { bucket: 'by_project[2]', checksum: 1, count: 1, priority: 3 }
546
+ {
547
+ bucket: 'by_project[1]',
548
+ checksum: 1,
549
+ count: 1,
550
+ priority: 3,
551
+ subscriptions: [{ default: 0 }]
552
+ },
553
+ {
554
+ bucket: 'by_project[2]',
555
+ checksum: 1,
556
+ count: 1,
557
+ priority: 3,
558
+ subscriptions: [{ default: 0 }]
559
+ }
501
560
  ],
502
561
  last_op_id: '1',
562
+ streams: [
563
+ {
564
+ is_default: true,
565
+ name: 'by_project',
566
+ errors: []
567
+ }
568
+ ],
503
569
  write_checkpoint: undefined
504
570
  }
505
571
  });
@@ -544,13 +610,245 @@ bucket_definitions:
544
610
  expect(line2.checkpointLine).toEqual({
545
611
  checkpoint_diff: {
546
612
  removed_buckets: [],
547
- updated_buckets: [{ bucket: 'by_project[3]', checksum: 1, count: 1, priority: 3 }],
613
+ updated_buckets: [
614
+ {
615
+ bucket: 'by_project[3]',
616
+ checksum: 1,
617
+ count: 1,
618
+ priority: 3,
619
+ subscriptions: [{ default: 0 }]
620
+ }
621
+ ],
548
622
  last_op_id: '2',
549
623
  write_checkpoint: undefined
550
624
  }
551
625
  });
552
626
  expect(line2.getFilteredBucketPositions()).toEqual(new Map([['by_project[3]', 0n]]));
553
627
  });
628
+
629
+ describe('streams', () => {
630
+ let source: { -readonly [P in keyof BucketSource]: BucketSource[P] };
631
+ let storage: MockBucketChecksumStateStorage;
632
+
633
+ function checksumState(source: string | boolean, options?: Partial<BucketChecksumStateOptions>) {
634
+ if (typeof source == 'boolean') {
635
+ source = `
636
+ streams:
637
+ stream:
638
+ auto_subscribe: ${source}
639
+ query: SELECT * FROM assets WHERE id IN ifnull(subscription.parameter('ids'), '["default"]');
640
+
641
+ config:
642
+ edition: 2
643
+ `;
644
+ }
645
+
646
+ const rules = SqlSyncRules.fromYaml(source, {
647
+ defaultSchema: 'public'
648
+ });
649
+
650
+ return new BucketChecksumState({
651
+ syncContext,
652
+ syncRequest,
653
+ tokenPayload,
654
+ syncRules: { syncRules: rules, version: 1 },
655
+ bucketStorage: storage,
656
+ ...options
657
+ });
658
+ }
659
+
660
+ beforeEach(() => {
661
+ storage = new MockBucketChecksumStateStorage();
662
+ storage.updateTestChecksum({ bucket: '1#stream|0["default"]', checksum: 1, count: 1 });
663
+ storage.updateTestChecksum({ bucket: '1#stream|0["a"]', checksum: 1, count: 1 });
664
+ storage.updateTestChecksum({ bucket: '1#stream|0["b"]', checksum: 1, count: 1 });
665
+ });
666
+
667
+ test('includes defaults', async () => {
668
+ const state = checksumState(true);
669
+ const line = await state.buildNextCheckpointLine({
670
+ base: storage.makeCheckpoint(1n),
671
+ writeCheckpoint: null,
672
+ update: CHECKPOINT_INVALIDATE_ALL
673
+ })!;
674
+ line?.advance();
675
+ expect(line?.checkpointLine).toEqual({
676
+ checkpoint: {
677
+ buckets: [
678
+ { bucket: '1#stream|0["default"]', checksum: 1, count: 1, priority: 3, subscriptions: [{ default: 0 }] }
679
+ ],
680
+ last_op_id: '1',
681
+ write_checkpoint: undefined,
682
+ streams: [{ name: 'stream', is_default: true, errors: [] }]
683
+ }
684
+ });
685
+ });
686
+
687
+ test('can exclude defaults', async () => {
688
+ const state = checksumState(true, { syncRequest: { streams: { include_defaults: false, subscriptions: [] } } });
689
+
690
+ const line = await state.buildNextCheckpointLine({
691
+ base: storage.makeCheckpoint(1n),
692
+ writeCheckpoint: null,
693
+ update: CHECKPOINT_INVALIDATE_ALL
694
+ })!;
695
+ line?.advance();
696
+ expect(line?.checkpointLine).toEqual({
697
+ checkpoint: {
698
+ buckets: [],
699
+ last_op_id: '1',
700
+ write_checkpoint: undefined,
701
+ streams: []
702
+ }
703
+ });
704
+ });
705
+
706
+ test('custom subscriptions', async () => {
707
+ const state = checksumState(true, {
708
+ syncRequest: {
709
+ streams: {
710
+ subscriptions: [
711
+ { stream: 'stream', parameters: { ids: '["a"]' }, override_priority: null },
712
+ { stream: 'stream', parameters: { ids: '["b"]' }, override_priority: 1 }
713
+ ]
714
+ }
715
+ }
716
+ });
717
+
718
+ const line = await state.buildNextCheckpointLine({
719
+ base: storage.makeCheckpoint(1n),
720
+ writeCheckpoint: null,
721
+ update: CHECKPOINT_INVALIDATE_ALL
722
+ })!;
723
+ line?.advance();
724
+ expect(line?.checkpointLine).toEqual({
725
+ checkpoint: {
726
+ buckets: [
727
+ { bucket: '1#stream|0["a"]', checksum: 1, count: 1, priority: 3, subscriptions: [{ sub: 0 }] },
728
+ { bucket: '1#stream|0["b"]', checksum: 1, count: 1, priority: 1, subscriptions: [{ sub: 1 }] },
729
+ { bucket: '1#stream|0["default"]', checksum: 1, count: 1, priority: 3, subscriptions: [{ default: 0 }] }
730
+ ],
731
+ last_op_id: '1',
732
+ write_checkpoint: undefined,
733
+ streams: [{ name: 'stream', is_default: true, errors: [] }]
734
+ }
735
+ });
736
+ });
737
+
738
+ test('overlap between custom subscriptions', async () => {
739
+ const state = checksumState(false, {
740
+ syncRequest: {
741
+ streams: {
742
+ subscriptions: [
743
+ { stream: 'stream', parameters: { ids: '["a", "b"]' }, override_priority: null },
744
+ { stream: 'stream', parameters: { ids: '["b"]' }, override_priority: 1 }
745
+ ]
746
+ }
747
+ }
748
+ });
749
+
750
+ const line = await state.buildNextCheckpointLine({
751
+ base: storage.makeCheckpoint(1n),
752
+ writeCheckpoint: null,
753
+ update: CHECKPOINT_INVALIDATE_ALL
754
+ })!;
755
+ line?.advance();
756
+ expect(line?.checkpointLine).toEqual({
757
+ checkpoint: {
758
+ buckets: [
759
+ { bucket: '1#stream|0["a"]', checksum: 1, count: 1, priority: 3, subscriptions: [{ sub: 0 }] },
760
+ { bucket: '1#stream|0["b"]', checksum: 1, count: 1, priority: 1, subscriptions: [{ sub: 0 }, { sub: 1 }] }
761
+ ],
762
+ last_op_id: '1',
763
+ write_checkpoint: undefined,
764
+ streams: [{ name: 'stream', is_default: false, errors: [] }]
765
+ }
766
+ });
767
+ });
768
+
769
+ test('overlap between default and custom subscription', async () => {
770
+ const state = checksumState(true, {
771
+ syncRequest: {
772
+ streams: {
773
+ subscriptions: [{ stream: 'stream', parameters: { ids: '["a", "default"]' }, override_priority: 1 }]
774
+ }
775
+ }
776
+ });
777
+
778
+ const line = await state.buildNextCheckpointLine({
779
+ base: storage.makeCheckpoint(1n),
780
+ writeCheckpoint: null,
781
+ update: CHECKPOINT_INVALIDATE_ALL
782
+ })!;
783
+ line?.advance();
784
+ expect(line?.checkpointLine).toEqual({
785
+ checkpoint: {
786
+ buckets: [
787
+ { bucket: '1#stream|0["a"]', checksum: 1, count: 1, priority: 1, subscriptions: [{ sub: 0 }] },
788
+ {
789
+ bucket: '1#stream|0["default"]',
790
+ checksum: 1,
791
+ count: 1,
792
+ priority: 1,
793
+ subscriptions: [{ sub: 0 }, { default: 0 }]
794
+ }
795
+ ],
796
+ last_op_id: '1',
797
+ write_checkpoint: undefined,
798
+ streams: [{ name: 'stream', is_default: true, errors: [] }]
799
+ }
800
+ });
801
+ });
802
+
803
+ test('reports errors', async () => {
804
+ const state = checksumState(true, {
805
+ syncRequest: {
806
+ streams: {
807
+ subscriptions: [
808
+ { stream: 'stream', parameters: { ids: '["a", "b"]' }, override_priority: 1 },
809
+ { stream: 'stream', parameters: { ids: 'invalid json' }, override_priority: null }
810
+ ]
811
+ }
812
+ }
813
+ });
814
+
815
+ const line = await state.buildNextCheckpointLine({
816
+ base: storage.makeCheckpoint(1n),
817
+ writeCheckpoint: null,
818
+ update: CHECKPOINT_INVALIDATE_ALL
819
+ })!;
820
+ line?.advance();
821
+ expect(line?.checkpointLine).toEqual({
822
+ checkpoint: {
823
+ buckets: [
824
+ { bucket: '1#stream|0["a"]', checksum: 1, count: 1, priority: 1, subscriptions: [{ sub: 0 }] },
825
+ { bucket: '1#stream|0["b"]', checksum: 1, count: 1, priority: 1, subscriptions: [{ sub: 0 }] },
826
+ {
827
+ bucket: '1#stream|0["default"]',
828
+ checksum: 1,
829
+ count: 1,
830
+ priority: 3,
831
+ subscriptions: [{ default: 0 }]
832
+ }
833
+ ],
834
+ last_op_id: '1',
835
+ write_checkpoint: undefined,
836
+ streams: [
837
+ {
838
+ name: 'stream',
839
+ is_default: true,
840
+ errors: [
841
+ {
842
+ message: 'Error evaluating bucket ids: Unexpected token \'i\', "invalid json" is not valid JSON',
843
+ subscription: 1
844
+ }
845
+ ]
846
+ }
847
+ ]
848
+ }
849
+ });
850
+ });
851
+ });
554
852
  });
555
853
 
556
854
  class MockBucketChecksumStateStorage implements BucketChecksumStateStorage {