@powersync/service-core-tests 0.17.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 (29) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/test-utils/AbstractStreamTestContext.d.ts +3 -3
  3. package/dist/test-utils/AbstractStreamTestContext.js +1 -1
  4. package/dist/test-utils/AbstractStreamTestContext.js.map +1 -1
  5. package/dist/test-utils/StorageDataHelpers.d.ts +3 -3
  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 +1 -0
  10. package/dist/test-utils/general-utils.js +9 -0
  11. package/dist/test-utils/general-utils.js.map +1 -1
  12. package/dist/tests/register-compacting-tests.js +16 -16
  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 +46 -28
  17. package/dist/tests/register-data-storage-data-tests.js.map +1 -1
  18. package/dist/tests/register-sync-tests.js +135 -5
  19. package/dist/tests/register-sync-tests.js.map +1 -1
  20. package/package.json +5 -5
  21. package/src/test-utils/AbstractStreamTestContext.ts +3 -2
  22. package/src/test-utils/StorageDataHelpers.ts +3 -2
  23. package/src/test-utils/bucket-validation.ts +3 -3
  24. package/src/test-utils/general-utils.ts +10 -0
  25. package/src/tests/register-compacting-tests.ts +32 -16
  26. package/src/tests/register-data-storage-checkpoint-tests.ts +12 -2
  27. package/src/tests/register-data-storage-data-tests.ts +82 -32
  28. package/src/tests/register-sync-tests.ts +153 -5
  29. package/tsconfig.tsbuildinfo +1 -1
@@ -75,7 +75,9 @@ bucket_definitions:
75
75
  const { checkpoint } = await bucketStorage.getCheckpoint();
76
76
 
77
77
  const request = bucketRequest(syncRules, 'global[]');
