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