exisjs 0.7.12 → 0.7.13

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 (116) hide show
  1. package/README.md +195 -39
  2. package/dist/config/types.d.ts +2 -2
  3. package/dist/config/types.d.ts.map +1 -1
  4. package/dist/decorators/params.d.ts +21 -0
  5. package/dist/decorators/params.d.ts.map +1 -1
  6. package/dist/decorators/params.js +21 -1
  7. package/dist/decorators/params.js.map +1 -1
  8. package/dist/di/container.d.ts +15 -6
  9. package/dist/di/container.d.ts.map +1 -1
  10. package/dist/di/container.js +26 -3
  11. package/dist/di/container.js.map +1 -1
  12. package/dist/di/index.d.ts +1 -1
  13. package/dist/di/index.d.ts.map +1 -1
  14. package/dist/di/index.js.map +1 -1
  15. package/dist/di/inject.d.ts +2 -1
  16. package/dist/di/inject.d.ts.map +1 -1
  17. package/dist/di/inject.js +2 -1
  18. package/dist/di/inject.js.map +1 -1
  19. package/dist/docs/BENCHMARKS.md +4 -4
  20. package/dist/docs/content/README.md +43 -44
  21. package/dist/docs/content/boundaries.mdx +138 -61
  22. package/dist/docs/content/cli/commands.mdx +151 -128
  23. package/dist/docs/content/cli/overview.mdx +61 -47
  24. package/dist/docs/content/cli/usage.mdx +107 -30
  25. package/dist/docs/content/config.mdx +145 -158
  26. package/dist/docs/content/controllers.mdx +193 -464
  27. package/dist/docs/content/cron.mdx +183 -69
  28. package/dist/docs/content/decorators.mdx +449 -154
  29. package/dist/docs/content/errors.mdx +202 -305
  30. package/dist/docs/content/exisjs-mcp.mdx +59 -49
  31. package/dist/docs/content/installation.mdx +243 -157
  32. package/dist/docs/content/introduction.mdx +220 -48
  33. package/dist/docs/content/middleware.mdx +171 -497
  34. package/dist/docs/content/modules.mdx +141 -83
  35. package/dist/docs/content/providers.mdx +216 -180
  36. package/dist/docs/content/requests.mdx +168 -488
  37. package/dist/docs/content/responses.mdx +200 -533
  38. package/dist/docs/content/routing.mdx +235 -598
  39. package/dist/docs/content/security.mdx +233 -57
  40. package/dist/docs/content/structure.mdx +151 -240
  41. package/dist/docs/content/swagger.mdx +192 -91
  42. package/dist/docs/content/testing.mdx +205 -74
  43. package/dist/docs/content/validation.mdx +479 -341
  44. package/dist/docs/reference/README.md +34 -2
  45. package/dist/docs/reference/config.mdx +277 -75
  46. package/dist/docs/reference/context.mdx +197 -106
  47. package/dist/docs/reference/cron.mdx +266 -152
  48. package/dist/docs/reference/database.mdx +135 -53
  49. package/dist/docs/reference/decorators.mdx +234 -116
  50. package/dist/docs/reference/di.mdx +146 -112
  51. package/dist/docs/reference/error.mdx +155 -178
  52. package/dist/docs/reference/exis.mdx +141 -70
  53. package/dist/docs/reference/logger.mdx +110 -126
  54. package/dist/docs/reference/middleware.mdx +235 -158
  55. package/dist/docs/reference/plugin.mdx +136 -64
  56. package/dist/docs/reference/router.mdx +275 -293
  57. package/dist/docs/reference/sanitize.mdx +91 -87
  58. package/dist/docs/reference/swagger.mdx +280 -100
  59. package/dist/docs/reference/testing.mdx +223 -89
  60. package/dist/docs/reference/validator.mdx +194 -206
  61. package/dist/lib/start-server.js +3 -8
  62. package/dist/lib/start-server.js.map +1 -1
  63. package/dist/module/module.d.ts +6 -3
  64. package/dist/module/module.d.ts.map +1 -1
  65. package/dist/module/module.js +17 -3
  66. package/dist/module/module.js.map +1 -1
  67. package/dist/router/controller-registrar.d.ts.map +1 -1
  68. package/dist/router/controller-registrar.js +26 -4
  69. package/dist/router/controller-registrar.js.map +1 -1
  70. package/dist/server/app.d.ts +3 -1
  71. package/dist/server/app.d.ts.map +1 -1
  72. package/dist/server/app.js +24 -8
  73. package/dist/server/app.js.map +1 -1
  74. package/dist/server/request.d.ts +2 -0
  75. package/dist/server/request.d.ts.map +1 -1
  76. package/dist/server/request.js +11 -0
  77. package/dist/server/request.js.map +1 -1
  78. package/dist/swagger/generator.d.ts.map +1 -1
  79. package/dist/swagger/generator.js +130 -10
  80. package/dist/swagger/generator.js.map +1 -1
  81. package/dist/telemetry/index.d.ts +12 -0
  82. package/dist/telemetry/index.d.ts.map +1 -0
  83. package/dist/telemetry/index.js +121 -0
  84. package/dist/telemetry/index.js.map +1 -0
  85. package/dist/testing/client.d.ts +1 -0
  86. package/dist/testing/client.d.ts.map +1 -1
  87. package/dist/testing/client.js +4 -1
  88. package/dist/testing/client.js.map +1 -1
  89. package/dist/testing/index.d.ts.map +1 -1
  90. package/dist/testing/index.js +11 -4
  91. package/dist/testing/index.js.map +1 -1
  92. package/dist/validator/builder.d.ts +26 -1
  93. package/dist/validator/builder.d.ts.map +1 -1
  94. package/dist/validator/builder.js +31 -0
  95. package/dist/validator/builder.js.map +1 -1
  96. package/dist/validator/engine.d.ts +23 -0
  97. package/dist/validator/engine.d.ts.map +1 -1
  98. package/dist/validator/engine.js +228 -31
  99. package/dist/validator/engine.js.map +1 -1
  100. package/dist/validator/index.d.ts +8 -4
  101. package/dist/validator/index.d.ts.map +1 -1
  102. package/dist/validator/index.js +4 -1
  103. package/dist/validator/index.js.map +1 -1
  104. package/dist/validator/tex-types.d.ts +15 -0
  105. package/dist/validator/tex-types.d.ts.map +1 -1
  106. package/dist/validator/tex-types.js +102 -0
  107. package/dist/validator/tex-types.js.map +1 -1
  108. package/dist/validator/tex.d.ts +1 -1
  109. package/dist/validator/tex.d.ts.map +1 -1
  110. package/dist/validator/tex.js +3 -1
  111. package/dist/validator/tex.js.map +1 -1
  112. package/dist/validator/types.d.ts +1 -0
  113. package/dist/validator/types.d.ts.map +1 -1
  114. package/package.json +46 -14
  115. package/dist/docs/content/dataloader.mdx +0 -170
  116. package/dist/docs/reference/dataloader.mdx +0 -91
