@tldraw/validate 4.2.0 → 4.2.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.
@@ -322,6 +322,20 @@ declare function model<T extends {
322
322
  readonly id: string;
323
323
  }>(name: string, validator: Validatable<T>): Validator<T>;
324
324
 
325
+ /**
326
+ * Validator that ensures a value is a finite, non-zero number. Allows negative numbers.
327
+ * Useful for scale factors that can be negative (for flipping) but not zero.
328
+ *
329
+ * @example
330
+ * ```ts
331
+ * const scale = T.nonZeroFiniteNumber.validate(-1.5) // Returns -1.5 (valid, allows negative)
332
+ * T.nonZeroFiniteNumber.validate(0) // Throws ValidationError: "Expected a non-zero number, got 0"
333
+ * T.nonZeroFiniteNumber.validate(Infinity) // Throws ValidationError
334
+ * ```
335
+ * @public
336
+ */
337
+ declare const nonZeroFiniteNumber: Validator<number>;
338
+
325
339
  /**
326
340
  * Validator that ensures a value is a positive integer (\> 0). Rejects zero and negative integers.
327
341
  *
@@ -613,6 +627,8 @@ declare namespace T {
613
627
  number,
614
628
  positiveNumber,
615
629
  nonZeroNumber,
630
+ nonZeroFiniteNumber,
631
+ unitInterval,
616
632
  integer,
617
633
  positiveInteger,
618
634
  nonZeroInteger,
@@ -735,6 +751,22 @@ export declare type UnionValidatorConfig<Key extends string, Config> = {
735
751
  };
736
752
  };
737
753
 
754
+ /**
755
+ * Validator that ensures a value is a number in the unit interval [0, 1].
756
+ * Useful for opacity, percentages expressed as decimals, and other normalized values.
757
+ *
758
+ * @example
759
+ * ```ts
760
+ * const opacity = T.unitInterval.validate(0.5) // Returns 0.5
761
+ * T.unitInterval.validate(0) // Returns 0 (valid)
762
+ * T.unitInterval.validate(1) // Returns 1 (valid)
763
+ * T.unitInterval.validate(1.5) // Throws ValidationError
764
+ * T.unitInterval.validate(-0.1) // Throws ValidationError
765
+ * ```
766
+ * @public
767
+ */
768
+ declare const unitInterval: Validator<number>;
769
+
738
770
  /**
739
771
  * Validator that accepts any value without type checking. Useful as a starting point for
740
772
  * building custom validations or when you need to accept truly unknown data.
@@ -854,13 +886,17 @@ declare class ValidationError extends Error {
854
886
  export declare class Validator<T> implements Validatable<T> {
855
887
  readonly validationFn: ValidatorFn<T>;
856
888
  readonly validateUsingKnownGoodVersionFn?: undefined | ValidatorUsingKnownGoodVersionFn<T>;
889
+ /* Excluded from this release type: skipSameValueCheck */
857
890
  /**
858
891
  * Creates a new Validator instance.
859
892
  *
860
893
  * validationFn - Function that validates and returns a value of type T
861
894
  * validateUsingKnownGoodVersionFn - Optional performance-optimized validation function
895
+ * skipSameValueCheck - Internal flag to skip dev check for validators that transform values
862
896
  */
