@powersync/service-module-postgres 0.0.0-dev-20260313100403 → 0.0.0-dev-20260515144844

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 (84) 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/PgManager.js +3 -2
  9. package/dist/replication/PgManager.js.map +1 -1
  10. package/dist/replication/PostgresErrorRateLimiter.js +1 -1
  11. package/dist/replication/PostgresErrorRateLimiter.js.map +1 -1
  12. package/dist/replication/SnapshotQuery.js +8 -7
  13. package/dist/replication/SnapshotQuery.js.map +1 -1
  14. package/dist/replication/WalStream.d.ts +37 -14
  15. package/dist/replication/WalStream.js +175 -60
  16. package/dist/replication/WalStream.js.map +1 -1
  17. package/dist/replication/WalStreamReplicationJob.d.ts +1 -1
  18. package/dist/replication/WalStreamReplicationJob.js +9 -6
  19. package/dist/replication/WalStreamReplicationJob.js.map +1 -1
  20. package/dist/replication/WalStreamReplicator.d.ts +0 -1
  21. package/dist/replication/WalStreamReplicator.js +0 -22
  22. package/dist/replication/WalStreamReplicator.js.map +1 -1
  23. package/dist/replication/replication-index.d.ts +3 -1
  24. package/dist/replication/replication-index.js +3 -1
  25. package/dist/replication/replication-index.js.map +1 -1
  26. package/dist/replication/replication-utils.d.ts +3 -11
  27. package/dist/replication/replication-utils.js +103 -165
  28. package/dist/replication/replication-utils.js.map +1 -1
  29. package/dist/replication/rquery.d.ts +5 -0
  30. package/dist/replication/rquery.js +35 -0
  31. package/dist/replication/rquery.js.map +1 -0
  32. package/dist/replication/wal-budget-utils.d.ts +23 -0
  33. package/dist/replication/wal-budget-utils.js +57 -0
  34. package/dist/replication/wal-budget-utils.js.map +1 -0
  35. package/dist/types/registry.js +1 -1
  36. package/dist/types/registry.js.map +1 -1
  37. package/dist/utils/errors.d.ts +2 -0
  38. package/dist/utils/errors.js +30 -0
  39. package/dist/utils/errors.js.map +1 -0
  40. package/package.json +17 -13
  41. package/sql/check-source-configuration.plpgsql +13 -0
  42. package/sql/debug-tables-info-batched.plpgsql +230 -0
  43. package/CHANGELOG.md +0 -834
  44. package/src/api/PostgresRouteAPIAdapter.ts +0 -356
  45. package/src/index.ts +0 -1
  46. package/src/module/PostgresModule.ts +0 -122
  47. package/src/replication/ConnectionManagerFactory.ts +0 -33
  48. package/src/replication/PgManager.ts +0 -122
  49. package/src/replication/PgRelation.ts +0 -41
  50. package/src/replication/PostgresErrorRateLimiter.ts +0 -48
  51. package/src/replication/SnapshotQuery.ts +0 -213
  52. package/src/replication/WalStream.ts +0 -1157
  53. package/src/replication/WalStreamReplicationJob.ts +0 -138
  54. package/src/replication/WalStreamReplicator.ts +0 -79
  55. package/src/replication/replication-index.ts +0 -5
  56. package/src/replication/replication-utils.ts +0 -398
  57. package/src/types/registry.ts +0 -275
  58. package/src/types/resolver.ts +0 -227
  59. package/src/types/types.ts +0 -44
  60. package/src/utils/application-name.ts +0 -8
  61. package/src/utils/migration_lib.ts +0 -80
  62. package/src/utils/populate_test_data.ts +0 -37
  63. package/src/utils/populate_test_data_worker.ts +0 -53
  64. package/src/utils/postgres_version.ts +0 -8
  65. package/test/src/checkpoints.test.ts +0 -86
  66. package/test/src/chunked_snapshots.test.ts +0 -161
  67. package/test/src/env.ts +0 -11
  68. package/test/src/large_batch.test.ts +0 -241
  69. package/test/src/pg_test.test.ts +0 -729
  70. package/test/src/resuming_snapshots.test.ts +0 -160
  71. package/test/src/route_api_adapter.test.ts +0 -62
  72. package/test/src/schema_changes.test.ts +0 -655
  73. package/test/src/setup.ts +0 -12
  74. package/test/src/slow_tests.test.ts +0 -519
  75. package/test/src/storage_combination.test.ts +0 -35
  76. package/test/src/types/registry.test.ts +0 -149
  77. package/test/src/util.ts +0 -151
  78. package/test/src/validation.test.ts +0 -63
  79. package/test/src/wal_stream.test.ts +0 -607
  80. package/test/src/wal_stream_utils.ts +0 -284
  81. package/test/tsconfig.json +0 -27
  82. package/tsconfig.json +0 -34
  83. package/tsconfig.tsbuildinfo +0 -1
  84. package/vitest.config.ts +0 -3
