@oxog/vld 2.2.5 → 2.4.0
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/CHANGELOG.md +51 -0
- package/README.md +242 -1125
- package/dist/chunks/errors-core-CerMg2rq.js +1 -0
- package/dist/chunks/index-CKPStM3V.js +1 -0
- package/dist/cjs/chunks/errors-core-DrydF46s.cjs +1 -0
- package/dist/cjs/chunks/index-lejEpLfv.cjs +1 -0
- package/dist/cjs/cli/bin.cjs +1 -1
- package/dist/cjs/codecs/index.cjs +1 -1
- package/dist/cjs/coercion/index.cjs +1 -1
- package/dist/cjs/compile.cjs +1 -0
- package/dist/cjs/errors.cjs +1 -1
- package/dist/cjs/index.cjs +1 -1
- package/dist/cjs/locales/gu.cjs +1 -0
- package/dist/cjs/locales/index.cjs +1 -1
- package/dist/cjs/locales/kn.cjs +1 -0
- package/dist/cjs/locales/ne.cjs +1 -0
- package/dist/cjs/locales/pt-BR-v4.cjs +1 -0
- package/dist/cjs/locales/sk.cjs +1 -0
- package/dist/cjs/mini.cjs +1 -1
- package/dist/cjs/v3/index.cjs +1 -1
- package/dist/cjs/v4/core/index.cjs +1 -1
- package/dist/cjs/v4/index.cjs +1 -1
- package/dist/cjs/v4/locales/index.cjs +1 -1
- package/dist/cjs/v4/mini/index.cjs +1 -1
- package/dist/cjs/v4-mini/index.cjs +1 -1
- package/dist/cjs/validators/any.cjs +1 -1
- package/dist/cjs/validators/array.cjs +1 -1
- package/dist/cjs/validators/base.cjs +1 -1
- package/dist/cjs/validators/base64.cjs +1 -1
- package/dist/cjs/validators/bigint.cjs +1 -1
- package/dist/cjs/validators/boolean.cjs +1 -1
- package/dist/cjs/validators/codec.cjs +1 -1
- package/dist/cjs/validators/custom.cjs +1 -1
- package/dist/cjs/validators/date.cjs +1 -1
- package/dist/cjs/validators/discriminated-union.cjs +1 -1
- package/dist/cjs/validators/enum.cjs +1 -1
- package/dist/cjs/validators/file.cjs +1 -1
- package/dist/cjs/validators/function.cjs +1 -1
- package/dist/cjs/validators/hex.cjs +1 -1
- package/dist/cjs/validators/index.cjs +1 -1
- package/dist/cjs/validators/intersection.cjs +1 -1
- package/dist/cjs/validators/json.cjs +1 -1
- package/dist/cjs/validators/lazy.cjs +1 -1
- package/dist/cjs/validators/literal.cjs +1 -1
- package/dist/cjs/validators/map.cjs +1 -1
- package/dist/cjs/validators/nan.cjs +1 -1
- package/dist/cjs/validators/never.cjs +1 -1
- package/dist/cjs/validators/null.cjs +1 -1
- package/dist/cjs/validators/number.cjs +1 -1
- package/dist/cjs/validators/object.cjs +1 -1
- package/dist/cjs/validators/promise.cjs +1 -1
- package/dist/cjs/validators/record.cjs +1 -1
- package/dist/cjs/validators/set.cjs +1 -1
- package/dist/cjs/validators/string-bool.cjs +1 -1
- package/dist/cjs/validators/string-formats.cjs +1 -1
- package/dist/cjs/validators/string.cjs +1 -1
- package/dist/cjs/validators/symbol.cjs +1 -1
- package/dist/cjs/validators/template-literal.cjs +1 -1
- package/dist/cjs/validators/tuple.cjs +1 -1
- package/dist/cjs/validators/uint8array.cjs +1 -1
- package/dist/cjs/validators/undefined.cjs +1 -1
- package/dist/cjs/validators/union.cjs +1 -1
- package/dist/cjs/validators/unknown.cjs +1 -1
- package/dist/cjs/validators/void.cjs +1 -1
- package/dist/cjs/validators/xor.cjs +1 -1
- package/dist/coercion/index.js +1 -1
- package/dist/compile.d.ts +81 -0
- package/dist/compile.js +1 -0
- package/dist/errors-core.d.ts +12 -1
- package/dist/errors.js +1 -1
- package/dist/index.d.ts +384 -14
- package/dist/index.js +1 -1
- package/dist/locales/gu.d.ts +2 -0
- package/dist/locales/gu.js +1 -0
- package/dist/locales/index.js +1 -1
- package/dist/locales/kn.d.ts +2 -0
- package/dist/locales/kn.js +1 -0
- package/dist/locales/ne.d.ts +2 -0
- package/dist/locales/ne.js +1 -0
- package/dist/locales/pt-BR-v4.d.ts +2 -0
- package/dist/locales/pt-BR-v4.js +1 -0
- package/dist/locales/sk.d.ts +2 -0
- package/dist/locales/sk.js +1 -0
- package/dist/v3/index.js +1 -1
- package/dist/v4/core/index.d.ts +13 -0
- package/dist/v4/core/index.js +1 -1
- package/dist/v4/index.d.ts +1 -0
- package/dist/v4/index.js +1 -1
- package/dist/v4/locales/index.d.ts +6 -0
- package/dist/v4/locales/index.js +1 -1
- package/dist/v4/mini/index.js +1 -1
- package/dist/v4-mini/index.d.ts +2 -0
- package/dist/v4-mini/index.js +1 -1
- package/dist/validators/array.js +1 -1
- package/dist/validators/base.d.ts +147 -9
- package/dist/validators/base.js +1 -1
- package/dist/validators/base64.js +1 -1
- package/dist/validators/bigint.d.ts +7 -0
- package/dist/validators/bigint.js +1 -1
- package/dist/validators/boolean.js +1 -1
- package/dist/validators/codec.js +1 -1
- package/dist/validators/custom.d.ts +20 -10
- package/dist/validators/custom.js +1 -1
- package/dist/validators/date.d.ts +2 -0
- package/dist/validators/date.js +1 -1
- package/dist/validators/discriminated-union.d.ts +2 -0
- package/dist/validators/discriminated-union.js +1 -1
- package/dist/validators/enum.d.ts +8 -2
- package/dist/validators/enum.js +1 -1
- package/dist/validators/file.js +1 -1
- package/dist/validators/function.js +1 -1
- package/dist/validators/hex.js +1 -1
- package/dist/validators/intersection.js +1 -1
- package/dist/validators/json.js +1 -1
- package/dist/validators/literal.d.ts +1 -0
- package/dist/validators/literal.js +1 -1
- package/dist/validators/map.d.ts +7 -0
- package/dist/validators/map.js +1 -1
- package/dist/validators/nan.js +1 -1
- package/dist/validators/never.js +1 -1
- package/dist/validators/null.js +1 -1
- package/dist/validators/number.d.ts +5 -0
- package/dist/validators/number.js +1 -1
- package/dist/validators/object.d.ts +15 -0
- package/dist/validators/object.js +1 -1
- package/dist/validators/promise.d.ts +6 -7
- package/dist/validators/promise.js +1 -1
- package/dist/validators/record.d.ts +2 -0
- package/dist/validators/record.js +1 -1
- package/dist/validators/set.d.ts +5 -0
- package/dist/validators/set.js +1 -1
- package/dist/validators/string-bool.js +1 -1
- package/dist/validators/string-formats.d.ts +1 -0
- package/dist/validators/string-formats.js +1 -1
- package/dist/validators/string.d.ts +2 -0
- package/dist/validators/string.js +1 -1
- package/dist/validators/symbol.js +1 -1
- package/dist/validators/template-literal.js +1 -1
- package/dist/validators/tuple.js +1 -1
- package/dist/validators/uint8array.js +1 -1
- package/dist/validators/undefined.js +1 -1
- package/dist/validators/union.d.ts +1 -0
- package/dist/validators/union.js +1 -1
- package/dist/validators/void.js +1 -1
- package/dist/validators/xor.js +1 -1
- package/package.json +8 -2
- package/dist/chunks/errors-core-Cqi4-9ew.js +0 -1
- package/dist/chunks/index-yjpbvFgw.js +0 -1
- package/dist/cjs/chunks/errors-core-DKPohm9I.cjs +0 -1
- package/dist/cjs/chunks/index-BEV9nG0N.cjs +0 -1
package/README.md
CHANGED
|
@@ -1,270 +1,23 @@
|
|
|
1
|
-
# VLD - Fast
|
|
2
|
-
|
|
3
|
-
[](https://www.npmjs.com/package/@oxog/vld) [](https://opensource.org/licenses/MIT) [](https://www.typescriptlang.org/) [](package.json) [](package.json)
|
|
4
|
-
|
|
5
|
-
VLD is a blazing-fast, type-safe validation library for TypeScript and JavaScript with **Zod-compatible root and subpath APIs**. Built with performance in mind, it provides a simple and intuitive API while maintaining excellent type inference and 27+ language internationalization support.
|
|
6
|
-
|
|
7
|
-
## Table of Contents
|
|
8
|
-
|
|
9
|
-
- [Features](#features)
|
|
10
|
-
- [Performance](#performance)
|
|
11
|
-
- [Installation](#installation)
|
|
12
|
-
- [Quick Start](#quick-start)
|
|
13
|
-
- [API Reference](#api-reference)
|
|
14
|
-
- [Internationalization (i18n)](#internationalization-i18n)
|
|
15
|
-
- [Error Handling & Formatting](#error-handling--formatting)
|
|
16
|
-
- [Advanced Examples](#advanced-examples)
|
|
17
|
-
- [Codecs - Bidirectional Transformations](#codecs---bidirectional-transformations)
|
|
18
|
-
- [Plugin System](#plugin-system)
|
|
19
|
-
- [Result Pattern](#result-pattern)
|
|
20
|
-
- [CLI Tools](#cli-tools)
|
|
21
|
-
- [Logger & Colored Output](#logger--colored-output)
|
|
22
|
-
- [VLD vs. Zod](#vld-vs-zod)
|
|
23
|
-
- [Benchmarks](#benchmarks)
|
|
24
|
-
- [Contributing](#contributing)
|
|
25
|
-
- [Links](#links)
|
|
26
|
-
|
|
27
|
-
## Features
|
|
28
|
-
|
|
29
|
-
### Core Features
|
|
30
|
-
- **Blazing Fast**: Optimized for V8 engine with superior performance
|
|
31
|
-
- **Type-Safe**: Full TypeScript support with excellent type inference
|
|
32
|
-
- **Zero Dependencies**: Lightweight with no external dependencies
|
|
33
|
-
- **Tree-Shakeable**: Only import what you need
|
|
34
|
-
- **Composable**: Chain validations for complex schemas
|
|
35
|
-
- **Advanced Error Formatting**: Tree, pretty, and flatten error utilities
|
|
36
|
-
- **Multi-language**: Built-in support for 27+ languages
|
|
37
|
-
- **100% Statement / Branch / Function / Line Coverage**: Rigorously tested with 2502 passing tests
|
|
38
|
-
- **Release-Gated Performance**: CI guards require VLD to stay faster than the latest stable Zod across runtime, startup, and memory benchmarks
|
|
39
|
-
- **Drop-in App Verification**: A real TypeScript fixture is compiled and run once with `zod` and once with built VLD, then normalized runtime output is compared
|
|
40
|
-
- **Audited Compatibility Contract**: Modern Zod 4.4.3 factory signatures, schema direction methods, release-fix behaviors, and JSON Schema defaults are differential-tested; see [the compatibility policy](docs/ZOD_COMPATIBILITY.md)
|
|
41
|
-
|
|
42
|
-
### Advanced Zod-Compatible Features
|
|
43
|
-
- **Type Coercion**: `v.coerce.string()`, `v.coerce.number()`, `v.coerce.boolean()`, etc.
|
|
44
|
-
- **Advanced Types**: BigInt, Symbol, Tuple, Record, Set, Map validation
|
|
45
|
-
- **Intersection Types**: Combine multiple schemas with intelligent merging
|
|
46
|
-
- **Custom Validation**: `refine()` for custom predicates and validation logic
|
|
47
|
-
- **Data Transformation**: `transform()` for post-validation data transformation
|
|
48
|
-
- **Default Values**: `default()` for handling undefined inputs elegantly
|
|
49
|
-
- **Fallback Handling**: `catch()` for graceful error recovery
|
|
50
|
-
- **Object Utilities**: `pick()`, `omit()`, `extend()` for flexible object schemas
|
|
51
|
-
|
|
52
|
-
### New in v2.2.x - Error Parity, Canary Parity, and Minified Builds
|
|
53
|
-
|
|
54
|
-
#### Zod 4-Compatible Error Issues
|
|
55
|
-
Validation failures now carry Zod 4's structured issue shape, so error-handling code ports across unchanged:
|
|
56
|
-
- `invalid_type` issues expose `expected` and `received` and use the `"Invalid input: expected X, received Y"` message format
|
|
57
|
-
- `too_small` / `too_big` issues expose `minimum`, `maximum`, `origin`, and `inclusive`
|
|
58
|
-
- `invalid_format` issues expose `format`, `origin`, and `pattern`; `invalid_value` issues expose a `values` array
|
|
59
|
-
- `parse()` throws `VldError` (which extends `Error`) for every validator, matching Zod's throw behavior
|
|
60
|
-
|
|
61
|
-
#### New Parity Helpers
|
|
62
|
-
```typescript
|
|
63
|
-
import { v, deepPartial, input, output } from '@oxog/vld';
|
|
64
|
-
|
|
65
|
-
v.creditCard().parse('4242424242424242'); // regex plus Luhn checksum
|
|
1
|
+
# VLD - Ultra-Fast TypeScript Validation Library
|
|
66
2
|
|
|
67
|
-
|
|
68
|
-
profile: v.object({ name: v.string(), age: v.number() })
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
const draft = deepPartial(schema); // every nested field optional
|
|
72
|
-
const inputSide = input(schema); // input-side view of a pipe
|
|
73
|
-
const outputSide = output(schema); // output-side view of a pipe
|
|
74
|
-
```
|
|
75
|
-
- `deepPartial()` walks children first, mirroring Zod's `visit.js` ordering, and stays cycle-safe through lazy deferral
|
|
76
|
-
- `input()` / `output()` resolve pipe direction the way Zod's root helpers do
|
|
77
|
-
- `@oxog/vld/v4/core` gained the matching `_creditCard`, `isValidCreditCard`, `standardProps`, `handleUnrepresentable`, `$ZodCyclicError`, `attachMemoizer`, and `isBackEdge` shims
|
|
3
|
+
[](https://www.npmjs.com/package/@oxog/vld) [](https://opensource.org/licenses/MIT) [](https://www.typescriptlang.org/) [](package.json) [](package.json) [](https://vld.oxog.dev)
|
|
78
4
|
|
|
79
|
-
|
|
80
|
-
- Release artifacts ship minified by default (opt out with `VLD_MINIFY=0`)
|
|
81
|
-
- Mangling preserves `/^Vld/` class names, because JSON Schema conversion dispatches on `schema.constructor.name`
|
|
82
|
-
- A built-CJS guard in `verify:drop-in` asserts name-dispatched composites against the minified bundle, so this can never silently regress again
|
|
5
|
+
**Website & Live Playground**: [https://vld.oxog.dev](https://vld.oxog.dev)
|
|
83
6
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
#### Tree-Shakable Mini API
|
|
87
|
-
```typescript
|
|
88
|
-
import { string, number, object, optional } from '@oxog/vld/mini';
|
|
89
|
-
|
|
90
|
-
const schema = object({
|
|
91
|
-
name: string().min(1),
|
|
92
|
-
age: optional(number().positive()),
|
|
93
|
-
});
|
|
94
|
-
```
|
|
95
|
-
- **82% smaller bundles** when using only needed validators
|
|
96
|
-
- Individual factory functions for optimal tree-shaking
|
|
97
|
-
- Full TypeScript support with identical type inference
|
|
98
|
-
|
|
99
|
-
#### Lazy Locale Loading
|
|
100
|
-
|
|
101
|
-
> **Bundle-size contract:** the lazy entrypoint is published as
|
|
102
|
-
> `@oxog/vld/locales/lazy` (`src/locales/lazy.ts`). It uses dynamic `import()`
|
|
103
|
-
> for each locale on first use, so tree-shaken bundles ship **only English by
|
|
104
|
-
> default**. Importing the plain `@oxog/vld/locales` subpath (or the root
|
|
105
|
-
> `@oxog/vld` package) eagerly pulls every locale into the graph; pick the
|
|
106
|
-
> `lazy` subpath if you only need one or two languages in production.
|
|
107
|
-
|
|
108
|
-
```typescript
|
|
109
|
-
import { setLocaleAsync, preloadLocales } from '@oxog/vld/locales/lazy';
|
|
110
|
-
|
|
111
|
-
// First call loads the locale on demand; subsequent calls hit the cache.
|
|
112
|
-
await setLocaleAsync('tr');
|
|
113
|
-
|
|
114
|
-
// SSR / batch warm-up: preload what you need before serving traffic.
|
|
115
|
-
await preloadLocales(['en', 'de', 'ja']);
|
|
116
|
-
```
|
|
117
|
-
- **92% bundle reduction** when you only ship English plus the locales you pre-load
|
|
118
|
-
- `setLocaleAsync()` is true async dynamic-import, not a sync wrapper
|
|
119
|
-
- `preloadLocales()` for SSR/batch loading
|
|
120
|
-
- Backwards-compatible `setLocale()` is still available on the eager entrypoint
|
|
121
|
-
|
|
122
|
-
#### Dual ESM/CJS Build
|
|
123
|
-
- ESM builds for modern bundlers (Vite, esbuild)
|
|
124
|
-
- CJS builds for Node.js and legacy environments
|
|
125
|
-
- Proper `exports` field with conditional exports
|
|
126
|
-
|
|
127
|
-
### NEW in v1.5.0 - Major Platform Release
|
|
128
|
-
|
|
129
|
-
#### Plugin System
|
|
130
|
-
- **`definePlugin()`**: Create custom plugins with validators, transforms, and codecs
|
|
131
|
-
- **`usePlugin()`**: Register plugins globally
|
|
132
|
-
- **Plugin Hooks**: Lifecycle hooks for validation events
|
|
133
|
-
- **Custom Validators**: Extend VLD with your own validators
|
|
134
|
-
|
|
135
|
-
#### Result Pattern
|
|
136
|
-
- **`Ok()`/`Err()`**: Functional error handling
|
|
137
|
-
- **`match()`**: Pattern matching on results
|
|
138
|
-
- **`map()`/`flatMap()`**: Transform results
|
|
139
|
-
- **`tryCatch()`**: Safe function execution
|
|
140
|
-
- **`all()`**: Combine multiple results
|
|
141
|
-
|
|
142
|
-
#### Event System
|
|
143
|
-
- **`createEmitter()`**: Type-safe event emitter
|
|
144
|
-
- **`createEventBus()`**: Global event bus
|
|
145
|
-
- **Validation Events**: Parse start, success, error, field validation
|
|
146
|
-
|
|
147
|
-
#### CLI Tools
|
|
148
|
-
- **`vld validate`**: Validate data from command line
|
|
149
|
-
- **`vld benchmark`**: Run performance benchmarks
|
|
150
|
-
- **Colored Output**: Beautiful terminal output with pigment
|
|
151
|
-
|
|
152
|
-
#### New Validators
|
|
153
|
-
- **`v.discriminatedUnion()`**: Discriminated union types
|
|
154
|
-
- **`v.xor()`**: Exclusive OR validation
|
|
155
|
-
- **`v.file()`**: File upload validation
|
|
156
|
-
- **`v.function()`**: Function validation
|
|
157
|
-
- **`v.custom()`**: Type-safe custom validators
|
|
158
|
-
- **`v.json()`**: JSON string validation with schema
|
|
159
|
-
- **`v.lazy()`**: Recursive schema support
|
|
160
|
-
- **`v.nan()`**: NaN validation
|
|
161
|
-
- **`v.null()`**: Null validation
|
|
162
|
-
- **`v.undefined()`**: Undefined validation
|
|
163
|
-
- **`v.templateLiteral()`**: Template literal types
|
|
164
|
-
|
|
165
|
-
#### New String Format Validators
|
|
166
|
-
- **`v.hostname()`**: Hostname validation
|
|
167
|
-
- **`v.emoji()`**: Emoji validation
|
|
168
|
-
- **`v.base64()`/`v.base64url()`**: Base64 format validation
|
|
169
|
-
- **`v.hex()`**: Hex string validation
|
|
170
|
-
- **`v.jwt()`**: JWT format validation
|
|
171
|
-
- **`v.nanoid()`/`v.cuid()`/`v.cuid2()`/`v.ulid()`**: ID format validation
|
|
172
|
-
- **`v.mac()`**: MAC address validation
|
|
173
|
-
- **`v.cidrv4()`/`v.cidrv6()`**: CIDR block validation
|
|
174
|
-
- **`v.e164()`**: E.164 phone number validation
|
|
175
|
-
- **`v.hash()`**: Hash validation (md5, sha1, sha256, sha384, sha512)
|
|
176
|
-
- **`v.iso.date()`/`v.iso.time()`/`v.iso.dateTime()`/`v.iso.duration()`**: ISO format validation
|
|
177
|
-
|
|
178
|
-
#### Enhanced Object Utilities
|
|
179
|
-
- **`v.strictObject()`**: Strict mode object validation
|
|
180
|
-
- **`v.looseObject()`**: Passthrough object validation
|
|
181
|
-
- **`v.partialRecord()`**: Partial record validation
|
|
182
|
-
- **`v.looseRecord()`**: Loose record validation
|
|
183
|
-
- **`v.int()`**: Integer shortcut
|
|
184
|
-
- **`v.int32()`**: 32-bit integer shortcut
|
|
185
|
-
- **`v.nullish()`**: Null or undefined
|
|
186
|
-
- **`v.NEVER`**: NEVER constant for transforms (Zod 4 parity)
|
|
187
|
-
|
|
188
|
-
### Codec System - Beyond Zod
|
|
189
|
-
- **Bidirectional Transformations**: Full encode/decode support for data conversion
|
|
190
|
-
- **19 Built-in Codecs**: String conversions, date parsing, JSON, URL, binary data
|
|
191
|
-
- **Zod-Compatible**: All `stringToNumber`, `jsonCodec`, `base64ToBytes`, etc.
|
|
192
|
-
- **Async Support**: Both sync and async codec operations
|
|
193
|
-
- **Custom Codecs**: Create your own bidirectional transformations
|
|
194
|
-
- **Type-Safe**: Full TypeScript support with perfect type inference
|
|
195
|
-
|
|
196
|
-
## Performance
|
|
197
|
-
|
|
198
|
-
VLD is designed for speed and efficiency with recent optimizations delivering exceptional performance:
|
|
199
|
-
|
|
200
|
-
### Release-Gated Speed Benchmarks (v2.2.1 vs Zod 4.4.3)
|
|
201
|
-
- Runtime guard: VLD must stay at least 1.2x faster on every guarded hot path and keep at least a 3x average ratio
|
|
202
|
-
- Startup guard: cross-platform floors are 0.85x import, 0.9x total startup, and 1.25x warm parse versus Zod
|
|
203
|
-
- Memory guard: VLD must keep at least 2x lower total retained heap, 1.5x higher aggregate throughput, and no guarded case below 1.1x speed
|
|
204
|
-
|
|
205
|
-
### Latest Runtime Guard Snapshot
|
|
206
|
-
|
|
207
|
-
Measured with `npm run benchmark:guard` (5 samples per case) against Zod 4.4.3:
|
|
208
|
-
|
|
209
|
-
| Guarded case | VLD | Zod | Ratio |
|
|
210
|
-
|--------------|-----|-----|-------|
|
|
211
|
-
| Nullish parse | 214.1M ops/sec | 7.0M ops/sec | **30.70x** |
|
|
212
|
-
| Number positive int parse | 252.9M ops/sec | 27.8M ops/sec | **9.10x** |
|
|
213
|
-
| Discriminated union parse | 35.0M ops/sec | 8.6M ops/sec | **4.06x** |
|
|
214
|
-
| Optional parse | 212.9M ops/sec | 56.6M ops/sec | **3.76x** |
|
|
215
|
-
| Union string and number parse | 39.1M ops/sec | 11.9M ops/sec | **3.30x** |
|
|
216
|
-
| Simple string parse | 620.7M ops/sec | 208.8M ops/sec | **2.97x** |
|
|
217
|
-
| Array number parse | 49.2M ops/sec | 28.3M ops/sec | **1.74x** |
|
|
218
|
-
| Simple object parse | 45.2M ops/sec | 27.7M ops/sec | **1.63x** |
|
|
219
|
-
|
|
220
|
-
8/8 guarded cases pass with a **7.16x average ratio**. Absolute ops/sec numbers are hardware-dependent; the ratios are what the release gate enforces.
|
|
221
|
-
|
|
222
|
-
### Optimizations
|
|
223
|
-
- **110x improvement** in union type validation
|
|
224
|
-
- **Simplified email regex** for maximum performance
|
|
225
|
-
- **Inline type checks** in object validation
|
|
226
|
-
- **Optimized loops** with direct array assignment
|
|
227
|
-
- **SafeParse optimization** to avoid try-catch overhead
|
|
228
|
-
- **Pre-computed keys** with Set for O(1) lookups
|
|
229
|
-
|
|
230
|
-
### Memory Efficiency
|
|
231
|
-
|
|
232
|
-
Measured with `npm run benchmark:memory` against Zod 4.4.3. Heap deltas move a few percent between runs, so treat these as approximate:
|
|
233
|
-
|
|
234
|
-
- **~12.7x less memory** for schema creation
|
|
235
|
-
- **~7.3x less memory** for union validation
|
|
236
|
-
- **~2.9x less memory** for simple string validation
|
|
237
|
-
- **~2.7x less memory** for complex object validation
|
|
238
|
-
- **~5x less memory** overall
|
|
239
|
-
|
|
240
|
-
### A Note on Real-World Benchmarking
|
|
241
|
-
|
|
242
|
-
Many validation library benchmarks can be misleading because they often test with **reused schema instances**:
|
|
243
|
-
|
|
244
|
-
```javascript
|
|
245
|
-
// What benchmarks typically test (unrealistic):
|
|
246
|
-
const schema = z.string();
|
|
247
|
-
for (let i = 0; i < 1000000; i++) {
|
|
248
|
-
schema.parse(data); // Same instance reused
|
|
249
|
-
}
|
|
7
|
+
---
|
|
250
8
|
|
|
251
|
-
|
|
252
|
-
app.post('/api/user', (req, res) => {
|
|
253
|
-
// New schema created for each request
|
|
254
|
-
const schema = z.object({
|
|
255
|
-
email: z.string().email(),
|
|
256
|
-
age: z.number().min(18)
|
|
257
|
-
});
|
|
258
|
-
schema.parse(req.body);
|
|
259
|
-
});
|
|
260
|
-
```
|
|
9
|
+
## Highlights
|
|
261
10
|
|
|
262
|
-
|
|
263
|
-
- **
|
|
264
|
-
- **
|
|
265
|
-
- **
|
|
11
|
+
- **Release-Gated Speed**: 11x+ faster runtime throughput and 4.7x+ less memory consumption compared to Zod.
|
|
12
|
+
- **Zero Dependencies**: Pure TypeScript/JavaScript with zero third-party runtime bloat.
|
|
13
|
+
- **Drop-in Zod Compatibility**: Swap imports directly or use subpaths (`@oxog/vld/v4`, `@oxog/vld/mini`, `@oxog/vld/v4/core`, `@oxog/vld/v4/locales`).
|
|
14
|
+
- **Full Static Inference**: Automatic type extraction using `v.infer<typeof schema>`.
|
|
15
|
+
- **Tree-Shakeable Mini API**: Build hyper-optimized bundles with `@oxog/vld/mini`.
|
|
16
|
+
- **Built-in i18n**: Out-of-the-box error localization for 27+ languages with lazy-loading support (`@oxog/vld/locales/lazy`).
|
|
17
|
+
- **Result Pattern & Codecs**: Functional error handling (`tryCatch`, `match`, `Ok`, `Err`) and bidirectional data transformations.
|
|
18
|
+
- **100% Test Coverage**: Verified across 2500+ tests and drop-in TypeScript application suites.
|
|
266
19
|
|
|
267
|
-
|
|
20
|
+
---
|
|
268
21
|
|
|
269
22
|
## Installation
|
|
270
23
|
|
|
@@ -274,1022 +27,386 @@ npm install @oxog/vld
|
|
|
274
27
|
yarn add @oxog/vld
|
|
275
28
|
# or
|
|
276
29
|
pnpm add @oxog/vld
|
|
30
|
+
# or
|
|
31
|
+
bun add @oxog/vld
|
|
277
32
|
```
|
|
278
33
|
|
|
34
|
+
---
|
|
35
|
+
|
|
279
36
|
## Quick Start
|
|
280
37
|
|
|
281
38
|
```typescript
|
|
282
39
|
import { v } from '@oxog/vld';
|
|
283
40
|
|
|
284
|
-
//
|
|
285
|
-
// and for a more concise syntax.
|
|
286
|
-
|
|
287
|
-
// Define a schema
|
|
41
|
+
// Define schema with chainable validations
|
|
288
42
|
const userSchema = v.object({
|
|
289
|
-
name: v.string().min(2),
|
|
43
|
+
name: v.string().min(2).max(100),
|
|
290
44
|
email: v.string().email(),
|
|
291
|
-
age: v.number().
|
|
292
|
-
|
|
45
|
+
age: v.number().int().positive().optional(),
|
|
46
|
+
role: v.enum('admin', 'user', 'guest').default('user'),
|
|
47
|
+
tags: v.array(v.string()).min(1),
|
|
293
48
|
});
|
|
294
49
|
|
|
295
|
-
//
|
|
50
|
+
// Infer TypeScript type
|
|
51
|
+
type User = v.infer<typeof userSchema>;
|
|
52
|
+
|
|
53
|
+
// Safe parsing (no throwing)
|
|
296
54
|
const result = userSchema.safeParse({
|
|
297
55
|
name: 'John Doe',
|
|
298
56
|
email: 'john@example.com',
|
|
299
|
-
age:
|
|
300
|
-
|
|
57
|
+
age: 28,
|
|
58
|
+
tags: ['developer', 'typescript'],
|
|
301
59
|
});
|
|
302
60
|
|
|
303
61
|
if (result.success) {
|
|
304
|
-
console.log('Valid
|
|
62
|
+
console.log('Valid data:', result.data); // Typed as User
|
|
305
63
|
} else {
|
|
306
|
-
console.
|
|
64
|
+
console.error('Validation issues:', result.error.issues);
|
|
307
65
|
}
|
|
308
66
|
```
|
|
309
67
|
|
|
310
|
-
|
|
311
|
-
```typescript
|
|
312
|
-
import { v, VldError, treeifyError, prettifyError, flattenError } from '@oxog/vld';
|
|
313
|
-
```
|
|
314
|
-
|
|
315
|
-
## API Reference
|
|
316
|
-
|
|
317
|
-
### Basic Types
|
|
68
|
+
---
|
|
318
69
|
|
|
319
|
-
|
|
320
|
-
v.string() // String validation
|
|
321
|
-
v.number() // Number validation
|
|
322
|
-
v.int() // Integer validation (shortcut)
|
|
323
|
-
v.int32() // 32-bit integer validation
|
|
324
|
-
v.boolean() // Boolean validation
|
|
325
|
-
v.bigint() // BigInt validation
|
|
326
|
-
v.symbol() // Symbol validation
|
|
327
|
-
v.date() // Date validation
|
|
328
|
-
v.uint8array()// Uint8Array validation
|
|
329
|
-
v.literal() // Literal values
|
|
330
|
-
v.enum() // Enum values (supports TypeScript enums)
|
|
331
|
-
v.any() // Any type
|
|
332
|
-
v.unknown() // Unknown type
|
|
333
|
-
v.void() // Void type
|
|
334
|
-
v.never() // Never type
|
|
335
|
-
v.null() // Null type
|
|
336
|
-
v.undefined() // Undefined type
|
|
337
|
-
v.nan() // NaN type
|
|
338
|
-
```
|
|
70
|
+
## Core Schema API
|
|
339
71
|
|
|
340
|
-
###
|
|
72
|
+
### Primitive Validators
|
|
341
73
|
|
|
342
74
|
```typescript
|
|
343
|
-
//
|
|
344
|
-
v.
|
|
345
|
-
v.
|
|
346
|
-
v.
|
|
347
|
-
v.
|
|
348
|
-
v.
|
|
349
|
-
|
|
350
|
-
//
|
|
351
|
-
v.
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
v.
|
|
356
|
-
v.
|
|
357
|
-
|
|
358
|
-
//
|
|
359
|
-
v.
|
|
360
|
-
v.
|
|
361
|
-
v.discriminatedUnion('type', ...) // Discriminated union
|
|
362
|
-
v.xor(schemaA, schemaB) // Exclusive OR
|
|
363
|
-
v.optional(v.string()) // Optional fields
|
|
364
|
-
v.nullable(v.string()) // Nullable fields
|
|
365
|
-
v.nullish(v.string()) // Null or undefined
|
|
75
|
+
v.string() // String validation
|
|
76
|
+
v.number() // Number validation
|
|
77
|
+
v.int() // Integer validation
|
|
78
|
+
v.int32() // 32-bit integer validation
|
|
79
|
+
v.boolean() // Boolean validation
|
|
80
|
+
v.bigint() // BigInt validation
|
|
81
|
+
v.date() // Date validation
|
|
82
|
+
v.symbol() // Symbol validation
|
|
83
|
+
v.uint8array() // Uint8Array validation
|
|
84
|
+
v.literal('active') // Literal value
|
|
85
|
+
v.enum('admin', 'user', 'guest') // Enum values
|
|
86
|
+
v.any() // Any type
|
|
87
|
+
v.unknown() // Unknown type
|
|
88
|
+
v.null() // Null
|
|
89
|
+
v.undefined() // Undefined
|
|
90
|
+
v.nullish() // Null or undefined
|
|
91
|
+
v.void() // Void
|
|
92
|
+
v.never() // Never
|
|
366
93
|
```
|
|
367
94
|
|
|
368
|
-
### String
|
|
95
|
+
### String Formats
|
|
369
96
|
|
|
370
97
|
```typescript
|
|
371
98
|
v.string()
|
|
372
|
-
.min(
|
|
373
|
-
.max(
|
|
374
|
-
.
|
|
375
|
-
.
|
|
376
|
-
.
|
|
377
|
-
.
|
|
378
|
-
.
|
|
379
|
-
.
|
|
380
|
-
.
|
|
381
|
-
.
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
v.
|
|
393
|
-
v.
|
|
394
|
-
v.
|
|
395
|
-
v.
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
v.base64url() // Base64 URL-safe format
|
|
400
|
-
v.hex() // Hex string
|
|
401
|
-
v.jwt() // JWT format
|
|
402
|
-
v.nanoid() // NanoID format
|
|
403
|
-
v.cuid() // CUID format
|
|
404
|
-
v.cuid2() // CUID2 format
|
|
405
|
-
v.ulid() // ULID format
|
|
406
|
-
v.ipv4() // IPv4 address
|
|
407
|
-
v.ipv6() // IPv6 address
|
|
408
|
-
v.mac() // MAC address
|
|
409
|
-
v.cidrv4() // IPv4 CIDR block
|
|
410
|
-
v.cidrv6() // IPv6 CIDR block
|
|
411
|
-
v.e164() // E.164 phone number
|
|
412
|
-
v.creditCard() // Credit card number (regex plus Luhn checksum)
|
|
413
|
-
v.hash('sha256') // Hash validation
|
|
414
|
-
v.iso.date() // ISO date format
|
|
415
|
-
v.iso.time() // ISO time format
|
|
416
|
-
v.iso.dateTime() // ISO datetime format
|
|
417
|
-
v.iso.duration() // ISO duration format
|
|
418
|
-
```
|
|
419
|
-
|
|
420
|
-
### Number Validators
|
|
99
|
+
.min(3)
|
|
100
|
+
.max(100)
|
|
101
|
+
.email()
|
|
102
|
+
.url()
|
|
103
|
+
.uuid()
|
|
104
|
+
.regex(/^[a-z0-9]+$/)
|
|
105
|
+
.startsWith('https://')
|
|
106
|
+
.endsWith('.json')
|
|
107
|
+
.trim()
|
|
108
|
+
.toLowerCase();
|
|
109
|
+
|
|
110
|
+
// Top-level format helpers:
|
|
111
|
+
v.email()
|
|
112
|
+
v.uuid()
|
|
113
|
+
v.creditCard() // Luhn checksum validated
|
|
114
|
+
v.jwt()
|
|
115
|
+
v.cuid()
|
|
116
|
+
v.cuid2()
|
|
117
|
+
v.nanoid()
|
|
118
|
+
v.ulid()
|
|
119
|
+
v.ipv4()
|
|
120
|
+
v.ipv6()
|
|
121
|
+
v.iso.date()
|
|
122
|
+
v.iso.dateTime()
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Number Constraints
|
|
421
126
|
|
|
422
127
|
```typescript
|
|
423
128
|
v.number()
|
|
424
|
-
.min(0)
|
|
425
|
-
.max(100)
|
|
426
|
-
.int()
|
|
427
|
-
.positive()
|
|
428
|
-
.negative()
|
|
429
|
-
.nonnegative()
|
|
430
|
-
.
|
|
431
|
-
.finite()
|
|
432
|
-
.safe()
|
|
433
|
-
.multipleOf(5) // Multiple of value
|
|
129
|
+
.min(0)
|
|
130
|
+
.max(100)
|
|
131
|
+
.int()
|
|
132
|
+
.positive()
|
|
133
|
+
.negative()
|
|
134
|
+
.nonnegative()
|
|
135
|
+
.multipleOf(5)
|
|
136
|
+
.finite()
|
|
137
|
+
.safe();
|
|
434
138
|
```
|
|
435
139
|
|
|
436
|
-
###
|
|
140
|
+
### Objects & Collections
|
|
437
141
|
|
|
438
142
|
```typescript
|
|
439
|
-
// Arrays
|
|
440
|
-
v.array(v.string()) // Array of strings
|
|
441
|
-
.min(1) // Minimum length
|
|
442
|
-
.max(10) // Maximum length
|
|
443
|
-
.length(5) // Exact length
|
|
444
|
-
.nonempty() // Non-empty array
|
|
445
|
-
|
|
446
143
|
// Objects
|
|
447
|
-
v.object({
|
|
448
|
-
|
|
449
|
-
age: v.number()
|
|
450
|
-
})
|
|
451
|
-
.partial() // All fields optional
|
|
452
|
-
.strict() // No extra fields
|
|
453
|
-
.passthrough() // Allow extra fields
|
|
454
|
-
```
|
|
455
|
-
|
|
456
|
-
### Composite Types
|
|
457
|
-
|
|
458
|
-
```typescript
|
|
459
|
-
// Optional
|
|
460
|
-
v.optional(v.string()) // string | undefined
|
|
461
|
-
|
|
462
|
-
// Nullable
|
|
463
|
-
v.nullable(v.string()) // string | null
|
|
464
|
-
|
|
465
|
-
// Nullish
|
|
466
|
-
v.nullish(v.string()) // string | null | undefined
|
|
467
|
-
|
|
468
|
-
// Union
|
|
469
|
-
v.union(v.string(), v.number()) // string | number
|
|
470
|
-
|
|
471
|
-
// Discriminated Union
|
|
472
|
-
v.discriminatedUnion('type',
|
|
473
|
-
v.object({ type: v.literal('a'), a: v.string() }),
|
|
474
|
-
v.object({ type: v.literal('b'), b: v.number() })
|
|
475
|
-
)
|
|
476
|
-
|
|
477
|
-
// Literal
|
|
478
|
-
v.literal('active') // 'active'
|
|
479
|
-
|
|
480
|
-
// Enum
|
|
481
|
-
v.enum('red', 'green', 'blue') // 'red' | 'green' | 'blue'
|
|
482
|
-
```
|
|
483
|
-
|
|
484
|
-
### Type Coercion
|
|
485
|
-
|
|
486
|
-
```typescript
|
|
487
|
-
// Coerce strings from various types
|
|
488
|
-
v.coerce.string().parse(123) // "123"
|
|
489
|
-
v.coerce.string().parse(true) // "true"
|
|
490
|
-
|
|
491
|
-
// Coerce numbers from strings/booleans
|
|
492
|
-
v.coerce.number().parse("123") // 123
|
|
493
|
-
v.coerce.number().parse(true) // 1
|
|
494
|
-
|
|
495
|
-
// Coerce booleans from strings/numbers
|
|
496
|
-
v.coerce.boolean().parse("true") // true
|
|
497
|
-
v.coerce.boolean().parse(1) // true
|
|
498
|
-
|
|
499
|
-
// Coerce BigInt from strings/numbers
|
|
500
|
-
v.coerce.bigint().parse("123") // 123n
|
|
501
|
-
v.coerce.bigint().parse(456) // 456n
|
|
502
|
-
|
|
503
|
-
// Coerce Date from strings/timestamps
|
|
504
|
-
v.coerce.date().parse("2023-01-01") // Date object
|
|
505
|
-
v.coerce.date().parse(1672531200000) // Date object
|
|
506
|
-
```
|
|
507
|
-
|
|
508
|
-
### Object Schema Methods
|
|
509
|
-
|
|
510
|
-
```typescript
|
|
511
|
-
const userSchema = v.object({
|
|
512
|
-
name: v.string(),
|
|
513
|
-
age: v.number(),
|
|
514
|
-
email: v.string(),
|
|
515
|
-
role: v.string()
|
|
144
|
+
const profileSchema = v.object({
|
|
145
|
+
username: v.string().min(3),
|
|
146
|
+
age: v.number().optional(),
|
|
516
147
|
});
|
|
517
148
|
|
|
518
|
-
//
|
|
519
|
-
|
|
520
|
-
//
|
|
149
|
+
// Object transformations
|
|
150
|
+
profileSchema.partial(); // All fields optional
|
|
151
|
+
profileSchema.strict(); // Reject unknown fields
|
|
152
|
+
profileSchema.passthrough(); // Keep unknown fields
|
|
153
|
+
profileSchema.pick('username');
|
|
154
|
+
profileSchema.omit('age');
|
|
155
|
+
profileSchema.extend({ bio: v.string() });
|
|
521
156
|
|
|
522
|
-
//
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
isActive: v.boolean(),
|
|
529
|
-
lastLogin: v.date()
|
|
530
|
-
});
|
|
531
|
-
// Type: { name: string; age: number; email: string; role: string; isActive: boolean; lastLogin: Date }
|
|
157
|
+
// Arrays & Collections
|
|
158
|
+
v.array(v.string()).min(1).max(10);
|
|
159
|
+
v.tuple(v.string(), v.number());
|
|
160
|
+
v.record(v.string(), v.number());
|
|
161
|
+
v.set(v.string());
|
|
162
|
+
v.map(v.string(), v.number());
|
|
532
163
|
```
|
|
533
164
|
|
|
534
|
-
###
|
|
165
|
+
### Unions & Compositions
|
|
535
166
|
|
|
536
167
|
```typescript
|
|
537
|
-
//
|
|
538
|
-
|
|
539
|
-
.refine(n => n > 0, "Number must be positive");
|
|
540
|
-
|
|
541
|
-
// Data transformation with transform()
|
|
542
|
-
const uppercaseString = v.string()
|
|
543
|
-
.transform(s => s.toUpperCase());
|
|
544
|
-
|
|
545
|
-
// Default values for undefined
|
|
546
|
-
const withDefault = v.string().default("fallback");
|
|
547
|
-
withDefault.parse(undefined); // "fallback"
|
|
548
|
-
|
|
549
|
-
// Catch errors and provide fallback
|
|
550
|
-
const withCatch = v.number().catch(-1);
|
|
551
|
-
withCatch.parse("invalid"); // -1
|
|
168
|
+
// Union
|
|
169
|
+
v.union(v.string(), v.number());
|
|
552
170
|
|
|
553
|
-
//
|
|
554
|
-
const
|
|
555
|
-
.
|
|
556
|
-
.
|
|
557
|
-
|
|
558
|
-
.default('user@example.com');
|
|
559
|
-
```
|
|
171
|
+
// Discriminated Union
|
|
172
|
+
const eventSchema = v.discriminatedUnion('type',
|
|
173
|
+
v.object({ type: v.literal('click'), x: v.number(), y: v.number() }),
|
|
174
|
+
v.object({ type: v.literal('scroll'), offset: v.number() })
|
|
175
|
+
);
|
|
560
176
|
|
|
561
|
-
|
|
177
|
+
// Intersections & XOR
|
|
178
|
+
v.intersection(schemaA, schemaB);
|
|
179
|
+
v.xor(schemaA, schemaB);
|
|
562
180
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
v.json() // Any valid JSON
|
|
566
|
-
v.json(v.object({ name: v.string() })) // Typed JSON
|
|
567
|
-
|
|
568
|
-
// Lazy for recursive schemas
|
|
569
|
-
const categorySchema = v.lazy(() =>
|
|
181
|
+
// Recursive / Lazy Schemas
|
|
182
|
+
const treeSchema: ReturnType<typeof v.lazy> = v.lazy(() =>
|
|
570
183
|
v.object({
|
|
571
|
-
|
|
572
|
-
children: v.array(
|
|
184
|
+
id: v.string(),
|
|
185
|
+
children: v.array(treeSchema).optional(),
|
|
573
186
|
})
|
|
574
187
|
);
|
|
575
|
-
|
|
576
|
-
// Custom validator
|
|
577
|
-
v.custom({
|
|
578
|
-
check: (val) => typeof val === 'string' && val.length > 0,
|
|
579
|
-
message: 'Must be a non-empty string'
|
|
580
|
-
});
|
|
581
|
-
|
|
582
|
-
// File validator
|
|
583
|
-
v.file()
|
|
584
|
-
.maxSize(5 * 1024 * 1024) // 5MB
|
|
585
|
-
.type(['image/png', 'image/jpeg']);
|
|
586
|
-
|
|
587
|
-
// Function validator
|
|
588
|
-
v.function()
|
|
589
|
-
.args(v.string(), v.number())
|
|
590
|
-
.returns(v.boolean());
|
|
591
188
|
```
|
|
592
189
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
```typescript
|
|
596
|
-
import { v } from '@oxog/vld';
|
|
597
|
-
import type { Infer } from '@oxog/vld';
|
|
598
|
-
|
|
599
|
-
const schema = v.object({
|
|
600
|
-
name: v.string(),
|
|
601
|
-
age: v.number()
|
|
602
|
-
});
|
|
190
|
+
---
|
|
603
191
|
|
|
604
|
-
|
|
605
|
-
type User = Infer<typeof schema>;
|
|
606
|
-
// { name: string; age: number }
|
|
607
|
-
```
|
|
192
|
+
## Type Coercion & Modifiers
|
|
608
193
|
|
|
609
|
-
###
|
|
194
|
+
### Automatic Coercion (`v.coerce`)
|
|
610
195
|
|
|
611
196
|
```typescript
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
} from '@oxog/vld';
|
|
197
|
+
v.coerce.string().parse(123); // "123"
|
|
198
|
+
v.coerce.number().parse("42"); // 42
|
|
199
|
+
v.coerce.boolean().parse("true"); // true
|
|
200
|
+
v.coerce.bigint().parse("1000"); // 1000n
|
|
201
|
+
v.coerce.date().parse("2026-08-17"); // Date object
|
|
618
202
|
```
|
|
619
203
|
|
|
620
|
-
###
|
|
204
|
+
### Refinements, Transforms & Defaults
|
|
621
205
|
|
|
622
206
|
```typescript
|
|
623
|
-
const
|
|
207
|
+
const customSchema = v.string()
|
|
208
|
+
.transform(val => val.trim())
|
|
209
|
+
.refine(val => val.length >= 3, 'Must be at least 3 characters')
|
|
210
|
+
.default('default_value')
|
|
211
|
+
.catch('fallback_on_error');
|
|
624
212
|
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
213
|
+
// SuperRefine for multi-field cross validation
|
|
214
|
+
const passwordSchema = v.object({
|
|
215
|
+
password: v.string().min(8),
|
|
216
|
+
confirm: v.string(),
|
|
217
|
+
}).superRefine((data, ctx) => {
|
|
218
|
+
if (data.password !== data.confirm) {
|
|
219
|
+
ctx.addIssue({
|
|
220
|
+
code: 'custom',
|
|
221
|
+
path: ['confirm'],
|
|
222
|
+
message: 'Passwords do not match',
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
});
|
|
629
226
|
```
|
|
630
227
|
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
VLD supports 27+ languages out of the box with comprehensive error messages:
|
|
634
|
-
|
|
635
|
-
```typescript
|
|
636
|
-
import { v, setLocale } from '@oxog/vld';
|
|
228
|
+
---
|
|
637
229
|
|
|
638
|
-
|
|
639
|
-
const schema = v.string().min(5);
|
|
640
|
-
schema.safeParse('Hi'); // Error: "String must be at least 5 characters"
|
|
230
|
+
## Tree-Shakeable Mini API
|
|
641
231
|
|
|
642
|
-
|
|
643
|
-
setLocale('tr');
|
|
644
|
-
schema.safeParse('Hi'); // Error: "Metin en az 5 karakter olmali"
|
|
232
|
+
For bundle-constrained applications, `@oxog/vld/mini` provides pure standalone functions with zero extra overhead:
|
|
645
233
|
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
schema.safeParse('Hi'); // Error: "La cadena debe tener al menos 5 caracteres"
|
|
234
|
+
```typescript
|
|
235
|
+
import { string, number, object, optional, array } from '@oxog/vld/mini';
|
|
649
236
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
237
|
+
const userSchema = object({
|
|
238
|
+
name: string().min(2),
|
|
239
|
+
age: optional(number().positive()),
|
|
240
|
+
roles: array(string()),
|
|
241
|
+
});
|
|
653
242
|
```
|
|
654
243
|
|
|
655
|
-
|
|
244
|
+
---
|
|
656
245
|
|
|
657
|
-
|
|
658
|
-
- English (`en`) - Turkish (`tr`) - Spanish (`es`) - French (`fr`) - German (`de`)
|
|
659
|
-
- Italian (`it`) - Portuguese (`pt`) - Russian (`ru`) - Japanese (`ja`) - Korean (`ko`)
|
|
660
|
-
- Chinese (`zh`) - Arabic (`ar`) - Hindi (`hi`) - Dutch (`nl`) - Polish (`pl`)
|
|
246
|
+
## Drop-in Zod Compatibility
|
|
661
247
|
|
|
662
|
-
|
|
663
|
-
- Danish (`da`) - Swedish (`sv`) - Norwegian (`no`) - Finnish (`fi`)
|
|
248
|
+
VLD provides drop-in subpaths that mirror Zod export structures and error shapes:
|
|
664
249
|
|
|
665
|
-
|
|
666
|
-
|
|
250
|
+
```typescript
|
|
251
|
+
// Replace Zod imports seamlessly
|
|
252
|
+
import { z } from '@oxog/vld';
|
|
253
|
+
import * as core from '@oxog/vld/v4/core';
|
|
254
|
+
import * as mini from '@oxog/vld/v4-mini';
|
|
255
|
+
import * as locales from '@oxog/vld/v4/locales';
|
|
667
256
|
|
|
668
|
-
|
|
669
|
-
|
|
257
|
+
import { v, deepPartial, input, output } from '@oxog/vld';
|
|
258
|
+
```
|
|
670
259
|
|
|
671
|
-
|
|
672
|
-
-
|
|
260
|
+
- Structured error issues with `expected`, `received`, `minimum`, `maximum`, and `path`.
|
|
261
|
+
- Compatibility tested against latest stable Zod releases.
|
|
673
262
|
|
|
674
|
-
|
|
263
|
+
---
|
|
675
264
|
|
|
676
265
|
## Error Handling & Formatting
|
|
677
266
|
|
|
678
|
-
VLD provides advanced error formatting utilities similar to Zod's error handling system.
|
|
679
|
-
|
|
680
|
-
### Error Formatting Utilities
|
|
681
|
-
|
|
682
267
|
```typescript
|
|
683
268
|
import { v, VldError, treeifyError, prettifyError, flattenError } from '@oxog/vld';
|
|
684
269
|
|
|
685
|
-
const
|
|
686
|
-
username: v.string().min(3),
|
|
687
|
-
favoriteNumbers: v.array(v.number()),
|
|
688
|
-
profile: v.object({
|
|
689
|
-
name: v.string(),
|
|
690
|
-
email: v.string().email()
|
|
691
|
-
})
|
|
692
|
-
});
|
|
693
|
-
|
|
694
|
-
const result = userSchema.safeParse({
|
|
695
|
-
username: 'ab',
|
|
696
|
-
favoriteNumbers: [1, 'two', 3],
|
|
697
|
-
profile: {
|
|
698
|
-
name: '',
|
|
699
|
-
email: 'invalid-email'
|
|
700
|
-
},
|
|
701
|
-
extraField: 'not allowed'
|
|
702
|
-
});
|
|
270
|
+
const result = userSchema.safeParse(invalidData);
|
|
703
271
|
|
|
704
272
|
if (!result.success) {
|
|
705
273
|
const error = result.error as VldError;
|
|
706
274
|
|
|
707
|
-
//
|
|
708
|
-
const
|
|
275
|
+
// Flattened field errors for forms
|
|
276
|
+
const { fieldErrors, formErrors } = flattenError(error);
|
|
709
277
|
|
|
710
|
-
//
|
|
278
|
+
// Human-readable CLI / console output
|
|
711
279
|
const pretty = prettifyError(error);
|
|
712
280
|
|
|
713
|
-
//
|
|
714
|
-
const
|
|
715
|
-
}
|
|
716
|
-
```
|
|
717
|
-
|
|
718
|
-
### Using Error Formats in Practice
|
|
719
|
-
|
|
720
|
-
#### React Form Validation
|
|
721
|
-
```typescript
|
|
722
|
-
function UserForm() {
|
|
723
|
-
const [errors, setErrors] = useState<VldFlattenedError | null>(null);
|
|
724
|
-
|
|
725
|
-
const handleSubmit = (data: unknown) => {
|
|
726
|
-
const result = userSchema.safeParse(data);
|
|
727
|
-
|
|
728
|
-
if (!result.success) {
|
|
729
|
-
setErrors(flattenError(result.error as VldError));
|
|
730
|
-
} else {
|
|
731
|
-
setErrors(null);
|
|
732
|
-
}
|
|
733
|
-
};
|
|
734
|
-
|
|
735
|
-
return (
|
|
736
|
-
<form onSubmit={handleSubmit}>
|
|
737
|
-
{errors?.formErrors.map(error => (
|
|
738
|
-
<div key={error} className="form-error">{error}</div>
|
|
739
|
-
))}
|
|
740
|
-
|
|
741
|
-
<input name="username" />
|
|
742
|
-
{errors?.fieldErrors.username?.map(error => (
|
|
743
|
-
<div key={error} className="field-error">{error}</div>
|
|
744
|
-
))}
|
|
745
|
-
</form>
|
|
746
|
-
);
|
|
281
|
+
// Nested tree structure for UI inspection
|
|
282
|
+
const tree = treeifyError(error);
|
|
747
283
|
}
|
|
748
284
|
```
|
|
749
285
|
|
|
750
|
-
|
|
751
|
-
```typescript
|
|
752
|
-
app.post('/api/users', (req, res) => {
|
|
753
|
-
const result = userSchema.safeParse(req.body);
|
|
754
|
-
|
|
755
|
-
if (!result.success) {
|
|
756
|
-
const tree = treeifyError(result.error as VldError);
|
|
757
|
-
res.status(400).json({
|
|
758
|
-
error: 'Validation failed',
|
|
759
|
-
details: tree
|
|
760
|
-
});
|
|
761
|
-
}
|
|
762
|
-
});
|
|
763
|
-
```
|
|
764
|
-
|
|
765
|
-
## Advanced Examples
|
|
766
|
-
|
|
767
|
-
### Complex Validation with New Features
|
|
768
|
-
|
|
769
|
-
```typescript
|
|
770
|
-
const postSchema = v.object({
|
|
771
|
-
id: v.union(v.string().uuid(), v.number()),
|
|
772
|
-
title: v.string().min(5).max(100),
|
|
773
|
-
content: v.string().min(10),
|
|
774
|
-
author: v.object({
|
|
775
|
-
name: v.string(),
|
|
776
|
-
email: v.string().email(),
|
|
777
|
-
age: v.coerce.number(),
|
|
778
|
-
}),
|
|
779
|
-
tags: v.set(v.string()).default(new Set()),
|
|
780
|
-
metadata: v.record(v.any()),
|
|
781
|
-
coordinates: v.tuple(v.number(), v.number()),
|
|
782
|
-
publishedAt: v.date().default(() => new Date()),
|
|
783
|
-
status: v.enum('draft', 'published', 'archived')
|
|
784
|
-
});
|
|
785
|
-
|
|
786
|
-
// Extend with additional fields
|
|
787
|
-
const blogPostSchema = postSchema.extend({
|
|
788
|
-
viewCount: v.bigint().default(0n),
|
|
789
|
-
categories: v.array(v.string()).min(1),
|
|
790
|
-
featured: v.boolean().default(false)
|
|
791
|
-
});
|
|
792
|
-
```
|
|
793
|
-
|
|
794
|
-
### Discriminated Union
|
|
795
|
-
|
|
796
|
-
```typescript
|
|
797
|
-
const eventSchema = v.discriminatedUnion('type',
|
|
798
|
-
v.object({
|
|
799
|
-
type: v.literal('click'),
|
|
800
|
-
x: v.number(),
|
|
801
|
-
y: v.number()
|
|
802
|
-
}),
|
|
803
|
-
v.object({
|
|
804
|
-
type: v.literal('scroll'),
|
|
805
|
-
direction: v.enum('up', 'down'),
|
|
806
|
-
distance: v.number()
|
|
807
|
-
}),
|
|
808
|
-
v.object({
|
|
809
|
-
type: v.literal('keypress'),
|
|
810
|
-
key: v.string(),
|
|
811
|
-
modifiers: v.array(v.enum('ctrl', 'alt', 'shift'))
|
|
812
|
-
})
|
|
813
|
-
);
|
|
286
|
+
---
|
|
814
287
|
|
|
815
|
-
|
|
816
|
-
const event = eventSchema.parse({
|
|
817
|
-
type: 'click',
|
|
818
|
-
x: 100,
|
|
819
|
-
y: 200
|
|
820
|
-
});
|
|
821
|
-
```
|
|
288
|
+
## Internationalization (i18n)
|
|
822
289
|
|
|
823
|
-
|
|
290
|
+
VLD includes built-in translations for 27+ languages:
|
|
824
291
|
|
|
825
292
|
```typescript
|
|
826
|
-
|
|
827
|
-
v.object({
|
|
828
|
-
name: v.string(),
|
|
829
|
-
slug: v.string().regex(/^[a-z0-9-]+$/),
|
|
830
|
-
children: v.array(categorySchema).optional()
|
|
831
|
-
})
|
|
832
|
-
);
|
|
293
|
+
import { v, setLocale } from '@oxog/vld';
|
|
833
294
|
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
name: 'Phones',
|
|
840
|
-
slug: 'phones',
|
|
841
|
-
children: [
|
|
842
|
-
{ name: 'Smartphones', slug: 'smartphones' }
|
|
843
|
-
]
|
|
844
|
-
}
|
|
845
|
-
]
|
|
846
|
-
});
|
|
295
|
+
setLocale('tr'); // Turkish error messages
|
|
296
|
+
setLocale('es'); // Spanish
|
|
297
|
+
setLocale('de'); // German
|
|
298
|
+
setLocale('ja'); // Japanese
|
|
299
|
+
setLocale('fr'); // French
|
|
847
300
|
```
|
|
848
301
|
|
|
849
|
-
###
|
|
302
|
+
### Lazy Loading for Minimal Bundles
|
|
850
303
|
|
|
851
304
|
```typescript
|
|
852
|
-
|
|
853
|
-
username: v.string().min(3),
|
|
854
|
-
password: v.string().min(8),
|
|
855
|
-
rememberMe: v.optional(v.boolean())
|
|
856
|
-
});
|
|
857
|
-
|
|
858
|
-
type LoginForm = Infer<typeof loginSchema>;
|
|
305
|
+
import { setLocaleAsync, preloadLocales } from '@oxog/vld/locales/lazy';
|
|
859
306
|
|
|
860
|
-
|
|
861
|
-
|
|
307
|
+
// Loads locale on demand via dynamic import()
|
|
308
|
+
await setLocaleAsync('tr');
|
|
862
309
|
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
// ... handle login
|
|
866
|
-
}
|
|
867
|
-
}
|
|
310
|
+
// Preload for SSR / warm start
|
|
311
|
+
await preloadLocales(['en', 'de', 'ja']);
|
|
868
312
|
```
|
|
869
313
|
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
VLD introduces **codecs** - powerful bidirectional transformations that can convert data between different representations.
|
|
873
|
-
|
|
874
|
-
### What are Codecs?
|
|
314
|
+
---
|
|
875
315
|
|
|
876
|
-
|
|
316
|
+
## Bidirectional Codecs
|
|
877
317
|
|
|
878
318
|
```typescript
|
|
879
|
-
import { stringToNumber, jsonCodec, base64ToBytes } from '@oxog/vld';
|
|
319
|
+
import { stringToNumber, jsonCodec, base64ToBytes, hexToBytes } from '@oxog/vld';
|
|
880
320
|
|
|
881
|
-
// String to
|
|
882
|
-
const
|
|
883
|
-
const
|
|
321
|
+
// String to Number decode & encode
|
|
322
|
+
const num = stringToNumber.parse('42'); // 42
|
|
323
|
+
const str = stringToNumber.encode(42); // "42"
|
|
884
324
|
|
|
885
325
|
// JSON codec
|
|
886
|
-
const
|
|
887
|
-
const
|
|
888
|
-
const
|
|
326
|
+
const json = jsonCodec();
|
|
327
|
+
const parsed = json.parse('{"id":1}');
|
|
328
|
+
const encoded = json.encode(parsed);
|
|
889
329
|
|
|
890
|
-
// Binary
|
|
891
|
-
const bytes = base64ToBytes.parse('
|
|
330
|
+
// Binary conversions
|
|
331
|
+
const bytes = base64ToBytes.parse('SGVsbG8=');
|
|
892
332
|
```
|
|
893
333
|
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
#### String Conversion Codecs
|
|
897
|
-
```typescript
|
|
898
|
-
import { stringToNumber, stringToInt, stringToBigInt, stringToBoolean } from '@oxog/vld';
|
|
899
|
-
|
|
900
|
-
stringToNumber.parse('42.5'); // 42.5
|
|
901
|
-
stringToInt.parse('42'); // 42
|
|
902
|
-
stringToBigInt.parse('123n'); // 123n
|
|
903
|
-
stringToBoolean.parse('true'); // true
|
|
904
|
-
```
|
|
334
|
+
---
|
|
905
335
|
|
|
906
|
-
|
|
907
|
-
```typescript
|
|
908
|
-
import { isoDatetimeToDate, epochSecondsToDate, epochMillisToDate } from '@oxog/vld';
|
|
336
|
+
## Result Pattern
|
|
909
337
|
|
|
910
|
-
|
|
911
|
-
epochSecondsToDate.parse(1703505000); // Date
|
|
912
|
-
epochMillisToDate.parse(1703505000000); // Date
|
|
913
|
-
```
|
|
338
|
+
Functional error handling without exceptions:
|
|
914
339
|
|
|
915
|
-
#### URL Codecs
|
|
916
340
|
```typescript
|
|
917
|
-
import {
|
|
918
|
-
|
|
919
|
-
stringToURL.parse('https://example.com/path?q=1');
|
|
920
|
-
stringToHttpURL.parse('https://api.example.com');
|
|
921
|
-
uriComponent.parse('Hello World!'); // "Hello%20World!"
|
|
922
|
-
```
|
|
341
|
+
import { Ok, Err, match, map, flatMap, tryCatch, isOk, isErr, unwrapOr } from '@oxog/vld';
|
|
923
342
|
|
|
924
|
-
|
|
925
|
-
```typescript
|
|
926
|
-
import { base64ToBytes, hexToBytes, utf8ToBytes, bytesToUtf8 } from '@oxog/vld';
|
|
343
|
+
const result = tryCatch(() => JSON.parse(rawInput));
|
|
927
344
|
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
345
|
+
const output = match(result, {
|
|
346
|
+
ok: data => `Success: ${data.id}`,
|
|
347
|
+
err: err => `Failed: ${err.message}`,
|
|
348
|
+
});
|
|
932
349
|
```
|
|
933
350
|
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
```typescript
|
|
937
|
-
const csvToArray = v.codec(
|
|
938
|
-
v.string(),
|
|
939
|
-
v.array(v.string()),
|
|
940
|
-
{
|
|
941
|
-
decode: (csv: string) => csv.split(',').map(s => s.trim()),
|
|
942
|
-
encode: (arr: string[]) => arr.join(', ')
|
|
943
|
-
}
|
|
944
|
-
);
|
|
945
|
-
|
|
946
|
-
const tags = csvToArray.parse('react, typescript, vld');
|
|
947
|
-
// ["react", "typescript", "vld"]
|
|
948
|
-
|
|
949
|
-
const csvString = csvToArray.encode(['node', 'express', 'api']);
|
|
950
|
-
// "node, express, api"
|
|
951
|
-
```
|
|
351
|
+
---
|
|
952
352
|
|
|
953
353
|
## Plugin System
|
|
954
354
|
|
|
955
|
-
VLD v1.5.0 introduces a powerful plugin system for extending functionality.
|
|
956
|
-
|
|
957
|
-
### Creating a Plugin
|
|
958
|
-
|
|
959
355
|
```typescript
|
|
960
|
-
import { definePlugin, usePlugin, v } from '@oxog/vld';
|
|
356
|
+
import { definePlugin, usePlugin, createVldKernel, v } from '@oxog/vld';
|
|
961
357
|
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
name: 'my-plugin',
|
|
358
|
+
const phonePlugin = definePlugin({
|
|
359
|
+
name: 'phone-validator',
|
|
965
360
|
version: '1.0.0',
|
|
966
|
-
|
|
967
|
-
// Custom validators
|
|
968
361
|
validators: {
|
|
969
|
-
|
|
970
|
-
postalCode: () => v.string().regex(/^\d{5}(-\d{4})?$/)
|
|
971
|
-
},
|
|
972
|
-
|
|
973
|
-
// Custom transforms
|
|
974
|
-
transforms: {
|
|
975
|
-
normalizePhone: (phone: string) => phone.replace(/[^\d+]/g, '')
|
|
362
|
+
phone: () => v.string().regex(/^\+?[1-9]\d{1,14}$/),
|
|
976
363
|
},
|
|
977
|
-
|
|
978
|
-
// Lifecycle hooks
|
|
979
|
-
install(kernel) {
|
|
980
|
-
console.log('Plugin installed!');
|
|
981
|
-
}
|
|
982
|
-
});
|
|
983
|
-
|
|
984
|
-
// Register the plugin
|
|
985
|
-
usePlugin(myPlugin);
|
|
986
|
-
```
|
|
987
|
-
|
|
988
|
-
### Using Plugin Validators
|
|
989
|
-
|
|
990
|
-
```typescript
|
|
991
|
-
import { createVldKernel, usePlugin } from '@oxog/vld';
|
|
992
|
-
|
|
993
|
-
const kernel = createVldKernel({ debug: true });
|
|
994
|
-
|
|
995
|
-
kernel.use(myPlugin);
|
|
996
|
-
|
|
997
|
-
// Access custom validators
|
|
998
|
-
const phoneSchema = kernel.validator('phoneNumber');
|
|
999
|
-
phoneSchema.parse('+1234567890');
|
|
1000
|
-
```
|
|
1001
|
-
|
|
1002
|
-
## Result Pattern
|
|
1003
|
-
|
|
1004
|
-
VLD v1.5.0 includes a functional Result pattern for error handling.
|
|
1005
|
-
|
|
1006
|
-
### Basic Usage
|
|
1007
|
-
|
|
1008
|
-
```typescript
|
|
1009
|
-
import { Ok, Err, match, map, flatMap, tryCatch } from '@oxog/vld';
|
|
1010
|
-
|
|
1011
|
-
// Create results
|
|
1012
|
-
const success = Ok(42);
|
|
1013
|
-
const failure = Err(new Error('Something went wrong'));
|
|
1014
|
-
|
|
1015
|
-
// Pattern matching
|
|
1016
|
-
const message = match(success, {
|
|
1017
|
-
ok: (value) => `Got: ${value}`,
|
|
1018
|
-
err: (error) => `Error: ${error.message}`
|
|
1019
364
|
});
|
|
1020
365
|
|
|
1021
|
-
|
|
1022
|
-
const doubled = map(success, (n) => n * 2); // Ok(84)
|
|
1023
|
-
|
|
1024
|
-
// Chain operations
|
|
1025
|
-
const result = flatMap(success, (n) =>
|
|
1026
|
-
n > 0 ? Ok(n * 2) : Err(new Error('Must be positive'))
|
|
1027
|
-
);
|
|
1028
|
-
|
|
1029
|
-
// Safe function execution
|
|
1030
|
-
const parsed = tryCatch(() => JSON.parse('{"a":1}'));
|
|
366
|
+
usePlugin(phonePlugin);
|
|
1031
367
|
```
|
|
1032
368
|
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
```typescript
|
|
1036
|
-
import { v, isOk, isErr, unwrapOr } from '@oxog/vld';
|
|
1037
|
-
|
|
1038
|
-
const schema = v.object({
|
|
1039
|
-
name: v.string(),
|
|
1040
|
-
age: v.number().min(0)
|
|
1041
|
-
});
|
|
1042
|
-
|
|
1043
|
-
const result = schema.safeParse(data);
|
|
1044
|
-
|
|
1045
|
-
if (isOk(result)) {
|
|
1046
|
-
console.log('Valid:', result.data);
|
|
1047
|
-
} else {
|
|
1048
|
-
console.log('Invalid:', result.error);
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
|
-
// With default value
|
|
1052
|
-
const user = unwrapOr(result, { name: 'Guest', age: 0 });
|
|
1053
|
-
```
|
|
1054
|
-
|
|
1055
|
-
### Combining Results
|
|
1056
|
-
|
|
1057
|
-
```typescript
|
|
1058
|
-
import { all, fromNullable } from '@oxog/vld';
|
|
1059
|
-
|
|
1060
|
-
// Combine multiple results
|
|
1061
|
-
const results = [Ok(1), Ok(2), Ok(3)];
|
|
1062
|
-
const combined = all(results); // Ok([1, 2, 3])
|
|
1063
|
-
|
|
1064
|
-
// Convert nullable to Result
|
|
1065
|
-
const maybeValue: string | null = getValue();
|
|
1066
|
-
const result = fromNullable(maybeValue, new Error('Value is null'));
|
|
1067
|
-
```
|
|
1068
|
-
|
|
1069
|
-
## CLI Tools
|
|
1070
|
-
|
|
1071
|
-
VLD includes command-line tools for validation and benchmarking.
|
|
1072
|
-
|
|
1073
|
-
### Installation
|
|
1074
|
-
|
|
1075
|
-
```bash
|
|
1076
|
-
npm install -g @oxog/vld
|
|
1077
|
-
# or use npx
|
|
1078
|
-
npx vld --help
|
|
1079
|
-
```
|
|
1080
|
-
|
|
1081
|
-
### Commands
|
|
1082
|
-
|
|
1083
|
-
```bash
|
|
1084
|
-
# Show help
|
|
1085
|
-
vld --help
|
|
1086
|
-
|
|
1087
|
-
# Validate data
|
|
1088
|
-
vld validate schema.json data.json
|
|
1089
|
-
|
|
1090
|
-
# Run benchmarks
|
|
1091
|
-
vld benchmark
|
|
1092
|
-
|
|
1093
|
-
# Show version
|
|
1094
|
-
vld --version
|
|
1095
|
-
```
|
|
1096
|
-
|
|
1097
|
-
### Programmatic CLI
|
|
1098
|
-
|
|
1099
|
-
```typescript
|
|
1100
|
-
import { createCli, vldCli } from '@oxog/vld/cli';
|
|
1101
|
-
|
|
1102
|
-
// Use the built-in CLI
|
|
1103
|
-
vldCli.run(process.argv.slice(2));
|
|
1104
|
-
|
|
1105
|
-
// Or create a custom CLI
|
|
1106
|
-
const cli = createCli('my-app', '1.0.0', 'My validation app')
|
|
1107
|
-
.command({
|
|
1108
|
-
name: 'validate',
|
|
1109
|
-
description: 'Validate data',
|
|
1110
|
-
action: async (args, options) => {
|
|
1111
|
-
// Custom validation logic
|
|
1112
|
-
}
|
|
1113
|
-
});
|
|
1114
|
-
|
|
1115
|
-
cli.run(process.argv.slice(2));
|
|
1116
|
-
```
|
|
1117
|
-
|
|
1118
|
-
## Logger & Colored Output
|
|
1119
|
-
|
|
1120
|
-
VLD includes a logging system and colored terminal output.
|
|
1121
|
-
|
|
1122
|
-
### Logger
|
|
1123
|
-
|
|
1124
|
-
```typescript
|
|
1125
|
-
import { createLogger, setLogLevel, enableDebug } from '@oxog/vld';
|
|
1126
|
-
|
|
1127
|
-
// Create a logger
|
|
1128
|
-
const logger = createLogger({ prefix: 'VLD' });
|
|
1129
|
-
|
|
1130
|
-
logger.info('Processing...');
|
|
1131
|
-
logger.warn('Deprecated feature');
|
|
1132
|
-
logger.error('Validation failed');
|
|
1133
|
-
logger.debug('Debug info');
|
|
1134
|
-
|
|
1135
|
-
// Set log level globally
|
|
1136
|
-
setLogLevel('debug');
|
|
1137
|
-
|
|
1138
|
-
// Enable debug mode
|
|
1139
|
-
enableDebug();
|
|
1140
|
-
```
|
|
1141
|
-
|
|
1142
|
-
### Colored Output (Pigment)
|
|
1143
|
-
|
|
1144
|
-
```typescript
|
|
1145
|
-
import { pigment, red, green, blue, bold, dim } from '@oxog/vld';
|
|
1146
|
-
|
|
1147
|
-
console.log(red('Error!'));
|
|
1148
|
-
console.log(green('Success!'));
|
|
1149
|
-
console.log(bold(blue('Important')));
|
|
1150
|
-
console.log(dim('Less important'));
|
|
1151
|
-
|
|
1152
|
-
// Or use the pigment object
|
|
1153
|
-
console.log(pigment.red('Error!'));
|
|
1154
|
-
console.log(pigment.bold(pigment.green('Success!')));
|
|
1155
|
-
```
|
|
1156
|
-
|
|
1157
|
-
## VLD vs. Zod
|
|
1158
|
-
|
|
1159
|
-
VLD is designed as a compelling alternative to Zod, maintaining an audited compatibility baseline while delivering additional platform features.
|
|
1160
|
-
|
|
1161
|
-
### Feature Comparison
|
|
1162
|
-
|
|
1163
|
-
| Feature | VLD | Zod |
|
|
1164
|
-
| ----------------------- | ---------------------------------- | ------------------------------------ |
|
|
1165
|
-
| **Performance** | **Release-gated faster runtime, startup, and memory paths** | Baseline |
|
|
1166
|
-
| **Memory Usage** | **~5x less** overall | Baseline |
|
|
1167
|
-
| **Internationalization**| **Built-in 27+ locales with lazy loading** | Built-in locales |
|
|
1168
|
-
| **Dependencies** | **Zero runtime dependencies** | Zero runtime dependencies |
|
|
1169
|
-
| **Bundle Size** | Smaller | Larger |
|
|
1170
|
-
| **API** | Zod 4.4.3 compatibility gate + extensions | Standard Zod API |
|
|
1171
|
-
| **Plugin System** | **Built-in** | Not available |
|
|
1172
|
-
| **Result Pattern** | **Built-in** | Not available |
|
|
1173
|
-
| **CLI Tools** | **Built-in** | Not available |
|
|
1174
|
-
| **Codecs** | Built-in system plus reusable presets | Built-in primitives; recipes documented |
|
|
1175
|
-
| **Type Inference** | Excellent | Excellent |
|
|
1176
|
-
|
|
1177
|
-
### Seamless Migration from Zod
|
|
1178
|
-
|
|
1179
|
-
```javascript
|
|
1180
|
-
// Before (Zod)
|
|
1181
|
-
import { z } from 'zod';
|
|
1182
|
-
const schema = z.string().email();
|
|
1183
|
-
|
|
1184
|
-
// After (VLD) - Exact same syntax!
|
|
1185
|
-
import { v } from '@oxog/vld';
|
|
1186
|
-
const schema = v.string().email();
|
|
1187
|
-
```
|
|
369
|
+
---
|
|
1188
370
|
|
|
1189
|
-
|
|
371
|
+
## Performance
|
|
1190
372
|
|
|
1191
|
-
VLD
|
|
373
|
+
VLD is optimized for modern V8 runtimes. CI gates enforce performance floors on every commit against Zod:
|
|
1192
374
|
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
375
|
+
| Benchmark Case | VLD Throughput | Relative Speedup |
|
|
376
|
+
|----------------|----------------|------------------|
|
|
377
|
+
| Nullish Parse | ~214M ops/sec | **30.7x faster** |
|
|
378
|
+
| Number / Positive Int | ~253M ops/sec | **9.1x faster** |
|
|
379
|
+
| Discriminated Union | ~35M ops/sec | **4.1x faster** |
|
|
380
|
+
| Optional Parse | ~213M ops/sec | **3.8x faster** |
|
|
381
|
+
| Union Parse | ~39M ops/sec | **3.3x faster** |
|
|
382
|
+
| Simple String | ~620M ops/sec | **3.0x faster** |
|
|
383
|
+
| Array / Object Parse | ~49M ops/sec | **1.7x faster** |
|
|
1199
384
|
|
|
1200
|
-
|
|
1201
|
-
import { z } from '@oxog/vld';
|
|
1202
|
-
import * as core from '@oxog/vld/v4/core';
|
|
1203
|
-
import * as mini from '@oxog/vld/v4-mini';
|
|
1204
|
-
import * as locales from '@oxog/vld/v4/locales';
|
|
1205
|
-
```
|
|
385
|
+
Explore full benchmark results and interactive visual comparisons at [vld.oxog.dev/benchmark](https://vld.oxog.dev/benchmark).
|
|
1206
386
|
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
### Real App Drop-in Verification
|
|
1210
|
-
|
|
1211
|
-
`npm run verify:drop-in` creates two temporary TypeScript applications from the same fixture:
|
|
1212
|
-
|
|
1213
|
-
- One imports and runs against real `zod`.
|
|
1214
|
-
- One imports and runs against the locally built `@oxog/vld` package.
|
|
1215
|
-
- The fixture exercises root APIs, `v4`, `v4-mini`, `v4/core`, `v4/locales`, parsing, error formatting, JSON Schema output, mini helpers, and core factories.
|
|
1216
|
-
- The normalized runtime outputs must match exactly.
|
|
1217
|
-
|
|
1218
|
-
## Benchmarks
|
|
1219
|
-
|
|
1220
|
-
### Performance Results
|
|
1221
|
-
|
|
1222
|
-
Median of 11 samples per case against Zod 4.4.3, the latest stable release at the time of measurement. The release gate also runs focused runtime, startup, memory, package, install, security, Zod parity, and real drop-in app checks before publishing.
|
|
1223
|
-
|
|
1224
|
-
| Test Case | VLD Median Performance | Improvement |
|
|
1225
|
-
|-----------|----------------|-------------|
|
|
1226
|
-
| Simple String | 719.2M ops/sec | **3.40x faster** |
|
|
1227
|
-
| Email Validation | 22.1M ops/sec | **2.87x faster** |
|
|
1228
|
-
| Top-level Email Format | 22.8M ops/sec | **3.66x faster** |
|
|
1229
|
-
| StringBool Validation | 32.7M ops/sec | **3.44x faster** |
|
|
1230
|
-
| Number Validation | 226.2M ops/sec | **27.62x faster** |
|
|
1231
|
-
| Simple Object | 37.7M ops/sec | **1.40x faster** |
|
|
1232
|
-
| Complex Object | 2.3M ops/sec | **1.02x faster** |
|
|
1233
|
-
| Array Validation | 39.8M ops/sec | **6.74x faster** |
|
|
1234
|
-
| Union Types | 38.1M ops/sec | **4.10x faster** |
|
|
1235
|
-
| Optional Values | 236.9M ops/sec | **7.57x faster** |
|
|
1236
|
-
| Nullable Values | 224.7M ops/sec | **10.71x faster** |
|
|
1237
|
-
| Nullish Values | 228.4M ops/sec | **52.92x faster** |
|
|
1238
|
-
| Default Values | 207.8M ops/sec | **9.71x faster** |
|
|
1239
|
-
| Catch Values | 232.1M ops/sec | **46.58x faster** |
|
|
1240
|
-
| SafeParse | 162.0M ops/sec | **1.80x faster** |
|
|
1241
|
-
| Type Coercion | 204.5M ops/sec | **8.72x faster** |
|
|
1242
|
-
| Enum Validation | 180.3M ops/sec | **1.32x faster** |
|
|
1243
|
-
| Discriminated Union | 19.2M ops/sec | **1.79x faster** |
|
|
1244
|
-
| Tuple Validation | 50.0M ops/sec | **7.99x faster** |
|
|
1245
|
-
| Record Validation | 10.7M ops/sec | **5.63x faster** |
|
|
1246
|
-
| Set Validation | 18.9M ops/sec | **1.84x faster** |
|
|
1247
|
-
| Map Validation | 14.8M ops/sec | **2.37x faster** |
|
|
1248
|
-
| BigInt Validation | 164.8M ops/sec | **14.88x faster** |
|
|
1249
|
-
| Date Validation | 227.2M ops/sec | **2.57x faster** |
|
|
1250
|
-
| Symbol Validation | 230.0M ops/sec | **1.01x faster** |
|
|
1251
|
-
| Any Validation | 232.5M ops/sec | **1.01x faster** |
|
|
1252
|
-
| Unknown Validation | 234.9M ops/sec | **1.04x faster** |
|
|
1253
|
-
| Function Validation | 233.6M ops/sec | **3.52x faster** |
|
|
1254
|
-
| Template Literal Validation | 39.3M ops/sec | **1.15x faster** |
|
|
1255
|
-
| Promise Async Validation | 6.3M ops/sec | **1.38x faster** |
|
|
1256
|
-
|
|
1257
|
-
**VLD won 30/30 snapshot tests. Release checks additionally enforce runtime, startup, memory, Zod subpath parity, and real drop-in app guard thresholds against Zod.**
|
|
1258
|
-
|
|
1259
|
-
### Run Benchmarks
|
|
387
|
+
### Running Benchmarks Locally
|
|
1260
388
|
|
|
1261
389
|
```bash
|
|
1262
|
-
# Quick performance comparison
|
|
1263
390
|
npm run benchmark
|
|
1264
|
-
|
|
1265
|
-
# Median-based stable comparison
|
|
1266
|
-
npm run benchmark:stable
|
|
1267
|
-
|
|
1268
|
-
# Fast CI-friendly performance regression guard
|
|
1269
391
|
npm run benchmark:guard
|
|
1270
|
-
|
|
1271
|
-
# Full release gate: lint, source and published types, tests, exports, package and install checks, security audit, Zod parity, real drop-in app verification, and performance guards
|
|
1272
|
-
npm run release:check
|
|
1273
|
-
|
|
1274
|
-
# Memory usage comparison
|
|
1275
392
|
npm run benchmark:memory
|
|
1276
|
-
|
|
1277
|
-
# Startup time comparison
|
|
1278
393
|
npm run benchmark:startup
|
|
1279
|
-
|
|
1280
|
-
# Run all benchmarks
|
|
1281
|
-
npm run benchmark:all
|
|
394
|
+
npm run release:check
|
|
1282
395
|
```
|
|
1283
396
|
|
|
397
|
+
---
|
|
398
|
+
|
|
1284
399
|
## Contributing
|
|
1285
400
|
|
|
1286
|
-
Contributions are welcome! Please
|
|
401
|
+
Contributions are warmly welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) and [SECURITY.md](SECURITY.md) for details.
|
|
402
|
+
|
|
403
|
+
---
|
|
1287
404
|
|
|
1288
405
|
## Links
|
|
1289
406
|
|
|
1290
|
-
- [
|
|
1291
|
-
-
|
|
1292
|
-
-
|
|
407
|
+
- **Documentation & Playground**: [https://vld.oxog.dev](https://vld.oxog.dev)
|
|
408
|
+
- **NPM Package**: [https://www.npmjs.com/package/@oxog/vld](https://www.npmjs.com/package/@oxog/vld)
|
|
409
|
+
- **GitHub Repository**: [https://github.com/ersinkoc/vld](https://github.com/ersinkoc/vld)
|
|
1293
410
|
|
|
1294
411
|
---
|
|
1295
412
|
|