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