@m2k-5f/pgtx 2.7.3 → 2.8.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/README.md +24 -13
- package/dist/connection.d.ts +4 -1
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +147 -111
- package/dist/error.d.ts +2 -0
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +41 -0
- package/dist/protocol/connection-request-writer.d.ts +87 -4
- package/dist/protocol/connection-request-writer.d.ts.map +1 -1
- package/dist/protocol/connection-request-writer.js +310 -86
- package/dist/protocol/connection-response-reader.d.ts +72 -15
- package/dist/protocol/connection-response-reader.d.ts.map +1 -1
- package/dist/protocol/connection-response-reader.js +264 -246
- package/dist/protocol/constants.js +8 -8
- package/dist/protocol/types.d.ts +18 -0
- package/dist/protocol/types.d.ts.map +1 -0
- package/dist/protocol/types.js +354 -0
- package/dist/query.d.ts +23 -9
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +32 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -50,37 +50,48 @@ Benchmarks run on GitHub Actions (Ubuntu, 2 vCPUs), reproducible, sources in thi
|
|
|
50
50
|
|
|
51
51
|
Bun.sql is Bun's own built-in driver, written in native code and generally treated as the speed baseline in that ecosystem. Pgtx stays ahead of it at every concurrency level tested — the gap doesn't come from JS-vs-native, it comes from the protocol implementation.
|
|
52
52
|
|
|
53
|
-
**How:** everything you fire concurrently against the same connection gets folded into one pipelined write — Parse/Bind/Execute for every query in the batch goes out in a single `socket.write()`, and results get demuxed as they come back, in order, without buffering rows you haven't asked for yet. Prepared statements are cached and deduplicated automatically, row descriptions are cached alongside them
|
|
53
|
+
**How:** everything you fire concurrently against the same connection gets folded into one pipelined write — Parse/Bind/Execute for every query in the batch goes out in a single `socket.write()`, and results get demuxed as they come back, in order, without buffering rows you haven't asked for yet. Prepared statements are cached and deduplicated automatically, row descriptions are cached alongside them. None of this requires you to change how you write queries.
|
|
54
|
+
|
|
54
55
|
|
|
55
56
|
## The parts worth knowing about
|
|
56
57
|
|
|
57
58
|
|
|
58
|
-
###
|
|
59
|
+
### Error handling
|
|
60
|
+
|
|
61
|
+
Every query returns a `Future<T[], PostgresError>` instead of a bare `Promise`. `await` works as usual, while `Future` also provides typed error handling and recovery without a `try/catch` pyramid.
|
|
59
62
|
|
|
60
|
-
|
|
63
|
+
Queries in the same pipeline are isolated from each other. Each query gets its own `Sync`, so an error in one query does not affect other queries that were sent in the same batch.
|
|
61
64
|
|
|
62
65
|
```typescript
|
|
63
66
|
const [a, b] = await Promise.allSettled([
|
|
64
67
|
pool.execute`UPDATE accounts SET balance = balance + 100 WHERE id = ${1}`,
|
|
65
68
|
pool.execute`INSERT INTO accounts (id) VALUES (${1})` // duplicate key, fails
|
|
66
69
|
])
|
|
67
|
-
// a: fulfilled, the balance update is committed regardless of b's outcome
|
|
68
|
-
```
|
|
69
70
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
### Errors you can pattern-match on
|
|
71
|
+
// a: fulfilled — the balance update is committed
|
|
72
|
+
// b: rejected — the duplicate key error only affects this query
|
|
73
|
+
```
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
Errors can be matched and recovered directly on the `Future`:
|
|
76
76
|
|
|
77
77
|
```typescript
|
|
78
|
-
const users = await pool.query<User>`
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
const users = await pool.query<User>`
|
|
79
|
+
SELECT * FROM users WHERE id = ${1}
|
|
80
|
+
`
|
|
81
|
+
.recoverIf(err => err.code === '42P01', []) // undefined_table → []
|
|
82
|
+
.recoverIf(err => err.code === '23505', []) // unique_violation → []
|
|
81
83
|
.tapErr(err => logger.error(err))
|
|
82
84
|
```
|
|
83
85
|
|
|
86
|
+
This isolation is per statement, not atomicity across multiple statements. If several queries must succeed or fail together, use `begin()` and `savepoint()`.
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
### PostgreSQL Type Support
|
|
90
|
+
|
|
91
|
+
The driver provides **100% support for PostgreSQL data types using the full binary protocol**. All supported types are encoded and decoded directly in PostgreSQL's binary wire format, without falling back to text-based parsing.
|
|
92
|
+
|
|
93
|
+
For a complete list of supported PostgreSQL types, their JavaScript input/output types, and string formats, see the [PostgreSQL Data Types](./DATATYPES.md) reference.
|
|
94
|
+
|
|
84
95
|
|
|
85
96
|
### Transactions and savepoints
|
|
86
97
|
|
package/dist/connection.d.ts
CHANGED
|
@@ -26,9 +26,11 @@ export declare class Connection {
|
|
|
26
26
|
private _listeningCallbacks;
|
|
27
27
|
private _stmtCounter;
|
|
28
28
|
private _nextStatement;
|
|
29
|
+
private _logError;
|
|
30
|
+
private _logQuery;
|
|
31
|
+
private _logNotice;
|
|
29
32
|
private _registerShedule;
|
|
30
33
|
private _shedule;
|
|
31
|
-
private _parseQuery;
|
|
32
34
|
private _registerQuery;
|
|
33
35
|
private constructor();
|
|
34
36
|
/**
|
|
@@ -86,6 +88,7 @@ export declare class Connection {
|
|
|
86
88
|
get isOpened(): boolean;
|
|
87
89
|
/** Whether the connection is closed or closing. */
|
|
88
90
|
get isClosed(): boolean;
|
|
91
|
+
private _tryFinishClosing;
|
|
89
92
|
/**
|
|
90
93
|
* Closes the connection, awaiting for all pending queries. Not usable afterward.
|
|
91
94
|
*/
|
package/dist/connection.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../src/connection.ts"],"names":[],"mappings":"AAGA,OAAO,EAAiC,uBAAuB,EAAuC,GAAG,EAAiB,MAAM,SAAS,CAAA;AACzI,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAK3C,OAAO,EAAS,MAAM,EAAM,MAAM,eAAe,CAAA;AACjD,OAAO,EAAkD,aAAa,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../src/connection.ts"],"names":[],"mappings":"AAGA,OAAO,EAAiC,uBAAuB,EAAuC,GAAG,EAAiB,MAAM,SAAS,CAAA;AACzI,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAK3C,OAAO,EAAS,MAAM,EAAM,MAAM,eAAe,CAAA;AACjD,OAAO,EAAkD,aAAa,EAAE,MAAM,SAAS,CAAA;AAgBvF;;;;;;;;;GASG;AACH,qBAAa,UAAU;IACnB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IAEzC,OAAO,CAAC,OAAO,CAAqC;IACpD,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,MAAM,CAA6B;IAE3C,OAAO,CAAC,QAAQ,CAAsD;IACtE,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,aAAa,CAA2C;IAEhE,OAAO,CAAC,OAAO,CAAiB;IAEhC,OAAO,CAAC,OAAO,CAAsC;IACrD,OAAO,CAAC,QAAQ,CAA6D;IAE7E,OAAO,CAAC,mBAAmB,CAAyD;IACpF,OAAO,CAAC,YAAY,CAAI;IAExB,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,SAAS;IAWjB,OAAO,CAAC,SAAS;IAYjB,OAAO,CAAC,UAAU;IAWlB,OAAO,CAAC,gBAAgB;IAQxB,OAAO,CAAC,QAAQ;IAShB,OAAO,CAAC,cAAc;IA6BtB,OAAO;IAYP;;;OAGG;IACH,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,uBAAuB;IAkB1C;;;;;;OAMG;IACH,KAAK,CAAC,CAAC,SAAS,GAAG,EAAE,SAAS,EAAE,oBAAoB,EAAE,GAAG,MAAM,EAAE,GAAG,EAAE;IAatE,OAAO,CAAC,aAAa;IAmDrB;;;;;OAKG;IACH,OAAO,CAAC,SAAS,EAAE,oBAAoB,EAAE,GAAG,MAAM,EAAE,GAAG,EAAE;IAazD,OAAO,CAAC,eAAe;IAmDvB;;;;;;OAMG;IACH,MAAM,CAAC,CAAC,SAAS,GAAG,EAAE,SAAS,EAAE,oBAAoB,EAAE,GAAG,MAAM,EAAE,GAAG,EAAE;IA2BvE,OAAO,CAAC,cAAc;IAwDtB;;;;;;;OAOG;IACH,KAAK,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,WAAW,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC;IAoB7D,2EAA2E;IAC3E,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,MAAW;IAOhD,sFAAsF;IACtF,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI;IAe/D,4EAA4E;IAC5E,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,CAAC,IAAI,EAAE,aAAa,CAAC;IAoB/F,OAAO,CAAC,UAAU;IAelB,OAAO,CAAC,iBAAiB;IA6BzB,OAAO,CAAC,qBAAqB;IAW7B,OAAO,KAAK,aAAa,GAExB;IAGD,OAAO,CAAC,aAAa;IA4GrB,kDAAkD;IAClD,IAAI,QAAQ,YAEX;IAGD,mDAAmD;IACnD,IAAI,QAAQ,YAEX;IAGD,OAAO,CAAC,iBAAiB;IAczB;;OAEG;IACH,KAAK;CAoBR"}
|
package/dist/connection.js
CHANGED
|
@@ -3,7 +3,7 @@ import { compileSqlTemplate } from "./utils";
|
|
|
3
3
|
import { Transaction } from "./transaction";
|
|
4
4
|
import { SocketConnector } from "./protocol/socket-connector";
|
|
5
5
|
import { Queue } from "./queue";
|
|
6
|
-
import { CollectQuery, StreamQuery, ExecuteQuery } from "./query";
|
|
6
|
+
import { CollectQuery, StreamQuery, ExecuteQuery, ParseQuery } from "./query";
|
|
7
7
|
import { sql } from ".";
|
|
8
8
|
import { Begin, Future, Ok } from 'fluent-future';
|
|
9
9
|
import { ErrConnectionClosed, ErrConnectionReconnecting } from "./error";
|
|
@@ -29,6 +29,28 @@ export class Connection {
|
|
|
29
29
|
_nextStatement() {
|
|
30
30
|
return `s-${this._stmtCounter++}`;
|
|
31
31
|
}
|
|
32
|
+
_logError(error) {
|
|
33
|
+
if (this.config.logLevel === 'error' || this.config.logLevel === 'notice' || this.config.logLevel === 'query') {
|
|
34
|
+
console.log(`\n\x1b[31m┌─ ERROR ────────────────────────────────────────\x1b[0m\n`
|
|
35
|
+
+ `\x1b[31m│\x1b[0m ${error}\n`
|
|
36
|
+
+ `\x1b[31m└────────────────────────────────────────────────\x1b[0m\n`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
_logQuery(text, args) {
|
|
40
|
+
if (this.config.logLevel === 'query') {
|
|
41
|
+
console.log(`\n\x1b[36m┌─ QUERY ─────────────────────────────────────────\x1b[0m\n`
|
|
42
|
+
+ `\x1b[36m│\x1b[0m ${text}\n`
|
|
43
|
+
+ `${args.length !== 0 ? `\x1b[36m│\x1b[0m \x1b[90mArguments:\x1b[0m [${args}]\n` : ''}`
|
|
44
|
+
+ `\x1b[36m└────────────────────────────────────────────────\x1b[0m`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
_logNotice(notice) {
|
|
48
|
+
if (this.config.logLevel === 'notice' || this.config.logLevel === 'query') {
|
|
49
|
+
console.log(`\n\x1b[33m┌─ NOTICE ───────────────────────────────────────\x1b[0m\n`
|
|
50
|
+
+ `\x1b[33m│\x1b[0m ${notice}\n`
|
|
51
|
+
+ `\x1b[33m└────────────────────────────────────────────────\x1b[0m\n`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
32
54
|
_registerShedule() {
|
|
33
55
|
if (!this._sheduled) {
|
|
34
56
|
this._sheduled = true;
|
|
@@ -39,23 +61,29 @@ export class Connection {
|
|
|
39
61
|
_shedule() {
|
|
40
62
|
if (this._reconnecting)
|
|
41
63
|
return;
|
|
42
|
-
this._socket.write(this._writer);
|
|
64
|
+
this._writer.hasMore && this._socket.write(this._writer);
|
|
43
65
|
this._writer.clear();
|
|
44
66
|
this._sheduled = false;
|
|
45
67
|
}
|
|
46
|
-
_parseQuery(query) {
|
|
47
|
-
this._registerShedule();
|
|
48
|
-
this._writer
|
|
49
|
-
.writeParse(query.statement, query.text)
|
|
50
|
-
.writeDescribe(DescribeType.Statement, query.statement);
|
|
51
|
-
}
|
|
52
68
|
_registerQuery(query) {
|
|
53
69
|
this._registerShedule();
|
|
54
|
-
|
|
70
|
+
if (query instanceof ParseQuery) {
|
|
71
|
+
this._writer
|
|
72
|
+
.writeParse(query.meta.statement, query.text)
|
|
73
|
+
.writeDescribe(DescribeType.Statement, query.meta.statement)
|
|
74
|
+
.writeSync();
|
|
75
|
+
this._queue.push(query);
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
const err = this._writer
|
|
79
|
+
.writeBind("", query.meta, query.args);
|
|
80
|
+
if (err)
|
|
81
|
+
return err;
|
|
55
82
|
this._writer
|
|
56
|
-
.writeBind("", query.statement, query.args, this._parsed[query.text]?.parameters)
|
|
57
83
|
.writeExecute("")
|
|
58
84
|
.writeSync();
|
|
85
|
+
this._queue.push(query);
|
|
86
|
+
return null;
|
|
59
87
|
}
|
|
60
88
|
constructor(socket, config) {
|
|
61
89
|
this._writer = ConnectionRequestBuffer.new(65536);
|
|
@@ -64,8 +92,8 @@ export class Connection {
|
|
|
64
92
|
this._closing = null;
|
|
65
93
|
this._closed = false;
|
|
66
94
|
this._reconnecting = null;
|
|
67
|
-
this._parsed =
|
|
68
|
-
this._parsing =
|
|
95
|
+
this._parsed = new Map();
|
|
96
|
+
this._parsing = new Map();
|
|
69
97
|
this._listeningCallbacks = new Map();
|
|
70
98
|
this._stmtCounter = 0;
|
|
71
99
|
this.config = config;
|
|
@@ -106,29 +134,32 @@ export class Connection {
|
|
|
106
134
|
return this._performQuery(text, args, resolvers);
|
|
107
135
|
}
|
|
108
136
|
_performQuery(text, args, resolvers) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
+ `\x1b[36m│\x1b[0m ${text}\n`
|
|
112
|
-
+ `${args.length !== 0 ? `\x1b[36m│\x1b[0m \x1b[90mArguments:\x1b[0m [${args}]\n` : ''}`
|
|
113
|
-
+ `\x1b[36m└────────────────────────────────────────────────\x1b[0m`);
|
|
114
|
-
}
|
|
115
|
-
const parsed = this._parsed[text];
|
|
137
|
+
this._logQuery(text, args);
|
|
138
|
+
const parsed = this._parsed.get(text);
|
|
116
139
|
if (parsed) {
|
|
117
|
-
const query = new CollectQuery(parsed
|
|
118
|
-
this._registerQuery(query);
|
|
140
|
+
const query = new CollectQuery(parsed, text, args, parsed.columns, resolvers, this.config.queryTimeout);
|
|
141
|
+
const err = this._registerQuery(query);
|
|
142
|
+
if (err) {
|
|
143
|
+
query.error(err);
|
|
144
|
+
this._logError(err);
|
|
145
|
+
}
|
|
119
146
|
return query.resolvers.future;
|
|
120
147
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
this.
|
|
125
|
-
return query.resolvers.future;
|
|
148
|
+
if (!this._parsing.has(text)) {
|
|
149
|
+
const parseQuery = new ParseQuery({ statement: this._nextStatement(), columns: EMPTY_ARRAY, parameters: EMPTY_ARRAY }, text, Future.withResolvers(), this.config.queryTimeout);
|
|
150
|
+
this._registerQuery(parseQuery);
|
|
151
|
+
this._parsing.set(text, parseQuery.resolvers.future);
|
|
126
152
|
}
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
153
|
+
const parsing = this._parsing.get(text);
|
|
154
|
+
return parsing.andThen(meta => {
|
|
155
|
+
const query = new CollectQuery(meta, text, args, meta.columns, resolvers, this.config.queryTimeout);
|
|
156
|
+
const err = this._registerQuery(query);
|
|
157
|
+
if (err) {
|
|
158
|
+
query.error(err);
|
|
159
|
+
this._logError(err);
|
|
160
|
+
}
|
|
161
|
+
return query.resolvers.future;
|
|
162
|
+
});
|
|
132
163
|
}
|
|
133
164
|
/**
|
|
134
165
|
* Like {@link query}, but for statements that don't return rows (INSERT/UPDATE/DDL/etc).
|
|
@@ -146,29 +177,32 @@ export class Connection {
|
|
|
146
177
|
return this._performExecute(text, args, resolvers);
|
|
147
178
|
}
|
|
148
179
|
_performExecute(text, args, resolvers) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
+ `\x1b[35m│\x1b[0m ${text}\n`
|
|
152
|
-
+ `${args.length !== 0 ? `\x1b[35m│\x1b[0m \x1b[90mArguments:\x1b[0m [${args}]\n` : ''}`
|
|
153
|
-
+ `\x1b[35m└────────────────────────────────────────────────\x1b[0m`);
|
|
154
|
-
}
|
|
155
|
-
const parsed = this._parsed[text];
|
|
180
|
+
this._logQuery(text, args);
|
|
181
|
+
const parsed = this._parsed.get(text);
|
|
156
182
|
if (parsed) {
|
|
157
|
-
const query = new ExecuteQuery(parsed
|
|
158
|
-
this._registerQuery(query);
|
|
183
|
+
const query = new ExecuteQuery(parsed, text, args, resolvers, this.config.queryTimeout);
|
|
184
|
+
const err = this._registerQuery(query);
|
|
185
|
+
if (err) {
|
|
186
|
+
query.error(err);
|
|
187
|
+
this._logError(err);
|
|
188
|
+
}
|
|
159
189
|
return query.resolvers.future;
|
|
160
190
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
this.
|
|
165
|
-
return query.resolvers.future;
|
|
191
|
+
if (!this._parsing.has(text)) {
|
|
192
|
+
const parseQuery = new ParseQuery({ statement: this._nextStatement(), columns: EMPTY_ARRAY, parameters: EMPTY_ARRAY }, text, Future.withResolvers(), this.config.queryTimeout);
|
|
193
|
+
this._registerQuery(parseQuery);
|
|
194
|
+
this._parsing.set(text, parseQuery.resolvers.future);
|
|
166
195
|
}
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
196
|
+
const parsing = this._parsing.get(text);
|
|
197
|
+
return parsing.andThen(meta => {
|
|
198
|
+
const query = new ExecuteQuery(meta, text, args, resolvers, this.config.queryTimeout);
|
|
199
|
+
const err = this._registerQuery(query);
|
|
200
|
+
if (err) {
|
|
201
|
+
query.error(err);
|
|
202
|
+
this._logError(err);
|
|
203
|
+
}
|
|
204
|
+
return query.resolvers.future;
|
|
205
|
+
});
|
|
172
206
|
}
|
|
173
207
|
/**
|
|
174
208
|
* Streams query results as a `ReadableStream`, without buffering rows in memory.
|
|
@@ -197,28 +231,36 @@ export class Connection {
|
|
|
197
231
|
return stream;
|
|
198
232
|
}
|
|
199
233
|
_performStream(text, args, controller) {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
+ `\x1b[34m│\x1b[0m ${text}\n`
|
|
203
|
-
+ `${args.length !== 0 ? `\x1b[34m│\x1b[0m \x1b[90mArguments:\x1b[0m [${args}]\n` : ''}`
|
|
204
|
-
+ `\x1b[34m└────────────────────────────────────────────────\x1b[0m`);
|
|
205
|
-
}
|
|
206
|
-
const parsed = this._parsed[text];
|
|
234
|
+
this._logQuery(text, args);
|
|
235
|
+
const parsed = this._parsed.get(text);
|
|
207
236
|
if (parsed) {
|
|
208
|
-
const query = new StreamQuery(parsed
|
|
209
|
-
this._registerQuery(query);
|
|
237
|
+
const query = new StreamQuery(parsed, text, args, controller, parsed.columns, this.config.queryTimeout);
|
|
238
|
+
const err = this._registerQuery(query);
|
|
239
|
+
if (err) {
|
|
240
|
+
controller.error(err);
|
|
241
|
+
this._logError(err);
|
|
242
|
+
}
|
|
210
243
|
return;
|
|
211
244
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
this.
|
|
216
|
-
return;
|
|
245
|
+
if (!this._parsing.has(text)) {
|
|
246
|
+
const parseQuery = new ParseQuery({ statement: this._nextStatement(), columns: EMPTY_ARRAY, parameters: EMPTY_ARRAY }, text, Future.withResolvers(), this.config.queryTimeout);
|
|
247
|
+
this._registerQuery(parseQuery);
|
|
248
|
+
this._parsing.set(text, parseQuery.resolvers.future);
|
|
217
249
|
}
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
250
|
+
const parsing = this._parsing.get(text);
|
|
251
|
+
parsing
|
|
252
|
+
.tap(meta => {
|
|
253
|
+
const query = new StreamQuery(meta, text, args, controller, null, this.config.queryTimeout);
|
|
254
|
+
const err = this._registerQuery(query);
|
|
255
|
+
if (err) {
|
|
256
|
+
controller.error(err);
|
|
257
|
+
this._logError(err);
|
|
258
|
+
}
|
|
259
|
+
})
|
|
260
|
+
.tapErr(err => {
|
|
261
|
+
controller.error(err);
|
|
262
|
+
})
|
|
263
|
+
.recover();
|
|
222
264
|
}
|
|
223
265
|
/**
|
|
224
266
|
* Runs `txCallback` inside `BEGIN`/`COMMIT`, rolling back on error.
|
|
@@ -294,8 +336,8 @@ export class Connection {
|
|
|
294
336
|
}
|
|
295
337
|
_performReconnect() {
|
|
296
338
|
this._socket.destroy();
|
|
297
|
-
this._parsed
|
|
298
|
-
this._parsing
|
|
339
|
+
this._parsed.clear();
|
|
340
|
+
this._parsing.clear();
|
|
299
341
|
this._sheduled = false;
|
|
300
342
|
while (this._queue.hasMore) {
|
|
301
343
|
this._queue.shift.error(ErrConnectionReconnecting);
|
|
@@ -324,37 +366,31 @@ export class Connection {
|
|
|
324
366
|
_handlePacket(type, length, reader) {
|
|
325
367
|
switch (type) {
|
|
326
368
|
case ResponseTypes.ParseComplete:
|
|
327
|
-
case ResponseTypes.NoData:
|
|
328
|
-
case ResponseTypes.CloseComplete: break;
|
|
329
369
|
case ResponseTypes.BindComplete:
|
|
370
|
+
case ResponseTypes.CloseComplete: break;
|
|
371
|
+
case ResponseTypes.ParameterDescription:
|
|
330
372
|
{
|
|
331
373
|
const query = this._currentQuery;
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
}
|
|
335
|
-
if (!query.columns) {
|
|
336
|
-
query.columns = this._parsed[query.text].columns;
|
|
337
|
-
}
|
|
374
|
+
const parameters = reader.readParameterDescription();
|
|
375
|
+
query.meta.parameters = parameters;
|
|
338
376
|
}
|
|
339
377
|
break;
|
|
340
|
-
case ResponseTypes.
|
|
378
|
+
case ResponseTypes.RowDescription:
|
|
341
379
|
{
|
|
342
380
|
const query = this._currentQuery;
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
};
|
|
349
|
-
delete this._parsing[query.text];
|
|
350
|
-
this._parsed[query.text] = meta;
|
|
381
|
+
const columns = reader.readRowDescription();
|
|
382
|
+
query.meta.columns = columns;
|
|
383
|
+
this._parsing.delete(query.text);
|
|
384
|
+
this._parsed.set(query.text, query.meta);
|
|
385
|
+
query.complete();
|
|
351
386
|
}
|
|
352
387
|
break;
|
|
353
|
-
case ResponseTypes.
|
|
388
|
+
case ResponseTypes.NoData:
|
|
354
389
|
{
|
|
355
390
|
const query = this._currentQuery;
|
|
356
|
-
|
|
357
|
-
this._parsed
|
|
391
|
+
this._parsing.delete(query.text);
|
|
392
|
+
this._parsed.set(query.text, query.meta);
|
|
393
|
+
query.complete();
|
|
358
394
|
}
|
|
359
395
|
break;
|
|
360
396
|
case ResponseTypes.DataRow:
|
|
@@ -364,7 +400,7 @@ export class Connection {
|
|
|
364
400
|
reader.skipBytes(length);
|
|
365
401
|
break;
|
|
366
402
|
}
|
|
367
|
-
query.push(reader.readDataRow(query.columns, this.config.int8toBigint));
|
|
403
|
+
query.push(reader.readDataRow(query.meta.columns, this.config.int8toBigint));
|
|
368
404
|
}
|
|
369
405
|
break;
|
|
370
406
|
case ResponseTypes.ComandComplete:
|
|
@@ -377,15 +413,10 @@ export class Connection {
|
|
|
377
413
|
case ResponseTypes.ErrorResponse:
|
|
378
414
|
{
|
|
379
415
|
const error = reader.readErrorResponse();
|
|
380
|
-
|
|
381
|
-
console.log(`\n\x1b[31m┌─ ERROR ────────────────────────────────────────\x1b[0m\n`
|
|
382
|
-
+ `\x1b[31m│\x1b[0m ${error}\n`
|
|
383
|
-
+ `\x1b[31m└────────────────────────────────────────────────\x1b[0m\n`);
|
|
384
|
-
}
|
|
416
|
+
this._logError(error);
|
|
385
417
|
const query = this._currentQuery;
|
|
386
|
-
if (
|
|
387
|
-
|
|
388
|
-
}
|
|
418
|
+
if (query instanceof ParseQuery)
|
|
419
|
+
this._parsing.delete(query.text);
|
|
389
420
|
query.error(error);
|
|
390
421
|
}
|
|
391
422
|
break;
|
|
@@ -393,17 +424,13 @@ export class Connection {
|
|
|
393
424
|
{
|
|
394
425
|
reader.skipBytes(length);
|
|
395
426
|
this._queue.next();
|
|
396
|
-
this.
|
|
427
|
+
this._tryFinishClosing();
|
|
397
428
|
}
|
|
398
429
|
break;
|
|
399
430
|
case ResponseTypes.Notice:
|
|
400
431
|
{
|
|
401
|
-
const
|
|
402
|
-
|
|
403
|
-
console.log(`\n\x1b[33m┌─ NOTICE ───────────────────────────────────────\x1b[0m\n`
|
|
404
|
-
+ `\x1b[33m│\x1b[0m ${message}\n`
|
|
405
|
-
+ `\x1b[33m└────────────────────────────────────────────────\x1b[0m\n`);
|
|
406
|
-
}
|
|
432
|
+
const notice = reader.readErrorResponse();
|
|
433
|
+
this._logNotice(notice);
|
|
407
434
|
}
|
|
408
435
|
break;
|
|
409
436
|
case ResponseTypes.NotificationResponse:
|
|
@@ -430,6 +457,19 @@ export class Connection {
|
|
|
430
457
|
get isClosed() {
|
|
431
458
|
return this._closed || !!this._closing;
|
|
432
459
|
}
|
|
460
|
+
_tryFinishClosing() {
|
|
461
|
+
if (!this._closing)
|
|
462
|
+
return;
|
|
463
|
+
if (this._queue.hasMore)
|
|
464
|
+
return;
|
|
465
|
+
setTimeout(() => {
|
|
466
|
+
if (!this._closing)
|
|
467
|
+
return;
|
|
468
|
+
if (!this._queue.hasMore) {
|
|
469
|
+
this._closing.resolve();
|
|
470
|
+
}
|
|
471
|
+
}, 0);
|
|
472
|
+
}
|
|
433
473
|
/**
|
|
434
474
|
* Closes the connection, awaiting for all pending queries. Not usable afterward.
|
|
435
475
|
*/
|
|
@@ -439,11 +479,6 @@ export class Connection {
|
|
|
439
479
|
if (this._closing) {
|
|
440
480
|
return this._closing.future;
|
|
441
481
|
}
|
|
442
|
-
if (this._queue.isFree) {
|
|
443
|
-
this._closed = true;
|
|
444
|
-
this._socket.destroy();
|
|
445
|
-
return Future.resolve();
|
|
446
|
-
}
|
|
447
482
|
const closing = Future.withResolvers();
|
|
448
483
|
this._closing = closing;
|
|
449
484
|
closing.future.tap(() => {
|
|
@@ -451,6 +486,7 @@ export class Connection {
|
|
|
451
486
|
this._closed = true;
|
|
452
487
|
this._socket.destroy();
|
|
453
488
|
});
|
|
489
|
+
this._tryFinishClosing();
|
|
454
490
|
return closing.future;
|
|
455
491
|
}
|
|
456
492
|
}
|
package/dist/error.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DataTypeOid } from "./protocol/constants";
|
|
1
2
|
export declare class PostgresError extends Error {
|
|
2
3
|
message: string;
|
|
3
4
|
code: string;
|
|
@@ -30,4 +31,5 @@ export declare const ErrSSLDenied: PostgresError;
|
|
|
30
31
|
export declare const ErrDatabaseNotFound: PostgresError;
|
|
31
32
|
export declare const ErrUntrustedCertificate: PostgresError;
|
|
32
33
|
export declare const ErrCertificateFileNotFound: PostgresError;
|
|
34
|
+
export declare function createBindTypeError(index: number, expectedType: DataTypeOid, actualValue: unknown): PostgresError;
|
|
33
35
|
//# sourceMappingURL=error.d.ts.map
|
package/dist/error.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../src/error.ts"],"names":[],"mappings":"AAAA,qBAAa,aAAc,SAAQ,KAAK;IAEhB,OAAO,EAAE,MAAM;IACxB,IAAI,EAAE,MAAM;IACZ,MAAM,EAAE,MAAM;IACd,QAAQ,EAAE,MAAM;IAChB,KAAK,EAAE,MAAM;IACb,IAAI,EAAE,MAAM;IACZ,QAAQ,EAAE,MAAM;IAChB,QAAQ,EAAE,MAAM;IAChB,UAAU,EAAE,MAAM;gBART,OAAO,EAAE,MAAM,EACxB,IAAI,GAAE,MAAqB,EAC3B,MAAM,GAAE,MAAW,EACnB,QAAQ,GAAE,MAAW,EACrB,KAAK,GAAE,MAAW,EAClB,IAAI,GAAE,MAAW,EACjB,QAAQ,GAAE,MAAW,EACrB,QAAQ,GAAE,MAAW,EACrB,UAAU,GAAE,MAAW;IAMlC,IAAI,YAAY,IAAI,OAAO,CAK1B;IAED,IAAI,WAAW,IAAI,OAAO,CAKzB;IAED,IAAI,uBAAuB,IAAI,OAAO,CAErC;IAED,IAAI,8BAA8B,IAAI,OAAO,CAE5C;IAED,IAAI,qBAAqB,IAAI,OAAO,CAEnC;IAED,IAAI,UAAU,IAAI,OAAO,CAExB;IAED,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,IAAI,mBAAmB,IAAI,OAAO,CAEjC;CACJ;AAGD,eAAO,MAAM,gBAAgB,eAAmF,CAAA;AAChH,eAAO,MAAM,mBAAmB,eAAqE,CAAA;AACrG,eAAO,MAAM,yBAAyB,eAAiD,CAAA;AAEvF,eAAO,MAAM,eAAe,eAA8C,CAAA;AAE1E,eAAO,MAAM,aAAa,eAAkE,CAAA;AAE5F,eAAO,MAAM,oBAAoB,eAA6E,CAAA;AAE9G,eAAO,MAAM,mBAAmB,eAA8E,CAAA;AAC9G,eAAO,MAAM,yBAAyB,eAA2F,CAAA;AACjI,eAAO,MAAM,YAAY,eAA4D,CAAA;AACrF,eAAO,MAAM,mBAAmB,eAAwD,CAAA;AACxF,eAAO,MAAM,uBAAuB,eAA4E,CAAA;AAChH,eAAO,MAAM,0BAA0B,eAAkF,CAAA"}
|
|
1
|
+
{"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../src/error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAgB,MAAM,sBAAsB,CAAA;AAGhE,qBAAa,aAAc,SAAQ,KAAK;IAEhB,OAAO,EAAE,MAAM;IACxB,IAAI,EAAE,MAAM;IACZ,MAAM,EAAE,MAAM;IACd,QAAQ,EAAE,MAAM;IAChB,KAAK,EAAE,MAAM;IACb,IAAI,EAAE,MAAM;IACZ,QAAQ,EAAE,MAAM;IAChB,QAAQ,EAAE,MAAM;IAChB,UAAU,EAAE,MAAM;gBART,OAAO,EAAE,MAAM,EACxB,IAAI,GAAE,MAAqB,EAC3B,MAAM,GAAE,MAAW,EACnB,QAAQ,GAAE,MAAW,EACrB,KAAK,GAAE,MAAW,EAClB,IAAI,GAAE,MAAW,EACjB,QAAQ,GAAE,MAAW,EACrB,QAAQ,GAAE,MAAW,EACrB,UAAU,GAAE,MAAW;IAMlC,IAAI,YAAY,IAAI,OAAO,CAK1B;IAED,IAAI,WAAW,IAAI,OAAO,CAKzB;IAED,IAAI,uBAAuB,IAAI,OAAO,CAErC;IAED,IAAI,8BAA8B,IAAI,OAAO,CAE5C;IAED,IAAI,qBAAqB,IAAI,OAAO,CAEnC;IAED,IAAI,UAAU,IAAI,OAAO,CAExB;IAED,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,IAAI,mBAAmB,IAAI,OAAO,CAEjC;CACJ;AAGD,eAAO,MAAM,gBAAgB,eAAmF,CAAA;AAChH,eAAO,MAAM,mBAAmB,eAAqE,CAAA;AACrG,eAAO,MAAM,yBAAyB,eAAiD,CAAA;AAEvF,eAAO,MAAM,eAAe,eAA8C,CAAA;AAE1E,eAAO,MAAM,aAAa,eAAkE,CAAA;AAE5F,eAAO,MAAM,oBAAoB,eAA6E,CAAA;AAE9G,eAAO,MAAM,mBAAmB,eAA8E,CAAA;AAC9G,eAAO,MAAM,yBAAyB,eAA2F,CAAA;AACjI,eAAO,MAAM,YAAY,eAA4D,CAAA;AACrF,eAAO,MAAM,mBAAmB,eAAwD,CAAA;AACxF,eAAO,MAAM,uBAAuB,eAA4E,CAAA;AAChH,eAAO,MAAM,0BAA0B,eAAkF,CAAA;AAgCzH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,OAAO,GAAG,aAAa,CAqBjH"}
|
package/dist/error.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { handlers } from "./protocol/types";
|
|
1
2
|
export class PostgresError extends Error {
|
|
2
3
|
constructor(message, code = 'undeclared', detail = '', severity = '', where = '', hint = '', position = '', dataType = '', constraint = '') {
|
|
3
4
|
super(message);
|
|
@@ -51,3 +52,43 @@ export const ErrSSLDenied = new PostgresError("SSL is required but server denied
|
|
|
51
52
|
export const ErrDatabaseNotFound = new PostgresError('Database with that dsn not found');
|
|
52
53
|
export const ErrUntrustedCertificate = new PostgresError("Database SSL certificate is untrusted or self-signed");
|
|
53
54
|
export const ErrCertificateFileNotFound = new PostgresError("The SSL certificate file specified in caPath was not found");
|
|
55
|
+
function describeValue(value) {
|
|
56
|
+
if (value === null)
|
|
57
|
+
return 'null';
|
|
58
|
+
if (value === undefined)
|
|
59
|
+
return 'undefined';
|
|
60
|
+
if (typeof value === 'bigint')
|
|
61
|
+
return `${value}n`;
|
|
62
|
+
if (typeof value === 'string') {
|
|
63
|
+
return value.length > 60 ? `${JSON.stringify(value.slice(0, 60))}…` : JSON.stringify(value);
|
|
64
|
+
}
|
|
65
|
+
if (Buffer.isBuffer(value) || value instanceof Uint8Array) {
|
|
66
|
+
return `Uint8Array(length=${value.length})`;
|
|
67
|
+
}
|
|
68
|
+
if (value instanceof Date) {
|
|
69
|
+
return `Date(${isNaN(value.getTime()) ? 'Invalid Date' : value.toISOString()})`;
|
|
70
|
+
}
|
|
71
|
+
if (Array.isArray(value)) {
|
|
72
|
+
const preview = value.slice(0, 5).map(describeValue).join(', ');
|
|
73
|
+
return `[${preview}${value.length > 5 ? ', …' : ''}] (length=${value.length})`;
|
|
74
|
+
}
|
|
75
|
+
if (typeof value === 'object') {
|
|
76
|
+
try {
|
|
77
|
+
const json = JSON.stringify(value);
|
|
78
|
+
return json.length > 120 ? `${json.slice(0, 120)}…` : json;
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return Object.prototype.toString.call(value);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return String(value);
|
|
85
|
+
}
|
|
86
|
+
export function createBindTypeError(index, expectedType, actualValue) {
|
|
87
|
+
const position = index + 1;
|
|
88
|
+
const info = handlers[expectedType];
|
|
89
|
+
const expectedPgType = info?.pgType ?? `oid ${expectedType}`;
|
|
90
|
+
const expectedJsShape = info?.jsShape ?? '(тип не описан в справочнике)';
|
|
91
|
+
const message = `Bind error: Parameter $${position} type mismatch. ` +
|
|
92
|
+
`Expected PostgreSQL type "${expectedPgType}" (${expectedJsShape}), received: ${describeValue(actualValue)}.`;
|
|
93
|
+
return new PostgresError(message, '22000', `Parameter $${position} failed client-side binary validation for pg type "${expectedPgType}".`, 'ERROR', 'writeBind() inside driver', `Pass a value matching ${expectedJsShape} for $${position}.`, '', expectedPgType);
|
|
94
|
+
}
|