exisjs 0.7.8 → 0.7.10

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 (147) hide show
  1. package/dist/cli/commands/dev.d.ts.map +1 -1
  2. package/dist/cli/commands/dev.js +14 -4
  3. package/dist/cli/commands/dev.js.map +1 -1
  4. package/dist/cli/resolve-aliases.js +1 -1
  5. package/dist/cli/resolve-aliases.js.map +1 -1
  6. package/dist/config/config.js +1 -1
  7. package/dist/config/config.js.map +1 -1
  8. package/dist/decorators/caching.d.ts +53 -0
  9. package/dist/decorators/caching.d.ts.map +1 -0
  10. package/dist/decorators/caching.js +154 -0
  11. package/dist/decorators/caching.js.map +1 -0
  12. package/dist/decorators/constants.d.ts +6 -0
  13. package/dist/decorators/constants.d.ts.map +1 -1
  14. package/dist/decorators/constants.js +7 -1
  15. package/dist/decorators/constants.js.map +1 -1
  16. package/dist/decorators/core.d.ts +14 -0
  17. package/dist/decorators/core.d.ts.map +1 -1
  18. package/dist/decorators/core.js +156 -41
  19. package/dist/decorators/core.js.map +1 -1
  20. package/dist/decorators/filters.d.ts +29 -0
  21. package/dist/decorators/filters.d.ts.map +1 -0
  22. package/dist/decorators/filters.js +30 -0
  23. package/dist/decorators/filters.js.map +1 -0
  24. package/dist/decorators/index.d.ts +7 -0
  25. package/dist/decorators/index.d.ts.map +1 -1
  26. package/dist/decorators/index.js +7 -0
  27. package/dist/decorators/index.js.map +1 -1
  28. package/dist/decorators/lifecycle.d.ts +66 -0
  29. package/dist/decorators/lifecycle.d.ts.map +1 -0
  30. package/dist/decorators/lifecycle.js +3 -0
  31. package/dist/decorators/lifecycle.js.map +1 -0
  32. package/dist/decorators/middleware.d.ts +24 -3
  33. package/dist/decorators/middleware.d.ts.map +1 -1
  34. package/dist/decorators/middleware.js +24 -3
  35. package/dist/decorators/middleware.js.map +1 -1
  36. package/dist/decorators/params.d.ts +3 -7
  37. package/dist/decorators/params.d.ts.map +1 -1
  38. package/dist/decorators/params.js +1 -7
  39. package/dist/decorators/params.js.map +1 -1
  40. package/dist/decorators/pipes.d.ts +91 -0
  41. package/dist/decorators/pipes.d.ts.map +1 -0
  42. package/dist/decorators/pipes.js +283 -0
  43. package/dist/decorators/pipes.js.map +1 -0
  44. package/dist/decorators/profile.d.ts +32 -0
  45. package/dist/decorators/profile.d.ts.map +1 -0
  46. package/dist/decorators/profile.js +61 -0
  47. package/dist/decorators/profile.js.map +1 -0
  48. package/dist/decorators/response.d.ts +0 -1
  49. package/dist/decorators/response.d.ts.map +1 -1
  50. package/dist/decorators/response.js +1 -18
  51. package/dist/decorators/response.js.map +1 -1
  52. package/dist/decorators/routing.d.ts +22 -12
  53. package/dist/decorators/routing.d.ts.map +1 -1
  54. package/dist/decorators/routing.js +49 -3
  55. package/dist/decorators/routing.js.map +1 -1
  56. package/dist/decorators/security.d.ts +42 -0
  57. package/dist/decorators/security.d.ts.map +1 -0
  58. package/dist/decorators/security.js +110 -0
  59. package/dist/decorators/security.js.map +1 -0
  60. package/dist/decorators/transaction.d.ts +29 -0
  61. package/dist/decorators/transaction.d.ts.map +1 -0
  62. package/dist/decorators/transaction.js +91 -0
  63. package/dist/decorators/transaction.js.map +1 -0
  64. package/dist/di/container.d.ts +8 -3
  65. package/dist/di/container.d.ts.map +1 -1
  66. package/dist/di/container.js +214 -66
  67. package/dist/di/container.js.map +1 -1
  68. package/dist/di/decorators.d.ts +23 -5
  69. package/dist/di/decorators.d.ts.map +1 -1
  70. package/dist/di/decorators.js +83 -20
  71. package/dist/di/decorators.js.map +1 -1
  72. package/dist/di/forward-ref.d.ts +23 -0
  73. package/dist/di/forward-ref.d.ts.map +1 -0
  74. package/dist/di/forward-ref.js +30 -0
  75. package/dist/di/forward-ref.js.map +1 -0
  76. package/dist/di/index.d.ts +2 -1
  77. package/dist/di/index.d.ts.map +1 -1
  78. package/dist/di/index.js +8 -1
  79. package/dist/di/index.js.map +1 -1
  80. package/dist/docs/content/controllers.mdx +230 -0
  81. package/dist/docs/content/decorators.mdx +89 -4
  82. package/dist/docs/content/errors.mdx +44 -21
  83. package/dist/docs/content/modules.mdx +23 -4
  84. package/dist/docs/content/providers.mdx +90 -6
  85. package/dist/docs/content/requests.mdx +9 -3
  86. package/dist/docs/content/responses.mdx +19 -5
  87. package/dist/docs/reference/config.mdx +3 -2
  88. package/dist/docs/reference/decorators.mdx +42 -8
  89. package/dist/docs/reference/di.mdx +45 -12
  90. package/dist/docs/reference/router.mdx +75 -0
  91. package/dist/docs/reference/validator.mdx +2 -2
  92. package/dist/error/errors.d.ts +24 -12
  93. package/dist/error/errors.d.ts.map +1 -1
  94. package/dist/error/errors.js +48 -12
  95. package/dist/error/errors.js.map +1 -1
  96. package/dist/router/controller-registrar.d.ts.map +1 -1
  97. package/dist/router/controller-registrar.js +217 -94
  98. package/dist/router/controller-registrar.js.map +1 -1
  99. package/dist/router/index.d.ts.map +1 -1
  100. package/dist/router/index.js.map +1 -1
  101. package/dist/router/route-builder.d.ts +160 -29
  102. package/dist/router/route-builder.d.ts.map +1 -1
  103. package/dist/router/route-builder.js +105 -31
  104. package/dist/router/route-builder.js.map +1 -1
  105. package/dist/router/route-scanner.d.ts +3 -2
  106. package/dist/router/route-scanner.d.ts.map +1 -1
  107. package/dist/router/route-scanner.js.map +1 -1
  108. package/dist/router/router.d.ts.map +1 -1
  109. package/dist/router/router.js +44 -0
  110. package/dist/router/router.js.map +1 -1
  111. package/dist/router/scanner/functional-compiler.d.ts +1 -1
  112. package/dist/router/scanner/functional-compiler.d.ts.map +1 -1
  113. package/dist/router/scanner/functional-compiler.js +403 -64
  114. package/dist/router/scanner/functional-compiler.js.map +1 -1
  115. package/dist/router/types.d.ts +4 -2
  116. package/dist/router/types.d.ts.map +1 -1
  117. package/dist/server/app.d.ts +2 -0
  118. package/dist/server/app.d.ts.map +1 -1
  119. package/dist/server/app.js +40 -0
  120. package/dist/server/app.js.map +1 -1
  121. package/dist/server/helpers/etag.d.ts +1 -0
  122. package/dist/server/helpers/etag.d.ts.map +1 -1
  123. package/dist/server/helpers/etag.js +121 -10
  124. package/dist/server/helpers/etag.js.map +1 -1
  125. package/dist/server/helpers/ip.d.ts.map +1 -1
  126. package/dist/server/helpers/ip.js +75 -17
  127. package/dist/server/helpers/ip.js.map +1 -1
  128. package/dist/server/response.d.ts.map +1 -1
  129. package/dist/server/response.js +24 -9
  130. package/dist/server/response.js.map +1 -1
  131. package/dist/swagger/generator.d.ts.map +1 -1
  132. package/dist/swagger/generator.js +66 -22
  133. package/dist/swagger/generator.js.map +1 -1
  134. package/dist/testing/client.d.ts +16 -12
  135. package/dist/testing/client.d.ts.map +1 -1
  136. package/dist/testing/client.js +51 -15
  137. package/dist/testing/client.js.map +1 -1
  138. package/dist/testing/index.d.ts.map +1 -1
  139. package/dist/testing/index.js +10 -0
  140. package/dist/testing/index.js.map +1 -1
  141. package/dist/utils/logger.d.ts.map +1 -1
  142. package/dist/utils/logger.js +2 -0
  143. package/dist/utils/logger.js.map +1 -1
  144. package/dist/validator/engine.d.ts.map +1 -1
  145. package/dist/validator/engine.js +45 -0
  146. package/dist/validator/engine.js.map +1 -1
  147. package/package.json +1 -1
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.forwardRef = forwardRef;
4
+ exports.isForwardRef = isForwardRef;
5
+ /**
6
+ * Creates a lazy forward reference for resolving circular dependencies between providers or modules.
7
+ *
8
+ * Example:
9
+ * ```ts
10
+ * @Injectable()
11
+ * export class OrderService {
12
+ * constructor(
13
+ * @Inject(forwardRef(() => InventoryService))
14
+ * private inventoryService: InventoryService
15
+ * ) {}
16
+ * }
17
+ * ```
18
+ */
19
+ function forwardRef(fn) {
20
+ return { forwardRef: fn };
21
+ }
22
+ /**
23
+ * Checks if a value is a ForwardReference wrapper.
24
+ */
25
+ function isForwardRef(val) {
26
+ return (val !== null &&
27
+ typeof val === 'object' &&
28
+ typeof val.forwardRef === 'function');
29
+ }
30
+ //# sourceMappingURL=forward-ref.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forward-ref.js","sourceRoot":"","sources":["../../src/di/forward-ref.ts"],"names":[],"mappings":";;AAkBA,gCAEC;AAKD,oCAMC;AA3BD;;;;;;;;;;;;;GAaG;AACH,SAAgB,UAAU,CAAU,EAAW;IAC7C,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE,CAAA;AAC3B,CAAC;AAED;;GAEG;AACH,SAAgB,YAAY,CAAC,GAAQ;IACnC,OAAO,CACL,GAAG,KAAK,IAAI;QACZ,OAAO,GAAG,KAAK,QAAQ;QACvB,OAAO,GAAG,CAAC,UAAU,KAAK,UAAU,CACrC,CAAA;AACH,CAAC"}
@@ -1,5 +1,6 @@
1
1
  export { inject } from './inject';
