@m2k-5f/pgtx 2.4.1 → 2.5.1

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/dist/pool.d.ts CHANGED
@@ -67,7 +67,26 @@ export declare class Pool {
67
67
  * }
68
68
  * ```
69
69
  */
70
- acquire(): Future<Connection, Error>;
70
+ acquire(): Future<Connection, PostgresError>;
71
+ /**
72
+ * Provides a safe execution context for performing low-level operations
73
+ * directly on a single, dedicated `Connection` instance.
74
+ *
75
+ * Automatically borrows a free socket from the pool, forwards it to the provided callback function,
76
+ * and guarantees that the connection is released back to the pool once the execution completes,
77
+ * even if errors or unexpected exceptions are thrown. Prevents connection descriptor leaks.
78
+ *
79
+ * @template T The return type of the provided callback function.
80
+ * @param {(conn: Connection) => Promise<T>} fn A callback function that operates on the allocated Connection.
81
+ * @returns {Future<T, PostgresError>} A `Future` that resolves with the return value of the callback.
82
+ *
83
+ * @example
84
+ * // Executing low-level engine commands on a single, pinned connection
85
+ * const status = await pool.withAcquire(async (conn) => {
86
+ * return await conn.query`SELECT pg_is_in_recovery()`;
87
+ * });
88
+ */
89
+ withAcquire<T>(fn: (conn: Connection) => Promise<T>): Future<T, unknown>;
71
90
  /**
72
91
  * Releases the connection back to the pool.
73
92
  *
@@ -105,7 +124,7 @@ export declare class Pool {
105
124
  * })
106
125
  * ```
107
126
  */
108
- begin<T>(txCallback: (transaction: Transaction) => Promise<T>): Future<T, Error>;
127
+ begin<T>(txCallback: (transaction: Transaction) => Promise<T>): Future<T, unknown>;
109
128
  /**
110
129
  * Executes a one-off query using pipeline.
111
130
  *
@@ -129,7 +148,33 @@ export declare class Pool {
129
148
  * const users = await pool.query<User>`SELECT * FROM users`
130
149
  * ```
131
150
  */
132
- query<T extends Record<string, any>>(templates: TemplateStringsArray, ...args: any[]): Future<T[], Error>;
151
+ query<T extends Record<string, any>>(templates: TemplateStringsArray, ...args: any[]): Future<T[], PostgresError>;
152
+ /**
153
+ * Executes an SQL query in streaming mode.
154
+ *
155
+ * Data is streamed directly from the PostgreSQL binary network buffer into the Web Streams API
156
+ * (`ReadableStream`), bypassing any intermediate array allocation or row accumulation in the JS heap.
157
+ * This pattern provides a true Zero-Memory Footprint and is ideal for exporting massive tables
158
+ * or piping database payloads directly into HTTP responses (e.g., via `Bun.serve` or fetch `Response`).
159
+ *
160
+ * @template T The expected shape of a single row interface.
161
+ * @param {TemplateStringsArray} templates The SQL string parts from the tagged template literal.
162
+ * @param {...any} args The parameterized query arguments.
163
+ * @returns {ReadableStream<T>} Synchronously returns a native Web ReadableStream instance.
164
+ *
165
+ * @example
166
+ * // Streaming a giant table directly to an HTTP response (Bun.serve)
167
+ * const userStream = pool.stream<User>`SELECT id, name FROM users`;
168
+ * return new Response(userStream, { headers: { 'Content-Type': 'application/json' } });
169
+ *
170
+ * @example
171
+ * // Asynchronously iterating over rows as they arrive from the wire socket
172
+ * const stream = pool.stream<User>`SELECT * FROM orders WHERE status = ${'processed'}`;
173
+ * for await (const row of stream) {
174
+ * console.log(row.id, row.amount); // Row object is eligible for GC immediately after iteration
175
+ * }
176
+ */
177
+ stream<T extends Record<string, any>>(templates: TemplateStringsArray, ...args: any[]): ReadableStream<T>;
133
178
  /**
134
179
  * Sends an asynchronous notification to a channel via `pg_notify`.
135
180
  *
@@ -142,6 +187,31 @@ export declare class Pool {
142
187
  * ```
143
188
  */
144
189
  notify(channelName: string, payload?: string): Future<[], PostgresError>;
