exisjs 0.7.5 → 0.7.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/dist/cli/commands/dev.d.ts.map +1 -1
  2. package/dist/cli/commands/dev.js +156 -85
  3. package/dist/cli/commands/dev.js.map +1 -1
  4. package/dist/cli/commands/start.d.ts.map +1 -1
  5. package/dist/cli/commands/start.js +54 -16
  6. package/dist/cli/commands/start.js.map +1 -1
  7. package/dist/cli/index.js +10 -1
  8. package/dist/cli/index.js.map +1 -1
  9. package/dist/cli/manifest.d.ts.map +1 -1
  10. package/dist/cli/manifest.js +106 -13
  11. package/dist/cli/manifest.js.map +1 -1
  12. package/dist/cli/resolve-aliases.d.ts +1 -1
  13. package/dist/cli/resolve-aliases.d.ts.map +1 -1
  14. package/dist/cli/resolve-aliases.js +30 -13
  15. package/dist/cli/resolve-aliases.js.map +1 -1
  16. package/dist/config/config.d.ts +3 -1
  17. package/dist/config/config.d.ts.map +1 -1
  18. package/dist/config/config.js.map +1 -1
  19. package/dist/config/types.d.ts +2 -0
  20. package/dist/config/types.d.ts.map +1 -1
  21. package/dist/decorators/middleware.d.ts +65 -2
  22. package/dist/decorators/middleware.d.ts.map +1 -1
  23. package/dist/decorators/middleware.js +148 -62
  24. package/dist/decorators/middleware.js.map +1 -1
  25. package/dist/decorators/params.d.ts +5 -0
  26. package/dist/decorators/params.d.ts.map +1 -1
  27. package/dist/decorators/params.js +11 -1
  28. package/dist/decorators/params.js.map +1 -1
  29. package/dist/decorators/response.d.ts.map +1 -1
  30. package/dist/decorators/response.js +32 -22
  31. package/dist/decorators/response.js.map +1 -1
  32. package/dist/di/container.d.ts.map +1 -1
  33. package/dist/di/container.js +6 -1
  34. package/dist/di/container.js.map +1 -1
  35. package/dist/docs/content/cli/commands.mdx +1 -1
  36. package/dist/docs/content/controllers.mdx +127 -10
  37. package/dist/docs/content/decorators.mdx +22 -4
  38. package/dist/docs/content/introduction.mdx +7 -7
  39. package/dist/docs/content/middleware.mdx +269 -6
  40. package/dist/docs/content/requests.mdx +180 -6
  41. package/dist/docs/content/responses.mdx +283 -32
  42. package/dist/docs/content/routing.mdx +46 -0
  43. package/dist/docs/content/validation.mdx +190 -11
  44. package/dist/docs/reference/context.mdx +11 -0
  45. package/dist/docs/reference/decorators.mdx +14 -4
  46. package/dist/docs/reference/error.mdx +44 -0
  47. package/dist/docs/reference/logger.mdx +175 -43
  48. package/dist/docs/reference/middleware.mdx +31 -0
  49. package/dist/docs/reference/router.mdx +22 -0
  50. package/dist/docs/reference/validator.mdx +83 -23
  51. package/dist/error/errors.d.ts.map +1 -1
  52. package/dist/error/errors.js +95 -43
  53. package/dist/error/errors.js.map +1 -1
  54. package/dist/error/index.d.ts +1 -0
  55. package/dist/error/index.d.ts.map +1 -1
  56. package/dist/error/index.js +11 -1
  57. package/dist/error/index.js.map +1 -1
  58. package/dist/error/overlay.d.ts +42 -0
  59. package/dist/error/overlay.d.ts.map +1 -0
  60. package/dist/error/overlay.js +306 -0
  61. package/dist/error/overlay.js.map +1 -0
  62. package/dist/index.d.ts +1 -0
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +22 -1
  65. package/dist/index.js.map +1 -1
  66. package/dist/lib/start-server.js +33 -7
  67. package/dist/lib/start-server.js.map +1 -1
  68. package/dist/logger/index.d.ts +1 -1
  69. package/dist/logger/index.d.ts.map +1 -1
  70. package/dist/logger/index.js +9 -1
  71. package/dist/logger/index.js.map +1 -1
  72. package/dist/middleware/guard.d.ts +2 -2
  73. package/dist/middleware/guard.d.ts.map +1 -1
  74. package/dist/middleware/guard.js.map +1 -1
  75. package/dist/middleware/ip-filter.d.ts +1 -0
  76. package/dist/middleware/ip-filter.d.ts.map +1 -1
  77. package/dist/middleware/ip-filter.js +2 -0
  78. package/dist/middleware/ip-filter.js.map +1 -1
  79. package/dist/middleware/middleware.d.ts +2 -2
  80. package/dist/middleware/middleware.d.ts.map +1 -1
  81. package/dist/middleware/middleware.js +20 -2
  82. package/dist/middleware/middleware.js.map +1 -1
  83. package/dist/middleware/security.d.ts +43 -1
  84. package/dist/middleware/security.d.ts.map +1 -1
  85. package/dist/middleware/security.js +101 -0
  86. package/dist/middleware/security.js.map +1 -1
  87. package/dist/response/index.d.ts +229 -6
  88. package/dist/response/index.d.ts.map +1 -1
  89. package/dist/response/index.js +272 -2
  90. package/dist/response/index.js.map +1 -1
  91. package/dist/router/boundary.d.ts +17 -9
  92. package/dist/router/boundary.d.ts.map +1 -1
  93. package/dist/router/boundary.js.map +1 -1
  94. package/dist/router/controller-registrar.d.ts.map +1 -1
  95. package/dist/router/controller-registrar.js +56 -1
  96. package/dist/router/controller-registrar.js.map +1 -1
  97. package/dist/router/index.d.ts +2 -2
  98. package/dist/router/index.d.ts.map +1 -1
  99. package/dist/router/index.js +2 -1
  100. package/dist/router/index.js.map +1 -1
  101. package/dist/router/route-builder.d.ts +70 -36
  102. package/dist/router/route-builder.d.ts.map +1 -1
  103. package/dist/router/route-builder.js +13 -15
  104. package/dist/router/route-builder.js.map +1 -1
  105. package/dist/router/route-scanner.d.ts.map +1 -1
  106. package/dist/router/route-scanner.js +24 -2
  107. package/dist/router/route-scanner.js.map +1 -1
  108. package/dist/router/router.d.ts.map +1 -1
  109. package/dist/router/router.js +35 -17
  110. package/dist/router/router.js.map +1 -1
  111. package/dist/router/types.d.ts +8 -0
  112. package/dist/router/types.d.ts.map +1 -1
  113. package/dist/server/app.d.ts +1 -0
  114. package/dist/server/app.d.ts.map +1 -1
  115. package/dist/server/app.js +10 -0
  116. package/dist/server/app.js.map +1 -1
  117. package/dist/server/bootstrapper.d.ts.map +1 -1
  118. package/dist/server/bootstrapper.js +91 -30
  119. package/dist/server/bootstrapper.js.map +1 -1
  120. package/dist/server/cluster.d.ts.map +1 -1
  121. package/dist/server/cluster.js +13 -2
  122. package/dist/server/cluster.js.map +1 -1
  123. package/dist/server/context.d.ts +8 -0
  124. package/dist/server/context.d.ts.map +1 -1
  125. package/dist/server/context.js +50 -6
  126. package/dist/server/context.js.map +1 -1
  127. package/dist/server/lifecycle.d.ts +22 -0
  128. package/dist/server/lifecycle.d.ts.map +1 -0
  129. package/dist/server/lifecycle.js +106 -0
  130. package/dist/server/lifecycle.js.map +1 -0
  131. package/dist/server/request-handler.d.ts +1 -0
  132. package/dist/server/request-handler.d.ts.map +1 -1
  133. package/dist/server/request-handler.js +27 -17
  134. package/dist/server/request-handler.js.map +1 -1
  135. package/dist/server/request.d.ts.map +1 -1
  136. package/dist/server/request.js +18 -0
  137. package/dist/server/request.js.map +1 -1
  138. package/dist/server/response.d.ts +10 -1
  139. package/dist/server/response.d.ts.map +1 -1
  140. package/dist/server/response.js +154 -2
  141. package/dist/server/response.js.map +1 -1
  142. package/dist/testing/index.d.ts.map +1 -1
  143. package/dist/testing/index.js +6 -1
  144. package/dist/testing/index.js.map +1 -1
  145. package/dist/testing/mocks.d.ts.map +1 -1
  146. package/dist/testing/mocks.js +15 -3
  147. package/dist/testing/mocks.js.map +1 -1
  148. package/dist/utils/logger.d.ts +8 -0
  149. package/dist/utils/logger.d.ts.map +1 -1
  150. package/dist/utils/logger.js +184 -1
  151. package/dist/utils/logger.js.map +1 -1
  152. package/dist/validator/tex-types.d.ts +2 -1
  153. package/dist/validator/tex-types.d.ts.map +1 -1
  154. package/dist/validator/tex-types.js +2 -1
  155. package/dist/validator/tex-types.js.map +1 -1
  156. package/dist/validator/tex.d.ts +31 -15
  157. package/dist/validator/tex.d.ts.map +1 -1
  158. package/dist/validator/tex.js +280 -42
  159. package/dist/validator/tex.js.map +1 -1
  160. package/package.json +4 -4
  161. package/dist/dev/error-overlay.d.ts +0 -3
  162. package/dist/dev/error-overlay.d.ts.map +0 -1
  163. package/dist/dev/error-overlay.js +0 -118
  164. package/dist/dev/error-overlay.js.map +0 -1
