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
@@ -79,14 +79,30 @@ export default class UsersController {
79
79
 
80
80
  ## 2. Standardized JSON Responses (`exisjs/response`)
81
81
 
82
- To keep your APIs consistent, ExisJS exports standard `success()` and `error()` builders.
83
- Using these ensures your entire application adheres to a unified `{ success: boolean, data?: any, error?: any }` structure, which frontend teams love.
82
+ To keep your APIs consistent and predictable across teams, ExisJS provides standard response helpers, envelope constructors, status code constants, and RFC 7807 problem details in `exisjs/response` (also re-exported from the root `exisjs` package).
84
83
 
85
- #### Functional Paradigm
84
+ Using these ensures your entire backend adheres to a clean, strongly-typed `{ success: boolean, data?: any, error?: any, meta?: any }` structure that frontend clients and SDK generators can reliably consume.
85
+
86
+ ### Available Response Builders
87
+
88
+ | Builder | HTTP Status | Envelope / Output | Purpose |
89
+ | :--- | :--- | :--- | :--- |
90
+ | `success(data, message?, meta?)` | 200 OK | `{ success: true, data, message?, meta? }` | Standard successful payload |
91
+ | `created(data, message?, meta?)` | 201 Created | `{ success: true, data, message: 'Created', meta? }` | Created resource |
92
+ | `accepted(data?, message?, meta?)` | 202 Accepted | `{ success: true, data, message: 'Accepted', meta? }` | Queued async/background job |
93
+ | `noContent()` | 204 No Content | `{ success: true, data: null }` | Empty successful action |
94
+ | `error(msg, code?, details?, status?)` | 4xx / 5xx | `{ success: false, error: { message, code?, details?, status? } }` | Structured error envelope |
95
+ | `problem(title, status, detail?, opts?)` | Custom | RFC 7807 / RFC 9457 Problem Details object | Standard API problem specification |
96
+ | `paginate(items, total, options)` | 200 OK | `{ success: true, data: items, pagination: { ... } }` | Standard pagination envelope |
97
+ | `getPaginationSkip(query)` | N/A | `{ skip, limit, page }` | Database offset/limit calculator |
98
+ | `json(data, status?, headers?)` | Custom | `{ data, status, headers }` | Typed JSON payload descriptor |
99
+ | `redirect(url, status?)` | 3xx | `{ redirect, status }` | Redirection descriptor |
100
+
101
+ #### Functional Paradigm Example
86
102
 
87
103
  ```typescript filename="src/http/route.ts"
88
104
  import { controller, route } from 'exisjs/router'
89
- import { success, error } from 'exisjs/response'
105
+ import { success, created, error, HttpStatus } from 'exisjs/response'
90
106
 
91
107
  export default controller({
92
108
  getUser: route.get('/:id', {
@@ -94,7 +110,7 @@ export default controller({
94
110
  const user = db.users.find(req.params.id)
95
111
 
96
112
  if (!user) {
97
- res.status(404)
113
+ res.status(HttpStatus.NOT_FOUND)
98
114
  // Generates: { success: false, error: { message: "User not found", code: "NOT_FOUND" } }
99
115
  return error("User not found", "NOT_FOUND")
100
116
  }
@@ -102,15 +118,24 @@ export default controller({
102
118
  // Generates: { success: true, data: { id: 1, name: "Alice" }, message: "Found user" }
103
119
  return success(user, "Found user")
104
120
  }
121
+ }),
122
+
123
+ createUser: route.post('/', {
124
+ handle(req, res) {
125
+ const user = db.users.create(req.body)
126
+ res.status(HttpStatus.CREATED)
127
+ // Generates: { success: true, data: user, message: "User registered" }
128
+ return created(user, "User registered")
129
+ }
105
130
  })
106
131
  })
107
132
  ```
108
133
 
109
- #### Class-Based (OOP) Paradigm
134
+ #### Class-Based (OOP) Paradigm Example
110
135
 
111
136
  ```typescript filename="src/http/route.ts"
112
- import { Controller, Get, Param, Res } from 'exisjs/decorators'
113
- import { success, error } from 'exisjs/response'
137
+ import { Controller, Get, Post, Param, Body, Res } from 'exisjs/decorators'
138
+ import { success, created, error, HttpStatus } from 'exisjs/response'
114
139
 
115
140
  @Controller()
116
141
  export default class UsersController {
@@ -119,23 +144,49 @@ export default class UsersController {
119
144
  const user = db.users.find(id)
120
145
 
121
146
  if (!user) {
122
- res.status(404)
123
- // Generates: { success: false, error: { message: "User not found", code: "NOT_FOUND" } }
147
+ res.status(HttpStatus.NOT_FOUND)
124
148
  return error("User not found", "NOT_FOUND")
125
149
  }
126
150
 
127
- // Generates: { success: true, data: { id: 1, name: "Alice" }, message: "Found user" }
128
151
  return success(user, "Found user")
129
152
  }
153
+
154
+ @Post('/')
155
+ async createUser(@Body() body: any, @Res() res: any) {
156
+ const user = db.users.create(body)
157
+ res.status(HttpStatus.CREATED)
158
+ return created(user, "User created successfully")
159
+ }
130
160
  }
131
161
  ```
132
162
 
163
+ ### RFC 7807 / RFC 9457 Problem Details (`problem`)
164
+
165
+ For enterprise and public REST APIs requiring standardized error models, `problem()` produces RFC-compliant Problem Details:
166
+
167
+ ```typescript
168
+ import { problem, HttpStatus } from 'exisjs/response'
169
+
170
+ res.status(HttpStatus.UNPROCESSABLE_ENTITY)
171
+ return problem('Invalid Request Parameters', 422, 'One or more fields failed schema validation', {
172
+ type: 'https://api.example.com/errors/validation',
173
+ instance: req.url,
174
+ invalidParams: [
175
+ { name: 'email', reason: 'Must be a valid email address' },
176
+ { name: 'age', reason: 'Must be greater than 18' }
177
+ ]
178
+ })
179
+ ```
180
+
181
+
133
182
  ---
134
183
 
135
184
  ## 3. Standardized Pagination Envelopes (`paginate`)
136
185
 
137
186
  To eliminate pagination inconsistency across different endpoints, ExisJS exports standard `paginate()` and `getPaginationSkip()` utilities directly from `exisjs/validator` (as well as `exisjs/response`).
138
187
 
188
+ #### Functional Paradigm
189
+
139
190
  ```typescript filename="src/http/products/route.ts"
140
191
  import { controller, route } from 'exisjs/router'
141
192
  import { tex, paginate, getPaginationSkip } from 'exisjs/validator'
@@ -160,6 +211,30 @@ export default controller({
160
211
  })
161
212
  ```
162
213
 
214
+ #### Class-Based (OOP) Paradigm
215
+
216
+ ```typescript filename="src/http/products/route.ts"
217
+ import { Controller, Get, Query } from 'exisjs/decorators'
218
+ import { tex, paginate, getPaginationSkip } from 'exisjs/validator'
219
+
220
+ const ProductQuerySchema = tex.pagination({ defaultLimit: 15, maxLimit: 50 })
221
+
222
+ @Controller('/products')
223
+ export default class ProductsController {
224
+ @Get('/', { query: ProductQuerySchema })
225
+ async listProducts(@Query() query: any) {
226
+ const { skip, limit, page } = getPaginationSkip(query)
227
+
228
+ const [items, total] = await Promise.all([
229
+ db.product.findMany({ skip, take: limit, orderBy: { createdAt: 'desc' } }),
230
+ db.product.count(),
231
+ ])
232
+
233
+ return paginate(items, total, { page, limit })
234
+ }
235
+ }
236
+ ```
237
+
163
238
  ### The Standardized Pagination Envelope
164
239
 
165
240
  Calling `paginate(items, total, { page, limit })` produces a uniform envelope:
@@ -298,22 +373,28 @@ export default class ReportController {
298
373
  }
299
374
  ```
300
375
 
301
- ### `res.sendStream()`
302
- Pipe any Node.js `Readable` stream directly to the client with built-in leak prevention:
303
- - **Client Disconnect Cleanup**: If the client aborts or closes the connection prematurely, the source stream is immediately destroyed (`stream.destroy()`), releasing underlying file handles or external sockets.
304
- - **Unhandled Error Catching**: Errors emitted during stream consumption are trapped gracefully, preventing unhandled stream crashes.
376
+ ### `res.sendStream()` & `res.stream()`
377
+ ExisJS provides first-class streaming support across `res.sendStream()` (aliased as `res.stream()`), accepting:
378
+ - **Node.js `Readable` streams** (e.g. `fs.createReadStream`, database cursors, zlib streams)
379
+ - **Web Standard `ReadableStream`** (e.g. from `fetch()`, OpenAI SDK, Anthropic SDK, and AI frameworks)
380
+ - **`AsyncIterable` & Async Generators** (e.g. `async function* generate() { yield ... }`)
305
381
 
306
- #### Functional Paradigm
382
+ #### Core Reliability Guarantees:
383
+ - **Socket Backpressure Control**: If the underlying network socket buffer fills up (`res.raw.write()` returns `false`), ExisJS automatically pauses stream consumption until the `'drain'` event fires, preventing fast producers from exhausting server RAM.
384
+ - **Client Disconnect Teardown**: If the client aborts or closes the connection prematurely (`req.raw.on('close')`), ExisJS cancels Web stream readers (`reader.cancel()`), destroys Node streams (`stream.destroy()`), or terminates async generators (`generator.return()`).
385
+ - **Resilient Error Trapping**: Stream transmission failures are trapped without crashing the Node.js process. If headers have not yet been sent, a structured 500 error envelope is returned.
307
386
 
308
- ```typescript
387
+ #### 1. Streaming Node.js Readable Streams
388
+
389
+ ##### Functional Paradigm
390
+ ```typescript filename="src/http/video/route.ts"
309
391
  import { controller, route } from 'exisjs/router'
310
392
  import fs from 'node:fs'
311
393
 
312
394
  export default controller({
313
- streamVideo: route.get('/video', {
395
+ streamVideo: route.get('/', {
314
396
  handle(req, res) {
315
397
  const videoStream = fs.createReadStream('/path/to/video.mp4')
316
-
317
398
  res.type('video/mp4')
318
399
  res.sendStream(videoStream)
319
400
  }
@@ -321,36 +402,175 @@ export default controller({
321
402
  })
322
403
  ```
323
404
 
324
- #### Class-Based (OOP) Paradigm
325
-
326
- ```typescript
405
+ ##### Class-Based (OOP) Paradigm
406
+ ```typescript filename="src/http/video/route.ts"
327
407
  import { Controller, Get, Res } from 'exisjs/decorators'
408
+ import type { Response } from 'exisjs/router'
328
409
  import fs from 'node:fs'
329
410
 
330
- @Controller()
411
+ @Controller('/video')
331
412
  export default class VideoController {
332
- @Get('/video')
333
- async streamVideo(@Res() res: any) {
413
+ @Get('/')
414
+ streamVideo(@Res() res: Response) {
334
415
  const videoStream = fs.createReadStream('/path/to/video.mp4')
335
-
336
416
  res.type('video/mp4')
337
417
  res.sendStream(videoStream)
338
418
  }
339
419
  }
340
420
  ```
341
421
 
422
+ #### 2. Streaming Web Standard `ReadableStream` (AI SDKs & Fetch)
423
+
424
+ ##### Functional Paradigm
425
+ ```typescript filename="src/http/ai/route.ts"
426
+ import { controller, route } from 'exisjs/router'
427
+
428
+ export default controller({
429
+ chat: route.post('/', {
430
+ async handle(req, res) {
431
+ const response = await fetch('https://api.openai.com/v1/chat/completions', {
432
+ method: 'POST',
433
+ headers: { 'Authorization': `Bearer ${process.env.OPENAI_API_KEY}` },
434
+ body: JSON.stringify({ model: 'gpt-4o', stream: true, messages: req.body.messages }),
435
+ })
436
+
437
+ res.type('text/event-stream')
438
+ res.stream(response.body!) // Web ReadableStream piped with backpressure
439
+ }
440
+ })
441
+ })
442
+ ```
443
+
444
+ ##### Class-Based (OOP) Paradigm
445
+ ```typescript filename="src/http/ai/route.ts"
446
+ import { Controller, Post, Body, Res } from 'exisjs/decorators'
447
+ import type { Response } from 'exisjs/router'
448
+
449
+ @Controller('/ai')
450
+ export default class AiController {
451
+ @Post('/chat')
452
+ async chat(@Body() body: any, @Res() res: Response) {
453
+ const response = await fetch('https://api.openai.com/v1/chat/completions', {
454
+ method: 'POST',
455
+ headers: { 'Authorization': `Bearer ${process.env.OPENAI_API_KEY}` },
456
+ body: JSON.stringify({ model: 'gpt-4o', stream: true, messages: body.messages }),
457
+ })
458
+
459
+ res.type('text/event-stream')
460
+ res.stream(response.body!)
461
+ }
462
+ }
463
+ ```
464
+
465
+ #### 3. Streaming Async Generators
466
+
467
+ ##### Functional Paradigm
468
+ ```typescript filename="src/http/feed/route.ts"
469
+ import { controller, route } from 'exisjs/router'
470
+
471
+ export default controller({
472
+ feed: route.get('/', {
473
+ handle(req, res) {
474
+ async function* generateData() {
475
+ for (let i = 0; i < 100; i++) {
476
+ yield `data: item ${i}\n\n`
477
+ await new Promise((r) => setTimeout(r, 50))
478
+ }
479
+ }
480
+
481
+ res.type('text/event-stream')
482
+ res.stream(generateData())
483
+ }
484
+ })
485
+ })
486
+ ```
487
+
488
+ ##### Class-Based (OOP) Paradigm
489
+ ```typescript filename="src/http/feed/route.ts"
490
+ import { Controller, Get, Res } from 'exisjs/decorators'
491
+ import type { Response } from 'exisjs/router'
492
+
493
+ @Controller('/feed')
494
+ export default class FeedController {
495
+ @Get('/')
496
+ streamFeed(@Res() res: Response) {
497
+ async function* generateData() {
498
+ for (let i = 0; i < 100; i++) {
499
+ yield `data: item ${i}\n\n`
500
+ await new Promise((r) => setTimeout(r, 50))
501
+ }
502
+ }
503
+
504
+ res.type('text/event-stream')
505
+ res.stream(generateData())
506
+ }
507
+ }
508
+ ```
509
+
510
+ #### 4. Automatic Stream Piping from Handler Return Values
511
+ Route handlers returning a stream or async generator directly are automatically detected by the ExisJS pipeline and piped via `res.sendStream()`:
512
+
513
+ ##### Functional Paradigm
514
+ ```typescript filename="src/http/export/route.ts"
515
+ import { controller, route } from 'exisjs/router'
516
+ import fs from 'node:fs'
517
+
518
+ export default controller({
519
+ exportCsv: route.get('/', {
520
+ handle() {
521
+ // Simply return the readable stream; ExisJS auto-pipes it to res!
522
+ return fs.createReadStream('./data/export.csv')
523
+ }
524
+ })
525
+ })
526
+ ```
527
+
528
+ ##### Class-Based (OOP) Paradigm
529
+ ```typescript filename="src/http/export/route.ts"
530
+ import { Controller, Get } from 'exisjs/decorators'
531
+ import fs from 'node:fs'
532
+
533
+ @Controller('/export')
534
+ export default class ExportController {
535
+ @Get('/')
536
+ exportCsv() {
537
+ // Simply return the readable stream; ExisJS auto-pipes it to res!
538
+ return fs.createReadStream('./data/export.csv')
539
+ }
540
+ }
541
+ ```
542
+
342
543
  ---
343
544
 
344
545
  ## 5. Redirects
345
546
 
346
547
  You can instantly redirect users to another URL using `res.redirect()`. It defaults to a `302 Found` temporary redirect, but you can override the status code.
347
548
 
348
- ```typescript
349
- // Temporary Redirect (302)
350
- res.redirect('/login')
549
+ #### Functional Paradigm
550
+ ```typescript filename="src/http/auth/route.ts"
551
+ import { controller, route } from 'exisjs/router'
552
+
553
+ export default controller({
554
+ login: route.get('/login', {
555
+ handle(req, res) {
556
+ res.redirect('https://auth.example.com/login', 302)
557
+ }
558
+ })
559
+ })
560
+ ```
561
+
562
+ #### Class-Based (OOP) Paradigm
563
+ ```typescript filename="src/http/auth/route.ts"
564
+ import { Controller, Get, Res } from 'exisjs/decorators'
565
+ import type { Response } from 'exisjs/router'
351
566
 
352
- // Permanent Redirect (301)
353
- res.redirect('https://google.com', 301)
567
+ @Controller('/auth')
568
+ export default class AuthController {
569
+ @Get('/login')
570
+ login(@Res() res: Response) {
571
+ res.redirect('https://auth.example.com/login', 302)
572
+ }
573
+ }
354
574
  ```
355
575
 
356
576
  ---
@@ -428,7 +648,9 @@ export default class EventsController {
428
648
 
429
649
  ### AI / LLM Streaming with `sse.pipeFrom()`
430
650
 
431
- You can directly stream chunks from any `AsyncIterable` (such as the OpenAI SDK or custom generators) using `sse.pipeFrom()`:
651
+ You can directly stream chunks from any `AsyncIterable` (such as the OpenAI SDK, Anthropic SDK, or custom async generators) using `sse.pipeFrom()`:
652
+
653
+ #### Functional Paradigm
432
654
 
433
655
  ```typescript filename="src/http/ai/route.ts"
434
656
  import { controller, route } from 'exisjs/router'
@@ -457,6 +679,35 @@ export default controller({
457
679
  })
458
680
  ```
459
681
 
682
+ #### Class-Based (OOP) Paradigm
683
+
684
+ ```typescript filename="src/http/ai/route.ts"
685
+ import { Controller, Post, Res } from 'exisjs/decorators'
686
+ import type { Response } from 'exisjs/router'
687
+
688
+ @Controller('/ai')
689
+ export default class AiController {
690
+ @Post('/chat')
691
+ chat(@Res() res: Response) {
692
+ res.sse(async (sse) => {
693
+ async function* generateTokens() {
694
+ yield 'Hello'
695
+ yield ' from'
696
+ yield ' ExisJS'
697
+ yield ' AI streaming!'
698
+ }
699
+
700
+ await sse.pipeFrom(generateTokens(), (token) => ({
701
+ event: 'delta',
702
+ data: { text: token }
703
+ }))
704
+
705
+ sse.close()
706
+ })
707
+ }
708
+ }
709
+ ```
710
+
460
711
  ### SSE Options & Methods
461
712
 
462
713
  - `res.sse(options?)`: Options include:
@@ -653,6 +653,51 @@ export default class OrdersController {
653
653
 
654
654
 
655
655
 
656
+ ## Type-Safe Client Route Manifest Contract (`AppRouter`)
657
+
658
+ ExisJS generates a compile-time route manifest in `.exis/types.d.ts` (and exposed via root `exis.d.ts`), converting your filesystem routes into an `AppRouter` type definition.
659
+
660
+ ### Zero-Cost Return Type & DTO Preservation
661
+ When you return data from `handle()`, ExisJS preserves the exact TypeScript return type (including Drizzle/Prisma models, Tex schemas, or custom records) on the route definition:
662
+
663
+ ```typescript filename="src/http/users/route.ts"
664
+ import { controller, route } from 'exisjs/router'
665
+ import { tex } from 'exisjs/validator'
666
+
667
+ export default controller({
668
+ getUser: route.get('/:id', {
669
+ params: tex.object({ id: tex.string() }),
670
+ async handle({ params }) {
671
+ // Inferred return type is strictly preserved as { id: string, name: string, active: boolean }
672
+ return { id: params.id, name: 'Alice', active: true }
673
+ }
674
+ })
675
+ })
676
+ ```
677
+
678
+ ### Consuming `AppRouter` with `@exisjs/fetch`
679
+ You can import `AppRouter` in your frontend or external client applications for zero-cost end-to-end type safety and autocomplete:
680
+
681
+ ```typescript filename="src/client/api.ts"
682
+ import { createClient } from '@exisjs/fetch'
683
+ import type { AppRouter } from '../server/.exis/types'
684
+
685
+ const api = createClient<AppRouter>({
686
+ baseUrl: 'http://localhost:4000',
687
+ })
688
+
689
+ // 1. Hierarchical dot-notation RPC call:
690
+ const user = await api.users('123').getUser.get()
691
+
692
+ // 2. Direct path index call:
693
+ const userByPath = await api['/users/:id'].getUser.get({ params: { id: '123' } })
694
+ ```
695
+
696
+ ### Dynamic Dev Watcher Sync
697
+ During `exis dev`, adding, renaming, moving, or deleting route files automatically updates `.exis/types.d.ts` and `.exis/types/index.d.ts` in real-time without requiring a dev server restart or manual `.exis` folder deletion.
698
+
699
+ ---
700
+
656
701
  ## Modular Architecture
657
702
 
658
703
  While putting all logic in `route.ts` is fine for small apps, ExisJS strongly encourages separating concerns alongside your route definition:
@@ -661,3 +706,4 @@ While putting all logic in `route.ts` is fine for small apps, ExisJS strongly en
661
706
  - **`service.ts`**: Pure functions that interact with your database.
662
707
  - **`controller.ts`**: Handles business orchestration.
663
708
  - **`route.ts`**: Wires it to the HTTP network.
709
+