exisjs 0.9.0 → 0.9.2

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 (123) hide show
  1. package/dist/config/config.d.ts.map +1 -1
  2. package/dist/config/config.js +2 -1
  3. package/dist/config/config.js.map +1 -1
  4. package/dist/config/types.d.ts +5 -0
  5. package/dist/config/types.d.ts.map +1 -1
  6. package/dist/database/lifecycle.d.ts +19 -2
  7. package/dist/database/lifecycle.d.ts.map +1 -1
  8. package/dist/database/lifecycle.js +92 -53
  9. package/dist/database/lifecycle.js.map +1 -1
  10. package/dist/decorators/transaction.d.ts +0 -21
  11. package/dist/decorators/transaction.d.ts.map +1 -1
  12. package/dist/decorators/transaction.js +52 -32
  13. package/dist/decorators/transaction.js.map +1 -1
  14. package/dist/docs/content/boundaries.mdx +2 -0
  15. package/dist/docs/content/config.mdx +4 -2
  16. package/dist/docs/content/decorators.mdx +1 -1
  17. package/dist/docs/content/middleware.mdx +4 -2
  18. package/dist/docs/content/requests.mdx +2 -0
  19. package/dist/docs/content/responses.mdx +54 -0
  20. package/dist/docs/content/validation.mdx +46 -11
  21. package/dist/docs/reference/README.md +16 -16
  22. package/dist/docs/reference/config.mdx +2 -1
  23. package/dist/docs/reference/database.mdx +3 -2
  24. package/dist/docs/reference/middleware.mdx +1 -1
  25. package/dist/docs/reference/sanitize.mdx +129 -134
  26. package/dist/docs/reference/validator.mdx +11 -3
  27. package/dist/middleware/compression.d.ts.map +1 -1
  28. package/dist/middleware/compression.js +6 -5
  29. package/dist/middleware/compression.js.map +1 -1
  30. package/dist/middleware/middleware.d.ts.map +1 -1
  31. package/dist/middleware/middleware.js +47 -34
  32. package/dist/middleware/middleware.js.map +1 -1
  33. package/dist/router/controller-registrar.d.ts.map +1 -1
  34. package/dist/router/controller-registrar.js +4 -7
  35. package/dist/router/controller-registrar.js.map +1 -1
  36. package/dist/router/route-scanner.d.ts +12 -4
  37. package/dist/router/route-scanner.d.ts.map +1 -1
  38. package/dist/router/route-scanner.js +46 -31
  39. package/dist/router/route-scanner.js.map +1 -1
  40. package/dist/router/router.d.ts +3 -0
  41. package/dist/router/router.d.ts.map +1 -1
  42. package/dist/router/router.js +40 -35
  43. package/dist/router/router.js.map +1 -1
  44. package/dist/router/scanner/boundary-loader.d.ts.map +1 -1
  45. package/dist/router/scanner/boundary-loader.js +25 -30
  46. package/dist/router/scanner/boundary-loader.js.map +1 -1
  47. package/dist/router/scanner/functional-compiler.d.ts.map +1 -1
  48. package/dist/router/scanner/functional-compiler.js +29 -29
  49. package/dist/router/scanner/functional-compiler.js.map +1 -1
  50. package/dist/sanitize/index.d.ts +29 -3
  51. package/dist/sanitize/index.d.ts.map +1 -1
  52. package/dist/sanitize/index.js +91 -32
  53. package/dist/sanitize/index.js.map +1 -1
  54. package/dist/server/adapters/bun-adapter.d.ts +1 -1
  55. package/dist/server/adapters/bun-adapter.d.ts.map +1 -1
  56. package/dist/server/adapters/bun-adapter.js.map +1 -1
  57. package/dist/server/app.d.ts.map +1 -1
  58. package/dist/server/app.js +5 -2
  59. package/dist/server/app.js.map +1 -1
  60. package/dist/server/bootstrapper.d.ts.map +1 -1
  61. package/dist/server/bootstrapper.js +1 -1
  62. package/dist/server/bootstrapper.js.map +1 -1
  63. package/dist/server/helpers/body-parser.d.ts +1 -1
  64. package/dist/server/helpers/body-parser.d.ts.map +1 -1
  65. package/dist/server/helpers/body-parser.js +9 -7
  66. package/dist/server/helpers/body-parser.js.map +1 -1
  67. package/dist/server/helpers/conditional.d.ts +60 -0
  68. package/dist/server/helpers/conditional.d.ts.map +1 -0
  69. package/dist/server/helpers/conditional.js +189 -0
  70. package/dist/server/helpers/conditional.js.map +1 -0
  71. package/dist/server/helpers/etag.d.ts +0 -1
  72. package/dist/server/helpers/etag.d.ts.map +1 -1
  73. package/dist/server/helpers/etag.js +0 -10
  74. package/dist/server/helpers/etag.js.map +1 -1
  75. package/dist/server/helpers/index.d.ts +2 -1
  76. package/dist/server/helpers/index.d.ts.map +1 -1
  77. package/dist/server/helpers/index.js +16 -2
  78. package/dist/server/helpers/index.js.map +1 -1
  79. package/dist/server/request-handler.d.ts.map +1 -1
  80. package/dist/server/request-handler.js +12 -2
  81. package/dist/server/request-handler.js.map +1 -1
  82. package/dist/server/request.d.ts +11 -3
  83. package/dist/server/request.d.ts.map +1 -1
  84. package/dist/server/request.js +41 -83
  85. package/dist/server/request.js.map +1 -1
  86. package/dist/server/response.d.ts +43 -2
  87. package/dist/server/response.d.ts.map +1 -1
  88. package/dist/server/response.js +151 -70
  89. package/dist/server/response.js.map +1 -1
  90. package/dist/validator/builder.d.ts +12 -1
  91. package/dist/validator/builder.d.ts.map +1 -1
  92. package/dist/validator/builder.js +74 -41
  93. package/dist/validator/builder.js.map +1 -1
  94. package/dist/validator/compile.d.ts +7 -2
  95. package/dist/validator/compile.d.ts.map +1 -1
  96. package/dist/validator/compile.js +85 -54
  97. package/dist/validator/compile.js.map +1 -1
  98. package/dist/validator/engine.d.ts +75 -6
  99. package/dist/validator/engine.d.ts.map +1 -1
  100. package/dist/validator/engine.js +483 -540
  101. package/dist/validator/engine.js.map +1 -1
  102. package/dist/validator/error.d.ts.map +1 -1
  103. package/dist/validator/error.js +7 -0
  104. package/dist/validator/error.js.map +1 -1
  105. package/dist/validator/rules.d.ts +66 -7
  106. package/dist/validator/rules.d.ts.map +1 -1
  107. package/dist/validator/rules.js +435 -109
  108. package/dist/validator/rules.js.map +1 -1
  109. package/dist/validator/tex-types.d.ts +15 -0
  110. package/dist/validator/tex-types.d.ts.map +1 -1
  111. package/dist/validator/tex-types.js +127 -64
  112. package/dist/validator/tex-types.js.map +1 -1
  113. package/dist/validator/text.d.ts +2 -0
  114. package/dist/validator/text.d.ts.map +1 -1
  115. package/dist/validator/text.js +31 -13
  116. package/dist/validator/text.js.map +1 -1
  117. package/dist/validator/types.d.ts +15 -0
  118. package/dist/validator/types.d.ts.map +1 -1
  119. package/package.json +3 -5
  120. package/dist/sanitize/sanitize.d.ts +0 -33
  121. package/dist/sanitize/sanitize.d.ts.map +0 -1
  122. package/dist/sanitize/sanitize.js +0 -104
  123. package/dist/sanitize/sanitize.js.map +0 -1
