@powersync/service-module-postgres 0.19.3 → 0.19.4

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 (72) hide show
  1. package/dist/api/PostgresRouteAPIAdapter.d.ts +1 -1
  2. package/dist/api/PostgresRouteAPIAdapter.js +63 -72
  3. package/dist/api/PostgresRouteAPIAdapter.js.map +1 -1
  4. package/dist/module/PostgresModule.js.map +1 -1
  5. package/dist/replication/MissingReplicationSlotError.d.ts +41 -0
  6. package/dist/replication/MissingReplicationSlotError.js +33 -0
  7. package/dist/replication/MissingReplicationSlotError.js.map +1 -0
  8. package/dist/replication/PostgresErrorRateLimiter.js +1 -1
  9. package/dist/replication/PostgresErrorRateLimiter.js.map +1 -1
  10. package/dist/replication/SnapshotQuery.js +2 -2
  11. package/dist/replication/SnapshotQuery.js.map +1 -1
  12. package/dist/replication/WalStream.d.ts +35 -3
  13. package/dist/replication/WalStream.js +135 -9
  14. package/dist/replication/WalStream.js.map +1 -1
  15. package/dist/replication/WalStreamReplicationJob.js +6 -3
  16. package/dist/replication/WalStreamReplicationJob.js.map +1 -1
  17. package/dist/replication/replication-index.d.ts +3 -1
  18. package/dist/replication/replication-index.js +3 -1
  19. package/dist/replication/replication-index.js.map +1 -1
  20. package/dist/replication/replication-utils.d.ts +3 -11
  21. package/dist/replication/replication-utils.js +101 -164
  22. package/dist/replication/replication-utils.js.map +1 -1
  23. package/dist/replication/wal-budget-utils.d.ts +23 -0
  24. package/dist/replication/wal-budget-utils.js +57 -0
  25. package/dist/replication/wal-budget-utils.js.map +1 -0
  26. package/dist/types/registry.js +1 -1
  27. package/dist/types/registry.js.map +1 -1
  28. package/package.json +15 -11
  29. package/sql/check-source-configuration.plpgsql +13 -0
  30. package/sql/debug-tables-info-batched.plpgsql +230 -0
  31. package/CHANGELOG.md +0 -858
  32. package/src/api/PostgresRouteAPIAdapter.ts +0 -356
  33. package/src/index.ts +0 -1
  34. package/src/module/PostgresModule.ts +0 -122
  35. package/src/replication/ConnectionManagerFactory.ts +0 -33
  36. package/src/replication/PgManager.ts +0 -122
  37. package/src/replication/PgRelation.ts +0 -41
  38. package/src/replication/PostgresErrorRateLimiter.ts +0 -48
  39. package/src/replication/SnapshotQuery.ts +0 -213
  40. package/src/replication/WalStream.ts +0 -1137
  41. package/src/replication/WalStreamReplicationJob.ts +0 -138
  42. package/src/replication/WalStreamReplicator.ts +0 -53
  43. package/src/replication/replication-index.ts +0 -5
  44. package/src/replication/replication-utils.ts +0 -398
  45. package/src/types/registry.ts +0 -275
  46. package/src/types/resolver.ts +0 -227
  47. package/src/types/types.ts +0 -44
  48. package/src/utils/application-name.ts +0 -8
  49. package/src/utils/migration_lib.ts +0 -80
  50. package/src/utils/populate_test_data.ts +0 -37
  51. package/src/utils/populate_test_data_worker.ts +0 -53
  52. package/src/utils/postgres_version.ts +0 -8
  53. package/test/src/checkpoints.test.ts +0 -86
  54. package/test/src/chunked_snapshots.test.ts +0 -161
  55. package/test/src/env.ts +0 -11
  56. package/test/src/large_batch.test.ts +0 -241
  57. package/test/src/pg_test.test.ts +0 -729
  58. package/test/src/resuming_snapshots.test.ts +0 -160
  59. package/test/src/route_api_adapter.test.ts +0 -62
  60. package/test/src/schema_changes.test.ts +0 -655
  61. package/test/src/setup.ts +0 -12
  62. package/test/src/slow_tests.test.ts +0 -519
  63. package/test/src/storage_combination.test.ts +0 -35
  64. package/test/src/types/registry.test.ts +0 -149
  65. package/test/src/util.ts +0 -151
  66. package/test/src/validation.test.ts +0 -63
  67. package/test/src/wal_stream.test.ts +0 -607
  68. package/test/src/wal_stream_utils.ts +0 -284
  69. package/test/tsconfig.json +0 -27
  70. package/tsconfig.json +0 -34
  71. package/tsconfig.tsbuildinfo +0 -1
  72. package/vitest.config.ts +0 -3
