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
@@ -0,0 +1,228 @@
1
+ ---
2
+ nextUrl: "/docs/reference/swagger"
3
+ nextTitle: "Swagger API Reference"
4
+ ---
5
+
6
+ # Swagger & OpenAPI 3.1 Documentation
7
+
8
+ ExisJS comes with a native, zero-bloat **Swagger & OpenAPI 3.1** documentation engine. It automatically discovers all registered routes, introspects **Tex** and **Zod** validation schemas, and serves interactive API documentation at `/docs`.
9
+
10
+ ---
11
+
12
+ ## Quick Start
13
+
14
+ Enable Swagger documentation in your `exis.config.ts`:
15
+
16
+ ```typescript filename="exis.config.ts"
17
+ import { defineConfig } from 'exisjs/config'
18
+
19
+ export default defineConfig({
20
+ docs: {
21
+ enabled: true, // Mounts UI at /docs and spec at /docs/json
22
+ title: 'Store API Documentation',
23
+ version: '1.0.0',
24
+ description: 'Auto-generated API documentation with native schema validation',
25
+ ui: 'swagger-ui', // 'swagger-ui' | 'scalar'
26
+ }
27
+ })
28
+ ```
29
+
30
+ Once enabled:
31
+ * Visit `http://localhost:3000/docs` to view the interactive documentation UI.
32
+ * Visit `http://localhost:3000/docs/json` (or `/docs/openapi.json`) for the raw OpenAPI 3.1 specification.
33
+
34
+ ---
35
+
36
+ ## Interactive Documentation UIs
37
+
38
+ ExisJS supports two built-in UI renderers:
39
+
40
+ ### 1. Swagger UI (`ui: 'swagger-ui'`, default)
41
+ The industry-standard interactive Swagger explorer featuring **Try it out**, live request execution, and parameter inspection.
42
+
43
+ ### 2. Scalar API Reference (`ui: 'scalar'`)
44
+ A modern, high-performance, dark-mode-first API reference documentation viewer with integrated code snippets and fast search.
45
+
46
+ ```typescript filename="exis.config.ts"
47
+ export default defineConfig({
48
+ docs: {
49
+ enabled: true,
50
+ ui: 'scalar', // Switches UI to modern Scalar
51
+ }
52
+ })
53
+ ```
54
+
55
+ ---
56
+
57
+ ## Documenting Functional Routes
58
+
59
+ When writing functional route controllers (`export default controller({ ... })`), add metadata directly to the route definition:
60
+
61
+ ```typescript filename="src/http/users/route.ts"
62
+ import { controller, route } from 'exisjs/router'
63
+ import { tex, getPaginationSkip } from 'exisjs/validator'
64
+
65
+ export default controller({
66
+ listUsers: route.get('/', {
67
+ summary: 'List active users',
68
+ description: 'Returns a paginated list of all active users in the system.',
69
+ tags: ['Users'],
70
+ query: tex.pagination({ defaultLimit: 25, maxLimit: 100 }),
71
+ responses: {
72
+ 200: {
73
+ description: 'Paginated user list',
74
+ schema: tex.object({
75
+ data: tex.array(tex.object({ id: tex.uuid(), name: tex.string() })),
76
+ total: tex.number(),
77
+ }),
78
+ },
79
+ 400: { description: 'Invalid query parameters' },
80
+ },
81
+ async handle({ query }) {
82
+ const { skip, limit } = getPaginationSkip(query)
83
+ return UserService.find(skip, limit)
84
+ },
85
+ }),
86
+
87
+ createUser: route.post('/', {
88
+ summary: 'Create a new user',
89
+ tags: ['Users'],
90
+ body: tex.object({
91
+ name: tex.string({ min: 2 }),
92
+ email: tex.email(),
93
+ }),
94
+ responses: {
95
+ 201: { description: 'User successfully created' },
96
+ 400: { description: 'Validation error' },
97
+ },
98
+ async handle({ body }) {
99
+ return UserService.create(body)
100
+ },
101
+ }),
102
+
103
+ getUser: route.get('/:id', {
104
+ summary: 'Get user by UUID',
105
+ tags: ['Users'],
106
+ params: tex.object({ id: tex.uuid() }),
107
+ responses: {
108
+ 200: { description: 'User details found' },
109
+ 404: { description: 'User not found' },
110
+ },
111
+ async handle({ params }) {
112
+ return UserService.findById(params.id)
113
+ },
114
+ }),
115
+ })
116
+ ```
117
+
118
+ ---
119
+
120
+ ## Documenting Class-Based Controllers (OOP)
121
+
122
+ For class-based controllers, use decorators from `exisjs/swagger` or `exisjs/decorators`:
123
+
124
+ ```typescript filename="src/http/products/controller.ts"
125
+ import { Controller, Get, Post, Body, Param, Query } from 'exisjs/decorators'
126
+ import {
127
+ ApiTags,
128
+ ApiOperation,
129
+ ApiResponse,
130
+ ApiBearerAuth,
131
+ ApiExclude,
132
+ } from 'exisjs/swagger'
133
+ import { tex } from 'exisjs/validator'
134
+
135
+ @ApiTags('Products')
136
+ @ApiBearerAuth()
137
+ @Controller('/products')
138
+ export class ProductController {
139
+ @Get('/')
140
+ @ApiOperation({
141
+ summary: 'List products',
142
+ description: 'Fetches paginated list of catalog products',
143
+ })
144
+ @ApiResponse({ status: 200, description: 'List of products' })
145
+ async list(@Query('page') page: number) {
146
+ return ProductService.findAll(page)
147
+ }
148
+
149
+ @Post('/')
150
+ @ApiOperation({ summary: 'Create new product' })
151
+ @ApiResponse({ status: 201, description: 'Product created' })
152
+ async create(@Body() dto: any) {
153
+ return ProductService.create(dto)
154
+ }
155
+
156
+ @Get('/internal-stats')
157
+ @ApiExclude() // Excluded from Swagger documentation
158
+ async stats() {
159
+ return { uptime: process.uptime() }
160
+ }
161
+ }
162
+ ```
163
+
164
+ ---
165
+
166
+ ## Cascading Boundary Metadata
167
+
168
+ Cascading boundaries (`boundary.ts`) can assign shared tags, descriptions, or security requirements to all nested routes:
169
+
170
+ ```typescript filename="src/http/admin/boundary.ts"
171
+ import { defineBoundary } from 'exisjs/router'
172
+
173
+ export const config = defineBoundary({
174
+ tags: ['Admin Portal'],
175
+ security: [{ bearerAuth: [] }],
176
+ })
177
+ ```
178
+
179
+ ---
180
+
181
+ ## Security Schemes & Authentication
182
+
183
+ Define reusable security schemes (such as Bearer JWT, API Keys, or OAuth2) in `exis.config.ts`:
184
+
185
+ ```typescript filename="exis.config.ts"
186
+ export default defineConfig({
187
+ docs: {
188
+ enabled: true,
189
+ securitySchemes: {
190
+ bearerAuth: {
191
+ type: 'http',
192
+ scheme: 'bearer',
193
+ bearerFormat: 'JWT',
194
+ description: 'Enter your JWT token: Bearer <token>',
195
+ },
196
+ apiKeyAuth: {
197
+ type: 'apiKey',
198
+ in: 'header',
199
+ name: 'X-API-KEY',
200
+ },
201
+ },
202
+ security: [
203
+ { bearerAuth: [] },
204
+ ],
205
+ },
206
+ })
207
+ ```
208
+
209
+ ---
210
+
211
+ ## Programmatic / Custom Setup
212
+
213
+ You can also mount Swagger programmatically on any `App` instance:
214
+
215
+ ```typescript filename="src/index.ts"
216
+ import { App } from 'exisjs'
217
+ import { swagger } from 'exisjs/swagger'
218
+
219
+ const app = new App()
220
+
221
+ // Mount custom swagger documentation
222
+ swagger({
223
+ title: 'Custom API',
224
+ version: '2.0.0',
225
+ path: '/api/docs',
226
+ specPath: '/api/docs/spec.json',
227
+ })(app)
228
+ ```
@@ -32,22 +32,34 @@ const safeData = userSchema.parse(rawInput)
32
32
 
