@powersync/service-core 1.11.3 → 1.12.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 (107) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/dist/auth/CachedKeyCollector.js +2 -7
  3. package/dist/auth/CachedKeyCollector.js.map +1 -1
  4. package/dist/auth/CompoundKeyCollector.js.map +1 -1
  5. package/dist/auth/KeyCollector.d.ts +2 -2
  6. package/dist/auth/KeyStore.js +32 -14
  7. package/dist/auth/KeyStore.js.map +1 -1
  8. package/dist/auth/RemoteJWKSCollector.d.ts +1 -0
  9. package/dist/auth/RemoteJWKSCollector.js +39 -16
  10. package/dist/auth/RemoteJWKSCollector.js.map +1 -1
  11. package/dist/auth/auth-index.d.ts +1 -0
  12. package/dist/auth/auth-index.js +1 -0
  13. package/dist/auth/auth-index.js.map +1 -1
  14. package/dist/auth/utils.d.ts +6 -0
  15. package/dist/auth/utils.js +97 -0
  16. package/dist/auth/utils.js.map +1 -0
  17. package/dist/entry/commands/compact-action.js +4 -1
  18. package/dist/entry/commands/compact-action.js.map +1 -1
  19. package/dist/entry/commands/migrate-action.js +4 -1
  20. package/dist/entry/commands/migrate-action.js.map +1 -1
  21. package/dist/entry/commands/test-connection-action.js +4 -1
  22. package/dist/entry/commands/test-connection-action.js.map +1 -1
  23. package/dist/routes/RouterEngine.d.ts +2 -0
  24. package/dist/routes/RouterEngine.js +15 -10
  25. package/dist/routes/RouterEngine.js.map +1 -1
  26. package/dist/routes/auth.d.ts +5 -16
  27. package/dist/routes/auth.js +6 -4
  28. package/dist/routes/auth.js.map +1 -1
  29. package/dist/routes/configure-fastify.d.ts +3 -21
  30. package/dist/routes/configure-fastify.js +3 -6
  31. package/dist/routes/configure-fastify.js.map +1 -1
  32. package/dist/routes/configure-rsocket.js +28 -14
  33. package/dist/routes/configure-rsocket.js.map +1 -1
  34. package/dist/routes/endpoints/admin.js.map +1 -1
  35. package/dist/routes/endpoints/checkpointing.d.ts +4 -28
  36. package/dist/routes/endpoints/checkpointing.js.map +1 -1
  37. package/dist/routes/endpoints/route-endpoints-index.d.ts +1 -0
  38. package/dist/routes/endpoints/route-endpoints-index.js +1 -0
  39. package/dist/routes/endpoints/route-endpoints-index.js.map +1 -1
  40. package/dist/routes/endpoints/socket-route.js +22 -8
  41. package/dist/routes/endpoints/socket-route.js.map +1 -1
  42. package/dist/routes/endpoints/sync-rules.js.map +1 -1
  43. package/dist/routes/endpoints/sync-stream.d.ts +2 -14
  44. package/dist/routes/endpoints/sync-stream.js +28 -9
  45. package/dist/routes/endpoints/sync-stream.js.map +1 -1
  46. package/dist/routes/route-register.js +10 -6
  47. package/dist/routes/route-register.js.map +1 -1
  48. package/dist/routes/router.d.ts +8 -7
  49. package/dist/routes/router.js.map +1 -1
  50. package/dist/runner/teardown.js +4 -1
  51. package/dist/runner/teardown.js.map +1 -1
  52. package/dist/sync/BucketChecksumState.d.ts +40 -18
  53. package/dist/sync/BucketChecksumState.js +120 -72
  54. package/dist/sync/BucketChecksumState.js.map +1 -1
  55. package/dist/sync/RequestTracker.d.ts +22 -1
  56. package/dist/sync/RequestTracker.js +51 -2
  57. package/dist/sync/RequestTracker.js.map +1 -1
  58. package/dist/sync/sync.d.ts +3 -5
  59. package/dist/sync/sync.js +47 -32
  60. package/dist/sync/sync.js.map +1 -1
  61. package/dist/system/ServiceContext.d.ts +19 -4
  62. package/dist/system/ServiceContext.js +20 -8
  63. package/dist/system/ServiceContext.js.map +1 -1
  64. package/dist/util/config/collectors/config-collector.js +4 -33
  65. package/dist/util/config/collectors/config-collector.js.map +1 -1
  66. package/dist/util/config/collectors/impl/yaml-env.d.ts +7 -0
  67. package/dist/util/config/collectors/impl/yaml-env.js +59 -0
  68. package/dist/util/config/collectors/impl/yaml-env.js.map +1 -0
  69. package/dist/util/config/compound-config-collector.js +18 -1
  70. package/dist/util/config/compound-config-collector.js.map +1 -1
  71. package/dist/util/config/types.d.ts +11 -0
  72. package/package.json +6 -7
  73. package/src/auth/CachedKeyCollector.ts +4 -6
  74. package/src/auth/CompoundKeyCollector.ts +2 -1
  75. package/src/auth/KeyCollector.ts +2 -2
  76. package/src/auth/KeyStore.ts +45 -20
  77. package/src/auth/RemoteJWKSCollector.ts +39 -16
  78. package/src/auth/auth-index.ts +1 -0
  79. package/src/auth/utils.ts +102 -0
  80. package/src/entry/commands/compact-action.ts +4 -1
  81. package/src/entry/commands/migrate-action.ts +4 -1
  82. package/src/entry/commands/test-connection-action.ts +4 -1
  83. package/src/routes/RouterEngine.ts +21 -11
  84. package/src/routes/auth.ts +7 -6
  85. package/src/routes/configure-fastify.ts +6 -8
  86. package/src/routes/configure-rsocket.ts +33 -18
  87. package/src/routes/endpoints/admin.ts +5 -5
  88. package/src/routes/endpoints/checkpointing.ts +2 -2
  89. package/src/routes/endpoints/route-endpoints-index.ts +1 -0
  90. package/src/routes/endpoints/socket-route.ts +27 -11
  91. package/src/routes/endpoints/sync-rules.ts +4 -4
  92. package/src/routes/endpoints/sync-stream.ts +34 -11
  93. package/src/routes/route-register.ts +10 -7
  94. package/src/routes/router.ts +11 -4
  95. package/src/runner/teardown.ts +5 -1
  96. package/src/sync/BucketChecksumState.ts +160 -75
  97. package/src/sync/RequestTracker.ts +70 -3
  98. package/src/sync/sync.ts +69 -46
  99. package/src/system/ServiceContext.ts +31 -12
  100. package/src/util/config/collectors/config-collector.ts +4 -40
  101. package/src/util/config/collectors/impl/yaml-env.ts +67 -0
  102. package/src/util/config/compound-config-collector.ts +22 -5
  103. package/src/util/config/types.ts +13 -0
  104. package/test/src/auth.test.ts +29 -11
  105. package/test/src/config.test.ts +72 -0
  106. package/test/src/sync/BucketChecksumState.test.ts +32 -18
  107. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,72 @@
