@powersync/service-core-tests 0.0.0-dev-20260602073133 → 0.0.0-dev-20260909133214
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.
- package/CHANGELOG.md +112 -4
- package/dist/test-utils/AbstractStreamTestContext.d.ts +7 -6
- package/dist/test-utils/AbstractStreamTestContext.js +28 -25
- package/dist/test-utils/AbstractStreamTestContext.js.map +1 -1
- package/dist/test-utils/StorageDataHelpers.d.ts +6 -5
- package/dist/test-utils/StorageDataHelpers.js +40 -2
- package/dist/test-utils/StorageDataHelpers.js.map +1 -1
- package/dist/test-utils/bucket-validation.d.ts +3 -3
- package/dist/test-utils/bucket-validation.js +3 -3
- package/dist/test-utils/general-utils.d.ts +16 -3
- package/dist/test-utils/general-utils.js +37 -4
- package/dist/test-utils/general-utils.js.map +1 -1
- package/dist/test-utils/storage-combinations.d.ts +13 -0
- package/dist/test-utils/storage-combinations.js +26 -0
- package/dist/test-utils/storage-combinations.js.map +1 -0
- package/dist/test-utils/stream_utils.d.ts +0 -1
- package/dist/test-utils/stream_utils.js +0 -10
- package/dist/test-utils/stream_utils.js.map +1 -1
- package/dist/test-utils/test-utils-index.d.ts +1 -0
- package/dist/test-utils/test-utils-index.js +1 -0
- package/dist/test-utils/test-utils-index.js.map +1 -1
- package/dist/tests/register-compacting-tests.js +36 -29
- package/dist/tests/register-compacting-tests.js.map +1 -1
- package/dist/tests/register-data-storage-checkpoint-tests.js +138 -14
- package/dist/tests/register-data-storage-checkpoint-tests.js.map +1 -1
- package/dist/tests/register-data-storage-data-tests.js +205 -138
- package/dist/tests/register-data-storage-data-tests.js.map +1 -1
- package/dist/tests/register-data-storage-parameter-tests.js +44 -42
- package/dist/tests/register-data-storage-parameter-tests.js.map +1 -1
- package/dist/tests/register-parameter-compacting-tests.js +3 -2
- package/dist/tests/register-parameter-compacting-tests.js.map +1 -1
- package/dist/tests/register-sync-tests.js +404 -86
- package/dist/tests/register-sync-tests.js.map +1 -1
- package/dist/tests/util.d.ts +8 -2
- package/dist/tests/util.js +12 -0
- package/dist/tests/util.js.map +1 -1
- package/package.json +5 -6
- package/src/test-utils/AbstractStreamTestContext.ts +31 -27
- package/src/test-utils/StorageDataHelpers.ts +52 -8
- package/src/test-utils/bucket-validation.ts +3 -3
- package/src/test-utils/general-utils.ts +62 -10
- package/src/test-utils/storage-combinations.ts +51 -0
- package/src/test-utils/stream_utils.ts +0 -11
- package/src/test-utils/test-utils-index.ts +1 -0
- package/src/tests/register-compacting-tests.ts +55 -32
- package/src/tests/register-data-storage-checkpoint-tests.ts +171 -14
- package/src/tests/register-data-storage-data-tests.ts +251 -150
- package/src/tests/register-data-storage-parameter-tests.ts +58 -42
- package/src/tests/register-parameter-compacting-tests.ts +3 -2
- package/src/tests/register-sync-tests.ts +443 -88
- package/src/tests/util.ts +15 -2
- package/tsconfig.json +1 -4
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import {
|
|
2
|
+
STORAGE_VERSION_1,
|
|
3
|
+
STORAGE_VERSION_2,
|
|
4
|
+
STORAGE_VERSION_4,
|
|
5
|
+
TestStorageConfig,
|
|
6
|
+
TestStorageFactory
|
|
7
|
+
} from '@powersync/service-core';
|
|
8
|
+
import { describe, TestOptions } from 'vitest';
|
|
9
|
+
|
|
10
|
+
const MONGO_STORAGE_VERSIONS = [STORAGE_VERSION_1, STORAGE_VERSION_2, STORAGE_VERSION_4];
|
|
11
|
+
const POSTGRES_STORAGE_VERSIONS = [STORAGE_VERSION_1, STORAGE_VERSION_2];
|
|
12
|
+
|
|
13
|
+
export interface StorageVersionTestContext {
|
|
14
|
+
factory: TestStorageFactory;
|
|
15
|
+
storageVersion: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface StorageCombinationTestConfig {
|
|
19
|
+
mongodb?: TestStorageConfig;
|
|
20
|
+
postgres?: TestStorageConfig;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function describeStorageCombinations(
|
|
24
|
+
config: StorageCombinationTestConfig,
|
|
25
|
+
options: TestOptions & { storageVersions?: number[] },
|
|
26
|
+
fn: (context: StorageVersionTestContext) => void
|
|
27
|
+
) {
|
|
28
|
+
const describeFactory = (storageName: string, storage: TestStorageConfig, supportedStorageVersions: number[]) => {
|
|
29
|
+
const storageVersions = (options.storageVersions ?? supportedStorageVersions).filter((version) =>
|
|
30
|
+
supportedStorageVersions.includes(version)
|
|
31
|
+
);
|
|
32
|
+
describe(`${storageName} storage`, options, function () {
|
|
33
|
+
for (const storageVersion of storageVersions) {
|
|
34
|
+
describe(`storage v${storageVersion}`, function () {
|
|
35
|
+
fn({
|
|
36
|
+
factory: storage.factory,
|
|
37
|
+
storageVersion
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
if (config.mongodb) {
|
|
45
|
+
describeFactory('mongodb', config.mongodb, MONGO_STORAGE_VERSIONS);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (config.postgres) {
|
|
49
|
+
describeFactory('postgres', config.postgres, POSTGRES_STORAGE_VERSIONS);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -22,17 +22,6 @@ export function compareIds(a: utils.OplogEntry, b: utils.OplogEntry) {
|
|
|
22
22
|
return a.object_id!.localeCompare(b.object_id!);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
export async function oneFromAsync<T>(source: Iterable<T> | AsyncIterable<T>): Promise<T> {
|
|
26
|
-
const items: T[] = [];
|
|
27
|
-
for await (const item of source) {
|
|
28
|
-
items.push(item);
|
|
29
|
-
}
|
|
30
|
-
if (items.length != 1) {
|
|
31
|
-
throw new Error(`One item expected, got: ${items.length}`);
|
|
32
|
-
}
|
|
33
|
-
return items[0];
|
|
34
|
-
}
|
|
35
|
-
|
|
36
25
|
export async function fromAsync<T>(source: Iterable<T> | AsyncIterable<T>): Promise<T[]> {
|
|
37
26
|
const items: T[] = [];
|
|
38
27
|
for await (const item of source) {
|
|
@@ -2,5 +2,6 @@ export * from './AbstractStreamTestContext.js';
|
|
|
2
2
|
export * from './bucket-validation.js';
|
|
3
3
|
export * from './general-utils.js';
|
|
4
4
|
export * from './MetricsHelper.js';
|
|
5
|
+
export * from './storage-combinations.js';
|
|
5
6
|
export * from './StorageDataHelpers.js';
|
|
6
7
|
export * from './stream_utils.js';
|
|
@@ -2,7 +2,7 @@ import { addChecksums, storage, updateSyncRulesFromYaml } from '@powersync/servi
|
|
|
2
2
|
import { expect, test } from 'vitest';
|
|
3
3
|
import * as test_utils from '../test-utils/test-utils-index.js';
|
|
4
4
|
import { bucketRequest } from '../test-utils/test-utils-index.js';
|
|
5
|
-
import { bucketRequestMap, bucketRequests } from './util.js';
|
|
5
|
+
import { bucketRequestMap, bucketRequests, compactActive } from './util.js';
|
|
6
6
|
|
|
7
7
|
export function registerCompactTests(config: storage.TestStorageConfig) {
|
|
8
8
|
const generateStorageFactory = config.factory;
|
|
@@ -17,6 +17,7 @@ bucket_definitions:
|
|
|
17
17
|
`)
|
|
18
18
|
);
|
|
19
19
|
const bucketStorage = factory.getInstance(syncRules);
|
|
20
|
+
const syncRulesContent = syncRules.syncConfigContent[0];
|
|
20
21
|
|
|
21
22
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
22
23
|
const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
@@ -53,11 +54,13 @@ bucket_definitions:
|
|
|
53
54
|
|
|
54
55
|
const checkpoint = writer.last_flushed_op!;
|
|
55
56
|
|
|
56
|
-
const request = bucketRequest(
|
|
57
|
+
const request = bucketRequest(syncRulesContent, 'global[]');
|
|
57
58
|
|
|
58
|
-
const batchBefore = await test_utils.
|
|
59
|
+
const batchBefore = await test_utils.getSingleBatchItem(
|
|
60
|
+
bucketStorage.getBucketDataBatch(test_utils.testCheckpoint(checkpoint), [request])
|
|
61
|
+
);
|
|
59
62
|
const dataBefore = batchBefore.chunkData.data;
|
|
60
|
-
const checksumBefore = await bucketStorage.getChecksums(checkpoint, [request]);
|
|
63
|
+
const checksumBefore = await bucketStorage.getChecksums(test_utils.testCheckpoint(checkpoint), [request]);
|
|
61
64
|
|
|
62
65
|
expect(dataBefore).toMatchObject([
|
|
63
66
|
{
|
|
@@ -78,7 +81,7 @@ bucket_definitions:
|
|
|
78
81
|
]);
|
|
79
82
|
expect(batchBefore.targetOp).toEqual(null);
|
|
80
83
|
|
|
81
|
-
await
|
|
84
|
+
await compactActive(factory, {
|
|
82
85
|
clearBatchLimit: 2,
|
|
83
86
|
moveBatchLimit: 1,
|
|
84
87
|
moveBatchQueryLimit: 1,
|
|
@@ -86,11 +89,13 @@ bucket_definitions:
|
|
|
86
89
|
minChangeRatio: 0
|
|
87
90
|
});
|
|
88
91
|
|
|
89
|
-
const batchAfter = await test_utils.
|
|
92
|
+
const batchAfter = await test_utils.getSingleBatchItem(
|
|
93
|
+
bucketStorage.getBucketDataBatch(test_utils.testCheckpoint(checkpoint), [request])
|
|
94
|
+
);
|
|
90
95
|
const dataAfter = batchAfter.chunkData.data;
|
|
91
|
-
const checksumAfter = await bucketStorage.getChecksums(checkpoint, [request]);
|
|
96
|
+
const checksumAfter = await bucketStorage.getChecksums(test_utils.testCheckpoint(checkpoint), [request]);
|
|
92
97
|
bucketStorage.clearChecksumCache();
|
|
93
|
-
const checksumAfter2 = await bucketStorage.getChecksums(checkpoint, [request]);
|
|
98
|
+
const checksumAfter2 = await bucketStorage.getChecksums(test_utils.testCheckpoint(checkpoint), [request]);
|
|
94
99
|
|
|
95
100
|
expect(batchAfter.targetOp).toEqual(3n);
|
|
96
101
|
expect(dataAfter).toMatchObject([
|
|
@@ -124,6 +129,7 @@ bucket_definitions:
|
|
|
124
129
|
`)
|
|
125
130
|
);
|
|
126
131
|
const bucketStorage = factory.getInstance(syncRules);
|
|
132
|
+
const syncRulesContent = syncRules.syncConfigContent[0];
|
|
127
133
|
|
|
128
134
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
129
135
|
const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
@@ -168,11 +174,13 @@ bucket_definitions:
|
|
|
168
174
|
await writer.flush();
|
|
169
175
|
|
|
170
176
|
const checkpoint = writer.last_flushed_op!;
|
|
171
|
-
const request = bucketRequest(
|
|
177
|
+
const request = bucketRequest(syncRulesContent, 'global[]');
|
|
172
178
|
|
|
173
|
-
const batchBefore = await test_utils.
|
|
179
|
+
const batchBefore = await test_utils.getSingleBatchItem(
|
|
180
|
+
bucketStorage.getBucketDataBatch(test_utils.testCheckpoint(checkpoint), [request])
|
|
181
|
+
);
|
|
174
182
|
const dataBefore = batchBefore.chunkData.data;
|
|
175
|
-
const checksumBefore = await bucketStorage.getChecksums(checkpoint, [request]);
|
|
183
|
+
const checksumBefore = await bucketStorage.getChecksums(test_utils.testCheckpoint(checkpoint), [request]);
|
|
176
184
|
|
|
177
185
|
// op_id sequence depends on the storage implementation
|
|
178
186
|
expect(dataBefore).toMatchObject([
|
|
@@ -194,7 +202,7 @@ bucket_definitions:
|
|
|
194
202
|
}
|
|
195
203
|
]);
|
|
196
204
|
|
|
197
|
-
await
|
|
205
|
+
await compactActive(factory, {
|
|
198
206
|
clearBatchLimit: 2,
|
|
199
207
|
moveBatchLimit: 1,
|
|
200
208
|
moveBatchQueryLimit: 1,
|
|
@@ -202,10 +210,12 @@ bucket_definitions:
|
|
|
202
210
|
minChangeRatio: 0
|
|
203
211
|
});
|
|
204
212
|
|
|
205
|
-
const batchAfter = await test_utils.
|
|
213
|
+
const batchAfter = await test_utils.getSingleBatchItem(
|
|
214
|
+
bucketStorage.getBucketDataBatch(test_utils.testCheckpoint(checkpoint), [request])
|
|
215
|
+
);
|
|
206
216
|
const dataAfter = batchAfter.chunkData.data;
|
|
207
217
|
bucketStorage.clearChecksumCache();
|
|
208
|
-
const checksumAfter = await bucketStorage.getChecksums(checkpoint, [request]);
|
|
218
|
+
const checksumAfter = await bucketStorage.getChecksums(test_utils.testCheckpoint(checkpoint), [request]);
|
|
209
219
|
|
|
210
220
|
expect(batchAfter.targetOp).toBeLessThanOrEqual(checkpoint);
|
|
211
221
|
expect(dataAfter).toMatchObject([
|
|
@@ -240,6 +250,7 @@ bucket_definitions:
|
|
|
240
250
|
`)
|
|
241
251
|
);
|
|
242
252
|
const bucketStorage = factory.getInstance(syncRules);
|
|
253
|
+
const syncRulesContent = syncRules.syncConfigContent[0];
|
|
243
254
|
|
|
244
255
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
245
256
|
const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
@@ -275,7 +286,7 @@ bucket_definitions:
|
|
|
275
286
|
await writer.flush();
|
|
276
287
|
|
|
277
288
|
const checkpoint1 = writer.last_flushed_op!;
|
|
278
|
-
const request = bucketRequest(
|
|
289
|
+
const request = bucketRequest(syncRulesContent, 'global[]');
|
|
279
290
|
await using writer2 = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
280
291
|
const testTable2 = await test_utils.resolveTestTable(writer2, 'test', ['id'], config);
|
|
281
292
|
await writer2.save({
|
|
@@ -290,7 +301,7 @@ bucket_definitions:
|
|
|
290
301
|
await writer2.flush();
|
|
291
302
|
const checkpoint2 = writer2.last_flushed_op!;
|
|
292
303
|
|
|
293
|
-
await
|
|
304
|
+
await compactActive(factory, {
|
|
294
305
|
clearBatchLimit: 2,
|
|
295
306
|
moveBatchLimit: 1,
|
|
296
307
|
moveBatchQueryLimit: 1,
|
|
@@ -298,10 +309,12 @@ bucket_definitions:
|
|
|
298
309
|
minChangeRatio: 0
|
|
299
310
|
});
|
|
300
311
|
|
|
301
|
-
const batchAfter = await test_utils.
|
|
312
|
+
const batchAfter = await test_utils.getSingleBatchItem(
|
|
313
|
+
bucketStorage.getBucketDataBatch(test_utils.testCheckpoint(checkpoint2), [request])
|
|
314
|
+
);
|
|
302
315
|
const dataAfter = batchAfter.chunkData.data;
|
|
303
316
|
await bucketStorage.clearChecksumCache();
|
|
304
|
-
const checksumAfter = await bucketStorage.getChecksums(checkpoint2, [request]);
|
|
317
|
+
const checksumAfter = await bucketStorage.getChecksums(test_utils.testCheckpoint(checkpoint2), [request]);
|
|
305
318
|
|
|
306
319
|
expect(dataAfter).toMatchObject([
|
|
307
320
|
{
|
|
@@ -328,6 +341,7 @@ bucket_definitions:
|
|
|
328
341
|
- select * from test where b = bucket.b`)
|
|
329
342
|
);
|
|
330
343
|
const bucketStorage = factory.getInstance(syncRules);
|
|
344
|
+
const syncRulesContent = syncRules.syncConfigContent[0];
|
|
331
345
|
|
|
332
346
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
333
347
|
const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
@@ -402,7 +416,7 @@ bucket_definitions:
|
|
|
402
416
|
|
|
403
417
|
const checkpoint = writer.last_flushed_op!;
|
|
404
418
|
|
|
405
|
-
await
|
|
419
|
+
await compactActive(factory, {
|
|
406
420
|
clearBatchLimit: 100,
|
|
407
421
|
moveBatchLimit: 100,
|
|
408
422
|
moveBatchQueryLimit: 100, // Larger limit for a larger window of operations
|
|
@@ -410,10 +424,10 @@ bucket_definitions:
|
|
|
410
424
|
minChangeRatio: 0
|
|
411
425
|
});
|
|
412
426
|
|
|
413
|
-
const batchAfter = await test_utils.
|
|
427
|
+
const batchAfter = await test_utils.getBatchArray(
|
|
414
428
|
bucketStorage.getBucketDataBatch(
|
|
415
|
-
checkpoint,
|
|
416
|
-
bucketRequestMap(
|
|
429
|
+
test_utils.testCheckpoint(checkpoint),
|
|
430
|
+
bucketRequestMap(syncRulesContent, [
|
|
417
431
|
['grouped["b1"]', 0n],
|
|
418
432
|
['grouped["b2"]', 0n]
|
|
419
433
|
])
|
|
@@ -458,6 +472,7 @@ bucket_definitions:
|
|
|
458
472
|
`)
|
|
459
473
|
);
|
|
460
474
|
const bucketStorage = factory.getInstance(syncRules);
|
|
475
|
+
const syncRulesContent = syncRules.syncConfigContent[0];
|
|
461
476
|
|
|
462
477
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
463
478
|
const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
@@ -492,7 +507,7 @@ bucket_definitions:
|
|
|
492
507
|
await writer.commit('1/1');
|
|
493
508
|
await writer.flush();
|
|
494
509
|
|
|
495
|
-
await
|
|
510
|
+
await compactActive(factory, {
|
|
496
511
|
clearBatchLimit: 2,
|
|
497
512
|
moveBatchLimit: 1,
|
|
498
513
|
moveBatchQueryLimit: 1,
|
|
@@ -513,9 +528,9 @@ bucket_definitions:
|
|
|
513
528
|
await writer2.commit('2/1');
|
|
514
529
|
await writer2.flush();
|
|
515
530
|
const checkpoint2 = writer2.last_flushed_op!;
|
|
516
|
-
const request = bucketRequest(
|
|
531
|
+
const request = bucketRequest(syncRulesContent, 'global[]');
|
|
517
532
|
await bucketStorage.clearChecksumCache();
|
|
518
|
-
const checksumAfter = await bucketStorage.getChecksums(checkpoint2, [request]);
|
|
533
|
+
const checksumAfter = await bucketStorage.getChecksums(test_utils.testCheckpoint(checkpoint2), [request]);
|
|
519
534
|
const globalChecksum = checksumAfter.get(request.bucket);
|
|
520
535
|
expect(globalChecksum).toMatchObject({
|
|
521
536
|
bucket: request.bucket,
|
|
@@ -536,6 +551,7 @@ bucket_definitions:
|
|
|
536
551
|
`)
|
|
537
552
|
);
|
|
538
553
|
const bucketStorage = factory.getInstance(syncRules);
|
|
554
|
+
const syncRulesContent = syncRules.syncConfigContent[0];
|
|
539
555
|
|
|
540
556
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
541
557
|
const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
@@ -562,7 +578,10 @@ bucket_definitions:
|
|
|
562
578
|
await writer.flush();
|
|
563
579
|
|
|
564
580
|
// Get checksums here just to populate the cache
|
|
565
|
-
await bucketStorage.getChecksums(
|
|
581
|
+
await bucketStorage.getChecksums(
|
|
582
|
+
test_utils.testCheckpoint(writer.last_flushed_op!),
|
|
583
|
+
bucketRequests(syncRulesContent, ['global[]'])
|
|
584
|
+
);
|
|
566
585
|
await using writer2 = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
567
586
|
const testTable2 = await test_utils.resolveTestTable(writer2, 'test', ['id'], config);
|
|
568
587
|
await writer2.save({
|
|
@@ -576,7 +595,7 @@ bucket_definitions:
|
|
|
576
595
|
await writer2.commit('2/1');
|
|
577
596
|
await writer2.flush();
|
|
578
597
|
|
|
579
|
-
await
|
|
598
|
+
await compactActive(factory, {
|
|
580
599
|
clearBatchLimit: 20,
|
|
581
600
|
moveBatchLimit: 10,
|
|
582
601
|
moveBatchQueryLimit: 10,
|
|
@@ -585,9 +604,9 @@ bucket_definitions:
|
|
|
585
604
|
});
|
|
586
605
|
|
|
587
606
|
const checkpoint2 = writer2.last_flushed_op!;
|
|
588
|
-
const request = bucketRequest(
|
|
607
|
+
const request = bucketRequest(syncRulesContent, 'global[]');
|
|
589
608
|
// Check that the checksum was correctly updated with the clear operation after having a cached checksum
|
|
590
|
-
const checksumAfter = await bucketStorage.getChecksums(checkpoint2, [request]);
|
|
609
|
+
const checksumAfter = await bucketStorage.getChecksums(test_utils.testCheckpoint(checkpoint2), [request]);
|
|
591
610
|
const globalChecksum = checksumAfter.get(request.bucket);
|
|
592
611
|
expect(globalChecksum).toMatchObject({
|
|
593
612
|
bucket: request.bucket,
|
|
@@ -607,6 +626,7 @@ bucket_definitions:
|
|
|
607
626
|
`)
|
|
608
627
|
);
|
|
609
628
|
const bucketStorage = factory.getInstance(syncRules);
|
|
629
|
+
const syncRulesContent = syncRules.syncConfigContent[0];
|
|
610
630
|
|
|
611
631
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
612
632
|
const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
@@ -638,15 +658,18 @@ bucket_definitions:
|
|
|
638
658
|
expect(checkpointBeforeCompact.checkpoint).toEqual(checkpoint1);
|
|
639
659
|
|
|
640
660
|
// With default options, Postgres compaction should use the active checkpoint.
|
|
641
|
-
await
|
|
661
|
+
await compactActive(factory, {
|
|
642
662
|
moveBatchLimit: 1,
|
|
643
663
|
moveBatchQueryLimit: 1,
|
|
644
664
|
minBucketChanges: 1,
|
|
645
665
|
minChangeRatio: 0
|
|
646
666
|
});
|
|
647
667
|
|
|
648
|
-
const batchAfterDefaultCompact = await test_utils.
|
|
649
|
-
bucketStorage.getBucketDataBatch(
|
|
668
|
+
const batchAfterDefaultCompact = await test_utils.getSingleBatchItem(
|
|
669
|
+
bucketStorage.getBucketDataBatch(
|
|
670
|
+
test_utils.testCheckpoint(checkpoint2),
|
|
671
|
+
bucketRequestMap(syncRulesContent, [['global[]', 0n]])
|
|
672
|
+
)
|
|
650
673
|
);
|
|
651
674
|
|
|
652
675
|
// Operation 1 should remain a PUT because op_id=2 is above the default maxOpId checkpoint.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { storage, updateSyncRulesFromYaml } from '@powersync/service-core';
|
|
2
2
|
import { expect, test } from 'vitest';
|
|
3
3
|
import * as test_utils from '../test-utils/test-utils-index.js';
|
|
4
|
+
import { compactActive } from './util.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* @example
|
|
@@ -36,13 +37,16 @@ bucket_definitions:
|
|
|
36
37
|
const abortController = new AbortController();
|
|
37
38
|
context.onTestFinished(() => abortController.abort());
|
|
38
39
|
const iter = bucketStorage
|
|
39
|
-
.watchCheckpointChanges({
|
|
40
|
+
.watchCheckpointChanges({
|
|
41
|
+
user_id: 'user1',
|
|
42
|
+
signal: abortController.signal
|
|
43
|
+
})
|
|
40
44
|
[Symbol.asyncIterator]();
|
|
41
45
|
|
|
42
46
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
43
47
|
await writer.markAllSnapshotDone('1/1');
|
|
44
48
|
|
|
45
|
-
const writeCheckpoint = await
|
|
49
|
+
const writeCheckpoint = await createManagedWriteCheckpoint(bucketStorage, {
|
|
46
50
|
heads: { '1': '5/0' },
|
|
47
51
|
user_id: 'user1'
|
|
48
52
|
});
|
|
@@ -85,7 +89,10 @@ bucket_definitions:
|
|
|
85
89
|
const abortController = new AbortController();
|
|
86
90
|
context.onTestFinished(() => abortController.abort());
|
|
87
91
|
const iter = bucketStorage
|
|
88
|
-
.watchCheckpointChanges({
|
|
92
|
+
.watchCheckpointChanges({
|
|
93
|
+
user_id: 'user1',
|
|
94
|
+
signal: abortController.signal
|
|
95
|
+
})
|
|
89
96
|
[Symbol.asyncIterator]();
|
|
90
97
|
|
|
91
98
|
await writer.keepalive('5/0');
|
|
@@ -102,12 +109,12 @@ bucket_definitions:
|
|
|
102
109
|
}
|
|
103
110
|
});
|
|
104
111
|
|
|
105
|
-
const writeCheckpoint = await
|
|
112
|
+
const writeCheckpoint = await createManagedWriteCheckpoint(bucketStorage, {
|
|
106
113
|
heads: { '1': '6/0' },
|
|
107
114
|
user_id: 'user1'
|
|
108
115
|
});
|
|
109
116
|
// We have to trigger a new keepalive after the checkpoint, at least to cover postgres storage.
|
|
110
|
-
// This is what is
|
|
117
|
+
// This is what is effectively triggered by RouteAPI.createReplicationHead().
|
|
111
118
|
// MongoDB storage doesn't explicitly need this anymore.
|
|
112
119
|
await writer.keepalive('6/0');
|
|
113
120
|
|
|
@@ -128,7 +135,7 @@ bucket_definitions:
|
|
|
128
135
|
});
|
|
129
136
|
});
|
|
130
137
|
|
|
131
|
-
test('
|
|
138
|
+
test('managed write checkpoints - client supplied ids are monotonic', async () => {
|
|
132
139
|
await using factory = await generateStorageFactory();
|
|
133
140
|
const r = await factory.configureSyncRules(
|
|
134
141
|
updateSyncRulesFromYaml(
|
|
@@ -144,7 +151,75 @@ bucket_definitions:
|
|
|
144
151
|
)
|
|
145
152
|
);
|
|
146
153
|
const bucketStorage = factory.getInstance(r.persisted_sync_rules!);
|
|
147
|
-
|
|
154
|
+
|
|
155
|
+
const first = await createManagedWriteCheckpointResult(bucketStorage, {
|
|
156
|
+
user_id: 'user1',
|
|
157
|
+
heads: { '1': '5/0' },
|
|
158
|
+
checkpoint_request_id: 42n
|
|
159
|
+
});
|
|
160
|
+
expect(first.writeCheckpoints.get('user1')).toEqual(42n);
|
|
161
|
+
expect(first.shouldAdvance).toBe(true);
|
|
162
|
+
await expect(bucketStorage.lastWriteCheckpoint({ user_id: 'user1', heads: { '1': '5/0' } })).resolves.toEqual(42n);
|
|
163
|
+
|
|
164
|
+
const stale = await createManagedWriteCheckpointResult(bucketStorage, {
|
|
165
|
+
user_id: 'user1',
|
|
166
|
+
heads: { '1': '8/0' },
|
|
167
|
+
checkpoint_request_id: 41n
|
|
168
|
+
});
|
|
169
|
+
expect(stale.writeCheckpoints.get('user1')).toEqual(42n);
|
|
170
|
+
// The stored checkpoint is unchanged but has not been processed by replication
|
|
171
|
+
// yet, so the source marker must still be forced (e.g. for a retry after a
|
|
172
|
+
// previous attempt failed to advance the marker).
|
|
173
|
+
expect(stale.shouldAdvance).toBe(true);
|
|
174
|
+
await expect(bucketStorage.lastWriteCheckpoint({ user_id: 'user1', heads: { '1': '6/0' } })).resolves.toEqual(42n);
|
|
175
|
+
|
|
176
|
+
const retried = await createManagedWriteCheckpointResult(bucketStorage, {
|
|
177
|
+
user_id: 'user1',
|
|
178
|
+
heads: { '1': '8/0' },
|
|
179
|
+
checkpoint_request_id: 42n
|
|
180
|
+
});
|
|
181
|
+
expect(retried.writeCheckpoints.get('user1')).toEqual(42n);
|
|
182
|
+
// Same as above: the duplicate request still points at a pending checkpoint.
|
|
183
|
+
expect(retried.shouldAdvance).toBe(true);
|
|
184
|
+
await expect(bucketStorage.lastWriteCheckpoint({ user_id: 'user1', heads: { '1': '6/0' } })).resolves.toEqual(42n);
|
|
185
|
+
|
|
186
|
+
const advanced = await createManagedWriteCheckpointResult(bucketStorage, {
|
|
187
|
+
user_id: 'user1',
|
|
188
|
+
heads: { '1': '8/0' },
|
|
189
|
+
checkpoint_request_id: 43n
|
|
190
|
+
});
|
|
191
|
+
expect(advanced.writeCheckpoints.get('user1')).toEqual(43n);
|
|
192
|
+
expect(advanced.shouldAdvance).toBe(true);
|
|
193
|
+
await expect(bucketStorage.lastWriteCheckpoint({ user_id: 'user1', heads: { '1': '7/0' } })).resolves.toBeNull();
|
|
194
|
+
await expect(bucketStorage.lastWriteCheckpoint({ user_id: 'user1', heads: { '1': '8/0' } })).resolves.toEqual(43n);
|
|
195
|
+
|
|
196
|
+
const generated = await createManagedWriteCheckpointResult(bucketStorage, {
|
|
197
|
+
user_id: 'user1',
|
|
198
|
+
heads: { '1': '9/0' }
|
|
199
|
+
});
|
|
200
|
+
expect(generated.writeCheckpoints.get('user1')).toEqual(44n);
|
|
201
|
+
expect(generated.shouldAdvance).toBe(true);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test.runIf((storageVersion ?? 1) < 3)('custom write checkpoints - checkpoint after write', async (context) => {
|
|
205
|
+
await using factory = await generateStorageFactory();
|
|
206
|
+
const r = await factory.configureSyncRules(
|
|
207
|
+
updateSyncRulesFromYaml(
|
|
208
|
+
`
|
|
209
|
+
bucket_definitions:
|
|
210
|
+
mybucket:
|
|
211
|
+
data: []
|
|
212
|
+
`,
|
|
213
|
+
{
|
|
214
|
+
validate: false,
|
|
215
|
+
storageVersion
|
|
216
|
+
}
|
|
217
|
+
)
|
|
218
|
+
);
|
|
219
|
+
const bucketStorage = factory.getInstance(r.persisted_sync_rules!);
|
|
220
|
+
bucketStorage.setWriteCheckpointMode({
|
|
221
|
+
mode: storage.WriteCheckpointMode.CUSTOM
|
|
222
|
+
});
|
|
148
223
|
|
|
149
224
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
150
225
|
await writer.markAllSnapshotDone('1/1');
|
|
@@ -152,7 +227,10 @@ bucket_definitions:
|
|
|
152
227
|
const abortController = new AbortController();
|
|
153
228
|
context.onTestFinished(() => abortController.abort());
|
|
154
229
|
const iter = bucketStorage
|
|
155
|
-
.watchCheckpointChanges({
|
|
230
|
+
.watchCheckpointChanges({
|
|
231
|
+
user_id: 'user1',
|
|
232
|
+
signal: abortController.signal
|
|
233
|
+
})
|
|
156
234
|
[Symbol.asyncIterator]();
|
|
157
235
|
|
|
158
236
|
writer.addCustomWriteCheckpoint({
|
|
@@ -174,7 +252,7 @@ bucket_definitions:
|
|
|
174
252
|
});
|
|
175
253
|
});
|
|
176
254
|
|
|
177
|
-
test('custom write checkpoints - standalone checkpoint', async (context) => {
|
|
255
|
+
test.runIf((storageVersion ?? 1) < 3)('custom write checkpoints - standalone checkpoint', async (context) => {
|
|
178
256
|
await using factory = await generateStorageFactory();
|
|
179
257
|
const r = await factory.configureSyncRules(
|
|
180
258
|
updateSyncRulesFromYaml(
|
|
@@ -190,7 +268,9 @@ bucket_definitions:
|
|
|
190
268
|
)
|
|
191
269
|
);
|
|
192
270
|
const bucketStorage = factory.getInstance(r.persisted_sync_rules!);
|
|
193
|
-
bucketStorage.setWriteCheckpointMode(
|
|
271
|
+
bucketStorage.setWriteCheckpointMode({
|
|
272
|
+
mode: storage.WriteCheckpointMode.CUSTOM
|
|
273
|
+
});
|
|
194
274
|
|
|
195
275
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
196
276
|
await writer.markAllSnapshotDone('1/1');
|
|
@@ -198,7 +278,10 @@ bucket_definitions:
|
|
|
198
278
|
const abortController = new AbortController();
|
|
199
279
|
context.onTestFinished(() => abortController.abort());
|
|
200
280
|
const iter = bucketStorage
|
|
201
|
-
.watchCheckpointChanges({
|
|
281
|
+
.watchCheckpointChanges({
|
|
282
|
+
user_id: 'user1',
|
|
283
|
+
signal: abortController.signal
|
|
284
|
+
})
|
|
202
285
|
[Symbol.asyncIterator]();
|
|
203
286
|
|
|
204
287
|
// Flush to clear state
|
|
@@ -223,7 +306,7 @@ bucket_definitions:
|
|
|
223
306
|
});
|
|
224
307
|
});
|
|
225
308
|
|
|
226
|
-
test('custom write checkpoints - write after checkpoint', async (context) => {
|
|
309
|
+
test.runIf((storageVersion ?? 1) < 3)('custom write checkpoints - write after checkpoint', async (context) => {
|
|
227
310
|
await using factory = await generateStorageFactory();
|
|
228
311
|
const r = await factory.configureSyncRules(
|
|
229
312
|
updateSyncRulesFromYaml(
|
|
@@ -239,7 +322,9 @@ bucket_definitions:
|
|
|
239
322
|
)
|
|
240
323
|
);
|
|
241
324
|
const bucketStorage = factory.getInstance(r.persisted_sync_rules!);
|
|
242
|
-
bucketStorage.setWriteCheckpointMode(
|
|
325
|
+
bucketStorage.setWriteCheckpointMode({
|
|
326
|
+
mode: storage.WriteCheckpointMode.CUSTOM
|
|
327
|
+
});
|
|
243
328
|
|
|
244
329
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
245
330
|
await writer.markAllSnapshotDone('1/1');
|
|
@@ -247,7 +332,10 @@ bucket_definitions:
|
|
|
247
332
|
const abortController = new AbortController();
|
|
248
333
|
context.onTestFinished(() => abortController.abort());
|
|
249
334
|
const iter = bucketStorage
|
|
250
|
-
.watchCheckpointChanges({
|
|
335
|
+
.watchCheckpointChanges({
|
|
336
|
+
user_id: 'user1',
|
|
337
|
+
signal: abortController.signal
|
|
338
|
+
})
|
|
251
339
|
[Symbol.asyncIterator]();
|
|
252
340
|
|
|
253
341
|
await writer.keepalive('5/0');
|
|
@@ -301,4 +389,73 @@ bucket_definitions:
|
|
|
301
389
|
}
|
|
302
390
|
});
|
|
303
391
|
});
|
|
392
|
+
|
|
393
|
+
test.runIf((storageVersion ?? 1) < 3)(
|
|
394
|
+
'custom write checkpoints - checkpoint request markers are temporary',
|
|
395
|
+
async () => {
|
|
396
|
+
await using factory = await generateStorageFactory();
|
|
397
|
+
const r = await factory.configureSyncRules(
|
|
398
|
+
updateSyncRulesFromYaml(
|
|
399
|
+
`
|
|
400
|
+
bucket_definitions:
|
|
401
|
+
mybucket:
|
|
402
|
+
data: []
|
|
403
|
+
`,
|
|
404
|
+
{
|
|
405
|
+
validate: false,
|
|
406
|
+
storageVersion
|
|
407
|
+
}
|
|
408
|
+
)
|
|
409
|
+
);
|
|
410
|
+
const bucketStorage = factory.getInstance(r.persisted_sync_rules!);
|
|
411
|
+
bucketStorage.setWriteCheckpointMode({
|
|
412
|
+
mode: storage.WriteCheckpointMode.CUSTOM
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
416
|
+
await writer.markAllSnapshotDone('1/1');
|
|
417
|
+
|
|
418
|
+
writer.addCustomWriteCheckpoint({
|
|
419
|
+
checkpoint: 5n,
|
|
420
|
+
user_id: 'persistent'
|
|
421
|
+
});
|
|
422
|
+
writer.addCustomWriteCheckpoint({
|
|
423
|
+
checkpoint: 6n,
|
|
424
|
+
user_id: 'temporary',
|
|
425
|
+
checkpoint_requested_at: new Date('2024-01-01T00:00:00.000Z')
|
|
426
|
+
});
|
|
427
|
+
await writer.flush();
|
|
428
|
+
await writer.keepalive('1/1');
|
|
429
|
+
|
|
430
|
+
await expect(bucketStorage.lastWriteCheckpoint({ user_id: 'persistent' })).resolves.toEqual(5n);
|
|
431
|
+
await expect(bucketStorage.lastWriteCheckpoint({ user_id: 'temporary' })).resolves.toEqual(6n);
|
|
432
|
+
|
|
433
|
+
await compactActive(factory, {
|
|
434
|
+
compactBuckets: [],
|
|
435
|
+
deleteCheckpointRequestsBefore: new Date(Date.now() + 1_000)
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
await expect(bucketStorage.lastWriteCheckpoint({ user_id: 'persistent' })).resolves.toEqual(5n);
|
|
439
|
+
await expect(bucketStorage.lastWriteCheckpoint({ user_id: 'temporary' })).resolves.toBeNull();
|
|
440
|
+
}
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
async function createManagedWriteCheckpoint(
|
|
445
|
+
bucketStorage: storage.SyncRulesBucketStorage,
|
|
446
|
+
checkpoint: storage.ManagedWriteCheckpointOptions
|
|
447
|
+
) {
|
|
448
|
+
const result = await createManagedWriteCheckpointResult(bucketStorage, checkpoint);
|
|
449
|
+
const writeCheckpoint = result.writeCheckpoints.get(checkpoint.user_id);
|
|
450
|
+
expect(writeCheckpoint).not.toBeUndefined();
|
|
451
|
+
return writeCheckpoint!;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
async function createManagedWriteCheckpointResult(
|
|
455
|
+
bucketStorage: storage.SyncRulesBucketStorage,
|
|
456
|
+
checkpoint: storage.ManagedWriteCheckpointOptions
|
|
457
|
+
) {
|
|
458
|
+
const result = await bucketStorage.createManagedWriteCheckpoints([checkpoint]);
|
|
459
|
+
expect(result.writeCheckpoints.get(checkpoint.user_id)).not.toBeUndefined();
|
|
460
|
+
return result;
|
|
304
461
|
}
|