@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.
Files changed (56) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/README.md +2 -2
  3. package/dist/chunks/{bigint-ZKkWLdmi.js → bigint-CRS0QVsy.js} +242 -16
  4. package/dist/chunks/bigint-CRS0QVsy.js.map +1 -0
  5. package/dist/chunks/{date-7Iz2BGsL.js → date-50JV_Mfj.js} +2 -2
  6. package/dist/chunks/{date-7Iz2BGsL.js.map → date-50JV_Mfj.js.map} +1 -1
  7. package/dist/chunks/{index-BoV-4S1F.js → index-DO8CFMxD.js} +4 -4
  8. package/dist/chunks/{index-BoV-4S1F.js.map → index-DO8CFMxD.js.map} +1 -1
  9. package/dist/chunks/{json-nIhyRPOU.js → json-Cp4WO0M9.js} +226 -139
  10. package/dist/chunks/json-Cp4WO0M9.js.map +1 -0
  11. package/dist/chunks/{unknown-CjSRcELI.js → unknown-Dhzri_T-.js} +2 -2
  12. package/dist/chunks/{unknown-CjSRcELI.js.map → unknown-Dhzri_T-.js.map} +1 -1
  13. package/dist/cjs/index.cjs +1095 -151
  14. package/dist/cjs/index.cjs.map +1 -1
  15. package/dist/cjs/mini.cjs +462 -149
  16. package/dist/cjs/mini.cjs.map +1 -1
  17. package/dist/cli/commands/validate.d.ts.map +1 -1
  18. package/dist/codecs/index.js +3 -3
  19. package/dist/coercion/index.js +2 -2
  20. package/dist/index.d.ts +244 -0
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +550 -11
  23. package/dist/index.js.map +1 -1
  24. package/dist/mini.js +5 -5
  25. package/dist/utils/json-schema.d.ts +67 -0
  26. package/dist/utils/json-schema.d.ts.map +1 -0
  27. package/dist/validators/array.d.ts +5 -0
  28. package/dist/validators/array.d.ts.map +1 -1
  29. package/dist/validators/base.d.ts +79 -0
  30. package/dist/validators/base.d.ts.map +1 -1
  31. package/dist/validators/discriminated-union.d.ts.map +1 -1
  32. package/dist/validators/enum.d.ts +5 -1
  33. package/dist/validators/enum.d.ts.map +1 -1
  34. package/dist/validators/index.d.ts +3 -2
  35. package/dist/validators/index.d.ts.map +1 -1
  36. package/dist/validators/index.js +514 -4
  37. package/dist/validators/index.js.map +1 -1
  38. package/dist/validators/lazy.d.ts +8 -1
  39. package/dist/validators/lazy.d.ts.map +1 -1
  40. package/dist/validators/literal.d.ts +5 -1
  41. package/dist/validators/literal.d.ts.map +1 -1
  42. package/dist/validators/number.d.ts +30 -0
  43. package/dist/validators/number.d.ts.map +1 -1
  44. package/dist/validators/object.d.ts +30 -3
  45. package/dist/validators/object.d.ts.map +1 -1
  46. package/dist/validators/promise.d.ts +53 -0
  47. package/dist/validators/promise.d.ts.map +1 -0
  48. package/dist/validators/string-formats.d.ts +6 -0
  49. package/dist/validators/string-formats.d.ts.map +1 -1
  50. package/dist/validators/string.d.ts +12 -1
  51. package/dist/validators/string.d.ts.map +1 -1
  52. package/package.json +6 -5
  53. package/dist/chunks/bigint-ZKkWLdmi.js.map +0 -1
  54. package/dist/chunks/index-eWXdjyyy.js +0 -426
  55. package/dist/chunks/index-eWXdjyyy.js.map +0 -1
  56. package/dist/chunks/json-nIhyRPOU.js.map +0 -1
package/dist/cjs/mini.cjs CHANGED
@@ -88,6 +88,16 @@ class VldBase {
88
88
  nullish() {
89
89
  return new VldNullish(this);
90
90
  }
91
+ /**
92
+ * Make this validator exactly optional - allows undefined but not missing
93
+ * Unlike .optional() which treats missing as undefined, exactOptional()
94
+ * requires the key to be present but allows undefined as a value
95
+ * Zod 4 API parity
96
+ * @returns A new exact optional validator
97
+ */
98
+ exactOptional() {
99
+ return new VldExactOptional(this);
100
+ }
91
101
  /**
92
102
  * Pipe the output of this validator into another validator
93
103
  * @param next The next validator to pipe into
@@ -132,6 +142,49 @@ class VldBase {
132
142
  apply(fn) {
133
143
  return fn(this);
134
144
  }
145
+ check(predicate, message) {
146
+ return new VldRefine(this, predicate, message);
147
+ }
148
+ /**
149
+ * Get or set metadata for this schema
150
+ */
151
+ meta(data) {
152
+ if (data === undefined) {
153
+ return undefined;
154
+ }
155
+ return new VldMeta(this, data);
156
+ }
157
+ /**
158
+ * Add a description to this schema
159
+ * Zod 4 API parity - convenience method for .meta({ description: ... })
160
+ * @param description The description to add
161
+ * @returns A new validator with the description
162
+ */
163
+ describe(description) {
164
+ return new VldMeta(this, { description });
165
+ }
166
+ }
167
+ /**
168
+ * Metadata validator - wraps a schema with metadata
169
+ */
170
+ class VldMeta extends VldBase {
171
+ constructor(baseValidator, metadata) {
172
+ super();
173
+ this.baseValidator = baseValidator;
174
+ this.metadata = metadata;
175
+ }
176
+ parse(value) {
177
+ return this.baseValidator.parse(value);
178
+ }
179
+ safeParse(value) {
180
+ return this.baseValidator.safeParse(value);
181
+ }
182
+ /**
183
+ * Get the metadata
184
+ */
185
+ getMeta() {
186
+ return this.metadata;
187
+ }
135
188
  }