863
- constructor(validationFn: ValidatorFn<T>, validateUsingKnownGoodVersionFn?: undefined | ValidatorUsingKnownGoodVersionFn<T>);
897
+ constructor(validationFn: ValidatorFn<T>, validateUsingKnownGoodVersionFn?: undefined | ValidatorUsingKnownGoodVersionFn<T>,
898
+ /** @internal */
899
+ skipSameValueCheck?: boolean);
864
900
  /**
865
901
  * Validates an unknown value and returns it with the correct type. The returned value is
866
902
  * guaranteed to be referentially equal to the passed value.
package/dist-cjs/index.js CHANGED
@@ -41,7 +41,7 @@ var T = __toESM(require("./lib/validation"), 1);
41
41
  var import_validation = require("./lib/validation");
42
42
  (0, import_utils.registerTldrawLibraryVersion)(
43
43
  "@tldraw/validate",
44
- "4.2.0",
44
+ "4.2.2",
45
45
  "cjs"
46
46
  );
47
47
  //# sourceMappingURL=index.js.map
@@ -39,6 +39,7 @@ __export(validation_exports, {
39
39
  literal: () => literal,
40
40
  literalEnum: () => literalEnum,
41
41
  model: () => model,
42
+ nonZeroFiniteNumber: () => nonZeroFiniteNumber,
42
43
  nonZeroInteger: () => nonZeroInteger,
43
44
  nonZeroNumber: () => nonZeroNumber,
44
45
  nullable: () => nullable,
@@ -53,11 +54,13 @@ __export(validation_exports, {
53
54
  srcUrl: () => srcUrl,
54
55
  string: () => string,
55
56
  union: () => union,
57
+ unitInterval: () => unitInterval,
56
58
  unknown: () => unknown,
57
59
  unknownObject: () => unknownObject
58
60
  });
59
61
  module.exports = __toCommonJS(validation_exports);
60
62
  var import_utils = require("@tldraw/utils");
63
+ const IS_DEV = process.env.NODE_ENV !== "production";
61
64
  function formatPath(path) {
62
65
  if (!path.length) {
63
66
  return null;
@@ -134,10 +137,12 @@ class Validator {
134
137
  *
135
138
  * validationFn - Function that validates and returns a value of type T
136
139
  * validateUsingKnownGoodVersionFn - Optional performance-optimized validation function
140
+ * skipSameValueCheck - Internal flag to skip dev check for validators that transform values
137
141
  */
138
- constructor(validationFn, validateUsingKnownGoodVersionFn) {
142
+ constructor(validationFn, validateUsingKnownGoodVersionFn, skipSameValueCheck = false) {
139
143
  this.validationFn = validationFn;
140
144
  this.validateUsingKnownGoodVersionFn = validateUsingKnownGoodVersionFn;
145
+ this.skipSameValueCheck = skipSameValueCheck;
141
146
  }
142
147
  /**
143
148
  * Validates an unknown value and returns it with the correct type. The returned value is
@@ -161,7 +166,7 @@ class Validator {
161
166
  */
162
167
  validate(value) {
163
168
  const validated = this.validationFn(value);
164
- if (process.env.NODE_ENV !== "production" && !Object.is(value, validated)) {
169
+ if (IS_DEV && !this.skipSameValueCheck && !Object.is(value, validated)) {
165
170
  throw new ValidationError("Validator functions must return the same value they were passed");
166
171
  }
167
172
  return validated;
@@ -322,7 +327,9 @@ class Validator {
322
327
  return knownGoodValue;
323
328
  }
324
329
  return otherValidationFn(validated);
325
- }
330
+ },
331
+ true
332
+ // skipSameValueCheck: refine is designed to transform values
326
333
  );
327
334
  }
