exisjs 0.7.5 → 0.7.7

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 (321) hide show
  1. package/dist/cli/commands/build.d.ts.map +1 -1
  2. package/dist/cli/commands/build.js +12 -0
  3. package/dist/cli/commands/build.js.map +1 -1
  4. package/dist/cli/commands/clean.d.ts +5 -0
  5. package/dist/cli/commands/clean.d.ts.map +1 -0
  6. package/dist/cli/commands/clean.js +30 -0
  7. package/dist/cli/commands/clean.js.map +1 -0
  8. package/dist/cli/commands/dev.d.ts.map +1 -1
  9. package/dist/cli/commands/dev.js +171 -86
  10. package/dist/cli/commands/dev.js.map +1 -1
  11. package/dist/cli/commands/generate.d.ts +6 -1
  12. package/dist/cli/commands/generate.d.ts.map +1 -1
  13. package/dist/cli/commands/generate.js +57 -2
  14. package/dist/cli/commands/generate.js.map +1 -1
  15. package/dist/cli/commands/start.d.ts.map +1 -1
  16. package/dist/cli/commands/start.js +54 -16
  17. package/dist/cli/commands/start.js.map +1 -1
  18. package/dist/cli/index.js +26 -3
  19. package/dist/cli/index.js.map +1 -1
  20. package/dist/cli/manifest.d.ts +1 -0
  21. package/dist/cli/manifest.d.ts.map +1 -1
  22. package/dist/cli/manifest.js +264 -33
  23. package/dist/cli/manifest.js.map +1 -1
  24. package/dist/cli/resolve-aliases.d.ts +1 -1
  25. package/dist/cli/resolve-aliases.d.ts.map +1 -1
  26. package/dist/cli/resolve-aliases.js +30 -13
  27. package/dist/cli/resolve-aliases.js.map +1 -1
  28. package/dist/config/config.d.ts +3 -1
  29. package/dist/config/config.d.ts.map +1 -1
  30. package/dist/config/config.js +9 -0
  31. package/dist/config/config.js.map +1 -1
  32. package/dist/config/types.d.ts +6 -0
  33. package/dist/config/types.d.ts.map +1 -1
  34. package/dist/cron/expressions.d.ts +45 -0
  35. package/dist/cron/expressions.d.ts.map +1 -0
  36. package/dist/cron/expressions.js +87 -0
  37. package/dist/cron/expressions.js.map +1 -0
  38. package/dist/cron/index.d.ts +6 -0
  39. package/dist/cron/index.d.ts.map +1 -0
  40. package/dist/cron/index.js +22 -0
  41. package/dist/cron/index.js.map +1 -0
  42. package/dist/cron/job.d.ts +50 -0
  43. package/dist/cron/job.d.ts.map +1 -0
  44. package/dist/cron/job.js +247 -0
  45. package/dist/cron/job.js.map +1 -0
  46. package/dist/cron/manager.d.ts +105 -0
  47. package/dist/cron/manager.d.ts.map +1 -0
  48. package/dist/cron/manager.js +245 -0
  49. package/dist/cron/manager.js.map +1 -0
  50. package/dist/cron/parser.d.ts +22 -0
  51. package/dist/cron/parser.d.ts.map +1 -0
  52. package/dist/cron/parser.js +269 -0
  53. package/dist/cron/parser.js.map +1 -0
  54. package/dist/cron/types.d.ts +76 -0
  55. package/dist/cron/types.d.ts.map +1 -0
  56. package/dist/cron/types.js +3 -0
  57. package/dist/cron/types.js.map +1 -0
  58. package/dist/decorators/constants.d.ts +2 -0
  59. package/dist/decorators/constants.d.ts.map +1 -1
  60. package/dist/decorators/constants.js +3 -1
  61. package/dist/decorators/constants.js.map +1 -1
  62. package/dist/decorators/core.d.ts +29 -11
  63. package/dist/decorators/core.d.ts.map +1 -1
  64. package/dist/decorators/core.js +49 -11
  65. package/dist/decorators/core.js.map +1 -1
  66. package/dist/decorators/cron.d.ts +57 -0
  67. package/dist/decorators/cron.d.ts.map +1 -0
  68. package/dist/decorators/cron.js +143 -0
  69. package/dist/decorators/cron.js.map +1 -0
  70. package/dist/decorators/index.d.ts +2 -0
  71. package/dist/decorators/index.d.ts.map +1 -1
  72. package/dist/decorators/index.js +2 -0
  73. package/dist/decorators/index.js.map +1 -1
  74. package/dist/decorators/middleware.d.ts +65 -2
  75. package/dist/decorators/middleware.d.ts.map +1 -1
  76. package/dist/decorators/middleware.js +148 -62
  77. package/dist/decorators/middleware.js.map +1 -1
  78. package/dist/decorators/params.d.ts +102 -2
  79. package/dist/decorators/params.d.ts.map +1 -1
  80. package/dist/decorators/params.js +105 -3
  81. package/dist/decorators/params.js.map +1 -1
  82. package/dist/decorators/response.d.ts.map +1 -1
  83. package/dist/decorators/response.js +32 -22
  84. package/dist/decorators/response.js.map +1 -1
  85. package/dist/decorators/routing.d.ts +81 -16
  86. package/dist/decorators/routing.d.ts.map +1 -1
  87. package/dist/decorators/routing.js +81 -16
  88. package/dist/decorators/routing.js.map +1 -1
  89. package/dist/di/container.d.ts.map +1 -1
  90. package/dist/di/container.js +6 -1
  91. package/dist/di/container.js.map +1 -1
  92. package/dist/docs/content/cli/commands.mdx +1 -1
  93. package/dist/docs/content/controllers.mdx +127 -10
  94. package/dist/docs/content/cron.mdx +196 -0
  95. package/dist/docs/content/decorators.mdx +22 -4
  96. package/dist/docs/content/errors.mdx +58 -9
  97. package/dist/docs/content/introduction.mdx +7 -7
  98. package/dist/docs/content/middleware.mdx +269 -6
  99. package/dist/docs/content/requests.mdx +180 -6
  100. package/dist/docs/content/responses.mdx +283 -32
  101. package/dist/docs/content/routing.mdx +46 -0
  102. package/dist/docs/content/swagger.mdx +228 -0
  103. package/dist/docs/content/validation.mdx +190 -11
  104. package/dist/docs/reference/context.mdx +11 -0
  105. package/dist/docs/reference/cron.mdx +300 -0
  106. package/dist/docs/reference/decorators.mdx +22 -4
  107. package/dist/docs/reference/error.mdx +88 -0
  108. package/dist/docs/reference/logger.mdx +175 -43
  109. package/dist/docs/reference/middleware.mdx +31 -0
  110. package/dist/docs/reference/router.mdx +22 -0
  111. package/dist/docs/reference/swagger.mdx +143 -0
  112. package/dist/docs/reference/validator.mdx +83 -23
  113. package/dist/error/errors.d.ts.map +1 -1
  114. package/dist/error/errors.js +95 -43
  115. package/dist/error/errors.js.map +1 -1
  116. package/dist/error/index.d.ts +1 -0
  117. package/dist/error/index.d.ts.map +1 -1
  118. package/dist/error/index.js +11 -1
  119. package/dist/error/index.js.map +1 -1
  120. package/dist/error/overlay.d.ts +42 -0
  121. package/dist/error/overlay.d.ts.map +1 -0
  122. package/dist/error/overlay.js +306 -0
  123. package/dist/error/overlay.js.map +1 -0
  124. package/dist/index.d.ts +1 -0
  125. package/dist/index.d.ts.map +1 -1
  126. package/dist/index.js +22 -1
  127. package/dist/index.js.map +1 -1
  128. package/dist/lib/start-server.js +33 -7
  129. package/dist/lib/start-server.js.map +1 -1
  130. package/dist/logger/index.d.ts +1 -1
  131. package/dist/logger/index.d.ts.map +1 -1
  132. package/dist/logger/index.js +9 -1
  133. package/dist/logger/index.js.map +1 -1
  134. package/dist/middleware/guard.d.ts +2 -2
  135. package/dist/middleware/guard.d.ts.map +1 -1
  136. package/dist/middleware/guard.js.map +1 -1
  137. package/dist/middleware/ip-filter.d.ts +1 -0
  138. package/dist/middleware/ip-filter.d.ts.map +1 -1
  139. package/dist/middleware/ip-filter.js +2 -0
  140. package/dist/middleware/ip-filter.js.map +1 -1
  141. package/dist/middleware/middleware.d.ts +2 -2
  142. package/dist/middleware/middleware.d.ts.map +1 -1
  143. package/dist/middleware/middleware.js +20 -2
  144. package/dist/middleware/middleware.js.map +1 -1
  145. package/dist/middleware/security.d.ts +43 -1
  146. package/dist/middleware/security.d.ts.map +1 -1
  147. package/dist/middleware/security.js +101 -0
  148. package/dist/middleware/security.js.map +1 -1
  149. package/dist/response/index.d.ts +229 -6
  150. package/dist/response/index.d.ts.map +1 -1
  151. package/dist/response/index.js +272 -2
  152. package/dist/response/index.js.map +1 -1
  153. package/dist/router/boundary.d.ts +41 -11
  154. package/dist/router/boundary.d.ts.map +1 -1
  155. package/dist/router/boundary.js +24 -2
  156. package/dist/router/boundary.js.map +1 -1
  157. package/dist/router/controller-registrar.d.ts.map +1 -1
  158. package/dist/router/controller-registrar.js +108 -4
  159. package/dist/router/controller-registrar.js.map +1 -1
  160. package/dist/router/index.d.ts +2 -2
  161. package/dist/router/index.d.ts.map +1 -1
  162. package/dist/router/index.js +2 -1
  163. package/dist/router/index.js.map +1 -1
  164. package/dist/router/route-builder.d.ts +262 -65
  165. package/dist/router/route-builder.d.ts.map +1 -1
  166. package/dist/router/route-builder.js +181 -23
  167. package/dist/router/route-builder.js.map +1 -1
  168. package/dist/router/route-scanner.d.ts +6 -0
  169. package/dist/router/route-scanner.d.ts.map +1 -1
  170. package/dist/router/route-scanner.js +71 -792
  171. package/dist/router/route-scanner.js.map +1 -1
  172. package/dist/router/router.d.ts.map +1 -1
  173. package/dist/router/router.js +62 -17
  174. package/dist/router/router.js.map +1 -1
  175. package/dist/router/scanner/boundary-loader.d.ts +18 -0
  176. package/dist/router/scanner/boundary-loader.d.ts.map +1 -0
  177. package/dist/router/scanner/boundary-loader.js +539 -0
  178. package/dist/router/scanner/boundary-loader.js.map +1 -0
  179. package/dist/router/scanner/cron-mounter.d.ts +15 -0
  180. package/dist/router/scanner/cron-mounter.d.ts.map +1 -0
  181. package/dist/router/scanner/cron-mounter.js +216 -0
  182. package/dist/router/scanner/cron-mounter.js.map +1 -0
  183. package/dist/router/scanner/error-mounter.d.ts +14 -0
  184. package/dist/router/scanner/error-mounter.d.ts.map +1 -0
  185. package/dist/router/scanner/error-mounter.js +200 -0
  186. package/dist/router/scanner/error-mounter.js.map +1 -0
  187. package/dist/router/scanner/fs-scanner.d.ts +25 -0
  188. package/dist/router/scanner/fs-scanner.d.ts.map +1 -0
  189. package/dist/router/scanner/fs-scanner.js +115 -0
  190. package/dist/router/scanner/fs-scanner.js.map +1 -0
  191. package/dist/router/scanner/functional-compiler.d.ts +9 -0
  192. package/dist/router/scanner/functional-compiler.d.ts.map +1 -0
  193. package/dist/router/scanner/functional-compiler.js +233 -0
  194. package/dist/router/scanner/functional-compiler.js.map +1 -0
  195. package/dist/router/scanner/index.d.ts +9 -0
  196. package/dist/router/scanner/index.d.ts.map +1 -0
  197. package/dist/router/scanner/index.js +19 -0
  198. package/dist/router/scanner/index.js.map +1 -0
  199. package/dist/router/types.d.ts +23 -0
  200. package/dist/router/types.d.ts.map +1 -1
  201. package/dist/server/app.d.ts +3 -0
  202. package/dist/server/app.d.ts.map +1 -1
  203. package/dist/server/app.js +13 -0
  204. package/dist/server/app.js.map +1 -1
  205. package/dist/server/bootstrapper.d.ts.map +1 -1
  206. package/dist/server/bootstrapper.js +91 -30
  207. package/dist/server/bootstrapper.js.map +1 -1
  208. package/dist/server/cluster.d.ts.map +1 -1
  209. package/dist/server/cluster.js +13 -2
  210. package/dist/server/cluster.js.map +1 -1
  211. package/dist/server/context.d.ts +8 -0
  212. package/dist/server/context.d.ts.map +1 -1
  213. package/dist/server/context.js +50 -6
  214. package/dist/server/context.js.map +1 -1
  215. package/dist/server/helpers/body-parser.d.ts +18 -0
  216. package/dist/server/helpers/body-parser.d.ts.map +1 -0
  217. package/dist/server/helpers/body-parser.js +246 -0
  218. package/dist/server/helpers/body-parser.js.map +1 -0
  219. package/dist/server/helpers/cookie.d.ts +4 -0
  220. package/dist/server/helpers/cookie.d.ts.map +1 -0
  221. package/dist/server/helpers/cookie.js +61 -0
  222. package/dist/server/helpers/cookie.js.map +1 -0
  223. package/dist/server/helpers/etag.d.ts +3 -0
  224. package/dist/server/helpers/etag.d.ts.map +1 -0
  225. package/dist/server/helpers/etag.js +25 -0
  226. package/dist/server/helpers/etag.js.map +1 -0
  227. package/dist/server/helpers/index.d.ts +6 -0
  228. package/dist/server/helpers/index.d.ts.map +1 -0
  229. package/dist/server/helpers/index.js +21 -0
  230. package/dist/server/helpers/index.js.map +1 -0
  231. package/dist/server/helpers/ip.d.ts +9 -0
  232. package/dist/server/helpers/ip.d.ts.map +1 -0
  233. package/dist/server/helpers/ip.js +62 -0
  234. package/dist/server/helpers/ip.js.map +1 -0
  235. package/dist/server/helpers/stream.d.ts +4 -0
  236. package/dist/server/helpers/stream.d.ts.map +1 -0
  237. package/dist/server/helpers/stream.js +193 -0
  238. package/dist/server/helpers/stream.js.map +1 -0
  239. package/dist/server/lifecycle.d.ts +22 -0
  240. package/dist/server/lifecycle.d.ts.map +1 -0
  241. package/dist/server/lifecycle.js +106 -0
  242. package/dist/server/lifecycle.js.map +1 -0
  243. package/dist/server/request-handler.d.ts +1 -0
  244. package/dist/server/request-handler.d.ts.map +1 -1
  245. package/dist/server/request-handler.js +27 -17
  246. package/dist/server/request-handler.js.map +1 -1
  247. package/dist/server/request.d.ts +1 -139
  248. package/dist/server/request.d.ts.map +1 -1
  249. package/dist/server/request.js +37 -433
  250. package/dist/server/request.js.map +1 -1
  251. package/dist/server/response.d.ts +112 -156
  252. package/dist/server/response.d.ts.map +1 -1
  253. package/dist/server/response.js +129 -279
  254. package/dist/server/response.js.map +1 -1
  255. package/dist/swagger/decorators.d.ts +81 -0
  256. package/dist/swagger/decorators.d.ts.map +1 -0
  257. package/dist/swagger/decorators.js +172 -0
  258. package/dist/swagger/decorators.js.map +1 -0
  259. package/dist/swagger/generator.d.ts +19 -0
  260. package/dist/swagger/generator.d.ts.map +1 -0
  261. package/dist/swagger/generator.js +344 -0
  262. package/dist/swagger/generator.js.map +1 -0
  263. package/dist/swagger/index.d.ts +31 -0
  264. package/dist/swagger/index.d.ts.map +1 -0
  265. package/dist/swagger/index.js +53 -0
  266. package/dist/swagger/index.js.map +1 -0
  267. package/dist/swagger/mounter.d.ts +7 -0
  268. package/dist/swagger/mounter.d.ts.map +1 -0
  269. package/dist/swagger/mounter.js +56 -0
  270. package/dist/swagger/mounter.js.map +1 -0
  271. package/dist/swagger/types.d.ts +190 -0
  272. package/dist/swagger/types.d.ts.map +1 -0
  273. package/dist/swagger/types.js +3 -0
  274. package/dist/swagger/types.js.map +1 -0
  275. package/dist/swagger/ui.d.ts +6 -0
  276. package/dist/swagger/ui.d.ts.map +1 -0
  277. package/dist/swagger/ui.js +118 -0
  278. package/dist/swagger/ui.js.map +1 -0
  279. package/dist/testing/index.d.ts.map +1 -1
  280. package/dist/testing/index.js +6 -1
  281. package/dist/testing/index.js.map +1 -1
  282. package/dist/testing/mocks.d.ts.map +1 -1
  283. package/dist/testing/mocks.js +15 -3
  284. package/dist/testing/mocks.js.map +1 -1
  285. package/dist/utils/logger.d.ts +8 -0
  286. package/dist/utils/logger.d.ts.map +1 -1
  287. package/dist/utils/logger.js +184 -1
  288. package/dist/utils/logger.js.map +1 -1
  289. package/dist/validator/builder.d.ts +223 -0
  290. package/dist/validator/builder.d.ts.map +1 -0
  291. package/dist/validator/builder.js +517 -0
  292. package/dist/validator/builder.js.map +1 -0
  293. package/dist/validator/engine.d.ts +19 -0
  294. package/dist/validator/engine.d.ts.map +1 -0
  295. package/dist/validator/engine.js +568 -0
  296. package/dist/validator/engine.js.map +1 -0
  297. package/dist/validator/error.d.ts +12 -0
  298. package/dist/validator/error.d.ts.map +1 -0
  299. package/dist/validator/error.js +63 -0
  300. package/dist/validator/error.js.map +1 -0
  301. package/dist/validator/pagination.d.ts +40 -0
  302. package/dist/validator/pagination.d.ts.map +1 -0
  303. package/dist/validator/pagination.js +60 -0
  304. package/dist/validator/pagination.js.map +1 -0
  305. package/dist/validator/tex-types.d.ts +2 -1
  306. package/dist/validator/tex-types.d.ts.map +1 -1
  307. package/dist/validator/tex-types.js +2 -1
  308. package/dist/validator/tex-types.js.map +1 -1
  309. package/dist/validator/tex.d.ts +6 -142
  310. package/dist/validator/tex.d.ts.map +1 -1
  311. package/dist/validator/tex.js +14 -733
  312. package/dist/validator/tex.js.map +1 -1
  313. package/dist/validator/types.d.ts +83 -0
  314. package/dist/validator/types.d.ts.map +1 -0
  315. package/dist/validator/types.js +3 -0
  316. package/dist/validator/types.js.map +1 -0
  317. package/package.json +8 -4
  318. package/dist/dev/error-overlay.d.ts +0 -3
  319. package/dist/dev/error-overlay.d.ts.map +0 -1
  320. package/dist/dev/error-overlay.js +0 -118
  321. package/dist/dev/error-overlay.js.map +0 -1
