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