1
+ // Vitest Unit Tests
2
+ import { CompoundConfigCollector } from '@/index.js';
3
+ import { describe, expect, it, vi } from 'vitest';
4
+
5
+ describe('Config', () => {
6
+ it('should substitute env variables in YAML config', {}, async () => {
7
+ const type = 'mongodb';
8
+ vi.stubEnv('PS_MONGO_TYPE', type);
9
+
10
+ const yamlConfig = /* yaml */ `
11
+ # PowerSync config
12
+ replication:
13
+ connections: []
14
+ storage:
15
+ type: !env PS_MONGO_TYPE
16
+ `;
17
+
18
+ const collector = new CompoundConfigCollector();
19
+
20
+ const config = await collector.collectConfig({
21
+ config_base64: Buffer.from(yamlConfig, 'utf-8').toString('base64')
22
+ });
23
+
24
+ expect(config.storage.type).toBe(type);
25
+ });
26
+
27
+ it('should substitute boolean env variables in YAML config', {}, async () => {
28
+ vi.stubEnv('PS_MONGO_HEALTHCHECK', 'true');
29
+
30
+ const yamlConfig = /* yaml */ `
31
+ # PowerSync config
32
+ replication:
33
+ connections: []
34
+ storage:
35
+ type: mongodb
36
+ healthcheck:
37
+ probes:
38
+ use_http: !env PS_MONGO_HEALTHCHECK::boolean
39
+ `;
40
+
41
+ const collector = new CompoundConfigCollector();
42
+
43
+ const config = await collector.collectConfig({
44
+ config_base64: Buffer.from(yamlConfig, 'utf-8').toString('base64')
45
+ });
46
+
47
+ expect(config.healthcheck.probes.use_http).toBe(true);
48
+ });
49
+
50
+ it('should substitute number env variables in YAML config', {}, async () => {
51
+ vi.stubEnv('PS_MAX_BUCKETS', '1');
52
+
53
+ const yamlConfig = /* yaml */ `
54
+ # PowerSync config
55
+ replication:
56
+ connections: []
57
+ storage:
58
+ type: mongodb
59
+ api:
60
+ parameters:
61
+ max_buckets_per_connection: !env PS_MAX_BUCKETS::number
62
+ `;
63
+
64
+ const collector = new CompoundConfigCollector();
65
+
66
+ const config = await collector.collectConfig({
67
+ config_base64: Buffer.from(yamlConfig, 'utf-8').toString('base64')
68
+ });
69
+
70
+ expect(config.api_parameters.max_buckets_per_connection).toBe(1);
71
+ });
72
+ });
@@ -71,6 +71,7 @@ bucket_definitions:
71
71
  writeCheckpoint: null,
