@oxog/vld 2.0.1 → 2.0.2
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 +52 -0
- package/LICENSE +20 -20
- package/README.md +2 -2
- package/dist/chunks/{bigint-ZKkWLdmi.js → bigint-DgsCr2dC.js} +96 -15
- package/dist/chunks/bigint-DgsCr2dC.js.map +1 -0
- package/dist/chunks/{date-7Iz2BGsL.js → date-ODue_rtq.js} +2 -2
- package/dist/chunks/{date-7Iz2BGsL.js.map → date-ODue_rtq.js.map} +1 -1
- package/dist/chunks/{index-eWXdjyyy.js → index-BZqWZvVe.js} +4 -4
- package/dist/chunks/{index-eWXdjyyy.js.map → index-BZqWZvVe.js.map} +1 -1
- package/dist/chunks/{index-BoV-4S1F.js → index-CETyGkrv.js} +3 -3
- package/dist/chunks/index-CETyGkrv.js.map +1 -0
- package/dist/chunks/{json-nIhyRPOU.js → json-o20GFhTh.js} +225 -138
- package/dist/chunks/json-o20GFhTh.js.map +1 -0
- package/dist/chunks/{unknown-CjSRcELI.js → unknown-SLIH1VCf.js} +2 -2
- package/dist/chunks/unknown-SLIH1VCf.js.map +1 -0
- package/dist/cjs/index.cjs +318 -150
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/locales/index.cjs.map +1 -1
- package/dist/cjs/mini.cjs +317 -149
- package/dist/cjs/mini.cjs.map +1 -1
- package/dist/codecs/index.js +3 -3
- package/dist/coercion/index.js +2 -2
- package/dist/coercion/index.js.map +1 -1
- package/dist/index.js +8 -8
- package/dist/mini.js +5 -5
- package/dist/validators/array.d.ts +5 -0
- package/dist/validators/array.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.js +4 -4
- 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/object.d.ts +30 -3
- package/dist/validators/object.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 +1 -1
- package/dist/chunks/bigint-ZKkWLdmi.js.map +0 -1
- package/dist/chunks/index-BoV-4S1F.js.map +0 -1
- package/dist/chunks/json-nIhyRPOU.js.map +0 -1
- package/dist/chunks/unknown-CjSRcELI.js.map +0 -1
package/dist/cjs/index.cjs
CHANGED
|
@@ -3478,6 +3478,7 @@ const REGEX_PATTERNS = {
|
|
|
3478
3478
|
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
3479
|
/**
|
|
3480
3480
|
* Immutable string validator with chainable methods
|
|
3481
|
+
* Features pre-compiled validation functions for maximum performance
|
|
3481
3482
|
*/
|
|
3482
3483
|
class VldString extends VldBase {
|
|
3483
3484
|
/**
|
|
@@ -3485,12 +3486,100 @@ class VldString extends VldBase {
|
|
|
3485
3486
|
*/
|
|
3486
3487
|
constructor(config) {
|
|
3487
3488
|
super();
|
|
3489
|
+
// Cache for pre-compiled validation function
|
|
3490
|
+
this._compiledValidator = null;
|
|
3488
3491
|
this.config = {
|
|
3489
3492
|
checks: config?.checks || [],
|
|
3490
3493
|
transforms: config?.transforms || [],
|
|
3491
3494
|
errorMessage: config?.errorMessage
|
|
3492
3495
|
};
|
|
3493
3496
|
}
|
|
3497
|
+
/**
|
|
3498
|
+
* Compile all transforms and checks into a single optimized function
|
|
3499
|
+
* This eliminates loop overhead and enables better JIT optimization
|
|
3500
|
+
*/
|
|
3501
|
+
_compileValidator() {
|
|
3502
|
+
const transforms = this.config.transforms;
|
|
3503
|
+
const checks = this.config.checks;
|
|
3504
|
+
const errorMessage = this.config.errorMessage || getMessages().invalidString;
|
|
3505
|
+
// Fast path: no transforms or checks
|
|
3506
|
+
if (transforms.length === 0 && checks.length === 0) {
|
|
3507
|
+
return (value) => ({ success: true, value });
|
|
3508
|
+
}
|
|
3509
|
+
// Fast path: only transforms, no checks
|
|
3510
|
+
if (checks.length === 0) {
|
|
3511
|
+
switch (transforms.length) {
|
|
3512
|
+
case 1:
|
|
3513
|
+
return (value) => ({ success: true, value: transforms[0](value) });
|
|
3514
|
+
case 2:
|
|
3515
|
+
return (value) => ({ success: true, value: transforms[1](transforms[0](value)) });
|
|
3516
|
+
case 3:
|
|
3517
|
+
return (value) => ({ success: true, value: transforms[2](transforms[1](transforms[0](value))) });
|
|
3518
|
+
default:
|
|
3519
|
+
return (value) => {
|
|
3520
|
+
let result = value;
|
|
3521
|
+
for (let i = 0; i < transforms.length; i++) {
|
|
3522
|
+
result = transforms[i](result);
|
|
3523
|
+
}
|
|
3524
|
+
return { success: true, value: result };
|
|
3525
|
+
};
|
|
3526
|
+
}
|
|
3527
|
+
}
|
|
3528
|
+
// Fast path: only checks, no transforms
|
|
3529
|
+
if (transforms.length === 0) {
|
|
3530
|
+
switch (checks.length) {
|
|
3531
|
+
case 1:
|
|
3532
|
+
return (value) => {
|
|
3533
|
+
if (!checks[0](value))
|
|
3534
|
+
return { success: false, error: errorMessage };
|
|
3535
|
+
return { success: true, value };
|
|
3536
|
+
};
|
|
3537
|
+
case 2:
|
|
3538
|
+
return (value) => {
|
|
3539
|
+
if (!checks[0](value) || !checks[1](value))
|
|
3540
|
+
return { success: false, error: errorMessage };
|
|
3541
|
+
return { success: true, value };
|
|
3542
|
+
};
|
|
3543
|
+
case 3:
|
|
3544
|
+
return (value) => {
|
|
3545
|
+
if (!checks[0](value) || !checks[1](value) || !checks[2](value))
|
|
3546
|
+
return { success: false, error: errorMessage };
|
|
3547
|
+
return { success: true, value };
|
|
3548
|
+
};
|
|
3549
|
+
default:
|
|
3550
|
+
return (value) => {
|
|
3551
|
+
for (let i = 0; i < checks.length; i++) {
|
|
3552
|
+
if (!checks[i](value))
|
|
3553
|
+
return { success: false, error: errorMessage };
|
|
3554
|
+
}
|
|
3555
|
+
return { success: true, value };
|
|
3556
|
+
};
|
|
3557
|
+
}
|
|
3558
|
+
}
|
|
3559
|
+
// General case: both transforms and checks
|
|
3560
|
+
return (value) => {
|
|
3561
|
+
let result = value;
|
|
3562
|
+
// Apply transforms
|
|
3563
|
+
for (let i = 0; i < transforms.length; i++) {
|
|
3564
|
+
result = transforms[i](result);
|
|
3565
|
+
}
|
|
3566
|
+
// Apply checks
|
|
3567
|
+
for (let i = 0; i < checks.length; i++) {
|
|
3568
|
+
if (!checks[i](result))
|
|
3569
|
+
return { success: false, error: errorMessage };
|
|
3570
|
+
}
|
|
3571
|
+
return { success: true, value: result };
|
|
3572
|
+
};
|
|
3573
|
+
}
|
|
3574
|
+
/**
|
|
3575
|
+
* Get the cached compiled validator, creating it if necessary
|
|
3576
|
+
*/
|
|
3577
|
+
_getCompiledValidator() {
|
|
3578
|
+
if (!this._compiledValidator) {
|
|
3579
|
+
this._compiledValidator = this._compileValidator();
|
|
3580
|
+
}
|
|
3581
|
+
return this._compiledValidator;
|
|
3582
|
+
}
|
|
3494
3583
|
/**
|
|
3495
3584
|
* Create a new string validator
|
|
3496
3585
|
*/
|
|
@@ -3498,26 +3587,18 @@ class VldString extends VldBase {
|
|
|
3498
3587
|
return new VldString();
|
|
3499
3588
|
}
|
|
3500
3589
|
/**
|
|
3501
|
-
* Parse and validate a string value - ultra-optimized
|
|
3590
|
+
* Parse and validate a string value - ultra-optimized with pre-compiled validator
|
|
3502
3591
|
*/
|
|
3503
3592
|
parse(value) {
|
|
3504
3593
|
if (typeof value !== 'string') {
|
|
3505
3594
|
throw new Error(this.config.errorMessage || getMessages().invalidString);
|
|
3506
3595
|
}
|
|
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
|
-
}
|
|
3596
|
+
// Use pre-compiled validator for maximum performance
|
|
3597
|
+
const result = this._getCompiledValidator()(value);
|
|
3598
|
+
if (!result.success) {
|
|
3599
|
+
throw new Error(result.error);
|
|
3519
3600
|
}
|
|
3520
|
-
return result;
|
|
3601
|
+
return result.value;
|
|
3521
3602
|
}
|
|
3522
3603
|
/**
|
|
3523
3604
|
* Safely parse and validate a string value
|
|
@@ -4439,16 +4520,9 @@ class VldArray extends VldBase {
|
|
|
4439
4520
|
}
|
|
4440
4521
|
result[i] = parseResult.data; // Direct assignment is faster than push
|
|
4441
4522
|
}
|
|
4442
|
-
// Check uniqueness if required
|
|
4523
|
+
// Check uniqueness if required - optimized with Map-based approach
|
|
4443
4524
|
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
|
-
}
|
|
4525
|
+
this.checkUnique(result);
|
|
4452
4526
|
}
|
|
4453
4527
|
return result;
|
|
4454
4528
|
}
|
|
@@ -4463,6 +4537,38 @@ class VldArray extends VldBase {
|
|
|
4463
4537
|
return { success: false, error: error };
|
|
4464
4538
|
}
|
|
4465
4539
|
}
|
|
4540
|
+
/**
|
|
4541
|
+
* Optimized uniqueness check using Map-based approach
|
|
4542
|
+
* Avoids repeated stableStringify calls by caching serialized values
|
|
4543
|
+
*/
|
|
4544
|
+
checkUnique(items) {
|
|
4545
|
+
const seen = new Set(); // Set of seen keys
|
|
4546
|
+
const objectKeys = new WeakMap(); // Cache for object->string mappings
|
|
4547
|
+
for (const item of items) {
|
|
4548
|
+
let key;
|
|
4549
|
+
if (typeof item === 'object' && item !== null) {
|
|
4550
|
+
// Check if we've already serialized this object reference
|
|
4551
|
+
const cached = objectKeys.get(item);
|
|
4552
|
+
if (cached !== undefined) {
|
|
4553
|
+
key = cached;
|
|
4554
|
+
}
|
|
4555
|
+
else {
|
|
4556
|
+
// Serialize and cache
|
|
4557
|
+
const serialized = this.stableStringify(item);
|
|
4558
|
+
key = serialized;
|
|
4559
|
+
objectKeys.set(item, serialized);
|
|
4560
|
+
}
|
|
4561
|
+
}
|
|
4562
|
+
else {
|
|
4563
|
+
// Primitives can be used directly as keys
|
|
4564
|
+
key = item;
|
|
4565
|
+
}
|
|
4566
|
+
if (seen.has(key)) {
|
|
4567
|
+
throw new Error('Array must contain unique items');
|
|
4568
|
+
}
|
|
4569
|
+
seen.add(key);
|
|
4570
|
+
}
|
|
4571
|
+
}
|
|
4466
4572
|
/**
|
|
4467
4573
|
* Create a stable string representation of an object for hashing
|
|
4468
4574
|
* Handles circular references and deep nesting gracefully
|
|
@@ -4590,11 +4696,17 @@ class VldEnum extends VldBase {
|
|
|
4590
4696
|
/**
|
|
4591
4697
|
* Private constructor to enforce immutability
|
|
4592
4698
|
*/
|
|
4593
|
-
constructor(
|
|
4699
|
+
constructor(_values, errorMessage) {
|
|
4594
4700
|
super();
|
|
4595
|
-
this.
|
|
4701
|
+
this._values = _values;
|
|
4596
4702
|
this.errorMessage = errorMessage;
|
|
4597
4703
|
}
|
|
4704
|
+
/**
|
|
4705
|
+
* Get the enum values
|
|
4706
|
+
*/
|
|
4707
|
+
get values() {
|
|
4708
|
+
return this._values;
|
|
4709
|
+
}
|
|
4598
4710
|
/**
|
|
4599
4711
|
* Create a new enum validator
|
|
4600
4712
|
*/
|
|
@@ -4608,11 +4720,11 @@ class VldEnum extends VldBase {
|
|
|
4608
4720
|
// BUG-002 FIX: Add type check before includes() to prevent type confusion
|
|
4609
4721
|
if (typeof value !== 'string') {
|
|
4610
4722
|
throw new Error(this.errorMessage ||
|
|
4611
|
-
getMessages().enumExpected([...this.
|
|
4723
|
+
getMessages().enumExpected([...this._values], JSON.stringify(value)));
|
|
4612
4724
|
}
|
|
4613
|
-
if (!this.
|
|
4725
|
+
if (!this._values.includes(value)) {
|
|
4614
4726
|
throw new Error(this.errorMessage ||
|
|
4615
|
-
getMessages().enumExpected([...this.
|
|
4727
|
+
getMessages().enumExpected([...this._values], JSON.stringify(value)));
|
|
4616
4728
|
}
|
|
4617
4729
|
return value;
|
|
4618
4730
|
}
|
|
@@ -4625,16 +4737,16 @@ class VldEnum extends VldBase {
|
|
|
4625
4737
|
return {
|
|
4626
4738
|
success: false,
|
|
4627
4739
|
error: new Error(this.errorMessage ||
|
|
4628
|
-
getMessages().enumExpected([...this.
|
|
4740
|
+
getMessages().enumExpected([...this._values], JSON.stringify(value)))
|
|
4629
4741
|
};
|
|
4630
4742
|
}
|
|
4631
|
-
if (this.
|
|
4743
|
+
if (this._values.includes(value)) {
|
|
4632
4744
|
return { success: true, data: value };
|
|
4633
4745
|
}
|
|
4634
4746
|
return {
|
|
4635
4747
|
success: false,
|
|
4636
4748
|
error: new Error(this.errorMessage ||
|
|
4637
|
-
getMessages().enumExpected([...this.
|
|
4749
|
+
getMessages().enumExpected([...this._values], JSON.stringify(value)))
|
|
4638
4750
|
};
|
|
4639
4751
|
}
|
|
4640
4752
|
/**
|
|
@@ -4642,7 +4754,7 @@ class VldEnum extends VldBase {
|
|
|
4642
4754
|
* Creates a new enum validator without the specified values
|
|
4643
4755
|
*/
|
|
4644
4756
|
exclude(...excludeValues) {
|
|
4645
|
-
const filtered = this.
|
|
4757
|
+
const filtered = this._values.filter(v => !excludeValues.includes(v));
|
|
4646
4758
|
if (filtered.length === 0) {
|
|
4647
4759
|
throw new Error('Cannot exclude all enum values');
|
|
4648
4760
|
}
|
|
@@ -4654,7 +4766,7 @@ class VldEnum extends VldBase {
|
|
|
4654
4766
|
* Creates a new enum validator with only the specified values
|
|
4655
4767
|
*/
|
|
4656
4768
|
extract(...extractValues) {
|
|
4657
|
-
const extracted = this.
|
|
4769
|
+
const extracted = this._values.filter(v => extractValues.includes(v));
|
|
4658
4770
|
if (extracted.length === 0) {
|
|
4659
4771
|
throw new Error('Cannot extract non-existent enum values');
|
|
4660
4772
|
}
|
|
@@ -5175,10 +5287,30 @@ class VldObject extends VldBase {
|
|
|
5175
5287
|
*/
|
|
5176
5288
|
constructor(config) {
|
|
5177
5289
|
super();
|
|
5178
|
-
this.
|
|
5290
|
+
this._config = config;
|
|
5179
5291
|
// Pre-compute shape keys for faster access
|
|
5180
|
-
this.
|
|
5181
|
-
this.
|
|
5292
|
+
this._shapeKeys = Object.keys(config.shape);
|
|
5293
|
+
this._shapeKeysSet = new Set(this._shapeKeys);
|
|
5294
|
+
}
|
|
5295
|
+
/**
|
|
5296
|
+
* Get the validator configuration
|
|
5297
|
+
* @internal Used by discriminated union validator
|
|
5298
|
+
*/
|
|
5299
|
+
get config() {
|
|
5300
|
+
return this._config;
|
|
5301
|
+
}
|
|
5302
|
+
/**
|
|
5303
|
+
* Get the shape keys array
|
|
5304
|
+
*/
|
|
5305
|
+
get shapeKeys() {
|
|
5306
|
+
return this._shapeKeys;
|
|
5307
|
+
}
|
|
5308
|
+
/**
|
|
5309
|
+
* Get the shape keys set for O(1) lookups
|
|
5310
|
+
* @internal Used by discriminated union validator
|
|
5311
|
+
*/
|
|
5312
|
+
get shapeKeysSet() {
|
|
5313
|
+
return this._shapeKeysSet;
|
|
5182
5314
|
}
|
|
5183
5315
|
/**
|
|
5184
5316
|
* Create a new object validator
|
|
@@ -5193,14 +5325,14 @@ class VldObject extends VldBase {
|
|
|
5193
5325
|
parse(value) {
|
|
5194
5326
|
// Fast type check
|
|
5195
5327
|
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
5196
|
-
throw new Error(this.
|
|
5328
|
+
throw new Error(this._config.errorMessage || getMessages().invalidObject);
|
|
5197
5329
|
}
|
|
5198
5330
|
const obj = value;
|
|
5199
5331
|
const result = {};
|
|
5200
5332
|
// Ultra-optimized field validation with inline fast paths
|
|
5201
|
-
for (let i = 0; i < this.
|
|
5202
|
-
const key = this.
|
|
5203
|
-
const validator = this.
|
|
5333
|
+
for (let i = 0; i < this._shapeKeys.length; i++) {
|
|
5334
|
+
const key = this._shapeKeys[i];
|
|
5335
|
+
const validator = this._config.shape[key];
|
|
5204
5336
|
const fieldValue = obj[key];
|
|
5205
5337
|
// BUG-NEW-002 FIX: Use instanceof instead of constructor.name
|
|
5206
5338
|
// constructor.name breaks in minified builds where class names become 'a', 'b', etc.
|
|
@@ -5257,38 +5389,39 @@ class VldObject extends VldBase {
|
|
|
5257
5389
|
result[key] = parseResult.data;
|
|
5258
5390
|
}
|
|
5259
5391
|
}
|
|
5260
|
-
// Handle strict
|
|
5261
|
-
if (this.
|
|
5392
|
+
// Handle strict/passthrough/catchall modes - optimized single Object.keys() call
|
|
5393
|
+
if (this._config.strict || this._config.passthrough || this._config.catchall) {
|
|
5262
5394
|
const objKeys = Object.keys(obj);
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5395
|
+
// Handle strict mode - optimized with Set
|
|
5396
|
+
if (this._config.strict) {
|
|
5397
|
+
const extraKeys = [];
|
|
5398
|
+
for (let i = 0; i < objKeys.length; i++) {
|
|
5399
|
+
if (!this._shapeKeysSet.has(objKeys[i])) {
|
|
5400
|
+
extraKeys.push(objKeys[i]);
|
|
5401
|
+
}
|
|
5402
|
+
}
|
|
5403
|
+
if (extraKeys.length > 0) {
|
|
5404
|
+
throw new Error(getMessages().unexpectedKeys(extraKeys));
|
|
5267
5405
|
}
|
|
5268
5406
|
}
|
|
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];
|
|
5407
|
+
// Handle passthrough mode - optimized with comprehensive prototype pollution protection
|
|
5408
|
+
if (this._config.passthrough) {
|
|
5409
|
+
for (let i = 0; i < objKeys.length; i++) {
|
|
5410
|
+
const key = objKeys[i];
|
|
5411
|
+
// Skip dangerous keys to prevent prototype pollution
|
|
5412
|
+
if (!this._shapeKeysSet.has(key) && !this.isDangerousKey(key)) {
|
|
5413
|
+
result[key] = obj[key];
|
|
5414
|
+
}
|
|
5281
5415
|
}
|
|
5282
5416
|
}
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
result[key] = this.config.catchall.parse(obj[key]);
|
|
5417
|
+
// Handle catchall - validate extra keys with catchall validator
|
|
5418
|
+
if (this._config.catchall) {
|
|
5419
|
+
for (let i = 0; i < objKeys.length; i++) {
|
|
5420
|
+
const key = objKeys[i];
|
|
5421
|
+
// Skip keys already in shape and dangerous keys
|
|
5422
|
+
if (!this._shapeKeysSet.has(key) && !this.isDangerousKey(key)) {
|
|
5423
|
+
result[key] = this._config.catchall.parse(obj[key]);
|
|
5424
|
+
}
|
|
5292
5425
|
}
|
|
5293
5426
|
}
|
|
5294
5427
|
}
|
|
@@ -5303,15 +5436,15 @@ class VldObject extends VldBase {
|
|
|
5303
5436
|
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
5304
5437
|
return {
|
|
5305
5438
|
success: false,
|
|
5306
|
-
error: new Error(this.
|
|
5439
|
+
error: new Error(this._config.errorMessage || getMessages().invalidObject)
|
|
5307
5440
|
};
|
|
5308
5441
|
}
|
|
5309
5442
|
const obj = value;
|
|
5310
5443
|
const result = {};
|
|
5311
5444
|
// Validate all fields
|
|
5312
|
-
for (let i = 0; i < this.
|
|
5313
|
-
const key = this.
|
|
5314
|
-
const validator = this.
|
|
5445
|
+
for (let i = 0; i < this._shapeKeys.length; i++) {
|
|
5446
|
+
const key = this._shapeKeys[i];
|
|
5447
|
+
const validator = this._config.shape[key];
|
|
5315
5448
|
const fieldValue = obj[key];
|
|
5316
5449
|
const parseResult = validator.safeParse(fieldValue);
|
|
5317
5450
|
if (parseResult.success) {
|
|
@@ -5324,48 +5457,49 @@ class VldObject extends VldBase {
|
|
|
5324
5457
|
};
|
|
5325
5458
|
}
|
|
5326
5459
|
}
|
|
5327
|
-
// Handle strict
|
|
5328
|
-
if (this.
|
|
5460
|
+
// Handle strict/passthrough/catchall modes - optimized single Object.keys() call
|
|
5461
|
+
if (this._config.strict || this._config.passthrough || this._config.catchall) {
|
|
5329
5462
|
const objKeys = Object.keys(obj);
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5463
|
+
// Handle strict mode
|
|
5464
|
+
if (this._config.strict) {
|
|
5465
|
+
const extraKeys = [];
|
|
5466
|
+
for (let i = 0; i < objKeys.length; i++) {
|
|
5467
|
+
if (!this._shapeKeysSet.has(objKeys[i])) {
|
|
5468
|
+
extraKeys.push(objKeys[i]);
|
|
5469
|
+
}
|
|
5470
|
+
}
|
|
5471
|
+
if (extraKeys.length > 0) {
|
|
5472
|
+
return {
|
|
5473
|
+
success: false,
|
|
5474
|
+
error: new Error(getMessages().unexpectedKeys(extraKeys))
|
|
5475
|
+
};
|
|
5334
5476
|
}
|
|
5335
5477
|
}
|
|
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];
|
|
5478
|
+
// Handle passthrough mode with comprehensive prototype pollution protection
|
|
5479
|
+
if (this._config.passthrough) {
|
|
5480
|
+
for (let i = 0; i < objKeys.length; i++) {
|
|
5481
|
+
const key = objKeys[i];
|
|
5482
|
+
// Skip dangerous keys to prevent prototype pollution
|
|
5483
|
+
if (!this._shapeKeysSet.has(key) && !this.isDangerousKey(key)) {
|
|
5484
|
+
result[key] = obj[key];
|
|
5485
|
+
}
|
|
5351
5486
|
}
|
|
5352
5487
|
}
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5488
|
+
// Handle catchall - validate extra keys with catchall validator
|
|
5489
|
+
if (this._config.catchall) {
|
|
5490
|
+
for (let i = 0; i < objKeys.length; i++) {
|
|
5491
|
+
const key = objKeys[i];
|
|
5492
|
+
// Skip keys already in shape and dangerous keys
|
|
5493
|
+
if (!this._shapeKeysSet.has(key) && !this.isDangerousKey(key)) {
|
|
5494
|
+
const catchallResult = this._config.catchall.safeParse(obj[key]);
|
|
5495
|
+
if (!catchallResult.success) {
|
|
5496
|
+
return {
|
|
5497
|
+
success: false,
|
|
5498
|
+
error: new Error(getMessages().objectField(key, catchallResult.error.message))
|
|
5499
|
+
};
|
|
5500
|
+
}
|
|
5501
|
+
result[key] = catchallResult.data;
|
|
5367
5502
|
}
|
|
5368
|
-
result[key] = catchallResult.data;
|
|
5369
5503
|
}
|
|
5370
5504
|
}
|
|
5371
5505
|
}
|
|
@@ -5430,7 +5564,7 @@ class VldObject extends VldBase {
|
|
|
5430
5564
|
*/
|
|
5431
5565
|
strict(message) {
|
|
5432
5566
|
return new VldObject({
|
|
5433
|
-
...this.
|
|
5567
|
+
...this._config,
|
|
5434
5568
|
strict: true,
|
|
5435
5569
|
passthrough: false,
|
|
5436
5570
|
errorMessage: message
|
|
@@ -5441,7 +5575,7 @@ class VldObject extends VldBase {
|
|
|
5441
5575
|
*/
|
|
5442
5576
|
passthrough() {
|
|
5443
5577
|
return new VldObject({
|
|
5444
|
-
...this.
|
|
5578
|
+
...this._config,
|
|
5445
5579
|
strict: false,
|
|
5446
5580
|
passthrough: true
|
|
5447
5581
|
});
|
|
@@ -5451,11 +5585,11 @@ class VldObject extends VldBase {
|
|
|
5451
5585
|
*/
|
|
5452
5586
|
partial() {
|
|
5453
5587
|
const partialShape = {};
|
|
5454
|
-
for (const key in this.
|
|
5455
|
-
partialShape[key] = new VldOptional(this.
|
|
5588
|
+
for (const key in this._config.shape) {
|
|
5589
|
+
partialShape[key] = new VldOptional(this._config.shape[key]);
|
|
5456
5590
|
}
|
|
5457
5591
|
return new VldObject({
|
|
5458
|
-
...this.
|
|
5592
|
+
...this._config,
|
|
5459
5593
|
shape: partialShape
|
|
5460
5594
|
});
|
|
5461
5595
|
}
|
|
@@ -5464,8 +5598,8 @@ class VldObject extends VldBase {
|
|
|
5464
5598
|
*/
|
|
5465
5599
|
deepPartial() {
|
|
5466
5600
|
const deepPartialShape = {};
|
|
5467
|
-
for (const key in this.
|
|
5468
|
-
const validator = this.
|
|
5601
|
+
for (const key in this._config.shape) {
|
|
5602
|
+
const validator = this._config.shape[key];
|
|
5469
5603
|
if (validator instanceof VldObject) {
|
|
5470
5604
|
deepPartialShape[key] = new VldOptional(validator.deepPartial());
|
|
5471
5605
|
}
|
|
@@ -5474,7 +5608,7 @@ class VldObject extends VldBase {
|
|
|
5474
5608
|
}
|
|
5475
5609
|
}
|
|
5476
5610
|
return new VldObject({
|
|
5477
|
-
...this.
|
|
5611
|
+
...this._config,
|
|
5478
5612
|
shape: deepPartialShape
|
|
5479
5613
|
});
|
|
5480
5614
|
}
|
|
@@ -5484,12 +5618,12 @@ class VldObject extends VldBase {
|
|
|
5484
5618
|
pick(...keys) {
|
|
5485
5619
|
const pickedShape = {};
|
|
5486
5620
|
for (const key of keys) {
|
|
5487
|
-
if (key in this.
|
|
5488
|
-
pickedShape[key] = this.
|
|
5621
|
+
if (key in this._config.shape) {
|
|
5622
|
+
pickedShape[key] = this._config.shape[key];
|
|
5489
5623
|
}
|
|
5490
5624
|
}
|
|
5491
5625
|
return new VldObject({
|
|
5492
|
-
...this.
|
|
5626
|
+
...this._config,
|
|
5493
5627
|
shape: pickedShape
|
|
5494
5628
|
});
|
|
5495
5629
|
}
|
|
@@ -5499,13 +5633,13 @@ class VldObject extends VldBase {
|
|
|
5499
5633
|
omit(...keys) {
|
|
5500
5634
|
const omittedShape = {};
|
|
5501
5635
|
const keysToOmit = new Set(keys);
|
|
5502
|
-
for (const key in this.
|
|
5636
|
+
for (const key in this._config.shape) {
|
|
5503
5637
|
if (!keysToOmit.has(key)) {
|
|
5504
|
-
omittedShape[key] = this.
|
|
5638
|
+
omittedShape[key] = this._config.shape[key];
|
|
5505
5639
|
}
|
|
5506
5640
|
}
|
|
5507
5641
|
return new VldObject({
|
|
5508
|
-
...this.
|
|
5642
|
+
...this._config,
|
|
5509
5643
|
shape: omittedShape
|
|
5510
5644
|
});
|
|
5511
5645
|
}
|
|
@@ -5514,8 +5648,8 @@ class VldObject extends VldBase {
|
|
|
5514
5648
|
*/
|
|
5515
5649
|
extend(extension) {
|
|
5516
5650
|
return new VldObject({
|
|
5517
|
-
...this.
|
|
5518
|
-
shape: { ...this.
|
|
5651
|
+
...this._config,
|
|
5652
|
+
shape: { ...this._config.shape, ...extension }
|
|
5519
5653
|
});
|
|
5520
5654
|
}
|
|
5521
5655
|
/**
|
|
@@ -5523,8 +5657,8 @@ class VldObject extends VldBase {
|
|
|
5523
5657
|
*/
|
|
5524
5658
|
merge(other) {
|
|
5525
5659
|
return new VldObject({
|
|
5526
|
-
...this.
|
|
5527
|
-
shape: { ...this.
|
|
5660
|
+
...this._config,
|
|
5661
|
+
shape: { ...this._config.shape, ...other.config.shape }
|
|
5528
5662
|
});
|
|
5529
5663
|
}
|
|
5530
5664
|
/**
|
|
@@ -5532,8 +5666,8 @@ class VldObject extends VldBase {
|
|
|
5532
5666
|
*/
|
|
5533
5667
|
required() {
|
|
5534
5668
|
const requiredShape = {};
|
|
5535
|
-
for (const key in this.
|
|
5536
|
-
const validator = this.
|
|
5669
|
+
for (const key in this._config.shape) {
|
|
5670
|
+
const validator = this._config.shape[key];
|
|
5537
5671
|
// If it's optional, unwrap it
|
|
5538
5672
|
if (validator instanceof VldOptional) {
|
|
5539
5673
|
// BUG-001 FIX: Add defensive check for baseValidator property
|
|
@@ -5548,7 +5682,7 @@ class VldObject extends VldBase {
|
|
|
5548
5682
|
}
|
|
5549
5683
|
}
|
|
5550
5684
|
return new VldObject({
|
|
5551
|
-
...this.
|
|
5685
|
+
...this._config,
|
|
5552
5686
|
shape: requiredShape
|
|
5553
5687
|
});
|
|
5554
5688
|
}
|
|
@@ -5558,7 +5692,7 @@ class VldObject extends VldBase {
|
|
|
5558
5692
|
*/
|
|
5559
5693
|
catchall(schema) {
|
|
5560
5694
|
return new VldObject({
|
|
5561
|
-
...this.
|
|
5695
|
+
...this._config,
|
|
5562
5696
|
catchall: schema,
|
|
5563
5697
|
passthrough: false // catchall overrides passthrough
|
|
5564
5698
|
});
|
|
@@ -5568,14 +5702,14 @@ class VldObject extends VldBase {
|
|
|
5568
5702
|
* Zod 4 API parity - returns the shape object
|
|
5569
5703
|
*/
|
|
5570
5704
|
get shape() {
|
|
5571
|
-
return this.
|
|
5705
|
+
return this._config.shape;
|
|
5572
5706
|
}
|
|
5573
5707
|
/**
|
|
5574
5708
|
* Create an enum validator from object keys
|
|
5575
5709
|
* Zod 4 API parity - creates literal union of keys
|
|
5576
5710
|
*/
|
|
5577
5711
|
keyof() {
|
|
5578
|
-
const keys = Object.keys(this.
|
|
5712
|
+
const keys = Object.keys(this._config.shape);
|
|
5579
5713
|
if (keys.length === 0) {
|
|
5580
5714
|
throw new Error('Cannot create keyof enum from empty object');
|
|
5581
5715
|
}
|
|
@@ -5594,7 +5728,7 @@ class VldObject extends VldBase {
|
|
|
5594
5728
|
*/
|
|
5595
5729
|
safeExtend(extension) {
|
|
5596
5730
|
// Check for overlapping keys
|
|
5597
|
-
const existingKeys = new Set(Object.keys(this.
|
|
5731
|
+
const existingKeys = new Set(Object.keys(this._config.shape));
|
|
5598
5732
|
const extensionKeys = Object.keys(extension);
|
|
5599
5733
|
const overlappingKeys = [];
|
|
5600
5734
|
for (const key of extensionKeys) {
|
|
@@ -5606,8 +5740,8 @@ class VldObject extends VldBase {
|
|
|
5606
5740
|
throw new Error(`safeExtend: ${getMessages().safeExtendOverlap(overlappingKeys)}`);
|
|
5607
5741
|
}
|
|
5608
5742
|
return new VldObject({
|
|
5609
|
-
...this.
|
|
5610
|
-
shape: { ...this.
|
|
5743
|
+
...this._config,
|
|
5744
|
+
shape: { ...this._config.shape, ...extension }
|
|
5611
5745
|
});
|
|
5612
5746
|
}
|
|
5613
5747
|
}
|
|
@@ -5743,11 +5877,17 @@ class VldLiteral extends VldBase {
|
|
|
5743
5877
|
/**
|
|
5744
5878
|
* Private constructor to enforce immutability
|
|
5745
5879
|
*/
|
|
5746
|
-
constructor(
|
|
5880
|
+
constructor(_literal, errorMessage) {
|
|
5747
5881
|
super();
|
|
5748
|
-
this.
|
|
5882
|
+
this._literal = _literal;
|
|
5749
5883
|
this.errorMessage = errorMessage;
|
|
5750
5884
|
}
|
|
5885
|
+
/**
|
|
5886
|
+
* Get the literal value
|
|
5887
|
+
*/
|
|
5888
|
+
get literal() {
|
|
5889
|
+
return this._literal;
|
|
5890
|
+
}
|
|
5751
5891
|
/**
|
|
5752
5892
|
* Create a new literal validator
|
|
5753
5893
|
*/
|
|
@@ -5758,23 +5898,23 @@ class VldLiteral extends VldBase {
|
|
|
5758
5898
|
* Parse and validate a literal value
|
|
5759
5899
|
*/
|
|
5760
5900
|
parse(value) {
|
|
5761
|
-
if (value !== this.
|
|
5901
|
+
if (value !== this._literal) {
|
|
5762
5902
|
throw new Error(this.errorMessage ||
|
|
5763
|
-
getMessages().literalExpected(JSON.stringify(this.
|
|
5903
|
+
getMessages().literalExpected(JSON.stringify(this._literal), JSON.stringify(value)));
|
|
5764
5904
|
}
|
|
5765
|
-
return this.
|
|
5905
|
+
return this._literal;
|
|
5766
5906
|
}
|
|
5767
5907
|
/**
|
|
5768
5908
|
* Safely parse and validate a literal value
|
|
5769
5909
|
*/
|
|
5770
5910
|
safeParse(value) {
|
|
5771
|
-
if (value === this.
|
|
5772
|
-
return { success: true, data: this.
|
|
5911
|
+
if (value === this._literal) {
|
|
5912
|
+
return { success: true, data: this._literal };
|
|
5773
5913
|
}
|
|
5774
5914
|
return {
|
|
5775
5915
|
success: false,
|
|
5776
5916
|
error: new Error(this.errorMessage ||
|
|
5777
|
-
getMessages().literalExpected(JSON.stringify(this.
|
|
5917
|
+
getMessages().literalExpected(JSON.stringify(this._literal), JSON.stringify(value)))
|
|
5778
5918
|
};
|
|
5779
5919
|
}
|
|
5780
5920
|
}
|
|
@@ -6613,27 +6753,56 @@ class VldNan extends VldBase {
|
|
|
6613
6753
|
/**
|
|
6614
6754
|
* Lazy validator - defers schema evaluation until runtime
|
|
6615
6755
|
* Essential for recursive and self-referencing types
|
|
6756
|
+
*
|
|
6757
|
+
* MEMORY OPTIMIZATION: Uses WeakRef for the cached schema to allow garbage collection
|
|
6758
|
+
* when the validator is no longer in use. This prevents memory leaks in long-running
|
|
6759
|
+
* applications with dynamically created schemas.
|
|
6616
6760
|
*/
|
|
6617
6761
|
class VldLazy extends VldBase {
|
|
6618
6762
|
constructor(_schemaGetter) {
|
|
6619
6763
|
super();
|
|
6620
6764
|
this._schemaGetter = _schemaGetter;
|
|
6621
|
-
|
|
6765
|
+
// Use WeakRef to allow garbage collection of the cached schema
|
|
6766
|
+
this._cachedSchemaRef = null;
|
|
6767
|
+
// Keep a strong reference flag to prevent GC during active use
|
|
6768
|
+
this._strongRef = null;
|
|
6622
6769
|
}
|
|
6623
6770
|
static create(schemaGetter) {
|
|
6624
6771
|
return new VldLazy(schemaGetter);
|
|
6625
6772
|
}
|
|
6626
6773
|
/**
|
|
6627
6774
|
* Get the actual schema, caching it after first retrieval
|
|
6775
|
+
* Uses WeakRef to allow garbage collection when validator is not in use
|
|
6628
6776
|
*/
|
|
6629
6777
|
_getSchema() {
|
|
6630
|
-
if (
|
|
6631
|
-
|
|
6632
|
-
|
|
6633
|
-
|
|
6778
|
+
// Check if we have a strong reference first (active use)
|
|
6779
|
+
if (this._strongRef) {
|
|
6780
|
+
return this._strongRef;
|
|
6781
|
+
}
|
|
6782
|
+
// Try to get from WeakRef
|
|
6783
|
+
if (this._cachedSchemaRef) {
|
|
6784
|
+
const cached = this._cachedSchemaRef.deref();
|
|
6785
|
+
if (cached) {
|
|
6786
|
+
// Restore strong reference for active use
|
|
6787
|
+
this._strongRef = cached;
|
|
6788
|
+
return cached;
|
|
6789
|
+
}
|
|
6790
|
+
}
|
|
6791
|
+
// Create new schema
|
|
6792
|
+
const schema = this._schemaGetter();
|
|
6793
|
+
this._cachedSchemaRef = new WeakRef(schema);
|
|
6794
|
+
this._strongRef = schema;
|
|
6795
|
+
// Clear strong reference after a tick to allow GC
|
|
6796
|
+
// This keeps the schema alive during synchronous operations
|
|
6797
|
+
// but allows it to be collected if the validator is discarded
|
|
6798
|
+
Promise.resolve().then(() => {
|
|
6799
|
+
this._strongRef = null;
|
|
6800
|
+
});
|
|
6801
|
+
return schema;
|
|
6634
6802
|
}
|
|
6635
6803
|
/**
|
|
6636
6804
|
* Get the inner schema (unwrap)
|
|
6805
|
+
* Returns a strong reference that will keep the schema alive
|
|
6637
6806
|
*/
|
|
6638
6807
|
unwrap() {
|
|
6639
6808
|
return this._getSchema();
|
|
@@ -6656,11 +6825,10 @@ class VldLazy extends VldBase {
|
|
|
6656
6825
|
*/
|
|
6657
6826
|
function extractLiteralValues(schema) {
|
|
6658
6827
|
if (schema instanceof VldLiteral) {
|
|
6659
|
-
|
|
6660
|
-
return [value];
|
|
6828
|
+
return [schema.literal];
|
|
6661
6829
|
}
|
|
6662
6830
|
if (schema instanceof VldEnum) {
|
|
6663
|
-
return schema.values;
|
|
6831
|
+
return [...schema.values];
|
|
6664
6832
|
}
|
|
6665
6833
|
throw new Error('Discriminator must be a literal or enum schema');
|
|
6666
6834
|
}
|