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
@@ -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
@@ -78,6 +80,54 @@ const app = getActiveApp()
78
80
  const usersService = app.container.resolve(UsersService)
79
81
  ```
80
82
 
83
+ ## Service Lifecycle Hooks
84
+
85
+ ExisJS `@Injectable()` services can implement lifecycle interfaces. When the application starts up or shuts down gracefully, ExisJS automatically triggers these hooks on all managed singletons:
86
+
87
+ ```typescript filename="src/http/database/service.ts"
88
+ import { Injectable, type OnInit, type OnDestroy } from 'exisjs/decorators'
89
+
90
+ @Injectable()
91
+ export class DatabaseService implements OnInit, OnDestroy {
92
+ async onInit(): Promise<void> {
93
+ // Invoked automatically during app startup (app.create / app.listen)
94
+ await this.connectPool()
95
+ }
96
+
97
+ async onDestroy(): Promise<void> {
98
+ // Invoked automatically during graceful termination (app.close / SIGINT / SIGTERM)
99
+ await this.drainPool()
100
+ }
101
+ }
102
+ ```
103
+
104
+ ## Service Method Enhancements
105
+
106
+ You can supercharge service methods with off-heap caching, automated transaction wrapping, and SLA performance profiling:
107
+
108
+ ```typescript filename="src/http/users/service.ts"
109
+ import { Injectable, Cacheable, CacheEvict, Transactional, Profile } from 'exisjs/decorators'
110
+
111
+ @Injectable()
112
+ export class UsersService {
113
+ constructor(private db: DatabaseService) {}
114
+
115
+ // Caches return values off-heap in Rust LRU cache
116
+ @Cacheable({ ttl: 300, key: (id: string) => `user:${id}` })
117
+ @Profile({ thresholdMs: 100, warnSlow: true })
118
+ async findById(id: string) {
119
+ return this.db.query('SELECT * FROM users WHERE id = $1', [id])
120
+ }
121
+
122
+ // Runs in an atomic transaction; automatically rolls back on error
123
+ @Transactional()
124
+ @CacheEvict({ key: (id: string) => `user:${id}` })
125
+ async updateUser(id: string, data: any) {
126
+ return this.db.update('users', id, data)
127
+ }
128
+ }
129
+ ```
130
+
81
131
  ## Custom Providers
82
132
 
83
133
  ExisJS's DI container is flexible. By default, when you register a class, ExisJS instantiates it for you. However, you often need more control. You can register standard classes, raw values, factories, or aliased classes natively using `app.provide()`.
@@ -134,20 +184,31 @@ import { UsersService } from './users/service'
134
184
  export default class RootServer {}
135
185
  ```
136
186
 
137
- ### Constructor Injection (`@Inject` and `@Optional`)
187
+ ### Constructor & Property Injection (`@Inject`, `@Optional`, and `forwardRef`)
138
188
 
139
- 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()`:
140
190
 
141
191
  ```typescript filename="src/http/users/route.ts"
142
- 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'
143
194
  import { UsersService } from './service'
195
+ import { ConfigService } from '../config/service'
144
196
 
145
197
  @Controller('/users')
146
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
147
209
  constructor(
148
210
  private usersService: UsersService,
149
- @Inject('DATABASE_URL') private dbUrl: string,
150
- @Optional() @Inject('METRICS') private metrics?: any
211
+ @Inject(forwardRef(() => UsersService)) private circularRef: UsersService
151
212
  ) {}
152
213
 
153
214
  @Get('/:id')
@@ -157,6 +218,29 @@ export default class UsersController {
157
218
  }