@@ -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
+
@@ -97,7 +97,9 @@ Handling `multipart/form-data` in Node.js can be difficult. ExisJS provides mult
97
97
 
98
98
  ### A. Declarative Route Uploads (Recommended)
99
99
 
100
- You can declare file upload requirements directly inside your route schema. ExisJS automatically executes the multipart parser, validates MIME types, enforces byte caps, and populates `req.file` or `req.files` along with non-file fields in `req.body`:
100
+ You can declare file upload requirements directly inside your route schema or decorators. ExisJS automatically executes the multipart parser, validates MIME types, enforces byte caps, and populates `req.file` or `req.files` along with non-file fields in `req.body`:
101
+
102
+ #### Functional Paradigm
101
103
 
102
104
  ```typescript filename="src/http/upload/route.ts"
103
105
  import { controller, route } from 'exisjs/router'
@@ -128,10 +130,47 @@ export default controller({
128
130
  })
129
131
  ```
130
132
 
133
+ #### Class-Based (OOP) Paradigm
134
+
135
+ ```typescript filename="src/http/upload/route.ts"
136
+ import { Controller, Post, UseUpload, UploadedFile, Body } from 'exisjs/decorators'
137
+ import { tex } from 'exisjs/validator'
138
+ import type { UploadedFile as IUploadedFile } from 'exisjs/router'
139
+
140
+ const TitleSchema = tex.object({
141
+ title: tex.string({ min: 1 }),
142
+ })
143
+
144
+ @Controller('/upload')
145
+ export default class UploadController {
146
+ @Post('/')
147
+ @UseUpload({
148
+ field: 'photo',
149
+ maxBytes: 10 * 1024 * 1024,
150
+ mimeTypes: ['image/jpeg', 'image/png', 'image/webp'],
151
+ })
152
+ async uploadPhoto(
153
+ @UploadedFile() file: IUploadedFile,
154
+ @Body(TitleSchema) body: { title: string }
155
+ ) {
156
+ console.log(`Uploaded ${file.originalname} (${file.size} bytes)`)
157
+
158
+ return {
159
+ success: true,
160
+ filename: file.filename,
161
+ mimetype: file.mimetype,
162
+ title: body.title,
163
+ }
164
+ }
165
+ }
166
+ ```
167
+
131
168
  ### B. First-Party `fileUpload` Middleware
