@powersync/service-core-tests 0.16.0 → 0.17.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 (36) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/test-utils/AbstractStreamTestContext.d.ts +6 -5
  3. package/dist/test-utils/AbstractStreamTestContext.js +26 -22
  4. package/dist/test-utils/AbstractStreamTestContext.js.map +1 -1
  5. package/dist/test-utils/StorageDataHelpers.d.ts +5 -5
  6. package/dist/test-utils/StorageDataHelpers.js.map +1 -1
  7. package/dist/test-utils/bucket-validation.d.ts +3 -3
  8. package/dist/test-utils/bucket-validation.js +3 -3
  9. package/dist/test-utils/general-utils.d.ts +13 -2
  10. package/dist/test-utils/general-utils.js +24 -3
  11. package/dist/test-utils/general-utils.js.map +1 -1
  12. package/dist/tests/register-compacting-tests.js +28 -21
  13. package/dist/tests/register-compacting-tests.js.map +1 -1
  14. package/dist/tests/register-data-storage-checkpoint-tests.js +8 -2
  15. package/dist/tests/register-data-storage-checkpoint-tests.js.map +1 -1
  16. package/dist/tests/register-data-storage-data-tests.js +148 -101
  17. package/dist/tests/register-data-storage-data-tests.js.map +1 -1
  18. package/dist/tests/register-data-storage-parameter-tests.js +44 -42
  19. package/dist/tests/register-data-storage-parameter-tests.js.map +1 -1
  20. package/dist/tests/register-sync-tests.js +141 -8
  21. package/dist/tests/register-sync-tests.js.map +1 -1
  22. package/dist/tests/util.d.ts +2 -2
  23. package/dist/tests/util.js.map +1 -1
  24. package/package.json +6 -7
  25. package/src/test-utils/AbstractStreamTestContext.ts +29 -24
  26. package/src/test-utils/StorageDataHelpers.ts +7 -6
  27. package/src/test-utils/bucket-validation.ts +3 -3
  28. package/src/test-utils/general-utils.ts +35 -7
  29. package/src/tests/register-compacting-tests.ts +45 -22
  30. package/src/tests/register-data-storage-checkpoint-tests.ts +12 -2
  31. package/src/tests/register-data-storage-data-tests.ts +210 -107
  32. package/src/tests/register-data-storage-parameter-tests.ts +58 -42
  33. package/src/tests/register-sync-tests.ts +159 -8
  34. package/src/tests/util.ts +2 -2
  35. package/tsconfig.json +1 -4
  36. package/tsconfig.tsbuildinfo +1 -1