@@ -1,259 +1,278 @@
1
1
  ---
2
- title: "Router & Controllers"
3
- description: "API Reference for defining routes and controllers in ExisJS."
2
+ title: "exisjs/router"
3
+ description: "API Reference for ExisJS routing engines: Functional routing (controller, route.*, defineBoundary) and Class-Based OOP routing (decorators, parameter injectors)."
4
+ nextUrl: "/docs/reference/validator"
5
+ nextTitle: "Validator Reference"
4
6
  ---
5
7
 
6
- # Router & Controllers
8
+ # `exisjs/router`
7
9
 
8
- ExisJS provides two distinct ways to build your API: **Functional Routing** (using `route` and `controller`) and **Class-Based Routing** (using standard decorators). You can use either paradigm, or even mix both in the same application.
10
+ ExisJS provides two routing paradigms with full feature parity: **Functional Routing** (`exisjs/router`) and **Class-Based OOP Routing** (`exisjs/decorators`).
9
11
 
10
12
  ---
11
13
 
12
- ## 1. Functional Routing
14
+ ## 1. Functional Routing API
13
15
 
14
- The functional router provides a lightweight, type-safe API via the `route` and `controller` exports from `exisjs/router`.
15
-
16
- These utilities allow you to define HTTP endpoints with built-in validation schemas, automatically typed execution contexts, and file-level configuration.
17
-
18
- ### `controller()`
19
-
20
- The `controller` function wraps a collection of routes and configuration options (like middleware or CORS) inside a single file.
21
-
22
- ### Usage
16
+ The functional router allows defining endpoints and boundaries using composable objects and functions.
23
17
 
