@powersync/service-core 1.11.3 → 1.12.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 (114) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/dist/api/RouteAPI.d.ts +0 -4
  3. package/dist/auth/CachedKeyCollector.js +2 -7
  4. package/dist/auth/CachedKeyCollector.js.map +1 -1
  5. package/dist/auth/CompoundKeyCollector.js.map +1 -1
  6. package/dist/auth/KeyCollector.d.ts +2 -2
  7. package/dist/auth/KeyStore.js +32 -14
  8. package/dist/auth/KeyStore.js.map +1 -1
  9. package/dist/auth/RemoteJWKSCollector.d.ts +1 -0
  10. package/dist/auth/RemoteJWKSCollector.js +39 -16
  11. package/dist/auth/RemoteJWKSCollector.js.map +1 -1
  12. package/dist/auth/auth-index.d.ts +1 -0
  13. package/dist/auth/auth-index.js +1 -0
  14. package/dist/auth/auth-index.js.map +1 -1
  15. package/dist/auth/utils.d.ts +6 -0
  16. package/dist/auth/utils.js +97 -0
  17. package/dist/auth/utils.js.map +1 -0
  18. package/dist/entry/commands/compact-action.js +4 -1
  19. package/dist/entry/commands/compact-action.js.map +1 -1
  20. package/dist/entry/commands/migrate-action.js +4 -1
  21. package/dist/entry/commands/migrate-action.js.map +1 -1
  22. package/dist/entry/commands/test-connection-action.js +4 -1
  23. package/dist/entry/commands/test-connection-action.js.map +1 -1
  24. package/dist/routes/RouterEngine.d.ts +2 -0
  25. package/dist/routes/RouterEngine.js +15 -10
  26. package/dist/routes/RouterEngine.js.map +1 -1
  27. package/dist/routes/auth.d.ts +5 -16
  28. package/dist/routes/auth.js +6 -4
  29. package/dist/routes/auth.js.map +1 -1
  30. package/dist/routes/configure-fastify.d.ts +3 -21
  31. package/dist/routes/configure-fastify.js +3 -6
  32. package/dist/routes/configure-fastify.js.map +1 -1
  33. package/dist/routes/configure-rsocket.js +28 -14
  34. package/dist/routes/configure-rsocket.js.map +1 -1
  35. package/dist/routes/endpoints/admin.js.map +1 -1
  36. package/dist/routes/endpoints/checkpointing.d.ts +4 -28
  37. package/dist/routes/endpoints/checkpointing.js.map +1 -1
  38. package/dist/routes/endpoints/route-endpoints-index.d.ts +1 -0
  39. package/dist/routes/endpoints/route-endpoints-index.js +1 -0
  40. package/dist/routes/endpoints/route-endpoints-index.js.map +1 -1
  41. package/dist/routes/endpoints/socket-route.js +22 -8
  42. package/dist/routes/endpoints/socket-route.js.map +1 -1
  43. package/dist/routes/endpoints/sync-rules.js +6 -6
  44. package/dist/routes/endpoints/sync-rules.js.map +1 -1
  45. package/dist/routes/endpoints/sync-stream.d.ts +2 -14
  46. package/dist/routes/endpoints/sync-stream.js +28 -9
  47. package/dist/routes/endpoints/sync-stream.js.map +1 -1
  48. package/dist/routes/route-register.js +10 -6
  49. package/dist/routes/route-register.js.map +1 -1
  50. package/dist/routes/router.d.ts +8 -7
  51. package/dist/routes/router.js.map +1 -1
  52. package/dist/runner/teardown.js +4 -1
  53. package/dist/runner/teardown.js.map +1 -1
  54. package/dist/sync/BucketChecksumState.d.ts +40 -18
  55. package/dist/sync/BucketChecksumState.js +122 -74
  56. package/dist/sync/BucketChecksumState.js.map +1 -1
  57. package/dist/sync/RequestTracker.d.ts +22 -1
  58. package/dist/sync/RequestTracker.js +51 -2
  59. package/dist/sync/RequestTracker.js.map +1 -1
  60. package/dist/sync/sync.d.ts +3 -5
  61. package/dist/sync/sync.js +49 -34
  62. package/dist/sync/sync.js.map +1 -1
  63. package/dist/system/ServiceContext.d.ts +19 -4
  64. package/dist/system/ServiceContext.js +20 -8
  65. package/dist/system/ServiceContext.js.map +1 -1
  66. package/dist/util/config/collectors/config-collector.js +4 -33
  67. package/dist/util/config/collectors/config-collector.js.map +1 -1
  68. package/dist/util/config/collectors/impl/yaml-env.d.ts +7 -0
  69. package/dist/util/config/collectors/impl/yaml-env.js +59 -0
  70. package/dist/util/config/collectors/impl/yaml-env.js.map +1 -0
  71. package/dist/util/config/compound-config-collector.js +18 -1
  72. package/dist/util/config/compound-config-collector.js.map +1 -1
  73. package/dist/util/config/types.d.ts +11 -0
  74. package/dist/util/protocol-types.d.ts +9 -9
  75. package/dist/util/protocol-types.js.map +1 -1
  76. package/dist/util/utils.d.ts +1 -1
  77. package/package.json +6 -7
  78. package/src/api/RouteAPI.ts +0 -5
  79. package/src/auth/CachedKeyCollector.ts +4 -6
  80. package/src/auth/CompoundKeyCollector.ts +2 -1
  81. package/src/auth/KeyCollector.ts +2 -2
  82. package/src/auth/KeyStore.ts +45 -20
  83. package/src/auth/RemoteJWKSCollector.ts +39 -16
  84. package/src/auth/auth-index.ts +1 -0
  85. package/src/auth/utils.ts +102 -0
  86. package/src/entry/commands/compact-action.ts +4 -1
  87. package/src/entry/commands/migrate-action.ts +4 -1
  88. package/src/entry/commands/test-connection-action.ts +4 -1
  89. package/src/routes/RouterEngine.ts +21 -11
  90. package/src/routes/auth.ts +7 -6
  91. package/src/routes/configure-fastify.ts +6 -8
  92. package/src/routes/configure-rsocket.ts +33 -18
  93. package/src/routes/endpoints/admin.ts +5 -5
  94. package/src/routes/endpoints/checkpointing.ts +2 -2
  95. package/src/routes/endpoints/route-endpoints-index.ts +1 -0
  96. package/src/routes/endpoints/socket-route.ts +27 -11
  97. package/src/routes/endpoints/sync-rules.ts +10 -10
  98. package/src/routes/endpoints/sync-stream.ts +34 -11
  99. package/src/routes/route-register.ts +10 -7
  100. package/src/routes/router.ts +11 -4
  101. package/src/runner/teardown.ts +5 -1
  102. package/src/sync/BucketChecksumState.ts +162 -77
  103. package/src/sync/RequestTracker.ts +70 -3
  104. package/src/sync/sync.ts +72 -49
  105. package/src/system/ServiceContext.ts +31 -12
  106. package/src/util/config/collectors/config-collector.ts +4 -40
  107. package/src/util/config/collectors/impl/yaml-env.ts +67 -0
  108. package/src/util/config/compound-config-collector.ts +22 -5
  109. package/src/util/config/types.ts +13 -0
  110. package/src/util/protocol-types.ts +15 -10
  111. package/test/src/auth.test.ts +29 -11
  112. package/test/src/config.test.ts +72 -0
  113. package/test/src/sync/BucketChecksumState.test.ts +32 -18
  114. package/tsconfig.tsbuildinfo +1 -1