72
72
  update: CHECKPOINT_INVALIDATE_ALL
73
73
  }))!;
74
+ line.advance();
74
75
  expect(line.checkpointLine).toEqual({
75
76
  checkpoint: {
76
77
  buckets: [{ bucket: 'global[]', checksum: 1, count: 1, priority: 3 }],
@@ -85,10 +86,11 @@ bucket_definitions:
85
86
  }
86
87
  ]);
87
88
  // This is the bucket data to be fetched
88
- expect(state.getFilteredBucketPositions(line.bucketsToFetch)).toEqual(new Map([['global[]', 0n]]));
89
+ expect(line.getFilteredBucketPositions()).toEqual(new Map([['global[]', 0n]]));
89
90
 
90
91
  // This similuates the bucket data being sent
91
- state.updateBucketPosition({ bucket: 'global[]', nextAfter: 1n, hasMore: false });
92
+ line.advance();
93
+ line.updateBucketPosition({ bucket: 'global[]', nextAfter: 1n, hasMore: false });
92
94
 
93
95
  // Update bucket storage state
94
96
  storage.updateTestChecksum({ bucket: 'global[]', checksum: 2, count: 2 });
@@ -104,6 +106,7 @@ bucket_definitions:
104
106
  invalidateParameterBuckets: false
105
107
  }
106
108
  }))!;
109
+ line2.advance();
107
110
  expect(line2.checkpointLine).toEqual({
108
111
  checkpoint_diff: {
109
112
  removed_buckets: [],
@@ -112,7 +115,7 @@ bucket_definitions:
112
115
  write_checkpoint: undefined
113
116
  }
114
117
  });
115
- expect(state.getFilteredBucketPositions(line2.bucketsToFetch)).toEqual(new Map([['global[]', 1n]]));
118
+ expect(line2.getFilteredBucketPositions()).toEqual(new Map([['global[]', 1n]]));
116
119
  });
117
120
 
118
121
  test('global bucket with initial state', async () => {
@@ -137,6 +140,7 @@ bucket_definitions:
137
140
  writeCheckpoint: null,
138
141
  update: CHECKPOINT_INVALIDATE_ALL
139
142
  }))!;
143
+ line.advance();
140
144
  expect(line.checkpointLine).toEqual({
141
145
  checkpoint: {
142
146
  buckets: [{ bucket: 'global[]', checksum: 1, count: 1, priority: 3 }],
@@ -151,7 +155,7 @@ bucket_definitions:
151
155
  }
152
156
  ]);
