zet-lib 1.4.11 → 1.4.13
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/lib/Form.js +13 -1
- package/lib/Pool.js +286 -190
- package/lib/zAppRouter.js +378 -91
- package/lib/zRoute.js +202 -22
- package/package.json +6 -5
package/lib/Pool.js
CHANGED
|
@@ -1,85 +1,92 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
2
|
var __createBinding =
|
|
3
3
|
(this && this.__createBinding) ||
|
|
4
4
|
(Object.create
|
|
5
5
|
? function (o, m, k, k2) {
|
|
6
|
-
if (k2 === undefined) k2 = k
|
|
7
|
-
var desc = Object.getOwnPropertyDescriptor(m, k)
|
|
8
|
-
if (
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (
|
|
9
|
+
!desc ||
|
|
10
|
+
("get" in desc ? !m.__esModule : desc.writable || desc.configurable)
|
|
11
|
+
) {
|
|
9
12
|
desc = {
|
|
10
13
|
enumerable: true,
|
|
11
14
|
get: function () {
|
|
12
|
-
return m[k]
|
|
15
|
+
return m[k];
|
|
13
16
|
},
|
|
14
|
-
}
|
|
17
|
+
};
|
|
15
18
|
}
|
|
16
|
-
Object.defineProperty(o, k2, desc)
|
|
19
|
+
Object.defineProperty(o, k2, desc);
|
|
17
20
|
}
|
|
18
21
|
: function (o, m, k, k2) {
|
|
19
|
-
if (k2 === undefined) k2 = k
|
|
20
|
-
o[k2] = m[k]
|
|
21
|
-
})
|
|
22
|
+
if (k2 === undefined) k2 = k;
|
|
23
|
+
o[k2] = m[k];
|
|
24
|
+
});
|
|
22
25
|
var __setModuleDefault =
|
|
23
26
|
(this && this.__setModuleDefault) ||
|
|
24
27
|
(Object.create
|
|
25
28
|
? function (o, v) {
|
|
26
|
-
Object.defineProperty(o,
|
|
29
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
27
30
|
}
|
|
28
31
|
: function (o, v) {
|
|
29
|
-
o[
|
|
30
|
-
})
|
|
32
|
+
o["default"] = v;
|
|
33
|
+
});
|
|
31
34
|
var __importStar =
|
|
32
35
|
(this && this.__importStar) ||
|
|
33
36
|
function (mod) {
|
|
34
|
-
if (mod && mod.__esModule) return mod
|
|
35
|
-
var result = {}
|
|
36
|
-
if (mod != null)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
const
|
|
37
|
+
if (mod && mod.__esModule) return mod;
|
|
38
|
+
var result = {};
|
|
39
|
+
if (mod != null)
|
|
40
|
+
for (var k in mod)
|
|
41
|
+
if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k))
|
|
42
|
+
__createBinding(result, mod, k);
|
|
43
|
+
__setModuleDefault(result, mod);
|
|
44
|
+
return result;
|
|
45
|
+
};
|
|
46
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
|
+
exports.Pool = void 0;
|
|
48
|
+
const events_1 = require("events");
|
|
49
|
+
const fs = __importStar(require("fs"));
|
|
50
|
+
const path = __importStar(require("path"));
|
|
51
|
+
const { promisify } = require("util");
|
|
52
|
+
const setTimeoutPromise = promisify(setTimeout);
|
|
53
|
+
const pg_1 = require("pg");
|
|
47
54
|
function pgToString(value) {
|
|
48
|
-
return value.toString()
|
|
55
|
+
return value.toString();
|
|
49
56
|
}
|
|
50
|
-
pg_1.types.setTypeParser(1082, pgToString) // date
|
|
51
|
-
const uuid_1 = require(
|
|
52
|
-
const ErrorWithCode_1 = require(
|
|
57
|
+
pg_1.types.setTypeParser(1082, pgToString); // date
|
|
58
|
+
const uuid_1 = require("uuid");
|
|
59
|
+
const ErrorWithCode_1 = require("./ErrorWithCode");
|
|
53
60
|
class Pool extends events_1.EventEmitter {
|
|
54
61
|
/**
|
|
55
62
|
* Gets the number of queued requests waiting for a database connection
|
|
56
63
|
*/
|
|
57
64
|
get waitingCount() {
|
|
58
|
-
return this.connectionQueue.length
|
|
65
|
+
return this.connectionQueue.length;
|
|
59
66
|
}
|
|
60
67
|
/**
|
|
61
68
|
* Gets the number of idle connections
|
|
62
69
|
*/
|
|
63
70
|
get idleCount() {
|
|
64
|
-
return this.idleConnections.length
|
|
71
|
+
return this.idleConnections.length;
|
|
65
72
|
}
|
|
66
73
|
/**
|
|
67
74
|
* Gets the total number of connections in the pool
|
|
68
75
|
*/
|
|
69
76
|
get totalCount() {
|
|
70
|
-
return this.connections.length
|
|
77
|
+
return this.connections.length;
|
|
71
78
|
}
|
|
72
|
-
options
|
|
79
|
+
options;
|
|
73
80
|
// Internal event emitter used to handle queued connection requests
|
|
74
|
-
connectionQueueEventEmitter
|
|
75
|
-
connections = []
|
|
81
|
+
connectionQueueEventEmitter;
|
|
82
|
+
connections = [];
|
|
76
83
|
// Should self order by idle timeout ascending
|
|
77
|
-
idleConnections = []
|
|
78
|
-
connectionQueue = []
|
|
79
|
-
isEnding = false
|
|
84
|
+
idleConnections = [];
|
|
85
|
+
connectionQueue = [];
|
|
86
|
+
isEnding = false;
|
|
80
87
|
constructor(options) {
|
|
81
88
|
// eslint-disable-next-line constructor-super
|
|
82
|
-
super()
|
|
89
|
+
super();
|
|
83
90
|
const defaultOptions = {
|
|
84
91
|
poolSize: 10,
|
|
85
92
|
idleTimeoutMillis: 10000,
|
|
@@ -87,7 +94,12 @@ class Pool extends events_1.EventEmitter {
|
|
|
87
94
|
connectionTimeoutMillis: 5000,
|
|
88
95
|
retryConnectionMaxRetries: 5,
|
|
89
96
|
retryConnectionWaitMillis: 100,
|
|
90
|
-
retryConnectionErrorCodes: [
|
|
97
|
+
retryConnectionErrorCodes: [
|
|
98
|
+
"ENOTFOUND",
|
|
99
|
+
"EAI_AGAIN",
|
|
100
|
+
"ERR_PG_CONNECT_TIMEOUT",
|
|
101
|
+
"timeout expired",
|
|
102
|
+
],
|
|
91
103
|
reconnectOnDatabaseIsStartingError: true,
|
|
92
104
|
waitForDatabaseStartupMillis: 0,
|
|
93
105
|
databaseStartupTimeoutMillis: 90000,
|
|
@@ -100,26 +112,28 @@ class Pool extends events_1.EventEmitter {
|
|
|
100
112
|
namedParameterFindRegExp: /@([\w])+\b/g,
|
|
101
113
|
getNamedParameterReplaceRegExp(namedParameter) {
|
|
102
114
|
// eslint-disable-next-line security/detect-non-literal-regexp
|
|
103
|
-
return new RegExp(`@${namedParameter}\\b`,
|
|
115
|
+
return new RegExp(`@${namedParameter}\\b`, "gm");
|
|
104
116
|
},
|
|
105
117
|
getNamedParameterName(namedParameterWithSymbols) {
|
|
106
118
|
// Remove leading @ symbol
|
|
107
|
-
return namedParameterWithSymbols.substring(1)
|
|
119
|
+
return namedParameterWithSymbols.substring(1);
|
|
108
120
|
},
|
|
109
|
-
}
|
|
110
|
-
const { ssl, ...otherOptions } = options
|
|
111
|
-
this.options = { ...defaultOptions, ...otherOptions }
|
|
112
|
-
if (ssl ===
|
|
121
|
+
};
|
|
122
|
+
const { ssl, ...otherOptions } = options;
|
|
123
|
+
this.options = { ...defaultOptions, ...otherOptions };
|
|
124
|
+
if (ssl === "aws-rds") {
|
|
113
125
|
this.options.ssl = {
|
|
114
126
|
rejectUnauthorized: true,
|
|
115
127
|
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
116
|
-
ca: fs.readFileSync(
|
|
117
|
-
|
|
118
|
-
|
|
128
|
+
ca: fs.readFileSync(
|
|
129
|
+
path.join(__dirname, "./certs/rds-global-bundle.pem")
|
|
130
|
+
),
|
|
131
|
+
minVersion: "TLSv1.2",
|
|
132
|
+
};
|
|
119
133
|
} else {
|
|
120
|
-
this.options.ssl = ssl
|
|
134
|
+
this.options.ssl = ssl;
|
|
121
135
|
}
|
|
122
|
-
this.connectionQueueEventEmitter = new events_1.EventEmitter()
|
|
136
|
+
this.connectionQueueEventEmitter = new events_1.EventEmitter();
|
|
123
137
|
}
|
|
124
138
|
/**
|
|
125
139
|
* Gets a client connection from the pool.
|
|
@@ -127,56 +141,66 @@ class Pool extends events_1.EventEmitter {
|
|
|
127
141
|
*/
|
|
128
142
|
async connect() {
|
|
129
143
|
if (this.isEnding) {
|
|
130
|
-
throw new ErrorWithCode_1.ErrorWithCode(
|
|
144
|
+
throw new ErrorWithCode_1.ErrorWithCode(
|
|
145
|
+
"Cannot use pool after calling end() on the pool",
|
|
146
|
+
"ERR_PG_CONNECT_POOL_ENDED"
|
|
147
|
+
);
|
|
131
148
|
}
|
|
132
|
-
const idleConnection = this.idleConnections.shift()
|
|
149
|
+
const idleConnection = this.idleConnections.shift();
|
|
133
150
|
if (idleConnection) {
|
|
134
151
|
if (idleConnection.idleTimeoutTimer) {
|
|
135
|
-
clearTimeout(idleConnection.idleTimeoutTimer)
|
|
152
|
+
clearTimeout(idleConnection.idleTimeoutTimer);
|
|
136
153
|
}
|
|
137
|
-
this.emit(
|
|
138
|
-
return idleConnection
|
|
154
|
+
this.emit("idleConnectionActivated");
|
|
155
|
+
return idleConnection;
|
|
139
156
|
}
|
|
140
|
-
const id = (0, uuid_1.v4)()
|
|
157
|
+
const id = (0, uuid_1.v4)();
|
|
141
158
|
if (this.connections.length < this.options.poolSize) {
|
|
142
|
-
this.connections.push(id)
|
|
159
|
+
this.connections.push(id);
|
|
143
160
|
try {
|
|
144
|
-
const connection = await this._createConnection(id)
|
|
145
|
-
return connection
|
|
161
|
+
const connection = await this._createConnection(id);
|
|
162
|
+
return connection;
|
|
146
163
|
} catch (ex) {
|
|
147
164
|
// Remove the connection id since we failed to connect
|
|
148
|
-
const connectionIndex = this.connections.indexOf(id)
|
|
165
|
+
const connectionIndex = this.connections.indexOf(id);
|
|
149
166
|
if (connectionIndex > -1) {
|
|
150
|
-
this.connections.splice(connectionIndex, 1)
|
|
167
|
+
this.connections.splice(connectionIndex, 1);
|
|
151
168
|
}
|
|
152
|
-
throw ex
|
|
169
|
+
throw ex;
|
|
153
170
|
}
|
|
154
171
|
}
|
|
155
|
-
this.emit(
|
|
156
|
-
this.connectionQueue.push(id)
|
|
157
|
-
let connectionTimeoutTimer = null
|
|
172
|
+
this.emit("connectionRequestQueued");
|
|
173
|
+
this.connectionQueue.push(id);
|
|
174
|
+
let connectionTimeoutTimer = null;
|
|
158
175
|
return await Promise.race([
|
|
159
176
|
new Promise((resolve) => {
|
|
160
177
|
this.connectionQueueEventEmitter.on(`connection_${id}`, (client) => {
|
|
161
178
|
if (connectionTimeoutTimer) {
|
|
162
|
-
clearTimeout(connectionTimeoutTimer)
|
|
179
|
+
clearTimeout(connectionTimeoutTimer);
|
|
163
180
|
}
|
|
164
|
-
this.connectionQueueEventEmitter.removeAllListeners(
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
181
|
+
this.connectionQueueEventEmitter.removeAllListeners(
|
|
182
|
+
`connection_${id}`
|
|
183
|
+
);
|
|
184
|
+
this.emit("connectionRequestDequeued");
|
|
185
|
+
resolve(client);
|
|
186
|
+
});
|
|
168
187
|
}),
|
|
169
188
|
(async () => {
|
|
170
|
-
connectionTimeoutTimer = await (
|
|
171
|
-
|
|
189
|
+
connectionTimeoutTimer = await setTimeoutPromise(
|
|
190
|
+
this.options.waitForAvailableConnectionTimeoutMillis
|
|
191
|
+
);
|
|
192
|
+
this.connectionQueueEventEmitter.removeAllListeners(`connection_${id}`);
|
|
172
193
|
// Remove this connection attempt from the connection queue
|
|
173
|
-
const index = this.connectionQueue.indexOf(id)
|
|
194
|
+
const index = this.connectionQueue.indexOf(id);
|
|
174
195
|
if (index > -1) {
|
|
175
|
-
this.connectionQueue.splice(index, 1)
|
|
196
|
+
this.connectionQueue.splice(index, 1);
|
|
176
197
|
}
|
|
177
|
-
throw new ErrorWithCode_1.ErrorWithCode(
|
|
198
|
+
throw new ErrorWithCode_1.ErrorWithCode(
|
|
199
|
+
"Timed out while waiting for available connection in pool",
|
|
200
|
+
"ERR_PG_CONNECT_POOL_CONNECTION_TIMEOUT"
|
|
201
|
+
);
|
|
178
202
|
})(),
|
|
179
|
-
])
|
|
203
|
+
]);
|
|
180
204
|
}
|
|
181
205
|
/**
|
|
182
206
|
* Gets a connection to the database and executes the specified query. This method will release the connection back to the pool when the query has finished.
|
|
@@ -186,104 +210,140 @@ class Pool extends events_1.EventEmitter {
|
|
|
186
210
|
query(text, values) {
|
|
187
211
|
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
188
212
|
if (Array.isArray(values)) {
|
|
189
|
-
return this._query(text, values)
|
|
213
|
+
return this._query(text, values);
|
|
190
214
|
}
|
|
191
215
|
if (!values || !Object.keys(values).length) {
|
|
192
|
-
return this._query(text)
|
|
216
|
+
return this._query(text);
|
|
193
217
|
}
|
|
194
218
|
// eslint-disable-next-line @typescript-eslint/prefer-regexp-exec
|
|
195
|
-
const tokenMatches = text.match(this.options.namedParameterFindRegExp)
|
|
219
|
+
const tokenMatches = text.match(this.options.namedParameterFindRegExp);
|
|
196
220
|
if (!tokenMatches) {
|
|
197
|
-
throw new ErrorWithCode_1.ErrorWithCode(
|
|
221
|
+
throw new ErrorWithCode_1.ErrorWithCode(
|
|
222
|
+
"Did not find named parameters in in the query. Expected named parameter form is @foo",
|
|
223
|
+
"ERR_PG_QUERY_NO_NAMED_PARAMETERS"
|
|
224
|
+
);
|
|
198
225
|
}
|
|
199
226
|
// Get unique token names
|
|
200
227
|
// https://stackoverflow.com/a/45886147/3085
|
|
201
|
-
const tokens = Array.from(
|
|
202
|
-
|
|
228
|
+
const tokens = Array.from(
|
|
229
|
+
new Set(tokenMatches.map(this.options.getNamedParameterName))
|
|
230
|
+
);
|
|
231
|
+
const missingParameters = [];
|
|
203
232
|
for (const token of tokens) {
|
|
204
233
|
if (!(token in values)) {
|
|
205
|
-
missingParameters.push(token)
|
|
234
|
+
missingParameters.push(token);
|
|
206
235
|
}
|
|
207
236
|
}
|
|
208
237
|
if (missingParameters.length) {
|
|
209
|
-
throw new ErrorWithCode_1.ErrorWithCode(
|
|
238
|
+
throw new ErrorWithCode_1.ErrorWithCode(
|
|
239
|
+
`Missing query parameter(s): ${missingParameters.join(", ")}`,
|
|
240
|
+
"ERR_PG_QUERY_MISSING_QUERY_PARAMETER"
|
|
241
|
+
);
|
|
210
242
|
}
|
|
211
|
-
let sql = text.slice()
|
|
212
|
-
const params = []
|
|
213
|
-
let tokenIndex = 1
|
|
243
|
+
let sql = text.slice();
|
|
244
|
+
const params = [];
|
|
245
|
+
let tokenIndex = 1;
|
|
214
246
|
for (const token of tokens) {
|
|
215
|
-
sql = sql.replace(
|
|
216
|
-
|
|
217
|
-
|
|
247
|
+
sql = sql.replace(
|
|
248
|
+
this.options.getNamedParameterReplaceRegExp(token),
|
|
249
|
+
`$${tokenIndex}`
|
|
250
|
+
);
|
|
251
|
+
params.push(values[token]);
|
|
252
|
+
tokenIndex += 1;
|
|
218
253
|
}
|
|
219
|
-
return this._query(sql, params)
|
|
254
|
+
return this._query(sql, params);
|
|
220
255
|
}
|
|
221
256
|
/**
|
|
222
257
|
* Drains the pool of all active client connections and prevents additional connections
|
|
223
258
|
*/
|
|
224
259
|
end() {
|
|
225
|
-
this.isEnding = true
|
|
226
|
-
return this.drainIdleConnections()
|
|
260
|
+
this.isEnding = true;
|
|
261
|
+
return this.drainIdleConnections();
|
|
227
262
|
}
|
|
228
263
|
/**
|
|
229
264
|
* Drains the pool of all idle client connections.
|
|
230
265
|
*/
|
|
231
266
|
async drainIdleConnections() {
|
|
232
|
-
await Promise.all(
|
|
267
|
+
await Promise.all(
|
|
268
|
+
[...this.idleConnections].map((idleConnection) =>
|
|
269
|
+
this._removeConnection(idleConnection)
|
|
270
|
+
)
|
|
271
|
+
);
|
|
233
272
|
}
|
|
234
273
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
235
274
|
async _query(text, values, reconnectQueryStartTime) {
|
|
236
|
-
const connection = await this.connect()
|
|
237
|
-
let removeConnection = false
|
|
238
|
-
let timeoutError
|
|
239
|
-
let connectionError
|
|
275
|
+
const connection = await this.connect();
|
|
276
|
+
let removeConnection = false;
|
|
277
|
+
let timeoutError;
|
|
278
|
+
let connectionError;
|
|
240
279
|
try {
|
|
241
|
-
const results = await connection.query(text, values)
|
|
242
|
-
return results
|
|
280
|
+
const results = await connection.query(text, values);
|
|
281
|
+
return results;
|
|
243
282
|
} catch (ex) {
|
|
244
|
-
const { message } = ex
|
|
245
|
-
if (
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
removeConnection = true
|
|
283
|
+
const { message } = ex;
|
|
284
|
+
if (
|
|
285
|
+
this.options.reconnectOnReadOnlyTransactionError &&
|
|
286
|
+
/cannot execute [\s\w]+ in a read-only transaction/giu.test(message)
|
|
287
|
+
) {
|
|
288
|
+
timeoutError = ex;
|
|
289
|
+
removeConnection = true;
|
|
290
|
+
} else if (
|
|
291
|
+
this.options.reconnectOnConnectionError &&
|
|
292
|
+
/Client has encountered a connection error and is not queryable/giu.test(
|
|
293
|
+
message
|
|
294
|
+
)
|
|
295
|
+
) {
|
|
296
|
+
connectionError = ex;
|
|
297
|
+
removeConnection = true;
|
|
251
298
|
} else {
|
|
252
|
-
throw ex
|
|
299
|
+
throw ex;
|
|
253
300
|
}
|
|
254
301
|
} finally {
|
|
255
|
-
await connection.release(removeConnection)
|
|
302
|
+
await connection.release(removeConnection);
|
|
256
303
|
}
|
|
257
304
|
// If we get here, that means that the query was attempted with a read-only connection.
|
|
258
305
|
// This can happen when the cluster fails over to a read-replica
|
|
259
306
|
if (timeoutError) {
|
|
260
|
-
this.emit(
|
|
307
|
+
this.emit("queryDeniedForReadOnlyTransaction");
|
|
261
308
|
} else if (connectionError) {
|
|
262
309
|
// This can happen when a cluster fails over
|
|
263
|
-
this.emit(
|
|
310
|
+
this.emit("queryDeniedForConnectionError");
|
|
264
311
|
}
|
|
265
312
|
// Clear all idle connections and try the query again with a fresh connection
|
|
266
|
-
await this.drainIdleConnections()
|
|
313
|
+
await this.drainIdleConnections();
|
|
267
314
|
if (!reconnectQueryStartTime) {
|
|
268
315
|
// eslint-disable-next-line no-param-reassign
|
|
269
|
-
reconnectQueryStartTime = process.hrtime()
|
|
316
|
+
reconnectQueryStartTime = process.hrtime();
|
|
270
317
|
}
|
|
271
|
-
if (
|
|
272
|
-
|
|
318
|
+
if (
|
|
319
|
+
timeoutError &&
|
|
320
|
+
this.options.waitForReconnectReadOnlyTransactionMillis > 0
|
|
321
|
+
) {
|
|
322
|
+
await setTimeoutPromise(
|
|
323
|
+
this.options.waitForReconnectReadOnlyTransactionMillis
|
|
324
|
+
);
|
|
273
325
|
}
|
|
274
326
|
if (connectionError && this.options.waitForReconnectConnectionMillis > 0) {
|
|
275
|
-
await (
|
|
327
|
+
await setTimeoutPromise(this.options.waitForReconnectConnectionMillis);
|
|
276
328
|
}
|
|
277
|
-
const diff = process.hrtime(reconnectQueryStartTime)
|
|
278
|
-
const timeSinceLastRun = Number(
|
|
279
|
-
|
|
280
|
-
|
|
329
|
+
const diff = process.hrtime(reconnectQueryStartTime);
|
|
330
|
+
const timeSinceLastRun = Number(
|
|
331
|
+
(diff[0] * 1e3 + diff[1] * 1e-6).toFixed(3)
|
|
332
|
+
);
|
|
333
|
+
if (
|
|
334
|
+
timeoutError &&
|
|
335
|
+
timeSinceLastRun > this.options.readOnlyTransactionReconnectTimeoutMillis
|
|
336
|
+
) {
|
|
337
|
+
throw timeoutError;
|
|
281
338
|
}
|
|
282
|
-
if (
|
|
283
|
-
|
|
339
|
+
if (
|
|
340
|
+
connectionError &&
|
|
341
|
+
timeSinceLastRun > this.options.connectionReconnectTimeoutMillis
|
|
342
|
+
) {
|
|
343
|
+
throw connectionError;
|
|
284
344
|
}
|
|
285
|
-
const results = await this._query(text, values, reconnectQueryStartTime)
|
|
286
|
-
return results
|
|
345
|
+
const results = await this._query(text, values, reconnectQueryStartTime);
|
|
346
|
+
return results;
|
|
287
347
|
}
|
|
288
348
|
/**
|
|
289
349
|
* Creates a new client connection to add to the pool
|
|
@@ -292,9 +352,14 @@ class Pool extends events_1.EventEmitter {
|
|
|
292
352
|
* @param {bigint} [createConnectionStartTime] - High-resolution time (in nanoseconds) for when the connection was created
|
|
293
353
|
* @param {[number,number]} [databaseStartupStartTime] - hrtime when the db was first listed as starting up
|
|
294
354
|
*/
|
|
295
|
-
async _createConnection(
|
|
296
|
-
|
|
297
|
-
|
|
355
|
+
async _createConnection(
|
|
356
|
+
connectionId,
|
|
357
|
+
retryAttempt = 0,
|
|
358
|
+
createConnectionStartTime = process.hrtime.bigint(),
|
|
359
|
+
databaseStartupStartTime
|
|
360
|
+
) {
|
|
361
|
+
const client = new pg_1.Client(this.options);
|
|
362
|
+
client.uniqueId = connectionId;
|
|
298
363
|
/**
|
|
299
364
|
* Releases the client connection back to the pool, to be used by another query.
|
|
300
365
|
*
|
|
@@ -302,136 +367,167 @@ class Pool extends events_1.EventEmitter {
|
|
|
302
367
|
*/
|
|
303
368
|
client.release = async (removeConnection = false) => {
|
|
304
369
|
if (this.isEnding || removeConnection) {
|
|
305
|
-
await this._removeConnection(client)
|
|
306
|
-
return
|
|
370
|
+
await this._removeConnection(client);
|
|
371
|
+
return;
|
|
307
372
|
}
|
|
308
|
-
const id = this.connectionQueue.shift()
|
|
373
|
+
const id = this.connectionQueue.shift();
|
|
309
374
|
// Return the connection to be used by a queued request
|
|
310
375
|
if (id) {
|
|
311
|
-
this.connectionQueueEventEmitter.emit(`connection_${id}`, client)
|
|
376
|
+
this.connectionQueueEventEmitter.emit(`connection_${id}`, client);
|
|
312
377
|
} else if (this.options.idleTimeoutMillis > 0) {
|
|
313
378
|
client.idleTimeoutTimer = setTimeout(() => {
|
|
314
379
|
// eslint-disable-next-line no-void
|
|
315
|
-
void this._removeConnection(client)
|
|
316
|
-
}, this.options.idleTimeoutMillis)
|
|
317
|
-
this.idleConnections.push(client)
|
|
318
|
-
this.emit(
|
|
380
|
+
void this._removeConnection(client);
|
|
381
|
+
}, this.options.idleTimeoutMillis);
|
|
382
|
+
this.idleConnections.push(client);
|
|
383
|
+
this.emit("connectionIdle");
|
|
319
384
|
} else {
|
|
320
|
-
await this._removeConnection(client)
|
|
385
|
+
await this._removeConnection(client);
|
|
321
386
|
}
|
|
322
|
-
}
|
|
387
|
+
};
|
|
323
388
|
client.errorHandler = (err) => {
|
|
324
389
|
// fire and forget, we will always emit the error.
|
|
325
390
|
// eslint-disable-next-line no-void
|
|
326
|
-
void this._removeConnection(client).finally(() =>
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
391
|
+
void this._removeConnection(client).finally(() =>
|
|
392
|
+
this.emit("error", err, client)
|
|
393
|
+
);
|
|
394
|
+
};
|
|
395
|
+
client.on("error", client.errorHandler);
|
|
396
|
+
let connectionTimeoutTimer = null;
|
|
397
|
+
const { connectionTimeoutMillis } = this.options;
|
|
331
398
|
try {
|
|
332
399
|
await Promise.race([
|
|
333
400
|
(async function connectClient() {
|
|
334
401
|
try {
|
|
335
|
-
await client.connect()
|
|
402
|
+
await client.connect();
|
|
336
403
|
} finally {
|
|
337
404
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
338
405
|
if (connectionTimeoutTimer) {
|
|
339
|
-
clearTimeout(connectionTimeoutTimer)
|
|
406
|
+
clearTimeout(connectionTimeoutTimer);
|
|
340
407
|
}
|
|
341
408
|
}
|
|
342
409
|
})(),
|
|
343
410
|
(async function connectTimeout() {
|
|
344
|
-
connectionTimeoutTimer = await (
|
|
345
|
-
|
|
411
|
+
connectionTimeoutTimer = await setTimeoutPromise(
|
|
412
|
+
connectionTimeoutMillis
|
|
413
|
+
);
|
|
414
|
+
throw new ErrorWithCode_1.ErrorWithCode(
|
|
415
|
+
"Timed out trying to connect to postgres",
|
|
416
|
+
"ERR_PG_CONNECT_TIMEOUT"
|
|
417
|
+
);
|
|
346
418
|
})(),
|
|
347
|
-
])
|
|
348
|
-
this.emit(
|
|
419
|
+
]);
|
|
420
|
+
this.emit("connectionAddedToPool", {
|
|
349
421
|
connectionId,
|
|
350
422
|
retryAttempt,
|
|
351
423
|
startTime: createConnectionStartTime,
|
|
352
|
-
})
|
|
424
|
+
});
|
|
353
425
|
} catch (ex) {
|
|
354
|
-
const { connection } = client
|
|
426
|
+
const { connection } = client;
|
|
355
427
|
if (connection) {
|
|
356
428
|
// Force a disconnect of the socket, if it exists.
|
|
357
|
-
connection.stream.destroy()
|
|
429
|
+
connection.stream.destroy();
|
|
358
430
|
}
|
|
359
|
-
await client.end()
|
|
360
|
-
const { message, code } = ex
|
|
361
|
-
let retryConnection = false
|
|
431
|
+
await client.end();
|
|
432
|
+
const { message, code } = ex;
|
|
433
|
+
let retryConnection = false;
|
|
362
434
|
if (this.options.retryConnectionMaxRetries) {
|
|
363
435
|
if (code) {
|
|
364
|
-
retryConnection =
|
|
436
|
+
retryConnection =
|
|
437
|
+
this.options.retryConnectionErrorCodes.includes(code);
|
|
365
438
|
} else {
|
|
366
439
|
for (const errorCode of this.options.retryConnectionErrorCodes) {
|
|
367
440
|
if (message.includes(errorCode)) {
|
|
368
|
-
retryConnection = true
|
|
369
|
-
break
|
|
441
|
+
retryConnection = true;
|
|
442
|
+
break;
|
|
370
443
|
}
|
|
371
444
|
}
|
|
372
445
|
}
|
|
373
446
|
}
|
|
374
|
-
if (
|
|
375
|
-
|
|
447
|
+
if (
|
|
448
|
+
retryConnection &&
|
|
449
|
+
retryAttempt < this.options.retryConnectionMaxRetries
|
|
450
|
+
) {
|
|
451
|
+
this.emit("retryConnectionOnError");
|
|
376
452
|
if (this.options.retryConnectionWaitMillis > 0) {
|
|
377
|
-
await (
|
|
453
|
+
await setTimeoutPromise(this.options.retryConnectionWaitMillis);
|
|
378
454
|
}
|
|
379
|
-
const connectionAfterRetry = await this._createConnection(
|
|
380
|
-
|
|
455
|
+
const connectionAfterRetry = await this._createConnection(
|
|
456
|
+
connectionId,
|
|
457
|
+
retryAttempt + 1,
|
|
458
|
+
createConnectionStartTime,
|
|
459
|
+
databaseStartupStartTime
|
|
460
|
+
);
|
|
461
|
+
return connectionAfterRetry;
|
|
381
462
|
}
|
|
382
|
-
if (
|
|
383
|
-
this.
|
|
463
|
+
if (
|
|
464
|
+
this.options.reconnectOnDatabaseIsStartingError &&
|
|
465
|
+
/the database system is starting up/giu.test(message)
|
|
466
|
+
) {
|
|
467
|
+
this.emit("waitingForDatabaseToStart");
|
|
384
468
|
if (!databaseStartupStartTime) {
|
|
385
469
|
// eslint-disable-next-line no-param-reassign
|
|
386
|
-
databaseStartupStartTime = process.hrtime()
|
|
470
|
+
databaseStartupStartTime = process.hrtime();
|
|
387
471
|
}
|
|
388
472
|
if (this.options.waitForDatabaseStartupMillis > 0) {
|
|
389
|
-
await (
|
|
473
|
+
await setTimeoutPromise(this.options.waitForDatabaseStartupMillis);
|
|
390
474
|
}
|
|
391
|
-
const diff = process.hrtime(databaseStartupStartTime)
|
|
392
|
-
const timeSinceFirstConnectAttempt = Number(
|
|
393
|
-
|
|
394
|
-
|
|
475
|
+
const diff = process.hrtime(databaseStartupStartTime);
|
|
476
|
+
const timeSinceFirstConnectAttempt = Number(
|
|
477
|
+
(diff[0] * 1e3 + diff[1] * 1e-6).toFixed(3)
|
|
478
|
+
);
|
|
479
|
+
if (
|
|
480
|
+
timeSinceFirstConnectAttempt >
|
|
481
|
+
this.options.databaseStartupTimeoutMillis
|
|
482
|
+
) {
|
|
483
|
+
throw ex;
|
|
395
484
|
}
|
|
396
|
-
const connectionAfterRetry = await this._createConnection(
|
|
397
|
-
|
|
485
|
+
const connectionAfterRetry = await this._createConnection(
|
|
486
|
+
connectionId,
|
|
487
|
+
0,
|
|
488
|
+
createConnectionStartTime,
|
|
489
|
+
databaseStartupStartTime
|
|
490
|
+
);
|
|
491
|
+
return connectionAfterRetry;
|
|
398
492
|
}
|
|
399
|
-
throw ex
|
|
493
|
+
throw ex;
|
|
400
494
|
}
|
|
401
|
-
return client
|
|
495
|
+
return client;
|
|
402
496
|
}
|
|
403
497
|
/**
|
|
404
498
|
* Removes the client connection from the pool and tries to gracefully shut it down
|
|
405
499
|
* @param {PoolClient} client
|
|
406
500
|
*/
|
|
407
501
|
async _removeConnection(client) {
|
|
408
|
-
client.removeListener(
|
|
502
|
+
client.removeListener("error", client.errorHandler);
|
|
409
503
|
// Ignore any errors when ending the connection
|
|
410
|
-
client.on(
|
|
504
|
+
client.on("error", () => {
|
|
411
505
|
// NOOP
|
|
412
|
-
})
|
|
506
|
+
});
|
|
413
507
|
if (client.idleTimeoutTimer) {
|
|
414
|
-
clearTimeout(client.idleTimeoutTimer)
|
|
508
|
+
clearTimeout(client.idleTimeoutTimer);
|
|
415
509
|
}
|
|
416
|
-
const idleConnectionIndex = this.idleConnections.findIndex(
|
|
510
|
+
const idleConnectionIndex = this.idleConnections.findIndex(
|
|
511
|
+
(connection) => connection.uniqueId === client.uniqueId
|
|
512
|
+
);
|
|
417
513
|
if (idleConnectionIndex > -1) {
|
|
418
|
-
this.idleConnections.splice(idleConnectionIndex, 1)
|
|
419
|
-
this.emit(
|
|
514
|
+
this.idleConnections.splice(idleConnectionIndex, 1);
|
|
515
|
+
this.emit("connectionRemovedFromIdlePool");
|
|
420
516
|
}
|
|
421
|
-
const connectionIndex = this.connections.indexOf(client.uniqueId)
|
|
517
|
+
const connectionIndex = this.connections.indexOf(client.uniqueId);
|
|
422
518
|
if (connectionIndex > -1) {
|
|
423
|
-
this.connections.splice(connectionIndex, 1)
|
|
519
|
+
this.connections.splice(connectionIndex, 1);
|
|
424
520
|
}
|
|
425
521
|
try {
|
|
426
|
-
await client.end()
|
|
522
|
+
await client.end();
|
|
427
523
|
} catch (ex) {
|
|
428
|
-
const { message } = ex
|
|
524
|
+
const { message } = ex;
|
|
429
525
|
if (!/This socket has been ended by the other party/giu.test(message)) {
|
|
430
|
-
this.emit(
|
|
526
|
+
this.emit("error", ex);
|
|
431
527
|
}
|
|
432
528
|
}
|
|
433
|
-
this.emit(
|
|
529
|
+
this.emit("connectionRemovedFromPool");
|
|
434
530
|
}
|
|
435
531
|
}
|
|
436
|
-
exports.Pool = Pool
|
|
532
|
+
exports.Pool = Pool;
|
|
437
533
|
//# sourceMappingURL=index.js.map
|