@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.
@@ -21,6 +21,17 @@
21
21
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
22
  * SOFTWARE.
23
23
  */
24
+ var __assign = (this && this.__assign) || function () {
25
+ __assign = Object.assign || function(t) {
26
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
27
+ s = arguments[i];
28
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
29
+ t[p] = s[p];
30
+ }
31
+ return t;
32
+ };
33
+ return __assign.apply(this, arguments);
34
+ };
24
35
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
25
36
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
26
37
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -94,8 +105,11 @@ var __values = (this && this.__values) || function(o) {
94
105
  throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
95
106
  };
96
107
  import 'reflect-metadata';
108
+ import { getLogger } from './logger.utils';
109
+ import { createFinalErrorResponse } from './response.utils';
97
110
  import { HttpStatusCodes } from './http-status-codes';
98
- import { ForbiddenException } from './exception.utils';
111
+ import { rateLimit } from 'express-rate-limit';
112
+ import { TTLCache } from './cache.utils';
99
113
  // Metadata keys
100
114
  var ROUTES_KEY = Symbol('routes');
101
115
  var MIDDLEWARE_KEY = Symbol('middlewares');
@@ -106,6 +120,69 @@ var BEFORE_KEY = Symbol('before');
106
120
  var AFTER_KEY = Symbol('after');
107
121
  var ROLES_KEY = Symbol('roles');
108
122
  var REDIRECT_KEY = Symbol('redirect');
123
+ var CACHE_KEY = Symbol('cache');
124
+ var RATE_LIMIT_KEY = Symbol('rateLimit');
125
+ var CONTENT_TYPE_KEY = Symbol('contentType');
126
+ var VERSION_KEY = Symbol('version');
127
+ var TIMEOUT_KEY = Symbol('timeout');
128
+ var LOG_KEY = Symbol('log');
129
+ /**
130
+ * RateLimiter cache that uses TTLCache for automatic TTL and LRU handling.
131
+ */
132
+ var RateLimiterCache = /** @class */ (function () {
133
+ function RateLimiterCache(maxSize, ttlMs) {
134
+ if (maxSize === void 0) { maxSize = 100; }
135
+ if (ttlMs === void 0) { ttlMs = 5 * 60 * 1000; }
136
+ this.cache = new TTLCache({
137
+ maxSize: maxSize,
138
+ ttlMs: ttlMs
139
+ });
140
+ }
141
+ RateLimiterCache.prototype.generateKey = function (options) {
142
+ return "".concat(options.max, ":").concat(options.windowMs, ":").concat(options.standardHeaders, ":").concat(options.legacyHeaders);
143
+ };
144
+ RateLimiterCache.prototype.get = function (options) {
145
+ var key = this.generateKey(options);
146
+ var cached = this.cache.get(key);
147
+ if (cached) {
148
+ return cached.limiter;
149
+ }
150
+ // Create new limiter
151
+ var limiter = rateLimit(__assign(__assign({}, options), { handler: function (req, res) {
152
+ var errorResponse = createFinalErrorResponse(req, HttpStatusCodes.TOO_MANY_REQUESTS, 'Too Many Requests');
153
+ res.status(HttpStatusCodes.TOO_MANY_REQUESTS).json(errorResponse);
154
+ } }));
155
+ // Store in cache
156
+ this.cache.set(key, {
157
+ limiter: limiter,
158
+ config: key
159
+ });
160
+ return limiter;
161
+ };
162
+ RateLimiterCache.prototype.clear = function () {
163
+ this.cache.clear();
164
+ };
165
+ RateLimiterCache.prototype.size = function () {
166
+ return this.cache.size();
167
+ };
168
+ RateLimiterCache.prototype.destroy = function () {
169
+ this.cache.destroy();
170
+ };
171
+ return RateLimiterCache;
172
+ }());
173
+ export { RateLimiterCache };
174
+ // Global cache instance
175
+ var rateLimiterCache = new RateLimiterCache();
176
+ function normalizeHeaderValue(value) {
177
+ if (typeof value === 'undefined')
178
+ return undefined;
179
+ if (typeof value === 'string')
180
+ return value;
181
+ if (Array.isArray(value) && value.every(function (item) { return typeof item === 'string'; })) {
182
+ return value;
183
+ }
184
+ return String(value);
185
+ }
109
186
  /**
110
187
  * Factory function that creates HTTP method decorators.
111
188
  *
@@ -372,8 +449,8 @@ export function HttpCode(status) {
372
449
  /**
373
450
  * Decorator that adds a custom HTTP header to the response.
374
451
  *
375
- * @param name - Header name
376
- * @param value - Header value
452
+ * @param header - Header name-value pairs or a single header name and value
453
+ * @param value - Header value if a single header name is provided
377
454
  * @returns Method decorator
378
455
  *
379
456
  * @example
@@ -384,13 +461,45 @@ export function HttpCode(status) {
384
461
  * // Response will include the Cache-Control header
385
462
  * return { data: '...' };
386
463
  * }
387
- * ```
388
464
  */