2
- export { Container } from './container';
2
+ export { Container, INJECT_METADATA, PROPERTY_INJECT_METADATA, OPTIONAL_METADATA, SCOPE_METADATA, } from './container';
3
3
  export { Inject, Optional } from './decorators';
4
4
  export type { ProviderToken, ProviderDefinition, ValueProvider, FactoryProvider, ClassProvider, } from './container';
5
+ export { forwardRef, isForwardRef, type ForwardReference } from './forward-ref';
5
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/di/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAC/C,YAAY,EACV,aAAa,EACb,kBAAkB,EAClB,aAAa,EACb,eAAe,EACf,aAAa,GACd,MAAM,aAAa,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/di/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AACjC,OAAO,EACL,SAAS,EACT,eAAe,EACf,wBAAwB,EACxB,iBAAiB,EACjB,cAAc,GACf,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAC/C,YAAY,EACV,aAAa,EACb,kBAAkB,EAClB,aAAa,EACb,eAAe,EACf,aAAa,GACd,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAA"}
package/dist/di/index.js CHANGED
@@ -1,11 +1,18 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Optional = exports.Inject = exports.Container = exports.inject = void 0;
3
+ exports.isForwardRef = exports.forwardRef = exports.Optional = exports.Inject = exports.SCOPE_METADATA = exports.OPTIONAL_METADATA = exports.PROPERTY_INJECT_METADATA = exports.INJECT_METADATA = exports.Container = exports.inject = void 0;
4
4
  var inject_1 = require("./inject");