190
+ /**
191
+ * Asynchronously subscribes to pub/sub events on a specific PostgreSQL channel (LISTEN).
192
+ *
193
+ * This method automatically claims a dedicated connection from the pool, registers the callback
194
+ * to handle incoming asynchronous database notices (`NotificationResponse` packets), and returns
195
+ * a lazy unsubscribe function wrapped in a `Future`.
196
+ *
197
+ * Invoking the returned unsubscribe function will automatically issue the `UNLISTEN` command
198
+ * to the database backend, clean up the memory callback, and safely release the connection back to the pool.
199
+ *
200
+ * @param {string} channel The name of the PostgreSQL notification channel.
201
+ * @param {(payload: string) => void} callback The event handler invoked when a NOTIFY message arrives.
202
+ * @returns {Future<() => Promise<void>, PostgresError>} A `Future` resolving to an async unsubscribe function.
203
+ *
204
+ * @example
205
+ * // Subscribing to database events directly from the Pool
206
+ * const unlisten = await pool.listen('order_created', (payload) => {
207
+ * const order = JSON.parse(payload);
208
+ * console.log(`New order received: ${order.id}`);
209
+ * });
210
+ *
211
+ * // When the subscription is no longer needed (e.g., during teardown or server stop):
212
+ * await unlisten(); // The socket cleanly issues UNLISTEN and returns to the pool of free connections.
213
+ */
214
+ listen(channel: string, callback: (payload: string) => void): Future<() => Promise<void>, PostgresError>;
145
215
  /**
146
216
  * Number of available (idle) connections in the pool.
147
217
  */
@@ -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;AAG3C,OAAO,EAAE,MAAM,EAAW,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAGxC,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;IACH,OAAO;IAyBP;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,CAAC,IAAI,EAAE,UAAU;IAiBxB;;;;;;;;;;;;;;;;;OAiBG;IACH,KAAK,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,WAAW,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC;IAW7D;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,KAAK,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,SAAS,EAAE,oBAAoB,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE;IAuBpF;;;;;;;;;;OAUG;IACH,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,MAAW,GACuB,MAAM,CAAC,EAAE,EAAE,aAAa,CAAC;IAIhG;;OAEG;IACH,IAAI,IAAI,WAEP;IAGD;;;OAGG;IACH,IAAI,KAAK,WAER;IAGD;;;;;;;;;;OAUG;IACH,KAAK;CAWR"}
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;AAG3C,OAAO,EAAS,MAAM,EAAM,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAGxC,KAAK,UAAU,GAAG,gBAAgB,GAAG;IACjC,GAAG,CAAC,EAAE,MAAM,CAAA;CACf,CAAA;AAWD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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;IACH,OAAO;IA2BP;;;;;;;;;;;;;;;;;OAiBG;IACH,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC;IAUnD;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,CAAC,IAAI,EAAE,UAAU;IAiBxB;;;;;;;;;;;;;;;;;OAiBG;IACH,KAAK,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,WAAW,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC;IAY7D;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,KAAK,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,SAAS,EAAE,oBAAoB,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE;IAuBpF;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,MAAM,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,SAAS,EAAE,oBAAoB,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,cAAc,CAAC,CAAC,CAAC;IAoCzG;;;;;;;;;;OAUG;IACH,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,MAAW,GACuB,MAAM,CAAC,EAAE,EAAE,aAAa,CAAC;IAIhG;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI;IAY3D;;OAEG;IACH,IAAI,IAAI,WAEP;IAGD;;;OAGG;IACH,IAAI,KAAK,WAER;IAGD;;;;;;;;;;OAUG;IACH,KAAK;CAWR"}
package/dist/pool.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import { Connection } from "./connection";
2
2
  import { Queue } from "./queue";
