@powersync/service-core 0.0.0-dev-20240709124106 → 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.
- package/CHANGELOG.md +39 -2
- package/dist/entry/cli-entry.js +2 -1
- package/dist/entry/cli-entry.js.map +1 -1
- package/dist/entry/commands/compact-action.d.ts +2 -0
- package/dist/entry/commands/compact-action.js +48 -0
- package/dist/entry/commands/compact-action.js.map +1 -0
- package/dist/entry/entry-index.d.ts +1 -0
- package/dist/entry/entry-index.js +1 -0
- package/dist/entry/entry-index.js.map +1 -1
- package/dist/routes/configure-fastify.d.ts +883 -0
- package/dist/routes/configure-fastify.js +58 -0
- package/dist/routes/configure-fastify.js.map +1 -0
- package/dist/routes/configure-rsocket.d.ts +13 -0
- package/dist/routes/configure-rsocket.js +46 -0
- package/dist/routes/configure-rsocket.js.map +1 -0
- package/dist/routes/endpoints/socket-route.js +10 -9
- package/dist/routes/endpoints/socket-route.js.map +1 -1
- package/dist/routes/endpoints/sync-stream.js +9 -1
- package/dist/routes/endpoints/sync-stream.js.map +1 -1
- package/dist/routes/route-register.d.ts +1 -1
- package/dist/routes/route-register.js +2 -1
- package/dist/routes/route-register.js.map +1 -1
- package/dist/routes/router-socket.d.ts +4 -4
- package/dist/routes/router-socket.js.map +1 -1
- package/dist/routes/router.d.ts +1 -0
- package/dist/routes/router.js.map +1 -1
- package/dist/routes/routes-index.d.ts +2 -0
- package/dist/routes/routes-index.js +2 -0
- package/dist/routes/routes-index.js.map +1 -1
- package/dist/storage/BucketStorage.d.ts +31 -1
- package/dist/storage/BucketStorage.js.map +1 -1
- package/dist/storage/mongo/MongoCompactor.d.ts +40 -0
- package/dist/storage/mongo/MongoCompactor.js +292 -0
- package/dist/storage/mongo/MongoCompactor.js.map +1 -0
- package/dist/storage/mongo/MongoSyncBucketStorage.d.ts +3 -2
- package/dist/storage/mongo/MongoSyncBucketStorage.js +19 -13
- package/dist/storage/mongo/MongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/mongo/models.d.ts +5 -4
- package/dist/storage/mongo/models.js.map +1 -1
- package/dist/storage/mongo/util.d.ts +3 -0
- package/dist/storage/mongo/util.js +22 -0
- package/dist/storage/mongo/util.js.map +1 -1
- package/dist/sync/RequestTracker.d.ts +9 -0
- package/dist/sync/RequestTracker.js +19 -0
- package/dist/sync/RequestTracker.js.map +1 -0
- package/dist/sync/sync-index.d.ts +1 -0
- package/dist/sync/sync-index.js +1 -0
- package/dist/sync/sync-index.js.map +1 -1
- package/dist/sync/sync.d.ts +2 -0
- package/dist/sync/sync.js +51 -18
- package/dist/sync/sync.js.map +1 -1
- package/dist/sync/util.d.ts +2 -1
- package/dist/sync/util.js +2 -3
- package/dist/sync/util.js.map +1 -1
- package/package.json +6 -6
- package/src/entry/cli-entry.ts +2 -1
- package/src/entry/commands/compact-action.ts +54 -0
- package/src/entry/entry-index.ts +1 -0
- package/src/routes/configure-fastify.ts +102 -0
- package/src/routes/configure-rsocket.ts +59 -0
- package/src/routes/endpoints/socket-route.ts +10 -9
- package/src/routes/endpoints/sync-stream.ts +10 -1
- package/src/routes/route-register.ts +3 -2
- package/src/routes/router-socket.ts +5 -5
- package/src/routes/router.ts +2 -0
- package/src/routes/routes-index.ts +2 -0
- package/src/storage/BucketStorage.ts +36 -1
- package/src/storage/mongo/MongoCompactor.ts +371 -0
- package/src/storage/mongo/MongoSyncBucketStorage.ts +25 -14
- package/src/storage/mongo/models.ts +5 -4
- package/src/storage/mongo/util.ts +25 -0
- package/src/sync/RequestTracker.ts +21 -0
- package/src/sync/sync-index.ts +1 -0
- package/src/sync/sync.ts +61 -17
- package/src/sync/util.ts +6 -2
- package/test/src/__snapshots__/sync.test.ts.snap +85 -0
- package/test/src/bucket_validation.test.ts +142 -0
- package/test/src/bucket_validation.ts +116 -0
- package/test/src/compacting.test.ts +207 -0
- package/test/src/data_storage.test.ts +19 -60
- package/test/src/slow_tests.test.ts +144 -102
- package/test/src/sync.test.ts +176 -28
- package/test/src/util.ts +65 -1
- package/test/src/wal_stream_utils.ts +13 -4
- package/tsconfig.tsbuildinfo +1 -1
package/test/src/sync.test.ts
CHANGED
|
@@ -1,33 +1,18 @@
|
|
|
1
|
-
import
|
|
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
|
|
10
|
-
import { lsnMakeComparable } from '@powersync/service-jpgwire';
|
|
11
|
-
import { RequestParameters } from '@powersync/service-sync-rules';
|
|
10
|
+
import { makeTestTable, MONGO_STORAGE_FACTORY, StorageFactory } from './util.js';
|
|
12
11
|
|
|
13
12
|
describe('sync - mongodb', function () {
|
|
14
13
|
defineTests(MONGO_STORAGE_FACTORY);
|
|
15
14
|
});
|
|
16
15
|
|
|
17
|
-
function makeTestTable(name: string, columns?: string[] | undefined) {
|
|
18
|
-
const relId = hashData('table', name, (columns ?? ['id']).join(','));
|
|
19
|
-
const id = new bson.ObjectId('6544e3899293153fa7b38331');
|
|
20
|
-
return new SourceTable(
|
|
21
|
-
id,
|
|
22
|
-
SourceTable.DEFAULT_TAG,
|
|
23
|
-
relId,
|
|
24
|
-
SourceTable.DEFAULT_SCHEMA,
|
|
25
|
-
name,
|
|
26
|
-
(columns ?? ['id']).map((column) => ({ name: column, typeOid: 25 })),
|
|
27
|
-
true
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
16
|
const TEST_TABLE = makeTestTable('test', ['id']);
|
|
32
17
|
|
|
33
18
|
const BASIC_SYNC_RULES = `
|
|
@@ -38,6 +23,8 @@ bucket_definitions:
|
|
|
38
23
|
`;
|
|
39
24
|
|
|
40
25
|
function defineTests(factory: StorageFactory) {
|
|
26
|
+
const tracker = new RequestTracker();
|
|
27
|
+
|
|
41
28
|
test('sync global data', async () => {
|
|
42
29
|
const f = await factory();
|
|
43
30
|
|
|
@@ -78,6 +65,7 @@ function defineTests(factory: StorageFactory) {
|
|
|
78
65
|
include_checksum: true,
|
|
79
66
|
raw_data: true
|
|
80
67
|
},
|
|
68
|
+
tracker,
|
|
81
69
|
syncParams: new RequestParameters({ sub: '' }, {}),
|
|
82
70
|
token: { exp: Date.now() / 1000 + 10 } as any
|
|
83
71
|
});
|
|
@@ -118,6 +106,7 @@ function defineTests(factory: StorageFactory) {
|
|
|
118
106
|
include_checksum: true,
|
|
119
107
|
raw_data: false
|
|
120
108
|
},
|
|
109
|
+
tracker,
|
|
121
110
|
syncParams: new RequestParameters({ sub: '' }, {}),
|
|
122
111
|
token: { exp: Date.now() / 1000 + 10 } as any
|
|
123
112
|
});
|
|
@@ -146,6 +135,7 @@ function defineTests(factory: StorageFactory) {
|
|
|
146
135
|
include_checksum: true,
|
|
147
136
|
raw_data: true
|
|
148
137
|
},
|
|
138
|
+
tracker,
|
|
149
139
|
syncParams: new RequestParameters({ sub: '' }, {}),
|
|
150
140
|
token: { exp: 0 } as any
|
|
151
141
|
});
|
|
@@ -172,6 +162,7 @@ function defineTests(factory: StorageFactory) {
|
|
|
172
162
|
include_checksum: true,
|
|
173
163
|
raw_data: true
|
|
174
164
|
},
|
|
165
|
+
tracker,
|
|
175
166
|
syncParams: new RequestParameters({ sub: '' }, {}),
|
|
176
167
|
token: { exp: Date.now() / 1000 + 10 } as any
|
|
177
168
|
});
|
|
@@ -232,6 +223,7 @@ function defineTests(factory: StorageFactory) {
|
|
|
232
223
|
include_checksum: true,
|
|
233
224
|
raw_data: true
|
|
234
225
|
},
|
|
226
|
+
tracker,
|
|
235
227
|
syncParams: new RequestParameters({ sub: '' }, {}),
|
|
236
228
|
token: { exp: exp } as any
|
|
237
229
|
});
|
|
@@ -243,15 +235,156 @@ function defineTests(factory: StorageFactory) {
|
|
|
243
235
|
const expLines = await getCheckpointLines(iter);
|
|
244
236
|
expect(expLines).toMatchSnapshot();
|
|
245
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
|
+
});
|
|
246
376
|
}
|
|
247
377
|
|
|
248
378
|
/**
|
|
249
|
-
* Get lines on an iterator until
|
|
379
|
+
* Get lines on an iterator until isDone(line) == true.
|
|
250
380
|
*
|
|
251
|
-
* Does not stop the iterator.
|
|
381
|
+
* Does not stop the iterator unless options.consume is true.
|
|
252
382
|
*/
|
|
253
|
-
async function
|
|
254
|
-
|
|
383
|
+
async function consumeIterator<T>(
|
|
384
|
+
iter: AsyncIterator<T>,
|
|
385
|
+
options: { isDone: (line: T) => boolean; consume?: boolean }
|
|
386
|
+
) {
|
|
387
|
+
let lines: T[] = [];
|
|
255
388
|
try {
|
|
256
389
|
const controller = new AbortController();
|
|
257
390
|
const timeout = timers.setTimeout(1500, { value: null, done: 'timeout' }, { signal: controller.signal });
|
|
@@ -266,7 +399,7 @@ async function getCheckpointLines(iter: AsyncIterator<any>, options?: { consume?
|
|
|
266
399
|
if (value) {
|
|
267
400
|
lines.push(value);
|
|
268
401
|
}
|
|
269
|
-
if (done || value
|
|
402
|
+
if (done || options.isDone(value)) {
|
|
270
403
|
break;
|
|
271
404
|
}
|
|
272
405
|
}
|
|
@@ -284,11 +417,26 @@ async function getCheckpointLines(iter: AsyncIterator<any>, options?: { consume?
|
|
|
284
417
|
}
|
|
285
418
|
}
|
|
286
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
|
+
|
|
287
435
|
/**
|
|
288
436
|
* Get lines on an iterator until the next checkpoint_complete.
|
|
289
437
|
*
|
|
290
438
|
* Stops the iterator afterwards.
|
|
291
439
|
*/
|
|
292
|
-
async function consumeCheckpointLines(iterable: AsyncIterable<
|
|
440
|
+
async function consumeCheckpointLines(iterable: AsyncIterable<StreamingSyncLine | string | null>): Promise<any[]> {
|
|
293
441
|
return getCheckpointLines(iterable[Symbol.asyncIterator](), { consume: true });
|
|
294
442
|
}
|
package/test/src/util.ts
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
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 { 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';
|
|
10
14
|
|
|
11
15
|
// The metrics need to be initialised before they can be used
|
|
12
16
|
await Metrics.initialise({
|
|
@@ -27,6 +31,10 @@ export const MONGO_STORAGE_FACTORY: StorageFactory = async () => {
|
|
|
27
31
|
};
|
|
28
32
|
|
|
29
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
|
+
|
|
30
38
|
await db.query(`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`);
|
|
31
39
|
try {
|
|
32
40
|
await db.query(`DROP PUBLICATION powersync`);
|
|
@@ -74,3 +82,59 @@ export async function connectMongo() {
|
|
|
74
82
|
const db = new PowerSyncMongo(client);
|
|
75
83
|
return db;
|
|
76
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
|
+
}
|
|
@@ -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
|
-
|
|
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
|
+
}
|