@oxog/vld 1.5.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +116 -0
- package/README.md +558 -517
- package/dist/chunks/bigint-ZKkWLdmi.js +1628 -0
- package/dist/chunks/bigint-ZKkWLdmi.js.map +1 -0
- package/dist/{coercion/string.js → chunks/date-7Iz2BGsL.js} +271 -5
- package/dist/chunks/date-7Iz2BGsL.js.map +1 -0
- package/dist/chunks/index-BoV-4S1F.js +894 -0
- package/dist/chunks/index-BoV-4S1F.js.map +1 -0
- package/dist/chunks/index-eWXdjyyy.js +426 -0
- package/dist/chunks/index-eWXdjyyy.js.map +1 -0
- package/dist/chunks/json-nIhyRPOU.js +1934 -0
- package/dist/chunks/json-nIhyRPOU.js.map +1 -0
- package/dist/{validators/unknown.js → chunks/unknown-CjSRcELI.js} +6 -3
- package/dist/chunks/unknown-CjSRcELI.js.map +1 -0
- package/dist/cjs/errors.cjs +387 -0
- package/dist/cjs/errors.cjs.map +1 -0
- package/dist/cjs/index.cjs +9815 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/cjs/locales/index.cjs +2877 -0
- package/dist/cjs/locales/index.cjs.map +1 -0
- package/dist/cjs/locales/lazy.cjs +3169 -0
- package/dist/cjs/locales/lazy.cjs.map +1 -0
- package/dist/cjs/mini.cjs +7161 -0
- package/dist/cjs/mini.cjs.map +1 -0
- package/dist/codecs/index.js +4 -275
- package/dist/codecs/index.js.map +1 -1
- package/dist/coercion/index.d.ts +11 -0
- package/dist/coercion/index.d.ts.map +1 -0
- package/dist/coercion/{bigint.js → index.js} +7 -4
- package/dist/coercion/index.js.map +1 -0
- package/dist/compat/emitter.js +6 -4
- package/dist/compat/emitter.js.map +1 -1
- package/dist/compat/result.js +21 -19
- package/dist/compat/result.js.map +1 -1
- package/dist/errors.js +13 -10
- package/dist/errors.js.map +1 -1
- package/dist/index.js +225 -126
- package/dist/index.js.map +1 -1
- package/dist/kernel.js +10 -7
- package/dist/kernel.js.map +1 -1
- package/dist/locales/af.js +4 -2
- package/dist/locales/af.js.map +1 -1
- package/dist/locales/ar.js +4 -2
- package/dist/locales/ar.js.map +1 -1
- package/dist/locales/bn.js +4 -2
- package/dist/locales/bn.js.map +1 -1
- package/dist/locales/da.js +4 -2
- package/dist/locales/da.js.map +1 -1
- package/dist/locales/de.js +4 -2
- package/dist/locales/de.js.map +1 -1
- package/dist/locales/en.js +4 -2
- package/dist/locales/en.js.map +1 -1
- package/dist/locales/es-MX.js +4 -2
- package/dist/locales/es-MX.js.map +1 -1
- package/dist/locales/es.js +4 -2
- package/dist/locales/es.js.map +1 -1
- package/dist/locales/fi.js +4 -2
- package/dist/locales/fi.js.map +1 -1
- package/dist/locales/fr.js +4 -2
- package/dist/locales/fr.js.map +1 -1
- package/dist/locales/hi.js +4 -2
- package/dist/locales/hi.js.map +1 -1
- package/dist/locales/id.js +4 -2
- package/dist/locales/id.js.map +1 -1
- package/dist/locales/index.js +3 -91
- package/dist/locales/index.js.map +1 -1
- package/dist/locales/it.js +4 -2
- package/dist/locales/it.js.map +1 -1
- package/dist/locales/ja.js +4 -2
- package/dist/locales/ja.js.map +1 -1
- package/dist/locales/ko.js +4 -2
- package/dist/locales/ko.js.map +1 -1
- package/dist/locales/lazy.d.ts +111 -0
- package/dist/locales/lazy.d.ts.map +1 -0
- package/dist/locales/lazy.js +220 -0
- package/dist/locales/lazy.js.map +1 -0
- package/dist/locales/nl.js +4 -2
- package/dist/locales/nl.js.map +1 -1
- package/dist/locales/no.js +4 -2
- package/dist/locales/no.js.map +1 -1
- package/dist/locales/pl.js +4 -2
- package/dist/locales/pl.js.map +1 -1
- package/dist/locales/pt-BR.js +4 -2
- package/dist/locales/pt-BR.js.map +1 -1
- package/dist/locales/pt.js +4 -2
- package/dist/locales/pt.js.map +1 -1
- package/dist/locales/ru.js +4 -2
- package/dist/locales/ru.js.map +1 -1
- package/dist/locales/sv.js +4 -2
- package/dist/locales/sv.js.map +1 -1
- package/dist/locales/sw.js +4 -2
- package/dist/locales/sw.js.map +1 -1
- package/dist/locales/th.js +4 -2
- package/dist/locales/th.js.map +1 -1
- package/dist/locales/tr.js +4 -2
- package/dist/locales/tr.js.map +1 -1
- package/dist/locales/vi.js +4 -2
- package/dist/locales/vi.js.map +1 -1
- package/dist/locales/zh.js +4 -2
- package/dist/locales/zh.js.map +1 -1
- package/dist/mini.d.ts +182 -0
- package/dist/mini.d.ts.map +1 -0
- package/dist/mini.js +132 -0
- package/dist/mini.js.map +1 -0
- package/dist/pigment.js +8 -7
- package/dist/pigment.js.map +1 -1
- package/dist/validators/index.js +5 -45
- package/dist/validators/index.js.map +1 -1
- package/package.json +69 -11
- package/dist/cli/benchmark.d.ts +0 -15
- package/dist/cli/benchmark.d.ts.map +0 -1
- package/dist/cli/benchmark.js +0 -248
- package/dist/cli/benchmark.js.map +0 -1
- package/dist/cli/bin.js +0 -35
- package/dist/cli/bin.js.map +0 -1
- package/dist/cli/commands/benchmark.js +0 -204
- package/dist/cli/commands/benchmark.js.map +0 -1
- package/dist/cli/commands/validate.js +0 -157
- package/dist/cli/commands/validate.js.map +0 -1
- package/dist/cli/generate.d.ts +0 -14
- package/dist/cli/generate.d.ts.map +0 -1
- package/dist/cli/generate.js +0 -233
- package/dist/cli/generate.js.map +0 -1
- package/dist/cli/index.js +0 -182
- package/dist/cli/index.js.map +0 -1
- package/dist/cli/validate.d.ts +0 -15
- package/dist/cli/validate.d.ts.map +0 -1
- package/dist/cli/validate.js +0 -162
- package/dist/cli/validate.js.map +0 -1
- package/dist/coercion/bigint.js.map +0 -1
- package/dist/coercion/boolean.js +0 -59
- package/dist/coercion/boolean.js.map +0 -1
- package/dist/coercion/date.js +0 -52
- package/dist/coercion/date.js.map +0 -1
- package/dist/coercion/number.js +0 -160
- package/dist/coercion/number.js.map +0 -1
- package/dist/coercion/string.js.map +0 -1
- package/dist/compat/index.js +0 -14
- package/dist/compat/index.js.map +0 -1
- package/dist/events.js +0 -8
- package/dist/events.js.map +0 -1
- package/dist/guards.d.ts +0 -143
- package/dist/guards.d.ts.map +0 -1
- package/dist/guards.js +0 -238
- package/dist/guards.js.map +0 -1
- package/dist/index-new.d.ts.map +0 -1
- package/dist/locales/backup-en.js +0 -105
- package/dist/locales/backup-en.js.map +0 -1
- package/dist/locales/types.js +0 -2
- package/dist/locales/types.js.map +0 -1
- package/dist/locales.d.ts +0 -54
- package/dist/locales.d.ts.map +0 -1
- package/dist/locales.js +0 -732
- package/dist/locales.js.map +0 -1
- package/dist/logger.js +0 -184
- package/dist/logger.js.map +0 -1
- package/dist/plugins/codecs.d.ts +0 -17
- package/dist/plugins/codecs.d.ts.map +0 -1
- package/dist/plugins/codecs.js +0 -29
- package/dist/plugins/codecs.js.map +0 -1
- package/dist/plugins/coercion.d.ts +0 -13
- package/dist/plugins/coercion.d.ts.map +0 -1
- package/dist/plugins/coercion.js +0 -34
- package/dist/plugins/coercion.js.map +0 -1
- package/dist/plugins/core-validators.d.ts +0 -13
- package/dist/plugins/core-validators.d.ts.map +0 -1
- package/dist/plugins/core-validators.js +0 -96
- package/dist/plugins/core-validators.js.map +0 -1
- package/dist/plugins/index.js +0 -8
- package/dist/plugins/index.js.map +0 -1
- package/dist/plugins/kernel.d.ts +0 -29
- package/dist/plugins/kernel.d.ts.map +0 -1
- package/dist/plugins/kernel.js +0 -486
- package/dist/plugins/kernel.js.map +0 -1
- package/dist/plugins/types.js +0 -8
- package/dist/plugins/types.js.map +0 -1
- package/dist/style.d.ts +0 -123
- package/dist/style.d.ts.map +0 -1
- package/dist/style.js +0 -369
- package/dist/style.js.map +0 -1
- package/dist/utils/codec-utils.js +0 -229
- package/dist/utils/codec-utils.js.map +0 -1
- package/dist/utils/deep-merge.js +0 -76
- package/dist/utils/deep-merge.js.map +0 -1
- package/dist/utils/ip-validation.js +0 -98
- package/dist/utils/ip-validation.js.map +0 -1
- package/dist/utils/security.js +0 -61
- package/dist/utils/security.js.map +0 -1
- package/dist/validators/any.js +0 -25
- package/dist/validators/any.js.map +0 -1
- package/dist/validators/array.js +0 -190
- package/dist/validators/array.js.map +0 -1
- package/dist/validators/base.js +0 -499
- package/dist/validators/base.js.map +0 -1
- package/dist/validators/base64.js +0 -65
- package/dist/validators/base64.js.map +0 -1
- package/dist/validators/bigint.js +0 -142
- package/dist/validators/bigint.js.map +0 -1
- package/dist/validators/boolean.js +0 -114
- package/dist/validators/boolean.js.map +0 -1
- package/dist/validators/codec.js +0 -182
- package/dist/validators/codec.js.map +0 -1
- package/dist/validators/custom.js +0 -50
- package/dist/validators/custom.js.map +0 -1
- package/dist/validators/date.js +0 -227
- package/dist/validators/date.js.map +0 -1
- package/dist/validators/discriminated-union.js +0 -98
- package/dist/validators/discriminated-union.js.map +0 -1
- package/dist/validators/enum.js +0 -82
- package/dist/validators/enum.js.map +0 -1
- package/dist/validators/file.js +0 -114
- package/dist/validators/file.js.map +0 -1
- package/dist/validators/function.js +0 -45
- package/dist/validators/function.js.map +0 -1
- package/dist/validators/hex.js +0 -62
- package/dist/validators/hex.js.map +0 -1
- package/dist/validators/intersection.js +0 -64
- package/dist/validators/intersection.js.map +0 -1
- package/dist/validators/json.js +0 -64
- package/dist/validators/json.js.map +0 -1
- package/dist/validators/lazy.js +0 -42
- package/dist/validators/lazy.js.map +0 -1
- package/dist/validators/literal.js +0 -45
- package/dist/validators/literal.js.map +0 -1
- package/dist/validators/map.js +0 -54
- package/dist/validators/map.js.map +0 -1
- package/dist/validators/nan.js +0 -32
- package/dist/validators/nan.js.map +0 -1
- package/dist/validators/never.js +0 -29
- package/dist/validators/never.js.map +0 -1
- package/dist/validators/null.js +0 -31
- package/dist/validators/null.js.map +0 -1
- package/dist/validators/number.js +0 -225
- package/dist/validators/number.js.map +0 -1
- package/dist/validators/object-optimized.d.ts +0 -23
- package/dist/validators/object-optimized.d.ts.map +0 -1
- package/dist/validators/object-optimized.js +0 -179
- package/dist/validators/object-optimized.js.map +0 -1
- package/dist/validators/object-ultra.d.ts +0 -34
- package/dist/validators/object-ultra.d.ts.map +0 -1
- package/dist/validators/object-ultra.js +0 -175
- package/dist/validators/object-ultra.js.map +0 -1
- package/dist/validators/object.js +0 -458
- package/dist/validators/object.js.map +0 -1
- package/dist/validators/record.js +0 -128
- package/dist/validators/record.js.map +0 -1
- package/dist/validators/set.js +0 -51
- package/dist/validators/set.js.map +0 -1
- package/dist/validators/string-bool.js +0 -127
- package/dist/validators/string-bool.js.map +0 -1
- package/dist/validators/string-formats.js +0 -120
- package/dist/validators/string-formats.js.map +0 -1
- package/dist/validators/string-ultra.d.ts +0 -49
- package/dist/validators/string-ultra.d.ts.map +0 -1
- package/dist/validators/string-ultra.js +0 -217
- package/dist/validators/string-ultra.js.map +0 -1
- package/dist/validators/string.js +0 -253
- package/dist/validators/string.js.map +0 -1
- package/dist/validators/symbol.js +0 -42
- package/dist/validators/symbol.js.map +0 -1
- package/dist/validators/template-literal.js +0 -102
- package/dist/validators/template-literal.js.map +0 -1
- package/dist/validators/tuple.js +0 -55
- package/dist/validators/tuple.js.map +0 -1
- package/dist/validators/uint8array.js +0 -98
- package/dist/validators/uint8array.js.map +0 -1
- package/dist/validators/undefined.js +0 -31
- package/dist/validators/undefined.js.map +0 -1
- package/dist/validators/union-optimized.d.ts +0 -15
- package/dist/validators/union-optimized.d.ts.map +0 -1
- package/dist/validators/union-optimized.js +0 -92
- package/dist/validators/union-optimized.js.map +0 -1
- package/dist/validators/union.js +0 -126
- package/dist/validators/union.js.map +0 -1
- package/dist/validators/unknown.js.map +0 -1
- package/dist/validators/void.js +0 -35
- package/dist/validators/void.js.map +0 -1
- package/dist/validators/xor.js +0 -64
- package/dist/validators/xor.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,75 +1,164 @@
|
|
|
1
1
|
# VLD - Fast & Lightweight TypeScript Validation Library
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/@oxog/vld) [](https://opensource.org/licenses/MIT) [](https://www.typescriptlang.org/) [](package.json) [](https://www.npmjs.com/package/@oxog/vld) [](https://opensource.org/licenses/MIT) [](https://www.typescriptlang.org/) [](package.json) [](package.json)
|
|
4
4
|
|
|
5
5
|
VLD is a blazing-fast, type-safe validation library for TypeScript and JavaScript with **full Zod feature parity**. Built with performance in mind, it provides a simple and intuitive API while maintaining excellent type inference and 27+ language internationalization support.
|
|
6
6
|
|
|
7
|
-
##
|
|
8
|
-
|
|
9
|
-
- [
|
|
10
|
-
- [
|
|
11
|
-
- [
|
|
12
|
-
- [
|
|
13
|
-
- [
|
|
14
|
-
- [
|
|
15
|
-
- [
|
|
16
|
-
- [
|
|
17
|
-
- [
|
|
18
|
-
- [
|
|
19
|
-
- [
|
|
20
|
-
- [
|
|
21
|
-
- [
|
|
22
|
-
- [
|
|
23
|
-
|
|
24
|
-
|
|
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](#%EF%B8%8F-error-handling--formatting)
|
|
16
|
+
- [Advanced Examples](#-advanced-examples)
|
|
17
|
+
- [Why VLD?](#-why-vld)
|
|
18
|
+
- [Codecs - Bidirectional Transformations](#-codecs---bidirectional-transformations)
|
|
19
|
+
- [Plugin System](#-plugin-system)
|
|
20
|
+
- [Result Pattern](#-result-pattern)
|
|
21
|
+
- [CLI Tools](#-cli-tools)
|
|
22
|
+
- [Migrating from Zod](#-migrating-from-zod)
|
|
23
|
+
- [Benchmarks](#-benchmarks)
|
|
24
|
+
- [Contributing](#-contributing)
|
|
25
|
+
- [Links](#-links)
|
|
26
|
+
|
|
27
|
+
## Features
|
|
25
28
|
|
|
26
29
|
### Core Features
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
|
|
37
|
-
### Advanced Zod-Compatible Features
|
|
38
|
-
-
|
|
39
|
-
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
43
|
-
-
|
|
44
|
-
-
|
|
45
|
-
-
|
|
46
|
-
|
|
47
|
-
###
|
|
48
|
-
|
|
49
|
-
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
+
- **99.23% Test Coverage**: Rigorously tested with 1858 passing tests
|
|
38
|
+
- **Industry Leading Performance**: 1.98x faster than Zod on average
|
|
39
|
+
|
|
40
|
+
### Advanced Zod-Compatible Features
|
|
41
|
+
- **Type Coercion**: `v.coerce.string()`, `v.coerce.number()`, `v.coerce.boolean()`, etc.
|
|
42
|
+
- **Advanced Types**: BigInt, Symbol, Tuple, Record, Set, Map validation
|
|
43
|
+
- **Intersection Types**: Combine multiple schemas with intelligent merging
|
|
44
|
+
- **Custom Validation**: `refine()` for custom predicates and validation logic
|
|
45
|
+
- **Data Transformation**: `transform()` for post-validation data transformation
|
|
46
|
+
- **Default Values**: `default()` for handling undefined inputs elegantly
|
|
47
|
+
- **Fallback Handling**: `catch()` for graceful error recovery
|
|
48
|
+
- **Object Utilities**: `pick()`, `omit()`, `extend()` for flexible object schemas
|
|
49
|
+
|
|
50
|
+
### NEW in v2.0.0 - Modular Architecture
|
|
51
|
+
|
|
52
|
+
#### Tree-Shakable Mini API
|
|
53
|
+
```typescript
|
|
54
|
+
import { string, number, object, optional } from '@oxog/vld/mini';
|
|
55
|
+
|
|
56
|
+
const schema = object({
|
|
57
|
+
name: string().min(1),
|
|
58
|
+
age: optional(number().positive()),
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
- **82% smaller bundles** when using only needed validators
|
|
62
|
+
- Individual factory functions for optimal tree-shaking
|
|
63
|
+
- Full TypeScript support with identical type inference
|
|
64
|
+
|
|
65
|
+
#### Lazy Locale Loading
|
|
66
|
+
```typescript
|
|
67
|
+
import { setLocaleAsync } from '@oxog/vld/locales';
|
|
68
|
+
await setLocaleAsync('tr'); // Loads Turkish on demand
|
|
69
|
+
```
|
|
70
|
+
- **92% bundle reduction** - Only English bundled by default
|
|
71
|
+
- `preloadLocales()` for SSR/batch loading
|
|
72
|
+
- Full backwards compatibility with `setLocale()`
|
|
73
|
+
|
|
74
|
+
#### Dual ESM/CJS Build
|
|
75
|
+
- ESM builds for modern bundlers (Vite, esbuild)
|
|
76
|
+
- CJS builds for Node.js and legacy environments
|
|
77
|
+
- Proper `exports` field with conditional exports
|
|
78
|
+
|
|
79
|
+
### NEW in v1.5.0 - Major Platform Release
|
|
80
|
+
|
|
81
|
+
#### Plugin System
|
|
82
|
+
- **`definePlugin()`**: Create custom plugins with validators, transforms, and codecs
|
|
83
|
+
- **`usePlugin()`**: Register plugins globally
|
|
84
|
+
- **Plugin Hooks**: Lifecycle hooks for validation events
|
|
85
|
+
- **Custom Validators**: Extend VLD with your own validators
|
|
86
|
+
|
|
87
|
+
#### Result Pattern
|
|
88
|
+
- **`Ok()`/`Err()`**: Functional error handling
|
|
89
|
+
- **`match()`**: Pattern matching on results
|
|
90
|
+
- **`map()`/`flatMap()`**: Transform results
|
|
91
|
+
- **`tryCatch()`**: Safe function execution
|
|
92
|
+
- **`all()`**: Combine multiple results
|
|
93
|
+
|
|
94
|
+
#### Event System
|
|
95
|
+
- **`createEmitter()`**: Type-safe event emitter
|
|
96
|
+
- **`createEventBus()`**: Global event bus
|
|
97
|
+
- **Validation Events**: Parse start, success, error, field validation
|
|
98
|
+
|
|
99
|
+
#### CLI Tools
|
|
100
|
+
- **`vld validate`**: Validate data from command line
|
|
101
|
+
- **`vld benchmark`**: Run performance benchmarks
|
|
102
|
+
- **Colored Output**: Beautiful terminal output with pigment
|
|
103
|
+
|
|
104
|
+
#### New Validators
|
|
105
|
+
- **`v.discriminatedUnion()`**: Discriminated union types
|
|
106
|
+
- **`v.xor()`**: Exclusive OR validation
|
|
107
|
+
- **`v.file()`**: File upload validation
|
|
108
|
+
- **`v.function()`**: Function validation
|
|
109
|
+
- **`v.custom()`**: Type-safe custom validators
|
|
110
|
+
- **`v.json()`**: JSON string validation with schema
|
|
111
|
+
- **`v.lazy()`**: Recursive schema support
|
|
112
|
+
- **`v.nan()`**: NaN validation
|
|
113
|
+
- **`v.null()`**: Null validation
|
|
114
|
+
- **`v.undefined()`**: Undefined validation
|
|
115
|
+
- **`v.templateLiteral()`**: Template literal types
|
|
116
|
+
|
|
117
|
+
#### New String Format Validators
|
|
118
|
+
- **`v.hostname()`**: Hostname validation
|
|
119
|
+
- **`v.emoji()`**: Emoji validation
|
|
120
|
+
- **`v.base64()`/`v.base64url()`**: Base64 format validation
|
|
121
|
+
- **`v.hex()`**: Hex string validation
|
|
122
|
+
- **`v.jwt()`**: JWT format validation
|
|
123
|
+
- **`v.nanoid()`/`v.cuid()`/`v.cuid2()`/`v.ulid()`**: ID format validation
|
|
124
|
+
- **`v.mac()`**: MAC address validation
|
|
125
|
+
- **`v.cidrv4()`/`v.cidrv6()`**: CIDR block validation
|
|
126
|
+
- **`v.e164()`**: E.164 phone number validation
|
|
127
|
+
- **`v.hash()`**: Hash validation (md5, sha1, sha256, sha384, sha512)
|
|
128
|
+
- **`v.iso.date()`/`v.iso.time()`/`v.iso.dateTime()`/`v.iso.duration()`**: ISO format validation
|
|
129
|
+
|
|
130
|
+
#### Enhanced Object Utilities
|
|
131
|
+
- **`v.strictObject()`**: Strict mode object validation
|
|
132
|
+
- **`v.looseObject()`**: Passthrough object validation
|
|
133
|
+
- **`v.partialRecord()`**: Partial record validation
|
|
134
|
+
- **`v.looseRecord()`**: Loose record validation
|
|
135
|
+
- **`v.int()`**: Integer shortcut
|
|
136
|
+
- **`v.int32()`**: 32-bit integer shortcut
|
|
137
|
+
- **`v.nullish()`**: Null or undefined
|
|
138
|
+
- **`v.NEVER`**: NEVER constant for transforms (Zod 4 parity)
|
|
139
|
+
|
|
140
|
+
### Codec System - Beyond Zod
|
|
141
|
+
- **Bidirectional Transformations**: Full encode/decode support for data conversion
|
|
142
|
+
- **19 Built-in Codecs**: String conversions, date parsing, JSON, URL, binary data
|
|
143
|
+
- **Zod-Compatible**: All `stringToNumber`, `jsonCodec`, `base64ToBytes`, etc.
|
|
144
|
+
- **Async Support**: Both sync and async codec operations
|
|
145
|
+
- **Custom Codecs**: Create your own bidirectional transformations
|
|
146
|
+
- **Type-Safe**: Full TypeScript support with perfect type inference
|
|
147
|
+
|
|
148
|
+
## Performance
|
|
61
149
|
|
|
62
150
|
VLD is designed for speed and efficiency with recent optimizations delivering exceptional performance:
|
|
63
151
|
|
|
64
|
-
### Speed Benchmarks (v1.
|
|
65
|
-
- **
|
|
66
|
-
- **3.
|
|
67
|
-
- **
|
|
68
|
-
- **
|
|
69
|
-
- **
|
|
70
|
-
- **2.
|
|
152
|
+
### Speed Benchmarks (v1.5.0)
|
|
153
|
+
- **3.25x faster** for email validation
|
|
154
|
+
- **3.23x faster** for number validation
|
|
155
|
+
- **3.16x faster** for optional validation
|
|
156
|
+
- **2.73x faster** for safeParse operations
|
|
157
|
+
- **2.08x faster** for enum validation
|
|
158
|
+
- **2.03x faster** for simple string validation
|
|
159
|
+
- **1.98x faster** overall average performance
|
|
71
160
|
|
|
72
|
-
###
|
|
161
|
+
### Optimizations
|
|
73
162
|
- **110x improvement** in union type validation
|
|
74
163
|
- **Simplified email regex** for maximum performance
|
|
75
164
|
- **Inline type checks** in object validation
|
|
@@ -112,7 +201,7 @@ When testing real-world patterns:
|
|
|
112
201
|
|
|
113
202
|
Run `npm run benchmark:truth` to see the real performance difference.
|
|
114
203
|
|
|
115
|
-
##
|
|
204
|
+
## Installation
|
|
116
205
|
|
|
117
206
|
```bash
|
|
118
207
|
npm install @oxog/vld
|
|
@@ -122,7 +211,7 @@ yarn add @oxog/vld
|
|
|
122
211
|
pnpm add @oxog/vld
|
|
123
212
|
```
|
|
124
213
|
|
|
125
|
-
##
|
|
214
|
+
## Quick Start
|
|
126
215
|
|
|
127
216
|
```typescript
|
|
128
217
|
import { v } from '@oxog/vld';
|
|
@@ -158,13 +247,15 @@ For advanced error formatting:
|
|
|
158
247
|
import { v, VldError, treeifyError, prettifyError, flattenError } from '@oxog/vld';
|
|
159
248
|
```
|
|
160
249
|
|
|
161
|
-
##
|
|
250
|
+
## API Reference
|
|
162
251
|
|
|
163
252
|
### Basic Types
|
|
164
253
|
|
|
165
254
|
```typescript
|
|
166
255
|
v.string() // String validation
|
|
167
|
-
v.number() // Number validation
|
|
256
|
+
v.number() // Number validation
|
|
257
|
+
v.int() // Integer validation (shortcut)
|
|
258
|
+
v.int32() // 32-bit integer validation
|
|
168
259
|
v.boolean() // Boolean validation
|
|
169
260
|
v.bigint() // BigInt validation
|
|
170
261
|
v.symbol() // Symbol validation
|
|
@@ -173,9 +264,12 @@ v.uint8array()// Uint8Array validation
|
|
|
173
264
|
v.literal() // Literal values
|
|
174
265
|
v.enum() // Enum values (supports TypeScript enums)
|
|
175
266
|
v.any() // Any type
|
|
176
|
-
v.unknown() // Unknown type
|
|
267
|
+
v.unknown() // Unknown type
|
|
177
268
|
v.void() // Void type
|
|
178
269
|
v.never() // Never type
|
|
270
|
+
v.null() // Null type
|
|
271
|
+
v.undefined() // Undefined type
|
|
272
|
+
v.nan() // NaN type
|
|
179
273
|
```
|
|
180
274
|
|
|
181
275
|
### Advanced Types
|
|
@@ -191,14 +285,19 @@ v.map(v.string(), v.number()) // Map validation
|
|
|
191
285
|
// Objects
|
|
192
286
|
v.object({ // Object schema
|
|
193
287
|
name: v.string(),
|
|
194
|
-
age: v.number()
|
|
288
|
+
age: v.number()
|
|
195
289
|
})
|
|
290
|
+
v.strictObject({...}) // No extra fields allowed
|
|
291
|
+
v.looseObject({...}) // Extra fields passed through
|
|
196
292
|
|
|
197
293
|
// Composition
|
|
198
294
|
v.union(v.string(), v.number()) // Union types
|
|
199
295
|
v.intersection(schemaA, schemaB) // Intersection types
|
|
296
|
+
v.discriminatedUnion('type', ...) // Discriminated union
|
|
297
|
+
v.xor(schemaA, schemaB) // Exclusive OR
|
|
200
298
|
v.optional(v.string()) // Optional fields
|
|
201
299
|
v.nullable(v.string()) // Nullable fields
|
|
300
|
+
v.nullish(v.string()) // Null or undefined
|
|
202
301
|
```
|
|
203
302
|
|
|
204
303
|
### String Validators
|
|
@@ -222,6 +321,36 @@ v.string()
|
|
|
222
321
|
.nonempty() // Non-empty string
|
|
223
322
|
```
|
|
224
323
|
|
|
324
|
+
### String Format Validators (Top-Level)
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
v.email() // Email validation
|
|
328
|
+
v.uuid() // UUID validation
|
|
329
|
+
v.uuid({ version: 'v4' }) // UUID v4 validation
|
|
330
|
+
v.uuidv4() // UUID v4 shortcut
|
|
331
|
+
v.hostname() // Hostname validation
|
|
332
|
+
v.emoji() // Emoji validation
|
|
333
|
+
v.base64() // Base64 format
|
|
334
|
+
v.base64url() // Base64 URL-safe format
|
|
335
|
+
v.hex() // Hex string
|
|
336
|
+
v.jwt() // JWT format
|
|
337
|
+
v.nanoid() // NanoID format
|
|
338
|
+
v.cuid() // CUID format
|
|
339
|
+
v.cuid2() // CUID2 format
|
|
340
|
+
v.ulid() // ULID format
|
|
341
|
+
v.ipv4() // IPv4 address
|
|
342
|
+
v.ipv6() // IPv6 address
|
|
343
|
+
v.mac() // MAC address
|
|
344
|
+
v.cidrv4() // IPv4 CIDR block
|
|
345
|
+
v.cidrv6() // IPv6 CIDR block
|
|
346
|
+
v.e164() // E.164 phone number
|
|
347
|
+
v.hash('sha256') // Hash validation
|
|
348
|
+
v.iso.date() // ISO date format
|
|
349
|
+
v.iso.time() // ISO time format
|
|
350
|
+
v.iso.dateTime() // ISO datetime format
|
|
351
|
+
v.iso.duration() // ISO duration format
|
|
352
|
+
```
|
|
353
|
+
|
|
225
354
|
### Number Validators
|
|
226
355
|
|
|
227
356
|
```typescript
|
|
@@ -255,6 +384,7 @@ v.object({
|
|
|
255
384
|
})
|
|
256
385
|
.partial() // All fields optional
|
|
257
386
|
.strict() // No extra fields
|
|
387
|
+
.passthrough() // Allow extra fields
|
|
258
388
|
```
|
|
259
389
|
|
|
260
390
|
### Composite Types
|
|
@@ -266,9 +396,18 @@ v.optional(v.string()) // string | undefined
|
|
|
266
396
|
// Nullable
|
|
267
397
|
v.nullable(v.string()) // string | null
|
|
268
398
|
|
|
399
|
+
// Nullish
|
|
400
|
+
v.nullish(v.string()) // string | null | undefined
|
|
401
|
+
|
|
269
402
|
// Union
|
|
270
403
|
v.union(v.string(), v.number()) // string | number
|
|
271
404
|
|
|
405
|
+
// Discriminated Union
|
|
406
|
+
v.discriminatedUnion('type',
|
|
407
|
+
v.object({ type: v.literal('a'), a: v.string() }),
|
|
408
|
+
v.object({ type: v.literal('b'), b: v.number() })
|
|
409
|
+
)
|
|
410
|
+
|
|
272
411
|
// Literal
|
|
273
412
|
v.literal('active') // 'active'
|
|
274
413
|
|
|
@@ -283,7 +422,7 @@ v.enum('red', 'green', 'blue') // 'red' | 'green' | 'blue'
|
|
|
283
422
|
v.coerce.string().parse(123) // "123"
|
|
284
423
|
v.coerce.string().parse(true) // "true"
|
|
285
424
|
|
|
286
|
-
// Coerce numbers from strings/booleans
|
|
425
|
+
// Coerce numbers from strings/booleans
|
|
287
426
|
v.coerce.number().parse("123") // 123
|
|
288
427
|
v.coerce.number().parse(true) // 1
|
|
289
428
|
|
|
@@ -314,7 +453,7 @@ const userSchema = v.object({
|
|
|
314
453
|
const publicSchema = userSchema.pick('name', 'age');
|
|
315
454
|
// Type: { name: string; age: number }
|
|
316
455
|
|
|
317
|
-
// Omit sensitive fields
|
|
456
|
+
// Omit sensitive fields
|
|
318
457
|
const safeSchema = userSchema.omit('email', 'role');
|
|
319
458
|
// Type: { name: string; age: number }
|
|
320
459
|
|
|
@@ -333,7 +472,7 @@ const extendedSchema = userSchema.extend({
|
|
|
333
472
|
const positiveNumber = v.number()
|
|
334
473
|
.refine(n => n > 0, "Number must be positive");
|
|
335
474
|
|
|
336
|
-
// Data transformation with transform()
|
|
475
|
+
// Data transformation with transform()
|
|
337
476
|
const uppercaseString = v.string()
|
|
338
477
|
.transform(s => s.toUpperCase());
|
|
339
478
|
|
|
@@ -353,6 +492,38 @@ const complexSchema = v.string()
|
|
|
353
492
|
.default('user@example.com');
|
|
354
493
|
```
|
|
355
494
|
|
|
495
|
+
### Special Validators
|
|
496
|
+
|
|
497
|
+
```typescript
|
|
498
|
+
// JSON validator with optional schema
|
|
499
|
+
v.json() // Any valid JSON
|
|
500
|
+
v.json(v.object({ name: v.string() })) // Typed JSON
|
|
501
|
+
|
|
502
|
+
// Lazy for recursive schemas
|
|
503
|
+
const categorySchema = v.lazy(() =>
|
|
504
|
+
v.object({
|
|
505
|
+
name: v.string(),
|
|
506
|
+
children: v.array(categorySchema).optional()
|
|
507
|
+
})
|
|
508
|
+
);
|
|
509
|
+
|
|
510
|
+
// Custom validator
|
|
511
|
+
v.custom({
|
|
512
|
+
check: (val) => typeof val === 'string' && val.length > 0,
|
|
513
|
+
message: 'Must be a non-empty string'
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
// File validator
|
|
517
|
+
v.file()
|
|
518
|
+
.maxSize(5 * 1024 * 1024) // 5MB
|
|
519
|
+
.type(['image/png', 'image/jpeg']);
|
|
520
|
+
|
|
521
|
+
// Function validator
|
|
522
|
+
v.function()
|
|
523
|
+
.args(v.string(), v.number())
|
|
524
|
+
.returns(v.boolean());
|
|
525
|
+
```
|
|
526
|
+
|
|
356
527
|
### Type Inference
|
|
357
528
|
|
|
358
529
|
```typescript
|
|
@@ -371,7 +542,7 @@ type User = Infer<typeof schema>;
|
|
|
371
542
|
### Error Formatting Types
|
|
372
543
|
|
|
373
544
|
```typescript
|
|
374
|
-
import {
|
|
545
|
+
import {
|
|
375
546
|
VldError, // Main error class
|
|
376
547
|
VldIssue, // Individual validation issue
|
|
377
548
|
VldErrorTree, // Nested error structure
|
|
@@ -390,7 +561,7 @@ if (!result.success) {
|
|
|
390
561
|
}
|
|
391
562
|
```
|
|
392
563
|
|
|
393
|
-
##
|
|
564
|
+
## Internationalization (i18n)
|
|
394
565
|
|
|
395
566
|
VLD supports 27+ languages out of the box with comprehensive error messages:
|
|
396
567
|
|
|
@@ -403,7 +574,7 @@ schema.safeParse('Hi'); // Error: "String must be at least 5 characters"
|
|
|
403
574
|
|
|
404
575
|
// Switch to Turkish
|
|
405
576
|
setLocale('tr');
|
|
406
|
-
schema.safeParse('Hi'); // Error: "Metin en az 5 karakter
|
|
577
|
+
schema.safeParse('Hi'); // Error: "Metin en az 5 karakter olmali"
|
|
407
578
|
|
|
408
579
|
// Switch to Spanish
|
|
409
580
|
setLocale('es');
|
|
@@ -411,42 +582,39 @@ schema.safeParse('Hi'); // Error: "La cadena debe tener al menos 5 caracteres"
|
|
|
411
582
|
|
|
412
583
|
// Switch to Japanese
|
|
413
584
|
setLocale('ja');
|
|
414
|
-
schema.safeParse('Hi'); // Error: "
|
|
585
|
+
schema.safeParse('Hi'); // Error: "..."
|
|
415
586
|
```
|
|
416
587
|
|
|
417
588
|
### Supported Languages
|
|
418
589
|
|
|
419
590
|
#### Base Languages (15):
|
|
420
|
-
-
|
|
421
|
-
-
|
|
422
|
-
-
|
|
591
|
+
- English (`en`) - Turkish (`tr`) - Spanish (`es`) - French (`fr`) - German (`de`)
|
|
592
|
+
- Italian (`it`) - Portuguese (`pt`) - Russian (`ru`) - Japanese (`ja`) - Korean (`ko`)
|
|
593
|
+
- Chinese (`zh`) - Arabic (`ar`) - Hindi (`hi`) - Dutch (`nl`) - Polish (`pl`)
|
|
423
594
|
|
|
424
595
|
#### European Languages (4):
|
|
425
|
-
-
|
|
596
|
+
- Danish (`da`) - Swedish (`sv`) - Norwegian (`no`) - Finnish (`fi`)
|
|
426
597
|
|
|
427
598
|
#### Asian Languages (4):
|
|
428
|
-
-
|
|
599
|
+
- Thai (`th`) - Vietnamese (`vi`) - Indonesian (`id`) - Bengali (`bn`)
|
|
429
600
|
|
|
430
601
|
#### African Languages (2):
|
|
431
|
-
-
|
|
602
|
+
- Swahili (`sw`) - Afrikaans (`af`)
|
|
432
603
|
|
|
433
604
|
#### American Languages (2):
|
|
434
|
-
-
|
|
605
|
+
- Portuguese Brazil (`pt-BR`) - Spanish Mexico (`es-MX`)
|
|
435
606
|
|
|
436
|
-
**Plus 75+ additional languages** supported through comprehensive type definitions with English fallback
|
|
607
|
+
**Plus 75+ additional languages** supported through comprehensive type definitions with English fallback.
|
|
437
608
|
|
|
438
|
-
##
|
|
609
|
+
## Error Handling & Formatting
|
|
439
610
|
|
|
440
|
-
VLD provides advanced error formatting utilities similar to Zod's error handling system.
|
|
611
|
+
VLD provides advanced error formatting utilities similar to Zod's error handling system.
|
|
441
612
|
|
|
442
613
|
### Error Formatting Utilities
|
|
443
614
|
|
|
444
615
|
```typescript
|
|
445
616
|
import { v, VldError, treeifyError, prettifyError, flattenError } from '@oxog/vld';
|
|
446
617
|
|
|
447
|
-
// Note: Error formatting utilities like `treeifyError` are separate named exports
|
|
448
|
-
// and are not part of the main `v` object.
|
|
449
|
-
|
|
450
618
|
const userSchema = v.object({
|
|
451
619
|
username: v.string().min(3),
|
|
452
620
|
favoriteNumbers: v.array(v.number()),
|
|
@@ -456,10 +624,9 @@ const userSchema = v.object({
|
|
|
456
624
|
})
|
|
457
625
|
});
|
|
458
626
|
|
|
459
|
-
// This will fail validation
|
|
460
627
|
const result = userSchema.safeParse({
|
|
461
|
-
username: 'ab',
|
|
462
|
-
favoriteNumbers: [1, 'two', 3],
|
|
628
|
+
username: 'ab',
|
|
629
|
+
favoriteNumbers: [1, 'two', 3],
|
|
463
630
|
profile: {
|
|
464
631
|
name: '',
|
|
465
632
|
email: 'invalid-email'
|
|
@@ -469,60 +636,15 @@ const result = userSchema.safeParse({
|
|
|
469
636
|
|
|
470
637
|
if (!result.success) {
|
|
471
638
|
const error = result.error as VldError;
|
|
472
|
-
|
|
639
|
+
|
|
473
640
|
// 1. Tree Format - Nested structure for complex UIs
|
|
474
641
|
const tree = treeifyError(error);
|
|
475
|
-
|
|
476
|
-
/*
|
|
477
|
-
{
|
|
478
|
-
errors: ['Unrecognized key: "extraField"'],
|
|
479
|
-
properties: {
|
|
480
|
-
username: { errors: ['String must be at least 3 characters'] },
|
|
481
|
-
favoriteNumbers: {
|
|
482
|
-
items: [
|
|
483
|
-
undefined,
|
|
484
|
-
{ errors: ['Expected number, received string'] },
|
|
485
|
-
undefined
|
|
486
|
-
]
|
|
487
|
-
},
|
|
488
|
-
profile: {
|
|
489
|
-
properties: {
|
|
490
|
-
name: { errors: ['String cannot be empty'] },
|
|
491
|
-
email: { errors: ['Invalid email format'] }
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
*/
|
|
497
|
-
|
|
642
|
+
|
|
498
643
|
// 2. Pretty Format - Human-readable console output
|
|
499
644
|
const pretty = prettifyError(error);
|
|
500
|
-
|
|
501
|
-
/*
|
|
502
|
-
✖ Unrecognized key: "extraField"
|
|
503
|
-
✖ String must be at least 3 characters
|
|
504
|
-
→ at username
|
|
505
|
-
✖ Expected number, received string
|
|
506
|
-
→ at favoriteNumbers[1]
|
|
507
|
-
✖ String cannot be empty
|
|
508
|
-
→ at profile.name
|
|
509
|
-
✖ Invalid email format
|
|
510
|
-
→ at profile.email
|
|
511
|
-
*/
|
|
512
|
-
|
|
645
|
+
|
|
513
646
|
// 3. Flatten Format - Simple form validation
|
|
514
647
|
const flattened = flattenError(error);
|
|
515
|
-
console.log(flattened);
|
|
516
|
-
/*
|
|
517
|
-
{
|
|
518
|
-
formErrors: ['Unrecognized key: "extraField"'],
|
|
519
|
-
fieldErrors: {
|
|
520
|
-
username: ['String must be at least 3 characters'],
|
|
521
|
-
favoriteNumbers: ['Expected number, received string'],
|
|
522
|
-
profile: ['String cannot be empty', 'Invalid email format']
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
*/
|
|
526
648
|
}
|
|
527
649
|
```
|
|
528
650
|
|
|
@@ -532,24 +654,23 @@ if (!result.success) {
|
|
|
532
654
|
```typescript
|
|
533
655
|
function UserForm() {
|
|
534
656
|
const [errors, setErrors] = useState<VldFlattenedError | null>(null);
|
|
535
|
-
|
|
657
|
+
|
|
536
658
|
const handleSubmit = (data: unknown) => {
|
|
537
659
|
const result = userSchema.safeParse(data);
|
|
538
|
-
|
|
660
|
+
|
|
539
661
|
if (!result.success) {
|
|
540
662
|
setErrors(flattenError(result.error as VldError));
|
|
541
663
|
} else {
|
|
542
664
|
setErrors(null);
|
|
543
|
-
// Process valid data
|
|
544
665
|
}
|
|
545
666
|
};
|
|
546
|
-
|
|
667
|
+
|
|
547
668
|
return (
|
|
548
669
|
<form onSubmit={handleSubmit}>
|
|
549
670
|
{errors?.formErrors.map(error => (
|
|
550
671
|
<div key={error} className="form-error">{error}</div>
|
|
551
672
|
))}
|
|
552
|
-
|
|
673
|
+
|
|
553
674
|
<input name="username" />
|
|
554
675
|
{errors?.fieldErrors.username?.map(error => (
|
|
555
676
|
<div key={error} className="field-error">{error}</div>
|
|
@@ -563,32 +684,18 @@ function UserForm() {
|
|
|
563
684
|
```typescript
|
|
564
685
|
app.post('/api/users', (req, res) => {
|
|
565
686
|
const result = userSchema.safeParse(req.body);
|
|
566
|
-
|
|
687
|
+
|
|
567
688
|
if (!result.success) {
|
|
568
689
|
const tree = treeifyError(result.error as VldError);
|
|
569
690
|
res.status(400).json({
|
|
570
691
|
error: 'Validation failed',
|
|
571
692
|
details: tree
|
|
572
693
|
});
|
|
573
|
-
} else {
|
|
574
|
-
// Process valid user data
|
|
575
694
|
}
|
|
576
695
|
});
|
|
577
696
|
```
|
|
578
697
|
|
|
579
|
-
|
|
580
|
-
```typescript
|
|
581
|
-
function validateAndLog(data: unknown) {
|
|
582
|
-
const result = userSchema.safeParse(data);
|
|
583
|
-
|
|
584
|
-
if (!result.success) {
|
|
585
|
-
console.log('Validation failed:');
|
|
586
|
-
console.log(prettifyError(result.error as VldError));
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
```
|
|
590
|
-
|
|
591
|
-
## 🔥 Advanced Examples
|
|
698
|
+
## Advanced Examples
|
|
592
699
|
|
|
593
700
|
### Complex Validation with New Features
|
|
594
701
|
|
|
@@ -600,11 +707,11 @@ const postSchema = v.object({
|
|
|
600
707
|
author: v.object({
|
|
601
708
|
name: v.string(),
|
|
602
709
|
email: v.string().email(),
|
|
603
|
-
age: v.coerce.number(),
|
|
710
|
+
age: v.coerce.number(),
|
|
604
711
|
}),
|
|
605
|
-
tags: v.set(v.string()).default(new Set()),
|
|
606
|
-
metadata: v.record(v.any()),
|
|
607
|
-
coordinates: v.tuple(v.number(), v.number()),
|
|
712
|
+
tags: v.set(v.string()).default(new Set()),
|
|
713
|
+
metadata: v.record(v.any()),
|
|
714
|
+
coordinates: v.tuple(v.number(), v.number()),
|
|
608
715
|
publishedAt: v.date().default(() => new Date()),
|
|
609
716
|
status: v.enum('draft', 'published', 'archived')
|
|
610
717
|
});
|
|
@@ -615,125 +722,60 @@ const blogPostSchema = postSchema.extend({
|
|
|
615
722
|
categories: v.array(v.string()).min(1),
|
|
616
723
|
featured: v.boolean().default(false)
|
|
617
724
|
});
|
|
618
|
-
|
|
619
|
-
// Create a public version without sensitive data
|
|
620
|
-
const publicPostSchema = blogPostSchema
|
|
621
|
-
.omit('author')
|
|
622
|
-
.extend({
|
|
623
|
-
authorName: v.string()
|
|
624
|
-
});
|
|
625
725
|
```
|
|
626
726
|
|
|
627
|
-
###
|
|
727
|
+
### Discriminated Union
|
|
628
728
|
|
|
629
729
|
```typescript
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
.
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
preferences: v.record(v.any()).default({}),
|
|
649
|
-
|
|
650
|
-
tags: v.union(
|
|
651
|
-
v.array(v.string()),
|
|
652
|
-
v.coerce.string().transform(s => s.split(','))
|
|
653
|
-
).default([])
|
|
654
|
-
});
|
|
655
|
-
|
|
656
|
-
// Intersection for combining user types
|
|
657
|
-
const baseUser = v.object({
|
|
658
|
-
id: v.string(),
|
|
659
|
-
name: v.string()
|
|
660
|
-
});
|
|
730
|
+
const eventSchema = v.discriminatedUnion('type',
|
|
731
|
+
v.object({
|
|
732
|
+
type: v.literal('click'),
|
|
733
|
+
x: v.number(),
|
|
734
|
+
y: v.number()
|
|
735
|
+
}),
|
|
736
|
+
v.object({
|
|
737
|
+
type: v.literal('scroll'),
|
|
738
|
+
direction: v.enum('up', 'down'),
|
|
739
|
+
distance: v.number()
|
|
740
|
+
}),
|
|
741
|
+
v.object({
|
|
742
|
+
type: v.literal('keypress'),
|
|
743
|
+
key: v.string(),
|
|
744
|
+
modifiers: v.array(v.enum('ctrl', 'alt', 'shift'))
|
|
745
|
+
})
|
|
746
|
+
);
|
|
661
747
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
748
|
+
// Type-safe parsing
|
|
749
|
+
const event = eventSchema.parse({
|
|
750
|
+
type: 'click',
|
|
751
|
+
x: 100,
|
|
752
|
+
y: 200
|
|
665
753
|
});
|
|
666
|
-
|
|
667
|
-
const adminSchema = v.intersection(baseUser, adminUser);
|
|
668
754
|
```
|
|
669
755
|
|
|
670
|
-
###
|
|
756
|
+
### Recursive Schemas
|
|
671
757
|
|
|
672
758
|
```typescript
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
)
|
|
679
|
-
|
|
680
|
-
// Map validation for configuration
|
|
681
|
-
const configSchema = v.map(
|
|
682
|
-
v.string().min(1), // keys must be non-empty strings
|
|
683
|
-
v.union(v.string(), v.number(), v.boolean()) // values can be mixed types
|
|
759
|
+
const categorySchema: ReturnType<typeof v.lazy> = v.lazy(() =>
|
|
760
|
+
v.object({
|
|
761
|
+
name: v.string(),
|
|
762
|
+
slug: v.string().regex(/^[a-z0-9-]+$/),
|
|
763
|
+
children: v.array(categorySchema).optional()
|
|
764
|
+
})
|
|
684
765
|
);
|
|
685
766
|
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
username: v.string()
|
|
699
|
-
.min(3)
|
|
700
|
-
.max(20)
|
|
701
|
-
.refine(s => /^[a-zA-Z0-9_]+$/.test(s), 'Invalid username format'),
|
|
702
|
-
|
|
703
|
-
email: v.coerce.string()
|
|
704
|
-
.transform(s => s.toLowerCase().trim())
|
|
705
|
-
.refine(s => s.includes('@'), 'Invalid email'),
|
|
706
|
-
|
|
707
|
-
// Age with fallback
|
|
708
|
-
age: v.coerce.number()
|
|
709
|
-
.min(13)
|
|
710
|
-
.max(120)
|
|
711
|
-
.catch(null),
|
|
712
|
-
|
|
713
|
-
// Preferences as key-value store
|
|
714
|
-
preferences: v.record(v.any()).default({}),
|
|
715
|
-
|
|
716
|
-
// Roles as a set for uniqueness
|
|
717
|
-
roles: v.set(v.enum('user', 'admin', 'moderator'))
|
|
718
|
-
.default(new Set(['user'])),
|
|
719
|
-
|
|
720
|
-
// Metadata with BigInt support
|
|
721
|
-
createdAt: v.coerce.date(),
|
|
722
|
-
userId: v.coerce.bigint(),
|
|
723
|
-
|
|
724
|
-
// Optional complex nested data
|
|
725
|
-
profile: v.object({
|
|
726
|
-
bio: v.string().max(500).default(''),
|
|
727
|
-
location: v.tuple(v.number(), v.number()).optional(),
|
|
728
|
-
socialLinks: v.record(v.string().url()).default({})
|
|
729
|
-
}).optional()
|
|
730
|
-
});
|
|
731
|
-
|
|
732
|
-
// Specialized schemas using pick/omit
|
|
733
|
-
const publicUserSchema = apiUserSchema.pick('username', 'profile');
|
|
734
|
-
const adminUserSchema = apiUserSchema.extend({
|
|
735
|
-
adminNotes: v.string().optional(),
|
|
736
|
-
lastLogin: v.date().optional()
|
|
767
|
+
const category = categorySchema.parse({
|
|
768
|
+
name: 'Electronics',
|
|
769
|
+
slug: 'electronics',
|
|
770
|
+
children: [
|
|
771
|
+
{
|
|
772
|
+
name: 'Phones',
|
|
773
|
+
slug: 'phones',
|
|
774
|
+
children: [
|
|
775
|
+
{ name: 'Smartphones', slug: 'smartphones' }
|
|
776
|
+
]
|
|
777
|
+
}
|
|
778
|
+
]
|
|
737
779
|
});
|
|
738
780
|
```
|
|
739
781
|
|
|
@@ -750,318 +792,323 @@ type LoginForm = Infer<typeof loginSchema>;
|
|
|
750
792
|
|
|
751
793
|
function handleLogin(data: unknown) {
|
|
752
794
|
const result = loginSchema.safeParse(data);
|
|
753
|
-
|
|
795
|
+
|
|
754
796
|
if (result.success) {
|
|
755
|
-
// data is now typed as LoginForm
|
|
756
797
|
const { username, password, rememberMe } = result.data;
|
|
757
798
|
// ... handle login
|
|
758
|
-
} else {
|
|
759
|
-
// Handle validation errors
|
|
760
|
-
console.error(result.error);
|
|
761
799
|
}
|
|
762
800
|
}
|
|
763
801
|
```
|
|
764
802
|
|
|
765
|
-
##
|
|
803
|
+
## Codecs - Bidirectional Transformations
|
|
766
804
|
|
|
767
|
-
VLD
|
|
805
|
+
VLD introduces **codecs** - powerful bidirectional transformations that can convert data between different representations.
|
|
768
806
|
|
|
769
|
-
###
|
|
807
|
+
### What are Codecs?
|
|
770
808
|
|
|
771
|
-
|
|
772
|
-
| ----------------------- | ---------------------------------- | ------------------------------------ |
|
|
773
|
-
| **Performance** | **~2.07x faster** (average) | Baseline |
|
|
774
|
-
| **Memory Usage** | **~78% less** overall | Baseline |
|
|
775
|
-
| **Internationalization**| ✅ **Built-in (27+ languages)** | ❌ Requires third-party library |
|
|
776
|
-
| **Dependencies** | **Zero** | `zod-i18n` for locales |
|
|
777
|
-
| **Bundle Size** | Smaller | Larger |
|
|
778
|
-
| **API** | 100% Zod-compatible | Standard Zod API |
|
|
779
|
-
| **Codecs** | ✅ Built-in, bidirectional | ✅ Via external `zod-codecs` |
|
|
780
|
-
| **Error Formatting** | ✅ Advanced (tree, pretty, flatten)| ✅ Advanced (tree, pretty, flatten)|
|
|
781
|
-
| **Type Inference** | ✅ Excellent | ✅ Excellent |
|
|
809
|
+
Codecs enable safe, type-checked conversions between different data formats:
|
|
782
810
|
|
|
783
|
-
|
|
811
|
+
```typescript
|
|
812
|
+
import { stringToNumber, jsonCodec, base64ToBytes } from '@oxog/vld';
|
|
784
813
|
|
|
785
|
-
|
|
814
|
+
// String to number conversion
|
|
815
|
+
const age = stringToNumber.parse('25'); // 25
|
|
816
|
+
const price = stringToNumber.encode(99.99); // "99.99"
|
|
786
817
|
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
const
|
|
818
|
+
// JSON codec
|
|
819
|
+
const userJson = jsonCodec();
|
|
820
|
+
const user = userJson.parse('{"name":"John","age":30}');
|
|
821
|
+
const jsonString = userJson.encode(user);
|
|
791
822
|
|
|
792
|
-
//
|
|
793
|
-
|
|
794
|
-
const schema = v.string().email();
|
|
823
|
+
// Binary data
|
|
824
|
+
const bytes = base64ToBytes.parse('SGVsbG8gV29ybGQ=');
|
|
795
825
|
```
|
|
796
826
|
|
|
797
|
-
|
|
827
|
+
### Built-in Codecs
|
|
798
828
|
|
|
799
|
-
|
|
829
|
+
#### String Conversion Codecs
|
|
830
|
+
```typescript
|
|
831
|
+
import { stringToNumber, stringToInt, stringToBigInt, stringToBoolean } from '@oxog/vld';
|
|
800
832
|
|
|
801
|
-
|
|
833
|
+
stringToNumber.parse('42.5'); // 42.5
|
|
834
|
+
stringToInt.parse('42'); // 42
|
|
835
|
+
stringToBigInt.parse('123n'); // 123n
|
|
836
|
+
stringToBoolean.parse('true'); // true
|
|
837
|
+
```
|
|
802
838
|
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
- **Network protocols**: Handle data encoding/decoding
|
|
807
|
-
- **Configuration parsing**: Convert config strings to typed values
|
|
839
|
+
#### Date Conversion Codecs
|
|
840
|
+
```typescript
|
|
841
|
+
import { isoDatetimeToDate, epochSecondsToDate, epochMillisToDate } from '@oxog/vld';
|
|
808
842
|
|
|
809
|
-
|
|
843
|
+
isoDatetimeToDate.parse('2023-12-25T10:30:00.000Z'); // Date
|
|
844
|
+
epochSecondsToDate.parse(1703505000); // Date
|
|
845
|
+
epochMillisToDate.parse(1703505000000); // Date
|
|
846
|
+
```
|
|
810
847
|
|
|
811
|
-
|
|
848
|
+
#### URL Codecs
|
|
849
|
+
```typescript
|
|
850
|
+
import { stringToURL, stringToHttpURL, uriComponent } from '@oxog/vld';
|
|
812
851
|
|
|
813
|
-
|
|
852
|
+
stringToURL.parse('https://example.com/path?q=1');
|
|
853
|
+
stringToHttpURL.parse('https://api.example.com');
|
|
854
|
+
uriComponent.parse('Hello World!'); // "Hello%20World!"
|
|
855
|
+
```
|
|
814
856
|
|
|
857
|
+
#### Binary Data Codecs
|
|
815
858
|
```typescript
|
|
816
|
-
import {
|
|
859
|
+
import { base64ToBytes, hexToBytes, utf8ToBytes, bytesToUtf8 } from '@oxog/vld';
|
|
817
860
|
|
|
818
|
-
//
|
|
819
|
-
|
|
820
|
-
|
|
861
|
+
base64ToBytes.parse('SGVsbG8='); // Uint8Array
|
|
862
|
+
hexToBytes.parse('48656c6c6f'); // Uint8Array
|
|
863
|
+
utf8ToBytes.parse('Hello'); // Uint8Array
|
|
864
|
+
bytesToUtf8.parse(new Uint8Array([72, 101, 108, 108, 111])); // "Hello"
|
|
865
|
+
```
|
|
821
866
|
|
|
822
|
-
|
|
823
|
-
const count = stringToInt.parse('42'); // 42
|
|
824
|
-
stringToInt.parse('42.5'); // ❌ Validation error: must be integer
|
|
867
|
+
### Custom Codecs
|
|
825
868
|
|
|
826
|
-
|
|
827
|
-
const
|
|
869
|
+
```typescript
|
|
870
|
+
const csvToArray = v.codec(
|
|
871
|
+
v.string(),
|
|
872
|
+
v.array(v.string()),
|
|
873
|
+
{
|
|
874
|
+
decode: (csv: string) => csv.split(',').map(s => s.trim()),
|
|
875
|
+
encode: (arr: string[]) => arr.join(', ')
|
|
876
|
+
}
|
|
877
|
+
);
|
|
828
878
|
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
stringToBoolean.parse('false'); // false
|
|
835
|
-
stringToBoolean.parse('0'); // false
|
|
879
|
+
const tags = csvToArray.parse('react, typescript, vld');
|
|
880
|
+
// ["react", "typescript", "vld"]
|
|
881
|
+
|
|
882
|
+
const csvString = csvToArray.encode(['node', 'express', 'api']);
|
|
883
|
+
// "node, express, api"
|
|
836
884
|
```
|
|
837
885
|
|
|
838
|
-
|
|
886
|
+
## Plugin System
|
|
887
|
+
|
|
888
|
+
VLD v1.5.0 introduces a powerful plugin system for extending functionality.
|
|
889
|
+
|
|
890
|
+
### Creating a Plugin
|
|
839
891
|
|
|
840
892
|
```typescript
|
|
841
|
-
import {
|
|
893
|
+
import { definePlugin, usePlugin, v } from '@oxog/vld';
|
|
894
|
+
|
|
895
|
+
// Define a custom plugin
|
|
896
|
+
const myPlugin = definePlugin({
|
|
897
|
+
name: 'my-plugin',
|
|
898
|
+
version: '1.0.0',
|
|
842
899
|
|
|
843
|
-
//
|
|
844
|
-
|
|
845
|
-
|
|
900
|
+
// Custom validators
|
|
901
|
+
validators: {
|
|
902
|
+
phoneNumber: () => v.string().regex(/^\+?[1-9]\d{1,14}$/),
|
|
903
|
+
postalCode: () => v.string().regex(/^\d{5}(-\d{4})?$/)
|
|
904
|
+
},
|
|
846
905
|
|
|
847
|
-
//
|
|
848
|
-
|
|
849
|
-
|
|
906
|
+
// Custom transforms
|
|
907
|
+
transforms: {
|
|
908
|
+
normalizePhone: (phone: string) => phone.replace(/[^\d+]/g, '')
|
|
909
|
+
},
|
|
850
910
|
|
|
851
|
-
//
|
|
852
|
-
|
|
853
|
-
console.log(
|
|
911
|
+
// Lifecycle hooks
|
|
912
|
+
install(kernel) {
|
|
913
|
+
console.log('Plugin installed!');
|
|
914
|
+
}
|
|
915
|
+
});
|
|
854
916
|
|
|
855
|
-
//
|
|
856
|
-
|
|
917
|
+
// Register the plugin
|
|
918
|
+
usePlugin(myPlugin);
|
|
857
919
|
```
|
|
858
920
|
|
|
859
|
-
|
|
921
|
+
### Using Plugin Validators
|
|
860
922
|
|
|
861
923
|
```typescript
|
|
862
|
-
import {
|
|
924
|
+
import { createVldKernel, usePlugin } from '@oxog/vld';
|
|
863
925
|
|
|
864
|
-
|
|
865
|
-
const userJson = jsonCodec();
|
|
866
|
-
const user = userJson.parse('{"name":"John","age":30}'); // { name: "John", age: 30 }
|
|
867
|
-
const jsonString = userJson.encode(user); // '{"name":"John","age":30}'
|
|
926
|
+
const kernel = createVldKernel({ debug: true });
|
|
868
927
|
|
|
869
|
-
|
|
870
|
-
const userSchema = v.object({
|
|
871
|
-
name: v.string(),
|
|
872
|
-
age: v.number()
|
|
873
|
-
});
|
|
874
|
-
const typedJsonCodec = jsonCodec(userSchema);
|
|
875
|
-
const validatedUser = typedJsonCodec.parse('{"name":"John","age":30}'); // Fully typed!
|
|
928
|
+
kernel.use(myPlugin);
|
|
876
929
|
|
|
877
|
-
//
|
|
878
|
-
const
|
|
879
|
-
|
|
880
|
-
const decoded = b64JsonCodec.parse(encoded); // { name: "Alice", age: 25 }
|
|
930
|
+
// Access custom validators
|
|
931
|
+
const phoneSchema = kernel.validator('phoneNumber');
|
|
932
|
+
phoneSchema.parse('+1234567890');
|
|
881
933
|
```
|
|
882
934
|
|
|
883
|
-
|
|
935
|
+
## Result Pattern
|
|
936
|
+
|
|
937
|
+
VLD v1.5.0 includes a functional Result pattern for error handling.
|
|
938
|
+
|
|
939
|
+
### Basic Usage
|
|
884
940
|
|
|
885
941
|
```typescript
|
|
886
|
-
import {
|
|
942
|
+
import { Ok, Err, match, map, flatMap, tryCatch } from '@oxog/vld';
|
|
943
|
+
|
|
944
|
+
// Create results
|
|
945
|
+
const success = Ok(42);
|
|
946
|
+
const failure = Err(new Error('Something went wrong'));
|
|
947
|
+
|
|
948
|
+
// Pattern matching
|
|
949
|
+
const message = match(success, {
|
|
950
|
+
ok: (value) => `Got: ${value}`,
|
|
951
|
+
err: (error) => `Error: ${error.message}`
|
|
952
|
+
});
|
|
887
953
|
|
|
888
|
-
//
|
|
889
|
-
const
|
|
890
|
-
console.log(url.hostname); // "example.com"
|
|
891
|
-
console.log(url.searchParams.get('param')); // "value"
|
|
954
|
+
// Transform results
|
|
955
|
+
const doubled = map(success, (n) => n * 2); // Ok(84)
|
|
892
956
|
|
|
893
|
-
//
|
|
894
|
-
const
|
|
895
|
-
|
|
957
|
+
// Chain operations
|
|
958
|
+
const result = flatMap(success, (n) =>
|
|
959
|
+
n > 0 ? Ok(n * 2) : Err(new Error('Must be positive'))
|
|
960
|
+
);
|
|
896
961
|
|
|
897
|
-
//
|
|
898
|
-
const
|
|
899
|
-
const decoded = uriComponent.encode(encoded); // "Hello World! 🚀"
|
|
962
|
+
// Safe function execution
|
|
963
|
+
const parsed = tryCatch(() => JSON.parse('{"a":1}'));
|
|
900
964
|
```
|
|
901
965
|
|
|
902
|
-
|
|
966
|
+
### With Validation
|
|
903
967
|
|
|
904
968
|
```typescript
|
|
905
|
-
import {
|
|
969
|
+
import { v, isOk, isErr, unwrapOr } from '@oxog/vld';
|
|
906
970
|
|
|
907
|
-
|
|
908
|
-
|
|
971
|
+
const schema = v.object({
|
|
972
|
+
name: v.string(),
|
|
973
|
+
age: v.number().min(0)
|
|
974
|
+
});
|
|
975
|
+
|
|
976
|
+
const result = schema.safeParse(data);
|
|
977
|
+
|
|
978
|
+
if (isOk(result)) {
|
|
979
|
+
console.log('Valid:', result.data);
|
|
980
|
+
} else {
|
|
981
|
+
console.log('Invalid:', result.error);
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
// With default value
|
|
985
|
+
const user = unwrapOr(result, { name: 'Guest', age: 0 });
|
|
986
|
+
```
|
|
909
987
|
|
|
910
|
-
|
|
911
|
-
const bytes2 = hexToBytes.parse('48656c6c6f'); // Uint8Array([72, 101, 108, 108, 111])
|
|
988
|
+
### Combining Results
|
|
912
989
|
|
|
913
|
-
|
|
914
|
-
|
|
990
|
+
```typescript
|
|
991
|
+
import { all, fromNullable } from '@oxog/vld';
|
|
915
992
|
|
|
916
|
-
//
|
|
917
|
-
const
|
|
993
|
+
// Combine multiple results
|
|
994
|
+
const results = [Ok(1), Ok(2), Ok(3)];
|
|
995
|
+
const combined = all(results); // Ok([1, 2, 3])
|
|
918
996
|
|
|
919
|
-
//
|
|
920
|
-
const
|
|
921
|
-
const
|
|
997
|
+
// Convert nullable to Result
|
|
998
|
+
const maybeValue: string | null = getValue();
|
|
999
|
+
const result = fromNullable(maybeValue, new Error('Value is null'));
|
|
922
1000
|
```
|
|
923
1001
|
|
|
924
|
-
|
|
1002
|
+
## CLI Tools
|
|
925
1003
|
|
|
926
|
-
|
|
1004
|
+
VLD includes command-line tools for validation and benchmarking.
|
|
927
1005
|
|
|
928
|
-
|
|
929
|
-
import { v } from '@oxog/vld';
|
|
1006
|
+
### Installation
|
|
930
1007
|
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
decode: (csv: string) => csv.split(',').map(s => s.trim()),
|
|
937
|
-
encode: (arr: string[]) => arr.join(', ')
|
|
938
|
-
}
|
|
939
|
-
);
|
|
1008
|
+
```bash
|
|
1009
|
+
npm install -g @oxog/vld
|
|
1010
|
+
# or use npx
|
|
1011
|
+
npx vld --help
|
|
1012
|
+
```
|
|
940
1013
|
|
|
941
|
-
|
|
942
|
-
const csvString = csvToArray.encode(['node', 'express', 'api']); // "node, express, api"
|
|
1014
|
+
### Commands
|
|
943
1015
|
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
const [key, value] = line.split('=');
|
|
957
|
-
if (key === 'PORT') config.port = parseInt(value, 10);
|
|
958
|
-
if (key === 'DEBUG') config.debug = value === 'true';
|
|
959
|
-
if (key === 'DB_URL') config.dbUrl = value;
|
|
960
|
-
});
|
|
961
|
-
return config;
|
|
962
|
-
},
|
|
963
|
-
encode: (config) => [
|
|
964
|
-
`PORT=${config.port}`,
|
|
965
|
-
`DEBUG=${config.debug}`,
|
|
966
|
-
`DB_URL=${config.dbUrl}`
|
|
967
|
-
].join('\n')
|
|
968
|
-
}
|
|
969
|
-
);
|
|
1016
|
+
```bash
|
|
1017
|
+
# Show help
|
|
1018
|
+
vld --help
|
|
1019
|
+
|
|
1020
|
+
# Validate data
|
|
1021
|
+
vld validate schema.json data.json
|
|
1022
|
+
|
|
1023
|
+
# Run benchmarks
|
|
1024
|
+
vld benchmark
|
|
1025
|
+
|
|
1026
|
+
# Show version
|
|
1027
|
+
vld --version
|
|
970
1028
|
```
|
|
971
1029
|
|
|
972
|
-
###
|
|
1030
|
+
### Programmatic CLI
|
|
973
1031
|
|
|
974
|
-
#### **Async Codecs**
|
|
975
1032
|
```typescript
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
body: JSON.stringify(obj)
|
|
989
|
-
});
|
|
990
|
-
return response.text();
|
|
1033
|
+
import { createCli, vldCli } from '@oxog/vld/cli';
|
|
1034
|
+
|
|
1035
|
+
// Use the built-in CLI
|
|
1036
|
+
vldCli.run(process.argv.slice(2));
|
|
1037
|
+
|
|
1038
|
+
// Or create a custom CLI
|
|
1039
|
+
const cli = createCli('my-app', '1.0.0', 'My validation app')
|
|
1040
|
+
.command({
|
|
1041
|
+
name: 'validate',
|
|
1042
|
+
description: 'Validate data',
|
|
1043
|
+
action: async (args, options) => {
|
|
1044
|
+
// Custom validation logic
|
|
991
1045
|
}
|
|
992
|
-
}
|
|
993
|
-
);
|
|
1046
|
+
});
|
|
994
1047
|
|
|
995
|
-
|
|
996
|
-
const result = await asyncCodec.parseAsync('input-data');
|
|
997
|
-
const encoded = await asyncCodec.encodeAsync({ data: 'output' });
|
|
1048
|
+
cli.run(process.argv.slice(2));
|
|
998
1049
|
```
|
|
999
1050
|
|
|
1000
|
-
|
|
1001
|
-
```typescript
|
|
1002
|
-
const safeParseResult = stringToNumber.safeParse('not-a-number');
|
|
1003
|
-
if (!safeParseResult.success) {
|
|
1004
|
-
console.error('Parse failed:', safeParseResult.error.message);
|
|
1005
|
-
}
|
|
1051
|
+
## Logger & Colored Output
|
|
1006
1052
|
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
}
|
|
1011
|
-
```
|
|
1053
|
+
VLD includes a logging system and colored terminal output.
|
|
1054
|
+
|
|
1055
|
+
### Logger
|
|
1012
1056
|
|
|
1013
|
-
#### **JWT Payload Decoder**
|
|
1014
1057
|
```typescript
|
|
1015
|
-
import {
|
|
1058
|
+
import { createLogger, setLogLevel, enableDebug } from '@oxog/vld';
|
|
1016
1059
|
|
|
1017
|
-
//
|
|
1018
|
-
const
|
|
1019
|
-
sub: v.string(),
|
|
1020
|
-
name: v.string(),
|
|
1021
|
-
iat: v.number()
|
|
1022
|
-
});
|
|
1060
|
+
// Create a logger
|
|
1061
|
+
const logger = createLogger({ prefix: 'VLD' });
|
|
1023
1062
|
|
|
1024
|
-
|
|
1025
|
-
|
|
1063
|
+
logger.info('Processing...');
|
|
1064
|
+
logger.warn('Deprecated feature');
|
|
1065
|
+
logger.error('Validation failed');
|
|
1066
|
+
logger.debug('Debug info');
|
|
1026
1067
|
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
console.log(payload.sub); // "1234567890"
|
|
1030
|
-
```
|
|
1068
|
+
// Set log level globally
|
|
1069
|
+
setLogLevel('debug');
|
|
1031
1070
|
|
|
1032
|
-
|
|
1071
|
+
// Enable debug mode
|
|
1072
|
+
enableDebug();
|
|
1073
|
+
```
|
|
1033
1074
|
|
|
1034
|
-
|
|
1035
|
-
|---------|--------|-----------|
|
|
1036
|
-
| **Direction** | Bidirectional (encode/decode) | Unidirectional (transform only) |
|
|
1037
|
-
| **Type Safety** | Input and output validation | Output validation only |
|
|
1038
|
-
| **Use Case** | Data serialization, API boundaries | Data cleaning, formatting |
|
|
1039
|
-
| **Performance** | Optimized for round-trips | Optimized for single direction |
|
|
1075
|
+
### Colored Output (Pigment)
|
|
1040
1076
|
|
|
1041
1077
|
```typescript
|
|
1042
|
-
|
|
1043
|
-
const upperCase = v.string().transform(s => s.toUpperCase());
|
|
1044
|
-
const result = upperCase.parse('hello'); // "HELLO"
|
|
1045
|
-
// No way to get back to "hello"
|
|
1078
|
+
import { pigment, red, green, blue, bold, dim } from '@oxog/vld';
|
|
1046
1079
|
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
);
|
|
1056
|
-
const encoded = upperCaseCodec.parse('hello'); // "HELLO"
|
|
1057
|
-
const original = upperCaseCodec.encode('HELLO'); // "hello"
|
|
1080
|
+
console.log(red('Error!'));
|
|
1081
|
+
console.log(green('Success!'));
|
|
1082
|
+
console.log(bold(blue('Important')));
|
|
1083
|
+
console.log(dim('Less important'));
|
|
1084
|
+
|
|
1085
|
+
// Or use the pigment object
|
|
1086
|
+
console.log(pigment.red('Error!'));
|
|
1087
|
+
console.log(pigment.bold(pigment.green('Success!')));
|
|
1058
1088
|
```
|
|
1059
1089
|
|
|
1060
|
-
##
|
|
1090
|
+
## VLD vs. Zod
|
|
1061
1091
|
|
|
1062
|
-
VLD
|
|
1092
|
+
VLD is designed as a compelling alternative to Zod, offering full feature parity while delivering significant improvements.
|
|
1093
|
+
|
|
1094
|
+
### Feature Comparison
|
|
1095
|
+
|
|
1096
|
+
| Feature | VLD | Zod |
|
|
1097
|
+
| ----------------------- | ---------------------------------- | ------------------------------------ |
|
|
1098
|
+
| **Performance** | **~1.98x faster** (average) | Baseline |
|
|
1099
|
+
| **Memory Usage** | **~78% less** overall | Baseline |
|
|
1100
|
+
| **Internationalization**| **Built-in (27+ languages)** | Requires third-party library |
|
|
1101
|
+
| **Dependencies** | **Zero** | `zod-i18n` for locales |
|
|
1102
|
+
| **Bundle Size** | Smaller | Larger |
|
|
1103
|
+
| **API** | 100% Zod-compatible | Standard Zod API |
|
|
1104
|
+
| **Plugin System** | **Built-in** | Not available |
|
|
1105
|
+
| **Result Pattern** | **Built-in** | Not available |
|
|
1106
|
+
| **CLI Tools** | **Built-in** | Not available |
|
|
1107
|
+
| **Codecs** | Built-in, bidirectional | Via external `zod-codecs` |
|
|
1108
|
+
| **Type Inference** | Excellent | Excellent |
|
|
1109
|
+
|
|
1110
|
+
### Seamless Migration from Zod
|
|
1063
1111
|
|
|
1064
|
-
### Simple Migration
|
|
1065
1112
|
```javascript
|
|
1066
1113
|
// Before (Zod)
|
|
1067
1114
|
import { z } from 'zod';
|
|
@@ -1072,32 +1119,26 @@ import { v } from '@oxog/vld';
|
|
|
1072
1119
|
const schema = v.string().email();
|
|
1073
1120
|
```
|
|
1074
1121
|
|
|
1075
|
-
|
|
1076
|
-
- **⚡ Performance**: 2-4x faster for most operations
|
|
1077
|
-
- **💾 Memory**: Uses 1.18-1.82x less memory than Zod
|
|
1078
|
-
- **🌍 Internationalization**: Built-in 27+ language support
|
|
1079
|
-
- **📦 Bundle Size**: Smaller with zero dependencies
|
|
1080
|
-
- **🔒 Security**: Immutable validators prevent memory leaks
|
|
1081
|
-
- **✅ Testing**: 96.55% test coverage with 1142 tests
|
|
1082
|
-
|
|
1083
|
-
## 📈 Benchmarks
|
|
1122
|
+
## Benchmarks
|
|
1084
1123
|
|
|
1085
1124
|
### Performance Results
|
|
1086
1125
|
|
|
1087
|
-
Latest benchmark results show VLD consistently outperforming Zod:
|
|
1088
|
-
|
|
1089
1126
|
| Test Case | VLD Performance | Improvement |
|
|
1090
1127
|
|-----------|----------------|-------------|
|
|
1091
|
-
| Simple String |
|
|
1092
|
-
| Email Validation |
|
|
1093
|
-
| Number Validation |
|
|
1094
|
-
| Object
|
|
1095
|
-
|
|
|
1096
|
-
|
|
|
1097
|
-
|
|
|
1098
|
-
|
|
|
1099
|
-
|
|
1100
|
-
|
|
1128
|
+
| Simple String | 73.0M ops/sec | **2.03x faster** |
|
|
1129
|
+
| Email Validation | 21.8M ops/sec | **3.25x faster** |
|
|
1130
|
+
| Number Validation | 36.3M ops/sec | **3.23x faster** |
|
|
1131
|
+
| Simple Object | 7.1M ops/sec | **1.02x faster** |
|
|
1132
|
+
| Complex Object | 1.9M ops/sec | **1.34x faster** |
|
|
1133
|
+
| Array Validation | 7.5M ops/sec | **1.35x faster** |
|
|
1134
|
+
| Union Types | 7.1M ops/sec | **1.29x faster** |
|
|
1135
|
+
| Optional Values | 36.1M ops/sec | **3.16x faster** |
|
|
1136
|
+
| SafeParse | 60.0M ops/sec | **2.73x faster** |
|
|
1137
|
+
| Type Coercion | 20.4M ops/sec | **1.01x faster** |
|
|
1138
|
+
| Enum Validation | 60.3M ops/sec | **2.08x faster** |
|
|
1139
|
+
| Discriminated Union | 3.6M ops/sec | Zod 1.27x faster |
|
|
1140
|
+
|
|
1141
|
+
**VLD won 11/12 tests | Average: 1.98x faster than Zod**
|
|
1101
1142
|
|
|
1102
1143
|
### Run Benchmarks
|
|
1103
1144
|
|
|
@@ -1115,11 +1156,11 @@ npm run benchmark:startup
|
|
|
1115
1156
|
npm run benchmark:all
|
|
1116
1157
|
```
|
|
1117
1158
|
|
|
1118
|
-
##
|
|
1159
|
+
## Contributing
|
|
1119
1160
|
|
|
1120
1161
|
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
1121
1162
|
|
|
1122
|
-
##
|
|
1163
|
+
## Links
|
|
1123
1164
|
|
|
1124
1165
|
- [Documentation](https://vld.oxog.dev)
|
|
1125
1166
|
- [NPM Package](https://www.npmjs.com/package/@oxog/vld)
|
|
@@ -1127,4 +1168,4 @@ Contributions are welcome! Please feel free to submit a Pull Request.
|
|
|
1127
1168
|
|
|
1128
1169
|
---
|
|
1129
1170
|
|
|
1130
|
-
Made with
|
|
1171
|
+
Made with Love by [Ersin KOC](https://github.com/ersinkoc)
|