@@ -451,6 +451,60 @@ export default controller({
451
451
  })
452
452
  ```
453
453
 
454
+ ## Caching & Conditional Requests (ETag, 304, 412)
455
+
456
+ ExisJS follows the conditional request rules of RFC 9110 §13 everywhere: for automatic ETags, in `res.checkPreconditions()`, for `serveStatic` and for `res.download(path)`.
457
+
458
+ | Request header | Checked against | Result when it does not hold |
459
+ | :--- | :--- | :--- |
460
+ | `If-Match` | `ETag`, strong comparison | `412 Precondition Failed` |
461
+ | `If-Unmodified-Since` (only without `If-Match`) | `Last-Modified` | `412 Precondition Failed` |
462
+ | `If-None-Match` | `ETag`, weak comparison; lists and `*` supported | Matches: `304` for GET/HEAD, `412` for writes |
463
+ | `If-Modified-Since` (only without `If-None-Match`, GET/HEAD) | `Last-Modified` | Unchanged: `304 Not Modified` |
464
+
465
+ A `304` carries the `ETag`, `Last-Modified`, `Cache-Control` and `Vary` of the `200` it replaces, and no body or `Content-Type`. Errors use the standard envelope (`PRECONDITION_FAILED`, `PRECONDITION_REQUIRED`).
466
+
467
+ ### Automatic ETags
468
+
469
+ With `etag: true` in `exis.config.ts` (or `conditionalGet()` on some routes), every successful `GET`/`HEAD` response sent with `res.json()`, `res.send()` or a returned value gets a weak ETag hashed from its body, and a matching `If-None-Match` is answered with `304`. The handler still runs, so this saves bandwidth, not work. Writes and error responses get no ETag.
470
+
471
+ ### Validators from your data: `res.checkPreconditions()`
472
+
473
+ When the resource has a version or update time, check it **before** doing the work. On reads this skips the query for the body and serialization; on writes it prevents lost updates: a client that edited an old copy gets `412` instead of overwriting someone else's change.
474
+
475
+ ```typescript filename="src/http/documents/[id]/route.ts"
476
+ import { controller, route } from 'exisjs/router'
477
+
478
+ export default controller({
479
+ read: route.get('/', {
480
+ async handle({ params, res }) {
481
+ const meta = await documents.meta(params.id) // cheap: version + updatedAt
482
+ // Sets ETag / Last-Modified; answers 304 and returns true when current
483
+ if (res.checkPreconditions({ etag: `v${meta.version}`, lastModified: meta.updatedAt })) return
484
+ return documents.find(params.id)
485
+ },
486
+ }),
487
+
488
+ update: route.put('/', {
489
+ async handle({ params, body, res }) {
490
+ const current = await documents.meta(params.id)
491
+ // 412 if the client's If-Match is stale; 428 if it sent no precondition
492
+ res.checkPreconditions({ etag: `v${current.version}` }, { required: true })
493
+ const updated = await documents.update(params.id, body)
494
+ res.setHeader('ETag', `"v${updated.version}"`) // the new state
495
+ return updated
496
+ },
497
+ }),
498
+ })
499
+ ```
500
+
501
+ - `etag` may be bare (`v3` is sent as `"v3"`) or a full tag (`W/"v3"`). Use a value that changes whenever the representation changes: a version column, a hash, `updatedAt`.
502
+ - `lastModified` takes a `Date`, a timestamp or an HTTP date. It is rounded to whole seconds, and a time in the future is capped at the current time.
503
+ - For "create only if absent" (`PUT` with `If-None-Match: *`), pass `exists: false` when the resource does not exist yet; an existing one answers `412`.
504
+ - On writes, the old validators are not sent back. Set the new `ETag` after the change, as above.
505
+
506
+ `req.fresh` (and `req.stale`) apply the same rules to the response headers set so far.
507
+
454
508
  ---
455
509
 
456
510
  ## Next Steps
@@ -45,15 +45,16 @@ ExisJS provides type-safe primitives that accept modifier options and configurat
45
45
 
46
46
  | Primitive | Signature | Description |
47
47
  | :--- | :--- | :--- |
48
- | `tex.string()` | `tex.string(options?)` | Validates strings. Supports `{ min, max, trim, toLowerCase, toUpperCase, collapseWhitespace, escapeHtml, stripHtml, slugify, mask, preventSql, preventTraversal, coerce, default }`. |
49
- | `tex.number()` | `tex.number(options?)` | Validates numbers. Supports `{ min, max, coerce, default }`. |
50
- | `tex.boolean()` | `tex.boolean(options?)` | Validates booleans. Supports `{ coerce, default }`. Coerces `"true"`, `"false"`, `"1"`, `"0"`. |
48
+ | `tex.string()` | `tex.string(options?)` | Validates strings. Supports `{ min, max, length, pattern, trim, toLowerCase, toUpperCase, collapseWhitespace, escapeHtml, stripHtml, slugify, mask, preventSql, preventTraversal, coerce, default }`. `pattern` is a `RegExp` checked after trimming and case changes. |
49
+ | `tex.number()` | `tex.number(options?)` | Validates finite numbers. Supports `{ int, min, max, coerce, default }`; `int: true` accepts whole numbers only. |
50
+ | `tex.boolean()` | `tex.boolean(options?)` | Validates booleans. Supports `{ coerce, default }`. Coerces `"true"`, `"false"`, `"1"`, `"0"` (any case, surrounding spaces ignored) and `1` / `0`. |
51
51
  | `tex.email()` | `tex.email(options?)` | Validates RFC-compliant email formatting. Supports `{ trim, toLowerCase, mask, default }`. |
52
+ | `tex.url()` | `tex.url(options?)` | Validates absolute URLs. Only `http` and `https` unless `{ protocols: ['https'] }` says otherwise, so `javascript:` links are rejected. Supports `{ trim, min, max, pattern, default }`. |
52
53
  | `tex.uuid()` | `tex.uuid(options?)` | Validates UUID strings. Supports `{ version: 1 \| 4, default }`. |
53
54
  | `tex.cuid()` | `tex.cuid(options?)` | Validates CUID identifiers. Supports `{ default }`. |
54
55
  | `tex.password()` | `tex.password(options?)` | Validates password complexity. Supports `{ min, max, requireNumbers, requireSymbols, requireUppercase, requireLowercase }`. |
55
56
  | `tex.creditCard()` | `tex.creditCard(options?)` | Validates credit card numbers using the Luhn algorithm. Supports `{ mask: true }`. |
56
- | `tex.date()` | `tex.date(options?)` | Validates JavaScript `Date` instances or ISO strings with `{ coerce: true, minDate, maxDate, default }`. |
57
+ | `tex.date()` | `tex.date(options?)` | Validates a `Date`, a date string or a timestamp, with `{ coerce, minDate, maxDate, default }`. An unparsable value is rejected, and the parsed value is always a `Date` (`coerce` is accepted but no longer needed). |
57
58
  | `tex.array()` | `tex.array(schema, options?)` | Validates arrays of items. Accepts primitive schemas, `tex.object(...)`, or shorthand `{ ... }`. Supports `{ min, max, dedupe, coerce, default }`. |
58
59
  | `tex.enum()` | `tex.enum(values, options?)` | Validates against an allowed list of string literals. Supports `{ default }`. |
59
60
  | `tex.literal()` | `tex.literal(value, options?)` | Validates against an exact primitive value (`string \| number \| boolean`). |
@@ -78,7 +79,7 @@ All primitive builders accept modifier options directly in their configuration a
78
79
  | **Optional** | `{ optional: true }` | `.optional()` | Allows `undefined`. |
79
80
  | **Nullable** | `{ nullable: true }` | `.nullable()` | Allows `null`. |
80
81
  | **Nullish** | `{ nullish: true }` | `.nullish()` | Allows both `null` and `undefined`. |
81
- | **Default** | `{ default: value \| () => value }` | N/A | Used when the field is missing. A client may omit the field, and after parsing it always has a value. |
82
+ | **Default** | `{ default: value \| () => value }` | `.default(value)` | Used when the field is missing (or blank, for a form field). A client may omit the field, and after parsing it always has a value. Object and array defaults are copied, so parses never share them. |
82
83
 
83
84
  ```typescript filename="example.ts"
