@powersync/service-module-postgres 0.6.1 → 0.7.1

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 (32) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/api/PostgresRouteAPIAdapter.d.ts +2 -1
  3. package/dist/api/PostgresRouteAPIAdapter.js +15 -8
  4. package/dist/api/PostgresRouteAPIAdapter.js.map +1 -1
  5. package/dist/auth/SupabaseKeyCollector.js +6 -5
  6. package/dist/auth/SupabaseKeyCollector.js.map +1 -1
  7. package/dist/module/PostgresModule.js +2 -2
  8. package/dist/module/PostgresModule.js.map +1 -1
  9. package/dist/replication/ConnectionManagerFactory.js +2 -0
  10. package/dist/replication/ConnectionManagerFactory.js.map +1 -1
  11. package/dist/replication/PgManager.js +8 -2
  12. package/dist/replication/PgManager.js.map +1 -1
  13. package/dist/replication/PostgresErrorRateLimiter.js +5 -7
  14. package/dist/replication/PostgresErrorRateLimiter.js.map +1 -1
  15. package/dist/replication/WalStream.d.ts +1 -1
  16. package/dist/replication/WalStream.js +9 -3
  17. package/dist/replication/WalStream.js.map +1 -1
  18. package/dist/replication/WalStreamReplicationJob.js +5 -3
  19. package/dist/replication/WalStreamReplicationJob.js.map +1 -1
  20. package/dist/replication/WalStreamReplicator.js +2 -1
  21. package/dist/replication/WalStreamReplicator.js.map +1 -1
  22. package/dist/utils/migration_lib.js +1 -3
  23. package/dist/utils/migration_lib.js.map +1 -1
  24. package/dist/utils/populate_test_data.js +1 -1
  25. package/dist/utils/populate_test_data.js.map +1 -1
  26. package/package.json +9 -9
  27. package/src/api/PostgresRouteAPIAdapter.ts +12 -7
  28. package/src/replication/WalStreamReplicationJob.ts +1 -1
  29. package/test/src/checkpoints.test.ts +70 -0
  30. package/test/src/util.ts +1 -1
  31. package/test/src/wal_stream_utils.ts +1 -1
  32. package/tsconfig.tsbuildinfo +1 -1
@@ -1,6 +1,6 @@
1
1
  import * as lib_postgres from '@powersync/lib-service-postgres';
2
2
  import { ErrorCode, ServiceError } from '@powersync/lib-services-framework';
3
- import { api, ParseSyncRulesOptions } from '@powersync/service-core';
3
+ import { api, ParseSyncRulesOptions, ReplicationHeadCallback } from '@powersync/service-core';
4
4
  import * as pgwire from '@powersync/service-jpgwire';
5
5
  import * as sync_rules from '@powersync/service-sync-rules';
6
6
  import * as service_types from '@powersync/service-types';