132
169
 
133
170
  ExisJS exports a dedicated `fileUpload` middleware from `exisjs/middleware` supporting single, multiple, and multi-field uploads:
134
171
 
172
+ #### Functional Paradigm
173
+
135
174
  ```typescript filename="src/http/documents/route.ts"
136
175
  import { controller, route } from 'exisjs/router'
137
176
  import { fileUpload } from 'exisjs/middleware'
@@ -173,10 +212,44 @@ export default controller({
173
212
  })
174
213
  ```
175
214
 
215
+ #### Class-Based (OOP) Paradigm
216
+
217
+ ```typescript filename="src/http/documents/route.ts"
218
+ import { Controller, Post, Use, UploadedFile, UploadedFiles } from 'exisjs/decorators'
219
+ import { fileUpload } from 'exisjs/middleware'
220
+ import type { UploadedFile as IUploadedFile } from 'exisjs/router'
221
+
222
+ @Controller('/documents')
223
+ export default class DocumentsController {
224
+ // Single file upload
225
+ @Post('/single')
226
+ @Use(fileUpload.single('document', { maxBytes: 15 * 1024 * 1024, dest: './uploads/docs' }))
227
+ async uploadSingle(@UploadedFile() file: IUploadedFile) {
228
+ return { file }
229
+ }
230
+
231
+ // Multiple files with the same field name
232
+ @Post('/gallery')
233
+ @Use(fileUpload.array('photos', 5, { mimeTypes: ['image/*'] }))
234
+ async uploadGallery(@UploadedFiles() files: IUploadedFile[]) {
235
+ return { count: files.length, files }
236
+ }
237
+
238
+ // Multiple distinct fields
239
+ @Post('/profile')
240
+ @Use(fileUpload.fields([{ name: 'avatar', maxCount: 1 }, { name: 'cover', maxCount: 1 }]))
241
+ async uploadProfile(@UploadedFiles() files: Record<string, IUploadedFile[]>) {
242
+ return { files }
243
+ }
244
+ }
245
+ ```
246
+
176
247
  ### C. Integrating Express/Connect Middleware (e.g. Multer)
