@m2k-5f/pgtx 2.6.0 → 2.6.2
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 +214 -480
- package/dist/batch.d.ts +3 -2
- package/dist/batch.d.ts.map +1 -1
- package/dist/batch.js +9 -9
- package/dist/clauses/abstract.clause.d.ts.map +1 -1
- package/dist/connection.d.ts +42 -175
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +205 -290
- package/dist/error.d.ts +5 -0
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +5 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/pool.d.ts +35 -192
- package/dist/pool.d.ts.map +1 -1
- package/dist/pool.js +70 -202
- package/dist/protocol/connection-response-reader.d.ts +3 -4
- package/dist/protocol/connection-response-reader.d.ts.map +1 -1
- package/dist/protocol/connection-response-reader.js +4 -2
- package/dist/protocol/socket-authorization.d.ts +1 -10
- package/dist/protocol/socket-authorization.d.ts.map +1 -1
- package/dist/protocol/socket-authorization.js +1 -4
- package/dist/protocol/socket-connector.d.ts +5 -5
- package/dist/protocol/socket-connector.d.ts.map +1 -1
- package/dist/protocol/socket-connector.js +24 -17
- package/dist/query.d.ts +17 -26
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +24 -27
- package/dist/transaction.d.ts +11 -4
- package/dist/transaction.d.ts.map +1 -1
- package/dist/transaction.js +24 -11
- package/dist/types.d.ts +59 -11
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/connection.js
CHANGED
|
@@ -5,262 +5,197 @@ 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 {
|
|
8
|
+
import { CollectQuery, StreamQuery, ExecuteQuery } from "./query";
|
|
9
9
|
import { sql } from ".";
|
|
10
10
|
import { Begin, Future, Ok } from 'fluent-future';
|
|
11
11
|
import { ErrConnectionClosed, ErrConnectionReconnecting } from "./error";
|
|
12
12
|
import { Batch } from "./batch";
|
|
13
|
+
import { nextTick } from "process";
|
|
14
|
+
const shedule = {
|
|
15
|
+
Immediate: setImmediate,
|
|
16
|
+
afterMicrotask: setTimeout,
|
|
17
|
+
beforeMicrotask: nextTick
|
|
18
|
+
};
|
|
13
19
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* Supports:
|
|
17
|
-
* - Tagged template queries with automatic parameter binding
|
|
18
|
-
* - Prepared statements with caching
|
|
19
|
-
* - Transaction management with savepoints
|
|
20
|
-
* - Pipeline execution for concurrent queries
|
|
20
|
+
* A dedicated connection to PostgreSQL: tagged-template queries, prepared
|
|
21
|
+
* statement caching, transactions with savepoints, and pipelined execution.
|
|
21
22
|
*
|
|
22
23
|
* @example
|
|
23
|
-
*
|
|
24
|
-
* const conn = await Connection.new({
|
|
25
|
-
* host: 'localhost',
|
|
26
|
-
* user: 'postgres',
|
|
27
|
-
* password: 'postgres',
|
|
28
|
-
* database: 'test'
|
|
29
|
-
* })
|
|
30
|
-
*
|
|
31
|
-
* // Simple query
|
|
24
|
+
* const conn = await Connection.new({ host: 'localhost', user: 'postgres', password: 'postgres', database: 'test' })
|
|
32
25
|
* const users = await conn.query`SELECT * FROM users WHERE id = ${1}`
|
|
33
|
-
*
|
|
34
|
-
* // Transaction
|
|
35
|
-
* await conn.begin(async tx => {
|
|
36
|
-
* await tx.query`INSERT INTO users ...`
|
|
37
|
-
* })
|
|
38
|
-
*
|
|
39
|
-
* // Close connection
|
|
26
|
+
* await conn.begin(async tx => tx.query`INSERT INTO users ...`)
|
|
40
27
|
* conn.close()
|
|
41
|
-
* ```
|
|
42
28
|
*/
|
|
43
29
|
export class Connection {
|
|
44
30
|
_nextStatement() {
|
|
45
31
|
return `s-${this._stmtCounter++}`;
|
|
46
32
|
}
|
|
47
|
-
constructor(
|
|
33
|
+
constructor(config, socket) {
|
|
48
34
|
this._activeBatch = null;
|
|
49
|
-
this.
|
|
50
|
-
this.
|
|
35
|
+
this._closing = null;
|
|
36
|
+
this._closed = false;
|
|
37
|
+
this._reconnecting = null;
|
|
51
38
|
this._cachedBuffer = ConnectionRequestWriter.new();
|
|
52
39
|
this._batchQueue = new Queue();
|
|
53
40
|
this._parsed = new Map();
|
|
54
41
|
this._parsing = new Map();
|
|
55
42
|
this._listeningCallbacks = new Map();
|
|
56
43
|
this._stmtCounter = 0;
|
|
57
|
-
this.
|
|
58
|
-
this.
|
|
59
|
-
this._socket = new SocketConnector(socket, (type, _, reader) => this._handlePacket(type, reader), () => this._registerReconnect());
|
|
44
|
+
this.config = config;
|
|
45
|
+
this._socket = new SocketConnector(socket, (type, length, reader) => this._handlePacket(type, reader, length), () => this._reconnect());
|
|
60
46
|
}
|
|
61
47
|
/**
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* @param params - Connection parameters
|
|
65
|
-
* @returns A new Connection instance
|
|
66
|
-
* @throws {PostgresError} If authentication fails or connection cannot be established
|
|
67
|
-
*
|
|
68
|
-
* @example
|
|
69
|
-
* ```ts
|
|
70
|
-
* const conn = await Connection.new({
|
|
71
|
-
* host: 'localhost',
|
|
72
|
-
* port: 5432,
|
|
73
|
-
* user: 'postgres',
|
|
74
|
-
* password: 'secret',
|
|
75
|
-
* database: 'myapp'
|
|
76
|
-
* })
|
|
77
|
-
* ```
|
|
48
|
+
* Opens a new connection and authenticates.
|
|
49
|
+
* @throws {PostgresError} if authentication fails or the connection can't be established
|
|
78
50
|
*/
|
|
79
|
-
static new(
|
|
51
|
+
static new(config) {
|
|
52
|
+
const conf = {
|
|
53
|
+
...config,
|
|
54
|
+
logLevel: config.logLevel || 'error',
|
|
55
|
+
int8toBigint: config.int8toBigint || false,
|
|
56
|
+
queryTimeout: config.queryTimeout || 30000,
|
|
57
|
+
syncShedule: config.syncShedule || 'afterMicrotask'
|
|
58
|
+
};
|
|
80
59
|
const writer = ConnectionRequestWriter.new();
|
|
81
|
-
return createAuthorizedSocket(writer,
|
|
82
|
-
.andThen(socket => Ok(new Connection(
|
|
60
|
+
return createAuthorizedSocket(writer, conf)
|
|
61
|
+
.andThen(socket => Ok(new Connection(conf, socket)));
|
|
83
62
|
}
|
|
84
63
|
_registerBatch() {
|
|
85
64
|
if (!this._activeBatch) {
|
|
86
65
|
const batch = new Batch(this._cachedBuffer.clear());
|
|
87
66
|
this._activeBatch = batch;
|
|
88
|
-
|
|
89
|
-
this._sync(batch);
|
|
90
|
-
});
|
|
67
|
+
shedule[this.config.syncShedule](() => this._sync(batch));
|
|
91
68
|
return batch;
|
|
92
69
|
}
|
|
93
70
|
return this._activeBatch;
|
|
94
71
|
}
|
|
95
72
|
_sync(batch) {
|
|
96
|
-
if (
|
|
97
|
-
batch.reject(ErrConnectionClosed);
|
|
73
|
+
if (this._reconnecting)
|
|
98
74
|
return;
|
|
99
|
-
}
|
|
100
|
-
if (this._isReconnecting) {
|
|
101
|
-
this._reconnect()
|
|
102
|
-
.then(() => {
|
|
103
|
-
this._activeBatch = null;
|
|
104
|
-
void this._restoreSubscriptions();
|
|
105
|
-
this._socket.write(this._registerBatch().end());
|
|
106
|
-
this._batchQueue.push(this._registerBatch());
|
|
107
|
-
})
|
|
108
|
-
.then(() => {
|
|
109
|
-
this._isReconnecting = false;
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
75
|
this._socket.write(batch.end());
|
|
113
76
|
this._activeBatch = null;
|
|
114
77
|
this._batchQueue.push(batch);
|
|
115
78
|
}
|
|
116
79
|
/**
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
* Parameters are automatically bound to `$1, $2, ...` placeholders.
|
|
120
|
-
* Queries with parameters use prepared statements for performance.
|
|
121
|
-
*
|
|
122
|
-
* @param templates - Tagged template string with SQL
|
|
123
|
-
* @param args - Query parameters
|
|
124
|
-
* @returns Array of rows with proper typing
|
|
125
|
-
* @throws {Error} If connection is dead or query fails
|
|
80
|
+
* Runs a query, binding template values as `$1, $2, ...`.
|
|
81
|
+
* Parameterized queries are cached as prepared statements.
|
|
126
82
|
*
|
|
127
83
|
* @example
|
|
128
|
-
*
|
|
129
|
-
* // Simple query
|
|
130
|
-
* const users = await conn.query`SELECT * FROM users`
|
|
131
|
-
*
|
|
132
|
-
* // With parameters
|
|
133
|
-
* const user = await conn.query`SELECT * FROM users WHERE id = ${1}`
|
|
134
|
-
*
|
|
135
|
-
* // With typed result
|
|
136
|
-
* type User = { id: number, name: string }
|
|
137
|
-
* const users = await conn.query<User>`SELECT * FROM users`
|
|
138
|
-
* ```
|
|
84
|
+
* const users = await conn.query<User>`SELECT * FROM users WHERE id = ${1}`
|
|
139
85
|
*/
|
|
140
86
|
query(templates, ...params) {
|
|
141
|
-
if (
|
|
87
|
+
if (this.isClosed)
|
|
142
88
|
return Future.reject(ErrConnectionClosed);
|
|
143
|
-
if (this.
|
|
144
|
-
return
|
|
89
|
+
if (this._reconnecting)
|
|
90
|
+
return this._reconnecting.andThen(() => this._performQuery(templates, ...params));
|
|
91
|
+
return this._performQuery(templates, ...params);
|
|
92
|
+
}
|
|
93
|
+
_performQuery(templates, ...params) {
|
|
145
94
|
const { text, args } = compileSqlTemplate(templates, params);
|
|
146
|
-
if (this.
|
|
95
|
+
if (this.config.logLevel === 'query') {
|
|
147
96
|
console.log(`\nQUERY: ${text}\n${args.length !== 0 ? `ARGUMENTS: [${args}]\n` : ""}`);
|
|
148
97
|
}
|
|
149
|
-
const batch = this._registerBatch();
|
|
150
98
|
if (this._parsed.has(text)) {
|
|
151
99
|
const meta = this._parsed.get(text);
|
|
152
|
-
const query = new
|
|
153
|
-
|
|
100
|
+
const query = new CollectQuery(meta.statement, text, args, meta.columns, this.config.queryTimeout);
|
|
101
|
+
this._registerBatch().registerQuery(query);
|
|
154
102
|
return query.future;
|
|
155
103
|
}
|
|
156
|
-
if (
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
161
|
-
const future = this._parsing.get(text);
|
|
162
|
-
return future.andThen(meta => {
|
|
163
|
-
const query = new SimpleQuery(meta.statement, text, args, meta.columns);
|
|
164
|
-
this._registerBatch().registerQuery(query, this.params.queryTimeout || 30000);
|
|
104
|
+
if (this._parsing.has(text)) {
|
|
105
|
+
const statement = this._parsing.get(text);
|
|
106
|
+
const query = new CollectQuery(statement, text, args, null, this.config.queryTimeout);
|
|
107
|
+
this._registerBatch().registerQuery(query);
|
|
165
108
|
return query.future;
|
|
166
|
-
}
|
|
109
|
+
}
|
|
110
|
+
const query = new CollectQuery(this._nextStatement(), text, args, null, this.config.queryTimeout);
|
|
111
|
+
this._parsing.set(text, query.statement);
|
|
112
|
+
this._registerBatch().registerParse(query);
|
|
113
|
+
this._registerBatch().registerQuery(query);
|
|
114
|
+
return query.future;
|
|
167
115
|
}
|
|
168
116
|
/**
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
* Automatically handles:
|
|
172
|
-
* - `BEGIN` before the callback
|
|
173
|
-
* - `COMMIT` on successful completion
|
|
174
|
-
* - `ROLLBACK` if an error is thrown
|
|
117
|
+
* Like {@link query}, but for statements that don't return rows (INSERT/UPDATE/DDL/etc).
|
|
175
118
|
*
|
|
176
|
-
* @
|
|
177
|
-
*
|
|
178
|
-
|
|
119
|
+
* @example
|
|
120
|
+
* await conn.execute`UPDATE users SET name = ${name} WHERE id = ${id}`
|
|
121
|
+
*/
|
|
122
|
+
execute(templates, ...params) {
|
|
123
|
+
if (this.isClosed)
|
|
124
|
+
return Future.reject(ErrConnectionClosed);
|
|
125
|
+
if (this._reconnecting)
|
|
126
|
+
return this._reconnecting.andThen(() => this._performExecute(templates, ...params));
|
|
127
|
+
return this._performExecute(templates, ...params);
|
|
128
|
+
}
|
|
129
|
+
_performExecute(templates, ...params) {
|
|
130
|
+
const { text, args } = compileSqlTemplate(templates, params);
|
|
131
|
+
if (this.config.logLevel === 'query') {
|
|
132
|
+
console.log(`\nQUERY: ${text}\n${args.length !== 0 ? `ARGUMENTS: [${args}]\n` : ""}`);
|
|
133
|
+
}
|
|
134
|
+
if (this._parsed.has(text)) {
|
|
135
|
+
const meta = this._parsed.get(text);
|
|
136
|
+
const query = new ExecuteQuery(meta.statement, text, args, meta.columns, this.config.queryTimeout);
|
|
137
|
+
this._registerBatch().registerQuery(query);
|
|
138
|
+
return query.future;
|
|
139
|
+
}
|
|
140
|
+
if (this._parsing.has(text)) {
|
|
141
|
+
const statement = this._parsing.get(text);
|
|
142
|
+
const query = new ExecuteQuery(statement, text, args, null, this.config.queryTimeout);
|
|
143
|
+
this._registerBatch().registerQuery(query);
|
|
144
|
+
return query.future;
|
|
145
|
+
}
|
|
146
|
+
const query = new ExecuteQuery(this._nextStatement(), text, args, null, this.config.queryTimeout);
|
|
147
|
+
this._parsing.set(text, query.statement);
|
|
148
|
+
this._registerBatch().registerParse(query);
|
|
149
|
+
this._registerBatch().registerQuery(query);
|
|
150
|
+
return query.future;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Runs `txCallback` inside `BEGIN`/`COMMIT`, rolling back on error.
|
|
179
154
|
*
|
|
180
155
|
* @example
|
|
181
|
-
*
|
|
182
|
-
* const result = await conn.begin(async tx => {
|
|
183
|
-
* await tx.query`INSERT INTO accounts (id, balance) VALUES (1, 100)`
|
|
156
|
+
* await conn.begin(async tx => {
|
|
184
157
|
* await tx.query`UPDATE accounts SET balance = balance - 10 WHERE id = 1`
|
|
185
|
-
* return { success: true }
|
|
186
158
|
* })
|
|
187
|
-
* ```
|
|
188
159
|
*/
|
|
189
160
|
begin(txCallback) {
|
|
190
|
-
if (
|
|
161
|
+
if (this.isClosed)
|
|
191
162
|
return Future.reject(ErrConnectionClosed);
|
|
192
|
-
if (this._isReconnecting)
|
|
193
|
-
return Future.reject(ErrConnectionReconnecting);
|
|
194
|
-
const tx = new Transaction(this);
|
|
195
163
|
return Begin()
|
|
196
|
-
.andThen(() =>
|
|
197
|
-
.andThen(() =>
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
164
|
+
.andThen(() => this.execute `begin`)
|
|
165
|
+
.andThen(() => {
|
|
166
|
+
const tx = new Transaction(this);
|
|
167
|
+
return Future.of(txCallback(tx))
|
|
168
|
+
.tap(() => {
|
|
169
|
+
if (tx.isActive)
|
|
170
|
+
return tx.commit();
|
|
171
|
+
})
|
|
172
|
+
.tapErr(() => {
|
|
173
|
+
if (tx.isActive)
|
|
174
|
+
return tx.rollback();
|
|
175
|
+
});
|
|
176
|
+
});
|
|
206
177
|
}
|
|
207
|
-
/**
|
|
208
|
-
* Sends an asynchronous notification to a channel via `pg_notify`.
|
|
209
|
-
*
|
|
210
|
-
* @param channelName - The channel identifier
|
|
211
|
-
* @param payload - Optional string data (max 8000 bytes)
|
|
212
|
-
*
|
|
213
|
-
* @example
|
|
214
|
-
* ```ts
|
|
215
|
-
* await conn.notify('events', 'hello')
|
|
216
|
-
* ```
|
|
217
|
-
*/
|
|
178
|
+
/** Sends a `pg_notify` message on `channelName` (payload ≤ 8000 bytes). */
|
|
218
179
|
notify(channelName, payload = "") {
|
|
219
|
-
if (
|
|
180
|
+
if (this.isClosed)
|
|
220
181
|
return Future.reject(ErrConnectionClosed);
|
|
221
|
-
|
|
222
|
-
return Future.reject(ErrConnectionReconnecting);
|
|
223
|
-
return this.query `select pg_notify(${channelName}, ${payload})`;
|
|
182
|
+
return this.execute `select pg_notify(${channelName}, ${payload})`.map(() => { });
|
|
224
183
|
}
|
|
225
|
-
/**
|
|
226
|
-
* Subscribes a callback to a channel. Sends `LISTEN` on the first subscription.
|
|
227
|
-
*
|
|
228
|
-
* @param channelName - The channel identifier
|
|
229
|
-
* @param callback - Function invoked when a notification arrives
|
|
230
|
-
*
|
|
231
|
-
* @example
|
|
232
|
-
* ```ts
|
|
233
|
-
* await conn.listen('events', data => console.log(data))
|
|
234
|
-
* ```
|
|
235
|
-
*/
|
|
184
|
+
/** Subscribes `callback` to `channelName`, issuing `LISTEN` on first subscription. */
|
|
236
185
|
listen(channelName, callback) {
|
|
237
|
-
if (
|
|
186
|
+
if (this.isClosed)
|
|
238
187
|
return Future.reject(ErrConnectionClosed);
|
|
239
|
-
if (this._isReconnecting)
|
|
240
|
-
return Future.reject(ErrConnectionReconnecting);
|
|
241
188
|
if (!this._listeningCallbacks.has(channelName)) {
|
|
242
189
|
this._listeningCallbacks.set(channelName, new Set());
|
|
243
190
|
}
|
|
244
191
|
const callbackSet = this._listeningCallbacks.get(channelName);
|
|
245
192
|
callbackSet.add(callback);
|
|
246
|
-
return this.
|
|
193
|
+
return this.execute `listen ${sql.ident(channelName)};`;
|
|
247
194
|
}
|
|
248
|
-
/**
|
|
249
|
-
* Unsubscribes a callback. Sends `UNLISTEN` if no callbacks remain for the channel.
|
|
250
|
-
*
|
|
251
|
-
* @param channelName - The channel identifier
|
|
252
|
-
* @param callback - The registered callback to remove
|
|
253
|
-
*
|
|
254
|
-
* @example
|
|
255
|
-
* ```ts
|
|
256
|
-
* await conn.unlisten('events', callback)
|
|
257
|
-
* ```
|
|
258
|
-
*/
|
|
195
|
+
/** Unsubscribes `callback`, issuing `UNLISTEN` once no callbacks remain. */
|
|
259
196
|
unlisten(channelName, callback) {
|
|
260
|
-
if (
|
|
197
|
+
if (this.isClosed)
|
|
261
198
|
return Future.reject(ErrConnectionClosed);
|
|
262
|
-
if (this._isReconnecting)
|
|
263
|
-
return Future.reject(ErrConnectionReconnecting);
|
|
264
199
|
if (!this._listeningCallbacks.has(channelName)) {
|
|
265
200
|
return Ok();
|
|
266
201
|
}
|
|
@@ -268,133 +203,99 @@ export class Connection {
|
|
|
268
203
|
callbackSet.delete(callback);
|
|
269
204
|
if (callbackSet.size === 0) {
|
|
270
205
|
this._listeningCallbacks.delete(channelName);
|
|
271
|
-
return this.
|
|
206
|
+
return this.execute `unlisten ${sql.ident(channelName)};`.map(() => { });
|
|
272
207
|
}
|
|
273
208
|
return Ok();
|
|
274
209
|
}
|
|
275
210
|
/**
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
* Data is streamed directly from the PostgreSQL binary network buffer into the Web Streams API
|
|
279
|
-
* (`ReadableStream`), bypassing any intermediate array allocation or row accumulation in the JS heap.
|
|
280
|
-
* This pattern provides a true Zero-Memory Footprint and is ideal for exporting massive tables
|
|
281
|
-
* or piping database payloads directly into HTTP responses (e.g., via `Bun.serve` or fetch `Response`).
|
|
282
|
-
*
|
|
283
|
-
* @template T The expected shape of a single row interface.
|
|
284
|
-
* @param {TemplateStringsArray} templates The SQL string parts from the tagged template literal.
|
|
285
|
-
* @param {...any} args The parameterized query arguments.
|
|
286
|
-
* @returns {ReadableStream<T>} Synchronously returns a native Web ReadableStream instance.
|
|
287
|
-
*
|
|
288
|
-
* @example
|
|
289
|
-
* // Streaming a giant table directly to an HTTP response (Bun.serve)
|
|
290
|
-
* const userStream = conn.stream<User>`SELECT id, name FROM users`;
|
|
291
|
-
* return new Response(userStream, { headers: { 'Content-Type': 'application/json' } });
|
|
211
|
+
* Streams query results as a `ReadableStream`, without buffering rows in memory.
|
|
212
|
+
* Ideal for large result sets or piping straight into an HTTP response.
|
|
292
213
|
*
|
|
293
214
|
* @example
|
|
294
|
-
*
|
|
295
|
-
* const stream = conn.stream<User>`SELECT * FROM orders WHERE status = ${'processed'}`;
|
|
296
|
-
* for await (const row of stream) {
|
|
297
|
-
* console.log(row.id, row.amount); // Row object is eligible for GC immediately after iteration
|
|
298
|
-
* }
|
|
215
|
+
* for await (const row of conn.stream<User>`SELECT * FROM orders`) { ... }
|
|
299
216
|
*/
|
|
300
217
|
stream(templates, ...params) {
|
|
301
|
-
if (
|
|
218
|
+
if (this.isClosed)
|
|
302
219
|
throw ErrConnectionClosed;
|
|
303
|
-
if (this._isReconnecting)
|
|
304
|
-
throw ErrConnectionReconnecting;
|
|
305
|
-
const { text, args } = compileSqlTemplate(templates, params);
|
|
306
|
-
if (this._logLevel === 'query') {
|
|
307
|
-
console.log(`\nQUERY: ${text}\n${args.length !== 0 ? `ARGUMENTS: [${args}]\n` : ""}`);
|
|
308
|
-
}
|
|
309
220
|
let controller;
|
|
310
221
|
const stream = new ReadableStream({
|
|
311
222
|
start: c => {
|
|
312
223
|
controller = c;
|
|
313
224
|
}
|
|
314
225
|
});
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
batch.registerQuery(query, this.params.queryTimeout || 30000);
|
|
226
|
+
if (this._reconnecting) {
|
|
227
|
+
this._reconnecting
|
|
228
|
+
.tap(() => this._performStream(templates, params, controller))
|
|
229
|
+
.tapErr(err => controller.error(err));
|
|
320
230
|
return stream;
|
|
321
231
|
}
|
|
322
|
-
|
|
323
|
-
const parseQuery = new ParseQuery(this._nextStatement(), text);
|
|
324
|
-
this._parsing.set(text, parseQuery.future);
|
|
325
|
-
batch.registerQuery(parseQuery, this.params.queryTimeout || 30000);
|
|
326
|
-
}
|
|
327
|
-
const future = this._parsing.get(text);
|
|
328
|
-
future
|
|
329
|
-
.tap(meta => {
|
|
330
|
-
const query = new StreamQuery(meta.statement, text, args, controller, meta.columns);
|
|
331
|
-
this._registerBatch().registerQuery(query, this.params.queryTimeout || 30000);
|
|
332
|
-
})
|
|
333
|
-
.catch(err => controller.error(err));
|
|
232
|
+
this._performStream(templates, params, controller);
|
|
334
233
|
return stream;
|
|
335
234
|
}
|
|
336
|
-
|
|
337
|
-
if (!this._isOpened)
|
|
338
|
-
throw ErrConnectionClosed;
|
|
339
|
-
if (this._isReconnecting)
|
|
340
|
-
throw ErrConnectionReconnecting;
|
|
235
|
+
_performStream(templates, params, controller) {
|
|
341
236
|
const { text, args } = compileSqlTemplate(templates, params);
|
|
342
|
-
if (this.
|
|
237
|
+
if (this.config.logLevel === 'query') {
|
|
343
238
|
console.log(`\nQUERY: ${text}\n${args.length !== 0 ? `ARGUMENTS: [${args}]\n` : ""}`);
|
|
344
239
|
}
|
|
345
|
-
const batch = this._registerBatch();
|
|
346
240
|
if (this._parsed.has(text)) {
|
|
347
241
|
const meta = this._parsed.get(text);
|
|
348
|
-
const query = new StreamQuery(meta.statement, text, args, controller, meta.columns);
|
|
349
|
-
|
|
242
|
+
const query = new StreamQuery(meta.statement, text, args, controller, meta.columns, this.config.queryTimeout);
|
|
243
|
+
this._registerBatch().registerQuery(query);
|
|
244
|
+
return;
|
|
350
245
|
}
|
|
351
|
-
if (
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
|
|
246
|
+
if (this._parsing.has(text)) {
|
|
247
|
+
const statement = this._parsing.get(text);
|
|
248
|
+
const query = new StreamQuery(statement, text, args, controller, null, this.config.queryTimeout);
|
|
249
|
+
this._registerBatch().registerQuery(query);
|
|
250
|
+
return;
|
|
355
251
|
}
|
|
356
|
-
const
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
this._registerBatch().registerQuery(query, this.params.queryTimeout || 30000);
|
|
361
|
-
})
|
|
362
|
-
.catch(err => controller.error(err));
|
|
252
|
+
const query = new StreamQuery(this._nextStatement(), text, args, controller, null, this.config.queryTimeout);
|
|
253
|
+
this._parsing.set(text, query.statement);
|
|
254
|
+
this._registerBatch().registerParse(query);
|
|
255
|
+
this._registerBatch().registerQuery(query);
|
|
363
256
|
}
|
|
364
|
-
|
|
365
|
-
if (this.
|
|
257
|
+
_reconnect() {
|
|
258
|
+
if (this.isClosed)
|
|
366
259
|
return;
|
|
367
|
-
this.
|
|
260
|
+
if (this._reconnecting)
|
|
261
|
+
return;
|
|
262
|
+
this._reconnecting = this._performReconnect()
|
|
263
|
+
.tap(() => this._reconnecting = null)
|
|
264
|
+
.andThen(() => this._restoreSubscriptions())
|
|
265
|
+
.tapErr(() => {
|
|
266
|
+
this._reconnecting = null;
|
|
267
|
+
this._reconnect();
|
|
268
|
+
})
|
|
269
|
+
.recover();
|
|
368
270
|
}
|
|
369
|
-
|
|
271
|
+
_performReconnect() {
|
|
272
|
+
this._socket.destroy();
|
|
370
273
|
this._parsed.clear();
|
|
371
274
|
this._parsing.clear();
|
|
275
|
+
this._activeBatch?.reject(ErrConnectionReconnecting);
|
|
372
276
|
this._activeBatch = null;
|
|
373
|
-
this.
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
277
|
+
while (this._batchQueue.hasMore) {
|
|
278
|
+
this._batchQueue.shift.reject(ErrConnectionReconnecting);
|
|
279
|
+
}
|
|
280
|
+
return createAuthorizedSocket(ConnectionRequestWriter.new(), this.config)
|
|
281
|
+
.andThen(socket => {
|
|
282
|
+
const connector = new SocketConnector(socket, (type, length, reader) => this._handlePacket(type, reader, length), () => this._reconnect());
|
|
283
|
+
this._socket = connector;
|
|
284
|
+
return Ok();
|
|
285
|
+
});
|
|
380
286
|
}
|
|
381
287
|
_restoreSubscriptions() {
|
|
382
288
|
if (this._listeningCallbacks.size === 0)
|
|
383
|
-
return
|
|
384
|
-
const
|
|
385
|
-
return this.
|
|
289
|
+
return Future.resolve();
|
|
290
|
+
const futures = Array.from(this._listeningCallbacks.keys()).map(channel => {
|
|
291
|
+
return this.execute `LISTEN ${sql.ident(channel)};`;
|
|
386
292
|
});
|
|
387
|
-
return
|
|
293
|
+
return Future.all(futures).map(() => { });
|
|
388
294
|
}
|
|
389
295
|
_getCurrentQuery() {
|
|
390
296
|
return this._batchQueue.current.current;
|
|
391
297
|
}
|
|
392
|
-
|
|
393
|
-
while (this._batchQueue.hasMore) {
|
|
394
|
-
this._batchQueue.shift.reject(cause);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
_handlePacket(type, reader) {
|
|
298
|
+
_handlePacket(type, reader, length) {
|
|
398
299
|
switch (type) {
|
|
399
300
|
case ResponseTypes.ParseComplete:
|
|
400
301
|
{
|
|
@@ -404,6 +305,10 @@ export class Connection {
|
|
|
404
305
|
case ResponseTypes.BindComplete:
|
|
405
306
|
{
|
|
406
307
|
reader.readBindComplete();
|
|
308
|
+
const query = this._getCurrentQuery();
|
|
309
|
+
if (!query.columns) {
|
|
310
|
+
query.columns = this._parsed.get(query.text).columns;
|
|
311
|
+
}
|
|
407
312
|
}
|
|
408
313
|
break;
|
|
409
314
|
case ResponseTypes.CloseComplete:
|
|
@@ -422,26 +327,24 @@ export class Connection {
|
|
|
422
327
|
const query = this._getCurrentQuery();
|
|
423
328
|
const meta = { statement: query.statement, columns: [] };
|
|
424
329
|
this._parsing.delete(query.text);
|
|
425
|
-
query.resolve(meta);
|
|
426
330
|
this._parsed.set(query.text, meta);
|
|
427
|
-
this._batchQueue.current.next();
|
|
428
331
|
}
|
|
429
332
|
break;
|
|
430
333
|
case ResponseTypes.RowDescription:
|
|
431
334
|
{
|
|
432
335
|
const columns = reader.readRowDescription();
|
|
433
336
|
const query = this._getCurrentQuery();
|
|
434
|
-
const meta = {
|
|
337
|
+
const meta = {
|
|
338
|
+
statement: query.statement, columns
|
|
339
|
+
};
|
|
435
340
|
this._parsing.delete(query.text);
|
|
436
|
-
query.resolve(meta);
|
|
437
341
|
this._parsed.set(query.text, meta);
|
|
438
|
-
this._batchQueue.current.next();
|
|
439
342
|
}
|
|
440
343
|
break;
|
|
441
344
|
case ResponseTypes.DataRow:
|
|
442
345
|
{
|
|
443
346
|
let query = this._getCurrentQuery();
|
|
444
|
-
query.push(reader.readDataRow(query.columns, this.
|
|
347
|
+
query.push(reader.readDataRow(query.columns, this.config.int8toBigint));
|
|
445
348
|
}
|
|
446
349
|
break;
|
|
447
350
|
case ResponseTypes.ComandComplete:
|
|
@@ -455,11 +358,10 @@ export class Connection {
|
|
|
455
358
|
case ResponseTypes.ErrorResponse:
|
|
456
359
|
{
|
|
457
360
|
const error = reader.readErrorResponse();
|
|
458
|
-
if (this.
|
|
361
|
+
if (this.config.logLevel === 'error' || this.config.logLevel === 'notice' || this.config.logLevel === 'query') {
|
|
459
362
|
console.log(`\nError: ${error}\n`);
|
|
460
363
|
}
|
|
461
|
-
|
|
462
|
-
this._parsing.delete(query.text);
|
|
364
|
+
this._parsing.clear();
|
|
463
365
|
this._batchQueue.current.reject(error);
|
|
464
366
|
}
|
|
465
367
|
break;
|
|
@@ -467,12 +369,13 @@ export class Connection {
|
|
|
467
369
|
{
|
|
468
370
|
reader.readReadyForQuery();
|
|
469
371
|
this._batchQueue.next();
|
|
372
|
+
this._closing && !this._hasQueries && this._closing.resolve();
|
|
470
373
|
}
|
|
471
374
|
break;
|
|
472
375
|
case ResponseTypes.Notice:
|
|
473
376
|
{
|
|
474
377
|
const message = reader.readErrorResponse();
|
|
475
|
-
if (this.
|
|
378
|
+
if (this.config.logLevel === 'notice' || this.config.logLevel === 'query') {
|
|
476
379
|
console.log(`\nError: ${message}\n`);
|
|
477
380
|
}
|
|
478
381
|
}
|
|
@@ -489,26 +392,38 @@ export class Connection {
|
|
|
489
392
|
default: console.log('Undeclared response type: ', type);
|
|
490
393
|
}
|
|
491
394
|
}
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
395
|
+
get _hasQueries() {
|
|
396
|
+
return this._batchQueue.hasMore || this._activeBatch;
|
|
397
|
+
}
|
|
398
|
+
/** Whether the connection is alive and usable. */
|
|
496
399
|
get isOpened() {
|
|
497
|
-
return this.
|
|
400
|
+
return !this._closing && !this._closed;
|
|
401
|
+
}
|
|
402
|
+
/** Whether the connection is closed or closing. */
|
|
403
|
+
get isClosed() {
|
|
404
|
+
return this._closed || !!this._closing;
|
|
498
405
|
}
|
|
499
406
|
/**
|
|
500
|
-
* Closes the connection
|
|
501
|
-
* All pending queries will be rejected with an error.
|
|
502
|
-
* The connection cannot be used after this call.
|
|
503
|
-
*
|
|
504
|
-
* @example
|
|
505
|
-
* ```ts
|
|
506
|
-
* conn.close()
|
|
507
|
-
* ```
|
|
407
|
+
* Closes the connection, awaiting for all pending queries. Not usable afterward.
|
|
508
408
|
*/
|
|
509
409
|
close() {
|
|
510
|
-
this.
|
|
511
|
-
|
|
512
|
-
this.
|
|
410
|
+
if (this._closed)
|
|
411
|
+
return Future.resolve();
|
|
412
|
+
if (this._closing) {
|
|
413
|
+
return this._closing.future;
|
|
414
|
+
}
|
|
415
|
+
if (!this._hasQueries) {
|
|
416
|
+
this._closed = true;
|
|
417
|
+
this._socket.destroy();
|
|
418
|
+
return Future.resolve();
|
|
419
|
+
}
|
|
420
|
+
const closing = Future.withResolvers();
|
|
421
|
+
this._closing = closing;
|
|
422
|
+
closing.future.tap(() => {
|
|
423
|
+
this._closing = null;
|
|
424
|
+
this._closed = true;
|
|
425
|
+
this._socket.destroy();
|
|
426
|
+
});
|
|
427
|
+
return closing.future;
|
|
513
428
|
}
|
|
514
429
|
}
|