@powersync/service-module-postgres 0.0.0-dev-20260203155513 → 0.0.0-dev-20260223082111
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 +52 -9
- package/dist/replication/replication-utils.d.ts +2 -2
- package/package.json +11 -11
- package/src/replication/replication-utils.ts +2 -2
- package/test/src/checkpoints.test.ts +4 -4
- package/test/src/chunked_snapshots.test.ts +8 -4
- package/test/src/large_batch.test.ts +16 -22
- package/test/src/resuming_snapshots.test.ts +14 -7
- package/test/src/route_api_adapter.test.ts +3 -1
- package/test/src/schema_changes.test.ts +87 -37
- package/test/src/slow_tests.test.ts +12 -6
- package/test/src/util.ts +35 -9
- package/test/src/validation.test.ts +4 -3
- package/test/src/wal_stream.test.ts +26 -24
- package/test/src/wal_stream_utils.ts +56 -10
- package/test/tsconfig.json +3 -7
- package/tsconfig.tsbuildinfo +1 -1
- package/test/src/__snapshots__/schema_changes.test.ts.snap +0 -5
|
@@ -1,23 +1,27 @@
|
|
|
1
1
|
import { PgManager } from '@module/replication/PgManager.js';
|
|
2
2
|
import { PUBLICATION_NAME, WalStream, WalStreamOptions } from '@module/replication/WalStream.js';
|
|
3
|
+
import { CustomTypeRegistry } from '@module/types/registry.js';
|
|
3
4
|
import {
|
|
4
5
|
BucketStorageFactory,
|
|
5
6
|
createCoreReplicationMetrics,
|
|
6
7
|
initializeCoreReplicationMetrics,
|
|
7
8
|
InternalOpId,
|
|
9
|
+
LEGACY_STORAGE_VERSION,
|
|
8
10
|
OplogEntry,
|
|
11
|
+
STORAGE_VERSION_CONFIG,
|
|
9
12
|
storage,
|
|
10
|
-
SyncRulesBucketStorage
|
|
13
|
+
SyncRulesBucketStorage,
|
|
14
|
+
updateSyncRulesFromYaml
|
|
11
15
|
} from '@powersync/service-core';
|
|
12
16
|
import { METRICS_HELPER, test_utils } from '@powersync/service-core-tests';
|
|
13
17
|
import * as pgwire from '@powersync/service-jpgwire';
|
|
14
18
|
import { clearTestDb, getClientCheckpoint, TEST_CONNECTION_OPTIONS } from './util.js';
|
|
15
|
-
import { CustomTypeRegistry } from '@module/types/registry.js';
|
|
16
19
|
|
|
17
20
|
export class WalStreamTestContext implements AsyncDisposable {
|
|
18
21
|
private _walStream?: WalStream;
|
|
19
22
|
private abortController = new AbortController();
|
|
20
23
|
private streamPromise?: Promise<void>;
|
|
24
|
+
private syncRulesId?: number;
|
|
21
25
|
public storage?: SyncRulesBucketStorage;
|
|
22
26
|
private replicationConnection?: pgwire.PgConnection;
|
|
23
27
|
private snapshotPromise?: Promise<void>;
|
|
@@ -30,7 +34,7 @@ export class WalStreamTestContext implements AsyncDisposable {
|
|
|
30
34
|
*/
|
|
31
35
|
static async open(
|
|
32
36
|
factory: (options: storage.TestStorageOptions) => Promise<BucketStorageFactory>,
|
|
33
|
-
options?: { doNotClear?: boolean; walStreamOptions?: Partial<WalStreamOptions> }
|
|
37
|
+
options?: { doNotClear?: boolean; storageVersion?: number; walStreamOptions?: Partial<WalStreamOptions> }
|
|
34
38
|
) {
|
|
35
39
|
const f = await factory({ doNotClear: options?.doNotClear });
|
|
36
40
|
const connectionManager = new PgManager(TEST_CONNECTION_OPTIONS, {});
|
|
@@ -39,13 +43,18 @@ export class WalStreamTestContext implements AsyncDisposable {
|
|
|
39
43
|
await clearTestDb(connectionManager.pool);
|
|
40
44
|
}
|
|
41
45
|
|
|
42
|
-
|
|
46
|
+
const storageVersion = options?.storageVersion ?? LEGACY_STORAGE_VERSION;
|
|
47
|
+
const versionedBuckets = STORAGE_VERSION_CONFIG[storageVersion]?.versionedBuckets ?? false;
|
|
48
|
+
|
|
49
|
+
return new WalStreamTestContext(f, connectionManager, options?.walStreamOptions, storageVersion, versionedBuckets);
|
|
43
50
|
}
|
|
44
51
|
|
|
45
52
|
constructor(
|
|
46
53
|
public factory: BucketStorageFactory,
|
|
47
54
|
public connectionManager: PgManager,
|
|
48
|
-
private walStreamOptions?: Partial<WalStreamOptions
|
|
55
|
+
private walStreamOptions?: Partial<WalStreamOptions>,
|
|
56
|
+
private storageVersion: number = LEGACY_STORAGE_VERSION,
|
|
57
|
+
private versionedBuckets: boolean = STORAGE_VERSION_CONFIG[storageVersion]?.versionedBuckets ?? false
|
|
49
58
|
) {
|
|
50
59
|
createCoreReplicationMetrics(METRICS_HELPER.metricsEngine);
|
|
51
60
|
initializeCoreReplicationMetrics(METRICS_HELPER.metricsEngine);
|
|
@@ -95,7 +104,10 @@ export class WalStreamTestContext implements AsyncDisposable {
|
|
|
95
104
|
}
|
|
96
105
|
|
|
97
106
|
async updateSyncRules(content: string) {
|
|
98
|
-
const syncRules = await this.factory.updateSyncRules(
|
|
107
|
+
const syncRules = await this.factory.updateSyncRules(
|
|
108
|
+
updateSyncRulesFromYaml(content, { validate: true, storageVersion: this.storageVersion })
|
|
109
|
+
);
|
|
110
|
+
this.syncRulesId = syncRules.id;
|
|
99
111
|
this.storage = this.factory.getInstance(syncRules);
|
|
100
112
|
return this.storage!;
|
|
101
113
|
}
|
|
@@ -106,6 +118,7 @@ export class WalStreamTestContext implements AsyncDisposable {
|
|
|
106
118
|
throw new Error(`Next sync rules not available`);
|
|
107
119
|
}
|
|
108
120
|
|
|
121
|
+
this.syncRulesId = syncRules.id;
|
|
109
122
|
this.storage = this.factory.getInstance(syncRules);
|
|
110
123
|
return this.storage!;
|
|
111
124
|
}
|
|
@@ -116,6 +129,7 @@ export class WalStreamTestContext implements AsyncDisposable {
|
|
|
116
129
|
throw new Error(`Active sync rules not available`);
|
|
117
130
|
}
|
|
118
131
|
|
|
132
|
+
this.syncRulesId = syncRules.id;
|
|
119
133
|
this.storage = this.factory.getInstance(syncRules);
|
|
120
134
|
return this.storage!;
|
|
121
135
|
}
|
|
@@ -177,9 +191,21 @@ export class WalStreamTestContext implements AsyncDisposable {
|
|
|
177
191
|
return checkpoint;
|
|
178
192
|
}
|
|
179
193
|
|
|
194
|
+
private resolveBucketName(bucket: string) {
|
|
195
|
+
if (!this.versionedBuckets || /^\d+#/.test(bucket)) {
|
|
196
|
+
return bucket;
|
|
197
|
+
}
|
|
198
|
+
if (this.syncRulesId == null) {
|
|
199
|
+
throw new Error('Sync rules not configured - call updateSyncRules() first');
|
|
200
|
+
}
|
|
201
|
+
return `${this.syncRulesId}#${bucket}`;
|
|
202
|
+
}
|
|
203
|
+
|
|
180
204
|
async getBucketsDataBatch(buckets: Record<string, InternalOpId>, options?: { timeout?: number }) {
|
|
181
205
|
let checkpoint = await this.getCheckpoint(options);
|
|
182
|
-
const map = new Map<string, InternalOpId>(
|
|
206
|
+
const map = new Map<string, InternalOpId>(
|
|
207
|
+
Object.entries(buckets).map(([bucket, opId]) => [this.resolveBucketName(bucket), opId])
|
|
208
|
+
);
|
|
183
209
|
return test_utils.fromAsync(this.storage!.getBucketDataBatch(checkpoint, map));
|
|
184
210
|
}
|
|
185
211
|
|
|
@@ -191,8 +217,9 @@ export class WalStreamTestContext implements AsyncDisposable {
|
|
|
191
217
|
if (typeof start == 'string') {
|
|
192
218
|
start = BigInt(start);
|
|
193
219
|
}
|
|
220
|
+
const resolvedBucket = this.resolveBucketName(bucket);
|
|
194
221
|
const checkpoint = await this.getCheckpoint(options);
|
|
195
|
-
const map = new Map<string, InternalOpId>([[
|
|
222
|
+
const map = new Map<string, InternalOpId>([[resolvedBucket, start]]);
|
|
196
223
|
let data: OplogEntry[] = [];
|
|
197
224
|
while (true) {
|
|
198
225
|
const batch = this.storage!.getBucketDataBatch(checkpoint, map);
|
|
@@ -202,11 +229,29 @@ export class WalStreamTestContext implements AsyncDisposable {
|
|
|
202
229
|
if (batches.length == 0 || !batches[0]!.chunkData.has_more) {
|
|
203
230
|
break;
|
|
204
231
|
}
|
|
205
|
-
map.set(
|
|
232
|
+
map.set(resolvedBucket, BigInt(batches[0]!.chunkData.next_after));
|
|
206
233
|
}
|
|
207
234
|
return data;
|
|
208
235
|
}
|
|
209
236
|
|
|
237
|
+
async getChecksums(buckets: string[], options?: { timeout?: number }) {
|
|
238
|
+
const checkpoint = await this.getCheckpoint(options);
|
|
239
|
+
const versionedBuckets = buckets.map((bucket) => this.resolveBucketName(bucket));
|
|
240
|
+
const checksums = await this.storage!.getChecksums(checkpoint, versionedBuckets);
|
|
241
|
+
|
|
242
|
+
const unversioned = new Map();
|
|
243
|
+
for (let i = 0; i < buckets.length; i++) {
|
|
244
|
+
unversioned.set(buckets[i], checksums.get(versionedBuckets[i])!);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return unversioned;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async getChecksum(bucket: string, options?: { timeout?: number }) {
|
|
251
|
+
const checksums = await this.getChecksums([bucket], options);
|
|
252
|
+
return checksums.get(bucket);
|
|
253
|
+
}
|
|
254
|
+
|
|
210
255
|
/**
|
|
211
256
|
* This does not wait for a client checkpoint.
|
|
212
257
|
*/
|
|
@@ -215,8 +260,9 @@ export class WalStreamTestContext implements AsyncDisposable {
|
|
|
215
260
|
if (typeof start == 'string') {
|
|
216
261
|
start = BigInt(start);
|
|
217
262
|
}
|
|
263
|
+
const resolvedBucket = this.resolveBucketName(bucket);
|
|
218
264
|
const { checkpoint } = await this.storage!.getCheckpoint();
|
|
219
|
-
const map = new Map<string, InternalOpId>([[
|
|
265
|
+
const map = new Map<string, InternalOpId>([[resolvedBucket, start]]);
|
|
220
266
|
const batch = this.storage!.getBucketDataBatch(checkpoint, map);
|
|
221
267
|
const batches = await test_utils.fromAsync(batch);
|
|
222
268
|
return batches[0]?.chunkData.data ?? [];
|
package/test/tsconfig.json
CHANGED
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
{
|
|
2
|
-
"extends": "../../../tsconfig.
|
|
2
|
+
"extends": "../../../tsconfig.tests.json",
|
|
3
3
|
"compilerOptions": {
|
|
4
|
-
"rootDir": "src",
|
|
5
4
|
"baseUrl": "./",
|
|
6
|
-
"noEmit": true,
|
|
7
|
-
"esModuleInterop": true,
|
|
8
|
-
"skipLibCheck": true,
|
|
9
|
-
"sourceMap": true,
|
|
10
5
|
"paths": {
|
|
11
6
|
"@/*": ["../../../packages/service-core/src/*"],
|
|
12
7
|
"@module/*": ["../src/*"],
|
|
13
8
|
"@core-tests/*": ["../../../packages/service-core/test/src/*"]
|
|
14
|
-
}
|
|
9
|
+
},
|
|
10
|
+
"rootDir": "src"
|
|
15
11
|
},
|
|
16
12
|
"include": ["src"],
|
|
17
13
|
"references": [
|