389
465
  export function Header(name, value) {
390
- return function (target, propertyKey, _descriptor) {
391
- var headers = Reflect.getMetadata(HEADER_KEY, target, propertyKey) || {};
392
- headers[name] = value;
393
- Reflect.defineMetadata(HEADER_KEY, headers, target, propertyKey);
466
+ return Headers(name, value);
467
+ }
468
+ /**
469
+ * Decorator that adds a custom HTTP headers to the response.
470
+ *
471
+ * @param headers - Header name-value pairs or a single header name and value
472
+ * @param value - Header value if a single header name is provided
473
+ * @returns Method decorator
474
+ *
475
+ * @example
476
+ * ```ts
477
+ * @Get('/data')
478
+ * @Headers('Cache-Control', 'max-age=60')
479
+ * getData() {
480
+ * // Response will include the Cache-Control header
481
+ * return { data: '...' };
482
+ * }
483
+ *
484
+ * @Get('/data/:id')
485
+ * @Headers({
486
+ * 'Cache-Control': 'max-age=60',
487
+ * 'X-Custom-Header': 'custom-value',
488
+ * 'Content-Security-Policy': "default-src 'self'"
489
+ * })
490
+ * getData() {
491
+ * // Response will include all specified headers
492
+ * return { data: '...' };
493
+ * }
494
+ *
495
+ * ```
496
+ */
497
+ export function Headers(headers, value) {
498
+ return function (target, propertyKey) {
499
+ var _a;
500
+ var existing = Reflect.getMetadata(HEADER_KEY, target, propertyKey) || {};
501
+ var newHeaders = typeof headers === 'string' ? (_a = {}, _a[headers] = value, _a) : headers;
502
+ Reflect.defineMetadata(HEADER_KEY, __assign(__assign({}, existing), newHeaders), target, propertyKey);
394
503
  };
395
504
  }
396
505
  /**
@@ -497,6 +606,162 @@ export function Redirect(url, statusCode) {
497
606
  return descriptor;
498
607
  };
499
608
  }
609
+ /**
610
+ * Decorator that adds caching to a route response.
611
+ *
612
+ * @param ttlSeconds - Time to live in seconds for the cache
613
+ * @returns Method decorator
614
+ *
615
+ * @example
616
+ * ```ts
617
+ * @Get('/data')
618
+ * @Cache(300) // Cache for 5 minutes
619
+ * getData() {
620
+ * return { data: 'expensive operation result' };
621
+ * }
622
+ * ```
623
+ */
624
+ export function Cache(ttlSeconds) {
625
+ return function (target, propertyKey, _descriptor) {
626
+ Reflect.defineMetadata(CACHE_KEY, { ttlSeconds: ttlSeconds }, target, propertyKey);
627
+ };
628
+ }
629
+ /**
630
+ * Decorator that applies rate limiting to a route.
631
+ * Note: Requires 'express-rate-limit' package to be installed.
632
+ *
633
+ * @param limit - Maximum number of requests allowed in the window
634
+ * @param windowMs - Time window in milliseconds
635
+ * @returns Method decorator
636
+ *
637
+ * Default Options:
638
+ * - standardHeaders: true
639
+ * - legacyHeaders: false
640
+ *
641
+ * @example
642
+ * ```ts
643
+ * @Post('/login')
644
+ * @RateLimit({ max: 5, windowMs: 60000, standardHeaders: true, legacyHeaders: false }) // 5 requests per minute
645
+ * login(@Body() credentials: LoginDto) {
646
+ * return this.authService.login(credentials);
647
+ * }
648
+ * ```
649
+ */
650
+ export function RateLimit(options) {
651
+ var opts = __assign({ standardHeaders: true, legacyHeaders: false }, options);
652
+ return function (target, propertyKey, _descriptor) {
653
+ Reflect.defineMetadata(RATE_LIMIT_KEY, opts, target, propertyKey);
654
+ };
655
+ }
656
+ /**
657
+ * Decorator that sets the content type for the response.
658
+ *
659
+ * @param type - MIME type for the response
660
+ * @returns Method decorator
661
+ *
662
+ * @example
663
+ * ```ts
664
+ * @Get('/download')
665
+ * @ContentType('application/pdf')
666
+ * downloadPdf() {
667
+ * return this.fileService.generatePdf();
668
+ * }
669
+ * ```
670
+ */
671
+ export function ContentType(type) {
672
+ return function (target, propertyKey, _descriptor) {
673
+ Reflect.defineMetadata(CONTENT_TYPE_KEY, { type: type }, target, propertyKey);
674
+ };
675
+ }
676
+ /**
677
+ * Decorator that adds API versioning to a route.
678
+ *
679
+ * @param version - Version string for the API endpoint
680
+ * @param options - Versioning options
681
+ * @returns Method decorator
682
+ *
683
+ * Default Options:
684
+ * - addPrefix: true
685
+ * - addHeader: true
686
+ * - headerName: 'X-API-Version'
687
+ *
688
+ * @example
689
+ * ```ts
690
+ *
691
+ * @Get('/users')
692
+ * @Version('v2')
693
+ * getUsersV2() {
694
+ * return this.userService.findAllV2();
695
+ * }
696
+ *
697
+ * @Get('/users')
698
+ * @Version('v2', { addPrefix: true, addHeader: true, headerName: 'X-API-Version' })
699
+ * getUsersV2() {
700
+ * // Route becomes /v2/users
701
+ * return this.userService.findAllV2();
702
+ * }
703
+ * ```
704
+ */
705
+ export function Version(version, options) {
706
+ var opts = __assign({ addPrefix: true, addHeader: true, headerName: 'X-API-Version' }, options);
707
+ return function (target, propertyKey, _descriptor) {
708
+ Reflect.defineMetadata(VERSION_KEY, { version: version, options: opts }, target, propertyKey);
709
+ };
710
+ }
711
+ /**
712
+ * Decorator that sets a timeout for route execution.
713
+ *
714
+ * @param ms - Timeout in milliseconds
715
+ * @returns Method decorator
716
+ *
717
+ * @example
718
+ * ```ts
719
+ * @Get('/slow-operation')
720
+ * @Timeout(30000) // 30 second timeout
721
+ * slowOperation() {
722
+ * return this.heavyService.processData();
723
+ * }
724
+ * ```
725
+ */
726
+ export function Timeout(ms) {
727
+ return function (target, propertyKey, _descriptor) {
728
+ Reflect.defineMetadata(TIMEOUT_KEY, { ms: ms }, target, propertyKey);
729
+ };
730
+ }
731
+ /**
732
+ * Decorator that adds comprehensive logging to a route.
733
+ *
734
+ * @param options - Logging configuration options
735
+ * @returns Method decorator
736
+ *
737
+ * Default Options:
738
+ * - logEntry: true
739
+ * - logExit: true
740
+ * - logBody: false
741
+ * - logParams: false
742
+ * - logResponse: false
743
+ *
744
+ * @example
745
+ * ```ts
746
+ * @Post('/users')
747
+ * @Log({
748
+ * logEntry: true,
749
+ * logExit: true,
750
+ * logBody: true,
751
+ * logParams: true,
752
+ * logResponse: false
753
+ * })
754
+ * createUser(@Body() userData: any) {
755
+ * return this.userService.create(userData);
756
+ * }
757
+ * ```
758
+ */
759
+ export function Log(options) {
760
+ return function (target, propertyKey, _descriptor) {
761
+ var config = __assign({ logEntry: true, logExit: true, logBody: false, logParams: false, logResponse: false }, options);
762
+ Reflect.defineMetadata(LOG_KEY, config, target, propertyKey);
763
+ };
764
+ }
500
765
  /**
501
766
  * Registers all controller classes with the provided router.
502
767
  * This function processes all decorators and sets up the Express routes.
@@ -510,61 +775,160 @@ export function registerControllers(router, controllers) {
510
775
  var instance = new ControllerClass();
511
776
  var basePath = Reflect.getMetadata('basePath', ControllerClass) || '';
512
777
  var routes = Reflect.getMetadata(ROUTES_KEY, ControllerClass) || [];
778
+ // Get controller-level decorators (fallback values)
779
+ var controllerRateLimit = Reflect.getMetadata(RATE_LIMIT_KEY, ControllerClass);
780
+ var controllerCache = Reflect.getMetadata(CACHE_KEY, ControllerClass);
781
+ var controllerTimeout = Reflect.getMetadata(TIMEOUT_KEY, ControllerClass);
782
+ var controllerVersion = Reflect.getMetadata(VERSION_KEY, ControllerClass);
783
+ var controllerRoles = Reflect.getMetadata(ROLES_KEY, ControllerClass);
784
+ var controllerLogConfig = Reflect.getMetadata(LOG_KEY, ControllerClass);
785
+ var controllerHeaders = Reflect.getMetadata(HEADER_KEY, ControllerClass) || {};
513
786
  routes.forEach(function (_a) {
514
787
  var _b;
788
+ var _c;
515
789
  var path = _a.path, method = _a.method, handlerName = _a.handlerName;
516
790
  var middlewares = Reflect.getMetadata(MIDDLEWARE_KEY, instance, handlerName) || [];
517
791
  var params = Reflect.getMetadata(PARAMS_KEY, instance, handlerName) || [];
518
792
  var httpCode = Reflect.getMetadata(HTTP_CODE_KEY, instance, handlerName);
519
- var headers = Reflect.getMetadata(HEADER_KEY, instance, handlerName) || {};
793
+ // Merge controller-level and method-level headers
794
+ var methodHeaders = Reflect.getMetadata(HEADER_KEY, instance, handlerName) || {};
795
+ var headers = __assign(__assign({}, controllerHeaders), methodHeaders);
520
796
  var beforeHooks = Reflect.getMetadata(BEFORE_KEY, instance, handlerName) || [];
521
797
  var afterHooks = Reflect.getMetadata(AFTER_KEY, instance, handlerName) || [];
522
- var roles = Reflect.getMetadata(ROLES_KEY, instance, handlerName) || [];
523
798
  var redirect = Reflect.getMetadata(REDIRECT_KEY, instance, handlerName);
799
+ var contentType = Reflect.getMetadata(CONTENT_TYPE_KEY, instance, handlerName);
800
+ // Use method-level decorators if present, otherwise fall back to controller-level
801
+ var roles = Reflect.getMetadata(ROLES_KEY, instance, handlerName) || controllerRoles || [];
802
+ var cache = Reflect.getMetadata(CACHE_KEY, instance, handlerName) || controllerCache;
803
+ var rateLimitOptions = Reflect.getMetadata(RATE_LIMIT_KEY, instance, handlerName) || controllerRateLimit;
804
+ var version = Reflect.getMetadata(VERSION_KEY, instance, handlerName) || controllerVersion;
805
+ var timeout = Reflect.getMetadata(TIMEOUT_KEY, instance, handlerName) || controllerTimeout;
806
+ var logConfig = Reflect.getMetadata(LOG_KEY, instance, handlerName) || controllerLogConfig;
807
+ // Create rate limiter for this specific route if needed
808
+ var rateLimiter = null;
809
+ if (rateLimitOptions) {
810
+ try {
811
+ rateLimiter = rateLimiterCache.get(rateLimitOptions);
812
+ }
813
+ catch (err) {
814
+ getLogger().warn({ err: err }, 'express-rate-limit not available, skipping rate limiting for this route');
815
+ }
816
+ }
817
+ var finalPath = path;
818
+ if ((_c = version === null || version === void 0 ? void 0 : version.options) === null || _c === void 0 ? void 0 : _c.addPrefix) {
819
+ finalPath = "/".concat(version.version).concat(path);
820
+ }
524
821
  var handler = function (req, res, next) { return __awaiter(_this, void 0, void 0, function () {
525
- var beforeHooks_1, beforeHooks_1_1, fn, e_1_1, args_1, result, awaited, _a, redirectUrl, redirectStatus, _b, _c, _d, k, v, afterHooks_1, afterHooks_1_1, fn, e_2_1, err_1;
822
+ var timeoutId, timedOut, logger, logData, errorResponse, beforeHooks_1, beforeHooks_1_1, fn, e_1_1, args_1, result, awaited, _a, redirectUrl, redirectStatus, _b, _c, _d, k, v, normalized, logger, logData, afterHooks_1, afterHooks_1_1, fn, e_2_1, err_1;
526
823
  var e_1, _e, _f, e_3, _g, e_2, _h;
527
824
  var _j, _k, _l, _m;
528
825
  return __generator(this, function (_o) {
529
826
  switch (_o.label) {
530
827
  case 0:
531
- _o.trys.push([0, 20, , 21]);
828
+ // Set start time for duration tracking
829
+ req['startTime'] = Date.now();
830
+ timedOut = false;
831
+ _o.label = 1;
832
+ case 1:
833
+ _o.trys.push([1, 23, , 24]);
834
+ // Handle timeout setup
835
+ if (timeout) {
836
+ timeoutId = setTimeout(function () {
837
+ if (!res.headersSent && !timedOut) {
838
+ timedOut = true;
839
+ var errorResponse = createFinalErrorResponse(req, HttpStatusCodes.REQUEST_TIMEOUT, 'Request timed out');
840
+ res.status(HttpStatusCodes.REQUEST_TIMEOUT).json(errorResponse);
841
+ }
842
+ }, timeout.ms);
843
+ }
844
+ if (timedOut)
845
+ return [2 /*return*/];
846
+ if (!rateLimiter) return [3 /*break*/, 3];
847
+ return [4 /*yield*/, new Promise(function (resolve, reject) {
848
+ rateLimiter(req, res, function (err) {
849
+ if (err)
850
+ reject(err);
851
+ else
852
+ resolve();
853
+ });
854
+ })];
855
+ case 2:
856
+ _o.sent();
857
+ _o.label = 3;
858
+ case 3:
859
+ // Handle content type
860
+ if (!res.headersSent && contentType) {
861
+ res.setHeader('Content-Type', contentType.type);
862
+ }
863
+ // Handle versioning header
864
+ if (((_j = version === null || version === void 0 ? void 0 : version.options) === null || _j === void 0 ? void 0 : _j.addHeader) && ((_k = version === null || version === void 0 ? void 0 : version.options) === null || _k === void 0 ? void 0 : _k.headerName) && (version === null || version === void 0 ? void 0 : version.version)) {
865
+ if (!res.headersSent) {
866
+ res.setHeader(version.options.headerName, version.version);
867
+ }
868
+ }
869
+ // Handle caching
870
+ if (cache && !res.headersSent) {
871
+ res.setHeader('Cache-Control', "public, max-age=".concat(cache.ttlSeconds));
872
+ }
873
+ // Handle logging - entry
874
+ if (logConfig === null || logConfig === void 0 ? void 0 : logConfig.logEntry) {
875
+ logger = getLogger();
876
+ logData = {
877
+ method: req.method,
878
+ url: req.originalUrl || req.url,
879
+ userAgent: req.get('User-Agent')
880
+ };
881
+ if (logConfig.logParams) {
882
+ logData.params = req.params;
883
+ logData.query = req.query;
884
+ }
885
+ if (logConfig.logBody)
886
+ logData.body = req.body;
887
+ logger.info({ entry: logData }, 'Route Entry:');
888
+ }
532
889
  // Handle roles-based access control