177
248
 
178
249
  If your enterprise project relies on existing Express middleware like **Multer** (for custom storage engines, S3 direct-to-cloud streams, or specialized filters), ExisJS provides the `fromExpress()` bridge:
179
250
 
251
+ #### Functional Paradigm
252
+
180
253
  ```typescript filename="src/http/legacy/route.ts"
181
254
  import { controller, route } from 'exisjs/router'
182
255
  import { fromExpress } from 'exisjs/middleware'
@@ -199,10 +272,36 @@ export default controller({
199
272
  })
200
273
  ```
201
274
 
275
+ #### Class-Based (OOP) Paradigm
276
+
277
+ ```typescript filename="src/http/legacy/route.ts"
278
+ import { Controller, Post, Use, Req } from 'exisjs/decorators'
279
+ import { fromExpress } from 'exisjs/middleware'
280
+ import type { Request } from 'exisjs/router'
281
+ import multer from 'multer'
282
+
283
+ const upload = multer({
284
+ storage: multer.memoryStorage(),
285
+ limits: { fileSize: 10 * 1024 * 1024 },
286
+ })
287
+
288
+ @Controller('/legacy')
289
+ export default class LegacyUploadController {
290
+ @Post('/upload')
291
+ @Use(fromExpress(upload.single('file')))
292
+ async upload(@Req() req: Request) {
293
+ const file = (req.raw as any).file || req.file
294
+ return { success: true, originalname: file?.originalname }
295
+ }
296
+ }
297
+ ```
298
+
202
299
  ### D. Manual `req.formData()` & Streaming Uploads