158
219
  ```
159
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
+
160
244
  ## Injection Scopes
161
245
 
162
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,9 +83,21 @@ 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.
97
+ - `@UseGuards(...guards)`: Dedicated decorator to apply guards to a class or method.
98
+ - `@UseInterceptors(...interceptors)`: Dedicated decorator to apply interceptors to a class or method.
99
+ - `@UseFilters(...filters)`: Dedicated decorator to apply exception filters to a class or method.
100
+ - `@Catch(...exceptions)`: Class decorator for `ExceptionFilter` implementations declaring handled exception types.
89
101
  - `@Upload(fieldName, options?)` / `@UseUpload(...)`: Configures multipart file upload handling for a method, populating `@UploadedFile()` or `@UploadedFiles()`.
90
102
  - `@Timeout(ms: number | TimeoutOptions)`: Overrides the request timeout for a class or method (e.g. `@Timeout(120000)`). Passing `0` disables timeouts for long-running streaming or exports.
91
103
  - `@Idempotent(options?)`: Caches responses based on the `Idempotency-Key` header using native in-memory off-heap storage. Works at class and method level.
@@ -98,13 +110,32 @@ Parameter decorators automatically extract data from the incoming request and in
98
110
 
99
111
  ---
100
112
 
101
- ## 4. Response & Metadata Modifiers
113
+ ## 4. Security & Access Control Decorators
114
+
115
+ - `@Permissions(...permissions: string[])`: Sets required permission keys for a controller or route method (automatically bypassed if `req.user.isSuperAdmin` is true).
116
+ - `@Roles(...roles: string[])`: Sets required role names for a controller or route method.
117
+ - `@Public()` / `@IsPublic()` / `@AllowAnonymous()`: Marks a controller or route as public, instructing global authentication guards to bypass credential checks.
118
+
119
+ ---
120
+
121
+ ## 5. Service Enhancements: Caching, Transactions & Profiling
122
+
123
+ - `@Cacheable({ ttl?: number, key?: string | ((...args) => string) })`: Caches method return values in ExisJS's off-heap native memory LRU cache.
124
+ - `@CacheEvict({ key: string | ((...args) => string) })`: Evicts cached entries matching the key when the method completes.
125
+ - `@Transactional(options?)`: Automatically wraps service operations in an atomic database transaction.
126
+ - `@Profile({ thresholdMs?: number, warnSlow?: boolean, log?: Function })`: Profiles method execution time with sub-millisecond precision.
127
+ - `OnInit` (`onInit()`): Invoked when the application or service boots up.
128
+ - `OnDestroy` (`onDestroy()`): Invoked when the application gracefully terminates.
129
+ - `OnModuleInit`, `OnModuleDestroy`, `OnApplicationBootstrap`: Modular lifecycle hooks.
130
+
131
+ ---
132
+
133
+ ## 6. Response & Metadata Modifiers
102
134
 
103
135
  - `@HttpCode(code: number)`: Enforces a custom HTTP status code on success.
104
136
  - `@Header(name: string, value: string)`: Appends an HTTP header to the response.
105
137
  - `@Returns(schema)`: Attaches response schema for OpenAPI documentation.
106
138
  - `@Redirect(url: string, statusCode?: number)`: Redirects request to target URL.
107
- - `@Permissions(...permissions: string[])`: Sets required permissions on the route.
108
139
  - `@Hosts(...hosts: string[])`: Restricts routes to specific Host headers.
109
140
  - `@SetMetadata(key, value)`: Attaches arbitrary custom metadata to a class or handler method, retrievable via `Reflector`.
110
141
  - `Reflector`: Utility class to inspect metadata attached by `@SetMetadata`:
@@ -114,20 +145,23 @@ Parameter decorators automatically extract data from the incoming request and in
114
145
 
115
146
  ---
116
147
 
117
- ## 5. Dependency Injection & Modules
148
+ ## 7. Dependency Injection & Modules
118
149
 
150
+ - `@Global()`: Marks a `@Module()` as global, automatically making its exported providers available across the entire application without explicit re-importing.
119
151
  - `@Module({ imports?, controllers?, providers?, exports? })`: OOP class decorator equivalent to `defineModule()` for structuring application modules.
120
152
  - `@Injectable(options?)`: Marks a service class as an IoC provider managed by the container. Supports `{ scope: 'singleton' | 'request' | 'transient' }`.
121
- - `@Inject(token)`: Constructor parameter decorator from `exisjs/di` for injecting dependencies by class, string, or symbol token.
122
- - `@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.
123
156
  - `@Boundary(config)`: Marks a class as a folder-scoped Boundary configuring CORS, headers, exclusions, and pipeline steps.
124
157
  - `@Server(config)`: Marks a class as a root server definition.
125
158
 
126
159
  ---
127
160
 
128
- ## 6. Cron & Task Scheduling
161
+ ## 8. Cron & Task Scheduling
129
162
 
130
163
  - `@Cron(schedule: string, options?: CronJobOptions)`: Schedules a class method as a recurring cron job on any `@Injectable()` service.
131
164
  - `@Interval(ms: number, options?: IntervalOptions)`: Schedules a class method to execute repeatedly every `ms` milliseconds.
