exisjs 0.7.5 → 0.7.6

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 (164) hide show
  1. package/dist/cli/commands/dev.d.ts.map +1 -1
  2. package/dist/cli/commands/dev.js +156 -85
  3. package/dist/cli/commands/dev.js.map +1 -1
  4. package/dist/cli/commands/start.d.ts.map +1 -1
  5. package/dist/cli/commands/start.js +54 -16
  6. package/dist/cli/commands/start.js.map +1 -1
  7. package/dist/cli/index.js +10 -1
  8. package/dist/cli/index.js.map +1 -1
  9. package/dist/cli/manifest.d.ts.map +1 -1
  10. package/dist/cli/manifest.js +106 -13
  11. package/dist/cli/manifest.js.map +1 -1
  12. package/dist/cli/resolve-aliases.d.ts +1 -1
  13. package/dist/cli/resolve-aliases.d.ts.map +1 -1
  14. package/dist/cli/resolve-aliases.js +30 -13
  15. package/dist/cli/resolve-aliases.js.map +1 -1
  16. package/dist/config/config.d.ts +3 -1
  17. package/dist/config/config.d.ts.map +1 -1
  18. package/dist/config/config.js.map +1 -1
  19. package/dist/config/types.d.ts +2 -0
  20. package/dist/config/types.d.ts.map +1 -1
  21. package/dist/decorators/middleware.d.ts +65 -2
  22. package/dist/decorators/middleware.d.ts.map +1 -1
  23. package/dist/decorators/middleware.js +148 -62
  24. package/dist/decorators/middleware.js.map +1 -1
  25. package/dist/decorators/params.d.ts +5 -0
  26. package/dist/decorators/params.d.ts.map +1 -1
  27. package/dist/decorators/params.js +11 -1
  28. package/dist/decorators/params.js.map +1 -1
  29. package/dist/decorators/response.d.ts.map +1 -1
  30. package/dist/decorators/response.js +32 -22
  31. package/dist/decorators/response.js.map +1 -1
  32. package/dist/di/container.d.ts.map +1 -1
  33. package/dist/di/container.js +6 -1
  34. package/dist/di/container.js.map +1 -1
  35. package/dist/docs/content/cli/commands.mdx +1 -1
  36. package/dist/docs/content/controllers.mdx +127 -10
  37. package/dist/docs/content/decorators.mdx +22 -4
  38. package/dist/docs/content/introduction.mdx +7 -7
  39. package/dist/docs/content/middleware.mdx +269 -6
  40. package/dist/docs/content/requests.mdx +180 -6
  41. package/dist/docs/content/responses.mdx +283 -32
  42. package/dist/docs/content/routing.mdx +46 -0
  43. package/dist/docs/content/validation.mdx +190 -11
  44. package/dist/docs/reference/context.mdx +11 -0
  45. package/dist/docs/reference/decorators.mdx +14 -4
  46. package/dist/docs/reference/error.mdx +44 -0
  47. package/dist/docs/reference/logger.mdx +175 -43
  48. package/dist/docs/reference/middleware.mdx +31 -0
  49. package/dist/docs/reference/router.mdx +22 -0
  50. package/dist/docs/reference/validator.mdx +83 -23
  51. package/dist/error/errors.d.ts.map +1 -1
  52. package/dist/error/errors.js +95 -43
  53. package/dist/error/errors.js.map +1 -1
  54. package/dist/error/index.d.ts +1 -0
  55. package/dist/error/index.d.ts.map +1 -1
  56. package/dist/error/index.js +11 -1
  57. package/dist/error/index.js.map +1 -1
  58. package/dist/error/overlay.d.ts +42 -0
  59. package/dist/error/overlay.d.ts.map +1 -0
  60. package/dist/error/overlay.js +306 -0
  61. package/dist/error/overlay.js.map +1 -0
  62. package/dist/index.d.ts +1 -0
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +22 -1
  65. package/dist/index.js.map +1 -1
  66. package/dist/lib/start-server.js +33 -7
  67. package/dist/lib/start-server.js.map +1 -1
  68. package/dist/logger/index.d.ts +1 -1
  69. package/dist/logger/index.d.ts.map +1 -1
  70. package/dist/logger/index.js +9 -1
  71. package/dist/logger/index.js.map +1 -1
  72. package/dist/middleware/guard.d.ts +2 -2
  73. package/dist/middleware/guard.d.ts.map +1 -1
  74. package/dist/middleware/guard.js.map +1 -1
  75. package/dist/middleware/ip-filter.d.ts +1 -0
  76. package/dist/middleware/ip-filter.d.ts.map +1 -1
  77. package/dist/middleware/ip-filter.js +2 -0
  78. package/dist/middleware/ip-filter.js.map +1 -1
  79. package/dist/middleware/middleware.d.ts +2 -2
  80. package/dist/middleware/middleware.d.ts.map +1 -1
  81. package/dist/middleware/middleware.js +20 -2
  82. package/dist/middleware/middleware.js.map +1 -1
  83. package/dist/middleware/security.d.ts +43 -1
  84. package/dist/middleware/security.d.ts.map +1 -1
  85. package/dist/middleware/security.js +101 -0
  86. package/dist/middleware/security.js.map +1 -1
  87. package/dist/response/index.d.ts +229 -6
  88. package/dist/response/index.d.ts.map +1 -1
  89. package/dist/response/index.js +272 -2
  90. package/dist/response/index.js.map +1 -1
  91. package/dist/router/boundary.d.ts +17 -9
  92. package/dist/router/boundary.d.ts.map +1 -1
  93. package/dist/router/boundary.js.map +1 -1
  94. package/dist/router/controller-registrar.d.ts.map +1 -1
  95. package/dist/router/controller-registrar.js +56 -1
  96. package/dist/router/controller-registrar.js.map +1 -1
  97. package/dist/router/index.d.ts +2 -2
  98. package/dist/router/index.d.ts.map +1 -1
  99. package/dist/router/index.js +2 -1
  100. package/dist/router/index.js.map +1 -1
  101. package/dist/router/route-builder.d.ts +70 -36
  102. package/dist/router/route-builder.d.ts.map +1 -1
  103. package/dist/router/route-builder.js +13 -15
  104. package/dist/router/route-builder.js.map +1 -1
  105. package/dist/router/route-scanner.d.ts.map +1 -1
  106. package/dist/router/route-scanner.js +24 -2
  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 +35 -17
  110. package/dist/router/router.js.map +1 -1
  111. package/dist/router/types.d.ts +8 -0
  112. package/dist/router/types.d.ts.map +1 -1
  113. package/dist/server/app.d.ts +1 -0
  114. package/dist/server/app.d.ts.map +1 -1
  115. package/dist/server/app.js +10 -0
  116. package/dist/server/app.js.map +1 -1
  117. package/dist/server/bootstrapper.d.ts.map +1 -1
  118. package/dist/server/bootstrapper.js +91 -30
  119. package/dist/server/bootstrapper.js.map +1 -1
  120. package/dist/server/cluster.d.ts.map +1 -1
  121. package/dist/server/cluster.js +13 -2
  122. package/dist/server/cluster.js.map +1 -1
  123. package/dist/server/context.d.ts +8 -0
  124. package/dist/server/context.d.ts.map +1 -1
  125. package/dist/server/context.js +50 -6
  126. package/dist/server/context.js.map +1 -1
  127. package/dist/server/lifecycle.d.ts +22 -0
  128. package/dist/server/lifecycle.d.ts.map +1 -0
  129. package/dist/server/lifecycle.js +106 -0
  130. package/dist/server/lifecycle.js.map +1 -0
  131. package/dist/server/request-handler.d.ts +1 -0
  132. package/dist/server/request-handler.d.ts.map +1 -1
  133. package/dist/server/request-handler.js +27 -17
  134. package/dist/server/request-handler.js.map +1 -1
  135. package/dist/server/request.d.ts.map +1 -1
  136. package/dist/server/request.js +18 -0
  137. package/dist/server/request.js.map +1 -1
  138. package/dist/server/response.d.ts +10 -1
  139. package/dist/server/response.d.ts.map +1 -1
  140. package/dist/server/response.js +154 -2
  141. package/dist/server/response.js.map +1 -1
  142. package/dist/testing/index.d.ts.map +1 -1
  143. package/dist/testing/index.js +6 -1
  144. package/dist/testing/index.js.map +1 -1
  145. package/dist/testing/mocks.d.ts.map +1 -1
  146. package/dist/testing/mocks.js +15 -3
  147. package/dist/testing/mocks.js.map +1 -1
  148. package/dist/utils/logger.d.ts +8 -0
  149. package/dist/utils/logger.d.ts.map +1 -1
  150. package/dist/utils/logger.js +184 -1
  151. package/dist/utils/logger.js.map +1 -1
  152. package/dist/validator/tex-types.d.ts +2 -1
  153. package/dist/validator/tex-types.d.ts.map +1 -1
  154. package/dist/validator/tex-types.js +2 -1
  155. package/dist/validator/tex-types.js.map +1 -1
  156. package/dist/validator/tex.d.ts +31 -15
  157. package/dist/validator/tex.d.ts.map +1 -1
  158. package/dist/validator/tex.js +280 -42
  159. package/dist/validator/tex.js.map +1 -1
  160. package/package.json +4 -4
  161. package/dist/dev/error-overlay.d.ts +0 -3
  162. package/dist/dev/error-overlay.d.ts.map +0 -1
  163. package/dist/dev/error-overlay.js +0 -118
  164. package/dist/dev/error-overlay.js.map +0 -1
