@oxog/vld 1.4.0 → 1.5.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.
Files changed (187) hide show
  1. package/CHANGELOG.md +333 -333
  2. package/README.md +1130 -1130
  3. package/dist/cli/benchmark.d.ts +15 -0
  4. package/dist/cli/benchmark.d.ts.map +1 -0
  5. package/dist/cli/benchmark.js +248 -0
  6. package/dist/cli/benchmark.js.map +1 -0
  7. package/dist/cli/bin.d.ts +8 -0
  8. package/dist/cli/bin.d.ts.map +1 -0
  9. package/dist/cli/bin.js +35 -0
  10. package/dist/cli/bin.js.map +1 -0
  11. package/dist/cli/commands/benchmark.d.ts +11 -0
  12. package/dist/cli/commands/benchmark.d.ts.map +1 -0
  13. package/dist/cli/commands/benchmark.js +204 -0
  14. package/dist/cli/commands/benchmark.js.map +1 -0
  15. package/dist/cli/commands/validate.d.ts +11 -0
  16. package/dist/cli/commands/validate.d.ts.map +1 -0
  17. package/dist/cli/commands/validate.js +157 -0
  18. package/dist/cli/commands/validate.js.map +1 -0
  19. package/dist/cli/generate.d.ts +14 -0
  20. package/dist/cli/generate.d.ts.map +1 -0
  21. package/dist/cli/generate.js +233 -0
  22. package/dist/cli/generate.js.map +1 -0
  23. package/dist/cli/index.d.ts +59 -0
  24. package/dist/cli/index.d.ts.map +1 -0
  25. package/dist/cli/index.js +182 -0
  26. package/dist/cli/index.js.map +1 -0
  27. package/dist/cli/validate.d.ts +15 -0
  28. package/dist/cli/validate.d.ts.map +1 -0
  29. package/dist/cli/validate.js +162 -0
  30. package/dist/cli/validate.js.map +1 -0
  31. package/dist/codecs/index.d.ts.map +1 -1
  32. package/dist/codecs/index.js +29 -10
  33. package/dist/codecs/index.js.map +1 -1
  34. package/dist/coercion/boolean.d.ts.map +1 -1
  35. package/dist/coercion/boolean.js +23 -31
  36. package/dist/coercion/boolean.js.map +1 -1
  37. package/dist/coercion/date.js +1 -1
  38. package/dist/coercion/date.js.map +1 -1
  39. package/dist/compat/emitter.d.ts +73 -0
  40. package/dist/compat/emitter.d.ts.map +1 -0
  41. package/dist/compat/emitter.js +179 -0
  42. package/dist/compat/emitter.js.map +1 -0
  43. package/dist/compat/index.d.ts +15 -0
  44. package/dist/compat/index.d.ts.map +1 -0
  45. package/dist/compat/index.js +14 -0
  46. package/dist/compat/index.js.map +1 -0
  47. package/dist/compat/result.d.ts +128 -0
  48. package/dist/compat/result.d.ts.map +1 -0
  49. package/dist/compat/result.js +169 -0
  50. package/dist/compat/result.js.map +1 -0
  51. package/dist/errors.d.ts +58 -1
  52. package/dist/errors.d.ts.map +1 -1
  53. package/dist/errors.js +97 -9
  54. package/dist/errors.js.map +1 -1
  55. package/dist/events.d.ts +140 -0
  56. package/dist/events.d.ts.map +1 -0
  57. package/dist/events.js +8 -0
  58. package/dist/events.js.map +1 -0
  59. package/dist/guards.d.ts +143 -0
  60. package/dist/guards.d.ts.map +1 -0
  61. package/dist/guards.js +238 -0
  62. package/dist/guards.js.map +1 -0
  63. package/dist/index-new.d.ts.map +1 -0
  64. package/dist/index.d.ts +13 -1
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +29 -1
  67. package/dist/index.js.map +1 -1
  68. package/dist/kernel.d.ts +29 -0
  69. package/dist/kernel.d.ts.map +1 -0
  70. package/dist/kernel.js +402 -0
  71. package/dist/kernel.js.map +1 -0
  72. package/dist/locales/af.js.map +1 -1
  73. package/dist/locales/ar.js.map +1 -1
  74. package/dist/locales/backup-en.js.map +1 -1
  75. package/dist/locales/bn.js.map +1 -1
  76. package/dist/locales/da.js.map +1 -1
  77. package/dist/locales/de.js.map +1 -1
  78. package/dist/locales/en.js.map +1 -1
  79. package/dist/locales/es-MX.js.map +1 -1
  80. package/dist/locales/es.js.map +1 -1
  81. package/dist/locales/fi.js.map +1 -1
  82. package/dist/locales/fr.js.map +1 -1
  83. package/dist/locales/hi.js.map +1 -1
  84. package/dist/locales/id.js.map +1 -1
  85. package/dist/locales/it.js.map +1 -1
  86. package/dist/locales/ja.js.map +1 -1
  87. package/dist/locales/ko.js.map +1 -1
  88. package/dist/locales/nl.js.map +1 -1
  89. package/dist/locales/no.js.map +1 -1
  90. package/dist/locales/pl.js.map +1 -1
  91. package/dist/locales/pt-BR.js.map +1 -1
  92. package/dist/locales/pt.js.map +1 -1
  93. package/dist/locales/ru.js.map +1 -1
  94. package/dist/locales/sv.js.map +1 -1
  95. package/dist/locales/sw.js.map +1 -1
  96. package/dist/locales/th.js.map +1 -1
  97. package/dist/locales/tr.js.map +1 -1
  98. package/dist/locales/types.js.map +1 -1
  99. package/dist/locales/vi.js.map +1 -1
  100. package/dist/locales/zh.js.map +1 -1
  101. package/dist/locales.d.ts +54 -0
  102. package/dist/locales.d.ts.map +1 -0
  103. package/dist/locales.js +732 -0
  104. package/dist/locales.js.map +1 -0
  105. package/dist/logger.d.ts +94 -0
  106. package/dist/logger.d.ts.map +1 -0
  107. package/dist/logger.js +184 -0
  108. package/dist/logger.js.map +1 -0
  109. package/dist/pigment.d.ts +124 -0
  110. package/dist/pigment.d.ts.map +1 -0
  111. package/dist/pigment.js +177 -0
  112. package/dist/pigment.js.map +1 -0
  113. package/dist/plugins/codecs.d.ts +17 -0
  114. package/dist/plugins/codecs.d.ts.map +1 -0
  115. package/dist/plugins/codecs.js +29 -0
  116. package/dist/plugins/codecs.js.map +1 -0
  117. package/dist/plugins/coercion.d.ts +13 -0
  118. package/dist/plugins/coercion.d.ts.map +1 -0
  119. package/dist/plugins/coercion.js +34 -0
  120. package/dist/plugins/coercion.js.map +1 -0
  121. package/dist/plugins/core-validators.d.ts +13 -0
  122. package/dist/plugins/core-validators.d.ts.map +1 -0
  123. package/dist/plugins/core-validators.js +96 -0
  124. package/dist/plugins/core-validators.js.map +1 -0
  125. package/dist/plugins/index.d.ts +8 -0
  126. package/dist/plugins/index.d.ts.map +1 -0
  127. package/dist/plugins/index.js +8 -0
  128. package/dist/plugins/index.js.map +1 -0
  129. package/dist/plugins/kernel.d.ts +29 -0
  130. package/dist/plugins/kernel.d.ts.map +1 -0
  131. package/dist/plugins/kernel.js +486 -0
  132. package/dist/plugins/kernel.js.map +1 -0
  133. package/dist/plugins/types.d.ts +186 -0
  134. package/dist/plugins/types.d.ts.map +1 -0
  135. package/dist/plugins/types.js +8 -0
  136. package/dist/plugins/types.js.map +1 -0
  137. package/dist/style.d.ts +123 -0
  138. package/dist/style.d.ts.map +1 -0
  139. package/dist/style.js +369 -0
  140. package/dist/style.js.map +1 -0
  141. package/dist/utils/codec-utils.d.ts.map +1 -1
  142. package/dist/utils/codec-utils.js +6 -0
  143. package/dist/utils/codec-utils.js.map +1 -1
  144. package/dist/utils/ip-validation.d.ts.map +1 -1
  145. package/dist/utils/ip-validation.js +34 -14
  146. package/dist/utils/ip-validation.js.map +1 -1
  147. package/dist/validators/base.d.ts +3 -0
  148. package/dist/validators/base.d.ts.map +1 -1
  149. package/dist/validators/base.js +8 -0
  150. package/dist/validators/base.js.map +1 -1
  151. package/dist/validators/bigint.js.map +1 -1
  152. package/dist/validators/custom.js.map +1 -1
  153. package/dist/validators/discriminated-union.js.map +1 -1
  154. package/dist/validators/file.js.map +1 -1
  155. package/dist/validators/function.js.map +1 -1
  156. package/dist/validators/index.js.map +1 -1
  157. package/dist/validators/json.js.map +1 -1
  158. package/dist/validators/lazy.js.map +1 -1
  159. package/dist/validators/nan.js.map +1 -1
  160. package/dist/validators/null.js.map +1 -1
  161. package/dist/validators/object-optimized.d.ts +23 -0
  162. package/dist/validators/object-optimized.d.ts.map +1 -0
  163. package/dist/validators/object-optimized.js +179 -0
  164. package/dist/validators/object-optimized.js.map +1 -0
  165. package/dist/validators/object-ultra.d.ts +34 -0
  166. package/dist/validators/object-ultra.d.ts.map +1 -0
  167. package/dist/validators/object-ultra.js +175 -0
  168. package/dist/validators/object-ultra.js.map +1 -0
  169. package/dist/validators/object.d.ts.map +1 -1
  170. package/dist/validators/object.js +1 -1
  171. package/dist/validators/object.js.map +1 -1
  172. package/dist/validators/string-bool.js.map +1 -1
  173. package/dist/validators/string-formats.js.map +1 -1
  174. package/dist/validators/string-ultra.d.ts +49 -0
  175. package/dist/validators/string-ultra.d.ts.map +1 -0
  176. package/dist/validators/string-ultra.js +217 -0
  177. package/dist/validators/string-ultra.js.map +1 -0
  178. package/dist/validators/template-literal.d.ts.map +1 -1
  179. package/dist/validators/template-literal.js +13 -12
  180. package/dist/validators/template-literal.js.map +1 -1
  181. package/dist/validators/undefined.js.map +1 -1
  182. package/dist/validators/union-optimized.d.ts +15 -0
  183. package/dist/validators/union-optimized.d.ts.map +1 -0
  184. package/dist/validators/union-optimized.js +92 -0
  185. package/dist/validators/union-optimized.js.map +1 -0
  186. package/dist/validators/xor.js.map +1 -1
  187. package/package.json +95 -79