3
- import { Future, Resolve } from "fluent-future";
3
+ import { Begin, Future, Ok } from "fluent-future";
4
+ import { PostgresError } from "./error";
5
+ const ErrPoolClosed = new PostgresError('Pool closed');
4
6
  /**
5
7
  * The main entry point for Pgtx.
6
8
  * Manages a connection pool and provides high-level API for queries and transactions.
@@ -72,18 +74,42 @@ export class Pool {
72
74
  while (this._available.hasMore) {
73
75
  const conn = this._available.shift;
74
76
  if (conn.isOpened) {
75
- return Resolve(conn);
77
+ return Ok(conn);
76
78
  }
77
79
  this._total--;
78
80
  }
79
81
  if (this._total < this._max) {
80
82
  this._total++;
81
- return Future.of(Connection.new(this._config))
83
+ return Connection.new(this._config)
82
84
  .tapErr(() => this._total--);
83
85
  }
84
- return Future.of(new Promise((resolve, reject) => {
85
- this._waiting.push({ resolve, reject });
86
- }));
86
+ const { future, reject, resolve } = Future.withResolvers();
87
+ this._waiting.push({ resolve, reject });
88
+ return future;
89
+ }
90
+ /**
91
+ * Provides a safe execution context for performing low-level operations
92
+ * directly on a single, dedicated `Connection` instance.
93
+ *
94
+ * Automatically borrows a free socket from the pool, forwards it to the provided callback function,
95
+ * and guarantees that the connection is released back to the pool once the execution completes,
96
+ * even if errors or unexpected exceptions are thrown. Prevents connection descriptor leaks.
97
+ *
98
+ * @template T The return type of the provided callback function.
99
+ * @param {(conn: Connection) => Promise<T>} fn A callback function that operates on the allocated Connection.
100
+ * @returns {Future<T, PostgresError>} A `Future` that resolves with the return value of the callback.
101
+ *
102
+ * @example
103
+ * // Executing low-level engine commands on a single, pinned connection
104
+ * const status = await pool.withAcquire(async (conn) => {
105
+ * return await conn.query`SELECT pg_is_in_recovery()`;
106
+ * });
107
+ */
108
+ withAcquire(fn) {
109
+ return Begin()
110
+ .andThen(() => this.acquire())
111
+ .andThen(conn => Future.of(fn(conn))
112
+ .finally(() => this.release(conn)));
87
113
  }
88
114
  /**
89
115
  * Releases the connection back to the pool.
@@ -135,7 +161,8 @@ export class Pool {
135
161
  */
136
162
  begin(txCallback) {
137
163
  this._checkClosed();
138
- return this.acquire()
164
+ return Begin()
165
+ .andThen(() => this.acquire())
139
166
  .andThen(conn => conn.begin(txCallback)
140
167
  .finally(() => this.release(conn)));
141
168
  }
@@ -179,6 +206,58 @@ export class Pool {
179
206
  return conn.query(templates, ...args);
180
207
  });
181
208
  }