@@ -101,13 +101,29 @@ In most cases, you don't need to manually access the entire request object. Inst
101
101
  <td className="px-4 py-3 font-mono text-xs"><span className="text-primary">@</span>Ip()</td>
102
102
  <td className="px-4 py-3 text-muted-foreground">The client's IP address</td>
103
103
  </tr>
104
+ <tr className="bg-background hover:bg-muted/30 transition-colors">
105
+ <td className="px-4 py-3 font-mono text-xs"><span className="text-primary">@</span>Cookies(name?) / <span className="text-primary">@</span>Cookie(name?)</td>
106
+ <td className="px-4 py-3 text-muted-foreground">Incoming parsed cookies or specific cookie value</td>
107
+ </tr>
108
+ <tr className="bg-background hover:bg-muted/30 transition-colors">
109
+ <td className="px-4 py-3 font-mono text-xs"><span className="text-primary">@</span>State(key?)</td>
110
+ <td className="px-4 py-3 text-muted-foreground">Request state from execution context or middleware</td>
111
+ </tr>
112
+ <tr className="bg-background hover:bg-muted/30 transition-colors">
113
+ <td className="px-4 py-3 font-mono text-xs"><span className="text-primary">@</span>AppCtx()</td>
114
+ <td className="px-4 py-3 text-muted-foreground">The root ExisJS Application instance</td>
115
+ </tr>
116
+ <tr className="bg-background hover:bg-muted/30 transition-colors">
117
+ <td className="px-4 py-3 font-mono text-xs"><span className="text-primary">@</span>Fields(name?)</td>
118
+ <td className="px-4 py-3 text-muted-foreground">Non-file multipart form fields</td>
119
+ </tr>
104
120
  <tr className="bg-background hover:bg-muted/30 transition-colors">