132
165
  - `@TimeoutTask(ms: number, options?: TimeoutOptions)` (alias `@ScheduledTimeout`): Schedules a class method to execute once after `ms` milliseconds following application bootstrap.
133
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,16 +151,30 @@ export default class UserController {
132
151
  }
133
152
  ```
134
153
 
135
- You can also use property-level `inject()` inside your Controller methods:
136
154
 
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')
155
+ ---
156
+
157
+ ## Lifecycle Hooks (`OnInit`, `OnDestroy`)
158
+
159
+ Providers can implement lifecycle interfaces. When the application container initializes or gracefully closes, ExisJS automatically triggers these hooks:
160
+
161
+ - **`OnInit` (`onInit()` / `onModuleInit()`)**: Executed when the container initializes during `app.create()` or `app.listen()`.
162
+ - **`OnDestroy` (`onDestroy()` / `onModuleDestroy()`)**: Executed when the application receives a termination signal or `app.close()` is called.
163
+ - **`OnApplicationBootstrap` (`onApplicationBootstrap()`)**: Executed after all modules are initialized and before the server begins listening for incoming requests.
164
+
165
+ ```typescript filename="src/http/database/service.ts"
166
+ import { Injectable, type OnInit, type OnDestroy } from 'exisjs/decorators'
167
+
168
+ @Injectable()
169
+ export class DatabaseService implements OnInit, OnDestroy {
170
+ async onInit(): Promise<void> {
171
+ await this.pool.connect()
172
+ }
173
+
174
+ async onDestroy(): Promise<void> {
175
+ await this.pool.end()
144
176
  }
145
177
  }