24
18
  ```typescript
25
- import { controller, route } from 'exisjs/router'
26
- import { authMiddleware } from '../../middleware/auth'
27
-
28
- export default controller({
29
- // Global config applied to all routes in this controller
30
- cors: true,
31
- middleware: [authMiddleware],
32
-
33
- // Your individual routes
34
- getUser: route.get('/user', {
35
- async handle({ req }) {
36
- return { user: req.user }
37
- }
38
- })
39
- })
19
+ import {
20
+ route,
21
+ controller,
22
+ createRouter,
23
+ defineMiddleware,
24
+ defineBoundary,
25
+ validate,
26
+ getContext,
27
+ setContext,
28
+ getRequest,
29
+ getResponse,
30
+ after,
31
+ fileUpload,
32
+ SSEStream,
33
+ } from 'exisjs/router'
34
+
35
+ import type {
36
+ RouteConfig,
37
+ RouteDefinition,
38
+ ControllerConfig,
39
+ SuperContext,
40
+ BoundaryConfig,
41
+ BoundaryContext,
42
+ BoundaryExcludeRule,
43
+ Request,
44
+ Response,
45
+ NextFunction,
46
+ } from 'exisjs/router'
40
47
  ```
41
48
 
42
- ### Options (`ControllerConfig`)
43
- - `cors` (`boolean | CorsConfig`): Enables or configures CORS for all routes in this file.
44
- - `middleware` (`Handler[]`): Array of middleware functions applied to all routes in this file.
45
- - `filters` (`any | any[]`): Route filters applied to all routes.
46
- - `onError` (`HookError`): A file-level error handler.
47
- - `onResponse` (`HookResponse`): A file-level response hook.
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
-
50
- ### `controller.merge(...controllers)`
49
+ ### `controller(config)`
51
50
 
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.
51
+ Wraps route definitions, file-scoped middlewares, guards, and CORS policies into an exportable controller slice.
53
52
 
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
- })
53
+ ```typescript
54
+ function controller<T extends Record<string, any>>(
55
+ config: ControllerConfig<T>
56
+ ): ControllerDefinition<T>
57
+ ```
70
58
 
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
- })
59
+ #### `ControllerConfig` Options
79
60
 
80
- // Export combined controller as default
81
- export default controller.merge(authRoutes, oauthRoutes)
82
- ```
61
+ | Property | Type | Description |
62
+ | :--- | :--- | :--- |
63
+ | `cors` | `boolean \| CorsConfig` | CORS configuration applied to all routes in this controller file. |
64
+ | `middleware` / `middlewares` | `Handler \| Handler[]` | Middleware functions executed prior to controller route handlers. |
65
+ | `guards` | `Guard[]` | Authorization guards executed before controller endpoints. |
66
+ | `interceptors` | `Interceptor[]` | Response interceptors applied to this controller. |
67
+ | `filters` | `Filter[]` | Exception filters applied across this controller. |
68
+ | `onError` | `(err: Error, req: Request, res: Response) => any` | Controller-scoped error hook. |
69
+ | `onResponse` | `(data: any, req: Request, res: Response) => any` | Controller-scoped response hook. |
70
+ | `[key: string]` | `RouteDefinition` | Route builder definitions (e.g. `list: route.get(...)`). |
83
71
 
84
- ### `controller.extend(base, overrides)`
72
+ #### Composition Methods
85
73
 
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.
74
+ - **`controller.merge(...controllers)`**: Combines multiple functional controller slices into a single unified controller.
75
+ - **`controller.extend(base, overrides)`**: Inherits routes and configuration from a base controller, allowing overrides and additions.
87
76
 
88
- ```typescript filename="src/http/products/route.ts"
77
+ ```typescript
89
78
  import { controller, route } from 'exisjs/router'
90
79
 
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', {
80
+ const base = controller({
81
+ list: route.get('/', {
111
82
  async handle() {
112
- return { featured: ['Product 1', 'Product 2'] }
113
- }
83
+ return { items: [] }
84
+ },
114
85
  }),
86
+ })
115
87
 
116
- // Override base endpoint
117
- getById: route.get('/:id', {
118
- async handle({ params }) {
119
- return { customProduct: true, id: params.id }
120
- }
121
- })
88
+ export default controller.extend(base, {
89
+ create: route.post('/', {
90
+ async handle({ body }) {
91
+ return { created: true }
92
+ },
93
+ }),
122
94
  })
123
95
  ```
124
96
 
125
97
  ---
126
98
 
127
- ## `route`
99
+ ### `route` Builder
128
100
 
129
- The `route` object provides factory functions for every HTTP method (e.g., `route.get`, `route.post`, `route.patch`, `route.delete`, `route.ws`, `route.sse`).
101
+ Constructs route definitions for standard HTTP methods and streaming protocols:
130
102
 
