@webergency-utils/typechecker 0.1.9 → 0.1.10

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/README.md CHANGED
@@ -1,38 +1,64 @@
1
1
  # @webergency-utils/typechecker
2
2
 
3
- `@webergency-utils/typechecker` is a high-performance, zero-runtime-dependency TypeScript compiler plugin (transformer) that converts your static TypeScript types into optimized, hashed, and hoisted runtime validators.
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
4
 
5
- It is designed to work seamlessly with the `@webergency-utils/server` library, automatically enforcing strict data validation at compile time, and provides standard validation API wrappers matching `typia` with extended options for coercion and array handling.
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.
6
6
 
7
- ---
7
+ ## TL;DR
8
8
 
9
- ## Features
9
+ ```typescript
10
+ import { validate, constraint, format } from '@webergency-utils/typechecker';
10
11
 
11
- - **⚡ Blazing Fast**: No runtime schema parsing or generic reflection. Code is generated at compile time as highly optimized JavaScript pipelines.
12
- - **📦 Zero Dependency**: The generated code has absolutely zero external dependencies.
13
- - **🔄 Advanced Type Checking**: Full support for Unions, Intersections, Nested Objects, Tuples, and Optional Properties.
14
- - **🏷️ Tag-Based Validation**: Custom JSON-Schema validation tags directly inside your TypeScript types (e.g. `MinLength<8>`, `Format<'email'>`).
15
- - **🛡️ Multiple Validation Modes**: Easily switch between `'strict'`, `'relaxed'`, and `'strip'` modes.
16
- - **📈 Coercion & Coalescing**: Extended options for type conversion and single-value array wrapping (highly useful for HTTP Query parameters!).
12
+ interface User {
13
+ id: string & format.UUID;
14
+ name: string & constraint.MinLength<2>;
15
+ age: number & constraint.Minimum<18>;
16
+ }
17
17
 
18
- ---
18
+ const input: unknown = {
19
+ id: '550e8400-e29b-41d4-a716-446655440000',
20
+ name: 'Alice',
21
+ age: 25,
22
+ };
23
+
24
+ // Validate input against the User type definition
25
+ const result = validate<User>(input);
26
+
27
+ if (result.success) {
28
+ // TypeScript narrows type to User here
29
+ console.log('User is valid:', result.data);
30
+ } else {
31
+ console.error('Validation failed:', result.errors);
32
+ }
33
+ ```
34
+
35
+ ## Installation & Setup
36
+
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.
19
38
 
20
- ## Installation
39
+ ### 1. Install Dependencies
21
40
 
22
- Since this library is a TypeScript compiler plugin, you will need a tool like `ts-patch` or `ts-node` to hook into the compilation process.
41
+ Install the core package, along with `ts-patch` as a development dependency:
23
42
 
24
43
  ```bash
25
44
  npm install @webergency-utils/typechecker
26
- npm install -D ts-patch
45
+ npm install --save-dev ts-patch
27
46
  ```
28
47
 
29
- Run `ts-patch install` to patch your local TypeScript installation.
48
+ ### 2. Inject Compiler Hook
30
49
 
31
- ---
50
+ Run the patcher command to set up `ts-patch` inside your local TypeScript installation:
32
51
 
33
- ## Configuration
52
+ ```bash
53
+ npx ts-patch install
54
+ ```
34
55
 
35
- Update your `tsconfig.json` to include the transformer in the `compilerOptions.plugins` array:
56
+ > [!NOTE]
57
+ > It is recommended to add `ts-patch install` to your `package.json` `prepare` script so it runs automatically after every dependency installation.
58
+
59
+ ### 3. Configure tsconfig.json
60
+
61
+ Register the typechecker transform plugin under the `plugins` array of `compilerOptions` in your `tsconfig.json`:
36
62
 
37
63
  ```json
38
64
  {
@@ -49,329 +75,283 @@ Update your `tsconfig.json` to include the transformer in the `compilerOptions.p
49
75
 
50
76
  ---
51
77
 
52
- ## Usage
78
+ ## Architecture & Internals
53
79
 
54
- You can validate unknown data anywhere in your code. The transformer intercepts these calls and replaces them with direct, optimized validation functions.
80
+ The package utilizes a custom TypeScript compiler transformer and language service plugin to deliver highly efficient type-safe runtime validations.
55
81
 
