@powersync/service-core-tests 0.17.2 → 0.18.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 (46) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/dist/test-utils/AbstractStreamTestContext.d.ts +1 -1
  3. package/dist/test-utils/AbstractStreamTestContext.js +2 -3
  4. package/dist/test-utils/AbstractStreamTestContext.js.map +1 -1
  5. package/dist/test-utils/StorageDataHelpers.d.ts +3 -2
  6. package/dist/test-utils/StorageDataHelpers.js +40 -2
  7. package/dist/test-utils/StorageDataHelpers.js.map +1 -1
  8. package/dist/test-utils/general-utils.d.ts +3 -1
  9. package/dist/test-utils/general-utils.js +13 -1
  10. package/dist/test-utils/general-utils.js.map +1 -1
  11. package/dist/test-utils/storage-combinations.d.ts +13 -0
  12. package/dist/test-utils/storage-combinations.js +26 -0
  13. package/dist/test-utils/storage-combinations.js.map +1 -0
  14. package/dist/test-utils/stream_utils.d.ts +0 -1
  15. package/dist/test-utils/stream_utils.js +0 -10
  16. package/dist/test-utils/stream_utils.js.map +1 -1
  17. package/dist/test-utils/test-utils-index.d.ts +1 -0
  18. package/dist/test-utils/test-utils-index.js +1 -0
  19. package/dist/test-utils/test-utils-index.js.map +1 -1
  20. package/dist/tests/register-compacting-tests.js +15 -15
  21. package/dist/tests/register-compacting-tests.js.map +1 -1
  22. package/dist/tests/register-data-storage-checkpoint-tests.js +132 -14
  23. package/dist/tests/register-data-storage-checkpoint-tests.js.map +1 -1
  24. package/dist/tests/register-data-storage-data-tests.js +116 -96
  25. package/dist/tests/register-data-storage-data-tests.js.map +1 -1
  26. package/dist/tests/register-parameter-compacting-tests.js +3 -2
  27. package/dist/tests/register-parameter-compacting-tests.js.map +1 -1
  28. package/dist/tests/register-sync-tests.js +267 -82
  29. package/dist/tests/register-sync-tests.js.map +1 -1
  30. package/dist/tests/util.d.ts +6 -0
  31. package/dist/tests/util.js +12 -0
  32. package/dist/tests/util.js.map +1 -1
  33. package/package.json +4 -4
  34. package/src/test-utils/AbstractStreamTestContext.ts +2 -3
  35. package/src/test-utils/StorageDataHelpers.ts +45 -2
  36. package/src/test-utils/general-utils.ts +27 -3
  37. package/src/test-utils/storage-combinations.ts +51 -0
  38. package/src/test-utils/stream_utils.ts +0 -11
  39. package/src/test-utils/test-utils-index.ts +1 -0
  40. package/src/tests/register-compacting-tests.ts +15 -15
  41. package/src/tests/register-data-storage-checkpoint-tests.ts +161 -14
  42. package/src/tests/register-data-storage-data-tests.ts +132 -134
  43. package/src/tests/register-parameter-compacting-tests.ts +3 -2
  44. package/src/tests/register-sync-tests.ts +288 -84
  45. package/src/tests/util.ts +13 -0
  46. package/tsconfig.tsbuildinfo +1 -1
@@ -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,7 +37,10 @@ bucket_definitions:
36
37
  const abortController = new AbortController();
37
38
  context.onTestFinished(() => abortController.abort());
38
39
  const iter = bucketStorage
39
- .watchCheckpointChanges({ user_id: 'user1', signal: abortController.signal })
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);
@@ -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({ user_id: 'user1', signal: abortController.signal })
92
+ .watchCheckpointChanges({
93
+ user_id: 'user1',
94
+ signal: abortController.signal
95
+ })
89
96
  [Symbol.asyncIterator]();
90
97
 
91
98
  await writer.keepalive('5/0');
@@ -107,7 +114,7 @@ bucket_definitions:
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 effetively triggered with RouteAPI.createReplicationHead().
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('custom write checkpoints - checkpoint after write', async (context) => {
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
- bucketStorage.setWriteCheckpointMode(storage.WriteCheckpointMode.CUSTOM);
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({ user_id: 'user1', signal: abortController.signal })
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(storage.WriteCheckpointMode.CUSTOM);
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({ user_id: 'user1', signal: abortController.signal })
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(storage.WriteCheckpointMode.CUSTOM);
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({ user_id: 'user1', signal: abortController.signal })
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,14 +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
+ );
304
442
  }
305
443
 
306
444
  async function createManagedWriteCheckpoint(
307
445
  bucketStorage: storage.SyncRulesBucketStorage,
308
446
  checkpoint: storage.ManagedWriteCheckpointOptions
309
447
  ) {
310
- const checkpoints = await bucketStorage.createManagedWriteCheckpoints([checkpoint]);
311
- const writeCheckpoint = checkpoints.get(checkpoint.user_id);
448
+ const result = await createManagedWriteCheckpointResult(bucketStorage, checkpoint);
449
+ const writeCheckpoint = result.writeCheckpoints.get(checkpoint.user_id);
312
450
  expect(writeCheckpoint).not.toBeUndefined();
313
451
  return writeCheckpoint!;
314
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;
461
+ }