@tachybase/module-multi-app 1.6.0 → 1.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.
Files changed (105) hide show
  1. package/README.md +34 -34
  2. package/README.zh-CN.md +34 -34
  3. package/client.d.ts +1 -1
  4. package/client.js +1 -1
  5. package/dist/externalVersion.js +5 -5
  6. package/dist/locale/en-US.json +48 -48
  7. package/dist/locale/es-ES.json +9 -9
  8. package/dist/locale/ko_KR.json +11 -11
  9. package/dist/locale/pt-BR.json +9 -9
  10. package/dist/locale/zh-CN.json +58 -58
  11. package/dist/node_modules/mariadb/callback.js +43 -8
  12. package/dist/node_modules/mariadb/check-node.js +30 -0
  13. package/dist/node_modules/mariadb/lib/cluster-callback.js +84 -0
  14. package/dist/node_modules/mariadb/lib/cluster.js +446 -0
  15. package/dist/node_modules/mariadb/lib/cmd/batch-bulk.js +576 -177
  16. package/dist/node_modules/mariadb/lib/cmd/change-user.js +54 -44
  17. package/dist/node_modules/mariadb/lib/cmd/class/ok-packet.js +3 -2
  18. package/dist/node_modules/mariadb/lib/cmd/class/prepare-cache-wrapper.js +46 -0
  19. package/dist/node_modules/mariadb/lib/cmd/class/prepare-result-packet.js +141 -0
  20. package/dist/node_modules/mariadb/lib/cmd/class/prepare-wrapper.js +70 -0
  21. package/dist/node_modules/mariadb/lib/cmd/close-prepare.js +38 -0
  22. package/dist/node_modules/mariadb/lib/cmd/column-definition.js +145 -47
  23. package/dist/node_modules/mariadb/lib/cmd/command.js +41 -75
  24. package/dist/node_modules/mariadb/lib/cmd/decoder/binary-decoder.js +282 -0
  25. package/dist/node_modules/mariadb/lib/cmd/decoder/text-decoder.js +210 -0
  26. package/dist/node_modules/mariadb/lib/cmd/{common-binary-cmd.js → encoder/binary-encoder.js} +34 -77
  27. package/dist/node_modules/mariadb/lib/cmd/encoder/text-encoder.js +311 -0
  28. package/dist/node_modules/mariadb/lib/cmd/execute-stream.js +61 -0
  29. package/dist/node_modules/mariadb/lib/cmd/execute.js +338 -0
  30. package/dist/node_modules/mariadb/lib/cmd/handshake/auth/caching-sha2-password-auth.js +25 -62
  31. package/dist/node_modules/mariadb/lib/cmd/handshake/auth/clear-password-auth.js +39 -6
  32. package/dist/node_modules/mariadb/lib/cmd/handshake/auth/ed25519-password-auth.js +48 -16
  33. package/dist/node_modules/mariadb/lib/cmd/handshake/auth/handshake.js +198 -0
  34. package/dist/node_modules/mariadb/lib/cmd/handshake/{initial-handshake.js → auth/initial-handshake.js} +10 -8
  35. package/dist/node_modules/mariadb/lib/cmd/handshake/auth/native-password-auth.js +22 -9
  36. package/dist/node_modules/mariadb/lib/cmd/handshake/auth/pam-password-auth.js +9 -4
  37. package/dist/node_modules/mariadb/lib/cmd/handshake/auth/parsec-auth.js +115 -0
  38. package/dist/node_modules/mariadb/lib/cmd/handshake/auth/plugin-auth.js +12 -5
  39. package/dist/node_modules/mariadb/lib/cmd/handshake/auth/sha256-password-auth.js +44 -33
  40. package/dist/node_modules/mariadb/lib/cmd/handshake/authentication.js +335 -0
  41. package/dist/node_modules/mariadb/lib/cmd/handshake/client-capabilities.js +20 -19
  42. package/dist/node_modules/mariadb/lib/cmd/handshake/ssl-request.js +6 -3
  43. package/dist/node_modules/mariadb/lib/cmd/parser.js +861 -0
  44. package/dist/node_modules/mariadb/lib/cmd/ping.js +17 -18
  45. package/dist/node_modules/mariadb/lib/cmd/prepare.js +170 -0
  46. package/dist/node_modules/mariadb/lib/cmd/query.js +281 -144
  47. package/dist/node_modules/mariadb/lib/cmd/quit.js +9 -6
  48. package/dist/node_modules/mariadb/lib/cmd/reset.js +15 -19
  49. package/dist/node_modules/mariadb/lib/cmd/stream.js +21 -6
  50. package/dist/node_modules/mariadb/lib/config/cluster-options.js +23 -0
  51. package/dist/node_modules/mariadb/lib/config/connection-options.js +196 -132
  52. package/dist/node_modules/mariadb/lib/config/pool-options.js +27 -19
  53. package/dist/node_modules/mariadb/lib/connection-callback.js +492 -120
  54. package/dist/node_modules/mariadb/lib/connection-promise.js +372 -0
  55. package/dist/node_modules/mariadb/lib/connection.js +1739 -1016
  56. package/dist/node_modules/mariadb/lib/const/capabilities.js +36 -30
  57. package/dist/node_modules/mariadb/lib/const/collations.js +972 -36
  58. package/dist/node_modules/mariadb/lib/const/connection_status.js +3 -0
  59. package/dist/node_modules/mariadb/lib/const/error-code.js +35 -11
  60. package/dist/node_modules/mariadb/lib/const/field-detail.js +3 -0
  61. package/dist/node_modules/mariadb/lib/const/field-type.js +7 -4
  62. package/dist/node_modules/mariadb/lib/const/server-status.js +4 -1
  63. package/dist/node_modules/mariadb/lib/const/state-change.js +3 -0
  64. package/dist/node_modules/mariadb/lib/filtered-cluster-callback.js +136 -0
  65. package/dist/node_modules/mariadb/lib/filtered-cluster.js +118 -0
  66. package/dist/node_modules/mariadb/lib/io/compression-input-stream.js +14 -13
  67. package/dist/node_modules/mariadb/lib/io/compression-output-stream.js +21 -18
  68. package/dist/node_modules/mariadb/lib/io/packet-input-stream.js +75 -64
  69. package/dist/node_modules/mariadb/lib/io/packet-node-encoded.js +13 -9
  70. package/dist/node_modules/mariadb/lib/io/packet-node-iconv.js +12 -10
  71. package/dist/node_modules/mariadb/lib/io/packet-output-stream.js +402 -134
  72. package/dist/node_modules/mariadb/lib/io/packet.js +287 -202
  73. package/dist/node_modules/mariadb/lib/lru-prepare-cache.js +84 -0
  74. package/dist/node_modules/mariadb/lib/misc/connection-information.js +15 -32
  75. package/dist/node_modules/mariadb/lib/misc/errors.js +68 -25
  76. package/dist/node_modules/mariadb/lib/misc/parse.js +207 -711
  77. package/dist/node_modules/mariadb/lib/misc/utils.js +34 -62
  78. package/dist/node_modules/mariadb/lib/pool-callback.js +213 -174
  79. package/dist/node_modules/mariadb/lib/pool-promise.js +228 -94
  80. package/dist/node_modules/mariadb/lib/pool.js +951 -0
  81. package/dist/node_modules/mariadb/package.json +1 -1
  82. package/dist/node_modules/mariadb/promise.js +1 -34
  83. package/dist/node_modules/mariadb/types/callback.d.ts +207 -0
  84. package/dist/node_modules/mariadb/types/index.d.ts +94 -674
  85. package/dist/node_modules/mariadb/types/share.d.ts +804 -0
  86. package/dist/node_modules/qs/package.json +1 -1
  87. package/dist/server/actions/apps.js +2 -2
  88. package/dist/server/app-lifecycle.d.ts +1 -1
  89. package/dist/server/app-lifecycle.js +4 -4
  90. package/dist/server/models/application.d.ts +1 -1
  91. package/package.json +7 -7
  92. package/server.d.ts +2 -2
  93. package/server.js +1 -1
  94. package/dist/node_modules/mariadb/lib/cmd/batch-rewrite.js +0 -372
  95. package/dist/node_modules/mariadb/lib/cmd/common-text-cmd.js +0 -427
  96. package/dist/node_modules/mariadb/lib/cmd/handshake/client-handshake-response.js +0 -126
  97. package/dist/node_modules/mariadb/lib/cmd/handshake/handshake.js +0 -292
  98. package/dist/node_modules/mariadb/lib/cmd/resultset.js +0 -607
  99. package/dist/node_modules/mariadb/lib/config/pool-cluster-options.js +0 -19
  100. package/dist/node_modules/mariadb/lib/filtered-pool-cluster.js +0 -81
  101. package/dist/node_modules/mariadb/lib/io/bulk-packet.js +0 -590
  102. package/dist/node_modules/mariadb/lib/io/rewrite-packet.js +0 -481
  103. package/dist/node_modules/mariadb/lib/pool-base.js +0 -611
  104. package/dist/node_modules/mariadb/lib/pool-cluster-callback.js +0 -66
  105. package/dist/node_modules/mariadb/lib/pool-cluster.js +0 -407
