@powersync/service-core-tests 0.14.0 → 0.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 (38) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/dist/test-utils/general-utils.d.ts +22 -3
  3. package/dist/test-utils/general-utils.js +56 -3
  4. package/dist/test-utils/general-utils.js.map +1 -1
  5. package/dist/test-utils/stream_utils.js +2 -2
  6. package/dist/test-utils/stream_utils.js.map +1 -1
  7. package/dist/tests/register-compacting-tests.d.ts +1 -1
  8. package/dist/tests/register-compacting-tests.js +360 -297
  9. package/dist/tests/register-compacting-tests.js.map +1 -1
  10. package/dist/tests/register-data-storage-checkpoint-tests.d.ts +1 -1
  11. package/dist/tests/register-data-storage-checkpoint-tests.js +59 -48
  12. package/dist/tests/register-data-storage-checkpoint-tests.js.map +1 -1
  13. package/dist/tests/register-data-storage-data-tests.d.ts +2 -2
  14. package/dist/tests/register-data-storage-data-tests.js +1112 -612
  15. package/dist/tests/register-data-storage-data-tests.js.map +1 -1
  16. package/dist/tests/register-data-storage-parameter-tests.d.ts +1 -1
  17. package/dist/tests/register-data-storage-parameter-tests.js +273 -254
  18. package/dist/tests/register-data-storage-parameter-tests.js.map +1 -1
  19. package/dist/tests/register-parameter-compacting-tests.d.ts +1 -1
  20. package/dist/tests/register-parameter-compacting-tests.js +83 -87
  21. package/dist/tests/register-parameter-compacting-tests.js.map +1 -1
  22. package/dist/tests/register-sync-tests.d.ts +2 -1
  23. package/dist/tests/register-sync-tests.js +479 -451
  24. package/dist/tests/register-sync-tests.js.map +1 -1
  25. package/dist/tests/util.d.ts +5 -4
  26. package/dist/tests/util.js +27 -12
  27. package/dist/tests/util.js.map +1 -1
  28. package/package.json +3 -3
  29. package/src/test-utils/general-utils.ts +81 -4
  30. package/src/test-utils/stream_utils.ts +2 -2
  31. package/src/tests/register-compacting-tests.ts +376 -322
  32. package/src/tests/register-data-storage-checkpoint-tests.ts +85 -53
  33. package/src/tests/register-data-storage-data-tests.ts +1050 -559
  34. package/src/tests/register-data-storage-parameter-tests.ts +330 -288
  35. package/src/tests/register-parameter-compacting-tests.ts +87 -90
  36. package/src/tests/register-sync-tests.ts +390 -380
  37. package/src/tests/util.ts +46 -17
  38. package/tsconfig.tsbuildinfo +1 -1
@@ -14,14 +14,11 @@ import * as timers from 'timers/promises';
14
14
  import { fileURLToPath } from 'url';
15
15
  import { expect, test } from 'vitest';
16
16
  import * as test_utils from '../test-utils/test-utils-index.js';
17
- import { METRICS_HELPER } from '../test-utils/test-utils-index.js';
18
- import { bucketRequest } from './util.js';
17
+ import { bucketRequest, METRICS_HELPER } from '../test-utils/test-utils-index.js';
19
18
 
20
19
  const __filename = fileURLToPath(import.meta.url);
21
20
  const __dirname = path.dirname(__filename);
22
21
 
23
- const TEST_TABLE = test_utils.makeTestTable('test', ['id']);
24
-
25
22
  const BASIC_SYNC_RULES = `
26
23
  bucket_definitions:
27
24
  mybucket:
@@ -39,7 +36,15 @@ export const SYNC_SNAPSHOT_PATH = path.resolve(__dirname, '../__snapshots/sync.t
39
36
  * });
40
37
  * ```
41
38
  */