146
178
  ```
147
179
 
180
+
@@ -47,6 +47,81 @@ export default controller({
47
47
  - `onResponse` (`HookResponse`): A file-level response hook.
48
48
  - `[key: string]` (`RouteDefinition`): **Any other key** is treated as a route definition (e.g. `getUser: route.get(...)`). This is how you bind routes to the controller!
49
49
 
50
+ ### `controller.merge(...controllers)`
51
+
52
+ Merges multiple functional controllers into a single unified controller definition. This enables splitting large `route.ts` files into modular, domain-specific controller pieces while exporting a single composite controller from the route file.
53
+
54
+ ```typescript filename="src/http/auth/route.ts"
55
+ import { controller, route } from 'exisjs/router'
56
+
57
+ // Sub-controller 1: credentials & sessions
58
+ const authRoutes = controller({
59
+ login: route.post('/login', {
60
+ async handle({ body }) {
61
+ return { token: 'jwt-token' }
62
+ }
63
+ }),
64
+ logout: route.post('/logout', {
65
+ async handle() {
66
+ return { success: true }
67
+ }
68
+ })
69
+ })
70
+
71
+ // Sub-controller 2: OAuth integrations
72
+ const oauthRoutes = controller({
73
+ google: route.get('/google', {
74
+ async handle() {
75
+ return { redirectUrl: 'https://accounts.google.com/o/oauth2/auth' }
76
+ }
77
+ })
78
+ })
79
+
80
+ // Export combined controller as default
81
+ export default controller.merge(authRoutes, oauthRoutes)
82
+ ```
83
+
84
+ ### `controller.extend(base, overrides)`
85
+
86
+ Inherits all route definitions, middlewares, filters, and configuration from a `base` controller and applies overrides or adds new route endpoints. This provides the functional equivalent of class-based controller inheritance for reusable CRUD templates.
87
+
88
+ ```typescript filename="src/http/products/route.ts"
89
+ import { controller, route } from 'exisjs/router'
90
+
91
+ // Reusable generic CRUD controller factory
92
+ function createBaseCrud(resource: string) {
93
+ return controller({
94
+ list: route.get('/', {
95
+ async handle() {
96
+ return { resource, items: [] }
97
+ }
98
+ }),
99
+ getById: route.get('/:id', {
100
+ async handle({ params }) {
101
+ return { resource, id: params.id }
102
+ }
103
+ })
104
+ })
105
+ }
106
+
107
+ // Inherit base endpoints and add specialized or overridden routes
108
+ export default controller.extend(createBaseCrud('products'), {
109
+ // Additional specialized route
110
+ featured: route.get('/featured', {
111
+ async handle() {
112
+ return { featured: ['Product 1', 'Product 2'] }
113
+ }
114
+ }),
115
+
116
+ // Override base endpoint
117
+ getById: route.get('/:id', {
118
+ async handle({ params }) {
119
+ return { customProduct: true, id: params.id }
120
+ }
121
+ })
122
+ })
123
+ ```
124
+
50
125
  ---
51
126
 
52
127
  ## `route`
@@ -14,8 +14,8 @@ ExisJS ships with a efficient, integrated validation library out of the box, exp
14
14
  The `tex` object provides factory functions for every common primitive and data structure:
15
15
 
16
16
  - `tex.string(options?)`: Validates a string (supports min, max, trim, sanitizers, masking, SQL injection prevention, defaults).
17
- - `tex.number(options?)`: Validates a numeric value (supports min, max, coercion, defaults).
18
- - `tex.boolean(options?)`: Validates a boolean value (supports coercion, defaults).
17
+ - `tex.number(options?)`: Validates a numeric value. Automatically coerces numeric strings in query parameters (e.g., `?limit=100` $\to$ `100`) and supports `{ coerce: true, min, max, default }`.
18
+ - `tex.boolean(options?)`: Validates a boolean value. Automatically coerces boolean strings in query parameters (e.g., `?active=true` $\to$ `true`, `"1"`/`"0"`, `"yes"`/`"no"`) and supports `{ coerce: true, default }`.
19
19
  - `tex.email(options?)`: Validates standard email formatting.
20
20
  - `tex.uuid(options?)`: Validates UUID v1 or v4 strings (`{ version: 1 | 4 }`).
21
21
  - `tex.cuid(options?)`: Validates CUID identifiers.
@@ -51,18 +51,30 @@ export declare class ServiceUnavailableError extends HttpError {
51
51
  export declare class GatewayTimeoutError extends HttpError {
52
52
  constructor(message?: string);
53
53
  }
54
- export declare const HttpException: typeof HttpError;
55
- export declare const BadRequestException: typeof BadRequestError;
56
- export declare const UnauthorizedException: typeof UnauthorizedError;
57
- export declare const ForbiddenException: typeof ForbiddenError;
58
- export declare const NotFoundException: typeof NotFoundError;
59
- export declare const ConflictException: typeof ConflictError;
60
- export declare const PayloadTooLargeException: typeof PayloadTooLargeError;
61
- export declare const UnprocessableException: typeof UnprocessableError;
62
- export declare const RateLimitException: typeof RateLimitError;
63
- export declare const InternalException: typeof InternalError;
64
- export declare const ServiceUnavailableException: typeof ServiceUnavailableError;
65
- export declare const GatewayTimeoutException: typeof GatewayTimeoutError;
54
+ export declare class HttpException extends HttpError {
55
+ }
56
+ export declare class BadRequestException extends BadRequestError {
57
+ }
58
+ export declare class UnauthorizedException extends UnauthorizedError {
59
+ }
60
+ export declare class ForbiddenException extends ForbiddenError {
61
+ }
62
+ export declare class NotFoundException extends NotFoundError {
63
+ }
64
+ export declare class ConflictException extends ConflictError {
65
+ }
66
+ export declare class PayloadTooLargeException extends PayloadTooLargeError {
67
+ }
68
+ export declare class UnprocessableException extends UnprocessableError {
69
+ }
70
+ export declare class RateLimitException extends RateLimitError {
71
+ }
72
+ export declare class InternalException extends InternalError {
73
+ }
74
+ export declare class ServiceUnavailableException extends ServiceUnavailableError {
75
+ }
76
+ export declare class GatewayTimeoutException extends GatewayTimeoutError {
77
+ }
66
78
  export declare function renderValidationTable(rows: {
67
79
  field: string;
68
80
  received: string;
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/error/errors.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,UAAU,CAAA;AAKrD,qBAAa,SAAU,SAAQ,KAAK;IAClC,SAAgB,UAAU,EAAE,MAAM,CAAA;IAClC,SAAgB,IAAI,EAAE,MAAM,CAAA;IAC5B,SAAgB,aAAa,EAAE,OAAO,CAAA;IACtC,SAAgB,OAAO,CAAC,EAAE,OAAO,CAAA;gBAG/B,OAAO,EAAE,MAAM,EACf,UAAU,SAAM,EAChB,IAAI,SAAmB,EACvB,OAAO,CAAC,EAAE,OAAO;IAenB,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS;IAIhE,MAAM,CAAC,YAAY,CAAC,OAAO,SAAiB,GAAG,SAAS;IAIxD,MAAM,CAAC,SAAS,CAAC,OAAO,SAAc,GAAG,SAAS;IAIlD,MAAM,CAAC,QAAQ,CAAC,QAAQ,SAAa,GAAG,SAAS;IAIjD,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS;IAI3C,MAAM,CAAC,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS;IAInE,MAAM,CAAC,eAAe,CACpB,OAAO,SAAsB,EAC7B,OAAO,CAAC,EAAE,OAAO,GAChB,SAAS;IAIZ,MAAM,CAAC,eAAe,CAAC,OAAO,SAAsB,GAAG,SAAS;IAIhE,MAAM,CAAC,QAAQ,CAAC,OAAO,SAA0B,GAAG,SAAS;IAI7D,MAAM,CAAC,kBAAkB,CAAC,OAAO,SAAwB,GAAG,SAAS;IAIrE,MAAM,CAAC,cAAc,CAAC,OAAO,SAAoB,GAAG,SAAS;IAI7D,MAAM,IAAI,MAAM;CAUjB;AAED,qBAAa,eAAgB,SAAQ,SAAS;gBAChC,OAAO,SAAgB,EAAE,OAAO,CAAC,EAAE,OAAO;CAIvD;AAED,qBAAa,iBAAkB,SAAQ,SAAS;gBAClC,OAAO,SAAiB;CAIrC;AAED,qBAAa,cAAe,SAAQ,SAAS;gBAC/B,OAAO,SAAc;CAIlC;AAED,qBAAa,aAAc,SAAQ,SAAS;gBAC9B,QAAQ,SAAa;CAIlC;AAED,qBAAa,aAAc,SAAQ,SAAS;gBAC9B,OAAO,SAAa;CAIjC;AAED,qBAAa,kBAAmB,SAAQ,SAAS;gBACnC,OAAO,SAAyB,EAAE,OAAO,CAAC,EAAE,OAAO;CAIhE;AAED,qBAAa,oBAAqB,SAAQ,SAAS;gBACrC,OAAO,SAAsB,EAAE,OAAO,CAAC,EAAE,OAAO;CAI7D;AAED,qBAAa,cAAe,SAAQ,SAAS;gBAC/B,OAAO,SAAsB;CAI1C;AAED,qBAAa,aAAc,SAAQ,SAAS;gBAC9B,OAAO,SAA0B;CAI9C;AAED,qBAAa,uBAAwB,SAAQ,SAAS;gBACxC,OAAO,SAAwB;CAI5C;AAED,qBAAa,mBAAoB,SAAQ,SAAS;gBACpC,OAAO,SAAoB;CAIxC;AAGD,eAAO,MAAM,aAAa,kBAAY,CAAA;AACtC,eAAO,MAAM,mBAAmB,wBAAkB,CAAA;AAClD,eAAO,MAAM,qBAAqB,0BAAoB,CAAA;AACtD,eAAO,MAAM,kBAAkB,uBAAiB,CAAA;AAChD,eAAO,MAAM,iBAAiB,sBAAgB,CAAA;AAC9C,eAAO,MAAM,iBAAiB,sBAAgB,CAAA;AAC9C,eAAO,MAAM,wBAAwB,6BAAuB,CAAA;AAC5D,eAAO,MAAM,sBAAsB,2BAAqB,CAAA;AACxD,eAAO,MAAM,kBAAkB,uBAAiB,CAAA;AAChD,eAAO,MAAM,iBAAiB,sBAAgB,CAAA;AAC9C,eAAO,MAAM,2BAA2B,gCAA0B,CAAA;AAClE,eAAO,MAAM,uBAAuB,4BAAsB,CAAA;AA6G1D,wBAAgB,qBAAqB,CACnC,IAAI,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,EAAE,GAC5D,MAAM,CA6BR;AAED,wBAAgB,kBAAkB,CAAC,KAAK,UAAQ,GAAG,YAAY,CAoS9D;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,OAAO,GAAG,OAAO,CAIjD"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/error/errors.ts"],"names":[],"mappings":"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"}
@@ -63,6 +63,7 @@ class HttpError extends Error {
63
63
  message: this.message,
64
64
  ...(this.details !== undefined && { details: this.details }),
65
65
  },
66
+ ...(this.details && typeof this.details === 'object' ? this.details : {}),
66
67
  };
67
68
  }
68
69
  }
@@ -145,18 +146,42 @@ class GatewayTimeoutError extends HttpError {
145
146
  }
146
147
  exports.GatewayTimeoutError = GatewayTimeoutError;
147
148
  // ─── Exception Aliases ────────────────────────────────────────────────────────
148
- exports.HttpException = HttpError;
149
- exports.BadRequestException = BadRequestError;
150
- exports.UnauthorizedException = UnauthorizedError;
151
- exports.ForbiddenException = ForbiddenError;
152
- exports.NotFoundException = NotFoundError;
153
- exports.ConflictException = ConflictError;
154
- exports.PayloadTooLargeException = PayloadTooLargeError;
155
- exports.UnprocessableException = UnprocessableError;
156
- exports.RateLimitException = RateLimitError;
157
- exports.InternalException = InternalError;
158
- exports.ServiceUnavailableException = ServiceUnavailableError;
159
- exports.GatewayTimeoutException = GatewayTimeoutError;
149
+ class HttpException extends HttpError {
150
+ }
151
+ exports.HttpException = HttpException;
152
+ class BadRequestException extends BadRequestError {
153
+ }
154
+ exports.BadRequestException = BadRequestException;
155
+ class UnauthorizedException extends UnauthorizedError {
156
+ }
157
+ exports.UnauthorizedException = UnauthorizedException;
158
+ class ForbiddenException extends ForbiddenError {
159
+ }
160
+ exports.ForbiddenException = ForbiddenException;
161
+ class NotFoundException extends NotFoundError {
162
+ }
163
+ exports.NotFoundException = NotFoundException;
164
+ class ConflictException extends ConflictError {
165
+ }
166
+ exports.ConflictException = ConflictException;
167
+ class PayloadTooLargeException extends PayloadTooLargeError {
168
+ }
169
+ exports.PayloadTooLargeException = PayloadTooLargeException;
170
+ class UnprocessableException extends UnprocessableError {
171
+ }
172
+ exports.UnprocessableException = UnprocessableException;
173
+ class RateLimitException extends RateLimitError {
174
+ }
175
+ exports.RateLimitException = RateLimitException;
176
+ class InternalException extends InternalError {
177
+ }
178
+ exports.InternalException = InternalException;
179
+ class ServiceUnavailableException extends ServiceUnavailableError {
180
+ }
181
+ exports.ServiceUnavailableException = ServiceUnavailableException;
182
+ class GatewayTimeoutException extends GatewayTimeoutError {
183
+ }
184
+ exports.GatewayTimeoutException = GatewayTimeoutException;
160
185
  // ─── Global Error Handler ─────────────────────────────────────────────────────
161
186
  function escapeHtml(str) {
162
187
  if (typeof str !== 'string')
@@ -478,11 +503,14 @@ function createErrorHandler(isDev = false) {
478
503
  httpPart: err.httpPart,
479
504
  }));
480
505
  res.status(400).json({
506
+ success: false,
481
507
  statusCode: 400,
482
508
  error: 'Bad Request',
483
509
  message: message || 'Validation Error',
510
+ validationErrors: errorsMap,
484
511
  errors: errorsMap,
485
512
  details: responseDetails,
513
+ timestamp: new Date().toISOString(),
486
514
  });
487
515
  return;
488
516
  }
@@ -490,10 +518,14 @@ function createErrorHandler(isDev = false) {
490
518
  if (err instanceof SyntaxError && 'body' in err) {
491
519
  res.status(400).json({
492
520
  success: false,
521
+ statusCode: 400,
493
522
  error: {
494
523
  code: 'INVALID_JSON',
495
524
  message: 'Invalid JSON in request body',
496
525
  },
526
+ message: 'Invalid JSON in request body',
527
+ validationErrors: null,
528
+ timestamp: new Date().toISOString(),
497
529
  });
498
530
  return;
499
531
  }
@@ -514,11 +546,15 @@ function createErrorHandler(isDev = false) {
514
546
  }
515
547
  res.status(500).json({
516
548
  success: false,
549
+ statusCode: 500,
517
550
  error: {
518
551
  code: 'INTERNAL_ERROR',
519
552
  message: isDev ? err.message : 'An unexpected error occurred',
520
553
  ...(isDev && { stack: err.stack }),
521
554
  },
555
+ message: isDev ? err.message : 'An unexpected error occurred',
556
+ validationErrors: null,
557
+ timestamp: new Date().toISOString(),
522
558
  });
523
559
  };
524
560
  }