@m2k-5f/pgtx 2.1.0 → 2.2.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 CHANGED
@@ -113,6 +113,36 @@ await pool.begin(async (tx) => {
113
113
  })
114
114
  ```
115
115
 
116
+
117
+ ### Async Notifications (LISTEN / NOTIFY)
118
+
119
+ Pgtx natively handles PostgreSQL `LISTEN/NOTIFY` protocol messages asynchronously without interrupting multiplexed query pipeline.
120
+
121
+ ```typescript
122
+ // 1. Sending a notification
123
+ await pool.notify('user_events', JSON.stringify({ id: 42, action: 'signup' }))
124
+
125
+ // 2. Receiving notifications (Requires a dedicated connection from the pool)
126
+ const conn = await pool.acquire()
127
+
128
+ const onEvent = (payload: string) => {
129
+ console.log(`Received payload: ${payload}`)
130
+ }
131
+
132
+ // Multiplexes multiple callbacks onto a single LISTEN command seamlessly
133
+ await conn.listen('user_events', onEvent)
134
+ await conn.listen('user_events', (data) => logToFile(data))
135
+
136
+ // Clean up callbacks (Sends UNLISTEN only when the channel has zero callbacks left)
137
+ await conn.unlisten('user_events', onEvent)
138
+
139
+ // Keep the connection active as long as you need notifications!
140
+ // Do NOT release it back to the pool prematurely.
141
+ ```
142
+
143
+ > ⚠️ **Architecture Note:** While `notify` is atomic and can be triggered directly from the `Pool` on any random socket, `listen` and `unlisten` are stateful commands tied to a specific PostgreSQL backend process. Therefore, subscription methods are **strictly available only on explicit `Connection` instances** fetched via `pool.acquire()`.
144
+
145
+
116
146
  ### Bulk Inserts
117
147
 
118
148
  ```typescript
