@powersync/service-core 1.20.0 → 1.20.2

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 (54) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/auth/utils.d.ts +2 -1
  3. package/dist/auth/utils.js +1 -1
  4. package/dist/auth/utils.js.map +1 -1
  5. package/dist/entry/commands/compact-action.js +26 -5
  6. package/dist/entry/commands/compact-action.js.map +1 -1
  7. package/dist/routes/endpoints/admin.js +1 -0
  8. package/dist/routes/endpoints/admin.js.map +1 -1
  9. package/dist/routes/endpoints/sync-stream.js +6 -1
  10. package/dist/routes/endpoints/sync-stream.js.map +1 -1
  11. package/dist/storage/BucketStorageBatch.d.ts +29 -8
  12. package/dist/storage/BucketStorageBatch.js.map +1 -1
  13. package/dist/storage/BucketStorageFactory.d.ts +5 -0
  14. package/dist/storage/ChecksumCache.d.ts +5 -2
  15. package/dist/storage/ChecksumCache.js +8 -4
  16. package/dist/storage/ChecksumCache.js.map +1 -1
  17. package/dist/storage/PersistedSyncRulesContent.d.ts +6 -2
  18. package/dist/storage/PersistedSyncRulesContent.js +2 -1
  19. package/dist/storage/PersistedSyncRulesContent.js.map +1 -1
  20. package/dist/storage/SourceTable.d.ts +7 -2
  21. package/dist/storage/SourceTable.js.map +1 -1
  22. package/dist/storage/StorageVersionConfig.d.ts +33 -0
  23. package/dist/storage/StorageVersionConfig.js +39 -6
  24. package/dist/storage/StorageVersionConfig.js.map +1 -1
  25. package/dist/storage/SyncRulesBucketStorage.d.ts +26 -6
  26. package/dist/storage/SyncRulesBucketStorage.js.map +1 -1
  27. package/dist/sync/BucketChecksumState.d.ts +3 -3
  28. package/dist/sync/BucketChecksumState.js +12 -42
  29. package/dist/sync/BucketChecksumState.js.map +1 -1
  30. package/dist/sync/sync.js.map +1 -1
  31. package/dist/sync/util.d.ts +1 -0
  32. package/dist/sync/util.js +10 -0
  33. package/dist/sync/util.js.map +1 -1
  34. package/package.json +4 -4
  35. package/src/auth/utils.ts +5 -2
  36. package/src/entry/commands/compact-action.ts +29 -5
  37. package/src/routes/endpoints/admin.ts +1 -0
  38. package/src/routes/endpoints/sync-stream.ts +6 -1
  39. package/src/storage/BucketStorageBatch.ts +33 -9
  40. package/src/storage/BucketStorageFactory.ts +6 -0
  41. package/src/storage/ChecksumCache.ts +14 -6
  42. package/src/storage/PersistedSyncRulesContent.ts +7 -2
  43. package/src/storage/SourceTable.ts +7 -1
  44. package/src/storage/StorageVersionConfig.ts +54 -6
  45. package/src/storage/SyncRulesBucketStorage.ts +33 -6
  46. package/src/sync/BucketChecksumState.ts +18 -49
  47. package/src/sync/sync.ts +9 -3
  48. package/src/sync/util.ts +10 -0
  49. package/test/src/auth.test.ts +20 -1
  50. package/test/src/checksum_cache.test.ts +102 -57
  51. package/test/src/config.test.ts +1 -0
  52. package/test/src/sync/BucketChecksumState.test.ts +53 -21
  53. package/test/src/utils.ts +9 -0
  54. package/tsconfig.tsbuildinfo +1 -1
@@ -1,7 +1,9 @@
1
1
  import { ChecksumCache, FetchChecksums, FetchPartialBucketChecksum } from '@/storage/ChecksumCache.js';
2
2
  import { addChecksums, BucketChecksum, InternalOpId, PartialChecksum } from '@/util/util-index.js';
3
+ import { BucketDataSource } from '@powersync/service-sync-rules';
3
4
  import * as crypto from 'node:crypto';
4
5
  import { describe, expect, it } from 'vitest';
6
+ import { removeSource } from './utils.js';
5
7
 