5
5
  Object.defineProperty(exports, "inject", { enumerable: true, get: function () { return inject_1.inject; } });
6
6
  var container_1 = require("./container");
7
7
  Object.defineProperty(exports, "Container", { enumerable: true, get: function () { return container_1.Container; } });
8
+ Object.defineProperty(exports, "INJECT_METADATA", { enumerable: true, get: function () { return container_1.INJECT_METADATA; } });
9
+ Object.defineProperty(exports, "PROPERTY_INJECT_METADATA", { enumerable: true, get: function () { return container_1.PROPERTY_INJECT_METADATA; } });
10
+ Object.defineProperty(exports, "OPTIONAL_METADATA", { enumerable: true, get: function () { return container_1.OPTIONAL_METADATA; } });
11
+ Object.defineProperty(exports, "SCOPE_METADATA", { enumerable: true, get: function () { return container_1.SCOPE_METADATA; } });
8
12
  var decorators_1 = require("./decorators");
9
13
  Object.defineProperty(exports, "Inject", { enumerable: true, get: function () { return decorators_1.Inject; } });
10
14
  Object.defineProperty(exports, "Optional", { enumerable: true, get: function () { return decorators_1.Optional; } });
15
+ var forward_ref_1 = require("./forward-ref");
16
+ Object.defineProperty(exports, "forwardRef", { enumerable: true, get: function () { return forward_ref_1.forwardRef; } });
17
+ Object.defineProperty(exports, "isForwardRef", { enumerable: true, get: function () { return forward_ref_1.isForwardRef; } });
11
18
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/di/index.ts"],"names":[],"mappings":";;;AAAA,mCAAiC;AAAxB,gGAAA,MAAM,OAAA;AACf,yCAAuC;AAA9B,sGAAA,SAAS,OAAA;AAClB,2CAA+C;AAAtC,oGAAA,MAAM,OAAA;AAAE,sGAAA,QAAQ,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/di/index.ts"],"names":[],"mappings":";;;AAAA,mCAAiC;AAAxB,gGAAA,MAAM,OAAA;AACf,yCAMoB;AALlB,sGAAA,SAAS,OAAA;AACT,4GAAA,eAAe,OAAA;AACf,qHAAA,wBAAwB,OAAA;AACxB,8GAAA,iBAAiB,OAAA;AACjB,2GAAA,cAAc,OAAA;AAEhB,2CAA+C;AAAtC,oGAAA,MAAM,OAAA;AAAE,sGAAA,QAAQ,OAAA;AAQzB,6CAA+E;AAAtE,yGAAA,UAAU,OAAA;AAAE,2GAAA,YAAY,OAAA"}
@@ -353,6 +353,236 @@ export default class UsersController {
353
353
  }