@@ -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
+
@@ -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
+
@@ -68,6 +68,10 @@ Parameter decorators automatically extract data from the incoming request and in
68
68
  - `@Param(nameOrPipe?)`: Injects a URL path parameter (e.g., `id` from `/:id`).
69
69
  - `@Headers(nameOrPipe?)`: Injects an HTTP header by name.
70
70
  - `@QueryParam(nameOrPipe?)` / `@Query(nameOrPipe?)`: Injects a URL query parameter (e.g., `?search=term`).
71
+ - `@Cookies(nameOrPipe?)` / `@Cookie(nameOrPipe?)`: Injects incoming parsed cookies or a specific cookie value.
72
+ - `@State(keyOrPipe?)`: Injects request state from execution context or middleware.
73
+ - `@AppCtx()`: Injects the root ExisJS Application instance.
74
+ - `@Fields(nameOrPipe?)`: Injects non-file multipart form fields.
71
75
  - `@UploadedFile()`: Injects the uploaded file (`req.file`) into the method argument.
72
76
  - `@UploadedFiles()`: Injects the uploaded files array or dictionary (`req.files`) into the method argument.
73
77
  - `@HostParam(nameOrPipe?)`: Injects a subdomain parameter.
@@ -83,8 +87,14 @@ Parameter decorators automatically extract data from the incoming request and in
83
87
 
