@powersync/service-core 0.0.0-dev-20240718134716 → 0.0.0-dev-20240725112650

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 (97) hide show
  1. package/CHANGELOG.md +11 -6
  2. package/dist/entry/cli-entry.js +2 -1
  3. package/dist/entry/cli-entry.js.map +1 -1
  4. package/dist/entry/commands/compact-action.d.ts +2 -0
  5. package/dist/entry/commands/compact-action.js +48 -0
  6. package/dist/entry/commands/compact-action.js.map +1 -0
  7. package/dist/entry/entry-index.d.ts +1 -0
  8. package/dist/entry/entry-index.js +1 -0
  9. package/dist/entry/entry-index.js.map +1 -1
  10. package/dist/metrics/Metrics.d.ts +4 -3
  11. package/dist/metrics/Metrics.js +51 -0
  12. package/dist/metrics/Metrics.js.map +1 -1
  13. package/dist/replication/WalStream.js +6 -8
  14. package/dist/replication/WalStream.js.map +1 -1
  15. package/dist/routes/configure-fastify.d.ts +883 -0
  16. package/dist/routes/configure-fastify.js +58 -0
  17. package/dist/routes/configure-fastify.js.map +1 -0
  18. package/dist/routes/configure-rsocket.d.ts +13 -0
  19. package/dist/routes/configure-rsocket.js +46 -0
  20. package/dist/routes/configure-rsocket.js.map +1 -0
  21. package/dist/routes/endpoints/socket-route.js +6 -14
  22. package/dist/routes/endpoints/socket-route.js.map +1 -1
  23. package/dist/routes/endpoints/sync-stream.js +4 -5
  24. package/dist/routes/endpoints/sync-stream.js.map +1 -1
  25. package/dist/routes/route-register.d.ts +1 -1
  26. package/dist/routes/route-register.js +1 -1
  27. package/dist/routes/route-register.js.map +1 -1
  28. package/dist/routes/router-socket.d.ts +4 -4
  29. package/dist/routes/router-socket.js.map +1 -1
  30. package/dist/routes/router.d.ts +1 -0
  31. package/dist/routes/router.js.map +1 -1
  32. package/dist/routes/routes-index.d.ts +2 -0
  33. package/dist/routes/routes-index.js +2 -0
  34. package/dist/routes/routes-index.js.map +1 -1
  35. package/dist/storage/BucketStorage.d.ts +31 -1
  36. package/dist/storage/BucketStorage.js.map +1 -1
  37. package/dist/storage/mongo/MongoCompactor.d.ts +40 -0
  38. package/dist/storage/mongo/MongoCompactor.js +292 -0
  39. package/dist/storage/mongo/MongoCompactor.js.map +1 -0
  40. package/dist/storage/mongo/MongoSyncBucketStorage.d.ts +3 -2
  41. package/dist/storage/mongo/MongoSyncBucketStorage.js +19 -13
  42. package/dist/storage/mongo/MongoSyncBucketStorage.js.map +1 -1
  43. package/dist/storage/mongo/models.d.ts +5 -4
  44. package/dist/storage/mongo/models.js.map +1 -1
  45. package/dist/storage/mongo/util.d.ts +3 -0
  46. package/dist/storage/mongo/util.js +22 -0
  47. package/dist/storage/mongo/util.js.map +1 -1
  48. package/dist/sync/RequestTracker.js +2 -3
  49. package/dist/sync/RequestTracker.js.map +1 -1
  50. package/dist/sync/sync-index.d.ts +1 -0
  51. package/dist/sync/sync-index.js +1 -0
  52. package/dist/sync/sync-index.js.map +1 -1
  53. package/dist/sync/sync.js +20 -7
  54. package/dist/sync/sync.js.map +1 -1
  55. package/dist/sync/util.js.map +1 -1
  56. package/dist/util/config/collectors/config-collector.d.ts +12 -0
  57. package/dist/util/config/collectors/config-collector.js +43 -0
  58. package/dist/util/config/collectors/config-collector.js.map +1 -1
  59. package/dist/util/config/compound-config-collector.d.ts +3 -29
  60. package/dist/util/config/compound-config-collector.js +22 -69
  61. package/dist/util/config/compound-config-collector.js.map +1 -1
  62. package/package.json +6 -4
  63. package/src/entry/cli-entry.ts +2 -1
  64. package/src/entry/commands/compact-action.ts +54 -0
  65. package/src/entry/entry-index.ts +1 -0
  66. package/src/metrics/Metrics.ts +67 -2
  67. package/src/replication/WalStream.ts +6 -10
  68. package/src/routes/configure-fastify.ts +102 -0
  69. package/src/routes/configure-rsocket.ts +59 -0
  70. package/src/routes/endpoints/socket-route.ts +6 -15
  71. package/src/routes/endpoints/sync-stream.ts +4 -5
  72. package/src/routes/route-register.ts +2 -2
  73. package/src/routes/router-socket.ts +5 -5
  74. package/src/routes/router.ts +2 -0
  75. package/src/routes/routes-index.ts +2 -0
  76. package/src/storage/BucketStorage.ts +36 -1
  77. package/src/storage/mongo/MongoCompactor.ts +371 -0
  78. package/src/storage/mongo/MongoSyncBucketStorage.ts +25 -14
  79. package/src/storage/mongo/models.ts +5 -4
  80. package/src/storage/mongo/util.ts +25 -0
  81. package/src/sync/RequestTracker.ts +3 -3
  82. package/src/sync/sync-index.ts +1 -0
  83. package/src/sync/sync.ts +21 -7
  84. package/src/sync/util.ts +1 -0
  85. package/src/util/config/collectors/config-collector.ts +48 -0
  86. package/src/util/config/compound-config-collector.ts +23 -87
  87. package/test/src/__snapshots__/sync.test.ts.snap +85 -0
  88. package/test/src/bucket_validation.test.ts +142 -0
  89. package/test/src/bucket_validation.ts +116 -0
  90. package/test/src/compacting.test.ts +207 -0
  91. package/test/src/data_storage.test.ts +19 -60
  92. package/test/src/slow_tests.test.ts +144 -102
  93. package/test/src/sync.test.ts +169 -29
  94. package/test/src/util.ts +71 -13
  95. package/test/src/wal_stream.test.ts +21 -16
  96. package/test/src/wal_stream_utils.ts +13 -4
  97. package/tsconfig.tsbuildinfo +1 -1
