@webergency-utils/typechecker 0.1.9 → 0.2.1
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 +1 -1
- package/README.md +307 -277
- 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 +3 -2
- package/dist/index.d.ts +12 -6
- package/dist/index.js +19 -0
- package/dist/plugin.d.ts +7 -0
- package/dist/plugin.js +30 -0
- package/dist/runtime/casing.d.ts +3 -3
- package/dist/runtime/casing.js +4 -4
- 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 +34 -10
- package/dist/runtime/validators.js +915 -212
- package/dist/transformer.js +5 -1
- package/package.json +21 -6
package/README.md
CHANGED
|
@@ -1,38 +1,77 @@
|
|
|
1
1
|
# @webergency-utils/typechecker
|
|
2
2
|
|
|
3
|
-
|
|
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
4
|
|
|
5
|
-
|
|
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
|
|
|
9
|
-
|
|
18
|
+
```typescript
|
|
19
|
+
import { validate, constraint, format } from '@webergency-utils/typechecker';
|
|
10
20
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
- **📈 Coercion & Coalescing**: Extended options for type conversion and single-value array wrapping (highly useful for HTTP Query parameters!).
|
|
21
|
+
interface User {
|
|
22
|
+
id: string & format.UUID;
|
|
23
|
+
name: string & constraint.MinLength<2>;
|
|
24
|
+
age: number & constraint.Minimum<18>;
|
|
25
|
+
}
|
|
17
26
|
|
|
18
|
-
|
|
27
|
+
const input: unknown = {
|
|
28
|
+
id: '550e8400-e29b-41d4-a716-446655440000',
|
|
29
|
+
name: 'Alice',
|
|
30
|
+
age: 25,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Validate input against the User type definition
|
|
34
|
+
const result = validate<User>(input);
|
|
35
|
+
|
|
36
|
+
if (result.success) {
|
|
37
|
+
// TypeScript narrows type to User here
|
|
38
|
+
console.log('User is valid:', result.data);
|
|
39
|
+
} else {
|
|
40
|
+
console.error('Validation failed:', result.errors);
|
|
41
|
+
}
|
|
42
|
+
```
|
|
19
43
|
|
|
20
|
-
## Installation
|
|
44
|
+
## Installation & Setup
|
|
21
45
|
|
|
22
|
-
|
|
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.
|
|
51
|
+
|
|
52
|
+
### 1. Install Dependencies
|
|
53
|
+
|
|
54
|
+
Install the core package, along with `ts-patch` as a development dependency:
|
|
23
55
|
|
|
24
56
|
```bash
|
|
25
57
|
npm install @webergency-utils/typechecker
|
|
26
|
-
npm install -
|
|
58
|
+
npm install --save-dev ts-patch
|
|
27
59
|
```
|
|
28
60
|
|
|
29
|
-
|
|
61
|
+
### 2. Inject Compiler Hook
|
|
30
62
|
|
|
31
|
-
|
|
63
|
+
Run the patcher command to set up `ts-patch` inside your local TypeScript installation:
|
|
32
64
|
|
|
33
|
-
|
|
65
|
+
```bash
|
|
66
|
+
npx ts-patch install
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
> [!NOTE]
|
|
70
|
+
> It is recommended to add `ts-patch install` to your `package.json` `prepare` script so it runs automatically after every dependency installation.
|
|
71
|
+
|
|
72
|
+
### 3. Configure tsconfig.json
|
|
34
73
|
|
|
35
|
-
|
|
74
|
+
Register the typechecker **transformer** (and optionally the language service plugin) under `compilerOptions.plugins` in `tsconfig.json`:
|
|
36
75
|
|
|
37
76
|
```json
|
|
38
77
|
{
|
|
@@ -41,337 +80,328 @@ Update your `tsconfig.json` to include the transformer in the `compilerOptions.p
|
|
|
41
80
|
"module": "NodeNext",
|
|
42
81
|
"moduleResolution": "NodeNext",
|
|
43
82
|
"plugins": [
|
|
44
|
-
{ "transform": "@webergency-utils/typechecker" }
|
|
83
|
+
{ "transform": "@webergency-utils/typechecker/transformer" },
|
|
84
|
+
{ "name": "@webergency-utils/typechecker/plugin" }
|
|
45
85
|
]
|
|
46
86
|
}
|
|
47
87
|
}
|
|
48
88
|
```
|
|
49
89
|
|
|
90
|
+
The `transform` entry is required for AOT validation. The `name` entry enables IDE constraint diagnostics via the language service plugin.
|
|
50
91
|
---
|
|
51
92
|
|
|
52
|
-
##
|
|
93
|
+
## Architecture & Internals
|
|
53
94
|
|
|
54
|
-
|
|
95
|
+
The package utilizes a custom TypeScript compiler transformer and language service plugin to deliver highly efficient type-safe runtime validations.
|
|
55
96
|
|
|
56
|
-
|
|
57
|
-
import { is, assert, assertGuard, validate } from '@webergency-utils/typechecker';
|
|
97
|
+
### Build-Time Compilation Flow
|
|
58
98
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
99
|
+
```mermaid
|
|
100
|
+
graph TD
|
|
101
|
+
A[TypeScript Source Code] --> B[ts-patch / Compiler Hook]
|
|
102
|
+
B --> C[TypeScript compiler plugin / Transformer]
|
|
103
|
+
C --> D[Extract types via compiler TypeChecker]
|
|
104
|
+
D --> E[Generate optimized JS validator function]
|
|
105
|
+
E --> F[Hoisted Validator Registry & MetadataStore registration]
|
|
106
|
+
F --> G[Replace source call with MetadataStore invocation]
|
|
107
|
+
G --> H[Emit optimized JavaScript files]
|
|
108
|
+
```
|
|
63
109
|
|
|
64
|
-
|
|
110
|
+
1. **Build-Time Transformation**: The compiler transformer intercepts calls to validation helper functions (`validate`, `is`, `assert`, `assertGuard`, `jsonSchema`) containing generic arguments.
|
|
111
|
+
2. **Type Extraction & Analysis**: It parses the target TS type structure, extracting intersection constraints, formats, transforms, and defaults recursively.
|
|
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`.
|
|
113
|
+
4. **Call Replacement**: The transformer replaces the original compile-time call expressions with direct, zero-reflection references to the runtime `MetadataStore`.
|
|
65
114
|
|
|
66
|
-
|
|
67
|
-
if (is<Payload>(data)) {
|
|
68
|
-
console.log(data.id); // Narrowed to 'Payload'
|
|
69
|
-
}
|
|
115
|
+
### External dependencies
|
|
70
116
|
|
|
71
|
-
|
|
72
|
-
|
|
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.
|
|
73
120
|
|
|
74
|
-
|
|
75
|
-
assertGuard<Payload>(data);
|
|
76
|
-
console.log(data.id); // Narrowed in-place
|
|
121
|
+
### Static Constraint Diagnostics
|
|
77
122
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
123
|
+
The package includes an IDE / Language Service plugin that statically checks literal values against type constraints during editing or compilation:
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
import { constraint } from '@webergency-utils/typechecker';
|
|
127
|
+
|
|
128
|
+
// This yields a compilation error directly in the IDE:
|
|
129
|
+
// Type '5' is not assignable to type 'number & Minimum<18>'.
|
|
130
|
+
const age: number & constraint.Minimum<18> = 5;
|
|
85
131
|
```
|
|
86
132
|
|
|
87
133
|
---
|
|
88
134
|
|
|
89
|
-
|
|
135
|
+
## Glossary
|
|
136
|
+
|
|
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 that returns `true` if a value already matches `T` (no coercion; `from` is ignored).
|
|
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 already matches `T` and narrows the outer scope (no coercion; `from` is ignored).
|
|
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
|
+
- [`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.
|
|
143
|
+
- [`ResolveDefaults`](src/runtime/tags.ts): A helper type that removes the optional flag (`?`) from properties that have defined default values.
|
|
144
|
+
- [`convertPropertyCasing`](src/runtime/casing.ts): A runtime utility to recursively change the casing of object keys.
|
|
145
|
+
- [`toZodIssues`](src/runtime/validators.ts) / [`groupErrorsByPath`](src/runtime/validators.ts): Transform or group validation errors (including nested union `issues`).
|
|
146
|
+
- [`ZodLikeError`](src/runtime/validators.ts): Error class wrapping validation errors in a structure compatible with libraries expecting Zod errors.
|
|
147
|
+
- [`@webergency-utils/typechecker/transformer`](src/transformer.ts): Required `ts-patch` transform entry for AOT rewrite.
|
|
148
|
+
- [`@webergency-utils/typechecker/plugin`](src/plugin.ts): Optional language-service plugin for IDE static constraint diagnostics.
|
|
90
149
|
|
|
91
|
-
|
|
150
|
+
---
|
|
92
151
|
|
|
93
|
-
|
|
94
|
-
export type ValidationMode = 'strict' | 'relaxed' | 'strip';
|
|
152
|
+
## API Reference
|
|
95
153
|
|
|
96
|
-
|
|
97
|
-
mode?: ValidationMode; // default: 'strict'
|
|
98
|
-
tryConvert?: boolean; // Converts string numbers, booleans, and dates (ideal for query parameters)
|
|
99
|
-
wrapArrays?: boolean; // Wraps a single value into an array if the type expects an array
|
|
100
|
-
}
|
|
101
|
-
```
|
|
154
|
+
### Validation Functions
|
|
102
155
|
|
|
103
|
-
####
|
|
104
|
-
```typescript
|
|
105
|
-
// Relaxed Mode (ignores additional properties)
|
|
106
|
-
const user = assert<User>(data, 'relaxed');
|
|
107
|
-
|
|
108
|
-
// Strip Mode (strips out any unknown properties from returned object)
|
|
109
|
-
const cleanUser = assert<User>(data, 'strip');
|
|
110
|
-
|
|
111
|
-
// Query-String Coercion
|
|
112
|
-
const query = assert<SearchQuery>(rawQuery, {
|
|
113
|
-
mode: 'strip',
|
|
114
|
-
tryConvert: true, // Coerces "18" -> 18, "true" -> true, etc.
|
|
115
|
-
wrapArrays: true // Coerces "tag" -> ["tag"] if tags: string[] is expected
|
|
116
|
-
});
|
|
117
|
-
```
|
|
156
|
+
#### `validate<T>(input: unknown, options?: ValidationMode | ValidationOptions): IValidation<ResolveDefaults<T>>`
|
|
118
157
|
|
|
119
|
-
|
|
158
|
+
Validates input data against type `T` and returns a structured validation result.
|
|
120
159
|
|
|
121
|
-
|
|
160
|
+
- **Parameters**:
|
|
161
|
+
- `input`: The value to validate.
|
|
162
|
+
- `options` (optional): Either a `ValidationMode` string ('strict' | 'relaxed' | 'strip') or a `ValidationOptions` object.
|
|
163
|
+
- **Returns**: `IValidation<ResolveDefaults<T>>` containing validation status, converted/stripped data, and error details.
|
|
164
|
+
- **Example**:
|
|
165
|
+
```typescript
|
|
166
|
+
const result = validate<User>(data, 'strip');
|
|
167
|
+
```
|
|
122
168
|
|
|
123
|
-
|
|
169
|
+
#### `is<T>(input: unknown, options?: ValidationMode | ValidationOptions): input is ResolveDefaults<T>`
|
|
124
170
|
|
|
125
|
-
|
|
171
|
+
A type guard function checking if the input matches type `T`. Does **not** coerce; `from` is ignored.
|
|
126
172
|
|
|
127
|
-
|
|
128
|
-
|
|
173
|
+
- **Parameters**:
|
|
174
|
+
- `input`: The value to check.
|
|
175
|
+
- `options` (optional): Either a `ValidationMode` string or a `ValidationOptions` object (`from` has no effect).
|
|
176
|
+
- **Returns**: `boolean` (`true` if valid, `false` otherwise). Narrows type of `input` to `ResolveDefaults<T>` on success.
|
|
177
|
+
- **Example**:
|
|
178
|
+
```typescript
|
|
179
|
+
if (is<User>(data)) {
|
|
180
|
+
console.log(data.name);
|
|
181
|
+
}
|
|
182
|
+
```
|
|
129
183
|
|
|
130
|
-
|
|
131
|
-
id: string;
|
|
132
|
-
role: "admin" | "user";
|
|
133
|
-
age: number & Minimum<18>;
|
|
134
|
-
metadata: { tag: string } | { priority: number };
|
|
135
|
-
}
|
|
184
|
+
#### `assert<T>(input: unknown, options?: ValidationMode | ValidationOptions): ResolveDefaults<T>`
|
|
136
185
|
|
|
137
|
-
|
|
138
|
-
id: 123, // Error: expected string, got number
|
|
139
|
-
role: "guest", // Error: literal union mismatch
|
|
140
|
-
age: 15, // Error: minimum constraint failed
|
|
141
|
-
metadata: { } // Error: complex union mismatch
|
|
142
|
-
};
|
|
186
|
+
Validates input data and returns it, throwing a validation error on failure.
|
|
143
187
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
188
|
+
- **Parameters**:
|
|
189
|
+
- `input`: The value to validate.
|
|
190
|
+
- `options` (optional): Either a `ValidationMode` string or a `ValidationOptions` object.
|
|
191
|
+
- **Returns**: `ResolveDefaults<T>` (the validated value with defaults resolved).
|
|
192
|
+
- **Throws**: `Error` containing a list of path and constraint failures, or a custom error via `options.errorFactory`.
|
|
193
|
+
- **Example**:
|
|
194
|
+
```typescript
|
|
195
|
+
const user = assert<User>(data);
|
|
196
|
+
```
|
|
150
197
|
|
|
151
|
-
|
|
152
|
-
```json
|
|
153
|
-
{
|
|
154
|
-
"id": {
|
|
155
|
-
"value": 123,
|
|
156
|
-
"errors": ["Type<string>"]
|
|
157
|
-
},
|
|
158
|
-
"role": {
|
|
159
|
-
"value": "guest",
|
|
160
|
-
"errors": [
|
|
161
|
-
"Literal<'admin'>",
|
|
162
|
-
"Literal<'user'>"
|
|
163
|
-
]
|
|
164
|
-
},
|
|
165
|
-
"age": {
|
|
166
|
-
"value": 15,
|
|
167
|
-
"errors": ["Minimum<18>"]
|
|
168
|
-
},
|
|
169
|
-
"metadata": {
|
|
170
|
-
"value": {},
|
|
171
|
-
"errors": ["Type<{tag:string}|{priority:number}>"]
|
|
172
|
-
},
|
|
173
|
-
"metadata.tag": {
|
|
174
|
-
"value": undefined,
|
|
175
|
-
"errors": ["Type<string>"]
|
|
176
|
-
},
|
|
177
|
-
"metadata.priority": {
|
|
178
|
-
"value": undefined,
|
|
179
|
-
"errors": ["Type<number>"]
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
```
|
|
198
|
+
#### `assertGuard<T>(input: unknown, options?: ValidationMode | ValidationOptions): asserts input is ResolveDefaults<T>`
|
|
183
199
|
|
|
184
|
-
|
|
200
|
+
An assertion guard that throws a validation error if the input does not match type `T`. Does **not** coerce; `from` is ignored.
|
|
185
201
|
|
|
186
|
-
|
|
202
|
+
- **Parameters**:
|
|
203
|
+
- `input`: The value to check.
|
|
204
|
+
- `options` (optional): Either a `ValidationMode` string or a `ValidationOptions` object (`from` has no effect).
|
|
205
|
+
- **Returns**: `void`. Narrows the type of `input` in the enclosing scope on success.
|
|
206
|
+
- **Throws**: `Error` if validation fails (or a custom error via `options.errorFactory`).
|
|
207
|
+
- **Example**:
|
|
208
|
+
```typescript
|
|
209
|
+
assertGuard<User>(data);
|
|
210
|
+
```
|
|
187
211
|
|
|
188
|
-
|
|
212
|
+
#### `jsonSchema<T>(): any`
|
|
189
213
|
|
|
190
|
-
|
|
214
|
+
Generates a raw JSON Schema draft-07 object matching type `T` at compile time.
|
|
191
215
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
id: string;
|
|
198
|
-
profile?: {
|
|
199
|
-
details: {
|
|
200
|
-
email: string;
|
|
201
|
-
password: string;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
216
|
+
- **Returns**: `any` (a JSON Schema object).
|
|
217
|
+
- **Example**:
|
|
218
|
+
```typescript
|
|
219
|
+
const userSchema = jsonSchema<User>();
|
|
220
|
+
```
|
|
205
221
|
|
|
206
|
-
|
|
207
|
-
type ValidatedUser = WithModifiers<ExternalUser, {
|
|
208
|
-
'profile.details.email': format.Email;
|
|
209
|
-
'profile.details.password': constraint.MinLength<8>;
|
|
210
|
-
}>;
|
|
211
|
-
```
|
|
222
|
+
---
|
|
212
223
|
|
|
213
|
-
###
|
|
224
|
+
### Utility Functions and Classes
|
|
214
225
|
|
|
215
|
-
|
|
226
|
+
#### `convertPropertyCasing<T, C extends CasingFormat>(obj: T, casing: C, options?: ConvertCasingOptions): ConvertPropertyCasing<T, C>`
|
|
216
227
|
|
|
217
|
-
|
|
218
|
-
import { WithModifiers, constraint } from '@webergency-utils/typechecker';
|
|
219
|
-
|
|
220
|
-
interface DBConfig {
|
|
221
|
-
ssl?: boolean;
|
|
222
|
-
cert?: string;
|
|
223
|
-
auth?: {
|
|
224
|
-
username?: string;
|
|
225
|
-
password?: string;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
+
Recursively converts all property keys of an object to the specified casing format.
|
|
228
229
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
'
|
|
230
|
+
- **Parameters**:
|
|
231
|
+
- `obj`: The source object.
|
|
232
|
+
- `casing`: A `CasingFormat` string value (`'snake_case' | 'SNAKE_CASE' | 'camelCase' | 'camelCaseID' | 'PascalCase' | 'PascalCaseID' | 'kebab-case' | 'dot.case'`).
|
|
233
|
+
- `options` (optional): `ConvertCasingOptions` object.
|
|
234
|
+
- **Returns**: The casing-converted object with updated TypeScript property keys.
|
|
235
|
+
- **Example**:
|
|
236
|
+
```typescript
|
|
237
|
+
const apiResponse = convertPropertyCasing(user, 'camelCase');
|
|
238
|
+
```
|
|
232
239
|
|
|
233
|
-
|
|
234
|
-
'auth.password': constraint.Requires<'.username'>;
|
|
235
|
-
}>;
|
|
236
|
-
```
|
|
240
|
+
#### `toZodIssues(errors: IValidationError[]): any[]`
|
|
237
241
|
|
|
238
|
-
|
|
242
|
+
Converts internal validation errors into Zod-compatible issues. Flattens nested union `issues` into a flat list.
|
|
239
243
|
|
|
240
|
-
|
|
244
|
+
- **Parameters**:
|
|
245
|
+
- `errors`: Array of `IValidationError`.
|
|
246
|
+
- **Returns**: An array of Zod-like issues.
|
|
241
247
|
|
|
242
|
-
|
|
243
|
-
import { validate, constraint, Message } from '@webergency-utils/typechecker';
|
|
248
|
+
#### `groupErrorsByPath(errors: IValidationError[]): Record<string, { value: any, errors: string[] }>`
|
|
244
249
|
|
|
245
|
-
|
|
246
|
-
// Direct message in constraint tag
|
|
247
|
-
age: number & constraint.Minimum<18, "Must be 18 or older">;
|
|
250
|
+
Groups validation errors by path, including nested `issues` from failed unions.
|
|
248
251
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
}
|
|
252
|
-
```
|
|
252
|
+
- **Parameters**:
|
|
253
|
+
- `errors`: Array of `IValidationError`.
|
|
254
|
+
- **Returns**: A map of path → `{ value, errors }`.
|
|
253
255
|
|
|
254
|
-
|
|
256
|
+
#### `coerceQueryNumber(v: any): any` / `coerceQueryBoolean(v: any): any` / `coerceQueryDate(v: any): any` / `coerceJsonDate(v: any): any`
|
|
255
257
|
|
|
256
|
-
|
|
258
|
+
Shared coercion helpers used by `from: 'query'` / `from: 'json'` and by `transform.ToNumber` / `ToBoolean` / `ToDate`.
|
|
257
259
|
|
|
258
|
-
|
|
259
|
-
import { validate, toZodIssues, ZodLikeError } from '@webergency-utils/typechecker';
|
|
260
|
+
#### `class ZodLikeError extends Error`
|
|
260
261
|
|
|
261
|
-
|
|
262
|
-
if (!result.success) {
|
|
263
|
-
// 1. Convert errors array to Zod-compliant issues
|
|
264
|
-
const zodIssues = toZodIssues(result.errors);
|
|
265
|
-
console.log(zodIssues); // [{ code: "custom", path: ["age"], message: "Minimum<18>", received: 15 }]
|
|
262
|
+
An error class wrapper that transforms internal validation errors into a Zod-like error structure.
|
|
266
263
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
264
|
+
- **Constructor**: `constructor(errors: IValidationError[])`
|
|
265
|
+
- **Properties**:
|
|
266
|
+
- `name`: `'ZodError'`
|
|
267
|
+
- `issues`: Zod-like issue array.
|
|
271
268
|
|
|
272
269
|
---
|
|
273
270
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
The library provides validation tags and modifiers grouped into four logical namespaces. These can be imported directly or used via their namespaces (e.g., `constraint.MinLength` vs `MinLength`).
|
|
277
|
-
|
|
278
|
-
### 1. `constraint` Namespace
|
|
279
|
-
Impose structural and value-based constraints on primitives:
|
|
280
|
-
|
|
281
|
-
- `MinLength<N, Msg?>`: Minimum string length.
|
|
282
|
-
- `MaxLength<N, Msg?>`: Maximum string length.
|
|
283
|
-
- `Length<Min, Max>`: Helper combining MinLength and MaxLength.
|
|
284
|
-
- `Pattern<RegExp, Msg?>`: Regular expression validation.
|
|
285
|
-
- `Minimum<N, Msg?>` / `Maximum<N, Msg?>`: Inclusive numeric bounds (supports `number | bigint`).
|
|
286
|
-
- `ExclusiveMinimum<N, Msg?>` / `ExclusiveMaximum<N, Msg?>`: Exclusive numeric bounds.
|
|
287
|
-
- `Range<Min, Max>`: Helper combining Minimum and Maximum.
|
|
288
|
-
- `MultipleOf<N, Msg?>`: Enforces that the value is a multiple of `N`.
|
|
289
|
-
- `MinItems<N, Msg?>` / `MaxItems<N, Msg?>`: Array size constraints.
|
|
290
|
-
- `UniqueItems<Msg?>`: Enforces that all elements in an array or Set are deeply unique.
|
|
291
|
-
- `Custom<Fn, Msg?>`: Executes a custom validation function: `(val, ctx) => boolean`.
|
|
292
|
-
- `Requires<Paths, Msg?>`: Enforces cross-field dependency validation.
|
|
293
|
-
- `Message<Msg>`: Attaches a fallback custom error message to a property.
|
|
294
|
-
|
|
295
|
-
### 2. `format` Namespace
|
|
296
|
-
Pre-defined string formatting shortcuts:
|
|
297
|
-
|
|
298
|
-
- `format.Email`: RFC 5322 Email.
|
|
299
|
-
- `format.UUID`: UUID (v1-v5) format.
|
|
300
|
-
- `format.URL`: Full URL validation (HTTP/HTTPS/FTP).
|
|
301
|
-
- `format.IPv4` / `format.IPv6`: IP address validation.
|
|
302
|
-
- `format.DateTime`: ISO-8601 Date Time string.
|
|
303
|
-
- `format.Date`: ISO Date (YYYY-MM-DD).
|
|
304
|
-
- `format.Time`: Time string (HH:MM:SS).
|
|
305
|
-
- `format.Duration`: ISO-8601 duration (e.g., PT1H).
|
|
306
|
-
- `format.ObjectId`: 24-character MongoDB ObjectId.
|
|
307
|
-
- `format.Byte`: Base64 encoded string.
|
|
308
|
-
- `format.Password`: Always valid string, placeholder for password parameters.
|
|
309
|
-
- `format.Regex`: Valid regular expression string.
|
|
310
|
-
- `format.Hostname`: Valid domain name/hostname.
|
|
311
|
-
- `format.URI`: Valid URI.
|
|
271
|
+
### Interfaces and Types
|
|
312
272
|
|
|
313
|
-
|
|
314
|
-
Coerce or sanitize values before validation:
|
|
273
|
+
#### `interface ValidationOptions`
|
|
315
274
|
|
|
316
|
-
|
|
317
|
-
- `transform.LowerCase`: Converts string to lowercase.
|
|
318
|
-
- `transform.UpperCase`: Converts string to uppercase.
|
|
319
|
-
- `transform.Capitalize`: Capitalizes the first letter of a string.
|
|
320
|
-
- `transform.ToNumber`: Coerces inputs to numbers.
|
|
321
|
-
- `transform.ToBoolean`: Coerces inputs to booleans.
|
|
322
|
-
- `transform.ToDate`: Coerces string timestamp to a Date object.
|
|
323
|
-
- `transform.Custom<Fn>`: Applies a custom mapping function `(val) => any`.
|
|
275
|
+
Configuration options to customize validator behavior.
|
|
324
276
|
|
|
325
|
-
|
|
326
|
-
|
|
277
|
+
| Property | Type | Default | Description |
|
|
278
|
+
| :--- | :--- | :--- | :--- |
|
|
279
|
+
| `mode` | `'strict' \| 'relaxed' \| 'strip'` | `'strict'` | Validation mode strategy (strict key checking, relaxed, or key stripping). |
|
|
280
|
+
| `from` | `'json' \| 'query' \| ((key, value, type) => any)` | `undefined` | Input conversion mode for `validate` / `assert`. `'json'` revives JSON-impossible types (Date, RegExp, bigint, Set, Map). `'query'` also coerces querystring shapes (string→number/boolean, timestamps→Date, etc.). A function is called only on type mismatch. Ignored by `is` / `assertGuard`. |
|
|
281
|
+
| `wrapArrays` | `boolean` | `false` | Wraps non-array values into single-element arrays if an array is expected. |
|
|
282
|
+
| `mutate` | `boolean` | `false` | When true, write validated/coerced values onto the input. Default false: always return new containers. |
|
|
283
|
+
| `schema` | `any` | `undefined` | Custom JSON Schema instance. |
|
|
284
|
+
| `errorFactory` | `(errors: IValidationError[]) => Error` | `undefined` | Custom error factory for `assert` throwing. |
|
|
327
285
|
|
|
328
|
-
|
|
286
|
+
#### `interface IValidation<T>`
|
|
329
287
|
|
|
330
|
-
|
|
288
|
+
Result object returned by `validate`.
|
|
331
289
|
|
|
332
|
-
|
|
290
|
+
- `success`: `boolean`
|
|
291
|
+
- `data?`: `T`
|
|
292
|
+
- `errors?`: `IValidationError[]`
|
|
333
293
|
|
|
334
|
-
|
|
335
|
-
const age: number & Minimum<18> = 18; // OK
|
|
336
|
-
const plain: { age: number } = { age: 5 };
|
|
337
|
-
const tagged: { age: number & Minimum<18> } = plain; // OK
|
|
338
|
-
```
|
|
294
|
+
#### `interface IValidationError`
|
|
339
295
|
|
|
340
|
-
|
|
296
|
+
Details of a validation check failure.
|
|
341
297
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
const dupes: number[] & UniqueItems = [1, 1]; // Error: does not satisfy UniqueItems
|
|
347
|
-
```
|
|
298
|
+
- `path`: `string`
|
|
299
|
+
- `value`: `any`
|
|
300
|
+
- `error`: `string` (the constraint description or custom message)
|
|
301
|
+
- `issues?`: `IValidationError[]` — nested failures (used for unions: one summary error with per-arm details)
|
|
348
302
|
|
|
349
|
-
|
|
303
|
+
#### `type WithModifiers<T, M>`
|
|
350
304
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
email: string & Format<'email'> & transform.Trim & transform.LowerCase;
|
|
357
|
-
password: string & MinLength<8>;
|
|
358
|
-
age: number & Minimum<18> & constraint.Message<"Must be 18+">;
|
|
359
|
-
luckyNumbers: number[] & UniqueItems;
|
|
360
|
-
role: string & tag.Default<"user">;
|
|
361
|
-
}
|
|
362
|
-
```
|
|
305
|
+
Applies constraint, format, or transformation tags to properties of type `T` using a path mapping `M`.
|
|
306
|
+
|
|
307
|
+
- **Generics**:
|
|
308
|
+
- `T`: The baseline type to wrap.
|
|
309
|
+
- `M`: A key-value map where keys are dot-separated paths (e.g., `'profile.email'`) and values are tags.
|
|
363
310
|
|
|
364
311
|
---
|
|
365
312
|
|
|
366
|
-
|
|
313
|
+
### Tags & Modifiers
|
|
314
|
+
|
|
315
|
+
#### `constraint` Namespace
|
|
316
|
+
|
|
317
|
+
Used to apply value constraints to types.
|
|
318
|
+
|
|
319
|
+
| Tag | Description |
|
|
320
|
+
| :--- | :--- |
|
|
321
|
+
| `constraint.MinLength<N, Msg?>` | Restricts string length to $\ge N$. |
|
|
322
|
+
| `constraint.MaxLength<N, Msg?>` | Restricts string length to $\le N$. |
|
|
323
|
+
| `constraint.Length<Min, Max>` | Shorthand for `MinLength<Min> & MaxLength<Max>`. |
|
|
324
|
+
| `constraint.Pattern<Regex, Msg?>` | Validates string using regular expression `Regex`. |
|
|
325
|
+
| `constraint.Minimum<N, Msg?>` | Inclusive minimum value restriction for `number | bigint`. |
|
|
326
|
+
| `constraint.Maximum<N, Msg?>` | Inclusive maximum value restriction for `number | bigint`. |
|
|
327
|
+
| `constraint.Range<Min, Max>` | Shorthand for `Minimum<Min> & Maximum<Max>`. |
|
|
328
|
+
| `constraint.ExclusiveMinimum<N, Msg?>` | Exclusive minimum value restriction for `number | bigint`. |
|
|
329
|
+
| `constraint.ExclusiveMaximum<N, Msg?>` | Exclusive maximum value restriction for `number | bigint`. |
|
|
330
|
+
| `constraint.MultipleOf<N, Msg?>` | Restricts `number | bigint` to multiples of `N`. |
|
|
331
|
+
| `constraint.MinItems<N, Msg?>` | Restricts array length to $\ge N$. |
|
|
332
|
+
| `constraint.MaxItems<N, Msg?>` | Restricts array length to $\le N$. |
|
|
333
|
+
| `constraint.UniqueItems<Msg?>` | Restricts arrays to deeply unique items. |
|
|
334
|
+
| `constraint.Custom<Fn, Msg?>` | Runs a custom validation function: `(val, ctx) => boolean`. |
|
|
335
|
+
| `constraint.Requires<Path | [Paths], Msg?>` | Enforces that other object property paths exist. |
|
|
336
|
+
| `constraint.Message<Msg>` | Fallback custom error message. |
|
|
337
|
+
|
|
338
|
+
#### `format` Namespace
|
|
339
|
+
|
|
340
|
+
Standard formats for string primitives.
|
|
341
|
+
|
|
342
|
+
- `format.Email`: Practical mailbox check (`local@domain`, length limits, DNS-like domain with a real TLD). Not full RFC 5322.
|
|
343
|
+
- `format.IdnEmail`: Like email, but Unicode local/domain labels allowed.
|
|
344
|
+
- `format.UUID`: UUID (v1-v5).
|
|
345
|
+
- `format.URL`: HTTP/HTTPS/FTP URLs.
|
|
346
|
+
- `format.IPv4` / `format.IPv6`: IP addresses.
|
|
347
|
+
- `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`).
|
|
348
|
+
- `format.DateTime`: date-time validated via `new Date(...)`. With `from: 'query'`, returns a `Date`; otherwise keeps the string.
|
|
349
|
+
- `format.ObjectId`: MongoDB 24-character hex ObjectId.
|
|
350
|
+
- `format.Duration`: ISO-8601 duration.
|
|
351
|
+
- `format.Time`: Time string `HH:MM:SS` with a required timezone (`Z` or `±HH:MM`), e.g. `19:55:00Z`.
|
|
352
|
+
- `format.Byte`: Base64 string.
|
|
353
|
+
- `format.Password`: Any string (always valid placeholder).
|
|
354
|
+
- `format.Regex`: Valid regular expression string.
|
|
355
|
+
- `format.Hostname`: ASCII domain name (includes `localhost`).
|
|
356
|
+
- `format.IdnHostname`: Internationalized hostname (Unicode labels).
|
|
357
|
+
- `format.URI`: Absolute URI.
|
|
358
|
+
- `format.UriReference`: Absolute URI or relative reference.
|
|
359
|
+
- `format.IRI`: Absolute IRI (Unicode URI).
|
|
360
|
+
- `format.IriReference`: Absolute IRI or relative reference.
|
|
361
|
+
- `format.UriTemplate`: RFC 6570 URI template.
|
|
362
|
+
- Unknown `Format<'...'>` strings fail validation at runtime.
|
|
363
|
+
|
|
364
|
+
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`, …).
|
|
365
|
+
|
|
366
|
+
#### `transform` Namespace
|
|
367
367
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
368
|
+
Sanitizes and converts input values during validation.
|
|
369
|
+
|
|
370
|
+
- `transform.Trim`: Trims string whitespace.
|
|
371
|
+
- `transform.LowerCase`: Converts string to lowercase.
|
|
372
|
+
- `transform.UpperCase`: Converts string to uppercase.
|
|
373
|
+
- `transform.Capitalize`: Capitalizes the first letter.
|
|
374
|
+
- `transform.ToNumber`: Same coercion as `from: 'query'` for numbers (non-empty numeric strings → `parseFloat`).
|
|
375
|
+
- `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.
|
|
376
|
+
- `transform.ToDate`: Same coercion as `from: 'query'` for dates (parseable strings and finite timestamps).
|
|
377
|
+
- `transform.Custom<Fn>`: Custom mapping function: `(val) => any`.
|
|
378
|
+
|
|
379
|
+
#### `tag` Namespace
|
|
380
|
+
|
|
381
|
+
- `tag.Default<Value>`: Injects `Value` when a property is undefined. Removes the optional modifier (`?`) when resolved with `ResolveDefaults<T>`.
|
|
372
382
|
|
|
373
383
|
---
|
|
374
384
|
|
|
375
|
-
##
|
|
385
|
+
## Troubleshooting
|
|
386
|
+
|
|
387
|
+
### `validate`, `is`, or `assert` throw “transformer was not applied”
|
|
388
|
+
- **Cause**: The compiler transformer did not rewrite the call at build time. Untransformed stubs always throw.
|
|
389
|
+
- **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.
|
|
390
|
+
- **Fix**:
|
|
391
|
+
1. Verify `npx ts-patch install` was executed successfully.
|
|
392
|
+
2. Verify `{ "transform": "@webergency-utils/typechecker/transformer" }` is registered in `tsconfig.json` `compilerOptions.plugins`.
|
|
393
|
+
3. Ensure your bundler or compiler CLI compiles using patched `tsc`.
|
|
394
|
+
|
|
395
|
+
### IDE does not report constraint errors on literals
|
|
396
|
+
- **Cause**: The optional language service plugin is not loaded.
|
|
397
|
+
- **Fix**: Add `{ "name": "@webergency-utils/typechecker/plugin" }` alongside the transformer entry in `tsconfig.json` plugins, and restart the TypeScript language service in your editor.
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
## Maintenance
|
|
402
|
+
|
|
403
|
+
This package is actively maintained.
|
|
376
404
|
|
|
377
|
-
|
|
405
|
+
Bug reports and pull requests are welcome. Security issues and critical
|
|
406
|
+
regressions are prioritized. New features are considered when they align
|
|
407
|
+
with the package's existing scope.
|