209
+ /**
210
+ * Executes an SQL query in streaming mode.
211
+ *
212
+ * Data is streamed directly from the PostgreSQL binary network buffer into the Web Streams API
213
+ * (`ReadableStream`), bypassing any intermediate array allocation or row accumulation in the JS heap.
214
+ * This pattern provides a true Zero-Memory Footprint and is ideal for exporting massive tables
215
+ * or piping database payloads directly into HTTP responses (e.g., via `Bun.serve` or fetch `Response`).
216
+ *
217
+ * @template T The expected shape of a single row interface.
218
+ * @param {TemplateStringsArray} templates The SQL string parts from the tagged template literal.
219
+ * @param {...any} args The parameterized query arguments.
220
+ * @returns {ReadableStream<T>} Synchronously returns a native Web ReadableStream instance.
221
+ *
222
+ * @example
223
+ * // Streaming a giant table directly to an HTTP response (Bun.serve)
224
+ * const userStream = pool.stream<User>`SELECT id, name FROM users`;
225
+ * return new Response(userStream, { headers: { 'Content-Type': 'application/json' } });
226
+ *
227
+ * @example
228
+ * // Asynchronously iterating over rows as they arrive from the wire socket
229
+ * const stream = pool.stream<User>`SELECT * FROM orders WHERE status = ${'processed'}`;
230
+ * for await (const row of stream) {
231
+ * console.log(row.id, row.amount); // Row object is eligible for GC immediately after iteration
232
+ * }
233
+ */
234
+ stream(templates, ...args) {
235
+ this._checkClosed();
236
+ while (this._available.hasMore) {
237
+ const conn = this._available.shift;
238
+ if (!conn.isOpened) {
239
+ this._total--;
240
+ continue;
241
+ }
242
+ this._available.push(conn);
243
+ return conn.stream(templates, ...args);
244
+ }
245
+ let controller;
246
+ const stream = new ReadableStream({
247
+ start: c => {
248
+ controller = c;
249
+ }
250
+ });
251
+ this.acquire()
252
+ .tap(conn => {
253
+ this.release(conn);
254
+ conn['_streamWithController'](templates, args, controller);
255
+ })
256
+ .catch(err => {
257
+ controller.error(err);
258
+ });
259
+ return stream;
260
+ }
182
261
  /**
183
262
  * Sends an asynchronous notification to a channel via `pg_notify`.
184
263
  *
@@ -193,6 +272,38 @@ export class Pool {
193
272
  notify(channelName, payload = "") {
194
273
  return this.query `select pg_notify(${channelName}, ${payload})`;
195
274
  }
275
+ /**
276
+ * Asynchronously subscribes to pub/sub events on a specific PostgreSQL channel (LISTEN).
277
+ *
278
+ * This method automatically claims a dedicated connection from the pool, registers the callback
279
+ * to handle incoming asynchronous database notices (`NotificationResponse` packets), and returns
280
+ * a lazy unsubscribe function wrapped in a `Future`.
281
+ *
282
+ * Invoking the returned unsubscribe function will automatically issue the `UNLISTEN` command
283
+ * to the database backend, clean up the memory callback, and safely release the connection back to the pool.
284
+ *
285
+ * @param {string} channel The name of the PostgreSQL notification channel.
286
+ * @param {(payload: string) => void} callback The event handler invoked when a NOTIFY message arrives.
287
+ * @returns {Future<() => Promise<void>, PostgresError>} A `Future` resolving to an async unsubscribe function.
288
+ *
289
+ * @example
290
+ * // Subscribing to database events directly from the Pool
291
+ * const unlisten = await pool.listen('order_created', (payload) => {
292
+ * const order = JSON.parse(payload);
293
+ * console.log(`New order received: ${order.id}`);
294
+ * });
295
+ *
296
+ * // When the subscription is no longer needed (e.g., during teardown or server stop):
297
+ * await unlisten(); // The socket cleanly issues UNLISTEN and returns to the pool of free connections.
298
+ */
299
+ listen(channel, callback) {
300
+ return this.acquire()
301
+ .andThen(conn => conn.listen(channel, callback)
302
+ .map(() => async () => {
303
+ await conn.unlisten(channel, callback);
304
+ this.release(conn);
305
+ }));
306
+ }
196
307
  /**
197
308
  * Number of available (idle) connections in the pool.
198
309
  */
@@ -222,7 +333,7 @@ export class Pool {
222
333
  this._available.shift.close();
223
334
  }
224
335
  while (this._waiting.hasMore) {
225
- this._waiting.shift.reject(new Error('Pool closed'));
336
+ this._waiting.shift.reject(ErrPoolClosed);
226
337
  }
227
338
  this._total = 0;
228
339
  }