@@ -241,17 +241,22 @@ FROM pg_replication_slots WHERE slot_name = $1 LIMIT 1;`,
241
241
  // However, on Aurora (Postgres compatible), it can return an entirely different LSN,
242
242
  // causing the write checkpoints to never be replicated back to the client.
243
243
  // For those, we need to use pg_current_wal_lsn() instead.
244
- const { results } = await lib_postgres.retriedQuery(
245
- this.pool,
246
- { statement: `SELECT pg_current_wal_lsn() as lsn` },
247
- KEEPALIVE_STATEMENT
248
- );
244
+ const { results } = await lib_postgres.retriedQuery(this.pool, `SELECT pg_current_wal_lsn() as lsn`);
249
245
 
250
- // Specifically use the lsn from the first statement, not the second one.
251
246
  const lsn = results[0].rows[0][0];
252
247
  return String(lsn);
253
248
  }
254
249
 
250
+ async createReplicationHead<T>(callback: ReplicationHeadCallback<T>): Promise<T> {
251
+ const currentLsn = await this.getReplicationHead();
252
+
253
+ const r = await callback(currentLsn);
254
+
255
+ await lib_postgres.retriedQuery(this.pool, KEEPALIVE_STATEMENT);
256
+
257
+ return r;
258
+ }
259
+
255
260
  async getConnectionSchema(): Promise<service_types.DatabaseSchema[]> {
256
261
  // https://github.com/Borvik/vscode-postgres/blob/88ec5ed061a0c9bced6c5d4ec122d0759c3f3247/src/language/server.ts
257
262
  const results = await lib_postgres.retriedQuery(
@@ -99,7 +99,7 @@ export class WalStreamReplicationJob extends replication.AbstractReplicationJob
99
99
  });
100
100
  await stream.replicate();
101
101
  } catch (e) {
102
- this.logger.error(`Replication error`, e);
102
+ this.logger.error(`${this.slotName} Replication error`, e);
103
103
  if (e.cause != null) {
104
104
  // Example:
105
105
  // PgError.conn_ended: Unable to do postgres query on ended connection
@@ -0,0 +1,70 @@
1
+ import { PostgresRouteAPIAdapter } from '@module/api/PostgresRouteAPIAdapter.js';
2
+ import { checkpointUserId, createWriteCheckpoint } from '@powersync/service-core';
3
+ import { describe, test } from 'vitest';
4
+ import { INITIALIZED_MONGO_STORAGE_FACTORY } from './util.js';
5
+ import { WalStreamTestContext } from './wal_stream_utils.js';
6
+
7
+ import timers from 'node:timers/promises';
8
+
9
+ const BASIC_SYNC_RULES = `bucket_definitions:
10
+ global:
11
+ data:
12
+ - SELECT id, description, other FROM "test_data"`;
13
+
14
+ describe('checkpoint tests', () => {
15
+ test('write checkpoints', { timeout: 30_000 }, async () => {
16
+ const factory = INITIALIZED_MONGO_STORAGE_FACTORY;
17
+ await using context = await WalStreamTestContext.open(factory);
18
+
19
+ await context.updateSyncRules(BASIC_SYNC_RULES);
20
+ const { pool } = context;
21
+ const api = new PostgresRouteAPIAdapter(pool);
22
+
23
+ await pool.query(`CREATE TABLE test_data(id text primary key, description text, other text)`);
24
+
25
+ await context.replicateSnapshot();
26
+
27
+ context.startStreaming();
28
+
29
+ const controller = new AbortController();
30
+ try {
31
+ const stream = context.factory.watchWriteCheckpoint(
32
+ checkpointUserId('test_user', 'test_client'),
33
+ controller.signal
34
+ );
35
+
36
+ let lastWriteCheckpoint: bigint | null = null;
37
+
38
+ (async () => {
39
+ try {
40
+ for await (const cp of stream) {
41
+ lastWriteCheckpoint = cp.writeCheckpoint;
42
+ }
43
+ } catch (e) {
44
+ if (e.name != 'AbortError') {
45
+ throw e;
46
+ }
47
+ }
48
+ })();
49
+
50
+ for (let i = 0; i < 10; i++) {
51
+ const cp = await createWriteCheckpoint({
52
+ userId: 'test_user',
53
+ clientId: 'test_client',
54
+ api,
55
+ storage: context.factory
56
+ });
57
+
58
+ const start = Date.now();
59
+ while (lastWriteCheckpoint == null || lastWriteCheckpoint < BigInt(cp.writeCheckpoint)) {
60
+ if (Date.now() - start > 2_000) {
61
+ throw new Error(`Timeout while waiting for checkpoint`);
62
+ }
63
+ await timers.setTimeout(0, undefined, { signal: controller.signal });
64
+ }
65
+ }
66
+ } finally {
67
+ controller.abort();
68
+ }
69
+ });
70
+ });
package/test/src/util.ts CHANGED
@@ -68,7 +68,7 @@ export async function getClientCheckpoint(
68
68
  const start = Date.now();
69
69
 
70
70
  const api = new PostgresRouteAPIAdapter(db);
71
- const lsn = await api.getReplicationHead();
71
+ const lsn = await api.createReplicationHead(async (lsn) => lsn);
72
72
 
73
73
  // This old API needs a persisted checkpoint id.
74
74
  // Since we don't use LSNs anymore, the only way to get that is to wait.
@@ -62,7 +62,7 @@ export class WalStreamTestContext implements AsyncDisposable {
62
62
  }
63
63
 
64
64
  async updateSyncRules(content: string) {
65
- const syncRules = await this.factory.updateSyncRules({ content: content });
65
+ const syncRules = await this.factory.updateSyncRules({ content: content, validate: true });
66
66
  this.storage = this.factory.getInstance(syncRules);
67
67
  return this.storage!;
68
68
  }