203
300
 
204
301
  For manual control over incoming multipart streams without route middleware:
205
302
 
303
+ #### Functional Paradigm
304
+
206
305
  ```typescript filename="src/http/upload/route.ts"
207
306
  import { controller, route } from 'exisjs/router'
208
307
 
@@ -225,6 +324,29 @@ export default controller({
225
324
  })
226
325
  ```
227
326
 
327
+ #### Class-Based (OOP) Paradigm
328
+
329
+ ```typescript filename="src/http/upload/route.ts"
330
+ import { Controller, Post, Req } from 'exisjs/decorators'
331
+ import type { Request } from 'exisjs/router'
332
+
333
+ @Controller('/upload')
334
+ export default class ManualUploadController {
335
+ @Post('/avatar')
336
+ async uploadAvatar(@Req() req: Request) {
337
+ const { fields, files } = await req.formData()
338
+ const uploadedFile = files['profile_pic']
339
+
340
+ if (uploadedFile) {
341
+ const savedPath = await uploadedFile.saveToDisk('./uploads/avatars')
342
+ return { success: true, path: savedPath }
343
+ }
344
+
345
+ return { success: false, error: 'No file uploaded' }
346
+ }
347
+ }
348
+ ```
349
+
228
350
  ---
229
351
 
230
352
  ## 4. Body Size Limits & HTTP 413
