@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.
Files changed (87) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/LICENSE +67 -0
  3. package/README.md +3 -0
  4. package/dist/api/PostgresRouteAPIAdapter.d.ts +22 -0
  5. package/dist/api/PostgresRouteAPIAdapter.js +273 -0
  6. package/dist/api/PostgresRouteAPIAdapter.js.map +1 -0
  7. package/dist/auth/SupabaseKeyCollector.d.ts +22 -0
  8. package/dist/auth/SupabaseKeyCollector.js +64 -0
  9. package/dist/auth/SupabaseKeyCollector.js.map +1 -0
  10. package/dist/index.d.ts +3 -0
  11. package/dist/index.js +4 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/module/PostgresModule.d.ts +14 -0
  14. package/dist/module/PostgresModule.js +108 -0
  15. package/dist/module/PostgresModule.js.map +1 -0
  16. package/dist/replication/ConnectionManagerFactory.d.ts +10 -0
  17. package/dist/replication/ConnectionManagerFactory.js +21 -0
  18. package/dist/replication/ConnectionManagerFactory.js.map +1 -0
  19. package/dist/replication/PgManager.d.ts +25 -0
  20. package/dist/replication/PgManager.js +60 -0
  21. package/dist/replication/PgManager.js.map +1 -0
  22. package/dist/replication/PgRelation.d.ts +6 -0
  23. package/dist/replication/PgRelation.js +27 -0
  24. package/dist/replication/PgRelation.js.map +1 -0
  25. package/dist/replication/PostgresErrorRateLimiter.d.ts +11 -0
  26. package/dist/replication/PostgresErrorRateLimiter.js +43 -0
  27. package/dist/replication/PostgresErrorRateLimiter.js.map +1 -0
  28. package/dist/replication/WalStream.d.ts +53 -0
  29. package/dist/replication/WalStream.js +536 -0
  30. package/dist/replication/WalStream.js.map +1 -0
  31. package/dist/replication/WalStreamReplicationJob.d.ts +27 -0
  32. package/dist/replication/WalStreamReplicationJob.js +131 -0
  33. package/dist/replication/WalStreamReplicationJob.js.map +1 -0
  34. package/dist/replication/WalStreamReplicator.d.ts +13 -0
  35. package/dist/replication/WalStreamReplicator.js +36 -0
  36. package/dist/replication/WalStreamReplicator.js.map +1 -0
  37. package/dist/replication/replication-index.d.ts +5 -0
  38. package/dist/replication/replication-index.js +6 -0
  39. package/dist/replication/replication-index.js.map +1 -0
  40. package/dist/replication/replication-utils.d.ts +32 -0
  41. package/dist/replication/replication-utils.js +272 -0
  42. package/dist/replication/replication-utils.js.map +1 -0
  43. package/dist/types/types.d.ts +76 -0
  44. package/dist/types/types.js +110 -0
  45. package/dist/types/types.js.map +1 -0
  46. package/dist/utils/migration_lib.d.ts +11 -0
  47. package/dist/utils/migration_lib.js +64 -0
  48. package/dist/utils/migration_lib.js.map +1 -0
  49. package/dist/utils/pgwire_utils.d.ts +16 -0
  50. package/dist/utils/pgwire_utils.js +70 -0
  51. package/dist/utils/pgwire_utils.js.map +1 -0
  52. package/dist/utils/populate_test_data.d.ts +8 -0
  53. package/dist/utils/populate_test_data.js +65 -0
  54. package/dist/utils/populate_test_data.js.map +1 -0
  55. package/package.json +49 -0
  56. package/src/api/PostgresRouteAPIAdapter.ts +307 -0
  57. package/src/auth/SupabaseKeyCollector.ts +70 -0
  58. package/src/index.ts +5 -0
  59. package/src/module/PostgresModule.ts +122 -0
  60. package/src/replication/ConnectionManagerFactory.ts +28 -0
  61. package/src/replication/PgManager.ts +70 -0
  62. package/src/replication/PgRelation.ts +31 -0
  63. package/src/replication/PostgresErrorRateLimiter.ts +44 -0
  64. package/src/replication/WalStream.ts +639 -0
  65. package/src/replication/WalStreamReplicationJob.ts +142 -0
  66. package/src/replication/WalStreamReplicator.ts +45 -0
  67. package/src/replication/replication-index.ts +5 -0
  68. package/src/replication/replication-utils.ts +329 -0
  69. package/src/types/types.ts +159 -0
  70. package/src/utils/migration_lib.ts +79 -0
  71. package/src/utils/pgwire_utils.ts +73 -0
  72. package/src/utils/populate_test_data.ts +77 -0
  73. package/test/src/__snapshots__/pg_test.test.ts.snap +256 -0
  74. package/test/src/env.ts +7 -0
  75. package/test/src/large_batch.test.ts +195 -0
  76. package/test/src/pg_test.test.ts +450 -0
  77. package/test/src/schema_changes.test.ts +543 -0
  78. package/test/src/setup.ts +7 -0
  79. package/test/src/slow_tests.test.ts +335 -0
  80. package/test/src/util.ts +105 -0
  81. package/test/src/validation.test.ts +64 -0
  82. package/test/src/wal_stream.test.ts +319 -0
  83. package/test/src/wal_stream_utils.ts +121 -0
  84. package/test/tsconfig.json +28 -0
  85. package/tsconfig.json +31 -0
  86. package/tsconfig.tsbuildinfo +1 -0
  87. 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
+ }