131
- It takes a path and a `RouteConfig` object.
103
+ - `route.get(path, config)`
104
+ - `route.post(path, config)`
105
+ - `route.put(path, config)`
106
+ - `route.patch(path, config)`
107
+ - `route.delete(path, config)`
108
+ - `route.options(path, config)`
109
+ - `route.head(path, config)`
110
+ - `route.all(path, config)`
111
+ - `route.sse(path, config)`
112
+ - `route.ws(path, config)`
132
113
 
133
- ### Usage
114
+ #### `RouteConfig` Specification
134
115
 
135
116
  ```typescript
136
- import { controller, route } from 'exisjs/router'
137
- import { tex } from 'exisjs/validator'
138
-
139
- export default controller({
140
- createUser: route.post('/users', {
141
- body: tex.object({
142
- email: tex.email(),
143
- password: tex.string({ min: 8 })
144
- }),
145
- async handle(ctx) {
146
- // ctx.body is strictly typed!
147
- const { email, password } = ctx.body
148
- return { success: true, email }
149
- }
150
- })
151
- })
117
+ export interface RouteConfig<B = any, Q = any, P = any, TContext = Record<string, any>, TReturn = any> {
118
+ // Input Validation Schemas
119
+ body?: Schema<B>
120
+ query?: Schema<Q>
121
+ params?: Schema<P>
122
+ response?: Schema<TReturn>
123
+ returns?: Schema<TReturn>
124
+
125
+ // Execution Handler
126
+ handle: (ctx: SuperContext<B, Q, P, TContext>) => TReturn | Promise<TReturn>
127
+
128
+ // Pipeline & Security
129
+ middleware?: Handler<any, any, any, any, TContext> | Handler<any, any, any, any, TContext>[]
130
+ guards?: any | any[]
131
+ interceptors?: any | any[]
132
+ filters?: any | any[]
133
+ roles?: string | string[]
134
+ permissions?: string | string[]
135
+ cors?: boolean | CorsConfig
136
+ host?: string | string[]
137
+ public?: boolean
138
+
139
+ // File Uploads & Tuning
140
+ upload?: string | RouteUploadOptions
141
+ timeout?: number | { ms: number; statusCode?: number; message?: string }
142
+ httpCode?: number
143
+ headers?: Record<string, string>
144
+ redirect?: string | { url: string; statusCode?: number }
145
+
146
+ // OpenAPI Metadata
147
+ summary?: string
148
+ description?: string
149
+ tags?: string[]
150
+ operationId?: string
151
+ deprecated?: boolean
152
+ excludeFromDocs?: boolean
153
+ }
152
154
  ```
153
155
 
154
- ### Options (`RouteConfig`)
155
-
156
- - `body`: A validation schema for the request body (e.g., using `tex` or Zod).
157
- - `query`: A validation schema for the query string (e.g., `tex.pagination()`).
158
- - `params`: A validation schema for the URL path parameters.
159
- - `upload` (`string | RouteUploadOptions`): Declarative multipart upload configuration (e.g. `{ field: 'photo', maxBytes: 10 * 1024 * 1024, mimeTypes: [...] }`). Automatically mounts `fileUpload` middleware and populates `req.file` / `req.files`.
160
- - `timeout` (`number`): Per-route timeout override in milliseconds (e.g. `120000`). Overrides global timeout for this endpoint.
161
- - `response`: A validation schema to serialize/validate the outgoing response.
162
- - `middleware` (`Handler[]`): An array of middleware specific to this route.
163
- - `cors` (`boolean | CorsConfig`): Route-specific CORS overrides.
164
- - `filters` (`any | any[]`): Route-specific filters (like rate limiters or authorization checks).
165
- - `permissions` (`string[]`): An array of required permission flags to access this route.
166
- - `metadata` (`Record<string, any>`): Arbitrary metadata attached to the route for custom plugins/middleware.
167
- - `host` (`string | string[]`): Restrict this route to a specific subdomain/host (e.g. `api.example.com`).
168
- - `handle`: The async function that executes your business logic. It receives a `SuperContext`.
169
-
170
156
  ---
171
157
 
172
- ## `SuperContext`
158
+ ### `SuperContext`
173
159
 
174
- Every `handle` function receives a `SuperContext` object that provides fully-typed access to the request and application environment.
160
+ The strongly-typed execution context supplied to every functional `handle()` function:
175
161
 
