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