56
- ```typescript
57
- import { is, assert, assertGuard, validate } from '@webergency-utils/typechecker';
82
+ ### Build-Time Compilation Flow
58
83
 
59
- interface Payload {
60
- id: string;
61
- active: boolean;
62
- }
84
+ ```mermaid
85
+ graph TD
86
+ A[TypeScript Source Code] --> B[ts-patch / Compiler Hook]
87
+ B --> C[TypeScript compiler plugin / Transformer]
88
+ C --> D[Extract types via compiler TypeChecker]
89
+ D --> E[Generate optimized JS validator function]
90
+ E --> F[Hoisted Validator Registry & MetadataStore registration]
91
+ F --> G[Replace source call with MetadataStore invocation]
92
+ G --> H[Emit optimized JavaScript files]
93
+ ```
63
94
 
64
- const data: unknown = JSON.parse('{"id": "123", "active": true}');
95
+ 1. **Build-Time Transformation**: The compiler transformer intercepts calls to validation helper functions (`validate`, `is`, `assert`, `assertGuard`, `jsonSchema`) containing generic arguments.
96
+ 2. **Type Extraction & Analysis**: It parses the target TS type structure, extracting intersection constraints, formats, transforms, and defaults recursively.
97
+ 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
+ 4. **Call Replacement**: The transformer replaces the original compile-time call expressions with direct, zero-reflection references to the runtime `MetadataStore`.
65
99
 
66
- // 1. is() - returns a boolean (type guard)
67
- if (is<Payload>(data)) {
68
- console.log(data.id); // Narrowed to 'Payload'
69
- }
100
+ ### Static Constraint Diagnostics
70
101
 
71
- // 2. assert() - returns the narrowed value or throws an error
72
- const validData = assert<Payload>(data);
102
+ The package includes an IDE / Language Service plugin that statically checks literal values against type constraints during editing or compilation:
73
103
 
74
- // 3. assertGuard() - asserts the type for the current scope in-place
75
- assertGuard<Payload>(data);
76
- console.log(data.id); // Narrowed in-place
104
+ ```typescript
105
+ import { constraint } from '@webergency-utils/typechecker';
77
106
 
78
- // 4. validate() - returns a structured validation result with errors
79
- const result = validate<Payload>(data);
80
- if (result.success) {
81
- console.log(result.data);
82
- } else {
83
- console.error(result.errors); // Array of formatted errors
84
- }
107
+ // This yields a compilation error directly in the IDE:
108
+ // Type '5' is not assignable to type 'number & Minimum<18>'.
109
+ const age: number & constraint.Minimum<18> = 5;
85
110
  ```
86
111
 
87
112
  ---
88
113
 
89
- ### 3. Extended Options & Validation Modes
114
+ ## Glossary
90
115
 
91
- All validation APIs accept either a string `ValidationMode` or a custom `ValidationOptions` object:
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.
92
126
 
93
- ```typescript
94
- export type ValidationMode = 'strict' | 'relaxed' | 'strip';
127
+ ---
95
128
 
