exisjs 0.7.9 → 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 (105) hide show
  1. package/dist/config/config.js +1 -1
  2. package/dist/config/config.js.map +1 -1
  3. package/dist/decorators/constants.d.ts +2 -0
  4. package/dist/decorators/constants.d.ts.map +1 -1
  5. package/dist/decorators/constants.js +3 -1
  6. package/dist/decorators/constants.js.map +1 -1
  7. package/dist/decorators/core.d.ts +14 -0
  8. package/dist/decorators/core.d.ts.map +1 -1
  9. package/dist/decorators/core.js +156 -41
  10. package/dist/decorators/core.js.map +1 -1
  11. package/dist/decorators/filters.d.ts +2 -10
  12. package/dist/decorators/filters.d.ts.map +1 -1
  13. package/dist/decorators/filters.js.map +1 -1
  14. package/dist/decorators/index.d.ts +1 -0
  15. package/dist/decorators/index.d.ts.map +1 -1
  16. package/dist/decorators/index.js +1 -0
  17. package/dist/decorators/index.js.map +1 -1
  18. package/dist/decorators/lifecycle.d.ts +29 -0
  19. package/dist/decorators/lifecycle.d.ts.map +1 -1
  20. package/dist/decorators/params.d.ts +3 -7
  21. package/dist/decorators/params.d.ts.map +1 -1
  22. package/dist/decorators/params.js.map +1 -1
  23. package/dist/decorators/pipes.d.ts +91 -0
  24. package/dist/decorators/pipes.d.ts.map +1 -0
  25. package/dist/decorators/pipes.js +283 -0
  26. package/dist/decorators/pipes.js.map +1 -0
  27. package/dist/di/container.d.ts +6 -3
  28. package/dist/di/container.d.ts.map +1 -1
  29. package/dist/di/container.js +186 -68
  30. package/dist/di/container.js.map +1 -1
  31. package/dist/di/decorators.d.ts +23 -5
  32. package/dist/di/decorators.d.ts.map +1 -1
  33. package/dist/di/decorators.js +83 -20
  34. package/dist/di/decorators.js.map +1 -1
  35. package/dist/di/forward-ref.d.ts +23 -0
  36. package/dist/di/forward-ref.d.ts.map +1 -0
  37. package/dist/di/forward-ref.js +30 -0
  38. package/dist/di/forward-ref.js.map +1 -0
  39. package/dist/di/index.d.ts +2 -1
  40. package/dist/di/index.d.ts.map +1 -1
  41. package/dist/di/index.js +8 -1
  42. package/dist/di/index.js.map +1 -1
  43. package/dist/docs/content/controllers.mdx +230 -0
  44. package/dist/docs/content/errors.mdx +16 -9
  45. package/dist/docs/content/modules.mdx +23 -4
  46. package/dist/docs/content/providers.mdx +42 -6
  47. package/dist/docs/content/requests.mdx +9 -3
  48. package/dist/docs/content/responses.mdx +19 -5
  49. package/dist/docs/reference/config.mdx +3 -2
  50. package/dist/docs/reference/decorators.mdx +15 -4
  51. package/dist/docs/reference/di.mdx +23 -16
  52. package/dist/docs/reference/router.mdx +75 -0
  53. package/dist/docs/reference/validator.mdx +2 -2
  54. package/dist/error/errors.d.ts.map +1 -1
  55. package/dist/error/errors.js +12 -0
  56. package/dist/error/errors.js.map +1 -1
  57. package/dist/router/controller-registrar.d.ts.map +1 -1
  58. package/dist/router/controller-registrar.js +142 -87
  59. package/dist/router/controller-registrar.js.map +1 -1
  60. package/dist/router/index.d.ts.map +1 -1
  61. package/dist/router/index.js.map +1 -1
  62. package/dist/router/route-builder.d.ts +160 -29
  63. package/dist/router/route-builder.d.ts.map +1 -1
  64. package/dist/router/route-builder.js +105 -31
  65. package/dist/router/route-builder.js.map +1 -1
  66. package/dist/router/route-scanner.d.ts +3 -2
  67. package/dist/router/route-scanner.d.ts.map +1 -1
  68. package/dist/router/route-scanner.js.map +1 -1
  69. package/dist/router/router.d.ts.map +1 -1
  70. package/dist/router/router.js +44 -0
  71. package/dist/router/router.js.map +1 -1
  72. package/dist/router/scanner/functional-compiler.d.ts +1 -1
  73. package/dist/router/scanner/functional-compiler.d.ts.map +1 -1
  74. package/dist/router/scanner/functional-compiler.js +403 -64
  75. package/dist/router/scanner/functional-compiler.js.map +1 -1
  76. package/dist/router/types.d.ts +4 -2
  77. package/dist/router/types.d.ts.map +1 -1
  78. package/dist/server/app.d.ts +2 -0
  79. package/dist/server/app.d.ts.map +1 -1
  80. package/dist/server/app.js +38 -0
  81. package/dist/server/app.js.map +1 -1
  82. package/dist/server/helpers/etag.d.ts +1 -0
  83. package/dist/server/helpers/etag.d.ts.map +1 -1
  84. package/dist/server/helpers/etag.js +121 -10
  85. package/dist/server/helpers/etag.js.map +1 -1
  86. package/dist/server/helpers/ip.d.ts.map +1 -1
  87. package/dist/server/helpers/ip.js +75 -17
  88. package/dist/server/helpers/ip.js.map +1 -1
  89. package/dist/server/response.d.ts.map +1 -1
  90. package/dist/server/response.js +24 -9
  91. package/dist/server/response.js.map +1 -1
  92. package/dist/swagger/generator.d.ts.map +1 -1
  93. package/dist/swagger/generator.js +66 -22
  94. package/dist/swagger/generator.js.map +1 -1
  95. package/dist/testing/client.d.ts +15 -11
  96. package/dist/testing/client.d.ts.map +1 -1
  97. package/dist/testing/client.js +32 -11
  98. package/dist/testing/client.js.map +1 -1
  99. package/dist/utils/logger.d.ts.map +1 -1
  100. package/dist/utils/logger.js +2 -0
  101. package/dist/utils/logger.js.map +1 -1
  102. package/dist/validator/engine.d.ts.map +1 -1
  103. package/dist/validator/engine.js +45 -0
  104. package/dist/validator/engine.js.map +1 -1
  105. package/package.json +1 -1