176
162
  ```typescript
177
- import { controller, route } from '../router' // Import your custom router (see below)
178
- import { tex } from 'exisjs/validator'
179
-
180
- export default controller({
181
- getUserInfo: route.get('/info/:id', {
182
- params: tex.object({ id: tex.string() }),
183
- query: tex.object({ debug: tex.boolean({ optional: true }) }),
184
- async handle({ body, query, params, headers, req, res, app, state, user, workspace }) {
185
- // 1. `body`, `query`, and `params` are pre-validated and fully typed!
186
- const id = params.id
187
-
188
- // 2. `req` and `res` are the raw ExisJS request/response objects
189
- const ip = req.ip
190
-
191
- // 3. `app` is the global ExisJS application instance
192
- const db = app.resolve('Database')
193
-
194
- // 4. `resolve` is the request-scoped dependency injection resolver!
195
- const usersService = resolve(UsersService)
196
-
197
- // 5. `state` is a mutable object shared across middlewares
198
- const cacheHit = state.cacheHit
199
-
200
- // 6. `user` and `workspace` are strictly typed from your custom context!
201
- return { id, email: user.email, workspace }
202
- }
203
- })
204
- })
163
+ export interface SuperContext<B = any, Q = any, P = any, TContext = Record<string, any>> {
164
+ /** Pre-validated request body */
165
+ body: B
166
+ /** Pre-validated query string parameters */
167
+ query: Q
168
+ /** Pre-validated URL route parameters */
169
+ params: P
170
+ /** Request HTTP headers */
171
+ headers: Record<string, string | string[] | undefined>
172
+ /** ExisRequest instance */
173
+ req: Request<B, Q, P, TContext>
174
+ /** ExisResponse instance */
175
+ res: Response
176
+ /** Exis Application instance */
177
+ app: App
178
+ /** Mutable dictionary shared across route middlewares */
179
+ state: Record<string, any>
180
+ /** Request-scoped Dependency Injection resolver */
181
+ resolve: <T>(token: ProviderToken<T>) => T
182
+ /** Single uploaded file */
183
+ file?: ExisFile
184
+ /** Multiple uploaded files */
185
+ files?: ExisFile[] | Record<string, ExisFile[]>
186
+ /** Multipart text fields */
187
+ fields?: Record<string, any>
188
+ }
205
189
  ```
206
190
 
207
191
  ---
208
192
 
209
- ## `createRouter<T>()`
210
-
211
- If your application relies heavily on custom context (e.g., passing a `user` or `workspace` object injected via middleware to every route), you can create a strongly-typed custom router using `createRouter<T>()`.
193
+ ### `defineBoundary(config)`
212
194
 
213
- This removes the need to constantly cast or redefine types.
195
+ Declares hierarchical folder-level middleware, security controls, and response defaults inside `boundary.ts` files.
214
196
 
215
- ### Usage
197
+ ```typescript
198
+ function defineBoundary<TContext = Record<string, any>>(
199
+ config: BoundaryConfig<TContext>
200
+ ): BoundaryConfig<TContext>
201
+ ```
216
202
 
217
- ```typescript filename="src/router.ts"
218
- import { createRouter } from "exisjs/router";
203
+ #### `BoundaryConfig` Options
204
+
205
+ | Property | Type | Description |
206
+ | :--- | :--- | :--- |
207
+ | `middleware` / `middlewares` | `Handler \| Handler[]` | Middleware cascaded to all routes in this directory and subdirectories. |
208
+ | `cors` | `boolean \| CorsConfig` | Directory-scoped CORS policy. |
209
+ | `headers` | `Record<string, string>` | Default response headers applied to matching routes. |
210
+ | `guards` | `Guard[]` | Authorization guards applied to matching routes. |
211
+ | `interceptors` | `Interceptor[]` | Response interceptors applied to matching routes. |
212
+ | `filters` | `Filter[]` | Exception filters applied to matching routes. |
213
+ | `exclude` | `(string \| BoundaryExcludeRule)[]` | Route patterns or method rules excluded from this boundary. |
214
+ | `timeout` | `number \| ((req: Request) => number)` | Timeout threshold in milliseconds for routes in this directory. |
215
+ | `blockSuspiciousProbes` | `boolean \| BlockProbesOptions` | Suppresses and blackholes malicious scanner probes (`.env`, `wp-admin`, `.git`). |
216
+ | `metadata` | `Record<string, any>` | Metadata (e.g. OpenAPI tags) cascaded to all child routes. |
219
217
 
220
- interface User {
221
- userId: string;
222
- role: string;
223
- email: string;
224
- }
225
-
226
- interface AppContext {
227
- user: User;
228
- workspace: string; // Just an example to prove we can pass any property
229
- }
218
+ ---
230
219
 
231
- // Create a completely customized router scoped to this application
232
- export const { route, controller } = createRouter<AppContext>();
233
- ```
220
+ ## 2. Class-Based OOP Routing API (`exisjs/decorators`)
234
221
 