@@ -260,22 +382,24 @@ ExisJS ships with a **60-second default request timeout** in its `timeout()` mid
260
382
 
261
383
  ### Dynamic In-Handler Control
262
384
 
263
- Handlers can dynamically extend or cancel the active timeout on the fly using `req.setTimeout()` and `req.clearTimeout()`:
385
+ Handlers can dynamically extend or cancel the active timeout on the fly using `req.setTimeout()` and `req.clearTimeout()`, or declaratively via `@Timeout(ms)`:
386
+
387
+ #### Functional Paradigm
264
388
 
265
389
  ```typescript filename="src/http/ai/route.ts"
266
390
  import { controller, route } from 'exisjs/router'
267
391
 
268
392
  export default controller({
269
393
  generateVideo: route.post('/', {
394
+ timeoutMs: 180000, // 1. Declarative 3-minute timeout for this endpoint
270
395
  async handle({ req }) {
271
- // 1. Extend timeout for heavy AI pass generation (e.g. 3 minutes)
272
- req.setTimeout(180000)
396
+ // 2. Or dynamically adjust timeout during processing
397
+ req.setTimeout(240000)
273
398
 
274
399
  try {
275
400
  const result = await longRunningAiTask()
276
401
  return { success: true, result }
277
402
  } finally {
278
- // 2. Clear timeout once work completes
279
403
  req.clearTimeout()
280
404
  }
281
405
  }
@@ -283,6 +407,30 @@ export default controller({
283
407
  })
284
408
  ```
