@pxp-nd/core 1.2.88 → 1.2.90

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,2 +1,63 @@
1
1
  # PXP-ND core
2
- - Core module for pxp-nd
2
+ - Core module for pxp-nd
3
+
4
+ ## 1.2.90
5
+
6
+ Release de resiliencia de conexiones a base de datos. **Sin configurar nada, el comportamiento es idéntico a 1.2.89**: todas las opciones nuevas son opt-in.
7
+
8
+ ### `db.acquireTimeoutMs` (opcional)
9
+
10
+ Tiempo máximo, en milisegundos, que una ruta no-readonly espera para obtener una conexión del pool antes de fallar.
11
+
12
+ ```ts
13
+ new PxpApp({
14
+ // ...
15
+ db: { acquireTimeoutMs: 5000 },
16
+ });
17
+ ```
18
+
19
+ - Si vence, la request responde **503** con el mensaje `Database acquire timeout after N ms`.
20
+ - Solo se dispara cuando el pool está agotado; con conexiones libres no agrega latencia.
21
+ - `undefined` o `0` = comportamiento legacy (espera indefinida).
22
+
23
+ ### Decorador `@Timeout(ms)` y opción de ruta `timeoutMs`
24
+
25
+ Límite de duración por ruta. Se puede usar el decorador o la opción en la definición de la ruta:
26
+
27
+ ```ts
28
+ @Post('/save')
29
+ @Timeout(30000)
30
+ async save(params: any) { /* ... */ }
31
+
32
+ // o bien
33
+ @Post('/save', { timeoutMs: 30000 })
34
+ ```
35
+
36
+ Al vencer el tiempo:
37
+ - La request responde **503**.
38
+ - La conexión física se destruye, por lo que MySQL hace rollback de la transacción abierta.
39
+ - El handler puede seguir ejecutándose en segundo plano, pero ya sin acceso a la base de datos.
40
+ - Se registra la línea de log `[pxp-core] timeout-abandoned <transactionCode>`.
41
+
42
+ Sin decorador ni `timeoutMs`, la ruta no tiene límite (igual que 1.2.89).
43
+
44
+ ### Fix: fuga de conexiones si falla `startTransaction()`
45
+
46
+ Antes, si `startTransaction()` lanzaba un error, la conexión obtenida del pool nunca se liberaba y con el tiempo el pool quedaba agotado. Ahora la conexión se libera siempre.
47
+
48
+ ### Clon del controlador por request (`controllerForRequest`)
49
+
50
+ Portado desde 1.2.89: cada request trabaja sobre un clon del controlador, de modo que requests concurrentes ya no se pisan `user`, `headers` ni `transactionCode`.
51
+
52
+ ### Proyectos afectados
53
+
54
+ Dependen de core con `^1.2.x`, así que tomarán esta versión en su próximo `npm install`:
55
+
56
+ - tempo
57
+ - conductor-nd
58
+ - go-pxp-nd
59
+ - Api_Boa_ND
60
+ - electrica360_nd
61
+ - cleaners-pxp-nd
62
+
63
+ Sin configurar `db.acquireTimeoutMs` ni `@Timeout`/`timeoutMs`, ninguno de ellos cambia de comportamiento respecto a 1.2.89.
@@ -49,5 +49,10 @@ export interface IConfigPxpApp {
49
49
  expressJsonConfig?: IConfigExpress;
50
50
  expressUrlEncodedConfig?: IConfigExpress;
51
51
  permissionFunction?: boolean | ((user: any, transaction: string) => Promise<boolean>);
52
+ /** Database safety options. All optional; when omitted the behaviour is identical to <=1.2.89. */
53
+ db?: {
54
+ /** Max ms to wait for a pool connection before a non-readonly route fails with 503. Undefined/0 = wait forever (legacy). */
55
+ acquireTimeoutMs?: number;
56
+ };
52
57
  }
53
58
  export {};
@@ -12,6 +12,8 @@
12
12
  * Last modified : 2020-10-13 15:36:31
13
13
  * Last modified : 2021-03-10 15:36:31 - Favio Figueroa
14
14
  * Last modified : 2021-05-01 18:35:31 - Favio Figueroa
15
+ * Last modified : 2026-10-08 - Favio Figueroa - EF-23 per-request controller clone (from 1.2.89), startTransaction inside try
16
+ * Last modified : 2026-10-08 - Favio Figueroa - EF-23 acquire timeout + @Timeout decorator
15
17
  */