84
85
  import { tex } from 'exisjs/validator'
@@ -92,6 +93,21 @@ const Schema = tex.object({
92
93
  })
93
94
  ```
94
95
 
96
+ Every method returns a new schema, so a field schema can be shared between objects: `Name.optional()` in one object does not make `Name` optional anywhere else. `parse()` never modifies the object it is given; it returns a new one.
97
+
98
+ Nested object schemas take the same modifiers as methods: `.optional()`, `.nullable()` and `.nullish()`. A plain object literal is also a nested object:
99
+
100
+ ```typescript filename="src/http/leads/schema.ts"
101
+ const Address = tex.object({ city: tex.string(), pin: tex.string({ optional: true }) })
102
+
103
+ export const UpdateLead = tex.object({
104
+ status: tex.enum(['new', 'won', 'lost']),
105
+ address: Address.optional(), // may be left out
106
+ billing: Address.nullish(), // may be left out or sent as null to clear it
107
+ meta: { source: tex.string() }, // same as tex.object({ source: tex.string() })
108
+ })
109
+ ```
110
+
95
111
  ### Input and Output Types
96
112
 
97
113
  A field with a `default` may be left out of a request but is always present after parsing, so the two sides have different types:
@@ -301,20 +317,27 @@ export function sanitizeTitle(input: string): string {
301
317
  | Category | Function | Description |
302
318
  | :--- | :--- | :--- |
303
319
  | **Security** | `sanitize.escapeHtml(val)` | Escapes HTML entities (`&`, `<`, `>`, `"`, `'`). |
304
- | **Security** | `sanitize.stripHtml(val)` | Removes HTML tags and element markup. |
305
- | **Security** | `sanitize.preventSql(val)` | Neutralizes common SQL injection sequences. |
306
- | **Security** | `sanitize.preventTraversal(val)` | Strips directory traversal sequences (`../`, `..\\`). |
307
- | **Masking** | `sanitize.maskEmail(val)` | Masks email local-parts (e.g. `u***r@example.com`). |
320
+ | **Security** | `sanitize.stripHtml(val)` | Removes tags, comments, and `<script>` / `<style>` elements with their content. |
321
+ | **Security** | `sanitize.stripControlChars(val)` | Removes invisible control characters and bidirectional overrides. |
322
+ | **Security** | `sanitize.preventSql(val)` | Rejects common SQL injection probes (the field fails validation). |
323
+ | **Security** | `sanitize.preventTraversal(val)` | Rejects `..` path segments, also percent-encoded, and NUL bytes. |
324
+ | **Masking** | `sanitize.maskEmail(val)` | Masks email local-parts (e.g. `us***@example.com`). |
308
325
  | **Masking** | `sanitize.maskString(val)` | Masks characters leaving only boundary characters visible. |
309
326
  | **Formatting** | `sanitize.trim(val)` | Trims leading and trailing whitespace. |
310
327
  | **Formatting** | `sanitize.toLowerCase(val)` | Converts string to lowercase. |
311
328
  | **Formatting** | `sanitize.toUpperCase(val)` | Converts string to uppercase. |
312
329
  | **Formatting** | `sanitize.collapseWhitespace(val)` | Replaces consecutive whitespace with a single space. |
313
330
  | **Formatting** | `sanitize.slugify(val)` | Converts string into URL-friendly slug format. |
314
- | **Formatting** | `sanitize.truncate(length)(val)` | Truncates string to specified maximum length. |
331
+ | **Formatting** | `sanitize.normalizeEmail(val)` | Trims and lowercases an email address. |
332
+ | **Formatting** | `sanitize.truncate(length)(val)` | Truncates to `length` characters without splitting an emoji. |
333
+ | **Numbers** | `sanitize.round(decimals)(val)`, `sanitize.clamp(min, max)(val)` | Rounds; limits to a range. |
315
334
  | **Arrays & Objects** | `sanitize.dedupe(array)` | Removes duplicate elements using a `Set`. |
316
335
  | **Arrays & Objects** | `sanitize.compact(array)` | Removes `null`, `undefined`, and empty string values. |
317
- | **Arrays & Objects** | `sanitize.omit(keys)(obj)` | Omits specified keys from an object. |
336
+ | **Arrays & Objects** | `sanitize.trimElements(array)`, `sanitize.limitLength(n)(array)` | Trims string items; keeps the first `n` items. |
337
+ | **Arrays & Objects** | `sanitize.pick(keys)(obj)`, `sanitize.omit(keys)(obj)` | Keeps or removes keys (`pick` ignores inherited keys). |
338
+ | **Arrays & Objects** | `sanitize.deepTrim(value)` | Trims every string in a nested object or array. |
339
+
340
+ Sanitizers run before the field's type check. One that throws, such as `preventSql`, fails that field with a `400` (code `SANITIZATION_FAILED`) instead of crashing the request.
318
341
 