42
- export function registerSyncTests(factory: storage.TestStorageFactory, options: { storageVersion?: number } = {}) {
39
+ export function registerSyncTests(
40
+ configOrFactory: storage.TestStorageConfig | storage.TestStorageFactory,
41
+ options: { storageVersion?: number; tableIdStrings?: boolean } = {}
42
+ ) {
43
+ const config: storage.TestStorageConfig =
44
+ typeof configOrFactory == 'function'
45
+ ? { factory: configOrFactory, tableIdStrings: options.tableIdStrings ?? true }
46
+ : configOrFactory;
47
+ const factory = config.factory;
43
48
  createCoreAPIMetrics(METRICS_HELPER.metricsEngine);
44
49
  const tracker = new sync.RequestTracker(METRICS_HELPER.metricsEngine);
45
50
  const syncContext = new sync.SyncContext({
@@ -65,31 +70,33 @@ export function registerSyncTests(factory: storage.TestStorageFactory, options:
65
70
  });
66
71
 
67
72
  const bucketStorage = f.getInstance(syncRules);
73
+ await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
74
+ const sourceTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
68
75
 
69
- const result = await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
70
- await batch.save({
71
- sourceTable: TEST_TABLE,
72
- tag: storage.SaveOperationTag.INSERT,
73
- after: {
74
- id: 't1',
75
- description: 'Test 1'
76
- },
77
- afterReplicaId: 't1'
78
- });
76
+ await writer.markAllSnapshotDone('0/1');
79
77
 
80
- await batch.save({
81
- sourceTable: TEST_TABLE,
82
- tag: storage.SaveOperationTag.INSERT,
83
- after: {
84
- id: 't2',
85
- description: 'Test 2'
86
- },
87
- afterReplicaId: 't2'
88
- });
78
+ await writer.save({
79
+ sourceTable,
80
+ tag: storage.SaveOperationTag.INSERT,
81
+ after: {
82
+ id: 't1',
83
+ description: 'Test 1'
84
+ },
85
+ afterReplicaId: 't1'
86
+ });
89
87
 
90
- await batch.commit('0/1');
88
+ await writer.save({
89
+ sourceTable,
90
+ tag: storage.SaveOperationTag.INSERT,
91
+ after: {
92
+ id: 't2',
93
+ description: 'Test 2'
94
+ },
95
+ afterReplicaId: 't2'
91
96
  });
92
97
 
98
+ await writer.commit('0/1');
99
+
93
100
  const stream = sync.streamResponse({
94
101
  syncContext,
95
102
  bucketStorage: bucketStorage,
@@ -126,31 +133,32 @@ bucket_definitions:
126
133
  });
127
134
 
128
135
  const bucketStorage = f.getInstance(syncRules);
136
+ await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
137
+ const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
138
+
139
+ await writer.markAllSnapshotDone('0/1');
140
+ await writer.save({
141
+ sourceTable: testTable,
142
+ tag: storage.SaveOperationTag.INSERT,
143
+ after: {
144
+ id: 't1',
145
+ description: 'Test 1'
146
+ },
147
+ afterReplicaId: 't1'
148
+ });
129
149
 
130
- const result = await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
131
- await batch.save({
132
- sourceTable: TEST_TABLE,
133
- tag: storage.SaveOperationTag.INSERT,
134
- after: {
135
- id: 't1',
136
- description: 'Test 1'
137
- },
138
- afterReplicaId: 't1'
139
- });
140
-
141
- await batch.save({
142
- sourceTable: TEST_TABLE,
143
- tag: storage.SaveOperationTag.INSERT,
144
- after: {
145
- id: 'earlier',
146
- description: 'Test 2'
147
- },
148
- afterReplicaId: 'earlier'
149
- });
150
-
151
- await batch.commit('0/1');
150
+ await writer.save({
151
+ sourceTable: testTable,
152
+ tag: storage.SaveOperationTag.INSERT,
153
+ after: {
154
+ id: 'earlier',
155
+ description: 'Test 2'
156
+ },
157
+ afterReplicaId: 'earlier'
152
158
  });
153
159
 
160
+ await writer.commit('0/1');
161
+
154
162
  const stream = sync.streamResponse({
155
163
  syncContext,
156
164
  bucketStorage,
@@ -187,32 +195,33 @@ bucket_definitions:
187
195
  });
188
196
 
189
197
  const bucketStorage = f.getInstance(syncRules);
190
-
191
- await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
192
- // Initial data: Add one priority row and 10k low-priority rows.
193
- await batch.save({
194
- sourceTable: TEST_TABLE,
198
+ await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
199
+ const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
200
+
201
+ await writer.markAllSnapshotDone('0/1');
202
+ // Initial data: Add one priority row and 10k low-priority rows.
203
+ await writer.save({
204
+ sourceTable: testTable,
205
+ tag: storage.SaveOperationTag.INSERT,
206
+ after: {
207
+ id: 'highprio',
208
+ description: 'High priority row'
209
+ },
210
+ afterReplicaId: 'highprio'
211
+ });
212
+ for (let i = 0; i < 10_000; i++) {
213
+ await writer.save({
214
+ sourceTable: testTable,
195
215
  tag: storage.SaveOperationTag.INSERT,
196
216
  after: {
197
- id: 'highprio',
198
- description: 'High priority row'
217
+ id: `${i}`,
218
+ description: 'low prio'
199
219
  },
200
- afterReplicaId: 'highprio'
220
+ afterReplicaId: `${i}`
201
221
  });
202
- for (let i = 0; i < 10_000; i++) {
203
- await batch.save({
204
- sourceTable: TEST_TABLE,
205
- tag: storage.SaveOperationTag.INSERT,
206
- after: {
207
- id: `${i}`,
208
- description: 'low prio'
209
- },
210
- afterReplicaId: `${i}`
211
- });
212
- }
222
+ }
213
223
 
214
- await batch.commit('0/1');
215
- });
224
+ await writer.commit('0/1');
216
225
 
217
226
  const stream = sync.streamResponse({
218
227
  syncContext,
@@ -240,20 +249,18 @@ bucket_definitions:
240
249
  if (sentCheckpoints == 1) {
241
250
  // Save new data to interrupt the low-priority sync.
242
251
 
243
- await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
244
- // Add another high-priority row. This should interrupt the long-running low-priority sync.
245
- await batch.save({
246
- sourceTable: TEST_TABLE,
247
- tag: storage.SaveOperationTag.INSERT,
248
- after: {
249
- id: 'highprio2',
250
- description: 'Another high-priority row'
251
- },
252
- afterReplicaId: 'highprio2'
253
- });
254
-
255
- await batch.commit('0/2');
252
+ // Add another high-priority row. This should interrupt the long-running low-priority sync.
253
+ await writer.save({
254
+ sourceTable: testTable,
255
+ tag: storage.SaveOperationTag.INSERT,
256
+ after: {
257
+ id: 'highprio2',
258
+ description: 'Another high-priority row'
259
+ },
260
+ afterReplicaId: 'highprio2'
256
261
  });
262
+
263
+ await writer.commit('0/2');
257
264
  } else {
258
265
  // Low-priority sync from the first checkpoint was interrupted. This should not happen before
259
266
  // 1000 low-priority items were synchronized.
@@ -297,32 +304,33 @@ bucket_definitions:
297
304
  });
298
305
 
299
306
  const bucketStorage = f.getInstance(syncRules);
300
-
301
- await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
302
- // Initial data: Add one priority row and 10k low-priority rows.
303
- await batch.save({
304
- sourceTable: TEST_TABLE,
307
+ await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
308
+ const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
309
+
310
+ await writer.markAllSnapshotDone('0/1');
311
+ // Initial data: Add one priority row and 10k low-priority rows.
312
+ await writer.save({
313
+ sourceTable: testTable,
314
+ tag: storage.SaveOperationTag.INSERT,
315
+ after: {
316
+ id: 'highprio',
317
+ description: 'user_one'
318
+ },
319
+ afterReplicaId: 'highprio'
320
+ });
321
+ for (let i = 0; i < 10_000; i++) {
322
+ await writer.save({
323
+ sourceTable: testTable,
305
324
  tag: storage.SaveOperationTag.INSERT,
306
325
  after: {
307
- id: 'highprio',
308
- description: 'user_one'
326
+ id: `${i}`,
327
+ description: 'low prio'
309
328
  },
310
- afterReplicaId: 'highprio'
329
+ afterReplicaId: `${i}`
311
330
  });
312
- for (let i = 0; i < 10_000; i++) {
313
- await batch.save({
314
- sourceTable: TEST_TABLE,
315
- tag: storage.SaveOperationTag.INSERT,
316
- after: {
317
- id: `${i}`,
318
- description: 'low prio'
319
- },
320
- afterReplicaId: `${i}`
321
- });
322
- }
331
+ }
323
332
 
324
- await batch.commit('0/1');
325
- });
333
+ await writer.commit('0/1');
326
334
 
327
335
  const stream = sync.streamResponse({
328
336
  syncContext,
@@ -355,20 +363,18 @@ bucket_definitions:
355
363
  if (typeof next === 'object' && next !== null) {
356
364
  if ('partial_checkpoint_complete' in next) {
357
365
  if (sentCheckpoints == 1) {
358
- await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
359
- // Add a high-priority row that doesn't affect this sync stream.
360
- await batch.save({
361
- sourceTable: TEST_TABLE,
362
- tag: storage.SaveOperationTag.INSERT,
363
- after: {
364
- id: 'highprio2',
365
- description: 'user_two'
366
- },
367
- afterReplicaId: 'highprio2'
368
- });
369
-
370
- await batch.commit('0/2');
366
+ // Add a high-priority row that doesn't affect this sync stream.
367
+ await writer.save({
368
+ sourceTable: testTable,
369
+ tag: storage.SaveOperationTag.INSERT,
370
+ after: {
371
+ id: 'highprio2',
372
+ description: 'user_two'
373
+ },
374
+ afterReplicaId: 'highprio2'
371
375
  });
376
+
377
+ await writer.commit('0/2');
372
378
  } else {
373
379
  expect(sentCheckpoints).toBe(2);
374
380
  expect(sentRows).toBe(10002);
@@ -389,20 +395,18 @@ bucket_definitions:
389
395
  if (completedCheckpoints == 1) {
390
396
  expect(sentRows).toBe(10001);
391
397
 
392
- await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
393
- // Add a high-priority row that affects this sync stream.
394
- await batch.save({
395
- sourceTable: TEST_TABLE,
396
- tag: storage.SaveOperationTag.INSERT,
397
- after: {
398
- id: 'highprio3',
399
- description: 'user_one'
400
- },
401
- afterReplicaId: 'highprio3'
402
- });
403
-
404
- await batch.commit('0/3');
398
+ // Add a high-priority row that affects this sync stream.
399
+ await writer.save({
400
+ sourceTable: testTable,
401
+ tag: storage.SaveOperationTag.INSERT,
402
+ after: {
403
+ id: 'highprio3',
404
+ description: 'user_one'
405
+ },
406
+ afterReplicaId: 'highprio3'
405
407
  });
408
+
409
+ await writer.commit('0/3');
406
410
  }
407
411
  }
408
412
  }
@@ -438,32 +442,33 @@ bucket_definitions:
438
442
  });
439
443
 
440
444
  const bucketStorage = f.getInstance(syncRules);
441
-
442
- await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
443
- // Initial data: Add one priority row and 10k low-priority rows.
444
- await batch.save({
445
- sourceTable: TEST_TABLE,
445
+ await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
446
+ const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
447
+
448
+ await writer.markAllSnapshotDone('0/1');
449
+ // Initial data: Add one priority row and 10k low-priority rows.
450
+ await writer.save({
451
+ sourceTable: testTable,
452
+ tag: storage.SaveOperationTag.INSERT,
453
+ after: {
454
+ id: 'highprio',
455
+ description: 'High priority row'
456
+ },
457
+ afterReplicaId: 'highprio'
458
+ });
459
+ for (let i = 0; i < 2_000; i++) {
460
+ await writer.save({
461
+ sourceTable: testTable,
446
462
  tag: storage.SaveOperationTag.INSERT,
447
463
  after: {
448
- id: 'highprio',
449
- description: 'High priority row'
464
+ id: `${i}`,
465
+ description: 'low prio'
450
466
  },
451
- afterReplicaId: 'highprio'
467
+ afterReplicaId: `${i}`
452
468
  });
453
- for (let i = 0; i < 2_000; i++) {
454
- await batch.save({
455
- sourceTable: TEST_TABLE,
456
- tag: storage.SaveOperationTag.INSERT,
457
- after: {
458
- id: `${i}`,
459
- description: 'low prio'
460
- },
461
- afterReplicaId: `${i}`
462
- });
463
- }
469
+ }
464
470
 
465
- await batch.commit('0/1');
466
- });
471
+ await writer.commit('0/1');
467
472
 
468
473
  const stream = sync.streamResponse({
469
474
  syncContext,
@@ -500,31 +505,29 @@ bucket_definitions:
500
505
 
501
506
  if (sentRows == 1001) {
502
507
  // Save new data to interrupt the low-priority sync.
503
- await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
504
- // Add another high-priority row. This should interrupt the long-running low-priority sync.
505
- await batch.save({
506
- sourceTable: TEST_TABLE,
507
- tag: storage.SaveOperationTag.INSERT,
508
- after: {
509
- id: 'highprio2',
510
- description: 'Another high-priority row'
511
- },
512
- afterReplicaId: 'highprio2'
513
- });
514
-
515
- // Also add a low-priority row
516
- await batch.save({
517
- sourceTable: TEST_TABLE,
518
- tag: storage.SaveOperationTag.INSERT,
519
- after: {
520
- id: '2001',
521
- description: 'Another low-priority row'
522
- },
523
- afterReplicaId: '2001'
524
- });
525
-
526
- await batch.commit('0/2');
508
+ // Add another high-priority row. This should interrupt the long-running low-priority sync.
509
+ await writer.save({
510
+ sourceTable: testTable,
511
+ tag: storage.SaveOperationTag.INSERT,
512
+ after: {
513
+ id: 'highprio2',
514
+ description: 'Another high-priority row'
515
+ },
516
+ afterReplicaId: 'highprio2'
517
+ });
518
+
519
+ // Also add a low-priority row
520
+ await writer.save({
521
+ sourceTable: testTable,
522
+ tag: storage.SaveOperationTag.INSERT,
523
+ after: {
524
+ id: '2001',
525
+ description: 'Another low-priority row'
526
+ },
527
+ afterReplicaId: '2001'
527
528
  });
529
+
530
+ await writer.commit('0/2');
528
531
  }
529
532
 
530
533
  if (sentRows >= 1000 && sentRows <= 2001) {
@@ -567,19 +570,20 @@ bucket_definitions:
567
570
  content: BASIC_SYNC_RULES
568
571
  });
569
572
  const bucketStorage = f.getInstance(syncRules);
570
-
571
- await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
572
- await batch.save({
573
- sourceTable: TEST_TABLE,
574
- tag: storage.SaveOperationTag.INSERT,
575
- after: {
576
- id: 't1',
577
- description: 'sync'
578
- },
579
- afterReplicaId: 't1'
580
- });
581
- await batch.commit('0/1');
573
+ await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
574
+ const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
575
+
576
+ await writer.markAllSnapshotDone('0/1');
577
+ await writer.save({
578
+ sourceTable: testTable,
579
+ tag: storage.SaveOperationTag.INSERT,
580
+ after: {
581
+ id: 't1',
582
+ description: 'sync'
583
+ },
584
+ afterReplicaId: 't1'
582
585
  });
586
+ await writer.commit('0/1');
583
587
 
584
588
  const stream = sync.streamResponse({
585
589
  syncContext,
@@ -610,9 +614,7 @@ bucket_definitions:
610
614
  if (receivedCompletions == 1) {
611
615
  // Trigger an empty bucket update.
612
616
  await bucketStorage.createManagedWriteCheckpoint({ user_id: '', heads: { '1': '1/0' } });
613
- await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
614
- await batch.commit('1/0');
615
- });
617
+ await writer.commit('1/0');
616
618
  } else {
617
619
  break;
618
620
  }
@@ -624,29 +626,30 @@ bucket_definitions:
624
626
  });
625
627
 
626
628
  test('sync legacy non-raw data', async () => {
627
- const f = await factory();
629
+ await using f = await factory();
628
630
 
629
631
  const syncRules = await updateSyncRules(f, {
630
632
  content: BASIC_SYNC_RULES
631
633
  });
632
634
 
633
635
  const bucketStorage = await f.getInstance(syncRules);
634
-
635
- const result = await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
636
- await batch.save({
637
- sourceTable: TEST_TABLE,
638
- tag: storage.SaveOperationTag.INSERT,
639
- after: {
640
- id: 't1',
641
- description: 'Test\n"string"',
642
- large_num: 12345678901234567890n
643
- },
644
- afterReplicaId: 't1'
645
- });
646
-
647
- await batch.commit('0/1');
636
+ await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
637
+ const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
638
+
639
+ await writer.markAllSnapshotDone('0/1');
640
+ await writer.save({
641
+ sourceTable: testTable,
642
+ tag: storage.SaveOperationTag.INSERT,
643
+ after: {
644
+ id: 't1',
645
+ description: 'Test\n"string"',
646
+ large_num: 12345678901234567890n
647
+ },
648
+ afterReplicaId: 't1'
648
649
  });
649
650
 
651
+ await writer.commit('0/1');
652
+
650
653
  const stream = sync.streamResponse({
651
654
  syncContext,
652
655
  bucketStorage,
@@ -702,10 +705,11 @@ bucket_definitions:
702
705
  });
703
706
 
704
707
  const bucketStorage = await f.getInstance(syncRules);
708
+ await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
709
+ const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
705
710
  // Activate
706
- await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
707
- await batch.keepalive('0/0');
708
- });
711
+ await writer.markAllSnapshotDone('0/0');
712
+ await writer.keepalive('0/0');
709
713
 
710
714
  const stream = sync.streamResponse({
711
715
  syncContext,
@@ -727,36 +731,32 @@ bucket_definitions:
727
731
 
728
732
  expect(await getCheckpointLines(iter)).toMatchSnapshot();
729
733
 
730
- await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
731
- await batch.save({
732
- sourceTable: TEST_TABLE,
733
- tag: storage.SaveOperationTag.INSERT,
734
- after: {
735
- id: 't1',
736
- description: 'Test 1'
737
- },
738
- afterReplicaId: 't1'
739
- });
740
-
741
- await batch.commit('0/1');
734
+ await writer.save({
735
+ sourceTable: testTable,
736
+ tag: storage.SaveOperationTag.INSERT,
737
+ after: {
738
+ id: 't1',
739
+ description: 'Test 1'
740
+ },
741
+ afterReplicaId: 't1'
742
742
  });
743
743
 
744
- expect(await getCheckpointLines(iter)).toMatchSnapshot();
744
+ await writer.commit('0/1');
745
745
 
746
- await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
747
- await batch.save({
748
- sourceTable: TEST_TABLE,
749
- tag: storage.SaveOperationTag.INSERT,
750
- after: {
751
- id: 't2',
752
- description: 'Test 2'
753
- },
754
- afterReplicaId: 't2'
755
- });
746
+ expect(await getCheckpointLines(iter)).toMatchSnapshot();
756
747
 
757
- await batch.commit('0/2');
748
+ await writer.save({
749
+ sourceTable: testTable,
750
+ tag: storage.SaveOperationTag.INSERT,
751
+ after: {
752
+ id: 't2',
753
+ description: 'Test 2'
754
+ },
755
+ afterReplicaId: 't2'
758
756
  });
759
757
 
758
+ await writer.commit('0/2');
759
+
760
760
  expect(await getCheckpointLines(iter)).toMatchSnapshot();
761
761
  });
762
762
 
@@ -772,14 +772,13 @@ bucket_definitions:
772
772
  `
773
773
  });
774
774
 
775
- const usersTable = test_utils.makeTestTable('users', ['id']);
776
- const listsTable = test_utils.makeTestTable('lists', ['id']);
777
-
778
775
  const bucketStorage = await f.getInstance(syncRules);
776
+ await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
777
+ const usersTable = await test_utils.resolveTestTable(writer, 'users', ['id'], config, 1);
778
+
779
779
  // Activate
780
- await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
781
- await batch.keepalive('0/0');
782
- });
780
+ await writer.markAllSnapshotDone('0/0');
781
+ await writer.keepalive('0/0');
783
782
 
784
783
  const stream = sync.streamResponse({
785
784
  syncContext,
@@ -805,24 +804,24 @@ bucket_definitions:
805
804
  expect(checkpoint1).toMatchSnapshot();
806
805
 
807
806
  // Add user
808
- await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
809
- await batch.save({
810
- sourceTable: usersTable,
811
- tag: storage.SaveOperationTag.INSERT,
812
- after: {
813
- id: 'user1',
814
- name: 'User 1'
815
- },
816
- afterReplicaId: 'user1'
817
- });
818
-
819
- await batch.commit('0/1');
807
+ await writer.save({
808
+ sourceTable: usersTable,
809
+ tag: storage.SaveOperationTag.INSERT,
810
+ after: {
811
+ id: 'user1',
812
+ name: 'User 1'
813
+ },
814
+ afterReplicaId: 'user1'
820
815
  });
821
816
 
817
+ await writer.commit('0/1');
818
+
822
819
  const checkpoint2 = await getCheckpointLines(iter);
820
+
821
+ const { bucket } = test_utils.bucketRequest(syncRules, 'by_user["user1"]');
823
822
  expect(
824
823
  (checkpoint2[0] as StreamingSyncCheckpointDiff).checkpoint_diff?.updated_buckets?.map((b) => b.bucket)
825
- ).toEqual([bucketRequest(syncRules, 'by_user["user1"]')]);
824
+ ).toEqual([bucket]);
826
825
  expect(checkpoint2).toMatchSnapshot();
827
826
  });
828
827
 
@@ -838,25 +837,24 @@ bucket_definitions:
838
837
  `
839
838
  });
840
839
 
841
- const usersTable = test_utils.makeTestTable('users', ['id']);
842
- const listsTable = test_utils.makeTestTable('lists', ['id']);
843
-
844
840
  const bucketStorage = await f.getInstance(syncRules);
845
-
846
- await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
847
- await batch.save({
848
- sourceTable: usersTable,
849
- tag: storage.SaveOperationTag.INSERT,
850
- after: {
851
- id: 'user1',
852
- name: 'User 1'
853
- },
854
- afterReplicaId: 'user1'
855
- });
856
-
857
- await batch.commit('0/1');
841
+ await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
842
+ const usersTable = await test_utils.resolveTestTable(writer, 'users', ['id'], config, 1);
843
+ const listsTable = await test_utils.resolveTestTable(writer, 'lists', ['id'], config, 2);
844
+
845
+ await writer.markAllSnapshotDone('0/1');
846
+ await writer.save({
847
+ sourceTable: usersTable,
848
+ tag: storage.SaveOperationTag.INSERT,
849
+ after: {
850
+ id: 'user1',
851
+ name: 'User 1'
852
+ },
853
+ afterReplicaId: 'user1'
858
854
  });
859
855
 
856
+ await writer.commit('0/1');
857
+
860
858
  const stream = sync.streamResponse({
861
859
  syncContext,
862
860
  bucketStorage,
@@ -875,31 +873,29 @@ bucket_definitions:
875
873
  iter.return?.();
876
874
  });
877
875
 
876
+ const { bucket } = bucketRequest(syncRules, 'by_user["user1"]');
878
877
  const checkpoint1 = await getCheckpointLines(iter);
879
- expect((checkpoint1[0] as StreamingSyncCheckpoint).checkpoint?.buckets?.map((b) => b.bucket)).toEqual([
880
- bucketRequest(syncRules, 'by_user["user1"]')
881
- ]);
882
- expect(checkpoint1).toMatchSnapshot();
883
878
 
884
- await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
885
- await batch.save({
886
- sourceTable: listsTable,
887
- tag: storage.SaveOperationTag.INSERT,
888
- after: {
889
- id: 'list1',
890
- user_id: 'user1',
891
- name: 'User 1'
892
- },
893
- afterReplicaId: 'list1'
894
- });
879
+ expect((checkpoint1[0] as StreamingSyncCheckpoint).checkpoint?.buckets?.map((b) => b.bucket)).toEqual([bucket]);
880
+ expect(checkpoint1).toMatchSnapshot();
895
881
 
896
- await batch.commit('0/1');
882
+ await writer.save({
883
+ sourceTable: listsTable,
884
+ tag: storage.SaveOperationTag.INSERT,
885
+ after: {
886
+ id: 'list1',
887
+ user_id: 'user1',
888
+ name: 'User 1'
889
+ },
890
+ afterReplicaId: 'list1'
897
891
  });
898
892
 
893
+ await writer.commit('0/1');
894
+
899
895
  const checkpoint2 = await getCheckpointLines(iter);
900
896
  expect(
901
897
  (checkpoint2[0] as StreamingSyncCheckpointDiff).checkpoint_diff?.updated_buckets?.map((b) => b.bucket)
902
- ).toEqual([bucketRequest(syncRules, 'by_user["user1"]')]);
898
+ ).toEqual([bucket]);
903
899
  expect(checkpoint2).toMatchSnapshot();
904
900
  });