235
- Now, when you use this custom `route` object, the `SuperContext` will automatically infer your custom context fields!
222
+ The Class-Based paradigm uses standard TypeScript decorators for controller definitions, route methods, parameter injection, and pipeline lifecycle hooks.
236
223
 
237
- ```typescript filename="src/http/users/route.ts"
238
- import { route, controller } from '../../router'
239
-
240
- export default controller({
241
- me: route.get('/me', {
242
- async handle({ user, workspace }) {
243
- // `user` and `workspace` are strictly typed!
244
- return { id: user.userId, workspace }
245
- }
246
- })
247
- })
224
+ ```typescript
225
+ import {
226
+ Controller,
227
+ Get,
228
+ Post,
229
+ Put,
230
+ Patch,
231
+ Delete,
232
+ Options,
233
+ Head,
234
+ All,
235
+ Ws,
236
+ Sse,
237
+ Body,
238
+ Param,
239
+ Query,
240
+ QueryParam,
241
+ Headers,
242
+ Req,
243
+ Res,
244
+ Next,
245
+ Ip,
246
+ Session,
247
+ UploadedFile,
248
+ UploadedFiles,
249
+ createParamDecorator,
250
+ Use,
251
+ Guards,
252
+ Filters,
253
+ Interceptors,
254
+ HttpCode,
255
+ Header,
256
+ Redirect,
257
+ SetMetadata,
258
+ Reflector,
259
+ } from 'exisjs/decorators'
248
260
  ```
249
261
 
250
262
  ---
251
263
 
252
- ## 2. Class-Based Routing (OOP)
264
+ ### `@Controller(prefix?, options?)`
253
265
 
254
- If you prefer an Object-Oriented paradigm, ExisJS provides a robust set of decorators via `exisjs/decorators`. These decorators are built on standard ECMAScript decorators and do not require `reflect-metadata`, making them incredibly fast and lightweight.
266
+ Decorates a class as an HTTP controller slice mounted at the specified route prefix.
255
267
 
256
- ### Usage
268
+ ```typescript
269
+ function Controller(
270
+ prefixOrOptions?: string | ControllerOptions,
271
+ options?: ControllerOptions
272
+ ): ClassDecorator
273
+ ```
274
+
275
+ #### Example
257
276
 
258
277
  ```typescript filename="src/http/users/route.ts"
259
278
  import { Controller, Get, Post, Body, Param } from 'exisjs/decorators'
@@ -261,131 +280,94 @@ import { tex } from 'exisjs/validator'
261
280
 
262
281
  @Controller('/users')