319
342
  ---
320
343
 
@@ -340,6 +363,16 @@ export const RegisterSchema = tex.object({
340
363
  })
341
364
  ```
342
365
 
366
+ A field's `.refine()` and `.sanitize()` run wherever the field is: in nested objects, array items and record values too. An object's `.refine(fn, { message, path })` runs once all its fields are valid and reports the error on `path`. `parseAsync()` (used for route validation) also runs `.refineAsync()` rules at every depth.
367
+
368
+ To handle failures without `try` / `catch`, use `safeParse()`:
369
+
370
+ ```typescript
371
+ const result = RegisterSchema.safeParse(input)
372
+ if (!result.success) return { errors: result.errors } // [{ path, message, code, ... }]
373
+ const user = result.data
374
+ ```
375
+
343
376
  ---
344
377
 
345
378
  ## Discriminated Unions (`tex.discriminatedUnion`)
@@ -633,6 +666,8 @@ When incoming requests fail validation, ExisJS returns an HTTP `400 Bad Request`
633
666
  }
634
667
  ```
635
668
 
669
+ Every failing value is listed: each bad item of an array, each bad field of a nested object, up to 100 entries. Common `code` values: `MISSING_REQUIRED_FIELD`, `INVALID_TYPE`, `VALUE_TOO_SMALL`, `VALUE_TOO_LARGE`, `INVALID_LENGTH`, `INVALID_FORMAT` (pattern), `INVALID_EMAIL`, `INVALID_URL`, `INVALID_ENUM_VALUE`, `INVALID_DATE`, `DATE_TOO_EARLY`, `DATE_TOO_LATE`, `ARRAY_TOO_SHORT`, `ARRAY_TOO_LONG`, `UNRECOGNIZED_KEYS` (strict mode), `CUSTOM_VALIDATION` (`refine`), `SANITIZATION_FAILED`.
670
+
636
671
  In development mode (`NODE_ENV=development`), ExisJS also outputs a tabular summary of the failed validation constraints to the terminal.
637
672
 
638
673
  ---
@@ -8,22 +8,22 @@ This directory contains complete, information-dense API references, TypeScript i
8
8
 
9
9
  | Module / Entrypoint | Document | Description |
10
10
  | :------------------ | :------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------- |