136
189
  /**
137
190
  * Readonly validator - marks output as readonly
@@ -347,6 +400,38 @@ class VldOptional extends VldBase {
347
400
  return this.baseValidator;
348
401
  }
349
402
  }
403
+ /**
404
+ * Exact optional validator - allows undefined but requires key presence
405
+ * Unlike Optional which treats missing as undefined, ExactOptional
406
+ * requires the key to be present (not missing from object) but allows undefined
407
+ * Zod 4 API parity
408
+ */
409
+ class VldExactOptional extends VldBase {
410
+ constructor(baseValidator) {
411
+ super();
412
+ this.baseValidator = baseValidator;
413
+ }
414
+ static create(baseValidator) {
415
+ return new VldExactOptional(baseValidator);
416
+ }
417
+ parse(value) {
418
+ if (value === undefined) {
419
+ return undefined;
420
+ }
421
+ return this.baseValidator.parse(value);
422
+ }
423
+ safeParse(value) {
424
+ // Unlike regular optional, undefined is explicitly valid
425
+ // but we still validate through the base validator
426
+ if (value === undefined) {
427
+ return { success: true, data: undefined };
428
+ }
429
+ return this.baseValidator.safeParse(value);
430
+ }
431
+ unwrap() {
432
+ return this.baseValidator;
433
+ }
434
+ }
350
435
  /**
351
436
  * Nullable validator - allows null
352
437
  */
@@ -3461,6 +3546,7 @@ const REGEX_PATTERNS = {
3461
3546
  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]?)$/};
3462
3547
  /**
3463
3548
  * Immutable string validator with chainable methods
3549
+ * Features pre-compiled validation functions for maximum performance
3464
3550
  */