6
8
  /**
7
9
  * Create a deterministic BucketChecksum based on the bucket name and checkpoint for testing purposes.
@@ -67,6 +69,12 @@ describe('checksum cache', function () {
67
69
  return new ChecksumCache({ fetchChecksums: fetch });
68
70
  };
69
71
 
72
+ const DUMMY_SOURCE: BucketDataSource = null as any;
73
+
74
+ function removeLookupSources(lookups: FetchPartialBucketChecksum[]) {
75
+ return lookups.map((b) => removeSource(b));
76
+ }
77
+
70
78
  it('should handle a sequential lookups (a)', async function () {
71
79
  let lookups: FetchPartialBucketChecksum[][] = [];
72
80
  const cache = factory(async (batch) => {
@@ -74,13 +82,13 @@ describe('checksum cache', function () {
74
82
  return fetchTestChecksums(batch);
75
83
  });
76
84
 
77
- expect(await cache.getChecksums(123n, ['test'])).toEqual([TEST_123]);
85
+ expect(await cache.getChecksums(123n, [{ bucket: 'test', source: DUMMY_SOURCE }])).toEqual([TEST_123]);
78
86
 
79
- expect(await cache.getChecksums(1234n, ['test'])).toEqual([TEST_1234]);
87
+ expect(await cache.getChecksums(1234n, [{ bucket: 'test', source: DUMMY_SOURCE }])).toEqual([TEST_1234]);
80
88
 
81
- expect(await cache.getChecksums(123n, ['test2'])).toEqual([TEST2_123]);
89
+ expect(await cache.getChecksums(123n, [{ bucket: 'test2', source: DUMMY_SOURCE }])).toEqual([TEST2_123]);
82
90
 
83
- expect(lookups).toEqual([
91
+ expect(lookups.map(removeLookupSources)).toMatchObject([
84
92
  [{ bucket: 'test', end: 123n }],
85
93
  // This should use the previous lookup
86
94
  [{ bucket: 'test', start: 123n, end: 1234n }],
@@ -96,13 +104,13 @@ describe('checksum cache', function () {
96
104
  return fetchTestChecksums(batch);
97
105
  });
98
106
 
99
- expect(await cache.getChecksums(123n, ['test2'])).toEqual([TEST2_123]);
107
+ expect(await cache.getChecksums(123n, [{ bucket: 'test2', source: DUMMY_SOURCE }])).toEqual([TEST2_123]);
100
108
 
101
- expect(await cache.getChecksums(1234n, ['test'])).toEqual([TEST_1234]);
109
+ expect(await cache.getChecksums(1234n, [{ bucket: 'test', source: DUMMY_SOURCE }])).toEqual([TEST_1234]);
102
110
 
103
- expect(await cache.getChecksums(123n, ['test'])).toEqual([TEST_123]);
111
+ expect(await cache.getChecksums(123n, [{ bucket: 'test', source: DUMMY_SOURCE }])).toEqual([TEST_123]);
104
112
 
105
- expect(lookups).toEqual([
113
+ expect(lookups.map(removeLookupSources)).toEqual([
106
114
  // With this order, there is no option for a partial lookup
107
115
  [{ bucket: 'test2', end: 123n }],
108
116
  [{ bucket: 'test', end: 1234n }],
@@ -117,16 +125,16 @@ describe('checksum cache', function () {
117
125
  return fetchTestChecksums(batch);
118
126
  });
119
127
 
120
- const p1 = cache.getChecksums(123n, ['test']);
121
- const p2 = cache.getChecksums(1234n, ['test']);
122
- const p3 = cache.getChecksums(123n, ['test2']);
128
+ const p1 = cache.getChecksums(123n, [{ bucket: 'test', source: DUMMY_SOURCE }]);
129
+ const p2 = cache.getChecksums(1234n, [{ bucket: 'test', source: DUMMY_SOURCE }]);
130
+ const p3 = cache.getChecksums(123n, [{ bucket: 'test2', source: DUMMY_SOURCE }]);
123
131
 
124
132
  expect(await p1).toEqual([TEST_123]);
125
133
  expect(await p2).toEqual([TEST_1234]);
126
134
  expect(await p3).toEqual([TEST2_123]);
127
135
 
128
136
  // Concurrent requests, so we can't do a partial lookup for 123 -> 1234
129
- expect(lookups).toEqual([
137
+ expect(lookups.map(removeLookupSources)).toEqual([
130
138
  [{ bucket: 'test', end: 123n }],
131
139
  [{ bucket: 'test', end: 1234n }],
132
140
  [{ bucket: 'test2', end: 123n }]
@@ -140,15 +148,15 @@ describe('checksum cache', function () {
140
148
  return fetchTestChecksums(batch);
141
149
  });
142
150
 
143
- const p1 = cache.getChecksums(123n, ['test']);
144
- const p2 = cache.getChecksums(123n, ['test']);
151
+ const p1 = cache.getChecksums(123n, [{ bucket: 'test', source: DUMMY_SOURCE }]);
152
+ const p2 = cache.getChecksums(123n, [{ bucket: 'test', source: DUMMY_SOURCE }]);
145
153
 
146
154
  expect(await p1).toEqual([TEST_123]);
147
155
 
148
156
  expect(await p2).toEqual([TEST_123]);
149
157
 
150
158
  // The lookup should be deduplicated, even though it's in progress
151
- expect(lookups).toEqual([[{ bucket: 'test', end: 123n }]]);
159
+ expect(lookups.map(removeLookupSources)).toEqual([[{ bucket: 'test', end: 123n }]]);
152
160
  });
153
161
 
154
162
  it('should handle serial + concurrent lookups', async function () {
@@ -158,15 +166,15 @@ describe('checksum cache', function () {
158
166
  return fetchTestChecksums(batch);
159
167
  });
160
168
 
161
- expect(await cache.getChecksums(123n, ['test'])).toEqual([TEST_123]);
169
+ expect(await cache.getChecksums(123n, [{ bucket: 'test', source: DUMMY_SOURCE }])).toEqual([TEST_123]);
162
170
 
163
- const p2 = cache.getChecksums(1234n, ['test']);
164
- const p3 = cache.getChecksums(1234n, ['test']);
171
+ const p2 = cache.getChecksums(1234n, [{ bucket: 'test', source: DUMMY_SOURCE }]);
172
+ const p3 = cache.getChecksums(1234n, [{ bucket: 'test', source: DUMMY_SOURCE }]);
165
173
 
166
174
  expect(await p2).toEqual([TEST_1234]);
167
175
  expect(await p3).toEqual([TEST_1234]);
168
176
 
169
- expect(lookups).toEqual([
177
+ expect(lookups.map(removeLookupSources)).toEqual([
170
178
  [{ bucket: 'test', end: 123n }],
171
179
  // This lookup is deduplicated
172
180
  [{ bucket: 'test', start: 123n, end: 1234n }]
@@ -180,9 +188,14 @@ describe('checksum cache', function () {
180
188
  return fetchTestChecksums(batch);
181
189
  });
182
190
 
183
- expect(await cache.getChecksums(123n, ['test', 'test2'])).toEqual([TEST_123, TEST2_123]);
191
+ expect(
192
+ await cache.getChecksums(123n, [
193
+ { bucket: 'test', source: DUMMY_SOURCE },
194
+ { bucket: 'test2', source: DUMMY_SOURCE }
195
+ ])
196
+ ).toEqual([TEST_123, TEST2_123]);
184
197
 
185
- expect(lookups).toEqual([
198
+ expect(lookups.map(removeLookupSources)).toEqual([
186
199
  [
187
200
  // Both lookups in the same request
188
201
  { bucket: 'test', end: 123n },
@@ -198,10 +211,15 @@ describe('checksum cache', function () {
198
211
  return fetchTestChecksums(batch);
199
212
  });
200
213
 
201
- expect(await cache.getChecksums(123n, ['test'])).toEqual([TEST_123]);
202
- expect(await cache.getChecksums(123n, ['test', 'test2'])).toEqual([TEST_123, TEST2_123]);
214
+ expect(await cache.getChecksums(123n, [{ bucket: 'test', source: DUMMY_SOURCE }])).toEqual([TEST_123]);
215
+ expect(
216
+ await cache.getChecksums(123n, [
217
+ { bucket: 'test', source: DUMMY_SOURCE },
218
+ { bucket: 'test2', source: DUMMY_SOURCE }
219
+ ])
220
+ ).toEqual([TEST_123, TEST2_123]);
203
221
 
204
- expect(lookups).toEqual([
222
+ expect(lookups.map(removeLookupSources)).toEqual([
205
223
  // Request 1
206
224
  [{ bucket: 'test', end: 123n }],
207
225
  // Request 2
@@ -216,13 +234,19 @@ describe('checksum cache', function () {
216
234
  return fetchTestChecksums(batch);
217
235
  });
218
236
 
219
- const a = cache.getChecksums(123n, ['test', 'test2']);
220
- const b = cache.getChecksums(123n, ['test2', 'test3']);
237
+ const a = cache.getChecksums(123n, [
238
+ { bucket: 'test', source: DUMMY_SOURCE },
239
+ { bucket: 'test2', source: DUMMY_SOURCE }
240
+ ]);
241
+ const b = cache.getChecksums(123n, [
242
+ { bucket: 'test2', source: DUMMY_SOURCE },
243
+ { bucket: 'test3', source: DUMMY_SOURCE }
244
+ ]);
221
245
 
222
246
  expect(await a).toEqual([TEST_123, TEST2_123]);
223
247
  expect(await b).toEqual([TEST2_123, TEST3_123]);
224
248
 
225
- expect(lookups).toEqual([
249
+ expect(lookups.map(removeLookupSources)).toEqual([
226
250
  // Request A
227
251
  [
228
252
  { bucket: 'test', end: 123n },
@@ -240,9 +264,9 @@ describe('checksum cache', function () {
240
264
  return fetchTestChecksums(batch);
241
265
  });
242
266
 
243
- expect(await cache.getChecksums(123n, ['test'])).toEqual([TEST_123]);
267
+ expect(await cache.getChecksums(123n, [{ bucket: 'test', source: DUMMY_SOURCE }])).toEqual([TEST_123]);
244
268
 
245
- expect(await cache.getChecksums(125n, ['test'])).toEqual([
269
+ expect(await cache.getChecksums(125n, [{ bucket: 'test', source: DUMMY_SOURCE }])).toEqual([
246
270
  {
247
271
  bucket: 'test',
248
272
  checksum: -1865121912,
@@ -250,14 +274,14 @@ describe('checksum cache', function () {
250
274
  }
251
275
  ]);
252
276
 
253
- expect(await cache.getChecksums(124n, ['test'])).toEqual([
277
+ expect(await cache.getChecksums(124n, [{ bucket: 'test', source: DUMMY_SOURCE }])).toEqual([
254
278
  {
255
279
  bucket: 'test',
256
280
  checksum: 1887460431,
257
281
  count: 124
258
282
  }
259
283
  ]);
260
- expect(lookups).toEqual([
284
+ expect(lookups.map(removeLookupSources)).toEqual([
261
285
  [{ bucket: 'test', end: 123n }],
262
286
  [{ bucket: 'test', start: 123n, end: 125n }],
263
287
  [{ bucket: 'test', start: 123n, end: 124n }]
@@ -275,19 +299,31 @@ describe('checksum cache', function () {
275
299
  return fetchTestChecksums(batch);
276
300
  });
277
301
 
278
- const a = cache.getChecksums(123n, ['test', 'test2']);
279
- const b = cache.getChecksums(123n, ['test2', 'test3']);
302
+ const a = cache.getChecksums(123n, [
303
+ { bucket: 'test', source: DUMMY_SOURCE },
304
+ { bucket: 'test2', source: DUMMY_SOURCE }
305
+ ]);
306
+ const b = cache.getChecksums(123n, [
307
+ { bucket: 'test2', source: DUMMY_SOURCE },
308
+ { bucket: 'test3', source: DUMMY_SOURCE }
309
+ ]);
280
310
 
281
311
  await expect(a).rejects.toEqual(TEST_ERROR);
282
312
  await expect(b).rejects.toEqual(TEST_ERROR);
283
313
 
284
- const a2 = cache.getChecksums(123n, ['test', 'test2']);
285
- const b2 = cache.getChecksums(123n, ['test2', 'test3']);
314
+ const a2 = cache.getChecksums(123n, [
315
+ { bucket: 'test', source: DUMMY_SOURCE },
316
+ { bucket: 'test2', source: DUMMY_SOURCE }
317
+ ]);
318
+ const b2 = cache.getChecksums(123n, [
319
+ { bucket: 'test2', source: DUMMY_SOURCE },
320
+ { bucket: 'test3', source: DUMMY_SOURCE }
321
+ ]);
286
322
 
287
323
  expect(await a2).toEqual([TEST_123, TEST2_123]);
288
324
  expect(await b2).toEqual([TEST2_123, TEST3_123]);
289
325
 
290
- expect(lookups).toEqual([
326
+ expect(lookups.map(removeLookupSources)).toEqual([
291
327
  // Request A (fails)
292
328
  [
293
329
  { bucket: 'test', end: 123n },
@@ -311,11 +347,15 @@ describe('checksum cache', function () {
311
347
  return fetchTestChecksums(batch.filter((b) => b.bucket != 'test'));
312
348
  });
313
349
 
314
- expect(await cache.getChecksums(123n, ['test'])).toEqual([{ bucket: 'test', checksum: 0, count: 0 }]);
315
- expect(await cache.getChecksums(123n, ['test', 'test2'])).toEqual([
316
- { bucket: 'test', checksum: 0, count: 0 },
317
- TEST2_123
350
+ expect(await cache.getChecksums(123n, [{ bucket: 'test', source: DUMMY_SOURCE }])).toEqual([
351
+ { bucket: 'test', checksum: 0, count: 0 }
318
352
  ]);
353
+ expect(
354
+ await cache.getChecksums(123n, [
355
+ { bucket: 'test', source: DUMMY_SOURCE },
356
+ { bucket: 'test2', source: DUMMY_SOURCE }
357
+ ])
358
+ ).toEqual([{ bucket: 'test', checksum: 0, count: 0 }, TEST2_123]);
319
359
  });
320
360
 
321
361
  it('should handle missing checksums (b)', async function () {
@@ -325,8 +365,10 @@ describe('checksum cache', function () {
325
365
  return fetchTestChecksums(batch.filter((b) => b.bucket != 'test' || b.end != 123n));
326
366
  });
327
367
 
328
- expect(await cache.getChecksums(123n, ['test'])).toEqual([{ bucket: 'test', checksum: 0, count: 0 }]);
329
- expect(await cache.getChecksums(1234n, ['test'])).toEqual([
368
+ expect(await cache.getChecksums(123n, [{ bucket: 'test', source: DUMMY_SOURCE }])).toEqual([
369
+ { bucket: 'test', checksum: 0, count: 0 }
370
+ ]);
371
+ expect(await cache.getChecksums(1234n, [{ bucket: 'test', source: DUMMY_SOURCE }])).toEqual([
330
372
  {
331
373
  bucket: 'test',
332
374
  checksum: 1597020602,
@@ -334,7 +376,10 @@ describe('checksum cache', function () {
334
376
  }
335
377
  ]);
336
378
 
337
- expect(lookups).toEqual([[{ bucket: 'test', end: 123n }], [{ bucket: 'test', start: 123n, end: 1234n }]]);
379
+ expect(lookups.map(removeLookupSources)).toEqual([
380
+ [{ bucket: 'test', end: 123n }],
381
+ [{ bucket: 'test', start: 123n, end: 1234n }]
382
+ ]);
338
383
  });
339
384
 
340
385
  it('should use maxSize', async function () {
@@ -347,8 +392,8 @@ describe('checksum cache', function () {
347
392
  maxSize: 2
348
393
  });
349
394
 
350
- expect(await cache.getChecksums(123n, ['test'])).toEqual([TEST_123]);
351
- expect(await cache.getChecksums(124n, ['test'])).toEqual([
395
+ expect(await cache.getChecksums(123n, [{ bucket: 'test', source: DUMMY_SOURCE }])).toEqual([TEST_123]);
396
+ expect(await cache.getChecksums(124n, [{ bucket: 'test', source: DUMMY_SOURCE }])).toEqual([
352
397
  {
353
398
  bucket: 'test',
354
399
  checksum: 1887460431,
@@ -356,30 +401,30 @@ describe('checksum cache', function () {
356
401
  }
357
402
  ]);
358
403
 
359
- expect(await cache.getChecksums(125n, ['test'])).toEqual([
404
+ expect(await cache.getChecksums(125n, [{ bucket: 'test', source: DUMMY_SOURCE }])).toEqual([
360
405
  {
361
406
  bucket: 'test',
362
407
  checksum: -1865121912,
363
408
  count: 125
364
409
  }
365
410
  ]);
366
- expect(await cache.getChecksums(126n, ['test'])).toEqual([
411
+ expect(await cache.getChecksums(126n, [{ bucket: 'test', source: DUMMY_SOURCE }])).toEqual([
367
412
  {
368
413
  bucket: 'test',
369
414
  checksum: -1720007310,
370
415
  count: 126
371
416
  }
372
417
  ]);
373
- expect(await cache.getChecksums(124n, ['test'])).toEqual([
418
+ expect(await cache.getChecksums(124n, [{ bucket: 'test', source: DUMMY_SOURCE }])).toEqual([
374
419
  {
375
420
  bucket: 'test',
376
421
  checksum: 1887460431,
377
422
  count: 124
378
423
  }
379
424
  ]);
380
- expect(await cache.getChecksums(123n, ['test'])).toEqual([TEST_123]);
425
+ expect(await cache.getChecksums(123n, [{ bucket: 'test', source: DUMMY_SOURCE }])).toEqual([TEST_123]);
381
426
 
382
- expect(lookups).toEqual([
427
+ expect(lookups.map(removeLookupSources)).toEqual([
383
428
  [{ bucket: 'test', end: 123n }],
384
429
  [{ bucket: 'test', start: 123n, end: 124n }],
385
430
  [{ bucket: 'test', start: 124n, end: 125n }],
@@ -400,10 +445,10 @@ describe('checksum cache', function () {
400
445
  maxSize: 2
401
446
  });
402
447
 
403
- const p3 = cache.getChecksums(123n, ['test3']);
404
- const p4 = cache.getChecksums(123n, ['test4']);
405
- const p1 = cache.getChecksums(123n, ['test']);
406
- const p2 = cache.getChecksums(123n, ['test2']);
448
+ const p3 = cache.getChecksums(123n, [{ bucket: 'test3', source: DUMMY_SOURCE }]);
449
+ const p4 = cache.getChecksums(123n, [{ bucket: 'test4', source: DUMMY_SOURCE }]);
450
+ const p1 = cache.getChecksums(123n, [{ bucket: 'test', source: DUMMY_SOURCE }]);
451
+ const p2 = cache.getChecksums(123n, [{ bucket: 'test2', source: DUMMY_SOURCE }]);
407
452
 
408
453
  expect(await p1).toEqual([TEST_123]);
409
454
  expect(await p2).toEqual([TEST2_123]);
@@ -417,7 +462,7 @@ describe('checksum cache', function () {
417
462
  ]);
418
463
 
419
464
  // The lookup should be deduplicated, even though it's in progress
420
- expect(lookups).toEqual([
465
+ expect(lookups.map(removeLookupSources)).toEqual([
421
466
  [{ bucket: 'test3', end: 123n }],
422
467
  [{ bucket: 'test4', end: 123n }],
423
468
  [{ bucket: 'test', end: 123n }],
@@ -434,7 +479,7 @@ describe('checksum cache', function () {
434
479
  return fetchTestChecksums(batch);
435
480
  });
436
481
 
437
- expect(await cache.getChecksums(123n, ['test'])).toEqual([TEST_123]);
438
- expect(await cache.getChecksums(1234n, ['test'])).toEqual([TEST_1234]);
482
+ expect(await cache.getChecksums(123n, [{ bucket: 'test', source: DUMMY_SOURCE }])).toEqual([TEST_123]);
483
+ expect(await cache.getChecksums(1234n, [{ bucket: 'test', source: DUMMY_SOURCE }])).toEqual([TEST_1234]);
439
484
  });
440
485
  });
@@ -155,6 +155,7 @@ describe('Config', () => {
155
155
  type: mongodb
156
156
  `;
157
157
  const yamlSyncConfig = /* yaml */ `
158
+ # Sync config
158
159
  config:
159
160
  edition: 2
160
161
  streams:
@@ -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
+ }