@@ -1,284 +0,0 @@
1
- import { PgManager } from '@module/replication/PgManager.js';
2
- import { PUBLICATION_NAME, WalStream, WalStreamOptions } from '@module/replication/WalStream.js';
3
- import { ReplicationAbortedError } from '@powersync/lib-services-framework';
4
- import {
5
- BucketStorageFactory,
6
- createCoreReplicationMetrics,
7
- initializeCoreReplicationMetrics,
8
- InternalOpId,
9
- LEGACY_STORAGE_VERSION,
10
- OplogEntry,
11
- settledPromise,
12
- storage,
13
- STORAGE_VERSION_CONFIG,
14
- SyncRulesBucketStorage,
15
- unsettledPromise,
16
- updateSyncRulesFromYaml
17
- } from '@powersync/service-core';
18
- import { bucketRequest, METRICS_HELPER, test_utils } from '@powersync/service-core-tests';
19
- import * as pgwire from '@powersync/service-jpgwire';
20
- import { clearTestDb, getClientCheckpoint, TEST_CONNECTION_OPTIONS } from './util.js';
21
-
22
- export class WalStreamTestContext implements AsyncDisposable {
23
- private _walStream?: WalStream;
24
- private abortController = new AbortController();
25
- private syncRulesId?: number;
26
- private syncRulesContent?: storage.PersistedSyncRulesContent;
27
- public storage?: SyncRulesBucketStorage;
28
- private settledReplicationPromise?: Promise<PromiseSettledResult<void>>;
29
-
30
- /**
31
- * Tests operating on the wal stream need to configure the stream and manage asynchronous
32
- * replication, which gets a little tricky.
33
- *
34
- * This configures all the context, and tears it down afterwards.
35
- */
36
- static async open(
37
- factory: (options: storage.TestStorageOptions) => Promise<BucketStorageFactory>,
38
- options?: { doNotClear?: boolean; storageVersion?: number; walStreamOptions?: Partial<WalStreamOptions> }
39
- ) {
40
- const f = await factory({ doNotClear: options?.doNotClear });
41
- const connectionManager = new PgManager(TEST_CONNECTION_OPTIONS, {});
42
-
43
- if (!options?.doNotClear) {
44
- await clearTestDb(connectionManager.pool);
45
- }
46
-
47
- const storageVersion = options?.storageVersion ?? LEGACY_STORAGE_VERSION;
48
- const versionedBuckets = STORAGE_VERSION_CONFIG[storageVersion]?.versionedBuckets ?? false;
49
-
50
- return new WalStreamTestContext(f, connectionManager, options?.walStreamOptions, storageVersion, versionedBuckets);
51
- }
52
-
53
- constructor(
54
- public factory: BucketStorageFactory,
55
- public connectionManager: PgManager,
56
- private walStreamOptions?: Partial<WalStreamOptions>,
57
- private storageVersion: number = LEGACY_STORAGE_VERSION,
58
- private versionedBuckets: boolean = STORAGE_VERSION_CONFIG[storageVersion]?.versionedBuckets ?? false
59
- ) {
60
- createCoreReplicationMetrics(METRICS_HELPER.metricsEngine);
61
- initializeCoreReplicationMetrics(METRICS_HELPER.metricsEngine);
62
- }
63
-
64
- async [Symbol.asyncDispose]() {
65
- await this.dispose();
66
- }
67
-
68
- async dispose() {
69
- this.abortController.abort();
70
- try {
71
- await this.settledReplicationPromise;
72
- await this.connectionManager.destroy();
73
- await this.factory?.[Symbol.asyncDispose]();
74
- } catch (e) {
75
- // Throwing here may result in SuppressedError. The underlying errors often don't show up
76
- // in the test output, so we log it here.
77
- // If we could get vitest to log SuppressedError.error and SuppressedError.suppressed, we
78
- // could remove this.
79
- console.error('Error during WalStreamTestContext dispose', e);
80
- throw e;
81
- }
82
- }
83
-
84
- get pool() {
85
- return this.connectionManager.pool;
86
- }
87
-
88
- get connectionTag() {
89
- return this.connectionManager.connectionTag;
90
- }
91
-
92
- get publicationName() {
93
- return PUBLICATION_NAME;
94
- }
95
-
96
- async updateSyncRules(content: string) {
97
- const syncRules = await this.factory.updateSyncRules(
98
- updateSyncRulesFromYaml(content, { validate: true, storageVersion: this.storageVersion })
99
- );
100
- this.syncRulesId = syncRules.id;
101
- this.syncRulesContent = syncRules;
102
- this.storage = this.factory.getInstance(syncRules);
103
- return this.storage!;
104
- }
105
-
106
- async loadNextSyncRules() {
107
- const syncRules = await this.factory.getNextSyncRulesContent();
108
- if (syncRules == null) {
109
- throw new Error(`Next sync rules not available`);
110
- }
111
-
112
- this.syncRulesId = syncRules.id;
113
- this.syncRulesContent = syncRules;
114
- this.storage = this.factory.getInstance(syncRules);
115
- return this.storage!;
116
- }
117
-
118
- async loadActiveSyncRules() {
119
- const syncRules = await this.factory.getActiveSyncRulesContent();
120
- if (syncRules == null) {
121
- throw new Error(`Active sync rules not available`);
122
- }
123
-
124
- this.syncRulesId = syncRules.id;
125
- this.syncRulesContent = syncRules;
126
- this.storage = this.factory.getInstance(syncRules);
127
- return this.storage!;
128
- }
129
-
130
- private getSyncRulesContent(): storage.PersistedSyncRulesContent {
131
- if (this.syncRulesContent == null) {
132
- throw new Error('Sync rules not configured - call updateSyncRules() first');
133
- }
134
- return this.syncRulesContent;
135
- }
136
-
137
- get walStream() {
138
- if (this.storage == null) {
139
- throw new Error('updateSyncRules() first');
140
- }
141
- if (this._walStream) {
142
- return this._walStream;
143
- }
144
- const options: WalStreamOptions = {
145
- storage: this.storage,
146
- metrics: METRICS_HELPER.metricsEngine,
147
- connections: this.connectionManager,
148
- abort_signal: this.abortController.signal,
149
- ...this.walStreamOptions
150
- };
151
- this._walStream = new WalStream(options);
152
- return this._walStream!;
153
- }
154
-
155
- /**
156
- * Replicate a snapshot, start streaming, and wait for a consistent checkpoint.
157
- */
158
- async initializeReplication() {
159
- await this.replicateSnapshot();
160
- // Make sure we're up to date
161
- await this.getCheckpoint();
162
- }
163
-
164
- /**
165
- * Replicate the initial snapshot, and start streaming.
166
- */
167
- async replicateSnapshot() {
168
- // Use a settledPromise to avoid unhandled rejections
169
- this.settledReplicationPromise = settledPromise(this.walStream.replicate());
170
- try {
171
- await Promise.race([unsettledPromise(this.settledReplicationPromise), this.walStream.waitForInitialSnapshot()]);
172
- } catch (e) {
173
- if (e instanceof ReplicationAbortedError && e.cause != null) {
174
- // Edge case for tests: replicate() can throw an error, but we'd receive the ReplicationAbortedError from
175
- // waitForInitialSnapshot() first. In that case, prioritize the cause, e.g. MissingReplicationSlotError.
176
- // This is not a concern for production use, since we only use waitForInitialSnapshot() in tests.
177
- throw e.cause;
178
- }
179
- throw e;
180
- }
181
- }
182
-
183
- async getCheckpoint(options?: { timeout?: number }) {
184
- let checkpoint = await Promise.race([
185
- getClientCheckpoint(this.pool, this.factory, { timeout: options?.timeout ?? 15_000 }),
186
- unsettledPromise(this.settledReplicationPromise!)
187
- ]);
188
- if (checkpoint == null) {
189
- // This indicates an issue with the test setup - replicationPromise completed instead
190
- // of getClientCheckpoint()
191
- throw new Error('Test failure - replicationPromise completed');
192
- }
193
- return checkpoint;
194
- }
195
-
196
- async getBucketsDataBatch(buckets: Record<string, InternalOpId>, options?: { timeout?: number }) {
197
- let checkpoint = await this.getCheckpoint(options);
198
- const syncRules = this.getSyncRulesContent();
199
- const map = Object.entries(buckets).map(([bucket, start]) => bucketRequest(syncRules, bucket, start));
200
- return test_utils.fromAsync(this.storage!.getBucketDataBatch(checkpoint, map));
201
- }
202
-
203
- /**
204
- * This waits for a client checkpoint.
205
- */
206
- async getBucketData(bucket: string, start?: InternalOpId | string | undefined, options?: { timeout?: number }) {
207
- start ??= 0n;
208
- if (typeof start == 'string') {
209
- start = BigInt(start);
210
- }
211
- const syncRules = this.getSyncRulesContent();
212
- const checkpoint = await this.getCheckpoint(options);
213
- let map = [bucketRequest(syncRules, bucket, start)];
214
- let data: OplogEntry[] = [];
215
- while (true) {
216
- const batch = this.storage!.getBucketDataBatch(checkpoint, map);
217
-
218
- const batches = await test_utils.fromAsync(batch);
219
- data = data.concat(batches[0]?.chunkData.data ?? []);
220
- if (batches.length == 0 || !batches[0]!.chunkData.has_more) {
221
- break;
222
- }
223
- map = [bucketRequest(syncRules, bucket, BigInt(batches[0]!.chunkData.next_after))];
224
- }
225
- return data;
226
- }
227
-
228
- async getChecksums(buckets: string[], options?: { timeout?: number }) {
229
- const checkpoint = await this.getCheckpoint(options);
230
- const syncRules = this.getSyncRulesContent();
231
- const versionedBuckets = buckets.map((bucket) => bucketRequest(syncRules, bucket, 0n));
232
- const checksums = await this.storage!.getChecksums(checkpoint, versionedBuckets);
233
-
234
- const unversioned = new Map();
235
- for (let i = 0; i < buckets.length; i++) {
236
- unversioned.set(buckets[i], checksums.get(versionedBuckets[i].bucket)!);
237
- }
238
-
239
- return unversioned;
240
- }
241
-
242
- async getChecksum(bucket: string, options?: { timeout?: number }) {
243
- const checksums = await this.getChecksums([bucket], options);
244
- return checksums.get(bucket);
245
- }
246
-
247
- /**
248
- * This does not wait for a client checkpoint.
249
- */
250
- async getCurrentBucketData(bucket: string, start?: InternalOpId | string | undefined) {
251
- start ??= 0n;
252
- if (typeof start == 'string') {
253
- start = BigInt(start);
254
- }
255
- const syncRules = this.getSyncRulesContent();
256
- const { checkpoint } = await this.storage!.getCheckpoint();
257
- const map = [bucketRequest(syncRules, bucket, start)];
258
- const batch = this.storage!.getBucketDataBatch(checkpoint, map);
259
- const batches = await test_utils.fromAsync(batch);
260
- return batches[0]?.chunkData.data ?? [];
261
- }
262
- }
263
-
264
- export async function withMaxWalSize(db: pgwire.PgClient, size: string) {
265
- try {
266
- const r1 = await db.query(`SHOW max_slot_wal_keep_size`);
267
-
268
- await db.query(`ALTER SYSTEM SET max_slot_wal_keep_size = '100MB'`);
269
- await db.query(`SELECT pg_reload_conf()`);
270
-
271
- const oldSize = r1.results[0].rows[0].decodeWithoutCustomTypes(0);
272
-
273
- return {
274
- [Symbol.asyncDispose]: async () => {
275
- await db.query(`ALTER SYSTEM SET max_slot_wal_keep_size = '${oldSize}'`);
276
- await db.query(`SELECT pg_reload_conf()`);
277
- }
278
- };
279
- } catch (e) {
280
- const err = new Error(`Failed to configure max_slot_wal_keep_size for test`);
281
- err.cause = e;
282
- throw err;
283
- }
284
- }
@@ -1,27 +0,0 @@
1
- {
2
- "extends": "../../../tsconfig.tests.json",
3
- "compilerOptions": {
4
- "baseUrl": "./",
5
- "paths": {
6
- "@/*": ["../../../packages/service-core/src/*"],
7
- "@module/*": ["../src/*"],
8
- "@core-tests/*": ["../../../packages/service-core/test/src/*"]
9
- },
10
- "rootDir": "src"
11
- },
12
- "include": ["src"],
13
- "references": [
14
- {
15
- "path": "../"
16
- },
17
- {
18
- "path": "../../../packages/service-core-tests"
19
- },
20
- {
21
- "path": "../../module-mongodb-storage"
22
- },
23
- {
24
- "path": "../../module-postgres-storage"
25
- }
26
- ]
27
- }
package/tsconfig.json DELETED
@@ -1,34 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.base.json",
3
- "compilerOptions": {
4
- "rootDir": "src",
5
- "outDir": "dist",
6
- "esModuleInterop": true,
7
- "skipLibCheck": true,
8
- "sourceMap": true
9
- },
10
- "include": ["src"],
11
- "references": [
12
- {
13
- "path": "../../packages/types"
14
- },
15
- {
16
- "path": "../../packages/jsonbig"
17
- },
18
- {
19
- "path": "../../packages/jpgwire"
20
- },
21
- {
22
- "path": "../../packages/sync-rules"
23
- },
24
- {
25
- "path": "../../packages/service-core"
26
- },
27
- {
28
- "path": "../../libs/lib-services"
29
- },
30
- {
31
- "path": "../../libs/lib-postgres"
32
- }
33
- ]
34
- }