@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 +21 -0
- package/README.md +169 -47
- package/dist/engine/generators.d.ts +2 -1
- package/dist/engine/generators.js +37 -11
- package/dist/engine/resolver.js +279 -68
- package/dist/engine/staticAsserts.js +2 -1
- package/dist/index.d.ts +17 -13
- package/dist/index.js +35 -0
- package/dist/runtime/tags/format.d.ts +7 -0
- package/dist/runtime/tags/tag.d.ts +1 -1
- package/dist/runtime/tags.d.ts +1 -1
- package/dist/runtime/validators.d.ts +70 -16
- package/dist/runtime/validators.js +935 -219
- package/dist/transformer.js +37 -58
- package/package.json +12 -3
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
+
[](https://www.npmjs.com/package/@webergency-utils/typechecker)
|
|
6
|
+
[](https://www.npmjs.com/package/@webergency-utils/typechecker)
|
|
7
|
+
[](#maintenance)
|
|
8
|
+
[](https://www.npmjs.com/package/@webergency-utils/typechecker?activeTab=dependencies)
|
|
9
|
+
[](https://www.npmjs.com/package/@webergency-utils/typechecker)
|
|
10
|
+
<br>
|
|
11
|
+
[](https://securityscorecards.dev/viewer/?uri=github.com/webergency-utils/typechecker)
|
|
12
|
+
[](https://codecov.io/gh/webergency-utils/typechecker)
|
|
13
|
+
[](https://github.com/webergency-utils/typechecker/actions/workflows/ci.yml)
|
|
14
|
+
[](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
|
-
|
|
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
|
|
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`
|
|
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](
|
|
117
|
-
- [is](
|
|
118
|
-
- [assert](
|
|
119
|
-
- [assertGuard](
|
|
120
|
-
- [jsonSchema](
|
|
121
|
-
- [
|
|
122
|
-
- [
|
|
123
|
-
- [
|
|
124
|
-
- [
|
|
125
|
-
- [
|
|
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 |
|
|
170
|
+
#### `is<T>(input: unknown, options?: ValidationMode | GuardOptions): input is ResolveDefaults<T>`
|
|
147
171
|
|
|
148
|
-
A type guard
|
|
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 `
|
|
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 |
|
|
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
|
|
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 |
|
|
199
|
+
#### `assertGuard<T>(input: unknown, options?: ValidationMode | AssertGuardOptions): asserts input is ResolveDefaults<T>`
|
|
176
200
|
|
|
177
|
-
An assertion guard
|
|
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
|
|
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
|
-
|
|
336
|
+
Extends `GuardOptions` for `validate` / `validateSchema` (adds `mutate`; no `errorFactory`).
|
|
241
337
|
|
|
242
338
|
| Property | Type | Default | Description |
|
|
243
339
|
| :--- | :--- | :--- | :--- |
|
|
244
|
-
| `mode` | `
|
|
245
|
-
| `
|
|
246
|
-
| `
|
|
247
|
-
|
|
248
|
-
|
|
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,
|
|
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`:
|
|
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`:
|
|
310
|
-
- `format.DateTime`:
|
|
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`:
|
|
318
|
-
- `format.
|
|
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`:
|
|
329
|
-
- `transform.ToBoolean`:
|
|
330
|
-
- `transform.ToDate`:
|
|
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`
|
|
342
|
-
- **Cause**: The compiler transformer did not
|
|
343
|
-
- **Diagnostics Check**: Inspect your built `.js`
|
|
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
|
|
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 =
|
|
121
|
+
return 'v = validators.coerceQueryNumber(v)';
|
|
122
122
|
}
|
|
123
123
|
if (tc.type === 'transform' && tc.value === 'toboolean') {
|
|
124
|
-
return 'v = (v
|
|
124
|
+
return 'v = validators.coerceQueryBoolean(v)';
|
|
125
125
|
}
|
|
126
126
|
if (tc.type === 'transform' && tc.value === 'todate') {
|
|
127
|
-
return '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
|
|
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
|
-
|
|
202
|
-
|
|
203
|
-
validators.
|
|
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 =
|
|
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 (
|
|
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
|
+
}
|