84
88
  - `@Use(...middlewaresOrGuardsOrInterceptorsOrFilters)`: Unified decorator that binds middlewares, guards (`canActivate`), interceptors (`intercept`), or exception filters (`catch`) to a controller class or method.
85
89
  - `@Upload(fieldName, options?)` / `@UseUpload(...)`: Configures multipart file upload handling for a method, populating `@UploadedFile()` or `@UploadedFiles()`.
86
- - `@Timeout(ms: number | TimeoutOptions)`: Overrides the request timeout for this method (e.g. `@Timeout(120000)`). Passing `0` disables timeouts for long-running streaming or exports.
87
- - `@Idempotent(options?)`: Caches responses based on the `Idempotency-Key` header using native in-memory off-heap storage.
90
+ - `@Timeout(ms: number | TimeoutOptions)`: Overrides the request timeout for a class or method (e.g. `@Timeout(120000)`). Passing `0` disables timeouts for long-running streaming or exports.
91
+ - `@Idempotent(options?)`: Caches responses based on the `Idempotency-Key` header using native in-memory off-heap storage. Works at class and method level.
92
+ - `@RateLimit(options?)`: Enforces rate limits (e.g. `@RateLimit({ max: 100, windowMs: 60000 })`) at class or method level.
93
+ - `@Cors(options?)`: Enforces Cross-Origin Resource Sharing rules at class or method level.
94
+ - `@IpFilter(options)`: Enforces CIDR and IP allow/deny lists at class or method level.
95
+ - `@Compress()`: Enables automatic Gzip and Brotli compression on responses.
96
+ - `@Dedupe(options)`: Deduplicates concurrent identical requests sharing the same key.
97
+ - `@BlockProbes(options?)`: Intercepts vulnerability scanner probes (`.env`, `/.git`, `/.DS_Store`) and blackholes them without triggering log noise.
88
98
 