@@ -75,21 +75,21 @@ describe('JWT Auth', () => {
75
75
  defaultAudiences: ['other'],
76
76
  maxAge: '6m'
77
77
  })
78
- ).rejects.toThrow('unexpected "aud" claim value');
78
+ ).rejects.toThrow('[PSYNC_S2105] Unexpected "aud" claim value: "tests"');
79
79
 
80
80
  await expect(
81
81
  store.verifyJwt(signedJwt, {
82
82
  defaultAudiences: [],
83
83
  maxAge: '6m'
84
84
  })
85
- ).rejects.toThrow('unexpected "aud" claim value');
85
+ ).rejects.toThrow('[PSYNC_S2105] Unexpected "aud" claim value: "tests"');
86
86
 
87
87
  await expect(
88
88
  store.verifyJwt(signedJwt, {
89
89
  defaultAudiences: ['tests'],
90
90
  maxAge: '1m'
91
91
  })
92
- ).rejects.toThrow('Token must expire in a maximum of');
92
+ ).rejects.toThrow('[PSYNC_S2104] Token must expire in a maximum of 60 seconds, got 300s');
93
93
 
94
94
  const signedJwt2 = await new jose.SignJWT({})
95
95
  .setProtectedHeader({ alg: 'HS256', kid: 'k1' })