@@ -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
+
@@ -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
 
@@ -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`)
@@ -64,7 +64,9 @@ export default class UsersController {
64
64
  ```
65
65
 
66
66
  <Info title="Note on Dependency Injection">
67
- ExisJS utilizes a runtime container system. By default, ExisJS does not aggressively use `reflect-metadata` for automatic constructor injection (unlike NestJS) to maintain a minimal footprint. The `inject()` function is the primary and recommended way to resolve dependencies.
67
+ ExisJS provides two elegant ways to inject dependencies:
68
+ 1. **Property-Level / Functional Injection**: Use `inject(UsersService)` for concise, boilerplate-free injection.
69
+ 2. **Constructor Injection**: In OOP controllers and services, ExisJS inspects TypeScript's emitted `design:paramtypes` and `INJECTABLE_REGISTRY` to automatically inject service instances into constructor parameters (`constructor(private usersService: UsersService)`) without requiring explicit `@Inject()` decorators on every parameter!
68
70
  </Info>
69
71
 
70
72
  ### Extended: Manual Resolution
@@ -182,20 +184,31 @@ import { UsersService } from './users/service'
182
184
  export default class RootServer {}
183
185
  ```
184
186
 
185
- ### Constructor Injection (`@Inject` and `@Optional`)
187
+ ### Constructor & Property Injection (`@Inject`, `@Optional`, and `forwardRef`)
186
188
 
187
- In addition to property-level `inject()`, ExisJS provides full OOP constructor parameter injection with `@Inject(token)` and `@Optional()`:
189
+ In addition to property-level `inject()`, ExisJS provides full OOP constructor and property field injection with `@Inject(token)`, `@Optional()`, and `forwardRef()`:
188
190
 
189
191
  ```typescript filename="src/http/users/route.ts"
190
- import { Controller, Get, Param, Inject, Optional } from 'exisjs/decorators'
192
+ import { Controller, Get, Param } from 'exisjs/decorators'
193
+ import { Inject, Optional, forwardRef } from 'exisjs/di'
191
194
  import { UsersService } from './service'
