@catbee/utils 0.0.8-rc.0 → 0.0.8-rc.1
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 +25 -1
- package/build/esm/utils/decorators.utils.d.ts +196 -3
- package/build/esm/utils/decorators.utils.js +447 -56
- package/build/esm/utils/decorators.utils.js.map +1 -1
- package/build/esnext/utils/decorators.utils.d.ts +196 -3
- package/build/esnext/utils/decorators.utils.js +389 -16
- package/build/esnext/utils/decorators.utils.js.map +1 -1
- package/build/src/utils/decorators.utils.d.ts +196 -3
- package/build/src/utils/decorators.utils.js +397 -17
- package/build/src/utils/decorators.utils.js.map +1 -1
- package/package.json +2 -2
|
@@ -23,19 +23,29 @@
|
|
|
23
23
|
* SOFTWARE.
|
|
24
24
|
*/
|
|
25
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
-
exports.Res = exports.Req = exports.Body = exports.Param = exports.Query = exports.Connect = exports.Trace = exports.Head = exports.Options = exports.Delete = exports.Patch = exports.Put = exports.Post = exports.Get = void 0;
|
|
26
|
+
exports.rateLimiterCache = exports.Res = exports.Req = exports.Body = exports.Param = exports.Query = exports.Connect = exports.Trace = exports.Head = exports.Options = exports.Delete = exports.Patch = exports.Put = exports.Post = exports.Get = exports.RateLimiterCache = void 0;
|
|
27
27
|
exports.Controller = Controller;
|
|
28
28
|
exports.Use = Use;
|
|
29
29
|
exports.HttpCode = HttpCode;
|
|
30
30
|
exports.Header = Header;
|
|
31
|
+
exports.Headers = Headers;
|
|
31
32
|
exports.Before = Before;
|
|
32
33
|
exports.After = After;
|
|
33
34
|
exports.Roles = Roles;
|
|
34
35
|
exports.Redirect = Redirect;
|
|
36
|
+
exports.Cache = Cache;
|
|
37
|
+
exports.RateLimit = RateLimit;
|
|
38
|
+
exports.ContentType = ContentType;
|
|
39
|
+
exports.Version = Version;
|
|
40
|
+
exports.Timeout = Timeout;
|
|
41
|
+
exports.Log = Log;
|
|
35
42
|
exports.registerControllers = registerControllers;
|
|
36
43
|
require("reflect-metadata");
|
|
44
|
+
const logger_utils_1 = require("./logger.utils");
|
|
45
|
+
const response_utils_1 = require("./response.utils");
|
|
37
46
|
const http_status_codes_1 = require("./http-status-codes");
|
|
38
|
-
const
|
|
47
|
+
const express_rate_limit_1 = require("express-rate-limit");
|
|
48
|
+
const cache_utils_1 = require("./cache.utils");
|
|
39
49
|
// Metadata keys
|
|
40
50
|
const ROUTES_KEY = Symbol('routes');
|
|
41
51
|
const MIDDLEWARE_KEY = Symbol('middlewares');
|
|
@@ -46,6 +56,67 @@ const BEFORE_KEY = Symbol('before');
|
|
|
46
56
|
const AFTER_KEY = Symbol('after');
|
|
47
57
|
const ROLES_KEY = Symbol('roles');
|
|
48
58
|
const REDIRECT_KEY = Symbol('redirect');
|
|
59
|
+
const CACHE_KEY = Symbol('cache');
|
|
60
|
+
const RATE_LIMIT_KEY = Symbol('rateLimit');
|
|
61
|
+
const CONTENT_TYPE_KEY = Symbol('contentType');
|
|
62
|
+
const VERSION_KEY = Symbol('version');
|
|
63
|
+
const TIMEOUT_KEY = Symbol('timeout');
|
|
64
|
+
const LOG_KEY = Symbol('log');
|
|
65
|
+
/**
|
|
66
|
+
* RateLimiter cache that uses TTLCache for automatic TTL and LRU handling.
|
|
67
|
+
*/
|
|
68
|
+
class RateLimiterCache {
|
|
69
|
+
constructor(maxSize = 100, ttlMs = 5 * 60 * 1000) {
|
|
70
|
+
this.cache = new cache_utils_1.TTLCache({
|
|
71
|
+
maxSize,
|
|
72
|
+
ttlMs
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
generateKey(options) {
|
|
76
|
+
return `${options.max}:${options.windowMs}:${options.standardHeaders}:${options.legacyHeaders}`;
|
|
77
|
+
}
|
|
78
|
+
get(options) {
|
|
79
|
+
const key = this.generateKey(options);
|
|
80
|
+
const cached = this.cache.get(key);
|
|
81
|
+
if (cached) {
|
|
82
|
+
return cached.limiter;
|
|
83
|
+
}
|
|
84
|
+
// Create new limiter
|
|
85
|
+
const limiter = (0, express_rate_limit_1.rateLimit)(Object.assign(Object.assign({}, options), { handler: (req, res) => {
|
|
86
|
+
const errorResponse = (0, response_utils_1.createFinalErrorResponse)(req, http_status_codes_1.HttpStatusCodes.TOO_MANY_REQUESTS, 'Too Many Requests');
|
|
87
|
+
res.status(http_status_codes_1.HttpStatusCodes.TOO_MANY_REQUESTS).json(errorResponse);
|
|
88
|
+
} }));
|
|
89
|
+
// Store in cache
|
|
90
|
+
this.cache.set(key, {
|
|
91
|
+
limiter,
|
|
92
|
+
config: key
|
|
93
|
+
});
|
|
94
|
+
return limiter;
|
|
95
|
+
}
|
|
96
|
+
clear() {
|
|
97
|
+
this.cache.clear();
|
|
98
|
+
}
|
|
99
|
+
size() {
|
|
100
|
+
return this.cache.size();
|
|
101
|
+
}
|
|
102
|
+
destroy() {
|
|
103
|
+
this.cache.destroy();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
exports.RateLimiterCache = RateLimiterCache;
|
|
107
|
+
// Global cache instance
|
|
108
|
+
const rateLimiterCache = new RateLimiterCache();
|
|
109
|
+
exports.rateLimiterCache = rateLimiterCache;
|
|
110
|
+
function normalizeHeaderValue(value) {
|
|
111
|
+
if (typeof value === 'undefined')
|
|
112
|
+
return undefined;
|
|
113
|
+
if (typeof value === 'string')
|
|
114
|
+
return value;
|
|
115
|
+
if (Array.isArray(value) && value.every(item => typeof item === 'string')) {
|
|
116
|
+
return value;
|
|
117
|
+
}
|
|
118
|
+
return String(value);
|
|
119
|
+
}
|
|
49
120
|
/**
|
|
50
121
|
* Factory function that creates HTTP method decorators.
|
|
51
122
|
*
|
|
@@ -308,8 +379,8 @@ function HttpCode(status) {
|
|
|
308
379
|
/**
|
|
309
380
|
* Decorator that adds a custom HTTP header to the response.
|
|
310
381
|
*
|
|
311
|
-
* @param
|
|
312
|
-
* @param value - Header value
|
|
382
|
+
* @param header - Header name-value pairs or a single header name and value
|
|
383
|
+
* @param value - Header value if a single header name is provided
|
|
313
384
|
* @returns Method decorator
|
|
314
385
|
*
|
|
315
386
|
* @example
|
|
@@ -320,13 +391,44 @@ function HttpCode(status) {
|
|
|
320
391
|
* // Response will include the Cache-Control header
|
|
321
392
|
* return { data: '...' };
|
|
322
393
|
* }
|
|
323
|
-
* ```
|
|
324
394
|
*/
|
|
325
395
|
function Header(name, value) {
|
|
326
|
-
return (
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
396
|
+
return Headers(name, value);
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Decorator that adds a custom HTTP headers to the response.
|
|
400
|
+
*
|
|
401
|
+
* @param headers - Header name-value pairs or a single header name and value
|
|
402
|
+
* @param value - Header value if a single header name is provided
|
|
403
|
+
* @returns Method decorator
|
|
404
|
+
*
|
|
405
|
+
* @example
|
|
406
|
+
* ```ts
|
|
407
|
+
* @Get('/data')
|
|
408
|
+
* @Headers('Cache-Control', 'max-age=60')
|
|
409
|
+
* getData() {
|
|
410
|
+
* // Response will include the Cache-Control header
|
|
411
|
+
* return { data: '...' };
|
|
412
|
+
* }
|
|
413
|
+
*
|
|
414
|
+
* @Get('/data/:id')
|
|
415
|
+
* @Headers({
|
|
416
|
+
* 'Cache-Control': 'max-age=60',
|
|
417
|
+
* 'X-Custom-Header': 'custom-value',
|
|
418
|
+
* 'Content-Security-Policy': "default-src 'self'"
|
|
419
|
+
* })
|
|
420
|
+
* getData() {
|
|
421
|
+
* // Response will include all specified headers
|
|
422
|
+
* return { data: '...' };
|
|
423
|
+
* }
|
|
424
|
+
*
|
|
425
|
+
* ```
|
|
426
|
+
*/
|
|
427
|
+
function Headers(headers, value) {
|
|
428
|
+
return (target, propertyKey) => {
|
|
429
|
+
const existing = Reflect.getMetadata(HEADER_KEY, target, propertyKey) || {};
|
|
430
|
+
const newHeaders = typeof headers === 'string' ? { [headers]: value } : headers;
|
|
431
|
+
Reflect.defineMetadata(HEADER_KEY, Object.assign(Object.assign({}, existing), newHeaders), target, propertyKey);
|
|
330
432
|
};
|
|
331
433
|
}
|
|
332
434
|
/**
|
|
@@ -428,6 +530,162 @@ function Redirect(url, statusCode = 302) {
|
|
|
428
530
|
return descriptor;
|
|
429
531
|
};
|
|
430
532
|
}
|
|
533
|
+
/**
|
|
534
|
+
* Decorator that adds caching to a route response.
|
|
535
|
+
*
|
|
536
|
+
* @param ttlSeconds - Time to live in seconds for the cache
|
|
537
|
+
* @returns Method decorator
|
|
538
|
+
*
|
|
539
|
+
* @example
|
|
540
|
+
* ```ts
|
|
541
|
+
* @Get('/data')
|
|
542
|
+
* @Cache(300) // Cache for 5 minutes
|
|
543
|
+
* getData() {
|
|
544
|
+
* return { data: 'expensive operation result' };
|
|
545
|
+
* }
|
|
546
|
+
* ```
|
|
547
|
+
*/
|
|
548
|
+
function Cache(ttlSeconds) {
|
|
549
|
+
return (target, propertyKey, _descriptor) => {
|
|
550
|
+
Reflect.defineMetadata(CACHE_KEY, { ttlSeconds }, target, propertyKey);
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Decorator that applies rate limiting to a route.
|
|
555
|
+
* Note: Requires 'express-rate-limit' package to be installed.
|
|
556
|
+
*
|
|
557
|
+
* @param limit - Maximum number of requests allowed in the window
|
|
558
|
+
* @param windowMs - Time window in milliseconds
|
|
559
|
+
* @returns Method decorator
|
|
560
|
+
*
|
|
561
|
+
* Default Options:
|
|
562
|
+
* - standardHeaders: true
|
|
563
|
+
* - legacyHeaders: false
|
|
564
|
+
*
|
|
565
|
+
* @example
|
|
566
|
+
* ```ts
|
|
567
|
+
* @Post('/login')
|
|
568
|
+
* @RateLimit({ max: 5, windowMs: 60000, standardHeaders: true, legacyHeaders: false }) // 5 requests per minute
|
|
569
|
+
* login(@Body() credentials: LoginDto) {
|
|
570
|
+
* return this.authService.login(credentials);
|
|
571
|
+
* }
|
|
572
|
+
* ```
|
|
573
|
+
*/
|
|
574
|
+
function RateLimit(options) {
|
|
575
|
+
const opts = Object.assign({ standardHeaders: true, legacyHeaders: false }, options);
|
|
576
|
+
return (target, propertyKey, _descriptor) => {
|
|
577
|
+
Reflect.defineMetadata(RATE_LIMIT_KEY, opts, target, propertyKey);
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Decorator that sets the content type for the response.
|
|
582
|
+
*
|
|
583
|
+
* @param type - MIME type for the response
|
|
584
|
+
* @returns Method decorator
|
|
585
|
+
*
|
|
586
|
+
* @example
|
|
587
|
+
* ```ts
|
|
588
|
+
* @Get('/download')
|
|
589
|
+
* @ContentType('application/pdf')
|
|
590
|
+
* downloadPdf() {
|
|
591
|
+
* return this.fileService.generatePdf();
|
|
592
|
+
* }
|
|
593
|
+
* ```
|
|
594
|
+
*/
|
|
595
|
+
function ContentType(type) {
|
|
596
|
+
return (target, propertyKey, _descriptor) => {
|
|
597
|
+
Reflect.defineMetadata(CONTENT_TYPE_KEY, { type }, target, propertyKey);
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Decorator that adds API versioning to a route.
|
|
602
|
+
*
|
|
603
|
+
* @param version - Version string for the API endpoint
|
|
604
|
+
* @param options - Versioning options
|
|
605
|
+
* @returns Method decorator
|
|
606
|
+
*
|
|
607
|
+
* Default Options:
|
|
608
|
+
* - addPrefix: true
|
|
609
|
+
* - addHeader: true
|
|
610
|
+
* - headerName: 'X-API-Version'
|
|
611
|
+
*
|
|
612
|
+
* @example
|
|
613
|
+
* ```ts
|
|
614
|
+
*
|
|
615
|
+
* @Get('/users')
|
|
616
|
+
* @Version('v2')
|
|
617
|
+
* getUsersV2() {
|
|
618
|
+
* return this.userService.findAllV2();
|
|
619
|
+
* }
|
|
620
|
+
*
|
|
621
|
+
* @Get('/users')
|
|
622
|
+
* @Version('v2', { addPrefix: true, addHeader: true, headerName: 'X-API-Version' })
|
|
623
|
+
* getUsersV2() {
|
|
624
|
+
* // Route becomes /v2/users
|
|
625
|
+
* return this.userService.findAllV2();
|
|
626
|
+
* }
|
|
627
|
+
* ```
|
|
628
|
+
*/
|
|
629
|
+
function Version(version, options) {
|
|
630
|
+
const opts = Object.assign({ addPrefix: true, addHeader: true, headerName: 'X-API-Version' }, options);
|
|
631
|
+
return (target, propertyKey, _descriptor) => {
|
|
632
|
+
Reflect.defineMetadata(VERSION_KEY, { version, options: opts }, target, propertyKey);
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Decorator that sets a timeout for route execution.
|
|
637
|
+
*
|
|
638
|
+
* @param ms - Timeout in milliseconds
|
|
639
|
+
* @returns Method decorator
|
|
640
|
+
*
|
|
641
|
+
* @example
|
|
642
|
+
* ```ts
|
|
643
|
+
* @Get('/slow-operation')
|
|
644
|
+
* @Timeout(30000) // 30 second timeout
|
|
645
|
+
* slowOperation() {
|
|
646
|
+
* return this.heavyService.processData();
|
|
647
|
+
* }
|
|
648
|
+
* ```
|
|
649
|
+
*/
|
|
650
|
+
function Timeout(ms) {
|
|
651
|
+
return (target, propertyKey, _descriptor) => {
|
|
652
|
+
Reflect.defineMetadata(TIMEOUT_KEY, { ms }, target, propertyKey);
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Decorator that adds comprehensive logging to a route.
|
|
657
|
+
*
|
|
658
|
+
* @param options - Logging configuration options
|
|
659
|
+
* @returns Method decorator
|
|
660
|
+
*
|
|
661
|
+
* Default Options:
|
|
662
|
+
* - logEntry: true
|
|
663
|
+
* - logExit: true
|
|
664
|
+
* - logBody: false
|
|
665
|
+
* - logParams: false
|
|
666
|
+
* - logResponse: false
|
|
667
|
+
*
|
|
668
|
+
* @example
|
|
669
|
+
* ```ts
|
|
670
|
+
* @Post('/users')
|
|
671
|
+
* @Log({
|
|
672
|
+
* logEntry: true,
|
|
673
|
+
* logExit: true,
|
|
674
|
+
* logBody: true,
|
|
675
|
+
* logParams: true,
|
|
676
|
+
* logResponse: false
|
|
677
|
+
* })
|
|
678
|
+
* createUser(@Body() userData: any) {
|
|
679
|
+
* return this.userService.create(userData);
|
|
680
|
+
* }
|
|
681
|
+
* ```
|
|
682
|
+
*/
|
|
683
|
+
function Log(options) {
|
|
684
|
+
return (target, propertyKey, _descriptor) => {
|
|
685
|
+
const config = Object.assign({ logEntry: true, logExit: true, logBody: false, logParams: false, logResponse: false }, options);
|
|
686
|
+
Reflect.defineMetadata(LOG_KEY, config, target, propertyKey);
|
|
687
|
+
};
|
|
688
|
+
}
|
|
431
689
|
/**
|
|
432
690
|
* Registers all controller classes with the provided router.
|
|
433
691
|
* This function processes all decorators and sets up the Express routes.
|
|
@@ -440,25 +698,121 @@ function registerControllers(router, controllers) {
|
|
|
440
698
|
const instance = new ControllerClass();
|
|
441
699
|
const basePath = Reflect.getMetadata('basePath', ControllerClass) || '';
|
|
442
700
|
const routes = Reflect.getMetadata(ROUTES_KEY, ControllerClass) || [];
|
|
701
|
+
// Get controller-level decorators (fallback values)
|
|
702
|
+
const controllerRateLimit = Reflect.getMetadata(RATE_LIMIT_KEY, ControllerClass);
|
|
703
|
+
const controllerCache = Reflect.getMetadata(CACHE_KEY, ControllerClass);
|
|
704
|
+
const controllerTimeout = Reflect.getMetadata(TIMEOUT_KEY, ControllerClass);
|
|
705
|
+
const controllerVersion = Reflect.getMetadata(VERSION_KEY, ControllerClass);
|
|
706
|
+
const controllerRoles = Reflect.getMetadata(ROLES_KEY, ControllerClass);
|
|
707
|
+
const controllerLogConfig = Reflect.getMetadata(LOG_KEY, ControllerClass);
|
|
708
|
+
const controllerHeaders = Reflect.getMetadata(HEADER_KEY, ControllerClass) || {};
|
|
443
709
|
routes.forEach(({ path, method, handlerName }) => {
|
|
710
|
+
var _a;
|
|
444
711
|
const middlewares = Reflect.getMetadata(MIDDLEWARE_KEY, instance, handlerName) || [];
|
|
445
712
|
const params = Reflect.getMetadata(PARAMS_KEY, instance, handlerName) || [];
|
|
446
713
|
const httpCode = Reflect.getMetadata(HTTP_CODE_KEY, instance, handlerName);
|
|
447
|
-
|
|
714
|
+
// Merge controller-level and method-level headers
|
|
715
|
+
const methodHeaders = Reflect.getMetadata(HEADER_KEY, instance, handlerName) || {};
|
|
716
|
+
const headers = Object.assign(Object.assign({}, controllerHeaders), methodHeaders);
|
|
448
717
|
const beforeHooks = Reflect.getMetadata(BEFORE_KEY, instance, handlerName) || [];
|
|
449
718
|
const afterHooks = Reflect.getMetadata(AFTER_KEY, instance, handlerName) || [];
|
|
450
|
-
const roles = Reflect.getMetadata(ROLES_KEY, instance, handlerName) || [];
|
|
451
719
|
const redirect = Reflect.getMetadata(REDIRECT_KEY, instance, handlerName);
|
|
720
|
+
const contentType = Reflect.getMetadata(CONTENT_TYPE_KEY, instance, handlerName);
|
|
721
|
+
// Use method-level decorators if present, otherwise fall back to controller-level
|
|
722
|
+
const roles = Reflect.getMetadata(ROLES_KEY, instance, handlerName) || controllerRoles || [];
|
|
723
|
+
const cache = Reflect.getMetadata(CACHE_KEY, instance, handlerName) || controllerCache;
|
|
724
|
+
const rateLimitOptions = Reflect.getMetadata(RATE_LIMIT_KEY, instance, handlerName) || controllerRateLimit;
|
|
725
|
+
const version = Reflect.getMetadata(VERSION_KEY, instance, handlerName) || controllerVersion;
|
|
726
|
+
const timeout = Reflect.getMetadata(TIMEOUT_KEY, instance, handlerName) || controllerTimeout;
|
|
727
|
+
const logConfig = Reflect.getMetadata(LOG_KEY, instance, handlerName) || controllerLogConfig;
|
|
728
|
+
// Create rate limiter for this specific route if needed
|
|
729
|
+
let rateLimiter = null;
|
|
730
|
+
if (rateLimitOptions) {
|
|
731
|
+
try {
|
|
732
|
+
rateLimiter = rateLimiterCache.get(rateLimitOptions);
|
|
733
|
+
}
|
|
734
|
+
catch (err) {
|
|
735
|
+
(0, logger_utils_1.getLogger)().warn({ err }, 'express-rate-limit not available, skipping rate limiting for this route');
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
let finalPath = path;
|
|
739
|
+
if ((_a = version === null || version === void 0 ? void 0 : version.options) === null || _a === void 0 ? void 0 : _a.addPrefix) {
|
|
740
|
+
finalPath = `/${version.version}${path}`;
|
|
741
|
+
}
|
|
452
742
|
const handler = async (req, res, next) => {
|
|
453
743
|
var _a, _b, _c, _d;
|
|
744
|
+
// Set start time for duration tracking
|
|
745
|
+
req['startTime'] = Date.now();
|
|
746
|
+
let timeoutId;
|
|
747
|
+
let timedOut = false;
|
|
454
748
|
try {
|
|
749
|
+
// Handle timeout setup
|
|
750
|
+
if (timeout) {
|
|
751
|
+
timeoutId = setTimeout(() => {
|
|
752
|
+
if (!res.headersSent && !timedOut) {
|
|
753
|
+
timedOut = true;
|
|
754
|
+
const errorResponse = (0, response_utils_1.createFinalErrorResponse)(req, http_status_codes_1.HttpStatusCodes.REQUEST_TIMEOUT, 'Request timed out');
|
|
755
|
+
res.status(http_status_codes_1.HttpStatusCodes.REQUEST_TIMEOUT).json(errorResponse);
|
|
756
|
+
}
|
|
757
|
+
}, timeout.ms);
|
|
758
|
+
}
|
|
759
|
+
if (timedOut)
|
|
760
|
+
return;
|
|
761
|
+
// Handle rate limiting
|
|
762
|
+
if (rateLimiter) {
|
|
763
|
+
await new Promise((resolve, reject) => {
|
|
764
|
+
rateLimiter(req, res, (err) => {
|
|
765
|
+
if (err)
|
|
766
|
+
reject(err);
|
|
767
|
+
else
|
|
768
|
+
resolve();
|
|
769
|
+
});
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
// Handle content type
|
|
773
|
+
if (!res.headersSent && contentType) {
|
|
774
|
+
res.setHeader('Content-Type', contentType.type);
|
|
775
|
+
}
|
|
776
|
+
// Handle versioning header
|
|
777
|
+
if (((_a = version === null || version === void 0 ? void 0 : version.options) === null || _a === void 0 ? void 0 : _a.addHeader) && ((_b = version === null || version === void 0 ? void 0 : version.options) === null || _b === void 0 ? void 0 : _b.headerName) && (version === null || version === void 0 ? void 0 : version.version)) {
|
|
778
|
+
if (!res.headersSent) {
|
|
779
|
+
res.setHeader(version.options.headerName, version.version);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
// Handle caching
|
|
783
|
+
if (cache && !res.headersSent) {
|
|
784
|
+
res.setHeader('Cache-Control', `public, max-age=${cache.ttlSeconds}`);
|
|
785
|
+
}
|
|
786
|
+
// Handle logging - entry
|
|
787
|
+
if (logConfig === null || logConfig === void 0 ? void 0 : logConfig.logEntry) {
|
|
788
|
+
const logger = (0, logger_utils_1.getLogger)();
|
|
789
|
+
const logData = {
|
|
790
|
+
method: req.method,
|
|
791
|
+
url: req.originalUrl || req.url,
|
|
792
|
+
userAgent: req.get('User-Agent')
|
|
793
|
+
};
|
|
794
|
+
if (logConfig.logParams) {
|
|
795
|
+
logData.params = req.params;
|
|
796
|
+
logData.query = req.query;
|
|
797
|
+
}
|
|
798
|
+
if (logConfig.logBody)
|
|
799
|
+
logData.body = req.body;
|
|
800
|
+
logger.info({ entry: logData }, 'Route Entry:');
|
|
801
|
+
}
|
|
455
802
|
// Handle roles-based access control
|
|
456
|
-
if (roles.length && !((
|
|
457
|
-
|
|
803
|
+
if (roles.length && !((_d = (_c = req === null || req === void 0 ? void 0 : req.user) === null || _c === void 0 ? void 0 : _c.roles) === null || _d === void 0 ? void 0 : _d.some((role) => roles.includes(role)))) {
|
|
804
|
+
const errorResponse = (0, response_utils_1.createFinalErrorResponse)(req, http_status_codes_1.HttpStatusCodes.FORBIDDEN, 'Forbidden Insufficient Roles');
|
|
805
|
+
res.status(http_status_codes_1.HttpStatusCodes.FORBIDDEN).json(errorResponse);
|
|
806
|
+
if (timeoutId) {
|
|
807
|
+
clearTimeout(timeoutId);
|
|
808
|
+
}
|
|
458
809
|
return;
|
|
459
810
|
}
|
|
460
811
|
// Process static redirect if configured
|
|
461
812
|
if (redirect && redirect.url) {
|
|
813
|
+
if (timeoutId) {
|
|
814
|
+
clearTimeout(timeoutId);
|
|
815
|
+
}
|
|
462
816
|
return res.redirect(redirect.statusCode, redirect.url);
|
|
463
817
|
}
|
|
464
818
|
for (const fn of beforeHooks)
|
|
@@ -489,6 +843,12 @@ function registerControllers(router, controllers) {
|
|
|
489
843
|
const result = instance[handlerName](...args);
|
|
490
844
|
// Support both sync and async handlers
|
|
491
845
|
const awaited = result instanceof Promise ? await result : result;
|
|
846
|
+
// Clear timeout if operation completed
|
|
847
|
+
if (timeoutId) {
|
|
848
|
+
clearTimeout(timeoutId);
|
|
849
|
+
}
|
|
850
|
+
if (timedOut)
|
|
851
|
+
return;
|
|
492
852
|
// Handle dynamic redirects
|
|
493
853
|
if (redirect && awaited && typeof awaited === 'object' && 'url' in awaited) {
|
|
494
854
|
const redirectUrl = awaited.url;
|
|
@@ -497,19 +857,39 @@ function registerControllers(router, controllers) {
|
|
|
497
857
|
}
|
|
498
858
|
if (!res.headersSent && typeof awaited !== 'undefined') {
|
|
499
859
|
if (httpCode)
|
|
500
|
-
|
|
501
|
-
for (const [k, v] of Object.entries(headers))
|
|
502
|
-
|
|
860
|
+
res.status(httpCode);
|
|
861
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
862
|
+
const normalized = normalizeHeaderValue(v);
|
|
863
|
+
if (typeof normalized !== 'undefined') {
|
|
864
|
+
res.set(k, normalized);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
503
867
|
res.json(awaited);
|
|
504
868
|
}
|
|
869
|
+
// Handle logging - exit
|
|
870
|
+
if (logConfig === null || logConfig === void 0 ? void 0 : logConfig.logExit) {
|
|
871
|
+
const logger = (0, logger_utils_1.getLogger)();
|
|
872
|
+
const logData = {
|
|
873
|
+
method: req.method,
|
|
874
|
+
url: req.originalUrl || req.url,
|
|
875
|
+
statusCode: res.statusCode,
|
|
876
|
+
duration: `${Date.now() - req.startTime}ms`
|
|
877
|
+
};
|
|
878
|
+
if (logConfig.logResponse)
|
|
879
|
+
logData.response = awaited;
|
|
880
|
+
logger.info({ exit: logData }, 'Route Exit:');
|
|
881
|
+
}
|
|
505
882
|
for (const fn of afterHooks)
|
|
506
883
|
await fn(req, res, awaited);
|
|
507
884
|
}
|
|
508
885
|
catch (err) {
|
|
886
|
+
if (timeoutId) {
|
|
887
|
+
clearTimeout(timeoutId);
|
|
888
|
+
}
|
|
509
889
|
next(err);
|
|
510
890
|
}
|
|
511
891
|
};
|
|
512
|
-
router[method](basePath +
|
|
892
|
+
router[method](basePath + finalPath, ...middlewares, handler);
|
|
513
893
|
});
|
|
514
894
|
});
|
|
515
895
|
}
|