@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,275 +0,0 @@
1
- import {
2
- applyValueContext,
3
- CompatibilityContext,
4
- CompatibilityOption,
5
- CustomSqliteValue,
6
- DatabaseInputValue,
7
- SqliteValue,
8
- SqliteValueType,
9
- toSyncRulesValue
10
- } from '@powersync/service-sync-rules';
11
- import * as pgwire from '@powersync/service-jpgwire';
12
-
13
- interface BaseType {
14
- sqliteType: () => SqliteValueType;
15
- }
16
-
17
- /** A type natively supported by {@link pgwire.PgType.decode}. */
18
- interface BuiltinType extends BaseType {
19
- type: 'builtin';
20
- oid: number;
21
- }
22
-
23
- /**
24
- * An array type.
25
- */
26
- interface ArrayType extends BaseType {
27
- type: 'array';
28
- innerId: number;
29
- separatorCharCode: number;
30
- }
31
-
32
- /**
33
- * A domain type, like `CREATE DOMAIN api.rating_value AS FLOAT CHECK (VALUE BETWEEN 0 AND 5);`
34
- *
35
- * This type gets decoded and synced as the inner type (`FLOAT` in the example above).
36
- */
37
- interface DomainType extends BaseType {
38
- type: 'domain';
39
- innerId: number;
40
- }
41
-
42
- /**
43
- * A composite type as created by `CREATE TYPE AS`.
44
- *
45
- * These types are encoded as a tuple of values, so we recover attribute names to restore them as a JSON object.
46
- */
47
- interface CompositeType extends BaseType {
48
- type: 'composite';
49
- members: { name: string; typeId: number }[];
50
- }
51
-
52
- /**
53
- * A type created with `CREATE TYPE AS RANGE`.
54
- *
55
- * Ranges are represented as {@link pgwire.Range}. Multiranges are represented as arrays thereof.
56
- */
57
- interface RangeType extends BaseType {
58
- type: 'range' | 'multirange';
59
- innerId: number;
60
- }
61
-
62
- type KnownType = BuiltinType | ArrayType | DomainType | CompositeType | RangeType;
63
-
64
- interface UnknownType extends BaseType {
65
- type: 'unknown';
66
- }
67
-
68
- type MaybeKnownType = KnownType | UnknownType;
69
-
70
- const UNKNOWN_TYPE: UnknownType = {
71
- type: 'unknown',
72
- sqliteType: () => 'text'
73
- };
74
-
75
- class CustomTypeValue extends CustomSqliteValue {
76
- constructor(
77
- readonly oid: number,
78
- readonly cache: CustomTypeRegistry,
79
- readonly rawValue: string
80
- ) {
81
- super();
82
- }
83
-
84
- private lookup(): KnownType | UnknownType {
85
- return this.cache.lookupType(this.oid);
86
- }
87
-
88
- private decodeToDatabaseInputValue(context: CompatibilityContext): DatabaseInputValue {
89
- if (context.isEnabled(CompatibilityOption.customTypes)) {
90
- try {
91
- return this.cache.decodeWithCustomTypes(this.rawValue, this.oid);
92
- } catch (_e) {
93
- return this.rawValue;
94
- }
95
- } else {
96
- return pgwire.PgType.decode(this.rawValue, this.oid);
97
- }
98
- }
99
-
100
- toSqliteValue(context: CompatibilityContext): SqliteValue {
101
- const value = toSyncRulesValue(this.decodeToDatabaseInputValue(context));
102
- return applyValueContext(value, context);
103
- }
104
-
105
- get sqliteType(): SqliteValueType {
106
- return this.lookup().sqliteType();
107
- }
108
- }
109
-
110
- /**
111
- * A registry of custom types.
112
- *
113
- * These extend the builtin decoding behavior in {@link pgwire.PgType.decode} for user-defined types like `DOMAIN`s or
114
- * composite types.
115
- */
116
- export class CustomTypeRegistry {
117
- private readonly byOid: Map<number, KnownType>;
118
-
119
- constructor() {
120
- this.byOid = new Map();
121
-
122
- for (const builtin of Object.values(pgwire.PgTypeOid)) {
123
- if (typeof builtin == 'number') {
124
- // We need to know the SQLite type of builtins to implement CustomSqliteValue.sqliteType for DOMAIN types.
125
- let sqliteType: SqliteValueType;
126
- switch (builtin) {
127
- case pgwire.PgTypeOid.TEXT:
128
- case pgwire.PgTypeOid.UUID:
129
- case pgwire.PgTypeOid.VARCHAR:
130
- case pgwire.PgTypeOid.DATE:
131
- case pgwire.PgTypeOid.TIMESTAMP:
132
- case pgwire.PgTypeOid.TIMESTAMPTZ:
133
- case pgwire.PgTypeOid.TIME:
134
- case pgwire.PgTypeOid.JSON:
135
- case pgwire.PgTypeOid.JSONB:
136
- case pgwire.PgTypeOid.PG_LSN:
137
- sqliteType = 'text';
138
- break;
139
- case pgwire.PgTypeOid.BYTEA:
140
- sqliteType = 'blob';
141
- break;
142
- case pgwire.PgTypeOid.BOOL:
143
- case pgwire.PgTypeOid.INT2:
144
- case pgwire.PgTypeOid.INT4:
145
- case pgwire.PgTypeOid.OID:
146
- case pgwire.PgTypeOid.INT8:
147
- sqliteType = 'integer';
148
- break;
149
- case pgwire.PgTypeOid.FLOAT4:
150
- case pgwire.PgTypeOid.FLOAT8:
151
- sqliteType = 'real';
152
- break;
153
- default:
154
- sqliteType = 'text';
155
- }
156
-
157
- this.byOid.set(builtin, {
158
- type: 'builtin',
159
- oid: builtin,
160
- sqliteType: () => sqliteType
161
- });
162
- }
163
- }
164
-
165
- for (const [arrayId, innerId] of pgwire.ARRAY_TO_ELEM_OID.entries()) {
166
- // We can just use the default decoder, except for box[] because those use a different delimiter. We don't fix
167
- // this in PgType._decodeArray for backwards-compatibility.
168
- if (innerId == 603) {
169
- this.byOid.set(arrayId, {
170
- type: 'array',
171
- innerId,
172
- sqliteType: () => 'text', // these get encoded as JSON arrays
173
- separatorCharCode: 0x3b // ";"
174
- });
175
- } else {
176
- this.byOid.set(arrayId, {
177
- type: 'builtin',
178
- oid: arrayId,
179
- sqliteType: () => 'text' // these get encoded as JSON arrays
180
- });
181
- }
182
- }
183
- }
184
-
185
- knows(oid: number): boolean {
186
- return this.byOid.has(oid);
187
- }
188
-
189
- set(oid: number, value: KnownType) {
190
- this.byOid.set(oid, value);
191
- }
192
-
193
- setDomainType(oid: number, inner: number) {
194
- this.set(oid, {
195
- type: 'domain',
196
- innerId: inner,
197
- sqliteType: () => this.lookupType(inner).sqliteType()
198
- });
199
- }
200
-
201
- decodeWithCustomTypes(raw: string, oid: number): DatabaseInputValue {
202
- const resolved = this.lookupType(oid);
203
- switch (resolved.type) {
204
- case 'builtin':
205
- case 'unknown':
206
- return pgwire.PgType.decode(raw, oid);
207
- case 'domain':
208
- return this.decodeWithCustomTypes(raw, resolved.innerId);
209
- case 'composite': {
210
- const parsed: [string, any][] = [];
211
-
212
- new pgwire.StructureParser(raw).parseComposite((raw) => {
213
- const nextMember = resolved.members[parsed.length];
214
- if (nextMember) {
215
- const value = raw == null ? null : this.decodeWithCustomTypes(raw, nextMember.typeId);
216
- parsed.push([nextMember.name, value]);
217
- }
218
- });
219
- return Object.fromEntries(parsed);
220
- }
221
- case 'array': {
222
- // Nornalize "array of array of T" types into just "array of T", because Postgres arrays are natively multi-
223
- // dimensional. This may be required when we have a DOMAIN wrapper around an array followed by another array
224
- // around that domain.
225
- let innerId = resolved.innerId;
226
- while (true) {
227
- const resolvedInner = this.lookupType(innerId);
228
- if (resolvedInner.type == 'domain') {
229
- innerId = resolvedInner.innerId;
230
- } else if (resolvedInner.type == 'array') {
231
- innerId = resolvedInner.innerId;
232
- } else {
233
- break;
234
- }
235
- }
236
-
237
- return new pgwire.StructureParser(raw).parseArray(
238
- (source) => this.decodeWithCustomTypes(source, innerId),
239
- resolved.separatorCharCode
240
- );
241
- }
242
- case 'range':
243
- return new pgwire.StructureParser(raw).parseRange((s) => this.decodeWithCustomTypes(s, resolved.innerId));
244
- case 'multirange':
245
- return new pgwire.StructureParser(raw).parseMultiRange((s) => this.decodeWithCustomTypes(s, resolved.innerId));
246
- }
247
- }
248
-
249
- lookupType(type: number): KnownType | UnknownType {
250
- return this.byOid.get(type) ?? UNKNOWN_TYPE;
251
- }
252
-
253
- private isParsedWithoutCustomTypesSupport(type: MaybeKnownType): boolean {
254
- switch (type.type) {
255
- case 'builtin':
256
- case 'unknown':
257
- return true;
258
- case 'array':
259
- return type.separatorCharCode == pgwire.CHAR_CODE_COMMA && pgwire.ARRAY_TO_ELEM_OID.has(type.innerId);
260
- default:
261
- return false;
262
- }
263
- }
264
-
265
- decodeDatabaseValue(value: string, oid: number): DatabaseInputValue {
266
- const resolved = this.lookupType(oid);
267
- // For backwards-compatibility, some types are only properly parsed with a compatibility option. Others are synced
268
- // in the raw text representation by default, and are only parsed as JSON values when necessary.
269
- if (this.isParsedWithoutCustomTypesSupport(resolved)) {
270
- return pgwire.PgType.decode(value, oid);
271
- } else {
272
- return new CustomTypeValue(oid, this, value);
273
- }
274
- }
275
- }
@@ -1,227 +0,0 @@
1
- import * as pgwire from '@powersync/service-jpgwire';
2
- import { DatabaseInputRow, SqliteInputRow, toSyncRulesRow } from '@powersync/service-sync-rules';
3
- import semver from 'semver';
4
- import { getServerVersion } from '../utils/postgres_version.js';
5
- import { CustomTypeRegistry } from './registry.js';
6
-
7
- /**
8
- * Resolves descriptions used to decode values for custom postgres types.
9
- *
10
- * Custom types are resolved from the source database, which also involves crawling inner types (e.g. for composites).
11
- */
12
- export class PostgresTypeResolver {
13
- private cachedVersion: semver.SemVer | null = null;
14
- readonly registry: CustomTypeRegistry;
15
-
16
- constructor(private readonly pool: pgwire.PgClient) {
17
- this.registry = new CustomTypeRegistry();
18
- }
19
-
20
- private async fetchVersion(): Promise<semver.SemVer> {
21
- if (this.cachedVersion == null) {
22
- this.cachedVersion = (await getServerVersion(this.pool)) ?? semver.parse('0.0.1');
23
- }
24
-
25
- return this.cachedVersion!;
26
- }
27
-
28
- /**
29
- * @returns Whether the Postgres instance this type cache is connected to has support for the multirange type (which
30
- * is the case for Postgres 14 and later).
31
- */
32
- async supportsMultiRanges() {
33
- const version = await this.fetchVersion();
34
- return version.compare(PostgresTypeResolver.minVersionForMultirange) >= 0;
35
- }
36
-
37
- /**
38
- * Fetches information about indicated types.
39
- *
40
- * If a type references another custom type (e.g. because it's a composite type with a custom field), these are
41
- * automatically crawled as well.
42
- */
43
- public async fetchTypes(oids: number[]) {
44
- const multiRangeSupport = await this.supportsMultiRanges();
45
-
46
- let pending = oids.filter((id) => !this.registry.knows(id));
47
- // For details on columns, see https://www.postgresql.org/docs/current/catalog-pg-type.html
48
- const multiRangeDesc = `WHEN 'm' THEN json_build_object('inner', (SELECT rngsubtype FROM pg_range WHERE rngmultitypid = t.oid))`;
49
- const statement = `
50
- SELECT oid, t.typtype,
51
- CASE t.typtype
52
- WHEN 'b' THEN json_build_object('element_type', t.typelem, 'delim', (SELECT typdelim FROM pg_type i WHERE i.oid = t.typelem))
53
- WHEN 'd' THEN json_build_object('type', t.typbasetype)
54
- WHEN 'c' THEN json_build_object(
55
- 'elements',
56
- (SELECT json_agg(json_build_object('name', a.attname, 'type', a.atttypid))
57
- FROM pg_attribute a
58
- WHERE a.attrelid = t.typrelid)
59
- )
60
- WHEN 'r' THEN json_build_object('inner', (SELECT rngsubtype FROM pg_range WHERE rngtypid = t.oid))
61
- ${multiRangeSupport ? multiRangeDesc : ''}
62
- ELSE NULL
63
- END AS desc
64
- FROM pg_type t
65
- WHERE t.oid = ANY($1)
66
- `;
67
-
68
- while (pending.length != 0) {
69
- // 1016: int8 array
70
- const query = await this.pool.query({ statement, params: [{ type: 1016, value: pending }] });
71
- const stillPending: number[] = [];
72
-
73
- const requireType = (oid: number) => {
74
- if (!this.registry.knows(oid) && !pending.includes(oid) && !stillPending.includes(oid)) {
75
- stillPending.push(oid);
76
- }
77
- };
78
-
79
- for (const row of pgwire.pgwireRows(query)) {
80
- const oid = Number(row.oid);
81
- const desc = JSON.parse(row.desc);
82
-
83
- switch (row.typtype) {
84
- case 'b':
85
- const { element_type, delim } = desc;
86
-
87
- if (!this.registry.knows(oid)) {
88
- // This type is an array of another custom type.
89
- const inner = Number(element_type);
90
- if (inner != 0) {
91
- // Some array types like macaddr[] don't seem to have their inner type set properly - skip!
92
- requireType(inner);
93
- this.registry.set(oid, {
94
- type: 'array',
95
- innerId: inner,
96
- separatorCharCode: (delim as string).charCodeAt(0),
97
- sqliteType: () => 'text' // Since it's JSON
98
- });
99
- }
100
- }
101
- break;
102
- case 'c':
103
- // For composite types, we sync the JSON representation.
104
- const elements: { name: string; typeId: number }[] = [];
105
- for (const { name, type } of desc.elements) {
106
- const typeId = Number(type);
107
- elements.push({ name, typeId });
108
- requireType(typeId);
109
- }
110
-
111
- this.registry.set(oid, {
112
- type: 'composite',
113
- members: elements,
114
- sqliteType: () => 'text' // Since it's JSON
115
- });
116
- break;
117
- case 'd':
118
- // For domain values like CREATE DOMAIN api.rating_value AS FLOAT CHECK (VALUE BETWEEN 0 AND 5), we sync
119
- // the inner type (pg_type.typbasetype).
120
- const inner = Number(desc.type);
121
- this.registry.setDomainType(oid, inner);
122
- requireType(inner);
123
- break;
124
- case 'r':
125
- case 'm': {
126
- const inner = Number(desc.inner);
127
- this.registry.set(oid, {
128
- type: row.typtype == 'r' ? 'range' : 'multirange',
129
- innerId: inner,
130
- sqliteType: () => 'text' // Since it's JSON
131
- });
132
- }
133
- }
134
- }
135
-
136
- pending = stillPending;
137
- }
138
- }
139
-
140
- /**
141
- * Crawls all custom types referenced by table columns in the current database.
142
- */
143
- public async fetchTypesForSchema() {
144
- const sql = `
145
- SELECT DISTINCT a.atttypid AS type_oid
146
- FROM pg_attribute a
147
- JOIN pg_class c ON c.oid = a.attrelid
148
- JOIN pg_namespace cn ON cn.oid = c.relnamespace
149
- JOIN pg_type t ON t.oid = a.atttypid
150
- JOIN pg_namespace tn ON tn.oid = t.typnamespace
151
- WHERE a.attnum > 0
152
- AND NOT a.attisdropped
153
- AND cn.nspname not in ('information_schema', 'pg_catalog', 'pg_toast')
154
- `;
155
-
156
- const query = await this.pool.query(sql);
157
- let ids: number[] = [];
158
- for (const row of pgwire.pgwireRows(query)) {
159
- ids.push(Number(row.type_oid));
160
- }
161
-
162
- await this.fetchTypes(ids);
163
- }
164
-
165
- /**
166
- * pgwire message -> SQLite row.
167
- * @param message
168
- */
169
- constructAfterRecord(message: pgwire.PgoutputInsert | pgwire.PgoutputUpdate): SqliteInputRow {
170
- const rawData = (message as any).afterRaw;
171
-
172
- const record = this.decodeTuple(message.relation, rawData);
173
- return toSyncRulesRow(record);
174
- }
175
-
176
- /**
177
- * pgwire message -> SQLite row.
178
- * @param message
179
- */
180
- constructBeforeRecord(message: pgwire.PgoutputDelete | pgwire.PgoutputUpdate): SqliteInputRow | undefined {
181
- const rawData = (message as any).beforeRaw;
182
- if (rawData == null) {
183
- return undefined;
184
- }
185
- const record = this.decodeTuple(message.relation, rawData);
186
- return toSyncRulesRow(record);
187
- }
188
-
189
- /**
190
- * We need a high level of control over how values are decoded, to make sure there is no loss
191
- * of precision in the process.
192
- */
193
- decodeTuple(relation: pgwire.PgoutputRelation, tupleRaw: Record<string, any>): DatabaseInputRow {
194
- let result: Record<string, any> = {};
195
- for (const column of relation.columns) {
196
- const rawval = tupleRaw[column.name];
197
- result[column.name] =
198
- rawval == null
199
- ? // We can't decode null values, but it's important that null and undefined stay distinct because undefined
200
- // represents a TOASTed value.
201
- rawval
202
- : this.registry.decodeDatabaseValue(rawval, column.typeOid);
203
- }
204
-
205
- return result;
206
- }
207
-
208
- /**
209
- * We need a high level of control over how values are decoded, to make sure there is no loss
210
- * of precision in the process.
211
- */
212
- private decodeTupleForTable(columnMap: Record<string, number>, tupleRaw: Record<string, any>): DatabaseInputRow {
213
- let result: Record<string, any> = {};
214
- for (let columnName in tupleRaw) {
215
- const rawval = tupleRaw[columnName];
216
- const typeOid = columnMap[columnName];
217
- if (typeof rawval == 'string' && typeOid) {
218
- result[columnName] = this.registry.decodeDatabaseValue(rawval, typeOid);
219
- } else {
220
- result[columnName] = rawval;
221
- }
222
- }
223
- return result;
224
- }
225
-
226
- private static minVersionForMultirange: semver.SemVer = semver.parse('14.0.0')!;
227
- }
@@ -1,44 +0,0 @@
1
- import * as lib_postgres from '@powersync/lib-service-postgres';
2
- import * as service_types from '@powersync/service-types';
3
- import * as t from 'ts-codec';
4
-
5
- // Maintain backwards compatibility by exporting these
6
- export const validatePort = lib_postgres.validatePort;
7
- export const baseUri = lib_postgres.baseUri;
8
- export type NormalizedPostgresConnectionConfig = lib_postgres.NormalizedBasePostgresConnectionConfig;
9
- export const POSTGRES_CONNECTION_TYPE = lib_postgres.POSTGRES_CONNECTION_TYPE;
10
-
11
- export const PostgresConnectionConfig = service_types.configFile.DataSourceConfig.and(
12
- lib_postgres.BasePostgresConnectionConfig
13
- ).and(
14
- t.object({
15
- // Add any replication connection specific config here in future
16
- })
17
- );
18
-
19
- /**
20
- * Config input specified when starting services
21
- */
22
- export type PostgresConnectionConfig = t.Decoded<typeof PostgresConnectionConfig>;
23
-
24
- /**
25
- * Resolved version of {@link PostgresConnectionConfig}
26
- */
27
- export type ResolvedConnectionConfig = PostgresConnectionConfig & NormalizedPostgresConnectionConfig;
28
-
29
- export function isPostgresConfig(
30
- config: service_types.configFile.DataSourceConfig
31
- ): config is PostgresConnectionConfig {
32
- return config.type == lib_postgres.POSTGRES_CONNECTION_TYPE;
33
- }
34
-
35
- /**
36
- * Validate and normalize connection options.
37
- *
38
- * Returns destructured options.
39
- */
40
- export function normalizeConnectionConfig(options: PostgresConnectionConfig) {
41
- return {
42
- ...lib_postgres.normalizeConnectionConfig(options)
43
- } satisfies NormalizedPostgresConnectionConfig;
44
- }
@@ -1,8 +0,0 @@
1
- import { POWERSYNC_VERSION } from '@powersync/service-core';
2
-
3
- /**
4
- * application_name for PostgreSQL connections to the source database
5
- */
6
- export function getApplicationName() {
7
- return `powersync/${POWERSYNC_VERSION}`;
8
- }
@@ -1,80 +0,0 @@
1
- import { ServiceAssertionError } from '@powersync/lib-services-framework';
2
- import * as pgwire from '@powersync/service-jpgwire';
3
-
4
- export type MigrationFunction = (db: pgwire.PgConnection) => Promise<void>;
5
-
6
- interface Migration {
7
- id: number;
8
- name: string;
9
- up: MigrationFunction;
10
- }
11
-
12
- // Very loosely based on https://github.com/porsager/postgres-shift/
13
- export class Migrations {
14
- private migrations: Migration[] = [];
15
-
16
- add(id: number, name: string, up: MigrationFunction) {
17
- if (this.migrations.length > 0 && this.migrations[this.migrations.length - 1].id >= id) {
18
- throw new ServiceAssertionError('Migration ids must be strictly incrementing');
19
- }
20
- this.migrations.push({ id, up, name });
21
- }
22
-
23
- async up(db: pgwire.PgConnection) {
24
- await db.query('BEGIN');
25
- try {
26
- await this.ensureMigrationsTable(db);
27
- const current = await this.getCurrentMigration(db);
28
- let currentId = current ? current.id : 0;
29
-
30
- for (let migration of this.migrations) {
31
- if (migration.id <= currentId) {
32
- continue;
33
- }
34
- await migration.up(db);
35
-
36
- await db.query({
37
- statement: `
38
- insert into migrations (
39
- migration_id,
40
- name
41
- ) values (
42
- $1,
43
- $2
44
- )
45
- `,
46
- params: [
47
- { type: 'int4', value: migration.id },
48
- { type: 'varchar', value: migration.name }
49
- ]
50
- });
51
- }
52
-
53
- await db.query('COMMIT');
54
- } catch (e) {
55
- await db.query('ROLLBACK');
56
- throw e;
57
- }
58
- }
59
-
60
- getCurrentMigration(db: pgwire.PgConnection) {
61
- return db
62
- .query(
63
- `
64
- select migration_id as id from migrations
65
- order by migration_id desc
66
- limit 1
67
- `
68
- )
69
- .then((results) => ({ id: results.rows[0].decodeWithoutCustomTypes(0) as number }));
70
- }
71
-
72
- async ensureMigrationsTable(db: pgwire.PgConnection) {
73
- await db.query(`create table if not exists migrations (
74
- migration_id serial primary key,
75
- created_at timestamp with time zone not null default now(),
76
- name text
77
- )
78
- `);
79
- }
80
- }
@@ -1,37 +0,0 @@
1
- import { Worker } from 'node:worker_threads';
2
-
3
- import * as pgwire from '@powersync/service-jpgwire';
4
-
5
- // This util is actually for tests only, but we need it compiled to JS for the service to work, so it's placed in the service.
6
-
7
- export interface PopulateDataOptions {
8
- connection: pgwire.NormalizedConnectionConfig;
9
- num_transactions: number;
10
- per_transaction: number;
11
- size: number;
12
- }
13
-
14
- export async function populateData(options: PopulateDataOptions) {
15
- const WORKER_TIMEOUT = 30_000;
16
-
17
- const worker = new Worker(new URL('./populate_test_data_worker.js', import.meta.url), {
18
- workerData: options
19
- });
20
- const timeout = setTimeout(() => {
21
- // Exits with code 1 below
22
- worker.terminate();
23
- }, WORKER_TIMEOUT);
24
- try {
25
- return await new Promise<number>((resolve, reject) => {
26
- worker.on('message', resolve);
27
- worker.on('error', reject);
28
- worker.on('exit', (code) => {
29
- if (code !== 0) {
30
- reject(new Error(`Populating data failed with exit code ${code}`));
31
- }
32
- });
33
- });
34
- } finally {
35
- clearTimeout(timeout);
36
- }
37
- }