533
- if (roles.length && !((_k = (_j = req === null || req === void 0 ? void 0 : req.user) === null || _j === void 0 ? void 0 : _j.roles) === null || _k === void 0 ? void 0 : _k.some(function (role) { return roles.includes(role); }))) {
534
- res.status(HttpStatusCodes.FORBIDDEN).json(new ForbiddenException('Forbidden Insufficient Roles'));
890
+ if (roles.length && !((_m = (_l = req === null || req === void 0 ? void 0 : req.user) === null || _l === void 0 ? void 0 : _l.roles) === null || _m === void 0 ? void 0 : _m.some(function (role) { return roles.includes(role); }))) {
891
+ errorResponse = createFinalErrorResponse(req, HttpStatusCodes.FORBIDDEN, 'Forbidden Insufficient Roles');
892
+ res.status(HttpStatusCodes.FORBIDDEN).json(errorResponse);
893
+ if (timeoutId) {
894
+ clearTimeout(timeoutId);
895
+ }
535
896
  return [2 /*return*/];
536
897
  }
537
898
  // Process static redirect if configured
538
899
  if (redirect && redirect.url) {
900
+ if (timeoutId) {
901
+ clearTimeout(timeoutId);
902
+ }
539
903
  return [2 /*return*/, res.redirect(redirect.statusCode, redirect.url)];
540
904
  }
541
- _o.label = 1;
542
- case 1:
543
- _o.trys.push([1, 6, 7, 8]);
905
+ _o.label = 4;
906
+ case 4:
907
+ _o.trys.push([4, 9, 10, 11]);
544
908
  beforeHooks_1 = __values(beforeHooks), beforeHooks_1_1 = beforeHooks_1.next();
545
- _o.label = 2;
546
- case 2:
547
- if (!!beforeHooks_1_1.done) return [3 /*break*/, 5];
909
+ _o.label = 5;
910
+ case 5:
911
+ if (!!beforeHooks_1_1.done) return [3 /*break*/, 8];
548
912
  fn = beforeHooks_1_1.value;
549
913
  return [4 /*yield*/, fn(req, res)];
550
- case 3:
914
+ case 6:
551
915
  _o.sent();
552
- _o.label = 4;
553
- case 4:
916
+ _o.label = 7;
917
+ case 7:
554
918
  beforeHooks_1_1 = beforeHooks_1.next();
555
- return [3 /*break*/, 2];
556
- case 5: return [3 /*break*/, 8];
557
- case 6:
919
+ return [3 /*break*/, 5];
920
+ case 8: return [3 /*break*/, 11];
921
+ case 9:
558
922
  e_1_1 = _o.sent();
559
923
  e_1 = { error: e_1_1 };
560
- return [3 /*break*/, 8];
561
- case 7:
924
+ return [3 /*break*/, 11];
925
+ case 10:
562
926
  try {
563
927
  if (beforeHooks_1_1 && !beforeHooks_1_1.done && (_e = beforeHooks_1.return)) _e.call(beforeHooks_1);
564
928
  }
565
929
  finally { if (e_1) throw e_1.error; }
566
930
  return [7 /*endfinally*/];
567
- case 8:
931
+ case 11:
568
932
  args_1 = [];
569
933
  if (params.length) {
570
934
  params.forEach(function (_a) {
@@ -590,16 +954,22 @@ export function registerControllers(router, controllers) {
590
954
  });
591
955
  }
592
956
  result = (_f = instance)[handlerName].apply(_f, __spreadArray([], __read(args_1), false));
593
- if (!(result instanceof Promise)) return [3 /*break*/, 10];
957
+ if (!(result instanceof Promise)) return [3 /*break*/, 13];
594
958
  return [4 /*yield*/, result];
595
- case 9:
959
+ case 12:
596
960
  _a = _o.sent();
597
- return [3 /*break*/, 11];
598
- case 10:
961
+ return [3 /*break*/, 14];
962
+ case 13:
599
963
  _a = result;
600
- _o.label = 11;
601
- case 11:
964
+ _o.label = 14;
965
+ case 14:
602
966
  awaited = _a;
967
+ // Clear timeout if operation completed
968
+ if (timeoutId) {
969
+ clearTimeout(timeoutId);
970
+ }
971
+ if (timedOut)
972
+ return [2 /*return*/];
603
973
  // Handle dynamic redirects
604
974
  if (redirect && awaited && typeof awaited === 'object' && 'url' in awaited) {
605
975
  redirectUrl = awaited.url;
@@ -608,11 +978,14 @@ export function registerControllers(router, controllers) {
608
978
  }
609
979
  if (!res.headersSent && typeof awaited !== 'undefined') {
610
980
  if (httpCode)
611
- (_l = res.status) === null || _l === void 0 ? void 0 : _l.call(res, httpCode);
981
+ res.status(httpCode);
612
982
  try {
613
983
  for (_b = __values(Object.entries(headers)), _c = _b.next(); !_c.done; _c = _b.next()) {
614
984
  _d = __read(_c.value, 2), k = _d[0], v = _d[1];
615
- (_m = res.set) === null || _m === void 0 ? void 0 : _m.call(res, k, v);
985
+ normalized = normalizeHeaderValue(v);
986
+ if (typeof normalized !== 'undefined') {
987
+ res.set(k, normalized);
988
+ }
616
989
  }
617
990
  }
618
991
  catch (e_3_1) { e_3 = { error: e_3_1 }; }
@@ -624,43 +997,61 @@ export function registerControllers(router, controllers) {
624
997
  }
625
998
  res.json(awaited);
626
999
  }
627
- _o.label = 12;
628
- case 12:
629
- _o.trys.push([12, 17, 18, 19]);
1000
+ // Handle logging - exit
1001
+ if (logConfig === null || logConfig === void 0 ? void 0 : logConfig.logExit) {
1002
+ logger = getLogger();
1003
+ logData = {
1004
+ method: req.method,
1005
+ url: req.originalUrl || req.url,
1006
+ statusCode: res.statusCode,
1007
+ duration: "".concat(Date.now() - req.startTime, "ms")
1008
+ };
1009
+ if (logConfig.logResponse)
1010
+ logData.response = awaited;
1011
+ logger.info({ exit: logData }, 'Route Exit:');
1012
+ }
1013
+ _o.label = 15;
1014
+ case 15:
1015
+ _o.trys.push([15, 20, 21, 22]);
630
1016
  afterHooks_1 = __values(afterHooks), afterHooks_1_1 = afterHooks_1.next();
631
- _o.label = 13;
632
- case 13:
633
- if (!!afterHooks_1_1.done) return [3 /*break*/, 16];
1017
+ _o.label = 16;
1018
+ case 16:
1019
+ if (!!afterHooks_1_1.done) return [3 /*break*/, 19];
634
1020
  fn = afterHooks_1_1.value;
635
1021
  return [4 /*yield*/, fn(req, res, awaited)];
636
- case 14:
1022
+ case 17:
637
1023
  _o.sent();
638
- _o.label = 15;
639
- case 15:
1024
+ _o.label = 18;
1025
+ case 18:
640
1026
  afterHooks_1_1 = afterHooks_1.next();
641
- return [3 /*break*/, 13];
642
- case 16: return [3 /*break*/, 19];
643
- case 17:
1027
+ return [3 /*break*/, 16];
1028
+ case 19: return [3 /*break*/, 22];
1029
+ case 20:
644
1030
  e_2_1 = _o.sent();
645
1031
  e_2 = { error: e_2_1 };
646
- return [3 /*break*/, 19];
647
- case 18:
1032
+ return [3 /*break*/, 22];
1033
+ case 21:
648
1034
  try {
649
1035
  if (afterHooks_1_1 && !afterHooks_1_1.done && (_h = afterHooks_1.return)) _h.call(afterHooks_1);
650
1036
  }
651
1037
  finally { if (e_2) throw e_2.error; }
652
1038
  return [7 /*endfinally*/];
653
- case 19: return [3 /*break*/, 21];
654
- case 20:
1039
+ case 22: return [3 /*break*/, 24];
1040
+ case 23:
655
1041
  err_1 = _o.sent();
1042
+ if (timeoutId) {
1043
+ clearTimeout(timeoutId);
1044
+ }
656
1045
  next(err_1);
657
- return [3 /*break*/, 21];
658
- case 21: return [2 /*return*/];
1046
+ return [3 /*break*/, 24];
1047
+ case 24: return [2 /*return*/];
659
1048
  }
660
1049
  });
661
1050
  }); };
662
- (_b = router)[method].apply(_b, __spreadArray(__spreadArray([basePath + path], __read(middlewares), false), [handler], false));
1051
+ (_b = router)[method].apply(_b, __spreadArray(__spreadArray([basePath + finalPath], __read(middlewares), false), [handler], false));
663
1052
  });
664
1053
  });
665
1054
  }
1055
+ // Export for testing purposes
1056
+ export { rateLimiterCache };
666
1057
  //# sourceMappingURL=decorators.utils.js.map