@dcl/pg-component 0.1.0

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/README.md ADDED
@@ -0,0 +1,173 @@
1
+ # @dcl/pg-component
2
+
3
+ A PostgreSQL database component that provides connection pooling, transaction management, query streaming, and migration support.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @dcl/pg-component
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```typescript
14
+ import { createPgComponent } from '@dcl/pg-component'
15
+ import SQL from 'sql-template-strings'
16
+
17
+ // Create the component with required dependencies
18
+ const pg = await createPgComponent({ config, logs, metrics })
19
+
20
+ // Start the component (runs migrations if configured)
21
+ await pg.start()
22
+
23
+ // Execute queries using sql-template-strings for safe parameterization
24
+ const result = await pg.query<{ id: number; name: string }>(SQL`SELECT * FROM users WHERE id = ${userId}`)
25
+
26
+ // Stop the component (gracefully drains connections)
27
+ await pg.stop()
28
+ ```
29
+
30
+ ## Features
31
+
32
+ - **Connection pooling**: Efficient connection management using `pg` Pool
33
+ - **SQL injection protection**: Use `sql-template-strings` for safe parameterized queries
34
+ - **Transaction support**: Two transaction APIs for different use cases
35
+ - **Query streaming**: Memory-efficient streaming for large result sets
36
+ - **Migration support**: Built-in support for `node-pg-migrate`
37
+ - **Metrics integration**: Optional query duration metrics
38
+ - **Graceful shutdown**: Drains connections before closing the pool
39
+
40
+ ## Transactions
41
+
42
+ ### Using `withTransaction`
43
+
44
+ Provides direct access to the transaction client:
45
+
46
+ ```typescript
47
+ await pg.withTransaction(async (client) => {
48
+ await client.query('INSERT INTO users (name) VALUES ($1)', ['Alice'])
49
+ await client.query('INSERT INTO audit (action) VALUES ($1)', ['user_created'])
50
+ // Automatically commits on success, rolls back on error
51
+ })
52
+ ```
53
+
54
+ ### Using `withAsyncContextTransaction`
55
+
56
+ Uses AsyncLocalStorage so nested `query()` calls automatically use the transaction client:
57
+
58
+ ```typescript
59
+ await pg.withAsyncContextTransaction(async () => {
60
+ // All pg.query() calls within this callback use the same transaction
61
+ await pg.query(SQL`INSERT INTO users (name) VALUES ('Alice')`)
62
+ await pg.query(SQL`INSERT INTO audit (action) VALUES ('user_created')`)
63
+ // Automatically commits on success, rolls back on error
64
+ })
65
+ ```
66
+
67
+ ### Important Warnings
68
+
69
+ #### Do not use transaction control statements with `withAsyncContextTransaction`
70
+
71
+ When using `withAsyncContextTransaction`, do **not** execute `BEGIN`, `COMMIT`, or `ROLLBACK` via `query()`. The transaction lifecycle is managed automatically:
72
+
73
+ ```typescript
74
+ // ❌ WRONG - Don't do this
75
+ await pg.withAsyncContextTransaction(async () => {
76
+ await pg.query(SQL`BEGIN`) // Don't do this!
77
+ await pg.query(SQL`INSERT INTO users (name) VALUES ('Alice')`)
78
+ await pg.query(SQL`COMMIT`) // Don't do this!
79
+ })
80
+
81
+ // ✅ CORRECT
82
+ await pg.withAsyncContextTransaction(async () => {
83
+ await pg.query(SQL`INSERT INTO users (name) VALUES ('Alice')`)
84
+ // BEGIN/COMMIT/ROLLBACK are handled automatically
85
+ })
86
+ ```
87
+
88
+ #### Nesting transactions creates independent transactions
89
+
90
+ Calling `withTransaction` or `withAsyncContextTransaction` inside another transaction method will create **independent transactions**, not nested transactions. Each call acquires a new connection from the pool:
91
+
92
+ ```typescript
93
+ // ⚠️ WARNING: This creates TWO independent transactions
94
+ await pg.withAsyncContextTransaction(async () => {
95
+ await pg.query(SQL`INSERT INTO table1 (name) VALUES ('outer')`)
96
+
97
+ // This is a SEPARATE transaction with its own connection!
98
+ await pg.withTransaction(async (client) => {
99
+ await client.query(`INSERT INTO table2 (name) VALUES ('inner')`)
100
+ })
101
+ })
102
+ ```
103
+
104
+ If the inner transaction fails and rolls back, the outer transaction is **not** affected and will still commit. This is because PostgreSQL does not support true nested transactions, and each transaction method acquires its own connection.
105
+
106
+ ## Query Streaming
107
+
108
+ For large result sets, use `streamQuery` to avoid loading all rows into memory:
109
+
110
+ ```typescript
111
+ for await (const row of pg.streamQuery<User>(SQL`SELECT * FROM large_table`)) {
112
+ await processRow(row)
113
+ }
114
+ ```
115
+
116
+ ## Migrations
117
+
118
+ Configure migrations when creating the component:
119
+
120
+ ```typescript
121
+ const pg = await createPgComponent(
122
+ { config, logs },
123
+ {
124
+ migration: {
125
+ migrationsTable: 'pgmigrations',
126
+ dir: path.join(__dirname, 'migrations'),
127
+ direction: 'up',
128
+ count: Infinity
129
+ }
130
+ }
131
+ )
132
+ ```
133
+
134
+ ## Configuration
135
+
136
+ Environment variables read by the component:
137
+
138
+ | Variable | Type | Description |
139
+ | ------------------------------------- | -------- | ---------------------------------------- |
140
+ | `PG_COMPONENT_PSQL_CONNECTION_STRING` | `string` | PostgreSQL connection string |
141
+ | `PG_COMPONENT_PSQL_HOST` | `string` | Database host |
142
+ | `PG_COMPONENT_PSQL_PORT` | `number` | Database port |
143
+ | `PG_COMPONENT_PSQL_DATABASE` | `string` | Database name |
144
+ | `PG_COMPONENT_PSQL_USER` | `string` | Database user |
145
+ | `PG_COMPONENT_PSQL_PASSWORD` | `string` | Database password |
146
+ | `PG_COMPONENT_IDLE_TIMEOUT` | `number` | Idle connection timeout (ms) |
147
+ | `PG_COMPONENT_QUERY_TIMEOUT` | `number` | Query timeout (ms) |
148
+ | `PG_COMPONENT_STREAM_QUERY_TIMEOUT` | `number` | Stream query timeout (ms) |
149
+ | `PG_COMPONENT_GRACE_PERIODS` | `number` | Grace periods for shutdown (default: 10) |
150
+
151
+ ## Metrics
152
+
153
+ When a metrics component is provided, query durations are tracked:
154
+
155
+ ```typescript
156
+ // Pass a label to track query duration
157
+ const result = await pg.query(SQL`SELECT * FROM users`, 'get_users')
158
+ ```
159
+
160
+ Metric: `dcl_db_query_duration_seconds` with labels `query` and `status` (success/error)
161
+
162
+ ## Testing
163
+
164
+ Tests use [Testcontainers](https://testcontainers.com/) to run against a real PostgreSQL instance:
165
+
166
+ ```bash
167
+ # Requires Docker to be running
168
+ pnpm test
169
+ ```
170
+
171
+ ## License
172
+
173
+ Apache-2.0
@@ -0,0 +1,18 @@
1
+ import { IBaseComponent, IConfigComponent, ILoggerComponent } from '@well-known-components/interfaces';
2
+ import { Options, IPgComponent, IMetricsComponent } from './types';
3
+ export * from './types';
4
+ export * from './metrics';
5
+ export declare function runReportingQueryDurationMetric<T>(components: {
6
+ metrics: IMetricsComponent;
7
+ }, queryNameLabel: string, functionToRun: () => Promise<T>): Promise<T>;
8
+ /**
9
+ * Query a Postgres (https://www.postgresql.org) database with ease.
10
+ * It uses a pool behind the scenes and will try to gracefully close it after finishing the connection.
11
+ * @public
12
+ */
13
+ export declare function createPgComponent(components: {
14
+ logs: ILoggerComponent;
15
+ config: IConfigComponent;
16
+ metrics?: IMetricsComponent;
17
+ }, options?: Options): Promise<IPgComponent & IBaseComponent>;
18
+ //# sourceMappingURL=component.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../../src/component.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAA;AAOtG,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAwC,MAAM,SAAS,CAAA;AAExG,cAAc,SAAS,CAAA;AACvB,cAAc,WAAW,CAAA;AAEzB,wBAAsB,+BAA+B,CAAC,CAAC,EACrD,UAAU,EAAE;IAAE,OAAO,EAAE,iBAAiB,CAAA;CAAE,EAC1C,cAAc,EAAE,MAAM,EACtB,aAAa,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAC9B,OAAO,CAAC,CAAC,CAAC,CAcZ;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,UAAU,EAAE;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,MAAM,EAAE,gBAAgB,CAAC;IAAC,OAAO,CAAC,EAAE,iBAAiB,CAAA;CAAE,EAC7F,OAAO,GAAE,OAAY,GACpB,OAAO,CAAC,YAAY,GAAG,cAAc,CAAC,CA0OxC"}
@@ -0,0 +1,248 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ var __importDefault = (this && this.__importDefault) || function (mod) {
17
+ return (mod && mod.__esModule) ? mod : { "default": mod };
18
+ };
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.runReportingQueryDurationMetric = runReportingQueryDurationMetric;
21
+ exports.createPgComponent = createPgComponent;
22
+ const async_hooks_1 = require("async_hooks");
23
+ const pg_1 = require("pg");
24
+ const pg_query_stream_1 = __importDefault(require("pg-query-stream"));
25
+ const node_pg_migrate_1 = __importDefault(require("node-pg-migrate"));
26
+ const promises_1 = require("timers/promises");
27
+ __exportStar(require("./types"), exports);
28
+ __exportStar(require("./metrics"), exports);
29
+ async function runReportingQueryDurationMetric(components, queryNameLabel, functionToRun) {
30
+ const { metrics } = components;
31
+ const { end: endTimer } = metrics.startTimer('dcl_db_query_duration_seconds', {
32
+ query: queryNameLabel
33
+ });
34
+ try {
35
+ const res = await functionToRun();
36
+ endTimer({ status: 'success' });
37
+ return res;
38
+ }
39
+ catch (err) {
40
+ endTimer({ status: 'error' });
41
+ throw err;
42
+ }
43
+ }
44
+ /**
45
+ * Query a Postgres (https://www.postgresql.org) database with ease.
46
+ * It uses a pool behind the scenes and will try to gracefully close it after finishing the connection.
47
+ * @public
48
+ */
49
+ async function createPgComponent(components, options = {}) {
50
+ const { config, logs } = components;
51
+ const logger = logs.getLogger('pg-component');
52
+ // Environment
53
+ const [connectionString, port, host, database, user, password, idleTimeoutMillis, query_timeout] = await Promise.all([
54
+ config.getString('PG_COMPONENT_PSQL_CONNECTION_STRING'),
55
+ config.getNumber('PG_COMPONENT_PSQL_PORT'),
56
+ config.getString('PG_COMPONENT_PSQL_HOST'),
57
+ config.getString('PG_COMPONENT_PSQL_DATABASE'),
58
+ config.getString('PG_COMPONENT_PSQL_USER'),
59
+ config.getString('PG_COMPONENT_PSQL_PASSWORD'),
60
+ config.getNumber('PG_COMPONENT_IDLE_TIMEOUT'),
61
+ config.getNumber('PG_COMPONENT_QUERY_TIMEOUT')
62
+ ]);
63
+ const defaultOptions = {
64
+ connectionString,
65
+ port,
66
+ host,
67
+ database,
68
+ user,
69
+ password,
70
+ idleTimeoutMillis,
71
+ query_timeout
72
+ };
73
+ const STREAM_QUERY_TIMEOUT = await config.getNumber('PG_COMPONENT_STREAM_QUERY_TIMEOUT');
74
+ const GRACE_PERIODS = (await config.getNumber('PG_COMPONENT_GRACE_PERIODS')) || 10;
75
+ const finalOptions = { ...defaultOptions, ...options.pool };
76
+ if (!finalOptions.log) {
77
+ finalOptions.log = logger.debug.bind(logger);
78
+ }
79
+ // Config
80
+ const pool = new pg_1.Pool(finalOptions);
81
+ // Async context for transaction client
82
+ const transactionContext = new async_hooks_1.AsyncLocalStorage();
83
+ // Methods
84
+ async function start() {
85
+ try {
86
+ const db = await pool.connect();
87
+ try {
88
+ if (options.migration) {
89
+ logger.debug('Running migrations:');
90
+ const opt = {
91
+ ...options.migration,
92
+ dbClient: db
93
+ };
94
+ if (!opt.logger) {
95
+ opt.logger = logger;
96
+ }
97
+ await (0, node_pg_migrate_1.default)(opt);
98
+ }
99
+ }
100
+ catch (err) {
101
+ logger.error(err);
102
+ throw err;
103
+ }
104
+ finally {
105
+ db.release();
106
+ }
107
+ }
108
+ catch (error) {
109
+ logger.warn('Error starting pg-component:');
110
+ logger.error(error);
111
+ throw error;
112
+ }
113
+ }
114
+ async function withTransaction(callback) {
115
+ const client = await pool.connect();
116
+ try {
117
+ await client.query('BEGIN');
118
+ const result = await callback(client);
119
+ await client.query('COMMIT');
120
+ return result;
121
+ }
122
+ catch (error) {
123
+ await client.query('ROLLBACK');
124
+ throw error;
125
+ }
126
+ finally {
127
+ client.release();
128
+ }
129
+ }
130
+ async function withAsyncContextTransaction(callback) {
131
+ const client = await pool.connect();
132
+ try {
133
+ await client.query('BEGIN');
134
+ const result = await transactionContext.run(client, callback);
135
+ await client.query('COMMIT');
136
+ return result;
137
+ }
138
+ catch (error) {
139
+ await client.query('ROLLBACK');
140
+ throw error;
141
+ }
142
+ finally {
143
+ client.release();
144
+ }
145
+ }
146
+ async function defaultQuery(sql) {
147
+ const notices = [];
148
+ // Get the transaction's context client or connect a new one
149
+ const transactionClient = transactionContext.getStore();
150
+ const client = transactionClient ?? (await pool.connect());
151
+ function listenNotice(notice) {
152
+ notices.push(notice);
153
+ }
154
+ try {
155
+ client.on('notice', listenNotice);
156
+ const result = await client.query(sql);
157
+ return { ...result, rowCount: result.rowCount ?? 0, notices };
158
+ }
159
+ finally {
160
+ client.off('notice', listenNotice);
161
+ // Only release if we created a new connection (not from transaction context)
162
+ if (!transactionClient) {
163
+ client.release();
164
+ }
165
+ }
166
+ }
167
+ async function measuredQuery(sql, durationQueryNameLabel) {
168
+ const result = durationQueryNameLabel
169
+ ? await runReportingQueryDurationMetric({ metrics: components.metrics }, durationQueryNameLabel, () => defaultQuery(sql))
170
+ : await defaultQuery(sql);
171
+ return result;
172
+ }
173
+ async function* streamQuery(sql, config) {
174
+ const client = new pg_1.Client({
175
+ ...finalOptions,
176
+ query_timeout: STREAM_QUERY_TIMEOUT
177
+ });
178
+ await client.connect();
179
+ // https://github.com/brianc/node-postgres/issues/1860
180
+ // Uncaught TypeError: queryCallback is not a function
181
+ // finish - OK, this call is necessary to finish the query when we configure query_timeout due to a bug in pg
182
+ // finish - with error, this call is necessary to finish the query when we configure query_timeout due to a bug in pg
183
+ const stream = new pg_query_stream_1.default(sql.text, sql.values, config);
184
+ stream.callback = function () {
185
+ // noop
186
+ };
187
+ try {
188
+ client.query(stream);
189
+ for await (const row of stream) {
190
+ yield row;
191
+ }
192
+ stream.callback(undefined, undefined);
193
+ }
194
+ catch (err) {
195
+ stream.callback(err, undefined);
196
+ throw err;
197
+ }
198
+ finally {
199
+ stream.destroy();
200
+ await client.end();
201
+ }
202
+ }
203
+ let didStop = false;
204
+ async function stop() {
205
+ if (didStop) {
206
+ logger.error('Stop called more than once');
207
+ return;
208
+ }
209
+ didStop = true;
210
+ let gracePeriods = GRACE_PERIODS;
211
+ while (gracePeriods > 0 && pool.waitingCount > 0) {
212
+ logger.debug('Draining connections', {
213
+ waitingCount: pool.waitingCount,
214
+ gracePeriods
215
+ });
216
+ await (0, promises_1.setTimeout)(200);
217
+ gracePeriods -= 1;
218
+ }
219
+ const promise = pool.end();
220
+ let finished = false;
221
+ promise.finally(() => {
222
+ finished = true;
223
+ });
224
+ while (!finished && (pool.totalCount > 0 || pool.idleCount > 0 || pool.waitingCount > 0)) {
225
+ if (pool.totalCount) {
226
+ logger.log('Draining connections', {
227
+ totalCount: pool.totalCount,
228
+ idleCount: pool.idleCount,
229
+ waitingCount: pool.waitingCount
230
+ });
231
+ await (0, promises_1.setTimeout)(1000);
232
+ }
233
+ }
234
+ await promise;
235
+ }
236
+ function getPool() {
237
+ return pool;
238
+ }
239
+ return {
240
+ query: components.metrics ? measuredQuery : defaultQuery,
241
+ withTransaction,
242
+ withAsyncContextTransaction,
243
+ streamQuery,
244
+ getPool,
245
+ start,
246
+ stop
247
+ };
248
+ }
@@ -0,0 +1,3 @@
1
+ export * from './component';
2
+ export * from './types';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,SAAS,CAAA"}
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./component"), exports);
18
+ __exportStar(require("./types"), exports);
@@ -0,0 +1,7 @@
1
+ import { IMetricsComponent } from "@well-known-components/interfaces";
2
+ /**
3
+ * Metrics declarations, needed for your IMetricsComponent
4
+ * @public
5
+ */
6
+ export declare const metricDeclarations: IMetricsComponent.MetricsRecordDefinition<string>;
7
+ //# sourceMappingURL=metrics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../src/metrics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAA;AAErE;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAE,iBAAiB,CAAC,uBAAuB,CAAC,MAAM,CAMhF,CAAA"}
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.metricDeclarations = void 0;
4
+ const interfaces_1 = require("@well-known-components/interfaces");
5
+ /**
6
+ * Metrics declarations, needed for your IMetricsComponent
7
+ * @public
8
+ */
9
+ exports.metricDeclarations = {
10
+ dcl_db_query_duration_seconds: {
11
+ help: "Histogram of query duration to the database in seconds per query",
12
+ type: interfaces_1.IMetricsComponent.HistogramType,
13
+ labelNames: ["query", "status"], // status=(success|error)
14
+ },
15
+ };
@@ -0,0 +1,84 @@
1
+ import { IDatabase, IMetricsComponent as IBaseMetricsComponent } from '@well-known-components/interfaces';
2
+ import { Pool, PoolClient, PoolConfig } from 'pg';
3
+ import { NoticeMessage } from 'pg-protocol/dist/messages';
4
+ import { RunnerOption } from 'node-pg-migrate';
5
+ import { SQLStatement } from 'sql-template-strings';
6
+ import QueryStream from 'pg-query-stream';
7
+ import { metricDeclarations } from './metrics';
8
+ /**
9
+ * @internal
10
+ */
11
+ export type QueryStreamWithCallback = QueryStream & {
12
+ callback: Function;
13
+ };
14
+ /**
15
+ * @public
16
+ *
17
+ * Query result with notices.
18
+ */
19
+ export type QueryResult<T extends Record<string, any>> = IDatabase.IQueryResult<T> & {
20
+ notices: NoticeMessage[];
21
+ };
22
+ /**
23
+ * @public
24
+ */
25
+ export type Options = Partial<{
26
+ pool: PoolConfig;
27
+ migration: Omit<RunnerOption, 'databaseUrl' | 'dbClient'>;
28
+ }>;
29
+ /**
30
+ * @public
31
+ */
32
+ export interface IPgComponent extends IDatabase {
33
+ start(): Promise<void>;
34
+ query<T extends Record<string, any>>(sql: string): Promise<QueryResult<T>>;
35
+ query<T extends Record<string, any>>(sql: SQLStatement, durationQueryNameLabel?: string): Promise<QueryResult<T>>;
36
+ streamQuery<T = any>(sql: SQLStatement, config?: {
37
+ batchSize?: number;
38
+ }): AsyncGenerator<T>;
39
+ /**
40
+ * Executes a callback within a transaction using a client.
41
+ * The client is acquired from the pool and released after the callback is executed.
42
+ * If an error occurs, the transaction is rolled back and the client is released.
43
+ *
44
+ * @warning Nesting transaction methods (calling `withTransaction` or `withAsyncContextTransaction`
45
+ * inside this callback) will create independent transactions, not nested transactions.
46
+ * Each call acquires a new connection from the pool.
47
+ */
48
+ withTransaction<T>(callback: (client: PoolClient) => Promise<T>): Promise<T>;
49
+ /**
50
+ * Executes a callback within a transaction using async context.
51
+ * The client is acquired from the pool and released after the callback is executed.
52
+ * If an error occurs, the transaction is rolled back and the client is released.
53
+ * All calls to query() within the callback will automatically use the transaction's client.
54
+ *
55
+ * @warning Do not execute transaction control statements (BEGIN, COMMIT, ROLLBACK) via `query()`
56
+ * within this callback, as the transaction lifecycle is managed automatically.
57
+ *
58
+ * @warning Nesting transaction methods (calling `withTransaction` or `withAsyncContextTransaction`
59
+ * inside this callback) will create independent transactions, not nested transactions.
60
+ * Each call acquires a new connection from the pool.
61
+ */
62
+ withAsyncContextTransaction<T>(callback: () => Promise<T>): Promise<T>;
63
+ /**
64
+ * @internal
65
+ */
66
+ getPool(): Pool;
67
+ stop(): Promise<void>;
68
+ }
69
+ /**
70
+ * @public
71
+ */
72
+ export declare namespace IPgComponent {
73
+ /**
74
+ * @public
75
+ */
76
+ type Composable = {
77
+ pg: IPgComponent;
78
+ };
79
+ }
80
+ /**
81
+ * @public
82
+ */
83
+ export type IMetricsComponent = IBaseMetricsComponent<keyof typeof metricDeclarations>;
84
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,iBAAiB,IAAI,qBAAqB,EAAE,MAAM,mCAAmC,CAAA;AACzG,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,IAAI,CAAA;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAA;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AACnD,OAAO,WAAW,MAAM,iBAAiB,CAAA;AACzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAA;AAE9C;;GAEG;AACH,MAAM,MAAM,uBAAuB,GAAG,WAAW,GAAG;IAAE,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAAA;AAE1E;;;;GAIG;AACH,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,SAAS,CAAC,YAAY,CAAC,CAAC,CAAC,GAAG;IACnF,OAAO,EAAE,aAAa,EAAE,CAAA;CACzB,CAAA;AAED;;GAEG;AACH,MAAM,MAAM,OAAO,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,SAAS,EAAE,IAAI,CAAC,YAAY,EAAE,aAAa,GAAG,UAAU,CAAC,CAAA;CAAE,CAAC,CAAA;AAE9G;;GAEG;AACH,MAAM,WAAW,YAAa,SAAQ,SAAS;IAC7C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IAEtB,KAAK,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAA;IAC1E,KAAK,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,EAAE,YAAY,EAAE,sBAAsB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAA;IACjH,WAAW,CAAC,CAAC,GAAG,GAAG,EAAE,GAAG,EAAE,YAAY,EAAE,MAAM,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,cAAc,CAAC,CAAC,CAAC,CAAA;IAC3F;;;;;;;;OAQG;IACH,eAAe,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;IAC5E;;;;;;;;;;;;OAYG;IACH,2BAA2B,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;IAEtE;;OAEG;IACH,OAAO,IAAI,IAAI,CAAA;IAEf,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACtB;AAED;;GAEG;AACH,yBAAiB,YAAY,CAAC;IAC5B;;OAEG;IACH,KAAY,UAAU,GAAG;QACvB,EAAE,EAAE,YAAY,CAAA;KACjB,CAAA;CACF;AAED;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,qBAAqB,CAAC,MAAM,OAAO,kBAAkB,CAAC,CAAA"}
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@dcl/pg-component",
3
+ "version": "0.1.0",
4
+ "description": "PG component for core components library",
5
+ "main": "dist/src/index.js",
6
+ "types": "dist/src/index.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "dependencies": {
11
+ "@well-known-components/interfaces": "^1.5.2",
12
+ "node-pg-migrate": "^7.9.1",
13
+ "pg": "8.17.2",
14
+ "pg-protocol": "^1.11.0",
15
+ "pg-query-stream": "4.11.2",
16
+ "sql-template-strings": "2.2.2"
17
+ },
18
+ "peerDependencies": {
19
+ "@well-known-components/interfaces": "^1.5.2"
20
+ },
21
+ "devDependencies": {
22
+ "@testcontainers/postgresql": "^10.18.0",
23
+ "@types/pg": "^8.16.0",
24
+ "typescript": "^5.8.3"
25
+ },
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "scripts": {
30
+ "build": "tsc",
31
+ "dev": "tsc --watch",
32
+ "clean": "rm -rf dist",
33
+ "test": "jest --forceExit",
34
+ "lint": "echo \"No linting configured\""
35
+ }
36
+ }