328
335
  check(nameOrCheckFn, checkFn) {
@@ -350,11 +357,25 @@ class ArrayOfValidator extends Validator {
350
357
  (value) => {
351
358
  const arr = array.validate(value);
352
359
  for (let i = 0; i < arr.length; i++) {
353
- prefixError(i, () => itemValidator.validate(arr[i]));
360
+ if (IS_DEV) {
361
+ prefixError(i, () => itemValidator.validate(arr[i]));
362
+ } else {
363
+ try {
364
+ itemValidator.validate(arr[i]);
365
+ } catch (err) {
366
+ if (err instanceof ValidationError) {
367
+ throw new ValidationError(err.rawMessage, [i, ...err.path]);
368
+ }
369
+ throw new ValidationError(err.toString(), [i]);
370
+ }
371
+ }
354
372
  }
355
373
  return arr;
356
374
  },
357
375
  (knownGoodValue, newValue) => {
376
+ if (Object.is(knownGoodValue, newValue)) {
377
+ return knownGoodValue;
378
+ }
358
379
  if (!itemValidator.validateUsingKnownGoodVersion) return this.validate(newValue);
359
380
  const arr = array.validate(newValue);
360
381
  let isDifferent = knownGoodValue.length !== arr.length;
@@ -362,18 +383,46 @@ class ArrayOfValidator extends Validator {
362
383
  const item = arr[i];
363
384
  if (i >= knownGoodValue.length) {
364
385
  isDifferent = true;
365
- prefixError(i, () => itemValidator.validate(item));
386
+ if (IS_DEV) {
387
+ prefixError(i, () => itemValidator.validate(item));
388
+ } else {
389
+ try {
390
+ itemValidator.validate(item);
391
+ } catch (err) {
392
+ if (err instanceof ValidationError) {
393
+ throw new ValidationError(err.rawMessage, [i, ...err.path]);
394
+ }
395
+ throw new ValidationError(err.toString(), [i]);
396
+ }
397
+ }
366
398
  continue;
367
399
  }
368
400
  if (Object.is(knownGoodValue[i], item)) {
369
401
  continue;
370
402
  }
371
- const checkedItem = prefixError(
372
- i,
373
- () => itemValidator.validateUsingKnownGoodVersion(knownGoodValue[i], item)
374
- );
375
- if (!Object.is(checkedItem, knownGoodValue[i])) {
376
- isDifferent = true;
403
+ if (IS_DEV) {
404
+ const checkedItem = prefixError(
405
+ i,
406
+ () => itemValidator.validateUsingKnownGoodVersion(knownGoodValue[i], item)
407
+ );
408
+ if (!Object.is(checkedItem, knownGoodValue[i])) {
409
+ isDifferent = true;
410
+ }
411
+ } else {
412
+ try {
413
+ const checkedItem = itemValidator.validateUsingKnownGoodVersion(
414
+ knownGoodValue[i],
415
+ item
416
+ );
417
+ if (!Object.is(checkedItem, knownGoodValue[i])) {
418
+ isDifferent = true;
419
+ }
420
+ } catch (err) {
421
+ if (err instanceof ValidationError) {
422
+ throw new ValidationError(err.rawMessage, [i, ...err.path]);
423
+ }
424
+ throw new ValidationError(err.toString(), [i]);
425
+ }
377
426
  }
378
427
  }
379
428
  return isDifferent ? newValue : knownGoodValue;
@@ -433,11 +482,25 @@ class ObjectValidator extends Validator {
433
482
  if (typeof object2 !== "object" || object2 === null) {
434
483
  throw new ValidationError(`Expected object, got ${typeToString(object2)}`);
435
484
  }
436
- for (const [key, validator] of Object.entries(config)) {
437
- prefixError(key, () => {
438
- ;
439
- validator.validate((0, import_utils.getOwnProperty)(object2, key));
440
- });
485
+ for (const key in config) {
486
+ if (!(0, import_utils.hasOwnProperty)(config, key)) continue;
487
+ const validator = config[key];
488
+ if (IS_DEV) {
489
+ prefixError(key, () => {
490
+ ;
491
+ validator.validate((0, import_utils.getOwnProperty)(object2, key));
492
+ });
493
+ } else {
494
+ try {
495
+ ;
496
+ validator.validate((0, import_utils.getOwnProperty)(object2, key));
497
+ } catch (err) {
498
+ if (err instanceof ValidationError) {
499
+ throw new ValidationError(err.rawMessage, [key, ...err.path]);
500
+ }
501
+ throw new ValidationError(err.toString(), [key]);
502
+ }
503
+ }
441
504
  }
442
505
  if (!shouldAllowUnknownProperties) {
443
506
  for (const key of Object.keys(object2)) {
@@ -449,26 +512,46 @@ class ObjectValidator extends Validator {
449
512
  return object2;
450
513
  },
451
514
  (knownGoodValue, newValue) => {
515
+ if (Object.is(knownGoodValue, newValue)) {
516
+ return knownGoodValue;
517
+ }
452
518
  if (typeof newValue !== "object" || newValue === null) {
453
519
  throw new ValidationError(`Expected object, got ${typeToString(newValue)}`);
454
520
  }
455
521
  let isDifferent = false;
456
- for (const [key, validator] of Object.entries(config)) {
522
+ for (const key in config) {
523
+ if (!(0, import_utils.hasOwnProperty)(config, key)) continue;
524
+ const validator = config[key];
457
525
  const prev = (0, import_utils.getOwnProperty)(knownGoodValue, key);
458
526
  const next = (0, import_utils.getOwnProperty)(newValue, key);
459
527
  if (Object.is(prev, next)) {
460
528
  continue;
461
529
  }
462
- const checked = prefixError(key, () => {
463
- const validatable = validator;
464
- if (validatable.validateUsingKnownGoodVersion) {
465
- return validatable.validateUsingKnownGoodVersion(prev, next);
466
- } else {
467
- return validatable.validate(next);
530
+ if (IS_DEV) {
531
+ const checked = prefixError(key, () => {
532
+ const validatable = validator;
533
+ if (validatable.validateUsingKnownGoodVersion) {
534
+ return validatable.validateUsingKnownGoodVersion(prev, next);
535
+ } else {
536
+ return validatable.validate(next);
537
+ }
538
+ });
539
+ if (!Object.is(checked, prev)) {
540
+ isDifferent = true;
541
+ }
542
+ } else {
543
+ try {
544
+ const validatable = validator;
545
+ const checked = validatable.validateUsingKnownGoodVersion ? validatable.validateUsingKnownGoodVersion(prev, next) : validatable.validate(next);
546
+ if (!Object.is(checked, prev)) {
547
+ isDifferent = true;
548
+ }
549
+ } catch (err) {
550
+ if (err instanceof ValidationError) {
551
+ throw new ValidationError(err.rawMessage, [key, ...err.path]);
552
+ }
553
+ throw new ValidationError(err.toString(), [key]);
468
554
  }
469
- });
470
- if (!Object.is(checked, prev)) {
471
- isDifferent = true;
472
555
  }
473
556
  }
474
557
  if (!shouldAllowUnknownProperties) {
@@ -576,8 +659,13 @@ class UnionValidator extends Validator {
576
659
  throw new ValidationError(
577
660
  `Expected a string for key "${this.key}", got ${typeToString(variant)}`
578
661
  );
579
- } else if (this.useNumberKeys && !Number.isFinite(Number(variant))) {
580
- throw new ValidationError(`Expected a number for key "${this.key}", got "${variant}"`);
662
+ } else if (this.useNumberKeys) {
663
+ const numVariant = Number(variant);
664
+ if (numVariant - numVariant !== 0) {
665
+ throw new ValidationError(
666
+ `Expected a number for key "${this.key}", got "${variant}"`
667
+ );
668
+ }
581
669
  }
582
670
  const matchingSchema = (0, import_utils.hasOwnProperty)(this.config, variant) ? this.config[variant] : void 0;
583
671
  return { matchingSchema, variant };
@@ -613,11 +701,24 @@ class DictValidator extends Validator {
613
701
  if (typeof object2 !== "object" || object2 === null) {
614
702
  throw new ValidationError(`Expected object, got ${typeToString(object2)}`);
615
703
  }
616
- for (const [key, value] of Object.entries(object2)) {
617
- prefixError(key, () => {
618
- keyValidator.validate(key);
619
- valueValidator.validate(value);
620
- });
704
+ for (const key in object2) {
705
+ if (!(0, import_utils.hasOwnProperty)(object2, key)) continue;
706
+ if (IS_DEV) {
707
+ prefixError(key, () => {
708
+ keyValidator.validate(key);
709
+ valueValidator.validate(object2[key]);
710
+ });
711
+ } else {
712
+ try {
713
+ keyValidator.validate(key);
714
+ valueValidator.validate(object2[key]);
715
+ } catch (err) {
716
+ if (err instanceof ValidationError) {
717
+ throw new ValidationError(err.rawMessage, [key, ...err.path]);
718
+ }
719
+ throw new ValidationError(err.toString(), [key]);
720
+ }
721
+ }
621
722
  }
622
723
  return object2;
623
724
  },
@@ -625,36 +726,71 @@ class DictValidator extends Validator {
625
726
  if (typeof newValue !== "object" || newValue === null) {
626
727
  throw new ValidationError(`Expected object, got ${typeToString(newValue)}`);
627
728
  }
729
+ const newObj = newValue;
628
730
  let isDifferent = false;
629
- for (const [key, value] of Object.entries(newValue)) {
731
+ let newKeyCount = 0;
732
+ for (const key in newObj) {
733
+ if (!(0, import_utils.hasOwnProperty)(newObj, key)) continue;
734
+ newKeyCount++;
735
+ const next = newObj[key];
630
736
  if (!(0, import_utils.hasOwnProperty)(knownGoodValue, key)) {
631
737
  isDifferent = true;
632
- prefixError(key, () => {
633
- keyValidator.validate(key);
634
- valueValidator.validate(value);
635
- });
738
+ if (IS_DEV) {
739
+ prefixError(key, () => {
740
+ keyValidator.validate(key);
741
+ valueValidator.validate(next);
742
+ });
743
+ } else {
744
+ try {
745
+ keyValidator.validate(key);
746
+ valueValidator.validate(next);
747
+ } catch (err) {
748
+ if (err instanceof ValidationError) {
749
+ throw new ValidationError(err.rawMessage, [key, ...err.path]);
750
+ }
751
+ throw new ValidationError(err.toString(), [key]);
752
+ }
753
+ }
636
754
  continue;
637
755
  }
638
- const prev = (0, import_utils.getOwnProperty)(knownGoodValue, key);
639
- const next = value;
756
+ const prev = knownGoodValue[key];
640
757
  if (Object.is(prev, next)) {
641
758
  continue;
642
759
  }
643
- const checked = prefixError(key, () => {
644
- if (valueValidator.validateUsingKnownGoodVersion) {
645
- return valueValidator.validateUsingKnownGoodVersion(prev, next);
646
- } else {
647
- return valueValidator.validate(next);
760
+ if (IS_DEV) {
761
+ const checked = prefixError(key, () => {
762
+ if (valueValidator.validateUsingKnownGoodVersion) {
763
+ return valueValidator.validateUsingKnownGoodVersion(prev, next);
764
+ } else {
765
+ return valueValidator.validate(next);
766
+ }
767
+ });
768
+ if (!Object.is(checked, prev)) {
769
+ isDifferent = true;
770
+ }
771
+ } else {
772
+ try {
773
+ const checked = valueValidator.validateUsingKnownGoodVersion ? valueValidator.validateUsingKnownGoodVersion(prev, next) : valueValidator.validate(next);
774
+ if (!Object.is(checked, prev)) {
775
+ isDifferent = true;
776
+ }
777
+ } catch (err) {
778
+ if (err instanceof ValidationError) {
779
+ throw new ValidationError(err.rawMessage, [key, ...err.path]);
780
+ }
781
+ throw new ValidationError(err.toString(), [key]);
648
782
  }
649
- });
650
- if (!Object.is(checked, prev)) {
651
- isDifferent = true;
652
783
  }
653
784
  }
654
- for (const key of Object.keys(knownGoodValue)) {
655
- if (!(0, import_utils.hasOwnProperty)(newValue, key)) {
785
+ if (!isDifferent) {
786
+ let oldKeyCount = 0;
787
+ for (const key in knownGoodValue) {
788
+ if ((0, import_utils.hasOwnProperty)(knownGoodValue, key)) {
789
+ oldKeyCount++;
790
+ }
791
+ }
792
+ if (oldKeyCount !== newKeyCount) {
656
793
  isDifferent = true;
657
- break;
658
794
  }
659
795
  }
660
796
  return isDifferent ? newValue : knownGoodValue;
@@ -675,28 +811,125 @@ function typeofValidator(type) {
675
811
  const unknown = new Validator((value) => value);
676
812
  const any = new Validator((value) => value);
677
813
  const string = typeofValidator("string");
678
- const number = typeofValidator("number").check((number2) => {
679
- if (Number.isNaN(number2)) {
814
+ const number = new Validator((value) => {
815
+ if (Number.isFinite(value)) {
816
+ return value;
817
+ }
818
+ if (typeof value !== "number") {
819
+ throw new ValidationError(`Expected number, got ${typeToString(value)}`);
820
+ }
821
+ if (value !== value) {
680
822
  throw new ValidationError("Expected a number, got NaN");
681
823
  }
682
- if (!Number.isFinite(number2)) {
683
- throw new ValidationError(`Expected a finite number, got ${number2}`);
824
+ throw new ValidationError(`Expected a finite number, got ${value}`);
825
+ });
826
+ const positiveNumber = new Validator((value) => {
827
+ if (Number.isFinite(value) && value >= 0) {
828
+ return value;
829
+ }
830
+ if (typeof value !== "number") {
831
+ throw new ValidationError(`Expected number, got ${typeToString(value)}`);
832
+ }
833
+ if (value !== value) {
834
+ throw new ValidationError("Expected a number, got NaN");
684
835
  }
836
+ if (value < 0) {
837
+ throw new ValidationError(`Expected a positive number, got ${value}`);
838
+ }
839
+ throw new ValidationError(`Expected a finite number, got ${value}`);
685
840
  });
686
- const positiveNumber = number.check((value) => {
687
- if (value < 0) throw new ValidationError(`Expected a positive number, got ${value}`);
841
+ const nonZeroNumber = new Validator((value) => {
842
+ if (Number.isFinite(value) && value > 0) {
843
+ return value;
844
+ }
845
+ if (typeof value !== "number") {
846
+ throw new ValidationError(`Expected number, got ${typeToString(value)}`);
847
+ }
848
+ if (value !== value) {
849
+ throw new ValidationError("Expected a number, got NaN");
850
+ }
851
+ if (value <= 0) {
852
+ throw new ValidationError(`Expected a non-zero positive number, got ${value}`);
853
+ }
854
+ throw new ValidationError(`Expected a finite number, got ${value}`);
855
+ });
856
+ const nonZeroFiniteNumber = new Validator((value) => {
857
+ if (Number.isFinite(value) && value !== 0) {
858
+ return value;
859
+ }
860
+ if (typeof value !== "number") {
861
+ throw new ValidationError(`Expected number, got ${typeToString(value)}`);
862
+ }
863
+ if (value !== value) {
864
+ throw new ValidationError("Expected a number, got NaN");
865
+ }
866
+ if (value === 0) {
867
+ throw new ValidationError(`Expected a non-zero number, got 0`);
868
+ }
869
+ throw new ValidationError(`Expected a finite number, got ${value}`);
688
870
  });
689
- const nonZeroNumber = number.check((value) => {
690
- if (value <= 0) throw new ValidationError(`Expected a non-zero positive number, got ${value}`);
871
+ const unitInterval = new Validator((value) => {
872
+ if (Number.isFinite(value) && value >= 0 && value <= 1) {
873
+ return value;
874
+ }
875
+ if (typeof value !== "number") {
876
+ throw new ValidationError(`Expected number, got ${typeToString(value)}`);
877
+ }
878
+ if (value !== value) {
879
+ throw new ValidationError("Expected a number, got NaN");
880
+ }
881
+ throw new ValidationError(`Expected a number between 0 and 1, got ${value}`);
691
882
  });
692
- const integer = number.check((value) => {
693
- if (!Number.isInteger(value)) throw new ValidationError(`Expected an integer, got ${value}`);
883
+ const integer = new Validator((value) => {
884
+ if (Number.isInteger(value)) {
885
+ return value;
886
+ }
887
+ if (typeof value !== "number") {
888
+ throw new ValidationError(`Expected number, got ${typeToString(value)}`);
889
+ }
890
+ if (value !== value) {
891
+ throw new ValidationError("Expected a number, got NaN");
892
+ }
893
+ if (value - value !== 0) {
894
+ throw new ValidationError(`Expected a finite number, got ${value}`);
895
+ }
896
+ throw new ValidationError(`Expected an integer, got ${value}`);
694
897
  });
695
- const positiveInteger = integer.check((value) => {
696
- if (value < 0) throw new ValidationError(`Expected a positive integer, got ${value}`);
898
+ const positiveInteger = new Validator((value) => {
899
+ if (Number.isInteger(value) && value >= 0) {
900
+ return value;
901
+ }
902
+ if (typeof value !== "number") {
903
+ throw new ValidationError(`Expected number, got ${typeToString(value)}`);
904
+ }
905
+ if (value !== value) {
906
+ throw new ValidationError("Expected a number, got NaN");
907
+ }
908
+ if (value - value !== 0) {
909
+ throw new ValidationError(`Expected a finite number, got ${value}`);
910
+ }
911
+ if (value < 0) {
912
+ throw new ValidationError(`Expected a positive integer, got ${value}`);
913
+ }
914
+ throw new ValidationError(`Expected an integer, got ${value}`);
697
915
  });
698
- const nonZeroInteger = integer.check((value) => {
699
- if (value <= 0) throw new ValidationError(`Expected a non-zero positive integer, got ${value}`);
916
+ const nonZeroInteger = new Validator((value) => {
917
+ if (Number.isInteger(value) && value > 0) {
918
+ return value;
919
+ }
920
+ if (typeof value !== "number") {
921
+ throw new ValidationError(`Expected number, got ${typeToString(value)}`);
922
+ }
923
+ if (value !== value) {
924
+ throw new ValidationError("Expected a number, got NaN");
925
+ }
926
+ if (value - value !== 0) {
927
+ throw new ValidationError(`Expected a finite number, got ${value}`);
928
+ }
929
+ if (value <= 0) {
930
+ throw new ValidationError(`Expected a non-zero positive integer, got ${value}`);
931
+ }
932
+ throw new ValidationError(`Expected an integer, got ${value}`);
700
933
  });
701
934
  const boolean = typeofValidator("boolean");
702
935
  const bigint = typeofValidator("bigint");
@@ -862,13 +1095,14 @@ function optional(validator) {
862
1095
  return validator.validate(value);
863
1096
  },
864
1097
  (knownGoodValue, newValue) => {
865
- if (knownGoodValue === void 0 && newValue === void 0) return void 0;
866
1098
  if (newValue === void 0) return void 0;
867
1099
  if (validator.validateUsingKnownGoodVersion && knownGoodValue !== void 0) {
868
1100
  return validator.validateUsingKnownGoodVersion(knownGoodValue, newValue);
869
1101
  }
870
1102
  return validator.validate(newValue);
871
- }
1103
+ },
1104
+ // Propagate skipSameValueCheck from inner validator to allow refine wrappers
1105
+ validator instanceof Validator && validator.skipSameValueCheck
872
1106
  );
873
1107
  }
874
1108
  function nullable(validator) {
@@ -883,7 +1117,9 @@ function nullable(validator) {
883
1117
  return validator.validateUsingKnownGoodVersion(knownGoodValue, newValue);
884
1118
  }
885
1119
  return validator.validate(newValue);
886
- }
1120
+ },
1121
+ // Propagate skipSameValueCheck from inner validator to allow refine wrappers
1122
+ validator instanceof Validator && validator.skipSameValueCheck
887
1123
  );
888
1124
  }
889
1125
  function literalEnum(...values) {