285
409
 
410
+ #### Class-Based (OOP) Paradigm
411
+
412
+ ```typescript filename="src/http/ai/route.ts"
413
+ import { Controller, Post, Timeout, Req } from 'exisjs/decorators'
414
+ import type { Request } from 'exisjs/router'
415
+
416
+ @Controller('/ai')
417
+ export default class AiTaskController {
418
+ @Post('/generate-video')
419
+ @Timeout(180000) // 1. Declarative 3-minute timeout
420
+ async generateVideo(@Req() req: Request) {
421
+ // 2. Or dynamically adjust on the fly
422
+ req.setTimeout(240000)
423
+
424
+ try {
425
+ const result = await longRunningAiTask()
426
+ return { success: true, result }
427
+ } finally {
428
+ req.clearTimeout()
429
+ }
430
+ }
431
+ }
432
+ ```
433
+
286
434
  ---
287
435
 
288
436
  ## 6. HTTP Headers & Content Negotiation
@@ -321,7 +469,7 @@ ExisJS provides a first-class standard Web API `req.signal: AbortSignal` tied di
321
469
 
322
470
  When a client closes their browser tab, cancels a download, or aborts an HTTP request, `req.signal` immediately triggers an `abort` event. You can pass `req.signal` directly to ORMs, database drivers, and HTTP clients to cancel expensive in-flight operations and eliminate wasted server resources.
