@powersync/service-module-postgres 0.0.0-dev-20240918092408
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 +18 -0
- package/LICENSE +67 -0
- package/README.md +3 -0
- package/dist/api/PostgresRouteAPIAdapter.d.ts +22 -0
- package/dist/api/PostgresRouteAPIAdapter.js +273 -0
- package/dist/api/PostgresRouteAPIAdapter.js.map +1 -0
- package/dist/auth/SupabaseKeyCollector.d.ts +22 -0
- package/dist/auth/SupabaseKeyCollector.js +64 -0
- package/dist/auth/SupabaseKeyCollector.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/module/PostgresModule.d.ts +14 -0
- package/dist/module/PostgresModule.js +108 -0
- package/dist/module/PostgresModule.js.map +1 -0
- package/dist/replication/ConnectionManagerFactory.d.ts +10 -0
- package/dist/replication/ConnectionManagerFactory.js +21 -0
- package/dist/replication/ConnectionManagerFactory.js.map +1 -0
- package/dist/replication/PgManager.d.ts +25 -0
- package/dist/replication/PgManager.js +60 -0
- package/dist/replication/PgManager.js.map +1 -0
- package/dist/replication/PgRelation.d.ts +6 -0
- package/dist/replication/PgRelation.js +27 -0
- package/dist/replication/PgRelation.js.map +1 -0
- package/dist/replication/PostgresErrorRateLimiter.d.ts +11 -0
- package/dist/replication/PostgresErrorRateLimiter.js +43 -0
- package/dist/replication/PostgresErrorRateLimiter.js.map +1 -0
- package/dist/replication/WalStream.d.ts +53 -0
- package/dist/replication/WalStream.js +536 -0
- package/dist/replication/WalStream.js.map +1 -0
- package/dist/replication/WalStreamReplicationJob.d.ts +27 -0
- package/dist/replication/WalStreamReplicationJob.js +131 -0
- package/dist/replication/WalStreamReplicationJob.js.map +1 -0
- package/dist/replication/WalStreamReplicator.d.ts +13 -0
- package/dist/replication/WalStreamReplicator.js +36 -0
- package/dist/replication/WalStreamReplicator.js.map +1 -0
- package/dist/replication/replication-index.d.ts +5 -0
- package/dist/replication/replication-index.js +6 -0
- package/dist/replication/replication-index.js.map +1 -0
- package/dist/replication/replication-utils.d.ts +32 -0
- package/dist/replication/replication-utils.js +272 -0
- package/dist/replication/replication-utils.js.map +1 -0
- package/dist/types/types.d.ts +76 -0
- package/dist/types/types.js +110 -0
- package/dist/types/types.js.map +1 -0
- package/dist/utils/migration_lib.d.ts +11 -0
- package/dist/utils/migration_lib.js +64 -0
- package/dist/utils/migration_lib.js.map +1 -0
- package/dist/utils/pgwire_utils.d.ts +16 -0
- package/dist/utils/pgwire_utils.js +70 -0
- package/dist/utils/pgwire_utils.js.map +1 -0
- package/dist/utils/populate_test_data.d.ts +8 -0
- package/dist/utils/populate_test_data.js +65 -0
- package/dist/utils/populate_test_data.js.map +1 -0
- package/package.json +49 -0
- package/src/api/PostgresRouteAPIAdapter.ts +307 -0
- package/src/auth/SupabaseKeyCollector.ts +70 -0
- package/src/index.ts +5 -0
- package/src/module/PostgresModule.ts +122 -0
- package/src/replication/ConnectionManagerFactory.ts +28 -0
- package/src/replication/PgManager.ts +70 -0
- package/src/replication/PgRelation.ts +31 -0
- package/src/replication/PostgresErrorRateLimiter.ts +44 -0
- package/src/replication/WalStream.ts +639 -0
- package/src/replication/WalStreamReplicationJob.ts +142 -0
- package/src/replication/WalStreamReplicator.ts +45 -0
- package/src/replication/replication-index.ts +5 -0
- package/src/replication/replication-utils.ts +329 -0
- package/src/types/types.ts +159 -0
- package/src/utils/migration_lib.ts +79 -0
- package/src/utils/pgwire_utils.ts +73 -0
- package/src/utils/populate_test_data.ts +77 -0
- package/test/src/__snapshots__/pg_test.test.ts.snap +256 -0
- package/test/src/env.ts +7 -0
- package/test/src/large_batch.test.ts +195 -0
- package/test/src/pg_test.test.ts +450 -0
- package/test/src/schema_changes.test.ts +543 -0
- package/test/src/setup.ts +7 -0
- package/test/src/slow_tests.test.ts +335 -0
- package/test/src/util.ts +105 -0
- package/test/src/validation.test.ts +64 -0
- package/test/src/wal_stream.test.ts +319 -0
- package/test/src/wal_stream_utils.ts +121 -0
- package/test/tsconfig.json +28 -0
- package/tsconfig.json +31 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/vitest.config.ts +9 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import * as service_types from '@powersync/service-types';
|
|
2
|
+
import * as t from 'ts-codec';
|
|
3
|
+
import * as urijs from 'uri-js';
|
|
4
|
+
export const POSTGRES_CONNECTION_TYPE = 'postgresql';
|
|
5
|
+
export const PostgresConnectionConfig = service_types.configFile.dataSourceConfig.and(t.object({
|
|
6
|
+
type: t.literal(POSTGRES_CONNECTION_TYPE),
|
|
7
|
+
/** Unique identifier for the connection - optional when a single connection is present. */
|
|
8
|
+
id: t.string.optional(),
|
|
9
|
+
/** Tag used as reference in sync rules. Defaults to "default". Does not have to be unique. */
|
|
10
|
+
tag: t.string.optional(),
|
|
11
|
+
uri: t.string.optional(),
|
|
12
|
+
hostname: t.string.optional(),
|
|
13
|
+
port: service_types.configFile.portCodec.optional(),
|
|
14
|
+
username: t.string.optional(),
|
|
15
|
+
password: t.string.optional(),
|
|
16
|
+
database: t.string.optional(),
|
|
17
|
+
/** Defaults to verify-full */
|
|
18
|
+
sslmode: t.literal('verify-full').or(t.literal('verify-ca')).or(t.literal('disable')).optional(),
|
|
19
|
+
/** Required for verify-ca, optional for verify-full */
|
|
20
|
+
cacert: t.string.optional(),
|
|
21
|
+
client_certificate: t.string.optional(),
|
|
22
|
+
client_private_key: t.string.optional(),
|
|
23
|
+
/** Expose database credentials */
|
|
24
|
+
demo_database: t.boolean.optional(),
|
|
25
|
+
/**
|
|
26
|
+
* Prefix for the slot name. Defaults to "powersync_"
|
|
27
|
+
*/
|
|
28
|
+
slot_name_prefix: t.string.optional()
|
|
29
|
+
}));
|
|
30
|
+
/**
|
|
31
|
+
* Validate and normalize connection options.
|
|
32
|
+
*
|
|
33
|
+
* Returns destructured options.
|
|
34
|
+
*/
|
|
35
|
+
export function normalizeConnectionConfig(options) {
|
|
36
|
+
let uri;
|
|
37
|
+
if (options.uri) {
|
|
38
|
+
uri = urijs.parse(options.uri);
|
|
39
|
+
if (uri.scheme != 'postgresql' && uri.scheme != 'postgres') {
|
|
40
|
+
`Invalid URI - protocol must be postgresql, got ${uri.scheme}`;
|
|
41
|
+
}
|
|
42
|
+
else if (uri.scheme != 'postgresql') {
|
|
43
|
+
uri.scheme = 'postgresql';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
uri = urijs.parse('postgresql:///');
|
|
48
|
+
}
|
|
49
|
+
const hostname = options.hostname ?? uri.host ?? '';
|
|
50
|
+
const port = validatePort(options.port ?? uri.port ?? 5432);
|
|
51
|
+
const database = options.database ?? uri.path?.substring(1) ?? '';
|
|
52
|
+
const [uri_username, uri_password] = (uri.userinfo ?? '').split(':');
|
|
53
|
+
const username = options.username ?? uri_username ?? '';
|
|
54
|
+
const password = options.password ?? uri_password ?? '';
|
|
55
|
+
const sslmode = options.sslmode ?? 'verify-full'; // Configuration not supported via URI
|
|
56
|
+
const cacert = options.cacert;
|
|
57
|
+
if (sslmode == 'verify-ca' && cacert == null) {
|
|
58
|
+
throw new Error('Explicit cacert is required for sslmode=verify-ca');
|
|
59
|
+
}
|
|
60
|
+
if (hostname == '') {
|
|
61
|
+
throw new Error(`hostname required`);
|
|
62
|
+
}
|
|
63
|
+
if (username == '') {
|
|
64
|
+
throw new Error(`username required`);
|
|
65
|
+
}
|
|
66
|
+
if (password == '') {
|
|
67
|
+
throw new Error(`password required`);
|
|
68
|
+
}
|
|
69
|
+
if (database == '') {
|
|
70
|
+
throw new Error(`database required`);
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
id: options.id ?? 'default',
|
|
74
|
+
tag: options.tag ?? 'default',
|
|
75
|
+
hostname,
|
|
76
|
+
port,
|
|
77
|
+
database,
|
|
78
|
+
username,
|
|
79
|
+
password,
|
|
80
|
+
sslmode,
|
|
81
|
+
cacert,
|
|
82
|
+
client_certificate: options.client_certificate ?? undefined,
|
|
83
|
+
client_private_key: options.client_private_key ?? undefined
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Check whether the port is in a "safe" range.
|
|
88
|
+
*
|
|
89
|
+
* We do not support connecting to "privileged" ports.
|
|
90
|
+
*/
|
|
91
|
+
export function validatePort(port) {
|
|
92
|
+
if (typeof port == 'string') {
|
|
93
|
+
port = parseInt(port);
|
|
94
|
+
}
|
|
95
|
+
if (port >= 1024 && port <= 49151) {
|
|
96
|
+
return port;
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
throw new Error(`Port ${port} not supported`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Construct a postgres URI, without username, password or ssl options.
|
|
104
|
+
*
|
|
105
|
+
* Only contains hostname, port, database.
|
|
106
|
+
*/
|
|
107
|
+
export function baseUri(options) {
|
|
108
|
+
return `postgresql://${options.hostname}:${options.port}/${options.database}`;
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as pgwire from '@powersync/service-jpgwire';
|
|
2
|
+
export type MigrationFunction = (db: pgwire.PgConnection) => Promise<void>;
|
|
3
|
+
export declare class Migrations {
|
|
4
|
+
private migrations;
|
|
5
|
+
add(id: number, name: string, up: MigrationFunction): void;
|
|
6
|
+
up(db: pgwire.PgConnection): Promise<void>;
|
|
7
|
+
getCurrentMigration(db: pgwire.PgConnection): Promise<{
|
|
8
|
+
id: number;
|
|
9
|
+
}>;
|
|
10
|
+
ensureMigrationsTable(db: pgwire.PgConnection): Promise<void>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Very loosely based on https://github.com/porsager/postgres-shift/
|
|
2
|
+
export class Migrations {
|
|
3
|
+
constructor() {
|
|
4
|
+
this.migrations = [];
|
|
5
|
+
}
|
|
6
|
+
add(id, name, up) {
|
|
7
|
+
if (this.migrations.length > 0 && this.migrations[this.migrations.length - 1].id >= id) {
|
|
8
|
+
throw new Error('Migration ids must be strictly incrementing');
|
|
9
|
+
}
|
|
10
|
+
this.migrations.push({ id, up, name });
|
|
11
|
+
}
|
|
12
|
+
async up(db) {
|
|
13
|
+
await db.query('BEGIN');
|
|
14
|
+
try {
|
|
15
|
+
await this.ensureMigrationsTable(db);
|
|
16
|
+
const current = await this.getCurrentMigration(db);
|
|
17
|
+
let currentId = current ? current.id : 0;
|
|
18
|
+
for (let migration of this.migrations) {
|
|
19
|
+
if (migration.id <= currentId) {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
await migration.up(db);
|
|
23
|
+
await db.query({
|
|
24
|
+
statement: `
|
|
25
|
+
insert into migrations (
|
|
26
|
+
migration_id,
|
|
27
|
+
name
|
|
28
|
+
) values (
|
|
29
|
+
$1,
|
|
30
|
+
$2
|
|
31
|
+
)
|
|
32
|
+
`,
|
|
33
|
+
params: [
|
|
34
|
+
{ type: 'int4', value: migration.id },
|
|
35
|
+
{ type: 'varchar', value: migration.name }
|
|
36
|
+
]
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
await db.query('COMMIT');
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
await db.query('ROLLBACK');
|
|
43
|
+
throw e;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
getCurrentMigration(db) {
|
|
47
|
+
return db
|
|
48
|
+
.query(`
|
|
49
|
+
select migration_id as id from migrations
|
|
50
|
+
order by migration_id desc
|
|
51
|
+
limit 1
|
|
52
|
+
`)
|
|
53
|
+
.then((results) => ({ id: results.rows[0][0] }));
|
|
54
|
+
}
|
|
55
|
+
async ensureMigrationsTable(db) {
|
|
56
|
+
await db.query(`create table if not exists migrations (
|
|
57
|
+
migration_id serial primary key,
|
|
58
|
+
created_at timestamp with time zone not null default now(),
|
|
59
|
+
name text
|
|
60
|
+
)
|
|
61
|
+
`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=migration_lib.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migration_lib.js","sourceRoot":"","sources":["../../src/utils/migration_lib.ts"],"names":[],"mappings":"AAUA,oEAAoE;AACpE,MAAM,OAAO,UAAU;IAAvB;QACU,eAAU,GAAgB,EAAE,CAAC;IAkEvC,CAAC;IAhEC,GAAG,CAAC,EAAU,EAAE,IAAY,EAAE,EAAqB;QACjD,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE;YACtF,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;SAChE;QACD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;IACzC,CAAC;IAED,KAAK,CAAC,EAAE,CAAC,EAAuB;QAC9B,MAAM,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACxB,IAAI;YACF,MAAM,IAAI,CAAC,qBAAqB,CAAC,EAAE,CAAC,CAAC;YACrC,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAAC,EAAE,CAAC,CAAC;YACnD,IAAI,SAAS,GAAG,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;YAEzC,KAAK,IAAI,SAAS,IAAI,IAAI,CAAC,UAAU,EAAE;gBACrC,IAAI,SAAS,CAAC,EAAE,IAAI,SAAS,EAAE;oBAC7B,SAAS;iBACV;gBACD,MAAM,SAAS,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;gBAEvB,MAAM,EAAE,CAAC,KAAK,CAAC;oBACb,SAAS,EAAE;;;;;;;;KAQhB;oBACK,MAAM,EAAE;wBACN,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,EAAE;wBACrC,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,CAAC,IAAI,EAAE;qBAC3C;iBACF,CAAC,CAAC;aACJ;YAED,MAAM,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;SAC1B;QAAC,OAAO,CAAC,EAAE;YACV,MAAM,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;YAC3B,MAAM,CAAC,CAAC;SACT;IACH,CAAC;IAED,mBAAmB,CAAC,EAAuB;QACzC,OAAO,EAAE;aACN,KAAK,CACJ;;;;KAIH,CACE;aACA,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAW,EAAE,CAAC,CAAC,CAAC;IAC/D,CAAC;IAED,KAAK,CAAC,qBAAqB,CAAC,EAAuB;QACjD,MAAM,EAAE,CAAC,KAAK,CAAC;;;;;KAKd,CAAC,CAAC;IACL,CAAC;CACF"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as pgwire from '@powersync/service-jpgwire';
|
|
2
|
+
import { SqliteJsonValue, SqliteRow } from '@powersync/service-sync-rules';
|
|
3
|
+
/**
|
|
4
|
+
* pgwire message -> SQLite row.
|
|
5
|
+
* @param message
|
|
6
|
+
*/
|
|
7
|
+
export declare function constructAfterRecord(message: pgwire.PgoutputInsert | pgwire.PgoutputUpdate): SqliteRow;
|
|
8
|
+
/**
|
|
9
|
+
* pgwire message -> SQLite row.
|
|
10
|
+
* @param message
|
|
11
|
+
*/
|
|
12
|
+
export declare function constructBeforeRecord(message: pgwire.PgoutputDelete | pgwire.PgoutputUpdate): SqliteRow | undefined;
|
|
13
|
+
export declare function escapeIdentifier(identifier: string): string;
|
|
14
|
+
export declare function autoParameter(arg: SqliteJsonValue | boolean): pgwire.StatementParam;
|
|
15
|
+
export declare function retriedQuery(db: pgwire.PgClient, ...statements: pgwire.Statement[]): Promise<pgwire.PgResult>;
|
|
16
|
+
export declare function retriedQuery(db: pgwire.PgClient, query: string): Promise<pgwire.PgResult>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Adapted from https://github.com/kagis/pgwire/blob/0dc927f9f8990a903f238737326e53ba1c8d094f/mod.js#L2218
|
|
2
|
+
import * as pgwire from '@powersync/service-jpgwire';
|
|
3
|
+
import { toSyncRulesRow } from '@powersync/service-sync-rules';
|
|
4
|
+
import { logger } from '@powersync/lib-services-framework';
|
|
5
|
+
/**
|
|
6
|
+
* pgwire message -> SQLite row.
|
|
7
|
+
* @param message
|
|
8
|
+
*/
|
|
9
|
+
export function constructAfterRecord(message) {
|
|
10
|
+
const rawData = message.afterRaw;
|
|
11
|
+
const record = pgwire.decodeTuple(message.relation, rawData);
|
|
12
|
+
return toSyncRulesRow(record);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* pgwire message -> SQLite row.
|
|
16
|
+
* @param message
|
|
17
|
+
*/
|
|
18
|
+
export function constructBeforeRecord(message) {
|
|
19
|
+
const rawData = message.beforeRaw;
|
|
20
|
+
if (rawData == null) {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
const record = pgwire.decodeTuple(message.relation, rawData);
|
|
24
|
+
return toSyncRulesRow(record);
|
|
25
|
+
}
|
|
26
|
+
export function escapeIdentifier(identifier) {
|
|
27
|
+
return `"${identifier.replace(/"/g, '""').replace(/\./g, '"."')}"`;
|
|
28
|
+
}
|
|
29
|
+
export function autoParameter(arg) {
|
|
30
|
+
if (arg == null) {
|
|
31
|
+
return { type: 'varchar', value: null };
|
|
32
|
+
}
|
|
33
|
+
else if (typeof arg == 'string') {
|
|
34
|
+
return { type: 'varchar', value: arg };
|
|
35
|
+
}
|
|
36
|
+
else if (typeof arg == 'number') {
|
|
37
|
+
if (Number.isInteger(arg)) {
|
|
38
|
+
return { type: 'int8', value: arg };
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
return { type: 'float8', value: arg };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
else if (typeof arg == 'boolean') {
|
|
45
|
+
return { type: 'bool', value: arg };
|
|
46
|
+
}
|
|
47
|
+
else if (typeof arg == 'bigint') {
|
|
48
|
+
return { type: 'int8', value: arg };
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
throw new Error(`Unsupported query parameter: ${typeof arg}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Retry a simple query - up to 2 attempts total.
|
|
56
|
+
*/
|
|
57
|
+
export async function retriedQuery(db, ...args) {
|
|
58
|
+
for (let tries = 2;; tries--) {
|
|
59
|
+
try {
|
|
60
|
+
return await db.query(...args);
|
|
61
|
+
}
|
|
62
|
+
catch (e) {
|
|
63
|
+
if (tries == 1) {
|
|
64
|
+
throw e;
|
|
65
|
+
}
|
|
66
|
+
logger.warn('Query error, retrying', e);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=pgwire_utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pgwire_utils.js","sourceRoot":"","sources":["../../src/utils/pgwire_utils.ts"],"names":[],"mappings":"AAAA,0GAA0G;AAE1G,OAAO,KAAK,MAAM,MAAM,4BAA4B,CAAC;AACrD,OAAO,EAA8B,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAE3F,OAAO,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AAE3D;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAsD;IACzF,MAAM,OAAO,GAAI,OAAe,CAAC,QAAQ,CAAC;IAE1C,MAAM,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC7D,OAAO,cAAc,CAAC,MAAM,CAAC,CAAC;AAChC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAAsD;IAC1F,MAAM,OAAO,GAAI,OAAe,CAAC,SAAS,CAAC;IAC3C,IAAI,OAAO,IAAI,IAAI,EAAE;QACnB,OAAO,SAAS,CAAC;KAClB;IACD,MAAM,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC7D,OAAO,cAAc,CAAC,MAAM,CAAC,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,UAAkB;IACjD,OAAO,IAAI,UAAU,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC;AACrE,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,GAA8B;IAC1D,IAAI,GAAG,IAAI,IAAI,EAAE;QACf,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;KACzC;SAAM,IAAI,OAAO,GAAG,IAAI,QAAQ,EAAE;QACjC,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;KACxC;SAAM,IAAI,OAAO,GAAG,IAAI,QAAQ,EAAE;QACjC,IAAI,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE;YACzB,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;SACrC;aAAM;YACL,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;SACvC;KACF;SAAM,IAAI,OAAO,GAAG,IAAI,SAAS,EAAE;QAClC,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;KACrC;SAAM,IAAI,OAAO,GAAG,IAAI,QAAQ,EAAE;QACjC,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;KACrC;SAAM;QACL,MAAM,IAAI,KAAK,CAAC,gCAAgC,OAAO,GAAG,EAAE,CAAC,CAAC;KAC/D;AACH,CAAC;AAKD;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,EAAmB,EAAE,GAAG,IAAW;IACpE,KAAK,IAAI,KAAK,GAAG,CAAC,GAAI,KAAK,EAAE,EAAE;QAC7B,IAAI;YACF,OAAO,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;SAChC;QAAC,OAAO,CAAC,EAAE;YACV,IAAI,KAAK,IAAI,CAAC,EAAE;gBACd,MAAM,CAAC,CAAC;aACT;YACD,MAAM,CAAC,IAAI,CAAC,uBAAuB,EAAE,CAAC,CAAC,CAAC;SACzC;KACF;AACH,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import * as pgwire from '@powersync/service-jpgwire';
|
|
2
|
+
export interface PopulateDataOptions {
|
|
3
|
+
connection: pgwire.NormalizedConnectionConfig;
|
|
4
|
+
num_transactions: number;
|
|
5
|
+
per_transaction: number;
|
|
6
|
+
size: number;
|
|
7
|
+
}
|
|
8
|
+
export declare function populateData(options: PopulateDataOptions): Promise<number>;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import * as crypto from 'crypto';
|
|
2
|
+
import { Worker, isMainThread, parentPort, workerData } from 'node:worker_threads';
|
|
3
|
+
import * as pgwire from '@powersync/service-jpgwire';
|
|
4
|
+
if (isMainThread || parentPort == null) {
|
|
5
|
+
// Not a worker - ignore
|
|
6
|
+
}
|
|
7
|
+
else {
|
|
8
|
+
try {
|
|
9
|
+
const options = workerData;
|
|
10
|
+
const result = await populateDataInner(options);
|
|
11
|
+
parentPort.postMessage(result);
|
|
12
|
+
process.exit(0);
|
|
13
|
+
}
|
|
14
|
+
catch (e) {
|
|
15
|
+
// This is a bug, not a connection issue
|
|
16
|
+
console.error(e);
|
|
17
|
+
// Only closes the Worker thread
|
|
18
|
+
process.exit(2);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
async function populateDataInner(options) {
|
|
22
|
+
// Dedicated connection so we can release the memory easily
|
|
23
|
+
const initialDb = await pgwire.connectPgWire(options.connection, { type: 'standard' });
|
|
24
|
+
const largeDescription = crypto.randomBytes(options.size / 2).toString('hex');
|
|
25
|
+
let operation_count = 0;
|
|
26
|
+
for (let i = 0; i < options.num_transactions; i++) {
|
|
27
|
+
const prefix = `test${i}K`;
|
|
28
|
+
await initialDb.query({
|
|
29
|
+
statement: `INSERT INTO test_data(id, description, other) SELECT $1 || i, $2, 'foo' FROM generate_series(1, $3) i`,
|
|
30
|
+
params: [
|
|
31
|
+
{ type: 'varchar', value: prefix },
|
|
32
|
+
{ type: 'varchar', value: largeDescription },
|
|
33
|
+
{ type: 'int4', value: options.per_transaction }
|
|
34
|
+
]
|
|
35
|
+
});
|
|
36
|
+
operation_count += options.per_transaction;
|
|
37
|
+
}
|
|
38
|
+
await initialDb.end();
|
|
39
|
+
return operation_count;
|
|
40
|
+
}
|
|
41
|
+
export async function populateData(options) {
|
|
42
|
+
const WORKER_TIMEOUT = 30000;
|
|
43
|
+
const worker = new Worker(new URL('./populate_test_data.js', import.meta.url), {
|
|
44
|
+
workerData: options
|
|
45
|
+
});
|
|
46
|
+
const timeout = setTimeout(() => {
|
|
47
|
+
// Exits with code 1 below
|
|
48
|
+
worker.terminate();
|
|
49
|
+
}, WORKER_TIMEOUT);
|
|
50
|
+
try {
|
|
51
|
+
return await new Promise((resolve, reject) => {
|
|
52
|
+
worker.on('message', resolve);
|
|
53
|
+
worker.on('error', reject);
|
|
54
|
+
worker.on('exit', (code) => {
|
|
55
|
+
if (code !== 0) {
|
|
56
|
+
reject(new Error(`Populating data failed with exit code ${code}`));
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
clearTimeout(timeout);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=populate_test_data.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"populate_test_data.js","sourceRoot":"","sources":["../../src/utils/populate_test_data.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@powersync/service-module-postgres",
|
|
3
|
+
"repository": "https://github.com/powersync-ja/powersync-service",
|
|
4
|
+
"types": "dist/index.d.ts",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"version": "0.0.0-dev-20240918092408",
|
|
9
|
+
"main": "dist/index.js",
|
|
10
|
+
"license": "FSL-1.1-Apache-2.0",
|
|
11
|
+
"type": "module",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"import": "./dist/index.js",
|
|
15
|
+
"require": "./dist/index.js",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./types": {
|
|
19
|
+
"import": "./dist/types/types.js",
|
|
20
|
+
"require": "./dist/types/types.js",
|
|
21
|
+
"default": "./dist/types/types.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"pgwire": "github:kagis/pgwire#f1cb95f9a0f42a612bb5a6b67bb2eb793fc5fc87",
|
|
26
|
+
"jose": "^4.15.1",
|
|
27
|
+
"ts-codec": "^1.2.2",
|
|
28
|
+
"uuid": "^9.0.1",
|
|
29
|
+
"uri-js": "^4.4.1",
|
|
30
|
+
"@powersync/lib-services-framework": "0.0.0-dev-20240918092408",
|
|
31
|
+
"@powersync/service-core": "0.0.0-dev-20240918092408",
|
|
32
|
+
"@powersync/service-jpgwire": "0.0.0-dev-20240918092408",
|
|
33
|
+
"@powersync/service-jsonbig": "0.17.10",
|
|
34
|
+
"@powersync/service-sync-rules": "0.0.0-dev-20240918092408",
|
|
35
|
+
"@powersync/service-types": "0.0.0-dev-20240918092408"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/uuid": "^9.0.4",
|
|
39
|
+
"typescript": "^5.2.2",
|
|
40
|
+
"vitest": "^0.34.6",
|
|
41
|
+
"vite-tsconfig-paths": "^4.3.2"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsc -b",
|
|
45
|
+
"build:tests": "tsc -b test/tsconfig.json",
|
|
46
|
+
"clean": "rm -rf ./lib && tsc -b --clean",
|
|
47
|
+
"test": "vitest --no-threads"
|
|
48
|
+
}
|
|
49
|
+
}
|