3465
3551
  class VldString extends VldBase {
3466
3552
  /**
@@ -3468,12 +3554,100 @@ class VldString extends VldBase {
3468
3554
  */
3469
3555
  constructor(config) {
3470
3556
  super();
3557
+ // Cache for pre-compiled validation function
3558
+ this._compiledValidator = null;
3471
3559
  this.config = {
3472
3560
  checks: config?.checks || [],
3473
3561
  transforms: config?.transforms || [],
3474
3562
  errorMessage: config?.errorMessage
3475
3563
  };
3476
3564
  }
3565
+ /**
3566
+ * Compile all transforms and checks into a single optimized function
3567
+ * This eliminates loop overhead and enables better JIT optimization
3568
+ */
3569
+ _compileValidator() {
3570
+ const transforms = this.config.transforms;
3571
+ const checks = this.config.checks;
3572
+ const errorMessage = this.config.errorMessage || getMessages().invalidString;
3573
+ // Fast path: no transforms or checks
3574
+ if (transforms.length === 0 && checks.length === 0) {
3575
+ return (value) => ({ success: true, value });
3576
+ }
3577
+ // Fast path: only transforms, no checks
3578
+ if (checks.length === 0) {
3579
+ switch (transforms.length) {
3580
+ case 1:
3581
+ return (value) => ({ success: true, value: transforms[0](value) });
3582
+ case 2:
3583
+ return (value) => ({ success: true, value: transforms[1](transforms[0](value)) });
3584
+ case 3:
3585
+ return (value) => ({ success: true, value: transforms[2](transforms[1](transforms[0](value))) });
3586
+ default:
3587
+ return (value) => {
3588
+ let result = value;
3589
+ for (let i = 0; i < transforms.length; i++) {
3590
+ result = transforms[i](result);
3591
+ }
3592
+ return { success: true, value: result };
3593
+ };
3594
+ }
3595
+ }
3596
+ // Fast path: only checks, no transforms
3597
+ if (transforms.length === 0) {
3598
+ switch (checks.length) {
3599
+ case 1:
3600
+ return (value) => {
3601
+ if (!checks[0](value))
3602
+ return { success: false, error: errorMessage };
3603
+ return { success: true, value };
3604
+ };
3605
+ case 2:
3606
+ return (value) => {
3607
+ if (!checks[0](value) || !checks[1](value))
3608
+ return { success: false, error: errorMessage };
3609
+ return { success: true, value };
3610
+ };
3611
+ case 3:
3612
+ return (value) => {
3613
+ if (!checks[0](value) || !checks[1](value) || !checks[2](value))
3614
+ return { success: false, error: errorMessage };
3615
+ return { success: true, value };
3616
+ };
3617
+ default:
3618
+ return (value) => {
3619
+ for (let i = 0; i < checks.length; i++) {
3620
+ if (!checks[i](value))
3621
+ return { success: false, error: errorMessage };
3622
+ }
3623
+ return { success: true, value };
3624
+ };
3625
+ }
3626
+ }
3627
+ // General case: both transforms and checks
3628
+ return (value) => {
3629
+ let result = value;
3630
+ // Apply transforms
3631
+ for (let i = 0; i < transforms.length; i++) {
3632
+ result = transforms[i](result);
3633
+ }
3634
+ // Apply checks
3635
+ for (let i = 0; i < checks.length; i++) {
3636
+ if (!checks[i](result))
3637
+ return { success: false, error: errorMessage };
3638
+ }
3639
+ return { success: true, value: result };
3640
+ };
3641
+ }
3642
+ /**
3643
+ * Get the cached compiled validator, creating it if necessary
3644
+ */
3645
+ _getCompiledValidator() {
3646
+ if (!this._compiledValidator) {
3647
+ this._compiledValidator = this._compileValidator();
3648
+ }
3649
+ return this._compiledValidator;
3650
+ }
3477
3651
  /**
3478
3652
  * Create a new string validator
3479
3653
  */
@@ -3481,26 +3655,18 @@ class VldString extends VldBase {
3481
3655
  return new VldString();
3482
3656
  }
3483
3657
  /**
3484
- * Parse and validate a string value - ultra-optimized
3658
+ * Parse and validate a string value - ultra-optimized with pre-compiled validator
3485
3659
  */
3486
3660
  parse(value) {
3487
3661
  if (typeof value !== 'string') {
3488
3662
  throw new Error(this.config.errorMessage || getMessages().invalidString);
3489
3663
  }
3490
- let result = value;
3491
- // Apply transformations with optimized loop
3492
- const transformsLength = this.config.transforms.length;
3493
- for (let i = 0; i < transformsLength; i++) {
3494
- result = this.config.transforms[i](result);
3495
- }
3496
- // Apply checks with optimized loop and early termination
3497
- const checksLength = this.config.checks.length;
3498
- for (let i = 0; i < checksLength; i++) {
3499
- if (!this.config.checks[i](result)) {
3500
- throw new Error(this.config.errorMessage || getMessages().invalidString);
3501
- }
3664
+ // Use pre-compiled validator for maximum performance
3665
+ const result = this._getCompiledValidator()(value);
3666
+ if (!result.success) {
3667
+ throw new Error(result.error);
3502
3668
  }
3503
- return result;
3669
+ return result.value;
3504
3670
  }
3505
3671
  /**
3506
3672
  * Safely parse and validate a string value
@@ -3914,6 +4080,66 @@ class VldNumber extends VldBase {
3914
4080
  lte(value, message) {
3915
4081
  return this.max(value, message);
3916
4082
  }
4083
+ /**
4084
+ * Create a validator for unsigned 32-bit integers
4085
+ * Range: 0 to 4,294,967,295
4086
+ */
4087
+ uint32(message) {
4088
+ return new VldNumber({
4089
+ checks: [...this.config.checks, (v) => Number.isSafeInteger(v) && v >= 0 && v <= 4294967295],
4090
+ errorMessage: message || 'Expected an unsigned 32-bit integer'
4091
+ });
4092
+ }
4093
+ /**
4094
+ * Create a validator for unsigned 64-bit integers
4095
+ * Range: 0 to 2^53-1 (safe integer limit)
4096
+ */
4097
+ uint64(message) {
4098
+ return new VldNumber({
4099
+ checks: [...this.config.checks, (v) => Number.isSafeInteger(v) && v >= 0],
4100
+ errorMessage: message || 'Expected an unsigned 64-bit integer'
4101
+ });
4102
+ }
4103
+ /**
4104
+ * Create a validator for signed 32-bit integers
4105
+ * Range: -2,147,483,648 to 2,147,483,647
4106
+ */
4107
+ int32(message) {
4108
+ return new VldNumber({
4109
+ checks: [...this.config.checks, (v) => Number.isSafeInteger(v) && v >= -2147483648 && v <= 2147483647],
4110
+ errorMessage: message || 'Expected a signed 32-bit integer'
4111
+ });
4112
+ }
4113
+ /**
4114
+ * Create a validator for signed 64-bit integers
4115
+ * Range: -(2^53-1) to 2^53-1 (safe integer limit)
4116
+ */
4117
+ int64(message) {
4118
+ return new VldNumber({
4119
+ checks: [...this.config.checks, (v) => Number.isSafeInteger(v)],
4120
+ errorMessage: message || 'Expected a signed 64-bit integer'
4121
+ });
4122
+ }
4123
+ /**
4124
+ * Create a validator for 32-bit floats (IEEE 754 single precision)
4125
+ * Range: -3.4e38 to 3.4e38, precision ~7 decimal digits
4126
+ */
4127
+ float32(message) {
4128
+ return new VldNumber({
4129
+ checks: [...this.config.checks, (v) => Number.isFinite(v) && Math.abs(v) <= 3.4e38],
4130
+ errorMessage: message || 'Expected a 32-bit float'
4131
+ });
4132
+ }
4133
+ /**
4134
+ * Create a validator for 64-bit floats (IEEE 754 double precision)
4135
+ * Alias for standard number validation
4136
+ */
4137
+ float64(message) {
4138
+ return new VldNumber({
4139
+ checks: [...this.config.checks, (v) => Number.isFinite(v)],
4140
+ errorMessage: message || 'Expected a 64-bit float'
4141
+ });
4142
+ }
3917
4143
  }
3918
4144
 
3919
4145
  /**
@@ -4602,16 +4828,9 @@ class VldArray extends VldBase {
4602
4828
  }
4603
4829
  result[i] = parseResult.data; // Direct assignment is faster than push
4604
4830
  }
4605
- // Check uniqueness if required
4831
+ // Check uniqueness if required - optimized with Map-based approach
4606
4832
  if (this.config.unique) {
4607
- const seen = new Set();
4608
- for (const item of result) {
4609
- const key = typeof item === 'object' ? this.stableStringify(item) : item;
4610
- if (seen.has(key)) {
4611
- throw new Error('Array must contain unique items');
4612
- }
4613
- seen.add(key);
4614
- }
4833
+ this.checkUnique(result);
4615
4834
  }
4616
4835
  return result;
4617
4836
  }
@@ -4626,6 +4845,38 @@ class VldArray extends VldBase {
4626
4845
  return { success: false, error: error };
4627
4846
  }
4628
4847
  }
4848
+ /**
4849
+ * Optimized uniqueness check using Map-based approach
4850
+ * Avoids repeated stableStringify calls by caching serialized values
4851
+ */
4852
+ checkUnique(items) {
4853
+ const seen = new Set(); // Set of seen keys
4854
+ const objectKeys = new WeakMap(); // Cache for object->string mappings
4855
+ for (const item of items) {
4856
+ let key;
4857
+ if (typeof item === 'object' && item !== null) {
4858
+ // Check if we've already serialized this object reference
4859
+ const cached = objectKeys.get(item);
4860
+ if (cached !== undefined) {
4861
+ key = cached;
4862
+ }
4863
+ else {
4864
+ // Serialize and cache
4865
+ const serialized = this.stableStringify(item);
4866
+ key = serialized;
4867
+ objectKeys.set(item, serialized);
4868
+ }
4869
+ }
4870
+ else {
4871
+ // Primitives can be used directly as keys
4872
+ key = item;
4873
+ }
4874
+ if (seen.has(key)) {
4875
+ throw new Error('Array must contain unique items');
4876
+ }
4877
+ seen.add(key);
4878
+ }
4879
+ }
4629
4880
  /**
4630
4881
  * Create a stable string representation of an object for hashing
4631
4882
  * Handles circular references and deep nesting gracefully
@@ -4753,11 +5004,17 @@ class VldEnum extends VldBase {
4753
5004
  /**
4754
5005
  * Private constructor to enforce immutability
4755
5006
  */
4756
- constructor(values, errorMessage) {
5007
+ constructor(_values, errorMessage) {
4757
5008
  super();
4758
- this.values = values;
5009
+ this._values = _values;
4759
5010
  this.errorMessage = errorMessage;
4760
5011
  }
5012
+ /**
5013
+ * Get the enum values
5014
+ */
5015
+ get values() {
5016
+ return this._values;
5017
+ }
4761
5018
  /**
4762
5019
  * Create a new enum validator
4763
5020
  */
@@ -4771,11 +5028,11 @@ class VldEnum extends VldBase {
4771
5028
  // BUG-002 FIX: Add type check before includes() to prevent type confusion
4772
5029
  if (typeof value !== 'string') {
4773
5030
  throw new Error(this.errorMessage ||
4774
- getMessages().enumExpected([...this.values], JSON.stringify(value)));
5031
+ getMessages().enumExpected([...this._values], JSON.stringify(value)));
4775
5032
  }
4776
- if (!this.values.includes(value)) {
5033
+ if (!this._values.includes(value)) {
4777
5034
  throw new Error(this.errorMessage ||
4778
- getMessages().enumExpected([...this.values], JSON.stringify(value)));
5035
+ getMessages().enumExpected([...this._values], JSON.stringify(value)));
4779
5036
  }
4780
5037
  return value;
4781
5038
  }
@@ -4788,16 +5045,16 @@ class VldEnum extends VldBase {
4788
5045
  return {
4789
5046
  success: false,
4790
5047
  error: new Error(this.errorMessage ||
4791
- getMessages().enumExpected([...this.values], JSON.stringify(value)))
5048
+ getMessages().enumExpected([...this._values], JSON.stringify(value)))
4792
5049
  };
4793
5050
  }
