@powersync/service-core 1.7.1 → 1.8.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 (60) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/routes/RouterEngine.js.map +1 -1
  3. package/dist/routes/configure-fastify.d.ts +3 -3
  4. package/dist/routes/endpoints/checkpointing.d.ts +6 -6
  5. package/dist/routes/endpoints/socket-route.js +2 -1
  6. package/dist/routes/endpoints/socket-route.js.map +1 -1
  7. package/dist/routes/endpoints/sync-stream.js +2 -1
  8. package/dist/routes/endpoints/sync-stream.js.map +1 -1
  9. package/dist/storage/BucketStorageBatch.d.ts +2 -1
  10. package/dist/storage/BucketStorageBatch.js.map +1 -1
  11. package/dist/storage/ChecksumCache.d.ts +6 -6
  12. package/dist/storage/ChecksumCache.js +5 -6
  13. package/dist/storage/ChecksumCache.js.map +1 -1
  14. package/dist/storage/SyncRulesBucketStorage.d.ts +9 -9
  15. package/dist/sync/BucketChecksumState.d.ts +8 -4
  16. package/dist/sync/BucketChecksumState.js +16 -8
  17. package/dist/sync/BucketChecksumState.js.map +1 -1
  18. package/dist/sync/SyncContext.d.ts +17 -0
  19. package/dist/sync/SyncContext.js +23 -0
  20. package/dist/sync/SyncContext.js.map +1 -0
  21. package/dist/sync/sync-index.d.ts +1 -0
  22. package/dist/sync/sync-index.js +1 -0
  23. package/dist/sync/sync-index.js.map +1 -1
  24. package/dist/sync/sync.d.ts +4 -2
  25. package/dist/sync/sync.js +16 -24
  26. package/dist/sync/sync.js.map +1 -1
  27. package/dist/system/ServiceContext.d.ts +3 -0
  28. package/dist/system/ServiceContext.js +7 -0
  29. package/dist/system/ServiceContext.js.map +1 -1
  30. package/dist/util/config/compound-config-collector.js +13 -1
  31. package/dist/util/config/compound-config-collector.js.map +1 -1
  32. package/dist/util/config/defaults.d.ts +5 -0
  33. package/dist/util/config/defaults.js +6 -0
  34. package/dist/util/config/defaults.js.map +1 -0
  35. package/dist/util/config/types.d.ts +7 -2
  36. package/dist/util/config/types.js.map +1 -1
  37. package/dist/util/protocol-types.d.ts +10 -10
  38. package/dist/util/utils.d.ts +12 -2
  39. package/dist/util/utils.js +5 -1
  40. package/dist/util/utils.js.map +1 -1
  41. package/package.json +3 -3
  42. package/src/routes/RouterEngine.ts +1 -0
  43. package/src/routes/endpoints/socket-route.ts +2 -1
  44. package/src/routes/endpoints/sync-stream.ts +2 -1
  45. package/src/storage/BucketStorageBatch.ts +2 -1
  46. package/src/storage/ChecksumCache.ts +13 -14
  47. package/src/storage/SyncRulesBucketStorage.ts +10 -10
  48. package/src/sync/BucketChecksumState.ts +38 -12
  49. package/src/sync/SyncContext.ts +36 -0
  50. package/src/sync/sync-index.ts +1 -0
  51. package/src/sync/sync.ts +31 -32
  52. package/src/system/ServiceContext.ts +9 -0
  53. package/src/util/config/compound-config-collector.ts +24 -1
  54. package/src/util/config/defaults.ts +5 -0
  55. package/src/util/config/types.ts +8 -2
  56. package/src/util/protocol-types.ts +10 -10
  57. package/src/util/utils.ts +13 -2
  58. package/test/src/checksum_cache.test.ts +83 -84
  59. package/test/src/sync/BucketChecksumState.test.ts +62 -41
  60. package/tsconfig.tsbuildinfo +1 -1
@@ -4,7 +4,8 @@ import {
4
4
  BucketChecksumStateStorage,
5
5
  CHECKPOINT_INVALIDATE_ALL,
6
6
  ChecksumMap,
7
- OpId,
7
+ InternalOpId,
8
+ SyncContext,
8
9
  WatchFilterEvent
9
10
  } from '@/index.js';
10
11
  import { RequestParameters, SqliteJsonRow, SqliteJsonValue, SqlSyncRules } from '@powersync/service-sync-rules';
@@ -46,19 +47,26 @@ bucket_definitions:
46
47
  { defaultSchema: 'public' }
47
48
  );
48
49
 