354
354
  ```
355
355
 
356
+ ## Controller Inheritance (Base & Abstract Controllers)
357
+
358
+ ExisJS supports full OOP inheritance for controllers. You can create generic base or abstract CRUD controllers and inherit all route handlers, parameter decorators, middlewares, guards, interceptors, and filters down to subclasses:
359
+
360
+ ```typescript filename="src/http/base-crud.controller.ts"
361
+ import { Get, Post, Delete, Param, Body } from 'exisjs/decorators'
362
+
363
+ export abstract class BaseCrudController<T> {
364
+ protected abstract resourceName: string
365
+
366
+ @Get('/')
367
+ async findAll(): Promise<T[]> {
368
+ return []
369
+ }
370
+
371
+ @Get('/:id')
372
+ async findById(@Param('id') id: string): Promise<T> {
373
+ return { id, resource: this.resourceName } as any
374
+ }
375
+
376
+ @Delete('/:id')
377
+ async remove(@Param('id') id: string): Promise<{ deleted: boolean }> {
378
+ return { deleted: true }
379
+ }
380
+ }
381
+ ```
382
+
383
+ Subclasses inherit all parent endpoints and can add new routes or selectively override base methods:
384
+
385
+ ```typescript filename="src/http/users/route.ts"
386
+ import { Controller, Post, Body, Param } from 'exisjs/decorators'
387
+ import { BaseCrudController } from '../base-crud.controller'
388
+
389
+ export interface User {
390
+ id: string
391
+ name: string
392
+ }
393
+
394
+ @Controller('/users')
395
+ export default class UsersController extends BaseCrudController<User> {
396
+ protected resourceName = 'users'
397
+
398
+ // Subclass defines additional route
399
+ @Post('/')
400
+ async create(@Body() user: User) {
401
+ return { created: true, user }
402
+ }
403
+
404
+ // Subclass can override inherited remove handler
405
+ override async remove(@Param('id') id: string) {
406
+ return { deleted: true, id, customAudit: true }
407
+ }
408
+ }
409
+ ```
410
+
411
+ ### Functional Controller Inheritance (`controller.extend`)
412
+
413
+ In the functional paradigm, inheritance is achieved via `controller.extend(base, overrides)`. This inherits all route handlers, lifecycle hooks, and middlewares from a base controller factory and allows overriding endpoints or adding new routes with full type inference:
414
+
415
+ ```typescript filename="src/http/products/route.ts"
416
+ import { controller, route } from 'exisjs/router'
417
+
418
+ // 1. Generic functional base CRUD controller
419
+ function createBaseCrud(resourceName: string) {
420
+ return controller({
421
+ list: route.get('/', {
422
+ async handle() {
423
+ return { resource: resourceName, items: [] }
424
+ }
425
+ }),
426
+ getById: route.get('/:id', {
427
+ async handle({ params }) {
428
+ return { resource: resourceName, id: params.id }
429
+ }
430
+ }),
431
+ remove: route.delete('/:id', {
432
+ async handle({ params }) {
433
+ return { deleted: true, id: params.id }
434
+ }
435
+ })
436
+ })
437
+ }
438
+
439
+ // 2. Extend base controller with overrides and additional routes
440
+ export default controller.extend(createBaseCrud('products'), {
441
+ // Additional specialized route
442
+ featured: route.get('/featured', {
443
+ async handle() {
444
+ return { featured: ['Product 1', 'Product 2'] }
445
+ }
446
+ }),
447
+
448
+ // Override base endpoint
449
+ getById: route.get('/:id', {
450
+ async handle({ params }) {
451
+ return { customProduct: true, id: params.id }
452
+ }
453
+ })
454
+ })
455
+ ```
456
+
457
+ ## Functional Controller Composition (`controller.merge`)
458
+
459
+ When a single `route.ts` manages a feature area with multiple concerns (for example, auth flows containing credentials, sessions, and OAuth), `controller.merge(...controllers)` allows splitting logic into modular controller objects and merging them into a single default export:
460
+
461
+ ```typescript filename="src/http/auth/route.ts"
462
+ import { controller, route } from 'exisjs/router'
463
+
464
+ // 1. Modular controller for password authentication
465
+ const passwordAuth = controller({
466
+ login: route.post('/login', {
467
+ async handle({ body }) {
468
+ return { token: 'jwt-auth-token' }
469
+ }
470
+ }),
471
+ logout: route.post('/logout', {
472
+ async handle() {
473
+ return { success: true }
474
+ }
475
+ })
476
+ })
477
+
478
+ // 2. Modular controller for OAuth providers
479
+ const oauthProviders = controller({
480
+ google: route.get('/google', {
481
+ async handle() {
482
+ return { redirectUrl: 'https://accounts.google.com/o/oauth2/auth' }
483
+ }
484
+ }),
485
+ github: route.get('/github', {
486
+ async handle() {
487
+ return { redirectUrl: 'https://github.com/login/oauth/authorize' }
488
+ }
489
+ })
490
+ })
491
+
492
+ // 3. Merge both into the single exported controller for this folder
493
+ export default controller.merge(passwordAuth, oauthProviders)
494
+ ```
495
+
496
+ ## Built-in Transformation Pipes & `@UsePipes`
497
+
498
+ Pipes transform and validate incoming request data before it reaches the route handler. ExisJS provides built-in pipes for common primitives:
499
+
500
+ <div className="overflow-x-auto mb-6">
501
+ <table className="w-full text-sm text-left border border-border/50 rounded-lg overflow-hidden">
502
+ <thead className="bg-muted/50 text-foreground">
503
+ <tr>
504
+ <th className="px-4 py-3 border-b border-border/50 font-semibold w-1/3">Pipe</th>
505
+ <th className="px-4 py-3 border-b border-border/50 font-semibold">Description</th>
506
+ </tr>
507
+ </thead>
508
+ <tbody className="divide-y divide-border/50">
509
+ <tr className="bg-background hover:bg-muted/30 transition-colors">
510
+ <td className="px-4 py-3 font-mono text-xs">ParseIntPipe</td>
511
+ <td className="px-4 py-3 text-muted-foreground">Parses string/number parameter into integer or throws 400 Bad Request</td>
512
+ </tr>
513
+ <tr className="bg-background hover:bg-muted/30 transition-colors">
514
+ <td className="px-4 py-3 font-mono text-xs">ParseFloatPipe</td>
515
+ <td className="px-4 py-3 text-muted-foreground">Parses parameter into floating-point number</td>
516
+ </tr>
517
+ <tr className="bg-background hover:bg-muted/30 transition-colors">
518
+ <td className="px-4 py-3 font-mono text-xs">ParseBoolPipe</td>
519
+ <td className="px-4 py-3 text-muted-foreground">Parses boolean strings (<code>'true'</code>, <code>'false'</code>, <code>1</code>, <code>0</code>) into native booleans</td>
520
+ </tr>
521
+ <tr className="bg-background hover:bg-muted/30 transition-colors">
522
+ <td className="px-4 py-3 font-mono text-xs">ParseUUIDPipe</td>
523
+ <td className="px-4 py-3 text-muted-foreground">Validates UUID format (v3, v4, v5, or all)</td>
524
+ </tr>
525
+ <tr className="bg-background hover:bg-muted/30 transition-colors">
526
+ <td className="px-4 py-3 font-mono text-xs">ParseArrayPipe</td>
527
+ <td className="px-4 py-3 text-muted-foreground">Splits comma-separated strings into arrays and applies item pipes</td>
528
+ </tr>
529
+ <tr className="bg-background hover:bg-muted/30 transition-colors">
530
+ <td className="px-4 py-3 font-mono text-xs">ValidationPipe</td>
531
+ <td className="px-4 py-3 text-muted-foreground">Validates and transforms DTO classes or schema metatypes</td>
532
+ </tr>
533
+ </tbody>
534
+ </table>
535
+ </div>
536
+
537
+ ```typescript filename="src/http/items/route.ts"
538
+ import {
539
+ Controller,
540
+ Get,
541
+ Post,
542
+ Param,
543
+ Query,
544
+ Body,
545
+ UsePipes,
546
+ ParseIntPipe,
547
+ ParseBoolPipe,
548
+ ParseUUIDPipe,
549
+ ParseArrayPipe,
550
+ ValidationPipe,
551
+ } from 'exisjs/decorators'
552
+
553
+ @Controller('/items')
554
+ export default class ItemsController {
555
+ // 1. Parameter-level pipe
556
+ @Get('/:id')
557
+ async getItem(@Param('id', ParseIntPipe) id: number) {
558
+ return { id, isNumber: typeof id === 'number' }
559
+ }
560
+
561
+ // 2. Query boolean & array pipes
562
+ @Get('/')
563
+ async listItems(
564
+ @Query('active', ParseBoolPipe) active: boolean,
565
+ @Query('ids', new ParseArrayPipe({ items: ParseIntPipe })) ids: number[]
566
+ ) {
567
+ return { active, ids }
568
+ }
569
+
570
+ // 3. UUID validation pipe
571
+ @Get('/by-uuid/:uuid')
572
+ async getByUUID(@Param('uuid', ParseUUIDPipe) uuid: string) {
573
+ return { uuid }
574
+ }
575
+
576
+ // 4. Method-level or Class-level @UsePipes
577
+ @Post('/')
578
+ @UsePipes(new ValidationPipe({ transform: true }))
579
+ async createItem(@Body() body: any) {
580
+ return { created: true, body }
581
+ }
582
+ }
583
+ ```
584
+
356
585
  <Info title="Tip: Safely Returning with @Res">
357
586
  In some frameworks, injecting the `@Res()` object disables auto-serialization, causing the request to hang if you forget to call `res.send()`. ExisJS handles this differently: if you return a value from your handler, ExisJS will still safely auto-serialize it to JSON even if you injected `@Res()` to set a custom header.
358
587
  </Info>
588
+
@@ -178,6 +178,10 @@ export default class CheckoutController {
178
178
 
179
179
  ### Available Lifecycle & Middleware Decorators
180
180
  - `@Use(...items)` — Unified decorator that attaches middlewares, guards (`canActivate`), interceptors (`intercept`), or exception filters (`catch`) to a class or method.
181
+ - `@UseGuards(...guards)` — Explicit alias for `@Use` dedicated to route guards.
182
+ - `@UseInterceptors(...interceptors)` — Explicit alias for `@Use` dedicated to interceptors.
183
+ - `@UseFilters(...filters)` — Explicit alias for `@Use` dedicated to exception filters.
184
+ - `@Catch(...exceptions)` — Declares exception classes handled by an Exception Filter.
181
185
  - `@Timeout(ms: number | TimeoutOptions)` — Overrides the request timeout for a class or method.
182
186
  - `@Idempotent(options?)` — Caches responses based on the `Idempotency-Key` header using native in-memory off-heap storage.
183
187
  - `@RateLimit(options?)` — Enforces request rate limits on a controller class or route method.
@@ -190,7 +194,88 @@ export default class CheckoutController {
190
194
 
191
195
  ---
192
196
 
193
- ## 5. Response & Metadata Modifiers
197
+ ## 5. Security & Authorization Decorators
198
+
199
+ Declare access control rules directly on controller classes and route methods:
200
+
201
+ ```typescript filename="src/http/roles/route.ts"
202
+ import { Controller, Get, Post, Body, Use, Permissions, Roles, Public } from 'exisjs/decorators'
203
+ import { AuthGuard } from '@/common/guards/auth.guard'
204
+
205
+ @Controller('/roles')
206
+ @Use(AuthGuard) // Enforced on all routes in this controller
207
+ export default class RolesController {
208
+ @Get('/public')
209
+ @Public() // Bypasses auth guard for public inspection
210
+ async getPublicInfo() {
211
+ return { status: 'public' }
212
+ }
213
+
214
+ @Get('/')
215
+ @Roles('admin', 'manager') // Requires user to have admin or manager role
216
+ async getRoles() {
217
+ return { roles: [] }
218
+ }
219
+
220
+ @Post('/')
221
+ @Permissions('role:create', 'role:manage') // Granular permission check
222
+ async createRole(@Body() body: any) {
223
+ return { success: true }
224
+ }
225
+ }
226
+ ```
227
+
228
+ ### Available Security Decorators
229
+ - `@Permissions(...permissions: string[])` — Declares required permissions (auto-bypassed when `req.user.isSuperAdmin` is true).
230
+ - `@Roles(...roles: string[])` — Declares required role names.
231
+ - `@Public()` / `@IsPublic()` / `@AllowAnonymous()` — Marks an endpoint or controller as public, instructing global auth guards to skip credential verification.
232
+
233
+ ---
234
+
235
+ ## 6. Service Enhancements: Caching, Transactions & Profiling
236
+
237
+ ExisJS provides high-performance method decorators for `@Injectable()` services:
238
+
239
+ ```typescript filename="src/http/orders/service.ts"
240
+ import { Injectable, Cacheable, CacheEvict, Transactional, Profile, type OnInit, type OnDestroy } from 'exisjs/decorators'
241
+
242
+ @Injectable()
243
+ export class OrderService implements OnInit, OnDestroy {
244
+ async onInit() {
245
+ console.log('OrderService initialized')
246
+ }
247
+
248
+ async onDestroy() {
249
+ console.log('OrderService destroyed')
250
+ }
251
+
252
+ // Caches result in off-heap Rust cache for 300 seconds
253
+ @Cacheable({ ttl: 300, key: (id: string) => `order:${id}` })
254
+ @Profile({ thresholdMs: 50, warnSlow: true })
255
+ async getOrder(id: string) {
256
+ return this.db.query('SELECT * FROM orders WHERE id = $1', [id])
257
+ }
258
+
259
+ // Automatically runs in a database transaction and evicts cached entries
260
+ @Transactional()
261
+ @CacheEvict({ key: (id: string) => `order:${id}` })
262
+ async updateOrder(id: string, data: any) {
263
+ return this.db.update('orders', id, data)
264
+ }
265
+ }
266
+ ```
267
+
268
+ ### Available Service Decorators & Lifecycle Interfaces
269
+ - `@Cacheable({ ttl?: number, key?: string | ((...args) => string) })` — Caches method return values in off-heap memory.
270
+ - `@CacheEvict({ key: string | ((...args) => string) })` — Evicts cached keys on mutation.
271
+ - `@Transactional(options?)` — Wraps service operations in an atomic database transaction.
272
+ - `@Profile({ thresholdMs?: number, warnSlow?: boolean, log?: Function })` — Measures method execution time with sub-millisecond precision.
273
+ - `OnInit` (`onInit()`) & `OnDestroy` (`onDestroy()`) — Service startup and shutdown lifecycle hooks.
274
+ - `OnModuleInit`, `OnModuleDestroy`, `OnApplicationBootstrap` — Module lifecycle hooks.
275
+
276
+ ---
277
+
278
+ ## 7. Response & Metadata Modifiers
194
279
 
195
280
  Modify HTTP response status codes, redirect clients, or set headers declaratively:
196
281
 
@@ -219,14 +304,13 @@ export default class ApiController {
219
304
  - `@Header(name: string, value: string)` — Injects a response header.
220
305
  - `@Returns(schema)` — Attaches response schema for OpenAPI/Swagger generation.
221
306
  - `@Redirect(url: string, statusCode?: number)` — Redirects the client.
222
- - `@Permissions(...permissions: string[])` — Attaches route permissions metadata.
223
307
  - `@Hosts(...hosts: string[])` — Restricts route/controller to specific Host headers.
224
308
  - `@SetMetadata(key, value)` — Attaches custom metadata to a class or method.
225
- - `Reflector` — Static utility to inspect metadata attached via `@SetMetadata` across classes and methods (`Reflector.get`, `Reflector.getAllAndOverride`, `Reflector.getAllAndMerge`).
309
+ - `Reflector` — Static utility to inspect metadata attached via `@SetMetadata` across classes and methods.
226
310
 
227
311
  ---
228
312
 
229
- ## 6. Dependency Injection & Modules
313
+ ## 8. Dependency Injection & Modules
230
314
 
231
315
  To mark a service class as an injectable provider for the IoC container, use the `@Injectable()` decorator. ExisJS supports `singleton` (default), `request`, and `transient` scopes:
232
316
 
@@ -281,3 +365,4 @@ import { UserService } from './service'
281
365
  })
282
366
  export class UsersModule {}
283
367
  ```