153
157
  // This is the main difference between this and the previous test
154
- expect(state.getFilteredBucketPositions(line.bucketsToFetch)).toEqual(new Map([['global[]', 1n]]));
158
+ expect(line.getFilteredBucketPositions()).toEqual(new Map([['global[]', 1n]]));
155
159
  });
156
160
 
157
161
  test('multiple static buckets', async () => {
@@ -192,6 +196,7 @@ bucket_definitions:
192
196
  priority: 3
193
197
  }
194
198
  ]);
199
+ line.advance();
195
200
 
196
201
  storage.updateTestChecksum({ bucket: 'global[1]', checksum: 2, count: 2 });
197
202
  storage.updateTestChecksum({ bucket: 'global[2]', checksum: 2, count: 2 });
@@ -240,6 +245,7 @@ bucket_definitions:
240
245
  writeCheckpoint: null,
241
246
  update: CHECKPOINT_INVALIDATE_ALL
242
247
  }))!;
248
+ line.advance();
243
249
  expect(line.checkpointLine).toEqual({
244
250
  checkpoint: {
245
251
  buckets: [{ bucket: 'global[]', checksum: 1, count: 1, priority: 3 }],
@@ -253,7 +259,7 @@ bucket_definitions:
253
259
  priority: 3
254
260
  }
255
261
  ]);
256
- expect(state.getFilteredBucketPositions(line.bucketsToFetch)).toEqual(new Map([['global[]', 0n]]));
262
+ expect(line.getFilteredBucketPositions()).toEqual(new Map([['global[]', 0n]]));
257
263
  });
258
264
 