50
+ const syncContext = new SyncContext({
51
+ maxBuckets: 100,
52
+ maxParameterQueryResults: 100,
53
+ maxDataFetchConcurrency: 10
54
+ });
55
+
49
56
  test('global bucket with update', async () => {
50
57
  const storage = new MockBucketChecksumStateStorage();
51
58
  // Set intial state
52
59
  storage.updateTestChecksum({ bucket: 'global[]', checksum: 1, count: 1 });
53
60
 
54
61
  const state = new BucketChecksumState({
62
+ syncContext,
55
63
  syncParams: new RequestParameters({ sub: '' }, {}),
56
64
  syncRules: SYNC_RULES_GLOBAL,
57
65
  bucketStorage: storage
58
66
  });
59
67
 
60
68
  const line = (await state.buildNextCheckpointLine({
61
- base: { checkpoint: '1', lsn: '1' },
69
+ base: { checkpoint: 1n, lsn: '1' },
62
70
  writeCheckpoint: null,
63
71
  update: CHECKPOINT_INVALIDATE_ALL
64
72
  }))!;
@@ -76,17 +84,17 @@ bucket_definitions:
76
84
  }
77
85
  ]);
78
86
  // This is the bucket data to be fetched
79
- expect(state.getFilteredBucketPositions(line.bucketsToFetch)).toEqual(new Map([['global[]', '0']]));
87
+ expect(state.getFilteredBucketPositions(line.bucketsToFetch)).toEqual(new Map([['global[]', 0n]]));
80
88
 
81
89
  // This similuates the bucket data being sent
82
- state.updateBucketPosition({ bucket: 'global[]', nextAfter: '1', hasMore: false });
90
+ state.updateBucketPosition({ bucket: 'global[]', nextAfter: 1n, hasMore: false });
83
91
 
84
92
  // Update bucket storage state
85
93
  storage.updateTestChecksum({ bucket: 'global[]', checksum: 2, count: 2 });
86
94
 
87
95
  // Now we get a new line
88
96
  const line2 = (await state.buildNextCheckpointLine({
89
- base: { checkpoint: '2', lsn: '2' },
97
+ base: { checkpoint: 2n, lsn: '2' },
90
98
  writeCheckpoint: null,
91
99
  update: {
92
100
  updatedDataBuckets: ['global[]'],
@@ -103,7 +111,7 @@ bucket_definitions:
103
111
  write_checkpoint: undefined
104
112
  }
105
113
  });
106
- expect(state.getFilteredBucketPositions(line2.bucketsToFetch)).toEqual(new Map([['global[]', '1']]));
114
+ expect(state.getFilteredBucketPositions(line2.bucketsToFetch)).toEqual(new Map([['global[]', 1n]]));
107
115
  });
108
116
 
109
117
  test('global bucket with initial state', async () => {
@@ -115,15 +123,16 @@ bucket_definitions:
115
123
  storage.updateTestChecksum({ bucket: 'global[]', checksum: 1, count: 1 });
116
124
 
117
125
  const state = new BucketChecksumState({
126
+ syncContext,
118
127
  // Client sets the initial state here
119
- initialBucketPositions: [{ name: 'global[]', after: '1' }],
128
+ initialBucketPositions: [{ name: 'global[]', after: 1n }],
120
129
  syncParams: new RequestParameters({ sub: '' }, {}),
121
130
  syncRules: SYNC_RULES_GLOBAL,
122
131
  bucketStorage: storage
123
132
  });
124
133
 
125
134
  const line = (await state.buildNextCheckpointLine({
126
- base: { checkpoint: '1', lsn: '1' },
135
+ base: { checkpoint: 1n, lsn: '1' },
127
136
  writeCheckpoint: null,
128
137
  update: CHECKPOINT_INVALIDATE_ALL
129
138
  }))!;
@@ -141,7 +150,7 @@ bucket_definitions:
141
150
  }
142
151
  ]);
143
152
  // This is the main difference between this and the previous test
144
- expect(state.getFilteredBucketPositions(line.bucketsToFetch)).toEqual(new Map([['global[]', '1']]));
153
+ expect(state.getFilteredBucketPositions(line.bucketsToFetch)).toEqual(new Map([['global[]', 1n]]));
145
154
  });
146
155
 