263
282
  export default class UserController {
264
-
265
283
  @Get('/:id')
266
284
  async getUser(@Param('id') id: string) {
267
285
  return { id, name: 'Alice' }
268
286
  }
269
287
 
270
288
  @Post('/', {
271
- body: tex.object({ email: tex.email(), password: tex.string() })
289
+ body: tex.object({
290
+ name: tex.string(),
291
+ email: tex.email(),
292
+ }),
272
293
  })
273
- async createUser(@Body() body: any) {
274
- // body is automatically validated!
275
- return { success: true, email: body.email }
294
+ async createUser(@Body() body: { name: string; email: string }) {
295
+ return { success: true, user: body }
276
296
  }
277
297
  }
278
298
  ```
279
299
 
280
- ### Route Decorators
281
-
282
- - `@Get(path, schema?)`
283
- - `@Post(path, schema?)`
284
- - `@Put(path, schema?)`
285
- - `@Patch(path, schema?)`
286
- - `@Delete(path, schema?)`
287
- - `@Ws(path, schema?)`: Defines a WebSocket route.
288
- - `@Sse(path, schema?)`: Defines a Server-Sent Events stream route.
289
-
290
- *Note: Just like functional routes, you can pass an optional `RouteSchema` to any of these decorators to automatically validate requests!*
291
-
292
- ### Parameter Injectors
293
-
294
- Use parameter decorators to automatically extract data from the incoming request and inject them into your method arguments:
295
-
296
- - `@Body()`: Injects the parsed JSON request body.
297
- - `@Param('id')`: Injects a specific URL path parameter.
298
- - `@QueryParam('search')` / `@Query('search')`: Injects a specific query string parameter.
299
- - `@Headers('authorization')`: Injects a specific HTTP header.
300
- - `@Req()`: Injects the raw ExisJS Request object.
301
- - `@Res()`: Injects the raw ExisJS Response object.
302
- - `@Next()`: Injects the next function.
303
- - `@Socket()`: Injects the active WebSocket instance (for `@Ws` routes).
304
- - `@Stream()`: Injects the active SSE instance (for `@Sse` routes).
305
- - `createParamDecorator(factory)`: Factory to construct custom parameter decorators.
306
-
307
- ### Extended Decorators
308
-
309
- ExisJS provides a rich ecosystem of decorators to apply middleware, configure responses, and enforce security on both individual routes and entire controllers:
300
+ ---
310
301
 
311
- #### Configuration & Pipeline
312
- - `@Use(...items)`: Unified decorator that applies middlewares, Guards (`canActivate`), Interceptors (`intercept`), and Exception Filters (`catch`) to a route or entire controller.
313
- - `@Hosts('api.example.com')`: Restricts the route or controller to specific domains.
302
+ ### HTTP Method Decorators
314
303
 
315
- #### Security & Metadata
316
- - `@Permissions('admin')`: Requires specific permission flags.
317
- - `@SetMetadata(key, value)`: Attaches arbitrary metadata to classes and handler methods (retrieved via `Reflector`).
318
- - `Reflector`: Reads and merges metadata attached with `@SetMetadata`.
304
+ Method decorators attach HTTP handler logic and validation schemas to controller methods:
319
305
 
320
- #### Responses & Reliability
321
- - `@HttpCode(201)`: Forces a specific HTTP status code for successful responses.
322
- - `@Header('X-Custom', 'Value')`: Automatically injects a specific response header.
323
- - `@Returns(schema)`: Explicitly defines the response schema for OpenAPI generation.
324
- - `@Idempotent()`: Enforces idempotency keys for mutations (e.g., checkout/payments).
306
+ - `@Get(path?, schema?)`
307
+ - `@Post(path?, schema?)`
308
+ - `@Put(path?, schema?)`
309
+ - `@Patch(path?, schema?)`
310
+ - `@Delete(path?, schema?)`
311
+ - `@Options(path?, schema?)`
312
+ - `@Head(path?, schema?)`
313
+ - `@All(path?, schema?)`
314
+ - `@Sse(path?, schema?)`
315
+ - `@Ws(path?, schema?)`
325
316
 
326
317
  ---
327
318
 
328
- ## 3. Streaming & Backpressure Pipeline
319
+ ### Parameter Injector Decorators
329
320
 
330
- The response object supports universal streaming with backpressure management across all route types:
321
+ Parameter decorators extract and inject typed data directly into handler method parameters:
331
322
 
332
- - `res.sendStream(source)`: Streams a Node.js `stream.Readable`, Web `ReadableStream`, or `AsyncIterable` directly to the client socket. Pauses consumption when socket buffers fill up and terminates source readers upon client disconnection.
333
- - `res.stream(source)`: Alias for `sendStream()`.
334
- - **Automatic Handler Auto-Piping**: Returning a Node.js stream, Web standard `ReadableStream`, or `AsyncIterable` generator directly from `handle()` automatically delegates to `res.sendStream()`.
323
+ | Decorator | Injected Data | Example |
324
+ | :--- | :--- | :--- |
325
+ | `@Body(property?)` | Request body payload or sub-property | `@Body() data: CreateUserDto` |
326
+ | `@Param(key?)` | URL path parameters dictionary or specific param | `@Param('id') userId: string` |
327
+ | `@Query(key?)` / `@QueryParam(key?)` | Query string parameters dictionary or specific key | `@Query('limit') limit: number` |
328
+ | `@Headers(name?)` | Request headers dictionary or specific header | `@Headers('authorization') auth: string` |
329
+ | `@Req()` | Underlying `ExisRequest` instance | `@Req() req: Request` |
330
+ | `@Res()` | Underlying `ExisResponse` instance | `@Res() res: Response` |
331
+ | `@Next()` | Pipeline `NextFunction` callback | `@Next() next: NextFunction` |
332
+ | `@Ip()` | Client remote IP address | `@Ip() clientIp: string` |
333
+ | `@UploadedFile(field?)` | Single uploaded multipart file object | `@UploadedFile('avatar') file: ExisFile` |
334
+ | `@UploadedFiles(field?)` | Multiple uploaded multipart file objects | `@UploadedFiles('docs') files: ExisFile[]` |
335
335
 
336
336
  ---
337
337
 
338
- ## 4. Boundaries & Folder-Scoped Pipelines (`boundary.ts`)
338
+ ### `createParamDecorator(factory)`
339
339
 
340
- ExisJS introduces **Boundaries** (`boundary.ts`) to manage folder-scoped middleware, CORS policies, security probes, guards, interceptors, and default headers without having to wire them manually across every route file.
340
+ Constructs custom parameter decorators to extract specialized values from the request context:
341
341
 
342
- ### How Boundaries Work
343
-
344
- Place a `boundary.ts` file in any route directory (e.g., `src/http/boundary.ts` or `src/http/api/v1/boundary.ts`):
345
-
346
- ```typescript filename="src/http/api/boundary.ts"
347
- import { defineBoundary } from 'exisjs/router'
348
- import { authGuard } from '../../guards/auth'
342
+ ```typescript
343
+ import { createParamDecorator } from 'exisjs/decorators'
349
344
 
350
- export default defineBoundary({
351
- // Applied to all routes in src/http/api/* and subdirectories
352
- cors: {
353
- origin: 'https://app.example.com',
354
- credentials: true,
355
- },
356
- guards: [authGuard],
357
- headers: {
358
- 'X-API-Version': 'v1',
359
- },
360
- // Exclude public endpoints from this boundary's pipeline
361
- exclude: ['/api/health', '/api/public/*'],
345
+ export const CurrentUser = createParamDecorator((data: string | undefined, ctx) => {
346
+ const req = ctx.req
347
+ return data ? req.user?.[data] : req.user
362
348
  })
363
- ```
364
-
365
- ### Cascading & Precedence Rules
366
349
 
367
- When route slices are nested in subdirectories (e.g. `src/http/boundary.ts` -> `src/http/api/boundary.ts` -> `src/http/api/users/route.ts`), ExisJS evaluates boundary configurations using the following rules:
368
-
369
- 1. **Middlewares, Guards, Filters, & Interceptors (Cascading / Additive)**:
370
- - Evaluated **top-down** from parent boundary -> child boundary -> route-level controller.
371
- 2. **CORS Configuration (Innermost Precedence)**:
372
- - **Route-Level `cors`** (in `route.ts` / `controller({ cors: ... })`) overrides any boundary CORS.
373
- - **Child Boundary `cors`** (in `src/http/api/boundary.ts`) overrides parent boundary `cors` (in `src/http/boundary.ts`).
374
- - **Parent Boundary `cors`** applies if no child boundary or route overrides it.
375
- - **Global `app.options.cors`** acts as the root fallback.
376
- 3. **Response Headers (Deep Merge with Child Precedence)**:
377
- - Headers defined in child boundaries or route definitions overwrite identical header keys from parent boundaries.
350
+ // Usage in controller:
351
+ @Get('/profile')
352
+ getProfile(@CurrentUser() user: any, @CurrentUser('id') userId: string) {
353
+ return { userId, user }
354
+ }
355
+ ```
378
356
 
379
357
  ---
380
358
 
381
- ## 5. `AppRouter` Client Manifest Contract
382
-
383
- ExisJS generates a compile-time route manifest in `.exis/types.d.ts` and `.exis/types/index.d.ts`:
384
-
385
- - `AppRouterPaths`: Flat dictionary mapping URL route patterns (e.g. `'/users/:id'`) to their strongly-typed handler contracts (`(payload?: B, options?: { query?: Q; params?: P }) => Promise<R>`).
386
- - `AppRouterTree`: Hierarchical dot-notation tree mapping nested folder paths (`users: { ':id': ... }`) for `@exisjs/fetch`'s `createClient<AppRouter>()`.
387
- - `AppRouter`: Combined intersection type `AppRouterPaths & AppRouterTree`.
388
- - `RouteDefinition.__type`: Phantom type property on every route definition preserving exact inferred return types, DTO schemas, and Drizzle/Prisma models.
359
+ ### Pipeline & Response Decorators
360
+
361
+ | Decorator | Target | Description |
362
+ | :--- | :--- | :--- |
363
+ | `@Use(...middlewares)` | Class / Method | Applies middleware functions to the controller or route. |
364
+ | `@Guards(...guards)` | Class / Method | Attaches authorization guards. |
365
+ | `@Filters(...filters)` | Class / Method | Attaches exception filters. |
366
+ | `@Interceptors(...interceptors)` | Class / Method | Attaches response/request interceptors. |
367
+ | `@HttpCode(status)` | Method | Sets the default HTTP status code for successful responses. |
368
+ | `@Header(name, value)` | Method | Sets static HTTP response headers. |
369
+ | `@Redirect(url, status?)` | Method | Redirects responses to the target URL. |
370
+ | `@SetMetadata(key, value)` | Class / Method | Attaches arbitrary metadata readable via `Reflector`. |
389
371
 
390
372
 
391
373