368
+
@@ -98,33 +98,40 @@ export const uploadMedia = (req, res) => {
98
98
  }
99
99
  ```
100
100
 
101
- ### The Standardized Response
101
+ ### The Standardized Error Response Envelope
102
102
 
103
- When an `HttpError` is thrown, the client will automatically receive a structured JSON payload.
103
+ When an `HttpError` is thrown or a validation failure occurs, the client automatically receives a standardized JSON payload with status codes, error descriptions, and ISO timestamps:
104
104
 
105
- ```json filename="Response"
105
+ ```json filename="HTTP Error Response Envelope"
106
106
  {
107
107
  "success": false,
108
- "error": {
109
- "code": "NOT_FOUND",
110
- "message": "User not found"
111
- }
108
+ "statusCode": 404,
109
+ "error": "Not Found",
110
+ "message": "User not found",
111
+ "validationErrors": null,
112
+ "timestamp": "2026-09-27T10:11:42.000Z"
112
113
  }
113
114
  ```
114
115
 
115
116
  ### Validation Error Responses & Diagnostic Visibility
116
117
 
117
- When schema validation fails (via `tex`, Zod, or Yup), ExisJS produces a typed `400 Bad Request` featuring a clean `errors: Record<string, string>` map for seamless frontend form field binding:
118
+ When schema validation fails (via `tex`, Zod, or Yup), ExisJS produces a typed `400 Bad Request` containing both a concise `validationErrors` (and `errors`) map for seamless frontend form field binding:
118
119
 