147
156
  test('multiple static buckets', async () => {
@@ -151,13 +160,14 @@ bucket_definitions:
151
160
  storage.updateTestChecksum({ bucket: 'global[2]', checksum: 1, count: 1 });
152
161
 
153
162
  const state = new BucketChecksumState({
163
+ syncContext,
154
164
  syncParams: new RequestParameters({ sub: '' }, {}),
155
165
  syncRules: SYNC_RULES_GLOBAL_TWO,
156
166
  bucketStorage: storage
157
167
  });
158
168
 
159
169
  const line = (await state.buildNextCheckpointLine({
160
- base: { checkpoint: '1', lsn: '1' },
170
+ base: { checkpoint: 1n, lsn: '1' },
161
171
  writeCheckpoint: null,
162
172
  update: CHECKPOINT_INVALIDATE_ALL
163
173
  }))!;
@@ -186,7 +196,7 @@ bucket_definitions:
186
196
  storage.updateTestChecksum({ bucket: 'global[2]', checksum: 2, count: 2 });
187
197
 
188
198
  const line2 = (await state.buildNextCheckpointLine({
189
- base: { checkpoint: '2', lsn: '2' },
199
+ base: { checkpoint: 2n, lsn: '2' },
190
200
  writeCheckpoint: null,
191
201
  update: {
192
202
  ...CHECKPOINT_INVALIDATE_ALL,
@@ -214,8 +224,9 @@ bucket_definitions:
214
224
  const storage = new MockBucketChecksumStateStorage();
215
225
 
216
226
  const state = new BucketChecksumState({
227
+ syncContext,
217
228
  // Client sets the initial state here
218
- initialBucketPositions: [{ name: 'something_here[]', after: '1' }],
229
+ initialBucketPositions: [{ name: 'something_here[]', after: 1n }],
219
230
  syncParams: new RequestParameters({ sub: '' }, {}),
220
231
  syncRules: SYNC_RULES_GLOBAL,
221
232
  bucketStorage: storage
@@ -224,7 +235,7 @@ bucket_definitions:
224
235
  storage.updateTestChecksum({ bucket: 'global[]', checksum: 1, count: 1 });
225
236
 
226
237
  const line = (await state.buildNextCheckpointLine({
227
- base: { checkpoint: '1', lsn: '1' },
238
+ base: { checkpoint: 1n, lsn: '1' },
228
239
  writeCheckpoint: null,
229
240
  update: CHECKPOINT_INVALIDATE_ALL
230
241
  }))!;
@@ -241,7 +252,7 @@ bucket_definitions:
241
252
  priority: 3
242
253
  }
243
254
  ]);
244
- expect(state.getFilteredBucketPositions(line.bucketsToFetch)).toEqual(new Map([['global[]', '0']]));
255
+ expect(state.getFilteredBucketPositions(line.bucketsToFetch)).toEqual(new Map([['global[]', 0n]]));
245
256
  });
246
257
 
247
258
  test('invalidating individual bucket', async () => {
@@ -253,6 +264,7 @@ bucket_definitions:
253
264
  storage.updateTestChecksum({ bucket: 'global[2]', checksum: 1, count: 1 });
254
265
 
255
266
  const state = new BucketChecksumState({
267
+ syncContext,
256
268
  syncParams: new RequestParameters({ sub: '' }, {}),
257
269
  syncRules: SYNC_RULES_GLOBAL_TWO,
258
270
  bucketStorage: storage
@@ -262,19 +274,19 @@ bucket_definitions:
262
274
  // storage.filter = state.checkpointFilter;
263
275
 
264
276
  await state.buildNextCheckpointLine({
265
- base: { checkpoint: '1', lsn: '1' },
277
+ base: { checkpoint: 1n, lsn: '1' },
266
278
  writeCheckpoint: null,
267
279
  update: CHECKPOINT_INVALIDATE_ALL
268
280
  });
269
281
 
270
- state.updateBucketPosition({ bucket: 'global[1]', nextAfter: '1', hasMore: false });
271
- state.updateBucketPosition({ bucket: 'global[2]', nextAfter: '1', hasMore: false });
282
+ state.updateBucketPosition({ bucket: 'global[1]', nextAfter: 1n, hasMore: false });
283
+ state.updateBucketPosition({ bucket: 'global[2]', nextAfter: 1n, hasMore: false });
272
284
 
273
285
  storage.updateTestChecksum({ bucket: 'global[1]', checksum: 2, count: 2 });
274
286
  storage.updateTestChecksum({ bucket: 'global[2]', checksum: 2, count: 2 });
275
287
 
276
288
  const line2 = (await state.buildNextCheckpointLine({
277
- base: { checkpoint: '2', lsn: '2' },
289
+ base: { checkpoint: 2n, lsn: '2' },
278
290
  writeCheckpoint: null,
279
291
  update: {
280
292
  ...CHECKPOINT_INVALIDATE_ALL,
@@ -304,6 +316,7 @@ bucket_definitions:
304
316
  const storage = new MockBucketChecksumStateStorage();
305
317
 
306
318
  const state = new BucketChecksumState({
319
+ syncContext,
307
320
  syncParams: new RequestParameters({ sub: '' }, {}),
308
321
  syncRules: SYNC_RULES_GLOBAL_TWO,
309
322
  bucketStorage: storage
@@ -317,7 +330,7 @@ bucket_definitions:
317
330
  storage.updateTestChecksum({ bucket: 'global[2]', checksum: 1, count: 1 });
318
331
 
319
332
  await state.buildNextCheckpointLine({
320
- base: { checkpoint: '1', lsn: '1' },
333
+ base: { checkpoint: 1n, lsn: '1' },
321
334
  writeCheckpoint: null,
322
335
  update: CHECKPOINT_INVALIDATE_ALL
323
336
  });
@@ -326,7 +339,7 @@ bucket_definitions:
326
339
  storage.updateTestChecksum({ bucket: 'global[2]', checksum: 2, count: 2 });
327
340
 
328
341
  const line2 = (await state.buildNextCheckpointLine({
329
- base: { checkpoint: '2', lsn: '2' },
342
+ base: { checkpoint: 2n, lsn: '2' },
330
343
  writeCheckpoint: null,
331
344
  // Invalidate the state - will re-check all buckets
332
345
  update: CHECKPOINT_INVALIDATE_ALL
@@ -355,13 +368,14 @@ bucket_definitions:
355
368
  storage.updateTestChecksum({ bucket: 'global[2]', checksum: 3, count: 3 });
356
369
 
357
370
  const state = new BucketChecksumState({
371
+ syncContext,
358
372
  syncParams: new RequestParameters({ sub: '' }, {}),
359
373
  syncRules: SYNC_RULES_GLOBAL_TWO,
360
374
  bucketStorage: storage
361
375
  });
362
376
 
363
377
  const line = (await state.buildNextCheckpointLine({
364
- base: { checkpoint: '3', lsn: '3' },
378
+ base: { checkpoint: 3n, lsn: '3' },
365
379
  writeCheckpoint: null,
366
380
  update: CHECKPOINT_INVALIDATE_ALL
367
381
  }))!;
@@ -389,19 +403,19 @@ bucket_definitions:
389
403
  // This is the bucket data to be fetched
390
404
  expect(state.getFilteredBucketPositions(line.bucketsToFetch)).toEqual(
391
405
  new Map([
392
- ['global[1]', '0'],
393
- ['global[2]', '0']
406
+ ['global[1]', 0n],
407
+ ['global[2]', 0n]
394
408
  ])
395
409
  );
396
410
 
397
411
  // No data changes here.
398
412
  // We simulate partial data sent, before a checkpoint is interrupted.
399
- state.updateBucketPosition({ bucket: 'global[1]', nextAfter: '3', hasMore: false });
400
- state.updateBucketPosition({ bucket: 'global[2]', nextAfter: '1', hasMore: true });
413
+ state.updateBucketPosition({ bucket: 'global[1]', nextAfter: 3n, hasMore: false });
414
+ state.updateBucketPosition({ bucket: 'global[2]', nextAfter: 1n, hasMore: true });
401
415
  storage.updateTestChecksum({ bucket: 'global[1]', checksum: 4, count: 4 });
402
416
 
403
417
  const line2 = (await state.buildNextCheckpointLine({
404
- base: { checkpoint: '4', lsn: '4' },
418
+ base: { checkpoint: 4n, lsn: '4' },
405
419
  writeCheckpoint: null,
406
420
  update: {
407
421
  ...CHECKPOINT_INVALIDATE_ALL,
@@ -438,8 +452,8 @@ bucket_definitions:
438
452
 
439
453
  expect(state.getFilteredBucketPositions(line2.bucketsToFetch)).toEqual(
440
454
  new Map([
441
- ['global[1]', '3'],
442
- ['global[2]', '1']
455
+ ['global[1]', 3n],
456
+ ['global[2]', 1n]
443
457
  ])
444
458
  );
445
459
  });
@@ -452,19 +466,23 @@ bucket_definitions:
452
466
  storage.updateTestChecksum({ bucket: 'by_project[3]', checksum: 1, count: 1 });
453
467
 
454
468
  const state = new BucketChecksumState({
469
+ syncContext,
455
470
  syncParams: new RequestParameters({ sub: 'u1' }, {}),
456
471
  syncRules: SYNC_RULES_DYNAMIC,
457
472
  bucketStorage: storage
458
473
  });
459
474
 
460
- storage.getParameterSets = async (checkpoint: OpId, lookups: SqliteJsonValue[][]): Promise<SqliteJsonRow[]> => {
461
- expect(checkpoint).toEqual('1');
475
+ storage.getParameterSets = async (
476
+ checkpoint: InternalOpId,
477
+ lookups: SqliteJsonValue[][]
478
+ ): Promise<SqliteJsonRow[]> => {
479
+ expect(checkpoint).toEqual(1n);
462
480
  expect(lookups).toEqual([['by_project', '1', 'u1']]);
463
481
  return [{ id: 1 }, { id: 2 }];
464
482
  };
465
483
 
466
484
  const line = (await state.buildNextCheckpointLine({
467
- base: { checkpoint: '1', lsn: '1' },
485
+ base: { checkpoint: 1n, lsn: '1' },
468
486
  writeCheckpoint: null,
469
487
  update: CHECKPOINT_INVALIDATE_ALL
470
488
  }))!;
@@ -491,23 +509,26 @@ bucket_definitions:
491
509
  // This is the bucket data to be fetched
492
510
  expect(state.getFilteredBucketPositions(line.bucketsToFetch)).toEqual(
493
511
  new Map([
494
- ['by_project[1]', '0'],
495
- ['by_project[2]', '0']
512
+ ['by_project[1]', 0n],
513
+ ['by_project[2]', 0n]
496
514
  ])
497
515
  );
498
516
 
499
- state.updateBucketPosition({ bucket: 'by_project[1]', nextAfter: '1', hasMore: false });
500
- state.updateBucketPosition({ bucket: 'by_project[2]', nextAfter: '1', hasMore: false });
517
+ state.updateBucketPosition({ bucket: 'by_project[1]', nextAfter: 1n, hasMore: false });
518
+ state.updateBucketPosition({ bucket: 'by_project[2]', nextAfter: 1n, hasMore: false });
501
519
 
502
- storage.getParameterSets = async (checkpoint: OpId, lookups: SqliteJsonValue[][]): Promise<SqliteJsonRow[]> => {
503
- expect(checkpoint).toEqual('2');
520
+ storage.getParameterSets = async (
521
+ checkpoint: InternalOpId,
522
+ lookups: SqliteJsonValue[][]
523
+ ): Promise<SqliteJsonRow[]> => {
524
+ expect(checkpoint).toEqual(2n);
504
525
  expect(lookups).toEqual([['by_project', '1', 'u1']]);
505
526
  return [{ id: 1 }, { id: 2 }, { id: 3 }];
506
527
  };
507
528
 
508
529
  // Now we get a new line
509
530
  const line2 = (await state.buildNextCheckpointLine({
510
- base: { checkpoint: '2', lsn: '2' },
531
+ base: { checkpoint: 2n, lsn: '2' },
511
532
  writeCheckpoint: null,
512
533
  update: {
513
534
  invalidateDataBuckets: false,
@@ -524,7 +545,7 @@ bucket_definitions:
524
545
  write_checkpoint: undefined
525
546
  }
526
547
  });
527
- expect(state.getFilteredBucketPositions(line2.bucketsToFetch)).toEqual(new Map([['by_project[3]', '0']]));
548
+ expect(state.getFilteredBucketPositions(line2.bucketsToFetch)).toEqual(new Map([['by_project[3]', 0n]]));
528
549
  });
529
550
  });
530
551
 
@@ -543,7 +564,7 @@ class MockBucketChecksumStateStorage implements BucketChecksumStateStorage {
543
564
  this.filter?.({ invalidate: true });
544
565
  }
545
566
 
546
- async getChecksums(checkpoint: OpId, buckets: string[]): Promise<ChecksumMap> {
567
+ async getChecksums(checkpoint: InternalOpId, buckets: string[]): Promise<ChecksumMap> {
547
568
  return new Map<string, BucketChecksum>(
548
569
  buckets.map((bucket) => {
549
570
  const checksum = this.state.get(bucket);
@@ -559,7 +580,7 @@ class MockBucketChecksumStateStorage implements BucketChecksumStateStorage {
559
580
  );
560
581
  }
561
582
 
562
- async getParameterSets(checkpoint: OpId, lookups: SqliteJsonValue[][]): Promise<SqliteJsonRow[]> {
583
+ async getParameterSets(checkpoint: InternalOpId, lookups: SqliteJsonValue[][]): Promise<SqliteJsonRow[]> {
563
584
  throw new Error('Method not implemented.');
564
585
  }
565
586
  }