@m2k-5f/pgtx 2.4.1 → 2.5.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 +141 -36
- package/dist/connection.d.ts +30 -18
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +89 -8
- package/dist/pool.d.ts +72 -2
- package/dist/pool.d.ts.map +1 -1
- package/dist/pool.js +118 -7
- package/dist/protocol/connection-response-reader.d.ts +5 -2
- package/dist/protocol/connection-response-reader.d.ts.map +1 -1
- package/dist/protocol/connection-response-reader.js +6 -23
- package/dist/protocol/socket-authorization.d.ts.map +1 -1
- package/dist/protocol/socket-authorization.js +3 -3
- package/dist/protocol/socket-connector.d.ts +1 -1
- package/dist/protocol/socket-connector.d.ts.map +1 -1
- package/dist/protocol/socket-connector.js +2 -1
- package/dist/query.d.ts +21 -2
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +42 -4
- package/package.json +55 -55
package/README.md
CHANGED
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
|
|
56
56
|
- **Pipeline queries** — Automatic query multiplexing over PostgreSQL pipeline protocol
|
|
57
57
|
- **Tagged templates** — Natural SQL with type safety
|
|
58
|
+
- **Native Web Streams API** — Memory-efficient data streaming via `pool.stream()`
|
|
58
59
|
- **Transactions & Savepoints** — Nested transactions with rollback
|
|
59
60
|
- **Bulk inserts** — Auto-extract columns from objects
|
|
60
61
|
- **Dynamic updates** — Generate SET clauses from objects
|
|
@@ -63,42 +64,64 @@
|
|
|
63
64
|
- **Connection pool** — Auto-management connections with support for pipeline queries via the pool itself.
|
|
64
65
|
- **Zero dependencies** — Lightweight and blazing
|
|
65
66
|
|
|
66
|
-
|
|
67
|
+
---
|
|
67
68
|
|
|
68
|
-
|
|
69
|
+
## ⚡ Performance
|
|
69
70
|
|
|
70
|
-
|
|
71
|
-
*Measured using `mitata` inside GitHub Actions cloud runners (Ubuntu, 2 vCPUs, 10 DB Connections).*
|
|
71
|
+
All benchmarks are executed on **GitHub Actions** (Ubuntu, 2 vCPUs) and are fully reproducible. Benchmark sources are included in this repository.
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
| :--- | :---: | :---: | :---: |
|
|
75
|
-
| **Pgtx (Pipeline)** | **19.21 ms** | **Baseline (3.6×)** | **874.71 KB** |
|
|
76
|
-
| Postgres.js (Pipeline) | 70.50 ms | 3.6× Slower | 1.29 MB |
|
|
77
|
-
| node-postgres (no pipeline) | 327.07 ms | 17.0× Slower | 4.05 MB |
|
|
73
|
+
### 1. PostgreSQL Pipeline Stress Test
|
|
78
74
|
|
|
79
|
-
|
|
75
|
+
**3000 concurrent parameterized `SELECT` queries**
|
|
80
76
|
|
|
81
|
-
|
|
82
|
-
|
|
77
|
+
* Connection pool: **10 connections**
|
|
78
|
+
* Measured with **mitata**
|
|
83
79
|
|
|
84
|
-
|
|
85
|
-
|
|
80
|
+
| Driver | Avg Time | Relative Speed | Memory (p75) |
|
|
81
|
+
| :--------------------- | -----------: | -------------------: | -----------: |
|
|
82
|
+
| **Pgtx (Pipeline)** | **24.18 ms** | **Baseline (1.00×)** | **≈2.5 MB** |
|
|
83
|
+
| Postgres.js (Pipeline) | 83.36 ms | **3.45× slower** | ≈7.6 MB |
|
|
84
|
+
| node-postgres (`pg`) | 377.95 ms | **15.63× slower** | ≈11.4 MB |
|
|
86
85
|
|
|
87
|
-
|
|
86
|
+
### 2. Real-World HTTP Throughput
|
|
88
87
|
|
|
89
|
-
|
|
88
|
+
Simple `node:http` server serving a PostgreSQL-backed endpoint.
|
|
90
89
|
|
|
91
|
-
|
|
92
|
-
* Synchronous protocol encoding
|
|
93
|
-
* Batched socket writes
|
|
94
|
-
* Automatic prepared statement caching
|
|
95
|
-
* Row description caching
|
|
96
|
-
* Zero-dependency implementation
|
|
97
|
-
* Binary protocol support
|
|
90
|
+
Measured with:
|
|
98
91
|
|
|
99
|
-
|
|
92
|
+
```bash
|
|
93
|
+
wrk -t2 -c<N> -d10s http://localhost:3000/users
|
|
94
|
+
```
|
|
100
95
|
|
|
101
|
-
|
|
96
|
+
| Concurrent Connections | Pgtx | Postgres.js | Speedup |
|
|
97
|
+
| ---------------------: | ---------------: | -----------: | --------: |
|
|
98
|
+
| 50 | 5,272 req/s | 5,691 req/s | 0.93× |
|
|
99
|
+
| 200 | **12,918 req/s** | 6,724 req/s | **1.92×** |
|
|
100
|
+
| 1000 | **21,429 req/s** | 8,423 req/s | **2.54×** |
|
|
101
|
+
| 10000 | **22,486 req/s** | 12,764 req/s | **1.76×** |
|
|
102
|
+
|
|
103
|
+
### Why is Pgtx fast?
|
|
104
|
+
|
|
105
|
+
Pgtx is engineered for **throughput**, not for minimizing the latency of individual queries.
|
|
106
|
+
|
|
107
|
+
Instead of optimizing a single request in isolation, Pgtx minimizes per-query overhead under sustained concurrent load by combining:
|
|
108
|
+
|
|
109
|
+
* Pipeline query multiplexing
|
|
110
|
+
* Synchronous PostgreSQL wire protocol encoding
|
|
111
|
+
* Batched socket writes
|
|
112
|
+
* Automatic prepared statement caching
|
|
113
|
+
* Prepared statement deduplication
|
|
114
|
+
* Row description caching
|
|
115
|
+
* Binary protocol support
|
|
116
|
+
* Zero-dependency implementation
|
|
117
|
+
|
|
118
|
+
As concurrency increases, these optimizations significantly reduce protocol overhead, allowing Pgtx to scale more efficiently than traditional PostgreSQL drivers.
|
|
119
|
+
|
|
120
|
+
In the `mitata` benchmark, Pgtx also demonstrated approximately **3× lower memory usage** than Postgres.js while processing the same workload, reducing allocation pressure and improving sustained throughput under heavy load.
|
|
121
|
+
|
|
122
|
+
> **Blazing** isn't just a tagline — it's backed by reproducible benchmarks.
|
|
123
|
+
|
|
124
|
+
---
|
|
102
125
|
|
|
103
126
|
## 🔥 Why Pgtx?
|
|
104
127
|
|
|
@@ -156,6 +179,60 @@
|
|
|
156
179
|
> 🚀 Pgtx automatically groups concurrent queries into pipeline batches, reducing network overhead by up to 5x compared to sequential queries.
|
|
157
180
|
|
|
158
181
|
|
|
182
|
+
### High-Performance Data Streaming (`pool.stream`)
|
|
183
|
+
|
|
184
|
+
For heavy database lookups (exporting millions of rows, bulk reports, or large analytical dumps), memory accumulation is the ultimate killer of backend stability. Storing rows in a standard JavaScript array causes massive heap pollution and triggers blocking Garbage Collection spikes.
|
|
185
|
+
|
|
186
|
+
Pgtx solves this at the protocol level by introducing `pool.stream()`, which bypasses row aggregation entirely and pipes rows transitively directly into a native Web **`ReadableStream`**.
|
|
187
|
+
|
|
188
|
+
#### 1. Ultra-Low Memory Row Iteration
|
|
189
|
+
You can consume database rows sequentially using standard `for await...of` syntax. Rows are processed and evicted from memory the moment they arrive from the network socket buffer.
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
interface HeavyLog { id: number; data: string; timestamp: Date; }
|
|
193
|
+
|
|
194
|
+
const logStream = pool.stream<HeavyLog>`
|
|
195
|
+
SELECT id, data, timestamp FROM application_logs WHERE level = ${'error'}
|
|
196
|
+
`;
|
|
197
|
+
|
|
198
|
+
for await (const log of logStream) {
|
|
199
|
+
// Each log object is parsed on-the-fly and processed instantly.
|
|
200
|
+
// Zero rows are accumulated in the internal driver state!
|
|
201
|
+
console.log(`[${log.timestamp.toISOString()}] ${log.data}`);
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
#### 2. Streaming Directly to HTTP Responses (`Bun.serve`)
|
|
206
|
+
Since Pgtx implements the standardized Web Streams API, you can bridge your database query directly into an HTTP response body with absolutely zero intermediate buffers.
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
import { Pool } from "@m2k-5f/pgtx";
|
|
210
|
+
|
|
211
|
+
const pool = new Pool({ /* ... config ... */ });
|
|
212
|
+
|
|
213
|
+
export default {
|
|
214
|
+
port: 3000,
|
|
215
|
+
async fetch(request) {
|
|
216
|
+
const url = new URL(request.url);
|
|
217
|
+
|
|
218
|
+
if (url.pathname === "/export/users") {
|
|
219
|
+
// Synchronously returns a stream handle even if pool sockets are currently busy
|
|
220
|
+
const userStream = pool.stream`SELECT id, email, profile_metadata FROM giant_user_table`;
|
|
221
|
+
|
|
222
|
+
return new Response(userStream, {
|
|
223
|
+
headers: {
|
|
224
|
+
"Content-Type": "application/json",
|
|
225
|
+
"Transfer-Encoding": "chunked",
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return new Response("Not Found", { status: 404 });
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
|
|
159
236
|
### Transactions & Savepoints
|
|
160
237
|
|
|
161
238
|
```typescript
|
|
@@ -173,13 +250,36 @@
|
|
|
173
250
|
|
|
174
251
|
### Async Notifications (LISTEN / NOTIFY)
|
|
175
252
|
|
|
176
|
-
Pgtx natively handles PostgreSQL `LISTEN/NOTIFY` protocol messages asynchronously without interrupting multiplexed query pipeline.
|
|
253
|
+
Pgtx natively handles PostgreSQL `LISTEN/NOTIFY` protocol messages asynchronously without interrupting the multiplexed query pipeline. It offers two distinct ways to subscribe: high-level pool-driven subscriptions and low-level connection pinning.
|
|
177
254
|
|
|
255
|
+
#### 1. Sending a Notification
|
|
256
|
+
Notifications are atomic and can be triggered directly from the `Pool` utilizing any available socket:
|
|
178
257
|
```typescript
|
|
179
|
-
// 1. Sending a notification
|
|
180
258
|
await pool.notify('user_events', JSON.stringify({ id: 42, action: 'signup' }))
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
#### 2. High-Level Pool Subscription (Recommended)
|
|
262
|
+
You can subscribe directly via the `Pool` instance. Pgtx will automatically borrow a dedicated connection from the pool, issue the `LISTEN` command, and seamlessly manage its lifecycle.
|
|
263
|
+
|
|
264
|
+
The method returns a lazy, async **unsubscribe function** that cleanly handles `UNLISTEN` and returns the connection to the pool when invoked.
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
const onEvent = (payload: string) => {
|
|
268
|
+
console.log(`Received payload: ${payload}`)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Automatically borrows a connection and sets up the listener
|
|
272
|
+
const unsubscribe = await pool.listen('user_events', onEvent)
|
|
273
|
+
|
|
274
|
+
// When the subscription is no longer needed (e.g., server shutdown):
|
|
275
|
+
// It automatically sends UNLISTEN and releases the connection back to the pool!
|
|
276
|
+
await unsubscribe()
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
#### 3. Low-Level Connection Subscription (Stateful)
|
|
280
|
+
If you need complete control over a specific PostgreSQL backend process, you can acquire an explicit `Connection` instance. This allows you to multiplex multiple callbacks onto a single channel seamlessly.
|
|
181
281
|
|
|
182
|
-
|
|
282
|
+
```typescript
|
|
183
283
|
const conn = await pool.acquire()
|
|
184
284
|
|
|
185
285
|
const onEvent = (payload: string) => {
|
|
@@ -190,14 +290,15 @@
|
|
|
190
290
|
await conn.listen('user_events', onEvent)
|
|
191
291
|
await conn.listen('user_events', (data) => logToFile(data))
|
|
192
292
|
|
|
193
|
-
//
|
|
293
|
+
// Cleans up callbacks (Sends UNLISTEN only when the channel has zero callbacks left)
|
|
194
294
|
await conn.unlisten('user_events', onEvent)
|
|
195
295
|
|
|
196
|
-
//
|
|
197
|
-
// Do NOT release it back to the pool
|
|
296
|
+
// ⚠️ Manual lifecycle management is strictly required for this pattern!
|
|
297
|
+
// Do NOT release it back to the pool until you are completely done listening.
|
|
298
|
+
this.release(conn)
|
|
198
299
|
```
|
|
199
300
|
|
|
200
|
-
> ⚠️ **Architecture Note:** While `notify` is
|
|
301
|
+
> ⚠️ **Architecture Note:** While `pool.notify` is a fire-and-forget atomic command, subscription states (`LISTEN`/`UNLISTEN`) are strictly tied to specific PostgreSQL backend processes. Using the high-level `pool.listen()` is strongly recommended for application code, as it encapsulates socket management into an elegant, leak-proof callback boundary.
|
|
201
302
|
|
|
202
303
|
|
|
203
304
|
### Bulk Inserts
|
|
@@ -342,7 +443,7 @@
|
|
|
342
443
|
notify(channelName: string, payload?: string): Future<[], PostgresError>
|
|
343
444
|
listen(channelName: string, callback: (payload: string) => void): Future<[], PostgresError>
|
|
344
445
|
unlisten(channelName: string, callback: (payload: string) => void): Future<[], PostgresError>
|
|
345
|
-
|
|
446
|
+
stream<T extends Record<string, any>>(templates: TemplateStringsArray, ...params: any[]): ReadableStream<T>
|
|
346
447
|
get isAlive(): boolean
|
|
347
448
|
close(): void
|
|
348
449
|
}
|
|
@@ -353,7 +454,8 @@
|
|
|
353
454
|
host: string
|
|
354
455
|
port: number
|
|
355
456
|
database: string
|
|
356
|
-
|
|
457
|
+
queryTimeout?: number // default: 30 srconds
|
|
458
|
+
logLevel?: 'none' | 'error' | 'notice' | 'query' // default: "error"
|
|
357
459
|
}
|
|
358
460
|
```
|
|
359
461
|
|
|
@@ -363,10 +465,13 @@
|
|
|
363
465
|
class Pool {
|
|
364
466
|
constructor(config: PoolConfig)
|
|
365
467
|
|
|
366
|
-
query<T>(strings: TemplateStringsArray, ...values: any[]): Future<T[],
|
|
468
|
+
query<T>(strings: TemplateStringsArray, ...values: any[]): Future<T[], PostgresError>
|
|
367
469
|
begin<T>(callback: (tx: Transaction) => Promise<T>): Future<T, Error>
|
|
368
470
|
notify(channelName: string, payload?: string): Future<[], PostgresError>
|
|
369
|
-
|
|
471
|
+
listen(channel: string, callback: (payload: string) => void): Future<() => Promise<void>, PostgresError>
|
|
472
|
+
stream<T extends Record<string, any>>(templates: TemplateStringsArray, ...args: any[]): ReadableStream<T>
|
|
473
|
+
withAcquire<T>(fn: (conn: Connection) => Promise<T>): Future<T, Error>
|
|
474
|
+
acquire(): Future<Connection, PostgresError>
|
|
370
475
|
release(conn: Connection): void
|
|
371
476
|
close(): void
|
|
372
477
|
|
package/dist/connection.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Branded
|
|
1
|
+
import { Branded } from "./types";
|
|
2
2
|
import { Transaction } from "./transaction";
|
|
3
3
|
import { Future } from 'fluent-future';
|
|
4
4
|
import { PostgresError } from "./error";
|
|
@@ -11,23 +11,7 @@ export type ConnectionParams = {
|
|
|
11
11
|
database: string;
|
|
12
12
|
logLevel?: LogLevel;
|
|
13
13
|
int8toBigint?: boolean;
|
|
14
|
-
|
|
15
|
-
export type ExecuteQueueUnit = {
|
|
16
|
-
rows: (string | null)[][];
|
|
17
|
-
resolve: (value: any) => void;
|
|
18
|
-
reject: (err: Error) => void;
|
|
19
|
-
statementName: StatementName;
|
|
20
|
-
};
|
|
21
|
-
export type ParsingQueueUnit = {
|
|
22
|
-
resolve: (statementName: StatementName) => void;
|
|
23
|
-
reject: (error: Error) => void;
|
|
24
|
-
text: QueryText;
|
|
25
|
-
statementName: StatementName;
|
|
26
|
-
};
|
|
27
|
-
export type DescribeQueueUnit = {
|
|
28
|
-
resolve: (value: ColumnDescription[]) => void;
|
|
29
|
-
reject: (error: Error) => void;
|
|
30
|
-
statementName: StatementName;
|
|
14
|
+
queryTimeout?: number;
|
|
31
15
|
};
|
|
32
16
|
export type StatementName = Branded<string, 'StatementName'>;
|
|
33
17
|
export type QueryText = Branded<string, 'QueryText'>;
|
|
@@ -182,7 +166,35 @@ export declare class Connection {
|
|
|
182
166
|
* ```
|
|
183
167
|
*/
|
|
184
168
|
unlisten(channelName: string, callback: (payload: string) => void): Future<[], PostgresError>;
|
|
169
|
+
/**
|
|
170
|
+
* Executes an SQL query in streaming mode.
|
|
171
|
+
*
|
|
172
|
+
* Data is streamed directly from the PostgreSQL binary network buffer into the Web Streams API
|
|
173
|
+
* (`ReadableStream`), bypassing any intermediate array allocation or row accumulation in the JS heap.
|
|
174
|
+
* This pattern provides a true Zero-Memory Footprint and is ideal for exporting massive tables
|
|
175
|
+
* or piping database payloads directly into HTTP responses (e.g., via `Bun.serve` or fetch `Response`).
|
|
176
|
+
*
|
|
177
|
+
* @template T The expected shape of a single row interface.
|
|
178
|
+
* @param {TemplateStringsArray} templates The SQL string parts from the tagged template literal.
|
|
179
|
+
* @param {...any} args The parameterized query arguments.
|
|
180
|
+
* @returns {ReadableStream<T>} Synchronously returns a native Web ReadableStream instance.
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* // Streaming a giant table directly to an HTTP response (Bun.serve)
|
|
184
|
+
* const userStream = conn.stream<User>`SELECT id, name FROM users`;
|
|
185
|
+
* return new Response(userStream, { headers: { 'Content-Type': 'application/json' } });
|
|
186
|
+
*
|
|
187
|
+
* @example
|
|
188
|
+
* // Asynchronously iterating over rows as they arrive from the wire socket
|
|
189
|
+
* const stream = conn.stream<User>`SELECT * FROM orders WHERE status = ${'processed'}`;
|
|
190
|
+
* for await (const row of stream) {
|
|
191
|
+
* console.log(row.id, row.amount); // Row object is eligible for GC immediately after iteration
|
|
192
|
+
* }
|
|
193
|
+
*/
|
|
194
|
+
stream<T extends Record<string, any>>(templates: TemplateStringsArray, ...params: any[]): ReadableStream<T>;
|
|
195
|
+
private _streamWithController;
|
|
185
196
|
private _createQuery;
|
|
197
|
+
private _createStream;
|
|
186
198
|
private _writeQuery;
|
|
187
199
|
private _registerFlush;
|
|
188
200
|
private _flush;
|
package/dist/connection.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../src/connection.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../src/connection.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,OAAO,EAAqB,MAAM,SAAS,CAAA;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAK3C,OAAO,EAAQ,MAAM,EAAU,MAAM,eAAe,CAAA;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAGvC,KAAK,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAA;AAErD,MAAM,MAAM,gBAAgB,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAA;CACxB,CAAA;AAQD,MAAM,MAAM,aAAa,GAAG,OAAO,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;AAE5D,MAAM,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;AAEpD,MAAM,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,EAAE,aAAa,CAAC,CAAA;AAGxD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,qBAAa,UAAU;IACnB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,WAAW,CAAQ;IAC3B,OAAO,CAAC,SAAS,CAAO;IACxB,OAAO,CAAC,eAAe,CAAQ;IAC/B,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,OAAO,CAAyB;IAExC,OAAO,CAAC,eAAe,CAAwB;IAE/C,OAAO,CAAC,UAAU,CAAgD;IAClE,OAAO,CAAC,kBAAkB,CAA2B;IACrD,OAAO,CAAC,OAAO,CAAsC;IACrD,OAAO,CAAC,eAAe,CAAsC;IAE7D,OAAO,CAAC,mBAAmB,CAAyD;IACpF,OAAO,CAAC,YAAY,CAAI;IACxB,OAAO,CAAC,SAAS,CAAU;IAG3B,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,YAAY;IAKpB,OAAO;IAgBP;;;;;;;;;;;;;;;;;OAiBG;IACH,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,gBAAgB;IAOnC;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,KAAK,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,SAAS,EAAE,oBAAoB,EAAE,GAAG,MAAM,EAAE,GAAG,EAAE;IAkBtF;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,KAAK,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,WAAW,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC;IAmB7D;;;;;;;;;;OAUG;IACH,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,MAAW;IAMhD;;;;;;;;;;OAUG;IACH,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI;IAc/D;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,CAAC,EAAE,EAAE,aAAa,CAAC;IAmB7F;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,MAAM,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,SAAS,EAAE,oBAAoB,EAAE,GAAG,MAAM,EAAE,GAAG,EAAE;IA2BvF,OAAO,CAAC,qBAAqB;IAmB7B,OAAO,CAAC,YAAY;IAuDpB,OAAO,CAAC,aAAa;IA4DrB,OAAO,CAAC,WAAW;IAwBnB,OAAO,CAAC,cAAc;IAWtB,OAAO,CAAC,MAAM;IAsBd,OAAO,CAAC,kBAAkB;IAM1B,OAAO,CAAC,qBAAqB;YAWf,UAAU;IAmBxB,OAAO,CAAC,qBAAqB;IAW7B,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,eAAe;IAWvB,OAAO,CAAC,aAAa;IA2IrB;;;OAGG;IACH,IAAI,QAAQ,YAEX;IAGD;;;;;;;;;OASG;IACH,KAAK;CAQR"}
|
package/dist/connection.js
CHANGED
|
@@ -5,7 +5,7 @@ import { compileSqlTemplate } from "./utils/template-compiler";
|
|
|
5
5
|
import { Transaction } from "./transaction";
|
|
6
6
|
import { SocketConnector } from "./protocol/socket-connector";
|
|
7
7
|
import { Queue } from "./queue";
|
|
8
|
-
import { Query, QueryState } from "./query";
|
|
8
|
+
import { Query, QueryState, StreamQuery } from "./query";
|
|
9
9
|
import { sql } from ".";
|
|
10
10
|
import { Begin, Future, Resolve } from 'fluent-future';
|
|
11
11
|
import { PostgresError } from "./error";
|
|
@@ -62,7 +62,7 @@ export class Connection {
|
|
|
62
62
|
this._stmtCounter = 0;
|
|
63
63
|
this.params = params;
|
|
64
64
|
this._logLevel = logLevel;
|
|
65
|
-
this._socket = new SocketConnector(socket, (type, reader) => this._handlePacket(type, reader), (err) => this._registerReconnect());
|
|
65
|
+
this._socket = new SocketConnector(socket, (type, _, reader) => this._handlePacket(type, reader), (err) => this._registerReconnect());
|
|
66
66
|
this._writer = writer;
|
|
67
67
|
}
|
|
68
68
|
/**
|
|
@@ -121,11 +121,8 @@ export class Connection {
|
|
|
121
121
|
}
|
|
122
122
|
const query = this._createQuery(text, args);
|
|
123
123
|
this._writeQuery(query);
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
return error;
|
|
127
|
-
return new PostgresError(error.message);
|
|
128
|
-
});
|
|
124
|
+
query.startTimeout(this.params.queryTimeout || 30000);
|
|
125
|
+
return query.future;
|
|
129
126
|
}
|
|
130
127
|
/**
|
|
131
128
|
* Starts a managed transaction on this connection.
|
|
@@ -222,6 +219,61 @@ export class Connection {
|
|
|
222
219
|
}
|
|
223
220
|
return Resolve([]);
|
|
224
221
|
}
|
|
222
|
+
/**
|
|
223
|
+
* Executes an SQL query in streaming mode.
|
|
224
|
+
*
|
|
225
|
+
* Data is streamed directly from the PostgreSQL binary network buffer into the Web Streams API
|
|
226
|
+
* (`ReadableStream`), bypassing any intermediate array allocation or row accumulation in the JS heap.
|
|
227
|
+
* This pattern provides a true Zero-Memory Footprint and is ideal for exporting massive tables
|
|
228
|
+
* or piping database payloads directly into HTTP responses (e.g., via `Bun.serve` or fetch `Response`).
|
|
229
|
+
*
|
|
230
|
+
* @template T The expected shape of a single row interface.
|
|
231
|
+
* @param {TemplateStringsArray} templates The SQL string parts from the tagged template literal.
|
|
232
|
+
* @param {...any} args The parameterized query arguments.
|
|
233
|
+
* @returns {ReadableStream<T>} Synchronously returns a native Web ReadableStream instance.
|
|
234
|
+
*
|
|
235
|
+
* @example
|
|
236
|
+
* // Streaming a giant table directly to an HTTP response (Bun.serve)
|
|
237
|
+
* const userStream = conn.stream<User>`SELECT id, name FROM users`;
|
|
238
|
+
* return new Response(userStream, { headers: { 'Content-Type': 'application/json' } });
|
|
239
|
+
*
|
|
240
|
+
* @example
|
|
241
|
+
* // Asynchronously iterating over rows as they arrive from the wire socket
|
|
242
|
+
* const stream = conn.stream<User>`SELECT * FROM orders WHERE status = ${'processed'}`;
|
|
243
|
+
* for await (const row of stream) {
|
|
244
|
+
* console.log(row.id, row.amount); // Row object is eligible for GC immediately after iteration
|
|
245
|
+
* }
|
|
246
|
+
*/
|
|
247
|
+
stream(templates, ...params) {
|
|
248
|
+
this._checkOpened();
|
|
249
|
+
this._registerFlush();
|
|
250
|
+
const { text, args } = compileSqlTemplate({ templates, args: params });
|
|
251
|
+
if (this._logLevel === 'query') {
|
|
252
|
+
console.log(`\nQUERY: ${text}\n${args.length !== 0 ? `ARGUMENTS: [${args}]\n` : ""}`);
|
|
253
|
+
}
|
|
254
|
+
let controller;
|
|
255
|
+
const stream = new ReadableStream({
|
|
256
|
+
start: c => {
|
|
257
|
+
controller = c;
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
const query = this._createStream(text, args, controller);
|
|
261
|
+
this._writeQuery(query);
|
|
262
|
+
query.startTimeout(this.params.queryTimeout || 30000);
|
|
263
|
+
return stream;
|
|
264
|
+
}
|
|
265
|
+
_streamWithController(templates, params, controller) {
|
|
266
|
+
this._checkOpened();
|
|
267
|
+
this._registerFlush();
|
|
268
|
+
const { text, args } = compileSqlTemplate({ templates, args: params });
|
|
269
|
+
if (this._logLevel === 'query') {
|
|
270
|
+
console.log(`\nQUERY: ${text}\n${args.length !== 0 ? `ARGUMENTS: [${args}]\n` : ""}`);
|
|
271
|
+
}
|
|
272
|
+
const query = this._createStream(text, args, controller);
|
|
273
|
+
this._writeQuery(query);
|
|
274
|
+
query.startTimeout(this.params.queryTimeout || 30000);
|
|
275
|
+
return query;
|
|
276
|
+
}
|
|
225
277
|
_createQuery(text, args) {
|
|
226
278
|
if (this._parsed.has(text)) {
|
|
227
279
|
const statementName = this._parsed.get(text);
|
|
@@ -251,6 +303,35 @@ export class Connection {
|
|
|
251
303
|
}
|
|
252
304
|
}
|
|
253
305
|
}
|
|
306
|
+
_createStream(text, args, controller) {
|
|
307
|
+
if (this._parsed.has(text)) {
|
|
308
|
+
const statementName = this._parsed.get(text);
|
|
309
|
+
if (this._described.has(statementName)) {
|
|
310
|
+
const columns = this._described.get(statementName);
|
|
311
|
+
return new StreamQuery(text, args, QueryState.Executing, statementName, controller, columns);
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
if (this._describingPending.has(statementName)) {
|
|
315
|
+
return new StreamQuery(text, args, QueryState.Executing, statementName, controller);
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
this._describingPending.add(statementName);
|
|
319
|
+
return new StreamQuery(text, args, QueryState.Describing, statementName, controller);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
if (this._parsingPending.has(text)) {
|
|
325
|
+
const statementName = this._parsingPending.get(text);
|
|
326
|
+
return new StreamQuery(text, args, QueryState.Describing, statementName, controller);
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
const statementName = this._nextStatement();
|
|
330
|
+
this._parsingPending.set(text, statementName);
|
|
331
|
+
return new StreamQuery(text, args, QueryState.Parsing, statementName, controller);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
254
335
|
_writeQuery(query) {
|
|
255
336
|
if (query.state === QueryState.Parsing) {
|
|
256
337
|
this._writer
|
|
@@ -314,7 +395,7 @@ export class Connection {
|
|
|
314
395
|
async _reconnect() {
|
|
315
396
|
this._resetConnectionState(ErrConnectionReconnecring);
|
|
316
397
|
const socket = await createAuthorizedSocket(ConnectionRequestWriter.new(), this.params);
|
|
317
|
-
const connector = new SocketConnector(socket, (type, reader) => this._handlePacket(type, reader), (err) => this._registerReconnect());
|
|
398
|
+
const connector = new SocketConnector(socket, (type, _, reader) => this._handlePacket(type, reader), (err) => this._registerReconnect());
|
|
318
399
|
this._socket = connector;
|
|
319
400
|
void this._restoreSubscriptions();
|
|
320
401
|
this._isReconnecting = false;
|
package/dist/pool.d.ts
CHANGED
|
@@ -67,7 +67,26 @@ export declare class Pool {
|
|
|
67
67
|
* }
|
|
68
68
|
* ```
|
|
69
69
|
*/
|
|
70
|
-
acquire(): Future<Connection,
|
|
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, Error>;
|
|
71
90
|
/**
|
|
72
91
|
* Releases the connection back to the pool.
|
|
73
92
|
*
|
|
@@ -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[],
|
|
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
|
*/
|
package/dist/pool.d.ts.map
CHANGED
|
@@ -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,
|
|
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,EAAW,MAAM,eAAe,CAAC;AACvD,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, Resolve } 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.
|
|
@@ -78,12 +80,36 @@ export class Pool {
|
|
|
78
80
|
}
|
|
79
81
|
if (this._total < this._max) {
|
|
80
82
|
this._total++;
|
|
81
|
-
return
|
|
83
|
+
return Connection.new(this._config)
|
|
82
84
|
.tapErr(() => this._total--);
|
|
83
85
|
}
|
|
84
|
-
|
|
85
|
-
|
|
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
|
|
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(
|
|
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():
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
|
@@ -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,
|
|
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,kCAyHlG,CAAA"}
|
|
@@ -13,7 +13,7 @@ export const createAuthorizedSocket = (writer, params) => {
|
|
|
13
13
|
let clientMessage = '';
|
|
14
14
|
let serverMessage = '';
|
|
15
15
|
const socket = createConnection({ host: params.host, port: params.port });
|
|
16
|
-
const connector = new SocketConnector(socket, (type, reader) => {
|
|
16
|
+
const connector = new SocketConnector(socket, (type, length, reader) => {
|
|
17
17
|
writer.clear();
|
|
18
18
|
switch (type) {
|
|
19
19
|
case ResponseTypes.Authentication: {
|
|
@@ -42,7 +42,7 @@ export const createAuthorizedSocket = (writer, params) => {
|
|
|
42
42
|
break;
|
|
43
43
|
}
|
|
44
44
|
case AuthenticationCodes.SASLContinue: {
|
|
45
|
-
serverMessage = reader.readSaslMessage();
|
|
45
|
+
serverMessage = reader.readSaslMessage(length);
|
|
46
46
|
if (!params.password)
|
|
47
47
|
throw ErrPasswordRequired;
|
|
48
48
|
const parts = Object.fromEntries(serverMessage.split(',').map(x => x.split('=')));
|
|
@@ -61,7 +61,7 @@ export const createAuthorizedSocket = (writer, params) => {
|
|
|
61
61
|
break;
|
|
62
62
|
}
|
|
63
63
|
case AuthenticationCodes.SASLFinal: {
|
|
64
|
-
reader.readSaslMessage();
|
|
64
|
+
reader.readSaslMessage(length);
|
|
65
65
|
break;
|
|
66
66
|
}
|
|
67
67
|
}
|
|
@@ -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,
|
|
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
|
-
|
|
21
|
+
const { type, length } = reader.readType();
|
|
22
|
+
this._onData(type, length, reader);
|
|
22
23
|
}
|
|
23
24
|
});
|
|
24
25
|
_socket.on('error', error => {
|
package/dist/query.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { Future } from "fluent-future";
|
|
1
2
|
import { QueryText, StatementName } from "./connection";
|
|
2
3
|
import { ColumnDescription, ValueOF } from "./types";
|
|
4
|
+
import { PostgresError } from "./error";
|
|
3
5
|
export declare const QueryState: {
|
|
4
6
|
readonly Parsing: 0;
|
|
5
7
|
readonly Describing: 1;
|
|
@@ -14,14 +16,31 @@ export declare class Query<T> {
|
|
|
14
16
|
state: State;
|
|
15
17
|
statementName: StatementName;
|
|
16
18
|
columns?: ColumnDescription[] | undefined;
|
|
17
|
-
|
|
19
|
+
future: Future<T[], PostgresError>;
|
|
18
20
|
private _resolve;
|
|
19
21
|
private _reject;
|
|
20
22
|
private _rows;
|
|
23
|
+
private _timer?;
|
|
21
24
|
constructor(text: QueryText, args: (string | null)[], state: State, statementName: StatementName, columns?: ColumnDescription[] | undefined);
|
|
25
|
+
startTimeout(timeout: number): void;
|
|
22
26
|
setState(state: State): void;
|
|
23
27
|
push(value: T): void;
|
|
24
|
-
reject(cause:
|
|
28
|
+
reject(cause: PostgresError): void;
|
|
29
|
+
resolve(): void;
|
|
30
|
+
}
|
|
31
|
+
export declare class StreamQuery<T> {
|
|
32
|
+
text: QueryText;
|
|
33
|
+
args: (string | null)[];
|
|
34
|
+
state: State;
|
|
35
|
+
statementName: StatementName;
|
|
36
|
+
private _controller;
|
|
37
|
+
columns?: ColumnDescription[] | undefined;
|
|
38
|
+
private _timer?;
|
|
39
|
+
constructor(text: QueryText, args: (string | null)[], state: State, statementName: StatementName, _controller: ReadableStreamDefaultController<T>, columns?: ColumnDescription[] | undefined);
|
|
40
|
+
setState(state: State): void;
|
|
41
|
+
startTimeout(timeout: number): void;
|
|
42
|
+
push(value: T): void;
|
|
43
|
+
reject(cause: PostgresError): void;
|
|
25
44
|
resolve(): void;
|
|
26
45
|
}
|
|
27
46
|
//# sourceMappingURL=query.d.ts.map
|
package/dist/query.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACvC,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAGxC,eAAO,MAAM,UAAU;;;;;;CAMb,CAAA;AAGV,MAAM,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,UAAU,CAAC,CAAA;AAG9C,qBAAa,KAAK,CAAC,CAAC;IASL,IAAI,EAAE,SAAS;IACf,IAAI,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE;IACvB,KAAK,EAAE,KAAK;IACZ,aAAa,EAAE,aAAa;IAC5B,OAAO,CAAC,EAAE,iBAAiB,EAAE;IAZxC,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,EAAE,aAAa,CAAC,CAAA;IAClC,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,OAAO,CAAiC;IAChD,OAAO,CAAC,KAAK,CAAU;IAEvB,OAAO,CAAC,MAAM,CAAC,CAAgB;gBAGpB,IAAI,EAAE,SAAS,EACf,IAAI,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,EACvB,KAAK,EAAE,KAAK,EACZ,aAAa,EAAE,aAAa,EAC5B,OAAO,CAAC,EAAE,iBAAiB,EAAE,YAAA;IAQxC,YAAY,CAAC,OAAO,EAAE,MAAM;IAO5B,QAAQ,CAAC,KAAK,EAAE,KAAK;IAKrB,IAAI,CAAC,KAAK,EAAE,CAAC;IAKb,MAAM,CAAC,KAAK,EAAE,aAAa;IAM3B,OAAO;CAIV;AAED,qBAAa,WAAW,CAAC,CAAC;IAKX,IAAI,EAAE,SAAS;IACf,IAAI,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE;IACvB,KAAK,EAAE,KAAK;IACZ,aAAa,EAAE,aAAa;IACnC,OAAO,CAAC,WAAW;IACZ,OAAO,CAAC,EAAE,iBAAiB,EAAE;IARxC,OAAO,CAAC,MAAM,CAAC,CAAgB;gBAGpB,IAAI,EAAE,SAAS,EACf,IAAI,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,EACvB,KAAK,EAAE,KAAK,EACZ,aAAa,EAAE,aAAa,EAC3B,WAAW,EAAE,+BAA+B,CAAC,CAAC,CAAC,EAChD,OAAO,CAAC,EAAE,iBAAiB,EAAE,YAAA;IAGxC,QAAQ,CAAC,KAAK,EAAE,KAAK;IAIrB,YAAY,CAAC,OAAO,EAAE,MAAM;IAM5B,IAAI,CAAC,KAAK,EAAE,CAAC;IAIb,MAAM,CAAC,KAAK,EAAE,aAAa;IAK3B,OAAO;CAIV"}
|
package/dist/query.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Future } from "fluent-future";
|
|
2
|
+
import { PostgresError } from "./error";
|
|
1
3
|
export const QueryState = {
|
|
2
4
|
Parsing: 0,
|
|
3
5
|
Describing: 1,
|
|
@@ -13,10 +15,15 @@ export class Query {
|
|
|
13
15
|
this.statementName = statementName;
|
|
14
16
|
this.columns = columns;
|
|
15
17
|
this._rows = [];
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
const { future, reject, resolve } = Future.withResolvers();
|
|
19
|
+
this.future = future;
|
|
20
|
+
this._resolve = resolve;
|
|
21
|
+
this._reject = reject;
|
|
22
|
+
}
|
|
23
|
+
startTimeout(timeout) {
|
|
24
|
+
this._timer = setTimeout(() => {
|
|
25
|
+
this.reject(new PostgresError('Query timeout', '57014'));
|
|
26
|
+
}, timeout);
|
|
20
27
|
}
|
|
21
28
|
setState(state) {
|
|
22
29
|
this.state = state;
|
|
@@ -25,9 +32,40 @@ export class Query {
|
|
|
25
32
|
this._rows.push(value);
|
|
26
33
|
}
|
|
27
34
|
reject(cause) {
|
|
35
|
+
clearTimeout(this._timer);
|
|
28
36
|
this._reject(cause);
|
|
29
37
|
}
|
|
30
38
|
resolve() {
|
|
39
|
+
clearTimeout(this._timer);
|
|
31
40
|
this._resolve(this._rows);
|
|
32
41
|
}
|
|
33
42
|
}
|
|
43
|
+
export class StreamQuery {
|
|
44
|
+
constructor(text, args, state, statementName, _controller, columns) {
|
|
45
|
+
this.text = text;
|
|
46
|
+
this.args = args;
|
|
47
|
+
this.state = state;
|
|
48
|
+
this.statementName = statementName;
|
|
49
|
+
this._controller = _controller;
|
|
50
|
+
this.columns = columns;
|
|
51
|
+
}
|
|
52
|
+
setState(state) {
|
|
53
|
+
this.state = state;
|
|
54
|
+
}
|
|
55
|
+
startTimeout(timeout) {
|
|
56
|
+
this._timer = setTimeout(() => {
|
|
57
|
+
this.reject(new PostgresError('Query timeout', '57014'));
|
|
58
|
+
}, timeout);
|
|
59
|
+
}
|
|
60
|
+
push(value) {
|
|
61
|
+
this._controller.enqueue(value);
|
|
62
|
+
}
|
|
63
|
+
reject(cause) {
|
|
64
|
+
clearTimeout(this._timer);
|
|
65
|
+
this._controller.error(cause);
|
|
66
|
+
}
|
|
67
|
+
resolve() {
|
|
68
|
+
clearTimeout(this._timer);
|
|
69
|
+
this._controller.close();
|
|
70
|
+
}
|
|
71
|
+
}
|
package/package.json
CHANGED
|
@@ -1,55 +1,55 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@m2k-5f/pgtx",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"type": "module",
|
|
5
|
-
"description": "Blazing-fast PostgreSQL driver with pipeline support.",
|
|
6
|
-
"files": [
|
|
7
|
-
"dist",
|
|
8
|
-
"README.md"
|
|
9
|
-
],
|
|
10
|
-
"scripts": {
|
|
11
|
-
"build": "tsc",
|
|
12
|
-
"prepublishOnly": "npm run build",
|
|
13
|
-
"testGitHub": "node --import tsx --test $(find tests -name '*.test.ts')",
|
|
14
|
-
"test": "node --import tsx --test ./tests/*.test.ts",
|
|
15
|
-
"benchmark": "npx tsx benchmark.ts"
|
|
16
|
-
},
|
|
17
|
-
"repository": {
|
|
18
|
-
"type": "git",
|
|
19
|
-
"url": "git://github.com/M2K-5F/pgtx.git"
|
|
20
|
-
},
|
|
21
|
-
"keywords": [
|
|
22
|
-
"sql",
|
|
23
|
-
"postgres",
|
|
24
|
-
"driver",
|
|
25
|
-
"pipeline",
|
|
26
|
-
"query-builder",
|
|
27
|
-
"typescript",
|
|
28
|
-
"transactions",
|
|
29
|
-
"prepared-statements"
|
|
30
|
-
],
|
|
31
|
-
"author": "M2K-5F",
|
|
32
|
-
"license": "MIT",
|
|
33
|
-
"homepage": "https://github.com/M2K-5F/pgtx",
|
|
34
|
-
"bugs": {
|
|
35
|
-
"url": "https://github.com/M2K-5F/pgtx/issues"
|
|
36
|
-
},
|
|
37
|
-
"exports": {
|
|
38
|
-
".": {
|
|
39
|
-
"import": "./dist/index.js",
|
|
40
|
-
"require": "./dist/index.js",
|
|
41
|
-
"types": "./dist/index.d.ts"
|
|
42
|
-
}
|
|
43
|
-
},
|
|
44
|
-
"devDependencies": {
|
|
45
|
-
"@types/node": "^26.1.
|
|
46
|
-
"mitata": "^1.0.34",
|
|
47
|
-
"pg": "^8.22.0",
|
|
48
|
-
"postgres": "^3.4.9",
|
|
49
|
-
"tsx": "^4.
|
|
50
|
-
"typescript": "^5.
|
|
51
|
-
},
|
|
52
|
-
"dependencies": {
|
|
53
|
-
"fluent-future": "^1.3.
|
|
54
|
-
}
|
|
55
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@m2k-5f/pgtx",
|
|
3
|
+
"version": "2.5.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Blazing-fast PostgreSQL driver with pipeline support.",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist",
|
|
8
|
+
"README.md"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"prepublishOnly": "npm run build",
|
|
13
|
+
"testGitHub": "node --import tsx --test $(find tests -name '*.test.ts')",
|
|
14
|
+
"test": "node --import tsx --test ./tests/*.test.ts",
|
|
15
|
+
"benchmark": "npx tsx benchmark.ts"
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git://github.com/M2K-5F/pgtx.git"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"sql",
|
|
23
|
+
"postgres",
|
|
24
|
+
"driver",
|
|
25
|
+
"pipeline",
|
|
26
|
+
"query-builder",
|
|
27
|
+
"typescript",
|
|
28
|
+
"transactions",
|
|
29
|
+
"prepared-statements"
|
|
30
|
+
],
|
|
31
|
+
"author": "M2K-5F",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"homepage": "https://github.com/M2K-5F/pgtx",
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/M2K-5F/pgtx/issues"
|
|
36
|
+
},
|
|
37
|
+
"exports": {
|
|
38
|
+
".": {
|
|
39
|
+
"import": "./dist/index.js",
|
|
40
|
+
"require": "./dist/index.js",
|
|
41
|
+
"types": "./dist/index.d.ts"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/node": "^26.1.2",
|
|
46
|
+
"mitata": "^1.0.34",
|
|
47
|
+
"pg": "^8.22.0",
|
|
48
|
+
"postgres": "^3.4.9",
|
|
49
|
+
"tsx": "^4.23.5",
|
|
50
|
+
"typescript": "^5.9.3"
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"fluent-future": "^1.3.3"
|
|
54
|
+
}
|
|
55
|
+
}
|