@@ -1,34 +1,18 @@
1
- import * as bson from 'bson';
1
+ import { RequestTracker } from '@/sync/RequestTracker.js';
2
+ import { StreamingSyncLine } from '@/util/protocol-types.js';
3
+ import { lsnMakeComparable } from '@powersync/service-jpgwire';
4
+ import { JSONBig } from '@powersync/service-jsonbig';
5
+ import { RequestParameters } from '@powersync/service-sync-rules';
6
+ import * as timers from 'timers/promises';
2
7
  import { describe, expect, test } from 'vitest';
3
8
  import { ZERO_LSN } from '../../src/replication/WalStream.js';
4
- import { SourceTable } from '../../src/storage/SourceTable.js';
5
- import { hashData } from '../../src/util/utils.js';
6
- import { MONGO_STORAGE_FACTORY, StorageFactory } from './util.js';
7
- import { JSONBig } from '@powersync/service-jsonbig';
8
9
  import { streamResponse } from '../../src/sync/sync.js';
9
- import * as timers from 'timers/promises';
10
- import { lsnMakeComparable } from '@powersync/service-jpgwire';
11
- import { RequestParameters } from '@powersync/service-sync-rules';
12
- import { RequestTracker } from '@/sync/RequestTracker.js';
10
+ import { makeTestTable, MONGO_STORAGE_FACTORY, StorageFactory } from './util.js';
13
11
 
14
12
  describe('sync - mongodb', function () {
15
13
  defineTests(MONGO_STORAGE_FACTORY);
16
14
  });