@@ -32,7 +32,10 @@ export declare class ConnectionResponseReader {
32
32
  private currentPacketLength;
33
33
  private constructor();
34
34
  static from(buffer: Buffer): ConnectionResponseReader;
35
- readType(): ResponseType;
35
+ readType(): {
36
+ type: ResponseType;
37
+ length: number;
38
+ };
36
39
  readAuthentication(): AuthenticationCode;
37
40
  readMD5Salt(): Buffer<ArrayBufferLike>;
38
41
  readParameterStatus(): {
@@ -46,7 +49,7 @@ export declare class ConnectionResponseReader {
46
49
  readErrorResponse(): PostgresError;
47
50
  readReadyForQuery(): TransactionStatus;
48
51
  readSaslMechanisms(): string[];
49
- readSaslMessage(): string;
52
+ readSaslMessage(length: number): string;
50
53
  readRowDescription(): ColumnDescription[];
51
54
  readDataRow(descriptions: ColumnDescription[], int8toBigint?: boolean): Record<string, any>;
52
55
  readNotificationResponse(): {
@@ -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,EAA6B,YAAY,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAC5G,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAM3C,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,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAO3C,cAAc,IAAI,IAAI;IAOtB,mBAAmB,IAAI,IAAI;IAO3B,cAAc,IAAI,MAAM;IAaxB,SAAS,CAAC,SAAS,EAAE,MAAM;IAK3B,aAAa,IAAI,OAAO;IAaxB,iBAAiB;IAIjB,QAAQ;IAMR,YAAY,IAAI,MAAM;IAOtB,SAAS;IAST,WAAW,IAAI,MAAM;IAMrB,aAAa,CAAC,MAAM,EAAE,MAAM;CAW/B;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,CAAC,YAAY,EAAE,iBAAiB,EAAE,EAAE,YAAY,GAAE,OAAe,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IA8FlG,wBAAwB;;;;IAUxB,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,EAA6B,YAAY,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAC5G,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAM3C,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,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAO3C,cAAc,IAAI,IAAI;IAOtB,mBAAmB,IAAI,IAAI;IAO3B,cAAc,IAAI,MAAM;IAaxB,SAAS,CAAC,SAAS,EAAE,MAAM;IAK3B,aAAa,IAAI,OAAO;IAaxB,iBAAiB;IAIjB,QAAQ;IAMR,YAAY,IAAI,MAAM;IAOtB,SAAS;IAST,WAAW,IAAI,MAAM;IAMrB,aAAa,CAAC,MAAM,EAAE,MAAM;CAW/B;AAGD,qBAAa,wBAAwB;IAI7B,OAAO,CAAC,MAAM;IAHlB,OAAO,CAAC,mBAAmB,CAAK;IAEhC,OAAO;IAKP,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM;IAK1B,QAAQ;cACoC,YAAY;;;IAIxD,kBAAkB,IACoB,kBAAkB;IAIxD,WAAW;IAKX,mBAAmB;;;;IAQnB,kBAAkB;;;;IAQlB,iBAAiB,IAAI,aAAa;IA4ClC,iBAAiB,IACoB,iBAAiB;IAItD,kBAAkB,IAAI,MAAM,EAAE;IAa9B,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAKvC,kBAAkB;IAsBlB,WAAW,CAAC,YAAY,EAAE,iBAAiB,EAAE,EAAE,YAAY,GAAE,OAAe,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IA6FlG,wBAAwB;;;;IASxB,mBAAmB,IAAI,MAAM;IAK7B,OAAO;IAKP,aAAa;IAKb,iBAAiB;IAKjB,iBAAiB;IAGjB,gBAAgB;IAGhB,wBAAwB;IAOxB,UAAU;CACb"}
@@ -123,31 +123,27 @@ export class ConnectionResponseReader {
123
123
  return new ConnectionResponseReader(ConnectionResponseBuffer.from(buffer));
124
124
  }
125
125
  readType() {
126
- return this.buffer.readChar();
126
+ return { type: this.buffer.readChar(), length: this.buffer.readInt32() };
127
127
  }
128
128
  readAuthentication() {
129
- this.currentPacketLength = this.buffer.readInt32();
130
129
  return this.buffer.readInt32();
131
130
  }
132
131
  readMD5Salt() {
133
132
  return this.buffer.readBytes(4);
134
133
  }
135
134
  readParameterStatus() {
136
- this.buffer.readInt32();
137
135
  return {
138
136
  name: this.buffer.readCString(),
139
137
  value: this.buffer.readCString()
140
138
  };
141
139
  }
142
140
  readBackendKeyData() {
143
- this.buffer.readInt32();
144
141
  return {
145
142
  PID: this.buffer.readInt32(),
146
143
  secret: this.buffer.readInt32()
147
144
  };
148
145
  }
149
146
  readErrorResponse() {
150
- this.buffer.readInt32();
151
147
  let severity = '';
152
148
  let code = '';
153
149
  let message = '';
@@ -195,7 +191,6 @@ export class ConnectionResponseReader {
195
191
  return new PostgresError(message, code, detail, severity, where, hint, position, dataType, constraint);
196
192
  }
197
193
  readReadyForQuery() {
198
- this.buffer.readInt32();
199
194
  return this.buffer.readChar();
200
195
  }
201
196
  readSaslMechanisms() {
@@ -208,12 +203,10 @@ export class ConnectionResponseReader {
208
203
  }
209
204
  return mechanisms;
210
205
  }
211
- readSaslMessage() {
212
- const dataLength = this.currentPacketLength - 4 - 4;
213
- return this.buffer.readRawString(dataLength);
206
+ readSaslMessage(length) {
207
+ return this.buffer.readRawString(length - 4 - 4);
214
208
  }
215
209
  readRowDescription() {
216
- this.buffer.skipBytes(4);
217
210
  const columnsCount = this.buffer.readInt16();
218
211
  const columns = new Array(columnsCount);
219
212
  for (let i = 0; i < columnsCount; i++) {
@@ -230,7 +223,6 @@ export class ConnectionResponseReader {
230
223
  return columns;
231
224
  }
232
225
  readDataRow(descriptions, int8toBigint = false) {
233
- this.buffer.skipBytes(4);
234
226
  const fieldsCount = this.buffer.readInt16();
235
227
  const row = {};
236
228
  for (let i = 0; i < fieldsCount; i++) {
@@ -311,14 +303,12 @@ export class ConnectionResponseReader {
311
303
  return row;
312
304
  }
313
305
  readNotificationResponse() {
314
- this.buffer.readInt32();
315
306
  this.buffer.readInt32();
316
307
  const name = this.buffer.readCString();
317
308
  const payload = this.buffer.readCString();
318
309
  return { name, payload };
319
310
  }
320
311
  readCommandComplete() {
321
- this.buffer.readInt32();
322
312
  return this.buffer.readCString();
323
313
  }
324
314
  hasMore() {
@@ -330,20 +320,13 @@ export class ConnectionResponseReader {
330
320
  getResidualBuffer() {
331
321
  return this.buffer.getResidualBuffer();
332
322
  }
333
- readParseComplete() {
334
- this.buffer.readInt32();
335
- }
336
- readBindComplete() {
337
- this.buffer.readInt32();
338
- }
323
+ readParseComplete() { }
324
+ readBindComplete() { }
339
325
  readParameterDescription() {
340
- this.buffer.readInt32();
341
326
  const count = this.buffer.readInt16();
342
327
  for (let i = 0; i < count; i++) {
343
328
  this.buffer.readInt32();
344
329
  }
345
330
  }
346
- readNoData() {
347
- this.buffer.readInt32();
348
- }
331
+ readNoData() { }
349
332
  }
@@ -11,5 +11,6 @@ export type AuthorizationParams = {
11
11
  };
12
12
  export declare const ErrNonceMismatch: PostgresError;
13
13
  export declare const ErrPasswordRequired: PostgresError;
14
+ export declare const ErrSocketFailedDuringAuth: PostgresError;
14
15
  export declare const createAuthorizedSocket: (writer: ConnectionRequestWriter, params: AuthorizationParams) => Future<Socket, PostgresError>;
15
16
  //# sourceMappingURL=socket-authorization.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"socket-authorization.d.ts","sourceRoot":"","sources":["../../src/protocol/socket-authorization.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,MAAM,EAAE,MAAM,UAAU,CAAA;AACnD,OAAO,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAA;AAKrE,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AAGtC,MAAM,MAAM,mBAAmB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;CACpB,CAAA;AAGD,eAAO,MAAM,gBAAgB,eAAmF,CAAA;AAChH,eAAO,MAAM,mBAAmB,eAAqE,CAAA;AAIrG,eAAO,MAAM,sBAAsB,GAAI,QAAQ,uBAAuB,EAAE,QAAQ,mBAAmB,kCAwHlG,CAAA"}
1
+ {"version":3,"file":"socket-authorization.d.ts","sourceRoot":"","sources":["../../src/protocol/socket-authorization.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,MAAM,EAAE,MAAM,UAAU,CAAA;AACnD,OAAO,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAA;AAKrE,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AAGtC,MAAM,MAAM,mBAAmB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;CACpB,CAAA;AAGD,eAAO,MAAM,gBAAgB,eAAmF,CAAA;AAChH,eAAO,MAAM,mBAAmB,eAAqE,CAAA;AACrG,eAAO,MAAM,yBAAyB,eAAiD,CAAA;AAIvF,eAAO,MAAM,sBAAsB,GAAI,QAAQ,uBAAuB,EAAE,QAAQ,mBAAmB,kCAoHlG,CAAA"}
@@ -7,93 +7,90 @@ import { PostgresError } from "../error";
7
7
  import { Future } from "fluent-future";
8
8
  export const ErrNonceMismatch = new PostgresError("Protocol violation: server nonce doesn't match client nonce");
9
9
  export const ErrPasswordRequired = new PostgresError('The authorization method requires a password.');
10
+ export const ErrSocketFailedDuringAuth = new PostgresError("Socket failed during auth");
10
11
  export const createAuthorizedSocket = (writer, params) => {
11
- return Future.of(new Promise((resolve, reject) => {
12
- const nonce = generateNonce();
13
- let clientMessage = '';
14
- let serverMessage = '';
15
- const socket = createConnection({ host: params.host, port: params.port });
16
- const connector = new SocketConnector(socket, (type, reader) => {
17
- writer.clear();
18
- switch (type) {
19
- case ResponseTypes.Authentication: {
20
- switch (reader.readAuthentication()) {
21
- case AuthenticationCodes.Ok: break;
22
- case AuthenticationCodes.CleartextPassword: {
23
- if (!params.password)
24
- throw ErrPasswordRequired;
25
- connector.write(writer.writePassword(params.password));
26
- break;
27
- }
28
- case AuthenticationCodes.MD5Password: {
29
- const salt = reader.readMD5Salt();
30
- if (!params.password)
31
- throw ErrPasswordRequired;
32
- const password = encryptMd5(params.password, params.user, salt);
33
- connector.write(writer.writePassword(password));
34
- break;
35
- }
36
- case AuthenticationCodes.SASL: {
37
- reader.readSaslMechanisms();
38
- if (!params.password)
39
- throw ErrPasswordRequired;
40
- clientMessage = `n=${params.user},r=${nonce}`;
41
- connector.write(writer.writeSaslInitial('SCRAM-SHA-256', `n,,${clientMessage}`));
42
- break;
43
- }
44
- case AuthenticationCodes.SASLContinue: {
45
- serverMessage = reader.readSaslMessage();
46
- if (!params.password)
47
- throw ErrPasswordRequired;
48
- const parts = Object.fromEntries(serverMessage.split(',').map(x => x.split('=')));
49
- const serverNonce = parts.r;
50
- const saltBase64 = parts.s;
51
- const iterations = parseInt(parts.i, 10);
52
- if (!serverNonce.startsWith(nonce)) {
53
- connector.destroy();
54
- return reject(ErrNonceMismatch);
55
- }
56
- const clientFinalMessageWithoutProof = `c=biws,r=${serverNonce}`;
57
- const authMessage = `${clientMessage},${serverMessage},${clientFinalMessageWithoutProof}`;
58
- const { clientProof } = calculateScramAuth(params.password, saltBase64, iterations, authMessage);
59
- const clientFinalMessage = `${clientFinalMessageWithoutProof},p=${clientProof}`;
60
- connector.write(writer.writeSaslResponse(clientFinalMessage));
61
- break;
62
- }
63
- case AuthenticationCodes.SASLFinal: {
64
- reader.readSaslMessage();
65
- break;
12
+ const { future, reject, resolve } = Future.withResolvers();
13
+ const nonce = generateNonce();
14
+ let clientMessage = '';
15
+ let serverMessage = '';
16
+ const socket = createConnection({ host: params.host, port: params.port });
17
+ const connector = new SocketConnector(socket, (type, length, reader) => {
18
+ writer.clear();
19
+ switch (type) {
20
+ case ResponseTypes.Authentication: {
21
+ switch (reader.readAuthentication()) {
22
+ case AuthenticationCodes.Ok: break;
23
+ case AuthenticationCodes.CleartextPassword: {
24
+ if (!params.password)
25
+ throw ErrPasswordRequired;
26
+ connector.write(writer.writePassword(params.password));
27
+ break;
28
+ }
29
+ case AuthenticationCodes.MD5Password: {
30
+ const salt = reader.readMD5Salt();
31
+ if (!params.password)
32
+ throw ErrPasswordRequired;
33
+ const password = encryptMd5(params.password, params.user, salt);
34
+ connector.write(writer.writePassword(password));
35
+ break;
36
+ }
37
+ case AuthenticationCodes.SASL: {
38
+ reader.readSaslMechanisms();
39
+ if (!params.password)
40
+ throw ErrPasswordRequired;
41
+ clientMessage = `n=${params.user},r=${nonce}`;
42
+ connector.write(writer.writeSaslInitial('SCRAM-SHA-256', `n,,${clientMessage}`));
43
+ break;
44
+ }
45
+ case AuthenticationCodes.SASLContinue: {
46
+ serverMessage = reader.readSaslMessage(length);
47
+ if (!params.password)
48
+ throw ErrPasswordRequired;
49
+ const parts = Object.fromEntries(serverMessage.split(',').map(x => x.split('=')));
50
+ const serverNonce = parts.r;
51
+ const saltBase64 = parts.s;
52
+ const iterations = parseInt(parts.i, 10);
53
+ if (!serverNonce.startsWith(nonce)) {
54
+ connector.destroy();
55
+ return reject(ErrNonceMismatch);
66
56
  }
57
+ const clientFinalMessageWithoutProof = `c=biws,r=${serverNonce}`;
58
+ const authMessage = `${clientMessage},${serverMessage},${clientFinalMessageWithoutProof}`;
59
+ const { clientProof } = calculateScramAuth(params.password, saltBase64, iterations, authMessage);
60
+ const clientFinalMessage = `${clientFinalMessageWithoutProof},p=${clientProof}`;
61
+ connector.write(writer.writeSaslResponse(clientFinalMessage));
62
+ break;
63
+ }
64
+ case AuthenticationCodes.SASLFinal: {
65
+ reader.readSaslMessage(length);
66
+ break;
67
67
  }
68
- break;
69
- }
70
- case ResponseTypes.ParamaterStatus: {
71
- reader.readParameterStatus();
72
- break;
73
- }
74
- case ResponseTypes.ErrorResponse: {
75
- const error = reader.readErrorResponse();
76
- connector.destroy();
77
- reject(error);
78
- return;
79
- }
80
- case ResponseTypes.BackendKeyData: {
81
- reader.readBackendKeyData();
82
- }
83
- case ResponseTypes.ReadyForQuery: {
84
- reader.readReadyForQuery();
85
- resolve(connector.unwrapSocket());
86
- return;
87
68
  }
69
+ break;
88
70
  }
89
- }, error => {
90
- reject(error);
91
- });
92
- connector.write(writer.writeStartup(params.user, params.database));
93
- writer.clear();
94
- }), error => {
95
- if (error instanceof PostgresError)
96
- return error;
97
- return new PostgresError(error.message);
71
+ case ResponseTypes.ParamaterStatus: {
72
+ reader.readParameterStatus();
73
+ break;
74
+ }
75
+ case ResponseTypes.ErrorResponse: {
76
+ const error = reader.readErrorResponse();
77
+ connector.destroy();
78
+ reject(error);
79
+ return;
80
+ }
81
+ case ResponseTypes.BackendKeyData: {
82
+ reader.readBackendKeyData();
83
+ }
84
+ case ResponseTypes.ReadyForQuery: {
85
+ reader.readReadyForQuery();
86
+ resolve(connector.unwrapSocket());
87
+ return;
88
+ }
89
+ }
90
+ }, error => {
91
+ reject(ErrSocketFailedDuringAuth);
98
92
  });
93
+ connector.write(writer.writeStartup(params.user, params.database));
94
+ writer.clear();
95
+ return future;
99
96
  };
@@ -8,7 +8,7 @@ export declare class SocketConnector {
8
8
  private _onError;
9
9
  private residualBuffer;
10
10
  private _isDestroyed;
11
- constructor(_socket: Socket, _onData: (type: ResponseType, reader: ConnectionResponseReader) => void, _onError: (error: Error) => void);
11
+ constructor(_socket: Socket, _onData: (type: ResponseType, length: number, reader: ConnectionResponseReader) => void, _onError: (error: Error) => void);
12
12
  write(writer: ConnectionRequestWriter): void;
13
13
  unwrapSocket(): Socket;
14
14
  destroy(): void;
@@ -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;IA6B5C,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,MAAM,EAAE,MAAM,EAAE,wBAAwB,KAAK,IAAI,EACvF,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI;IA8B5C,KAAK,CAAC,MAAM,EAAE,uBAAuB;IAOrC,YAAY;IAQZ,OAAO;IAMP,IAAI,WAAW,YAA6B;CAC/C"}
@@ -18,7 +18,8 @@ export class SocketConnector {
18
18
  this.residualBuffer = reader.getResidualBuffer();
19
19
  return;
20
20
  }
21
- this._onData(reader.readType(), reader);
21
+ const { type, length } = reader.readType();
22
+ this._onData(type, length, reader);
22
23
  }
23
24
  });
24
25
  _socket.on('error', error => {