33
33
  ExisJS supports a comprehensive set of strict validation primitives:
34
34
 
35
- - `tex.string()` - String validation (supports options like `{ min: 5, max: 10, pattern: /regex/ }`).
36
- - `tex.email()` - Validates standard email formatting.
35
+ - `tex.string()` - String validation (supports options like `{ min, max, trim, toLowerCase, toUpperCase, escapeHtml, stripHtml, slugify, mask, default }`).
36
+ - `tex.email()` - Validates standard email formatting (supports `{ trim, toLowerCase, mask, default }`).
37
+ - `tex.uuid()` - Validates UUID strings (supports `{ version: 1 | 4, default }`).
38
+ - `tex.cuid()` - Validates CUID identifiers (supports `{ default }`).
39
+ - `tex.password()` - Validates password complexity (`{ min, max, requireNumbers, requireSymbols, requireUppercase, requireLowercase }`).
40
+ - `tex.creditCard()` - Validates credit card number formats with optional masking (`{ mask: true }`).
37
41
  - `tex.number()` - Strict number validation (fails if passed a string unless `coerce: true`).
38
- - `tex.number({ coerce: true, min: 0, max: 100 })` - Automatically coerces incoming string representations (e.g., query params) into native numbers.
42
+ - `tex.number({ coerce: true, min: 0, max: 100, default: 10 })` - Automatically coerces incoming string representations (e.g., query params) into native numbers with optional default fallback.
39
43
  - `tex.boolean()` - Strict boolean validation.