96
- export interface ValidationOptions {
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
- ```
129
+ ## API Reference
102
130
 
103
- #### Examples:
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
- ```
131
+ ### Validation Functions
118
132
 
119
- ---
133
+ #### `validate<T>(input: unknown, options?: ValidationMode | ValidationOptions): IValidation<ResolveDefaults<T>>`
120
134
 
121
- ### 4. Error Reporting & Grouping
135
+ Validates input data against type `T` and returns a structured validation result.
122
136
 
123
- When validation fails using `validate<T>()`, you receive a highly structured array of errors. To make this easy to consume for humans, LLMs, and UI libraries (like React Hook Form), the library provides a `groupErrorsByPath` helper that organizes these errors by their exact JSON path.
137
+ - **Parameters**:
138
+ - `input`: The value to validate.
139
+ - `options` (optional): Either a `ValidationMode` string ('strict' | 'relaxed' | 'strip') or a `ValidationOptions` object.
140
+ - **Returns**: `IValidation<ResolveDefaults<T>>` containing validation status, converted/stripped data, and error details.
141
+ - **Example**:
142
+ ```typescript
143
+ const result = validate<User>(data, 'strip');
144
+ ```
124
145
 
125
- The error strings follow a deterministic, parser-friendly `Constraint<Value>` format.
146
+ #### `is<T>(input: unknown, options?: ValidationMode | ValidationOptions): input is ResolveDefaults<T>`
126
147
 
127
- ```typescript
128
- import { validate, groupErrorsByPath, Minimum } from '@webergency-utils/typechecker';
148
+ A type guard function checking if the input matches type `T`.
129
149
 
130
- interface Payload {
131
- id: string;
132
- role: "admin" | "user";
133
- age: number & Minimum<18>;
134
- metadata: { tag: string } | { priority: number };
135
- }
150
+ - **Parameters**:
151
+ - `input`: The value to check.
152
+ - `options` (optional): Either a `ValidationMode` string or a `ValidationOptions` object.
153
+ - **Returns**: `boolean` (`true` if valid, `false` otherwise). Narrows type of `input` to `ResolveDefaults<T>` on success.
154
+ - **Example**:
155
+ ```typescript
156
+ if (is<User>(data)) {
157
+ console.log(data.name);
158
+ }
159
+ ```
136
160
 
137
- const data = {
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
- };
161
+ #### `assert<T>(input: unknown, options?: ValidationMode | ValidationOptions): ResolveDefaults<T>`
143
162
 
144
- const result = validate<Payload>(data);
145
- if (!result.success) {
146
- const grouped = groupErrorsByPath(result.errors);
147
- console.log(JSON.stringify(grouped, null, 2));
148
- }
149
- ```
163
+ Validates input data and returns it, throwing a validation error on failure.
150
164
 
151
- **Output:**
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
- ```
165
+ - **Parameters**:
166
+ - `input`: The value to validate.
167
+ - `options` (optional): Either a `ValidationMode` string or a `ValidationOptions` object.
168
+ - **Returns**: `ResolveDefaults<T>` (the validated value with defaults resolved).
169
+ - **Throws**: `Error` containing a list of path and constraint failures, or a custom error via `options.errorFactory`.
170
+ - **Example**:
171
+ ```typescript
172
+ const user = assert<User>(data);
173
+ ```
174
+
175
+ #### `assertGuard<T>(input: unknown, options?: ValidationMode | ValidationOptions): asserts input is ResolveDefaults<T>`
176
+
177
+ An assertion guard that throws a validation error if the input does not match type `T`.
178
+
179
+ - **Parameters**:
180
+ - `input`: The value to check.
181
+ - `options` (optional): Either a `ValidationMode` string or a `ValidationOptions` object.
182
+ - **Returns**: `void`. Narrows the type of `input` in the enclosing scope on success.
183
+ - **Throws**: `Error` if validation fails.
184
+ - **Example**:
185
+ ```typescript
186
+ assertGuard<User>(data);
187
+ ```
188
+
189
+ #### `jsonSchema<T>(): any`
183
190
 
184
- This flattened, grouped output is incredibly powerful—it tells the developer (or an AI agent) exactly *why* a complex union or object failed down to the very specific branch and missing property constraint.
191
+ Generates a raw JSON Schema draft-07 object matching type `T` at compile time.
192
+
193
+ - **Returns**: `any` (a JSON Schema object).
194
+ - **Example**:
195
+ ```typescript
196
+ const userSchema = jsonSchema<User>();
197
+ ```
185
198
 
186
199
  ---
187
200
 
188
- ### 5. Deep/External Type Validation (WithModifiers)
201
+ ### Utility Functions and Classes
189
202
 
190
- If you have deeply nested objects or external types that you cannot edit directly, you can add validation tags to specific properties using dot-notation path references. This prevents you from having to retype the entire structure.
203
+ #### `convertPropertyCasing<T, C extends CasingFormat>(obj: T, casing: C, options?: ConvertCasingOptions): ConvertPropertyCasing<T, C>`
191
204
 
192
- ```typescript
193
- import { WithModifiers, constraint, format } from '@webergency-utils/typechecker';
194
-
195
- // External type we cannot edit directly
196
- interface ExternalUser {
197
- id: string;
198
- profile?: {
199
- details: {
200
- email: string;
201
- password: string;
202
- }
203
- }
204
- }
205
+ Recursively converts all property keys of an object to the specified casing format.
205
206
 
206
- // Decorate specific paths using dot notation
207
- type ValidatedUser = WithModifiers<ExternalUser, {
208
- 'profile.details.email': format.Email;
209
- 'profile.details.password': constraint.MinLength<8>;
210
- }>;
211
- ```
207
+ - **Parameters**:
208
+ - `obj`: The source object.
209
+ - `casing`: A `CasingFormat` string value (`'snake_case' | 'SNAKE_CASE' | 'camelCase' | 'camelCaseID' | 'PascalCase' | 'PascalCaseID' | 'kebab-case' | 'dot.case'`).
210
+ - `options` (optional): `ConvertCasingOptions` object.
211
+ - **Returns**: The casing-converted object with updated TypeScript property keys.
212
+ - **Example**:
213
+ ```typescript
214
+ const apiResponse = convertPropertyCasing(user, 'camelCase');
215
+ ```
212
216
 
213
- ### 6. Cross-Field Dependencies (Requires)
217
+ #### `toZodIssues(errors: IValidationError[]): any[]`
214
218
 
215
- You can specify that a property requires the presence of other properties in the same object using absolute paths or relative dot-notation paths (e.g., `.sibling`, `..grandparent.cousin`):
219
+ Converts internal validation errors into Zod-compatible issues.
216
220
 
217
- ```typescript
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
- }
221
+ - **Parameters**:
222
+ - `errors`: Array of `IValidationError`.
223
+ - **Returns**: An array of Zod-like issues.
228
224
 