195
+ import { ConfigService } from '../config/service'
192
196
 
193
197
  @Controller('/users')
194
198
  export default class UsersController {
199
+ // 1. Property-level decorator injection
200
+ @Inject(ConfigService)
201
+ private config!: ConfigService
202
+
203
+ // 2. Optional token injection
204
+ @Inject('METRICS')
205
+ @Optional()
206
+ private metrics?: any
207
+
208
+ // 3. Constructor injection with forwardRef for circular dependencies
195
209
  constructor(
196
210
  private usersService: UsersService,
197
- @Inject('DATABASE_URL') private dbUrl: string,
198
- @Optional() @Inject('METRICS') private metrics?: any
211
+ @Inject(forwardRef(() => UsersService)) private circularRef: UsersService
199
212
  ) {}
200
213
 
201
214
  @Get('/:id')
@@ -205,6 +218,29 @@ export default class UsersController {
205
218
  }
206
219
  ```
207
220
 
221
+ ### Forward References (`forwardRef`) & Circular Dependencies
222
+
223
+ When two services mutually depend on each other (`ServiceA` requires `ServiceB` and `ServiceB` requires `ServiceA`), circular references normally lead to `ReferenceError` during module evaluation. ExisJS solves this using `forwardRef(() => Service)` alongside lazy ES6 proxy resolution:
224
+
225
+ ```typescript filename="src/services/order.service.ts"
226
+ import { Injectable } from 'exisjs/decorators'
227
+ import { Inject, forwardRef } from 'exisjs/di'
228
+ import { InventoryService } from './inventory.service'
229
+
230
+ @Injectable()
231
+ export class OrderService {
232
+ constructor(
233
+ @Inject(forwardRef(() => InventoryService))
234
+ private inventory: InventoryService
235
+ ) {}
236
+
237
+ processOrder(id: string) {
238
+ return this.inventory.reserveStock(id)
239
+ }
240
+ }
241
+ ```
242
+
243
+
208
244
  ## Injection Scopes
209
245
 
210
246
  For people coming from different programming language backgrounds, it might be unexpected to learn that in ExisJS, almost everything is shared across incoming requests. We have a connection pool to the database, singleton services with global state, etc. This is because Node.js doesn't follow the request/response Multi-Threaded Stateless Model.
@@ -451,11 +451,17 @@ const language = req.acceptsLanguages('en', 'es', 'fr')
451
451
 
452
452
  ---
453
453
 
454
- ## 7. Network Information & IPv6 Normalization
454
+ ## 7. Network Information & Reverse Proxy IP Resolution
455
455
 
456
- ExisJS handles trust-proxies and dual-stack IP networks automatically. If you are behind Cloudflare, Nginx, or an AWS Load Balancer, simply set `trustProxy: true` in your `exis()` config, and these properties will automatically resolve the true client IP from `X-Forwarded-For`.
456
+ ExisJS handles reverse proxies, CDNs, and dual-stack IP networks automatically. When deployed behind Cloudflare, AWS ALB, NGINX, Render, or GCP, set `trustProxy: true` in your `exis.config.ts` or define `TRUST_PROXY=true` in your environment variables.
457
457
 
458
- - **`req.ip`**: The client's IP address. Automatically normalizes IPv6-mapped IPv4 dual-stack addresses (e.g., `::ffff:192.168.1.50` $\to$ `192.168.1.50`) and converts IPv6 loopback `::1` $\to$ `127.0.0.1` for consistent rate limiting and geo-IP lookups.
458
+ When proxy trust is enabled, ExisJS evaluates industry-standard proxy and CDN headers in priority order:
459
+ 1. `CF-Connecting-IP` (Cloudflare)
460
+ 2. `True-Client-IP` (Akamai / Cloudflare Enterprise)
461
+ 3. `X-Real-IP` / `X-Client-IP` (NGINX / Reverse Proxies)
462
+ 4. `X-Forwarded-For` (Standard multi-hop proxy chains)
463
+
464
+ - **`req.ip`**: The real client's IP address. Automatically strips proxy hops, normalizes IPv6-mapped IPv4 dual-stack addresses (e.g., `::ffff:192.168.1.50` $\to$ `192.168.1.50`), and converts IPv6 loopback `::1` $\to$ `127.0.0.1` for consistent rate limiting and geo-IP lookups.
459
465
  - **`req.ips`**: An array of IPs if multiple proxies were traversed, each normalized cleanly.
460
466
  - **`req.hostname`**: The resolved hostname (from `Host` or `X-Forwarded-Host`).
461
467
  - **`req.protocol`**: Returns `'http'` or `'https'`.
@@ -575,17 +575,31 @@ export default class AuthController {
575
575
 
576
576
  ---
577
577
 
578
- ## 6. ETags & Conditional Requests (304 Not Modified)
578
+ ## 6. Safe Response Serialization (BSON, Dates & Prototype Auto-Unwrapping)
579
579
 
580
- ExisJS features native, automatic ETag generation.
580
+ ExisJS includes a robust, zero-crash serialization engine that automatically prepares complex objects before sending them down the wire:
581
581
 
582
- If the client sends an `If-None-Match` header matching the ETag of the current response, ExisJS will immediately abort serialization and send a `304 Not Modified` header with an empty body, saving bandwidth and CPU cycles.
582
+ - **Mongoose / ORM Documents**: Automatically unwraps documents by invoking `.toObject({ virtuals: true, getters: true })` or `.toJSON()`. You no longer need to call `.lean()` or sanitize documents manually.
583
+ - **MongoDB BSON Types**: Automatically converts `ObjectId`, `Decimal128`, `Long`, and `Binary` buffers to their clean string / base64 representations.
584
+ - **BigInt & Dates**: Converts native `BigInt` values to strings and `Date` instances to ISO 8601 strings.
585
+ - **Maps & Sets**: Automatically converts `Map` to plain objects and `Set` to arrays.
586
+ - **Circular Reference Guard**: Circular object references are safely detected and replaced with `'[Circular]'` rather than throwing fatal V8 JSON serialization exceptions.
587
+ - **Enhanced Diagnostics**: If an unhandled serialization issue occurs, ExisJS logs the exact property path (e.g. `'recentTransactions[0].accountId'`) for immediate debugging clarity.
583
588
 
584
- *Note: ETag generation is disabled by default, but can be enabled globally in your `exis()` config, or manually per-route via `res.etagEnabled = true`.*
589
+ ---
590
+
591
+ ## 7. ETags & Conditional Requests (304 Not Modified)
592
+
593
+ ExisJS features native ETag generation.
594
+
595
+ By default, ETags are disabled (`etag: false`) on dynamic REST API endpoints to minimize hashing CPU overhead and provide transparent logging timings.
596
+
597
+ When enabled globally (`etag: true` in `exis.config.ts`) or on a per-route basis (`res.etagEnabled = true`):
598
+ - If the client sends an `If-None-Match` header matching the response ETag, ExisJS immediately sends `304 Not Modified` with an empty body, saving bandwidth.
585
599
 
586
600
  ---
587
601
 
588
- ## 7. Server-Sent Events (SSE) & AI Streaming
602
+ ## 8. Server-Sent Events (SSE) & AI Streaming
589
603
 
590
604
  ExisJS provides a first-class `res.sse()` method for real-time live events and AI chat completions (OpenAI, Anthropic, Ollama, LangChain).
591
605
 
@@ -103,14 +103,15 @@ Here is the full list of options supported natively by the ExisJS configuration
103
103
  - **`host`** *(string)*: The host interface to bind to (default: `'0.0.0.0'`).
104
104
  - **`server`** *(`'node' | 'uws' | 'auto'`)*: ExisJS supports running on standard Node `http`, or incredibly fast C++ `uWebSockets.js` (`uws`). Default is `'auto'` (uses `uws` if installed, falls back to `node`).
105
105
  - **`workers`** *(number | `'safe'` | `'max'`)*: Number of Node.js Cluster workers to spawn. `'max'` uses all CPU cores. `'safe'` uses all cores minus 1 (to leave room for the OS).
106
- - **`trustProxy`** *(boolean)*: Enable if running behind a reverse proxy (like Nginx or AWS ALB) to trust the `X-Forwarded-For` headers.
106
+ - **`trustProxy`** *(boolean | number)*: Enable if running behind reverse proxies or CDNs (Cloudflare, AWS ALB, NGINX, Render) to evaluate `CF-Connecting-IP`, `True-Client-IP`, `X-Real-IP`, and `X-Forwarded-For`. (Can also be enabled via `TRUST_PROXY=true`).
107
107
 
108
108
  ### Security & Middlewares
109
109
  - **`cors`** *(boolean | object)*: Enable and configure Cross-Origin Resource Sharing globally.
110
110
  - **`helmet`** *(boolean | object)*: Enable strict security headers. Enabled by default.
111
111
  - **`logger`** *(boolean | object)*: Configure the efficient Pino request logger.
112
112
  - **`compression`** *(boolean | object)*: Enable gzip/brotli response compression.
113
- - **`bodyLimit`** *(number)*: Maximum JSON/URL-encoded payload size in bytes (default: `1MB`).
113
+ - **`etag`** *(boolean)*: Automatic ETag generation for HTTP conditional requests (default: `false` for dynamic API routes).
114
+ - **`bodyLimit`** *(number)*: Maximum JSON/URL-encoded payload size in bytes (default: `10MB`).
114
115
 
115
116
  ### Extended Integrations
116
117
  - **`queue`**: Define your background queue driver (`memory` or `redis`) and configure `maxConcurrent` processing limits.
@@ -83,8 +83,16 @@ Parameter decorators automatically extract data from the incoming request and in
83
83
 
84
84
  ---
85
85
 
86
- ## 3. Middleware & Pipeline Decorators
87
-
86
+ ## 3. Transformation Pipes & Middleware Decorators
87
+
88
+ - `@UsePipes(...pipes)`: Binds transformation and validation pipes to a controller class or route method.
89
+ - **Built-in Pipes**:
90
+ - `ParseIntPipe`: Parses numeric strings to integers (`options?: { errorHttpStatusCode, exceptionFactory }`).
91
+ - `ParseFloatPipe`: Parses numeric float strings (`options?: { errorHttpStatusCode, exceptionFactory }`).
92
+ - `ParseBoolPipe`: Parses boolean strings (`'true'`, `'false'`, `1`, `0`).
93
+ - `ParseUUIDPipe`: Validates UUID format (`options?: { version: '3' | '4' | '5' | 'all' }`).
94
+ - `ParseArrayPipe`: Splits comma-separated strings and applies item-level transformation pipes.
95
+ - `ValidationPipe`: Validates DTO classes and schemas (`options?: { transform: boolean }`).
88
96
  - `@Use(...middlewaresOrGuardsOrInterceptorsOrFilters)`: Unified decorator that binds middlewares, guards (`canActivate`), interceptors (`intercept`), or exception filters (`catch`) to a controller class or method.
89
97
  - `@UseGuards(...guards)`: Dedicated decorator to apply guards to a class or method.
90
98
  - `@UseInterceptors(...interceptors)`: Dedicated decorator to apply interceptors to a class or method.
@@ -139,10 +147,12 @@ Parameter decorators automatically extract data from the incoming request and in
139
147
 
140
148
  ## 7. Dependency Injection & Modules
141
149
 
150
+ - `@Global()`: Marks a `@Module()` as global, automatically making its exported providers available across the entire application without explicit re-importing.
142
151
  - `@Module({ imports?, controllers?, providers?, exports? })`: OOP class decorator equivalent to `defineModule()` for structuring application modules.
143
152
  - `@Injectable(options?)`: Marks a service class as an IoC provider managed by the container. Supports `{ scope: 'singleton' | 'request' | 'transient' }`.
144
- - `@Inject(token)`: Constructor parameter decorator from `exisjs/di` for injecting dependencies by class, string, or symbol token.
145
- - `@Optional()`: Constructor parameter decorator from `exisjs/di` resolving missing dependencies to `undefined` without throwing.
153
+ - `@Inject(token)`: Constructor parameter and class property decorator from `exisjs/di` for injecting dependencies by class, string, or symbol token.
154
+ - `@Optional()`: Parameter and property decorator from `exisjs/di` resolving missing dependencies to `undefined` without throwing.
155
+ - `forwardRef(() => Target)`: Creates a lazy forward reference for resolving circular dependencies in constructor or property injection.
146
156
  - `@Boundary(config)`: Marks a class as a folder-scoped Boundary configuring CORS, headers, exclusions, and pipeline steps.
147
157
  - `@Server(config)`: Marks a class as a root server definition.
148
158
 
@@ -154,3 +164,4 @@ Parameter decorators automatically extract data from the incoming request and in
154
164
  - `@Interval(ms: number, options?: IntervalOptions)`: Schedules a class method to execute repeatedly every `ms` milliseconds.
155
165
  - `@TimeoutTask(ms: number, options?: TimeoutOptions)` (alias `@ScheduledTimeout`): Schedules a class method to execute once after `ms` milliseconds following application bootstrap.
156
166
 
167
+
@@ -109,20 +109,39 @@ export default controller({
109
109
  > [!NOTE]
110
110
  > `inject()` relies on the active execution context. If you call `inject()` outside of an active HTTP request or application lifecycle hook (e.g., in a background `setInterval` without context), it will throw an error.
111
111
 
112
- ### 2. Constructor Injection in Classes (OOP)
112
+ ### 2. Constructor & Property Injection in Classes (OOP)
113
113
 
114
- ExisJS supports full OOP constructor parameter injection. You can inject services directly via type annotation or specify tokens with `@Inject()`, and designate optional dependencies with `@Optional()`:
114
+ ExisJS supports full OOP constructor parameter and property field injection with **automatic parameter type reflection**:
115
+ - **Automatic Type Reflection**: If `emitDecoratorMetadata: true` is enabled in `tsconfig.json`, ExisJS inspects `design:paramtypes` and automatically maps constructor parameter classes directly to dependencies registered in `INJECTABLE_REGISTRY`. You do not need `@Inject()` on standard service classes!
116
+ - **Property-Level `@Inject()`**: Inject dependencies directly onto class properties (`@Inject(Token) public service!: Service`) with automatic `design:type` reflection fallback.
117
+ - **Token Injection (`@Inject(token)`)**: Use `@Inject('TOKEN')` when injecting custom strings, config objects, or symbols.
118
+ - **Optional Dependencies (`@Optional()`)**: Mark dependencies that can safely resolve to `undefined` if not registered.
119
+ - **Forward References (`forwardRef(() => Service)`)**: Resolve circular dependencies between services using lazy forward references and ES6 Proxy delegates.
115
120
 
116
121
  ```typescript filename="src/http/users/route.ts"
117
- import { Controller, Get, Param, Inject, Optional } from 'exisjs/decorators'
122
+ import { Controller, Get, Param } from 'exisjs/decorators'
123
+ import { Inject, Optional, forwardRef } from 'exisjs/di'
118
124
  import { UserService } from './service'
125
+ import { ConfigService } from '../config/service'
119
126
 
120
127
  @Controller('/users')
121
128
  export default class UserController {
129
+ // 1. Property-level injection
130
+ @Inject(ConfigService)
131
+ private config!: ConfigService
132
+
133
+ // 2. Optional property injection
134
+ @Inject('ANALYTICS')
135
+ @Optional()
136
+ private analytics?: any
137
+
122
138
  constructor(
139
+ // 3. Automatically resolved from INJECTABLE_REGISTRY via type reflection!
123
140
  private userService: UserService,
141
+ // 4. Explicit token injection
124
142
  @Inject('DATABASE_URL') private dbUrl: string,
125
- @Optional() @Inject('ANALYTICS') private analytics?: any
143
+ // 5. Circular dependency resolution via forwardRef
144
+ @Inject(forwardRef(() => UserService)) private circularRef: UserService
126
145
  ) {}
127
146
 
128
147
  @Get('/:id')
@@ -132,18 +151,6 @@ export default class UserController {
132
151
  }
133
152
  ```
134
153
 
135
- You can also use property-level `inject()` inside your Controller methods:
136
-
137
- ```typescript
138
- @Controller('/users')
139
- export default class UserController {
140
- @Get('/')
141
- async getUsers() {
142
- const db = inject(Database)
143
- return db.query('SELECT * FROM users')
144
- }
145
- }
146
- ```
147
154
 
148
155
  ---
149
156