905
901
 
@@ -915,14 +911,13 @@ bucket_definitions:
915
911
  `
916
912
  });
917
913
 
918
- const usersTable = test_utils.makeTestTable('users', ['id']);
919
- const listsTable = test_utils.makeTestTable('lists', ['id']);
920
-
921
914
  const bucketStorage = await f.getInstance(syncRules);
915
+ await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
916
+ const usersTable = await test_utils.resolveTestTable(writer, 'users', ['id'], config, 1);
917
+ const listsTable = await test_utils.resolveTestTable(writer, 'lists', ['id'], config, 2);
922
918
  // Activate
923
- await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
924
- await batch.keepalive('0/0');
925
- });
919
+ await writer.markAllSnapshotDone('0/0');
920
+ await writer.keepalive('0/0');
926
921
 
927
922
  const stream = sync.streamResponse({
928
923
  syncContext,
@@ -945,35 +940,36 @@ bucket_definitions:
945
940
  // Initial empty checkpoint
946
941
  expect(await getCheckpointLines(iter)).toMatchSnapshot();
947
942
 
948
- await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
949
- await batch.save({
950
- sourceTable: listsTable,
951
- tag: storage.SaveOperationTag.INSERT,
952
- after: {
953
- id: 'list1',
954
- user_id: 'user1',
955
- name: 'User 1'
956
- },
957
- afterReplicaId: 'list1'
958
- });
959
-
960
- await batch.save({
961
- sourceTable: usersTable,
962
- tag: storage.SaveOperationTag.INSERT,
963
- after: {
964
- id: 'user1',
965
- name: 'User 1'
966
- },
967
- afterReplicaId: 'user1'
968
- });
943
+ await writer.markAllSnapshotDone('0/1');
944
+ await writer.save({
945
+ sourceTable: listsTable,
946
+ tag: storage.SaveOperationTag.INSERT,
947
+ after: {
948
+ id: 'list1',
949
+ user_id: 'user1',
950
+ name: 'User 1'
951
+ },
952
+ afterReplicaId: 'list1'
953
+ });
969
954
 
970
- await batch.commit('0/1');
955
+ await writer.save({
956
+ sourceTable: usersTable,
957
+ tag: storage.SaveOperationTag.INSERT,
958
+ after: {
959
+ id: 'user1',
960
+ name: 'User 1'
961
+ },
962
+ afterReplicaId: 'user1'
971
963
  });
972
964
 
965
+ await writer.commit('0/1');
966
+
967
+ const { bucket } = test_utils.bucketRequest(syncRules, 'by_user["user1"]');
968
+
973
969
  const checkpoint2 = await getCheckpointLines(iter);
974
970
  expect(
975
971
  (checkpoint2[0] as StreamingSyncCheckpointDiff).checkpoint_diff?.updated_buckets?.map((b) => b.bucket)
976
- ).toEqual([bucketRequest(syncRules, 'by_user["user1"]')]);
972
+ ).toEqual([bucket]);
977
973
  expect(checkpoint2).toMatchSnapshot();
978
974
  });
979
975
 
@@ -985,10 +981,10 @@ bucket_definitions:
985
981
  });
986
982
 
987
983
  const bucketStorage = await f.getInstance(syncRules);
984
+ await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
988
985
  // Activate
989
- await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
990
- await batch.keepalive('0/0');
991
- });
986
+ await writer.markAllSnapshotDone('0/0');
987
+ await writer.keepalive('0/0');
992
988
 
993
989
  const exp = Date.now() / 1000 + 0.1;
994
990
 
@@ -1030,31 +1026,32 @@ bucket_definitions:
1030
1026
  });
1031
1027
 
1032
1028
  const bucketStorage = await f.getInstance(syncRules);
1029
+ await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
1030
+ const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
1031
+
1032
+ await writer.markAllSnapshotDone('0/1');
1033
+ await writer.save({
1034
+ sourceTable: testTable,
1035
+ tag: storage.SaveOperationTag.INSERT,
1036
+ after: {
1037
+ id: 't1',
1038
+ description: 'Test 1'
1039
+ },
1040
+ afterReplicaId: 't1'
1041
+ });
1033
1042
 
1034
- await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
1035
- await batch.save({
1036
- sourceTable: TEST_TABLE,
1037
- tag: storage.SaveOperationTag.INSERT,
1038
- after: {
1039
- id: 't1',
1040
- description: 'Test 1'
1041
- },
1042
- afterReplicaId: 't1'
1043
- });
1044
-
1045
- await batch.save({
1046
- sourceTable: TEST_TABLE,
1047
- tag: storage.SaveOperationTag.INSERT,
1048
- after: {
1049
- id: 't2',
1050
- description: 'Test 2'
1051
- },
1052
- afterReplicaId: 't2'
1053
- });
1054
-
1055
- await batch.commit('0/1');
1043
+ await writer.save({
1044
+ sourceTable: testTable,
1045
+ tag: storage.SaveOperationTag.INSERT,
1046
+ after: {
1047
+ id: 't2',
1048
+ description: 'Test 2'
1049
+ },
1050
+ afterReplicaId: 't2'
1056
1051
  });
1057
1052
 
1053
+ await writer.commit('0/1');
1054
+
1058
1055
  const stream = sync.streamResponse({
1059
1056
  syncContext,
1060
1057
  bucketStorage,
@@ -1086,30 +1083,29 @@ bucket_definitions:
1086
1083
  // Now we save additional data AND compact before continuing.
1087
1084
  // This invalidates the checkpoint we've received above.
1088
1085
 
1089
- await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
1090
- await batch.save({
1091
- sourceTable: TEST_TABLE,
1092
- tag: storage.SaveOperationTag.UPDATE,
1093
- after: {
1094
- id: 't1',
1095
- description: 'Test 1b'
1096
- },
1097
- afterReplicaId: 't1'
1098
- });
1099
-
1100
- await batch.save({
1101
- sourceTable: TEST_TABLE,
1102
- tag: storage.SaveOperationTag.UPDATE,
1103
- after: {
1104
- id: 't2',
1105
- description: 'Test 2b'
1106
- },
1107
- afterReplicaId: 't2'
1108
- });
1086
+ await writer.markAllSnapshotDone('0/1');
1087
+ await writer.save({
1088
+ sourceTable: testTable,
1089
+ tag: storage.SaveOperationTag.UPDATE,
1090
+ after: {
1091
+ id: 't1',
1092
+ description: 'Test 1b'
1093
+ },
1094
+ afterReplicaId: 't1'
1095
+ });
1109
1096
 
1110
- await batch.commit('0/2');
1097
+ await writer.save({
1098
+ sourceTable: testTable,
1099
+ tag: storage.SaveOperationTag.UPDATE,
1100
+ after: {
1101
+ id: 't2',
1102
+ description: 'Test 2b'
1103
+ },
1104
+ afterReplicaId: 't2'
1111
1105
  });
1112
1106
 
1107
+ await writer.commit('0/2');
1108
+
1113
1109
  await bucketStorage.compact({
1114
1110
  minBucketChanges: 1,
1115
1111
  minChangeRatio: 0
@@ -1173,11 +1169,11 @@ bucket_definitions:
1173
1169
  });
1174
1170
 
1175
1171
  const bucketStorage = f.getInstance(syncRules);
1172
+ await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
1176
1173
 
1177
- await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
1178
- // <= the managed write checkpoint LSN below
1179
- await batch.commit('0/1');
1180
- });
1174
+ await writer.markAllSnapshotDone('0/1');
1175
+ // <= the managed write checkpoint LSN below
1176
+ await writer.commit('0/1');
1181
1177
 
1182
1178
  const checkpoint = await bucketStorage.createManagedWriteCheckpoint({
1183
1179
  user_id: 'test',
@@ -1209,10 +1205,9 @@ bucket_definitions:
1209
1205
  })
1210
1206
  });
1211
1207
 
1212
- await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
1213
- // must be >= the managed write checkpoint LSN
1214
- await batch.commit('1/0');
1215
- });
1208
+ await writer.markAllSnapshotDone('0/1');
1209
+ // must be >= the managed write checkpoint LSN
1210
+ await writer.commit('1/0');
1216
1211
 
1217
1212
  // At this point the LSN has advanced, so the write checkpoint should be
1218
1213
  // included in the next checkpoint message.
@@ -1226,9 +1221,12 @@ bucket_definitions:
1226
1221
  });
1227
1222
  });
1228
1223
 
1229
- test('encodes sync rules id in buckes for streams', async () => {
1224
+ test('encodes sync rules id in buckets for streams', async () => {
1230
1225
  await using f = await factory();
1231
- const rules = `
1226
+ // This test relies making an actual update to sync rules to test the different bucket names.
1227
+ // The actual naming scheme may change, as long as the two buckets have different names.
1228
+ const rules = [
1229
+ `
1232
1230
  streams:
1233
1231
  test:
1234
1232
  auto_subscribe: true
@@ -1236,26 +1234,38 @@ streams:
1236
1234
 
1237
1235
  config:
1238
1236
  edition: 2
1239
- `;
1237
+ `,
1238
+ `
1239
+ streams:
1240
+ test2:
1241
+ auto_subscribe: true
1242
+ query: SELECT * FROM test WHERE 1;
1243
+
1244
+ config:
1245
+ edition: 2
1246
+ `
1247
+ ];
1240
1248
 
1241
1249
  for (let i = 0; i < 2; i++) {
1242
1250
  const syncRules = await updateSyncRules(f, {
1243
- content: rules
1251
+ content: rules[i]
1244
1252
  });
1245
1253
  const bucketStorage = f.getInstance(syncRules);
1254
+ await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
1255
+
1256
+ const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config, i + 1);
1246
1257
 
1247
- await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
1248
- await batch.save({
1249
- sourceTable: TEST_TABLE,
1250
- tag: storage.SaveOperationTag.INSERT,
1251
- after: {
1252
- id: 't1',
1253
- description: 'Test 1'
1254
- },
1255
- afterReplicaId: 't1'
1256
- });
1257
- await batch.commit('0/1');
1258
+ await writer.markAllSnapshotDone('0/1');
1259
+ await writer.save({
1260
+ sourceTable: testTable,
1261
+ tag: storage.SaveOperationTag.INSERT,
1262
+ after: {
1263
+ id: 't1',
1264
+ description: 'Test 1'
1265
+ },
1266
+ afterReplicaId: 't1'
1258
1267
  });
1268
+ await writer.commit('0/1');
1259
1269
 
1260
1270
  const stream = sync.streamResponse({
1261
1271
  syncContext,