@@ -1,160 +0,0 @@
1
- import { METRICS_HELPER } from '@powersync/service-core-tests';
2
- import { ReplicationMetric } from '@powersync/service-types';
3
- import * as timers from 'node:timers/promises';
4
- import { describe, expect, test } from 'vitest';
5
- import { env } from './env.js';
6
- import { describeWithStorage, StorageVersionTestContext } from './util.js';
7
- import { WalStreamTestContext } from './wal_stream_utils.js';
8
-
9
- describe.skipIf(!(env.CI || env.SLOW_TESTS))('batch replication', function () {
10
- describeWithStorage({ timeout: 240_000 }, function ({ factory, storageVersion }) {
11
- test('resuming initial replication (1)', async () => {
12
- // Stop early - likely to not include deleted row in first replication attempt.
13
- await testResumingReplication(factory, storageVersion, 2000);
14
- });
15
- test('resuming initial replication (2)', async () => {
16
- // Stop late - likely to include deleted row in first replication attempt.
17
- await testResumingReplication(factory, storageVersion, 8000);
18
- });
19
- });
20
- });
21
-
22
- async function testResumingReplication(
23
- factory: StorageVersionTestContext['factory'],
24
- storageVersion: number,
25
- stopAfter: number
26
- ) {
27
- // This tests interrupting and then resuming initial replication.
28
- // We interrupt replication after test_data1 has fully replicated, and
29
- // test_data2 has partially replicated.
30
- // This test relies on interval behavior that is not 100% deterministic:
31
- // 1. We attempt to abort initial replication once a certain number of
32
- // rows have been replicated, but this is not exact. Our only requirement
33
- // is that we have not fully replicated test_data2 yet.
34
- // 2. Order of replication is not deterministic, so which specific rows
35
- // have been / have not been replicated at that point is not deterministic.
36
- // We do allow for some variation in the test results to account for this.
37
-
38
- await using context = await WalStreamTestContext.open(factory, {
39
- storageVersion,
40
- walStreamOptions: { snapshotChunkLength: 1000 }
41
- });
42
-
43
- await context.updateSyncRules(`bucket_definitions:
44
- global:
45
- data:
46
- - SELECT * FROM test_data1
47
- - SELECT * FROM test_data2`);
48
- const { pool } = context;
49
-
50
- await pool.query(`CREATE TABLE test_data1(id serial primary key, description text)`);
51
- await pool.query(`CREATE TABLE test_data2(id serial primary key, description text)`);
52
-
53
- await pool.query(
54
- {
55
- statement: `INSERT INTO test_data1(description) SELECT 'foo' FROM generate_series(1, 1000) i`
56
- },
57
- {
58
- statement: `INSERT INTO test_data2( description) SELECT 'foo' FROM generate_series(1, 10000) i`
59
- }
60
- );
61
-
62
- const p = context.replicateSnapshot();
63
-
64
- let done = false;
65
-
66
- const startRowCount = (await METRICS_HELPER.getMetricValueForTests(ReplicationMetric.ROWS_REPLICATED)) ?? 0;
67
- try {
68
- (async () => {
69
- while (!done) {
70
- const count =
71
- ((await METRICS_HELPER.getMetricValueForTests(ReplicationMetric.ROWS_REPLICATED)) ?? 0) - startRowCount;
72
-
73
- if (count >= stopAfter) {
74
- break;
75
- }
76
- await timers.setTimeout(1);
77
- }
78
- // This interrupts initial replication
79
- await context.dispose();
80
- })();
81
- // This confirms that initial replication was interrupted
82
- await expect(p).rejects.toThrowError();
83
- done = true;
84
- } finally {
85
- done = true;
86
- }
87
-
88
- // Bypass the usual "clear db on factory open" step.
89
- await using context2 = await WalStreamTestContext.open(factory, {
90
- doNotClear: true,
91
- storageVersion,
92
- walStreamOptions: { snapshotChunkLength: 1000 }
93
- });
94
-
95
- // This delete should be using one of the ids already replicated
96
- const {
97
- rows: [delete1]
98
- } = await context2.pool.query(`DELETE FROM test_data2 WHERE id = (SELECT id FROM test_data2 LIMIT 1) RETURNING id`);
99
- // This update should also be using one of the ids already replicated
100
- const {
101
- rows: [delete2]
102
- } = await context2.pool.query(
103
- `UPDATE test_data2 SET description = 'update1' WHERE id = (SELECT id FROM test_data2 LIMIT 1) RETURNING id`
104
- );
105
- const {
106
- rows: [delete3]
107
- } = await context2.pool.query(`INSERT INTO test_data2(description) SELECT 'insert1' RETURNING id`);
108
-
109
- await context2.loadNextSyncRules();
110
- await context2.replicateSnapshot();
111
-
112
- const data = await context2.getBucketData('global[]', undefined, {});
113
-
114
- const deletedRowOps = data.filter(
115
- (row) => row.object_type == 'test_data2' && row.object_id === String(delete1.decodeWithoutCustomTypes(0))
116
- );
117
- const updatedRowOps = data.filter(
118
- (row) => row.object_type == 'test_data2' && row.object_id === String(delete2.decodeWithoutCustomTypes(0))
119
- );
120
- const insertedRowOps = data.filter(
121
- (row) => row.object_type == 'test_data2' && row.object_id === String(delete3.decodeWithoutCustomTypes(0))
122
- );
123
-
124
- if (deletedRowOps.length != 0) {
125
- // The deleted row was part of the first replication batch,
126
- // so it is removed by streaming replication.
127
- expect(deletedRowOps.length).toEqual(2);
128
- expect(deletedRowOps[1].op).toEqual('REMOVE');
129
- } else {
130
- // The deleted row was not part of the first replication batch,
131
- // so it's not in the resulting ops at all.
132
- }
133
-
134
- expect(updatedRowOps.length).toBeGreaterThanOrEqual(2);
135
- // description for the first op could be 'foo' or 'update1'.
136
- // We only test the final version.
137
- expect(JSON.parse(updatedRowOps[updatedRowOps.length - 1].data as string).description).toEqual('update1');
138
-
139
- expect(insertedRowOps.length).toBeGreaterThanOrEqual(1);
140
- expect(JSON.parse(insertedRowOps[0].data as string).description).toEqual('insert1');
141
- expect(JSON.parse(insertedRowOps[insertedRowOps.length - 1].data as string).description).toEqual('insert1');
142
-
143
- // 1000 of test_data1 during first replication attempt.
144
- // N >= 1000 of test_data2 during first replication attempt.
145
- // 10000 - N - 1 + 1 of test_data2 during second replication attempt.
146
- // An additional update during streaming replication (2x total for this row).
147
- // An additional insert during streaming replication (2x total for this row).
148
- // If the deleted row was part of the first replication batch, it's removed by streaming replication.
149
- // This adds 2 ops.
150
- // We expect this to be 11002 for stopAfter: 2000, and 11004 for stopAfter: 8000.
151
- // However, this is not deterministic.
152
- const expectedCount = 11000 - 2 + insertedRowOps.length + updatedRowOps.length + deletedRowOps.length;
153
- expect(data.length).toEqual(expectedCount);
154
-
155
- const replicatedCount =
156
- ((await METRICS_HELPER.getMetricValueForTests(ReplicationMetric.ROWS_REPLICATED)) ?? 0) - startRowCount;
157
-
158
- // With resumable replication, there should be no need to re-replicate anything.
159
- expect(replicatedCount).toBeGreaterThanOrEqual(expectedCount);
160
- }
@@ -1,62 +0,0 @@
1
- import { PostgresRouteAPIAdapter } from '@module/api/PostgresRouteAPIAdapter.js';
2
- import { TYPE_INTEGER, TYPE_REAL, TYPE_TEXT } from '@powersync/service-sync-rules';
3
- import { describe, expect, test } from 'vitest';
4
- import { clearTestDb, connectPgPool } from './util.js';
5
-
6
- describe('PostgresRouteAPIAdapter tests', () => {
7
- test('infers connection schema', async () => {
8
- const db = await connectPgPool();
9
- try {
10
- await clearTestDb(db);
11
- const api = new PostgresRouteAPIAdapter(db);
12
-
13
- await db.query(`CREATE DOMAIN rating_value AS FLOAT CHECK (VALUE BETWEEN 0 AND 5)`);
14
- await db.query(`
15
- CREATE TABLE test_users (
16
- id TEXT NOT NULL PRIMARY KEY,
17
- is_admin BOOLEAN,
18
- rating RATING_VALUE
19
- );
20
- `);
21
-
22
- const schema = await api.getConnectionSchema();
23
- // Ignore any other potential schemas in the test database, for example the 'powersync' schema.
24
- const filtered = schema.filter((s) => s.name == 'public');
25
- expect(filtered).toStrictEqual([
26
- {
27
- name: 'public',
28
- tables: [
29
- {
30
- name: 'test_users',
31
- columns: [
32
- {
33
- internal_type: 'text',
34
- name: 'id',
35
- pg_type: 'text',
36
- sqlite_type: TYPE_TEXT,
37
- type: 'text'
38
- },
39
- {
40
- internal_type: 'boolean',
41
- name: 'is_admin',
42
- pg_type: 'bool',
43
- sqlite_type: TYPE_INTEGER,
44
- type: 'boolean'
45
- },
46
- {
47
- internal_type: 'rating_value',
48
- name: 'rating',
49
- pg_type: 'rating_value',
50
- sqlite_type: TYPE_REAL,
51
- type: 'rating_value'
52
- }
53
- ]
54
- }
55
- ]
56
- }
57
- ]);
58
- } finally {
59
- await db.end();
60
- }
61
- });
62
- });