@@ -26,7 +26,8 @@ export function registerDataStorageParameterTests(config: storage.TestStorageCon
26
26
 
27
27
  test('save and load parameters', async () => {
28
28
  await using factory = await generateStorageFactory();
29
- const syncRules = await factory.updateSyncRules(
29
+ const { stream: replicationStream, content: syncRules } = await test_utils.deploySyncRules(
30
+ factory,
30
31
  updateSyncRulesFromYaml(
31
32
  `
32
33
  bucket_definitions:
@@ -40,8 +41,8 @@ bucket_definitions:
40
41
  }
41
42
  )
42
43
  );
43
- const bucketStorage = factory.getInstance(syncRules);
44
- const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig();
44
+ const bucketStorage = factory.getInstance(replicationStream);
45
+ const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig;
45
46
 
46
47
  await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
47
48
  const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
@@ -92,7 +93,8 @@ bucket_definitions:
92
93
 
93
94
  test('it should use the latest version', async () => {
94
95
  await using factory = await generateStorageFactory();
95
- const syncRules = await factory.updateSyncRules(
96
+ const { stream: replicationStream, content: syncRules } = await test_utils.deploySyncRules(
97
+ factory,
96
98
  updateSyncRulesFromYaml(
97
99
  `
98
100
  bucket_definitions:
@@ -106,8 +108,8 @@ bucket_definitions:
106
108
  }
107
109
  )
108
110
  );
109
- const bucketStorage = factory.getInstance(syncRules);
110
- const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig();
111
+ const bucketStorage = factory.getInstance(replicationStream);
112
+ const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig;
111
113
 
112
114
  await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
113
115
  const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
@@ -163,7 +165,8 @@ bucket_definitions:
163
165
 
164
166
  test('it should use the latest version after updates', async () => {
165
167
  await using factory = await generateStorageFactory();
166
- const syncRules = await factory.updateSyncRules(
168
+ const { stream: replicationStream, content: syncRules } = await test_utils.deploySyncRules(
169
+ factory,
167
170
  updateSyncRulesFromYaml(
168
171
  `
169
172
  bucket_definitions:
@@ -177,8 +180,8 @@ bucket_definitions:
177
180
  { storageVersion }
178
181
  )
179
182
  );
180
- const bucketStorage = factory.getInstance(syncRules);
181
- const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig();
183
+ const bucketStorage = factory.getInstance(replicationStream);
184
+ const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig;
182
185
 
183
186
  await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
184
187
  const table = await test_utils.resolveTestTable(writer, 'todos', ['id', 'list_id'], config);
@@ -250,7 +253,8 @@ bucket_definitions:
250
253
 
251
254
  test('save and load parameters with different number types', async () => {
252
255
  await using factory = await generateStorageFactory();
253
- const syncRules = await factory.updateSyncRules(
256
+ const { stream: replicationStream, content: syncRules } = await test_utils.deploySyncRules(
257
+ factory,
254
258
  updateSyncRulesFromYaml(
255
259
  `
256
260
  bucket_definitions:
@@ -264,8 +268,8 @@ bucket_definitions:
264
268
  }
265
269
  )
266
270
  );
267
- const bucketStorage = factory.getInstance(syncRules);
268
- const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig();
271
+ const bucketStorage = factory.getInstance(replicationStream);
272
+ const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig;
269
273
 
270
274
  await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
271
275
  const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
@@ -318,7 +322,8 @@ bucket_definitions:
318
322
  // test this to ensure correct deserialization.
319
323
 
320
324
  await using factory = await generateStorageFactory();
321
- const syncRules = await factory.updateSyncRules(
325
+ const { stream: replicationStream, content: syncRules } = await test_utils.deploySyncRules(
326
+ factory,
322
327
  updateSyncRulesFromYaml(
323
328
  `
324
329
  bucket_definitions:
@@ -332,8 +337,8 @@ bucket_definitions:
332
337
  }
333
338
  )
334
339
  );
335
- const bucketStorage = factory.getInstance(syncRules);
336
- const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig();
340
+ const bucketStorage = factory.getInstance(replicationStream);
341
+ const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig;
337
342
 
338
343
  await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
339
344
  const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
@@ -385,7 +390,8 @@ bucket_definitions:
385
390
 
386
391
  test('save and load parameters with workspaceId', async () => {
387
392
  await using factory = await generateStorageFactory();
388
- const syncRules = await factory.updateSyncRules(
393
+ const { stream: replicationStream, content: syncRules } = await test_utils.deploySyncRules(
394
+ factory,
389
395
  updateSyncRulesFromYaml(
390
396
  `
391
397
  bucket_definitions:
@@ -400,8 +406,8 @@ bucket_definitions:
400
406
  }
401
407
  )
402
408
  );
403
- const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig();
404
- const bucketStorage = factory.getInstance(syncRules);
409
+ const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig;
410
+ const bucketStorage = factory.getInstance(replicationStream);
405
411
 
406
412
  await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
407
413
  const workspaceTable = await test_utils.resolveTestTable(writer, 'workspace', ['id'], config);
@@ -443,7 +449,8 @@ bucket_definitions:
443
449
 
444
450
  test('save and load parameters with dynamic global buckets', async () => {
445
451
  await using factory = await generateStorageFactory();
446
- const syncRules = await factory.updateSyncRules(
452
+ const { stream: replicationStream, content: syncRules } = await test_utils.deploySyncRules(
453
+ factory,
447
454
  updateSyncRulesFromYaml(
448
455
  `
449
456
  bucket_definitions:
@@ -458,8 +465,8 @@ bucket_definitions:
458
465
  }
459
466
  )
460
467
  );
461
- const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig();
462
- const bucketStorage = factory.getInstance(syncRules);
468
+ const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig;
469
+ const bucketStorage = factory.getInstance(replicationStream);
463
470
 
464
471
  await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
465
472
  const workspaceTable = await test_utils.resolveTestTable(writer, 'workspace', undefined, config);
@@ -535,7 +542,8 @@ bucket_definitions:
535
542
 
536
543
  test('multiple parameter queries', async () => {
537
544
  await using factory = await generateStorageFactory();
538
- const syncRules = await factory.updateSyncRules(
545
+ const { stream: replicationStream, content: syncRules } = await test_utils.deploySyncRules(
546
+ factory,
539
547
  updateSyncRulesFromYaml(
540
548
  `
541
549
  bucket_definitions:
@@ -552,8 +560,8 @@ bucket_definitions:
552
560
  }
553
561
  )
554
562
  );
555
- const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig();
556
- const bucketStorage = factory.getInstance(syncRules);
563
+ const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig;
564
+ const bucketStorage = factory.getInstance(replicationStream);
557
565
 
558
566
  await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
559
567
  const workspaceTable = await test_utils.resolveTestTable(writer, 'workspace', undefined, config);
@@ -639,7 +647,8 @@ bucket_definitions:
639
647
 
640
648
  test('truncate parameters', async () => {
641
649
  await using factory = await generateStorageFactory();
642
- const syncRules = await factory.updateSyncRules(
650
+ const { stream: replicationStream, content: syncRules } = await test_utils.deploySyncRules(
651
+ factory,
643
652
  updateSyncRulesFromYaml(
644
653
  `
645
654
  bucket_definitions:
@@ -653,8 +662,8 @@ bucket_definitions:
653
662
  }
654
663
  )
655
664
  );
656
- const bucketStorage = factory.getInstance(syncRules);
657
- const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig();
665
+ const bucketStorage = factory.getInstance(replicationStream);
666
+ const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig;
658
667
 
659
668
  await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
660
669
  const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
@@ -732,7 +741,8 @@ bucket_definitions:
732
741
 
733
742
  test('sync streams smoke test', async () => {
734
743
  await using factory = await generateStorageFactory();
735
- const syncRules = await factory.updateSyncRules(
744
+ const { stream: replicationStream, content: syncRules } = await test_utils.deploySyncRules(
745
+ factory,
736
746
  updateSyncRulesFromYaml(`
737
747
  config:
738
748
  edition: 3
@@ -744,8 +754,8 @@ streams:
744
754
  WHERE data.foo = param.bar AND param.baz = auth.user_id()
745
755
  `)
746
756
  );
747
- const bucketStorage = factory.getInstance(syncRules);
748
- const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig();
757
+ const bucketStorage = factory.getInstance(replicationStream);
758
+ const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig;
749
759
 
750
760
  await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
751
761
  const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
@@ -799,7 +809,8 @@ streams:
799
809
 
800
810
  test('respects parameter limit', async () => {
801
811
  await using factory = await generateStorageFactory();
802
- const syncRules = await factory.updateSyncRules(
812
+ const { stream: replicationStream, content: syncRules } = await test_utils.deploySyncRules(
813
+ factory,
803
814
  updateSyncRulesFromYaml(
804
815
  `
805
816
  config:
@@ -815,8 +826,8 @@ streams:
815
826
  }
816
827
  )
817
828
  );
818
- const bucketStorage = factory.getInstance(syncRules);
819
- const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig();
829
+ const bucketStorage = factory.getInstance(replicationStream);
830
+ const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig;
820
831
 
821
832
  await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
822
833
  const testTable = await test_utils.resolveTestTable(writer, 'b', ['id'], config);
@@ -851,7 +862,8 @@ streams:
851
862
 
852
863
  test('sync streams store multiple parameter outputs for a single source row and lookup', async () => {
853
864
  await using factory = await generateStorageFactory();
854
- const syncRules = await factory.updateSyncRules(
865
+ const { stream: replicationStream, content: syncRules } = await test_utils.deploySyncRules(
866
+ factory,
855
867
  updateSyncRulesFromYaml(`
856
868
  config:
857
869
  edition: 3
@@ -864,8 +876,8 @@ streams:
864
876
  WHERE a.x = x.value AND y.value = auth.user_id()
865
877
  `)
866
878
  );
867
- const bucketStorage = factory.getInstance(syncRules);
868
- const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig();
879
+ const bucketStorage = factory.getInstance(replicationStream);
880
+ const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig;
869
881
 
870
882
  await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
871
883
  const tableB = await test_utils.resolveTestTable(writer, 'b', ['id'], config);
@@ -979,7 +991,8 @@ streams:
979
991
 
980
992
  test('can request multiple lookups at once', async () => {
981
993
  await using factory = await generateStorageFactory();
982
- const syncRules = await factory.updateSyncRules(
994
+ const { stream: replicationStream, content: syncRules } = await test_utils.deploySyncRules(
995
+ factory,
983
996
  updateSyncRulesFromYaml(`
984
997
  config:
985
998
  edition: 3
@@ -992,10 +1005,12 @@ streams:
992
1005
  query: SELECT * FROM b WHERE p IN (SELECT id FROM param_b WHERE u = auth.user_id())
993
1006
  `)
994
1007
  );
995
- const bucketStorage = factory.getInstance(syncRules);
1008
+ const bucketStorage = factory.getInstance(replicationStream);
996
1009
  const parsedSyncRules = syncRules.parsed(test_utils.PARSE_OPTIONS);
997
1010
  const hydrationState = parsedSyncRules.hydrationState;
998
- const syncConfig = parsedSyncRules.syncConfigWithErrors.config;
1011
+ const [parsedSyncConfig] = parsedSyncRules.syncConfigs;
1012
+ expect(parsedSyncConfig).toBeDefined();
1013
+ const syncConfig = parsedSyncConfig.config;
999
1014
 
1000
1015
  await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
1001
1016
  const paramATable = await test_utils.resolveTestTable(writer, 'param_a', ['id'], config, 1);
@@ -1069,7 +1084,8 @@ streams:
1069
1084
 
1070
1085
  test('sync streams preserve duplicate downstream lookups with different provenance', async () => {
1071
1086
  await using factory = await generateStorageFactory();
1072
- const syncRules = await factory.updateSyncRules(
1087
+ const { stream: replicationStream, content: syncRules } = await test_utils.deploySyncRules(
1088
+ factory,
1073
1089
  updateSyncRulesFromYaml(
1074
1090
  `
1075
1091
  config:
@@ -1090,8 +1106,8 @@ streams:
1090
1106
  }
1091
1107
  )
1092
1108
  );
1093
- const bucketStorage = factory.getInstance(syncRules);
1094
- const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig();
1109
+ const bucketStorage = factory.getInstance(replicationStream);
1110
+ const sync_rules = syncRules.parsed(test_utils.PARSE_OPTIONS).hydratedSyncConfig;
1095
1111
 
1096
1112
  await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
1097
1113
  const tableB = await test_utils.resolveTestTable(writer, 'b', ['id'], config, 1);
@@ -687,7 +687,7 @@ bucket_definitions:
687
687
  receivedCompletions++;
688
688
  if (receivedCompletions == 1) {
689
689
  // Trigger an empty bucket update.
690
- await bucketStorage.createManagedWriteCheckpoint({ user_id: '', heads: { '1': '1/0' } });
690
+ await bucketStorage.createManagedWriteCheckpoints([{ user_id: '', heads: { '1': '1/0' } }]);
691
691
  await writer.commit('1/0');
692
692
  } else {
693
693
  break;
@@ -892,7 +892,8 @@ bucket_definitions:
892
892
 
893
893
  const checkpoint2 = await getCheckpointLines(iter);
894
894
 
895
- const { bucket } = test_utils.bucketRequest(syncRules, 'by_user["user1"]');
895
+ const syncRulesContent = syncRules.syncConfigContent[0];
896
+ const { bucket } = test_utils.bucketRequest(syncRulesContent, 'by_user["user1"]');
896
897
  expect(
897
898
  (checkpoint2[0] as StreamingSyncCheckpointDiff).checkpoint_diff?.updated_buckets?.map((b) => b.bucket)
898
899
  ).toEqual([bucket]);
@@ -947,7 +948,8 @@ bucket_definitions:
947
948
  iter.return?.();
948
949
  });
949
950
 
950
- const { bucket } = bucketRequest(syncRules, 'by_user["user1"]');
951
+ const syncRulesContent = syncRules.syncConfigContent[0];
952
+ const { bucket } = bucketRequest(syncRulesContent, 'by_user["user1"]');
951
953
  const checkpoint1 = await getCheckpointLines(iter);
952
954
 
953
955
  expect((checkpoint1[0] as StreamingSyncCheckpoint).checkpoint?.buckets?.map((b) => b.bucket)).toEqual([bucket]);
@@ -1038,7 +1040,8 @@ bucket_definitions:
1038
1040
 
1039
1041
  await writer.commit('0/1');
1040
1042
 
1041
- const { bucket } = test_utils.bucketRequest(syncRules, 'by_user["user1"]');
1043
+ const syncRulesContent = syncRules.syncConfigContent[0];
1044
+ const { bucket } = test_utils.bucketRequest(syncRulesContent, 'by_user["user1"]');
1042
1045
 
1043
1046
  const checkpoint2 = await getCheckpointLines(iter);
1044
1047
  expect(
@@ -1235,6 +1238,150 @@ bucket_definitions:
1235
1238
  });
1236
1239
  });
1237
1240
 
1241
+ test('compacting high-priority data invalidates checkpoint before lower priorities', async (context) => {
1242
+ // Scenario:
1243
+ // 1. Bucket priorities are used.
1244
+ // 2. Client syncs a high-priority bucket.
1245
+ // 3. Due to a concurrent compaction, the checkpoint is invalidated (via target_op).
1246
+ // 4. The service picks up that invalidation, and the batch stops, without emitting a "partial_checkpoint_complete". The intention is continuing with the next checkpoint.
1247
+ // 5. However, the loop handling priorities does not see that invalidation, it only sees "bucket data for this priority is done".
1248
+ // 6. The priority loop incorrectly continues with the next priority.
1249
+ // 7. The service emits the data, as well as a final checkpoint_complete.
1250
+ // 8. The client now received a checkpoint_complete without the full data for that checkpoint. It would pick up the missing data in the checksum check, requiring a full re-download of the affected buckets.
1251
+ await using f = await factory();
1252
+
1253
+ const syncRules = await updateSyncRules(f, {
1254
+ content: `
1255
+ bucket_definitions:
1256
+ high_priority:
1257
+ priority: 1
1258
+ data:
1259
+ - SELECT * FROM test WHERE substring(id, 1, 4) = 'high';
1260
+ low_priority:
1261
+ priority: 2
1262
+ data:
1263
+ - SELECT * FROM test WHERE id = 'low';
1264
+ `
1265
+ });
1266
+
1267
+ const bucketStorage = await f.getInstance(syncRules);
1268
+ await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
1269
+ const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
1270
+
1271
+ await writer.markAllSnapshotDone('0/1');
1272
+ await writer.save({
1273
+ sourceTable: testTable,
1274
+ tag: storage.SaveOperationTag.INSERT,
1275
+ after: {
1276
+ id: 'high1',
1277
+ description: 'High priority 1'
1278
+ },
1279
+ afterReplicaId: 'high1'
1280
+ });
1281
+ await writer.save({
1282
+ sourceTable: testTable,
1283
+ tag: storage.SaveOperationTag.INSERT,
1284
+ after: {
1285
+ id: 'high2',
1286
+ description: 'High priority 2'
1287
+ },
1288
+ afterReplicaId: 'high2'
1289
+ });
1290
+ await writer.save({
1291
+ sourceTable: testTable,
1292
+ tag: storage.SaveOperationTag.INSERT,
1293
+ after: {
1294
+ id: 'low',
1295
+ description: 'Low priority'
1296
+ },
1297
+ afterReplicaId: 'low'
1298
+ });
1299
+ await writer.commit('0/1');
1300
+
1301
+ const stream = sync.streamResponse({
1302
+ syncContext,
1303
+ bucketStorage,
1304
+ syncRules: bucketStorage.getParsedSyncRules(test_utils.PARSE_OPTIONS),
1305
+ params: {
1306
+ buckets: [],
1307
+ include_checksum: true,
1308
+ raw_data: true
1309
+ },
1310
+ tracker,
1311
+ token: new JwtPayload({ sub: '', exp: Date.now() / 1000 + 10 }),
1312
+ isEncodingAsBson: false
1313
+ });
1314
+
1315
+ const iter = stream[Symbol.asyncIterator]();
1316
+ await using _ = {
1317
+ async [Symbol.asyncDispose]() {
1318
+ await iter.return?.();
1319
+ }
1320
+ };
1321
+
1322
+ const checkpoint = await consumeIterator(iter, {
1323
+ consume: false,
1324
+ isDone: (line) => (line as any)?.checkpoint != null
1325
+ });
1326
+ expect(checkpoint[0]).toEqual({
1327
+ checkpoint: expect.objectContaining({
1328
+ last_op_id: '3'
1329
+ })
1330
+ });
1331
+
1332
+ await writer.save({
1333
+ sourceTable: testTable,
1334
+ tag: storage.SaveOperationTag.UPDATE,
1335
+ after: {
1336
+ id: 'high1',
1337
+ description: 'Updated high priority 1'
1338
+ },
1339
+ afterReplicaId: 'high1'
1340
+ });
1341
+ await writer.save({
1342
+ sourceTable: testTable,
1343
+ tag: storage.SaveOperationTag.UPDATE,
1344
+ after: {
1345
+ id: 'high2',
1346
+ description: 'Updated high priority 2'
1347
+ },
1348
+ afterReplicaId: 'high2'
1349
+ });
1350
+ await writer.commit('0/2');
1351
+
1352
+ await bucketStorage.compact({
1353
+ minBucketChanges: 1,
1354
+ minChangeRatio: 0
1355
+ });
1356
+
1357
+ const lines = await getCheckpointLines(iter, { consume: true });
1358
+ const nextCheckpointIndex = lines.findIndex((line) => (line as any)?.checkpoint_diff != null);
1359
+ expect(nextCheckpointIndex).toBeGreaterThan(0);
1360
+
1361
+ const invalidatedCheckpointLines = lines.slice(0, nextCheckpointIndex);
1362
+ expect(invalidatedCheckpointLines).not.toContainEqual(
1363
+ expect.objectContaining({ checkpoint_complete: expect.anything() })
1364
+ );
1365
+ expect(invalidatedCheckpointLines).not.toContainEqual(
1366
+ expect.objectContaining({
1367
+ data: expect.objectContaining({
1368
+ bucket: expect.stringContaining('low_priority')
1369
+ })
1370
+ })
1371
+ );
1372
+
1373
+ expect(lines[nextCheckpointIndex]).toEqual({
1374
+ checkpoint_diff: expect.objectContaining({
1375
+ last_op_id: '5'
1376
+ })
1377
+ });
1378
+ expect(lines.at(-1)).toEqual({
1379
+ checkpoint_complete: expect.objectContaining({
1380
+ last_op_id: '5'
1381
+ })
1382
+ });
1383
+ });
1384
+
1238
1385
  test('write checkpoint', async () => {
1239
1386
  await using f = await factory();
1240
1387
 
@@ -1249,10 +1396,14 @@ bucket_definitions:
1249
1396
  // <= the managed write checkpoint LSN below
1250
1397
  await writer.commit('0/1');
1251
1398
 
1252
- const checkpoint = await bucketStorage.createManagedWriteCheckpoint({
1253
- user_id: 'test',
1254
- heads: { '1': '1/0' }
1255
- });
1399
+ const checkpoint = (
1400
+ await bucketStorage.createManagedWriteCheckpoints([
1401
+ {
1402
+ user_id: 'test',
1403
+ heads: { '1': '1/0' }
1404
+ }
1405
+ ])
1406
+ ).get('test')!;
1256
1407
 
1257
1408
  const params: sync.SyncStreamParameters = {
1258
1409
  syncContext,
package/src/tests/util.ts CHANGED
@@ -9,14 +9,14 @@ import {
9
9
  import { bucketRequest } from '../test-utils/general-utils.js';
10
10
 
11
11
  export function bucketRequestMap(
12
- syncRules: storage.PersistedSyncRulesContent,
12
+ syncRules: storage.PersistedSyncConfigContent,
13
13
  buckets: Iterable<readonly [string, bigint]>
14
14
  ): storage.BucketDataRequest[] {
15
15
  return Array.from(buckets, ([bucketName, opId]) => bucketRequest(syncRules, bucketName, opId));
16
16
  }
17
17
 
18
18
  export function bucketRequests(
19
- syncRules: storage.PersistedSyncRulesContent,
19
+ syncRules: storage.PersistedSyncConfigContent,
20
20
  bucketNames: string[]
21
21
  ): storage.BucketChecksumRequest[] {
22
22
  return bucketNames.map((bucketName) => {
package/tsconfig.json CHANGED
@@ -2,10 +2,7 @@
2
2
  "extends": "../../tsconfig.base.json",
3
3
  "compilerOptions": {
4
4
  "rootDir": "src",
5
- "outDir": "dist",
6
- "esModuleInterop": true,
7
- "skipLibCheck": true,
8
- "sourceMap": true
5
+ "outDir": "dist"
9
6
  },
10
7
  "include": ["src"],
11
8
  "references": [