11
- | `exisjs` | [`exis.mdx`](file:///z:/projects/framework/exisjs/docs/reference/exis.mdx) | Root application builder (`exis` / `defineApp`), `App` instance methods, and process resolvers. |
12
- | `exisjs/config` | [`config.mdx`](file:///z:/projects/framework/exisjs/docs/reference/config.mdx) | Configuration schema (`ExisConfig`, `WatchConfig`, `DevConfig`), environment parsing, and phases. |
13
- | `exisjs/router` | [`router.mdx`](file:///z:/projects/framework/exisjs/docs/reference/router.mdx) | Functional route builder (`route.*`, `controller`), `Router` engine, and request/response types. |
14
- | `exisjs/decorators` | [`decorators.mdx`](file:///z:/projects/framework/exisjs/docs/reference/decorators.mdx) | Class, method, and parameter decorators for OOP architecture (`@Controller`, `@Get`, `@Body`). |
15
- | `exisjs/validator` | [`validator.mdx`](file:///z:/projects/framework/exisjs/docs/reference/validator.mdx) | `TexEngine` validator suite (`tex.*`), schema modifiers (`.optional()`, `.default()`), and pipes. |
16
- | `exisjs/database` | [`database.mdx`](file:///z:/projects/framework/exisjs/docs/reference/database.mdx) | Database lifecycle coordinator (`registerDatabase`, `DatabaseManager`), and transactions. |
17
- | `exisjs/logger` | [`logger.mdx`](file:///z:/projects/framework/exisjs/docs/reference/logger.mdx) | Structured Pino logger interface, log levels, sensitive credential redaction, and formatters. |
18
- | `exisjs/di` | [`di.mdx`](file:///z:/projects/framework/exisjs/docs/reference/di.mdx) | Inversion of Control `Container`, dependency injection tokens, `inject()`, and `forwardRef()`. |
19
- | `exisjs/error` | [`error.mdx`](file:///z:/projects/framework/exisjs/docs/reference/error.mdx) | HTTP exception hierarchy (`HttpError`, `BadRequestException`), error codes, and JSON envelopes. |
20
- | `exisjs/middleware` | [`middleware.mdx`](file:///z:/projects/framework/exisjs/docs/reference/middleware.mdx) | Built-in middleware suite: Helmet, CORS, CSRF, rate limiting, IP filtering, and multipart uploads. |
21
- | `exisjs/context` | [`context.mdx`](file:///z:/projects/framework/exisjs/docs/reference/context.mdx) | `AsyncLocalStorage` request context API (`getRequest`, `getResponse`, `getContext`, `after`). |
22
- | `exisjs/cron` | [`cron.mdx`](file:///z:/projects/framework/exisjs/docs/reference/cron.mdx) | Task scheduler (`cron()`, `@Cron()`), interval timers, presets, and `CronManager` runtime controls. |
23
- | `exisjs/sanitize` | [`sanitize.mdx`](file:///z:/projects/framework/exisjs/docs/reference/sanitize.mdx) | Security cleaners (`escapeHtml`, `preventSql`) and data transform utilities. |
24
- | `exisjs/swagger` | [`swagger.mdx`](file:///z:/projects/framework/exisjs/docs/reference/swagger.mdx) | OpenAPI 3.1 specification generation, Swagger UI / Scalar interactive rendering, and decorators. |
25
- | `exisjs/testing` | [`testing.mdx`](file:///z:/projects/framework/exisjs/docs/reference/testing.mdx) | Test runner (`node:test`), synthetic HTTP client (`createTestApp`), `expect` matchers, and `ex` mocks. |
26
- | `exisjs/plugin` | [`plugin.mdx`](file:///z:/projects/framework/exisjs/docs/reference/plugin.mdx) | Modular plugin development (`definePlugin`), lifecycle hooks, and dependency encapsulation. |
11
+ | `exisjs` | [`exis.mdx`](./exis.mdx) | Root application builder (`exis` / `defineApp`), `App` instance methods, and process resolvers. |
12
+ | `exisjs/config` | [`config.mdx`](./config.mdx) | Configuration schema (`ExisConfig`, `WatchConfig`, `DevConfig`), environment parsing, and phases. |
13
+ | `exisjs/router` | [`router.mdx`](./router.mdx) | Functional route builder (`route.*`, `controller`), `Router` engine, and request/response types. |
14
+ | `exisjs/decorators` | [`decorators.mdx`](./decorators.mdx) | Class, method, and parameter decorators for OOP architecture (`@Controller`, `@Get`, `@Body`). |
15
+ | `exisjs/validator` | [`validator.mdx`](./validator.mdx) | `TexEngine` validator suite (`tex.*`), schema modifiers (`.optional()`, `.default()`), and pipes. |
16
+ | `exisjs/database` | [`database.mdx`](./database.mdx) | Database lifecycle coordinator (`registerDatabase`, `DatabaseManager`), and transactions. |
17
+ | `exisjs/logger` | [`logger.mdx`](./logger.mdx) | Structured Pino logger interface, log levels, sensitive credential redaction, and formatters. |
18
+ | `exisjs/di` | [`di.mdx`](./di.mdx) | Inversion of Control `Container`, dependency injection tokens, `inject()`, and `forwardRef()`. |
19
+ | `exisjs/error` | [`error.mdx`](./error.mdx) | HTTP exception hierarchy (`HttpError`, `BadRequestException`), error codes, and JSON envelopes. |
20
+ | `exisjs/middleware` | [`middleware.mdx`](./middleware.mdx) | Built-in middleware suite: Helmet, CORS, CSRF, rate limiting, IP filtering, and multipart uploads. |
21
+ | `exisjs/context` | [`context.mdx`](./context.mdx) | `AsyncLocalStorage` request context API (`getRequest`, `getResponse`, `getContext`, `after`). |
22
+ | `exisjs/cron` | [`cron.mdx`](./cron.mdx) | Task scheduler (`cron()`, `@Cron()`), interval timers, presets, and `CronManager` runtime controls. |
23
+ | `exisjs/sanitize` | [`sanitize.mdx`](./sanitize.mdx) | Security cleaners (`escapeHtml`, `preventSql`) and data transform utilities. |
24
+ | `exisjs/swagger` | [`swagger.mdx`](./swagger.mdx) | OpenAPI 3.1 specification generation, Swagger UI / Scalar interactive rendering, and decorators. |
25
+ | `exisjs/testing` | [`testing.mdx`](./testing.mdx) | Test runner (`node:test`), synthetic HTTP client (`createTestApp`), `expect` matchers, and `ex` mocks. |
26
+ | `exisjs/plugin` | [`plugin.mdx`](./plugin.mdx) | Modular plugin development (`definePlugin`), lifecycle hooks, and dependency encapsulation. |
27
27
 
28
28
  ---
29
29
 
@@ -175,7 +175,7 @@ The root configuration schema accepted by `exis.config.ts`:
175
175
  | `trustProxy` | `boolean \| number` | `false` | `true`: trust all proxy headers (`X-Forwarded-For`, `CF-Connecting-IP`, `True-Client-IP`, `X-Real-IP`). `N`: trust only the N closest hops in `X-Forwarded-For`, counting the socket peer; CDN headers are ignored. |
176
176
  | `bodyLimit` | `number` | `10485760` (10MB) | Maximum payload size in bytes for JSON and URL-encoded request bodies. |
177
177
  | `compression` | `boolean` | `false` | Enables gzip and Brotli response compression. |
178
- | `etag` | `boolean` | `false` | Enables ETag computation for dynamic responses. |
178
+ | `etag` | `boolean` | `false` | Weak ETags on successful GET/HEAD responses, and `304` for a matching `If-None-Match`. |
179
179
  | `debugRouting` | `boolean` | `false` | Logs the matched file-system route file and applied boundaries for every request. |
180
180
  | `asyncContext` | `boolean` | `false` | Enables `AsyncLocalStorage` context tracking (`getContext()`). |
181
181
  | `transformResponse` | `boolean \| ((data: any, req: any, res: any) => any)` | `false` | Global transformation function to wrap route responses in a standardized envelope. |
@@ -299,6 +299,7 @@ export interface DevConfig {
299
299
  binaryInterval?: number
300
300
  extensions?: string[]
301
301
  debounceMs?: number
302
+ lazyRoutes?: boolean // default true: load each route file on its first request
302
303
  }
303
304
  ```
304
305
 
@@ -59,6 +59,7 @@ function registerDatabase(
59
59
  | `connect` | `() => Promise<void> \| void` | Asynchronous callback establishing client connection pools. |
60
60
  | `disconnect` | `() => Promise<void> \| void` | Asynchronous callback to safely terminate open sockets and pools. |
61
61
  | `healthCheck` / `isHealthy` | `() => Promise<boolean \| DatabaseHealthInfo> \| boolean \| DatabaseHealthInfo` | Optional callback returning health status boolean or detailed metrics. |
62
+ | `healthTimeoutMs` | `number` | How long the health check may take before it is reported as `status: 'error'`, so an unresponsive database fails `/_ready` instead of hanging it. Default `5000`. |
62
63
 
63
64
  #### Example: Prisma Lifecycle Registration
64
65
 
@@ -97,9 +98,9 @@ Static singleton registry coordinating all registered database adapters.
97
98
  | Method | Signature | Description |
98
99
  | :--- | :--- | :--- |
99
100
  | `register(db)` | `(db: DatabaseRegistration) => void` | Adds a database adapter to the manager. |
100
- | `connectAll()` | `() => Promise<void>` | Connects all registered database instances concurrently. |
101
+ | `connectAll()` | `() => Promise<void>` | Connects all registered databases in parallel, so boot waits for the slowest one rather than the sum. If any fail, the error names each failed database (`error.failures` lists them); the others stay connected and a later call retries only the failed ones. |
101
102
  | `disconnectAll()` | `() => Promise<void>` | Closes all database connections gracefully using `Promise.allSettled`. |
102
- | `checkHealth()` | `() => Promise<{ healthy: boolean; databases: Record<string, DatabaseHealthInfo> }>` | Executes health checks across all registered databases and aggregates results. |
103
+ | `checkHealth()` | `() => Promise<{ healthy: boolean; databases: Record<string, DatabaseHealthInfo> }>` | Runs every database's health check in parallel, each limited by its `healthTimeoutMs`. Calls made while a check is running share its result, so frequent probes from several load balancers cost one round of pings. |
103
104
  | `clear()` | `() => void` | Clears all registered database adapters (useful for test tear-downs). |
104
105
 
105
106
  #### Health Output Structures
@@ -317,6 +317,6 @@ app.use(fromExpress(morgan('combined')))
317
317
  | `requestId()` | `() => Handler` | Injects a unique `X-Request-Id` into request and response headers. |
318
318
  | `requestLogger(options?)` | `(options?: LoggerConfig) => Handler` | Formats and logs request completion timings and status codes. |
319
319
  | `serveStatic(root, options?)` | `(root: string, options?: StaticOptions) => Handler` | Serves static assets from disk with caching headers and streaming backpressure. |
320
- | `conditionalGet()` | `() => Handler` | Enables ETag validation and `304 Not Modified` responses. |
320
+ | `conditionalGet()` | `() => Handler` | Turns on automatic ETags and `304 Not Modified` for the routes it is applied to (as `etag: true` does app-wide). |
321
321
  | `validate(schema)` | `(schema: ValidateSchema) => Handler` | Validates request parameters against a `TexEngine` schema. |
322
322
 
@@ -1,134 +1,129 @@
1
- ---
2
- title: "exisjs/sanitize"
3
- description: "API Reference for ExisJS data sanitization engine: string cleaners, safeSanitize, and object transformers."
4
- nextUrl: "/docs/reference/swagger"
5
- nextTitle: "Swagger Reference"
6
- ---
7
-
8
- # `exisjs/sanitize`
9
-
10
- The `exisjs/sanitize` module provides pure data sanitization functions for cleaning and normalizing input strings, numbers, dates, arrays, and complex objects before business logic execution. It includes security transforms for XSS escaping, HTML stripping, and basic SQL injection and path traversal probe detection.
11
-
12
- ---
13
-
14
- ## Module Exports
15
-
16
- ```typescript
17
- import { sanitize } from 'exisjs/sanitize'
18
- ```
19
-
20
- ---
21
-
22
- ## 1. Security Sanitizers
23
-
24
- The SQL and path traversal checks reject common probe strings; they complement, and never replace, parameterized queries and resolved-path checks.
25
-
26
- | Function | Signature | Description |
27
- | :--- | :--- | :--- |
28
- | `sanitize.escapeHtml` | `(val: string) => string` | Escapes `&`, `<`, `>`, `"`, and `'` into HTML entity equivalents. |
29
- | `sanitize.stripHtml` | `(val: string) => string` | Strips all HTML tags and markup characters from the string. |
30
- | `sanitize.preventSql` | `(val: string) => string` | Neutralizes common SQL injection sequences and comment tokens. |
31
- | `sanitize.preventTraversal` | `(val: string) => string` | Strips directory traversal sequences (`../`, `..\\`, null bytes). |
32
- | `sanitize.maskEmail` | `(val: string) => string` | Obfuscates email addresses (e.g., `al***@example.com`). |
33
- | `sanitize.maskString` | `(val: string) => string` | Masks sensitive string segments for logging or auditing. |
34
-
35
- ---
36
-
37
- ## 2. Text & String Sanitizers
38
-
39
- | Function | Signature | Description |
40
- | :--- | :--- | :--- |
41
- | `sanitize.trim` | `(val: string) => string` | Strips leading and trailing whitespace. |
42
- | `sanitize.toLowerCase` | `(val: string) => string` | Converts all characters to lowercase. |
43
- | `sanitize.toUpperCase` | `(val: string) => string` | Converts all characters to uppercase. |
44
- | `sanitize.collapseWhitespace`| `(val: string) => string` | Collapses consecutive whitespace, tabs, and newlines into a single space. |
45
- | `sanitize.normalizeUnicode` | `(val: string) => string` | Normalizes Unicode characters to Canonical Decomposition followed by Canonical Composition (NFC). |
46
- | `sanitize.slugify` | `(val: string) => string` | Converts strings into clean URL-friendly slugs (`"Hello World!"` $\to$ `"hello-world"`). |
47
- | `sanitize.truncate(length)` | `(length: number) => (val: string) => string` | Truncates strings exceeding the specified maximum length. |
48
- | `sanitize.removeNonAlphanumeric` | `(val: string) => string` | Strips all characters except standard ASCII letters and numbers. |
49
- | `sanitize.normalizeLineEndings` | `(val: string) => string` | Converts Windows CRLF (`\r\n`) to POSIX LF (`\n`). |
50
-
51
- ---
52
-
53
- ## 3. Numbers & Dates
54
-
55
- ### Number Transforms
56
-
57
- | Function | Signature | Description |
58
- | :--- | :--- | :--- |
59
- | `sanitize.round` | `(val: number) => number` | Rounds a float to the nearest integer. |
60
- | `sanitize.clamp(min, max)` | `(min: number, max: number) => (val: number) => number` | Constrains numeric input to fall within `[min, max]`. |
61
- | `sanitize.defaultIfNaN(fallback)` | `(fallback: number) => (val: number) => number` | Returns the fallback number if input evaluates to `NaN`. |
62
-
63
- ### Date Transforms
64
-
65
- | Function | Signature | Description |
66
- | :--- | :--- | :--- |
67
- | `sanitize.normalizeToIsoString` | `(val: Date) => string` | Formats a Date object into an ISO 8601 string representation. |
68
- | `sanitize.stripTime` | `(val: Date) => Date` | Resets hours, minutes, seconds, and milliseconds to `00:00:00.000`. |
69
-
70
- ---
71
-
72
- ## 4. Arrays & Complex Objects
73
-
74
- ### Array Transforms
75
-
76
- | Function | Signature | Description |
77
- | :--- | :--- | :--- |
78
- | `sanitize.dedupe` | `<T>(val: T[]) => T[]` | Removes duplicate values preserving first-seen ordering. |
79
- | `sanitize.compact` | `<T>(val: T[]) => T[]` | Removes `null`, `undefined`, and empty strings (`""`). |
80
- | `sanitize.trimElements` | `(val: string[]) => string[]` | Trims whitespace from all string elements in an array. |
81
- | `sanitize.limitLength(n)` | `<T>(length: number) => (val: T[]) => T[]` | Slices an array to at most `length` elements. |
82
-
83
- ### Object Transforms
84
-
85
- | Function | Signature | Description |
86
- | :--- | :--- | :--- |
87
- | `sanitize.stripUnknownKeys(keys)` | `<T>(allowedKeys: (keyof T)[]) => (val: T) => T` | Strips unlisted properties from an object. |
88
- | `sanitize.deepTrimStringValues` | `<T>(val: T) => T` | Recursively walks an object graph and trims all nested string properties. |
89
- | `sanitize.omit(keys)` | `<T, K extends keyof T>(keys: K[]) => (val: T) => Omit<T, K>` | Removes specified keys from an object. |
90
- | `sanitize.pick(keys)` | `<T, K extends keyof T>(keys: K[]) => (val: T) => Pick<T, K>` | Returns an object containing only the specified keys. |
91
-
92
- ---
93
-
94
- ## 5. Integration with TexEngine Validation
95
-
96
- Sanitizers integrate with `tex` validator chains using the `.sanitize()` modifier. Sanitization occurs **before** rule validation checks:
97
-
98
- ```typescript filename="src/http/articles/schema.ts"
99
- import { tex } from 'exisjs/validator'
100
- import { sanitize } from 'exisjs/sanitize'
101
-
102
- export const CreateArticleSchema = tex.object({
103
- title: tex
104
- .string({ min: 5, max: 120 })
105
- .sanitize(sanitize.trim, sanitize.collapseWhitespace),
106
-
107
- slug: tex
108
- .string()
109
- .sanitize(sanitize.slugify),
110
-
111
- content: tex
112
- .string()
113
- .sanitize(sanitize.stripHtml),
114
-
115
- tags: tex
116
- .array(tex.string())
117
- .sanitize(sanitize.dedupe, sanitize.compact),
118
- })
119
- ```
120
-
121
- ---
122
-
123
- ## 6. Standalone Usage Example
124
-
125
- ```typescript filename="src/services/import.service.ts"
126
- import { sanitize } from 'exisjs/sanitize'
127
-
128
- export function processUserInput(rawContent: string): string {
129
- const step1 = sanitize.stripHtml(rawContent)
130
- const step2 = sanitize.collapseWhitespace(step1)
131
- const clean = sanitize.trim(step2)
132
- return clean
133
- }
134
- ```
1
+ ---
2
+ title: "exisjs/sanitize"
3
+ description: "API Reference for ExisJS data sanitization engine: string cleaners, safeSanitize, and object transformers."
4
+ nextUrl: "/docs/reference/swagger"
5
+ nextTitle: "Swagger Reference"
6
+ ---
7
+
8
+ # `exisjs/sanitize`
9
+
10
+ The `exisjs/sanitize` module provides pure data sanitization functions for cleaning and normalizing input strings, numbers, dates, arrays, and complex objects before business logic execution. It includes security transforms for XSS escaping, HTML stripping, and basic SQL injection and path traversal probe detection.
11
+
12
+ ---
13
+
14
+ ## Module Exports
15
+
16
+ ```typescript
17
+ import { sanitize } from 'exisjs/sanitize'
18
+ ```
19
+
20
+ ---
21
+
22
+ ## 1. Security Sanitizers
23
+
24
+ The SQL and path traversal checks reject common probe strings; they complement, and never replace, parameterized queries and resolved-path checks. A sanitizer that throws inside `.sanitize()` becomes a `400` validation error for that field (code `SANITIZATION_FAILED`).
25
+
26
+ | Function | Signature | Description |
27
+ | :--- | :--- | :--- |
28
+ | `sanitize.escapeHtml` | `(val: string) => string` | Escapes `&`, `<`, `>`, `"`, and `'` into HTML entities. Use it for text shown in HTML. |
29
+ | `sanitize.stripHtml` | `(val: string) => string` | Removes tags, comments, and `<script>` / `<style>` elements with their content. A `<` that does not start a tag (`a < b`) is kept. |
30
+ | `sanitize.stripControlChars` | `(val: string) => string` | Removes invisible control characters (keeps tab and line breaks) and the bidirectional overrides used to disguise text. |
31
+ | `sanitize.preventSql` | `(val: string) => string` | Throws on common SQL injection probes (`' or 1=1`, `union select`, ...). |
32
+ | `sanitize.preventTraversal` | `(val: string) => string` | Throws on a `..` path segment (also `\` separators and percent-encoded forms such as `%2e%2e%2f`) and on NUL bytes. `report..final.pdf` is allowed. |
33
+ | `sanitize.maskEmail` | `(val: string) => string` | Obfuscates email addresses (e.g., `al***@example.com`). |
34
+ | `sanitize.maskString` | `(val: string) => string` | Keeps the first and last two characters (`ab****yz`). |
35
+
36
+ ---
37
+
38
+ ## 2. Text & String Sanitizers
39
+
40
+ | Function | Signature | Description |
41
+ | :--- | :--- | :--- |
42
+ | `sanitize.trim` | `(val: string) => string` | Strips leading and trailing whitespace. |
43
+ | `sanitize.toLowerCase` | `(val: string) => string` | Converts all characters to lowercase. |
44
+ | `sanitize.toUpperCase` | `(val: string) => string` | Converts all characters to uppercase. |
45
+ | `sanitize.collapseWhitespace`| `(val: string) => string` | Collapses consecutive whitespace, tabs, and newlines into a single space. |
46
+ | `sanitize.normalizeUnicode` | `(val: string) => string` | Normalizes to Unicode NFC, so visually equal strings compare equal. |
47
+ | `sanitize.normalizeEmail` | `(val: string) => string` | Trims and lowercases an email address. |
48
+ | `sanitize.slugify` | `(val: string) => string` | URL-friendly slug; accented letters keep their base letter (`"Café Crème"` $ o$ `"cafe-creme"`). |
49
+ | `sanitize.truncate(length)` | `(length: number) => (val: string) => string` | Shortens to `length` characters without splitting an emoji in two. |
50
+ | `sanitize.removeNonAlphanumeric` | `(val: string) => string` | Strips all characters except ASCII letters and digits. |
51
+ | `sanitize.normalizeLineEndings` | `(val: string) => string` | Converts `
52
+ ` and lone `
53
+ `. |
54
+
55
+ ---
56
+
57
+ ## 3. Numbers
58
+
59
+ | Function | Signature | Description |
60
+ | :--- | :--- | :--- |
61
+ | `sanitize.round(decimals?)` | `(decimals = 0) => (val: number) => number` | Rounds to `decimals` places. |
62
+ | `sanitize.clamp(min, max)` | `(min: number, max: number) => (val: number) => number` | Constrains a number to `[min, max]`. |
63
+
64
+ ---
65
+
66
+ ## 4. Arrays & Objects
67
+
68
+ ### Array Transforms
69
+
70
+ | Function | Signature | Description |
71
+ | :--- | :--- | :--- |
72
+ | `sanitize.dedupe` | `<T>(val: T[]) => T[]` | Removes duplicate values preserving first-seen ordering. |
73
+ | `sanitize.compact` | `<T>(val: T[]) => T[]` | Removes `null`, `undefined`, and empty strings (`""`). |
74
+ | `sanitize.trimElements` | `(val: string[]) => string[]` | Trims every string item. |
75
+ | `sanitize.limitLength(n)` | `(length: number) => <T>(val: T[]) => T[]` | Keeps the first `length` items. |
76
+
77
+ ### Object Transforms
78
+
79
+ | Function | Signature | Description |
80
+ | :--- | :--- | :--- |
81
+ | `sanitize.pick(keys)` | `<T, K extends keyof T>(keys: K[]) => (val: T) => Pick<T, K>` | Keeps only the listed keys the object itself has (never inherited ones such as `constructor`). |
82
+ | `sanitize.omit(keys)` | `<T, K extends keyof T>(keys: K[]) => (val: T) => Omit<T, K>` | Removes the listed keys. |
83
+ | `sanitize.deepTrim` | `<T>(val: T) => T` | Trims every string in a nested object or array. |
84
+
85
+ Every sanitizer passes values of another type through unchanged (a string sanitizer returns a number as is, an array sanitizer returns a non-array as is), so they are safe on optional and nullable fields.
86
+
87
+ ---
88
+
89
+ ## 5. Integration with TexEngine Validation
90
+
91
+ Sanitizers integrate with `tex` validator chains using the `.sanitize()` modifier. Sanitization occurs **before** rule validation checks:
92
+
93
+ ```typescript filename="src/http/articles/schema.ts"
94
+ import { tex } from 'exisjs/validator'
95
+ import { sanitize } from 'exisjs/sanitize'
96
+
97
+ export const CreateArticleSchema = tex.object({
98
+ title: tex
99
+ .string({ min: 5, max: 120 })
100
+ .sanitize(sanitize.trim, sanitize.collapseWhitespace),
101
+
102
+ slug: tex
103
+ .string()
104
+ .sanitize(sanitize.slugify),
105
+
106
+ content: tex
107
+ .string()
108
+ .sanitize(sanitize.stripHtml),
109
+
110
+ tags: tex
111
+ .array(tex.string())
112
+ .sanitize(sanitize.dedupe, sanitize.compact),
113
+ })
114
+ ```
115
+
116
+ ---
117
+
118
+ ## 6. Standalone Usage Example
119
+
120
+ ```typescript filename="src/services/import.service.ts"
121
+ import { sanitize } from 'exisjs/sanitize'
122
+
123
+ export function processUserInput(rawContent: string): string {
124
+ const step1 = sanitize.stripHtml(rawContent)
125
+ const step2 = sanitize.collapseWhitespace(step1)
126
+ const clean = sanitize.trim(step2)
127
+ return clean
128
+ }
129
+ ```
@@ -55,8 +55,9 @@ The `tex` instance exposes factory functions for all standard primitives, comple
55
55
 
56
56
  | Method | Return Type | Options Interface | Description |
57
57
  | :--- | :--- | :--- | :--- |
58
- | `tex.string(options?)` | `TexType<string>` | `TexStringOptions` | String validation with trimming, HTML escaping, min/max bounds, masking, coercion, and injection checks. |
59
- | `tex.number(options?)` | `TexType<number>` | `TexNumberOptions` | Number validation with optional string coercion (`coerce: true`) and min/max limits. |
58
+ | `tex.string(options?)` | `TexType<string>` | `TexStringOptions` | String validation with trimming, HTML escaping, min/max/exact `length`, `pattern` (RegExp), masking, coercion, and injection checks. |
59
+ | `tex.number(options?)` | `TexType<number>` | `TexNumberOptions` | Number validation with optional string coercion (`coerce: true`), whole numbers (`int: true`) and min/max limits. |
60
+ | `tex.url(options?)` | `TexType<string>` | `TexUrlOptions` | Absolute URL; `http` / `https` only unless `protocols` lists others. |
60
61
  | `tex.boolean(options?)` | `TexType<boolean>` | `TexBooleanOptions` | Boolean validation with string/numeric coercion (`"true"`, `"1"` -> `true`). |
61
62
  | `tex.email(options?)` | `TexType<string>` | `TexStringOptions` | Standard RFC 5322 email format validation. |
62
63
  | `tex.uuid(options?)` | `TexType<string>` | `{ version?: 1 \| 4 }` | Validates UUID strings (v1 or v4). |
@@ -160,6 +161,8 @@ All `TexType` instances provide fluent modifier and documentation methods:
160
161
  | `.transform(fn)` | `<Out>(fn: (val: T) => Out) => TexTransformed<Out>` | Applies a synchronous transformation to validated field data. |
161
162
  | `.toOpenApi()` | `() => Record<string, any>` | Converts the type definition into an OpenAPI 3.1 schema object. |
162
163
 
164
+ Every method returns a new schema and leaves the original unchanged, so one field schema can be reused in several objects.
165
+
163
166
  ---
164
167
 
165
168
  ## `TexEngine` Schema Object Methods
@@ -168,15 +171,20 @@ All `TexType` instances provide fluent modifier and documentation methods:
168
171
 
169
172
  | Method | Signature | Description |
170
173
  | :--- | :--- | :--- |
174
+ | `optional()` | `() => TexEngine<T \| undefined>` | Used as a field of another object: the field may be left out. |
175
+ | `nullable()` | `() => TexEngine<T \| null>` | Used as a field of another object: the field may be `null`. |
176
+ | `nullish()` | `() => TexEngine<T \| null \| undefined>` | Used as a field of another object: the field may be left out or `null`. |
171
177
  | `partial()` | `() => TexEngine<Partial<T>>` | Returns a copy of the schema with all fields marked optional. |
172
178
  | `pick(keys)` | `<K extends keyof T>(keys: K[]) => TexEngine<Pick<T, K>>` | Creates a new schema containing only the specified keys. |
173
179
  | `omit(keys)` | `<K extends keyof T>(keys: K[]) => TexEngine<Omit<T, K>>` | Creates a new schema omitting the specified keys. |
174
180
  | `extend(shape)` | `<E>(extension: E) => TexEngine<T & ResolveSchema<E>>` | Merges additional or overridden fields into the existing schema. |
175
181
  | `merge(other)` | `<U>(other: TexEngine<U>) => TexEngine<T & U>` | Deeply merges two `TexEngine` schemas into a combined validator. |
182
+ | `refine(fn, opts?)` | `(fn: (data: T) => boolean, opts?: string \| { message?, path? }) => TexEngine<T>` | A check across fields, run once every field is valid; the error is reported on `path`. |
176
183
  | `transform(fn)` | `<Out>(fn: (data: T) => Out) => TexTransformedEngine<T, Out>` | Applies post-validation data reshaping to the entire schema output. |
177
184
  | `parse(data)` | `(data: unknown) => T` | Synchronously parses and validates data, throwing `ValidatorError` on failure. |
178
185
  | `parseAsync(data)` | `(data: unknown) => Promise<T>` | Asynchronously parses and validates data, evaluating async refinements. |
179
- | `safeParse(data)` | `(data: unknown) => { success: true, data: T } \| { success: false, error: ValidatorError }` | Validates data without throwing exceptions. |
186
+ | `safeParse(data)` | `(data: unknown) => { success: true, data: T } \| { success: false, errors, error: ValidatorError }` | Validates data without throwing; `errors` is the list of `{ path, message, code, ... }`. |
187
+ | `safeParseAsync(data)` | `(data: unknown) => Promise<SafeParseResult<T>>` | `safeParse` that also runs async refinements. |
180
188
  | `toOpenApi()` | `() => Record<string, any>` | Translates the schema into an OpenAPI 3.1 JSON schema representation. |
181
189
 
182
190
  ---
@@ -1 +1 @@
1
- {"version":3,"file":"compression.d.ts","sourceRoot":"","sources":["../../src/middleware/compression.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAqB,MAAM,UAAU,CAAA;AAyB1D,wBAAgB,WAAW,IAAI,OAAO,CAoGrC"}
1
+ {"version":3,"file":"compression.d.ts","sourceRoot":"","sources":["../../src/middleware/compression.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAqB,MAAM,UAAU,CAAA;AA0B1D,wBAAgB,WAAW,IAAI,OAAO,CAoGrC"}