@webergency-utils/typechecker 0.1.10 → 0.2.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2019-2026 Webergency s.r.o.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,8 +1,17 @@
1
1
  # @webergency-utils/typechecker
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/%40webergency-utils%2Ftypechecker)](https://www.npmjs.com/package/@webergency-utils/typechecker) [![Maintenance](https://img.shields.io/badge/maintenance-active-brightgreen.svg)](#maintenance) [![npm downloads](https://img.shields.io/npm/dm/%40webergency-utils%2Ftypechecker)](https://www.npmjs.com/package/@webergency-utils/typechecker) [![License](https://img.shields.io/npm/l/%40webergency-utils%2Ftypechecker)](https://www.npmjs.com/package/@webergency-utils/typechecker)
4
-
5
- An ahead-of-time (AOT) type validation engine and TypeScript compiler plugin that compiles TypeScript types directly into optimized runtime validation functions. It intercepts type definitions at build time to enforce value constraints, formatting, and defaults with zero runtime reflection and no third-party schema dependencies.
3
+ An ahead-of-time (AOT) TypeScript validation engine that compiles types into optimized runtime validators via a compiler transformer—no runtime reflection and no third-party schema library.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/%40webergency-utils%2Ftypechecker)](https://www.npmjs.com/package/@webergency-utils/typechecker)
6
+ [![License](https://img.shields.io/npm/l/%40webergency-utils%2Ftypechecker)](https://www.npmjs.com/package/@webergency-utils/typechecker)
7
+ [![Maintenance](https://img.shields.io/badge/maintenance-active-brightgreen.svg)](#maintenance)
8
+ [![dependencies](https://img.shields.io/badge/dependencies-0-brightgreen.svg)](https://www.npmjs.com/package/@webergency-utils/typechecker?activeTab=dependencies)
9
+ [![npm downloads](https://img.shields.io/npm/dm/%40webergency-utils%2Ftypechecker)](https://www.npmjs.com/package/@webergency-utils/typechecker)
10
+ <br>
11
+ [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/webergency-utils/typechecker/badge)](https://securityscorecards.dev/viewer/?uri=github.com/webergency-utils/typechecker)
12
+ [![codecov](https://codecov.io/gh/webergency-utils/typechecker/branch/main/graph/badge.svg)](https://codecov.io/gh/webergency-utils/typechecker)
13
+ [![tests](https://github.com/webergency-utils/typechecker/actions/workflows/ci.yml/badge.svg)](https://github.com/webergency-utils/typechecker/actions/workflows/ci.yml)
14
+ [![CodeQL](https://github.com/webergency-utils/typechecker/actions/workflows/codeql.yml/badge.svg)](https://github.com/webergency-utils/typechecker/actions/workflows/codeql.yml)
6
15
 
7
16
  ## TL;DR
8
17
 
@@ -34,7 +43,11 @@ if (result.success) {
34
43
 
35
44
  ## Installation & Setup
36
45
 
37
- Since this package is a TypeScript compiler plugin (transformer), you must compile your project using a compiler patcher like `ts-patch` to hook into TS compilation.
46
+ This package is a TypeScript compiler plugin (transformer). You must compile with a compiler patcher such as `ts-patch` so the transformer can hook into `tsc`.
47
+
48
+ **Peer dependency:** `typescript` `>=5.0.0` (required; provides the compiler API the transformer and language service plugin use).
49
+
50
+ There are **no runtime `dependencies`**—only the peer `typescript` and your build tooling.
38
51
 
39
52
  ### 1. Install Dependencies
40
53
 
@@ -58,7 +71,7 @@ npx ts-patch install
58
71
 
59
72
  ### 3. Configure tsconfig.json
60
73
 
61
- Register the typechecker transform plugin under the `plugins` array of `compilerOptions` in your `tsconfig.json`:
74
+ Register the typechecker **transformer** (and optionally the language service plugin) under `compilerOptions.plugins` in `tsconfig.json`:
62
75
 
63
76
  ```json
64
77
  {
@@ -67,12 +80,14 @@ Register the typechecker transform plugin under the `plugins` array of `compiler
67
80
  "module": "NodeNext",
68
81
  "moduleResolution": "NodeNext",
69
82
  "plugins": [
70
- { "transform": "@webergency-utils/typechecker" }
83
+ { "transform": "@webergency-utils/typechecker/transformer" },
84
+ { "name": "@webergency-utils/typechecker/plugin" }
71
85
  ]
72
86
  }
73
87
  }
74
88
  ```
75
89
 
90
+ The `transform` entry is required for AOT validation. The `name` entry enables IDE constraint diagnostics via the language service plugin.
76
91
  ---
77
92
 
78
93
  ## Architecture & Internals
@@ -92,11 +107,17 @@ graph TD
92
107
  G --> H[Emit optimized JavaScript files]
93
108
  ```
94
109
 
95
- 1. **Build-Time Transformation**: The compiler transformer intercepts calls to validation helper functions (`validate`, `is`, `assert`, `assertGuard`, `jsonSchema`) containing generic arguments.
110
+ 1. **Build-Time Transformation**: The compiler transformer intercepts calls to validation helper functions (`validate`, `is`, `assert`, `assertGuard`, `jsonSchema`, `validateSchema`, `isSchema`, `assertSchema`, `assertGuardSchema`).
96
111
  2. **Type Extraction & Analysis**: It parses the target TS type structure, extracting intersection constraints, formats, transforms, and defaults recursively.
97
112
  3. **Hoisted Registry**: The transformer generates highly optimized, direct JavaScript validator functions for each resolved type shape, generates a unique hash, and registers them in a global `MetadataStore`.
98
113
  4. **Call Replacement**: The transformer replaces the original compile-time call expressions with direct, zero-reflection references to the runtime `MetadataStore`.
99
114
 
115
+ ### External dependencies
116
+
117
+ - **Required peer:** `typescript` (`>=5.0.0`) — compiler API for the transformer and optional language service plugin.
118
+ - **Required build tooling:** `ts-patch` (or equivalent) — patches `tsc` so `compilerOptions.plugins` `transform` entries run.
119
+ - **Runtime dependencies:** none.
120
+
100
121
  ### Static Constraint Diagnostics
101
122
 
102
123
  The package includes an IDE / Language Service plugin that statically checks literal values against type constraints during editing or compilation:
@@ -113,16 +134,19 @@ const age: number & constraint.Minimum<18> = 5;
113
134
 
114
135
  ## Glossary
115
136
 
116
- - [validate](file:///Users/tomaskorenko/Projects/Github/webergency-utils/typechecker/src/index.ts#L22): Validates a value against a type, returning a structured result containing the validation status and a detailed list of errors.
117
- - [is](file:///Users/tomaskorenko/Projects/Github/webergency-utils/typechecker/src/index.ts#L19): A type guard function that returns `true` if a value is valid, narrowing its type for TypeScript.
118
- - [assert](file:///Users/tomaskorenko/Projects/Github/webergency-utils/typechecker/src/index.ts#L20): Validates a value and returns it, throwing a validation error on failure.
119
- - [assertGuard](file:///Users/tomaskorenko/Projects/Github/webergency-utils/typechecker/src/index.ts#L21): A type assertion function that throws if a value does not match the target type, narrowing the type in the outer scope.
120
- - [jsonSchema](file:///Users/tomaskorenko/Projects/Github/webergency-utils/typechecker/src/index.ts#L23): Generates and returns a raw JSON Schema draft-07 representation matching a TypeScript type at compile time.
121
- - [WithModifiers](file:///Users/tomaskorenko/Projects/Github/webergency-utils/typechecker/src/runtime/tags.ts#L79): A utility type that applies constraint, format, or transformation tags to properties of deeply nested or external types using dot-separated path mappings.
122
- - [ResolveDefaults](file:///Users/tomaskorenko/Projects/Github/webergency-utils/typechecker/src/runtime/tags.ts#L87): A helper type that removes the optional flag (`?`) from properties that have defined default values.
123
- - [convertPropertyCasing](file:///Users/tomaskorenko/Projects/Github/webergency-utils/typechecker/src/runtime/casing.ts#L190): A runtime utility to recursively change the casing of object keys.
124
- - [toZodIssues](file:///Users/tomaskorenko/Projects/Github/webergency-utils/typechecker/src/runtime/validators.ts#L991): Utility to transform internal typechecker validation errors into Zod-compatible issue structures.
125
- - [ZodLikeError](file:///Users/tomaskorenko/Projects/Github/webergency-utils/typechecker/src/runtime/validators.ts#L1017): Error class wrapping validation errors in a structure compatible with libraries expecting Zod errors.
137
+ - [`validate`](src/index.ts): Validates a value against a type, returning a structured result containing the validation status and a detailed list of errors.
138
+ - [`is`](src/index.ts): A type guard for `T`. Always mutates in place; `from` may coerce nested fields. Root replacement fails the guard.
139
+ - [`assert`](src/index.ts): Validates a value and returns it, throwing a validation error on failure (supports `from` coercion).
140
+ - [`assertGuard`](src/index.ts): Asserts a value is `T`. Always mutates in place; `from` may coerce nested fields. Root replacement throws.
141
+ - [`jsonSchema`](src/index.ts): Generates and returns a JSON Schema representation matching a TypeScript type at compile time (draft-07 shaped, with `x-typescript-type` for Date/RegExp/Set/Map/bigint/etc.).
142
+ - [`validateSchema`](src/index.ts) / [`isSchema`](src/index.ts) / [`assertSchema`](src/index.ts) / [`assertGuardSchema`](src/index.ts): Same entrypoints against a runtime JSON Schema value instead of a TypeScript generic.
143
+ - [`WithModifiers`](src/runtime/tags.ts): A utility type that applies constraint, format, or transformation tags to properties of deeply nested or external types using dot-separated path mappings.
144
+ - [`ResolveDefaults`](src/runtime/tags.ts): A helper type that removes the optional flag (`?`) from properties that have defined default values.
145
+ - [`convertPropertyCasing`](src/runtime/casing.ts): A runtime utility to recursively change the casing of object keys.
146
+ - [`toZodIssues`](src/runtime/validators.ts) / [`groupErrorsByPath`](src/runtime/validators.ts): Transform or group validation errors (including nested union `issues`).
147
+ - [`ZodLikeError`](src/runtime/validators.ts): Error class wrapping validation errors in a structure compatible with libraries expecting Zod errors.
148
+ - [`@webergency-utils/typechecker/transformer`](src/transformer.ts): Required `ts-patch` transform entry for AOT rewrite.
149
+ - [`@webergency-utils/typechecker/plugin`](src/plugin.ts): Optional language-service plugin for IDE static constraint diagnostics.
126
150
 
127
151
  ---
128
152
 
@@ -143,13 +167,13 @@ Validates input data against type `T` and returns a structured validation result
143
167
  const result = validate<User>(data, 'strip');
144
168
  ```
145
169
 
146
- #### `is<T>(input: unknown, options?: ValidationMode | ValidationOptions): input is ResolveDefaults<T>`
170
+ #### `is<T>(input: unknown, options?: ValidationMode | GuardOptions): input is ResolveDefaults<T>`
147
171
 
148
- A type guard function checking if the input matches type `T`.
172
+ A type guard for type `T`. Always mutates in place (no `mutate` option). `from` may coerce nested fields onto the same object; if validation would replace the root value (e.g. primitive `"42"` → `42`), returns `false`.
149
173
 
150
174
  - **Parameters**:
151
175
  - `input`: The value to check.
152
- - `options` (optional): Either a `ValidationMode` string or a `ValidationOptions` object.
176
+ - `options` (optional): Either a `ValidationMode` string or a `GuardOptions` object.
153
177
  - **Returns**: `boolean` (`true` if valid, `false` otherwise). Narrows type of `input` to `ResolveDefaults<T>` on success.
154
178
  - **Example**:
155
179
  ```typescript
@@ -158,13 +182,13 @@ A type guard function checking if the input matches type `T`.
158
182
  }
159
183
  ```
160
184
 
161
- #### `assert<T>(input: unknown, options?: ValidationMode | ValidationOptions): ResolveDefaults<T>`
185
+ #### `assert<T>(input: unknown, options?: ValidationMode | AssertOptions): ResolveDefaults<T>`
162
186
 
163
187
  Validates input data and returns it, throwing a validation error on failure.
164
188
 
165
189
  - **Parameters**:
166
190
  - `input`: The value to validate.
167
- - `options` (optional): Either a `ValidationMode` string or a `ValidationOptions` object.
191
+ - `options` (optional): Either a `ValidationMode` string or an `AssertOptions` object.
168
192
  - **Returns**: `ResolveDefaults<T>` (the validated value with defaults resolved).
169
193
  - **Throws**: `Error` containing a list of path and constraint failures, or a custom error via `options.errorFactory`.
170
194
  - **Example**:
@@ -172,15 +196,15 @@ Validates input data and returns it, throwing a validation error on failure.
172
196
  const user = assert<User>(data);
173
197
  ```
174
198
 
175
- #### `assertGuard<T>(input: unknown, options?: ValidationMode | ValidationOptions): asserts input is ResolveDefaults<T>`
199
+ #### `assertGuard<T>(input: unknown, options?: ValidationMode | AssertGuardOptions): asserts input is ResolveDefaults<T>`
176
200
 
177
- An assertion guard that throws a validation error if the input does not match type `T`.
201
+ An assertion guard for type `T`. Always mutates in place (no `mutate` option). `from` may coerce nested fields; root replacement fails with a normal type error (re-checked without `from`).
178
202
 
179
203
  - **Parameters**:
180
204
  - `input`: The value to check.
181
- - `options` (optional): Either a `ValidationMode` string or a `ValidationOptions` object.
205
+ - `options` (optional): Either a `ValidationMode` string or an `AssertGuardOptions` object.
182
206
  - **Returns**: `void`. Narrows the type of `input` in the enclosing scope on success.
183
- - **Throws**: `Error` if validation fails.
207
+ - **Throws**: `Error` if validation fails (or a custom error via `options.errorFactory`).
184
208
  - **Example**:
185
209
  ```typescript
186
210
  assertGuard<User>(data);
@@ -196,6 +220,31 @@ Generates a raw JSON Schema draft-07 object matching type `T` at compile time.
196
220
  const userSchema = jsonSchema<User>();
197
221
  ```
198
222
 
223
+ #### `validateSchema<T = any>(schema: any, input: unknown, options?: ValidationMode | ValidationOptions): IValidation<T>`
224
+
225
+ Validates `input` against a **runtime JSON Schema** value (not a TypeScript generic).
226
+
227
+ - **Parameters**:
228
+ - `schema`: JSON Schema object.
229
+ - `input`: The value to validate.
230
+ - `options` (optional): Same as `validate`.
231
+ - **Example**:
232
+ ```typescript
233
+ const result = validateSchema({ type: 'string', minLength: 2 }, name);
234
+ ```
235
+
236
+ #### `isSchema(schema: any, input: unknown, options?: ValidationMode | GuardOptions): boolean`
237
+
238
+ Schema type-predicate. Always mutates in place; root replacement fails the guard.
239
+
240
+ #### `assertSchema<T = any>(schema: any, input: unknown, options?: ValidationMode | AssertOptions): T`
241
+
242
+ Like `assert`, but against a JSON Schema value.
243
+
244
+ #### `assertGuardSchema(schema: any, input: unknown, options?: ValidationMode | AssertGuardOptions): void`
245
+
246
+ Like `assertGuard`, but against a JSON Schema value. Root replacement throws.
247
+
199
248
  ---
200
249
 
201
250
  ### Utility Functions and Classes
@@ -216,12 +265,24 @@ Recursively converts all property keys of an object to the specified casing form
216
265
 
217
266
  #### `toZodIssues(errors: IValidationError[]): any[]`
218
267
 
219
- Converts internal validation errors into Zod-compatible issues.
268
+ Converts internal validation errors into Zod-compatible issues. Flattens nested union `issues` into a flat list.
220
269
 
221
270
  - **Parameters**:
222
271
  - `errors`: Array of `IValidationError`.
223
272
  - **Returns**: An array of Zod-like issues.
224
273
 
274
+ #### `groupErrorsByPath(errors: IValidationError[]): Record<string, { value: any, errors: string[] }>`
275
+
276
+ Groups validation errors by path, including nested `issues` from failed unions.
277
+
278
+ - **Parameters**:
279
+ - `errors`: Array of `IValidationError`.
280
+ - **Returns**: A map of path → `{ value, errors }`.
281
+
282
+ #### `coerceQueryNumber(v: any): any` / `coerceQueryBoolean(v: any): any` / `coerceQueryDate(v: any): any` / `coerceJsonDate(v: any): any`
283
+
284
+ Shared coercion helpers used by `from: 'query'` / `from: 'json'` and by `transform.ToNumber` / `ToBoolean` / `ToDate`.
285
+
225
286
  #### `class ZodLikeError extends Error`
226
287
 
227
288
  An error class wrapper that transforms internal validation errors into a Zod-like error structure.
@@ -235,17 +296,64 @@ An error class wrapper that transforms internal validation errors into a Zod-lik
235
296
 
236
297
  ### Interfaces and Types
237
298
 
299
+ #### `type ValidationMode`
300
+
301
+ Controls **unknown object keys only**. It does **not** coerce or revive values — that is exclusively `from`.
302
+
303
+ | Value | Behavior |
304
+ | :--- | :--- |
305
+ | `'strict'` (default) | Reject properties not declared on the type/schema. |
306
+ | `'relaxed'` | Allow unknown properties and keep them on the result. No type conversion. |
307
+ | `'strip'` | Drop unknown properties from the result (in place when mutating). |
308
+
309
+ ```typescript
310
+ // OK: extra keys kept; age stays a string unless you also set from
311
+ validate<User>(data, 'relaxed');
312
+
313
+ // Coercion is a separate axis:
314
+ validate<User>(data, { mode: 'relaxed', from: 'query' });
315
+ ```
316
+
317
+ #### `interface GuardOptions`
318
+
319
+ Options for `is` / `isSchema`. Always mutate in place (no `mutate` / `errorFactory`).
320
+
321
+ | Property | Type | Default | Description |
322
+ | :--- | :--- | :--- | :--- |
323
+ | `mode` | `ValidationMode` | `'strict'` | Unknown-key policy (`strict` / `relaxed` / `strip`). Not coercion — see `ValidationMode` above. |
324
+ | `from` | `'json' \| 'query' \| ((val, ctx) => any)` | `undefined` | In-place coercion for nested fields. Custom callbacks receive `(val, PathContext & { kind: CoercionKind })`. Root replacement fails the guard. |
325
+
326
+ #### `interface AssertGuardOptions`
327
+
328
+ Extends `GuardOptions` for `assertGuard` / `assertGuardSchema`.
329
+
330
+ | Property | Type | Default | Description |
331
+ | :--- | :--- | :--- | :--- |
332
+ | `errorFactory` | `(errors: IValidationError[]) => Error` | `undefined` | Custom error factory when the guard throws. |
333
+
238
334
  #### `interface ValidationOptions`
239
335
 
240
- Configuration options to customize validator behavior.
336
+ Extends `GuardOptions` for `validate` / `validateSchema` (adds `mutate`; no `errorFactory`).
241
337
 
242
338
  | Property | Type | Default | Description |
243
339
  | :--- | :--- | :--- | :--- |
244
- | `mode` | `'strict' \| 'relaxed' \| 'strip'` | `'strict'` | Validation mode strategy (strict key checking, relaxed, or key stripping). |
245
- | `tryConvert` | `boolean` | `false` | Coerces compatible input primitives (e.g. numeric strings to numbers, ISO strings to Date). |
246
- | `wrapArrays` | `boolean` | `false` | Wraps non-array values into single-element arrays if an array is expected. |
247
- | `schema` | `any` | `undefined` | Custom JSON Schema instance. |
248
- | `errorFactory` | `(errors: IValidationError[]) => Error` | `undefined` | Custom error factory for `assert` throwing. |
340
+ | `mode` | `ValidationMode` | `'strict'` | Unknown-key policy (`strict` / `relaxed` / `strip`). Not coercion — see `ValidationMode` above. |
341
+ | `from` | `'json' \| 'query' \| ((val, ctx) => any)` | `undefined` | Input conversion mode. `'json'` revives JSON-impossible types. `'query'` also coerces querystring shapes. A custom function is `(val, { key, path, parent, root, index?, kind }) => any` and runs only on type mismatch. `kind` is a `CoercionKind` dispatch tag (not `typeof` / a TS type). `key` is the nearest named path segment (for `[n]` leaves, the closest named key above). |
342
+ | `mutate` | `boolean` | `false` | `true`: always write onto the input. `false`: always allocate new containers. |
343
+
344
+ #### `interface AssertOptions`
345
+
346
+ Extends `ValidationOptions` for `assert` / `assertSchema`.
347
+
348
+ | Property | Type | Default | Description |
349
+ | :--- | :--- | :--- | :--- |
350
+ | `errorFactory` | `(errors: IValidationError[]) => Error` | `undefined` | Custom error factory when assert throws. |
351
+
352
+ #### `type CoercionKind` / `interface PathContext` / `type FromCoercionContext`
353
+
354
+ - `CoercionKind`: expected runtime kind for custom `from` (`'Date' \| 'Array' \| …`) — a dispatch tag, not `typeof`.
355
+ - `PathContext`: `{ key, path, parent, root, index? }` shared by `constraint.Custom` and custom `from`.
356
+ - `FromCoercionContext`: `PathContext & { kind: CoercionKind }`.
249
357
 
250
358
  #### `interface IValidation<T>`
251
359
 
@@ -262,6 +370,7 @@ Details of a validation check failure.
262
370
  - `path`: `string`
263
371
  - `value`: `any`
264
372
  - `error`: `string` (the constraint description or custom message)
373
+ - `issues?`: `IValidationError[]` — nested failures (used for unions: one summary error with per-arm details)
265
374
 
266
375
  #### `type WithModifiers<T, M>`
267
376
 
@@ -294,7 +403,7 @@ Used to apply value constraints to types.
294
403
  | `constraint.MinItems<N, Msg?>` | Restricts array length to $\ge N$. |
295
404
  | `constraint.MaxItems<N, Msg?>` | Restricts array length to $\le N$. |
296
405
  | `constraint.UniqueItems<Msg?>` | Restricts arrays to deeply unique items. |
297
- | `constraint.Custom<Fn, Msg?>` | Runs a custom validation function: `(val, ctx) => boolean`. |
406
+ | `constraint.Custom<Fn, Msg?>` | Runs a custom validation function: `(val, PathContext) => boolean` (`key`, `path`, `parent`, `root`, optional `index`). |
298
407
  | `constraint.Requires<Path | [Paths], Msg?>` | Enforces that other object property paths exist. |
299
408
  | `constraint.Message<Msg>` | Fallback custom error message. |
300
409
 
@@ -302,20 +411,29 @@ Used to apply value constraints to types.
302
411
 
303
412
  Standard formats for string primitives.
304
413
 
305
- - `format.Email`: RFC-5322 email.
414
+ - `format.Email`: Practical mailbox check (`local@domain`, length limits, DNS-like domain with a real TLD). Not full RFC 5322.
415
+ - `format.IdnEmail`: Like email, but Unicode local/domain labels allowed.
306
416
  - `format.UUID`: UUID (v1-v5).
307
417
  - `format.URL`: HTTP/HTTPS/FTP URLs.
308
418
  - `format.IPv4` / `format.IPv6`: IP addresses.
309
- - `format.Date`: ISO date `YYYY-MM-DD`.
310
- - `format.DateTime`: ISO date-time.
419
+ - `format.Date`: `YYYY-MM-DD` validated via `new Date(...)` (calendar overflow like `2024-02-31` is allowed). With `from: 'query'`, the runtime value becomes a `Date` (the TypeScript type remains `string & format.Date`).
420
+ - `format.DateTime`: date-time validated via `new Date(...)`. With `from: 'query'`, returns a `Date`; otherwise keeps the string.
311
421
  - `format.ObjectId`: MongoDB 24-character hex ObjectId.
312
422
  - `format.Duration`: ISO-8601 duration.
313
- - `format.Time`: Time string `HH:MM:SS`.
423
+ - `format.Time`: Time string `HH:MM:SS` with a required timezone (`Z` or `±HH:MM`), e.g. `19:55:00Z`.
314
424
  - `format.Byte`: Base64 string.
315
425
  - `format.Password`: Any string (always valid placeholder).
316
426
  - `format.Regex`: Valid regular expression string.
317
- - `format.Hostname`: Valid domain name.
318
- - `format.URI`: Valid URI.
427
+ - `format.Hostname`: ASCII domain name (includes `localhost`).
428
+ - `format.IdnHostname`: Internationalized hostname (Unicode labels).
429
+ - `format.URI`: Absolute URI.
430
+ - `format.UriReference`: Absolute URI or relative reference.
431
+ - `format.IRI`: Absolute IRI (Unicode URI).
432
+ - `format.IriReference`: Absolute IRI or relative reference.
433
+ - `format.UriTemplate`: RFC 6570 URI template.
434
+ - Unknown `Format<'...'>` strings fail validation at runtime.
435
+
436
+ Object and record shapes require **plain objects** (`Object.prototype` or `null` prototype). Class instances, `Date`, `Map`, `Set`, typed arrays, etc. are rejected unless the type is a dedicated instance type (`Date`, `Map`, …).
319
437
 
320
438
  #### `transform` Namespace
321
439
 
@@ -325,9 +443,9 @@ Sanitizes and converts input values during validation.
325
443
  - `transform.LowerCase`: Converts string to lowercase.
326
444
  - `transform.UpperCase`: Converts string to uppercase.
327
445
  - `transform.Capitalize`: Capitalizes the first letter.
328
- - `transform.ToNumber`: Coerces inputs to numbers.
329
- - `transform.ToBoolean`: Coerces inputs to booleans.
330
- - `transform.ToDate`: Coerces inputs to Date objects.
446
+ - `transform.ToNumber`: Same coercion as `from: 'query'` for numbers (non-empty numeric strings → `parseFloat`).
447
+ - `transform.ToBoolean`: Same coercion as `from: 'query'` for booleans (`true`/`false`/`1`/`0`/`yes`/`no`/`on`/`off`); unknown values are left unchanged and fail the boolean check.
448
+ - `transform.ToDate`: Same coercion as `from: 'query'` for dates (parseable strings and finite timestamps).
331
449
  - `transform.Custom<Fn>`: Custom mapping function: `(val) => any`.
332
450
 
333
451
  #### `tag` Namespace
@@ -338,14 +456,18 @@ Sanitizes and converts input values during validation.
338
456
 
339
457
  ## Troubleshooting
340
458
 
341
- ### `validate`, `is`, or `assert` calls return empty results or throw at runtime
342
- - **Cause**: The compiler transformer did not execute during build.
343
- - **Diagnostics Check**: Inspect your built `.js` code. If the output still contains `validate<User>(data)` or other generic validation calls as functions, compilation was bypassed.
459
+ ### `validate`, `is`, or `assert` throw “transformer was not applied”
460
+ - **Cause**: The compiler transformer did not rewrite the call at build time. Untransformed stubs always throw.
461
+ - **Diagnostics Check**: Inspect your built `.js` output. If it still contains `validate(...)` / `is(...)` / `assert(...)` as package imports rather than `MetadataStore.validate(...)` (etc.), the transformer did not run.
344
462
  - **Fix**:
345
463
  1. Verify `npx ts-patch install` was executed successfully.
346
- 2. Verify the transformer plugin is registered in `tsconfig.json`.
464
+ 2. Verify `{ "transform": "@webergency-utils/typechecker/transformer" }` is registered in `tsconfig.json` `compilerOptions.plugins`.
347
465
  3. Ensure your bundler or compiler CLI compiles using patched `tsc`.
348
466
 
467
+ ### IDE does not report constraint errors on literals
468
+ - **Cause**: The optional language service plugin is not loaded.
469
+ - **Fix**: Add `{ "name": "@webergency-utils/typechecker/plugin" }` alongside the transformer entry in `tsconfig.json` plugins, and restart the TypeScript language service in your editor.
470
+
349
471
  ---
350
472
 
351
473
  ## Maintenance
@@ -11,7 +11,7 @@ export declare function createLiteralCheck(value: string | number | boolean | ts
11
11
  export declare function createArrayCheck(elementValidator: ts.Expression, requiredUtils: Set<string>): ts.Expression;
12
12
  export declare function createTemplateLiteralCheck(regexStr: string, expected: string, requiredUtils: Set<string>): ts.Expression;
13
13
  export declare function createUnionCheck(checks: ts.Expression[], requiredUtils: Set<string>, expected?: string): ts.Expression;
14
- export declare function createObjectCheck(props: any[], requiredUtils: Set<string>, expected?: string): ts.Expression;
14
+ export declare function createObjectCheck(props: any[], requiredUtils: Set<string>, expected?: string, indexValidator?: ts.Expression): ts.Expression;
15
15
  export declare function createRecordCheck(valueValidator: ts.Expression, requiredUtils: Set<string>): ts.Expression;
16
16
  export declare function createTupleCheck(checks: ts.Expression[], requiredUtils: Set<string>): ts.Expression;
17
17
  export declare function createDateCheck(requiredUtils: Set<string>): ts.Expression;
@@ -21,3 +21,4 @@ export declare function createUndefinedCheck(requiredUtils: Set<string>): ts.Exp
21
21
  export declare function createIntersectionCheck(checks: ts.Expression[], requiredUtils: Set<string>): ts.Expression;
22
22
  export declare function createSetCheck(elementValidator: ts.Expression, requiredUtils: Set<string>): ts.Expression;
23
23
  export declare function createMapCheck(keyValidator: ts.Expression, valueValidator: ts.Expression, requiredUtils: Set<string>): ts.Expression;
24
+ export declare function createInstanceOfCheck(typeName: string, requiredUtils: Set<string>): ts.Expression;
@@ -79,7 +79,7 @@ export function createConstrainedPrimitiveCheck(baseType, constraints, requiredU
79
79
  return `validators.pattern(v, path, ctx, new RegExp(${JSON.stringify(c.value)}), ${JSON.stringify('Pattern<' + c.value + '>')}${msgArg})`;
80
80
  }
81
81
  if (c.type === 'format') {
82
- return `validators.format(v, path, ctx, ${JSON.stringify(c.value)}${msgArg})`;
82
+ return `v = validators.format(v, path, ctx, ${JSON.stringify(c.value)}${msgArg})`;
83
83
  }
84
84
  if (c.type === 'minItems') {
85
85
  return `validators.minItems(v, path, ctx, ${valStr}${msgArg})`;
@@ -118,13 +118,13 @@ export function createConstrainedPrimitiveCheck(baseType, constraints, requiredU
118
118
  return 'if (typeof v === \'string\' && v.length > 0) v = v.charAt(0).toUpperCase() + v.slice(1)';
119
119
  }
120
120
  if (tc.type === 'transform' && tc.value === 'tonumber') {
121
- return 'v = Number(v)';
121
+ return 'v = validators.coerceQueryNumber(v)';
122
122
  }
123
123
  if (tc.type === 'transform' && tc.value === 'toboolean') {
124
- return 'v = (v === \'true\' || v === \'1\' || v === true || v === 1)';
124
+ return 'v = validators.coerceQueryBoolean(v)';
125
125
  }
126
126
  if (tc.type === 'transform' && tc.value === 'todate') {
127
- return 'v = new Date(v)';
127
+ return 'v = validators.coerceQueryDate(v)';
128
128
  }
129
129
  if (tc.type === 'transform_custom') {
130
130
  return `v = ${tc.value}(v)`;
@@ -188,19 +188,39 @@ export function createUnionCheck(checks, requiredUtils, expected = 'Type<Union>'
188
188
  ts.factory.createStringLiteral(expected)
189
189
  ]));
190
190
  }
191
- export function createObjectCheck(props, requiredUtils, expected = 'object') {
191
+ export function createObjectCheck(props, requiredUtils, expected = 'object', indexValidator) {
192
192
  requiredUtils.add('validators');
193
- const propDefinitions = props.map((p, i) => ts.factory.createArrayLiteralExpression([
193
+ const propDefinitions = props.map((p) => ts.factory.createArrayLiteralExpression([
194
194
  ts.factory.createStringLiteral(p.name),
195
195
  p.isOptional ? ts.factory.createTrue() : ts.factory.createFalse(),
196
196
  p.validator
197
197
  ]));
198
198
  const allowedKeys = props.map(p => ts.factory.createStringLiteral(p.name));
199
+ if (indexValidator) {
200
+ const tpl = `
201
+ (v, path, ctx) => {
202
+ const obj = validators.object(v, path, ctx, undefined, __EXPECTED__);
203
+ if (obj === false) return v;
204
+ const data = validators.objectShell(obj, ctx);
205
+ validators.props(obj, data, path, ctx, __PROPS__);
206
+ validators.additionalProps(obj, data, path, ctx, __KEYS__, __INDEX__);
207
+ return data;
208
+ }
209
+ `;
210
+ return injectNodes(templateToAst(tpl), {
211
+ '__KEYS__': ts.factory.createArrayLiteralExpression(allowedKeys),
212
+ '__EXPECTED__': ts.factory.createStringLiteral(expected),
213
+ '__PROPS__': ts.factory.createArrayLiteralExpression(propDefinitions, true),
214
+ '__INDEX__': indexValidator
215
+ });
216
+ }
199
217
  const tpl = `
200
218
  (v, path, ctx) => {
201
- if (!validators.object(v, path, ctx, __KEYS__, __EXPECTED__)) return v;
202
- const data = ctx.mode === 'strip' ? {} : v;
203
- validators.props(v, data, path, ctx, __PROPS__);
219
+ const obj = validators.object(v, path, ctx, __KEYS__, __EXPECTED__);
220
+ if (obj === false) return v;
221
+ const data = validators.objectShell(obj, ctx);
222
+ validators.props(obj, data, path, ctx, __PROPS__);
223
+ validators.stripExtras(data, ctx, __KEYS__);
204
224
  return data;
205
225
  }
206
226
  `;
@@ -243,10 +263,11 @@ export function createIntersectionCheck(checks, requiredUtils) {
243
263
  const tpl = `
244
264
  (v, path, ctx) => {
245
265
  const checks = __CHECKS__;
246
- let data = ctx.mode === "strip" ? (typeof v === "object" && v !== null && !Array.isArray(v) ? {} : v) : v;
266
+ let data = validators.objectShell(v, ctx);
247
267
  for (let i = 0; i < checks.length; i++) {
248
268
  const val = checks[i](v, path, ctx);
249
- if (ctx.mode === "strip" && typeof val === "object" && val !== null) Object.assign(data, val);
269
+ if (typeof val === "object" && val !== null && !Array.isArray(val) && typeof data === "object" && data !== null && !Array.isArray(data)) Object.assign(data, val);
270
+ else data = val;
250
271
  }
251
272
  return data;
252
273
  }
@@ -265,3 +286,8 @@ export function createMapCheck(keyValidator, valueValidator, requiredUtils) {
265
286
  const tpl = '(v, path, ctx) => validators.map(v, path, ctx, __KEY__, __VALUE__)';
266
287
  return injectNodes(templateToAst(tpl), { '__KEY__': keyValidator, '__VALUE__': valueValidator });
267
288
  }
289
+ export function createInstanceOfCheck(typeName, requiredUtils) {
290
+ requiredUtils.add('validators');
291
+ const tpl = `(v, path, ctx) => validators.instanceOf(v, path, ctx, ${JSON.stringify(typeName)})`;
292
+ return templateToAst(tpl);
293
+ }