323
471
 
324
- #### Example: Passing to Database Queries & Upstream Fetches
472
+ #### Functional Paradigm
325
473
 
326
474
  ```typescript filename="src/http/reports/route.ts"
327
475
  import { controller, route } from 'exisjs/router'
@@ -346,6 +494,32 @@ export default controller({
346
494
  })
347
495
  ```
348
496
 
497
+ #### Class-Based (OOP) Paradigm
498
+
499
+ ```typescript filename="src/http/reports/route.ts"
500
+ import { Controller, Get, Req } from 'exisjs/decorators'
501
+ import type { Request } from 'exisjs/router'
502
+
503
+ @Controller('/reports')
504
+ export default class ReportsController {
505
+ @Get('/')
506
+ async heavyReport(@Req() req: Request) {
507
+ // 1. Pass req.signal to fetch()
508
+ const upstream = await fetch('https://api.external.com/data', {
509
+ signal: req.signal,
510
+ })
511
+ const payload = await upstream.json()
512
+
513
+ // 2. Pass req.signal to your database queries
514
+ const results = await db.query(sql`SELECT * FROM heavy_analytics`, {
515
+ signal: req.signal,
516
+ })
517
+
518
+ return { payload, results }
519
+ }
520
+ }
521
+ ```
522
+
349
523
  ---
350
524
 
351
525
  ## 9. Context & Metadata