78
- const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, [request]));
78
+ const batch = await test_utils.fromAsync(
79
+ bucketStorage.getBucketDataBatch(test_utils.testCheckpoint(checkpoint), [request])
80
+ );
79
81
  const data = batch[0].chunkData.data.map((d) => {
80
82
  return {
81
83
  op: d.op,
@@ -92,7 +94,9 @@ bucket_definitions:
92
94
  { op: 'REMOVE', object_id: 'test1', checksum: c2 }
93
95
  ]);
94
96
 
95
- const checksums = [...(await bucketStorage.getChecksums(checkpoint, [request])).values()];
97
+ const checksums = [
98
+ ...(await bucketStorage.getChecksums(test_utils.testCheckpoint(checkpoint), [request])).values()
99
+ ];
96
100
  expect(checksums).toEqual([
97
101
  {
98
102
  bucket: request.bucket,
@@ -143,7 +147,9 @@ bucket_definitions:
143
147
  const { checkpoint } = await bucketStorage.getCheckpoint();
144
148
 
145
149
  const request = bucketRequest(syncRules, 'global[]');
146
- const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, [request]));
150
+ const batch = await test_utils.fromAsync(
151
+ bucketStorage.getBucketDataBatch(test_utils.testCheckpoint(checkpoint), [request])
152
+ );
147
153
  const data = batch[0].chunkData.data.map((d) => {
148
154
  return {
149
155
  op: d.op,
@@ -156,7 +162,9 @@ bucket_definitions:
156
162
 
157
163
  expect(data).toEqual([{ op: 'PUT', object_id: 'test1', checksum: c1 }]);
158
164
 
159
- const checksums = [...(await bucketStorage.getChecksums(checkpoint, [request])).values()];
165
+ const checksums = [
166
+ ...(await bucketStorage.getChecksums(test_utils.testCheckpoint(checkpoint), [request])).values()
167
+ ];
160
168
  expect(checksums).toEqual([
161
169
  {
162
170
  bucket: request.bucket,
@@ -212,7 +220,9 @@ bucket_definitions:
212
220
  const { checkpoint } = await bucketStorage.getCheckpoint();
213
221
 
214
222
  const request = bucketRequest(syncRules, 'global[]');
215
- const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, [request]));
223
+ const batch = await test_utils.fromAsync(
224
+ bucketStorage.getBucketDataBatch(test_utils.testCheckpoint(checkpoint), [request])
225
+ );
216
226
  const data = batch[0].chunkData.data.map((d) => {
217
227
  return {
218
228
  op: d.op,
@@ -225,7 +235,9 @@ bucket_definitions:
225
235
 
226
236
  expect(data).toEqual([{ op: 'PUT', object_id: 'test1', checksum: c1 }]);
227
237
 
228
- const checksums = [...(await bucketStorage.getChecksums(checkpoint, [request])).values()];
238
+ const checksums = [
239
+ ...(await bucketStorage.getChecksums(test_utils.testCheckpoint(checkpoint), [request])).values()
240
+ ];
229
241
  expect(checksums).toEqual([
230
242
  {
231
243
  bucket: request.bucket,
@@ -275,7 +287,9 @@ bucket_definitions:
275
287
  const { checkpoint } = await bucketStorage.getCheckpoint();
276
288
 
277
289
  const request = bucketRequest(syncRules, 'global[]');
278
- const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, [request]));
290
+ const batch = await test_utils.fromAsync(
291
+ bucketStorage.getBucketDataBatch(test_utils.testCheckpoint(checkpoint), [request])
292
+ );
279
293
  const data = batch[0].chunkData.data.map((d) => {
280
294
  return {
281
295
  op: d.op,
@@ -288,7 +302,9 @@ bucket_definitions:
288
302
 
289
303
  expect(data).toEqual([{ op: 'PUT', object_id: 'test1', checksum: c1 }]);
290
304
 
291
- const checksums = [...(await bucketStorage.getChecksums(checkpoint, [request])).values()];
305
+ const checksums = [
306
+ ...(await bucketStorage.getChecksums(test_utils.testCheckpoint(checkpoint), [request])).values()
307
+ ];
292
308
  expect(checksums).toEqual([
293
309
  {
294
310
  bucket: request.bucket,
@@ -362,7 +378,9 @@ bucket_definitions:
362
378
  const { checkpoint } = await bucketStorage.getCheckpoint();
363
379
 
364
380
  const request = bucketRequest(syncRules, 'global[]');
365
- const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, [request]));
381
+ const batch = await test_utils.fromAsync(
382
+ bucketStorage.getBucketDataBatch(test_utils.testCheckpoint(checkpoint), [request])
383
+ );
366
384
 
367
385
  expect(reduceBucket(batch[0].chunkData.data).slice(1)).toEqual([]);
368
386
 
@@ -432,7 +450,7 @@ bucket_definitions:
432
450
  await writer.commit('1/1');
433
451
  const { checkpoint } = await bucketStorage.getCheckpoint();
434
452
  const batch = await test_utils.fromAsync(
435
- bucketStorage.getBucketDataBatch(checkpoint, [bucketRequest(syncRules, 'global[]')])
453
+ bucketStorage.getBucketDataBatch(test_utils.testCheckpoint(checkpoint), [bucketRequest(syncRules, 'global[]')])
436
454
  );
437
455
  const data = batch[0].chunkData.data.map((d) => {
438
456
  return {
@@ -499,7 +517,9 @@ bucket_definitions:
499
517
  const { checkpoint } = await bucketStorage.getCheckpoint();
500
518
 
501
519
  const request = bucketRequest(syncRules, 'global[]');
502
- const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, [request]));
520
+ const batch = await test_utils.fromAsync(
521
+ bucketStorage.getBucketDataBatch(test_utils.testCheckpoint(checkpoint), [request])
522
+ );
503
523
  const data = batch[0].chunkData.data.map((d) => {
504
524
  return {
505
525
  op: d.op,
@@ -516,7 +536,9 @@ bucket_definitions:
516
536
  { op: 'REMOVE', object_id: 'test1', checksum: c2 }
517
537
  ]);
518
538
 
519
- const checksums = [...(await bucketStorage.getChecksums(checkpoint, [request])).values()];
539
+ const checksums = [
540
+ ...(await bucketStorage.getChecksums(test_utils.testCheckpoint(checkpoint), [request])).values()
541
+ ];
520
542
  expect(checksums).toEqual([
521
543
  {
522
544
  bucket: bucketRequest(syncRules, 'global[]').bucket,
@@ -619,7 +641,9 @@ bucket_definitions:
619
641
  const { checkpoint } = await bucketStorage.getCheckpoint();
620
642
 
621
643
  const request = bucketRequest(syncRules, 'global[]');
622
- const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, [request]));
644
+ const batch = await test_utils.fromAsync(
645
+ bucketStorage.getBucketDataBatch(test_utils.testCheckpoint(checkpoint), [request])
646
+ );
623
647
 
624
648
  const data = batch[0].chunkData.data.map((d) => {
625
649
  return {
@@ -639,7 +663,9 @@ bucket_definitions:
639
663
  { op: 'REMOVE', object_id: 'test1', checksum: c2 }
640
664
  ]);
641
665
 
642
- const checksums = [...(await bucketStorage.getChecksums(checkpoint, [request])).values()];
666
+ const checksums = [
667
+ ...(await bucketStorage.getChecksums(test_utils.testCheckpoint(checkpoint), [request])).values()
668
+ ];
643
669
  expect(checksums).toEqual([
644
670
  {
645
671
  bucket: bucketRequest(syncRules, 'global[]').bucket,
@@ -782,7 +808,9 @@ bucket_definitions:
782
808
  const checkpoint2 = result2!.flushed_op;
783
809
 
784
810
  const request = bucketRequest(syncRules, 'global[]', checkpoint1);
785
- const batch = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint2, [request]));
811
+ const batch = await test_utils.fromAsync(
812
+ bucketStorage.getBucketDataBatch(test_utils.testCheckpoint(checkpoint2), [request])
813
+ );
786
814
 
787
815
  const data = batch[0].chunkData.data.map((d) => {
788
816
  return {
@@ -887,7 +915,7 @@ bucket_definitions:
887
915
 
888
916
  const request = bucketRequest(syncRules, 'global[]');
889
917
  const batch = await test_utils.fromAsync(
890
- bucketStorage.getBucketDataBatch(checkpoint3, [{ ...request, start: checkpoint1 }])
918
+ bucketStorage.getBucketDataBatch(test_utils.testCheckpoint(checkpoint3), [{ ...request, start: checkpoint1 }])
891
919
  );
892
920
  const data = batch[0].chunkData.data.map((d) => {
893
921
  return {
@@ -1001,7 +1029,7 @@ bucket_definitions:
1001
1029
 
1002
1030
  const request = bucketRequest(syncRules, 'global[]');
1003
1031
  const batch = await test_utils.fromAsync(
1004
- bucketStorage.getBucketDataBatch(checkpoint3, [{ ...request, start: checkpoint1 }])
1032
+ bucketStorage.getBucketDataBatch(test_utils.testCheckpoint(checkpoint3), [{ ...request, start: checkpoint1 }])
1005
1033
  );
1006
1034
  const data = batch[0].chunkData.data.map((d) => {
1007
1035
  return {
@@ -1108,7 +1136,9 @@ bucket_definitions:
1108
1136
  };
1109
1137
 
1110
1138
  const request = bucketRequest(syncRules, 'global[]');
1111
- const batch1 = await test_utils.fromAsync(bucketStorage.getBucketDataBatch(checkpoint, [request], options));
1139
+ const batch1 = await test_utils.fromAsync(
1140
+ bucketStorage.getBucketDataBatch(test_utils.testCheckpoint(checkpoint), [request], options)
1141
+ );
1112
1142
  expect(test_utils.getBatchData(batch1)).toEqual([
1113
1143
  { op_id: '1', op: 'PUT', object_id: 'test1', checksum: 2871785649 },
1114
1144
  { op_id: '2', op: 'PUT', object_id: 'large1', checksum: 454746904 }
@@ -1121,7 +1151,7 @@ bucket_definitions:
1121
1151
 
1122
1152
  const batch2 = await test_utils.fromAsync(
1123
1153
  bucketStorage.getBucketDataBatch(
1124
- checkpoint,
1154
+ test_utils.testCheckpoint(checkpoint),
1125
1155
  [{ ...request, start: BigInt(batch1[0].chunkData.next_after) }],
1126
1156
  options
1127
1157
  )
@@ -1138,7 +1168,7 @@ bucket_definitions:
1138
1168
 
1139
1169
  const batch3 = await test_utils.fromAsync(
1140
1170
  bucketStorage.getBucketDataBatch(
1141
- checkpoint,
1171
+ test_utils.testCheckpoint(checkpoint),
1142
1172
  [{ ...request, start: BigInt(batch2[0].chunkData.next_after) }],
1143
1173
  options
1144
1174
  )
@@ -1186,7 +1216,9 @@ bucket_definitions:
1186
1216
  const { checkpoint } = await bucketStorage.getCheckpoint();
1187
1217
 
1188
1218
  const request = bucketRequest(syncRules, 'global[]');
1189
- const batch1 = await test_utils.oneFromAsync(bucketStorage.getBucketDataBatch(checkpoint, [request], { limit: 4 }));
1219
+ const batch1 = await test_utils.oneFromAsync(
1220
+ bucketStorage.getBucketDataBatch(test_utils.testCheckpoint(checkpoint), [request], { limit: 4 })
1221
+ );
1190
1222
 
1191
1223
  expect(test_utils.getBatchData(batch1)).toEqual([
1192
1224
  { op_id: '1', op: 'PUT', object_id: 'test1', checksum: 2871785649 },
@@ -1202,9 +1234,13 @@ bucket_definitions:
1202
1234
  });
1203
1235
 
1204
1236
  const batch2 = await test_utils.oneFromAsync(
1205
- bucketStorage.getBucketDataBatch(checkpoint, [{ ...request, start: BigInt(batch1.chunkData.next_after) }], {
1206
- limit: 4
1207
- })
1237
+ bucketStorage.getBucketDataBatch(
1238
+ test_utils.testCheckpoint(checkpoint),
1239
+ [{ ...request, start: BigInt(batch1.chunkData.next_after) }],
1240
+ {
1241
+ limit: 4
1242
+ }
1243
+ )
1208
1244
  );
1209
1245
  expect(test_utils.getBatchData(batch2)).toEqual([
1210
1246
  { op_id: '5', op: 'PUT', object_id: 'test5', checksum: 3686902721 },
@@ -1218,9 +1254,13 @@ bucket_definitions:
1218
1254
  });
1219
1255
 
1220
1256
  const batch3 = await test_utils.fromAsync(
1221
- bucketStorage.getBucketDataBatch(checkpoint, [{ ...request, start: BigInt(batch2.chunkData.next_after) }], {
1222
- limit: 4
1223
- })
1257
+ bucketStorage.getBucketDataBatch(
1258
+ test_utils.testCheckpoint(checkpoint),
1259
+ [{ ...request, start: BigInt(batch2.chunkData.next_after) }],
1260
+ {
1261
+ limit: 4
1262
+ }
1263
+ )
1224
1264
  );
1225
1265
  expect(test_utils.getBatchData(batch3)).toEqual([]);
1226
1266
 
@@ -1269,7 +1309,11 @@ bucket_definitions:
1269
1309
  const global1Request = bucketRequest(syncRules, 'global1[]', 0n);
1270
1310
  const global2Request = bucketRequest(syncRules, 'global2[]', 0n);
1271
1311
  const batch = await test_utils.fromAsync(
1272
- bucketStorage.getBucketDataBatch(checkpoint, [global1Request, global2Request], options)
1312
+ bucketStorage.getBucketDataBatch(
1313
+ test_utils.testCheckpoint(checkpoint),
1314
+ [global1Request, global2Request],
1315
+ options
1316
+ )
1273
1317
  );
1274
1318
 
1275
1319
  return { batch, global1Request, global2Request };
@@ -1519,9 +1563,13 @@ bucket_definitions:
1519
1563
  const { checkpoint } = await bucketStorage.getCheckpoint();
1520
1564
 
1521
1565
  const request = bucketRequest(syncRules, 'global[]');
1522
- const checksums = [...(await bucketStorage.getChecksums(checkpoint, [request])).values()];
1566
+ const checksums = [
1567
+ ...(await bucketStorage.getChecksums(test_utils.testCheckpoint(checkpoint), [request])).values()
1568
+ ];
1523
1569
  expect(checksums).toEqual([{ bucket: request.bucket, checksum: 1917136889, count: 1 }]);
1524
- const checksums2 = [...(await bucketStorage.getChecksums(checkpoint + 1n, [request])).values()];
1570
+ const checksums2 = [
1571
+ ...(await bucketStorage.getChecksums(test_utils.testCheckpoint(checkpoint + 1n), [request])).values()
1572
+ ];
1525
1573
  expect(checksums2).toEqual([{ bucket: request.bucket, checksum: 1917136889, count: 1 }]);
1526
1574
  });
1527
1575
 
@@ -1724,7 +1772,7 @@ bucket_definitions:
1724
1772
  const cp = await bucketStorage.getCheckpoint();
1725
1773
  expect(cp.lsn).toEqual('3/1');
1726
1774
  const data = await test_utils.fromAsync(
1727
- bucketStorage.getBucketDataBatch(cp.checkpoint, [bucketRequest(syncRules, 'global[]')])
1775
+ bucketStorage.getBucketDataBatch(test_utils.testCheckpoint(cp.checkpoint), [bucketRequest(syncRules, 'global[]')])
1728
1776
  );
1729
1777
 
1730
1778
  expect(data).toEqual([]);
@@ -1781,7 +1829,9 @@ bucket_definitions:
1781
1829
  const users = ['u1', 'u2', 'u3', 'u4'];
1782
1830
  const expectedChecksums = [346204588, 5261081, 134760718, -302639724];
1783
1831
  const bucketRequests = users.map((user) => bucketRequest(syncRules, `user["${user}"]`));
1784
- const checksums = [...(await bucketStorage.getChecksums(checkpoint, bucketRequests)).values()];
1832
+ const checksums = [
1833
+ ...(await bucketStorage.getChecksums(test_utils.testCheckpoint(checkpoint), bucketRequests)).values()
1834
+ ];
1785
1835
  checksums.sort((a, b) => a.bucket.localeCompare(b.bucket));
1786
1836
  const expected = bucketRequests.map((request, index) => ({
1787
1837
  bucket: request.bucket,
@@ -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;
@@ -1238,6 +1238,150 @@ bucket_definitions:
1238
1238
  });
1239
1239
  });
1240
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
+
1241
1385
  test('write checkpoint', async () => {
1242
1386
  await using f = await factory();
1243
1387
 
@@ -1252,10 +1396,14 @@ bucket_definitions:
1252
1396
  // <= the managed write checkpoint LSN below
1253
1397
  await writer.commit('0/1');
1254
1398
 
1255
- const checkpoint = await bucketStorage.createManagedWriteCheckpoint({
1256
- user_id: 'test',
1257
- heads: { '1': '1/0' }
1258
- });
1399
+ const checkpoint = (
1400
+ await bucketStorage.createManagedWriteCheckpoints([
1401
+ {
1402
+ user_id: 'test',
1403
+ heads: { '1': '1/0' }
1404
+ }
1405
+ ])
1406
+ ).get('test')!;
1259
1407
 
1260
1408
  const params: sync.SyncStreamParameters = {
1261
1409
  syncContext,