229
- type SecureConfig = WithModifiers<DBConfig, {
230
- // Absolute check: if "ssl" is defined, "cert" must exist at root
231
- 'ssl': constraint.Requires<'cert'>;
225
+ #### `class ZodLikeError extends Error`
232
226
 
233
- // Relative check: if "auth.password" is defined, its sibling "username" must exist
234
- 'auth.password': constraint.Requires<'.username'>;
235
- }>;
236
- ```
227
+ An error class wrapper that transforms internal validation errors into a Zod-like error structure.
237
228
 
238
- ### 7. Custom Error Messages
229
+ - **Constructor**: `constructor(errors: IValidationError[])`
230
+ - **Properties**:
231
+ - `name`: `'ZodError'`
232
+ - `issues`: Zod-like issue array.
239
233
 
240
- You can specify custom error messages for constraint violations. Most constraint tags accept a custom message as an optional template argument, or you can use the `Message<Msg>` tag:
234
+ ---
241
235
 
242
- ```typescript
243
- import { validate, constraint, Message } from '@webergency-utils/typechecker';
236
+ ### Interfaces and Types
244
237
 
245
- interface User {
246
- // Direct message in constraint tag
247
- age: number & constraint.Minimum<18, "Must be 18 or older">;
238
+ #### `interface ValidationOptions`
248
239
 
249
- // Fallback property message via Message<T>
250
- email: string & constraint.Format<'email'> & Message<"Please supply a valid email address">;
251
- }
252
- ```
240
+ Configuration options to customize validator behavior.
253
241
 
254
- ### 8. Zod Compatibility
242
+ | Property | Type | Default | Description |
243
+ | :--- | :--- | :--- | :--- |
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. |
255
249
 
256
- If you are integrating with libraries that expect Zod-style validation errors (such as React Hook Form or API gateways), you can convert the validation errors using the `toZodIssues` helper or throw a `ZodLikeError` which wraps the issues:
250
+ #### `interface IValidation<T>`
257
251
 
258
- ```typescript
259
- import { validate, toZodIssues, ZodLikeError } from '@webergency-utils/typechecker';
252
+ Result object returned by `validate`.
260
253
 
261
- const result = validate<User>(data);
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 }]
254
+ - `success`: `boolean`
255
+ - `data?`: `T`
256
+ - `errors?`: `IValidationError[]`
266
257
 