259
265
  test('invalidating individual bucket', async () => {
@@ -274,14 +280,14 @@ bucket_definitions:
274
280
  // We specifically do not set this here, so that we have manual control over the events.
275
281
  // storage.filter = state.checkpointFilter;
276
282
 
277
- await state.buildNextCheckpointLine({
283
+ const line = await state.buildNextCheckpointLine({
278
284
  base: { checkpoint: 1n, lsn: '1' },
279
285
  writeCheckpoint: null,
280
286
  update: CHECKPOINT_INVALIDATE_ALL
281
287
  });
282
-
283
- state.updateBucketPosition({ bucket: 'global[1]', nextAfter: 1n, hasMore: false });
284
- state.updateBucketPosition({ bucket: 'global[2]', nextAfter: 1n, hasMore: false });
288
+ line!.advance();
289
+ line!.updateBucketPosition({ bucket: 'global[1]', nextAfter: 1n, hasMore: false });
290
+ line!.updateBucketPosition({ bucket: 'global[2]', nextAfter: 1n, hasMore: false });
285
291
 
286
292
  storage.updateTestChecksum({ bucket: 'global[1]', checksum: 2, count: 2 });
287
293
  storage.updateTestChecksum({ bucket: 'global[2]', checksum: 2, count: 2 });
@@ -330,12 +336,14 @@ bucket_definitions:
330
336
  storage.updateTestChecksum({ bucket: 'global[1]', checksum: 1, count: 1 });
331
337
  storage.updateTestChecksum({ bucket: 'global[2]', checksum: 1, count: 1 });
332
338
 
333
- await state.buildNextCheckpointLine({
339
+ const line = await state.buildNextCheckpointLine({
334
340
  base: { checkpoint: 1n, lsn: '1' },
335
341
  writeCheckpoint: null,
336
342
  update: CHECKPOINT_INVALIDATE_ALL
337
343
  });
338
344
 
345
+ line!.advance();
346
+
339
347
  storage.updateTestChecksum({ bucket: 'global[1]', checksum: 2, count: 2 });
340
348
  storage.updateTestChecksum({ bucket: 'global[2]', checksum: 2, count: 2 });
341
349
 
@@ -380,6 +388,7 @@ bucket_definitions:
380
388
  writeCheckpoint: null,
381
389
  update: CHECKPOINT_INVALIDATE_ALL
382
390
  }))!;
391
+ line.advance();
383
392
  expect(line.checkpointLine).toEqual({
384
393
  checkpoint: {
385
394
  buckets: [
@@ -402,7 +411,7 @@ bucket_definitions:
402
411
  ]);
403
412
 
404
413
  // This is the bucket data to be fetched
405
- expect(state.getFilteredBucketPositions(line.bucketsToFetch)).toEqual(
414
+ expect(line.getFilteredBucketPositions()).toEqual(
406
415
  new Map([
407
416
  ['global[1]', 0n],
408
417
  ['global[2]', 0n]
@@ -411,8 +420,9 @@ bucket_definitions:
411
420
 
412
421
  // No data changes here.
413
422
  // We simulate partial data sent, before a checkpoint is interrupted.
414
- state.updateBucketPosition({ bucket: 'global[1]', nextAfter: 3n, hasMore: false });
415
- state.updateBucketPosition({ bucket: 'global[2]', nextAfter: 1n, hasMore: true });
423
+ line.advance();
424
+ line.updateBucketPosition({ bucket: 'global[1]', nextAfter: 3n, hasMore: false });
425
+ line.updateBucketPosition({ bucket: 'global[2]', nextAfter: 1n, hasMore: true });
416
426
  storage.updateTestChecksum({ bucket: 'global[1]', checksum: 4, count: 4 });
417
427
 
418
428
  const line2 = (await state.buildNextCheckpointLine({
@@ -424,6 +434,7 @@ bucket_definitions:
424
434
  updatedDataBuckets: new Set(['global[1]'])
425
435
  }
426
436
  }))!;
437
+ line2.advance();
427
438
  expect(line2.checkpointLine).toEqual({
428
439
  checkpoint_diff: {
429
440
  removed_buckets: [],
@@ -451,7 +462,7 @@ bucket_definitions:
451
462
  }
452
463
  ]);
453
464
 
454
- expect(state.getFilteredBucketPositions(line2.bucketsToFetch)).toEqual(
465
+ expect(line2.getFilteredBucketPositions()).toEqual(
455
466
  new Map([
456
467
  ['global[1]', 3n],
457
468
  ['global[2]', 1n]
@@ -507,16 +518,18 @@ bucket_definitions:
507
518
  priority: 3
508
519
  }
509
520
  ]);
521
+ line.advance();
510
522
  // This is the bucket data to be fetched
511
- expect(state.getFilteredBucketPositions(line.bucketsToFetch)).toEqual(
523
+ expect(line.getFilteredBucketPositions()).toEqual(
512
524
  new Map([
513
525
  ['by_project[1]', 0n],
514
526
  ['by_project[2]', 0n]
515
527
  ])
516
528
  );
517
529
 
518
- state.updateBucketPosition({ bucket: 'by_project[1]', nextAfter: 1n, hasMore: false });
519
- state.updateBucketPosition({ bucket: 'by_project[2]', nextAfter: 1n, hasMore: false });
530
+ line.advance();
531
+ line.updateBucketPosition({ bucket: 'by_project[1]', nextAfter: 1n, hasMore: false });
532
+ line.updateBucketPosition({ bucket: 'by_project[2]', nextAfter: 1n, hasMore: false });
520
533
 
521
534
  storage.getParameterSets = async (
522
535
  checkpoint: InternalOpId,
@@ -538,6 +551,7 @@ bucket_definitions:
538
551
  invalidateParameterBuckets: false
539
552
  }
540
553
  }))!;
554
+ line2.advance();
541
555
  expect(line2.checkpointLine).toEqual({
542
556
  checkpoint_diff: {
543
557
  removed_buckets: [],
@@ -546,7 +560,7 @@ bucket_definitions:
546
560
  write_checkpoint: undefined
547
561
  }
548
562
  });
549
- expect(state.getFilteredBucketPositions(line2.bucketsToFetch)).toEqual(new Map([['by_project[3]', 0n]]));
563
+ expect(line2.getFilteredBucketPositions()).toEqual(new Map([['by_project[3]', 0n]]));
550
564
  });
551
565
  });
552
566