89
99
  ---
90
100
 
@@ -108,8 +118,8 @@ Parameter decorators automatically extract data from the incoming request and in
108
118
 
109
119
  - `@Module({ imports?, controllers?, providers?, exports? })`: OOP class decorator equivalent to `defineModule()` for structuring application modules.
110
120
  - `@Injectable(options?)`: Marks a service class as an IoC provider managed by the container. Supports `{ scope: 'singleton' | 'request' | 'transient' }`.
111
- - `@Inject(token)`: Constructor parameter decorator for injecting dependencies by class, string, or symbol token.
112
- - `@Optional()`: Constructor parameter decorator resolving missing dependencies to `undefined` without throwing.
121
+ - `@Inject(token)`: Constructor parameter decorator from `exisjs/di` for injecting dependencies by class, string, or symbol token.
122
+ - `@Optional()`: Constructor parameter decorator from `exisjs/di` resolving missing dependencies to `undefined` without throwing.
113
123
  - `@Boundary(config)`: Marks a class as a folder-scoped Boundary configuring CORS, headers, exclusions, and pipeline steps.
114
124
  - `@Server(config)`: Marks a class as a root server definition.
115
125
 
@@ -170,3 +170,47 @@ app.use((err, req, res, next) => {
170
170
  ExisJS natively intercepts and handles Promise rejections at the core routing level.
171
171
 
172
172
  Unlike Express, you do **not** need to wrap your routes in an `asyncHandler`. However, for strict compatibility with older Express middleware ecosystems, `exisjs/error` does export an `asyncHandler` wrapper. Note that using it in ExisJS is entirely redundant and merely adds unnecessary Promise allocations.
173
+
174
+ ---
175
+
176
+ ## Developer Error Formatting & Overlay Utilities
177
+
178
+ `exisjs/error` also exports powerful error diagnostic utilities that you can use in custom error handlers, CLI scripts, background workers, and testing:
179
+
180
+ ```typescript
181
+ import {
182
+ formatDevError,
183
+ renderDevErrorToString,
184
+ categorizeError,
185
+ generateErrorHint,
186
+ buildCodeFrame,
187
+ stripInternalStackFrames,
188
+ devErrorResponse,
189
+ type ParsedError,
190
+ } from 'exisjs/error'
191
+ ```
192
+
193
+ ### Utility Functions
194
+
195
+ | Function | Signature | Description |
196
+ | :--- | :--- | :--- |
197
+ | `formatDevError` | `(err: Error, optionsOrFile?: string \| { routeFile?: string; title?: string }) => void` | Prints a formatted terminal error envelope with code frames and actionable hints |
198
+ | `renderDevErrorToString` | `(err: Error, optionsOrFile?: ...) => string` | Returns the formatted terminal error envelope string |
199
+ | `categorizeError` | `(err: Error) => string` | Intelligently classifies errors into domain categories (`Validation Schema Error`, `Environment Configuration Error`, etc.) |
200
+ | `generateErrorHint` | `(err: Error) => string \| null` | Generates actionable hints for common coding mistakes |
201
+ | `buildCodeFrame` | `(filePath: string, line: number, col?: number) => string` | Builds a source code snippet visualizer with gutter line numbers and caret indicator |
202
+ | `stripInternalStackFrames` | `(stack?: string) => string[]` | Strips internal Node.js and framework runtime stack frames |
203
+ | `devErrorResponse` | `(err: Error, routeFile?: string) => object` | Generates a structured developer error envelope payload for JSON API responses |
204
+
205
+ ### Example: Custom Worker or Script Error Handling
206
+
207
+ ```typescript filename="src/workers/import-csv.ts"
208
+ import { formatDevError, categorizeError } from 'exisjs/error'
209
+
210
+ try {
211
+ await processCsvImport(filePath)
212
+ } catch (err: any) {
213
+ // Prints a clean code frame with line numbers and actionable hints directly in terminal
214
+ formatDevError(err, { routeFile: filePath, title: categorizeError(err) })
215
+ }
216
+ ```