@@ -0,0 +1,951 @@
1
+ // SPDX-License-Identifier: LGPL-2.1-or-later
2
+ // Copyright (c) 2015-2025 MariaDB Corporation Ab
3
+
4
+ 'use strict';
5
+
6
+ const { EventEmitter } = require('events');
7
+
8
+ const Queue = require('denque');
9
+ const Errors = require('./misc/errors');
10
+ const Utils = require('./misc/utils');
11
+ const Connection = require('./connection');
12
+
13
+ class Pool extends EventEmitter {
14
+ opts;
15
+ #closed = false;
16
+ #connectionInCreation = false;
17
+ #errorCreatingConnection = null;
18
+ #idleConnections;
19
+ #activeConnections = {};
20
+ #requests = new Queue();
21
+ #unusedConnectionRemoverId;
22
+ #requestTimeoutId;
23
+ #connErrorNumber = 0;
24
+ #initialized = false;
25
+ _managePoolSizeTask;
26
+ _connectionCreationTask;
27
+
28
+ constructor(options) {
29
+ super();
30
+ this.opts = options;
31
+ this.#idleConnections = new Queue(null, { capacity: this.opts.connectionLimit });
32
+ this.on('_idle', this._processNextPendingRequest);
33
+ this.on('validateSize', this._managePoolSize);
34
+ this._managePoolSize();
35
+ }
36
+
37
+ //*****************************************************************
38
+ // pool automatic handlers
39
+ //*****************************************************************
40
+
41
+ /**
42
+ * Manages pool size by creating new connections when needed
43
+ */
44
+ _managePoolSize() {
45
+ // Only create new connections if conditions are met and no creation is in progress
46
+ if (!this._shouldCreateMoreConnections() || this._managePoolSizeTask) {
47
+ return;
48
+ }
49
+
50
+ this.#connectionInCreation = true;
51
+
52
+ const timeoutEnd = Date.now() + this.opts.initializationTimeout;
53
+ this._initiateConnectionCreation(timeoutEnd);
54
+ }
55
+
56
+ /**
57
+ * Initiates connection creation with proper error handling
58
+ * @param {number} timeoutEnd - When the connection attempt should time out
59
+ */
60
+ _initiateConnectionCreation(timeoutEnd) {
61
+ this._createPoolConnection(
62
+ // Success callback
63
+ () => this._onConnectionCreationSuccess(),
64
+ // Error callback
65
+ (err) => this._onConnectionCreationError(err, timeoutEnd),
66
+ timeoutEnd
67
+ );
68
+ }
69
+
70
+ /**
71
+ * Handles successful connection creation
72
+ */
73
+ _onConnectionCreationSuccess() {
74
+ this.#initialized = true;
75
+ this.#errorCreatingConnection = null;
76
+ this.#connErrorNumber = 0;
77
+ this._connectionCreationTask = null;
78
+
79
+ // Check if we need more connections
80
+ if (this._shouldCreateMoreConnections()) {
81
+ this.emit('validateSize');
82
+ }
83
+
84
+ this._startConnectionReaping();
85
+ }
86
+
87
+ /**
88
+ * Handles errors during connection creation
89
+ * @param {Error} err - The error that occurred
90
+ * @param {number} timeoutEnd - When the connection attempt should time out
91
+ */
92
+ _onConnectionCreationError(err, timeoutEnd) {
93
+ this.#connectionInCreation = false;
94
+ if (this.#closed) {
95
+ return;
96
+ }
97
+ if (this.#errorCreatingConnection) err = this.#errorCreatingConnection;
98
+
99
+ // Format error message based on pool state
100
+ let error;
101
+ if (!this.#initialized) {
102
+ error = Errors.createError(
103
+ `Error during pool initialization`,
104
+ Errors.ER_POOL_NOT_INITIALIZED,
105
+ null,
106
+ null,
107
+ null,
108
+ false,
109
+ null,
110
+ null,
111
+ err
112
+ );
113
+ } else {
114
+ error = Errors.createError(
115
+ `Pool fails to create connection`,
116
+ Errors.ER_POOL_NO_CONNECTION,
117
+ null,
118
+ null,
119
+ null,
120
+ false,
121
+ null,
122
+ null,
123
+ err
124
+ );
125
+ }
126
+
127
+ // Schedule next attempt with exponential backoff
128
+ const backoffTime = Math.min(++this.#connErrorNumber * 200, 10000);
129
+ this._scheduleRetryWithBackoff(backoffTime);
130
+
131
+ this.emit('error', error);
132
+ }
133
+
134
+ /**
135
+ * Schedules the next connection creation attempt with backoff
136
+ * @param {number} delay - Time to wait before next attempt
137
+ */
138
+ _scheduleRetryWithBackoff(delay) {
139
+ if (this.#closed) {
140
+ return;
141
+ }
142
+ this._managePoolSizeTask = setTimeout(() => {
143
+ this._managePoolSizeTask = null;
144
+ if (!this.#requests.isEmpty()) {
145
+ this._managePoolSize();
146
+ }
147
+ }, delay);
148
+ }
149
+
150
+ /**
151
+ * Creates a new connection for the pool with proper error handling
152
+ * @param {Function} onSuccess - Success callback
153
+ * @param {Function} onError - Error callback
154
+ * @param {number} timeoutEnd - Timestamp when connection attempt should time out
155
+ */
156
+ _createPoolConnection(onSuccess, onError, timeoutEnd) {
157
+ const minTimeout = timeoutEnd - Date.now();
158
+ const connectionOpts = Object.assign({}, this.opts.connOptions, {
159
+ connectTimeout: Math.max(1, Math.min(minTimeout, this.opts.connOptions.connectTimeout || Number.MAX_SAFE_INTEGER))
160
+ });
161
+ const conn = new Connection(connectionOpts);
162
+ this._connectionCreationTask = null;
163
+ // Use direct callback approach instead of Promise
164
+ conn
165
+ .connect()
166
+ .then((conn) => this._prepareNewConnection(conn, onSuccess, onError))
167
+ .catch((err) => this._handleConnectionCreationError(err, onSuccess, onError, timeoutEnd));
168
+ }
169
+
170
+ /**
171
+ * Sets up a newly created connection for use in the pool
172
+ * @param {Connection} conn - The new connection
173
+ * @param {Function} onSuccess - Success callback
174
+ * @param {Function} onError - Error callback
175
+ */
176
+ _prepareNewConnection(conn, onSuccess, onError) {
177
+ // Handle pool closed during connection creation
178
+ if (this.#closed) {
179
+ this._cleanupConnection(conn, 'pool_closed');
180
+ onError(
181
+ new Errors.createFatalError(
182
+ 'Cannot create new connection to pool, pool closed',
183
+ Errors.ER_ADD_CONNECTION_CLOSED_POOL
184
+ )
185
+ );
186
+ return;
187
+ }
188
+
189
+ // Initialize connection for pool use
190
+ conn.lastUse = Date.now();
191
+
192
+ // Setup connection for pool use
193
+ conn.forceEnd = conn.end;
194
+ conn.release = (callback) => this._handleRelease(conn, callback);
195
+ conn.end = conn.release;
196
+
197
+ // Override destroy method to handle pool cleanup
198
+ this._overrideConnectionMethods(conn);
199
+
200
+ // Setup error handler for connection failures
201
+ this._setupConnectionErrorHandler(conn);
202
+
203
+ // Add to idle connections and mark creation as complete
204
+ this.#idleConnections.push(conn);
205
+ this.#connectionInCreation = false;
206
+
207
+ // Emit events and call success callback
208
+ this.emit('_idle');
209
+ this.emit('connection', conn);
210
+ onSuccess(conn);
211
+ }
212
+
213
+ /**
214
+ * Overrides connection methods for pool integration
215
+ * @param {Connection} conn - The connection to modify
216
+ */
217
+ _overrideConnectionMethods(conn) {
218
+ const nativeDestroy = conn.destroy.bind(conn);
219
+ const pool = this;
220
+
221
+ conn.destroy = function () {
222
+ pool._endLeak(conn);
223
+ delete pool.#activeConnections[conn.threadId];
224
+ nativeDestroy();
225
+ pool.emit('validateSize');
226
+ };
227
+ }
228
+
229
+ /**
230
+ * Sets up error handler for a connection
231
+ * @param {Connection} conn - The connection to set up
232
+ */
233
+ _setupConnectionErrorHandler(conn) {
234
+ const pool = this;
235
+
236
+ conn.once('error', () => {
237
+ // Clean up this connection
238
+ pool._endLeak(conn);
239
+ delete pool.#activeConnections[conn.threadId];
240
+
241
+ // Process idle connections
242
+ pool._processIdleConnectionsOnError(conn);
243
+
244
+ // Check if we need to create more connections
245
+ setImmediate(() => {
246
+ if (!pool.#requests.isEmpty()) {
247
+ pool._managePoolSize();
248
+ }
249
+ });
250
+ });
251
+ }
252
+
253
+ /**
254
+ * Processes idle connections when an error occurs
255
+ * @param {Connection} errorConn - The connection that had an error
256
+ */
257
+ _processIdleConnectionsOnError(errorConn) {
258
+ let idx = 0;
259
+ while (idx < this.#idleConnections.length) {
260
+ const currConn = this.#idleConnections.peekAt(idx);
261
+
262
+ if (currConn === errorConn) {
263
+ this.#idleConnections.removeOne(idx);
264
+ continue;
265
+ }
266
+
267
+ // Force validation on other connections
268
+ currConn.lastUse = Math.min(currConn.lastUse, Date.now() - this.opts.minDelayValidation);
269
+ idx++;
270
+ }
271
+ }
272
+
273
+ /**
274
+ * Handles errors during connection creation
275
+ * @param {Error} err - The error that occurred
276
+ * @param {Function} onSuccess - Success callback
277
+ * @param {Function} onError - Error callback
278
+ * @param {number} timeoutEnd - Timestamp when connection attempt should time out
279
+ */
280
+ _handleConnectionCreationError(err, onSuccess, onError, timeoutEnd) {
281
+ // Handle connection creation errors
282
+ if (err instanceof AggregateError) {
283
+ err = err.errors[0];
284
+ }
285
+ if (!this.#errorCreatingConnection) this.#errorCreatingConnection = err;
286
+ // Determine if we should retry or fail
287
+ const isFatalError =
288
+ this.#closed || (err.errno && [1524, 1045, 1698].includes(err.errno)) || timeoutEnd < Date.now();
289
+ if (isFatalError) {
290
+ // Fatal error - call error callback with additional pool info
291
+ err.message = err.message + this._errorMsgAddon();
292
+ this._connectionCreationTask = null;
293
+ onError(err);
294
+ return;
295
+ }
296
+
297
+ // Retry connection after delay
298
+ this._connectionCreationTask = setTimeout(
299
+ () => this._createPoolConnection(onSuccess, onError, timeoutEnd),
300
+ Math.min(500, timeoutEnd - Date.now())
301
+ );
302
+ }
303
+
304
+ /**
305
+ * Checks for timed-out requests and rejects them
306
+ */
307
+ _checkRequestTimeouts() {
308
+ this.#requestTimeoutId = null;
309
+ const currentTime = Date.now();
310
+
311
+ while (this.#requests.length > 0) {
312
+ const request = this.#requests.peekFront();
313
+
314
+ if (this._hasRequestTimedOut(request, currentTime)) {
315
+ this._rejectTimedOutRequest(request, currentTime);
316
+ continue;
317
+ }
318
+
319
+ this._scheduleNextTimeoutCheck(request, currentTime);
320
+ return;
321
+ }
322
+ }
323
+
324
+ /**
325
+ * Checks if a request has timed out
326
+ * @param {Request} request - The request to check
327
+ * @param {number} currentTime - Current timestamp
328
+ * @returns {boolean} - True if request has timed out
329
+ */
330
+ _hasRequestTimedOut(request, currentTime) {
331
+ return request.timeout <= currentTime;
332
+ }
333
+
334
+ /**
335
+ * Rejects a timed out request
336
+ * @param {Request} request - The request to reject
337
+ * @param {number} currentTime - Current timestamp
338
+ */
339
+ _rejectTimedOutRequest(request, currentTime) {
340
+ this.#requests.shift();
341
+
342
+ // Determine the cause of the timeout
343
+ const timeoutCause = this.activeConnections() === 0 ? this.#errorCreatingConnection : null;
344
+ const waitTime = Math.abs(currentTime - (request.timeout - this.opts.acquireTimeout));
345
+
346
+ // Create appropriate error message with pool state information
347
+ const timeoutError = Errors.createError(
348
+ `pool timeout: failed to retrieve a connection from pool after ${waitTime}ms${this._errorMsgAddon()}`,
349
+ Errors.ER_GET_CONNECTION_TIMEOUT,
350
+ null,
351
+ 'HY000',
352
+ null,
353
+ false,
354
+ request.stack,
355
+ null,
356
+ timeoutCause
357
+ );
358
+
359
+ request.reject(timeoutError);
360
+ }
361
+
362
+ /**
363
+ * Schedules the next timeout check
364
+ * @param {Request} request - The next request in queue
365
+ * @param {number} currentTime - Current timestamp
366
+ */
367
+ _scheduleNextTimeoutCheck(request, currentTime) {
368
+ const timeUntilNextTimeout = request.timeout - currentTime;
369
+ this.#requestTimeoutId = setTimeout(() => this._checkRequestTimeouts(), timeUntilNextTimeout);
370
+ }
371
+
372
+ _destroy(conn) {
373
+ this._endLeak(conn);
374
+ delete this.#activeConnections[conn.threadId];
375
+ conn.lastUse = Date.now();
376
+ conn.forceEnd(
377
+ null,
378
+ () => {},
379
+ () => {}
380
+ );
381
+
382
+ if (this.totalConnections() === 0) {
383
+ this._stopConnectionReaping();
384
+ }
385
+
386
+ this.emit('validateSize');
387
+ }
388
+
389
+ release(conn) {
390
+ if (!this.#activeConnections[conn.threadId]) {
391
+ return; // Already released
392
+ }
393
+
394
+ this._endLeak(conn);
395
+ this.#activeConnections[conn.threadId] = null;
396
+ conn.lastUse = Date.now();
397
+
398
+ if (this.#closed) {
399
+ this._cleanupConnection(conn, 'pool_closed');
400
+ return;
401
+ }
402
+
403
+ // Only basic validation here - full validation happens when acquiring
404
+ if (conn.isValid()) {
405
+ this.emit('release', conn);
406
+ this.#idleConnections.push(conn);
407
+ process.nextTick(this.emit.bind(this, '_idle'));
408
+ } else {
409
+ this._cleanupConnection(conn, 'validation_failed');
410
+ }
411
+ }
412
+
413
+ _endLeak(conn) {
414
+ if (conn.leakProcess) {
415
+ clearTimeout(conn.leakProcess);
416
+ conn.leakProcess = null;
417
+ if (conn.leaked) {
418
+ conn.opts.logger.warning(
419
+ `Previous possible leak connection with thread ${conn.info.threadId} was returned to pool`
420
+ );
421
+ }
422
+ }
423
+ }
424
+
425
+ /**
426
+ * Permit to remove idle connection if unused for some time.
427
+ */
428
+ _startConnectionReaping() {
429
+ if (!this.#unusedConnectionRemoverId && this.opts.idleTimeout > 0) {
430
+ this.#unusedConnectionRemoverId = setInterval(this._removeIdleConnections.bind(this), 500);
431
+ }
432
+ }
433
+
434
+ _stopConnectionReaping() {
435
+ if (this.#unusedConnectionRemoverId && this.totalConnections() === 0) {
436
+ clearInterval(this.#unusedConnectionRemoverId);
437
+ }
438
+ }
439
+
440
+ /**
441
+ * Removes idle connections that have been unused for too long
442
+ */
443
+ _removeIdleConnections() {
444
+ const idleTimeRemoval = Date.now() - this.opts.idleTimeout * 1000;
445
+ let maxRemoval = Math.max(0, this.#idleConnections.length - this.opts.minimumIdle);
446
+
447
+ while (maxRemoval > 0) {
448
+ const conn = this.#idleConnections.peek();
449
+ maxRemoval--;
450
+
451
+ if (conn && conn.lastUse < idleTimeRemoval) {
452
+ this.#idleConnections.shift();
453
+ conn.forceEnd(
454
+ null,
455
+ () => {},
456
+ () => {}
457
+ );
458
+ continue;
459
+ }
460
+ break;
461
+ }
462
+
463
+ if (this.totalConnections() === 0) {
464
+ this._stopConnectionReaping();
465
+ }
466
+ this.emit('validateSize');
467
+ }
468
+
469
+ _shouldCreateMoreConnections() {
470
+ return (
471
+ !this.#connectionInCreation &&
472
+ this.#idleConnections.length < this.opts.minimumIdle &&
473
+ this.totalConnections() < this.opts.connectionLimit &&
474
+ !this.#closed
475
+ );
476
+ }
477
+
478
+ /**
479
+ * Processes the next request in the queue if connections are available
480
+ */
481
+ _processNextPendingRequest() {
482
+ clearTimeout(this.#requestTimeoutId);
483
+ this.#requestTimeoutId = null;
484
+
485
+ const request = this.#requests.shift();
486
+ if (!request) return;
487
+
488
+ const conn = this.#idleConnections.shift();
489
+ if (conn) {
490
+ if (this.opts.leakDetectionTimeout > 0) {
491
+ this._startLeakDetection(conn);
492
+ }
493
+ this.#activeConnections[conn.threadId] = conn;
494
+ this.emit('acquire', conn);
495
+ request.resolver(conn);
496
+ } else {
497
+ this.#requests.unshift(request);
498
+ }
499
+
500
+ this._checkRequestTimeouts();
501
+ }
502
+
503
+ _hasIdleConnection() {
504
+ return !this.#idleConnections.isEmpty();
505
+ }
506
+
507
+ /**
508
+ * Acquires an idle connection from the pool
509
+ * @param {Function} callback - Callback function(err, conn)
510
+ */
511
+ _acquireIdleConnection(callback) {
512
+ // Quick check if acquisition is possible
513
+ if (!this._hasIdleConnection() || this.#closed) {
514
+ callback(new Error('No idle connections available'));
515
+ return;
516
+ }
517
+
518
+ this._findValidIdleConnection(callback, false);
519
+ }
520
+
521
+ /**
522
+ * Search info object of an existing connection. to know server type and version.
523
+ * @returns information object if connection available.
524
+ */
525
+ _searchInfo() {
526
+ let info = null;
527
+ let conn = this.#idleConnections.get(0);
528
+
529
+ if (!conn) {
530
+ for (const threadId in Object.keys(this.#activeConnections)) {
531
+ conn = this.#activeConnections[threadId];
532
+ if (!conn) {
533
+ break;
534
+ }
535
+ }
536
+ }
537
+
538
+ if (conn) {
539
+ info = conn.info;
540
+ }
541
+ return info;
542
+ }
543
+
544
+ /**
545
+ * Recursively searches for a valid idle connection
546
+ * @param {Function} callback - Callback function(err, conn)
547
+ * @param {boolean} needPoolSizeCheck - Whether to check pool size after
548
+ */
549
+ _findValidIdleConnection(callback, needPoolSizeCheck) {
550
+ if (this.#idleConnections.isEmpty()) {
551
+ // No more connections to check
552
+ if (needPoolSizeCheck) {
553
+ setImmediate(() => this.emit('validateSize'));
554
+ }
555
+ callback(new Error('No valid connections found'));
556
+ return;
557
+ }
558
+
559
+ const conn = this.#idleConnections.shift();
560
+ this.#activeConnections[conn.threadId] = conn;
561
+ this._validateConnectionHealth(conn, (isValid) => {
562
+ if (isValid) {
563
+ if (this.opts.leakDetectionTimeout > 0) {
564
+ this._startLeakDetection(conn);
565
+ }
566
+
567
+ if (needPoolSizeCheck) {
568
+ setImmediate(() => this.emit('validateSize'));
569
+ }
570
+
571
+ callback(null, conn);
572
+ return;
573
+ } else {
574
+ delete this.#activeConnections[conn.threadId];
575
+ }
576
+
577
+ // Connection failed validation, try next one
578
+ this._findValidIdleConnection(callback, true);
579
+ });
580
+ }
581
+
582
+ /**
583
+ * Validates if a connection is healthy and can be used
584
+ * @param {Connection} conn - The connection to validate
585
+ * @param {Function} callback - Callback function(isValid)
586
+ */
587
+ _validateConnectionHealth(conn, callback) {
588
+ if (!conn) {
589
+ callback(false);
590
+ return;
591
+ }
592
+
593
+ // Skip validation if connection is already invalid or was recently used
594
+ const recentlyUsed = this.opts.minDelayValidation > 0 && Date.now() - conn.lastUse <= this.opts.minDelayValidation;
595
+
596
+ if (!conn.isValid() || recentlyUsed) {
597
+ callback(conn.isValid());
598
+ return;
599
+ }
600
+
601
+ // Perform ping to verify connection is responsive
602
+ const pingOptions = { opts: { timeout: this.opts.pingTimeout } };
603
+ conn.ping(
604
+ pingOptions,
605
+ () => callback(true),
606
+ () => callback(false)
607
+ );
608
+ }
609
+
610
+ _leakedConnections() {
611
+ let counter = 0;
612
+ for (const connection of Object.values(this.#activeConnections)) {
613
+ if (connection && connection.leaked) counter++;
614
+ }
615
+ return counter;
616
+ }
617
+
618
+ _errorMsgAddon() {
619
+ if (this.opts.leakDetectionTimeout > 0) {
620
+ return `\n (pool connections: active=${this.activeConnections()} idle=${this.idleConnections()} leak=${this._leakedConnections()} limit=${
621
+ this.opts.connectionLimit
622
+ })`;
623
+ }
624
+ return `\n (pool connections: active=${this.activeConnections()} idle=${this.idleConnections()} limit=${
625
+ this.opts.connectionLimit
626
+ })`;
627
+ }
628
+
629
+ toString() {
630
+ return `active=${this.activeConnections()} idle=${this.idleConnections()} limit=${this.opts.connectionLimit}`;
631
+ }
632
+
633
+ //*****************************************************************
634
+ // public methods
635
+ //*****************************************************************
636
+
637
+ get closed() {
638
+ return this.#closed;
639
+ }
640
+
641
+ /**
642
+ * Get current total connection number.
643
+ * @return {number}
644
+ */
645
+ totalConnections() {
646
+ return this.activeConnections() + this.idleConnections();
647
+ }
648
+
649
+ /**
650
+ * Get current active connections.
651
+ * @return {number}
652
+ */
653
+ activeConnections() {
654
+ let counter = 0;
655
+ for (const connection of Object.values(this.#activeConnections)) {
656
+ if (connection) counter++;
657
+ }
658
+ return counter;
659
+ }
660
+
661
+ /**
662
+ * Get current idle connection number.
663
+ * @return {number}
664
+ */
665
+ idleConnections() {
666
+ return this.#idleConnections.length;
667
+ }
668
+
669
+ /**
670
+ * Get current stacked connection request.
671
+ * @return {number}
672
+ */
673
+ taskQueueSize() {
674
+ return this.#requests.length;
675
+ }
676
+
677
+ escape(value) {
678
+ return Utils.escape(this.opts.connOptions, this._searchInfo(), value);
679
+ }
680
+
681
+ escapeId(value) {
682
+ return Utils.escapeId(this.opts.connOptions, this._searchInfo(), value);
683
+ }
684
+
685
+ //*****************************************************************
686
+ // promise methods
687
+ //*****************************************************************
688
+
689
+ /**
690
+ * Retrieve a connection from the pool.
691
+ * Create a new one if limit is not reached.
692
+ * wait until acquireTimeout.
693
+ * @param cmdParam for stackTrace error
694
+ * @param {Function} callback - Callback function(err, conn)
695
+ */
696
+ getConnection(cmdParam, callback) {
697
+ if (typeof cmdParam === 'function') {
698
+ callback = cmdParam;
699
+ cmdParam = {};
700
+ }
701
+
702
+ if (this.#closed) {
703
+ const err = Errors.createError(
704
+ 'pool is closed',
705
+ Errors.ER_POOL_ALREADY_CLOSED,
706
+ null,
707
+ 'HY000',
708
+ cmdParam === null ? null : cmdParam.sql,
709
+ false,
710
+ cmdParam.stack
711
+ );
712
+ callback(err);
713
+ return;
714
+ }
715
+
716
+ this._acquireIdleConnection((err, conn) => {
717
+ if (!err && conn) {
718
+ // connection is available
719
+ this.emit('acquire', conn);
720
+ callback(null, conn);
721
+ return;
722
+ }
723
+
724
+ if (this.#closed) {
725
+ callback(
726
+ Errors.createError(
727
+ 'Cannot add request to pool, pool is closed',
728
+ Errors.ER_POOL_ALREADY_CLOSED,
729
+ null,
730
+ 'HY000',
731
+ cmdParam === null ? null : cmdParam.sql,
732
+ false,
733
+ cmdParam.stack
734
+ )
735
+ );
736
+ return;
737
+ }
738
+
739
+ // no idle connection available
740
+ // creates a new connection if the limit is not reached
741
+ setImmediate(this.emit.bind(this, 'validateSize'));
742
+
743
+ // stack request
744
+ setImmediate(this.emit.bind(this, 'enqueue'));
745
+ const request = new Request(
746
+ Date.now() + this.opts.acquireTimeout,
747
+ cmdParam.stack,
748
+ (conn) => callback(null, conn),
749
+ (err) => callback(err)
750
+ );
751
+
752
+ this.#requests.push(request);
753
+
754
+ if (!this.#requestTimeoutId) {
755
+ this.#requestTimeoutId = setTimeout(this._checkRequestTimeouts.bind(this), this.opts.acquireTimeout);
756
+ }
757
+ });
758
+ }
759
+
760
+ /**
761
+ * Close all connection in pool
762
+ * Ends in multiple step :
763
+ * - close idle connections
764
+ * - ensure that no new request is possible
765
+ * (active connection release are automatically closed on release)
766
+ * - if remaining, after 10 seconds, close remaining active connections
767
+ *
768
+ * @return Promise
769
+ */
770
+ end() {
771
+ if (this.#closed) {
772
+ return Promise.reject(Errors.createError('pool is already closed', Errors.ER_POOL_ALREADY_CLOSED));
773
+ }
774
+
775
+ this.#closed = true;
776
+ clearInterval(this.#unusedConnectionRemoverId);
777
+ clearInterval(this._managePoolSizeTask);
778
+ clearTimeout(this._connectionCreationTask);
779
+ clearTimeout(this.#requestTimeoutId);
780
+
781
+ const cmdParam = {};
782
+ if (this.opts.trace) Error.captureStackTrace(cmdParam);
783
+ //close unused connections
784
+ const idleConnectionsEndings = [];
785
+ let conn;
786
+ while ((conn = this.#idleConnections.shift())) {
787
+ idleConnectionsEndings.push(new Promise(conn.forceEnd.bind(conn, cmdParam)));
788
+ }
789
+
790
+ clearTimeout(this.#requestTimeoutId);
791
+ this.#requestTimeoutId = null;
792
+
793
+ //reject all waiting task
794
+ if (!this.#requests.isEmpty()) {
795
+ const err = Errors.createError(
796
+ 'pool is ending, connection request aborted',
797
+ Errors.ER_CLOSING_POOL,
798
+ null,
799
+ 'HY000',
800
+ null,
801
+ false,
802
+ cmdParam.stack
803
+ );
804
+ let task;
805
+ while ((task = this.#requests.shift())) {
806
+ task.reject(err);
807
+ }
808
+ }
809
+ const pool = this;
810
+ return Promise.all(idleConnectionsEndings).then(async () => {
811
+ if (pool.activeConnections() > 0) {
812
+ // wait up to 10 seconds, that active connection are released
813
+ let remaining = 100;
814
+ while (remaining-- > 0) {
815
+ if (pool.activeConnections() > 0) {
816
+ await new Promise((res) => setTimeout(() => res(), 100));
817
+ }
818
+ }
819
+
820
+ // force close any remaining active connections
821
+ for (const connection of Object.values(pool.#activeConnections)) {
822
+ if (connection) connection.destroy();
823
+ }
824
+ }
825
+ return Promise.resolve();
826
+ });
827
+ }
828
+
829
+ _cleanupConnection(conn, reason = '') {
830
+ if (!conn) return;
831
+
832
+ this._endLeak(conn);
833
+ delete this.#activeConnections[conn.threadId];
834
+
835
+ try {
836
+ // using end in case pool ends while connection succeed without still having function wrappers
837
+ const endingFct = conn.forceEnd ? conn.forceEnd : conn.end;
838
+ endingFct.call(
839
+ conn,
840
+ null,
841
+ () => this.emit('connectionClosed', { threadId: conn.threadId, reason }),
842
+ () => {}
843
+ );
844
+ } catch (err) {
845
+ this.emit('error', new Error(`Failed to cleanup connection: ${err.message}`));
846
+ }
847
+
848
+ if (this.totalConnections() === 0) {
849
+ this._stopConnectionReaping();
850
+ }
851
+
852
+ this.emit('validateSize');
853
+ }
854
+
855
+ /**
856
+ * Handles the release of a connection back to the pool
857
+ * @param {Connection} conn - The connection to release
858
+ * @param {Function} callback - Callback function when complete
859
+ */
860
+ _handleRelease(conn, callback) {
861
+ callback = callback || function () {};
862
+
863
+ // Handle special cases first
864
+ if (this.#closed || !conn.isValid()) {
865
+ this._destroy(conn);
866
+ callback();
867
+ return;
868
+ }
869
+
870
+ // Skip transaction state reset if configured
871
+ if (this.opts.noControlAfterUse) {
872
+ this.release(conn);
873
+ callback();
874
+ return;
875
+ }
876
+
877
+ // Reset connection state before returning to pool
878
+ const resetFunction = this._getRevertFunction(conn);
879
+
880
+ resetFunction((err) => {
881
+ if (err) {
882
+ this._destroy(conn);
883
+ } else {
884
+ this.release(conn);
885
+ }
886
+ callback();
887
+ });
888
+ }
889
+
890
+ /**
891
+ * Get the appropriate function to reset connection state
892
+ * @returns {Function} Function that takes a callback
893
+ */
894
+ _getRevertFunction(conn) {
895
+ const canUseReset =
896
+ this.opts.resetAfterUse &&
897
+ conn.info.isMariaDB() &&
898
+ ((conn.info.serverVersion.minor === 2 && conn.info.hasMinVersion(10, 2, 22)) ||
899
+ conn.info.hasMinVersion(10, 3, 13));
900
+
901
+ return canUseReset
902
+ ? (callback) => conn.reset({}, callback)
903
+ : (callback) =>
904
+ conn.changeTransaction(
905
+ { sql: 'ROLLBACK' },
906
+ () => callback(null),
907
+ (err) => callback(err)
908
+ );
909
+ }
910
+
911
+ /**
912
+ * Sets up leak detection for a connection
913
+ * @param {Connection} conn - The connection to monitor
914
+ */
915
+ _startLeakDetection(conn) {
916
+ conn.lastUse = Date.now();
917
+ conn.leaked = false;
918
+
919
+ // Set timeout to detect potential leaks
920
+ conn.leakProcess = setTimeout(
921
+ () => {
922
+ conn.leaked = true;
923
+ const unusedTime = Date.now() - conn.lastUse;
924
+
925
+ // Log warning about potential leak
926
+ conn.opts.logger.warning(
927
+ `A possible connection leak on thread ${conn.info.threadId} ` +
928
+ `(connection not returned to pool for ${unusedTime}ms). ` +
929
+ `Has connection.release() been called?${this._errorMsgAddon()}`
930
+ );
931
+ },
932
+ this.opts.leakDetectionTimeout,
933
+ conn
934
+ );
935
+ }
936
+ }
937
+
938
+ class Request {
939
+ constructor(timeout, stack, resolver, rejecter) {
940
+ this.timeout = timeout;
941
+ this.stack = stack;
942
+ this.resolver = resolver;
943
+ this.rejecter = rejecter;
944
+ }
945
+
946
+ reject(err) {
947
+ process.nextTick(this.rejecter, err);
948
+ }
949
+ }
950
+
951
+ module.exports = Pool;