@oxog/vld 1.2.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +122 -0
- package/README.md +61 -43
- package/dist/coercion/bigint.js +1 -1
- package/dist/coercion/boolean.d.ts.map +1 -1
- package/dist/coercion/boolean.js +7 -3
- package/dist/coercion/boolean.js.map +1 -1
- package/dist/coercion/date.js +1 -1
- package/dist/coercion/number.d.ts.map +1 -1
- package/dist/coercion/number.js +20 -4
- package/dist/coercion/number.js.map +1 -1
- package/dist/coercion/string.d.ts.map +1 -1
- package/dist/coercion/string.js +84 -12
- package/dist/coercion/string.js.map +1 -1
- package/dist/index.d.ts +593 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +76 -5
- package/dist/index.js.map +1 -1
- package/dist/locales/af.d.ts.map +1 -1
- package/dist/locales/af.js +21 -4
- package/dist/locales/af.js.map +1 -1
- package/dist/locales/ar.d.ts.map +1 -1
- package/dist/locales/ar.js +21 -4
- package/dist/locales/ar.js.map +1 -1
- package/dist/locales/backup-en.d.ts +3 -0
- package/dist/locales/backup-en.d.ts.map +1 -0
- package/dist/locales/backup-en.js +105 -0
- package/dist/locales/backup-en.js.map +1 -0
- package/dist/locales/bn.d.ts.map +1 -1
- package/dist/locales/bn.js +21 -4
- package/dist/locales/bn.js.map +1 -1
- package/dist/locales/da.d.ts.map +1 -1
- package/dist/locales/da.js +21 -4
- package/dist/locales/da.js.map +1 -1
- package/dist/locales/de.d.ts.map +1 -1
- package/dist/locales/de.js +21 -4
- package/dist/locales/de.js.map +1 -1
- package/dist/locales/en.d.ts.map +1 -1
- package/dist/locales/en.js +21 -4
- package/dist/locales/en.js.map +1 -1
- package/dist/locales/es-MX.d.ts.map +1 -1
- package/dist/locales/es-MX.js +20 -4
- package/dist/locales/es-MX.js.map +1 -1
- package/dist/locales/es.d.ts.map +1 -1
- package/dist/locales/es.js +21 -4
- package/dist/locales/es.js.map +1 -1
- package/dist/locales/fi.d.ts.map +1 -1
- package/dist/locales/fi.js +21 -4
- package/dist/locales/fi.js.map +1 -1
- package/dist/locales/fr.d.ts.map +1 -1
- package/dist/locales/fr.js +21 -4
- package/dist/locales/fr.js.map +1 -1
- package/dist/locales/hi.d.ts.map +1 -1
- package/dist/locales/hi.js +21 -4
- package/dist/locales/hi.js.map +1 -1
- package/dist/locales/id.d.ts.map +1 -1
- package/dist/locales/id.js +21 -4
- package/dist/locales/id.js.map +1 -1
- package/dist/locales/it.d.ts.map +1 -1
- package/dist/locales/it.js +21 -4
- package/dist/locales/it.js.map +1 -1
- package/dist/locales/ja.d.ts.map +1 -1
- package/dist/locales/ja.js +21 -4
- package/dist/locales/ja.js.map +1 -1
- package/dist/locales/ko.d.ts.map +1 -1
- package/dist/locales/ko.js +21 -4
- package/dist/locales/ko.js.map +1 -1
- package/dist/locales/nl.d.ts.map +1 -1
- package/dist/locales/nl.js +21 -4
- package/dist/locales/nl.js.map +1 -1
- package/dist/locales/no.d.ts.map +1 -1
- package/dist/locales/no.js +21 -4
- package/dist/locales/no.js.map +1 -1
- package/dist/locales/pl.d.ts.map +1 -1
- package/dist/locales/pl.js +21 -4
- package/dist/locales/pl.js.map +1 -1
- package/dist/locales/pt-BR.d.ts.map +1 -1
- package/dist/locales/pt-BR.js +21 -4
- package/dist/locales/pt-BR.js.map +1 -1
- package/dist/locales/pt.d.ts.map +1 -1
- package/dist/locales/pt.js +21 -4
- package/dist/locales/pt.js.map +1 -1
- package/dist/locales/ru.d.ts.map +1 -1
- package/dist/locales/ru.js +21 -4
- package/dist/locales/ru.js.map +1 -1
- package/dist/locales/sv.d.ts.map +1 -1
- package/dist/locales/sv.js +21 -4
- package/dist/locales/sv.js.map +1 -1
- package/dist/locales/sw.d.ts.map +1 -1
- package/dist/locales/sw.js +21 -4
- package/dist/locales/sw.js.map +1 -1
- package/dist/locales/th.d.ts.map +1 -1
- package/dist/locales/th.js +21 -4
- package/dist/locales/th.js.map +1 -1
- package/dist/locales/tr.d.ts.map +1 -1
- package/dist/locales/tr.js +21 -4
- package/dist/locales/tr.js.map +1 -1
- package/dist/locales/types.d.ts +16 -3
- package/dist/locales/types.d.ts.map +1 -1
- package/dist/locales/types.js.map +1 -1
- package/dist/locales/vi.d.ts.map +1 -1
- package/dist/locales/vi.js +21 -4
- package/dist/locales/vi.js.map +1 -1
- package/dist/locales/zh.d.ts.map +1 -1
- package/dist/locales/zh.js +21 -4
- package/dist/locales/zh.js.map +1 -1
- package/dist/utils/codec-utils.d.ts +3 -1
- package/dist/utils/codec-utils.d.ts.map +1 -1
- package/dist/utils/codec-utils.js +110 -5
- package/dist/utils/codec-utils.js.map +1 -1
- package/dist/utils/deep-merge.d.ts +3 -0
- package/dist/utils/deep-merge.d.ts.map +1 -1
- package/dist/utils/deep-merge.js +30 -22
- package/dist/utils/deep-merge.js.map +1 -1
- package/dist/utils/ip-validation.d.ts +11 -0
- package/dist/utils/ip-validation.d.ts.map +1 -0
- package/dist/utils/ip-validation.js +78 -0
- package/dist/utils/ip-validation.js.map +1 -0
- package/dist/utils/security.d.ts +12 -0
- package/dist/utils/security.d.ts.map +1 -0
- package/dist/utils/security.js +61 -0
- package/dist/utils/security.js.map +1 -0
- package/dist/validators/array.d.ts +7 -0
- package/dist/validators/array.d.ts.map +1 -1
- package/dist/validators/array.js +57 -2
- package/dist/validators/array.js.map +1 -1
- package/dist/validators/base.d.ts +131 -0
- package/dist/validators/base.d.ts.map +1 -1
- package/dist/validators/base.js +247 -1
- package/dist/validators/base.js.map +1 -1
- package/dist/validators/base64.d.ts.map +1 -1
- package/dist/validators/base64.js +6 -3
- package/dist/validators/base64.js.map +1 -1
- package/dist/validators/bigint.d.ts +20 -0
- package/dist/validators/bigint.d.ts.map +1 -1
- package/dist/validators/bigint.js +39 -1
- package/dist/validators/bigint.js.map +1 -1
- package/dist/validators/boolean.js +29 -1
- package/dist/validators/boolean.js.map +1 -1
- package/dist/validators/codec.js +1 -1
- package/dist/validators/custom.d.ts +47 -0
- package/dist/validators/custom.d.ts.map +1 -0
- package/dist/validators/custom.js +50 -0
- package/dist/validators/custom.js.map +1 -0
- package/dist/validators/date.d.ts +21 -1
- package/dist/validators/date.d.ts.map +1 -1
- package/dist/validators/date.js +109 -30
- package/dist/validators/date.js.map +1 -1
- package/dist/validators/discriminated-union.d.ts +29 -0
- package/dist/validators/discriminated-union.d.ts.map +1 -0
- package/dist/validators/discriminated-union.js +98 -0
- package/dist/validators/discriminated-union.js.map +1 -0
- package/dist/validators/enum.d.ts +10 -0
- package/dist/validators/enum.d.ts.map +1 -1
- package/dist/validators/enum.js +38 -1
- package/dist/validators/enum.js.map +1 -1
- package/dist/validators/file.d.ts +57 -0
- package/dist/validators/file.d.ts.map +1 -0
- package/dist/validators/file.js +114 -0
- package/dist/validators/file.js.map +1 -0
- package/dist/validators/function.d.ts +26 -0
- package/dist/validators/function.d.ts.map +1 -0
- package/dist/validators/function.js +45 -0
- package/dist/validators/function.js.map +1 -0
- package/dist/validators/hex.d.ts.map +1 -1
- package/dist/validators/hex.js +3 -2
- package/dist/validators/hex.js.map +1 -1
- package/dist/validators/index.d.ts +18 -1
- package/dist/validators/index.d.ts.map +1 -1
- package/dist/validators/index.js +16 -1
- package/dist/validators/index.js.map +1 -1
- package/dist/validators/intersection.d.ts.map +1 -1
- package/dist/validators/intersection.js +15 -8
- package/dist/validators/intersection.js.map +1 -1
- package/dist/validators/json.d.ts +26 -0
- package/dist/validators/json.d.ts.map +1 -0
- package/dist/validators/json.js +64 -0
- package/dist/validators/json.js.map +1 -0
- package/dist/validators/lazy.d.ts +28 -0
- package/dist/validators/lazy.d.ts.map +1 -0
- package/dist/validators/lazy.js +42 -0
- package/dist/validators/lazy.js.map +1 -0
- package/dist/validators/literal.js +1 -1
- package/dist/validators/map.js +1 -1
- package/dist/validators/nan.d.ts +17 -0
- package/dist/validators/nan.d.ts.map +1 -0
- package/dist/validators/nan.js +32 -0
- package/dist/validators/nan.js.map +1 -0
- package/dist/validators/never.js +1 -1
- package/dist/validators/null.d.ts +16 -0
- package/dist/validators/null.d.ts.map +1 -0
- package/dist/validators/null.js +31 -0
- package/dist/validators/null.js.map +1 -0
- package/dist/validators/number.d.ts +22 -0
- package/dist/validators/number.d.ts.map +1 -1
- package/dist/validators/number.js +56 -4
- package/dist/validators/number.js.map +1 -1
- package/dist/validators/object.d.ts +37 -0
- package/dist/validators/object.d.ts.map +1 -1
- package/dist/validators/object.js +183 -14
- package/dist/validators/object.js.map +1 -1
- package/dist/validators/record.d.ts +15 -2
- package/dist/validators/record.d.ts.map +1 -1
- package/dist/validators/record.js +77 -1
- package/dist/validators/record.js.map +1 -1
- package/dist/validators/set.js +1 -1
- package/dist/validators/string-bool.d.ts +61 -0
- package/dist/validators/string-bool.d.ts.map +1 -0
- package/dist/validators/string-bool.js +127 -0
- package/dist/validators/string-bool.js.map +1 -0
- package/dist/validators/string-formats.d.ts +97 -0
- package/dist/validators/string-formats.d.ts.map +1 -0
- package/dist/validators/string-formats.js +120 -0
- package/dist/validators/string-formats.js.map +1 -0
- package/dist/validators/string.d.ts.map +1 -1
- package/dist/validators/string.js +16 -5
- package/dist/validators/string.js.map +1 -1
- package/dist/validators/symbol.js +1 -1
- package/dist/validators/template-literal.d.ts +32 -0
- package/dist/validators/template-literal.d.ts.map +1 -0
- package/dist/validators/template-literal.js +101 -0
- package/dist/validators/template-literal.js.map +1 -0
- package/dist/validators/tuple.js +1 -1
- package/dist/validators/uint8array.d.ts +21 -4
- package/dist/validators/uint8array.d.ts.map +1 -1
- package/dist/validators/uint8array.js +34 -23
- package/dist/validators/uint8array.js.map +1 -1
- package/dist/validators/undefined.d.ts +16 -0
- package/dist/validators/undefined.d.ts.map +1 -0
- package/dist/validators/undefined.js +31 -0
- package/dist/validators/undefined.js.map +1 -0
- package/dist/validators/union.d.ts +3 -0
- package/dist/validators/union.d.ts.map +1 -1
- package/dist/validators/union.js +46 -41
- package/dist/validators/union.js.map +1 -1
- package/dist/validators/void.js +1 -1
- package/dist/validators/xor.d.ts +23 -0
- package/dist/validators/xor.d.ts.map +1 -0
- package/dist/validators/xor.js +64 -0
- package/dist/validators/xor.js.map +1 -0
- package/package.json +2 -2
- package/dist/index-new.d.ts.map +0 -1
- package/dist/locales.d.ts +0 -54
- package/dist/locales.d.ts.map +0 -1
- package/dist/locales.js +0 -732
- package/dist/locales.js.map +0 -1
- package/dist/validators/object-optimized.d.ts +0 -23
- package/dist/validators/object-optimized.d.ts.map +0 -1
- package/dist/validators/object-optimized.js +0 -179
- package/dist/validators/object-optimized.js.map +0 -1
- package/dist/validators/object-ultra.d.ts +0 -34
- package/dist/validators/object-ultra.d.ts.map +0 -1
- package/dist/validators/object-ultra.js +0 -175
- package/dist/validators/object-ultra.js.map +0 -1
- package/dist/validators/string-ultra.d.ts +0 -49
- package/dist/validators/string-ultra.d.ts.map +0 -1
- package/dist/validators/string-ultra.js +0 -217
- package/dist/validators/string-ultra.js.map +0 -1
- package/dist/validators/union-optimized.d.ts +0 -15
- package/dist/validators/union-optimized.d.ts.map +0 -1
- package/dist/validators/union-optimized.js +0 -92
- package/dist/validators/union-optimized.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,128 @@ All notable changes to VLD will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.4.0] - 2026-01-02
|
|
9
|
+
|
|
10
|
+
### 🚀 **Zod 4 Full API Parity Achieved**
|
|
11
|
+
- **Complete Feature Set**: 100% Zod 4 API compatibility
|
|
12
|
+
- **1142 Tests Passing**: Comprehensive test coverage across all features
|
|
13
|
+
- **Production Ready**: All validators, codecs, and utilities fully tested
|
|
14
|
+
|
|
15
|
+
### ✨ New Features
|
|
16
|
+
|
|
17
|
+
#### **v.cidrv6() - IPv6 CIDR Block Validator**
|
|
18
|
+
New validator for IPv6 CIDR notation validation:
|
|
19
|
+
- Supports full IPv6 addresses with prefix lengths (0-128)
|
|
20
|
+
- Validates compressed IPv6 notation (`::`, `::1`, etc.)
|
|
21
|
+
- Rejects IPv4 CIDR blocks
|
|
22
|
+
|
|
23
|
+
#### **.apply() - External Function Chaining**
|
|
24
|
+
Apply external functions to validators for advanced composition:
|
|
25
|
+
- Enables functional composition patterns
|
|
26
|
+
- Supports custom validation pipelines
|
|
27
|
+
- Full TypeScript type inference
|
|
28
|
+
|
|
29
|
+
#### **.safeExtend() - Type-Safe Object Extension**
|
|
30
|
+
Safely extend object schemas without accidentally overriding existing fields:
|
|
31
|
+
- Prevents accidental field overrides in object schemas
|
|
32
|
+
- Clear error messages listing all conflicting keys
|
|
33
|
+
- Supports chaining multiple safeExtend calls
|
|
34
|
+
|
|
35
|
+
### 🌍 Internationalization Updates
|
|
36
|
+
- Added i18n messages for all new features in **27+ languages**
|
|
37
|
+
- New messages: `stringCidrv6`, `safeExtendOverlap`
|
|
38
|
+
- Updated all locale files with translations
|
|
39
|
+
|
|
40
|
+
### 🔧 Build System Improvements
|
|
41
|
+
- **Fixed ESM module resolution**: Directory imports now correctly resolve to `/index.js`
|
|
42
|
+
- **Updated fix-imports script**: Now handles directory-based imports properly
|
|
43
|
+
- **Renamed to CommonJS**: `scripts/fix-imports.cjs` for ESM package compatibility
|
|
44
|
+
|
|
45
|
+
### 📊 Performance
|
|
46
|
+
VLD continues to outperform Zod across all benchmarks:
|
|
47
|
+
- **2.52x faster** average performance
|
|
48
|
+
- **9/10 benchmark wins** vs Zod
|
|
49
|
+
- **2.41x less memory** usage overall
|
|
50
|
+
- **83x faster** schema creation
|
|
51
|
+
|
|
52
|
+
### 🧪 Testing
|
|
53
|
+
- **49 test suites** - All passing
|
|
54
|
+
- **1142 tests** - Comprehensive coverage
|
|
55
|
+
- **TypeScript strict mode** - Full type safety verified
|
|
56
|
+
|
|
57
|
+
## [1.3.1] - 2025-11-12
|
|
58
|
+
|
|
59
|
+
### 🎯 **100% Test Success Rate Achieved**
|
|
60
|
+
- **Perfect Test Coverage**: All 695 tests now passing (0 failures)
|
|
61
|
+
- **IPv6 Validation Fix**: Resolved final failing test for IPv6-mapped addresses
|
|
62
|
+
- **Security Validation**: All 4 critical security fixes thoroughly tested
|
|
63
|
+
|
|
64
|
+
### 🔧 Bug Fixes
|
|
65
|
+
- **IPv6 Validation**: Fixed validation for IPv4-mapped IPv6 addresses (`::ffff:192.0.2.1`)
|
|
66
|
+
- **Test Coverage**: Updated documentation to reflect 695 passing tests (up from 694)
|
|
67
|
+
|
|
68
|
+
### ✅ Quality Assurance
|
|
69
|
+
- **100% Test Success**: Achieved perfect test success rate across all test suites
|
|
70
|
+
- **Security Hardening**: All security vulnerabilities validated with comprehensive tests
|
|
71
|
+
- **Performance Maintained**: No performance impact from security improvements
|
|
72
|
+
|
|
73
|
+
## [1.3.0] - 2025-11-12
|
|
74
|
+
|
|
75
|
+
### 🔒 **Critical Security Update**
|
|
76
|
+
- **SECURITY**: Fixed 4 critical security vulnerabilities identified in comprehensive bug analysis
|
|
77
|
+
- **Enhanced Security**: Comprehensive protection against prototype pollution, ReDoS attacks, and type safety issues
|
|
78
|
+
- **Security-First**: All validators now include security controls while maintaining backwards compatibility
|
|
79
|
+
|
|
80
|
+
### 🛡️ Security Fixes Implemented
|
|
81
|
+
|
|
82
|
+
#### **BUG-001: Union Validator Type Safety** ✅ FIXED
|
|
83
|
+
- **Issue**: Constructor name spoofing vulnerability in union validators
|
|
84
|
+
- **Solution**: Replaced constructor name checking with secure feature detection
|
|
85
|
+
- **Impact**: Prevents malicious validator objects from bypassing type checks
|
|
86
|
+
- **Location**: `src/validators/union.ts`
|
|
87
|
+
|
|
88
|
+
#### **BUG-002: Prototype Pollution Prevention** ✅ FIXED
|
|
89
|
+
- **Issue**: Prototype pollution vulnerability in codec utilities
|
|
90
|
+
- **Solution**: Added comprehensive input validation and suspicious content detection
|
|
91
|
+
- **Impact**: Prevents `__proto__`, `constructor`, and `prototype` pollution attacks
|
|
92
|
+
- **Location**: `src/utils/codec-utils.ts`
|
|
93
|
+
|
|
94
|
+
#### **BUG-004: IPv6 ReDoS Prevention** ✅ FIXED
|
|
95
|
+
- **Issue**: Regular Expression Denial of Service (ReDoS) vulnerability in IPv6 validation
|
|
96
|
+
- **Solution**: Replaced complex regex with multi-step validation approach
|
|
97
|
+
- **Impact**: Prevents catastrophic backtracking attacks while maintaining IPv6 support
|
|
98
|
+
- **Location**: `src/validators/string.ts`, `src/coercion/string.ts`
|
|
99
|
+
|
|
100
|
+
#### **BUG-005: Safe String Coercion** ✅ FIXED
|
|
101
|
+
- **Issue**: Unsafe type coercion without length limits or sanitization
|
|
102
|
+
- **Solution**: Added length limits (1M characters) and control character sanitization
|
|
103
|
+
- **Impact**: Prevents DoS attacks and information disclosure through malicious strings
|
|
104
|
+
- **Location**: `src/coercion/string.ts`
|
|
105
|
+
|
|
106
|
+
### 📊 Quality Improvements
|
|
107
|
+
- **Test Coverage**: Maintained excellent coverage at **96.55%** with **695 passing tests**
|
|
108
|
+
- **Performance**: All security improvements maintain VLD's performance advantages
|
|
109
|
+
- **Backwards Compatibility**: All changes are fully backwards compatible
|
|
110
|
+
- **Security Testing**: Comprehensive security test suite added with 18 dedicated tests
|
|
111
|
+
|
|
112
|
+
### 🧪 Testing & Validation
|
|
113
|
+
- **Security Test Suite**: Added comprehensive security validation tests
|
|
114
|
+
- **Performance Tests**: Verified security fixes don't impact performance
|
|
115
|
+
- **Integration Tests**: Validated compatibility with existing codebases
|
|
116
|
+
- **Memory Tests**: Confirmed no memory leaks with security enhancements
|
|
117
|
+
|
|
118
|
+
### 📝 Documentation Updates
|
|
119
|
+
- **Security Documentation**: Detailed security analysis reports created
|
|
120
|
+
- **Bug Fix Reports**: Comprehensive documentation of all fixes implemented
|
|
121
|
+
- **Test Coverage**: Updated coverage metrics to reflect new security tests
|
|
122
|
+
- **README**: Updated to reflect latest test coverage and security improvements
|
|
123
|
+
|
|
124
|
+
### 🔧 Technical Details
|
|
125
|
+
- **Zero Breaking Changes**: All security improvements are backwards compatible
|
|
126
|
+
- **Immutable Architecture**: Security hardening maintains VLD's immutable validator pattern
|
|
127
|
+
- **Type Safety**: Enhanced type checking without compromising TypeScript inference
|
|
128
|
+
- **Error Handling**: Improved error messages for security-related validation failures
|
|
129
|
+
|
|
8
130
|
## [1.2.0] - 2025-08-24
|
|
9
131
|
|
|
10
132
|
### 🎯 **100% Test Success Rate Achieved**
|
package/README.md
CHANGED
|
@@ -1,13 +1,26 @@
|
|
|
1
1
|
# VLD - Fast & Lightweight TypeScript Validation Library
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/@oxog/vld)
|
|
4
|
-
[](https://opensource.org/licenses/MIT)
|
|
5
|
-
[](https://www.typescriptlang.org/)
|
|
6
|
-
[](package.json)
|
|
7
|
-
[](package.json)
|
|
3
|
+
[](https://www.npmjs.com/package/@oxog/vld) [](https://opensource.org/licenses/MIT) [](https://www.typescriptlang.org/) [](package.json) [](package.json)
|
|
8
4
|
|
|
9
5
|
VLD is a blazing-fast, type-safe validation library for TypeScript and JavaScript with **full Zod feature parity**. Built with performance in mind, it provides a simple and intuitive API while maintaining excellent type inference and 27+ language internationalization support.
|
|
10
6
|
|
|
7
|
+
## 📚 Table of Contents
|
|
8
|
+
|
|
9
|
+
- [🚀 Features](#-features)
|
|
10
|
+
- [📊 Performance](#-performance)
|
|
11
|
+
- [📦 Installation](#-installation)
|
|
12
|
+
- [🎯 Quick Start](#-quick-start)
|
|
13
|
+
- [📖 API Reference](#-api-reference)
|
|
14
|
+
- [🌍 Internationalization (i18n)](#-internationalization-i18n)
|
|
15
|
+
- [⚠️ Error Handling & Formatting](#️-error-handling--formatting)
|
|
16
|
+
- [🔥 Advanced Examples](#-advanced-examples)
|
|
17
|
+
- [🎯 Why VLD?](#-why-vld)
|
|
18
|
+
- [🔄 Codecs - Bidirectional Transformations](#-codecs---bidirectional-transformations)
|
|
19
|
+
- [🔄 Migrating from Zod](#-migrating-from-zod)
|
|
20
|
+
- [📈 Benchmarks](#-benchmarks)
|
|
21
|
+
- [🤝 Contributing](#-contributing)
|
|
22
|
+
- [🔗 Links](#-links)
|
|
23
|
+
|
|
11
24
|
## 🚀 Features
|
|
12
25
|
|
|
13
26
|
### Core Features
|
|
@@ -18,8 +31,8 @@ VLD is a blazing-fast, type-safe validation library for TypeScript and JavaScrip
|
|
|
18
31
|
- **🔧 Composable**: Chain validations for complex schemas
|
|
19
32
|
- **⚠️ Advanced Error Formatting**: Tree, pretty, and flatten error utilities
|
|
20
33
|
- **🌍 Multi-language**: Built-in support for 27+ languages
|
|
21
|
-
- **✅
|
|
22
|
-
- **🏆 Industry Leading Performance**: 2.
|
|
34
|
+
- **✅ 96.55% Test Coverage**: Rigorously tested with 1142 passing tests
|
|
35
|
+
- **🏆 Industry Leading Performance**: 2.52x faster than Zod on average
|
|
23
36
|
|
|
24
37
|
### Advanced Zod-Compatible Features
|
|
25
38
|
- **🔄 Type Coercion**: `v.coerce.string()`, `v.coerce.number()`, `v.coerce.boolean()`, etc.
|
|
@@ -31,7 +44,12 @@ VLD is a blazing-fast, type-safe validation library for TypeScript and JavaScrip
|
|
|
31
44
|
- **🛡️ Fallback Handling**: `catch()` for graceful error recovery
|
|
32
45
|
- **🎯 Object Utilities**: `pick()`, `omit()`, `extend()` for flexible object schemas
|
|
33
46
|
|
|
34
|
-
### 🚀 **NEW
|
|
47
|
+
### 🚀 **NEW in v1.4.0** - Zod 4 Full API Parity
|
|
48
|
+
- **🌐 `v.cidrv6()`**: IPv6 CIDR block validation
|
|
49
|
+
- **🔗 `.apply()`**: External function chaining for advanced composition
|
|
50
|
+
- **🛡️ `.safeExtend()`**: Type-safe object extension without accidental overrides
|
|
51
|
+
|
|
52
|
+
### 🚀 Codec System - Beyond Zod
|
|
35
53
|
- **↔️ Bidirectional Transformations**: Full encode/decode support for data conversion
|
|
36
54
|
- **📦 19 Built-in Codecs**: String conversions, date parsing, JSON, URL, binary data
|
|
37
55
|
- **🔗 Zod-Compatible**: All `stringToNumber`, `jsonCodec`, `base64ToBytes`, etc.
|
|
@@ -65,9 +83,9 @@ VLD is designed for speed and efficiency with recent optimizations delivering ex
|
|
|
65
83
|
- **86% less memory** for error handling
|
|
66
84
|
- **78% less memory** overall average
|
|
67
85
|
|
|
68
|
-
###
|
|
86
|
+
### A Note on Real-World Benchmarking
|
|
69
87
|
|
|
70
|
-
Many validation library benchmarks
|
|
88
|
+
Many validation library benchmarks can be misleading because they often test with **reused schema instances**:
|
|
71
89
|
|
|
72
90
|
```javascript
|
|
73
91
|
// What benchmarks typically test (unrealistic):
|
|
@@ -109,6 +127,9 @@ pnpm add @oxog/vld
|
|
|
109
127
|
```typescript
|
|
110
128
|
import { v } from '@oxog/vld';
|
|
111
129
|
|
|
130
|
+
// It is recommended to import as `v` for consistency with Zod's `z`
|
|
131
|
+
// and for a more concise syntax.
|
|
132
|
+
|
|
112
133
|
// Define a schema
|
|
113
134
|
const userSchema = v.object({
|
|
114
135
|
name: v.string().min(2),
|
|
@@ -148,8 +169,9 @@ v.boolean() // Boolean validation
|
|
|
148
169
|
v.bigint() // BigInt validation
|
|
149
170
|
v.symbol() // Symbol validation
|
|
150
171
|
v.date() // Date validation
|
|
172
|
+
v.uint8array()// Uint8Array validation
|
|
151
173
|
v.literal() // Literal values
|
|
152
|
-
v.enum() // Enum values
|
|
174
|
+
v.enum() // Enum values (supports TypeScript enums)
|
|
153
175
|
v.any() // Any type
|
|
154
176
|
v.unknown() // Unknown type
|
|
155
177
|
v.void() // Void type
|
|
@@ -422,6 +444,9 @@ VLD provides advanced error formatting utilities similar to Zod's error handling
|
|
|
422
444
|
```typescript
|
|
423
445
|
import { v, VldError, treeifyError, prettifyError, flattenError } from '@oxog/vld';
|
|
424
446
|
|
|
447
|
+
// Note: Error formatting utilities like `treeifyError` are separate named exports
|
|
448
|
+
// and are not part of the main `v` object.
|
|
449
|
+
|
|
425
450
|
const userSchema = v.object({
|
|
426
451
|
username: v.string().min(3),
|
|
427
452
|
favoriteNumbers: v.array(v.number()),
|
|
@@ -737,43 +762,36 @@ function handleLogin(data: unknown) {
|
|
|
737
762
|
}
|
|
738
763
|
```
|
|
739
764
|
|
|
740
|
-
##
|
|
765
|
+
## 🆚 VLD vs. Zod
|
|
741
766
|
|
|
742
|
-
|
|
743
|
-
VLD provides **complete Zod compatibility** with all advanced features including coercion, intersections, transformations, and object utilities - plus unique features like 27+ language internationalization.
|
|
767
|
+
VLD is designed as a compelling alternative to Zod, offering full feature parity while delivering significant improvements in performance, bundle size, and internationalization.
|
|
744
768
|
|
|
745
|
-
###
|
|
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.
|
|
769
|
+
### Feature Comparison
|
|
747
770
|
|
|
748
|
-
|
|
749
|
-
|
|
771
|
+
| Feature | VLD | Zod |
|
|
772
|
+
| ----------------------- | ---------------------------------- | ------------------------------------ |
|
|
773
|
+
| **Performance** | **~2.07x faster** (average) | Baseline |
|
|
774
|
+
| **Memory Usage** | **~78% less** overall | Baseline |
|
|
775
|
+
| **Internationalization**| ✅ **Built-in (27+ languages)** | ❌ Requires third-party library |
|
|
776
|
+
| **Dependencies** | **Zero** | `zod-i18n` for locales |
|
|
777
|
+
| **Bundle Size** | Smaller | Larger |
|
|
778
|
+
| **API** | 100% Zod-compatible | Standard Zod API |
|
|
779
|
+
| **Codecs** | ✅ Built-in, bidirectional | ✅ Via external `zod-codecs` |
|
|
780
|
+
| **Error Formatting** | ✅ Advanced (tree, pretty, flatten)| ✅ Advanced (tree, pretty, flatten)|
|
|
781
|
+
| **Type Inference** | ✅ Excellent | ✅ Excellent |
|
|
750
782
|
|
|
751
|
-
###
|
|
752
|
-
Our benchmarks test real-world scenarios, not just synthetic loops. VLD excels where it matters: in actual applications where schemas are created dynamically.
|
|
783
|
+
### 🔄 Seamless Migration from Zod
|
|
753
784
|
|
|
754
|
-
|
|
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
|
|
785
|
+
Migration is straightforward due to 100% API compatibility. You can typically just swap the import statement.
|
|
760
786
|
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
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);
|
|
787
|
+
```javascript
|
|
788
|
+
// Before (Zod)
|
|
789
|
+
import { z } from 'zod';
|
|
790
|
+
const schema = z.string().email();
|
|
772
791
|
|
|
773
|
-
//
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
.catch({ name: 'Unknown', email: 'no-email@example.com' });
|
|
792
|
+
// After (VLD) - Exact same syntax!
|
|
793
|
+
import { v } from '@oxog/vld';
|
|
794
|
+
const schema = v.string().email();
|
|
777
795
|
```
|
|
778
796
|
|
|
779
797
|
## 🔄 Codecs - Bidirectional Transformations
|
|
@@ -1060,7 +1078,7 @@ const schema = v.string().email();
|
|
|
1060
1078
|
- **🌍 Internationalization**: Built-in 27+ language support
|
|
1061
1079
|
- **📦 Bundle Size**: Smaller with zero dependencies
|
|
1062
1080
|
- **🔒 Security**: Immutable validators prevent memory leaks
|
|
1063
|
-
- **✅ Testing**:
|
|
1081
|
+
- **✅ Testing**: 96.55% test coverage with 1142 tests
|
|
1064
1082
|
|
|
1065
1083
|
## 📈 Benchmarks
|
|
1066
1084
|
|
|
@@ -1079,7 +1097,7 @@ Latest benchmark results show VLD consistently outperforming Zod:
|
|
|
1079
1097
|
| Optional Values | 32.7M ops/sec | **4.52x faster** |
|
|
1080
1098
|
| Type Coercion | 18.4M ops/sec | **1.46x faster** |
|
|
1081
1099
|
|
|
1082
|
-
**Average: 2.
|
|
1100
|
+
**Average: 2.52x faster than Zod**
|
|
1083
1101
|
|
|
1084
1102
|
### Run Benchmarks
|
|
1085
1103
|
|
package/dist/coercion/bigint.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"boolean.d.ts","sourceRoot":"","sources":["../../src/coercion/boolean.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAGjD;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,UAAU;IAC9C;;OAEG;IACH,MAAM,CAAC,MAAM,IAAI,gBAAgB;IAIjC;;OAEG;IACH,KAAK,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO;
|
|
1
|
+
{"version":3,"file":"boolean.d.ts","sourceRoot":"","sources":["../../src/coercion/boolean.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAGjD;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,UAAU;IAC9C;;OAEG;IACH,MAAM,CAAC,MAAM,IAAI,gBAAgB;IAIjC;;OAEG;IACH,KAAK,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO;IAyC9B;;OAEG;IACH,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC;CAOhD"}
|
package/dist/coercion/boolean.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { VldBoolean } from '../validators/boolean.js';
|
|
2
|
-
import { getMessages } from '../locales.js';
|
|
2
|
+
import { getMessages } from '../locales/index.js';
|
|
3
3
|
/**
|
|
4
4
|
* Boolean coercion validator that attempts to convert values to booleans
|
|
5
5
|
*/
|
|
@@ -38,8 +38,12 @@ export class VldCoerceBoolean extends VldBoolean {
|
|
|
38
38
|
if (value === null || value === undefined) {
|
|
39
39
|
throw new Error(getMessages().coercionFailed('boolean', value));
|
|
40
40
|
}
|
|
41
|
-
//
|
|
42
|
-
|
|
41
|
+
// Handle actual boolean values
|
|
42
|
+
if (typeof value === 'boolean') {
|
|
43
|
+
return super.parse(value);
|
|
44
|
+
}
|
|
45
|
+
// Reject all other unsupported types (objects, arrays, etc.)
|
|
46
|
+
throw new Error(getMessages().coercionFailed('boolean', value));
|
|
43
47
|
}
|
|
44
48
|
catch (error) {
|
|
45
49
|
if (error.message.includes('coercionFailed')) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"boolean.js","sourceRoot":"","sources":["../../src/coercion/boolean.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAEnD,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzC;;GAEG;AACH,MAAM,OAAO,gBAAiB,SAAQ,UAAU;IAC9C;;OAEG;IACH,MAAM,CAAC,MAAM;QACX,OAAO,IAAI,gBAAgB,EAAE,CAAC;IAChC,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,KAAc;QAClB,IAAI,CAAC;YACH,uBAAuB;YACvB,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;gBAC9B,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC;gBACzC,IAAI,KAAK,KAAK,MAAM,IAAI,KAAK,KAAK,GAAG,IAAI,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;oBAC3E,OAAO,IAAI,CAAC;gBACd,CAAC;gBACD,IAAI,KAAK,KAAK,OAAO,IAAI,KAAK,KAAK,GAAG,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,KAAK,EAAE,CAAC;oBAC5E,OAAO,KAAK,CAAC;gBACf,CAAC;gBACD,MAAM,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC,cAAc,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC;YAClE,CAAC;YAED,uBAAuB;YACvB,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;gBAC9B,IAAI,KAAK,KAAK,CAAC;oBAAE,OAAO,IAAI,CAAC;gBAC7B,IAAI,KAAK,KAAK,CAAC;oBAAE,OAAO,KAAK,CAAC;gBAC9B,MAAM,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC,cAAc,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC;YAClE,CAAC;YAED,4BAA4B;YAC5B,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;gBAC1C,MAAM,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC,cAAc,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC;YAClE,CAAC;YAED,
|
|
1
|
+
{"version":3,"file":"boolean.js","sourceRoot":"","sources":["../../src/coercion/boolean.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAEnD,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzC;;GAEG;AACH,MAAM,OAAO,gBAAiB,SAAQ,UAAU;IAC9C;;OAEG;IACH,MAAM,CAAC,MAAM;QACX,OAAO,IAAI,gBAAgB,EAAE,CAAC;IAChC,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,KAAc;QAClB,IAAI,CAAC;YACH,uBAAuB;YACvB,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;gBAC9B,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC;gBACzC,IAAI,KAAK,KAAK,MAAM,IAAI,KAAK,KAAK,GAAG,IAAI,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;oBAC3E,OAAO,IAAI,CAAC;gBACd,CAAC;gBACD,IAAI,KAAK,KAAK,OAAO,IAAI,KAAK,KAAK,GAAG,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,KAAK,EAAE,CAAC;oBAC5E,OAAO,KAAK,CAAC;gBACf,CAAC;gBACD,MAAM,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC,cAAc,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC;YAClE,CAAC;YAED,uBAAuB;YACvB,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;gBAC9B,IAAI,KAAK,KAAK,CAAC;oBAAE,OAAO,IAAI,CAAC;gBAC7B,IAAI,KAAK,KAAK,CAAC;oBAAE,OAAO,KAAK,CAAC;gBAC9B,MAAM,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC,cAAc,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC;YAClE,CAAC;YAED,4BAA4B;YAC5B,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;gBAC1C,MAAM,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC,cAAc,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC;YAClE,CAAC;YAED,+BAA+B;YAC/B,IAAI,OAAO,KAAK,KAAK,SAAS,EAAE,CAAC;gBAC/B,OAAO,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YAC5B,CAAC;YAED,6DAA6D;YAC7D,MAAM,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC,cAAc,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC;QAClE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAK,KAAe,CAAC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC;gBACxD,MAAM,KAAK,CAAC;YACd,CAAC;YACD,MAAM,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC,cAAc,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC;QAClE,CAAC;IACH,CAAC;IAED;;OAEG;IACH,SAAS,CAAC,KAAc;QACtB,IAAI,CAAC;YACH,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;QACpD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAc,EAAE,CAAC;QACnD,CAAC;IACH,CAAC;CACF","sourcesContent":["import { VldBoolean } from '../validators/boolean';\r\nimport { ParseResult } from '../validators/base';\r\nimport { getMessages } from '../locales';\r\n\r\n/**\r\n * Boolean coercion validator that attempts to convert values to booleans\r\n */\r\nexport class VldCoerceBoolean extends VldBoolean {\r\n /**\r\n * Create a new coerce boolean validator\r\n */\r\n static create(): VldCoerceBoolean {\r\n return new VldCoerceBoolean();\r\n }\r\n \r\n /**\r\n * Parse and coerce a value to boolean\r\n */\r\n parse(value: unknown): boolean {\r\n try {\r\n // Handle string values\r\n if (typeof value === 'string') {\r\n const lower = value.toLowerCase().trim();\r\n if (lower === 'true' || lower === '1' || lower === 'yes' || lower === 'on') {\r\n return true;\r\n }\r\n if (lower === 'false' || lower === '0' || lower === 'no' || lower === 'off') {\r\n return false;\r\n }\r\n throw new Error(getMessages().coercionFailed('boolean', value));\r\n }\r\n \r\n // Handle number values\r\n if (typeof value === 'number') {\r\n if (value === 1) return true;\r\n if (value === 0) return false;\r\n throw new Error(getMessages().coercionFailed('boolean', value));\r\n }\r\n \r\n // Handle null and undefined\r\n if (value === null || value === undefined) {\r\n throw new Error(getMessages().coercionFailed('boolean', value));\r\n }\r\n\r\n // Handle actual boolean values\r\n if (typeof value === 'boolean') {\r\n return super.parse(value);\r\n }\r\n\r\n // Reject all other unsupported types (objects, arrays, etc.)\r\n throw new Error(getMessages().coercionFailed('boolean', value));\r\n } catch (error) {\r\n if ((error as Error).message.includes('coercionFailed')) {\r\n throw error;\r\n }\r\n throw new Error(getMessages().coercionFailed('boolean', value));\r\n }\r\n }\r\n \r\n /**\r\n * Safely parse and coerce a value to boolean\r\n */\r\n safeParse(value: unknown): ParseResult<boolean> {\r\n try {\r\n return { success: true, data: this.parse(value) };\r\n } catch (error) {\r\n return { success: false, error: error as Error };\r\n }\r\n }\r\n}"]}
|
package/dist/coercion/date.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"number.d.ts","sourceRoot":"","sources":["../../src/coercion/number.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAGjD;;GAEG;AACH,qBAAa,eAAgB,SAAQ,SAAS;IAC5C,SAAS,aAAa,MAAM,CAAC,EAAE,GAAG;IAIlC;;OAEG;IACH,MAAM,CAAC,MAAM,IAAI,eAAe;IAKhC,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,eAAe;IAOrD,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,eAAe;IAOrD,GAAG,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,eAAe;IAOtC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,eAAe;IAO3C,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,eAAe;IAO3C,WAAW,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,eAAe;IAO9C,WAAW,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,eAAe;IAO9C,MAAM,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,eAAe;IAOzC,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,eAAe;IAOvC,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,eAAe;
|
|
1
|
+
{"version":3,"file":"number.d.ts","sourceRoot":"","sources":["../../src/coercion/number.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAGjD;;GAEG;AACH,qBAAa,eAAgB,SAAQ,SAAS;IAC5C,SAAS,aAAa,MAAM,CAAC,EAAE,GAAG;IAIlC;;OAEG;IACH,MAAM,CAAC,MAAM,IAAI,eAAe;IAKhC,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,eAAe;IAOrD,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,eAAe;IAOrD,GAAG,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,eAAe;IAOtC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,eAAe;IAO3C,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,eAAe;IAO3C,WAAW,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,eAAe;IAO9C,WAAW,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,eAAe;IAO9C,MAAM,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,eAAe;IAOzC,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,eAAe;IAOvC,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,eAAe;IAW5D,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,eAAe;IAItD,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,eAAe;IAOpE,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,eAAe;IAavC,GAAG,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,eAAe;IAatC;;OAEG;IACH,KAAK,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM;IAgC7B;;OAEG;IACH,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,CAAC,MAAM,CAAC;CAO/C"}
|
package/dist/coercion/number.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { VldNumber } from '../validators/number.js';
|
|
2
|
-
import { getMessages } from '../locales.js';
|
|
2
|
+
import { getMessages } from '../locales/index.js';
|
|
3
3
|
/**
|
|
4
4
|
* Number coercion validator that attempts to convert values to numbers
|
|
5
5
|
*/
|
|
@@ -70,7 +70,11 @@ export class VldCoerceNumber extends VldNumber {
|
|
|
70
70
|
}
|
|
71
71
|
multipleOf(value, message) {
|
|
72
72
|
return new VldCoerceNumber({
|
|
73
|
-
checks: [...this.config.checks, (v) =>
|
|
73
|
+
checks: [...this.config.checks, (v) => {
|
|
74
|
+
// BUG-004 FIX: Use epsilon comparison for floating point precision (consistent with VldNumber)
|
|
75
|
+
const remainder = Math.abs(v % value);
|
|
76
|
+
return remainder < Number.EPSILON || Math.abs(remainder - Math.abs(value)) < Number.EPSILON;
|
|
77
|
+
}],
|
|
74
78
|
errorMessage: message || getMessages().numberMultipleOf(value)
|
|
75
79
|
});
|
|
76
80
|
}
|
|
@@ -84,14 +88,26 @@ export class VldCoerceNumber extends VldNumber {
|
|
|
84
88
|
});
|
|
85
89
|
}
|
|
86
90
|
even(message) {
|
|
91
|
+
// BUG-NEW-007 FIX: Add integer check before even/odd validation
|
|
87
92
|
return new VldCoerceNumber({
|
|
88
|
-
checks: [...this.config.checks, (v) =>
|
|
93
|
+
checks: [...this.config.checks, (v) => {
|
|
94
|
+
if (!Number.isInteger(v)) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
return v % 2 === 0;
|
|
98
|
+
}],
|
|
89
99
|
errorMessage: message || 'Number must be even'
|
|
90
100
|
});
|
|
91
101
|
}
|
|
92
102
|
odd(message) {
|
|
103
|
+
// BUG-NEW-007 FIX: Add integer check before even/odd validation
|
|
93
104
|
return new VldCoerceNumber({
|
|
94
|
-
checks: [...this.config.checks, (v) =>
|
|
105
|
+
checks: [...this.config.checks, (v) => {
|
|
106
|
+
if (!Number.isInteger(v)) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
return v % 2 !== 0;
|
|
110
|
+
}],
|
|
95
111
|
errorMessage: message || 'Number must be odd'
|
|
96
112
|
});
|
|
97
113
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"number.js","sourceRoot":"","sources":["../../src/coercion/number.ts"],"names":[],"mappings":"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","sourcesContent":["import { VldNumber } from '../validators/number';\nimport { ParseResult } from '../validators/base';\nimport { getMessages } from '../locales';\n\n/**\n * Number coercion validator that attempts to convert values to numbers\n */\nexport class VldCoerceNumber extends VldNumber {\n protected constructor(config?: any) {\n super(config);\n }\n \n /**\n * Create a new coerce number validator\n */\n static create(): VldCoerceNumber {\n return new VldCoerceNumber();\n }\n \n // Override all chain methods to return VldCoerceNumber instances\n min(value: number, message?: string): VldCoerceNumber {\n return new VldCoerceNumber({\n checks: [...this.config.checks, (v: number) => v >= value],\n errorMessage: message || getMessages().numberMin(value)\n });\n }\n \n max(value: number, message?: string): VldCoerceNumber {\n return new VldCoerceNumber({\n checks: [...this.config.checks, (v: number) => v <= value],\n errorMessage: message || getMessages().numberMax(value)\n });\n }\n \n int(message?: string): VldCoerceNumber {\n return new VldCoerceNumber({\n checks: [...this.config.checks, (v: number) => Number.isInteger(v)],\n errorMessage: message || getMessages().numberInt\n });\n }\n \n positive(message?: string): VldCoerceNumber {\n return new VldCoerceNumber({\n checks: [...this.config.checks, (v: number) => v > 0],\n errorMessage: message || getMessages().numberPositive\n });\n }\n \n negative(message?: string): VldCoerceNumber {\n return new VldCoerceNumber({\n checks: [...this.config.checks, (v: number) => v < 0],\n errorMessage: message || getMessages().numberNegative\n });\n }\n \n nonnegative(message?: string): VldCoerceNumber {\n return new VldCoerceNumber({\n checks: [...this.config.checks, (v: number) => v >= 0],\n errorMessage: message || getMessages().numberNonnegative\n });\n }\n \n nonpositive(message?: string): VldCoerceNumber {\n return new VldCoerceNumber({\n checks: [...this.config.checks, (v: number) => v <= 0],\n errorMessage: message || getMessages().numberNonpositive\n });\n }\n \n finite(message?: string): VldCoerceNumber {\n return new VldCoerceNumber({\n checks: [...this.config.checks, (v: number) => Number.isFinite(v)],\n errorMessage: message || getMessages().numberFinite\n });\n }\n \n safe(message?: string): VldCoerceNumber {\n return new VldCoerceNumber({\n checks: [...this.config.checks, (v: number) => Number.isSafeInteger(v)],\n errorMessage: message || getMessages().numberSafe\n });\n }\n \n multipleOf(value: number, message?: string): VldCoerceNumber {\n return new VldCoerceNumber({\n checks: [...this.config.checks, (v: number) => v % value === 0],\n errorMessage: message || getMessages().numberMultipleOf(value)\n });\n }\n \n step(value: number, message?: string): VldCoerceNumber {\n return this.multipleOf(value, message);\n }\n \n between(min: number, max: number, message?: string): VldCoerceNumber {\n return new VldCoerceNumber({\n checks: [...this.config.checks, (v: number) => v >= min && v <= max],\n errorMessage: message || `Number must be between ${min} and ${max}`\n });\n }\n \n even(message?: string): VldCoerceNumber {\n return new VldCoerceNumber({\n checks: [...this.config.checks, (v: number) => v % 2 === 0],\n errorMessage: message || 'Number must be even'\n });\n }\n \n odd(message?: string): VldCoerceNumber {\n return new VldCoerceNumber({\n checks: [...this.config.checks, (v: number) => v % 2 !== 0],\n errorMessage: message || 'Number must be odd'\n });\n }\n \n /**\n * Parse and coerce a value to number\n */\n parse(value: unknown): number {\n // If it's already a valid number, use parent validation directly\n if (typeof value === 'number' && !isNaN(value)) {\n return super.parse(value);\n }\n \n let coerced: number;\n \n if (typeof value === 'string') {\n const trimmed = value.trim();\n if (trimmed === '') {\n throw new Error(getMessages().coercionFailed('number', value));\n }\n coerced = Number(trimmed);\n if (isNaN(coerced)) {\n throw new Error(getMessages().coercionFailed('number', value));\n }\n } else if (typeof value === 'boolean') {\n coerced = value ? 1 : 0;\n } else if (value === null || value === undefined) {\n throw new Error(getMessages().coercionFailed('number', value));\n } else {\n coerced = Number(value);\n if (isNaN(coerced)) {\n throw new Error(getMessages().coercionFailed('number', value));\n }\n }\n \n // Use parent validation with coerced value\n return super.parse(coerced);\n }\n \n /**\n * Safely parse and coerce a value to number\n */\n safeParse(value: unknown): ParseResult<number> {\n try {\n return { success: true, data: this.parse(value) };\n } catch (error) {\n return { success: false, error: error as Error };\n }\n }\n}"]}
|
|
1
|
+
{"version":3,"file":"number.js","sourceRoot":"","sources":["../../src/coercion/number.ts"],"names":[],"mappings":"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","sourcesContent":["import { VldNumber } from '../validators/number';\r\nimport { ParseResult } from '../validators/base';\r\nimport { getMessages } from '../locales';\r\n\r\n/**\r\n * Number coercion validator that attempts to convert values to numbers\r\n */\r\nexport class VldCoerceNumber extends VldNumber {\r\n protected constructor(config?: any) {\r\n super(config);\r\n }\r\n \r\n /**\r\n * Create a new coerce number validator\r\n */\r\n static create(): VldCoerceNumber {\r\n return new VldCoerceNumber();\r\n }\r\n \r\n // Override all chain methods to return VldCoerceNumber instances\r\n min(value: number, message?: string): VldCoerceNumber {\r\n return new VldCoerceNumber({\r\n checks: [...this.config.checks, (v: number) => v >= value],\r\n errorMessage: message || getMessages().numberMin(value)\r\n });\r\n }\r\n \r\n max(value: number, message?: string): VldCoerceNumber {\r\n return new VldCoerceNumber({\r\n checks: [...this.config.checks, (v: number) => v <= value],\r\n errorMessage: message || getMessages().numberMax(value)\r\n });\r\n }\r\n \r\n int(message?: string): VldCoerceNumber {\r\n return new VldCoerceNumber({\r\n checks: [...this.config.checks, (v: number) => Number.isInteger(v)],\r\n errorMessage: message || getMessages().numberInt\r\n });\r\n }\r\n \r\n positive(message?: string): VldCoerceNumber {\r\n return new VldCoerceNumber({\r\n checks: [...this.config.checks, (v: number) => v > 0],\r\n errorMessage: message || getMessages().numberPositive\r\n });\r\n }\r\n \r\n negative(message?: string): VldCoerceNumber {\r\n return new VldCoerceNumber({\r\n checks: [...this.config.checks, (v: number) => v < 0],\r\n errorMessage: message || getMessages().numberNegative\r\n });\r\n }\r\n \r\n nonnegative(message?: string): VldCoerceNumber {\r\n return new VldCoerceNumber({\r\n checks: [...this.config.checks, (v: number) => v >= 0],\r\n errorMessage: message || getMessages().numberNonnegative\r\n });\r\n }\r\n \r\n nonpositive(message?: string): VldCoerceNumber {\r\n return new VldCoerceNumber({\r\n checks: [...this.config.checks, (v: number) => v <= 0],\r\n errorMessage: message || getMessages().numberNonpositive\r\n });\r\n }\r\n \r\n finite(message?: string): VldCoerceNumber {\r\n return new VldCoerceNumber({\r\n checks: [...this.config.checks, (v: number) => Number.isFinite(v)],\r\n errorMessage: message || getMessages().numberFinite\r\n });\r\n }\r\n \r\n safe(message?: string): VldCoerceNumber {\r\n return new VldCoerceNumber({\r\n checks: [...this.config.checks, (v: number) => Number.isSafeInteger(v)],\r\n errorMessage: message || getMessages().numberSafe\r\n });\r\n }\r\n \r\n multipleOf(value: number, message?: string): VldCoerceNumber {\r\n return new VldCoerceNumber({\r\n checks: [...this.config.checks, (v: number) => {\r\n // BUG-004 FIX: Use epsilon comparison for floating point precision (consistent with VldNumber)\r\n const remainder = Math.abs(v % value);\r\n return remainder < Number.EPSILON || Math.abs(remainder - Math.abs(value)) < Number.EPSILON;\r\n }],\r\n errorMessage: message || getMessages().numberMultipleOf(value)\r\n });\r\n }\r\n \r\n step(value: number, message?: string): VldCoerceNumber {\r\n return this.multipleOf(value, message);\r\n }\r\n \r\n between(min: number, max: number, message?: string): VldCoerceNumber {\r\n return new VldCoerceNumber({\r\n checks: [...this.config.checks, (v: number) => v >= min && v <= max],\r\n errorMessage: message || `Number must be between ${min} and ${max}`\r\n });\r\n }\r\n \r\n even(message?: string): VldCoerceNumber {\r\n // BUG-NEW-007 FIX: Add integer check before even/odd validation\r\n return new VldCoerceNumber({\r\n checks: [...this.config.checks, (v: number) => {\r\n if (!Number.isInteger(v)) {\r\n return false;\r\n }\r\n return v % 2 === 0;\r\n }],\r\n errorMessage: message || 'Number must be even'\r\n });\r\n }\r\n \r\n odd(message?: string): VldCoerceNumber {\r\n // BUG-NEW-007 FIX: Add integer check before even/odd validation\r\n return new VldCoerceNumber({\r\n checks: [...this.config.checks, (v: number) => {\r\n if (!Number.isInteger(v)) {\r\n return false;\r\n }\r\n return v % 2 !== 0;\r\n }],\r\n errorMessage: message || 'Number must be odd'\r\n });\r\n }\r\n \r\n /**\r\n * Parse and coerce a value to number\r\n */\r\n parse(value: unknown): number {\r\n // If it's already a valid number, use parent validation directly\r\n if (typeof value === 'number' && !isNaN(value)) {\r\n return super.parse(value);\r\n }\r\n \r\n let coerced: number;\r\n \r\n if (typeof value === 'string') {\r\n const trimmed = value.trim();\r\n if (trimmed === '') {\r\n throw new Error(getMessages().coercionFailed('number', value));\r\n }\r\n coerced = Number(trimmed);\r\n if (isNaN(coerced)) {\r\n throw new Error(getMessages().coercionFailed('number', value));\r\n }\r\n } else if (typeof value === 'boolean') {\r\n coerced = value ? 1 : 0;\r\n } else if (value === null || value === undefined) {\r\n throw new Error(getMessages().coercionFailed('number', value));\r\n } else {\r\n coerced = Number(value);\r\n if (isNaN(coerced)) {\r\n throw new Error(getMessages().coercionFailed('number', value));\r\n }\r\n }\r\n \r\n // Use parent validation with coerced value\r\n return super.parse(coerced);\r\n }\r\n \r\n /**\r\n * Safely parse and coerce a value to number\r\n */\r\n safeParse(value: unknown): ParseResult<number> {\r\n try {\r\n return { success: true, data: this.parse(value) };\r\n } catch (error) {\r\n return { success: false, error: error as Error };\r\n }\r\n }\r\n}"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"string.d.ts","sourceRoot":"","sources":["../../src/coercion/string.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"string.d.ts","sourceRoot":"","sources":["../../src/coercion/string.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAOjD;;GAEG;AACH,qBAAa,eAAgB,SAAQ,SAAS;IAC5C,SAAS,aAAa,MAAM,CAAC,EAAE,GAAG;IAIlC;;OAEG;IACH,MAAM,CAAC,MAAM,IAAI,eAAe;IAKhC,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,eAAe;IAQtD,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,eAAe;IAQtD,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,eAAe;IAQzD,KAAK,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,eAAe;IAUxC,GAAG,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,eAAe;IAQtC,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,eAAe;IAQvC,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,eAAe;IAQzD,IAAI,IAAI,eAAe;IAQvB,WAAW,IAAI,eAAe;IAQ9B,WAAW,IAAI,eAAe;IAQ9B,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,eAAe;IAQ1D,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,eAAe;IAQxD,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,eAAe;IAQxD,EAAE,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,eAAe;IAarC,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,eAAe;IAQvC,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,eAAe;IAWvC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,eAAe;IAQ3C;;OAEG;IACH,KAAK,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM;IA2E7B;;OAEG;IACH,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,CAAC,MAAM,CAAC;CAO/C"}
|
package/dist/coercion/string.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { VldString } from '../validators/string.js';
|
|
2
|
-
import { getMessages } from '../locales.js';
|
|
2
|
+
import { getMessages } from '../locales/index.js';
|
|
3
|
+
import { isValidIPv6 } from '../utils/ip-validation.js';
|
|
4
|
+
// BUG-NEW-001 FIX: IPv6 validation moved to shared utility (src/utils/ip-validation.ts)
|
|
5
|
+
// to eliminate code duplication between string.ts and coercion/string.ts
|
|
3
6
|
/**
|
|
4
7
|
* String coercion validator that attempts to convert values to strings
|
|
5
8
|
*/
|
|
@@ -36,8 +39,10 @@ export class VldCoerceString extends VldString {
|
|
|
36
39
|
});
|
|
37
40
|
}
|
|
38
41
|
email(message) {
|
|
42
|
+
// BUG-007 FIX: Use simpler, ReDoS-safe regex (consistent with VldString)
|
|
43
|
+
const FAST_EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
39
44
|
return new VldCoerceString({
|
|
40
|
-
checks: [...this.config.checks, (v) =>
|
|
45
|
+
checks: [...this.config.checks, (v) => FAST_EMAIL_REGEX.test(v)],
|
|
41
46
|
transforms: this.config.transforms,
|
|
42
47
|
errorMessage: message || getMessages().stringEmail
|
|
43
48
|
});
|
|
@@ -107,9 +112,13 @@ export class VldCoerceString extends VldString {
|
|
|
107
112
|
}
|
|
108
113
|
ip(message) {
|
|
109
114
|
const ipv4 = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
|
110
|
-
const ipv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/;
|
|
111
115
|
return new VldCoerceString({
|
|
112
|
-
checks: [...this.config.checks, (v) =>
|
|
116
|
+
checks: [...this.config.checks, (v) => {
|
|
117
|
+
// Prevent ReDoS: Check length before validation
|
|
118
|
+
if (v.length > 100)
|
|
119
|
+
return false;
|
|
120
|
+
return ipv4.test(v) || isValidIPv6(v);
|
|
121
|
+
}],
|
|
113
122
|
transforms: this.config.transforms,
|
|
114
123
|
errorMessage: message || getMessages().stringIp
|
|
115
124
|
});
|
|
@@ -122,9 +131,11 @@ export class VldCoerceString extends VldString {
|
|
|
122
131
|
});
|
|
123
132
|
}
|
|
124
133
|
ipv6(message) {
|
|
125
|
-
const ipv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/;
|
|
126
134
|
return new VldCoerceString({
|
|
127
|
-
checks: [...this.config.checks, (v) =>
|
|
135
|
+
checks: [...this.config.checks, (v) => {
|
|
136
|
+
// Use safe IPv6 validation to prevent ReDoS attacks
|
|
137
|
+
return isValidIPv6(v);
|
|
138
|
+
}],
|
|
128
139
|
transforms: this.config.transforms,
|
|
129
140
|
errorMessage: message || getMessages().stringIpv6
|
|
130
141
|
});
|
|
@@ -140,18 +151,79 @@ export class VldCoerceString extends VldString {
|
|
|
140
151
|
* Parse and coerce a value to string
|
|
141
152
|
*/
|
|
142
153
|
parse(value) {
|
|
143
|
-
// If it's already a string,
|
|
154
|
+
// If it's already a string, apply security controls first
|
|
144
155
|
if (typeof value === 'string') {
|
|
145
|
-
|
|
156
|
+
// Enforce length limits to prevent DoS
|
|
157
|
+
if (value.length > 1000000) {
|
|
158
|
+
throw new Error(getMessages().coercionFailed('string', value));
|
|
159
|
+
}
|
|
160
|
+
// Sanitize control characters for security
|
|
161
|
+
// Remove characters that could cause issues in logs, databases, or UI
|
|
162
|
+
// eslint-disable-next-line no-control-regex -- Intentional removal of control characters for security
|
|
163
|
+
const sanitized = value.replace(/[\x00-\x1F\x7F]/g, '');
|
|
164
|
+
// Use parent validation with sanitized string
|
|
165
|
+
return super.parse(sanitized);
|
|
146
166
|
}
|
|
147
167
|
// Handle null and undefined explicitly
|
|
148
168
|
if (value === null || value === undefined) {
|
|
149
169
|
throw new Error(getMessages().coercionFailed('string', value));
|
|
150
170
|
}
|
|
151
|
-
//
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
171
|
+
// Safe type coercion with security measures
|
|
172
|
+
let coerced;
|
|
173
|
+
if (typeof value === 'number') {
|
|
174
|
+
// Numbers: safe conversion
|
|
175
|
+
coerced = String(value);
|
|
176
|
+
}
|
|
177
|
+
else if (typeof value === 'boolean') {
|
|
178
|
+
// Booleans: safe conversion
|
|
179
|
+
coerced = value ? 'true' : 'false';
|
|
180
|
+
}
|
|
181
|
+
else if (typeof value === 'bigint') {
|
|
182
|
+
// BigInt: safe conversion
|
|
183
|
+
coerced = value.toString();
|
|
184
|
+
}
|
|
185
|
+
else if (typeof value === 'symbol') {
|
|
186
|
+
// Symbols: explicit error to prevent potential issues
|
|
187
|
+
throw new Error(getMessages().coercionFailed('string', value));
|
|
188
|
+
}
|
|
189
|
+
else if (typeof value === 'object') {
|
|
190
|
+
// Objects: only allow specific safe object types
|
|
191
|
+
if (value instanceof Date) {
|
|
192
|
+
// Dates: convert to ISO string
|
|
193
|
+
coerced = value.toISOString();
|
|
194
|
+
}
|
|
195
|
+
else if (Array.isArray(value)) {
|
|
196
|
+
// Arrays: convert to comma-separated string (original behavior)
|
|
197
|
+
coerced = value.join(',');
|
|
198
|
+
}
|
|
199
|
+
else if (value instanceof RegExp) {
|
|
200
|
+
// RegExp: convert to string representation
|
|
201
|
+
coerced = value.toString();
|
|
202
|
+
}
|
|
203
|
+
else if (value instanceof Error) {
|
|
204
|
+
// Errors: use message property (safe)
|
|
205
|
+
coerced = value.message || value.toString();
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
// For plain objects, use the original String() behavior for backwards compatibility
|
|
209
|
+
coerced = String(value);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
// Fallback for other types
|
|
214
|
+
coerced = String(value);
|
|
215
|
+
}
|
|
216
|
+
// Apply security controls
|
|
217
|
+
// Enforce length limits to prevent DoS
|
|
218
|
+
if (coerced.length > 1000000) {
|
|
219
|
+
throw new Error(getMessages().coercionFailed('string', value));
|
|
220
|
+
}
|
|
221
|
+
// Sanitize control characters for security
|
|
222
|
+
// Remove characters that could cause issues in logs, databases, or UI
|
|
223
|
+
// eslint-disable-next-line no-control-regex -- Intentional removal of control characters for security
|
|
224
|
+
const sanitized = coerced.replace(/[\x00-\x1F\x7F]/g, '');
|
|
225
|
+
// Use parent validation with sanitized value
|
|
226
|
+
return super.parse(sanitized);
|
|
155
227
|
}
|
|
156
228
|
/**
|
|
157
229
|
* Safely parse and coerce a value to string
|