105
121
  <td className="px-4 py-3 font-mono text-xs"><span className="text-primary">@</span>UploadedFile()</td>
106
122
  <td className="px-4 py-3 text-muted-foreground">A single uploaded file (multipart/form-data)</td>
107
123
  </tr>
108
124
  <tr className="bg-background hover:bg-muted/30 transition-colors">
109
125
  <td className="px-4 py-3 font-mono text-xs"><span className="text-primary">@</span>UploadedFiles()</td>
110
- <td className="px-4 py-3 text-muted-foreground">Array of uploaded files</td>
126
+ <td className="px-4 py-3 text-muted-foreground">Array or dictionary of uploaded files</td>
111
127
  </tr>
112
128
  </tbody>
113
129
  </table>
@@ -115,22 +131,123 @@ In most cases, you don't need to manually access the entire request object. Inst
115
131
 
116
132
  ## Payloads & Validation
117
133
 
118
- For POST and PUT endpoints, you need to read the incoming JSON payload. Use the `@Body()` decorator to inject it.
134
+ For POST, PUT, and PATCH endpoints, you can validate and read incoming payloads using either method-level schemas or parameter-level `@Body(Schema)` decorators:
119
135
 
120
136
  ```typescript filename="src/http/users/route.ts"
121
- import { Controller, Post, Body } from 'exisjs/decorators'
137
+ import { Controller, Post, Body, HttpCode } from 'exisjs/decorators'
138
+ import { tex } from 'exisjs/validator'
139
+ import type { ResolveSchema } from 'exisjs/validator'
140
+
141
+ export const CreateUserSchema = tex.object({
142
+ name: tex.string({ min: 2 }),
143
+ email: tex.email(),
144
+ role: tex.enum(['admin', 'user'], { default: 'user' }),
145
+ })
122
146
 
123
- export class CreateUserDto {
124
- name: string;
125
- email: string;
126
- role: string;
147
+ export type CreateUserDto = ResolveSchema<typeof CreateUserSchema>
148
+
149
+ @Controller('users')
150
+ export default class UsersController {
151
+ // 1. Parameter-level validation schema
152
+ @Post('/')
153
+ @HttpCode(201)
154
+ async create(@Body(CreateUserSchema) dto: CreateUserDto) {
155
+ return { id: 3, ...dto }
156
+ }
157
+
158
+ // 2. Or method-level schema object
159
+ @Post('/raw', { body: CreateUserSchema })
160
+ async createWithMethodSchema(@Body() dto: CreateUserDto) {
161
+ return { id: 4, ...dto }
162
+ }
127
163
  }
164
+ ```
165
+
166
+ ## Dependency Injection in Controllers
167
+
168
+ Controllers can inject services using either the ergonomic `inject()` helper or standard constructor injection:
169
+
170
+ ```typescript filename="src/http/users/route.ts"
171
+ import { Controller, Get, Param } from 'exisjs/decorators'
172
+ import { inject, Inject, Optional } from 'exisjs/di'
173
+ import { UsersService } from './service'
128
174
 
129
175
  @Controller('users')
130
176
  export default class UsersController {
131
- @Post()
132
- async create(@Body() createUserDto: CreateUserDto) {
133
- return { id: 3, ...createUserDto }
177
+ // Approach A: Property-level injection (Recommended)
178
+ private usersService = inject(UsersService)
179
+
180
+ // Approach B: Constructor injection
181
+ constructor(
182
+ @Inject('DATABASE_URL') private dbUrl: string,
183
+ @Optional() @Inject('METRICS') private metrics?: any
184
+ ) {}
185
+
186
+ @Get('/:id')
187
+ async getUser(@Param('id') id: string) {
188
+ return this.usersService.findById(id)
189
+ }
190
+ }
191
+ ```
192
+
193
+ ## File Uploads
194
+
195
+ You can process file uploads in OOP controllers using `@UseUpload` or `@Upload` decorators alongside `@UploadedFile()` and `@UploadedFiles()`:
196
+
197
+ ```typescript filename="src/http/users/route.ts"
198
+ import { Controller, Post, UseUpload, UploadedFile, Body } from 'exisjs/decorators'
199
+ import type { UploadedFile as IUploadedFile } from 'exisjs/router'
200
+
201
+ @Controller('users')
202
+ export default class UsersController {
203
+ @Post('/avatar')
204
+ @UseUpload({
205
+ field: 'avatar',
206
+ maxBytes: 5 * 1024 * 1024,
207
+ mimeTypes: ['image/jpeg', 'image/png', 'image/webp'],
208
+ })
209
+ async uploadAvatar(
210
+ @UploadedFile() file: IUploadedFile,
211
+ @Body() body: { caption?: string }
212
+ ) {
213
+ return {
214
+ success: true,
215
+ filename: file.filename,
216
+ size: file.size,
217
+ caption: body.caption,
218
+ }
219
+ }
220
+ }
221
+ ```
222
+
223
+ ## Streaming & Downloads
224
+
225
+ OOP controllers support all streaming features including Node.js `Readable` streams, Web `ReadableStream`, async generators, and file downloads:
226
+
227
+ ```typescript filename="src/http/files/route.ts"
228
+ import { Controller, Get, Res } from 'exisjs/decorators'
229
+ import type { Response } from 'exisjs/router'
230
+ import fs from 'node:fs'
231
+
232
+ @Controller('files')
233
+ export default class FilesController {
234
+ // 1. Force file download
235
+ @Get('/download')
236
+ download(@Res() res: Response) {
237
+ res.download('./reports/annual.pdf', 'Annual-Report.pdf')
238
+ }
239
+
240
+ // 2. Stream Node.js Readable or auto-pipe return value
241
+ @Get('/stream')
242
+ streamCsv() {
243
+ return fs.createReadStream('./data/export.csv')
244
+ }
245
+
246
+ // 3. Server-Sent Events (SSE)
247
+ @Get('/events')
248
+ events(@Res() res: Response) {
249
+ const sse = res.sse()
250
+ sse.send({ event: 'ping', data: { time: Date.now() } })
134
251
  }
135
252
  }
136
253
  ```