16
18
  import { EntityManager } from 'typeorm';
17
19
  import { Request, Response, NextFunction } from 'express';
@@ -37,8 +39,8 @@ export declare class Controller implements ControllerInterface {
37
39
  private basicReadOnly;
38
40
  constructor(module: string, Entity?: any, config?: IConfigPxpApp, io?: any);
39
41
  private initializeRoutes;
40
- genericMethodWrapper(params: Record<string, unknown>, req: Request, next: NextFunction, res: Response, methodName: string, dbsettings: string, readonly: boolean, permission: boolean, log: boolean, logConfig: {}, isHtml: boolean, isFile: boolean): Promise<void>;
41
- ormMethodWrapper(params: Record<string, unknown>, req: Request | any, next: NextFunction, res: Response, methodName: string, readonly: boolean, permission?: boolean, log?: boolean, logConfig?: {}, isHtml?: boolean, isFile?: boolean): Promise<void>;
42
+ genericMethodWrapper(params: Record<string, unknown>, req: Request, next: NextFunction, res: Response, methodName: string, dbsettings: string, readonly: boolean, permission: boolean, log: boolean, logConfig: {}, isHtml: boolean, isFile: boolean, timeoutMs?: number): Promise<void>;
43
+ ormMethodWrapper(params: Record<string, unknown>, req: Request | any, next: NextFunction, res: Response, methodName: string, readonly: boolean, permission?: boolean, log?: boolean, logConfig?: {}, isHtml?: boolean, isFile?: boolean, timeoutMs?: number): Promise<void>;
42
44
  list(params: Record<string, unknown>): Promise<unknown>;
43
45
  add(params: Record<string, unknown>, manager: EntityManager): Promise<unknown>;
44
46
  edit(params: Record<string, unknown>, manager: EntityManager): Promise<unknown>;
@@ -37,6 +37,8 @@ exports.Controller = void 0;
37
37
  * Last modified : 2020-10-13 15:36:31
38
38
  * Last modified : 2021-03-10 15:36:31 - Favio Figueroa
39
39
  * Last modified : 2021-05-01 18:35:31 - Favio Figueroa
40
+ * Last modified : 2026-10-08 - Favio Figueroa - EF-23 per-request controller clone (from 1.2.89), startTransaction inside try
41
+ * Last modified : 2026-10-08 - Favio Figueroa - EF-23 acquire timeout + @Timeout decorator
40
42
  */
41
43
  const typeorm_1 = require("typeorm");
42
44
  const class_validator_1 = require("class-validator");
@@ -51,6 +53,7 @@ const ParseParams_1 = require("./middlewares/ParseParams");
51
53
  const isReportMiddleware_1 = require("./middlewares/isReportMiddleware");
52
54
  const pdf_1 = require("../reports/pdf");
53
55
  const xlsx_1 = require("../reports/xlsx");
56
+ const DbTimeouts_1 = require("./utils/DbTimeouts");
54
57
  // import * as entities from '@pxp-nd/entities';
55
58
  // const pxpEntities: any = entities;
56
59
  class Controller {
@@ -128,6 +131,8 @@ class Controller {
128
131
  // get type return
129
132
  let ishtml = Reflect.getMetadata('ishtml', this.constructor) || {};
130
133
  let isfile = Reflect.getMetadata('isfile', this.constructor) || {};
134
+ // get request timeouts (@Timeout)
135
+ const timeouts = Reflect.getMetadata('timeout', this.constructor) || {};
131
136
  const defaultConfig = Reflect.getMetadata('optionsRoute', this.constructor) || {};
132
137
  // define basic routes
133
138
  if (this.modelString !== '') {
@@ -146,6 +151,8 @@ class Controller {
146
151
  const isHtmlMethod = ishtml[route.methodName] !== undefined ? ishtml[route.methodName] : (defaultConfig[route.methodName] ? defaultConfig[route.methodName].ishtml : false);
147
152
  // is file
148
153
  const isFileMethod = isfile[route.methodName] !== undefined ? isfile[route.methodName] : (defaultConfig[route.methodName] ? defaultConfig[route.methodName].isfile : false);
154
+ // request timeout (ms), undefined = no timeout
155
+ const timeoutMethod = timeouts[route.methodName] !== undefined ? timeouts[route.methodName] : (defaultConfig[route.methodName] ? defaultConfig[route.methodName].timeoutMs : undefined);
149
156
  if (readOnlyMethod === null || readOnlyMethod === undefined) {
150
157
  throw new index_1.PxpError(500, 'ReadOnly decorator was not defined for ' +
151
158
  route.methodName +
@@ -164,7 +171,8 @@ class Controller {
164
171
  // Execute our method for this path and pass our express request and response object.
165
172
  // const params = { ...req.query, ...req.body, ...req.params };
166
173
  const params = Object.assign(Object.assign({}, req.files), req.paramasMerge);
167
- const controllerForRequest = Object.assign({}, this);
174
+ const controllerForRequest = Object.create(Object.getPrototypeOf(this));
175
+ Object.assign(controllerForRequest, this);
168
176
  controllerForRequest.user = req.user || null;
169
177
  controllerForRequest.headers = req.headers;
170
178
  controllerForRequest.pxpParams = req.pxpParams;
@@ -174,13 +182,13 @@ class Controller {
174
182
  .toLowerCase();
175
183
  controllerForRequest.validated = false;
176
184
  try {
177
- await index_1.__(this.genericMethodWrapper.call(controllerForRequest, params, req, next, res, route.methodName, methodDbSettings, readOnlyMethod, false, logKey, logValue, isHtmlMethod, isFileMethod));
185
+ await index_1.__(controllerForRequest.genericMethodWrapper(params, req, next, res, route.methodName, methodDbSettings, readOnlyMethod, false, logKey, logValue, isHtmlMethod, isFileMethod, timeoutMethod));
178
186
  }
179
187
  catch (ex) {
180
188
  const now = new Date();
181
189
  const iniAt = req.start;
182
190
  const endsAt = now.valueOf() - iniAt.valueOf();
183
- res.logId = await index_1.__(index_1.insertLog('nouser', 'mac', req.ip, 'error', ex.tecMessage, this.module, this.transactionCode, '', // query
191
+ res.logId = await index_1.__(index_1.insertLog('nouser', 'mac', req.ip, 'error', ex.tecMessage, controllerForRequest.module, controllerForRequest.transactionCode, '', // query
184
192
  params, ex.stack, ex.statusCode, endsAt, logValue));
185
193
  index_1.errorMiddleware(ex, req, res);
186
194
  }
@@ -197,7 +205,8 @@ class Controller {
197
205
  ], async (req, res, next) => {
198
206
  // Execute our method for this path and pass our express request and response object.
199
207
  const params = Object.assign(Object.assign({}, req.files), req.paramasMerge);
200
- const controllerForRequest = Object.assign({}, this);
208
+ const controllerForRequest = Object.create(Object.getPrototypeOf(this));
209
+ Object.assign(controllerForRequest, this);
201
210
  controllerForRequest.user = req.user || null;
202
211
  controllerForRequest.headers = req.headers;
203
212
  controllerForRequest.pxpParams = req.pxpParams;
@@ -207,13 +216,13 @@ class Controller {
207
216
  .toLowerCase();
208
217
  controllerForRequest.validated = false;
209
218
  try {
210
- await index_1.__(this.genericMethodWrapper.call(controllerForRequest, params, req, next, res, route.methodName, methodDbSettings, readOnlyMethod, permission[route.methodName], logKey, logValue, isHtmlMethod, isFileMethod));
219
+ await index_1.__(controllerForRequest.genericMethodWrapper(params, req, next, res, route.methodName, methodDbSettings, readOnlyMethod, permission[route.methodName], logKey, logValue, isHtmlMethod, isFileMethod, timeoutMethod));
211
220
  }
212
221
  catch (ex) {
213
222
  const now = new Date();
214
223
  const iniAt = req.start;
215
224
  const endsAt = now.valueOf() - iniAt.valueOf();
216
- res.logId = (await index_1.__(index_1.insertLog(controllerForRequest.user && controllerForRequest.user.username ? controllerForRequest.user.username : 'nouser', 'mac', req.ip, 'error', ex.tecMessage, this.module, this.transactionCode, '', // query
225
+ res.logId = (await index_1.__(index_1.insertLog(controllerForRequest.user && controllerForRequest.user.username ? controllerForRequest.user.username : 'nouser', 'mac', req.ip, 'error', ex.tecMessage, controllerForRequest.module, controllerForRequest.transactionCode, '', // query
217
226
  params, ex.stack, ex.statusCode, endsAt, logValue)));
218
227
  index_1.errorMiddleware(ex, req, res);
219
228
  }
@@ -221,9 +230,9 @@ class Controller {
221
230
  }
222
231
  });
223
232
  }
224
- async genericMethodWrapper(params, req, next, res, methodName, dbsettings, readonly, permission = true, log = true, logConfig = {}, isHtml, isFile) {
233
+ async genericMethodWrapper(params, req, next, res, methodName, dbsettings, readonly, permission = true, log = true, logConfig = {}, isHtml, isFile, timeoutMs) {
225
234
  if (dbsettings === 'Orm') {
226
- await index_1.__(this.ormMethodWrapper(params, req, next, res, methodName, readonly, permission, log, logConfig, isHtml, isFile));
235
+ await index_1.__(this.ormMethodWrapper(params, req, next, res, methodName, readonly, permission, log, logConfig, isHtml, isFile, timeoutMs));
227
236
  }
228
237
  else if (dbsettings === 'Procedure') {
229
238
  await index_1.__(this.procedureMethodWrapper(params, next, res, methodName, readonly, permission, log, logConfig));
@@ -232,7 +241,8 @@ class Controller {
232
241
  await this.sqlMethodWrapper(params, next, res, methodName, readonly, permission, log, logConfig);
233
242
  }
234
243
  }
235
- async ormMethodWrapper(params, req, next, res, methodName, readonly, permission = true, log = true, logConfig = {}, isHtml = false, isFile = false) {
244
+ async ormMethodWrapper(params, req, next, res, methodName, readonly, permission = true, log = true, logConfig = {}, isHtml = false, isFile = false, timeoutMs) {
245
+ var _a;
236
246
  let metResponse;
237
247
  if (permission) {
238
248
  if (this.user) {
@@ -248,19 +258,55 @@ class Controller {
248
258
  else {
249
259
  const connection = typeorm_1.getConnection(process.env.DB_WRITE_CONNECTION_NAME);
250
260
  const queryRunner = connection.createQueryRunner();
251
- // establish real database connection using our new query runner
252
- await index_1.__(queryRunner.connect());
253
- await index_1.__(queryRunner.startTransaction());
261
+ // establish real database connection using our new query runner.
262
+ // `db` is added to IConfigPxpApp by a later change; without it this is a plain connect().
263
+ const acquireTimeoutMs = (_a = this.config.db) === null || _a === void 0 ? void 0 : _a.acquireTimeoutMs;
264
+ await index_1.__(DbTimeouts_1.connectWithTimeout(queryRunner, acquireTimeoutMs, this.transactionCode));
265
+ let timedOut = false;
266
+ let timer;
254
267
  try {
255
- metResponse = (await eval(`this.${methodName}(params, queryRunner.manager, res, req)`));
268
+ // Start the transaction inside try so a failure here still releases the connection
269
+ await index_1.__(queryRunner.startTransaction());
270
+ const work = Promise.resolve(eval(`this.${methodName}(params, queryRunner.manager, res, req)`));
271
+ if (timeoutMs && timeoutMs > 0) {
272
+ metResponse = await Promise.race([
273
+ work,
274
+ new Promise((_, reject) => {
275
+ timer = setTimeout(() => {
276
+ timedOut = true;
277
+ reject(new DbTimeouts_1.DbTimeoutError('request', timeoutMs, this.transactionCode));
278
+ }, timeoutMs);
279
+ })
280
+ ]);
281
+ }
282
+ else {
283
+ metResponse = await work;
284
+ }
256
285
  await queryRunner.commitTransaction();
257
286
  }
258
287
  catch (err) {
259
- await queryRunner.rollbackTransaction();
288
+ // On request timeout skip rollback: it would queue behind the abandoned statement and delay the 503 (destroy rolls back server-side).
289
+ if (!timedOut && queryRunner.isTransactionActive) {
290
+ try {
291
+ await queryRunner.rollbackTransaction();
292
+ }
293
+ catch (_) {
294
+ // keep the original error; the connection is released or destroyed below
295
+ }
296
+ }
260
297
  throw err;
261
298
  }
262
299
  finally {
263
- await index_1.__(queryRunner.release());
300
+ if (timer)
301
+ clearTimeout(timer);
302
+ if (timedOut) {
303
+ // The handler may keep running in background; its connection is destroyed so it cannot commit.
304
+ console.warn(`[pxp-core] timeout-abandoned ${this.transactionCode}`);
305
+ await DbTimeouts_1.destroyQueryRunner(queryRunner);
306
+ }
307
+ else {
308
+ await index_1.__(queryRunner.release());
309
+ }
264
310
  }
265
311
  }
266
312
  if (log) {
@@ -8,6 +8,7 @@ export declare interface IOptionsRoute {
8
8
  dbSettings?: 'Procedure' | 'Orm' | 'Query';
9
9
  authentication?: boolean;
10
10
  log?: boolean | ILog;
11
+ timeoutMs?: number;
11
12
  }
12
13
  declare const Get: (path?: string, options?: IOptionsRoute) => (target: any, propertyKey: string) => void;
13
14
  declare const Post: (path?: string, options?: IOptionsRoute) => (target: any, propertyKey: string) => void;
@@ -19,9 +20,10 @@ declare const Model: (model: string) => ClassDecorator;
19
20
  declare const StoredProcedure: (storedProcedure: string) => ClassDecorator;
20
21
  declare const Authentication: (authentication?: boolean) => (target: any, propertyKey: string) => void;
21
22
  declare const ReadOnly: (ronly?: boolean) => (target: any, propertyKey: string) => void;
23
+ declare const Timeout: (ms: number) => (target: any, propertyKey: string) => void;
22
24
  declare const IsHtml: (ishtml?: boolean) => (target: any, propertyKey: string) => void;
23
25
  declare const IsFile: (isFile?: boolean) => (target: any, propertyKey: string) => void;
24
26
  declare const DbSettings: (modelType: 'Procedure' | 'Orm' | 'Query') => (target: any, propertyKey: string) => void;
25
27
  declare const Permission: (permission?: boolean) => (target: any, propertyKey: string) => void;
26
28
  declare const Log: (log?: boolean, config?: Record<string, unknown>) => (target: any, propertyKey: string) => void;
27
- export { Get, Post, Put, Delete, Patch, Route, Authentication, Log, Permission, DbSettings, ReadOnly, IsHtml, IsFile, Model, StoredProcedure };
29
+ export { Get, Post, Put, Delete, Patch, Route, Authentication, Log, Permission, DbSettings, ReadOnly, Timeout, IsHtml, IsFile, Model, StoredProcedure };
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.StoredProcedure = exports.Model = exports.IsFile = exports.IsHtml = exports.ReadOnly = exports.DbSettings = exports.Permission = exports.Log = exports.Authentication = exports.Route = exports.Patch = exports.Delete = exports.Put = exports.Post = exports.Get = void 0;
3
+ exports.StoredProcedure = exports.Model = exports.IsFile = exports.IsHtml = exports.Timeout = exports.ReadOnly = exports.DbSettings = exports.Permission = exports.Log = exports.Authentication = exports.Route = exports.Patch = exports.Delete = exports.Put = exports.Post = exports.Get = void 0;
4
4
  /**
5
5
  * Kplian Ltda 2020
6
6
  *
@@ -13,6 +13,7 @@ exports.StoredProcedure = exports.Model = exports.IsFile = exports.IsHtml = expo
13
13
  *
14
14
  * Created at : 2020-06-13 18:09:48
15
15
  * Last modified : 2020-09-17 18:29:51
16
+ * Last modified : 2026-10-08 - Favio Figueroa - EF-23 acquire timeout + @Timeout decorator
16
17
  */
17
18
  const RouteDefinition_1 = require("./RouteDefinition");
18
19
  ;
@@ -102,6 +103,10 @@ const Authentication = (authentication = true) => (target, propertyKey) => setPr
102
103
  exports.Authentication = Authentication;
103
104
  const ReadOnly = (ronly = true) => (target, propertyKey) => setProperty(target, propertyKey)(ronly, 'readonly');
104
105
  exports.ReadOnly = ReadOnly;
106
+ // Request timeout (ms) for a write transaction: on expiry the request fails with 503,
107
+ // the transaction is rolled back and the physical connection is destroyed.
108
+ const Timeout = (ms) => (target, propertyKey) => setProperty(target, propertyKey)(ms, 'timeout');
109
+ exports.Timeout = Timeout;
105
110
  const IsHtml = (ishtml = false) => (target, propertyKey) => setProperty(target, propertyKey)(ishtml, 'ishtml');
106
111
  exports.IsHtml = IsHtml;
107
112
  const IsFile = (isFile = false) => (target, propertyKey) => setProperty(target, propertyKey)(isFile, 'isfile');
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Kplian Ltda 2020
3
+ *
4
+ * MIT
5
+ *
6
+ * Database timeout helpers.
7
+ *
8
+ * @summary Connection acquire timeout, request timeout error and physical connection destroy.
9
+ * @author Favio Figueroa
10
+ *
11
+ * Created at : 2026-10-08 - Favio Figueroa - EF-23 acquire timeout + @Timeout decorator
12
+ */
13
+ import { QueryRunner } from 'typeorm';
14
+ import { PxpError } from '../PxpError';
15
+ export declare class DbTimeoutError extends PxpError {
16
+ kind: 'acquire' | 'request';
17
+ timeoutMs: number;
18
+ transactionCode: string | undefined;
19
+ constructor(kind: 'acquire' | 'request', ms: number, transactionCode?: string);
20
+ }
21
+ /**
22
+ * Destroys the physical driver connection behind the query runner (so it never
23
+ * goes back to the pool with an open transaction) and then releases the runner.
24
+ * mysql2 removes a destroyed connection from its pool on its own.
25
+ */
26
+ export declare function destroyQueryRunner(queryRunner: QueryRunner): Promise<void>;
27
+ /**
28
+ * Acquires the query runner connection with an upper bound.
29
+ * When ms is undefined or <= 0 this is exactly `await queryRunner.connect()`.
30
+ */
31
+ export declare function connectWithTimeout(queryRunner: QueryRunner, ms: number | undefined, transactionCode?: string): Promise<void>;
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.connectWithTimeout = exports.destroyQueryRunner = exports.DbTimeoutError = void 0;
4
+ const PxpError_1 = require("../PxpError");
5
+ class DbTimeoutError extends PxpError_1.PxpError {
6
+ constructor(kind, ms, transactionCode) {
7
+ super(503, `Database ${kind} timeout after ${ms} ms`);
8
+ this.kind = kind;
9
+ this.timeoutMs = ms;
10
+ this.transactionCode = transactionCode;
11
+ }
12
+ }
13
+ exports.DbTimeoutError = DbTimeoutError;
14
+ /**
15
+ * Destroys the physical driver connection behind the query runner (so it never
16
+ * goes back to the pool with an open transaction) and then releases the runner.
17
+ * mysql2 removes a destroyed connection from its pool on its own.
18
+ */
19
+ async function destroyQueryRunner(queryRunner) {
20
+ const databaseConnection = queryRunner.databaseConnection;
21
+ if (databaseConnection && typeof databaseConnection.destroy === 'function') {
22
+ databaseConnection.destroy();
23
+ }
24
+ try {
25
+ await queryRunner.release();
26
+ }
27
+ catch (_) {
28
+ // the connection was already destroyed; release failures are expected here
29
+ }
30
+ }
31
+ exports.destroyQueryRunner = destroyQueryRunner;
32
+ /**
33
+ * Acquires the query runner connection with an upper bound.
34
+ * When ms is undefined or <= 0 this is exactly `await queryRunner.connect()`.
35
+ */
36
+ async function connectWithTimeout(queryRunner, ms, transactionCode) {
37
+ if (!ms || ms <= 0) {
38
+ await queryRunner.connect();
39
+ return;
40
+ }
41
+ let timer;
42
+ let timedOut = false;
43
+ const connectPromise = queryRunner.connect();
44
+ const timeoutPromise = new Promise((_, reject) => {
45
+ timer = setTimeout(() => {
46
+ timedOut = true;
47
+ reject(new DbTimeoutError('acquire', ms, transactionCode));
48
+ }, ms);
49
+ });
50
+ try {
51
+ await Promise.race([connectPromise, timeoutPromise]);
52
+ }
53
+ catch (err) {
54
+ if (timedOut) {
55
+ // A late connection must be destroyed, not leaked into the pool.
56
+ connectPromise.then(() => destroyQueryRunner(queryRunner)).catch(() => { });
57
+ }
58
+ throw err;
59
+ }
60
+ finally {
61
+ if (timer)
62
+ clearTimeout(timer);
63
+ }
64
+ }
65
+ exports.connectWithTimeout = connectWithTimeout;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pxp-nd/core",
3
- "version": "1.2.88",
3
+ "version": "1.2.90",
4
4
  "description": "Contains main files for pxp-nd",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",