@oxog/vld 2.0.1 → 2.0.3
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 +94 -0
- package/README.md +2 -2
- package/dist/chunks/{bigint-ZKkWLdmi.js → bigint-CRS0QVsy.js} +242 -16
- package/dist/chunks/bigint-CRS0QVsy.js.map +1 -0
- package/dist/chunks/{date-7Iz2BGsL.js → date-50JV_Mfj.js} +2 -2
- package/dist/chunks/{date-7Iz2BGsL.js.map → date-50JV_Mfj.js.map} +1 -1
- package/dist/chunks/{index-BoV-4S1F.js → index-DO8CFMxD.js} +4 -4
- package/dist/chunks/{index-BoV-4S1F.js.map → index-DO8CFMxD.js.map} +1 -1
- package/dist/chunks/{json-nIhyRPOU.js → json-Cp4WO0M9.js} +226 -139
- package/dist/chunks/json-Cp4WO0M9.js.map +1 -0
- package/dist/chunks/{unknown-CjSRcELI.js → unknown-Dhzri_T-.js} +2 -2
- package/dist/chunks/{unknown-CjSRcELI.js.map → unknown-Dhzri_T-.js.map} +1 -1
- package/dist/cjs/index.cjs +1095 -151
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/mini.cjs +462 -149
- package/dist/cjs/mini.cjs.map +1 -1
- package/dist/cli/commands/validate.d.ts.map +1 -1
- package/dist/codecs/index.js +3 -3
- package/dist/coercion/index.js +2 -2
- package/dist/index.d.ts +244 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +550 -11
- package/dist/index.js.map +1 -1
- package/dist/mini.js +5 -5
- package/dist/utils/json-schema.d.ts +67 -0
- package/dist/utils/json-schema.d.ts.map +1 -0
- package/dist/validators/array.d.ts +5 -0
- package/dist/validators/array.d.ts.map +1 -1
- package/dist/validators/base.d.ts +79 -0
- package/dist/validators/base.d.ts.map +1 -1
- package/dist/validators/discriminated-union.d.ts.map +1 -1
- package/dist/validators/enum.d.ts +5 -1
- package/dist/validators/enum.d.ts.map +1 -1
- package/dist/validators/index.d.ts +3 -2
- package/dist/validators/index.d.ts.map +1 -1
- package/dist/validators/index.js +514 -4
- package/dist/validators/index.js.map +1 -1
- package/dist/validators/lazy.d.ts +8 -1
- package/dist/validators/lazy.d.ts.map +1 -1
- package/dist/validators/literal.d.ts +5 -1
- package/dist/validators/literal.d.ts.map +1 -1
- package/dist/validators/number.d.ts +30 -0
- package/dist/validators/number.d.ts.map +1 -1
- package/dist/validators/object.d.ts +30 -3
- package/dist/validators/object.d.ts.map +1 -1
- package/dist/validators/promise.d.ts +53 -0
- package/dist/validators/promise.d.ts.map +1 -0
- package/dist/validators/string-formats.d.ts +6 -0
- package/dist/validators/string-formats.d.ts.map +1 -1
- package/dist/validators/string.d.ts +12 -1
- package/dist/validators/string.d.ts.map +1 -1
- package/package.json +6 -5
- package/dist/chunks/bigint-ZKkWLdmi.js.map +0 -1
- package/dist/chunks/index-eWXdjyyy.js +0 -426
- package/dist/chunks/index-eWXdjyyy.js.map +0 -1
- package/dist/chunks/json-nIhyRPOU.js.map +0 -1
package/dist/cjs/index.cjs
CHANGED
|
@@ -90,6 +90,16 @@ class VldBase {
|
|
|
90
90
|
nullish() {
|
|
91
91
|
return new VldNullish(this);
|
|
92
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* Make this validator exactly optional - allows undefined but not missing
|
|
95
|
+
* Unlike .optional() which treats missing as undefined, exactOptional()
|
|
96
|
+
* requires the key to be present but allows undefined as a value
|
|
97
|
+
* Zod 4 API parity
|
|
98
|
+
* @returns A new exact optional validator
|
|
99
|
+
*/
|
|
100
|
+
exactOptional() {
|
|
101
|
+
return new VldExactOptional(this);
|
|
102
|
+
}
|
|
93
103
|
/**
|
|
94
104
|
* Pipe the output of this validator into another validator
|
|
95
105
|
* @param next The next validator to pipe into
|
|
@@ -134,6 +144,49 @@ class VldBase {
|
|
|
134
144
|
apply(fn) {
|
|
135
145
|
return fn(this);
|
|
136
146
|
}
|
|
147
|
+
check(predicate, message) {
|
|
148
|
+
return new VldRefine(this, predicate, message);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Get or set metadata for this schema
|
|
152
|
+
*/
|
|
153
|
+
meta(data) {
|
|
154
|
+
if (data === undefined) {
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
return new VldMeta(this, data);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Add a description to this schema
|
|
161
|
+
* Zod 4 API parity - convenience method for .meta({ description: ... })
|
|
162
|
+
* @param description The description to add
|
|
163
|
+
* @returns A new validator with the description
|
|
164
|
+
*/
|
|
165
|
+
describe(description) {
|
|
166
|
+
return new VldMeta(this, { description });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Metadata validator - wraps a schema with metadata
|
|
171
|
+
*/
|
|
172
|
+
class VldMeta extends VldBase {
|
|
173
|
+
constructor(baseValidator, metadata) {
|
|
174
|
+
super();
|
|
175
|
+
this.baseValidator = baseValidator;
|
|
176
|
+
this.metadata = metadata;
|
|
177
|
+
}
|
|
178
|
+
parse(value) {
|
|
179
|
+
return this.baseValidator.parse(value);
|
|
180
|
+
}
|
|
181
|
+
safeParse(value) {
|
|
182
|
+
return this.baseValidator.safeParse(value);
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Get the metadata
|
|
186
|
+
*/
|
|
187
|
+
getMeta() {
|
|
188
|
+
return this.metadata;
|
|
189
|
+
}
|
|
137
190
|
}
|
|
138
191
|
/**
|
|
139
192
|
* Readonly validator - marks output as readonly
|
|
@@ -349,6 +402,38 @@ class VldOptional extends VldBase {
|
|
|
349
402
|
return this.baseValidator;
|
|
350
403
|
}
|
|
351
404
|
}
|
|
405
|
+
/**
|
|
406
|
+
* Exact optional validator - allows undefined but requires key presence
|
|
407
|
+
* Unlike Optional which treats missing as undefined, ExactOptional
|
|
408
|
+
* requires the key to be present (not missing from object) but allows undefined
|
|
409
|
+
* Zod 4 API parity
|
|
410
|
+
*/
|
|
411
|
+
class VldExactOptional extends VldBase {
|
|
412
|
+
constructor(baseValidator) {
|
|
413
|
+
super();
|
|
414
|
+
this.baseValidator = baseValidator;
|
|
415
|
+
}
|
|
416
|
+
static create(baseValidator) {
|
|
417
|
+
return new VldExactOptional(baseValidator);
|
|
418
|
+
}
|
|
419
|
+
parse(value) {
|
|
420
|
+
if (value === undefined) {
|
|
421
|
+
return undefined;
|
|
422
|
+
}
|
|
423
|
+
return this.baseValidator.parse(value);
|
|
424
|
+
}
|
|
425
|
+
safeParse(value) {
|
|
426
|
+
// Unlike regular optional, undefined is explicitly valid
|
|
427
|
+
// but we still validate through the base validator
|
|
428
|
+
if (value === undefined) {
|
|
429
|
+
return { success: true, data: undefined };
|
|
430
|
+
}
|
|
431
|
+
return this.baseValidator.safeParse(value);
|
|
432
|
+
}
|
|
433
|
+
unwrap() {
|
|
434
|
+
return this.baseValidator;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
352
437
|
/**
|
|
353
438
|
* Nullable validator - allows null
|
|
354
439
|
*/
|
|
@@ -3478,6 +3563,7 @@ const REGEX_PATTERNS = {
|
|
|
3478
3563
|
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]?)$/};
|
|
3479
3564
|
/**
|
|
3480
3565
|
* Immutable string validator with chainable methods
|
|
3566
|
+
* Features pre-compiled validation functions for maximum performance
|
|
3481
3567
|
*/
|
|
3482
3568
|
class VldString extends VldBase {
|
|
3483
3569
|
/**
|
|
@@ -3485,12 +3571,100 @@ class VldString extends VldBase {
|
|
|
3485
3571
|
*/
|
|
3486
3572
|
constructor(config) {
|
|
3487
3573
|
super();
|
|
3574
|
+
// Cache for pre-compiled validation function
|
|
3575
|
+
this._compiledValidator = null;
|
|
3488
3576
|
this.config = {
|
|
3489
3577
|
checks: config?.checks || [],
|
|
3490
3578
|
transforms: config?.transforms || [],
|
|
3491
3579
|
errorMessage: config?.errorMessage
|
|
3492
3580
|
};
|
|
3493
3581
|
}
|
|
3582
|
+
/**
|
|
3583
|
+
* Compile all transforms and checks into a single optimized function
|
|
3584
|
+
* This eliminates loop overhead and enables better JIT optimization
|
|
3585
|
+
*/
|
|
3586
|
+
_compileValidator() {
|
|
3587
|
+
const transforms = this.config.transforms;
|
|
3588
|
+
const checks = this.config.checks;
|
|
3589
|
+
const errorMessage = this.config.errorMessage || getMessages().invalidString;
|
|
3590
|
+
// Fast path: no transforms or checks
|
|
3591
|
+
if (transforms.length === 0 && checks.length === 0) {
|
|
3592
|
+
return (value) => ({ success: true, value });
|
|
3593
|
+
}
|
|
3594
|
+
// Fast path: only transforms, no checks
|
|
3595
|
+
if (checks.length === 0) {
|
|
3596
|
+
switch (transforms.length) {
|
|
3597
|
+
case 1:
|
|
3598
|
+
return (value) => ({ success: true, value: transforms[0](value) });
|
|
3599
|
+
case 2:
|
|
3600
|
+
return (value) => ({ success: true, value: transforms[1](transforms[0](value)) });
|
|
3601
|
+
case 3:
|
|
3602
|
+
return (value) => ({ success: true, value: transforms[2](transforms[1](transforms[0](value))) });
|
|
3603
|
+
default:
|
|
3604
|
+
return (value) => {
|
|
3605
|
+
let result = value;
|
|
3606
|
+
for (let i = 0; i < transforms.length; i++) {
|
|
3607
|
+
result = transforms[i](result);
|
|
3608
|
+
}
|
|
3609
|
+
return { success: true, value: result };
|
|
3610
|
+
};
|
|
3611
|
+
}
|
|
3612
|
+
}
|
|
3613
|
+
// Fast path: only checks, no transforms
|
|
3614
|
+
if (transforms.length === 0) {
|
|
3615
|
+
switch (checks.length) {
|
|
3616
|
+
case 1:
|
|
3617
|
+
return (value) => {
|
|
3618
|
+
if (!checks[0](value))
|
|
3619
|
+
return { success: false, error: errorMessage };
|
|
3620
|
+
return { success: true, value };
|
|
3621
|
+
};
|
|
3622
|
+
case 2:
|
|
3623
|
+
return (value) => {
|
|
3624
|
+
if (!checks[0](value) || !checks[1](value))
|
|
3625
|
+
return { success: false, error: errorMessage };
|
|
3626
|
+
return { success: true, value };
|
|
3627
|
+
};
|
|
3628
|
+
case 3:
|
|
3629
|
+
return (value) => {
|
|
3630
|
+
if (!checks[0](value) || !checks[1](value) || !checks[2](value))
|
|
3631
|
+
return { success: false, error: errorMessage };
|
|
3632
|
+
return { success: true, value };
|
|
3633
|
+
};
|
|
3634
|
+
default:
|
|
3635
|
+
return (value) => {
|
|
3636
|
+
for (let i = 0; i < checks.length; i++) {
|
|
3637
|
+
if (!checks[i](value))
|
|
3638
|
+
return { success: false, error: errorMessage };
|
|
3639
|
+
}
|
|
3640
|
+
return { success: true, value };
|
|
3641
|
+
};
|
|
3642
|
+
}
|
|
3643
|
+
}
|
|
3644
|
+
// General case: both transforms and checks
|
|
3645
|
+
return (value) => {
|
|
3646
|
+
let result = value;
|
|
3647
|
+
// Apply transforms
|
|
3648
|
+
for (let i = 0; i < transforms.length; i++) {
|
|
3649
|
+
result = transforms[i](result);
|
|
3650
|
+
}
|
|
3651
|
+
// Apply checks
|
|
3652
|
+
for (let i = 0; i < checks.length; i++) {
|
|
3653
|
+
if (!checks[i](result))
|
|
3654
|
+
return { success: false, error: errorMessage };
|
|
3655
|
+
}
|
|
3656
|
+
return { success: true, value: result };
|
|
3657
|
+
};
|
|
3658
|
+
}
|
|
3659
|
+
/**
|
|
3660
|
+
* Get the cached compiled validator, creating it if necessary
|
|
3661
|
+
*/
|
|
3662
|
+
_getCompiledValidator() {
|
|
3663
|
+
if (!this._compiledValidator) {
|
|
3664
|
+
this._compiledValidator = this._compileValidator();
|
|
3665
|
+
}
|
|
3666
|
+
return this._compiledValidator;
|
|
3667
|
+
}
|
|
3494
3668
|
/**
|
|
3495
3669
|
* Create a new string validator
|
|
3496
3670
|
*/
|
|
@@ -3498,26 +3672,18 @@ class VldString extends VldBase {
|
|
|
3498
3672
|
return new VldString();
|
|
3499
3673
|
}
|
|
3500
3674
|
/**
|
|
3501
|
-
* Parse and validate a string value - ultra-optimized
|
|
3675
|
+
* Parse and validate a string value - ultra-optimized with pre-compiled validator
|
|
3502
3676
|
*/
|
|
3503
3677
|
parse(value) {
|
|
3504
3678
|
if (typeof value !== 'string') {
|
|
3505
3679
|
throw new Error(this.config.errorMessage || getMessages().invalidString);
|
|
3506
3680
|
}
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
result = this.config.transforms[i](result);
|
|
3512
|
-
}
|
|
3513
|
-
// Apply checks with optimized loop and early termination
|
|
3514
|
-
const checksLength = this.config.checks.length;
|
|
3515
|
-
for (let i = 0; i < checksLength; i++) {
|
|
3516
|
-
if (!this.config.checks[i](result)) {
|
|
3517
|
-
throw new Error(this.config.errorMessage || getMessages().invalidString);
|
|
3518
|
-
}
|
|
3681
|
+
// Use pre-compiled validator for maximum performance
|
|
3682
|
+
const result = this._getCompiledValidator()(value);
|
|
3683
|
+
if (!result.success) {
|
|
3684
|
+
throw new Error(result.error);
|
|
3519
3685
|
}
|
|
3520
|
-
return result;
|
|
3686
|
+
return result.value;
|
|
3521
3687
|
}
|
|
3522
3688
|
/**
|
|
3523
3689
|
* Safely parse and validate a string value
|
|
@@ -3931,6 +4097,66 @@ class VldNumber extends VldBase {
|
|
|
3931
4097
|
lte(value, message) {
|
|
3932
4098
|
return this.max(value, message);
|
|
3933
4099
|
}
|
|
4100
|
+
/**
|
|
4101
|
+
* Create a validator for unsigned 32-bit integers
|
|
4102
|
+
* Range: 0 to 4,294,967,295
|
|
4103
|
+
*/
|
|
4104
|
+
uint32(message) {
|
|
4105
|
+
return new VldNumber({
|
|
4106
|
+
checks: [...this.config.checks, (v) => Number.isSafeInteger(v) && v >= 0 && v <= 4294967295],
|
|
4107
|
+
errorMessage: message || 'Expected an unsigned 32-bit integer'
|
|
4108
|
+
});
|
|
4109
|
+
}
|
|
4110
|
+
/**
|
|
4111
|
+
* Create a validator for unsigned 64-bit integers
|
|
4112
|
+
* Range: 0 to 2^53-1 (safe integer limit)
|
|
4113
|
+
*/
|
|
4114
|
+
uint64(message) {
|
|
4115
|
+
return new VldNumber({
|
|
4116
|
+
checks: [...this.config.checks, (v) => Number.isSafeInteger(v) && v >= 0],
|
|
4117
|
+
errorMessage: message || 'Expected an unsigned 64-bit integer'
|
|
4118
|
+
});
|
|
4119
|
+
}
|
|
4120
|
+
/**
|
|
4121
|
+
* Create a validator for signed 32-bit integers
|
|
4122
|
+
* Range: -2,147,483,648 to 2,147,483,647
|
|
4123
|
+
*/
|
|
4124
|
+
int32(message) {
|
|
4125
|
+
return new VldNumber({
|
|
4126
|
+
checks: [...this.config.checks, (v) => Number.isSafeInteger(v) && v >= -2147483648 && v <= 2147483647],
|
|
4127
|
+
errorMessage: message || 'Expected a signed 32-bit integer'
|
|
4128
|
+
});
|
|
4129
|
+
}
|
|
4130
|
+
/**
|
|
4131
|
+
* Create a validator for signed 64-bit integers
|
|
4132
|
+
* Range: -(2^53-1) to 2^53-1 (safe integer limit)
|
|
4133
|
+
*/
|
|
4134
|
+
int64(message) {
|
|
4135
|
+
return new VldNumber({
|
|
4136
|
+
checks: [...this.config.checks, (v) => Number.isSafeInteger(v)],
|
|
4137
|
+
errorMessage: message || 'Expected a signed 64-bit integer'
|
|
4138
|
+
});
|
|
4139
|
+
}
|
|
4140
|
+
/**
|
|
4141
|
+
* Create a validator for 32-bit floats (IEEE 754 single precision)
|
|
4142
|
+
* Range: -3.4e38 to 3.4e38, precision ~7 decimal digits
|
|
4143
|
+
*/
|
|
4144
|
+
float32(message) {
|
|
4145
|
+
return new VldNumber({
|
|
4146
|
+
checks: [...this.config.checks, (v) => Number.isFinite(v) && Math.abs(v) <= 3.4e38],
|
|
4147
|
+
errorMessage: message || 'Expected a 32-bit float'
|
|
4148
|
+
});
|
|
4149
|
+
}
|
|
4150
|
+
/**
|
|
4151
|
+
* Create a validator for 64-bit floats (IEEE 754 double precision)
|
|
4152
|
+
* Alias for standard number validation
|
|
4153
|
+
*/
|
|
4154
|
+
float64(message) {
|
|
4155
|
+
return new VldNumber({
|
|
4156
|
+
checks: [...this.config.checks, (v) => Number.isFinite(v)],
|
|
4157
|
+
errorMessage: message || 'Expected a 64-bit float'
|
|
4158
|
+
});
|
|
4159
|
+
}
|
|
3934
4160
|
}
|
|
3935
4161
|
|
|
3936
4162
|
/**
|
|
@@ -4439,16 +4665,9 @@ class VldArray extends VldBase {
|
|
|
4439
4665
|
}
|
|
4440
4666
|
result[i] = parseResult.data; // Direct assignment is faster than push
|
|
4441
4667
|
}
|
|
4442
|
-
// Check uniqueness if required
|
|
4668
|
+
// Check uniqueness if required - optimized with Map-based approach
|
|
4443
4669
|
if (this.config.unique) {
|
|
4444
|
-
|
|
4445
|
-
for (const item of result) {
|
|
4446
|
-
const key = typeof item === 'object' ? this.stableStringify(item) : item;
|
|
4447
|
-
if (seen.has(key)) {
|
|
4448
|
-
throw new Error('Array must contain unique items');
|
|
4449
|
-
}
|
|
4450
|
-
seen.add(key);
|
|
4451
|
-
}
|
|
4670
|
+
this.checkUnique(result);
|
|
4452
4671
|
}
|
|
4453
4672
|
return result;
|
|
4454
4673
|
}
|
|
@@ -4463,6 +4682,38 @@ class VldArray extends VldBase {
|
|
|
4463
4682
|
return { success: false, error: error };
|
|
4464
4683
|
}
|
|
4465
4684
|
}
|
|
4685
|
+
/**
|
|
4686
|
+
* Optimized uniqueness check using Map-based approach
|
|
4687
|
+
* Avoids repeated stableStringify calls by caching serialized values
|
|
4688
|
+
*/
|
|
4689
|
+
checkUnique(items) {
|
|
4690
|
+
const seen = new Set(); // Set of seen keys
|
|
4691
|
+
const objectKeys = new WeakMap(); // Cache for object->string mappings
|
|
4692
|
+
for (const item of items) {
|
|
4693
|
+
let key;
|
|
4694
|
+
if (typeof item === 'object' && item !== null) {
|
|
4695
|
+
// Check if we've already serialized this object reference
|
|
4696
|
+
const cached = objectKeys.get(item);
|
|
4697
|
+
if (cached !== undefined) {
|
|
4698
|
+
key = cached;
|
|
4699
|
+
}
|
|
4700
|
+
else {
|
|
4701
|
+
// Serialize and cache
|
|
4702
|
+
const serialized = this.stableStringify(item);
|
|
4703
|
+
key = serialized;
|
|
4704
|
+
objectKeys.set(item, serialized);
|
|
4705
|
+
}
|
|
4706
|
+
}
|
|
4707
|
+
else {
|
|
4708
|
+
// Primitives can be used directly as keys
|
|
4709
|
+
key = item;
|
|
4710
|
+
}
|
|
4711
|
+
if (seen.has(key)) {
|
|
4712
|
+
throw new Error('Array must contain unique items');
|
|
4713
|
+
}
|
|
4714
|
+
seen.add(key);
|
|
4715
|
+
}
|
|
4716
|
+
}
|
|
4466
4717
|
/**
|
|
4467
4718
|
* Create a stable string representation of an object for hashing
|
|
4468
4719
|
* Handles circular references and deep nesting gracefully
|
|
@@ -4590,11 +4841,17 @@ class VldEnum extends VldBase {
|
|
|
4590
4841
|
/**
|
|
4591
4842
|
* Private constructor to enforce immutability
|
|
4592
4843
|
*/
|
|
4593
|
-
constructor(
|
|
4844
|
+
constructor(_values, errorMessage) {
|
|
4594
4845
|
super();
|
|
4595
|
-
this.
|
|
4846
|
+
this._values = _values;
|
|
4596
4847
|
this.errorMessage = errorMessage;
|
|
4597
4848
|
}
|
|
4849
|
+
/**
|
|
4850
|
+
* Get the enum values
|
|
4851
|
+
*/
|
|
4852
|
+
get values() {
|
|
4853
|
+
return this._values;
|
|
4854
|
+
}
|
|
4598
4855
|
/**
|
|
4599
4856
|
* Create a new enum validator
|
|
4600
4857
|
*/
|
|
@@ -4608,11 +4865,11 @@ class VldEnum extends VldBase {
|
|
|
4608
4865
|
// BUG-002 FIX: Add type check before includes() to prevent type confusion
|
|
4609
4866
|
if (typeof value !== 'string') {
|
|
4610
4867
|
throw new Error(this.errorMessage ||
|
|
4611
|
-
getMessages().enumExpected([...this.
|
|
4868
|
+
getMessages().enumExpected([...this._values], JSON.stringify(value)));
|
|
4612
4869
|
}
|
|
4613
|
-
if (!this.
|
|
4870
|
+
if (!this._values.includes(value)) {
|
|
4614
4871
|
throw new Error(this.errorMessage ||
|
|
4615
|
-
getMessages().enumExpected([...this.
|
|
4872
|
+
getMessages().enumExpected([...this._values], JSON.stringify(value)));
|
|
4616
4873
|
}
|
|
4617
4874
|
return value;
|
|
4618
4875
|
}
|
|
@@ -4625,16 +4882,16 @@ class VldEnum extends VldBase {
|
|
|
4625
4882
|
return {
|
|
4626
4883
|
success: false,
|
|
4627
4884
|
error: new Error(this.errorMessage ||
|
|
4628
|
-
getMessages().enumExpected([...this.
|
|
4885
|
+
getMessages().enumExpected([...this._values], JSON.stringify(value)))
|
|
4629
4886
|
};
|
|
4630
4887
|
}
|
|
4631
|
-
if (this.
|
|
4888
|
+
if (this._values.includes(value)) {
|
|
4632
4889
|
return { success: true, data: value };
|
|
4633
4890
|
}
|
|
4634
4891
|
return {
|
|
4635
4892
|
success: false,
|
|
4636
4893
|
error: new Error(this.errorMessage ||
|
|
4637
|
-
getMessages().enumExpected([...this.
|
|
4894
|
+
getMessages().enumExpected([...this._values], JSON.stringify(value)))
|
|
4638
4895
|
};
|
|
4639
4896
|
}
|
|
4640
4897
|
/**
|
|
@@ -4642,7 +4899,7 @@ class VldEnum extends VldBase {
|
|
|
4642
4899
|
* Creates a new enum validator without the specified values
|
|
4643
4900
|
*/
|
|
4644
4901
|
exclude(...excludeValues) {
|
|
4645
|
-
const filtered = this.
|
|
4902
|
+
const filtered = this._values.filter(v => !excludeValues.includes(v));
|
|
4646
4903
|
if (filtered.length === 0) {
|
|
4647
4904
|
throw new Error('Cannot exclude all enum values');
|
|
4648
4905
|
}
|
|
@@ -4654,7 +4911,7 @@ class VldEnum extends VldBase {
|
|
|
4654
4911
|
* Creates a new enum validator with only the specified values
|
|
4655
4912
|
*/
|
|
4656
4913
|
extract(...extractValues) {
|
|
4657
|
-
const extracted = this.
|
|
4914
|
+
const extracted = this._values.filter(v => extractValues.includes(v));
|
|
4658
4915
|
if (extracted.length === 0) {
|
|
4659
4916
|
throw new Error('Cannot extract non-existent enum values');
|
|
4660
4917
|
}
|
|
@@ -5175,10 +5432,30 @@ class VldObject extends VldBase {
|
|
|
5175
5432
|
*/
|
|
5176
5433
|
constructor(config) {
|
|
5177
5434
|
super();
|
|
5178
|
-
this.
|
|
5435
|
+
this._config = config;
|
|
5179
5436
|
// Pre-compute shape keys for faster access
|
|
5180
|
-
this.
|
|
5181
|
-
this.
|
|
5437
|
+
this._shapeKeys = Object.keys(config.shape);
|
|
5438
|
+
this._shapeKeysSet = new Set(this._shapeKeys);
|
|
5439
|
+
}
|
|
5440
|
+
/**
|
|
5441
|
+
* Get the validator configuration
|
|
5442
|
+
* @internal Used by discriminated union validator
|
|
5443
|
+
*/
|
|
5444
|
+
get config() {
|
|
5445
|
+
return this._config;
|
|
5446
|
+
}
|
|
5447
|
+
/**
|
|
5448
|
+
* Get the shape keys array
|
|
5449
|
+
*/
|
|
5450
|
+
get shapeKeys() {
|
|
5451
|
+
return this._shapeKeys;
|
|
5452
|
+
}
|
|
5453
|
+
/**
|
|
5454
|
+
* Get the shape keys set for O(1) lookups
|
|
5455
|
+
* @internal Used by discriminated union validator
|
|
5456
|
+
*/
|
|
5457
|
+
get shapeKeysSet() {
|
|
5458
|
+
return this._shapeKeysSet;
|
|
5182
5459
|
}
|
|
5183
5460
|
/**
|
|
5184
5461
|
* Create a new object validator
|
|
@@ -5193,14 +5470,14 @@ class VldObject extends VldBase {
|
|
|
5193
5470
|
parse(value) {
|
|
5194
5471
|
// Fast type check
|
|
5195
5472
|
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
5196
|
-
throw new Error(this.
|
|
5473
|
+
throw new Error(this._config.errorMessage || getMessages().invalidObject);
|
|
5197
5474
|
}
|
|
5198
5475
|
const obj = value;
|
|
5199
5476
|
const result = {};
|
|
5200
5477
|
// Ultra-optimized field validation with inline fast paths
|
|
5201
|
-
for (let i = 0; i < this.
|
|
5202
|
-
const key = this.
|
|
5203
|
-
const validator = this.
|
|
5478
|
+
for (let i = 0; i < this._shapeKeys.length; i++) {
|
|
5479
|
+
const key = this._shapeKeys[i];
|
|
5480
|
+
const validator = this._config.shape[key];
|
|
5204
5481
|
const fieldValue = obj[key];
|
|
5205
5482
|
// BUG-NEW-002 FIX: Use instanceof instead of constructor.name
|
|
5206
5483
|
// constructor.name breaks in minified builds where class names become 'a', 'b', etc.
|
|
@@ -5257,38 +5534,39 @@ class VldObject extends VldBase {
|
|
|
5257
5534
|
result[key] = parseResult.data;
|
|
5258
5535
|
}
|
|
5259
5536
|
}
|
|
5260
|
-
// Handle strict
|
|
5261
|
-
if (this.
|
|
5537
|
+
// Handle strict/passthrough/catchall modes - optimized single Object.keys() call
|
|
5538
|
+
if (this._config.strict || this._config.passthrough || this._config.catchall) {
|
|
5262
5539
|
const objKeys = Object.keys(obj);
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5540
|
+
// Handle strict mode - optimized with Set
|
|
5541
|
+
if (this._config.strict) {
|
|
5542
|
+
const extraKeys = [];
|
|
5543
|
+
for (let i = 0; i < objKeys.length; i++) {
|
|
5544
|
+
if (!this._shapeKeysSet.has(objKeys[i])) {
|
|
5545
|
+
extraKeys.push(objKeys[i]);
|
|
5546
|
+
}
|
|
5547
|
+
}
|
|
5548
|
+
if (extraKeys.length > 0) {
|
|
5549
|
+
throw new Error(getMessages().unexpectedKeys(extraKeys));
|
|
5267
5550
|
}
|
|
5268
5551
|
}
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
const key = objKeys[i];
|
|
5278
|
-
// Skip dangerous keys to prevent prototype pollution
|
|
5279
|
-
if (!this.shapeKeysSet.has(key) && !this.isDangerousKey(key)) {
|
|
5280
|
-
result[key] = obj[key];
|
|
5552
|
+
// Handle passthrough mode - optimized with comprehensive prototype pollution protection
|
|
5553
|
+
if (this._config.passthrough) {
|
|
5554
|
+
for (let i = 0; i < objKeys.length; i++) {
|
|
5555
|
+
const key = objKeys[i];
|
|
5556
|
+
// Skip dangerous keys to prevent prototype pollution
|
|
5557
|
+
if (!this._shapeKeysSet.has(key) && !this.isDangerousKey(key)) {
|
|
5558
|
+
result[key] = obj[key];
|
|
5559
|
+
}
|
|
5281
5560
|
}
|
|
5282
5561
|
}
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
result[key] = this.config.catchall.parse(obj[key]);
|
|
5562
|
+
// Handle catchall - validate extra keys with catchall validator
|
|
5563
|
+
if (this._config.catchall) {
|
|
5564
|
+
for (let i = 0; i < objKeys.length; i++) {
|
|
5565
|
+
const key = objKeys[i];
|
|
5566
|
+
// Skip keys already in shape and dangerous keys
|
|
5567
|
+
if (!this._shapeKeysSet.has(key) && !this.isDangerousKey(key)) {
|
|
5568
|
+
result[key] = this._config.catchall.parse(obj[key]);
|
|
5569
|
+
}
|
|
5292
5570
|
}
|
|
5293
5571
|
}
|
|
5294
5572
|
}
|
|
@@ -5303,15 +5581,15 @@ class VldObject extends VldBase {
|
|
|
5303
5581
|
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
5304
5582
|
return {
|
|
5305
5583
|
success: false,
|
|
5306
|
-
error: new Error(this.
|
|
5584
|
+
error: new Error(this._config.errorMessage || getMessages().invalidObject)
|
|
5307
5585
|
};
|
|
5308
5586
|
}
|
|
5309
5587
|
const obj = value;
|
|
5310
5588
|
const result = {};
|
|
5311
5589
|
// Validate all fields
|
|
5312
|
-
for (let i = 0; i < this.
|
|
5313
|
-
const key = this.
|
|
5314
|
-
const validator = this.
|
|
5590
|
+
for (let i = 0; i < this._shapeKeys.length; i++) {
|
|
5591
|
+
const key = this._shapeKeys[i];
|
|
5592
|
+
const validator = this._config.shape[key];
|
|
5315
5593
|
const fieldValue = obj[key];
|
|
5316
5594
|
const parseResult = validator.safeParse(fieldValue);
|
|
5317
5595
|
if (parseResult.success) {
|
|
@@ -5324,48 +5602,49 @@ class VldObject extends VldBase {
|
|
|
5324
5602
|
};
|
|
5325
5603
|
}
|
|
5326
5604
|
}
|
|
5327
|
-
// Handle strict
|
|
5328
|
-
if (this.
|
|
5605
|
+
// Handle strict/passthrough/catchall modes - optimized single Object.keys() call
|
|
5606
|
+
if (this._config.strict || this._config.passthrough || this._config.catchall) {
|
|
5329
5607
|
const objKeys = Object.keys(obj);
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5608
|
+
// Handle strict mode
|
|
5609
|
+
if (this._config.strict) {
|
|
5610
|
+
const extraKeys = [];
|
|
5611
|
+
for (let i = 0; i < objKeys.length; i++) {
|
|
5612
|
+
if (!this._shapeKeysSet.has(objKeys[i])) {
|
|
5613
|
+
extraKeys.push(objKeys[i]);
|
|
5614
|
+
}
|
|
5615
|
+
}
|
|
5616
|
+
if (extraKeys.length > 0) {
|
|
5617
|
+
return {
|
|
5618
|
+
success: false,
|
|
5619
|
+
error: new Error(getMessages().unexpectedKeys(extraKeys))
|
|
5620
|
+
};
|
|
5334
5621
|
}
|
|
5335
5622
|
}
|
|
5336
|
-
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
if (this.config.passthrough) {
|
|
5345
|
-
const objKeys = Object.keys(obj);
|
|
5346
|
-
for (let i = 0; i < objKeys.length; i++) {
|
|
5347
|
-
const key = objKeys[i];
|
|
5348
|
-
// Skip dangerous keys to prevent prototype pollution
|
|
5349
|
-
if (!this.shapeKeysSet.has(key) && !this.isDangerousKey(key)) {
|
|
5350
|
-
result[key] = obj[key];
|
|
5623
|
+
// Handle passthrough mode with comprehensive prototype pollution protection
|
|
5624
|
+
if (this._config.passthrough) {
|
|
5625
|
+
for (let i = 0; i < objKeys.length; i++) {
|
|
5626
|
+
const key = objKeys[i];
|
|
5627
|
+
// Skip dangerous keys to prevent prototype pollution
|
|
5628
|
+
if (!this._shapeKeysSet.has(key) && !this.isDangerousKey(key)) {
|
|
5629
|
+
result[key] = obj[key];
|
|
5630
|
+
}
|
|
5351
5631
|
}
|
|
5352
5632
|
}
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5633
|
+
// Handle catchall - validate extra keys with catchall validator
|
|
5634
|
+
if (this._config.catchall) {
|
|
5635
|
+
for (let i = 0; i < objKeys.length; i++) {
|
|
5636
|
+
const key = objKeys[i];
|
|
5637
|
+
// Skip keys already in shape and dangerous keys
|
|
5638
|
+
if (!this._shapeKeysSet.has(key) && !this.isDangerousKey(key)) {
|
|
5639
|
+
const catchallResult = this._config.catchall.safeParse(obj[key]);
|
|
5640
|
+
if (!catchallResult.success) {
|
|
5641
|
+
return {
|
|
5642
|
+
success: false,
|
|
5643
|
+
error: new Error(getMessages().objectField(key, catchallResult.error.message))
|
|
5644
|
+
};
|
|
5645
|
+
}
|
|
5646
|
+
result[key] = catchallResult.data;
|
|
5367
5647
|
}
|
|
5368
|
-
result[key] = catchallResult.data;
|
|
5369
5648
|
}
|
|
5370
5649
|
}
|
|
5371
5650
|
}
|
|
@@ -5430,7 +5709,7 @@ class VldObject extends VldBase {
|
|
|
5430
5709
|
*/
|
|
5431
5710
|
strict(message) {
|
|
5432
5711
|
return new VldObject({
|
|
5433
|
-
...this.
|
|
5712
|
+
...this._config,
|
|
5434
5713
|
strict: true,
|
|
5435
5714
|
passthrough: false,
|
|
5436
5715
|
errorMessage: message
|
|
@@ -5441,7 +5720,7 @@ class VldObject extends VldBase {
|
|
|
5441
5720
|
*/
|
|
5442
5721
|
passthrough() {
|
|
5443
5722
|
return new VldObject({
|
|
5444
|
-
...this.
|
|
5723
|
+
...this._config,
|
|
5445
5724
|
strict: false,
|
|
5446
5725
|
passthrough: true
|
|
5447
5726
|
});
|
|
@@ -5451,11 +5730,11 @@ class VldObject extends VldBase {
|
|
|
5451
5730
|
*/
|
|
5452
5731
|
partial() {
|
|
5453
5732
|
const partialShape = {};
|
|
5454
|
-
for (const key in this.
|
|
5455
|
-
partialShape[key] = new VldOptional(this.
|
|
5733
|
+
for (const key in this._config.shape) {
|
|
5734
|
+
partialShape[key] = new VldOptional(this._config.shape[key]);
|
|
5456
5735
|
}
|
|
5457
5736
|
return new VldObject({
|
|
5458
|
-
...this.
|
|
5737
|
+
...this._config,
|
|
5459
5738
|
shape: partialShape
|
|
5460
5739
|
});
|
|
5461
5740
|
}
|
|
@@ -5464,8 +5743,8 @@ class VldObject extends VldBase {
|
|
|
5464
5743
|
*/
|
|
5465
5744
|
deepPartial() {
|
|
5466
5745
|
const deepPartialShape = {};
|
|
5467
|
-
for (const key in this.
|
|
5468
|
-
const validator = this.
|
|
5746
|
+
for (const key in this._config.shape) {
|
|
5747
|
+
const validator = this._config.shape[key];
|
|
5469
5748
|
if (validator instanceof VldObject) {
|
|
5470
5749
|
deepPartialShape[key] = new VldOptional(validator.deepPartial());
|
|
5471
5750
|
}
|
|
@@ -5474,7 +5753,7 @@ class VldObject extends VldBase {
|
|
|
5474
5753
|
}
|
|
5475
5754
|
}
|
|
5476
5755
|
return new VldObject({
|
|
5477
|
-
...this.
|
|
5756
|
+
...this._config,
|
|
5478
5757
|
shape: deepPartialShape
|
|
5479
5758
|
});
|
|
5480
5759
|
}
|
|
@@ -5484,12 +5763,12 @@ class VldObject extends VldBase {
|
|
|
5484
5763
|
pick(...keys) {
|
|
5485
5764
|
const pickedShape = {};
|
|
5486
5765
|
for (const key of keys) {
|
|
5487
|
-
if (key in this.
|
|
5488
|
-
pickedShape[key] = this.
|
|
5766
|
+
if (key in this._config.shape) {
|
|
5767
|
+
pickedShape[key] = this._config.shape[key];
|
|
5489
5768
|
}
|
|
5490
5769
|
}
|
|
5491
5770
|
return new VldObject({
|
|
5492
|
-
...this.
|
|
5771
|
+
...this._config,
|
|
5493
5772
|
shape: pickedShape
|
|
5494
5773
|
});
|
|
5495
5774
|
}
|
|
@@ -5499,13 +5778,13 @@ class VldObject extends VldBase {
|
|
|
5499
5778
|
omit(...keys) {
|
|
5500
5779
|
const omittedShape = {};
|
|
5501
5780
|
const keysToOmit = new Set(keys);
|
|
5502
|
-
for (const key in this.
|
|
5781
|
+
for (const key in this._config.shape) {
|
|
5503
5782
|
if (!keysToOmit.has(key)) {
|
|
5504
|
-
omittedShape[key] = this.
|
|
5783
|
+
omittedShape[key] = this._config.shape[key];
|
|
5505
5784
|
}
|
|
5506
5785
|
}
|
|
5507
5786
|
return new VldObject({
|
|
5508
|
-
...this.
|
|
5787
|
+
...this._config,
|
|
5509
5788
|
shape: omittedShape
|
|
5510
5789
|
});
|
|
5511
5790
|
}
|
|
@@ -5514,8 +5793,8 @@ class VldObject extends VldBase {
|
|
|
5514
5793
|
*/
|
|
5515
5794
|
extend(extension) {
|
|
5516
5795
|
return new VldObject({
|
|
5517
|
-
...this.
|
|
5518
|
-
shape: { ...this.
|
|
5796
|
+
...this._config,
|
|
5797
|
+
shape: { ...this._config.shape, ...extension }
|
|
5519
5798
|
});
|
|
5520
5799
|
}
|
|
5521
5800
|
/**
|
|
@@ -5523,8 +5802,8 @@ class VldObject extends VldBase {
|
|
|
5523
5802
|
*/
|
|
5524
5803
|
merge(other) {
|
|
5525
5804
|
return new VldObject({
|
|
5526
|
-
...this.
|
|
5527
|
-
shape: { ...this.
|
|
5805
|
+
...this._config,
|
|
5806
|
+
shape: { ...this._config.shape, ...other.config.shape }
|
|
5528
5807
|
});
|
|
5529
5808
|
}
|
|
5530
5809
|
/**
|
|
@@ -5532,8 +5811,8 @@ class VldObject extends VldBase {
|
|
|
5532
5811
|
*/
|
|
5533
5812
|
required() {
|
|
5534
5813
|
const requiredShape = {};
|
|
5535
|
-
for (const key in this.
|
|
5536
|
-
const validator = this.
|
|
5814
|
+
for (const key in this._config.shape) {
|
|
5815
|
+
const validator = this._config.shape[key];
|
|
5537
5816
|
// If it's optional, unwrap it
|
|
5538
5817
|
if (validator instanceof VldOptional) {
|
|
5539
5818
|
// BUG-001 FIX: Add defensive check for baseValidator property
|
|
@@ -5548,7 +5827,7 @@ class VldObject extends VldBase {
|
|
|
5548
5827
|
}
|
|
5549
5828
|
}
|
|
5550
5829
|
return new VldObject({
|
|
5551
|
-
...this.
|
|
5830
|
+
...this._config,
|
|
5552
5831
|
shape: requiredShape
|
|
5553
5832
|
});
|
|
5554
5833
|
}
|
|
@@ -5558,7 +5837,7 @@ class VldObject extends VldBase {
|
|
|
5558
5837
|
*/
|
|
5559
5838
|
catchall(schema) {
|
|
5560
5839
|
return new VldObject({
|
|
5561
|
-
...this.
|
|
5840
|
+
...this._config,
|
|
5562
5841
|
catchall: schema,
|
|
5563
5842
|
passthrough: false // catchall overrides passthrough
|
|
5564
5843
|
});
|
|
@@ -5568,14 +5847,14 @@ class VldObject extends VldBase {
|
|
|
5568
5847
|
* Zod 4 API parity - returns the shape object
|
|
5569
5848
|
*/
|
|
5570
5849
|
get shape() {
|
|
5571
|
-
return this.
|
|
5850
|
+
return this._config.shape;
|
|
5572
5851
|
}
|
|
5573
5852
|
/**
|
|
5574
5853
|
* Create an enum validator from object keys
|
|
5575
5854
|
* Zod 4 API parity - creates literal union of keys
|
|
5576
5855
|
*/
|
|
5577
5856
|
keyof() {
|
|
5578
|
-
const keys = Object.keys(this.
|
|
5857
|
+
const keys = Object.keys(this._config.shape);
|
|
5579
5858
|
if (keys.length === 0) {
|
|
5580
5859
|
throw new Error('Cannot create keyof enum from empty object');
|
|
5581
5860
|
}
|
|
@@ -5594,7 +5873,7 @@ class VldObject extends VldBase {
|
|
|
5594
5873
|
*/
|
|
5595
5874
|
safeExtend(extension) {
|
|
5596
5875
|
// Check for overlapping keys
|
|
5597
|
-
const existingKeys = new Set(Object.keys(this.
|
|
5876
|
+
const existingKeys = new Set(Object.keys(this._config.shape));
|
|
5598
5877
|
const extensionKeys = Object.keys(extension);
|
|
5599
5878
|
const overlappingKeys = [];
|
|
5600
5879
|
for (const key of extensionKeys) {
|
|
@@ -5606,8 +5885,8 @@ class VldObject extends VldBase {
|
|
|
5606
5885
|
throw new Error(`safeExtend: ${getMessages().safeExtendOverlap(overlappingKeys)}`);
|
|
5607
5886
|
}
|
|
5608
5887
|
return new VldObject({
|
|
5609
|
-
...this.
|
|
5610
|
-
shape: { ...this.
|
|
5888
|
+
...this._config,
|
|
5889
|
+
shape: { ...this._config.shape, ...extension }
|
|
5611
5890
|
});
|
|
5612
5891
|
}
|
|
5613
5892
|
}
|
|
@@ -5743,11 +6022,17 @@ class VldLiteral extends VldBase {
|
|
|
5743
6022
|
/**
|
|
5744
6023
|
* Private constructor to enforce immutability
|
|
5745
6024
|
*/
|
|
5746
|
-
constructor(
|
|
6025
|
+
constructor(_literal, errorMessage) {
|
|
5747
6026
|
super();
|
|
5748
|
-
this.
|
|
6027
|
+
this._literal = _literal;
|
|
5749
6028
|
this.errorMessage = errorMessage;
|
|
5750
6029
|
}
|
|
6030
|
+
/**
|
|
6031
|
+
* Get the literal value
|
|
6032
|
+
*/
|
|
6033
|
+
get literal() {
|
|
6034
|
+
return this._literal;
|
|
6035
|
+
}
|
|
5751
6036
|
/**
|
|
5752
6037
|
* Create a new literal validator
|
|
5753
6038
|
*/
|
|
@@ -5758,23 +6043,23 @@ class VldLiteral extends VldBase {
|
|
|
5758
6043
|
* Parse and validate a literal value
|
|
5759
6044
|
*/
|
|
5760
6045
|
parse(value) {
|
|
5761
|
-
if (value !== this.
|
|
6046
|
+
if (value !== this._literal) {
|
|
5762
6047
|
throw new Error(this.errorMessage ||
|
|
5763
|
-
getMessages().literalExpected(JSON.stringify(this.
|
|
6048
|
+
getMessages().literalExpected(JSON.stringify(this._literal), JSON.stringify(value)));
|
|
5764
6049
|
}
|
|
5765
|
-
return this.
|
|
6050
|
+
return this._literal;
|
|
5766
6051
|
}
|
|
5767
6052
|
/**
|
|
5768
6053
|
* Safely parse and validate a literal value
|
|
5769
6054
|
*/
|
|
5770
6055
|
safeParse(value) {
|
|
5771
|
-
if (value === this.
|
|
5772
|
-
return { success: true, data: this.
|
|
6056
|
+
if (value === this._literal) {
|
|
6057
|
+
return { success: true, data: this._literal };
|
|
5773
6058
|
}
|
|
5774
6059
|
return {
|
|
5775
6060
|
success: false,
|
|
5776
6061
|
error: new Error(this.errorMessage ||
|
|
5777
|
-
getMessages().literalExpected(JSON.stringify(this.
|
|
6062
|
+
getMessages().literalExpected(JSON.stringify(this._literal), JSON.stringify(value)))
|
|
5778
6063
|
};
|
|
5779
6064
|
}
|
|
5780
6065
|
}
|
|
@@ -6613,27 +6898,56 @@ class VldNan extends VldBase {
|
|
|
6613
6898
|
/**
|
|
6614
6899
|
* Lazy validator - defers schema evaluation until runtime
|
|
6615
6900
|
* Essential for recursive and self-referencing types
|
|
6901
|
+
*
|
|
6902
|
+
* MEMORY OPTIMIZATION: Uses WeakRef for the cached schema to allow garbage collection
|
|
6903
|
+
* when the validator is no longer in use. This prevents memory leaks in long-running
|
|
6904
|
+
* applications with dynamically created schemas.
|
|
6616
6905
|
*/
|
|
6617
6906
|
class VldLazy extends VldBase {
|
|
6618
6907
|
constructor(_schemaGetter) {
|
|
6619
6908
|
super();
|
|
6620
6909
|
this._schemaGetter = _schemaGetter;
|
|
6621
|
-
|
|
6910
|
+
// Use WeakRef to allow garbage collection of the cached schema
|
|
6911
|
+
this._cachedSchemaRef = null;
|
|
6912
|
+
// Keep a strong reference flag to prevent GC during active use
|
|
6913
|
+
this._strongRef = null;
|
|
6622
6914
|
}
|
|
6623
6915
|
static create(schemaGetter) {
|
|
6624
6916
|
return new VldLazy(schemaGetter);
|
|
6625
6917
|
}
|
|
6626
6918
|
/**
|
|
6627
6919
|
* Get the actual schema, caching it after first retrieval
|
|
6920
|
+
* Uses WeakRef to allow garbage collection when validator is not in use
|
|
6628
6921
|
*/
|
|
6629
6922
|
_getSchema() {
|
|
6630
|
-
if (
|
|
6631
|
-
|
|
6632
|
-
|
|
6633
|
-
|
|
6923
|
+
// Check if we have a strong reference first (active use)
|
|
6924
|
+
if (this._strongRef) {
|
|
6925
|
+
return this._strongRef;
|
|
6926
|
+
}
|
|
6927
|
+
// Try to get from WeakRef
|
|
6928
|
+
if (this._cachedSchemaRef) {
|
|
6929
|
+
const cached = this._cachedSchemaRef.deref();
|
|
6930
|
+
if (cached) {
|
|
6931
|
+
// Restore strong reference for active use
|
|
6932
|
+
this._strongRef = cached;
|
|
6933
|
+
return cached;
|
|
6934
|
+
}
|
|
6935
|
+
}
|
|
6936
|
+
// Create new schema
|
|
6937
|
+
const schema = this._schemaGetter();
|
|
6938
|
+
this._cachedSchemaRef = new WeakRef(schema);
|
|
6939
|
+
this._strongRef = schema;
|
|
6940
|
+
// Clear strong reference after a tick to allow GC
|
|
6941
|
+
// This keeps the schema alive during synchronous operations
|
|
6942
|
+
// but allows it to be collected if the validator is discarded
|
|
6943
|
+
Promise.resolve().then(() => {
|
|
6944
|
+
this._strongRef = null;
|
|
6945
|
+
});
|
|
6946
|
+
return schema;
|
|
6634
6947
|
}
|
|
6635
6948
|
/**
|
|
6636
6949
|
* Get the inner schema (unwrap)
|
|
6950
|
+
* Returns a strong reference that will keep the schema alive
|
|
6637
6951
|
*/
|
|
6638
6952
|
unwrap() {
|
|
6639
6953
|
return this._getSchema();
|
|
@@ -6656,11 +6970,10 @@ class VldLazy extends VldBase {
|
|
|
6656
6970
|
*/
|
|
6657
6971
|
function extractLiteralValues(schema) {
|
|
6658
6972
|
if (schema instanceof VldLiteral) {
|
|
6659
|
-
|
|
6660
|
-
return [value];
|
|
6973
|
+
return [schema.literal];
|
|
6661
6974
|
}
|
|
6662
6975
|
if (schema instanceof VldEnum) {
|
|
6663
|
-
return schema.values;
|
|
6976
|
+
return [...schema.values];
|
|
6664
6977
|
}
|
|
6665
6978
|
throw new Error('Discriminator must be a literal or enum schema');
|
|
6666
6979
|
}
|
|
@@ -7957,6 +8270,87 @@ class VldUint8Array extends VldBase {
|
|
|
7957
8270
|
}
|
|
7958
8271
|
}
|
|
7959
8272
|
|
|
8273
|
+
/**
|
|
8274
|
+
* Promise validator - validates Promise values
|
|
8275
|
+
* Validates that the resolved value matches the inner schema
|
|
8276
|
+
*/
|
|
8277
|
+
/**
|
|
8278
|
+
* Promise validator - validates Promise<T> where T matches inner schema
|
|
8279
|
+
* Zod 4 API parity - allows async validation with proper type inference
|
|
8280
|
+
*
|
|
8281
|
+
* Validates that the input is a Promise, and validates the resolved value
|
|
8282
|
+
* against the inner schema when parsed.
|
|
8283
|
+
*
|
|
8284
|
+
* Use parseAsync() or safeParseAsync() for proper async handling.
|
|
8285
|
+
*/
|
|
8286
|
+
class VldPromise {
|
|
8287
|
+
constructor(inner) {
|
|
8288
|
+
this.inner = inner;
|
|
8289
|
+
}
|
|
8290
|
+
/**
|
|
8291
|
+
* Check if value is thenable (Promise-like)
|
|
8292
|
+
* Must be called BEFORE wrapping in Promise.resolve
|
|
8293
|
+
*/
|
|
8294
|
+
_isThenable(value) {
|
|
8295
|
+
return value !== null && typeof value.then === 'function';
|
|
8296
|
+
}
|
|
8297
|
+
/**
|
|
8298
|
+
* Parse and validate a Promise value asynchronously
|
|
8299
|
+
* @param value The Promise to validate
|
|
8300
|
+
* @returns The validated value
|
|
8301
|
+
* @throws {Error} If validation fails when Promise resolves
|
|
8302
|
+
*/
|
|
8303
|
+
async parse(value) {
|
|
8304
|
+
// Check if value is thenable BEFORE wrapping in Promise.resolve
|
|
8305
|
+
if (!this._isThenable(value)) {
|
|
8306
|
+
throw new Error('Expected a Promise value');
|
|
8307
|
+
}
|
|
8308
|
+
// Now we can safely await it
|
|
8309
|
+
const resolved = await Promise.resolve(value);
|
|
8310
|
+
return this.inner.parse(resolved);
|
|
8311
|
+
}
|
|
8312
|
+
/**
|
|
8313
|
+
* Safely parse and validate a Promise value asynchronously
|
|
8314
|
+
* @param value The Promise to validate
|
|
8315
|
+
* @returns A Promise resolving to ParseResult containing the validated value
|
|
8316
|
+
*/
|
|
8317
|
+
async safeParse(value) {
|
|
8318
|
+
// Check if value is thenable BEFORE wrapping in Promise.resolve
|
|
8319
|
+
if (!this._isThenable(value)) {
|
|
8320
|
+
return {
|
|
8321
|
+
success: false,
|
|
8322
|
+
error: new Error('Expected a Promise value')
|
|
8323
|
+
};
|
|
8324
|
+
}
|
|
8325
|
+
try {
|
|
8326
|
+
const resolved = await Promise.resolve(value);
|
|
8327
|
+
const validated = this.inner.parse(resolved);
|
|
8328
|
+
return {
|
|
8329
|
+
success: true,
|
|
8330
|
+
data: validated
|
|
8331
|
+
};
|
|
8332
|
+
}
|
|
8333
|
+
catch (err) {
|
|
8334
|
+
return {
|
|
8335
|
+
success: false,
|
|
8336
|
+
error: err instanceof Error ? err : new Error(String(err))
|
|
8337
|
+
};
|
|
8338
|
+
}
|
|
8339
|
+
}
|
|
8340
|
+
}
|
|
8341
|
+
/**
|
|
8342
|
+
* Create a Promise validator
|
|
8343
|
+
* @param inner The schema to validate the resolved value against
|
|
8344
|
+
* @returns A new Promise validator
|
|
8345
|
+
* @example
|
|
8346
|
+
* const promiseSchema = v.promise(v.string());
|
|
8347
|
+
* const result = await promiseSchema.parse(Promise.resolve("hello"));
|
|
8348
|
+
* // result is string
|
|
8349
|
+
*/
|
|
8350
|
+
function promise(inner) {
|
|
8351
|
+
return new VldPromise(inner);
|
|
8352
|
+
}
|
|
8353
|
+
|
|
7960
8354
|
/**
|
|
7961
8355
|
* BigInt coercion validator that attempts to convert values to bigint
|
|
7962
8356
|
*/
|
|
@@ -8101,6 +8495,10 @@ const REGEXES = {
|
|
|
8101
8495
|
sha256: /^[a-f0-9]{64}$/i,
|
|
8102
8496
|
sha384: /^[a-f0-9]{96}$/i,
|
|
8103
8497
|
sha512: /^[a-f0-9]{128}$/i,
|
|
8498
|
+
// Additional formats (Zod v4 parity)
|
|
8499
|
+
xid: /^[A-HJKMNP-TV-Z0-9]{20}$/, // ksort's XID format
|
|
8500
|
+
guid: /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/i, // Alias for UUID
|
|
8501
|
+
httpUrl: /^https?:\/\/[^\s/$.?#].[^\s]*$/, // HTTP/HTTPS URL
|
|
8104
8502
|
};
|
|
8105
8503
|
/**
|
|
8106
8504
|
* Generic string format validator
|
|
@@ -8161,6 +8559,9 @@ const mac = () => VldStringFormat.create('mac', (val) => REGEXES.mac.test(val));
|
|
|
8161
8559
|
const cidrv4 = () => VldStringFormat.create('cidrv4', (val) => REGEXES.cidrv4.test(val));
|
|
8162
8560
|
const cidrv6 = () => VldStringFormat.create('cidrv6', (val) => REGEXES.cidrv6.test(val));
|
|
8163
8561
|
const e164 = () => VldStringFormat.create('e164', (val) => REGEXES.e164.test(val));
|
|
8562
|
+
const xid = () => VldStringFormat.create('xid', (val) => REGEXES.xid.test(val));
|
|
8563
|
+
const guid = () => VldStringFormat.create('guid', (val) => REGEXES.guid.test(val));
|
|
8564
|
+
const httpUrl = () => VldStringFormat.create('httpUrl', (val) => REGEXES.httpUrl.test(val));
|
|
8164
8565
|
const hash = (algorithm) => VldStringFormat.create('hash', (val) => REGEXES[algorithm]?.test(val) ?? false, `Invalid ${algorithm} hash`);
|
|
8165
8566
|
/**
|
|
8166
8567
|
* ISO date/time validators
|
|
@@ -9599,6 +10000,534 @@ function createNoOpLogger() {
|
|
|
9599
10000
|
return createLogger({ level: 'silent' });
|
|
9600
10001
|
}
|
|
9601
10002
|
|
|
10003
|
+
/**
|
|
10004
|
+
* JSON Schema support for VLD validators
|
|
10005
|
+
* Provides conversion between VLD schemas and JSON Schema
|
|
10006
|
+
*/
|
|
10007
|
+
/**
|
|
10008
|
+
* Convert a VLD schema to JSON Schema
|
|
10009
|
+
* @param schema The VLD schema to convert
|
|
10010
|
+
* @param options Conversion options
|
|
10011
|
+
* @returns JSON Schema definition
|
|
10012
|
+
*/
|
|
10013
|
+
function toJSONSchema(schema, options = {}) {
|
|
10014
|
+
return schemaToJSONSchema(schema, options);
|
|
10015
|
+
}
|
|
10016
|
+
/**
|
|
10017
|
+
* Convert a JSON Schema to a VLD schema
|
|
10018
|
+
* @param json The JSON Schema definition
|
|
10019
|
+
* @returns A VLD schema
|
|
10020
|
+
*/
|
|
10021
|
+
function fromJSONSchema(json) {
|
|
10022
|
+
return jsonSchemaToVLD(json);
|
|
10023
|
+
}
|
|
10024
|
+
/**
|
|
10025
|
+
* Internal function to convert VLD schema to JSON Schema
|
|
10026
|
+
*/
|
|
10027
|
+
function schemaToJSONSchema(schema, options) {
|
|
10028
|
+
options.target || 'draft-07';
|
|
10029
|
+
// Handle primitives
|
|
10030
|
+
if (schema.constructor.name === 'VldString') {
|
|
10031
|
+
return buildStringSchema();
|
|
10032
|
+
}
|
|
10033
|
+
if (schema.constructor.name === 'VldNumber') {
|
|
10034
|
+
return buildNumberSchema(schema);
|
|
10035
|
+
}
|
|
10036
|
+
if (schema.constructor.name === 'VldBoolean') {
|
|
10037
|
+
return { type: 'boolean' };
|
|
10038
|
+
}
|
|
10039
|
+
if (schema.constructor.name === 'VldBigInt') {
|
|
10040
|
+
return { type: 'integer' };
|
|
10041
|
+
}
|
|
10042
|
+
if (schema.constructor.name === 'VldDate') {
|
|
10043
|
+
return { type: 'string', format: 'date-time' };
|
|
10044
|
+
}
|
|
10045
|
+
if (schema.constructor.name === 'VldArray') {
|
|
10046
|
+
return buildArraySchema(schema, options);
|
|
10047
|
+
}
|
|
10048
|
+
if (schema.constructor.name === 'VldObject') {
|
|
10049
|
+
return buildObjectSchema(schema, options);
|
|
10050
|
+
}
|
|
10051
|
+
// Handle union types
|
|
10052
|
+
if (schema.constructor.name === 'VldUnion') {
|
|
10053
|
+
return buildUnionSchema(schema, options);
|
|
10054
|
+
}
|
|
10055
|
+
// Handle literal types
|
|
10056
|
+
if (schema.constructor.name === 'VldLiteral') {
|
|
10057
|
+
return buildLiteralSchema(schema);
|
|
10058
|
+
}
|
|
10059
|
+
if (schema.constructor.name === 'VldEnum') {
|
|
10060
|
+
return buildEnumSchema(schema);
|
|
10061
|
+
}
|
|
10062
|
+
if (schema.constructor.name === 'VldRecord') {
|
|
10063
|
+
return buildRecordSchema(schema, options);
|
|
10064
|
+
}
|
|
10065
|
+
if (schema.constructor.name === 'VldTuple') {
|
|
10066
|
+
return buildTupleSchema(schema, options);
|
|
10067
|
+
}
|
|
10068
|
+
if (schema.constructor.name === 'VldIntersection') {
|
|
10069
|
+
return buildIntersectionSchema(schema, options);
|
|
10070
|
+
}
|
|
10071
|
+
if (schema.constructor.name === 'VldOptional') {
|
|
10072
|
+
return buildOptionalSchema(schema, options);
|
|
10073
|
+
}
|
|
10074
|
+
if (schema.constructor.name === 'VldNullable') {
|
|
10075
|
+
return buildNullableSchema(schema, options);
|
|
10076
|
+
}
|
|
10077
|
+
if (schema.constructor.name === 'VldNullish') {
|
|
10078
|
+
return buildNullishSchema(schema, options);
|
|
10079
|
+
}
|
|
10080
|
+
if (schema.constructor.name === 'VldExactOptional') {
|
|
10081
|
+
return buildExactOptionalSchema(schema, options);
|
|
10082
|
+
}
|
|
10083
|
+
if (schema.constructor.name === 'VldLazy') {
|
|
10084
|
+
return { type: 'object' }; // Placeholder for recursive schemas
|
|
10085
|
+
}
|
|
10086
|
+
if (schema.constructor.name === 'VldJson') {
|
|
10087
|
+
return {}; // Any JSON
|
|
10088
|
+
}
|
|
10089
|
+
if (schema.constructor.name === 'VldAny') {
|
|
10090
|
+
return {}; // JSON Schema true
|
|
10091
|
+
}
|
|
10092
|
+
if (schema.constructor.name === 'VldUnknown') {
|
|
10093
|
+
return {}; // JSON Schema true
|
|
10094
|
+
}
|
|
10095
|
+
if (schema.constructor.name === 'VldNever') {
|
|
10096
|
+
return { not: {} }; // JSON Schema false
|
|
10097
|
+
}
|
|
10098
|
+
if (schema.constructor.name === 'VldNull') {
|
|
10099
|
+
return { type: 'null' };
|
|
10100
|
+
}
|
|
10101
|
+
if (schema.constructor.name === 'VldUndefined') {
|
|
10102
|
+
return { not: {} }; // Undefined can't be represented in JSON Schema
|
|
10103
|
+
}
|
|
10104
|
+
if (schema.constructor.name === 'VldNan') {
|
|
10105
|
+
return { type: 'number', not: {} }; // NaN is a number type constraint
|
|
10106
|
+
}
|
|
10107
|
+
if (schema.constructor.name === 'VldVoid') {
|
|
10108
|
+
return { not: {} }; // Void/undefined can't be represented
|
|
10109
|
+
}
|
|
10110
|
+
// Handle branded types - unwrap and continue
|
|
10111
|
+
if (schema.constructor.name === 'VldBrand') {
|
|
10112
|
+
return schemaToJSONSchema(schema.baseValidator, options);
|
|
10113
|
+
}
|
|
10114
|
+
// Handle readonly types
|
|
10115
|
+
if (schema.constructor.name === 'VldReadonly') {
|
|
10116
|
+
return schemaToJSONSchema(schema.baseValidator, options);
|
|
10117
|
+
}
|
|
10118
|
+
// Handle transform types
|
|
10119
|
+
if (schema.constructor.name === 'VldTransform') {
|
|
10120
|
+
return schemaToJSONSchema(schema._inner, options);
|
|
10121
|
+
}
|
|
10122
|
+
// Handle meta types - unwrap metadata
|
|
10123
|
+
if (schema.constructor.name === 'VldMeta') {
|
|
10124
|
+
const metaSchema = schema;
|
|
10125
|
+
const result = schemaToJSONSchema(metaSchema.baseValidator, options);
|
|
10126
|
+
if (options.includeMetadata !== false && metaSchema.metadata) {
|
|
10127
|
+
const meta = metaSchema.metadata;
|
|
10128
|
+
if (meta.title)
|
|
10129
|
+
result.title = meta.title;
|
|
10130
|
+
if (meta.description)
|
|
10131
|
+
result.description = meta.description;
|
|
10132
|
+
if (meta.examples && options.includeExamples)
|
|
10133
|
+
result.examples = meta.examples;
|
|
10134
|
+
if (meta.default !== undefined)
|
|
10135
|
+
result.default = meta.default;
|
|
10136
|
+
if (meta.deprecated)
|
|
10137
|
+
result.deprecated = true;
|
|
10138
|
+
if (meta.readOnly)
|
|
10139
|
+
result.readOnly = true;
|
|
10140
|
+
if (meta.writeOnly)
|
|
10141
|
+
result.writeOnly = true;
|
|
10142
|
+
}
|
|
10143
|
+
return result;
|
|
10144
|
+
}
|
|
10145
|
+
// Handle refine/superRefine types
|
|
10146
|
+
if (schema.constructor.name === 'VldRefine' || schema.constructor.name === 'VldSuperRefine') {
|
|
10147
|
+
return schemaToJSONSchema(schema._inner || schema._baseValidator, options);
|
|
10148
|
+
}
|
|
10149
|
+
// Handle pipe types
|
|
10150
|
+
if (schema.constructor.name === 'VldPipe') {
|
|
10151
|
+
return schemaToJSONSchema(schema._next || schema.second, options);
|
|
10152
|
+
}
|
|
10153
|
+
// Handle default/catch types
|
|
10154
|
+
if (schema.constructor.name === 'VldDefault' || schema.constructor.name === 'VldCatch') {
|
|
10155
|
+
return schemaToJSONSchema(schema._inner || schema._baseValidator, options);
|
|
10156
|
+
}
|
|
10157
|
+
// Handle preprocess types
|
|
10158
|
+
if (schema.constructor.name === 'VldPreprocess') {
|
|
10159
|
+
return schemaToJSONSchema(schema._schema, options);
|
|
10160
|
+
}
|
|
10161
|
+
// Handle string format validators
|
|
10162
|
+
if (schema.constructor.name === 'VldStringFormat') {
|
|
10163
|
+
const formatSchema = schema;
|
|
10164
|
+
return { type: 'string', format: formatSchema._format };
|
|
10165
|
+
}
|
|
10166
|
+
// Fallback for unknown types
|
|
10167
|
+
return {};
|
|
10168
|
+
}
|
|
10169
|
+
/**
|
|
10170
|
+
* Build string JSON Schema
|
|
10171
|
+
*/
|
|
10172
|
+
function buildStringSchema(_target) {
|
|
10173
|
+
return { type: 'string' };
|
|
10174
|
+
}
|
|
10175
|
+
/**
|
|
10176
|
+
* Build number JSON Schema from VLD number schema
|
|
10177
|
+
*/
|
|
10178
|
+
function buildNumberSchema(schema, _target) {
|
|
10179
|
+
const config = schema.config || {};
|
|
10180
|
+
const checks = config.checks || [];
|
|
10181
|
+
const result = { type: 'number' };
|
|
10182
|
+
for (const check of checks) {
|
|
10183
|
+
// Try to extract constraints from closures
|
|
10184
|
+
const checkStr = check.toString();
|
|
10185
|
+
if (checkStr.includes('>=') || checkStr.includes('min')) {
|
|
10186
|
+
result.minimum = 0; // Default, actual value is in closure
|
|
10187
|
+
}
|
|
10188
|
+
if (checkStr.includes('<=') || checkStr.includes('max')) {
|
|
10189
|
+
result.maximum = 0; // Default, actual value is in closure
|
|
10190
|
+
}
|
|
10191
|
+
if (checkStr.includes('isInteger') || checkStr.includes('int')) {
|
|
10192
|
+
result.type = 'integer';
|
|
10193
|
+
}
|
|
10194
|
+
if (checkStr.includes('isSafeInteger')) {
|
|
10195
|
+
result.type = 'integer';
|
|
10196
|
+
}
|
|
10197
|
+
if (checkStr.includes('Number.isFinite')) ;
|
|
10198
|
+
}
|
|
10199
|
+
return result;
|
|
10200
|
+
}
|
|
10201
|
+
/**
|
|
10202
|
+
* Build array JSON Schema
|
|
10203
|
+
*/
|
|
10204
|
+
function buildArraySchema(schema, options) {
|
|
10205
|
+
const inner = schema._item || schema._inner;
|
|
10206
|
+
if (inner) {
|
|
10207
|
+
return {
|
|
10208
|
+
type: 'array',
|
|
10209
|
+
items: toJSONSchema(inner, options)
|
|
10210
|
+
};
|
|
10211
|
+
}
|
|
10212
|
+
return { type: 'array' };
|
|
10213
|
+
}
|
|
10214
|
+
/**
|
|
10215
|
+
* Build object JSON Schema
|
|
10216
|
+
*/
|
|
10217
|
+
function buildObjectSchema(schema, options) {
|
|
10218
|
+
const shape = schema._shape || schema.shape;
|
|
10219
|
+
if (!shape)
|
|
10220
|
+
return { type: 'object' };
|
|
10221
|
+
const properties = {};
|
|
10222
|
+
const required = [];
|
|
10223
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
10224
|
+
properties[key] = toJSONSchema(value, options);
|
|
10225
|
+
// VLD objects require all keys by default
|
|
10226
|
+
if (schema._strict !== false) {
|
|
10227
|
+
required.push(key);
|
|
10228
|
+
}
|
|
10229
|
+
}
|
|
10230
|
+
const result = {
|
|
10231
|
+
type: 'object',
|
|
10232
|
+
properties
|
|
10233
|
+
};
|
|
10234
|
+
if (required.length > 0) {
|
|
10235
|
+
result.required = required;
|
|
10236
|
+
}
|
|
10237
|
+
// Handle passthrough mode
|
|
10238
|
+
if (schema._passthrough || schema._loose) {
|
|
10239
|
+
result.additionalProperties = true;
|
|
10240
|
+
}
|
|
10241
|
+
else if (schema._strict) {
|
|
10242
|
+
result.additionalProperties = false;
|
|
10243
|
+
}
|
|
10244
|
+
return result;
|
|
10245
|
+
}
|
|
10246
|
+
/**
|
|
10247
|
+
* Build union JSON Schema
|
|
10248
|
+
*/
|
|
10249
|
+
function buildUnionSchema(schema, options) {
|
|
10250
|
+
const validators = schema._validators || schema._options || [];
|
|
10251
|
+
return {
|
|
10252
|
+
anyOf: validators.map((v) => toJSONSchema(v, options))
|
|
10253
|
+
};
|
|
10254
|
+
}
|
|
10255
|
+
/**
|
|
10256
|
+
* Build literal JSON Schema
|
|
10257
|
+
*/
|
|
10258
|
+
function buildLiteralSchema(schema) {
|
|
10259
|
+
const value = schema._value || schema.value;
|
|
10260
|
+
if (value === null)
|
|
10261
|
+
return { type: 'null' };
|
|
10262
|
+
if (typeof value === 'string')
|
|
10263
|
+
return { type: 'string', const: value };
|
|
10264
|
+
if (typeof value === 'number')
|
|
10265
|
+
return { type: 'number', const: value };
|
|
10266
|
+
if (typeof value === 'boolean')
|
|
10267
|
+
return { type: 'boolean', const: value };
|
|
10268
|
+
return { const: value };
|
|
10269
|
+
}
|
|
10270
|
+
/**
|
|
10271
|
+
* Build enum JSON Schema
|
|
10272
|
+
*/
|
|
10273
|
+
function buildEnumSchema(schema) {
|
|
10274
|
+
const values = schema._values || schema.values;
|
|
10275
|
+
if (Array.isArray(values)) {
|
|
10276
|
+
return { enum: [...values] };
|
|
10277
|
+
}
|
|
10278
|
+
return {};
|
|
10279
|
+
}
|
|
10280
|
+
/**
|
|
10281
|
+
* Build record JSON Schema
|
|
10282
|
+
*/
|
|
10283
|
+
function buildRecordSchema(schema, options) {
|
|
10284
|
+
const valueValidator = schema._value || schema._inner;
|
|
10285
|
+
if (valueValidator) {
|
|
10286
|
+
return {
|
|
10287
|
+
type: 'object',
|
|
10288
|
+
additionalProperties: toJSONSchema(valueValidator, options)
|
|
10289
|
+
};
|
|
10290
|
+
}
|
|
10291
|
+
return { type: 'object' };
|
|
10292
|
+
}
|
|
10293
|
+
/**
|
|
10294
|
+
* Build tuple JSON Schema
|
|
10295
|
+
*/
|
|
10296
|
+
function buildTupleSchema(schema, options) {
|
|
10297
|
+
const items = schema._items || schema.items;
|
|
10298
|
+
if (!items || !Array.isArray(items)) {
|
|
10299
|
+
return { type: 'array' };
|
|
10300
|
+
}
|
|
10301
|
+
return {
|
|
10302
|
+
type: 'array',
|
|
10303
|
+
items: items.map((item) => toJSONSchema(item, options)),
|
|
10304
|
+
minItems: items.length,
|
|
10305
|
+
maxItems: items.length
|
|
10306
|
+
};
|
|
10307
|
+
}
|
|
10308
|
+
/**
|
|
10309
|
+
* Build intersection JSON Schema
|
|
10310
|
+
*/
|
|
10311
|
+
function buildIntersectionSchema(schema, options) {
|
|
10312
|
+
const first = schema._first || schema.first;
|
|
10313
|
+
const second = schema._second || schema.second;
|
|
10314
|
+
const schemas = [];
|
|
10315
|
+
if (first)
|
|
10316
|
+
schemas.push(toJSONSchema(first, options));
|
|
10317
|
+
if (second)
|
|
10318
|
+
schemas.push(toJSONSchema(second, options));
|
|
10319
|
+
if (schemas.length === 0)
|
|
10320
|
+
return {};
|
|
10321
|
+
if (schemas.length === 1)
|
|
10322
|
+
return schemas[0];
|
|
10323
|
+
return { allOf: schemas };
|
|
10324
|
+
}
|
|
10325
|
+
/**
|
|
10326
|
+
* Build optional JSON Schema
|
|
10327
|
+
*/
|
|
10328
|
+
function buildOptionalSchema(schema, options) {
|
|
10329
|
+
const inner = schema._inner || schema._baseValidator;
|
|
10330
|
+
if (!inner)
|
|
10331
|
+
return {};
|
|
10332
|
+
const result = toJSONSchema(inner, options);
|
|
10333
|
+
// Remove from required array - but we don't track required here
|
|
10334
|
+
return result;
|
|
10335
|
+
}
|
|
10336
|
+
/**
|
|
10337
|
+
* Build nullable JSON Schema
|
|
10338
|
+
*/
|
|
10339
|
+
function buildNullableSchema(schema, options) {
|
|
10340
|
+
const inner = schema._inner || schema._baseValidator;
|
|
10341
|
+
if (!inner)
|
|
10342
|
+
return { type: 'null' };
|
|
10343
|
+
const result = toJSONSchema(inner, options);
|
|
10344
|
+
if (typeof result.type === 'string') {
|
|
10345
|
+
return { type: [result.type, 'null'] };
|
|
10346
|
+
}
|
|
10347
|
+
else if (Array.isArray(result.type)) {
|
|
10348
|
+
result.type.push('null');
|
|
10349
|
+
return result;
|
|
10350
|
+
}
|
|
10351
|
+
return {
|
|
10352
|
+
anyOf: [result, { type: 'null' }]
|
|
10353
|
+
};
|
|
10354
|
+
}
|
|
10355
|
+
/**
|
|
10356
|
+
* Build nullish JSON Schema
|
|
10357
|
+
*/
|
|
10358
|
+
function buildNullishSchema(schema, options) {
|
|
10359
|
+
const inner = schema._inner || schema._baseValidator;
|
|
10360
|
+
if (!inner)
|
|
10361
|
+
return {};
|
|
10362
|
+
return toJSONSchema(inner, options);
|
|
10363
|
+
}
|
|
10364
|
+
/**
|
|
10365
|
+
* Build exactOptional JSON Schema
|
|
10366
|
+
*/
|
|
10367
|
+
function buildExactOptionalSchema(schema, options) {
|
|
10368
|
+
const inner = schema._inner || schema._baseValidator;
|
|
10369
|
+
if (!inner)
|
|
10370
|
+
return {};
|
|
10371
|
+
return toJSONSchema(inner, options);
|
|
10372
|
+
}
|
|
10373
|
+
/**
|
|
10374
|
+
* Internal function to convert JSON Schema to VLD schema
|
|
10375
|
+
*/
|
|
10376
|
+
function jsonSchemaToVLD(json) {
|
|
10377
|
+
// This is a simplified implementation
|
|
10378
|
+
// Full implementation would recursively handle all JSON Schema types
|
|
10379
|
+
const VldString = require('../validators/string').VldString;
|
|
10380
|
+
const VldNumber = require('../validators/number').VldNumber;
|
|
10381
|
+
const VldBoolean = require('../validators/boolean').VldBoolean;
|
|
10382
|
+
const VldObject = require('../validators/object').VldObject;
|
|
10383
|
+
const VldArray = require('../validators/array').VldArray;
|
|
10384
|
+
const VldUnion = require('../validators/union').VldUnion;
|
|
10385
|
+
const VldLiteral = require('../validators/literal').VldLiteral;
|
|
10386
|
+
const VldEnum = require('../validators/enum').VldEnum;
|
|
10387
|
+
const VldNull = require('../validators/null').VldNull;
|
|
10388
|
+
const VldAny = require('../validators/any').VldAny;
|
|
10389
|
+
const VldRecord = require('../validators/record').VldRecord;
|
|
10390
|
+
// Handle $ref
|
|
10391
|
+
if (json.$ref) {
|
|
10392
|
+
// For now, return any - full $ref handling requires registry
|
|
10393
|
+
return VldAny.create();
|
|
10394
|
+
}
|
|
10395
|
+
// Handle anyOf/oneOf (union)
|
|
10396
|
+
if (json.anyOf || json.oneOf) {
|
|
10397
|
+
const options = (json.anyOf || json.oneOf)
|
|
10398
|
+
.map((s) => jsonSchemaToVLD(s))
|
|
10399
|
+
.filter(Boolean);
|
|
10400
|
+
if (options.length > 0) {
|
|
10401
|
+
return VldUnion.create(...options);
|
|
10402
|
+
}
|
|
10403
|
+
}
|
|
10404
|
+
// Handle allOf (intersection)
|
|
10405
|
+
if (json.allOf) {
|
|
10406
|
+
// For intersection, we'd need VldIntersection
|
|
10407
|
+
const first = jsonSchemaToVLD(json.allOf[0]);
|
|
10408
|
+
if (json.allOf.length > 1) {
|
|
10409
|
+
const second = jsonSchemaToVLD({ allOf: json.allOf.slice(1) });
|
|
10410
|
+
const VldIntersection = require('../validators/intersection').VldIntersection;
|
|
10411
|
+
return VldIntersection.create(first, second);
|
|
10412
|
+
}
|
|
10413
|
+
return first;
|
|
10414
|
+
}
|
|
10415
|
+
// Handle not
|
|
10416
|
+
if (json.not) {
|
|
10417
|
+
// For negation, we need special handling
|
|
10418
|
+
return VldAny.create();
|
|
10419
|
+
}
|
|
10420
|
+
// Handle const
|
|
10421
|
+
if (json.const !== undefined) {
|
|
10422
|
+
return VldLiteral.create(json.const);
|
|
10423
|
+
}
|
|
10424
|
+
// Handle enum
|
|
10425
|
+
if (json.enum) {
|
|
10426
|
+
return VldEnum.create(json.enum);
|
|
10427
|
+
}
|
|
10428
|
+
// Handle type
|
|
10429
|
+
const type = json.type;
|
|
10430
|
+
if (type === 'string' || type === undefined) {
|
|
10431
|
+
const s = VldString.create();
|
|
10432
|
+
if (json.minLength)
|
|
10433
|
+
s.minLength = json.minLength;
|
|
10434
|
+
if (json.maxLength)
|
|
10435
|
+
s.maxLength = json.maxLength;
|
|
10436
|
+
if (json.pattern)
|
|
10437
|
+
s.pattern = json.pattern;
|
|
10438
|
+
if (json.format) {
|
|
10439
|
+
// Map JSON Schema formats to VLD validators
|
|
10440
|
+
switch (json.format) {
|
|
10441
|
+
case 'date-time':
|
|
10442
|
+
case 'date':
|
|
10443
|
+
case 'time':
|
|
10444
|
+
// Would need DateTime validator
|
|
10445
|
+
break;
|
|
10446
|
+
case 'email':
|
|
10447
|
+
const email = require('../validators/string-formats').email;
|
|
10448
|
+
return email();
|
|
10449
|
+
case 'uri':
|
|
10450
|
+
case 'uri-reference':
|
|
10451
|
+
const httpUrl = require('../validators/string-formats').httpUrl;
|
|
10452
|
+
return httpUrl();
|
|
10453
|
+
case 'uuid':
|
|
10454
|
+
const uuid = require('../validators/string-formats').uuid;
|
|
10455
|
+
return uuid();
|
|
10456
|
+
}
|
|
10457
|
+
}
|
|
10458
|
+
return s;
|
|
10459
|
+
}
|
|
10460
|
+
if (type === 'number' || type === 'integer') {
|
|
10461
|
+
const n = VldNumber.create();
|
|
10462
|
+
if (type === 'integer')
|
|
10463
|
+
n.int();
|
|
10464
|
+
if (json.minimum !== undefined)
|
|
10465
|
+
n.min(json.minimum);
|
|
10466
|
+
if (json.maximum !== undefined)
|
|
10467
|
+
n.max(json.maximum);
|
|
10468
|
+
if (json.exclusiveMinimum !== undefined)
|
|
10469
|
+
n.gt(json.exclusiveMinimum);
|
|
10470
|
+
if (json.exclusiveMaximum !== undefined)
|
|
10471
|
+
n.lt(json.exclusiveMaximum);
|
|
10472
|
+
if (json.multipleOf !== undefined)
|
|
10473
|
+
n.multipleOf(json.multipleOf);
|
|
10474
|
+
return n;
|
|
10475
|
+
}
|
|
10476
|
+
if (type === 'boolean') {
|
|
10477
|
+
return VldBoolean.create();
|
|
10478
|
+
}
|
|
10479
|
+
if (type === 'array') {
|
|
10480
|
+
if (json.items && !Array.isArray(json.items)) {
|
|
10481
|
+
return VldArray.create(jsonSchemaToVLD(json.items));
|
|
10482
|
+
}
|
|
10483
|
+
return VldArray.create(VldAny.create());
|
|
10484
|
+
}
|
|
10485
|
+
if (type === 'object') {
|
|
10486
|
+
if (json.properties) {
|
|
10487
|
+
const shape = {};
|
|
10488
|
+
const required = json.required || [];
|
|
10489
|
+
for (const [key, propSchema] of Object.entries(json.properties)) {
|
|
10490
|
+
shape[key] = jsonSchemaToVLD(propSchema);
|
|
10491
|
+
}
|
|
10492
|
+
let obj = VldObject.create(shape);
|
|
10493
|
+
if (!required.length) {
|
|
10494
|
+
// If no required fields, make all optional
|
|
10495
|
+
for (const key of Object.keys(shape)) {
|
|
10496
|
+
if (!required.includes(key)) ;
|
|
10497
|
+
}
|
|
10498
|
+
}
|
|
10499
|
+
if (json.additionalProperties === false) {
|
|
10500
|
+
obj = obj.strict();
|
|
10501
|
+
}
|
|
10502
|
+
else if (json.additionalProperties === true) {
|
|
10503
|
+
obj = obj.passthrough();
|
|
10504
|
+
}
|
|
10505
|
+
else if (json.additionalProperties) {
|
|
10506
|
+
// additionalProperties is a schema
|
|
10507
|
+
const valueSchema = jsonSchemaToVLD(json.additionalProperties);
|
|
10508
|
+
obj = VldRecord.create(valueSchema);
|
|
10509
|
+
}
|
|
10510
|
+
return obj;
|
|
10511
|
+
}
|
|
10512
|
+
// Empty object schema
|
|
10513
|
+
return VldObject.create({});
|
|
10514
|
+
}
|
|
10515
|
+
if (type === 'null') {
|
|
10516
|
+
return VldNull.create();
|
|
10517
|
+
}
|
|
10518
|
+
if (Array.isArray(type)) {
|
|
10519
|
+
// Union of types
|
|
10520
|
+
const options = type.map((t) => {
|
|
10521
|
+
return jsonSchemaToVLD({ ...json, type: t });
|
|
10522
|
+
});
|
|
10523
|
+
if (options.length > 0) {
|
|
10524
|
+
return VldUnion.create(...options);
|
|
10525
|
+
}
|
|
10526
|
+
}
|
|
10527
|
+
// Fallback to any
|
|
10528
|
+
return VldAny.create();
|
|
10529
|
+
}
|
|
10530
|
+
|
|
9602
10531
|
/**
|
|
9603
10532
|
* VLD - Fast, Type-Safe Validation Library
|
|
9604
10533
|
* Zero dependencies, blazing fast performance
|
|
@@ -9624,6 +10553,11 @@ const v = {
|
|
|
9624
10553
|
number: () => VldNumber.create(),
|
|
9625
10554
|
int: () => VldNumber.create().int(),
|
|
9626
10555
|
int32: () => VldNumber.create().int().min(-2147483648).max(2147483647),
|
|
10556
|
+
uint32: () => VldNumber.create().uint32(),
|
|
10557
|
+
uint64: () => VldNumber.create().uint64(),
|
|
10558
|
+
int64: () => VldNumber.create().int64(),
|
|
10559
|
+
float32: () => VldNumber.create().float32(),
|
|
10560
|
+
float64: () => VldNumber.create().float64(),
|
|
9627
10561
|
boolean: () => VldBoolean.create(),
|
|
9628
10562
|
date: () => VldDate.create(),
|
|
9629
10563
|
bigint: () => VldBigInt.create(),
|
|
@@ -9662,6 +10596,7 @@ const v = {
|
|
|
9662
10596
|
optional: (validator) => VldOptional.create(validator),
|
|
9663
10597
|
nullable: (validator) => VldNullable.create(validator),
|
|
9664
10598
|
nullish: (validator) => VldNullish.create(validator),
|
|
10599
|
+
exactOptional: (validator) => VldExactOptional.create(validator),
|
|
9665
10600
|
// Recursive schemas
|
|
9666
10601
|
lazy: (schemaGetter) => VldLazy.create(schemaGetter),
|
|
9667
10602
|
// JSON validator
|
|
@@ -9710,6 +10645,10 @@ const v = {
|
|
|
9710
10645
|
duration: () => iso.duration(),
|
|
9711
10646
|
},
|
|
9712
10647
|
stringFormat: (name, validator) => stringFormat(name, validator),
|
|
10648
|
+
// Zod v4 parity string formats
|
|
10649
|
+
xid: () => xid(),
|
|
10650
|
+
guid: () => guid(),
|
|
10651
|
+
httpUrl: () => httpUrl(),
|
|
9713
10652
|
// Template literal validator
|
|
9714
10653
|
templateLiteral: (...components) => templateLiteral(...components),
|
|
9715
10654
|
// Codec validators (binary data validators)
|
|
@@ -9717,7 +10656,9 @@ const v = {
|
|
|
9717
10656
|
hexBytes: () => VldHex.create(),
|
|
9718
10657
|
uint8Array: () => VldUint8Array.create(),
|
|
9719
10658
|
// Codec factory
|
|
9720
|
-
codec: (inputValidator, outputValidator, transform) => VldCodec.create(inputValidator, outputValidator, transform)
|
|
10659
|
+
codec: (inputValidator, outputValidator, transform) => VldCodec.create(inputValidator, outputValidator, transform),
|
|
10660
|
+
// Promise validator (Zod v4 parity)
|
|
10661
|
+
promise: (inner) => promise(inner)
|
|
9721
10662
|
};
|
|
9722
10663
|
|
|
9723
10664
|
exports.Err = Err;
|
|
@@ -9726,6 +10667,7 @@ exports.ResultUtils = ResultUtils;
|
|
|
9726
10667
|
exports.VldBase = VldBase;
|
|
9727
10668
|
exports.VldError = VldError;
|
|
9728
10669
|
exports.VldIntersection = VldIntersection;
|
|
10670
|
+
exports.VldMeta = VldMeta;
|
|
9729
10671
|
exports.all = all;
|
|
9730
10672
|
exports.base64Json = base64Json;
|
|
9731
10673
|
exports.base64ToBytes = base64ToBytes;
|
|
@@ -9752,6 +10694,7 @@ exports.epochSecondsToDate = epochSecondsToDate;
|
|
|
9752
10694
|
exports.failure = failure;
|
|
9753
10695
|
exports.flatMap = flatMap;
|
|
9754
10696
|
exports.flattenError = flattenError;
|
|
10697
|
+
exports.fromJSONSchema = fromJSONSchema;
|
|
9755
10698
|
exports.fromNullable = fromNullable;
|
|
9756
10699
|
exports.getLocale = getLocale;
|
|
9757
10700
|
exports.getLogger = getLogger;
|
|
@@ -9794,6 +10737,7 @@ exports.stringToUint8Array = stringToUint8Array;
|
|
|
9794
10737
|
exports.strip = strip;
|
|
9795
10738
|
exports.success = success;
|
|
9796
10739
|
exports.supportsColor = supportsColor;
|
|
10740
|
+
exports.toJSONSchema = toJSONSchema;
|
|
9797
10741
|
exports.treeifyError = treeifyError;
|
|
9798
10742
|
exports.tryCatch = tryCatch;
|
|
9799
10743
|
exports.tryCatchAsync = tryCatchAsync;
|