@catbee/utils 0.0.8-rc.2 → 0.0.8-rc.3

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 (66) hide show
  1. package/build/cjs/config.d.ts +121 -0
  2. package/build/cjs/config.js +137 -0
  3. package/build/cjs/index.d.ts +26 -0
  4. package/build/cjs/index.js +68 -0
  5. package/build/cjs/servers/server.builder.d.ts +507 -0
  6. package/build/cjs/servers/server.builder.js +658 -0
  7. package/build/cjs/servers/server.d.ts +255 -0
  8. package/build/cjs/servers/server.js +970 -0
  9. package/build/cjs/types/api-response.d.ts +151 -0
  10. package/build/cjs/types/api-response.js +36 -0
  11. package/build/cjs/types/index.d.ts +124 -0
  12. package/build/cjs/types/index.js +25 -0
  13. package/build/cjs/types/server.d.ts +267 -0
  14. package/build/cjs/types/server.js +25 -0
  15. package/build/cjs/utils/array.utils.d.ts +167 -0
  16. package/build/cjs/utils/array.utils.js +363 -0
  17. package/build/cjs/utils/async.utils.d.ts +264 -0
  18. package/build/cjs/utils/async.utils.js +640 -0
  19. package/build/cjs/utils/cache.utils.d.ts +152 -0
  20. package/build/cjs/utils/cache.utils.js +298 -0
  21. package/build/cjs/utils/context-store.utils.d.ts +188 -0
  22. package/build/cjs/utils/context-store.utils.js +297 -0
  23. package/build/cjs/utils/crypto.utils.d.ts +159 -0
  24. package/build/cjs/utils/crypto.utils.js +295 -0
  25. package/build/cjs/utils/date.utils.d.ts +158 -0
  26. package/build/cjs/utils/date.utils.js +395 -0
  27. package/build/cjs/utils/decorators.utils.d.ts +511 -0
  28. package/build/cjs/utils/decorators.utils.js +1035 -0
  29. package/build/cjs/utils/dir.utils.d.ts +195 -0
  30. package/build/cjs/utils/dir.utils.js +500 -0
  31. package/build/cjs/utils/env.utils.d.ts +376 -0
  32. package/build/cjs/utils/env.utils.js +786 -0
  33. package/build/cjs/utils/exception.utils.d.ts +229 -0
  34. package/build/cjs/utils/exception.utils.js +408 -0
  35. package/build/cjs/utils/fs.utils.d.ts +163 -0
  36. package/build/cjs/utils/fs.utils.js +371 -0
  37. package/build/cjs/utils/http-status-codes.d.ts +265 -0
  38. package/build/cjs/utils/http-status-codes.js +297 -0
  39. package/build/cjs/utils/id.utils.d.ts +35 -0
  40. package/build/cjs/utils/id.utils.js +90 -0
  41. package/build/cjs/utils/logger.utils.d.ts +159 -0
  42. package/build/cjs/utils/logger.utils.js +350 -0
  43. package/build/cjs/utils/middleware.utils.d.ts +99 -0
  44. package/build/cjs/utils/middleware.utils.js +243 -0
  45. package/build/cjs/utils/obj.utils.d.ts +123 -0
  46. package/build/cjs/utils/obj.utils.js +428 -0
  47. package/build/cjs/utils/performance.utils.d.ts +135 -0
  48. package/build/cjs/utils/performance.utils.js +280 -0
  49. package/build/cjs/utils/request.utils.d.ts +85 -0
  50. package/build/cjs/utils/request.utils.js +199 -0
  51. package/build/cjs/utils/response.utils.d.ts +162 -0
  52. package/build/cjs/utils/response.utils.js +274 -0
  53. package/build/cjs/utils/stream.utils.d.ts +87 -0
  54. package/build/cjs/utils/stream.utils.js +217 -0
  55. package/build/cjs/utils/string.utils.d.ts +92 -0
  56. package/build/cjs/utils/string.utils.js +178 -0
  57. package/build/cjs/utils/type.utils.d.ts +89 -0
  58. package/build/cjs/utils/type.utils.js +195 -0
  59. package/build/cjs/utils/url.utils.d.ts +140 -0
  60. package/build/cjs/utils/url.utils.js +316 -0
  61. package/build/cjs/utils/validate.utils.d.ts +176 -0
  62. package/build/cjs/utils/validate.utils.js +344 -0
  63. package/build/esm/utils/context-store.utils.d.ts +1 -1
  64. package/build/esm/utils/context-store.utils.js +1 -1
  65. package/build/esm/utils/decorators.utils.js +1 -1
  66. package/package.json +287 -32