@@ -104,7 +104,25 @@ describe('JWT Auth', () => {
104
104
  defaultAudiences: ['tests'],
105
105
  maxAge: '5m'
106
106
  })
107
- ).rejects.toThrow('missing required "sub" claim');
107
+ ).rejects.toThrow('[PSYNC_S2101] JWT payload is missing a required claim "sub"');
108
+
109
+ // expired token
110
+ const d = Math.round(Date.now() / 1000);
111
+ const signedJwt3 = await new jose.SignJWT({})
112
+ .setProtectedHeader({ alg: 'HS256', kid: 'k1' })
113
+ .setSubject('f1')
114
+ .setIssuedAt(d - 500)
115
+ .setIssuer('tester')
116
+ .setAudience('tests')
117
+ .setExpirationTime(d - 400)
118
+ .sign(signKey);
119
+
120
+ await expect(
121
+ store.verifyJwt(signedJwt3, {
122
+ defaultAudiences: ['tests'],
123
+ maxAge: '5m'
124
+ })
125
+ ).rejects.toThrow('[PSYNC_S2103] JWT has expired');
108
126
  });
109
127
 
110
128
  test('Algorithm validation', async () => {
@@ -159,7 +177,7 @@ describe('JWT Auth', () => {
159
177
  maxAge: '6m'
160
178
  })
161
179
  ).rejects.toThrow(
162
- 'Could not find an appropriate key in the keystore. The key is missing or no key matched the token KID'
180
+ '[PSYNC_S2101] Could not find an appropriate key in the keystore. The key is missing or no key matched the token KID'
163
181
  );
164
182
 
165
183
  // Wrong kid
@@ -178,7 +196,7 @@ describe('JWT Auth', () => {
178
196
  maxAge: '6m'
179
197
  })
180
198
  ).rejects.toThrow(
181
- 'Could not find an appropriate key in the keystore. The key is missing or no key matched the token KID'
199
+ '[PSYNC_S2101] Could not find an appropriate key in the keystore. The key is missing or no key matched the token KID'
182
200
  );
183
201
 
184
202
  // No kid, matches sharedKey2
@@ -255,7 +273,7 @@ describe('JWT Auth', () => {
255
273
  defaultAudiences: ['tests'],
256
274
  maxAge: '6m'
257
275
  })
258
- ).rejects.toThrow('unexpected "aud" claim value');
276
+ ).rejects.toThrow('[PSYNC_S2105] Unexpected "aud" claim value: "tests"');
259
277
 
260
278
  const signedJwt3 = await new jose.SignJWT({})
261
279
  .setProtectedHeader({ alg: 'HS256', kid: 'k1' })
@@ -290,7 +308,7 @@ describe('JWT Auth', () => {
290
308
  reject_ip_ranges: ['local']
291
309
  }
292
310
  });
293
- await expect(invalid.getKeys()).rejects.toThrow('IPs in this range are not supported');
311
+ await expect(invalid.getKeys()).rejects.toThrow('[PSYNC_S2204] JWKS request failed');
294
312
 
295
313
  // IPs throw an error immediately
296
314
  expect(
@@ -345,7 +363,7 @@ describe('JWT Auth', () => {
345
363
  expect(key.kid).toEqual(publicKeyRSA.kid!);
346
364
 
347
365
  cached.addTimeForTests(301_000);
348
- currentResponse = Promise.reject('refresh failed');
366
+ currentResponse = Promise.reject(new Error('refresh failed'));
349
367
 
350
368
  // Uses the promise, refreshes in the background
351
369
  let response = await cached.getKeys();
@@ -357,14 +375,14 @@ describe('JWT Auth', () => {
357
375
  response = await cached.getKeys();
358
376
  // Still have the cached key, but also have the error
359
377
  expect(response.keys[0].kid).toEqual(publicKeyRSA.kid!);
360
- expect(response.errors[0].message).toMatch('Failed to fetch');
378
+ expect(response.errors[0].message).toMatch('[PSYNC_S2201] refresh failed');
361
379
 
362
380
  await cached.addTimeForTests(3601_000);
363
381
  response = await cached.getKeys();
364
382
 
365
383
  // Now the keys have expired, and the request still fails
366
384
  expect(response.keys).toEqual([]);
367
- expect(response.errors[0].message).toMatch('Failed to fetch');
385
+ expect(response.errors[0].message).toMatch('[PSYNC_S2201] refresh failed');
368
386
 
369
387
  currentResponse = Promise.resolve({
370
388
  errors: [],
@@ -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