17
15
 
18
- function makeTestTable(name: string, columns?: string[] | undefined) {
19
- const relId = hashData('table', name, (columns ?? ['id']).join(','));
20
- const id = new bson.ObjectId('6544e3899293153fa7b38331');
21
- return new SourceTable(
22
- id,
23
- SourceTable.DEFAULT_TAG,
24
- relId,
25
- SourceTable.DEFAULT_SCHEMA,
26
- name,
27
- (columns ?? ['id']).map((column) => ({ name: column, typeOid: 25 })),
28
- true
29
- );
30
- }
31
-
32
16
  const TEST_TABLE = makeTestTable('test', ['id']);
33
17
 
34
18
  const BASIC_SYNC_RULES = `
@@ -251,15 +235,156 @@ function defineTests(factory: StorageFactory) {
251
235
  const expLines = await getCheckpointLines(iter);
252
236
  expect(expLines).toMatchSnapshot();
253
237
  });
238
+
239
+ test('compacting data - invalidate checkpoint', async () => {
240
+ // This tests a case of a compact operation invalidating a checkpoint in the
241
+ // middle of syncing data.
242
+ // This is expected to be rare in practice, but it is important to handle
243
+ // this case correctly to maintain consistency on the client.
244
+
245
+ const f = await factory();
246
+
247
+ const syncRules = await f.updateSyncRules({
248
+ content: BASIC_SYNC_RULES
249
+ });
250
+
251
+ const storage = await f.getInstance(syncRules.parsed());
252
+ await storage.setSnapshotDone(ZERO_LSN);
253
+ await storage.autoActivate();
254
+
255
+ await storage.startBatch({}, async (batch) => {
256
+ await batch.save({
257
+ sourceTable: TEST_TABLE,
258
+ tag: 'insert',
259
+ after: {
260
+ id: 't1',
261
+ description: 'Test 1'
262
+ }
263
+ });
264
+
265
+ await batch.save({
266
+ sourceTable: TEST_TABLE,
267
+ tag: 'insert',
268
+ after: {
269
+ id: 't2',
270
+ description: 'Test 2'
271
+ }
272
+ });
273
+
274
+ await batch.commit(lsnMakeComparable('0/1'));
275
+ });
276
+
277
+ const stream = streamResponse({
278
+ storage: f,
279
+ params: {
280
+ buckets: [],
281
+ include_checksum: true,
282
+ raw_data: true
283
+ },
284
+ tracker,
285
+ syncParams: new RequestParameters({ sub: '' }, {}),
286
+ token: { exp: Date.now() / 1000 + 10 } as any
287
+ });
288
+
289
+ const iter = stream[Symbol.asyncIterator]();
290
+
291
+ // Only consume the first "checkpoint" message, and pause before receiving data.
292
+ const lines = await consumeIterator(iter, { consume: false, isDone: (line) => (line as any)?.checkpoint != null });
293
+ expect(lines).toMatchSnapshot();
294
+ expect(lines[0]).toEqual({
295
+ checkpoint: expect.objectContaining({
296
+ last_op_id: '2'
297
+ })
298
+ });
299
+
300
+ // Now we save additional data AND compact before continuing.
301
+ // This invalidates the checkpoint we've received above.
302
+
303
+ await storage.startBatch({}, async (batch) => {
304
+ await batch.save({
305
+ sourceTable: TEST_TABLE,
306
+ tag: 'update',
307
+ after: {
308
+ id: 't1',
309
+ description: 'Test 1b'
310
+ }
311
+ });
312
+
313
+ await batch.save({
314
+ sourceTable: TEST_TABLE,
315
+ tag: 'update',
316
+ after: {
317
+ id: 't2',
318
+ description: 'Test 2b'
319
+ }
320
+ });
321
+
322
+ await batch.commit(lsnMakeComparable('0/2'));
323
+ });
324
+
325
+ await storage.compact();
326
+
327
+ const lines2 = await getCheckpointLines(iter, { consume: true });
328
+
329
+ // Snapshot test checks for changes in general.
330
+ // The tests after that documents the specific things we're looking for
331
+ // in this test.
332
+ expect(lines2).toMatchSnapshot();
333
+
334
+ expect(lines2[0]).toEqual({
335
+ data: expect.objectContaining({
336
+ has_more: false,
337
+ data: [
338
+ // The first two ops have been replaced by a single CLEAR op
339
+ expect.objectContaining({
340
+ op: 'CLEAR'
341
+ })
342
+ ]
343
+ })
344
+ });
345
+
346
+ // Note: No checkpoint_complete here, since the checkpoint has been
347
+ // invalidated by the CLEAR op.
348
+
349
+ expect(lines2[1]).toEqual({
350
+ checkpoint_diff: expect.objectContaining({
351
+ last_op_id: '4'
352
+ })
353
+ });
354
+
355
+ expect(lines2[2]).toEqual({
356
+ data: expect.objectContaining({
357
+ has_more: false,
358
+ data: [
359
+ expect.objectContaining({
360
+ op: 'PUT'
361
+ }),
362
+ expect.objectContaining({
363
+ op: 'PUT'
364
+ })
365
+ ]
366
+ })
367
+ });
368
+
369
+ // Now we get a checkpoint_complete
370
+ expect(lines2[3]).toEqual({
371
+ checkpoint_complete: expect.objectContaining({
372
+ last_op_id: '4'
373
+ })
374
+ });
375
+ });
254
376
  }
255
377
 
256
378
  /**
257
- * Get lines on an iterator until the next checkpoint_complete.
379
+ * Get lines on an iterator until isDone(line) == true.
258
380
  *
259
- * Does not stop the iterator.
381
+ * Does not stop the iterator unless options.consume is true.
260
382
  */
261
- async function getCheckpointLines(iter: AsyncIterator<any>, options?: { consume?: boolean }): Promise<any[]> {
262
- let lines: any[] = [];
383
+ async function consumeIterator<T>(
384
+ iter: AsyncIterator<T>,
385
+ options: { isDone: (line: T) => boolean; consume?: boolean }
386
+ ) {
387
+ let lines: T[] = [];
263
388
  try {
264
389
  const controller = new AbortController();
265
390
  const timeout = timers.setTimeout(1500, { value: null, done: 'timeout' }, { signal: controller.signal });
@@ -274,7 +399,7 @@ async function getCheckpointLines(iter: AsyncIterator<any>, options?: { consume?
274
399
  if (value) {
275
400
  lines.push(value);
276
401
  }
277
- if (done || value.checkpoint_complete) {
402
+ if (done || options.isDone(value)) {
278
403
  break;
279
404
  }
280
405
  }
@@ -292,11 +417,26 @@ async function getCheckpointLines(iter: AsyncIterator<any>, options?: { consume?
292
417
  }
293
418
  }
294
419
 
420
+ /**
421
+ * Get lines on an iterator until the next checkpoint_complete.
422
+ *
423
+ * Does not stop the iterator unless options.consume is true.
424
+ */
425
+ async function getCheckpointLines(
426
+ iter: AsyncIterator<StreamingSyncLine | string | null>,
427
+ options?: { consume?: boolean }
428
+ ) {
429
+ return consumeIterator(iter, {
430
+ consume: options?.consume,
431
+ isDone: (line) => (line as any)?.checkpoint_complete
432
+ });
433
+ }
434
+
295
435
  /**
296
436
  * Get lines on an iterator until the next checkpoint_complete.
297
437
  *
298
438
  * Stops the iterator afterwards.
299
439
  */
300
- async function consumeCheckpointLines(iterable: AsyncIterable<any>): Promise<any[]> {
440
+ async function consumeCheckpointLines(iterable: AsyncIterable<StreamingSyncLine | string | null>): Promise<any[]> {
301
441
  return getCheckpointLines(iterable[Symbol.asyncIterator](), { consume: true });
302
442
  }
package/test/src/util.ts CHANGED
@@ -1,26 +1,24 @@
1
1
  import * as pgwire from '@powersync/service-jpgwire';
2
2
  import { normalizeConnection } from '@powersync/service-types';
3
3
  import * as mongo from 'mongodb';
4
- import { BucketStorageFactory } from '../../src/storage/BucketStorage.js';
4
+ import { BucketStorageFactory, SyncBucketDataBatch } from '../../src/storage/BucketStorage.js';
5
5
  import { MongoBucketStorage } from '../../src/storage/MongoBucketStorage.js';
6
6
  import { PowerSyncMongo } from '../../src/storage/mongo/db.js';
7
7
  import { escapeIdentifier } from '../../src/util/pgwire_utils.js';
8
8
  import { env } from './env.js';
9
9
  import { Metrics } from '@/metrics/Metrics.js';
10
- import { container } from '@powersync/lib-services-framework';
11
- import { MeterProvider } from '@opentelemetry/sdk-metrics';
12
- import { PrometheusExporter } from '@opentelemetry/exporter-prometheus';
10
+ import { hashData } from '@/util/utils.js';
11
+ import { SourceTable } from '@/storage/SourceTable.js';
12
+ import * as bson from 'bson';
13
+ import { SyncBucketData } from '@/util/protocol-types.js';
13
14
 
14
15
  // The metrics need to be initialised before they can be used
15
- const prometheus = new PrometheusExporter();
16
- const metrics = new Metrics(
17
- new MeterProvider({
18
- readers: [prometheus]
19
- }),
20
- prometheus
21
- );
22
- container.register(Metrics, metrics);
23
- metrics.resetCounters();
16
+ await Metrics.initialise({
17
+ disable_telemetry_sharing: true,
18
+ powersync_instance_id: 'test',
19
+ internal_metrics_endpoint: 'unused.for.tests.com'
20
+ });
21
+ Metrics.getInstance().resetCounters();
24
22
 
25
23
  export const TEST_URI = env.PG_TEST_URL;
26
24
 
@@ -33,6 +31,10 @@ export const MONGO_STORAGE_FACTORY: StorageFactory = async () => {
33
31
  };
34
32
 
35
33
  export async function clearTestDb(db: pgwire.PgClient) {
34
+ await db.query(
35
+ "select pg_drop_replication_slot(slot_name) from pg_replication_slots where active = false and slot_name like 'test_%'"
36
+ );
37
+
36
38
  await db.query(`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`);
37
39
  try {
38
40
  await db.query(`DROP PUBLICATION powersync`);
@@ -80,3 +82,59 @@ export async function connectMongo() {
80
82
  const db = new PowerSyncMongo(client);
81
83
  return db;
82
84
  }
85
+
86
+ export function makeTestTable(name: string, columns?: string[] | undefined) {
87
+ const relId = hashData('table', name, (columns ?? ['id']).join(','));
88
+ const id = new bson.ObjectId('6544e3899293153fa7b38331');
89
+ return new SourceTable(
90
+ id,
91
+ SourceTable.DEFAULT_TAG,
92
+ relId,
93
+ SourceTable.DEFAULT_SCHEMA,
94
+ name,
95
+ (columns ?? ['id']).map((column) => ({ name: column, typeOid: 25 })),
96
+ true
97
+ );
98
+ }
99
+
100
+ export function getBatchData(batch: SyncBucketData[] | SyncBucketDataBatch[] | SyncBucketDataBatch) {
101
+ const first = getFirst(batch);
102
+ if (first == null) {
103
+ return [];
104
+ }
105
+ return first.data.map((d) => {
106
+ return {
107
+ op_id: d.op_id,
108
+ op: d.op,
109
+ object_id: d.object_id,
110
+ checksum: d.checksum
111
+ };
112
+ });
113
+ }
114
+
115
+ export function getBatchMeta(batch: SyncBucketData[] | SyncBucketDataBatch[] | SyncBucketDataBatch) {
116
+ const first = getFirst(batch);
117
+ if (first == null) {
118
+ return null;
119
+ }
120
+ return {
121
+ has_more: first.has_more,
122
+ after: first.after,
123
+ next_after: first.next_after
124
+ };
125
+ }
126
+
127
+ function getFirst(batch: SyncBucketData[] | SyncBucketDataBatch[] | SyncBucketDataBatch): SyncBucketData | null {
128
+ if (!Array.isArray(batch)) {
129
+ return batch.batch;
130
+ }
131
+ if (batch.length == 0) {
132
+ return null;
133
+ }
134
+ let first = batch[0];
135
+ if ((first as SyncBucketDataBatch).batch != null) {
136
+ return (first as SyncBucketDataBatch).batch;
137
+ } else {
138
+ return first as SyncBucketData;
139
+ }
140
+ }
@@ -5,7 +5,6 @@ import { MONGO_STORAGE_FACTORY } from './util.js';
5
5
  import { putOp, removeOp, walStreamTest } from './wal_stream_utils.js';
6
6
  import { pgwireRows } from '@powersync/service-jpgwire';
7
7
  import { Metrics } from '@/metrics/Metrics.js';
8
- import { container } from '@powersync/lib-services-framework';
9
8
 
10
9
  type StorageFactory = () => Promise<BucketStorageFactory>;
11
10
 
@@ -42,9 +41,10 @@ bucket_definitions:
42
41
 
43
42
  await context.replicateSnapshot();
44
43
 
45
- const metrics = container.getImplementation(Metrics);
46
- const startRowCount = (await metrics.getMetricValueForTests('powersync_rows_replicated_total')) ?? 0;
47
- const startTxCount = (await metrics.getMetricValueForTests('powersync_transactions_replicated_total')) ?? 0;
44
+ const startRowCount =
45
+ (await Metrics.getInstance().getMetricValueForTests('powersync_rows_replicated_total')) ?? 0;
46
+ const startTxCount =
47
+ (await Metrics.getInstance().getMetricValueForTests('powersync_transactions_replicated_total')) ?? 0;
48
48
 
49
49
  context.startStreaming();
50
50
 
@@ -59,8 +59,9 @@ bucket_definitions:
59
59
  expect(data).toMatchObject([
60
60
  putOp('test_data', { id: test_id, description: 'test1', num: 1152921504606846976n })
61
61
  ]);
62
- const endRowCount = (await metrics.getMetricValueForTests('powersync_rows_replicated_total')) ?? 0;
63
- const endTxCount = (await metrics.getMetricValueForTests('powersync_transactions_replicated_total')) ?? 0;
62
+ const endRowCount = (await Metrics.getInstance().getMetricValueForTests('powersync_rows_replicated_total')) ?? 0;
63
+ const endTxCount =
64
+ (await Metrics.getInstance().getMetricValueForTests('powersync_transactions_replicated_total')) ?? 0;
64
65
  expect(endRowCount - startRowCount).toEqual(1);
65
66
  expect(endTxCount - startTxCount).toEqual(1);
66
67
  })
@@ -82,9 +83,10 @@ bucket_definitions:
82
83
 
83
84
  await context.replicateSnapshot();
84
85
 
85
- const metrics = container.getImplementation(Metrics);
86
- const startRowCount = (await metrics.getMetricValueForTests('powersync_rows_replicated_total')) ?? 0;
87
- const startTxCount = (await metrics.getMetricValueForTests('powersync_transactions_replicated_total')) ?? 0;
86
+ const startRowCount =
87
+ (await Metrics.getInstance().getMetricValueForTests('powersync_rows_replicated_total')) ?? 0;
88
+ const startTxCount =
89
+ (await Metrics.getInstance().getMetricValueForTests('powersync_transactions_replicated_total')) ?? 0;
88
90
 
89
91
  context.startStreaming();
90
92
 
@@ -95,8 +97,9 @@ bucket_definitions:
95
97
  const data = await context.getBucketData('global[]');
96
98
 
97
99
  expect(data).toMatchObject([putOp('test_DATA', { id: test_id, description: 'test1' })]);
98
- const endRowCount = (await metrics.getMetricValueForTests('powersync_rows_replicated_total')) ?? 0;
99
- const endTxCount = (await metrics.getMetricValueForTests('powersync_transactions_replicated_total')) ?? 0;
100
+ const endRowCount = (await Metrics.getInstance().getMetricValueForTests('powersync_rows_replicated_total')) ?? 0;
101
+ const endTxCount =
102
+ (await Metrics.getInstance().getMetricValueForTests('powersync_transactions_replicated_total')) ?? 0;
100
103
  expect(endRowCount - startRowCount).toEqual(1);
101
104
  expect(endTxCount - startTxCount).toEqual(1);
102
105
  })
@@ -290,9 +293,10 @@ bucket_definitions:
290
293
 
291
294
  await context.replicateSnapshot();
292
295
 
293
- const metrics = container.getImplementation(Metrics);
294
- const startRowCount = (await metrics.getMetricValueForTests('powersync_rows_replicated_total')) ?? 0;
295
- const startTxCount = (await metrics.getMetricValueForTests('powersync_transactions_replicated_total')) ?? 0;
296
+ const startRowCount =
297
+ (await Metrics.getInstance().getMetricValueForTests('powersync_rows_replicated_total')) ?? 0;
298
+ const startTxCount =
299
+ (await Metrics.getInstance().getMetricValueForTests('powersync_transactions_replicated_total')) ?? 0;
296
300
 
297
301
  context.startStreaming();
298
302
 
@@ -303,8 +307,9 @@ bucket_definitions:
303
307
  const data = await context.getBucketData('global[]');
304
308
 
305
309
  expect(data).toMatchObject([]);
306
- const endRowCount = (await metrics.getMetricValueForTests('powersync_rows_replicated_total')) ?? 0;
307
- const endTxCount = (await metrics.getMetricValueForTests('powersync_transactions_replicated_total')) ?? 0;
310
+ const endRowCount = (await Metrics.getInstance().getMetricValueForTests('powersync_rows_replicated_total')) ?? 0;
311
+ const endTxCount =
312
+ (await Metrics.getInstance().getMetricValueForTests('powersync_transactions_replicated_total')) ?? 0;
308
313
 
309
314
  // There was a transaction, but we should not replicate any actual data
310
315
  expect(endRowCount - startRowCount).toEqual(0);
@@ -20,9 +20,7 @@ export function walStreamTest(
20
20
  return async () => {
21
21
  const f = await factory();
22
22
  const connections = new PgManager(TEST_CONNECTION_OPTIONS, {});
23
- await connections.pool.query(
24
- 'select pg_drop_replication_slot(slot_name) from pg_replication_slots where active = false'
25
- );
23
+
26
24
  await clearTestDb(connections.pool);
27
25
  const context = new WalStreamTestContext(f, connections);
28
26
  try {
@@ -113,7 +111,7 @@ export class WalStreamTestContext {
113
111
  const map = new Map<string, string>([[bucket, start]]);
114
112
  const batch = await this.storage!.getBucketDataBatch(checkpoint, map);
115
113
  const batches = await fromAsync(batch);
116
- return batches[0]?.data ?? [];
114
+ return batches[0]?.batch.data ?? [];
117
115
  }
118
116
  }
119
117
 
@@ -145,3 +143,14 @@ export async function fromAsync<T>(source: Iterable<T> | AsyncIterable<T>): Prom
145
143
  }
146
144
  return items;
147
145
  }
146
+
147
+ export async function oneFromAsync<T>(source: Iterable<T> | AsyncIterable<T>): Promise<T> {
148
+ const items: T[] = [];
149
+ for await (const item of source) {
150
+ items.push(item);
151
+ }
152
+ if (items.length != 1) {
153
+ throw new Error(`One item expected, got: ${items.length}`);
154
+ }
155
+ return items[0];
156
+ }