@@ -0,0 +1,1035 @@
1
+ "use strict";
2
+ /*
3
+ * The MIT License
4
+ *
5
+ * Copyright (c) 2025 Catbee Technologies. https://catbee-utils.npm.hprasath.com/license
6
+ *
7
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ * of this software and associated documentation files (the "Software"), to deal
9
+ * in the Software without restriction, including without limitation the rights
10
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ * copies of the Software, and to permit persons to whom the Software is
12
+ * furnished to do so, subject to the following conditions:
13
+ *
14
+ * The above copyright notice and this permission notice shall be included in all
15
+ * copies or substantial portions of the Software.
16
+ *
17
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ * SOFTWARE.
24
+ */
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 = exports.DIContainer = void 0;
27
+ exports.Injectable = Injectable;
28
+ exports.Inject = Inject;
29
+ exports.Controller = Controller;
30
+ exports.Use = Use;
31
+ exports.HttpCode = HttpCode;
32
+ exports.Header = Header;
33
+ exports.Headers = Headers;
34
+ exports.Before = Before;
35
+ exports.After = After;
36
+ exports.Roles = Roles;
37
+ exports.Redirect = Redirect;
38
+ exports.Cache = Cache;
39
+ exports.RateLimit = RateLimit;
40
+ exports.ContentType = ContentType;
41
+ exports.Version = Version;
42
+ exports.Timeout = Timeout;
43
+ exports.Log = Log;
44
+ exports.registerControllers = registerControllers;
45
+ require("reflect-metadata");
46
+ const logger_utils_1 = require("./logger.utils");
47
+ const response_utils_1 = require("./response.utils");
48
+ const http_status_codes_1 = require("./http-status-codes");
49
+ const express_rate_limit_1 = require("express-rate-limit");
50
+ const cache_utils_1 = require("./cache.utils");
51
+ // Metadata keys
52
+ const ROUTES_KEY = Symbol('routes');
53
+ const MIDDLEWARE_KEY = Symbol('middlewares');
54
+ const PARAMS_KEY = Symbol('params');
55
+ const HTTP_CODE_KEY = Symbol('httpCode');
56
+ const HEADER_KEY = Symbol('headers');
57
+ const BEFORE_KEY = Symbol('before');
58
+ const AFTER_KEY = Symbol('after');
59
+ const ROLES_KEY = Symbol('roles');
60
+ const REDIRECT_KEY = Symbol('redirect');
61
+ const CACHE_KEY = Symbol('cache');
62
+ const RATE_LIMIT_KEY = Symbol('rateLimit');
63
+ const CONTENT_TYPE_KEY = Symbol('contentType');
64
+ const VERSION_KEY = Symbol('version');
65
+ const TIMEOUT_KEY = Symbol('timeout');
66
+ const LOG_KEY = Symbol('log');
67
+ /**
68
+ * RateLimiter cache that uses TTLCache for automatic TTL and LRU handling.
69
+ */
70
+ class RateLimiterCache {
71
+ cache;
72
+ constructor(maxSize = 100, ttlMs = 5 * 60 * 1000) {
73
+ this.cache = new cache_utils_1.TTLCache({
74
+ maxSize,
75
+ ttlMs
76
+ });
77
+ }
78
+ generateKey(options) {
79
+ return `${options.max}:${options.windowMs}:${options.standardHeaders}:${options.legacyHeaders}`;
80
+ }
81
+ get(options) {
82
+ const key = this.generateKey(options);
83
+ const cached = this.cache.get(key);
84
+ if (cached) {
85
+ return cached.limiter;
86
+ }
87
+ // Create new limiter
88
+ const limiter = (0, express_rate_limit_1.rateLimit)({
89
+ ...options,
90
+ handler: (req, res) => {
91
+ const errorResponse = (0, response_utils_1.createFinalErrorResponse)(req, http_status_codes_1.HttpStatusCodes.TOO_MANY_REQUESTS, 'Too Many Requests');
92
+ res.status(http_status_codes_1.HttpStatusCodes.TOO_MANY_REQUESTS).json(errorResponse);
93
+ }
94
+ });
95
+ // Store in cache
96
+ this.cache.set(key, {
97
+ limiter,
98
+ config: key
99
+ });
100
+ return limiter;
101
+ }
102
+ clear() {
103
+ this.cache.clear();
104
+ }
105
+ size() {
106
+ return this.cache.size();
107
+ }
108
+ destroy() {
109
+ this.cache.destroy();
110
+ }
111
+ }
112
+ // Global cache instance
113
+ const rateLimiterCache = new RateLimiterCache();
114
+ class DIContainer {
115
+ instances = new Map();
116
+ constructing = new Map();
117
+ register(target) {
118
+ // Mark as injectable, but do not instantiate yet
119
+ if (!this.instances.has(target) && !this.constructing.has(target)) {
120
+ // No-op: instantiation is deferred until get()
121
+ }
122
+ }
123
+ get(target) {
124
+ // Return existing instance if available
125
+ if (this.instances.has(target)) {
126
+ return this.instances.get(target);
127
+ }
128
+ // If currently constructing, return the proxy (for circular refs)
129
+ if (this.constructing.has(target)) {
130
+ return this.constructing.get(target);
131
+ }
132
+ // Mark as constructing (for circular dependency support)
133
+ let proxy = {};
134
+ this.constructing.set(target, proxy);
135
+ // Resolve constructor dependencies
136
+ const paramTypes = Reflect.getMetadata('design:paramtypes', target) || [];
137
+ const dependencies = paramTypes.map(dep => this.get(dep));
138
+ const instance = new target(...dependencies);
139
+ // Copy instance properties to proxy (for circular refs)
140
+ Object.assign(proxy, instance);
141
+ // Replace proxy with real instance
142
+ this.instances.set(target, proxy);
143
+ this.constructing.delete(target);
144
+ // Copy prototype (for instanceof checks)
145
+ Object.setPrototypeOf(proxy, target.prototype);
146
+ return proxy;
147
+ }
148
+ clear() {
149
+ this.instances.clear();
150
+ this.constructing.clear();
151
+ }
152
+ }
153
+ exports.DIContainer = DIContainer;
154
+ const diContainer = new DIContainer();
155
+ function normalizeHeaderValue(value) {
156
+ if (typeof value === 'undefined')
157
+ return undefined;
158
+ if (typeof value === 'string')
159
+ return value;
160
+ if (Array.isArray(value) && value.every(item => typeof item === 'string')) {
161
+ return value;
162
+ }
163
+ return String(value);
164
+ }
165
+ /**
166
+ * Injectable decorator for marking classes as injectable.
167
+ *
168
+ * @returns Class decorator that marks a class as injectable and registers it with the DI container.
169
+ */
170
+ function Injectable() {
171
+ return target => {
172
+ Reflect.defineMetadata('injectable', true, target);
173
+ diContainer.register(target);
174
+ };
175
+ }
176
+ /**
177
+ * Inject decorator for injecting dependencies into class properties.
178
+ *
179
+ * @param targetClass - The class to inject
180
+ * @returns Property decorator that injects the specified class into the property
181
+ */
182
+ function Inject(targetClass) {
183
+ return (target, propertyKey) => {
184
+ Object.defineProperty(target, propertyKey, {
185
+ get: function () {
186
+ return diContainer.get(targetClass);
187
+ },
188
+ enumerable: true,
189
+ configurable: true
190
+ });
191
+ };
192
+ }
193
+ /**
194
+ * Factory function that creates HTTP method decorators.
195
+ *
196
+ * @param method - HTTP method to create decorator for
197
+ * @returns A method decorator function
198
+ */
199
+ function createRouteDecorator(method) {
200
+ return (path) => {
201
+ return (target, propertyKey, _descriptor) => {
202
+ const routes = Reflect.getMetadata(ROUTES_KEY, target.constructor) || [];
203
+ routes.push({
204
+ path,
205
+ method,
206
+ handlerName: propertyKey
207
+ });
208
+ Reflect.defineMetadata(ROUTES_KEY, routes, target.constructor);
209
+ };
210
+ };
211
+ }
212
+ /**
213
+ * Decorator for GET HTTP method routes.
214
+ *
215
+ * @param path - URL path for the route
216
+ * @returns Method decorator
217
+ *
218
+ * @example
219
+ * ```ts
220
+ * @Get('/users')
221
+ * getUsers() {
222
+ * return this.userService.findAll();
223
+ * }
224
+ * ```
225
+ */
226
+ exports.Get = createRouteDecorator('get');
227
+ /**
228
+ * Decorator for POST HTTP method routes.
229
+ *
230
+ * @param path - URL path for the route
231
+ * @returns Method decorator
232
+ *
233
+ * @example
234
+ * ```ts
235
+ * @Post('/users')
236
+ * createUser(@Body() userData: any) {
237
+ * return this.userService.create(userData);
238
+ * }
239
+ * ```
240
+ */
241
+ exports.Post = createRouteDecorator('post');
242
+ /**
243
+ * Decorator for PUT HTTP method routes.
244
+ *
245
+ * @param path - URL path for the route
246
+ * @returns Method decorator
247
+ */
248
+ exports.Put = createRouteDecorator('put');
249
+ /**
250
+ * Decorator for PATCH HTTP method routes.
251
+ *
252
+ * @param path - URL path for the route
253
+ * @returns Method decorator
254
+ */
255
+ exports.Patch = createRouteDecorator('patch');
256
+ /**
257
+ * Decorator for DELETE HTTP method routes.
258
+ *
259
+ * @param path - URL path for the route
260
+ * @returns Method decorator
261
+ */
262
+ exports.Delete = createRouteDecorator('delete');
263
+ /**
264
+ * Decorator for OPTIONS HTTP method routes.
265
+ *
266
+ * @param path - URL path for the route
267
+ * @returns Method decorator
268
+ */
269
+ exports.Options = createRouteDecorator('options');
270
+ /**
271
+ * Decorator for HEAD HTTP method routes.
272
+ *
273
+ * @param path - URL path for the route
274
+ * @returns Method decorator
275
+ */
276
+ exports.Head = createRouteDecorator('head');
277
+ /**
278
+ * Decorator for TRACE HTTP method routes.
279
+ *
280
+ * @param path - URL path for the route
281
+ * @returns Method decorator
282
+ */
283
+ exports.Trace = createRouteDecorator('trace');
284
+ /**
285
+ * Decorator for CONNECT HTTP method routes.
286
+ *
287
+ * @param path - URL path for the route
288
+ * @returns Method decorator
289
+ */
290
+ exports.Connect = createRouteDecorator('connect');
291
+ /**
292
+ * Decorator that marks a class as a controller with a base path.
293
+ * Used as the entry point for routing configuration.
294
+ *
295
+ * @param basePath - Base URL path for all routes in this controller
296
+ * @returns Class decorator
297
+ *
298
+ * @example
299
+ * ```ts
300
+ * @Controller('/api/users')
301
+ * class UserController {
302
+ * // Controller methods...
303
+ * }
304
+ * ```
305
+ */
306
+ function Controller(basePath) {
307
+ return target => {
308
+ Reflect.defineMetadata('basePath', basePath, target);
309
+ };
310
+ }
311
+ /**
312
+ * Decorator that applies middleware to a controller method.
313
+ * Multiple middlewares can be applied and will execute in order.
314
+ *
315
+ * @param middlewares - Express middleware functions to apply
316
+ * @returns Method decorator
317
+ *
318
+ * @example
319
+ * ```ts
320
+ * @Get('/protected')
321
+ * @Use(authMiddleware, loggingMiddleware)
322
+ * getProtectedResource() {
323
+ * // This route is protected by auth middleware
324
+ * }
325
+ * ```
326
+ */
327
+ function Use(...middlewares) {
328
+ return (target, propertyKey, _descriptor) => {
329
+ const existing = Reflect.getMetadata(MIDDLEWARE_KEY, target, propertyKey) || [];
330
+ Reflect.defineMetadata(MIDDLEWARE_KEY, [...existing, ...middlewares], target, propertyKey);
331
+ };
332
+ }
333
+ /**
334
+ * Factory function that creates parameter decorators.
335
+ *
336
+ * @param type - Parameter type to extract from request
337
+ * @param key - Optional fixed key for extraction
338
+ * @returns Parameter decorator function
339
+ */
340
+ function createParamDecorator(type, key) {
341
+ return (paramKey) => {
342
+ return (target, propertyKey, parameterIndex) => {
343
+ const params = Reflect.getMetadata(PARAMS_KEY, target, propertyKey) || [];
344
+ // Ensure parameters are ordered by index
345
+ params.push({ index: parameterIndex, type, key: paramKey || key });
346
+ params.sort((a, b) => a.index - b.index);
347
+ Reflect.defineMetadata(PARAMS_KEY, params, target, propertyKey);
348
+ };
349
+ };
350
+ }
351
+ /**
352
+ * Decorator that extracts query parameters from request.
353
+ *
354
+ * @param paramKey - Optional key to extract specific query parameter
355
+ * @returns Parameter decorator
356
+ *
357
+ * @example
358
+ * ```ts
359
+ * @Get('/search')
360
+ * search(@Query('term') searchTerm: string) {
361
+ * // searchTerm will contain the value of req.query.term
362
+ * }
363
+ * ```
364
+ */
365
+ exports.Query = createParamDecorator('query');
366
+ /**
367
+ * Decorator that extracts route parameters from request.
368
+ *
369
+ * @param paramKey - Optional key to extract specific route parameter
370
+ * @returns Parameter decorator
371
+ *
372
+ * @example
373
+ * ```ts
374
+ * @Get('/users/:id')
375
+ * getUser(@Param('id') userId: string) {
376
+ * // userId will contain the value of req.params.id
377
+ * }
378
+ * ```
379
+ */
380
+ exports.Param = createParamDecorator('param');
381
+ /**
382
+ * Decorator that extracts body or body property from request.
383
+ *
384
+ * @param paramKey - Optional key to extract specific body property
385
+ * @returns Parameter decorator
386
+ *
387
+ * @example
388
+ * ```ts
389
+ * @Post('/users')
390
+ * createUser(@Body() userData: any) {
391
+ * // userData will contain the entire req.body
392
+ * }
393
+ *
394
+ * @Post('/update')
395
+ * updateName(@Body('name') name: string) {
396
+ * // name will contain the value of req.body.name
397
+ * }
398
+ * ```
399
+ */
400
+ exports.Body = createParamDecorator('body');
401
+ /**
402
+ * Decorator that injects the entire request object.
403
+ *
404
+ * @returns Parameter decorator
405
+ *
406
+ * @example
407
+ * ```ts
408
+ * @Get('/complex')
409
+ * complex(@Req() req: Request) {
410
+ * // Access the full request object
411
+ * console.log(req.headers);
412
+ * }
413
+ * ```
414
+ */
415
+ exports.Req = createParamDecorator('req');
416
+ /**
417
+ * Decorator that injects the response object.
418
+ *
419
+ * @returns Parameter decorator
420
+ *
421
+ * @example
422
+ * ```ts
423
+ * @Get('/custom')
424
+ * custom(@Res() res: Response) {
425
+ * // Direct access to response object
426
+ * return res.status(201).send('Created');
427
+ * }
428
+ * ```
429
+ */
430
+ exports.Res = createParamDecorator('res');
431
+ /**
432
+ * Decorator that sets a custom HTTP status code for a response.
433
+ *
434
+ * @param status - HTTP status code to use
435
+ * @returns Method decorator
436
+ *
437
+ * @example
438
+ * ```ts
439
+ * @Post('/users')
440
+ * @HttpCode(201)
441
+ * createUser(@Body() userData: any) {
442
+ * // Response will have 201 Created status code
443
+ * return { id: '123', ...userData };
444
+ * }
445
+ * ```
446
+ */
447
+ function HttpCode(status) {
448
+ return (target, propertyKey, _descriptor) => {
449
+ Reflect.defineMetadata(HTTP_CODE_KEY, status, target, propertyKey);
450
+ };
451
+ }
452
+ /**
453
+ * Decorator that adds a custom HTTP header to the response.
454
+ *
455
+ * @param header - Header name-value pairs or a single header name and value
456
+ * @param value - Header value if a single header name is provided
457
+ * @returns Method decorator
458
+ *
459
+ * @example
460
+ * ```ts
461
+ * @Get('/data')
462
+ * @Header('Cache-Control', 'max-age=60')
463
+ * getData() {
464
+ * // Response will include the Cache-Control header
465
+ * return { data: '...' };
466
+ * }
467
+ */
468
+ function Header(name, value) {
469
+ return Headers(name, value);
470
+ }
471
+ /**
472
+ * Decorator that adds a custom HTTP headers to the response.
473
+ *
474
+ * @param headers - Header name-value pairs or a single header name and value
475
+ * @param value - Header value if a single header name is provided
476
+ * @returns Method decorator
477
+ *
478
+ * @example
479
+ * ```ts
480
+ * @Get('/data')
481
+ * @Headers('Cache-Control', 'max-age=60')
482
+ * getData() {
483
+ * // Response will include the Cache-Control header
484
+ * return { data: '...' };
485
+ * }
486
+ *
487
+ * @Get('/data/:id')
488
+ * @Headers({
489
+ * 'Cache-Control': 'max-age=60',
490
+ * 'X-Custom-Header': 'custom-value',
491
+ * 'Content-Security-Policy': "default-src 'self'"
492
+ * })
493
+ * getData() {
494
+ * // Response will include all specified headers
495
+ * return { data: '...' };
496
+ * }
497
+ *
498
+ * ```
499
+ */
500
+ function Headers(headers, value) {
501
+ return (target, propertyKey) => {
502
+ if (typeof propertyKey === 'undefined') {
503
+ // Class decorator
504
+ const existing = Reflect.getMetadata(HEADER_KEY, target) || {};
505
+ const newHeaders = typeof headers === 'string' ? { [headers]: value } : headers;
506
+ Reflect.defineMetadata(HEADER_KEY, { ...existing, ...newHeaders }, target);
507
+ }
508
+ else {
509
+ // Method decorator
510
+ const existing = Reflect.getMetadata(HEADER_KEY, target, propertyKey) || {};
511
+ const newHeaders = typeof headers === 'string' ? { [headers]: value } : headers;
512
+ Reflect.defineMetadata(HEADER_KEY, { ...existing, ...newHeaders }, target, propertyKey);
513
+ }
514
+ };
515
+ }
516
+ /**
517
+ * Decorator that registers a function to run before route handler execution.
518
+ * Useful for pre-processing or logging.
519
+ *
520
+ * @param fn - Function to execute before the handler
521
+ * @returns Method decorator
522
+ *
523
+ * @example
524
+ * ```ts
525
+ * @Get('/users/:id')
526
+ * @Before((req, res) => console.log(`Accessing user ${req.params.id}`))
527
+ * getUser(@Param('id') id: string) {
528
+ * // Function will log before this handler runs
529
+ * }
530
+ * ```
531
+ */
532
+ function Before(fn) {
533
+ return (target, propertyKey, _descriptor) => {
534
+ const hooks = Reflect.getMetadata(BEFORE_KEY, target, propertyKey) || [];
535
+ hooks.push(fn);
536
+ Reflect.defineMetadata(BEFORE_KEY, hooks, target, propertyKey);
537
+ };
538
+ }
539
+ /**
540
+ * Decorator that registers a function to run after route handler execution.
541
+ * Can access the handler's result.
542
+ *
543
+ * @param fn - Function to execute after the handler
544
+ * @returns Method decorator
545
+ *
546
+ * @example
547
+ * ```ts
548
+ * @Get('/users/:id')
549
+ * @After((req, res, result) => console.log(`User data sent: ${JSON.stringify(result)}`))
550
+ * getUser(@Param('id') id: string) {
551
+ * // After this handler, the function will log the returned data
552
+ * return { id, name: 'Example' };
553
+ * }
554
+ * ```
555
+ */
556
+ function After(fn) {
557
+ return (target, propertyKey, _descriptor) => {
558
+ const hooks = Reflect.getMetadata(AFTER_KEY, target, propertyKey) || [];
559
+ hooks.push(fn);
560
+ Reflect.defineMetadata(AFTER_KEY, hooks, target, propertyKey);
561
+ };
562
+ }
563
+ /**
564
+ * Decorator that requires specific roles for accessing a route.
565
+ * Must be used with authentication middleware.
566
+ *
567
+ * Check req.user.roles for user roles[].
568
+ *
569
+ * @param roles - List of roles that can access this route
570
+ * @returns Method decorator
571
+ *
572
+ * @example
573
+ * ```ts
574
+ * @Get('/admin/settings')
575
+ * @Roles('admin', 'superuser')
576
+ * getSettings() {
577
+ * // Only admins and superusers can access
578
+ * return { settings: [...] };
579
+ * }
580
+ * ```
581
+ */
582
+ function Roles(...roles) {
583
+ return (target, propertyKey, _descriptor) => {
584
+ if (typeof propertyKey === 'undefined') {
585
+ // Class decorator
586
+ Reflect.defineMetadata(ROLES_KEY, roles, target);
587
+ }
588
+ else {
589
+ // Method decorator
590
+ Reflect.defineMetadata(ROLES_KEY, roles, target, propertyKey);
591
+ }
592
+ };
593
+ }
594
+ /**
595
+ * Decorator that redirects to another URL.
596
+ *
597
+ * @param url - URL to redirect to (can be absolute or relative)
598
+ * @param statusCode - HTTP status code for redirect (default: 302)
599
+ * @returns Method decorator
600
+ *
601
+ * @example
602
+ * ```ts
603
+ * @Get('/old-path')
604
+ * @Redirect('/new-path', 301)
605
+ * redirectToNewPath() {
606
+ * // This method won't be executed; automatic redirect happens
607
+ * }
608
+ *
609
+ * @Get('/dynamic-redirect')
610
+ * @Redirect()
611
+ * getDynamicRedirect() {
612
+ * // Return an object with url and optionally statusCode
613
+ * return { url: '/calculated-path', statusCode: 307 };
614
+ * }
615
+ * ```
616
+ */
617
+ function Redirect(url, statusCode = 302) {
618
+ return (target, propertyKey, descriptor) => {
619
+ // Store the redirect information in metadata
620
+ Reflect.defineMetadata(REDIRECT_KEY, { url, statusCode }, target, propertyKey);
621
+ return descriptor;
622
+ };
623
+ }
624
+ /**
625
+ * Decorator that adds caching to a route response.
626
+ *
627
+ * @param ttlSeconds - Time to live in seconds for the cache
628
+ * @returns Method decorator
629
+ *
630
+ * @example
631
+ * ```ts
632
+ * @Get('/data')
633
+ * @Cache(300) // Cache for 5 minutes
634
+ * getData() {
635
+ * return { data: 'expensive operation result' };
636
+ * }
637
+ * ```
638
+ */
639
+ function Cache(ttlSeconds) {
640
+ return (target, propertyKey, _descriptor) => {
641
+ if (typeof propertyKey === 'undefined') {
642
+ // Class decorator
643
+ Reflect.defineMetadata(CACHE_KEY, { ttlSeconds }, target);
644
+ }
645
+ else {
646
+ // Method decorator
647
+ Reflect.defineMetadata(CACHE_KEY, { ttlSeconds }, target, propertyKey);
648
+ }
649
+ };
650
+ }
651
+ /**
652
+ * Decorator that applies rate limiting to a route.
653
+ * Note: Requires 'express-rate-limit' package to be installed.
654
+ *
655
+ * @param limit - Maximum number of requests allowed in the window
656
+ * @param windowMs - Time window in milliseconds
657
+ * @returns Method decorator
658
+ *
659
+ * Default Options:
660
+ * - standardHeaders: true
661
+ * - legacyHeaders: false
662
+ *
663
+ * @example
664
+ * ```ts
665
+ * @Post('/login')
666
+ * @RateLimit({ max: 5, windowMs: 60000, standardHeaders: true, legacyHeaders: false }) // 5 requests per minute
667
+ * login(@Body() credentials: LoginDto) {
668
+ * return this.authService.login(credentials);
669
+ * }
670
+ * ```
671
+ */
672
+ function RateLimit(options) {
673
+ const opts = {
674
+ standardHeaders: true,
675
+ legacyHeaders: false,
676
+ ...options
677
+ };
678
+ return (target, propertyKey, _descriptor) => {
679
+ if (typeof propertyKey === 'undefined') {
680
+ // Class decorator
681
+ Reflect.defineMetadata(RATE_LIMIT_KEY, opts, target);
682
+ }
683
+ else {
684
+ // Method decorator
685
+ Reflect.defineMetadata(RATE_LIMIT_KEY, opts, target, propertyKey);
686
+ }
687
+ };
688
+ }
689
+ /**
690
+ * Decorator that sets the content type for the response.
691
+ *
692
+ * @param type - MIME type for the response
693
+ * @returns Method decorator
694
+ *
695
+ * @example
696
+ * ```ts
697
+ * @Get('/download')
698
+ * @ContentType('application/pdf')
699
+ * downloadPdf() {
700
+ * return this.fileService.generatePdf();
701
+ * }
702
+ * ```
703
+ */
704
+ function ContentType(type) {
705
+ return (target, propertyKey, _descriptor) => {
706
+ Reflect.defineMetadata(CONTENT_TYPE_KEY, { type }, target, propertyKey);
707
+ };
708
+ }
709
+ /**
710
+ * Decorator that adds API versioning to a route.
711
+ *
712
+ * @param version - Version string for the API endpoint
713
+ * @param options - Versioning options
714
+ * @returns Method decorator
715
+ *
716
+ * Default Options:
717
+ * - addPrefix: true
718
+ * - addHeader: true
719
+ * - headerName: 'X-API-Version'
720
+ *
721
+ * @example
722
+ * ```ts
723
+ *
724
+ * @Get('/users')
725
+ * @Version('v2')
726
+ * getUsersV2() {
727
+ * return this.userService.findAllV2();
728
+ * }
729
+ *
730
+ * @Get('/users')
731
+ * @Version('v2', { addPrefix: true, addHeader: true, headerName: 'X-API-Version' })
732
+ * getUsersV2() {
733
+ * // Route becomes /v2/users
734
+ * return this.userService.findAllV2();
735
+ * }
736
+ * ```
737
+ */
738
+ function Version(version, options) {
739
+ const opts = {
740
+ addPrefix: true,
741
+ addHeader: true,
742
+ headerName: 'X-API-Version',
743
+ ...options
744
+ };
745
+ return (target, propertyKey, _descriptor) => {
746
+ if (typeof propertyKey === 'undefined') {
747
+ // Class decorator
748
+ Reflect.defineMetadata(VERSION_KEY, { version, options: opts }, target);
749
+ }
750
+ else {
751
+ // Method decorator
752
+ Reflect.defineMetadata(VERSION_KEY, { version, options: opts }, target, propertyKey);
753
+ }
754
+ };
755
+ }
756
+ /**
757
+ * Decorator that sets a timeout for route execution.
758
+ *
759
+ * @param ms - Timeout in milliseconds
760
+ * @returns Method decorator
761
+ *
762
+ * @example
763
+ * ```ts
764
+ * @Get('/slow-operation')
765
+ * @Timeout(30000) // 30 second timeout
766
+ * slowOperation() {
767
+ * return this.heavyService.processData();
768
+ * }
769
+ * ```
770
+ */
771
+ function Timeout(ms) {
772
+ return (target, propertyKey, _descriptor) => {
773
+ if (typeof propertyKey === 'undefined') {
774
+ // Class decorator
775
+ Reflect.defineMetadata(TIMEOUT_KEY, { ms }, target);
776
+ }
777
+ else {
778
+ // Method decorator
779
+ Reflect.defineMetadata(TIMEOUT_KEY, { ms }, target, propertyKey);
780
+ }
781
+ };
782
+ }
783
+ /**
784
+ * Decorator that adds comprehensive logging to a route.
785
+ *
786
+ * @param options - Logging configuration options
787
+ * @returns Method decorator
788
+ *
789
+ * Default Options:
790
+ * - logEntry: true
791
+ * - logExit: true
792
+ * - logBody: false
793
+ * - logParams: false
794
+ * - logResponse: false
795
+ *
796
+ * @example
797
+ * ```ts
798
+ * @Post('/users')
799
+ * @Log({
800
+ * logEntry: true,
801
+ * logExit: true,
802
+ * logBody: true,
803
+ * logParams: true,
804
+ * logResponse: false
805
+ * })
806
+ * createUser(@Body() userData: any) {
807
+ * return this.userService.create(userData);
808
+ * }
809
+ * ```
810
+ */
811
+ function Log(options) {
812
+ return (target, propertyKey, _descriptor) => {
813
+ const config = {
814
+ logEntry: true,
815
+ logExit: true,
816
+ logBody: false,
817
+ logParams: false,
818
+ logResponse: false,
819
+ ...options
820
+ };
821
+ if (typeof propertyKey === 'undefined') {
822
+ // Class decorator
823
+ Reflect.defineMetadata(LOG_KEY, config, target);
824
+ }
825
+ else {
826
+ // Method decorator
827
+ Reflect.defineMetadata(LOG_KEY, config, target, propertyKey);
828
+ }
829
+ };
830
+ }
831
+ /**
832
+ * Registers all controller classes with the provided router.
833
+ * This function processes all decorators and sets up the Express routes.
834
+ *
835
+ * @param router - Express router instance
836
+ * @param controllers - Array of controller classes
837
+ */
838
+ function registerControllers(router, controllers) {
839
+ controllers.forEach(ControllerClass => {
840
+ // Use DI container to resolve controller (constructor injection + property injection)
841
+ const instance = diContainer.get(ControllerClass);
842
+ const basePath = Reflect.getMetadata('basePath', ControllerClass) || '';
843
+ const routes = Reflect.getMetadata(ROUTES_KEY, ControllerClass) || [];
844
+ // Get controller-level decorators (fallback values)
845
+ const controllerRateLimit = Reflect.getMetadata(RATE_LIMIT_KEY, ControllerClass);
846
+ const controllerCache = Reflect.getMetadata(CACHE_KEY, ControllerClass);
847
+ const controllerTimeout = Reflect.getMetadata(TIMEOUT_KEY, ControllerClass);
848
+ const controllerVersion = Reflect.getMetadata(VERSION_KEY, ControllerClass);
849
+ const controllerRoles = Reflect.getMetadata(ROLES_KEY, ControllerClass);
850
+ const controllerLogConfig = Reflect.getMetadata(LOG_KEY, ControllerClass);
851
+ const controllerHeaders = Reflect.getMetadata(HEADER_KEY, ControllerClass) || {};
852
+ routes.forEach(({ path, method, handlerName }) => {
853
+ const middlewares = Reflect.getMetadata(MIDDLEWARE_KEY, instance, handlerName) || [];
854
+ const params = Reflect.getMetadata(PARAMS_KEY, instance, handlerName) || [];
855
+ const httpCode = Reflect.getMetadata(HTTP_CODE_KEY, instance, handlerName);
856
+ // Merge controller-level and method-level headers
857
+ const methodHeaders = Reflect.getMetadata(HEADER_KEY, instance, handlerName) || {};
858
+ const headers = { ...controllerHeaders, ...methodHeaders };
859
+ const beforeHooks = Reflect.getMetadata(BEFORE_KEY, instance, handlerName) || [];
860
+ const afterHooks = Reflect.getMetadata(AFTER_KEY, instance, handlerName) || [];
861
+ const redirect = Reflect.getMetadata(REDIRECT_KEY, instance, handlerName);
862
+ const contentType = Reflect.getMetadata(CONTENT_TYPE_KEY, instance, handlerName);
863
+ // Use method-level decorators if present, otherwise fall back to controller-level
864
+ const roles = Reflect.getMetadata(ROLES_KEY, instance, handlerName) || controllerRoles || [];
865
+ const cache = Reflect.getMetadata(CACHE_KEY, instance, handlerName) || controllerCache;
866
+ const rateLimitOptions = Reflect.getMetadata(RATE_LIMIT_KEY, instance, handlerName) || controllerRateLimit;
867
+ const version = Reflect.getMetadata(VERSION_KEY, instance, handlerName) || controllerVersion;
868
+ const timeout = Reflect.getMetadata(TIMEOUT_KEY, instance, handlerName) || controllerTimeout;
869
+ const logConfig = Reflect.getMetadata(LOG_KEY, instance, handlerName) || controllerLogConfig;
870
+ // Create rate limiter for this specific route if needed
871
+ let rateLimiter = null;
872
+ if (rateLimitOptions) {
873
+ try {
874
+ rateLimiter = rateLimiterCache.get(rateLimitOptions);
875
+ }
876
+ catch (err) {
877
+ (0, logger_utils_1.getLogger)().warn({ err }, 'express-rate-limit not available, skipping rate limiting for this route');
878
+ }
879
+ }
880
+ let finalPath = path;
881
+ if (version?.options?.addPrefix) {
882
+ finalPath = `/${version.version}${path}`;
883
+ }
884
+ const handler = async (req, res, next) => {
885
+ // Set start time for duration tracking
886
+ req['startTime'] = Date.now();
887
+ let timeoutId;
888
+ let timedOut = false;
889
+ try {
890
+ // Handle timeout setup
891
+ if (timeout) {
892
+ timeoutId = setTimeout(() => {
893
+ if (!res.headersSent && !timedOut) {
894
+ timedOut = true;
895
+ const errorResponse = (0, response_utils_1.createFinalErrorResponse)(req, http_status_codes_1.HttpStatusCodes.REQUEST_TIMEOUT, 'Request timed out');
896
+ res.status(http_status_codes_1.HttpStatusCodes.REQUEST_TIMEOUT).json(errorResponse);
897
+ }
898
+ }, timeout.ms);
899
+ }
900
+ if (timedOut)
901
+ return;
902
+ // Handle rate limiting
903
+ if (rateLimiter) {
904
+ await new Promise((resolve, reject) => {
905
+ rateLimiter(req, res, (err) => {
906
+ if (err)
907
+ reject(err);
908
+ else
909
+ resolve();
910
+ });
911
+ });
912
+ }
913
+ // Handle content type
914
+ if (!res.headersSent && contentType) {
915
+ res.setHeader('Content-Type', contentType.type);
916
+ }
917
+ // Handle versioning header
918
+ if (version?.options?.addHeader && version?.options?.headerName && version?.version) {
919
+ if (!res.headersSent) {
920
+ res.setHeader(version.options.headerName, version.version);
921
+ }
922
+ }
923
+ // Handle caching
924
+ if (cache && !res.headersSent) {
925
+ res.setHeader('Cache-Control', `public, max-age=${cache.ttlSeconds}`);
926
+ }
927
+ // Handle logging - entry
928
+ if (logConfig?.logEntry) {
929
+ const logger = (0, logger_utils_1.getLogger)();
930
+ const logData = {
931
+ method: req.method,
932
+ url: req.originalUrl || req.url,
933
+ userAgent: req.get('User-Agent')
934
+ };
935
+ if (logConfig.logParams) {
936
+ logData.params = req.params;
937
+ logData.query = req.query;
938
+ }
939
+ if (logConfig.logBody)
940
+ logData.body = req.body;
941
+ logger.info({ entry: logData }, 'Route Entry:');
942
+ }
943
+ // Handle roles-based access control
944
+ if (roles.length && !req?.user?.roles?.some((role) => roles.includes(role))) {
945
+ const errorResponse = (0, response_utils_1.createFinalErrorResponse)(req, http_status_codes_1.HttpStatusCodes.FORBIDDEN, 'Forbidden Insufficient Roles');
946
+ res.status(http_status_codes_1.HttpStatusCodes.FORBIDDEN).json(errorResponse);
947
+ if (timeoutId) {
948
+ clearTimeout(timeoutId);
949
+ }
950
+ return;
951
+ }
952
+ // Process static redirect if configured
953
+ if (redirect && redirect.url) {
954
+ if (timeoutId) {
955
+ clearTimeout(timeoutId);
956
+ }
957
+ return res.redirect(redirect.statusCode, redirect.url);
958
+ }
959
+ for (const fn of beforeHooks)
960
+ await fn(req, res);
961
+ const args = [];
962
+ if (params.length) {
963
+ params.forEach(({ index, type, key }) => {
964
+ switch (type) {
965
+ case 'query':
966
+ args[index] = key ? req.query[key] : req.query;
967
+ break;
968
+ case 'param':
969
+ args[index] = key ? req.params[key] : req.params;
970
+ break;
971
+ case 'body':
972
+ args[index] = key ? req.body?.[key] : req.body;
973
+ break;
974
+ case 'req':
975
+ args[index] = req;
976
+ break;
977
+ case 'res':
978
+ args[index] = res;
979
+ break;
980
+ }
981
+ });
982
+ }
983
+ const result = instance[handlerName](...args);
984
+ // Support both sync and async handlers
985
+ const awaited = result instanceof Promise ? await result : result;
986
+ // Clear timeout if operation completed
987
+ if (timeoutId) {
988
+ clearTimeout(timeoutId);
989
+ }
990
+ if (timedOut)
991
+ return;
992
+ // Handle dynamic redirects
993
+ if (redirect && awaited && typeof awaited === 'object' && 'url' in awaited) {
994
+ const redirectUrl = awaited.url;
995
+ const redirectStatus = awaited.statusCode || redirect.statusCode;
996
+ return res.redirect(redirectStatus, redirectUrl);
997
+ }
998
+ if (!res.headersSent && typeof awaited !== 'undefined') {
999
+ if (httpCode)
1000
+ res.status(httpCode);
1001
+ for (const [k, v] of Object.entries(headers)) {
1002
+ const normalized = normalizeHeaderValue(v);
1003
+ if (typeof normalized !== 'undefined') {
1004
+ res.set(k, normalized);
1005
+ }
1006
+ }
1007
+ res.json(awaited);
1008
+ }
1009
+ // Handle logging - exit
1010
+ if (logConfig?.logExit) {
1011
+ const logger = (0, logger_utils_1.getLogger)();
1012
+ const logData = {
1013
+ method: req.method,
1014
+ url: req.originalUrl || req.url,
1015
+ statusCode: res.statusCode,
1016
+ duration: `${Date.now() - req.startTime}ms`
1017
+ };
1018
+ if (logConfig.logResponse)
1019
+ logData.response = awaited;
1020
+ logger.info({ exit: logData }, 'Route Exit:');
1021
+ }
1022
+ for (const fn of afterHooks)
1023
+ await fn(req, res, awaited);
1024
+ }
1025
+ catch (err) {
1026
+ if (timeoutId) {
1027
+ clearTimeout(timeoutId);
1028
+ }
1029
+ next(err);
1030
+ }
1031
+ };
1032
+ router[method](basePath + finalPath, ...middlewares, handler);
1033
+ });
1034
+ });
1035
+ }