@@ -134,6 +134,12 @@ export default class UsersController {
134
134
  - `@Param(nameOrPipe?)` — Extracts a route path param (e.g. `:id`).
135
135
  - `@QueryParam(nameOrPipe?)` / `@Query(nameOrPipe?)` — Extracts a query parameter from the URL query string.
136
136
  - `@Headers(nameOrPipe?)` — Extracts a request header by name.
137
+ - `@Cookies(nameOrPipe?)` / `@Cookie(nameOrPipe?)` — Injects incoming parsed cookies or a specific cookie value.
138
+ - `@State(keyOrPipe?)` — Injects request state from execution context or middleware.
139
+ - `@AppCtx()` — Injects the root ExisJS Application instance.
140
+ - `@Fields(nameOrPipe?)` — Injects non-file multipart form fields.
141
+ - `@UploadedFile(nameOrPipe?)` — Injects a single uploaded file.
142
+ - `@UploadedFiles(nameOrPipe?)` — Injects multiple uploaded files.
137
143
  - `@HostParam(nameOrPipe?)` — Extracts a host subdomain parameter.
138
144
  - `@Req()` — Injects the raw request object.
139
145
  - `@Res(options?)` — Injects the response object (e.g. `{ passthrough: true }`).
@@ -150,16 +156,19 @@ export default class UsersController {
150
156
 
151
157
  ## 4. Middleware & Pipeline Decorators
152
158
 
153
- You can bind middlewares, guards, interceptors, and exception filters to controllers or route methods using the unified `@Use()` decorator:
159
+ You can bind middlewares, guards, interceptors, and exception filters to controllers or route methods using first-class decorators:
154
160
 
155
161
  ```typescript filename="src/http/checkout/route.ts"
156
- import { Controller, Post, Use, Idempotent } from 'exisjs/decorators'
162
+ import { Controller, Post, Use, Idempotent, RateLimit, Cors, Timeout } from 'exisjs/decorators'
157
163
  import { requireAuth } from '../../middleware/auth'
158
164
 
159
165
  @Controller('/checkout')
160
166
  @Use(requireAuth) // Applies to all routes in this controller
167
+ @Cors({ origin: 'https://app.yourdomain.com' })
168
+ @RateLimit({ max: 10, windowMs: 60000 })
161
169
  export default class CheckoutController {
162
170
  @Post('/')
171
+ @Timeout(30000)
163
172
  @Idempotent({ ttlMs: 86400000 }) // In-memory off-heap idempotency protection
164
173
  async processPayment() {
165
174
  return { success: true }
@@ -169,7 +178,15 @@ export default class CheckoutController {
169
178
 
170
179
  ### Available Lifecycle & Middleware Decorators
171
180
  - `@Use(...items)` — Unified decorator that attaches middlewares, guards (`canActivate`), interceptors (`intercept`), or exception filters (`catch`) to a class or method.
172
- - `@Idempotent(options?)` — Caches responses based on the `Idempotency-Key` header using off-heap native memory.
181
+ - `@Timeout(ms: number | TimeoutOptions)` — Overrides the request timeout for a class or method.
182
+ - `@Idempotent(options?)` — Caches responses based on the `Idempotency-Key` header using native in-memory off-heap storage.
183
+ - `@RateLimit(options?)` — Enforces request rate limits on a controller class or route method.
184
+ - `@Cors(options?)` — Configures Cross-Origin Resource Sharing rules on a controller class or route method.
185
+ - `@IpFilter(options)` — Enforces IP/CIDR allowlist and denylist.
186
+ - `@Compress()` — Enables automatic response compression (Gzip/Brotli).
187
+ - `@Dedupe(options)` — Deduplicates concurrent identical requests sharing the same key.
188
+ - `@UseUpload(options)` / `@Upload(options)` — Configures multipart file upload handling.
189
+ - `@BlockProbes(options?)` — Intercepts scanner exploit probes (`.env`, `/.git`, `/.DS_Store`) and blackholes them without log noise.
173
190
 
174
191
  ---
175
192
 
@@ -229,7 +246,8 @@ export class UserService {
229
246
  You can inject dependencies using tokens or classes, and mark optional dependencies with `@Optional()`:
230
247
 
231
248
  ```typescript filename="src/http/users/route.ts"
232
- import { Controller, Get, Param, Inject, Optional } from 'exisjs/decorators'
249
+ import { Controller, Get, Param } from 'exisjs/decorators'
250
+ import { Inject, Optional } from 'exisjs/di'
233
251
  import { UserService } from './service'
234
252
 
235
253
  @Controller('/users')
@@ -20,7 +20,7 @@ Exis aims to provide that out-of-the-box application architecture for backends:
20
20
  The fastest way to start is the Exis CLI, which scaffolds a new project directory with the core files and a conventional base structure already in place. This is the recommended path for first-time users.
21
21
 
22
22
  <div className="my-6 max-w-lg">
23
- <CopyCommand command="npx @exisjs/create@latest my-backend" />
23
+ <CopyCommand command="npm create exisjs@latest my-backend" />
24
24
  </div>
25
25
 
26
26
  <Hint>
@@ -43,12 +43,12 @@ export default controller({
43
43
  getUsers: route.get('/', {
44
44
  handle(req) {
45
45
  // Return a realistic response structure
46
- return {
46
+ return {
47
47
  data: [
48
48
  { id: 1, name: 'Alice' },
49
- { id: 2, name: 'Bob' }
49
+ { id: 2, name: 'Bob' },
50
50
  ],
51
- total: 2
51
+ total: 2,
52
52
  }
53
53
  },
54
54
  }),
@@ -66,12 +66,12 @@ export default class UsersController {
66
66
  @Get('/')
67
67
  getUsers(@Req() req: Request) {
68
68
  // Return a realistic response structure
69
- return {
69
+ return {
70
70
  data: [
71
71
  { id: 1, name: 'Alice' },
72
- { id: 2, name: 'Bob' }
72
+ { id: 2, name: 'Bob' },
73
73
  ],
74
- total: 2
74
+ total: 2,
75
75
  }
76
76
  }
77
77
  }
@@ -32,9 +32,131 @@ export const config = defineBoundary({})
32
32
  export const logger = requestLogger({ level: 'debug' })
33
33
  ```
34
34
 
35
- ### Middleware vs Inline Execution
35
+ ### Middleware Context Type Inference (`defineMiddleware`)
36
36
 
37
- Middleware executes **before** your route handler. If you are using custom typed contexts to destructure variables (e.g., `async handle({ user })`), you must use middleware to populate `req.user`.
37
+ When custom middlewares attach data to `req` (such as authenticated user details, session data, or tenant tokens), ExisJS supports **End-to-End Middleware Context Type Inference**.
38
+
39
+ By using `defineMiddleware<TContext>()`, downstream route handlers automatically inherit strongly typed properties on `req` with zero manual type casts (`as any`) or boilerplate.
40
+
41
+ #### Local Route-Level Inference (Recommended)
42
+
43
+ ```typescript filename="src/http/route.ts"
44
+ import { controller, route, defineMiddleware } from 'exisjs/router'
45
+
46
+ // 1. Define your custom context shape
47
+ interface AuthContext {
48
+ user: {
49
+ id: string
50
+ role: 'admin' | 'user'
51
+ email: string
52
+ }
53
+ }
54
+
55
+ // 2. Define the middleware with your context
56
+ export const authMiddleware = defineMiddleware<AuthContext>((req, res, next) => {
57
+ req.user = {
58
+ id: 'usr_123',
59
+ role: 'admin',
60
+ email: 'alex@example.com'
61
+ }
62
+ next()
63
+ })
64
+
65
+ export default controller({
66
+ profile: route.get('/profile', {
67
+ middlewares: [authMiddleware],
68
+ handle({ req, res }) {
69
+ // ✨ req.user is automatically inferred directly from middlewares: [authMiddleware]!
70
+ // Full autocomplete and strict type safety:
71
+ return res.json({
72
+ userId: req.user.id,
73
+ role: req.user.role,
74
+ email: req.user.email
75
+ })
76
+ }
77
+ })
78
+ })
79
+ ```
80
+
81
+ #### Multi-Middleware Context Composition
82
+
83
+ When passing multiple middlewares, ExisJS automatically merges all injected contexts:
84
+
85
+ ```typescript
86
+ const authMw = defineMiddleware<{ user: { id: string } }>(...)
87
+ const tenantMw = defineMiddleware<{ tenantId: string }>(...)
88
+
89
+ export default controller({
90
+ data: route.get('/data', {
91
+ middlewares: [authMw, tenantMw],
92
+ handle({ req }) {
93
+ // req has both `req.user` and `req.tenantId` strictly typed!
94
+ return { userId: req.user.id, tenant: req.tenantId }
95
+ }
96
+ })
97
+ })
98
+ ```
99
+
100
+ #### Module-Scoped Context (`createRouter<TContext>()`)
101
+
102
+ If an entire file or subsystem shares the same context, use `createRouter<TContext>()`:
103
+
104
+ ```typescript filename="src/http/admin/route.ts"
105
+ import { createRouter } from 'exisjs/router'
106
+
107
+ interface AdminContext {
108
+ user: { id: string; role: 'admin' }
109
+ }
110
+
111
+ const { route, controller } = createRouter<AdminContext>()
112
+
113
+ export default controller({
114
+ settings: route.get('/settings', {
115
+ middlewares: [authMiddleware],
116
+ handle({ req }) {
117
+ return { adminId: req.user.id }
118
+ }
119
+ })
120
+ })
121
+ ```
122
+
123
+ #### Boundary-Scoped Context (`defineBoundary<TContext>()`)
124
+
125
+ Propagate context types across all routes in a directory tree:
126
+
127
+ ```typescript filename="src/http/admin/boundary.ts"
128
+ import { defineBoundary } from 'exisjs/router'
129
+
130
+ export const config = defineBoundary<AdminContext>({
131
+ beforeHandle: async ({ req }) => {
132
+ console.log(`Admin action by: ${req.user.id}`)
133
+ }
134
+ })
135
+ ```
136
+
137
+ #### Class-Based (OOP) Context Usage
138
+
139
+ In OOP controllers, you can attach typed middlewares via `@Use()` and type the injected `@Req()` object:
140
+
141
+ ```typescript filename="src/http/admin/route.ts"
142
+ import { Controller, Get, Use, Req } from 'exisjs/decorators'
143
+ import type { Request } from 'exisjs/router'
144
+ import { authMiddleware } from '../../middleware/auth'
145
+ import type { AuthContext } from '../../middleware/auth'
146
+
147
+ @Controller('/admin')
148
+ @Use(authMiddleware)
149
+ export default class AdminController {
150
+ @Get('/profile')
151
+ getProfile(@Req() req: Request & AuthContext) {
152
+ return {
153
+ userId: req.user.id,
154
+ role: req.user.role,
155
+ email: req.user.email,
156
+ }
157
+ }
158
+ }
159
+ ```
38
160
 
39
161
  #### Functional Paradigm
40
162
 
@@ -132,15 +254,42 @@ app.use(requestId())
132
254
 
133
255
  ExisJS provides high-performance rate limiting powered by Rust native memory:
134
256
 
135
- ```typescript filename="src/http/boundary.ts"
257
+ #### Functional Paradigm
258
+
259
+ ```typescript filename="src/http/auth/route.ts"
260
+ import { controller, route } from 'exisjs/router'
136
261
  import { rateLimit } from 'exisjs/middleware'
137
262
 
138
- export const limiter = rateLimit({
139
- windowMs: 60000,
140
- max: 100,
263
+ const authLimiter = rateLimit({ windowMs: 60000, max: 10 })
264
+
265
+ export default controller({
266
+ login: route.post('/login', {
267
+ middlewares: [authLimiter],
268
+ handle({ req }) {
269
+ return { status: 'ok' }
270
+ }
271
+ })
141
272
  })
142
273
  ```
143
274
 
275
+ #### Class-Based (OOP) Paradigm
276
+
277
+ ```typescript filename="src/http/auth/route.ts"
278
+ import { Controller, Post, Use } from 'exisjs/decorators'
279
+ import { rateLimit } from 'exisjs/middleware'
280
+
281
+ const authLimiter = rateLimit({ windowMs: 60000, max: 10 })
282
+
283
+ @Controller('/auth')
284
+ export default class AuthController {
285
+ @Post('/login')
286
+ @Use(authLimiter)
287
+ async login() {
288
+ return { status: 'ok' }
289
+ }
290
+ }
291
+ ```
292
+
144
293
  > [!NOTE]
145
294
  > Core rate limiting runs completely off-heap in-memory. For Redis-backed distributed rate limiting across clusters, use `@exisjs/cache`.
146
295
 
@@ -150,6 +299,24 @@ export const limiter = rateLimit({
150
299
 
151
300
  Idempotency caches responses based on the `Idempotency-Key` header, preventing duplicate mutations:
152
301
 
302
+ #### Functional Paradigm
303
+
304
+ ```typescript filename="src/http/checkout/route.ts"
305
+ import { controller, route } from 'exisjs/router'
306
+ import { idempotent } from 'exisjs/middleware'
307
+
308
+ export default controller({
309
+ checkout: route.post('/', {
310
+ middlewares: [idempotent({ ttlMs: 86400000 })],
311
+ async handle() {
312
+ return { success: true }
313
+ }
314
+ })
315
+ })
316
+ ```
317
+
318
+ #### Class-Based (OOP) Paradigm
319
+
153
320
  ```typescript filename="src/http/checkout/route.ts"
154
321
  import { Controller, Post, Idempotent } from 'exisjs/decorators'
155
322
 
@@ -169,6 +336,8 @@ export default class CheckoutController {
169
336
 
170
337
  If multiple clients request the exact same resource concurrently, `dedupe` collapses identical in-flight requests into a single operation:
171
338
 
339
+ #### Functional Paradigm
340
+
172
341
  ```typescript filename="src/http/stats/route.ts"
173
342
  import { controller, route } from 'exisjs/router'
174
343
  import { dedupe } from 'exisjs/middleware'
@@ -183,6 +352,22 @@ export default controller({
183
352
  })
184
353
  ```
185
354
 
355
+ #### Class-Based (OOP) Paradigm
356
+
357
+ ```typescript filename="src/http/stats/route.ts"
358
+ import { Controller, Get, Use } from 'exisjs/decorators'
359
+ import { dedupe } from 'exisjs/middleware'
360
+
361
+ @Controller('/stats')
362
+ export default class StatsController {
363
+ @Get('/')
364
+ @Use(dedupe({ ttlMs: 5000 }))
365
+ async getStats() {
366
+ return { activeUsers: 1000 }
367
+ }
368
+ }
369
+ ```
370
+
186
371
  ---
187
372
 
188
373
  ## 7. Static File Serving
@@ -351,3 +536,81 @@ export default controller({
351
536
  })
352
537
  ```
353
538
 
539
+ ---
540
+
541
+ ## 10. Security Scanner Noise Suppression & Blackhole (`blockSuspiciousProbes`)
542
+
543
+ In public production deployments, automated vulnerability scanners aggressively probe for `.env`, `/.git/HEAD`, `/.env.backup`, `/.DS_Store`, `wp-config.php`, etc.
544
+
545
+ Each probe generates a `404` error log with structured JSON logging (`{"level":40,"statusCode":404}`), flooding log aggregators and triggering false security alerts.
546
+
547
+ ExisJS provides `blockSuspiciousProbes` (alias `blockProbes`) to immediately intercept known exploit patterns, terminate the request with an empty response (blackhole), and suppress logger noise.
548
+
549
+ ### Global Usage (`server.ts` / `exis.config.ts`)
550
+
551
+ ```typescript filename="src/http/server.ts"
552
+ import { exis } from 'exisjs'
553
+ import { blockSuspiciousProbes } from 'exisjs/middleware'
554
+
555
+ export default exis({
556
+ async onStart(app) {
557
+ // Intercept probes before downstream routes or loggers
558
+ app.use(blockSuspiciousProbes({
559
+ statusCode: 404, // or 403
560
+ silent: true, // Suppresses level-40 warning logs in requestLogger
561
+ }))
562
+ }
563
+ })
564
+ ```
565
+
566
+ Or enable directly in `exis.config.ts`:
567
+
568
+ ```typescript filename="exis.config.ts"
569
+ import { defineConfig } from 'exisjs/config'
570
+
571
+ export default async () => defineConfig({
572
+ blockProbes: true, // or { statusCode: 404, silent: true }
573
+ })
574
+ ```
575
+
576
+ ### Folder-Scoped Boundary (`boundary.ts`)
577
+
578
+ ```typescript filename="src/http/boundary.ts"
579
+ import { defineBoundary } from 'exisjs/router'
580
+
581
+ export default defineBoundary({
582
+ blockProbes: {
583
+ statusCode: 404,
584
+ silent: true,
585
+ exclude: ['/.well-known/security.txt'],
586
+ }
587
+ })
588
+ ```
589
+
590
+ ### Class-Based (OOP) Decorator
591
+
592
+ ```typescript filename="src/http/api/route.ts"
593
+ import { Controller, Get, BlockProbes } from 'exisjs/decorators'
594
+
595
+ @Controller('/api')
596
+ @BlockProbes({ statusCode: 404 })
597
+ export default class ApiController {
598
+ @Get('/health')
599
+ health() {
600
+ return { status: 'healthy' }
601
+ }
602
+ }
603
+ ```
604
+
605
+ ### Configuration Options
606
+
607
+ | Option | Type | Default | Description |
608
+ | :--- | :--- | :--- | :--- |
609
+ | `statusCode` | `number` | `404` | HTTP status code to return for blocked probes |
610
+ | `blackhole` | `boolean` | `true` | Immediately terminates connection with empty body without calling downstream middleware |
611
+ | `silent` | `boolean` | `true` | Suppresses `requestLogger` warning logs for blocked probe requests |
612
+ | `patterns` | `(string \| RegExp)[]` | `[]` | Additional custom probe patterns to block |
613
+ | `exclude` | `(string \| RegExp)[]` | `[]` | Whitelist patterns to allow through without blocking |
614
+ | `onProbe` | `(req, res, pattern) => void` | `undefined` | Callback hook when a probe is detected (for SIEM or IP reputation metrics) |
615
+
616
+