@tldraw/validate 4.2.1 → 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.
@@ -5,6 +5,7 @@ import {
5
5
  hasOwnProperty,
6
6
  validateIndexKey
7
7
  } from "@tldraw/utils";
8
+ const IS_DEV = process.env.NODE_ENV !== "production";
8
9
  function formatPath(path) {
9
10
  if (!path.length) {
10
11
  return null;
@@ -81,10 +82,12 @@ class Validator {
81
82
  *
82
83
  * validationFn - Function that validates and returns a value of type T
83
84
  * validateUsingKnownGoodVersionFn - Optional performance-optimized validation function
85
+ * skipSameValueCheck - Internal flag to skip dev check for validators that transform values
84
86
  */
85
- constructor(validationFn, validateUsingKnownGoodVersionFn) {
87
+ constructor(validationFn, validateUsingKnownGoodVersionFn, skipSameValueCheck = false) {
86
88
  this.validationFn = validationFn;
87
89
  this.validateUsingKnownGoodVersionFn = validateUsingKnownGoodVersionFn;
90
+ this.skipSameValueCheck = skipSameValueCheck;
88
91
  }
89
92
  /**
90
93
  * Validates an unknown value and returns it with the correct type. The returned value is
@@ -108,7 +111,7 @@ class Validator {
108
111
  */
109
112
  validate(value) {
110
113
  const validated = this.validationFn(value);
111
- if (process.env.NODE_ENV !== "production" && !Object.is(value, validated)) {
114
+ if (IS_DEV && !this.skipSameValueCheck && !Object.is(value, validated)) {
112
115
  throw new ValidationError("Validator functions must return the same value they were passed");
113
116
  }
114
117
  return validated;
@@ -269,7 +272,9 @@ class Validator {
269
272
  return knownGoodValue;
270
273
  }
271
274
  return otherValidationFn(validated);
272
- }
275
+ },
276
+ true
277
+ // skipSameValueCheck: refine is designed to transform values
273
278
  );
274
279
  }
275
280
  check(nameOrCheckFn, checkFn) {
@@ -297,11 +302,25 @@ class ArrayOfValidator extends Validator {
297
302
  (value) => {
298
303
  const arr = array.validate(value);
299
304
  for (let i = 0; i < arr.length; i++) {
300
- prefixError(i, () => itemValidator.validate(arr[i]));
305
+ if (IS_DEV) {
306
+ prefixError(i, () => itemValidator.validate(arr[i]));
307
+ } else {
308
+ try {
309
+ itemValidator.validate(arr[i]);
310
+ } catch (err) {
311
+ if (err instanceof ValidationError) {
312
+ throw new ValidationError(err.rawMessage, [i, ...err.path]);
313
+ }
314
+ throw new ValidationError(err.toString(), [i]);
315
+ }
316
+ }
301
317
  }
302
318
  return arr;
303
319
  },
304
320
  (knownGoodValue, newValue) => {
321
+ if (Object.is(knownGoodValue, newValue)) {
322
+ return knownGoodValue;
323
+ }
305
324
  if (!itemValidator.validateUsingKnownGoodVersion) return this.validate(newValue);
306
325
  const arr = array.validate(newValue);
307
326
  let isDifferent = knownGoodValue.length !== arr.length;
@@ -309,18 +328,46 @@ class ArrayOfValidator extends Validator {
309
328
  const item = arr[i];
310
329
  if (i >= knownGoodValue.length) {
311
330
  isDifferent = true;
312
- prefixError(i, () => itemValidator.validate(item));
331
+ if (IS_DEV) {
332
+ prefixError(i, () => itemValidator.validate(item));
333
+ } else {
334
+ try {
335
+ itemValidator.validate(item);
336
+ } catch (err) {
337
+ if (err instanceof ValidationError) {
338
+ throw new ValidationError(err.rawMessage, [i, ...err.path]);
339
+ }
340
+ throw new ValidationError(err.toString(), [i]);
341
+ }
342
+ }
313
343
  continue;
314
344
  }
315
345
  if (Object.is(knownGoodValue[i], item)) {
316
346
  continue;
317
347
  }
318
- const checkedItem = prefixError(
319
- i,
320
- () => itemValidator.validateUsingKnownGoodVersion(knownGoodValue[i], item)
321
- );
322
- if (!Object.is(checkedItem, knownGoodValue[i])) {
323
- isDifferent = true;
348
+ if (IS_DEV) {
349
+ const checkedItem = prefixError(
350
+ i,
351
+ () => itemValidator.validateUsingKnownGoodVersion(knownGoodValue[i], item)
352
+ );
353
+ if (!Object.is(checkedItem, knownGoodValue[i])) {
354
+ isDifferent = true;
355
+ }
356
+ } else {
357
+ try {
358
+ const checkedItem = itemValidator.validateUsingKnownGoodVersion(
359
+ knownGoodValue[i],
360
+ item
361
+ );
362
+ if (!Object.is(checkedItem, knownGoodValue[i])) {
363
+ isDifferent = true;
364
+ }
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
+ }
324
371
  }
325
372
  }
326
373
  return isDifferent ? newValue : knownGoodValue;
@@ -380,11 +427,25 @@ class ObjectValidator extends Validator {
380
427
  if (typeof object2 !== "object" || object2 === null) {
381
428
  throw new ValidationError(`Expected object, got ${typeToString(object2)}`);
382
429
  }
383
- for (const [key, validator] of Object.entries(config)) {
384
- prefixError(key, () => {
385
- ;
386
- validator.validate(getOwnProperty(object2, key));
387
- });
430
+ for (const key in config) {
431
+ if (!hasOwnProperty(config, key)) continue;
432
+ const validator = config[key];
433
+ if (IS_DEV) {
434
+ prefixError(key, () => {
435
+ ;
436
+ validator.validate(getOwnProperty(object2, key));
437
+ });
438
+ } else {
439
+ try {
440
+ ;
441
+ validator.validate(getOwnProperty(object2, key));
442
+ } catch (err) {
443
+ if (err instanceof ValidationError) {
444
+ throw new ValidationError(err.rawMessage, [key, ...err.path]);
445
+ }
446
+ throw new ValidationError(err.toString(), [key]);
447
+ }
448
+ }
388
449
  }
389
450
  if (!shouldAllowUnknownProperties) {
390
451
  for (const key of Object.keys(object2)) {
@@ -396,26 +457,46 @@ class ObjectValidator extends Validator {
396
457
  return object2;
397
458
  },
398
459
  (knownGoodValue, newValue) => {
460
+ if (Object.is(knownGoodValue, newValue)) {
461
+ return knownGoodValue;
462
+ }
399
463
  if (typeof newValue !== "object" || newValue === null) {
400
464
  throw new ValidationError(`Expected object, got ${typeToString(newValue)}`);
401
465
  }
402
466
  let isDifferent = false;
403
- for (const [key, validator] of Object.entries(config)) {
467
+ for (const key in config) {
468
+ if (!hasOwnProperty(config, key)) continue;
469
+ const validator = config[key];
404
470
  const prev = getOwnProperty(knownGoodValue, key);
405
471
  const next = getOwnProperty(newValue, key);
406
472
  if (Object.is(prev, next)) {
407
473
  continue;
408
474
  }
409
- const checked = prefixError(key, () => {
410
- const validatable = validator;
411
- if (validatable.validateUsingKnownGoodVersion) {
412
- return validatable.validateUsingKnownGoodVersion(prev, next);
413
- } else {
414
- return validatable.validate(next);
475
+ if (IS_DEV) {
476
+ const checked = prefixError(key, () => {
477
+ const validatable = validator;
478
+ if (validatable.validateUsingKnownGoodVersion) {
479
+ return validatable.validateUsingKnownGoodVersion(prev, next);
480
+ } else {
481
+ return validatable.validate(next);
482
+ }
483
+ });
484
+ if (!Object.is(checked, prev)) {
485
+ isDifferent = true;
486
+ }
487
+ } else {
488
+ try {
489
+ const validatable = validator;
490
+ const checked = validatable.validateUsingKnownGoodVersion ? validatable.validateUsingKnownGoodVersion(prev, next) : validatable.validate(next);
491
+ if (!Object.is(checked, prev)) {
492
+ isDifferent = true;
493
+ }
494
+ } catch (err) {
495
+ if (err instanceof ValidationError) {
496
+ throw new ValidationError(err.rawMessage, [key, ...err.path]);
497
+ }
498
+ throw new ValidationError(err.toString(), [key]);
415
499
  }
416
- });
417
- if (!Object.is(checked, prev)) {
418
- isDifferent = true;
419
500
  }
420
501
  }
421
502
  if (!shouldAllowUnknownProperties) {
@@ -523,8 +604,13 @@ class UnionValidator extends Validator {
523
604
  throw new ValidationError(
524
605
  `Expected a string for key "${this.key}", got ${typeToString(variant)}`
525
606
  );
526
- } else if (this.useNumberKeys && !Number.isFinite(Number(variant))) {
527
- throw new ValidationError(`Expected a number for key "${this.key}", got "${variant}"`);
607
+ } else if (this.useNumberKeys) {
608
+ const numVariant = Number(variant);
609
+ if (numVariant - numVariant !== 0) {
610
+ throw new ValidationError(
611
+ `Expected a number for key "${this.key}", got "${variant}"`
612
+ );
613
+ }
528
614
  }
529
615
  const matchingSchema = hasOwnProperty(this.config, variant) ? this.config[variant] : void 0;
530
616
  return { matchingSchema, variant };
@@ -560,11 +646,24 @@ class DictValidator extends Validator {
560
646
  if (typeof object2 !== "object" || object2 === null) {
561
647
  throw new ValidationError(`Expected object, got ${typeToString(object2)}`);
562
648
  }
563
- for (const [key, value] of Object.entries(object2)) {
564
- prefixError(key, () => {
565
- keyValidator.validate(key);
566
- valueValidator.validate(value);
567
- });
649
+ for (const key in object2) {
650
+ if (!hasOwnProperty(object2, key)) continue;
651
+ if (IS_DEV) {
652
+ prefixError(key, () => {
653
+ keyValidator.validate(key);
654
+ valueValidator.validate(object2[key]);
655
+ });
656
+ } else {
657
+ try {
658
+ keyValidator.validate(key);
659
+ valueValidator.validate(object2[key]);
660
+ } catch (err) {
661
+ if (err instanceof ValidationError) {
662
+ throw new ValidationError(err.rawMessage, [key, ...err.path]);
663
+ }
664
+ throw new ValidationError(err.toString(), [key]);
665
+ }
666
+ }
568
667
  }
569
668
  return object2;
570
669
  },
@@ -572,36 +671,71 @@ class DictValidator extends Validator {
572
671
  if (typeof newValue !== "object" || newValue === null) {
573
672
  throw new ValidationError(`Expected object, got ${typeToString(newValue)}`);
574
673
  }
674
+ const newObj = newValue;
575
675
  let isDifferent = false;
576
- for (const [key, value] of Object.entries(newValue)) {
676
+ let newKeyCount = 0;
677
+ for (const key in newObj) {
678
+ if (!hasOwnProperty(newObj, key)) continue;
679
+ newKeyCount++;
680
+ const next = newObj[key];
577
681
  if (!hasOwnProperty(knownGoodValue, key)) {
578
682
  isDifferent = true;
579
- prefixError(key, () => {
580
- keyValidator.validate(key);
581
- valueValidator.validate(value);
582
- });
683
+ if (IS_DEV) {
684
+ prefixError(key, () => {
685
+ keyValidator.validate(key);
686
+ valueValidator.validate(next);
687
+ });
688
+ } else {
689
+ try {
690
+ keyValidator.validate(key);
691
+ valueValidator.validate(next);
692
+ } catch (err) {
693
+ if (err instanceof ValidationError) {
694
+ throw new ValidationError(err.rawMessage, [key, ...err.path]);
695
+ }
696
+ throw new ValidationError(err.toString(), [key]);
697
+ }
698
+ }
583
699
  continue;
584
700
  }
585
- const prev = getOwnProperty(knownGoodValue, key);
586
- const next = value;
701
+ const prev = knownGoodValue[key];
587
702
  if (Object.is(prev, next)) {
588
703
  continue;
589
704
  }
590
- const checked = prefixError(key, () => {
591
- if (valueValidator.validateUsingKnownGoodVersion) {
592
- return valueValidator.validateUsingKnownGoodVersion(prev, next);
593
- } else {
594
- return valueValidator.validate(next);
705
+ if (IS_DEV) {
706
+ const checked = prefixError(key, () => {
707
+ if (valueValidator.validateUsingKnownGoodVersion) {
708
+ return valueValidator.validateUsingKnownGoodVersion(prev, next);
709
+ } else {
710
+ return valueValidator.validate(next);
711
+ }
712
+ });
713
+ if (!Object.is(checked, prev)) {
714
+ isDifferent = true;
715
+ }
716
+ } else {
717
+ try {
718
+ const checked = valueValidator.validateUsingKnownGoodVersion ? valueValidator.validateUsingKnownGoodVersion(prev, next) : valueValidator.validate(next);
719
+ if (!Object.is(checked, prev)) {
720
+ isDifferent = true;
721
+ }
722
+ } catch (err) {
723
+ if (err instanceof ValidationError) {
724
+ throw new ValidationError(err.rawMessage, [key, ...err.path]);
725
+ }
726
+ throw new ValidationError(err.toString(), [key]);
595
727
  }
596
- });
597
- if (!Object.is(checked, prev)) {
598
- isDifferent = true;
599
728
  }
600
729
  }
601
- for (const key of Object.keys(knownGoodValue)) {
602
- if (!hasOwnProperty(newValue, key)) {
730
+ if (!isDifferent) {
731
+ let oldKeyCount = 0;
732
+ for (const key in knownGoodValue) {
733
+ if (hasOwnProperty(knownGoodValue, key)) {
734
+ oldKeyCount++;
735
+ }
736
+ }
737
+ if (oldKeyCount !== newKeyCount) {
603
738
  isDifferent = true;
604
- break;
605
739
  }
606
740
  }
607
741
  return isDifferent ? newValue : knownGoodValue;
@@ -622,28 +756,125 @@ function typeofValidator(type) {
622
756
  const unknown = new Validator((value) => value);
623
757
  const any = new Validator((value) => value);
624
758
  const string = typeofValidator("string");
625
- const number = typeofValidator("number").check((number2) => {
626
- if (Number.isNaN(number2)) {
759
+ const number = new Validator((value) => {
760
+ if (Number.isFinite(value)) {
761
+ return value;
762
+ }
763
+ if (typeof value !== "number") {
764
+ throw new ValidationError(`Expected number, got ${typeToString(value)}`);
765
+ }
766
+ if (value !== value) {
627
767
  throw new ValidationError("Expected a number, got NaN");
628
768
  }
629
- if (!Number.isFinite(number2)) {
630
- throw new ValidationError(`Expected a finite number, got ${number2}`);
769
+ throw new ValidationError(`Expected a finite number, got ${value}`);
770
+ });
771
+ const positiveNumber = new Validator((value) => {
772
+ if (Number.isFinite(value) && value >= 0) {
773
+ return value;
774
+ }
775
+ if (typeof value !== "number") {
776
+ throw new ValidationError(`Expected number, got ${typeToString(value)}`);
777
+ }
778
+ if (value !== value) {
779
+ throw new ValidationError("Expected a number, got NaN");
631
780
  }
781
+ if (value < 0) {
782
+ throw new ValidationError(`Expected a positive number, got ${value}`);
783
+ }
784
+ throw new ValidationError(`Expected a finite number, got ${value}`);
632
785
  });
633
- const positiveNumber = number.check((value) => {
634
- if (value < 0) throw new ValidationError(`Expected a positive number, got ${value}`);
786
+ const nonZeroNumber = new Validator((value) => {
787
+ if (Number.isFinite(value) && value > 0) {
788
+ return value;
789
+ }
790
+ if (typeof value !== "number") {
791
+ throw new ValidationError(`Expected number, got ${typeToString(value)}`);
792
+ }
793
+ if (value !== value) {
794
+ throw new ValidationError("Expected a number, got NaN");
795
+ }
796
+ if (value <= 0) {
797
+ throw new ValidationError(`Expected a non-zero positive number, got ${value}`);
798
+ }
799
+ throw new ValidationError(`Expected a finite number, got ${value}`);
800
+ });
801
+ const nonZeroFiniteNumber = new Validator((value) => {
802
+ if (Number.isFinite(value) && value !== 0) {
803
+ return value;
804
+ }
805
+ if (typeof value !== "number") {
806
+ throw new ValidationError(`Expected number, got ${typeToString(value)}`);
807
+ }
808
+ if (value !== value) {
809
+ throw new ValidationError("Expected a number, got NaN");
810
+ }
811
+ if (value === 0) {
812
+ throw new ValidationError(`Expected a non-zero number, got 0`);
813
+ }
814
+ throw new ValidationError(`Expected a finite number, got ${value}`);
635
815
  });
636
- const nonZeroNumber = number.check((value) => {
637
- if (value <= 0) throw new ValidationError(`Expected a non-zero positive number, got ${value}`);
816
+ const unitInterval = new Validator((value) => {
817
+ if (Number.isFinite(value) && value >= 0 && value <= 1) {
818
+ return value;
819
+ }
820
+ if (typeof value !== "number") {
821
+ throw new ValidationError(`Expected number, got ${typeToString(value)}`);
822
+ }
823
+ if (value !== value) {
824
+ throw new ValidationError("Expected a number, got NaN");
825
+ }
826
+ throw new ValidationError(`Expected a number between 0 and 1, got ${value}`);
638
827
  });
639
- const integer = number.check((value) => {
640
- if (!Number.isInteger(value)) throw new ValidationError(`Expected an integer, got ${value}`);
828
+ const integer = new Validator((value) => {
829
+ if (Number.isInteger(value)) {
830
+ return value;
831
+ }
832
+ if (typeof value !== "number") {
833
+ throw new ValidationError(`Expected number, got ${typeToString(value)}`);
834
+ }
835
+ if (value !== value) {
836
+ throw new ValidationError("Expected a number, got NaN");
837
+ }
838
+ if (value - value !== 0) {
839
+ throw new ValidationError(`Expected a finite number, got ${value}`);
840
+ }
841
+ throw new ValidationError(`Expected an integer, got ${value}`);
641
842
  });
642
- const positiveInteger = integer.check((value) => {
643
- if (value < 0) throw new ValidationError(`Expected a positive integer, got ${value}`);
843
+ const positiveInteger = new Validator((value) => {
844
+ if (Number.isInteger(value) && value >= 0) {
845
+ return value;
846
+ }
847
+ if (typeof value !== "number") {
848
+ throw new ValidationError(`Expected number, got ${typeToString(value)}`);
849
+ }
850
+ if (value !== value) {
851
+ throw new ValidationError("Expected a number, got NaN");
852
+ }
853
+ if (value - value !== 0) {
854
+ throw new ValidationError(`Expected a finite number, got ${value}`);
855
+ }
856
+ if (value < 0) {
857
+ throw new ValidationError(`Expected a positive integer, got ${value}`);
858
+ }
859
+ throw new ValidationError(`Expected an integer, got ${value}`);
644
860
  });
645
- const nonZeroInteger = integer.check((value) => {
646
- if (value <= 0) throw new ValidationError(`Expected a non-zero positive integer, got ${value}`);
861
+ const nonZeroInteger = new Validator((value) => {
862
+ if (Number.isInteger(value) && value > 0) {
863
+ return value;
864
+ }
865
+ if (typeof value !== "number") {
866
+ throw new ValidationError(`Expected number, got ${typeToString(value)}`);
867
+ }
868
+ if (value !== value) {
869
+ throw new ValidationError("Expected a number, got NaN");
870
+ }
871
+ if (value - value !== 0) {
872
+ throw new ValidationError(`Expected a finite number, got ${value}`);
873
+ }
874
+ if (value <= 0) {
875
+ throw new ValidationError(`Expected a non-zero positive integer, got ${value}`);
876
+ }
877
+ throw new ValidationError(`Expected an integer, got ${value}`);
647
878
  });
648
879
  const boolean = typeofValidator("boolean");
649
880
  const bigint = typeofValidator("bigint");
@@ -809,13 +1040,14 @@ function optional(validator) {
809
1040
  return validator.validate(value);
810
1041
  },
811
1042
  (knownGoodValue, newValue) => {
812
- if (knownGoodValue === void 0 && newValue === void 0) return void 0;
813
1043
  if (newValue === void 0) return void 0;
814
1044
  if (validator.validateUsingKnownGoodVersion && knownGoodValue !== void 0) {
815
1045
  return validator.validateUsingKnownGoodVersion(knownGoodValue, newValue);
816
1046
  }
817
1047
  return validator.validate(newValue);
818
- }
1048
+ },
1049
+ // Propagate skipSameValueCheck from inner validator to allow refine wrappers
1050
+ validator instanceof Validator && validator.skipSameValueCheck
819
1051
  );
820
1052
  }
821
1053
  function nullable(validator) {
@@ -830,7 +1062,9 @@ function nullable(validator) {
830
1062
  return validator.validateUsingKnownGoodVersion(knownGoodValue, newValue);
831
1063
  }
832
1064
  return validator.validate(newValue);
833
- }
1065
+ },
1066
+ // Propagate skipSameValueCheck from inner validator to allow refine wrappers
1067
+ validator instanceof Validator && validator.skipSameValueCheck
834
1068
  );
835
1069
  }
836
1070
  function literalEnum(...values) {
@@ -918,6 +1152,7 @@ export {
918
1152
  literal,
919
1153
  literalEnum,
920
1154
  model,
1155
+ nonZeroFiniteNumber,
921
1156
  nonZeroInteger,
922
1157
  nonZeroNumber,
923
1158
  nullable,
@@ -932,6 +1167,7 @@ export {
932
1167
  srcUrl,
933
1168
  string,
934
1169
  union,
1170
+ unitInterval,
935
1171
  unknown,
936
1172
  unknownObject
937
1173
  };