267
- // 2. Or throw a Zod-like Error
268
- throw new ZodLikeError(result.errors);
269
- }
270
- ```
258
+ #### `interface IValidationError`
259
+
260
+ Details of a validation check failure.
261
+
262
+ - `path`: `string`
263
+ - `value`: `any`
264
+ - `error`: `string` (the constraint description or custom message)
265
+
266
+ #### `type WithModifiers<T, M>`
267
+
268
+ Applies constraint, format, or transformation tags to properties of type `T` using a path mapping `M`.
269
+
270
+ - **Generics**:
271
+ - `T`: The baseline type to wrap.
272
+ - `M`: A key-value map where keys are dot-separated paths (e.g., `'profile.email'`) and values are tags.
271
273
 
272
274
  ---
273
275
 
274
- ## Supported Validation & Modifier Namespaces
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.
276
+ ### Tags & Modifiers
277
+
278
+ #### `constraint` Namespace
279
+
280
+ Used to apply value constraints to types.
281
+
282
+ | Tag | Description |
283
+ | :--- | :--- |
284
+ | `constraint.MinLength<N, Msg?>` | Restricts string length to $\ge N$. |
285
+ | `constraint.MaxLength<N, Msg?>` | Restricts string length to $\le N$. |
286
+ | `constraint.Length<Min, Max>` | Shorthand for `MinLength<Min> & MaxLength<Max>`. |
287
+ | `constraint.Pattern<Regex, Msg?>` | Validates string using regular expression `Regex`. |
288
+ | `constraint.Minimum<N, Msg?>` | Inclusive minimum value restriction for `number | bigint`. |
289
+ | `constraint.Maximum<N, Msg?>` | Inclusive maximum value restriction for `number | bigint`. |
290
+ | `constraint.Range<Min, Max>` | Shorthand for `Minimum<Min> & Maximum<Max>`. |
291
+ | `constraint.ExclusiveMinimum<N, Msg?>` | Exclusive minimum value restriction for `number | bigint`. |
292
+ | `constraint.ExclusiveMaximum<N, Msg?>` | Exclusive maximum value restriction for `number | bigint`. |
293
+ | `constraint.MultipleOf<N, Msg?>` | Restricts `number | bigint` to multiples of `N`. |
294
+ | `constraint.MinItems<N, Msg?>` | Restricts array length to $\ge N$. |
295
+ | `constraint.MaxItems<N, Msg?>` | Restricts array length to $\le N$. |
296
+ | `constraint.UniqueItems<Msg?>` | Restricts arrays to deeply unique items. |
297
+ | `constraint.Custom<Fn, Msg?>` | Runs a custom validation function: `(val, ctx) => boolean`. |
298
+ | `constraint.Requires<Path | [Paths], Msg?>` | Enforces that other object property paths exist. |
299
+ | `constraint.Message<Msg>` | Fallback custom error message. |
300
+
301
+ #### `format` Namespace
302
+
303
+ Standard formats for string primitives.
304
+
305
+ - `format.Email`: RFC-5322 email.
306
+ - `format.UUID`: UUID (v1-v5).
307
+ - `format.URL`: HTTP/HTTPS/FTP URLs.
308
+ - `format.IPv4` / `format.IPv6`: IP addresses.
309
+ - `format.Date`: ISO date `YYYY-MM-DD`.
310
+ - `format.DateTime`: ISO date-time.
311
+ - `format.ObjectId`: MongoDB 24-character hex ObjectId.
312
+ - `format.Duration`: ISO-8601 duration.
313
+ - `format.Time`: Time string `HH:MM:SS`.
314
+ - `format.Byte`: Base64 string.
315
+ - `format.Password`: Any string (always valid placeholder).
309
316
  - `format.Regex`: Valid regular expression string.
310
- - `format.Hostname`: Valid domain name/hostname.
317
+ - `format.Hostname`: Valid domain name.
311
318
  - `format.URI`: Valid URI.
312
319
 
313
- ### 3. `transform` Namespace
314
- Coerce or sanitize values before validation:
320
+ #### `transform` Namespace
315
321
 
316
- - `transform.Trim`: Trims whitespace from both ends of a string.
322
+ Sanitizes and converts input values during validation.
323
+
324
+ - `transform.Trim`: Trims string whitespace.
317
325
  - `transform.LowerCase`: Converts string to lowercase.
318
326
  - `transform.UpperCase`: Converts string to uppercase.
319
- - `transform.Capitalize`: Capitalizes the first letter of a string.
327
+ - `transform.Capitalize`: Capitalizes the first letter.
320
328
  - `transform.ToNumber`: Coerces inputs to numbers.
321
329
  - `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`.
324
-
325
- ### 4. `tag` Namespace
326
- Define defaults for optional fields:
327
-
328
- - `tag.Default<Value>`: Injects the specified `Value` if the property is `undefined` at validation time.
329
-
330
- ### Assignability & compile-time constants
331
-
332
- Constraint tags use **optional** phantom properties (same pattern as `tag.Default`), so plain values assign to tagged types:
330
+ - `transform.ToDate`: Coerces inputs to Date objects.
331
+ - `transform.Custom<Fn>`: Custom mapping function: `(val) => any`.
333
332
 
334
- ```typescript
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
- ```
339
-
340
- When the TypeScript plugin is enabled, **compile-time constants** that violate constraints produce diagnostics:
341
-
342
- ```typescript
343
- const tooYoung: number & Minimum<18> = 5; // Error: does not satisfy Minimum<18>
344
- const short: string & MinLength<3> = 'ab'; // Error: does not satisfy MinLength<3>
345
- const empty: string[] & MinItems<1> = []; // Error: does not satisfy MinItems<1>
346
- const dupes: number[] & UniqueItems = [1, 1]; // Error: does not satisfy UniqueItems
347
- ```
333
+ #### `tag` Namespace
348
334
 
349
- Non-constants (`number`, variables, function results) are not checked statically use `is` / `assert` / `validate` at runtime.
350
-
351
- #### Example:
352
- ```typescript
353
- import { MinLength, Minimum, Format, UniqueItems, tag, transform, constraint } from '@webergency-utils/typechecker';
354
-
355
- interface Profile {
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
- ```
335
+ - `tag.Default<Value>`: Injects `Value` when a property is undefined. Removes the optional modifier (`?`) when resolved with `ResolveDefaults<T>`.
363
336
 
364
337
  ---
365
338
 
366
- ## How it Works
339
+ ## Troubleshooting
367
340
 
368
- 1. **AST Analysis**: The transformer scans compile-time type signatures and generates highly nested, direct runtime checks.
369
- 2. **Circular References**: Safely handles recursive and circular types by generating self-referencing lazy functions.
370
- 3. **Hoisting & Deduping**: Identical type validations are hoisted to top-level constants and shared, minimizing footprint.
371
- 4. **Clean Emitted JS**: The output compiles into vanilla JS, utilizing direct, blazing-fast validation logic.
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.
344
+ - **Fix**:
345
+ 1. Verify `npx ts-patch install` was executed successfully.
346
+ 2. Verify the transformer plugin is registered in `tsconfig.json`.
347
+ 3. Ensure your bundler or compiler CLI compiles using patched `tsc`.
372
348
 
373
349
  ---
374
350
 
375
- ## License
351
+ ## Maintenance
352
+
353
+ This package is actively maintained.
376
354
 
377
- MIT © radixxko / [webergency-utils](https://github.com/webergency-utils)
355
+ Bug reports and pull requests are welcome. Security issues and critical
356
+ regressions are prioritized. New features are considered when they align
357
+ with the package's existing scope.
@@ -168,7 +168,7 @@ export function evaluateStaticConstraints(constant, constraints) {
168
168
  else if (c.type === 'multipleOf' && constant.kind === 'number') {
169
169
  const v = constant.value;
170
170
  const n = c.value;
171
- let ok = true;
171
+ let ok;
172
172
  if (typeof v === 'bigint' || typeof n === 'bigint') {
173
173
  ok = BigInt(v) % BigInt(n) === 0n;
174
174
  }
@@ -0,0 +1,7 @@
1
+ import ts from 'typescript';
2
+ declare function init(modules: {
3
+ typescript: typeof ts;
4
+ }): {
5
+ create: (info: ts.server.PluginCreateInfo) => any;
6
+ };
7
+ export default init;
package/dist/plugin.js ADDED
@@ -0,0 +1,30 @@
1
+ import { collectStaticConstraintDiagnostics } from './engine/staticAsserts.js';
2
+ function init(modules) {
3
+ function create(info) {
4
+ const proxy = Object.create(null);
5
+ const ls = info.languageService;
6
+ for (const key of Object.keys(ls)) {
7
+ proxy[key] = ls[key];
8
+ }
9
+ proxy.getSemanticDiagnostics = (fileName) => {
10
+ const base = ls.getSemanticDiagnostics(fileName);
11
+ const program = ls.getProgram();
12
+ if (!program) {
13
+ return base;
14
+ }
15
+ const sourceFile = program.getSourceFile(fileName);
16
+ if (!sourceFile || sourceFile.isDeclarationFile) {
17
+ return base;
18
+ }
19
+ if (fileName.includes('node_modules')) {
20
+ return base;
21
+ }
22
+ const checker = program.getTypeChecker();
23
+ const extra = collectStaticConstraintDiagnostics(sourceFile, checker);
24
+ return [...base, ...extra];
25
+ };
26
+ return proxy;
27
+ }
28
+ return { create };
29
+ }
30
+ export default init;
@@ -12,7 +12,7 @@ type SnakeToPascal<S extends string> = Capitalize<SnakeToCamel<S>>;
12
12
  type SnakeToKebab<S extends string> = S extends `${infer L}_${infer R}` ? `${L}-${SnakeToKebab<R>}` : S;
13
13
  type SnakeToDot<S extends string> = S extends `${infer L}_${infer R}` ? `${L}.${SnakeToDot<R>}` : S;
14
14
  type SnakeToUpperSnake<S extends string> = Uppercase<S>;
15
- type ReplaceId<S extends string> = S extends `${infer L}Id` ? `${L}ID` : S extends `id` ? `ID` : S extends `Id` ? `ID` : S;
15
+ type ReplaceId<S extends string> = S extends `${infer L}Id` ? `${L}ID` : S extends 'id' ? 'ID' : S extends 'Id' ? 'ID' : S;
16
16
  type ToCamelCaseID<S extends string> = ReplaceId<SnakeToCamel<S>>;
17
17
  type ToPascalCaseID<S extends string> = ReplaceId<SnakeToPascal<S>>;
18
18
  type Leading<S extends string> = S extends `_${infer R}` ? `_${Leading<R>}` : S extends `-${infer R}` ? `-${Leading<R>}` : S extends `.${infer R}` ? `.${Leading<R>}` : S extends `$${infer R}` ? `$${Leading<R>}` : '';
@@ -20,9 +20,9 @@ type Trailing<S extends string> = S extends `${infer L}_` ? `${Trailing<L>}_` :
20
20
  type StripLeading<S extends string> = S extends `_${infer R}` ? StripLeading<R> : S extends `-${infer R}` ? StripLeading<R> : S extends `.${infer R}` ? StripLeading<R> : S extends `$${infer R}` ? StripLeading<R> : S;
21
21
  type StripTrailing<S extends string> = S extends `${infer L}_` ? StripTrailing<L> : S extends `${infer L}-` ? StripTrailing<L> : S extends `${infer L}.` ? StripTrailing<L> : S extends `${infer L}$` ? StripTrailing<L> : S;
22
22
  type FormatCoreCasing<S extends string, Casing extends CasingFormat> = Casing extends 'snake_case' ? Normalized<S> : Casing extends 'SNAKE_CASE' ? SnakeToUpperSnake<Normalized<S>> : Casing extends 'camelCase' ? SnakeToCamel<Normalized<S>> : Casing extends 'PascalCase' ? SnakeToPascal<Normalized<S>> : Casing extends 'kebab-case' ? SnakeToKebab<Normalized<S>> : Casing extends 'dot.case' ? SnakeToDot<Normalized<S>> : Casing extends 'camelCaseID' ? ToCamelCaseID<Normalized<S>> : Casing extends 'PascalCaseID' ? ToPascalCaseID<Normalized<S>> : S;
23
- export type FormatCasing<S extends string, Casing extends CasingFormat, Options extends ConvertCasingOptions = {}> = Options['preserveEnds'] extends false ? FormatCoreCasing<StripLeading<StripTrailing<S>>, Casing> : `${Leading<S>}${FormatCoreCasing<StripLeading<StripTrailing<S>>, Casing>}${Trailing<S>}`;
23
+ export type FormatCasing<S extends string, Casing extends CasingFormat, Options extends ConvertCasingOptions = Record<never, never>> = Options['preserveEnds'] extends false ? FormatCoreCasing<StripLeading<StripTrailing<S>>, Casing> : `${Leading<S>}${FormatCoreCasing<StripLeading<StripTrailing<S>>, Casing>}${Trailing<S>}`;
24
24
  type ExtractArray<T> = T extends any[] ? T : never;
25
- export type ConvertPropertyCasing<T, Casing extends CasingFormat, Options extends ConvertCasingOptions = {}> = T extends [infer F, ...infer R] ? [ConvertPropertyCasing<F, Casing, Options>, ...ExtractArray<ConvertPropertyCasing<R, Casing, Options>>] : T extends (infer E)[] ? ConvertPropertyCasing<E, Casing, Options>[] : T extends string | number | boolean | symbol | bigint | null | undefined ? T : T extends Date | RegExp | Function | Map<any, any> | Set<any> | Promise<any> ? T : T extends object ? {
25
+ export type ConvertPropertyCasing<T, Casing extends CasingFormat, Options extends ConvertCasingOptions = Record<never, never>> = T extends [infer F, ...infer R] ? [ConvertPropertyCasing<F, Casing, Options>, ...ExtractArray<ConvertPropertyCasing<R, Casing, Options>>] : T extends (infer E)[] ? ConvertPropertyCasing<E, Casing, Options>[] : T extends string | number | boolean | symbol | bigint | null | undefined ? T : T extends Date | RegExp | Function | Map<any, any> | Set<any> | Promise<any> ? T : T extends object ? {
26
26
  [K in keyof T as K extends string ? FormatCasing<K, Casing, Options> : K]: ConvertPropertyCasing<T[K], Casing, Options>;
27
27
  } : T;
28
28
  export declare function convertPropertyCasing<T, C extends CasingFormat>(obj: T, casing: C, options?: ConvertCasingOptions): ConvertPropertyCasing<T, C>;
@@ -2,7 +2,7 @@ function normalizeString(str) {
2
2
  if (/^[A-Z0-9_]+$/.test(str)) {
3
3
  str = str.toLowerCase();
4
4
  }
5
- str = str.replace(/[-\.]/g, '_');
5
+ str = str.replace(/[-.]/g, '_');
6
6
  str = str.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase();
7
7
  return str.replace(/_+/g, '_').replace(/^_|_$/g, '');
8
8
  }
@@ -12,7 +12,7 @@ function formatCasing(str, casing, options = {}) {
12
12
  let trailing = '';
13
13
  let core = str;
14
14
  if (preserveEnds) {
15
- const match = str.match(/^([_\-\.\$]*)(.*?)([_\-\.\$]*)$/);
15
+ const match = str.match(/^([_\-.$]*)(.*?)([_\-.$]*)$/);
16
16
  if (match) {
17
17
  leading = match[1];
18
18
  core = match[2];
@@ -20,10 +20,10 @@ function formatCasing(str, casing, options = {}) {
20
20
  }
21
21
  }
22
22
  else {
23
- core = str.replace(/^([_\-\.\$]*)/, '').replace(/([_\-\.\$]*)$/, '');
23
+ core = str.replace(/^([_\-.$]*)/, '').replace(/([_\-.$]*)$/, '');
24
24
  }
25
25
  const normalized = normalizeString(core);
26
- let formatted = '';
26
+ let formatted;
27
27
  if (casing === 'snake_case') {
28
28
  formatted = normalized;
29
29
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webergency-utils/typechecker",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "type": "module",
5
5
  "description": "TypeScript compiler plugin for runtime validation",
6
6
  "author": "radixxko",
@@ -24,6 +24,12 @@
24
24
  "import": "./dist/transformer.js",
25
25
  "require": "./dist/transformer.js",
26
26
  "default": "./dist/transformer.js"
27
+ },
28
+ "./plugin": {
29
+ "types": "./dist/plugin.d.ts",
30
+ "import": "./dist/plugin.js",
31
+ "require": "./dist/plugin.js",
32
+ "default": "./dist/plugin.js"
27
33
  }
28
34
  },
29
35
  "files": [
@@ -31,11 +37,11 @@
31
37
  ],
32
38
  "scripts": {
33
39
  "lint": "eslint .",
40
+ "test": "vitest run",
41
+ "coverage": "vitest run --coverage",
34
42
  "clean": "rm -rf ./dist/* node_modules package-lock.json && npm i",
35
- "commit": "node -e \"const version = require('./package.json').version.trim(); require('child_process').execSync('git add . && git commit -m \\\"Version ' + version + '\\\"');\"",
36
43
  "build": "npm run clean && tsc && node update_imports.js",
37
- "test": "vitest run",
38
- "test:coverage": "vitest run --coverage",
44
+ "commit": "node -e \"const version = require('./package.json').version.trim(); require('child_process').execSync('git add . && git commit -m \\\"Version ' + version + '\\\"');\"",
39
45
  "version": "npm run build && npm publish --access public && npm run commit && git push"
40
46
  },
41
47
  "repository": {
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 webergency-utils
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.