package/README.md CHANGED
@@ -1,1130 +1,1130 @@
1
- # VLD - Fast & Lightweight TypeScript Validation Library
2
-
3
- [![NPM Version](https://img.shields.io/npm/v/@oxog/vld.svg)](https://www.npmjs.com/package/@oxog/vld) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/) [![Zero Dependencies](https://img.shields.io/badge/Dependencies-0-green.svg)](package.json) [![Test Coverage](https://img.shields.io/badge/Coverage-96.55%25-brightgreen.svg)](package.json)
4
-
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
-
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
- - [🎯 Why VLD?](#-why-vld)
18
- - [🔄 Codecs - Bidirectional Transformations](#-codecs---bidirectional-transformations)
19
- - [🔄 Migrating from Zod](#-migrating-from-zod)
20
- - [📈 Benchmarks](#-benchmarks)
21
- - [🤝 Contributing](#-contributing)
22
- - [🔗 Links](#-links)
23
-
24
- ## 🚀 Features
25
-
26
- ### Core Features
27
- - **⚡ Blazing Fast**: Optimized for V8 engine with superior performance
28
- - **🎯 Type-Safe**: Full TypeScript support with excellent type inference
29
- - **📦 Zero Dependencies**: Lightweight with no external dependencies
30
- - **🌳 Tree-Shakeable**: Only import what you need
31
- - **🔧 Composable**: Chain validations for complex schemas
32
- - **⚠️ Advanced Error Formatting**: Tree, pretty, and flatten error utilities
33
- - **🌍 Multi-language**: Built-in support for 27+ languages
34
- - **✅ 96.55% Test Coverage**: Rigorously tested with 1142 passing tests
35
- - **🏆 Industry Leading Performance**: 2.52x faster than Zod on average
36
-
37
- ### Advanced Zod-Compatible Features
38
- - **🔄 Type Coercion**: `v.coerce.string()`, `v.coerce.number()`, `v.coerce.boolean()`, etc.
39
- - **📊 Advanced Types**: BigInt, Symbol, Tuple, Record, Set, Map validation
40
- - **⚡ Intersection Types**: Combine multiple schemas with intelligent merging
41
- - **🎨 Custom Validation**: `refine()` for custom predicates and validation logic
42
- - **🔄 Data Transformation**: `transform()` for post-validation data transformation
43
- - **🏠 Default Values**: `default()` for handling undefined inputs elegantly
44
- - **🛡️ Fallback Handling**: `catch()` for graceful error recovery
45
- - **🎯 Object Utilities**: `pick()`, `omit()`, `extend()` for flexible object schemas
46
-
47
- ### 🚀 **NEW in v1.4.0** - Zod 4 Full API Parity
48
- - **🌐 `v.cidrv6()`**: IPv6 CIDR block validation
49
- - **🔗 `.apply()`**: External function chaining for advanced composition
50
- - **🛡️ `.safeExtend()`**: Type-safe object extension without accidental overrides
51
-
52
- ### 🚀 Codec System - Beyond Zod
53
- - **↔️ Bidirectional Transformations**: Full encode/decode support for data conversion
54
- - **📦 19 Built-in Codecs**: String conversions, date parsing, JSON, URL, binary data
55
- - **🔗 Zod-Compatible**: All `stringToNumber`, `jsonCodec`, `base64ToBytes`, etc.
56
- - **⚡ Async Support**: Both sync and async codec operations
57
- - **🛠 Custom Codecs**: Create your own bidirectional transformations
58
- - **🎯 Type-Safe**: Full TypeScript support with perfect type inference
59
-
60
- ## 📊 Performance
61
-
62
- VLD is designed for speed and efficiency with recent optimizations delivering exceptional performance:
63
-
64
- ### Speed Benchmarks (v1.0.0 - Optimized)
65
- - **4.6x faster** for number validation with constraints
66
- - **3.6x faster** for union type validation
67
- - **2.5x faster** for email validation
68
- - **1.9x faster** for array validation
69
- - **1.7x faster** for primitive string validation
70
- - **2.8x faster** overall average performance
71
-
72
- ### Recent Optimizations (v1.0.0)
73
- - **110x improvement** in union type validation
74
- - **Simplified email regex** for maximum performance
75
- - **Inline type checks** in object validation
76
- - **Optimized loops** with direct array assignment
77
- - **SafeParse optimization** to avoid try-catch overhead
78
- - **Pre-computed keys** with Set for O(1) lookups
79
-
80
- ### Memory Efficiency
81
- - **98% less memory** for validator creation
82
- - **51% less memory** for data parsing
83
- - **86% less memory** for error handling
84
- - **78% less memory** overall average
85
-
86
- ### A Note on Real-World Benchmarking
87
-
88
- Many validation library benchmarks can be misleading because they often test with **reused schema instances**:
89
-
90
- ```javascript
91
- // What benchmarks typically test (unrealistic):
92
- const schema = z.string();
93
- for (let i = 0; i < 1000000; i++) {
94
- schema.parse(data); // Same instance reused
95
- }
96
-
97
- // What happens in real applications:
98
- app.post('/api/user', (req, res) => {
99
- // New schema created for each request
100
- const schema = z.object({
101
- email: z.string().email(),
102
- age: z.number().min(18)
103
- });
104
- schema.parse(req.body);
105
- });
106
- ```
107
-
108
- When testing real-world patterns:
109
- - **Creating new instances**: VLD is **2000x faster** than Zod
110
- - **Reused instances**: Zod benefits from V8's singleton optimization
111
- - **Real applications**: Schemas are often created dynamically, where VLD excels
112
-
113
- Run `npm run benchmark:truth` to see the real performance difference.
114
-
115
- ## 📦 Installation
116
-
117
- ```bash
118
- npm install @oxog/vld
119
- # or
120
- yarn add @oxog/vld
121
- # or
122
- pnpm add @oxog/vld
123
- ```
124
-
125
- ## 🎯 Quick Start
126
-
127
- ```typescript
128
- import { v } from '@oxog/vld';
129
-
130
- // It is recommended to import as `v` for consistency with Zod's `z`
131
- // and for a more concise syntax.
132
-
133
- // Define a schema
134
- const userSchema = v.object({
135
- name: v.string().min(2),
136
- email: v.string().email(),
137
- age: v.number().min(18).max(100),
138
- isActive: v.boolean()
139
- });
140
-
141
- // Validate data
142
- const result = userSchema.safeParse({
143
- name: 'John Doe',
144
- email: 'john@example.com',
145
- age: 25,
146
- isActive: true
147
- });
148
-
149
- if (result.success) {
150
- console.log('Valid user:', result.data);
151
- } else {
152
- console.log('Validation error:', result.error);
153
- }
154
- ```
155
-
156
- For advanced error formatting:
157
- ```typescript
158
- import { v, VldError, treeifyError, prettifyError, flattenError } from '@oxog/vld';
159
- ```
160
-
161
- ## 📖 API Reference
162
-
163
- ### Basic Types
164
-
165
- ```typescript
166
- v.string() // String validation
167
- v.number() // Number validation
168
- v.boolean() // Boolean validation
169
- v.bigint() // BigInt validation
170
- v.symbol() // Symbol validation
171
- v.date() // Date validation
172
- v.uint8array()// Uint8Array validation
173
- v.literal() // Literal values
174
- v.enum() // Enum values (supports TypeScript enums)
175
- v.any() // Any type
176
- v.unknown() // Unknown type
177
- v.void() // Void type
178
- v.never() // Never type
179
- ```
180
-
181
- ### Advanced Types
182
-
183
- ```typescript
184
- // Collections
185
- v.array(v.string()) // Array validation
186
- v.tuple(v.string(), v.number()) // Fixed-length tuple
187
- v.record(v.number()) // Record/dictionary validation
188
- v.set(v.string()) // Set validation
189
- v.map(v.string(), v.number()) // Map validation
190
-
191
- // Objects
192
- v.object({ // Object schema
193
- name: v.string(),
194
- age: v.number()
195
- })
196
-
197
- // Composition
198
- v.union(v.string(), v.number()) // Union types
199
- v.intersection(schemaA, schemaB) // Intersection types
200
- v.optional(v.string()) // Optional fields
201
- v.nullable(v.string()) // Nullable fields
202
- ```
203
-
204
- ### String Validators
205
-
206
- ```typescript
207
- v.string()
208
- .min(5) // Minimum length
209
- .max(10) // Maximum length
210
- .length(8) // Exact length
211
- .email() // Email format
212
- .url() // URL format
213
- .uuid() // UUID format
214
- .regex(/pattern/) // Custom regex
215
- .startsWith('prefix') // String prefix
216
- .endsWith('suffix') // String suffix
217
- .includes('substring') // Contains substring
218
- .ip() // IP address (v4 or v6)
219
- .trim() // Trim whitespace
220
- .toLowerCase() // Convert to lowercase
221
- .toUpperCase() // Convert to uppercase
222
- .nonempty() // Non-empty string
223
- ```
224
-
225
- ### Number Validators
226
-
227
- ```typescript
228
- v.number()
229
- .min(0) // Minimum value
230
- .max(100) // Maximum value
231
- .int() // Integer only
232
- .positive() // Positive numbers
233
- .negative() // Negative numbers
234
- .nonnegative() // >= 0
235
- .nonpositive() // <= 0
236
- .finite() // Finite numbers
237
- .safe() // Safe integers
238
- .multipleOf(5) // Multiple of value
239
- ```
240
-
241
- ### Arrays and Objects
242
-
243
- ```typescript
244
- // Arrays
245
- v.array(v.string()) // Array of strings
246
- .min(1) // Minimum length
247
- .max(10) // Maximum length
248
- .length(5) // Exact length
249
- .nonempty() // Non-empty array
250
-
251
- // Objects
252
- v.object({
253
- name: v.string(),
254
- age: v.number()
255
- })
256
- .partial() // All fields optional
257
- .strict() // No extra fields
258
- ```
259
-
260
- ### Composite Types
261
-
262
- ```typescript
263
- // Optional
264
- v.optional(v.string()) // string | undefined
265
-
266
- // Nullable
267
- v.nullable(v.string()) // string | null
268
-
269
- // Union
270
- v.union(v.string(), v.number()) // string | number
271
-
272
- // Literal
273
- v.literal('active') // 'active'
274
-
275
- // Enum
276
- v.enum('red', 'green', 'blue') // 'red' | 'green' | 'blue'
277
- ```
278
-
279
- ### Type Coercion
280
-
281
- ```typescript
282
- // Coerce strings from various types
283
- v.coerce.string().parse(123) // "123"
284
- v.coerce.string().parse(true) // "true"
285
-
286
- // Coerce numbers from strings/booleans
287
- v.coerce.number().parse("123") // 123
288
- v.coerce.number().parse(true) // 1
289
-
290
- // Coerce booleans from strings/numbers
291
- v.coerce.boolean().parse("true") // true
292
- v.coerce.boolean().parse(1) // true
293
-
294
- // Coerce BigInt from strings/numbers
295
- v.coerce.bigint().parse("123") // 123n
296
- v.coerce.bigint().parse(456) // 456n
297
-
298
- // Coerce Date from strings/timestamps
299
- v.coerce.date().parse("2023-01-01") // Date object
300
- v.coerce.date().parse(1672531200000) // Date object
301
- ```
302
-
303
- ### Object Schema Methods
304
-
305
- ```typescript
306
- const userSchema = v.object({
307
- name: v.string(),
308
- age: v.number(),
309
- email: v.string(),
310
- role: v.string()
311
- });
312
-
313
- // Pick specific fields
314
- const publicSchema = userSchema.pick('name', 'age');
315
- // Type: { name: string; age: number }
316
-
317
- // Omit sensitive fields
318
- const safeSchema = userSchema.omit('email', 'role');
319
- // Type: { name: string; age: number }
320
-
321
- // Extend with new fields
322
- const extendedSchema = userSchema.extend({
323
- isActive: v.boolean(),
324
- lastLogin: v.date()
325
- });
326
- // Type: { name: string; age: number; email: string; role: string; isActive: boolean; lastLogin: Date }
327
- ```
328
-
329
- ### Advanced Validation Methods
330
-
331
- ```typescript
332
- // Custom validation with refine()
333
- const positiveNumber = v.number()
334
- .refine(n => n > 0, "Number must be positive");
335
-
336
- // Data transformation with transform()
337
- const uppercaseString = v.string()
338
- .transform(s => s.toUpperCase());
339
-
340
- // Default values for undefined
341
- const withDefault = v.string().default("fallback");
342
- withDefault.parse(undefined); // "fallback"
343
-
344
- // Catch errors and provide fallback
345
- const withCatch = v.number().catch(-1);
346
- withCatch.parse("invalid"); // -1
347
-
348
- // Method chaining
349
- const complexSchema = v.string()
350
- .min(3)
351
- .transform(s => s.trim())
352
- .refine(s => s.includes('@'), 'Must contain @')
353
- .default('user@example.com');
354
- ```
355
-
356
- ### Type Inference
357
-
358
- ```typescript
359
- import { v, Infer } from '@oxog/vld';
360
-
361
- const schema = v.object({
362
- name: v.string(),
363
- age: v.number()
364
- });
365
-
366
- // Automatically infer the type
367
- type User = Infer<typeof schema>;
368
- // { name: string; age: number }
369
- ```
370
-
371
- ### Error Formatting Types
372
-
373
- ```typescript
374
- import {
375
- VldError, // Main error class
376
- VldIssue, // Individual validation issue
377
- VldErrorTree, // Nested error structure
378
- VldFlattenedError // Flattened error structure
379
- } from '@oxog/vld';
380
- ```
381
-
382
- ### Custom Error Messages
383
-
384
- ```typescript
385
- const schema = v.string().min(8, 'Password must be at least 8 characters');
386
-
387
- const result = schema.safeParse('short');
388
- if (!result.success) {
389
- console.log(result.error.message); // 'Password must be at least 8 characters'
390
- }
391
- ```
392
-
393
- ## 🌍 Internationalization (i18n)
394
-
395
- VLD supports 27+ languages out of the box with comprehensive error messages:
396
-
397
- ```typescript
398
- import { v, setLocale } from '@oxog/vld';
399
-
400
- // Default is English
401
- const schema = v.string().min(5);
402
- schema.safeParse('Hi'); // Error: "String must be at least 5 characters"
403
-
404
- // Switch to Turkish
405
- setLocale('tr');
406
- schema.safeParse('Hi'); // Error: "Metin en az 5 karakter olmalı"
407
-
408
- // Switch to Spanish
409
- setLocale('es');
410
- schema.safeParse('Hi'); // Error: "La cadena debe tener al menos 5 caracteres"
411
-
412
- // Switch to Japanese
413
- setLocale('ja');
414
- schema.safeParse('Hi'); // Error: "文字列は5文字以上である必要があります"
415
- ```
416
-
417
- ### Supported Languages
418
-
419
- #### Base Languages (15):
420
- - 🇬🇧 English (`en`) - 🇹🇷 Turkish (`tr`) - 🇪🇸 Spanish (`es`) - 🇫🇷 French (`fr`) - 🇩🇪 German (`de`)
421
- - 🇮🇹 Italian (`it`) - 🇵🇹 Portuguese (`pt`) - 🇷🇺 Russian (`ru`) - 🇯🇵 Japanese (`ja`) - 🇰🇷 Korean (`ko`)
422
- - 🇨🇳 Chinese (`zh`) - 🇸🇦 Arabic (`ar`) - 🇮🇳 Hindi (`hi`) - 🇳🇱 Dutch (`nl`) - 🇵🇱 Polish (`pl`)
423
-
424
- #### European Languages (4):
425
- - 🇩🇰 Danish (`da`) - 🇸🇪 Swedish (`sv`) - 🇳🇴 Norwegian (`no`) - 🇫🇮 Finnish (`fi`)
426
-
427
- #### Asian Languages (4):
428
- - 🇹🇭 Thai (`th`) - 🇻🇳 Vietnamese (`vi`) - 🇮🇩 Indonesian (`id`) - 🇧🇩 Bengali (`bn`)
429
-
430
- #### African Languages (2):
431
- - 🇰🇪 Swahili (`sw`) - 🇿🇦 Afrikaans (`af`)
432
-
433
- #### American Languages (2):
434
- - 🇧🇷 Portuguese Brazil (`pt-BR`) - 🇲🇽 Spanish Mexico (`es-MX`)
435
-
436
- **Plus 75+ additional languages** supported through comprehensive type definitions with English fallback, including Icelandic, Czech, Slovak, Hungarian, Romanian, Bulgarian, Croatian, Slovenian, Greek, Hebrew, Persian, Georgian, Armenian, and many more!
437
-
438
- ## ⚠️ Error Handling & Formatting
439
-
440
- VLD provides advanced error formatting utilities similar to Zod's error handling system. These utilities help you transform validation errors into user-friendly formats for different use cases.
441
-
442
- ### Error Formatting Utilities
443
-
444
- ```typescript
445
- import { v, VldError, treeifyError, prettifyError, flattenError } from '@oxog/vld';
446
-
447
- // Note: Error formatting utilities like `treeifyError` are separate named exports
448
- // and are not part of the main `v` object.
449
-
450
- const userSchema = v.object({
451
- username: v.string().min(3),
452
- favoriteNumbers: v.array(v.number()),
453
- profile: v.object({
454
- name: v.string(),
455
- email: v.string().email()
456
- })
457
- });
458
-
459
- // This will fail validation
460
- const result = userSchema.safeParse({
461
- username: 'ab', // too short
462
- favoriteNumbers: [1, 'two', 3], // 'two' is not a number
463
- profile: {
464
- name: '',
465
- email: 'invalid-email'
466
- },
467
- extraField: 'not allowed'
468
- });
469
-
470
- if (!result.success) {
471
- const error = result.error as VldError;
472
-
473
- // 1. Tree Format - Nested structure for complex UIs
474
- const tree = treeifyError(error);
475
- console.log(tree);
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
-
498
- // 2. Pretty Format - Human-readable console output
499
- const pretty = prettifyError(error);
500
- console.log(pretty);
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
-
513
- // 3. Flatten Format - Simple form validation
514
- 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
- }
527
- ```
528
-
529
- ### Using Error Formats in Practice
530
-
531
- #### React Form Validation
532
- ```typescript
533
- function UserForm() {
534
- const [errors, setErrors] = useState<VldFlattenedError | null>(null);
535
-
536
- const handleSubmit = (data: unknown) => {
537
- const result = userSchema.safeParse(data);
538
-
539
- if (!result.success) {
540
- setErrors(flattenError(result.error as VldError));
541
- } else {
542
- setErrors(null);
543
- // Process valid data
544
- }
545
- };
546
-
547
- return (
548
- <form onSubmit={handleSubmit}>
549
- {errors?.formErrors.map(error => (
550
- <div key={error} className="form-error">{error}</div>
551
- ))}
552
-
553
- <input name="username" />
554
- {errors?.fieldErrors.username?.map(error => (
555
- <div key={error} className="field-error">{error}</div>
556
- ))}
557
- </form>
558
- );
559
- }
560
- ```
561
-
562
- #### API Error Responses
563
- ```typescript
564
- app.post('/api/users', (req, res) => {
565
- const result = userSchema.safeParse(req.body);
566
-
567
- if (!result.success) {
568
- const tree = treeifyError(result.error as VldError);
569
- res.status(400).json({
570
- error: 'Validation failed',
571
- details: tree
572
- });
573
- } else {
574
- // Process valid user data
575
- }
576
- });
577
- ```
578
-
579
- #### Console Debugging
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
592
-
593
- ### Complex Validation with New Features
594
-
595
- ```typescript
596
- const postSchema = v.object({
597
- id: v.union(v.string().uuid(), v.number()),
598
- title: v.string().min(5).max(100),
599
- content: v.string().min(10),
600
- author: v.object({
601
- name: v.string(),
602
- email: v.string().email(),
603
- age: v.coerce.number(), // Auto-convert to number
604
- }),
605
- tags: v.set(v.string()).default(new Set()), // Use Set instead of array
606
- metadata: v.record(v.any()), // Key-value metadata
607
- coordinates: v.tuple(v.number(), v.number()), // [lat, lng]
608
- publishedAt: v.date().default(() => new Date()),
609
- status: v.enum('draft', 'published', 'archived')
610
- });
611
-
612
- // Extend with additional fields
613
- const blogPostSchema = postSchema.extend({
614
- viewCount: v.bigint().default(0n),
615
- categories: v.array(v.string()).min(1),
616
- featured: v.boolean().default(false)
617
- });
618
-
619
- // Create a public version without sensitive data
620
- const publicPostSchema = blogPostSchema
621
- .omit('author')
622
- .extend({
623
- authorName: v.string()
624
- });
625
- ```
626
-
627
- ### Advanced Transformations & Validation
628
-
629
- ```typescript
630
- // Complex email processing with coercion and transformation
631
- const emailSchema = v.coerce.string()
632
- .transform(s => s.toLowerCase().trim())
633
- .refine(s => s.includes('@'), 'Must be valid email format')
634
- .transform(s => s.replace(/\+.*@/, '@')) // Remove plus addressing
635
- .catch('invalid@example.com');
636
-
637
- // Process user input with fallbacks
638
- const userInputSchema = v.object({
639
- name: v.coerce.string()
640
- .transform(s => s.trim())
641
- .refine(s => s.length > 0, 'Name cannot be empty')
642
- .default('Anonymous'),
643
-
644
- age: v.coerce.number()
645
- .refine(n => n >= 0 && n <= 150, 'Age must be realistic')
646
- .catch(0),
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
- });
661
-
662
- const adminUser = v.object({
663
- role: v.literal('admin'),
664
- permissions: v.array(v.string())
665
- });
666
-
667
- const adminSchema = v.intersection(baseUser, adminUser);
668
- ```
669
-
670
- ### Collection Validation
671
-
672
- ```typescript
673
- // Advanced tuple validation
674
- const coordinatesSchema = v.tuple(
675
- v.number().min(-90).max(90), // latitude
676
- v.number().min(-180).max(180), // longitude
677
- v.number().positive().optional() // altitude
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
684
- );
685
-
686
- // Set validation for unique tags
687
- const uniqueTagsSchema = v.set(v.string().min(1).max(20))
688
- .refine(tags => tags.size <= 10, 'Too many tags');
689
- ```
690
-
691
- ### Real-world API Schema
692
-
693
- ```typescript
694
- // Complete API endpoint schema with all features
695
- const apiUserSchema = v.object({
696
- // Basic info with coercion
697
- id: v.coerce.string(),
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()
737
- });
738
- ```
739
-
740
- ### Type-Safe Forms
741
-
742
- ```typescript
743
- const loginSchema = v.object({
744
- username: v.string().min(3),
745
- password: v.string().min(8),
746
- rememberMe: v.optional(v.boolean())
747
- });
748
-
749
- type LoginForm = Infer<typeof loginSchema>;
750
-
751
- function handleLogin(data: unknown) {
752
- const result = loginSchema.safeParse(data);
753
-
754
- if (result.success) {
755
- // data is now typed as LoginForm
756
- const { username, password, rememberMe } = result.data;
757
- // ... handle login
758
- } else {
759
- // Handle validation errors
760
- console.error(result.error);
761
- }
762
- }
763
- ```
764
-
765
- ## 🆚 VLD vs. Zod
766
-
767
- VLD is designed as a compelling alternative to Zod, offering full feature parity while delivering significant improvements in performance, bundle size, and internationalization.
768
-
769
- ### Feature Comparison
770
-
771
- | Feature | VLD | Zod |
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 |
782
-
783
- ### 🔄 Seamless Migration from Zod
784
-
785
- Migration is straightforward due to 100% API compatibility. You can typically just swap the import statement.
786
-
787
- ```javascript
788
- // Before (Zod)
789
- import { z } from 'zod';
790
- const schema = z.string().email();
791
-
792
- // After (VLD) - Exact same syntax!
793
- import { v } from '@oxog/vld';
794
- const schema = v.string().email();
795
- ```
796
-
797
- ## 🔄 Codecs - Bidirectional Transformations
798
-
799
- VLD introduces **codecs** - powerful bidirectional transformations that can convert data between different representations. Unlike simple transformations, codecs can both **decode** (input → output) and **encode** (output → input).
800
-
801
- ### 🎯 What are Codecs?
802
-
803
- Codecs enable safe, type-checked conversions between different data formats. They're perfect for:
804
- - **API boundaries**: Convert strings to structured data
805
- - **Database serialization**: Transform objects to/from storage formats
806
- - **Network protocols**: Handle data encoding/decoding
807
- - **Configuration parsing**: Convert config strings to typed values
808
-
809
- ### 📦 Built-in Codecs
810
-
811
- VLD provides all Zod-compatible codecs plus additional utilities:
812
-
813
- #### **String Conversion Codecs**
814
-
815
- ```typescript
816
- import { stringToNumber, stringToInt, stringToBigInt, stringToBoolean } from '@oxog/vld';
817
-
818
- // String to number conversion
819
- const age = stringToNumber.parse('25'); // 25
820
- const price = stringToNumber.encode(99.99); // "99.99"
821
-
822
- // String to integer (validates integer constraint)
823
- const count = stringToInt.parse('42'); // 42
824
- stringToInt.parse('42.5'); // ❌ Validation error: must be integer
825
-
826
- // String to BigInt for large numbers
827
- const bigNum = stringToBigInt.parse('123456789012345678901234567890'); // 123456789012345678901234567890n
828
-
829
- // String to boolean (flexible parsing)
830
- stringToBoolean.parse('true'); // true
831
- stringToBoolean.parse('1'); // true
832
- stringToBoolean.parse('yes'); // true
833
- stringToBoolean.parse('on'); // true
834
- stringToBoolean.parse('false'); // false
835
- stringToBoolean.parse('0'); // false
836
- ```
837
-
838
- #### **Date Conversion Codecs**
839
-
840
- ```typescript
841
- import { isoDatetimeToDate, epochSecondsToDate, epochMillisToDate } from '@oxog/vld';
842
-
843
- // ISO datetime string to Date
844
- const date1 = isoDatetimeToDate.parse('2023-12-25T10:30:00.000Z');
845
- console.log(date1.toISOString()); // "2023-12-25T10:30:00.000Z"
846
-
847
- // Unix epoch seconds to Date
848
- const date2 = epochSecondsToDate.parse(1703505000);
849
- console.log(date2.getFullYear()); // 2023
850
-
851
- // Unix epoch milliseconds to Date
852
- const date3 = epochMillisToDate.parse(1703505000000);
853
- console.log(date3.getMonth()); // 11 (December)
854
-
855
- // All support bidirectional conversion
856
- const backToEpoch = epochSecondsToDate.encode(new Date()); // Unix timestamp
857
- ```
858
-
859
- #### **JSON and Complex Data Codecs**
860
-
861
- ```typescript
862
- import { jsonCodec, base64Json } from '@oxog/vld';
863
-
864
- // Generic JSON codec
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}'
868
-
869
- // JSON codec with schema validation
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!
876
-
877
- // Base64-encoded JSON
878
- const b64JsonCodec = base64Json(userSchema);
879
- const encoded = b64JsonCodec.encode({ name: "Alice", age: 25 }); // Base64 string
880
- const decoded = b64JsonCodec.parse(encoded); // { name: "Alice", age: 25 }
881
- ```
882
-
883
- #### **URL and Web Codecs**
884
-
885
- ```typescript
886
- import { stringToURL, stringToHttpURL, uriComponent } from '@oxog/vld';
887
-
888
- // String to URL object
889
- const url = stringToURL.parse('https://example.com/path?param=value');
890
- console.log(url.hostname); // "example.com"
891
- console.log(url.searchParams.get('param')); // "value"
892
-
893
- // Restrict to HTTP/HTTPS only
894
- const httpUrl = stringToHttpURL.parse('https://api.example.com');
895
- stringToHttpURL.parse('ftp://files.example.com'); // ❌ Error: Must be HTTP/HTTPS
896
-
897
- // URI component encoding/decoding
898
- const encoded = uriComponent.parse('Hello World! 🚀'); // "Hello%20World!%20%F0%9F%9A%80"
899
- const decoded = uriComponent.encode(encoded); // "Hello World! 🚀"
900
- ```
901
-
902
- #### **Binary Data Codecs**
903
-
904
- ```typescript
905
- import { base64ToBytes, hexToBytes, utf8ToBytes, bytesToUtf8 } from '@oxog/vld';
906
-
907
- // Base64 to byte array
908
- const bytes1 = base64ToBytes.parse('SGVsbG8gV29ybGQ='); // Uint8Array([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100])
909
-
910
- // Hex to byte array
911
- const bytes2 = hexToBytes.parse('48656c6c6f'); // Uint8Array([72, 101, 108, 108, 111])
912
-
913
- // UTF-8 string to bytes
914
- const bytes3 = utf8ToBytes.parse('Hello! 👋'); // Uint8Array([...])
915
-
916
- // Bytes to UTF-8 string
917
- const text = bytesToUtf8.parse(bytes3); // "Hello! 👋"
918
-
919
- // All support round-trip conversion
920
- const original = 'Hello World!';
921
- const roundTrip = bytesToUtf8.parse(utf8ToBytes.parse(original)); // "Hello World!"
922
- ```
923
-
924
- ### 🛠 Custom Codecs
925
-
926
- Create your own codecs for specific use cases:
927
-
928
- ```typescript
929
- import { v } from '@oxog/vld';
930
-
931
- // Custom CSV to array codec
932
- const csvToArray = v.codec(
933
- v.string(), // Input: CSV string
934
- v.array(v.string()), // Output: Array of strings
935
- {
936
- decode: (csv: string) => csv.split(',').map(s => s.trim()),
937
- encode: (arr: string[]) => arr.join(', ')
938
- }
939
- );
940
-
941
- const tags = csvToArray.parse('react, typescript, vld'); // ["react", "typescript", "vld"]
942
- const csvString = csvToArray.encode(['node', 'express', 'api']); // "node, express, api"
943
-
944
- // Complex: Environment config codec
945
- const envConfigCodec = v.codec(
946
- v.string(),
947
- v.object({
948
- port: v.number(),
949
- debug: v.boolean(),
950
- dbUrl: v.string()
951
- }),
952
- {
953
- decode: (envString: string) => {
954
- const config = {};
955
- envString.split('\n').forEach(line => {
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
- );
970
- ```
971
-
972
- ### 🚀 Advanced Codec Features
973
-
974
- #### **Async Codecs**
975
- ```typescript
976
- const asyncCodec = v.codec(
977
- v.string(),
978
- v.object({ data: v.string() }),
979
- {
980
- decode: async (str: string) => {
981
- // Simulate API call
982
- const response = await fetch(`/api/decode?data=${str}`);
983
- return response.json();
984
- },
985
- encode: async (obj) => {
986
- const response = await fetch('/api/encode', {
987
- method: 'POST',
988
- body: JSON.stringify(obj)
989
- });
990
- return response.text();
991
- }
992
- }
993
- );
994
-
995
- // Use async methods
996
- const result = await asyncCodec.parseAsync('input-data');
997
- const encoded = await asyncCodec.encodeAsync({ data: 'output' });
998
- ```
999
-
1000
- #### **Error Handling**
1001
- ```typescript
1002
- const safeParseResult = stringToNumber.safeParse('not-a-number');
1003
- if (!safeParseResult.success) {
1004
- console.error('Parse failed:', safeParseResult.error.message);
1005
- }
1006
-
1007
- const safeEncodeResult = stringToNumber.safeEncode('invalid-input');
1008
- if (!safeEncodeResult.success) {
1009
- console.error('Encode failed:', safeEncodeResult.error.message);
1010
- }
1011
- ```
1012
-
1013
- #### **JWT Payload Decoder**
1014
- ```typescript
1015
- import { jwtPayload } from '@oxog/vld';
1016
-
1017
- // Decode JWT payload (read-only)
1018
- const payloadSchema = v.object({
1019
- sub: v.string(),
1020
- name: v.string(),
1021
- iat: v.number()
1022
- });
1023
-
1024
- const decoder = jwtPayload(payloadSchema);
1025
- const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
1026
-
1027
- const payload = decoder.parse(token);
1028
- console.log(payload.name); // "John Doe"
1029
- console.log(payload.sub); // "1234567890"
1030
- ```
1031
-
1032
- ### 🎯 Codec vs Transform
1033
-
1034
- | Feature | Codec | Transform |
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 |
1040
-
1041
- ```typescript
1042
- // Transform: One-way conversion
1043
- const upperCase = v.string().transform(s => s.toUpperCase());
1044
- const result = upperCase.parse('hello'); // "HELLO"
1045
- // No way to get back to "hello"
1046
-
1047
- // Codec: Two-way conversion
1048
- const upperCaseCodec = v.codec(
1049
- v.string(),
1050
- v.string(),
1051
- {
1052
- decode: s => s.toUpperCase(),
1053
- encode: s => s.toLowerCase()
1054
- }
1055
- );
1056
- const encoded = upperCaseCodec.parse('hello'); // "HELLO"
1057
- const original = upperCaseCodec.encode('HELLO'); // "hello"
1058
- ```
1059
-
1060
- ## 🔄 Migrating from Zod
1061
-
1062
- VLD provides 100% feature parity with Zod, making migration seamless:
1063
-
1064
- ### Simple Migration
1065
- ```javascript
1066
- // Before (Zod)
1067
- import { z } from 'zod';
1068
- const schema = z.string().email();
1069
-
1070
- // After (VLD) - Exact same syntax!
1071
- import { v } from '@oxog/vld';
1072
- const schema = v.string().email();
1073
- ```
1074
-
1075
- ### Why Migrate?
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
1084
-
1085
- ### Performance Results
1086
-
1087
- Latest benchmark results show VLD consistently outperforming Zod:
1088
-
1089
- | Test Case | VLD Performance | Improvement |
1090
- |-----------|----------------|-------------|
1091
- | Simple String | 44.4M ops/sec | **1.67x faster** |
1092
- | Email Validation | 18.6M ops/sec | **3.63x faster** |
1093
- | Number Validation | 22.7M ops/sec | **2.62x faster** |
1094
- | Object Validation | 7.6M ops/sec | **1.27x faster** |
1095
- | Array Validation | 6.7M ops/sec | **1.29x faster** |
1096
- | Union Types | 6.8M ops/sec | **1.54x faster** |
1097
- | Optional Values | 32.7M ops/sec | **4.52x faster** |
1098
- | Type Coercion | 18.4M ops/sec | **1.46x faster** |
1099
-
1100
- **Average: 2.52x faster than Zod**
1101
-
1102
- ### Run Benchmarks
1103
-
1104
- ```bash
1105
- # Quick performance comparison
1106
- npm run benchmark
1107
-
1108
- # Memory usage comparison
1109
- npm run benchmark:memory
1110
-
1111
- # Startup time comparison
1112
- npm run benchmark:startup
1113
-
1114
- # Run all benchmarks
1115
- npm run benchmark:all
1116
- ```
1117
-
1118
- ## 🤝 Contributing
1119
-
1120
- Contributions are welcome! Please feel free to submit a Pull Request.
1121
-
1122
- ## 🔗 Links
1123
-
1124
- - [NPM Package](https://www.npmjs.com/package/@oxog/vld)
1125
- - [GitHub Repository](https://github.com/ersinkoc/vld)
1126
- - [Documentation](https://github.com/ersinkoc/vld#readme)
1127
-
1128
- ---
1129
-
1130
- Made with ❤️ by Ersin KOÇ
1
+ # VLD - Fast & Lightweight TypeScript Validation Library
2
+
3
+ [![NPM Version](https://img.shields.io/npm/v/@oxog/vld.svg)](https://www.npmjs.com/package/@oxog/vld) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/) [![Zero Dependencies](https://img.shields.io/badge/Dependencies-0-green.svg)](package.json) [![Test Coverage](https://img.shields.io/badge/Coverage-96.55%25-brightgreen.svg)](package.json)
4
+
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
+
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
+ - [🎯 Why VLD?](#-why-vld)
18
+ - [🔄 Codecs - Bidirectional Transformations](#-codecs---bidirectional-transformations)
19
+ - [🔄 Migrating from Zod](#-migrating-from-zod)
20
+ - [📈 Benchmarks](#-benchmarks)
21
+ - [🤝 Contributing](#-contributing)
22
+ - [🔗 Links](#-links)
23
+
24
+ ## 🚀 Features
25
+
26
+ ### Core Features
27
+ - **⚡ Blazing Fast**: Optimized for V8 engine with superior performance
28
+ - **🎯 Type-Safe**: Full TypeScript support with excellent type inference
29
+ - **📦 Zero Dependencies**: Lightweight with no external dependencies
30
+ - **🌳 Tree-Shakeable**: Only import what you need
31
+ - **🔧 Composable**: Chain validations for complex schemas
32
+ - **⚠️ Advanced Error Formatting**: Tree, pretty, and flatten error utilities
33
+ - **🌍 Multi-language**: Built-in support for 27+ languages
34
+ - **✅ 96.55% Test Coverage**: Rigorously tested with 1142 passing tests
35
+ - **🏆 Industry Leading Performance**: 2.52x faster than Zod on average
36
+
37
+ ### Advanced Zod-Compatible Features
38
+ - **🔄 Type Coercion**: `v.coerce.string()`, `v.coerce.number()`, `v.coerce.boolean()`, etc.
39
+ - **📊 Advanced Types**: BigInt, Symbol, Tuple, Record, Set, Map validation
40
+ - **⚡ Intersection Types**: Combine multiple schemas with intelligent merging
41
+ - **🎨 Custom Validation**: `refine()` for custom predicates and validation logic
42
+ - **🔄 Data Transformation**: `transform()` for post-validation data transformation
43
+ - **🏠 Default Values**: `default()` for handling undefined inputs elegantly
44
+ - **🛡️ Fallback Handling**: `catch()` for graceful error recovery
45
+ - **🎯 Object Utilities**: `pick()`, `omit()`, `extend()` for flexible object schemas
46
+
47
+ ### 🚀 **NEW in v1.4.0** - Zod 4 Full API Parity
48
+ - **🌐 `v.cidrv6()`**: IPv6 CIDR block validation
49
+ - **🔗 `.apply()`**: External function chaining for advanced composition
50
+ - **🛡️ `.safeExtend()`**: Type-safe object extension without accidental overrides
51
+
52
+ ### 🚀 Codec System - Beyond Zod
53
+ - **↔️ Bidirectional Transformations**: Full encode/decode support for data conversion
54
+ - **📦 19 Built-in Codecs**: String conversions, date parsing, JSON, URL, binary data
55
+ - **🔗 Zod-Compatible**: All `stringToNumber`, `jsonCodec`, `base64ToBytes`, etc.
56
+ - **⚡ Async Support**: Both sync and async codec operations
57
+ - **🛠 Custom Codecs**: Create your own bidirectional transformations
58
+ - **🎯 Type-Safe**: Full TypeScript support with perfect type inference
59
+
60
+ ## 📊 Performance
61
+
62
+ VLD is designed for speed and efficiency with recent optimizations delivering exceptional performance:
63
+
64
+ ### Speed Benchmarks (v1.0.0 - Optimized)
65
+ - **4.6x faster** for number validation with constraints
66
+ - **3.6x faster** for union type validation
67
+ - **2.5x faster** for email validation
68
+ - **1.9x faster** for array validation
69
+ - **1.7x faster** for primitive string validation
70
+ - **2.8x faster** overall average performance
71
+
72
+ ### Recent Optimizations (v1.0.0)
73
+ - **110x improvement** in union type validation
74
+ - **Simplified email regex** for maximum performance
75
+ - **Inline type checks** in object validation
76
+ - **Optimized loops** with direct array assignment
77
+ - **SafeParse optimization** to avoid try-catch overhead
78
+ - **Pre-computed keys** with Set for O(1) lookups
79
+
80
+ ### Memory Efficiency
81
+ - **98% less memory** for validator creation
82
+ - **51% less memory** for data parsing
83
+ - **86% less memory** for error handling
84
+ - **78% less memory** overall average
85
+
86
+ ### A Note on Real-World Benchmarking
87
+
88
+ Many validation library benchmarks can be misleading because they often test with **reused schema instances**:
89
+
90
+ ```javascript
91
+ // What benchmarks typically test (unrealistic):
92
+ const schema = z.string();
93
+ for (let i = 0; i < 1000000; i++) {
94
+ schema.parse(data); // Same instance reused
95
+ }
96
+
97
+ // What happens in real applications:
98
+ app.post('/api/user', (req, res) => {
99
+ // New schema created for each request
100
+ const schema = z.object({
101
+ email: z.string().email(),
102
+ age: z.number().min(18)
103
+ });
104
+ schema.parse(req.body);
105
+ });
106
+ ```
107
+
108
+ When testing real-world patterns:
109
+ - **Creating new instances**: VLD is **2000x faster** than Zod
110
+ - **Reused instances**: Zod benefits from V8's singleton optimization
111
+ - **Real applications**: Schemas are often created dynamically, where VLD excels
112
+
113
+ Run `npm run benchmark:truth` to see the real performance difference.
114
+
115
+ ## 📦 Installation
116
+
117
+ ```bash
118
+ npm install @oxog/vld
119
+ # or
120
+ yarn add @oxog/vld
121
+ # or
122
+ pnpm add @oxog/vld
123
+ ```
124
+
125
+ ## 🎯 Quick Start
126
+
127
+ ```typescript
128
+ import { v } from '@oxog/vld';
129
+
130
+ // It is recommended to import as `v` for consistency with Zod's `z`
131
+ // and for a more concise syntax.
132
+
133
+ // Define a schema
134
+ const userSchema = v.object({
135
+ name: v.string().min(2),
136
+ email: v.string().email(),
137
+ age: v.number().min(18).max(100),
138
+ isActive: v.boolean()
139
+ });
140
+
141
+ // Validate data
142
+ const result = userSchema.safeParse({
143
+ name: 'John Doe',
144
+ email: 'john@example.com',
145
+ age: 25,
146
+ isActive: true
147
+ });
148
+
149
+ if (result.success) {
150
+ console.log('Valid user:', result.data);
151
+ } else {
152
+ console.log('Validation error:', result.error);
153
+ }
154
+ ```
155
+
156
+ For advanced error formatting:
157
+ ```typescript
158
+ import { v, VldError, treeifyError, prettifyError, flattenError } from '@oxog/vld';
159
+ ```
160
+
161
+ ## 📖 API Reference
162
+
163
+ ### Basic Types
164
+
165
+ ```typescript
166
+ v.string() // String validation
167
+ v.number() // Number validation
168
+ v.boolean() // Boolean validation
169
+ v.bigint() // BigInt validation
170
+ v.symbol() // Symbol validation
171
+ v.date() // Date validation
172
+ v.uint8array()// Uint8Array validation
173
+ v.literal() // Literal values
174
+ v.enum() // Enum values (supports TypeScript enums)
175
+ v.any() // Any type
176
+ v.unknown() // Unknown type
177
+ v.void() // Void type
178
+ v.never() // Never type
179
+ ```
180
+
181
+ ### Advanced Types
182
+
183
+ ```typescript
184
+ // Collections
185
+ v.array(v.string()) // Array validation
186
+ v.tuple(v.string(), v.number()) // Fixed-length tuple
187
+ v.record(v.number()) // Record/dictionary validation
188
+ v.set(v.string()) // Set validation
189
+ v.map(v.string(), v.number()) // Map validation
190
+
191
+ // Objects
192
+ v.object({ // Object schema
193
+ name: v.string(),
194
+ age: v.number()
195
+ })
196
+
197
+ // Composition
198
+ v.union(v.string(), v.number()) // Union types
199
+ v.intersection(schemaA, schemaB) // Intersection types
200
+ v.optional(v.string()) // Optional fields
201
+ v.nullable(v.string()) // Nullable fields
202
+ ```
203
+
204
+ ### String Validators
205
+
206
+ ```typescript
207
+ v.string()
208
+ .min(5) // Minimum length
209
+ .max(10) // Maximum length
210
+ .length(8) // Exact length
211
+ .email() // Email format
212
+ .url() // URL format
213
+ .uuid() // UUID format
214
+ .regex(/pattern/) // Custom regex
215
+ .startsWith('prefix') // String prefix
216
+ .endsWith('suffix') // String suffix
217
+ .includes('substring') // Contains substring
218
+ .ip() // IP address (v4 or v6)
219
+ .trim() // Trim whitespace
220
+ .toLowerCase() // Convert to lowercase
221
+ .toUpperCase() // Convert to uppercase
222
+ .nonempty() // Non-empty string
223
+ ```
224
+
225
+ ### Number Validators
226
+
227
+ ```typescript
228
+ v.number()
229
+ .min(0) // Minimum value
230
+ .max(100) // Maximum value
231
+ .int() // Integer only
232
+ .positive() // Positive numbers
233
+ .negative() // Negative numbers
234
+ .nonnegative() // >= 0
235
+ .nonpositive() // <= 0
236
+ .finite() // Finite numbers
237
+ .safe() // Safe integers
238
+ .multipleOf(5) // Multiple of value
239
+ ```
240
+
241
+ ### Arrays and Objects
242
+
243
+ ```typescript
244
+ // Arrays
245
+ v.array(v.string()) // Array of strings
246
+ .min(1) // Minimum length
247
+ .max(10) // Maximum length
248
+ .length(5) // Exact length
249
+ .nonempty() // Non-empty array
250
+
251
+ // Objects
252
+ v.object({
253
+ name: v.string(),
254
+ age: v.number()
255
+ })
256
+ .partial() // All fields optional
257
+ .strict() // No extra fields
258
+ ```
259
+
260
+ ### Composite Types
261
+
262
+ ```typescript
263
+ // Optional
264
+ v.optional(v.string()) // string | undefined
265
+
266
+ // Nullable
267
+ v.nullable(v.string()) // string | null
268
+
269
+ // Union
270
+ v.union(v.string(), v.number()) // string | number
271
+
272
+ // Literal
273
+ v.literal('active') // 'active'
274
+
275
+ // Enum
276
+ v.enum('red', 'green', 'blue') // 'red' | 'green' | 'blue'
277
+ ```
278
+
279
+ ### Type Coercion
280
+
281
+ ```typescript
282
+ // Coerce strings from various types
283
+ v.coerce.string().parse(123) // "123"
284
+ v.coerce.string().parse(true) // "true"
285
+
286
+ // Coerce numbers from strings/booleans
287
+ v.coerce.number().parse("123") // 123
288
+ v.coerce.number().parse(true) // 1
289
+
290
+ // Coerce booleans from strings/numbers
291
+ v.coerce.boolean().parse("true") // true
292
+ v.coerce.boolean().parse(1) // true
293
+
294
+ // Coerce BigInt from strings/numbers
295
+ v.coerce.bigint().parse("123") // 123n
296
+ v.coerce.bigint().parse(456) // 456n
297
+
298
+ // Coerce Date from strings/timestamps
299
+ v.coerce.date().parse("2023-01-01") // Date object
300
+ v.coerce.date().parse(1672531200000) // Date object
301
+ ```
302
+
303
+ ### Object Schema Methods
304
+
305
+ ```typescript
306
+ const userSchema = v.object({
307
+ name: v.string(),
308
+ age: v.number(),
309
+ email: v.string(),
310
+ role: v.string()
311
+ });
312
+
313
+ // Pick specific fields
314
+ const publicSchema = userSchema.pick('name', 'age');
315
+ // Type: { name: string; age: number }
316
+
317
+ // Omit sensitive fields
318
+ const safeSchema = userSchema.omit('email', 'role');
319
+ // Type: { name: string; age: number }
320
+
321
+ // Extend with new fields
322
+ const extendedSchema = userSchema.extend({
323
+ isActive: v.boolean(),
324
+ lastLogin: v.date()
325
+ });
326
+ // Type: { name: string; age: number; email: string; role: string; isActive: boolean; lastLogin: Date }
327
+ ```
328
+
329
+ ### Advanced Validation Methods
330
+
331
+ ```typescript
332
+ // Custom validation with refine()
333
+ const positiveNumber = v.number()
334
+ .refine(n => n > 0, "Number must be positive");
335
+
336
+ // Data transformation with transform()
337
+ const uppercaseString = v.string()
338
+ .transform(s => s.toUpperCase());
339
+
340
+ // Default values for undefined
341
+ const withDefault = v.string().default("fallback");
342
+ withDefault.parse(undefined); // "fallback"
343
+
344
+ // Catch errors and provide fallback
345
+ const withCatch = v.number().catch(-1);
346
+ withCatch.parse("invalid"); // -1
347
+
348
+ // Method chaining
349
+ const complexSchema = v.string()
350
+ .min(3)
351
+ .transform(s => s.trim())
352
+ .refine(s => s.includes('@'), 'Must contain @')
353
+ .default('user@example.com');
354
+ ```
355
+
356
+ ### Type Inference
357
+
358
+ ```typescript
359
+ import { v, Infer } from '@oxog/vld';
360
+
361
+ const schema = v.object({
362
+ name: v.string(),
363
+ age: v.number()
364
+ });
365
+
366
+ // Automatically infer the type
367
+ type User = Infer<typeof schema>;
368
+ // { name: string; age: number }
369
+ ```
370
+
371
+ ### Error Formatting Types
372
+
373
+ ```typescript
374
+ import {
375
+ VldError, // Main error class
376
+ VldIssue, // Individual validation issue
377
+ VldErrorTree, // Nested error structure
378
+ VldFlattenedError // Flattened error structure
379
+ } from '@oxog/vld';
380
+ ```
381
+
382
+ ### Custom Error Messages
383
+
384
+ ```typescript
385
+ const schema = v.string().min(8, 'Password must be at least 8 characters');
386
+
387
+ const result = schema.safeParse('short');
388
+ if (!result.success) {
389
+ console.log(result.error.message); // 'Password must be at least 8 characters'
390
+ }
391
+ ```
392
+
393
+ ## 🌍 Internationalization (i18n)
394
+
395
+ VLD supports 27+ languages out of the box with comprehensive error messages:
396
+
397
+ ```typescript
398
+ import { v, setLocale } from '@oxog/vld';
399
+
400
+ // Default is English
401
+ const schema = v.string().min(5);
402
+ schema.safeParse('Hi'); // Error: "String must be at least 5 characters"
403
+
404
+ // Switch to Turkish
405
+ setLocale('tr');
406
+ schema.safeParse('Hi'); // Error: "Metin en az 5 karakter olmalı"
407
+
408
+ // Switch to Spanish
409
+ setLocale('es');
410
+ schema.safeParse('Hi'); // Error: "La cadena debe tener al menos 5 caracteres"
411
+
412
+ // Switch to Japanese
413
+ setLocale('ja');
414
+ schema.safeParse('Hi'); // Error: "文字列は5文字以上である必要があります"
415
+ ```
416
+
417
+ ### Supported Languages
418
+
419
+ #### Base Languages (15):
420
+ - 🇬🇧 English (`en`) - 🇹🇷 Turkish (`tr`) - 🇪🇸 Spanish (`es`) - 🇫🇷 French (`fr`) - 🇩🇪 German (`de`)
421
+ - 🇮🇹 Italian (`it`) - 🇵🇹 Portuguese (`pt`) - 🇷🇺 Russian (`ru`) - 🇯🇵 Japanese (`ja`) - 🇰🇷 Korean (`ko`)
422
+ - 🇨🇳 Chinese (`zh`) - 🇸🇦 Arabic (`ar`) - 🇮🇳 Hindi (`hi`) - 🇳🇱 Dutch (`nl`) - 🇵🇱 Polish (`pl`)
423
+
424
+ #### European Languages (4):
425
+ - 🇩🇰 Danish (`da`) - 🇸🇪 Swedish (`sv`) - 🇳🇴 Norwegian (`no`) - 🇫🇮 Finnish (`fi`)
426
+
427
+ #### Asian Languages (4):
428
+ - 🇹🇭 Thai (`th`) - 🇻🇳 Vietnamese (`vi`) - 🇮🇩 Indonesian (`id`) - 🇧🇩 Bengali (`bn`)
429
+
430
+ #### African Languages (2):
431
+ - 🇰🇪 Swahili (`sw`) - 🇿🇦 Afrikaans (`af`)
432
+
433
+ #### American Languages (2):
434
+ - 🇧🇷 Portuguese Brazil (`pt-BR`) - 🇲🇽 Spanish Mexico (`es-MX`)
435
+
436
+ **Plus 75+ additional languages** supported through comprehensive type definitions with English fallback, including Icelandic, Czech, Slovak, Hungarian, Romanian, Bulgarian, Croatian, Slovenian, Greek, Hebrew, Persian, Georgian, Armenian, and many more!
437
+
438
+ ## ⚠️ Error Handling & Formatting
439
+
440
+ VLD provides advanced error formatting utilities similar to Zod's error handling system. These utilities help you transform validation errors into user-friendly formats for different use cases.
441
+
442
+ ### Error Formatting Utilities
443
+
444
+ ```typescript
445
+ import { v, VldError, treeifyError, prettifyError, flattenError } from '@oxog/vld';
446
+
447
+ // Note: Error formatting utilities like `treeifyError` are separate named exports
448
+ // and are not part of the main `v` object.
449
+
450
+ const userSchema = v.object({
451
+ username: v.string().min(3),
452
+ favoriteNumbers: v.array(v.number()),
453
+ profile: v.object({
454
+ name: v.string(),
455
+ email: v.string().email()
456
+ })
457
+ });
458
+
459
+ // This will fail validation
460
+ const result = userSchema.safeParse({
461
+ username: 'ab', // too short
462
+ favoriteNumbers: [1, 'two', 3], // 'two' is not a number
463
+ profile: {
464
+ name: '',
465
+ email: 'invalid-email'
466
+ },
467
+ extraField: 'not allowed'
468
+ });
469
+
470
+ if (!result.success) {
471
+ const error = result.error as VldError;
472
+
473
+ // 1. Tree Format - Nested structure for complex UIs
474
+ const tree = treeifyError(error);
475
+ console.log(tree);
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
+
498
+ // 2. Pretty Format - Human-readable console output
499
+ const pretty = prettifyError(error);
500
+ console.log(pretty);
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
+
513
+ // 3. Flatten Format - Simple form validation
514
+ 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
+ }
527
+ ```
528
+
529
+ ### Using Error Formats in Practice
530
+
531
+ #### React Form Validation
532
+ ```typescript
533
+ function UserForm() {
534
+ const [errors, setErrors] = useState<VldFlattenedError | null>(null);
535
+
536
+ const handleSubmit = (data: unknown) => {
537
+ const result = userSchema.safeParse(data);
538
+
539
+ if (!result.success) {
540
+ setErrors(flattenError(result.error as VldError));
541
+ } else {
542
+ setErrors(null);
543
+ // Process valid data
544
+ }
545
+ };
546
+
547
+ return (
548
+ <form onSubmit={handleSubmit}>
549
+ {errors?.formErrors.map(error => (
550
+ <div key={error} className="form-error">{error}</div>
551
+ ))}
552
+
553
+ <input name="username" />
554
+ {errors?.fieldErrors.username?.map(error => (
555
+ <div key={error} className="field-error">{error}</div>
556
+ ))}
557
+ </form>
558
+ );
559
+ }
560
+ ```
561
+
562
+ #### API Error Responses
563
+ ```typescript
564
+ app.post('/api/users', (req, res) => {
565
+ const result = userSchema.safeParse(req.body);
566
+
567
+ if (!result.success) {
568
+ const tree = treeifyError(result.error as VldError);
569
+ res.status(400).json({
570
+ error: 'Validation failed',
571
+ details: tree
572
+ });
573
+ } else {
574
+ // Process valid user data
575
+ }
576
+ });
577
+ ```
578
+
579
+ #### Console Debugging
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
592
+
593
+ ### Complex Validation with New Features
594
+
595
+ ```typescript
596
+ const postSchema = v.object({
597
+ id: v.union(v.string().uuid(), v.number()),
598
+ title: v.string().min(5).max(100),
599
+ content: v.string().min(10),
600
+ author: v.object({
601
+ name: v.string(),
602
+ email: v.string().email(),
603
+ age: v.coerce.number(), // Auto-convert to number
604
+ }),
605
+ tags: v.set(v.string()).default(new Set()), // Use Set instead of array
606
+ metadata: v.record(v.any()), // Key-value metadata
607
+ coordinates: v.tuple(v.number(), v.number()), // [lat, lng]
608
+ publishedAt: v.date().default(() => new Date()),
609
+ status: v.enum('draft', 'published', 'archived')
610
+ });
611
+
612
+ // Extend with additional fields
613
+ const blogPostSchema = postSchema.extend({
614
+ viewCount: v.bigint().default(0n),
615
+ categories: v.array(v.string()).min(1),
616
+ featured: v.boolean().default(false)
617
+ });
618
+
619
+ // Create a public version without sensitive data
620
+ const publicPostSchema = blogPostSchema
621
+ .omit('author')
622
+ .extend({
623
+ authorName: v.string()
624
+ });
625
+ ```
626
+
627
+ ### Advanced Transformations & Validation
628
+
629
+ ```typescript
630
+ // Complex email processing with coercion and transformation
631
+ const emailSchema = v.coerce.string()
632
+ .transform(s => s.toLowerCase().trim())
633
+ .refine(s => s.includes('@'), 'Must be valid email format')
634
+ .transform(s => s.replace(/\+.*@/, '@')) // Remove plus addressing
635
+ .catch('invalid@example.com');
636
+
637
+ // Process user input with fallbacks
638
+ const userInputSchema = v.object({
639
+ name: v.coerce.string()
640
+ .transform(s => s.trim())
641
+ .refine(s => s.length > 0, 'Name cannot be empty')
642
+ .default('Anonymous'),
643
+
644
+ age: v.coerce.number()
645
+ .refine(n => n >= 0 && n <= 150, 'Age must be realistic')
646
+ .catch(0),
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
+ });
661
+
662
+ const adminUser = v.object({
663
+ role: v.literal('admin'),
664
+ permissions: v.array(v.string())
665
+ });
666
+
667
+ const adminSchema = v.intersection(baseUser, adminUser);
668
+ ```
669
+
670
+ ### Collection Validation
671
+
672
+ ```typescript
673
+ // Advanced tuple validation
674
+ const coordinatesSchema = v.tuple(
675
+ v.number().min(-90).max(90), // latitude
676
+ v.number().min(-180).max(180), // longitude
677
+ v.number().positive().optional() // altitude
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
684
+ );
685
+
686
+ // Set validation for unique tags
687
+ const uniqueTagsSchema = v.set(v.string().min(1).max(20))
688
+ .refine(tags => tags.size <= 10, 'Too many tags');
689
+ ```
690
+
691
+ ### Real-world API Schema
692
+
693
+ ```typescript
694
+ // Complete API endpoint schema with all features
695
+ const apiUserSchema = v.object({
696
+ // Basic info with coercion
697
+ id: v.coerce.string(),
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()
737
+ });
738
+ ```
739
+
740
+ ### Type-Safe Forms
741
+
742
+ ```typescript
743
+ const loginSchema = v.object({
744
+ username: v.string().min(3),
745
+ password: v.string().min(8),
746
+ rememberMe: v.optional(v.boolean())
747
+ });
748
+
749
+ type LoginForm = Infer<typeof loginSchema>;
750
+
751
+ function handleLogin(data: unknown) {
752
+ const result = loginSchema.safeParse(data);
753
+
754
+ if (result.success) {
755
+ // data is now typed as LoginForm
756
+ const { username, password, rememberMe } = result.data;
757
+ // ... handle login
758
+ } else {
759
+ // Handle validation errors
760
+ console.error(result.error);
761
+ }
762
+ }
763
+ ```
764
+
765
+ ## 🆚 VLD vs. Zod
766
+
767
+ VLD is designed as a compelling alternative to Zod, offering full feature parity while delivering significant improvements in performance, bundle size, and internationalization.
768
+
769
+ ### Feature Comparison
770
+
771
+ | Feature | VLD | Zod |
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 |
782
+
783
+ ### 🔄 Seamless Migration from Zod
784
+
785
+ Migration is straightforward due to 100% API compatibility. You can typically just swap the import statement.
786
+
787
+ ```javascript
788
+ // Before (Zod)
789
+ import { z } from 'zod';
790
+ const schema = z.string().email();
791
+
792
+ // After (VLD) - Exact same syntax!
793
+ import { v } from '@oxog/vld';
794
+ const schema = v.string().email();
795
+ ```
796
+
797
+ ## 🔄 Codecs - Bidirectional Transformations
798
+
799
+ VLD introduces **codecs** - powerful bidirectional transformations that can convert data between different representations. Unlike simple transformations, codecs can both **decode** (input → output) and **encode** (output → input).
800
+
801
+ ### 🎯 What are Codecs?
802
+
803
+ Codecs enable safe, type-checked conversions between different data formats. They're perfect for:
804
+ - **API boundaries**: Convert strings to structured data
805
+ - **Database serialization**: Transform objects to/from storage formats
806
+ - **Network protocols**: Handle data encoding/decoding
807
+ - **Configuration parsing**: Convert config strings to typed values
808
+
809
+ ### 📦 Built-in Codecs
810
+
811
+ VLD provides all Zod-compatible codecs plus additional utilities:
812
+
813
+ #### **String Conversion Codecs**
814
+
815
+ ```typescript
816
+ import { stringToNumber, stringToInt, stringToBigInt, stringToBoolean } from '@oxog/vld';
817
+
818
+ // String to number conversion
819
+ const age = stringToNumber.parse('25'); // 25
820
+ const price = stringToNumber.encode(99.99); // "99.99"
821
+
822
+ // String to integer (validates integer constraint)
823
+ const count = stringToInt.parse('42'); // 42
824
+ stringToInt.parse('42.5'); // ❌ Validation error: must be integer
825
+
826
+ // String to BigInt for large numbers
827
+ const bigNum = stringToBigInt.parse('123456789012345678901234567890'); // 123456789012345678901234567890n
828
+
829
+ // String to boolean (flexible parsing)
830
+ stringToBoolean.parse('true'); // true
831
+ stringToBoolean.parse('1'); // true
832
+ stringToBoolean.parse('yes'); // true
833
+ stringToBoolean.parse('on'); // true
834
+ stringToBoolean.parse('false'); // false
835
+ stringToBoolean.parse('0'); // false
836
+ ```
837
+
838
+ #### **Date Conversion Codecs**
839
+
840
+ ```typescript
841
+ import { isoDatetimeToDate, epochSecondsToDate, epochMillisToDate } from '@oxog/vld';
842
+
843
+ // ISO datetime string to Date
844
+ const date1 = isoDatetimeToDate.parse('2023-12-25T10:30:00.000Z');
845
+ console.log(date1.toISOString()); // "2023-12-25T10:30:00.000Z"
846
+
847
+ // Unix epoch seconds to Date
848
+ const date2 = epochSecondsToDate.parse(1703505000);
849
+ console.log(date2.getFullYear()); // 2023
850
+
851
+ // Unix epoch milliseconds to Date
852
+ const date3 = epochMillisToDate.parse(1703505000000);
853
+ console.log(date3.getMonth()); // 11 (December)
854
+
855
+ // All support bidirectional conversion
856
+ const backToEpoch = epochSecondsToDate.encode(new Date()); // Unix timestamp
857
+ ```
858
+
859
+ #### **JSON and Complex Data Codecs**
860
+
861
+ ```typescript
862
+ import { jsonCodec, base64Json } from '@oxog/vld';
863
+
864
+ // Generic JSON codec
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}'
868
+
869
+ // JSON codec with schema validation
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!
876
+
877
+ // Base64-encoded JSON
878
+ const b64JsonCodec = base64Json(userSchema);
879
+ const encoded = b64JsonCodec.encode({ name: "Alice", age: 25 }); // Base64 string
880
+ const decoded = b64JsonCodec.parse(encoded); // { name: "Alice", age: 25 }
881
+ ```
882
+
883
+ #### **URL and Web Codecs**
884
+
885
+ ```typescript
886
+ import { stringToURL, stringToHttpURL, uriComponent } from '@oxog/vld';
887
+
888
+ // String to URL object
889
+ const url = stringToURL.parse('https://example.com/path?param=value');
890
+ console.log(url.hostname); // "example.com"
891
+ console.log(url.searchParams.get('param')); // "value"
892
+
893
+ // Restrict to HTTP/HTTPS only
894
+ const httpUrl = stringToHttpURL.parse('https://api.example.com');
895
+ stringToHttpURL.parse('ftp://files.example.com'); // ❌ Error: Must be HTTP/HTTPS
896
+
897
+ // URI component encoding/decoding
898
+ const encoded = uriComponent.parse('Hello World! 🚀'); // "Hello%20World!%20%F0%9F%9A%80"
899
+ const decoded = uriComponent.encode(encoded); // "Hello World! 🚀"
900
+ ```
901
+
902
+ #### **Binary Data Codecs**
903
+
904
+ ```typescript
905
+ import { base64ToBytes, hexToBytes, utf8ToBytes, bytesToUtf8 } from '@oxog/vld';
906
+
907
+ // Base64 to byte array
908
+ const bytes1 = base64ToBytes.parse('SGVsbG8gV29ybGQ='); // Uint8Array([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100])
909
+
910
+ // Hex to byte array
911
+ const bytes2 = hexToBytes.parse('48656c6c6f'); // Uint8Array([72, 101, 108, 108, 111])
912
+
913
+ // UTF-8 string to bytes
914
+ const bytes3 = utf8ToBytes.parse('Hello! 👋'); // Uint8Array([...])
915
+
916
+ // Bytes to UTF-8 string
917
+ const text = bytesToUtf8.parse(bytes3); // "Hello! 👋"
918
+
919
+ // All support round-trip conversion
920
+ const original = 'Hello World!';
921
+ const roundTrip = bytesToUtf8.parse(utf8ToBytes.parse(original)); // "Hello World!"
922
+ ```
923
+
924
+ ### 🛠 Custom Codecs
925
+
926
+ Create your own codecs for specific use cases:
927
+
928
+ ```typescript
929
+ import { v } from '@oxog/vld';
930
+
931
+ // Custom CSV to array codec
932
+ const csvToArray = v.codec(
933
+ v.string(), // Input: CSV string
934
+ v.array(v.string()), // Output: Array of strings
935
+ {
936
+ decode: (csv: string) => csv.split(',').map(s => s.trim()),
937
+ encode: (arr: string[]) => arr.join(', ')
938
+ }
939
+ );
940
+
941
+ const tags = csvToArray.parse('react, typescript, vld'); // ["react", "typescript", "vld"]
942
+ const csvString = csvToArray.encode(['node', 'express', 'api']); // "node, express, api"
943
+
944
+ // Complex: Environment config codec
945
+ const envConfigCodec = v.codec(
946
+ v.string(),
947
+ v.object({
948
+ port: v.number(),
949
+ debug: v.boolean(),
950
+ dbUrl: v.string()
951
+ }),
952
+ {
953
+ decode: (envString: string) => {
954
+ const config = {};
955
+ envString.split('\n').forEach(line => {
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
+ );
970
+ ```
971
+
972
+ ### 🚀 Advanced Codec Features
973
+
974
+ #### **Async Codecs**
975
+ ```typescript
976
+ const asyncCodec = v.codec(
977
+ v.string(),
978
+ v.object({ data: v.string() }),
979
+ {
980
+ decode: async (str: string) => {
981
+ // Simulate API call
982
+ const response = await fetch(`/api/decode?data=${str}`);
983
+ return response.json();
984
+ },
985
+ encode: async (obj) => {
986
+ const response = await fetch('/api/encode', {
987
+ method: 'POST',
988
+ body: JSON.stringify(obj)
989
+ });
990
+ return response.text();
991
+ }
992
+ }
993
+ );
994
+
995
+ // Use async methods
996
+ const result = await asyncCodec.parseAsync('input-data');
997
+ const encoded = await asyncCodec.encodeAsync({ data: 'output' });
998
+ ```
999
+
1000
+ #### **Error Handling**
1001
+ ```typescript
1002
+ const safeParseResult = stringToNumber.safeParse('not-a-number');
1003
+ if (!safeParseResult.success) {
1004
+ console.error('Parse failed:', safeParseResult.error.message);
1005
+ }
1006
+
1007
+ const safeEncodeResult = stringToNumber.safeEncode('invalid-input');
1008
+ if (!safeEncodeResult.success) {
1009
+ console.error('Encode failed:', safeEncodeResult.error.message);
1010
+ }
1011
+ ```
1012
+
1013
+ #### **JWT Payload Decoder**
1014
+ ```typescript
1015
+ import { jwtPayload } from '@oxog/vld';
1016
+
1017
+ // Decode JWT payload (read-only)
1018
+ const payloadSchema = v.object({
1019
+ sub: v.string(),
1020
+ name: v.string(),
1021
+ iat: v.number()
1022
+ });
1023
+
1024
+ const decoder = jwtPayload(payloadSchema);
1025
+ const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
1026
+
1027
+ const payload = decoder.parse(token);
1028
+ console.log(payload.name); // "John Doe"
1029
+ console.log(payload.sub); // "1234567890"
1030
+ ```
1031
+
1032
+ ### 🎯 Codec vs Transform
1033
+
1034
+ | Feature | Codec | Transform |
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 |
1040
+
1041
+ ```typescript
1042
+ // Transform: One-way conversion
1043
+ const upperCase = v.string().transform(s => s.toUpperCase());
1044
+ const result = upperCase.parse('hello'); // "HELLO"
1045
+ // No way to get back to "hello"
1046
+
1047
+ // Codec: Two-way conversion
1048
+ const upperCaseCodec = v.codec(
1049
+ v.string(),
1050
+ v.string(),
1051
+ {
1052
+ decode: s => s.toUpperCase(),
1053
+ encode: s => s.toLowerCase()
1054
+ }
1055
+ );
1056
+ const encoded = upperCaseCodec.parse('hello'); // "HELLO"
1057
+ const original = upperCaseCodec.encode('HELLO'); // "hello"
1058
+ ```
1059
+
1060
+ ## 🔄 Migrating from Zod
1061
+
1062
+ VLD provides 100% feature parity with Zod, making migration seamless:
1063
+
1064
+ ### Simple Migration
1065
+ ```javascript
1066
+ // Before (Zod)
1067
+ import { z } from 'zod';
1068
+ const schema = z.string().email();
1069
+
1070
+ // After (VLD) - Exact same syntax!
1071
+ import { v } from '@oxog/vld';
1072
+ const schema = v.string().email();
1073
+ ```
1074
+
1075
+ ### Why Migrate?
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
1084
+
1085
+ ### Performance Results
1086
+
1087
+ Latest benchmark results show VLD consistently outperforming Zod:
1088
+
1089
+ | Test Case | VLD Performance | Improvement |
1090
+ |-----------|----------------|-------------|
1091
+ | Simple String | 44.4M ops/sec | **1.67x faster** |
1092
+ | Email Validation | 18.6M ops/sec | **3.63x faster** |
1093
+ | Number Validation | 22.7M ops/sec | **2.62x faster** |
1094
+ | Object Validation | 7.6M ops/sec | **1.27x faster** |
1095
+ | Array Validation | 6.7M ops/sec | **1.29x faster** |
1096
+ | Union Types | 6.8M ops/sec | **1.54x faster** |
1097
+ | Optional Values | 32.7M ops/sec | **4.52x faster** |
1098
+ | Type Coercion | 18.4M ops/sec | **1.46x faster** |
1099
+
1100
+ **Average: 2.52x faster than Zod**
1101
+
1102
+ ### Run Benchmarks
1103
+
1104
+ ```bash
1105
+ # Quick performance comparison
1106
+ npm run benchmark
1107
+
1108
+ # Memory usage comparison
1109
+ npm run benchmark:memory
1110
+
1111
+ # Startup time comparison
1112
+ npm run benchmark:startup
1113
+
1114
+ # Run all benchmarks
1115
+ npm run benchmark:all
1116
+ ```
1117
+
1118
+ ## 🤝 Contributing
1119
+
1120
+ Contributions are welcome! Please feel free to submit a Pull Request.
1121
+
1122
+ ## 🔗 Links
1123
+
1124
+ - [Documentation](https://vld.oxog.dev)
1125
+ - [NPM Package](https://www.npmjs.com/package/@oxog/vld)
1126
+ - [GitHub Repository](https://github.com/ersinkoc/vld)
1127
+
1128
+ ---
1129
+
1130
+ Made with ❤️ by [Ersin KOÇ](https://github.com/ersinkoc)