119
120
  ```json filename="HTTP 400 Bad Request Response"
120
121
  {
122
+ "success": false,
121
123
  "statusCode": 400,
122
124
  "error": "Bad Request",
123
125
  "message": "Validation Error: email: Invalid email format, age: Field must be >= 18",
126
+ "validationErrors": {
127
+ "email": "Invalid email format",
128
+ "age": "Field must be >= 18"
129
+ },
124
130
  "errors": {
125
131
  "email": "Invalid email format",
126
132
  "age": "Field must be >= 18"
127
- }
133
+ },
134
+ "timestamp": "2026-09-27T10:11:42.000Z"
128
135
  }
129
136
  ```
130
137
 
@@ -335,26 +342,42 @@ export default controller({
335
342
 
336
343
  ### Class-Based (OOP) Paradigm
337
344
 
338
- ```typescript filename="src/http/users/route.ts"
339
- import { Controller, Get, UseFilters } from 'exisjs/decorators'
340
- import type { Request, Response } from 'exisjs/router'
345
+ You can use function handlers or structured class-based `ExceptionFilter` implementations with `@Catch()`:
346
+
347
+ ```typescript filename="src/http/filters/not-found.filter.ts"
348
+ import { Catch, type ExceptionFilter, type ArgumentsHost } from 'exisjs/decorators'
349
+ import { NotFoundException } from 'exisjs/error'
341
350
 
342
- function localErrorHandler(err: any, req: Request, res: Response, next: any) {
343
- console.log('Users Controller Error Handler Caught:', err.message)
344
- res.status(400).json({ success: false, localError: err.message })
351
+ @Catch(NotFoundException)
352
+ export class NotFoundExceptionFilter implements ExceptionFilter<NotFoundException> {
353
+ catch(exception: NotFoundException, host: ArgumentsHost) {
354
+ const { res } = host
355
+ res.status(404).json({
356
+ statusCode: 404,
357
+ error: 'Not Found',
358
+ message: exception.message,
359
+ timestamp: new Date().toISOString(),
360
+ })
361
+ }
345
362
  }
363
+ ```
346
364
 
347
- @Controller()
348
- @UseFilters(localErrorHandler)
365
+ ```typescript filename="src/http/users/route.ts"
366
+ import { Controller, Get, UseFilters, Use } from 'exisjs/decorators'
367
+ import { NotFoundException } from 'exisjs/error'
368
+ import { NotFoundExceptionFilter } from '../filters/not-found.filter'
369
+
370
+ @Controller('/users')
371
+ @Use(NotFoundExceptionFilter) // or @UseFilters(NotFoundExceptionFilter)
349
372
  export default class UsersController {
350
- @Get('/crash')
351
- async crash() {
352
- throw new Error("Something went wrong in the users module")
373
+ @Get('/:id')
374
+ async getUser() {
375
+ throw new NotFoundException('User')
353
376
  }
354
377
  }
355
378
  ```
356
379
 
357
- By combining Exception Filters and Local Error Hooks, ExisJS gives you control over your error boundaries.
380
+ By combining Exception Filters and Local Error Hooks, ExisJS gives you complete control over your error boundaries.
358
381
 
359
382
  ## Observability (BYOM/BYOT)
360
383
 
@@ -76,7 +76,7 @@ export default exis({
76
76
 
77
77
  ```
78
78
 
79
- #### Class-Based (OOP) Paradigm (`@Module`)
79
+ #### Class-Based (OOP) Paradigm (`@Module` & `@Global`)
80
80
 
81
81
  For applications following the OOP paradigm, you can define modules using the `@Module` class decorator:
82
82
 
@@ -95,23 +95,42 @@ import { DatabaseModule } from './database.module'
95
95
  export class AuthModule {}
96
96
  ```
97
97
 
98
- You can then register it into your server:
98
+ #### Global Modules (`@Global`)
99
+
100
+ If you have utility services, audit loggers, or database connections that should be globally available across all modules without having to list the module in every `imports: [...]` array, decorate the module with `@Global()`:
101
+
102
+ ```typescript filename="src/modules/core.module.ts"
103
+ import { Global, Module } from 'exisjs/decorators'
104
+ import { AuditService } from './audit.service'
105
+ import { LoggerService } from './logger.service'
106
+
107
+ @Global()
108
+ @Module({
109
+ providers: [AuditService, LoggerService],
110
+ exports: [AuditService, LoggerService],
111
+ })
112
+ export class CoreModule {}
113
+ ```
114
+
115
+ You can then register it into your server or root module once:
99
116
 
100
117
  ```typescript filename="src/http/server.ts"
101
118
  import { Server } from 'exisjs/decorators'
102
119
  import { AuthModule } from '../modules/auth.module'
120
+ import { CoreModule } from '../modules/core.module'
103
121
  import type { App } from 'exisjs'
104
122
 
105
123
  @Server({
106
- plugins: [AuthModule]
124
+ plugins: [CoreModule, AuthModule]
107
125
  })
108
126
  export default class RootServer {
109
127
  async onStart(app: App) {
110
- console.log("Server with AuthModule booted")
128
+ console.log("Server with Global CoreModule and AuthModule booted")
111
129
  }
112
130
  }
113
131
  ```
114
132
 
133
+
115
134
  ---
116
135
 
117
136
  ## 2. Framework Plugins (`definePlugin`)