40
- - `tex.boolean({ coerce: true })` - Coerces `"true"`/`"1"` and `"false"`/`"0"` into native booleans.
44
+ - `tex.boolean({ coerce: true, default: false })` - Coerces `"true"`/`"1"` and `"false"`/`"0"` into native booleans.
41
45
  - `tex.date()` - Strict Date instance validation.
42
- - `tex.date({ coerce: true, minDate: new Date('2020-01-01'), maxDate: new Date() })` - Automatically parses ISO 8601 strings, UNIX timestamps, or date strings into native JavaScript `Date` objects with optional boundary checks.
43
- - `tex.array(schema, { coerce: true, min: 1, max: 10 })` - Validates an array of items. When `{ coerce: true }` is enabled, automatically converts single items or comma-separated query strings (`"apple,banana"`) into typed arrays. Accepts primitive types, `tex.object(...)`, or shorthand plain objects `{ ... }`.
46
+ - `tex.date({ coerce: true, minDate: '2020-01-01', maxDate: '2030-01-01', default: () => new Date() })` - Automatically parses ISO 8601 strings, UNIX timestamps, or date strings into native JavaScript `Date` objects with optional boundary checks and lazy default factories.
47
+ - `tex.array(schema, { coerce: true, min: 1, max: 10, default: [] })` - Validates an array of items. When `{ coerce: true }` is enabled, automatically converts single items or comma-separated query strings (`"apple,banana"`) into typed arrays. Accepts primitive types, `tex.object(...)`, or shorthand plain objects `{ ... }`.
48
+ - `tex.enum(['admin', 'user'], { default: 'user' })` - Ensures the value matches one of the provided exact strings.
49
+ - `tex.literal(value)` - Validates against an exact primitive literal (`string | number | boolean`).
50
+ - `tex.record(schema)` - Validates a dictionary/record of key-value pairs matching the value schema.
51
+ - `tex.any()` - Allows any payload value without constraint.
52
+ - `tex.file({ maxSize: 5 * 1024 * 1024, mimeTypes: ['image/png', 'image/jpeg'] })` - Validates uploaded file payloads (size and MIME type).
44
53
  - `tex.object({ ... })` - Validates structured JSON objects.
45
- - `tex.enum(['A', 'B'])` - Ensures the value matches one of the provided exact strings.
46
54
 
47
55
  All primitives accept modifier options or chained helper methods:
48
56
  - `.optional()` / `{ optional: true }` - Allows `undefined`.
49
57
  - `.nullable()` / `{ nullable: true }` - Allows `null`.
50
58
  - `.nullish()` / `{ nullish: true }` - Allows both `null` and `undefined`.