@@ -252,6 +282,9 @@ class Connection {
252
282
 
253
283
  query<T>(strings: TemplateStringsArray, ...values: any[]): Promise<T[]>
254
284
  begin<T>(callback: (tx: Transaction) => Promise<T>): Promise<T>
285
+ notify(channelName: string, payload?: string): Promise<[]>
286
+ listen(channelName: string, callback: (payload: string) => void): Promise<[]>
287
+ unlisten(channelName: string, callback: (payload: string) => void): Promise<[]>
255
288
 
256
289
  get isAlive(): boolean
257
290
  close(): void
@@ -275,6 +308,7 @@ class Pool {
275
308
 
276
309
  query<T>(strings: TemplateStringsArray, ...values: any[]): Promise<T[]>
277
310
  begin<T>(callback: (tx: Transaction) => Promise<T>): Promise<T>
311
+ notify(channelName: string, payload?: string): Promise<[]>
278
312
  acquire(): Promise<Connection>
279
313
  release(conn: Connection): void
280
314
  close(): Promise<void>
@@ -26,8 +26,9 @@ export type DescribeQueueUnit = {
26
26
  reject: (error: Error) => void;
27
27
  statementName: StatementName;
28
28
  };
29
- export type StatementName = Branded<string, 'statementName'>;
30
- export type QueryText = Branded<string, 'queryText'>;
29
+ export type StatementName = Branded<string, 'StatementName'>;
30
+ export type QueryText = Branded<string, 'QueryText'>;
31
+ export type ChannelName = Branded<string, "ChannelName">;
31
32
  /**
32
33
  * Represents a single dedicated connection to the PostgreSQL database.
33
34
  *
@@ -59,8 +60,10 @@ export type QueryText = Branded<string, 'queryText'>;
59
60
  * ```
60
61
  */
61
62
  export declare class Connection {
63
+ private readonly params;
62
64
  private _isFlushing;
63
- private _isAlive;
65
+ private _isOpened;
66
+ private _isReconnecting;
64
67
  private _socket;
65
68
  private _writer;
66
69
  private _pipelinesQueue;
@@ -68,6 +71,7 @@ export declare class Connection {
68
71
  private _describingPending;
69
72
  private _parsed;
70
73
  private _parsingPending;
74
+ private _listeningCallbacks;
71
75
  private _stmtCounter;
72
76
  private _logLevel;
73
77
  private _nextStatement;
@@ -116,8 +120,6 @@ export declare class Connection {
116
120
  * ```
117
121
  */
118
122
  query<T extends Record<string, any>>(templates: TemplateStringsArray, ...params: any[]): Promise<T[]>;
119
- private _createQuery;
120
- private _writeQuery;
121
123
  /**
122
124
  * Starts a managed transaction on this connection.
123
125
  *
@@ -140,8 +142,48 @@ export declare class Connection {
140
142
  * ```
141
143
  */
142
144
  begin<T>(txCallback: (transaction: Transaction) => Promise<T>): Promise<T>;
145
+ /**
146
+ * Sends an asynchronous notification to a channel via `pg_notify`.
147
+ *
148
+ * @param channelName - The channel identifier
149
+ * @param payload - Optional string data (max 8000 bytes)
150
+ *
151
+ * @example
152
+ * ```ts
153
+ * await conn.notify('events', 'hello')
154
+ * ```
155
+ */
156
+ notify(channelName: string, payload?: string): Promise<[]>;
157
+ /**
158
+ * Subscribes a callback to a channel. Sends `LISTEN` on the first subscription.
159
+ *
160
+ * @param channelName - The channel identifier
161
+ * @param callback - Function invoked when a notification arrives
162
+ *
163
+ * @example
164
+ * ```ts
165
+ * await conn.listen('events', data => console.log(data))
166
+ * ```
167
+ */
168
+ listen(channelName: string, callback: (payload: string) => void): Promise<Record<string, any>[]>;
169
+ /**
170
+ * Unsubscribes a callback. Sends `UNLISTEN` if no callbacks remain for the channel.
171
+ *
172
+ * @param channelName - The channel identifier
173
+ * @param callback - The registered callback to remove
174
+ *
175
+ * @example
176
+ * ```ts
177
+ * await conn.unlisten('events', callback)
178
+ * ```
179
+ */
180
+ unlisten(channelName: string, callback: (payload: string) => void): Promise<void> | Promise<Record<string, any>[]>;
181
+ private _createQuery;
182
+ private _writeQuery;
143
183
  private _registerFlush;
144
184
  private _flush;
185
+ private _reconnect;
186
+ private _restoreSubscriptions;
145
187
  private _getCurrentQuery;
146
188
  private _rejectPipeline;
147
189
  private _handlePacket;
@@ -149,7 +191,7 @@ export declare class Connection {
149
191
  * Checks if the connection is still alive and usable.
150
192
  * Returns `false` if the socket is destroyed or connection is dead.
151
193
  */
152
- get isAlive(): boolean;
194
+ get isOpened(): boolean;
153
195
  private _destroyConnection;
154
196
  /**
155
197
  * Closes the connection immediately.
@@ -1 +1 @@
1
- {"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../src/connection.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../src/connection.ts"],"names":[],"mappings":"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"}
@@ -11,6 +11,7 @@ const transaction_1 = require("./transaction");
11
11
  const socket_connector_1 = require("./protocol/socket-connector");
12
12
  const queue_1 = require("./queue");
13
13
  const query_1 = require("./query");
14
+ const _1 = require(".");
14
15
  const ErrConnectionDead = new Error("Connection is Dead");
15
16
  /**
16
17
  * Represents a single dedicated connection to the PostgreSQL database.
@@ -46,18 +47,22 @@ class Connection {
46
47
  _nextStatement() {
47
48
  return `s-${this._stmtCounter++}`;
48
49
  }
49
- constructor(socket, writer, logLevel) {
50
+ constructor(socket, writer, logLevel, params) {
50
51
  this._isFlushing = false;
51
- this._isAlive = true;
52
+ this._isOpened = true;
53
+ this._isReconnecting = false;
52
54
  this._pipelinesQueue = new queue_1.Queue();
53
55
  this._described = new Map();
54
56
  this._describingPending = new Set();
55
57
  this._parsed = new Map();
56
58
  this._parsingPending = new Map();
59
+ this._listeningCallbacks = new Map();
57
60
  this._stmtCounter = 0;
61
+ this.params = params;
58
62
  this._logLevel = logLevel;
59
63
  this._socket = new socket_connector_1.SocketConnector(socket, (type, reader) => this._handlePacket(type, reader), (err) => {
60
- this._destroyConnection();
64
+ this._isReconnecting = true;
65
+ this._rejectPipeline(ErrConnectionDead);
61
66
  });
62
67
  this._writer = writer;
63
68
  }
@@ -82,7 +87,7 @@ class Connection {
82
87
  static async new(params) {
83
88
  const writer = connection_request_writer_1.ConnectionRequestWriter.new();
84
89
  const socket = await (0, socket_authorization_1.createAuthorizedSocket)(writer, params);
85
- return new Connection(socket, writer, params.logLevel || 'error');
90
+ return new Connection(socket, writer, params.logLevel || 'error', params);
86
91
  }
87
92
  /**
88
93
  * Executes a query using tagged template literals.
@@ -110,7 +115,7 @@ class Connection {
110
115
  */
111
116
  query(templates, ...params) {
112
117
  this._registerFlush();
113
- if (!this._isAlive)
118
+ if (!this._isOpened)
114
119
  throw ErrConnectionDead;
115
120
  const { text, args } = (0, template_compiler_1.compileSqlTemplate)({ templates, args: params });
116
121
  if (this._logLevel === 'query') {
@@ -120,6 +125,100 @@ class Connection {
120
125
  this._writeQuery(query);
121
126
  return query.promise;
122
127
  }
128
+ /**
129
+ * Starts a managed transaction on this connection.
130
+ *
131
+ * Automatically handles:
132
+ * - `BEGIN` before the callback
133
+ * - `COMMIT` on successful completion
134
+ * - `ROLLBACK` if an error is thrown
135
+ *
136
+ * @param txCallback - Async function that receives a `Transaction` instance
137
+ * @returns The value returned from the callback
138
+ * @throws {Error} If connection is dead or transaction fails
139
+ *
140
+ * @example
141
+ * ```ts
142
+ * const result = await conn.begin(async tx => {
143
+ * await tx.query`INSERT INTO accounts (id, balance) VALUES (1, 100)`
144
+ * await tx.query`UPDATE accounts SET balance = balance - 10 WHERE id = 1`
145
+ * return { success: true }
146
+ * })
147
+ * ```
148
+ */
149
+ async begin(txCallback) {
150
+ if (!this._isOpened)
151
+ throw ErrConnectionDead;
152
+ const tx = new transaction_1.Transaction(this);
153
+ try {
154
+ await tx.query `BEGIN`;
155
+ const result = await txCallback(tx);
156
+ if (tx.isActive)
157
+ await tx.commit();
158
+ return result;
159
+ }
160
+ catch (err) {
161
+ if (tx.isActive)
162
+ await tx.rollback();
163
+ throw err;
164
+ }
165
+ }
166
+ /**
167
+ * Sends an asynchronous notification to a channel via `pg_notify`.
168
+ *
169
+ * @param channelName - The channel identifier
170
+ * @param payload - Optional string data (max 8000 bytes)
171
+ *
172
+ * @example
173
+ * ```ts
174
+ * await conn.notify('events', 'hello')
175
+ * ```
176
+ */
177
+ notify(channelName, payload = "") {
178
+ return this.query `select pg_notify(${channelName}, ${payload})`;
179
+ }
180
+ /**
181
+ * Subscribes a callback to a channel. Sends `LISTEN` on the first subscription.
182
+ *
183
+ * @param channelName - The channel identifier
184
+ * @param callback - Function invoked when a notification arrives
185
+ *
186
+ * @example
187
+ * ```ts
188
+ * await conn.listen('events', data => console.log(data))
189
+ * ```
190
+ */
191
+ listen(channelName, callback) {
192
+ if (!this._listeningCallbacks.has(channelName)) {
193
+ this._listeningCallbacks.set(channelName, new Set());
194
+ }
195
+ const callbackSet = this._listeningCallbacks.get(channelName);
196
+ callbackSet.add(callback);
197
+ return this.query `listen ${_1.sql.ident(channelName)};`;
198
+ }
199
+ /**
200
+ * Unsubscribes a callback. Sends `UNLISTEN` if no callbacks remain for the channel.
201
+ *
202
+ * @param channelName - The channel identifier
203
+ * @param callback - The registered callback to remove
204
+ *
205
+ * @example
206
+ * ```ts
207
+ * await conn.unlisten('events', callback)
208
+ * ```
209
+ */
210
+ unlisten(channelName, callback) {
211
+ if (!this._listeningCallbacks.has(channelName)) {
212
+ return Promise.resolve();
213
+ }
214
+ const callbackSet = this._listeningCallbacks.get(channelName);
215
+ callbackSet.delete(callback);
216
+ if (callbackSet.size === 0) {
217
+ this._listeningCallbacks.delete(channelName);
218
+ return this.query `unlisten ${_1.sql.ident(channelName)};`;
219
+ }
220
+ return Promise.resolve();
221
+ }
123
222
  _createQuery(text, args) {
124
223
  if (this._parsed.has(text)) {
125
224
  const statementName = this._parsed.get(text);
@@ -170,44 +269,6 @@ class Connection {
170
269
  }
171
270
  this._pipelinesQueue.get().push(query);
172
271
  }
173
- /**
174
- * Starts a managed transaction on this connection.
175
- *
176
- * Automatically handles:
177
- * - `BEGIN` before the callback
178
- * - `COMMIT` on successful completion
179
- * - `ROLLBACK` if an error is thrown
180
- *
181
- * @param txCallback - Async function that receives a `Transaction` instance
182
- * @returns The value returned from the callback
183
- * @throws {Error} If connection is dead or transaction fails
184
- *
185
- * @example
186
- * ```ts
187
- * const result = await conn.begin(async tx => {
188
- * await tx.query`INSERT INTO accounts (id, balance) VALUES (1, 100)`
189
- * await tx.query`UPDATE accounts SET balance = balance - 10 WHERE id = 1`
190
- * return { success: true }
191
- * })
192
- * ```
193
- */
194
- async begin(txCallback) {
195
- if (!this._isAlive)
196
- throw ErrConnectionDead;
197
- const tx = new transaction_1.Transaction(this);
198
- try {
199
- await tx.query `BEGIN`;
200
- const result = await txCallback(tx);
201
- if (tx.isActive)
202
- await tx.commit();
203
- return result;
204
- }
205
- catch (err) {
206
- if (tx.isActive)
207
- await tx.rollback();
208
- throw err;
209
- }
210
- }
211
272
  _registerFlush() {
212
273
  if (!this._isFlushing) {
213
274
  this._isFlushing = true;
@@ -218,18 +279,54 @@ class Connection {
218
279
  }
219
280
  }
220
281
  _flush() {
221
- this._isFlushing = false;
222
- if (!this._isAlive) {
282
+ if (!this._isOpened) {
223
283
  this._rejectPipeline(ErrConnectionDead);
224
284
  return;
225
285
  }
226
- try {
286
+ if (this._isReconnecting) {
287
+ this._reconnect().then(socket => {
288
+ this._isFlushing = false;
289
+ socket.write(this._writer.writeSync());
290
+ this._writer.clear();
291
+ });
292
+ }
293
+ else {
294
+ this._isFlushing = false;
227
295
  this._socket.write(this._writer.writeSync());
228
296
  this._writer.clear();
229
297
  }
230
- catch (err) {
231
- this._destroyConnection();
298
+ }
299
+ _reconnect() {
300
+ this._parsed.clear();
301
+ this._described.clear();
302
+ this._describingPending.clear();
303
+ this._parsingPending.clear();
304
+ this._writer.clear();
305
+ while (this._pipelinesQueue.hasMore) {
306
+ this._rejectPipeline(ErrConnectionDead);
307
+ this._pipelinesQueue.next();
232
308
  }
309
+ this._pipelinesQueue = new queue_1.Queue();
310
+ this._pipelinesQueue.push(new queue_1.Queue());
311
+ return (0, socket_authorization_1.createAuthorizedSocket)(connection_request_writer_1.ConnectionRequestWriter.new(), this.params)
312
+ .then(socket => {
313
+ const connector = new socket_connector_1.SocketConnector(socket, (type, reader) => this._handlePacket(type, reader), (err) => {
314
+ this._isReconnecting = true;
315
+ this._rejectPipeline(ErrConnectionDead);
316
+ });
317
+ this._socket = connector;
318
+ this._restoreSubscriptions();
319
+ this._isReconnecting = false;
320
+ return connector;
321
+ });
322
+ }
323
+ _restoreSubscriptions() {
324
+ if (this._listeningCallbacks.size === 0)
325
+ return Promise.resolve();
326
+ const promises = Array.from(this._listeningCallbacks.keys()).map(channel => {
327
+ return this.query `LISTEN ${_1.sql.ident(channel)};`;
328
+ });
329
+ return Promise.all(promises);
233
330
  }
234
331
  _getCurrentQuery() {
235
332
  return this._pipelinesQueue.get().get();
@@ -350,6 +447,15 @@ class Connection {
350
447
  }
351
448
  }
352
449
  break;
450
+ case constants_1.ResponseTypes.NotificationResponse:
451
+ {
452
+ const { name, payload } = reader.readNotificationResponse();
453
+ const callbackSet = this._listeningCallbacks.get(name);
454
+ if (!callbackSet)
455
+ break;
456
+ callbackSet.forEach(cb => cb(payload));
457
+ }
458
+ break;
353
459
  default: console.warn('Undeclared response type: ', type);
354
460
  }
355
461
  }
@@ -357,11 +463,11 @@ class Connection {
357
463
  * Checks if the connection is still alive and usable.
358
464
  * Returns `false` if the socket is destroyed or connection is dead.
359
465
  */
360
- get isAlive() {
361
- return this._isAlive && !this._socket.isDestroyed;
466
+ get isOpened() {
467
+ return this._isOpened;
362
468
  }
363
469
  _destroyConnection() {
364
- this._isAlive = false;
470
+ this._isOpened = false;
365
471
  this._socket.destroy();
366
472
  while (this._pipelinesQueue.hasMore) {
367
473
  this._rejectPipeline(ErrConnectionDead);
package/dist/pool.d.ts CHANGED
@@ -128,6 +128,18 @@ export declare class Pool {
128
128
  * ```
129
129
  */
130
130
  query<T extends Record<string, any>>(templates: TemplateStringsArray, ...args: any[]): Promise<T[]>;
131
+ /**
132
+ * Sends an asynchronous notification to a channel via `pg_notify`.
133
+ *
134
+ * @param channelName - The channel identifier
135
+ * @param payload - Optional string data (max 8000 bytes)
136
+ *
137
+ * @example
138
+ * ```ts
139
+ * await pool.notify('events', 'hello')
140
+ * ```
141
+ */
142
+ notify(channelName: string, payload?: string): Promise<[]>;
131
143
  /**
132
144
  * Number of available (idle) connections in the pool.
133
145
  */
@@ -1 +1 @@
1
- {"version":3,"file":"pool.d.ts","sourceRoot":"","sources":["../src/pool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAK3C,KAAK,UAAU,GAAG,gBAAgB,GAAG;IACjC,GAAG,CAAC,EAAE,MAAM,CAAA;CACf,CAAA;AAQD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,qBAAa,IAAI;IACb,OAAO,CAAC,UAAU,CAA0B;IAC5C,OAAO,CAAC,OAAO,CAAkB;IACjC,OAAO,CAAC,IAAI,CAAQ;IACpB,OAAO,CAAC,MAAM,CAAI;IAClB,OAAO,CAAC,QAAQ,CAAsB;IACtC,OAAO,CAAC,SAAS,CAAQ;IAEzB,OAAO,CAAC,YAAY;gBAIR,MAAM,EAAE,UAAU;IAM9B;;;;;;;;;;;;;;;;;OAiBG;IACG,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC;IA6BpC;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,CAAC,IAAI,EAAE,UAAU;IAoBxB;;;;;;;;;;;;;;;;;OAiBG;IACG,KAAK,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,WAAW,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAYhF;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,KAAK,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,SAAS,EAAE,oBAAoB,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;IAmCnG;;OAEG;IACH,IAAI,IAAI,WAEP;IAGD;;;OAGG;IACH,IAAI,KAAK,WAER;IAGD;;;;;;;;;;OAUG;IACG,KAAK;CAed"}
1
+ {"version":3,"file":"pool.d.ts","sourceRoot":"","sources":["../src/pool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAK3C,KAAK,UAAU,GAAG,gBAAgB,GAAG;IACjC,GAAG,CAAC,EAAE,MAAM,CAAA;CACf,CAAA;AAQD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,qBAAa,IAAI;IACb,OAAO,CAAC,UAAU,CAA0B;IAC5C,OAAO,CAAC,OAAO,CAAkB;IACjC,OAAO,CAAC,IAAI,CAAQ;IACpB,OAAO,CAAC,MAAM,CAAI;IAClB,OAAO,CAAC,QAAQ,CAAsB;IACtC,OAAO,CAAC,SAAS,CAAQ;IAEzB,OAAO,CAAC,YAAY;gBAIR,MAAM,EAAE,UAAU;IAM9B;;;;;;;;;;;;;;;;;OAiBG;IACG,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC;IA6BpC;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,CAAC,IAAI,EAAE,UAAU;IAoBxB;;;;;;;;;;;;;;;;;OAiBG;IACG,KAAK,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,WAAW,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAYhF;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,KAAK,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,SAAS,EAAE,oBAAoB,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;IAmCnG;;;;;;;;;;OAUG;IACH,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,MAAW,GACuB,OAAO,CAAC,EAAE,CAAC;IAIlF;;OAEG;IACH,IAAI,IAAI,WAEP;IAGD;;;OAGG;IACH,IAAI,KAAK,WAER;IAGD;;;;;;;;;;OAUG;IACG,KAAK;CAed"}
package/dist/pool.js CHANGED
@@ -74,7 +74,7 @@ class Pool {
74
74
  while (this._available.hasMore) {
75
75
  const conn = this._available.get();
76
76
  this._available.next();
77
- if (conn.isAlive) {
77
+ if (conn.isOpened) {
78
78
  return conn;
79
79
  }
80
80
  this._total--;
@@ -113,7 +113,7 @@ class Pool {
113
113
  */
114
114
  release(conn) {
115
115
  this._checkClosed();
116
- if (!conn.isAlive) {
116
+ if (!conn.isOpened) {
117
117
  this._total--;
118
118
  return;
119
119
  }
@@ -180,7 +180,7 @@ class Pool {
180
180
  this._checkClosed();
181
181
  while (this._available.hasMore) {
182
182
  const conn = this._available.get();
183
- if (conn.isAlive) {
183
+ if (conn.isOpened) {
184
184
  return conn.query(templates, ...args);
185
185
  }
186
186
  this._available.next();
@@ -204,6 +204,20 @@ class Pool {
204
204
  return conn.query(templates, ...args);
205
205
  });
206
206
  }
207
+ /**
208
+ * Sends an asynchronous notification to a channel via `pg_notify`.
209
+ *
210
+ * @param channelName - The channel identifier
211
+ * @param payload - Optional string data (max 8000 bytes)
212
+ *
213
+ * @example
214
+ * ```ts
215
+ * await pool.notify('events', 'hello')
216
+ * ```
217
+ */
218
+ notify(channelName, payload = "") {
219
+ return this.query `select pg_notify(${channelName}, ${payload})`;
220
+ }
207
221
  /**
208
222
  * Number of available (idle) connections in the pool.
209
223
  */
@@ -1,6 +1,7 @@
1
1
  import { AuthenticationCode, ResponseType, TransactionStatus } from "./constants";
2
2
  import { ColumnDescription } from "../types";
3
3
  import { PostgresError } from "../error";
4
+ import { ChannelName } from "../connection";
4
5
  export declare class ConnectionResponseBuffer {
5
6
  private buffer;
6
7
  private caret;
@@ -38,6 +39,10 @@ export declare class ConnectionResponseReader {
38
39
  readSaslMessage(): string;
39
40
  readRowDescription(): ColumnDescription[];
40
41
  readDataRow(): (string | null)[];
42
+ readNotificationResponse(): {
43
+ name: ChannelName;
44
+ payload: string;
45
+ };
41
46
  readCommandComplete(): string;
42
47
  hasMore(): boolean;
43
48
  hasFullPacket(): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"connection-response-reader.d.ts","sourceRoot":"","sources":["../../src/protocol/connection-response-reader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AACjF,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAGxC,qBAAa,wBAAwB;IAI7B,OAAO,CAAC,MAAM;IAHlB,OAAO,CAAC,KAAK,CAAI;IAEjB,OAAO;IAKP,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM;IAK1B,QAAQ,IAAI,MAAM;IAOlB,SAAS,IAAI,MAAM;IAOnB,SAAS,IAAI,MAAM;IAOnB,WAAW,IAAI,MAAM;IAQrB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAOrC,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAOjC,OAAO,IAAI,OAAO;IAKlB,aAAa,IAAI,OAAO;IAaxB,iBAAiB;CAGpB;AAGD,qBAAa,wBAAwB;IAI7B,OAAO,CAAC,MAAM;IAHlB,OAAO,CAAC,mBAAmB,CAAK;IAEhC,OAAO;IAKP,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM;IAK1B,QAAQ,IAC6B,YAAY;IAIjD,kBAAkB,IAGoB,kBAAkB;IAIxD,WAAW;IAKX,mBAAmB;;;;IAUnB,kBAAkB;;;;IAUlB,iBAAiB,IAAI,aAAa;IA8ClC,iBAAiB,IAEoB,iBAAiB;IAItD,kBAAkB,IAAI,MAAM,EAAE;IAa9B,eAAe,IAAI,MAAM;IAOzB,kBAAkB;IAuBlB,WAAW;IAmBX,mBAAmB,IAAI,MAAM;IAM7B,OAAO;IAKP,aAAa;IAKb,iBAAiB;IAKjB,iBAAiB;IAKjB,gBAAgB;IAKhB,wBAAwB;IAQxB,UAAU;CAGb"}
1
+ {"version":3,"file":"connection-response-reader.d.ts","sourceRoot":"","sources":["../../src/protocol/connection-response-reader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AACjF,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAG3C,qBAAa,wBAAwB;IAI7B,OAAO,CAAC,MAAM;IAHlB,OAAO,CAAC,KAAK,CAAI;IAEjB,OAAO;IAKP,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM;IAK1B,QAAQ,IAAI,MAAM;IAOlB,SAAS,IAAI,MAAM;IAOnB,SAAS,IAAI,MAAM;IAOnB,WAAW,IAAI,MAAM;IAQrB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAOrC,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAOjC,OAAO,IAAI,OAAO;IAKlB,aAAa,IAAI,OAAO;IAaxB,iBAAiB;CAGpB;AAGD,qBAAa,wBAAwB;IAI7B,OAAO,CAAC,MAAM;IAHlB,OAAO,CAAC,mBAAmB,CAAK;IAEhC,OAAO;IAKP,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM;IAK1B,QAAQ,IAC6B,YAAY;IAIjD,kBAAkB,IAGoB,kBAAkB;IAIxD,WAAW;IAKX,mBAAmB;;;;IAUnB,kBAAkB;;;;IAUlB,iBAAiB,IAAI,aAAa;IA8ClC,iBAAiB,IAEoB,iBAAiB;IAItD,kBAAkB,IAAI,MAAM,EAAE;IAa9B,eAAe,IAAI,MAAM;IAOzB,kBAAkB;IAuBlB,WAAW;IAmBX,wBAAwB;;;;IAUxB,mBAAmB,IAAI,MAAM;IAM7B,OAAO;IAKP,aAAa;IAKb,iBAAiB;IAKjB,iBAAiB;IAKjB,gBAAgB;IAKhB,wBAAwB;IAQxB,UAAU;CAGb"}
@@ -188,6 +188,13 @@ class ConnectionResponseReader {
188
188
  }
189
189
  return rowValues;
190
190
  }
191
+ readNotificationResponse() {
192
+ this.buffer.readInt32();
193
+ this.buffer.readInt32();
194
+ const name = this.buffer.readCString();
195
+ const payload = this.buffer.readCString();
196
+ return { name, payload };
197
+ }
191
198
  readCommandComplete() {
192
199
  this.buffer.readInt32();
193
200
  return this.buffer.readCString();
@@ -26,6 +26,7 @@ export declare const ResponseTypes: {
26
26
  readonly ParameterDescription: "t";
27
27
  readonly NoData: "n";
28
28
  readonly Notice: "N";
29
+ readonly NotificationResponse: "A";
29
30
  };
30
31
  export type ResponseType = ValueOF<typeof ResponseTypes>;
31
32
  export declare const AuthenticationCodes: {
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/protocol/constants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAA;AAElC,eAAO,MAAM,YAAY;;;;;;;;;CASf,CAAA;AAEV,MAAM,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,YAAY,CAAC,CAAA;AAGtD,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;CAgBhB,CAAA;AAGV,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,aAAa,CAAC,CAAA;AAGxD,eAAO,MAAM,mBAAmB;;;;;;;CAOtB,CAAA;AAEV,MAAM,MAAM,kBAAkB,GAAG,OAAO,CAAC,OAAO,mBAAmB,CAAC,CAAA;AAGpE,eAAO,MAAM,mBAAmB;;;;CAItB,CAAA;AAEV,MAAM,MAAM,iBAAiB,GAAG,OAAO,CAAC,OAAO,mBAAmB,CAAC,CAAA"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/protocol/constants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAA;AAElC,eAAO,MAAM,YAAY;;;;;;;;;CASf,CAAA;AAEV,MAAM,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,YAAY,CAAC,CAAA;AAGtD,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;CAiBhB,CAAA;AAGV,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,aAAa,CAAC,CAAA;AAGxD,eAAO,MAAM,mBAAmB;;;;;;;CAOtB,CAAA;AAEV,MAAM,MAAM,kBAAkB,GAAG,OAAO,CAAC,OAAO,mBAAmB,CAAC,CAAA;AAGpE,eAAO,MAAM,mBAAmB;;;;CAItB,CAAA;AAEV,MAAM,MAAM,iBAAiB,GAAG,OAAO,CAAC,OAAO,mBAAmB,CAAC,CAAA"}
@@ -26,7 +26,8 @@ exports.ResponseTypes = {
26
26
  CloseComplete: "3",
27
27
  ParameterDescription: "t",
28
28
  NoData: "n",
29
- Notice: "N"
29
+ Notice: "N",
30
+ NotificationResponse: "A"
30
31
  };
31
32
  exports.AuthenticationCodes = {
32
33
  Ok: 0,
@@ -1 +1 @@
1
- {"version":3,"file":"socket-connector.d.ts","sourceRoot":"","sources":["../../src/protocol/socket-connector.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,KAAK,CAAA;AAC5B,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC1C,OAAO,EAAE,wBAAwB,EAAE,MAAM,wCAAwC,CAAA;AACjF,OAAO,EAAE,uBAAuB,EAAE,MAAM,uCAAuC,CAAA;AAE/E,qBAAa,eAAe;IAKpB,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,QAAQ;IANpB,OAAO,CAAC,cAAc,CAAsB;IAC5C,OAAO,CAAC,YAAY,CAAQ;gBAGhB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,wBAAwB,KAAK,IAAI,EACvE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI;IA4B5C,KAAK,CAAC,MAAM,EAAE,uBAAuB;IAOrC,YAAY;IAQZ,OAAO;IAMP,IAAI,WAAW,YAA6B;CAC/C"}
1
+ {"version":3,"file":"socket-connector.d.ts","sourceRoot":"","sources":["../../src/protocol/socket-connector.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,KAAK,CAAA;AAC5B,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC1C,OAAO,EAAE,wBAAwB,EAAE,MAAM,wCAAwC,CAAA;AACjF,OAAO,EAAE,uBAAuB,EAAE,MAAM,uCAAuC,CAAA;AAE/E,qBAAa,eAAe;IAKpB,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,QAAQ;IANpB,OAAO,CAAC,cAAc,CAAsB;IAC5C,OAAO,CAAC,YAAY,CAAQ;gBAGhB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,wBAAwB,KAAK,IAAI,EACvE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI;IA6B5C,KAAK,CAAC,MAAM,EAAE,uBAAuB;IAOrC,YAAY;IAQZ,OAAO;IAMP,IAAI,WAAW,YAA6B;CAC/C"}
@@ -9,6 +9,7 @@ class SocketConnector {
9
9
  this._onError = _onError;
10
10
  this.residualBuffer = null;
11
11
  this._isDestroyed = false;
12
+ _socket.setKeepAlive(true, 10000);
12
13
  _socket.on('data', buffer => {
13
14
  const currentBuffer = this.residualBuffer
14
15
  ? Buffer.concat([this.residualBuffer, buffer])
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@m2k-5f/pgtx",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "Blazing-fast PostgreSQL driver with pipeline support.",
5
5
  "files": [
6
6
  "dist",