@powersync/service-module-postgres 0.0.0-dev-20250304151813 → 0.0.0-dev-20250306152715
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 +25 -5
- package/dist/module/PostgresModule.d.ts +1 -1
- package/dist/module/PostgresModule.js +8 -9
- package/dist/module/PostgresModule.js.map +1 -1
- package/dist/replication/WalStream.d.ts +3 -1
- package/dist/replication/WalStream.js +10 -7
- package/dist/replication/WalStream.js.map +1 -1
- package/dist/replication/WalStreamReplicationJob.js +1 -0
- package/dist/replication/WalStreamReplicationJob.js.map +1 -1
- package/dist/replication/WalStreamReplicator.js +1 -0
- package/dist/replication/WalStreamReplicator.js.map +1 -1
- package/package.json +8 -8
- package/src/module/PostgresModule.ts +8 -10
- package/src/replication/WalStream.ts +11 -8
- package/src/replication/WalStreamReplicationJob.ts +1 -0
- package/src/replication/WalStreamReplicator.ts +1 -0
- package/test/src/large_batch.test.ts +5 -3
- package/test/src/setup.ts +2 -3
- package/test/src/slow_tests.test.ts +20 -13
- package/test/src/util.ts +2 -2
- package/test/src/wal_stream.test.ts +15 -14
- package/test/src/wal_stream_utils.ts +32 -14
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as bson from 'bson';
|
|
2
|
-
import { afterEach, describe, expect, test } from 'vitest';
|
|
2
|
+
import { afterEach, beforeAll, describe, expect, test } from 'vitest';
|
|
3
3
|
import { WalStream, WalStreamOptions } from '../../src/replication/WalStream.js';
|
|
4
4
|
import { env } from './env.js';
|
|
5
5
|
import {
|
|
@@ -15,8 +15,8 @@ import * as pgwire from '@powersync/service-jpgwire';
|
|
|
15
15
|
import { SqliteRow } from '@powersync/service-sync-rules';
|
|
16
16
|
|
|
17
17
|
import { PgManager } from '@module/replication/PgManager.js';
|
|
18
|
-
import { storage } from '@powersync/service-core';
|
|
19
|
-
import { test_utils } from '@powersync/service-core-tests';
|
|
18
|
+
import { createCoreReplicationMetrics, initializeCoreReplicationMetrics, storage } from '@powersync/service-core';
|
|
19
|
+
import { METRICS_HELPER, test_utils } from '@powersync/service-core-tests';
|
|
20
20
|
import * as mongo_storage from '@powersync/service-module-mongodb-storage';
|
|
21
21
|
import * as postgres_storage from '@powersync/service-module-postgres-storage';
|
|
22
22
|
import * as timers from 'node:timers/promises';
|
|
@@ -49,6 +49,11 @@ function defineSlowTests(factory: storage.TestStorageFactory) {
|
|
|
49
49
|
let abortController: AbortController | undefined;
|
|
50
50
|
let streamPromise: Promise<void> | undefined;
|
|
51
51
|
|
|
52
|
+
beforeAll(async () => {
|
|
53
|
+
createCoreReplicationMetrics(METRICS_HELPER.metricsEngine);
|
|
54
|
+
initializeCoreReplicationMetrics(METRICS_HELPER.metricsEngine);
|
|
55
|
+
});
|
|
56
|
+
|
|
52
57
|
afterEach(async () => {
|
|
53
58
|
// This cleans up, similar to WalStreamTestContext.dispose().
|
|
54
59
|
// These tests are a little more complex than what is supported by WalStreamTestContext.
|
|
@@ -98,7 +103,8 @@ bucket_definitions:
|
|
|
98
103
|
const options: WalStreamOptions = {
|
|
99
104
|
abort_signal: abortController.signal,
|
|
100
105
|
connections,
|
|
101
|
-
storage: storage
|
|
106
|
+
storage: storage,
|
|
107
|
+
metrics: METRICS_HELPER.metricsEngine
|
|
102
108
|
};
|
|
103
109
|
walStream = new WalStream(options);
|
|
104
110
|
|
|
@@ -178,7 +184,7 @@ bucket_definitions:
|
|
|
178
184
|
break;
|
|
179
185
|
}
|
|
180
186
|
|
|
181
|
-
const checkpoint =
|
|
187
|
+
const checkpoint = (await storage.getCheckpoint()).checkpoint;
|
|
182
188
|
if (f instanceof mongo_storage.storage.MongoBucketStorage) {
|
|
183
189
|
const opsBefore = (await f.db.bucket_data.find().sort({ _id: 1 }).toArray())
|
|
184
190
|
.filter((row) => row._id.o <= checkpoint)
|
|
@@ -344,13 +350,14 @@ bucket_definitions:
|
|
|
344
350
|
const connections = new PgManager(TEST_CONNECTION_OPTIONS, {});
|
|
345
351
|
const replicationConnection = await connections.replicationConnection();
|
|
346
352
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
353
|
+
abortController = new AbortController();
|
|
354
|
+
const options: WalStreamOptions = {
|
|
355
|
+
abort_signal: abortController.signal,
|
|
356
|
+
connections,
|
|
357
|
+
storage: storage,
|
|
358
|
+
metrics: METRICS_HELPER.metricsEngine
|
|
359
|
+
};
|
|
360
|
+
walStream = new WalStream(options);
|
|
354
361
|
|
|
355
362
|
await storage.clear();
|
|
356
363
|
|
|
@@ -403,7 +410,7 @@ bucket_definitions:
|
|
|
403
410
|
getClientCheckpoint(pool, storage.factory, { timeout: TIMEOUT_MARGIN_MS }),
|
|
404
411
|
streamPromise
|
|
405
412
|
]);
|
|
406
|
-
if (
|
|
413
|
+
if (checkpoint == null) {
|
|
407
414
|
// This indicates an issue with the test setup - streamingPromise completed instead
|
|
408
415
|
// of getClientCheckpoint()
|
|
409
416
|
throw new Error('Test failure - streamingPromise completed');
|
package/test/src/util.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { PostgresRouteAPIAdapter } from '@module/api/PostgresRouteAPIAdapter.js'
|
|
|
2
2
|
import * as types from '@module/types/types.js';
|
|
3
3
|
import * as lib_postgres from '@powersync/lib-service-postgres';
|
|
4
4
|
import { logger } from '@powersync/lib-services-framework';
|
|
5
|
-
import { BucketStorageFactory,
|
|
5
|
+
import { BucketStorageFactory, InternalOpId, TestStorageOptions } from '@powersync/service-core';
|
|
6
6
|
import * as pgwire from '@powersync/service-jpgwire';
|
|
7
7
|
import * as mongo_storage from '@powersync/service-module-mongodb-storage';
|
|
8
8
|
import * as postgres_storage from '@powersync/service-module-postgres-storage';
|
|
@@ -64,7 +64,7 @@ export async function getClientCheckpoint(
|
|
|
64
64
|
db: pgwire.PgClient,
|
|
65
65
|
storageFactory: BucketStorageFactory,
|
|
66
66
|
options?: { timeout?: number }
|
|
67
|
-
): Promise<
|
|
67
|
+
): Promise<InternalOpId> {
|
|
68
68
|
const start = Date.now();
|
|
69
69
|
|
|
70
70
|
const api = new PostgresRouteAPIAdapter(db);
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { MissingReplicationSlotError } from '@module/replication/WalStream.js';
|
|
2
|
-
import {
|
|
3
|
-
import { putOp, removeOp } from '@powersync/service-core-tests';
|
|
2
|
+
import { storage } from '@powersync/service-core';
|
|
3
|
+
import { METRICS_HELPER, putOp, removeOp } from '@powersync/service-core-tests';
|
|
4
4
|
import { pgwireRows } from '@powersync/service-jpgwire';
|
|
5
5
|
import * as crypto from 'crypto';
|
|
6
6
|
import { describe, expect, test } from 'vitest';
|
|
7
7
|
import { env } from './env.js';
|
|
8
8
|
import { INITIALIZED_MONGO_STORAGE_FACTORY, INITIALIZED_POSTGRES_STORAGE_FACTORY } from './util.js';
|
|
9
9
|
import { WalStreamTestContext } from './wal_stream_utils.js';
|
|
10
|
+
import { ReplicationMetric } from '@powersync/service-types';
|
|
10
11
|
|
|
11
12
|
const BASIC_SYNC_RULES = `
|
|
12
13
|
bucket_definitions:
|
|
@@ -40,9 +41,9 @@ bucket_definitions:
|
|
|
40
41
|
|
|
41
42
|
await context.replicateSnapshot();
|
|
42
43
|
|
|
43
|
-
const startRowCount = (await
|
|
44
|
+
const startRowCount = (await METRICS_HELPER.getMetricValueForTests(ReplicationMetric.ROWS_REPLICATED_TOTAL)) ?? 0;
|
|
44
45
|
const startTxCount =
|
|
45
|
-
(await
|
|
46
|
+
(await METRICS_HELPER.getMetricValueForTests(ReplicationMetric.TRANSACTIONS_REPLICATED_TOTAL)) ?? 0;
|
|
46
47
|
|
|
47
48
|
context.startStreaming();
|
|
48
49
|
|
|
@@ -55,9 +56,9 @@ bucket_definitions:
|
|
|
55
56
|
const data = await context.getBucketData('global[]');
|
|
56
57
|
|
|
57
58
|
expect(data).toMatchObject([putOp('test_data', { id: test_id, description: 'test1', num: 1152921504606846976n })]);
|
|
58
|
-
const endRowCount = (await
|
|
59
|
+
const endRowCount = (await METRICS_HELPER.getMetricValueForTests(ReplicationMetric.ROWS_REPLICATED_TOTAL)) ?? 0;
|
|
59
60
|
const endTxCount =
|
|
60
|
-
(await
|
|
61
|
+
(await METRICS_HELPER.getMetricValueForTests(ReplicationMetric.TRANSACTIONS_REPLICATED_TOTAL)) ?? 0;
|
|
61
62
|
expect(endRowCount - startRowCount).toEqual(1);
|
|
62
63
|
expect(endTxCount - startTxCount).toEqual(1);
|
|
63
64
|
});
|
|
@@ -77,9 +78,9 @@ bucket_definitions:
|
|
|
77
78
|
|
|
78
79
|
await context.replicateSnapshot();
|
|
79
80
|
|
|
80
|
-
const startRowCount = (await
|
|
81
|
+
const startRowCount = (await METRICS_HELPER.getMetricValueForTests(ReplicationMetric.ROWS_REPLICATED_TOTAL)) ?? 0;
|
|
81
82
|
const startTxCount =
|
|
82
|
-
(await
|
|
83
|
+
(await METRICS_HELPER.getMetricValueForTests(ReplicationMetric.TRANSACTIONS_REPLICATED_TOTAL)) ?? 0;
|
|
83
84
|
|
|
84
85
|
context.startStreaming();
|
|
85
86
|
|
|
@@ -90,9 +91,9 @@ bucket_definitions:
|
|
|
90
91
|
const data = await context.getBucketData('global[]');
|
|
91
92
|
|
|
92
93
|
expect(data).toMatchObject([putOp('test_DATA', { id: test_id, description: 'test1' })]);
|
|
93
|
-
const endRowCount = (await
|
|
94
|
+
const endRowCount = (await METRICS_HELPER.getMetricValueForTests(ReplicationMetric.ROWS_REPLICATED_TOTAL)) ?? 0;
|
|
94
95
|
const endTxCount =
|
|
95
|
-
(await
|
|
96
|
+
(await METRICS_HELPER.getMetricValueForTests(ReplicationMetric.TRANSACTIONS_REPLICATED_TOTAL)) ?? 0;
|
|
96
97
|
expect(endRowCount - startRowCount).toEqual(1);
|
|
97
98
|
expect(endTxCount - startTxCount).toEqual(1);
|
|
98
99
|
});
|
|
@@ -274,9 +275,9 @@ bucket_definitions:
|
|
|
274
275
|
|
|
275
276
|
await context.replicateSnapshot();
|
|
276
277
|
|
|
277
|
-
const startRowCount = (await
|
|
278
|
+
const startRowCount = (await METRICS_HELPER.getMetricValueForTests(ReplicationMetric.ROWS_REPLICATED_TOTAL)) ?? 0;
|
|
278
279
|
const startTxCount =
|
|
279
|
-
(await
|
|
280
|
+
(await METRICS_HELPER.getMetricValueForTests(ReplicationMetric.TRANSACTIONS_REPLICATED_TOTAL)) ?? 0;
|
|
280
281
|
|
|
281
282
|
context.startStreaming();
|
|
282
283
|
|
|
@@ -287,9 +288,9 @@ bucket_definitions:
|
|
|
287
288
|
const data = await context.getBucketData('global[]');
|
|
288
289
|
|
|
289
290
|
expect(data).toMatchObject([]);
|
|
290
|
-
const endRowCount = (await
|
|
291
|
+
const endRowCount = (await METRICS_HELPER.getMetricValueForTests(ReplicationMetric.ROWS_REPLICATED_TOTAL)) ?? 0;
|
|
291
292
|
const endTxCount =
|
|
292
|
-
(await
|
|
293
|
+
(await METRICS_HELPER.getMetricValueForTests(ReplicationMetric.TRANSACTIONS_REPLICATED_TOTAL)) ?? 0;
|
|
293
294
|
|
|
294
295
|
// There was a transaction, but we should not replicate any actual data
|
|
295
296
|
expect(endRowCount - startRowCount).toEqual(0);
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import { PgManager } from '@module/replication/PgManager.js';
|
|
2
2
|
import { PUBLICATION_NAME, WalStream, WalStreamOptions } from '@module/replication/WalStream.js';
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
BucketStorageFactory,
|
|
5
|
+
createCoreReplicationMetrics,
|
|
6
|
+
initializeCoreReplicationMetrics,
|
|
7
|
+
InternalOpId,
|
|
8
|
+
OplogEntry,
|
|
9
|
+
storage,
|
|
10
|
+
SyncRulesBucketStorage
|
|
11
|
+
} from '@powersync/service-core';
|
|
12
|
+
import { METRICS_HELPER, test_utils } from '@powersync/service-core-tests';
|
|
5
13
|
import * as pgwire from '@powersync/service-jpgwire';
|
|
6
14
|
import { clearTestDb, getClientCheckpoint, TEST_CONNECTION_OPTIONS } from './util.js';
|
|
7
15
|
|
|
@@ -35,7 +43,10 @@ export class WalStreamTestContext implements AsyncDisposable {
|
|
|
35
43
|
constructor(
|
|
36
44
|
public factory: BucketStorageFactory,
|
|
37
45
|
public connectionManager: PgManager
|
|
38
|
-
) {
|
|
46
|
+
) {
|
|
47
|
+
createCoreReplicationMetrics(METRICS_HELPER.metricsEngine);
|
|
48
|
+
initializeCoreReplicationMetrics(METRICS_HELPER.metricsEngine);
|
|
49
|
+
}
|
|
39
50
|
|
|
40
51
|
async [Symbol.asyncDispose]() {
|
|
41
52
|
await this.dispose();
|
|
@@ -95,6 +106,7 @@ export class WalStreamTestContext implements AsyncDisposable {
|
|
|
95
106
|
}
|
|
96
107
|
const options: WalStreamOptions = {
|
|
97
108
|
storage: this.storage,
|
|
109
|
+
metrics: METRICS_HELPER.metricsEngine,
|
|
98
110
|
connections: this.connectionManager,
|
|
99
111
|
abort_signal: this.abortController.signal
|
|
100
112
|
};
|
|
@@ -120,27 +132,30 @@ export class WalStreamTestContext implements AsyncDisposable {
|
|
|
120
132
|
getClientCheckpoint(this.pool, this.factory, { timeout: options?.timeout ?? 15_000 }),
|
|
121
133
|
this.streamPromise
|
|
122
134
|
]);
|
|
123
|
-
if (
|
|
135
|
+
if (checkpoint == null) {
|
|
124
136
|
// This indicates an issue with the test setup - streamingPromise completed instead
|
|
125
137
|
// of getClientCheckpoint()
|
|
126
138
|
throw new Error('Test failure - streamingPromise completed');
|
|
127
139
|
}
|
|
128
|
-
return checkpoint
|
|
140
|
+
return checkpoint;
|
|
129
141
|
}
|
|
130
142
|
|
|
131
|
-
async getBucketsDataBatch(buckets: Record<string,
|
|
143
|
+
async getBucketsDataBatch(buckets: Record<string, InternalOpId>, options?: { timeout?: number }) {
|
|
132
144
|
let checkpoint = await this.getCheckpoint(options);
|
|
133
|
-
const map = new Map<string,
|
|
145
|
+
const map = new Map<string, InternalOpId>(Object.entries(buckets));
|
|
134
146
|
return test_utils.fromAsync(this.storage!.getBucketDataBatch(checkpoint, map));
|
|
135
147
|
}
|
|
136
148
|
|
|
137
149
|
/**
|
|
138
150
|
* This waits for a client checkpoint.
|
|
139
151
|
*/
|
|
140
|
-
async getBucketData(bucket: string, start?: string, options?: { timeout?: number }) {
|
|
141
|
-
start ??=
|
|
152
|
+
async getBucketData(bucket: string, start?: InternalOpId | string | undefined, options?: { timeout?: number }) {
|
|
153
|
+
start ??= 0n;
|
|
154
|
+
if (typeof start == 'string') {
|
|
155
|
+
start = BigInt(start);
|
|
156
|
+
}
|
|
142
157
|
const checkpoint = await this.getCheckpoint(options);
|
|
143
|
-
const map = new Map<string,
|
|
158
|
+
const map = new Map<string, InternalOpId>([[bucket, start]]);
|
|
144
159
|
let data: OplogEntry[] = [];
|
|
145
160
|
while (true) {
|
|
146
161
|
const batch = this.storage!.getBucketDataBatch(checkpoint, map);
|
|
@@ -150,7 +165,7 @@ export class WalStreamTestContext implements AsyncDisposable {
|
|
|
150
165
|
if (batches.length == 0 || !batches[0]!.batch.has_more) {
|
|
151
166
|
break;
|
|
152
167
|
}
|
|
153
|
-
map.set(bucket, batches[0]!.batch.next_after);
|
|
168
|
+
map.set(bucket, BigInt(batches[0]!.batch.next_after));
|
|
154
169
|
}
|
|
155
170
|
return data;
|
|
156
171
|
}
|
|
@@ -158,10 +173,13 @@ export class WalStreamTestContext implements AsyncDisposable {
|
|
|
158
173
|
/**
|
|
159
174
|
* This does not wait for a client checkpoint.
|
|
160
175
|
*/
|
|
161
|
-
async getCurrentBucketData(bucket: string, start?: string) {
|
|
162
|
-
start ??=
|
|
176
|
+
async getCurrentBucketData(bucket: string, start?: InternalOpId | string | undefined) {
|
|
177
|
+
start ??= 0n;
|
|
178
|
+
if (typeof start == 'string') {
|
|
179
|
+
start = BigInt(start);
|
|
180
|
+
}
|
|
163
181
|
const { checkpoint } = await this.storage!.getCheckpoint();
|
|
164
|
-
const map = new Map<string,
|
|
182
|
+
const map = new Map<string, InternalOpId>([[bucket, start]]);
|
|
165
183
|
const batch = this.storage!.getBucketDataBatch(checkpoint, map);
|
|
166
184
|
const batches = await test_utils.fromAsync(batch);
|
|
167
185
|
return batches[0]?.batch.data ?? [];
|