59
+ - `{ default: value | (() => value) }` - Provides a static default or dynamic factory function when the field is omitted or `undefined`. Automatically marks the field as optional in schemas and infers concrete types in TypeScript.
60
+
61
+ > [!TIP]
62
+ > **Unified Defaults without Method Chaining**: ExisJS avoids `.default()` chaining overhead in favor of declarative options objects (e.g. `tex.number({ default: 3000 })` or `tex.date({ default: () => new Date() })`). Dynamic factory functions evaluate per request so timestamps and UUIDs remain fresh!
51
63
 
52
64
  > [!TIP]
53
65
  > **Automatic Empty String `""` Normalization**: When forms or query parameters pass empty strings `""` for fields marked as `.optional()`, `.nullable()`, or `.nullish()`, ExisJS automatically converts them to `undefined` or `null` before running type validation. This prevents false positive type errors on numeric, UUID, or enum fields!
@@ -260,6 +272,8 @@ export const RegisterUserSchema = tex.object({
260
272
 
261
273
  Query strings in HTTP cannot natively represent arrays or date instances. ExisJS solves this elegantly through `{ coerce: true }`:
262
274
 
275
+ #### Functional Paradigm
276
+
263
277
  ```typescript filename="src/http/events/route.ts"
264
278
  import { controller, route } from 'exisjs/router'
265
279
  import { tex } from 'exisjs/validator'
@@ -290,12 +304,39 @@ export default controller({
290
304
  })
291
305
  ```
292
306
 
307
+ #### Class-Based (OOP) Paradigm
308
+
309
+ ```typescript filename="src/http/events/route.ts"
310
+ import { Controller, Get, Query } from 'exisjs/decorators'
311
+ import { tex } from 'exisjs/validator'
312
+ import type { ResolveSchema } from 'exisjs/validator'
313
+
314
+ const FilterSchema = tex.object({
315
+ from: tex.date({ coerce: true, optional: true }),
316
+ to: tex.date({ coerce: true, optional: true }),
317
+ tags: tex.array(tex.string(), { coerce: true, optional: true }),
318
+ ids: tex.array(tex.number({ coerce: true }), { coerce: true, optional: true }),
319
+ })
320
+
321
+ type FilterDto = ResolveSchema<typeof FilterSchema>
322
+
323
+ @Controller('/events')
324
+ export default class EventsController {
325
+ @Get('/', { query: FilterSchema })
326
+ listEvents(@Query() query: FilterDto) {
327
+ return { filter: query }
328
+ }
329
+ }
330
+ ```
331
+
293
332
  ---
294
333
 
295
334
  ## Pagination Schemas (`tex.pagination`)
296
335
 
297
336
  ExisJS provides a dedicated schema primitive for query pagination. It automatically coerces incoming string query parameters (`?page=2&limit=25&sort=name&order=asc`) into typed numbers and strings while enforcing sensible bounds:
298
337
 
338
+ #### Functional Paradigm
339
+
299
340
  ```typescript filename="src/http/items/route.ts"
300
341
  import { controller, route } from 'exisjs/router'
301
342
  import { tex, paginate, getPaginationSkip } from 'exisjs/validator'
@@ -319,11 +360,35 @@ export default controller({
319
360
  })
320
361
  ```
321
362
 
363
+ #### Class-Based (OOP) Paradigm
364
+
365
+ ```typescript filename="src/http/items/route.ts"
366
+ import { Controller, Get, Query } from 'exisjs/decorators'
367
+ import { tex, paginate, getPaginationSkip } from 'exisjs/validator'
368
+
369
+ const ItemQuerySchema = tex.pagination({
370
+ defaultLimit: 20,
371
+ maxLimit: 100,
372
+ })
373
+
374
+ @Controller('/items')
375
+ export default class ItemsController {
376
+ @Get('/', { query: ItemQuerySchema })
377
+ async list(@Query() query: any) {
378
+ const { skip, limit, page } = getPaginationSkip(query)
379
+ const items = await fetchItems({ skip, limit, sort: query.sort, order: query.order })
380
+ const total = await countItems()
381
+
382
+ return paginate(items, total, { page, limit })
383
+ }
384
+ }
385
+ ```
386
+
322
387
  ---
323
388
 
324
389
  ## Error Handling & Developer Diagnostics
325
390
 
326
- If a request fails validation, ExisJS intercepts the rejection and returns a standardized `400 Bad Request` containing a machine-readable `errors: Record<string, string>` dictionary for frontend form field mapping:
391
+ If a request fails validation, ExisJS intercepts the rejection and returns a standardized `400 Bad Request` containing both a legacy `errors: Record<string, string>` dictionary and a structured `details: ValidationErrorDescriptor[]` array for frontend form field mapping:
327
392
 
328
393
  ```json filename="HTTP 400 Bad Request Response"
329
394
  {
@@ -333,16 +398,32 @@ If a request fails validation, ExisJS intercepts the rejection and returns a sta
333
398
  "errors": {
334
399
  "username": "Field must be at least 4 characters",
335
400
  "profile.tags": "Expected array of strings"
336
- }
401
+ },
402
+ "details": [
403
+ {
404
+ "path": "username",
405
+ "message": "Field must be at least 4 characters",
406
+ "expected": "min: 4",
407
+ "received": "abc",
408
+ "code": "VALUE_TOO_SMALL"
409
+ },
410
+ {
411
+ "path": "profile.tags",
412
+ "message": "Expected array of strings",
413
+ "expected": "Array of string",
414
+ "received": "number",
415
+ "code": "INVALID_TYPE"
416
+ }
417
+ ]
337
418
  }
338
419
  ```
339
420
 
340
421
  ### Dev Mode Terminal Diagnostics
341
422
 
342
- When validation fails in development mode, ExisJS prints an immediate tabular diagnostic to your terminal displaying the failing HTTP part, exact field path, received value, and expected schema constraint:
423
+ When validation fails in development mode, ExisJS prints an immediate tabular diagnostic to your terminal displaying the HTTP method, route path, failing HTTP part (`body`, `query`, `params`, `headers`), exact field path, received value, and expected schema constraint:
343
424
 
344
425
  ```text
345
- [VALIDATION FAILED] POST /visits (part: body)
426
+ [VALIDATION FAILED] POST /api/v1/visits (part: body)
346
427
  ┌─────────────┬───────────────────────────┬──────────────────┐
347
428
  │ Field │ Received │ Expected │
348
429
  ├─────────────┼───────────────────────────┼──────────────────┤
@@ -351,3 +432,101 @@ When validation fails in development mode, ExisJS prints an immediate tabular di
351
432
  └─────────────┴───────────────────────────┴──────────────────┘
352
433
  ```
353
434
 
435
+ ---
436
+
437
+ ## Environment Variable Validation (`tex.env`)
438
+
439
+ Because `process.env` always supplies raw string values (e.g. `"4000"`, `"true"`, `"1"`), ExisJS includes a dedicated `tex.env()` schema parser. It automatically injects coercion for numbers, booleans, dates, and arrays, allowing you to validate your environment variables on boot without writing `{ coerce: true }` on every field:
440
+
441
+ ```typescript filename="src/config/env.ts"
442
+ import { tex } from 'exisjs/validator'
443
+
444
+ export const env = tex.env({
445
+ PORT: tex.number({ default: 4000 }), // Automatically coerced from "4000" -> 4000
446
+ DEBUG: tex.boolean({ default: false }), // Parsed from "true" | "1" | "false" | "0"
447
+ DATABASE_URL: tex.string(),
448
+ JWT_SECRET: tex.string({ min: 32 }),
449
+ ALLOWED_ORIGINS: tex.array(tex.string()), // Parsed from "http://localhost:3000,https://app.com"
450
+ NODE_ENV: tex.enum(['development', 'production', 'test'] as const, {
451
+ default: 'development',
452
+ }),
453
+ }).parse(process.env)
454
+ ```
455
+
456
+ > [!TIP]
457
+ > **Zero-Boilerplate Boot Safety**: `tex.env()` automatically validates `process.env` on server startup. If a required environment variable is missing, it crashes immediately with a clean diagnostic log before the HTTP listener opens.
458
+
459
+ ---
460
+
461
+ ## Third-Party Validator Interoperability (Zod, Valibot, ArkType)
462
+
463
+ While ExisJS provides the high-performance native `tex` engine out of the box, it is **100% interoperable with external schema libraries like Zod, Valibot, or ArkType** via Duck Typing.
464
+
465
+ Any object with a `.parse()` or `.safeParse()` method can be passed directly as a route schema or used in `src/config/env.ts`.
466
+
467
+ ### Using Zod in Routes
468
+
469
+ #### Functional Paradigm
470
+
471
+ ```typescript filename="src/http/users/route.ts"
472
+ import { controller, route } from 'exisjs/router'
473
+ import { z } from 'zod'
474
+
475
+ const CreateUserSchema = z.object({
476
+ name: z.string().min(3),
477
+ email: z.string().email(),
478
+ role: z.enum(['admin', 'user']),
479
+ })
480
+
481
+ export default controller({
482
+ create: route.post('/', {
483
+ body: CreateUserSchema, // 👈 Directly pass your Zod schema!
484
+ async handle({ body, res }) {
485
+ return res.status(201).json({ success: true, user: body })
486
+ }
487
+ })
488
+ })
489
+ ```
490
+
491
+ #### Class-Based (OOP) Paradigm
492
+
493
+ ```typescript filename="src/http/users/route.ts"
494
+ import { Controller, Post, Body, HttpCode } from 'exisjs/decorators'
495
+ import { z } from 'zod'
496
+
497
+ const CreateUserSchema = z.object({
498
+ name: z.string().min(3),
499
+ email: z.string().email(),
500
+ role: z.enum(['admin', 'user']),
501
+ })
502
+
503
+ type CreateUserDto = z.infer<typeof CreateUserSchema>
504
+
505
+ @Controller('/users')
506
+ export default class UsersController {
507
+ @Post('/')
508
+ @HttpCode(201)
509
+ async create(@Body(CreateUserSchema) body: CreateUserDto) {
510
+ return { success: true, user: body }
511
+ }
512
+ }
513
+ ```
514
+
515
+ ### Using Zod for Environment Validation
516
+
517
+ ```typescript filename="src/config/env.ts"
518
+ import { z } from 'zod'
519
+
520
+ const EnvSchema = z.object({
521
+ PORT: z.coerce.number().default(4000),
522
+ NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
523
+ DATABASE_URL: z.string().url(),
524
+ JWT_SECRET: z.string().min(32),
525
+ })
526
+
527
+ export const env = EnvSchema.parse(process.env)
528
+ ```
529
+
530
+ > [!NOTE]
531
+ > **Universal Error Normalization**: Regardless of which library you use (**Zod**, **Valibot**, **Yup**, **Joi**, **ArkType**, or any Standard Schema-compliant library), ExisJS inspects the error structure (`issues`, `errors`, `inner`) and automatically normalizes it into standardized `400 Bad Request` JSON responses with complete field mappings and dev-mode terminal overlays.
532
+
@@ -166,3 +166,14 @@ export default class UserController {
166
166
 
167
167
  > [!TIP]
168
168
  > For deep services inside an OOP architecture, you can still seamlessly use `getContext()`, `getRequest()`, or `after()` from `exisjs/router` to access the async context without prop-drilling!
169
+
170
+ ---
171
+
172
+ ## Context Isolation & Memory Leak Safety
173
+
174
+ In high-concurrency micro-benchmarks or long-lived streaming connections (SSE / WebSockets), holding references in `AsyncLocalStorage` without explicit teardown can lead to memory retention. ExisJS enforces a deterministic context cleanup lifecycle:
175
+
176
+ 1. **Deterministic Teardown (`cleanupContext`)**: When a response emits the `'finish'` or `'close'` event, ExisJS automatically executes all remaining `after()` callbacks, clears request-scoped dependency injection instances (`diCache`), purges the `state` dictionary, and unlinks circular references (`req = null!`, `res = null!`, `app = null!`).
177
+ 2. **`try / finally` Dispatch Boundary**: The router wraps every handler dispatch in a `try ... finally` boundary, guaranteeing that unhandled exceptions or rejected promises cannot leak state into subsequent async continuations.
178
+ 3. **Recycling Pool Memory Scrubbing**: When recycled `ExisRequest` and `ExisResponse` instances return to the internal object pool, their internal dependency injection caches and request metadata are scrubbed clean.
179
+