4794
- if (this.values.includes(value)) {
5051
+ if (this._values.includes(value)) {
4795
5052
  return { success: true, data: value };
4796
5053
  }
4797
5054
  return {
4798
5055
  success: false,
4799
5056
  error: new Error(this.errorMessage ||
4800
- getMessages().enumExpected([...this.values], JSON.stringify(value)))
5057
+ getMessages().enumExpected([...this._values], JSON.stringify(value)))
4801
5058
  };
4802
5059
  }
4803
5060
  /**
@@ -4805,7 +5062,7 @@ class VldEnum extends VldBase {
4805
5062
  * Creates a new enum validator without the specified values
4806
5063
  */
4807
5064
  exclude(...excludeValues) {
4808
- const filtered = this.values.filter(v => !excludeValues.includes(v));
5065
+ const filtered = this._values.filter(v => !excludeValues.includes(v));
4809
5066
  if (filtered.length === 0) {
4810
5067
  throw new Error('Cannot exclude all enum values');
4811
5068
  }
@@ -4817,7 +5074,7 @@ class VldEnum extends VldBase {
4817
5074
  * Creates a new enum validator with only the specified values
4818
5075
  */
4819
5076
  extract(...extractValues) {
4820
- const extracted = this.values.filter(v => extractValues.includes(v));
5077
+ const extracted = this._values.filter(v => extractValues.includes(v));
4821
5078
  if (extracted.length === 0) {
4822
5079
  throw new Error('Cannot extract non-existent enum values');
4823
5080
  }
@@ -5338,10 +5595,30 @@ class VldObject extends VldBase {
5338
5595
  */
5339
5596
  constructor(config) {
5340
5597
  super();
5341
- this.config = config;
5598
+ this._config = config;
5342
5599
  // Pre-compute shape keys for faster access
5343
- this.shapeKeys = Object.keys(config.shape);
5344
- this.shapeKeysSet = new Set(this.shapeKeys);
5600
+ this._shapeKeys = Object.keys(config.shape);
5601
+ this._shapeKeysSet = new Set(this._shapeKeys);
5602
+ }
5603
+ /**
5604
+ * Get the validator configuration
5605
+ * @internal Used by discriminated union validator
5606
+ */
5607
+ get config() {
5608
+ return this._config;
5609
+ }
5610
+ /**
5611
+ * Get the shape keys array
5612
+ */
5613
+ get shapeKeys() {
5614
+ return this._shapeKeys;
5615
+ }
5616
+ /**
5617
+ * Get the shape keys set for O(1) lookups
5618
+ * @internal Used by discriminated union validator
5619
+ */
5620
+ get shapeKeysSet() {
5621
+ return this._shapeKeysSet;
5345
5622
  }
5346
5623
  /**
5347
5624
  * Create a new object validator
@@ -5356,14 +5633,14 @@ class VldObject extends VldBase {
5356
5633
  parse(value) {
5357
5634
  // Fast type check
5358
5635
  if (typeof value !== 'object' || value === null || Array.isArray(value)) {
5359
- throw new Error(this.config.errorMessage || getMessages().invalidObject);
5636
+ throw new Error(this._config.errorMessage || getMessages().invalidObject);
5360
5637
  }
5361
5638
  const obj = value;
5362
5639
  const result = {};
5363
5640
  // Ultra-optimized field validation with inline fast paths
5364
- for (let i = 0; i < this.shapeKeys.length; i++) {
5365
- const key = this.shapeKeys[i];
5366
- const validator = this.config.shape[key];
5641
+ for (let i = 0; i < this._shapeKeys.length; i++) {
5642
+ const key = this._shapeKeys[i];
5643
+ const validator = this._config.shape[key];
5367
5644
  const fieldValue = obj[key];
5368
5645
  // BUG-NEW-002 FIX: Use instanceof instead of constructor.name
5369
5646
  // constructor.name breaks in minified builds where class names become 'a', 'b', etc.
@@ -5420,38 +5697,39 @@ class VldObject extends VldBase {
5420
5697
  result[key] = parseResult.data;
5421
5698
  }
5422
5699
  }
5423
- // Handle strict mode - optimized with Set
5424
- if (this.config.strict) {
5700
+ // Handle strict/passthrough/catchall modes - optimized single Object.keys() call
5701
+ if (this._config.strict || this._config.passthrough || this._config.catchall) {
5425
5702
  const objKeys = Object.keys(obj);
5426
- const extraKeys = [];
5427
- for (let i = 0; i < objKeys.length; i++) {
5428
- if (!this.shapeKeysSet.has(objKeys[i])) {
5429
- extraKeys.push(objKeys[i]);
5703
+ // Handle strict mode - optimized with Set
5704
+ if (this._config.strict) {
5705
+ const extraKeys = [];
5706
+ for (let i = 0; i < objKeys.length; i++) {
5707
+ if (!this._shapeKeysSet.has(objKeys[i])) {
5708
+ extraKeys.push(objKeys[i]);
5709
+ }
5710
+ }
5711
+ if (extraKeys.length > 0) {
5712
+ throw new Error(getMessages().unexpectedKeys(extraKeys));
5430
5713
  }
5431
5714
  }
5432
- if (extraKeys.length > 0) {
5433
- throw new Error(getMessages().unexpectedKeys(extraKeys));
5434
- }
5435
- }
5436
- // Handle passthrough mode - optimized with comprehensive prototype pollution protection
5437
- if (this.config.passthrough) {
5438
- const objKeys = Object.keys(obj);
5439
- for (let i = 0; i < objKeys.length; i++) {
5440
- const key = objKeys[i];
5441
- // Skip dangerous keys to prevent prototype pollution
5442
- if (!this.shapeKeysSet.has(key) && !this.isDangerousKey(key)) {
5443
- result[key] = obj[key];
5715
+ // Handle passthrough mode - optimized with comprehensive prototype pollution protection
5716
+ if (this._config.passthrough) {
5717
+ for (let i = 0; i < objKeys.length; i++) {
5718
+ const key = objKeys[i];
5719
+ // Skip dangerous keys to prevent prototype pollution
5720
+ if (!this._shapeKeysSet.has(key) && !this.isDangerousKey(key)) {
5721
+ result[key] = obj[key];
5722
+ }
5444
5723
  }
5445
5724
  }
5446
- }
5447
- // Handle catchall - validate extra keys with catchall validator
5448
- if (this.config.catchall) {
5449
- const objKeys = Object.keys(obj);
5450
- for (let i = 0; i < objKeys.length; i++) {
5451
- const key = objKeys[i];
5452
- // Skip keys already in shape and dangerous keys
5453
- if (!this.shapeKeysSet.has(key) && !this.isDangerousKey(key)) {
5454
- result[key] = this.config.catchall.parse(obj[key]);
5725
+ // Handle catchall - validate extra keys with catchall validator
5726
+ if (this._config.catchall) {
5727
+ for (let i = 0; i < objKeys.length; i++) {
5728
+ const key = objKeys[i];
5729
+ // Skip keys already in shape and dangerous keys
5730
+ if (!this._shapeKeysSet.has(key) && !this.isDangerousKey(key)) {
5731
+ result[key] = this._config.catchall.parse(obj[key]);
5732
+ }
5455
5733
  }
5456
5734
  }
5457
5735
  }
@@ -5466,15 +5744,15 @@ class VldObject extends VldBase {
5466
5744
  if (typeof value !== 'object' || value === null || Array.isArray(value)) {
5467
5745
  return {
5468
5746
  success: false,
5469
- error: new Error(this.config.errorMessage || getMessages().invalidObject)
5747
+ error: new Error(this._config.errorMessage || getMessages().invalidObject)
5470
5748
  };
5471
5749
  }
5472
5750
  const obj = value;
5473
5751
  const result = {};
5474
5752
  // Validate all fields
5475
- for (let i = 0; i < this.shapeKeys.length; i++) {
5476
- const key = this.shapeKeys[i];
5477
- const validator = this.config.shape[key];
5753
+ for (let i = 0; i < this._shapeKeys.length; i++) {
5754
+ const key = this._shapeKeys[i];
5755
+ const validator = this._config.shape[key];
5478
5756
  const fieldValue = obj[key];
5479
5757
  const parseResult = validator.safeParse(fieldValue);
5480
5758
  if (parseResult.success) {
@@ -5487,48 +5765,49 @@ class VldObject extends VldBase {
5487
5765
  };
5488
5766
  }
5489
5767
  }
5490
- // Handle strict mode
5491
- if (this.config.strict) {
5768
+ // Handle strict/passthrough/catchall modes - optimized single Object.keys() call
5769
+ if (this._config.strict || this._config.passthrough || this._config.catchall) {
5492
5770
  const objKeys = Object.keys(obj);
5493
- const extraKeys = [];
5494
- for (let i = 0; i < objKeys.length; i++) {
5495
- if (!this.shapeKeysSet.has(objKeys[i])) {
5496
- extraKeys.push(objKeys[i]);
5771
+ // Handle strict mode
5772
+ if (this._config.strict) {
5773
+ const extraKeys = [];
5774
+ for (let i = 0; i < objKeys.length; i++) {
5775
+ if (!this._shapeKeysSet.has(objKeys[i])) {
5776
+ extraKeys.push(objKeys[i]);
5777
+ }
5778
+ }
5779
+ if (extraKeys.length > 0) {
5780
+ return {
5781
+ success: false,
5782
+ error: new Error(getMessages().unexpectedKeys(extraKeys))
5783
+ };
5497
5784
  }
5498
5785
  }
5499
- if (extraKeys.length > 0) {
5500
- return {
5501
- success: false,
5502
- error: new Error(getMessages().unexpectedKeys(extraKeys))
5503
- };
5504
- }
5505
- }
5506
- // Handle passthrough mode with comprehensive prototype pollution protection
5507
- if (this.config.passthrough) {
5508
- const objKeys = Object.keys(obj);
5509
- for (let i = 0; i < objKeys.length; i++) {
5510
- const key = objKeys[i];
5511
- // Skip dangerous keys to prevent prototype pollution
5512
- if (!this.shapeKeysSet.has(key) && !this.isDangerousKey(key)) {
5513
- result[key] = obj[key];
5786
+ // Handle passthrough mode with comprehensive prototype pollution protection
5787
+ if (this._config.passthrough) {
5788
+ for (let i = 0; i < objKeys.length; i++) {
5789
+ const key = objKeys[i];
5790
+ // Skip dangerous keys to prevent prototype pollution
5791
+ if (!this._shapeKeysSet.has(key) && !this.isDangerousKey(key)) {
5792
+ result[key] = obj[key];
5793
+ }
5514
5794
  }
5515
5795
  }
5516
- }
5517
- // Handle catchall - validate extra keys with catchall validator
5518
- if (this.config.catchall) {
5519
- const objKeys = Object.keys(obj);
5520
- for (let i = 0; i < objKeys.length; i++) {
5521
- const key = objKeys[i];
5522
- // Skip keys already in shape and dangerous keys
5523
- if (!this.shapeKeysSet.has(key) && !this.isDangerousKey(key)) {
5524
- const catchallResult = this.config.catchall.safeParse(obj[key]);
5525
- if (!catchallResult.success) {
5526
- return {
5527
- success: false,
5528
- error: new Error(getMessages().objectField(key, catchallResult.error.message))
5529
- };
5796
+ // Handle catchall - validate extra keys with catchall validator
5797
+ if (this._config.catchall) {
5798
+ for (let i = 0; i < objKeys.length; i++) {
5799
+ const key = objKeys[i];
5800
+ // Skip keys already in shape and dangerous keys
5801
+ if (!this._shapeKeysSet.has(key) && !this.isDangerousKey(key)) {
5802
+ const catchallResult = this._config.catchall.safeParse(obj[key]);
5803
+ if (!catchallResult.success) {
5804
+ return {
5805
+ success: false,
5806
+ error: new Error(getMessages().objectField(key, catchallResult.error.message))
5807
+ };
5808
+ }
5809
+ result[key] = catchallResult.data;
5530
5810
  }
5531
- result[key] = catchallResult.data;
5532
5811
  }
5533
5812
  }
5534
5813
  }
@@ -5593,7 +5872,7 @@ class VldObject extends VldBase {
5593
5872
  */
5594
5873
  strict(message) {
5595
5874
  return new VldObject({
5596
- ...this.config,
5875
+ ...this._config,
5597
5876
  strict: true,
5598
5877
  passthrough: false,
5599
5878
  errorMessage: message
@@ -5604,7 +5883,7 @@ class VldObject extends VldBase {
5604
5883
  */
5605
5884
  passthrough() {
5606
5885
  return new VldObject({
5607
- ...this.config,
5886
+ ...this._config,
5608
5887
  strict: false,
5609
5888
  passthrough: true
5610
5889
  });
@@ -5614,11 +5893,11 @@ class VldObject extends VldBase {
5614
5893
  */
5615
5894
  partial() {
5616
5895
  const partialShape = {};
5617
- for (const key in this.config.shape) {
5618
- partialShape[key] = new VldOptional(this.config.shape[key]);
5896
+ for (const key in this._config.shape) {
5897
+ partialShape[key] = new VldOptional(this._config.shape[key]);
5619
5898
  }
5620
5899
  return new VldObject({
5621
- ...this.config,
5900
+ ...this._config,
5622
5901
  shape: partialShape
5623
5902
  });
5624
5903
  }
@@ -5627,8 +5906,8 @@ class VldObject extends VldBase {
5627
5906
  */
5628
5907
  deepPartial() {
5629
5908
  const deepPartialShape = {};
5630
- for (const key in this.config.shape) {
5631
- const validator = this.config.shape[key];
5909
+ for (const key in this._config.shape) {
5910
+ const validator = this._config.shape[key];
5632
5911
  if (validator instanceof VldObject) {
5633
5912
  deepPartialShape[key] = new VldOptional(validator.deepPartial());
5634
5913
  }
@@ -5637,7 +5916,7 @@ class VldObject extends VldBase {
5637
5916
  }
5638
5917
  }
5639
5918
  return new VldObject({
5640
- ...this.config,
5919
+ ...this._config,
5641
5920
  shape: deepPartialShape
5642
5921
  });
5643
5922
  }
@@ -5647,12 +5926,12 @@ class VldObject extends VldBase {
5647
5926
  pick(...keys) {
5648
5927
  const pickedShape = {};
5649
5928
  for (const key of keys) {
5650
- if (key in this.config.shape) {
5651
- pickedShape[key] = this.config.shape[key];
5929
+ if (key in this._config.shape) {
5930
+ pickedShape[key] = this._config.shape[key];
5652
5931
  }
5653
5932
  }
5654
5933
  return new VldObject({
5655
- ...this.config,
5934
+ ...this._config,
5656
5935
  shape: pickedShape
5657
5936
  });
5658
5937
  }
@@ -5662,13 +5941,13 @@ class VldObject extends VldBase {
5662
5941
  omit(...keys) {
5663
5942
  const omittedShape = {};
5664
5943
  const keysToOmit = new Set(keys);
5665
- for (const key in this.config.shape) {
5944
+ for (const key in this._config.shape) {
5666
5945
  if (!keysToOmit.has(key)) {
5667
- omittedShape[key] = this.config.shape[key];
5946
+ omittedShape[key] = this._config.shape[key];
5668
5947
  }
5669
5948
  }
5670
5949
  return new VldObject({
5671
- ...this.config,
5950
+ ...this._config,
5672
5951
  shape: omittedShape
5673
5952
  });
5674
5953
  }
@@ -5677,8 +5956,8 @@ class VldObject extends VldBase {
5677
5956
  */
5678
5957
  extend(extension) {
5679
5958
  return new VldObject({
5680
- ...this.config,
5681
- shape: { ...this.config.shape, ...extension }
5959
+ ...this._config,
5960
+ shape: { ...this._config.shape, ...extension }
5682
5961
  });
5683
5962
  }
5684
5963
  /**
@@ -5686,8 +5965,8 @@ class VldObject extends VldBase {
5686
5965
  */
5687
5966
  merge(other) {
5688
5967
  return new VldObject({
5689
- ...this.config,
5690
- shape: { ...this.config.shape, ...other.config.shape }
5968
+ ...this._config,
5969
+ shape: { ...this._config.shape, ...other.config.shape }
5691
5970
  });
5692
5971
  }
5693
5972
  /**
@@ -5695,8 +5974,8 @@ class VldObject extends VldBase {
5695
5974
  */
5696
5975
  required() {
5697
5976
  const requiredShape = {};
5698
- for (const key in this.config.shape) {
5699
- const validator = this.config.shape[key];
5977
+ for (const key in this._config.shape) {
5978
+ const validator = this._config.shape[key];
5700
5979
  // If it's optional, unwrap it
5701
5980
  if (validator instanceof VldOptional) {
5702
5981
  // BUG-001 FIX: Add defensive check for baseValidator property
@@ -5711,7 +5990,7 @@ class VldObject extends VldBase {
5711
5990
  }
5712
5991
  }
5713
5992
  return new VldObject({
5714
- ...this.config,
5993
+ ...this._config,
5715
5994
  shape: requiredShape
5716
5995
  });
5717
5996
  }
@@ -5721,7 +6000,7 @@ class VldObject extends VldBase {
5721
6000
  */
5722
6001
  catchall(schema) {
5723
6002
  return new VldObject({
5724
- ...this.config,
6003
+ ...this._config,
5725
6004
  catchall: schema,
5726
6005
  passthrough: false // catchall overrides passthrough
5727
6006
  });
@@ -5731,14 +6010,14 @@ class VldObject extends VldBase {
5731
6010
  * Zod 4 API parity - returns the shape object
5732
6011
  */
5733
6012
  get shape() {
5734
- return this.config.shape;
6013
+ return this._config.shape;
5735
6014
  }
5736
6015
  /**
5737
6016
  * Create an enum validator from object keys
5738
6017
  * Zod 4 API parity - creates literal union of keys
5739
6018
  */
5740
6019
  keyof() {
5741
- const keys = Object.keys(this.config.shape);
6020
+ const keys = Object.keys(this._config.shape);
5742
6021
  if (keys.length === 0) {
5743
6022
  throw new Error('Cannot create keyof enum from empty object');
5744
6023
  }
@@ -5757,7 +6036,7 @@ class VldObject extends VldBase {
5757
6036
  */
5758
6037
  safeExtend(extension) {
5759
6038
  // Check for overlapping keys
5760
- const existingKeys = new Set(Object.keys(this.config.shape));
6039
+ const existingKeys = new Set(Object.keys(this._config.shape));
5761
6040
  const extensionKeys = Object.keys(extension);
5762
6041
  const overlappingKeys = [];
5763
6042
  for (const key of extensionKeys) {
@@ -5769,8 +6048,8 @@ class VldObject extends VldBase {
5769
6048
  throw new Error(`safeExtend: ${getMessages().safeExtendOverlap(overlappingKeys)}`);
5770
6049
  }
5771
6050
  return new VldObject({
5772
- ...this.config,
5773
- shape: { ...this.config.shape, ...extension }
6051
+ ...this._config,
6052
+ shape: { ...this._config.shape, ...extension }
5774
6053
  });
5775
6054
  }
5776
6055
  }
@@ -6353,11 +6632,17 @@ class VldLiteral extends VldBase {
6353
6632
  /**
6354
6633
  * Private constructor to enforce immutability
6355
6634
  */
6356
- constructor(literal, errorMessage) {
6635
+ constructor(_literal, errorMessage) {
6357
6636
  super();
6358
- this.literal = literal;
6637
+ this._literal = _literal;
6359
6638
  this.errorMessage = errorMessage;
6360
6639
  }
6640
+ /**
6641
+ * Get the literal value
6642
+ */
6643
+ get literal() {
6644
+ return this._literal;
6645
+ }
6361
6646
  /**
6362
6647
  * Create a new literal validator
6363
6648
  */
@@ -6368,23 +6653,23 @@ class VldLiteral extends VldBase {
6368
6653
  * Parse and validate a literal value
6369
6654
  */
6370
6655
  parse(value) {
6371
- if (value !== this.literal) {
6656
+ if (value !== this._literal) {
6372
6657
  throw new Error(this.errorMessage ||
6373
- getMessages().literalExpected(JSON.stringify(this.literal), JSON.stringify(value)));
6658
+ getMessages().literalExpected(JSON.stringify(this._literal), JSON.stringify(value)));
6374
6659
  }
6375
- return this.literal;
6660
+ return this._literal;
6376
6661
  }
6377
6662
  /**
6378
6663
  * Safely parse and validate a literal value
6379
6664
  */
6380
6665
  safeParse(value) {
6381
- if (value === this.literal) {
6382
- return { success: true, data: this.literal };
6666
+ if (value === this._literal) {
6667
+ return { success: true, data: this._literal };
6383
6668
  }
6384
6669
  return {
6385
6670
  success: false,
6386
6671
  error: new Error(this.errorMessage ||
6387
- getMessages().literalExpected(JSON.stringify(this.literal), JSON.stringify(value)))
6672
+ getMessages().literalExpected(JSON.stringify(this._literal), JSON.stringify(value)))
6388
6673
  };
6389
6674
  }
6390
6675
  }
@@ -6596,27 +6881,56 @@ class VldNan extends VldBase {
6596
6881
  /**
6597
6882
  * Lazy validator - defers schema evaluation until runtime
6598
6883
  * Essential for recursive and self-referencing types
6884
+ *
6885
+ * MEMORY OPTIMIZATION: Uses WeakRef for the cached schema to allow garbage collection
6886
+ * when the validator is no longer in use. This prevents memory leaks in long-running
6887
+ * applications with dynamically created schemas.
6599
6888
  */
6600
6889
  class VldLazy extends VldBase {
6601
6890
  constructor(_schemaGetter) {
6602
6891
  super();
6603
6892
  this._schemaGetter = _schemaGetter;
6604
- this._cachedSchema = null;
6893
+ // Use WeakRef to allow garbage collection of the cached schema
6894
+ this._cachedSchemaRef = null;
6895
+ // Keep a strong reference flag to prevent GC during active use
6896
+ this._strongRef = null;
6605
6897
  }
6606
6898
  static create(schemaGetter) {
6607
6899
  return new VldLazy(schemaGetter);
6608
6900
  }
6609
6901
  /**
6610
6902
  * Get the actual schema, caching it after first retrieval
6903
+ * Uses WeakRef to allow garbage collection when validator is not in use
6611
6904
  */
6612
6905
  _getSchema() {
6613
- if (!this._cachedSchema) {
6614
- this._cachedSchema = this._schemaGetter();
6906
+ // Check if we have a strong reference first (active use)
6907
+ if (this._strongRef) {
6908
+ return this._strongRef;
6909
+ }
6910
+ // Try to get from WeakRef
6911
+ if (this._cachedSchemaRef) {
6912
+ const cached = this._cachedSchemaRef.deref();
6913
+ if (cached) {
6914
+ // Restore strong reference for active use
6915
+ this._strongRef = cached;
6916
+ return cached;
6917
+ }
6615
6918
  }
6616
- return this._cachedSchema;
6919
+ // Create new schema
6920
+ const schema = this._schemaGetter();
6921
+ this._cachedSchemaRef = new WeakRef(schema);
6922
+ this._strongRef = schema;
6923
+ // Clear strong reference after a tick to allow GC
6924
+ // This keeps the schema alive during synchronous operations
6925
+ // but allows it to be collected if the validator is discarded
6926
+ Promise.resolve().then(() => {
6927
+ this._strongRef = null;
6928
+ });
6929
+ return schema;
6617
6930
  }
6618
6931
  /**
6619
6932
  * Get the inner schema (unwrap)
6933
+ * Returns a strong reference that will keep the schema alive
6620
6934
  */
6621
6935
  unwrap() {
6622
6936
  return this._getSchema();
@@ -6639,11 +6953,10 @@ class VldLazy extends VldBase {
6639
6953
  */
6640
6954
  function extractLiteralValues(schema) {
6641
6955
  if (schema instanceof VldLiteral) {
6642
- const value = schema.literal;
6643
- return [value];
6956
+ return [schema.literal];
6644
6957
  }
6645
6958
  if (schema instanceof VldEnum) {
6646
- return schema.values;
6959
+ return [...schema.values];
6647
6960
  }
6648
6961
  throw new Error('Discriminator must be a literal or enum schema');
6649
6962
  }