@evolu/common 8.10.0 → 8.11.0

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 (144) hide show
  1. package/dist/src/Config.d.ts +22 -22
  2. package/dist/src/Config.d.ts.map +1 -1
  3. package/dist/src/Console.d.ts +62 -7
  4. package/dist/src/Console.d.ts.map +1 -1
  5. package/dist/src/Console.js +20 -4
  6. package/dist/src/Crypto.d.ts +76 -4
  7. package/dist/src/Crypto.d.ts.map +1 -1
  8. package/dist/src/Crypto.js +55 -4
  9. package/dist/src/Error.d.ts +45 -0
  10. package/dist/src/Error.d.ts.map +1 -1
  11. package/dist/src/Error.js +69 -0
  12. package/dist/src/Fs.d.ts +92 -18
  13. package/dist/src/Fs.d.ts.map +1 -1
  14. package/dist/src/Fs.js +2 -0
  15. package/dist/src/Identicon.d.ts +2 -2
  16. package/dist/src/Identicon.js +2 -2
  17. package/dist/src/LeakDetector.d.ts +22 -3
  18. package/dist/src/LeakDetector.d.ts.map +1 -1
  19. package/dist/src/LeakDetector.js +12 -2
  20. package/dist/src/LockManager.d.ts +8 -0
  21. package/dist/src/LockManager.d.ts.map +1 -1
  22. package/dist/src/LockManager.js +6 -0
  23. package/dist/src/Object.d.ts.map +1 -1
  24. package/dist/src/Object.js +5 -0
  25. package/dist/src/Platform.d.ts +47 -7
  26. package/dist/src/Platform.d.ts.map +1 -1
  27. package/dist/src/Platform.js +24 -5
  28. package/dist/src/Random.d.ts +25 -2
  29. package/dist/src/Random.d.ts.map +1 -1
  30. package/dist/src/Random.js +14 -2
  31. package/dist/src/Resource.d.ts +156 -1
  32. package/dist/src/Resource.d.ts.map +1 -1
  33. package/dist/src/Resource.js +201 -72
  34. package/dist/src/Schedule.d.ts +11 -10
  35. package/dist/src/Schedule.d.ts.map +1 -1
  36. package/dist/src/Schedule.js +1 -1
  37. package/dist/src/Sqlite.d.ts +132 -16
  38. package/dist/src/Sqlite.d.ts.map +1 -1
  39. package/dist/src/Sqlite.js +63 -9
  40. package/dist/src/Task.d.ts +15 -4
  41. package/dist/src/Task.d.ts.map +1 -1
  42. package/dist/src/Task.js +41 -15
  43. package/dist/src/Test.d.ts +9 -0
  44. package/dist/src/Test.d.ts.map +1 -1
  45. package/dist/src/Test.js +4 -0
  46. package/dist/src/Time.d.ts +106 -9
  47. package/dist/src/Time.d.ts.map +1 -1
  48. package/dist/src/Time.js +55 -4
  49. package/dist/src/Type.d.ts +1455 -1310
  50. package/dist/src/Type.d.ts.map +1 -1
  51. package/dist/src/Type.js +1274 -517
  52. package/dist/src/WebSocket.d.ts +164 -13
  53. package/dist/src/WebSocket.d.ts.map +1 -1
  54. package/dist/src/WebSocket.js +133 -24
  55. package/dist/src/Worker.d.ts +90 -8
  56. package/dist/src/Worker.d.ts.map +1 -1
  57. package/dist/src/Worker.js +28 -2
  58. package/dist/src/index.d.ts +6 -7
  59. package/dist/src/index.d.ts.map +1 -1
  60. package/dist/src/index.js +2 -3
  61. package/dist/src/local-first/Db.d.ts +52 -3
  62. package/dist/src/local-first/Db.d.ts.map +1 -1
  63. package/dist/src/local-first/Db.js +412 -137
  64. package/dist/src/local-first/Evolu.d.ts +336 -211
  65. package/dist/src/local-first/Evolu.d.ts.map +1 -1
  66. package/dist/src/local-first/Evolu.js +102 -15
  67. package/dist/src/local-first/Owner.d.ts +13 -30
  68. package/dist/src/local-first/Owner.d.ts.map +1 -1
  69. package/dist/src/local-first/Owner.js +13 -30
  70. package/dist/src/local-first/Protocol.d.ts +94 -16
  71. package/dist/src/local-first/Protocol.d.ts.map +1 -1
  72. package/dist/src/local-first/Protocol.js +118 -38
  73. package/dist/src/local-first/Query.d.ts +8 -15
  74. package/dist/src/local-first/Query.d.ts.map +1 -1
  75. package/dist/src/local-first/Schema.d.ts +335 -21
  76. package/dist/src/local-first/Schema.d.ts.map +1 -1
  77. package/dist/src/local-first/Schema.js +214 -17
  78. package/dist/src/local-first/Shared.d.ts +537 -22
  79. package/dist/src/local-first/Shared.d.ts.map +1 -1
  80. package/dist/src/local-first/Shared.js +1437 -234
  81. package/dist/src/local-first/Storage.d.ts +192 -14
  82. package/dist/src/local-first/Storage.d.ts.map +1 -1
  83. package/dist/src/local-first/Storage.js +81 -20
  84. package/dist/src/local-first/Timestamp.d.ts +392 -41
  85. package/dist/src/local-first/Timestamp.d.ts.map +1 -1
  86. package/dist/src/local-first/Timestamp.js +403 -81
  87. package/dist/src/local-first/index.d.ts +0 -1
  88. package/dist/src/local-first/index.d.ts.map +1 -1
  89. package/dist/src/local-first/index.js +0 -1
  90. package/package.json +1 -1
  91. package/src/Assert.test.ts +2 -5
  92. package/src/Config.test.ts +2 -6
  93. package/src/Config.ts +133 -133
  94. package/src/Console.ts +62 -7
  95. package/src/Crypto.ts +76 -4
  96. package/src/Eq.test.ts +2 -3
  97. package/src/Error.test.ts +76 -3
  98. package/src/Error.ts +71 -0
  99. package/src/Fs.ts +92 -18
  100. package/src/Identicon.ts +2 -2
  101. package/src/LeakDetector.ts +22 -3
  102. package/src/LockManager.ts +8 -0
  103. package/src/Object.test.ts +27 -12
  104. package/src/Object.ts +5 -0
  105. package/src/Platform.ts +50 -8
  106. package/src/Random.ts +25 -2
  107. package/src/Resource.test.ts +837 -0
  108. package/src/Resource.ts +235 -15
  109. package/src/Schedule.test.ts +50 -12
  110. package/src/Schedule.ts +24 -14
  111. package/src/Sqlite.ts +137 -17
  112. package/src/Task.test.ts +189 -8
  113. package/src/Task.ts +56 -17
  114. package/src/Test.ts +9 -0
  115. package/src/Time.ts +106 -9
  116. package/src/Type.test.ts +946 -1028
  117. package/src/Type.ts +4195 -3136
  118. package/src/Types.test.ts +4 -14
  119. package/src/WebSocket.ts +313 -40
  120. package/src/Worker.ts +90 -8
  121. package/src/index.ts +15 -6
  122. package/src/local-first/Db.ts +644 -339
  123. package/src/local-first/Evolu.test.ts +686 -21
  124. package/src/local-first/Evolu.ts +450 -228
  125. package/src/local-first/Owner.ts +13 -30
  126. package/src/local-first/Protocol.test.ts +617 -10
  127. package/src/local-first/Protocol.ts +196 -72
  128. package/src/local-first/Query.ts +8 -15
  129. package/src/local-first/Schema.test.ts +143 -0
  130. package/src/local-first/Schema.ts +363 -24
  131. package/src/local-first/Shared.test.ts +7731 -559
  132. package/src/local-first/Shared.ts +2036 -267
  133. package/src/local-first/Storage.ts +218 -32
  134. package/src/local-first/Timestamp.test.ts +344 -70
  135. package/src/local-first/Timestamp.ts +434 -118
  136. package/src/local-first/index.ts +0 -1
  137. package/dist/src/local-first/Error.d.ts +0 -12
  138. package/dist/src/local-first/Error.d.ts.map +0 -1
  139. package/dist/src/local-first/Error.js +0 -6
  140. package/dist/src/local-first/LocalAuth.d.ts +0 -150
  141. package/dist/src/local-first/LocalAuth.d.ts.map +0 -1
  142. package/dist/src/local-first/LocalAuth.js +0 -179
  143. package/src/local-first/Error.ts +0 -17
  144. package/src/local-first/LocalAuth.ts +0 -457
package/dist/src/Type.js CHANGED
@@ -460,7 +460,7 @@ import { createMutableRecord, getObjectKind, isPlainObject, } from "./Object.js"
460
460
  import { hasNodeBuffer } from "./Platform.js";
461
461
  import { err, flatMapResult, getOk, getOrNull, ok, trySync, } from "./Result.js";
462
462
  import { safelyStringifyUnknownValue } from "./String.js";
463
- import { instance, isInstance, } from "./Types.js";
463
+ import { isInstance, } from "./Types.js";
464
464
  /**
465
465
  * Converts an error from a {@link Type} into formatted issues with paths.
466
466
  *
@@ -497,7 +497,7 @@ import { instance, isInstance, } from "./Types.js";
497
497
  */
498
498
  export const typeErrorToIssues = (type, error) => {
499
499
  const runtimeType = type;
500
- return runtimeType[getRuntimeTypeIssuesSymbol](error, "all").map((issue) => ({
500
+ return runtimeType[getRuntimeTypeIssuesSymbol](error, "all", []).map((issue) => ({
501
501
  path: issue.path,
502
502
  message: formatRuntimeTypeIssue(issue),
503
503
  }));
@@ -506,50 +506,76 @@ const outputValidationSymbol =
506
506
  /*#__PURE__*/ globalThis.Symbol();
507
507
  const getRuntimeTypeIssuesSymbol =
508
508
  /*#__PURE__*/ globalThis.Symbol();
509
- const singleRuntimeTypeIssue = (formatterName, error, defaultFormatter, path = []) => [
509
+ // Copying a short path in a loop is about twice as fast as spreading it.
510
+ const appendIssuePath = (path, key) => {
511
+ const issuePath = createMutableArray(path.length + 1);
512
+ for (let index = 0; index < path.length; index++) {
513
+ issuePath[index] = path[index];
514
+ }
515
+ issuePath[path.length] = key;
516
+ return issuePath;
517
+ };
518
+ const singleRuntimeTypeIssue = (formatterName, error, defaultFormatter, path) => [
510
519
  { name: formatterName, error, path, formatError: defaultFormatter },
511
520
  ];
512
- const prependRuntimeTypeIssuePath = (key, issues) => issues.map((issue) => ({
513
- ...issue,
514
- path: [key, ...issue.path],
515
- }));
516
- const createCollectionRuntimeTypeIssues = (name, issuesKind, defaultFormatter, getNestedType) => (error, mode) => {
521
+ const createCollectionRuntimeTypeIssues = (name, issuesKind, defaultFormatter, getNestedType) => (error, mode, path) => {
517
522
  const reason = error.reason;
518
523
  if (reason.kind !== issuesKind) {
519
- return singleRuntimeTypeIssue(name, error, defaultFormatter);
524
+ return singleRuntimeTypeIssue(name, error, defaultFormatter, path);
520
525
  }
521
526
  const allIssues = reason.issues;
522
- const issues = mode === "first" ? [allIssues[0]] : allIssues;
523
- return issues.flatMap((issue) => {
524
- const path = "key" in issue ? issue.key : issue.index;
525
- if (issue.error !== undefined) {
526
- return prependRuntimeTypeIssuePath(path, getNestedType(issue)[getRuntimeTypeIssuesSymbol](issue.error, mode));
527
+ const issueCount = mode === "first" ? 1 : allIssues.length;
528
+ const result = [];
529
+ for (let index = 0; index < issueCount; index++) {
530
+ const issue = allIssues[index];
531
+ const issuePath = appendIssuePath(path, "key" in issue ? issue.key : issue.index);
532
+ if (issue.error === undefined) {
533
+ result.push({
534
+ name,
535
+ error: mode === "first"
536
+ ? error
537
+ : {
538
+ type: name,
539
+ reason: { kind: issuesKind, issues: [issue] },
540
+ },
541
+ path: issuePath,
542
+ formatError: defaultFormatter,
543
+ });
544
+ continue;
527
545
  }
528
- return singleRuntimeTypeIssue(name, mode === "first"
529
- ? error
530
- : {
531
- type: name,
532
- reason: { kind: issuesKind, issues: [issue] },
533
- }, defaultFormatter, [path]);
534
- });
546
+ const nestedIssues = getNestedType(issue)[getRuntimeTypeIssuesSymbol](issue.error, mode, issuePath);
547
+ // Nested issues are a new array, so one visited issue returns them.
548
+ if (issueCount === 1)
549
+ return nestedIssues;
550
+ for (const nestedIssue of nestedIssues)
551
+ result.push(nestedIssue);
552
+ }
553
+ return result;
535
554
  };
536
555
  const formatDefaultRuntimeTypeIssue = (issue) => issue.formatError(issue.error);
537
556
  const formatRuntimeTypeIssue = (issue, formatIssue = formatDefaultRuntimeTypeIssue) => {
538
- const summary = formatIssue(issue);
557
+ let message = formatIssue(issue);
539
558
  if (issue.alternatives === undefined)
540
- return summary;
541
- return [
542
- summary,
543
- ...issue.alternatives.flatMap(({ index, name, issues }) => issues.map((issue) => {
544
- const path = issue.path
545
- .map((key) => typeof key === "string"
546
- ? `[${JSON.stringify(key)}]`
547
- : `[${globalThis.String(key)}]`)
548
- .join("");
549
- const message = formatRuntimeTypeIssue(issue, formatIssue).replaceAll("\n", "\n ");
550
- return `- ${index}: ${name}${path}: ${message}`;
551
- })),
552
- ].join("\n");
559
+ return message;
560
+ // Each alternative issue starts a "- " line; continuation lines of a
561
+ // multi-line message are indented.
562
+ for (const { index, name, issues } of issue.alternatives) {
563
+ for (const alternativeIssue of issues) {
564
+ let path = "";
565
+ for (const key of alternativeIssue.path) {
566
+ path +=
567
+ typeof key === "string"
568
+ ? `[${JSON.stringify(key)}]`
569
+ : `[${globalThis.String(key)}]`;
570
+ }
571
+ const alternativeMessage = formatRuntimeTypeIssue(alternativeIssue, formatIssue);
572
+ // replaceAll is slow even without a match.
573
+ message += `\n- ${index}: ${name}${path}: ${alternativeMessage.includes("\n")
574
+ ? alternativeMessage.replaceAll("\n", "\n ")
575
+ : alternativeMessage}`;
576
+ }
577
+ }
578
+ return message;
553
579
  };
554
580
  export function assertType(type, value) {
555
581
  if (type === undefined)
@@ -746,8 +772,12 @@ const withFormatError = (source, formatIssue, localizedTypeBySource) => {
746
772
  })),
747
773
  }),
748
774
  });
749
- const getTypeIssues = (error, mode) => source[getRuntimeTypeIssuesSymbol](error, mode).map(localizeIssue);
750
- const derived = createTypeNode(source.name, parent, source.fromUnknown, source.is, source[outputValidationSymbol], source[fromSymbol], source[encoderSymbol].parent ?? source[encoderSymbol], getTypeIssues, undefined, formatIssue);
775
+ const getTypeIssues = (error, mode, path) => source[getRuntimeTypeIssuesSymbol](error, mode, path).map(localizeIssue);
776
+ const derived = createTypeNode(source.name, parent, source.fromUnknown, source.is, source[outputValidationSymbol], source[fromSymbol], source[encoderSymbol].parent ?? source[encoderSymbol], getTypeIssues, {
777
+ formatIssue,
778
+ check: source[checkSymbol],
779
+ refinements: source[refinementsSymbol],
780
+ });
751
781
  // Localization changes error rendering, not conversion semantics. Preserve
752
782
  // specialized public input operations such as json's single-parse Json
753
783
  // boundary. Private operation chains on derived still support composition.
@@ -795,9 +825,17 @@ const localizeTypeReflection = (value, formatIssue, localizedTypeBySource) => {
795
825
  return localized;
796
826
  };
797
827
  export function createType(name, fromUnknownOrParent, fromParentOrFormatError, formatError) {
798
- return typeof fromUnknownOrParent === "function"
799
- ? createRootType(name, assertRefinementIdentity(fromUnknownOrParent), fromParentOrFormatError)
800
- : createChildType(name, fromUnknownOrParent, assertRefinementIdentity(fromParentOrFormatError), formatError);
828
+ if (typeof fromUnknownOrParent === "function") {
829
+ const fromUnknown = assertRefinementIdentity(fromUnknownOrParent);
830
+ return createRootType(name, fromUnknown, fromParentOrFormatError, {
831
+ // The asserted identity makes this an identity Type.
832
+ check: (value) => {
833
+ const result = fromUnknown(value);
834
+ return result.ok ? undefined : result.error;
835
+ },
836
+ });
837
+ }
838
+ return createChildType(name, fromUnknownOrParent, assertRefinementIdentity(fromParentOrFormatError), formatError);
801
839
  }
802
840
  const assertRefinementIdentity = (refinement) => (value) => {
803
841
  const result = refinement(value);
@@ -847,15 +885,27 @@ const assertRefinementIdentity = (refinement) => (value) => {
847
885
  *
848
886
  * @group Construction
849
887
  */
850
- export const createTypeWithError = (name, type, mapError, formatError) => createRootType(name, (value, options) => {
851
- const result = type.fromUnknown(value, options);
852
- return result.ok ? result : err(mapError(result.error, value));
853
- }, formatError);
854
- const createRootType = (name, fromUnknown, formatError, getTypeIssues) => {
888
+ export const createTypeWithError = (name, type, mapError, formatError) => {
889
+ const check = type[checkSymbol];
890
+ return createRootType(name, (value, options) => {
891
+ const result = type.fromUnknown(value, options);
892
+ return result.ok ? result : err(mapError(result.error, value));
893
+ }, formatError, {
894
+ check: check &&
895
+ ((value, options) => {
896
+ const error = check(value, options);
897
+ return error === undefined ? undefined : mapError(error, value);
898
+ }),
899
+ });
900
+ };
901
+ const createRootType = (name, fromUnknown, formatError, { getTypeIssues, isOutput, check, } = {}) => {
855
902
  const runtimeFormatError = formatError;
856
903
  const runtimeGetTypeIssues = getTypeIssues ??
857
- ((error) => singleRuntimeTypeIssue(error.type === "TypeOf" ? name : error.type, error, runtimeFormatError));
858
- const is = (value) => fromUnknown(value).ok;
904
+ ((error, _mode, path) => singleRuntimeTypeIssue(error.type === "TypeOf" ? name : error.type, error, runtimeFormatError, path));
905
+ const is = isOutput ??
906
+ (check
907
+ ? (value) => check(value, firstValidationOptions) === undefined
908
+ : (value) => fromUnknown(value).ok);
859
909
  const from = (value, options = firstValidationOptions) => {
860
910
  assertTypeOutput(name, is, fromUnknown, value, options);
861
911
  return ok(value);
@@ -869,7 +919,7 @@ const createRootType = (name, fromUnknown, formatError, getTypeIssues) => {
869
919
  return value;
870
920
  };
871
921
  return {
872
- ...instance("Type"),
922
+ "~evolu/instance": "Type",
873
923
  name,
874
924
  parent: null,
875
925
  fromUnknown,
@@ -879,32 +929,134 @@ const createRootType = (name, fromUnknown, formatError, getTypeIssues) => {
879
929
  to,
880
930
  orThrow,
881
931
  orNull: to,
882
- "~standard": createStandardSchemaProps(fromUnknown, runtimeGetTypeIssues, formatDefaultRuntimeTypeIssue),
932
+ "~standard": {
933
+ version: 1,
934
+ vendor: "evolu",
935
+ validate: (value) => {
936
+ const result = fromUnknown(value, allValidationOptions);
937
+ return result.ok
938
+ ? { value: result.value }
939
+ : {
940
+ issues: runtimeGetTypeIssues(result.error, "all", []).map((issue) => ({
941
+ message: formatRuntimeTypeIssue(issue),
942
+ path: issue.path,
943
+ })),
944
+ };
945
+ },
946
+ },
883
947
  [outputValidationSymbol]: fromUnknown,
884
948
  [fromSymbol]: ok,
885
949
  [encoderSymbol]: identity,
886
950
  [getRuntimeTypeIssuesSymbol]: runtimeGetTypeIssues,
951
+ [checkSymbol]: check,
952
+ [refinementsSymbol]: undefined,
953
+ [arrayTypeSymbol]: undefined,
954
+ [setTypeSymbol]: undefined,
887
955
  };
888
956
  };
889
957
  const createChildType = (name, parent, fromParent, formatOwnError,
890
958
  // Wrapped child errors can delegate their structured issues.
891
- getTypeIssuesOverride) => {
959
+ getTypeIssuesOverride,
960
+ // A refinement `fromParent` only checks the parent Output and returns
961
+ // `ok()`, so the child can return the parent Result unchanged.
962
+ isRefinement = false) => {
892
963
  const typeParent = parent;
893
964
  const defaultFormatter = formatOwnError;
894
965
  const getTypeIssues = getTypeIssuesOverride ??
895
966
  (formatOwnError
896
- ? (error, mode) => error.type === name
897
- ? singleRuntimeTypeIssue(name, error, defaultFormatter)
898
- : typeParent[getRuntimeTypeIssuesSymbol](error, mode)
967
+ ? (error, mode, path) => error.type === name
968
+ ? singleRuntimeTypeIssue(name, error, defaultFormatter, path)
969
+ : typeParent[getRuntimeTypeIssuesSymbol](error, mode, path)
899
970
  : typeParent[getRuntimeTypeIssuesSymbol]);
971
+ // Validators get only the value and no receiver (refinements are read into
972
+ // a local before a call), so default parameters, `arguments`, and `this`
973
+ // behave as in a direct call.
900
974
  const validate = fromParent;
901
- const mapFromParent = (result, options) => (result.ok ? validate(result.value, options) : result);
902
- const fromParentOperation = mapRuntimeOperations(typeParent[fromSymbol], (operation) => mapRuntimeResult(operation, mapFromParent));
975
+ // Returning the parent Result avoids allocating an equal Ok per layer.
976
+ const mapFromParent = isRefinement
977
+ ? (result) => {
978
+ if (!result.ok)
979
+ return result;
980
+ const refined = validate(result.value);
981
+ return refined.ok ? result : refined;
982
+ }
983
+ : (result) => result.ok ? validate(result.value) : result;
984
+ // Parent operations are read once, so validation skips repeated property
985
+ // lookups on heterogeneous Type nodes.
986
+ const parentFromUnknown = typeParent.fromUnknown;
987
+ const parentValidateOutput = typeParent[outputValidationSymbol];
988
+ const parentFrom = typeParent[fromSymbol];
989
+ const fromParentOperation = (value, options = firstValidationOptions) => mapFromParent(parentFrom(value, options));
990
+ if (parentFrom.parent) {
991
+ fromParentOperation.parent = mapRuntimeOperations(parentFrom.parent, (operation) => mapRuntimeResult(operation, mapFromParent));
992
+ }
903
993
  const from = createFromOperation(fromParentOperation);
904
- const fromUnknown = (value, options = firstValidationOptions) => mapFromParent(typeParent.fromUnknown(value, options), options);
905
- const validateOutput = (value, options = firstValidationOptions) => mapFromParent(typeParent[outputValidationSymbol](value, options), options);
994
+ // Refinement children form a chain whose refinements run in order after
995
+ // its base, the nearest ancestor that is not a refinement, so nested
996
+ // refinements need no call per level.
997
+ const parentRefinements = isRefinement
998
+ ? typeParent[refinementsSymbol]
999
+ : undefined;
1000
+ const refinements = parentRefinements
1001
+ ? [...parentRefinements, validate]
1002
+ : [validate];
1003
+ const refinementCount = refinements.length;
1004
+ let base = typeParent;
1005
+ if (isRefinement) {
1006
+ while (base[refinementsSymbol])
1007
+ base = base.parent;
1008
+ }
1009
+ const isBase = base.is;
1010
+ const is = (value) => {
1011
+ if (!isBase(value))
1012
+ return false;
1013
+ for (let index = 0; index < refinementCount; index++) {
1014
+ const refine = refinements[index];
1015
+ if (!refine(value).ok)
1016
+ return false;
1017
+ }
1018
+ return true;
1019
+ };
1020
+ const baseCheck = base[checkSymbol];
1021
+ // A child returns its parent Output unchanged, so a child of an identity
1022
+ // Type is one too.
1023
+ const check = baseCheck &&
1024
+ ((value, options) => {
1025
+ const error = baseCheck(value, options);
1026
+ if (error !== undefined)
1027
+ return error;
1028
+ for (let index = 0; index < refinementCount; index++) {
1029
+ const refine = refinements[index];
1030
+ const result = refine(value);
1031
+ if (!result.ok)
1032
+ return result.error;
1033
+ }
1034
+ return undefined;
1035
+ });
1036
+ const validateBase = base.fromUnknown;
1037
+ // Like nested refinement children, it returns the base Result or the first
1038
+ // refinement Err. A single refinement is faster with its direct call.
1039
+ const fromUnknown = refinementCount > 1
1040
+ ? (value, options = firstValidationOptions) => {
1041
+ const result = validateBase(value, options);
1042
+ if (!result.ok)
1043
+ return result;
1044
+ for (let index = 0; index < refinementCount; index++) {
1045
+ const refine = refinements[index];
1046
+ const refined = refine(result.value);
1047
+ if (!refined.ok)
1048
+ return refined;
1049
+ }
1050
+ return result;
1051
+ }
1052
+ : (value, options = firstValidationOptions) => mapFromParent(parentFromUnknown(value, options));
1053
+ // A root parent validates its Output with fromUnknown, and so does a child
1054
+ // of such a parent.
1055
+ const validateOutput = parentValidateOutput === parentFromUnknown
1056
+ ? fromUnknown
1057
+ : (value, options = firstValidationOptions) => mapFromParent(parentValidateOutput(value, options));
906
1058
  const to = identity;
907
- return createTypeNode(name, typeParent, fromUnknown, (value) => typeParent.is(value) && validate(value, firstValidationOptions).ok, validateOutput, from, to, getTypeIssues);
1059
+ return createTypeNode(name, typeParent, fromUnknown, is, validateOutput, from, to, getTypeIssues, { check, refinements: isRefinement ? refinements : undefined });
908
1060
  };
909
1061
  export function transform(name, parent, output, { from: transformFrom, to: transformTo, }, formatOwnError) {
910
1062
  const typeParent = parent;
@@ -925,20 +1077,20 @@ export function transform(name, parent, output, { from: transformFrom, to: trans
925
1077
  return parentValue;
926
1078
  };
927
1079
  const defaultFormatter = formatOwnError;
928
- const getTypeIssues = (error, mode) => {
1080
+ const getTypeIssues = (error, mode, path) => {
929
1081
  if (error.type !== name) {
930
- return typeParent[getRuntimeTypeIssuesSymbol](error, mode);
1082
+ return typeParent[getRuntimeTypeIssuesSymbol](error, mode, path);
931
1083
  }
932
1084
  if ("outputError" in error) {
933
- return typeOutput[getRuntimeTypeIssuesSymbol](error.outputError, mode);
1085
+ return typeOutput[getRuntimeTypeIssuesSymbol](error.outputError, mode, path);
934
1086
  }
935
- return singleRuntimeTypeIssue(name, error, defaultFormatter);
1087
+ return singleRuntimeTypeIssue(name, error, defaultFormatter, path);
936
1088
  };
937
1089
  const validateOutput = (value, options) => {
938
1090
  const result = typeOutput[outputValidationSymbol](value, options);
939
1091
  return result.ok ? result : err({ type: name, outputError: result.error });
940
1092
  };
941
- return createTypeNode(name, typeParent, fromUnknown, typeOutput.is, validateOutput, from, to, getTypeIssues, { output: typeOutput });
1093
+ return createTypeNode(name, typeParent, fromUnknown, typeOutput.is, validateOutput, from, to, getTypeIssues, { additionalProperties: { output: typeOutput } });
942
1094
  }
943
1095
  const firstValidationOptions = { errors: "first" };
944
1096
  const allValidationOptions = { errors: "all" };
@@ -948,6 +1100,33 @@ const fromSymbol =
948
1100
  /*#__PURE__*/ globalThis.Symbol();
949
1101
  const templateLiteralSyntaxSymbol =
950
1102
  /*#__PURE__*/ globalThis.Symbol();
1103
+ // A Type node caches the Array and Set Types of itself as an element in these
1104
+ // own properties; a Type whose slot cannot be written, such as a frozen one,
1105
+ // uses the typeByElement WeakMaps. A WeakMap alone keeps each entry's value
1106
+ // alive through young-generation GCs, so every schema created at runtime would
1107
+ // be promoted.
1108
+ const arrayTypeSymbol = /*#__PURE__*/ globalThis.Symbol();
1109
+ const setTypeSymbol = /*#__PURE__*/ globalThis.Symbol();
1110
+ const checkSymbol =
1111
+ /*#__PURE__*/ globalThis.Symbol();
1112
+ const refinementsSymbol =
1113
+ /*#__PURE__*/ globalThis.Symbol();
1114
+ // The `fromUnknown` of an identity Type.
1115
+ const checkToFromUnknown = (check) => (value, options = firstValidationOptions) => {
1116
+ const error = check(value, options);
1117
+ return error === undefined ? ok(value) : err(error);
1118
+ };
1119
+ // The checks of identity Types, or `undefined` if any Type is not one.
1120
+ const getRuntimeChecks = (types) => {
1121
+ const checks = [];
1122
+ for (const type of types) {
1123
+ const check = type[checkSymbol];
1124
+ if (check === undefined)
1125
+ return undefined;
1126
+ checks.push(check);
1127
+ }
1128
+ return checks;
1129
+ };
951
1130
  const mapRuntimeResult = (operation, map) => (value, options = firstValidationOptions) => map(operation(value, options), options);
952
1131
  const createRuntimeOrThrow = (fromInput, formatError) => mapRuntimeResult(fromInput, (result) => {
953
1132
  if (result.ok)
@@ -969,37 +1148,56 @@ const createFromOperation = (parent) => {
969
1148
  from.parent = parent;
970
1149
  return from;
971
1150
  };
972
- const createToOperation = (parent, own) => {
973
- const mapOwnToParent = (operation) => (value) => operation(own(value));
974
- const to = mapOwnToParent(parent);
975
- const toParent = mapRuntimeResult(own, identity);
976
- if (parent.parent) {
977
- toParent.parent = mapRuntimeOperations(parent.parent, mapOwnToParent);
978
- }
979
- to.parent = toParent;
980
- return to;
981
- };
1151
+ // Composite Types read these operations of their Types on first use, so
1152
+ // construction allocates no arrays and validation skips property lookups on
1153
+ // heterogeneous Type nodes.
1154
+ const getFromUnknown = (type) => type.fromUnknown;
1155
+ const getValidateOutput = (type) => type[outputValidationSymbol];
1156
+ const getIs = (type) => type.is;
982
1157
  function getTerminalRuntimeNode(node) {
983
1158
  while (node.parent != null)
984
1159
  node = node.parent;
985
1160
  return node;
986
1161
  }
987
- const createTypeNode = (name, parent, fromUnknown, is, validateOutput, from, ownTo, getTypeIssues, additionalProperties, formatIssue = formatDefaultRuntimeTypeIssue) => {
1162
+ const createTypeNode = (name, parent, fromUnknown, is, validateOutput, from, ownTo, getTypeIssues, { additionalProperties, formatIssue = formatDefaultRuntimeTypeIssue, check, refinements, } = {}) => {
988
1163
  const runtimeParent = parent;
989
- const to = runtimeParent
990
- ? createToOperation(runtimeParent[encoderSymbol], ownTo)
991
- : ownTo;
1164
+ const parentTo = runtimeParent?.[encoderSymbol];
1165
+ // Operations defined directly here share this call's closure context, so a
1166
+ // Type node does not allocate one context per helper.
1167
+ let to = ownTo;
1168
+ if (parentTo) {
1169
+ to = (value) => parentTo(ownTo(value));
1170
+ const toParent = (value, options = firstValidationOptions) => ownTo(value, options);
1171
+ if (parentTo.parent) {
1172
+ toParent.parent = mapRuntimeOperations(parentTo.parent, (operation) => (value) => operation(ownTo(value)));
1173
+ }
1174
+ to.parent = toParent;
1175
+ }
992
1176
  const runtimeFormatError = runtimeParent?.[getRuntimeTypeIssuesSymbol] === getTypeIssues
993
1177
  ? runtimeParent.formatError
994
- : (error) => formatRuntimeTypeIssue(getTypeIssues(error, "first")[0], formatIssue);
995
- const typedFrom = addRuntimeAssertions(name, is, validateOutput, parent, from);
1178
+ : (error) => formatRuntimeTypeIssue(getTypeIssues(error, "first", [])[0], formatIssue);
1179
+ const typedFrom = (value, options = firstValidationOptions) => {
1180
+ assertTypeOutput(name, is, validateOutput, value, options);
1181
+ return from(value, options);
1182
+ };
1183
+ if (from.parent) {
1184
+ typedFrom.parent = addRuntimeAssertions(runtimeParent, from.parent);
1185
+ }
996
1186
  const fromInput = getTerminalRuntimeNode(typedFrom);
997
- const typedTo = mapRuntimeOperations(to, (operation) => (value) => {
1187
+ const typedTo = (value) => {
998
1188
  assertTypeOutput(name, is, validateOutput, value);
999
- return operation(value);
1000
- });
1189
+ return to(value);
1190
+ };
1191
+ if (to.parent) {
1192
+ typedTo.parent = mapRuntimeOperations(to.parent, (operation) => (value) => {
1193
+ assertTypeOutput(name, is, validateOutput, value);
1194
+ return operation(value);
1195
+ });
1196
+ }
1197
+ // A spread-free literal gets a fixed shape; spreading in the middle made
1198
+ // every later property a slow generic definition.
1001
1199
  const type = {
1002
- ...instance("Type"),
1200
+ "~evolu/instance": "Type",
1003
1201
  name,
1004
1202
  parent,
1005
1203
  fromUnknown,
@@ -1007,41 +1205,55 @@ const createTypeNode = (name, parent, fromUnknown, is, validateOutput, from, own
1007
1205
  is,
1008
1206
  from: typedFrom,
1009
1207
  to: typedTo,
1010
- orThrow: createRuntimeOrThrow(fromInput, runtimeFormatError),
1011
- orNull: mapRuntimeResult(fromInput, getOrNull),
1012
- "~standard": createStandardSchemaProps(fromUnknown, getTypeIssues, formatIssue),
1013
- ...additionalProperties,
1208
+ orThrow: (value, options = firstValidationOptions) => {
1209
+ const result = fromInput(value, options);
1210
+ if (result.ok)
1211
+ return result.value;
1212
+ throw new Error(runtimeFormatError(result.error), {
1213
+ cause: result.error,
1214
+ });
1215
+ },
1216
+ orNull: (value, options = firstValidationOptions) => getOrNull(fromInput(value, options)),
1217
+ "~standard": {
1218
+ version: 1,
1219
+ vendor: "evolu",
1220
+ validate: (value) => {
1221
+ const result = fromUnknown(value, allValidationOptions);
1222
+ return result.ok
1223
+ ? { value: result.value }
1224
+ : {
1225
+ issues: getTypeIssues(result.error, "all", []).map((issue) => ({
1226
+ message: formatRuntimeTypeIssue(issue, formatIssue),
1227
+ path: issue.path,
1228
+ })),
1229
+ };
1230
+ },
1231
+ },
1014
1232
  [outputValidationSymbol]: validateOutput,
1015
1233
  [fromSymbol]: from,
1016
1234
  [encoderSymbol]: to,
1017
1235
  [getRuntimeTypeIssuesSymbol]: getTypeIssues,
1236
+ [checkSymbol]: check,
1237
+ [refinementsSymbol]: refinements,
1238
+ [arrayTypeSymbol]: undefined,
1239
+ [setTypeSymbol]: undefined,
1018
1240
  };
1241
+ if (additionalProperties !== undefined) {
1242
+ globalThis.Object.assign(type, additionalProperties);
1243
+ }
1019
1244
  return type;
1020
1245
  };
1021
- const createStandardSchemaProps = (fromUnknown, getTypeIssues, formatIssue) => ({
1022
- version: 1,
1023
- vendor: "evolu",
1024
- validate: (value) => {
1025
- const result = fromUnknown(value, allValidationOptions);
1026
- return result.ok
1027
- ? { value: result.value }
1028
- : {
1029
- issues: getTypeIssues(result.error, "all").map((issue) => ({
1030
- message: formatRuntimeTypeIssue(issue, formatIssue),
1031
- path: issue.path,
1032
- })),
1033
- };
1034
- },
1035
- });
1036
- const addRuntimeAssertions = (name, is, validateOutput, parent, operation) => {
1246
+ // Each `from.parent` suffix asserts the Output of the next Type toward the
1247
+ // root before running the remaining pipeline.
1248
+ const addRuntimeAssertions = (boundary, operation) => {
1249
+ const { name, is } = boundary;
1250
+ const validateOutput = boundary[outputValidationSymbol];
1037
1251
  const asserted = (value, options = firstValidationOptions) => {
1038
1252
  assertTypeOutput(name, is, validateOutput, value, options);
1039
1253
  return operation(value, options);
1040
1254
  };
1041
1255
  if (operation.parent) {
1042
- const typeParent = parent;
1043
- const runtimeParent = typeParent;
1044
- asserted.parent = addRuntimeAssertions(runtimeParent.name, runtimeParent.is, runtimeParent[outputValidationSymbol], runtimeParent.parent, operation.parent);
1256
+ asserted.parent = addRuntimeAssertions(boundary.parent, operation.parent);
1045
1257
  }
1046
1258
  return asserted;
1047
1259
  };
@@ -1050,18 +1262,26 @@ const addRuntimeAssertions = (name, is, validateOutput, parent, operation) => {
1050
1262
  *
1051
1263
  * @group Base
1052
1264
  */
1053
- export const Unknown = /*#__PURE__*/ createRootType("Unknown", ok, identity);
1265
+ export const Unknown = /*#__PURE__*/ createRootType("Unknown", ok, identity, { isOutput: () => true, check: () => undefined });
1054
1266
  /**
1055
1267
  * A {@link Type} rejecting every value.
1056
1268
  *
1057
1269
  * @group Base
1058
1270
  */
1059
- export const Never = /*#__PURE__*/ createRootType("Never", (value) => err({ type: "Never", value }), (error) => `A value ${safelyStringifyUnknownValue(error.value)} is not valid for type Never.`);
1271
+ export const Never = /*#__PURE__*/ createRootType("Never", (value) => err({ type: "Never", value }), (error) => `A value ${safelyStringifyUnknownValue(error.value)} is not valid for type Never.`, {
1272
+ isOutput: () => false,
1273
+ check: (value) => ({ type: "Never", value }),
1274
+ });
1060
1275
  const createTypeOfType = (name) => {
1061
1276
  const typeOf = name.toLowerCase();
1062
1277
  return createRootType(name, (value) => typeof value === typeOf
1063
1278
  ? ok(value)
1064
- : err({ type: "TypeOf", expected: name, value }), (error) => `A value ${safelyStringifyUnknownValue(error.value)} is not a ${typeOf}.`);
1279
+ : err({ type: "TypeOf", expected: name, value }), (error) => `A value ${safelyStringifyUnknownValue(error.value)} is not a ${typeOf}.`, {
1280
+ isOutput: (value) => typeof value === typeOf,
1281
+ check: (value) => typeof value === typeOf
1282
+ ? undefined
1283
+ : { type: "TypeOf", expected: name, value },
1284
+ });
1065
1285
  };
1066
1286
  /**
1067
1287
  * A JavaScript string {@link Type} without additional constraints.
@@ -1254,18 +1474,25 @@ export const EvoluType = /*#__PURE__*/ createType("EvoluType", (value) => isInst
1254
1474
  : err({ type: "EvoluType", value }), (error) => `A value ${safelyStringifyUnknownValue(error.value)} is not an Evolu Type.`);
1255
1475
  export function objectTag(name, outputType) {
1256
1476
  const formatError = (error) => `A value ${safelyStringifyUnknownValue(error.value)} does not have the expected object tag ${safelyStringifyUnknownValue(error.expected)}.`;
1477
+ const tag = `[object ${name}]`;
1257
1478
  if (outputType === undefined) {
1258
- return createRootType(name, (value) => hasObjectTag(value, name)
1479
+ return createRootType(name, (value) => hasObjectTag(value, tag)
1259
1480
  ? ok(value)
1260
- : err({ type: "ObjectTag", expected: name, value }), formatError);
1481
+ : err({ type: "ObjectTag", expected: name, value }), formatError, {
1482
+ isOutput: (value) => hasObjectTag(value, tag),
1483
+ check: (value) => hasObjectTag(value, tag)
1484
+ ? undefined
1485
+ : { type: "ObjectTag", expected: name, value },
1486
+ });
1261
1487
  }
1262
- return globalThis.Object.assign(createChildType("ObjectTag", outputType, (value) => hasObjectTag(value, name)
1488
+ return globalThis.Object.assign(createChildType("ObjectTag", outputType, (value) => hasObjectTag(value, tag)
1263
1489
  ? ok(value)
1264
1490
  : err({ type: "ObjectTag", expected: name, value }), formatError), { expected: name });
1265
1491
  }
1266
- const hasObjectTag = (value, expected) => value !== null &&
1492
+ // Takes the complete `[object Name]` tag, so hot calls build no string.
1493
+ const hasObjectTag = (value, tag) => value !== null &&
1267
1494
  (typeof value === "object" || typeof value === "function") &&
1268
- globalThis.Object.prototype.toString.call(value) === `[object ${expected}]`;
1495
+ globalThis.Object.prototype.toString.call(value) === tag;
1269
1496
  /**
1270
1497
  * A realm-neutral JavaScript Date {@link Type} for trusted values.
1271
1498
  *
@@ -1327,7 +1554,12 @@ export const instanceOf = (constructor) => {
1327
1554
  const constructorName = concreteConstructor.name;
1328
1555
  const is = (value) => globalThis.Function.prototype[globalThis.Symbol.hasInstance].call(concreteConstructor, value);
1329
1556
  const fromUnknown = (value) => is(value) ? ok(value) : err({ type: "InstanceOf", constructorName, value });
1330
- return globalThis.Object.assign(createRootType("InstanceOf", fromUnknown, (error) => `A value ${safelyStringifyUnknownValue(error.value)} is not an instance of ${error.constructorName}.`), { constructor: concreteConstructor });
1557
+ return globalThis.Object.assign(createRootType("InstanceOf", fromUnknown, (error) => `A value ${safelyStringifyUnknownValue(error.value)} is not an instance of ${error.constructorName}.`, {
1558
+ isOutput: is,
1559
+ check: (value) => is(value)
1560
+ ? undefined
1561
+ : { type: "InstanceOf", constructorName, value },
1562
+ }), { constructor: concreteConstructor });
1331
1563
  };
1332
1564
  /**
1333
1565
  * Literal {@link Type}.
@@ -1372,9 +1604,6 @@ export const instanceOf = (constructor) => {
1372
1604
  */
1373
1605
  export const literal = (expected) => {
1374
1606
  const literalExpected = expected;
1375
- const validate = (value) => value === literalExpected
1376
- ? ok(value)
1377
- : err({ type: "Literal", expected: literalExpected, value });
1378
1607
  const parent = typeof literalExpected === "string"
1379
1608
  ? String
1380
1609
  : typeof literalExpected === "number"
@@ -1386,8 +1615,17 @@ export const literal = (expected) => {
1386
1615
  : null;
1387
1616
  const formatError = (error) => `The value ${safelyStringifyUnknownValue(error.value)} is not strictly equal to the expected literal: ${globalThis.String(error.expected)}.`;
1388
1617
  return globalThis.Object.assign(parent
1389
- ? createChildType("Literal", parent, validate, formatError)
1390
- : createRootType("Literal", validate, formatError), {
1618
+ ? createChildType("Literal", parent, (value) => value === literalExpected
1619
+ ? ok()
1620
+ : err({ type: "Literal", expected: literalExpected, value }), formatError, undefined, true)
1621
+ : createRootType("Literal", (value) => value === literalExpected
1622
+ ? ok(value)
1623
+ : err({ type: "Literal", expected: literalExpected, value }), formatError, {
1624
+ isOutput: (value) => value === literalExpected,
1625
+ check: (value) => value === literalExpected
1626
+ ? undefined
1627
+ : { type: "Literal", expected: literalExpected, value },
1628
+ }), {
1391
1629
  expected: literalExpected,
1392
1630
  [templateLiteralSyntaxSymbol]: true,
1393
1631
  });
@@ -1409,16 +1647,35 @@ export function union(...typesOrLiterals) {
1409
1647
  ? typeOrLiteral
1410
1648
  : literal(typeOrLiteral));
1411
1649
  const inputMembers = members.map(getTerminalRuntimeNode);
1412
- const inputFrom = createUnionValidation(inputMembers, (member, value, options) => member.fromUnknown(value, options));
1413
- const inputValidateOutput = createUnionValidation(inputMembers, (member, value, options) => member[outputValidationSymbol](value, options));
1650
+ const inputCheck = createUnionCheck(inputMembers);
1651
+ const inputFrom = inputCheck
1652
+ ? checkToFromUnknown(inputCheck)
1653
+ : createUnionValidation(inputMembers, getFromUnknown);
1414
1654
  const getTypeIssues = createUnionRuntimeTypeIssues(members);
1415
- const input = createTypeNode("Union", null, inputFrom, (value) => inputMembers.some((member) => member.is(value)), inputValidateOutput, ok, identity, createUnionRuntimeTypeIssues(inputMembers));
1416
- const fromUnknown = createUnionValidation(members, (member, value, options) => member.fromUnknown(value, options));
1417
- const validateOutput = createUnionValidation(members, (member, value, options) => member[outputValidationSymbol](value, options));
1418
- const memberFromInputs = members.map((member) => getTerminalRuntimeNode(member[fromSymbol]));
1419
- const fromParent = createUnionValidation(members, (_member, value, options, index) => inputMembers[index].is(value)
1420
- ? memberFromInputs[index](value, options)
1421
- : undefined);
1655
+ const input = createTypeNode("Union", null, inputFrom, createUnionIs(inputMembers), inputCheck
1656
+ ? inputFrom
1657
+ : createUnionValidation(inputMembers, getValidateOutput), ok, identity, createUnionRuntimeTypeIssues(inputMembers), { check: inputCheck });
1658
+ // Literal members accept exactly their `===` equal values, so a match skips
1659
+ // them.
1660
+ const isLiteralUnion = typesOrLiterals.every((typeOrLiteral) => typeOrLiteral === null || typeof typeOrLiteral !== "object");
1661
+ const isLiteral = (value) =>
1662
+ // oxlint-disable-next-line typescript/prefer-includes -- Like Literal validation, indexOf uses ===, so NaN never matches.
1663
+ typesOrLiterals.indexOf(value) !== -1;
1664
+ const checkMembers = createUnionCheck(members);
1665
+ const check = checkMembers && isLiteralUnion
1666
+ ? (value, options) => isLiteral(value) ? undefined : checkMembers(value, options)
1667
+ : checkMembers;
1668
+ const fromUnknown = check
1669
+ ? checkToFromUnknown(check)
1670
+ : createUnionValidation(members, getFromUnknown);
1671
+ const validateOutput = check
1672
+ ? fromUnknown
1673
+ : createUnionValidation(members, getValidateOutput);
1674
+ const fromParent = createUnionValidation(members, (member, index) => {
1675
+ const memberFromInput = getTerminalRuntimeNode(member[fromSymbol]);
1676
+ const inputIs = inputMembers[index].is;
1677
+ return (value, options) => inputIs(value) ? memberFromInput(value, options) : undefined;
1678
+ });
1422
1679
  const getOutputMember = (value) => members.find((member) => member.is(value));
1423
1680
  const from = createFromOperation(fromParent);
1424
1681
  const to = (value) => {
@@ -1426,38 +1683,99 @@ export function union(...typesOrLiterals) {
1426
1683
  assertNonNullable(member);
1427
1684
  return member[encoderSymbol](value);
1428
1685
  };
1429
- return createTypeNode("Union", input, fromUnknown, (value) => members.some((member) => member.is(value)), validateOutput, from, to, getTypeIssues, { members, [templateLiteralSyntaxSymbol]: true });
1686
+ return createTypeNode("Union", input, fromUnknown, isLiteralUnion ? isLiteral : createUnionIs(members), validateOutput, from, to, getTypeIssues, {
1687
+ additionalProperties: { members, [templateLiteralSyntaxSymbol]: true },
1688
+ check,
1689
+ });
1430
1690
  }
1431
- const createUnionRuntimeTypeIssues = (members) => (error) => [
1691
+ const createUnionRuntimeTypeIssues = (members) => (error, _mode, path) => [
1432
1692
  {
1433
1693
  name: "Union",
1434
1694
  error,
1435
- path: [],
1695
+ path,
1436
1696
  formatError: () => "A value does not match any allowed variant.",
1697
+ // Alternative issue paths are relative to the Union value.
1437
1698
  alternatives: error.errors.map(({ index, error }) => ({
1438
1699
  index,
1439
1700
  name: members[index].name,
1440
- issues: members[index][getRuntimeTypeIssuesSymbol](error, "all"),
1701
+ issues: members[index][getRuntimeTypeIssuesSymbol](error, "all", []),
1441
1702
  })),
1442
1703
  },
1443
1704
  ];
1444
- const createUnionValidation = (members, validateMember) => (value, options = firstValidationOptions) => {
1445
- let errors;
1446
- for (let index = 0; index < members.length; index++) {
1447
- const result = validateMember(members[index], value, options, index);
1448
- if (result === undefined)
1449
- continue;
1450
- if (result.ok)
1451
- return result;
1452
- if (errors === undefined || options.errors === "all") {
1453
- (errors ??= []).push({ index, error: result.error });
1454
- }
1705
+ const createUnionIs = (members) => {
1706
+ // Two members, as in nullOr, are checked without a loop.
1707
+ if (members.length === 2) {
1708
+ const firstIs = members[0].is;
1709
+ const secondIs = members[1].is;
1710
+ return (value) => firstIs(value) || secondIs(value);
1455
1711
  }
1456
- assertNonNullable(errors);
1457
- return err({
1458
- type: "Union",
1459
- errors: errors,
1460
- });
1712
+ let memberIs;
1713
+ return (value) => {
1714
+ const isMembers = (memberIs ??= members.map(getIs));
1715
+ for (let index = 0; index < isMembers.length; index++) {
1716
+ if (isMembers[index](value))
1717
+ return true;
1718
+ }
1719
+ return false;
1720
+ };
1721
+ };
1722
+ // A Union of identity Types returns the Result of the first valid member, so
1723
+ // it is an identity Type too.
1724
+ const createUnionCheck = (members) => {
1725
+ const checks = getRuntimeChecks(members);
1726
+ if (checks === undefined)
1727
+ return undefined;
1728
+ return (value, options) => {
1729
+ // The same errors as createUnionValidation.
1730
+ let first;
1731
+ let errors;
1732
+ for (let index = 0; index < checks.length; index++) {
1733
+ const error = checks[index](value, options);
1734
+ if (error === undefined)
1735
+ return undefined;
1736
+ if (first === undefined) {
1737
+ first = { index, error };
1738
+ }
1739
+ else if (options.errors === "all") {
1740
+ (errors ??= [first]).push({ index, error });
1741
+ }
1742
+ }
1743
+ assertNonNullable(first);
1744
+ return {
1745
+ type: "Union",
1746
+ errors: (errors ?? [first]),
1747
+ };
1748
+ };
1749
+ };
1750
+ // A member validation returns `undefined` to skip its member.
1751
+ const createUnionValidation = (members, getMemberValidation) => {
1752
+ let memberValidations;
1753
+ return (value, options = firstValidationOptions) => {
1754
+ const validations = (memberValidations ??=
1755
+ members.map(getMemberValidation));
1756
+ // A later member often matches (nullOr), so the error array is created
1757
+ // only when a second member fails or the Union fails.
1758
+ let first;
1759
+ let errors;
1760
+ for (let index = 0; index < validations.length; index++) {
1761
+ const result = validations[index](value, options);
1762
+ if (result === undefined)
1763
+ continue;
1764
+ if (result.ok)
1765
+ return result;
1766
+ if (first === undefined) {
1767
+ first = { index, error: result.error };
1768
+ }
1769
+ else if (options.errors === "all") {
1770
+ (errors ??= [first]).push({ index, error: result.error });
1771
+ }
1772
+ }
1773
+ assertNonNullable(first);
1774
+ return err({
1775
+ type: "Union",
1776
+ errors: (errors ?? [first]),
1777
+ });
1778
+ };
1461
1779
  };
1462
1780
  /**
1463
1781
  * Union {@link Type} containing the supplied Type and `undefined`.
@@ -1751,14 +2069,14 @@ const createTemplateLiteralParserType = (templateParts) => {
1751
2069
  return result;
1752
2070
  return err({ type: "TemplateLiteral", value: stringResult.value });
1753
2071
  };
1754
- const getTypeIssues = (error, mode) => {
2072
+ const getTypeIssues = (error, mode, path) => {
1755
2073
  if (error.type !== "TemplateLiteral") {
1756
- return String[getRuntimeTypeIssuesSymbol](error, mode);
2074
+ return String[getRuntimeTypeIssuesSymbol](error, mode, path);
1757
2075
  }
1758
2076
  if ("outputError" in error) {
1759
- return runtimeOutput[getRuntimeTypeIssuesSymbol](error.outputError, mode);
2077
+ return runtimeOutput[getRuntimeTypeIssuesSymbol](error.outputError, mode, path);
1760
2078
  }
1761
- return singleRuntimeTypeIssue("TemplateLiteral", error, ((error) => `The value ${safelyStringifyUnknownValue(error.value)} does not match the template literal.`));
2079
+ return singleRuntimeTypeIssue("TemplateLiteral", error, ((error) => `The value ${safelyStringifyUnknownValue(error.value)} does not match the template literal.`), path);
1762
2080
  };
1763
2081
  const canonicalStringFromUnknown = (value, options = firstValidationOptions) => {
1764
2082
  const stringResult = String.fromUnknown(value, options);
@@ -1767,7 +2085,7 @@ const createTemplateLiteralParserType = (templateParts) => {
1767
2085
  : stringResult;
1768
2086
  };
1769
2087
  const canonicalStringFrom = createFromOperation((value, options = firstValidationOptions) => canonicalizeString(value, options));
1770
- const stringType = createTypeNode("TemplateLiteral", String, canonicalStringFromUnknown, (value) => validateCanonicalString(value, firstValidationOptions).ok, validateCanonicalString, canonicalStringFrom, identity, getTypeIssues, reflection);
2088
+ const stringType = createTypeNode("TemplateLiteral", String, canonicalStringFromUnknown, (value) => validateCanonicalString(value, firstValidationOptions).ok, validateCanonicalString, canonicalStringFrom, identity, getTypeIssues, { additionalProperties: reflection });
1771
2089
  const fromUnknown = (value, options = firstValidationOptions) => {
1772
2090
  const stringResult = String.fromUnknown(value, options);
1773
2091
  if (!stringResult.ok)
@@ -1797,7 +2115,7 @@ const createTemplateLiteralParserType = (templateParts) => {
1797
2115
  };
1798
2116
  fromCanonicalString.parent = fromString;
1799
2117
  const from = createFromOperation(fromCanonicalString);
1800
- const type = createTypeNode("TemplateLiteral", stringType, fromUnknown, runtimeOutput.is, runtimeOutput[outputValidationSymbol], from, encodeCaptures, getTypeIssues, reflection);
2118
+ const type = createTypeNode("TemplateLiteral", stringType, fromUnknown, runtimeOutput.is, runtimeOutput[outputValidationSymbol], from, encodeCaptures, getTypeIssues, { additionalProperties: reflection });
1801
2119
  type.from.parent =
1802
2120
  fromCanonicalString;
1803
2121
  return type;
@@ -1939,9 +2257,7 @@ const getTemplateLiteralPartFraming = (part) => {
1939
2257
  return getTemplateLiteralPartFraming(type.parent);
1940
2258
  };
1941
2259
  export function brand(name, parent, validate, formatError) {
1942
- return createChildType(name, parent, validate
1943
- ? (value) => flatMapResult(validate(value), () => ok(value))
1944
- : ok, formatError);
2260
+ return createChildType(name, parent, validate ?? (() => ok()), formatError, undefined, true);
1945
2261
  }
1946
2262
  /**
1947
2263
  * Canonical ISO date-time {@link String}.
@@ -4162,9 +4478,13 @@ const isArrayCollection = (value, isElement) => {
4162
4478
  };
4163
4479
  const arrayRuntimeConfig = {
4164
4480
  name: "Array",
4481
+ typeSymbol: arrayTypeSymbol,
4165
4482
  typeByElement: arrayTypeByElement,
4166
4483
  validate: validateArrayCollection,
4167
4484
  validateItems: (value, validateElement, options) => validateArrayItems(value, validateElement, options, false),
4485
+ check: (value, checkElement, options) => Array.isArray(value)
4486
+ ? checkIndexedArrayItems("Array", value, checkElement, options)
4487
+ : { type: "Array", reason: { kind: "NotArray", value } },
4168
4488
  encode: encodeArrayCollection,
4169
4489
  is: isArrayCollection,
4170
4490
  formatError: ((error) => {
@@ -4189,23 +4509,10 @@ const validateArrayItems = (value, validate, options, checkStructure) => validat
4189
4509
  // Encoding and Output membership stay feature-local because sharing those
4190
4510
  // shorter hot paths measurably increases standalone bundles.
4191
4511
  const validateIndexedArrayItems = (name, value, validate, options, checkStructure) => {
4192
- let issues;
4512
+ let issues = checkStructure
4513
+ ? getArrayExcessPropertyIssues(value, options)
4514
+ : undefined;
4193
4515
  let output;
4194
- if (checkStructure) {
4195
- for (const key of Reflect.ownKeys(value)) {
4196
- if (key === "length")
4197
- continue;
4198
- if (typeof key === "string") {
4199
- const index = globalThis.Number(key) >>> 0;
4200
- if (index < value.length && globalThis.String(index) === key) {
4201
- continue;
4202
- }
4203
- }
4204
- (issues ??= []).push({ kind: "ExcessProperty", key });
4205
- if (options.errors === "first")
4206
- break;
4207
- }
4208
- }
4209
4516
  for (let index = 0; (issues === undefined || options.errors === "all") && index < value.length; index++) {
4210
4517
  let item;
4211
4518
  if (checkStructure) {
@@ -4252,6 +4559,66 @@ const validateIndexedArrayItems = (name, value, validate, options, checkStructur
4252
4559
  },
4253
4560
  });
4254
4561
  };
4562
+ // Like validateIndexedArrayItems for identity elements, which leave the value
4563
+ // unchanged, so it returns only the error.
4564
+ const checkIndexedArrayItems = (name, value, check, options) => {
4565
+ let issues = getArrayExcessPropertyIssues(value, options);
4566
+ for (let index = 0; (issues === undefined || options.errors === "all") && index < value.length; index++) {
4567
+ const descriptor = globalThis.Object.getOwnPropertyDescriptor(value, index);
4568
+ if (descriptor === undefined) {
4569
+ (issues ??= []).push({ kind: "Hole", index });
4570
+ if (options.errors === "first")
4571
+ break;
4572
+ continue;
4573
+ }
4574
+ if (!("value" in descriptor)) {
4575
+ (issues ??= []).push({ kind: "Accessor", index });
4576
+ if (options.errors === "first")
4577
+ break;
4578
+ continue;
4579
+ }
4580
+ const error = check(descriptor.value, options, index);
4581
+ if (error === undefined)
4582
+ continue;
4583
+ (issues ??= []).push({ kind: "Element", index, error });
4584
+ if (options.errors === "first")
4585
+ break;
4586
+ }
4587
+ return issues === undefined
4588
+ ? undefined
4589
+ : {
4590
+ type: name,
4591
+ reason: {
4592
+ kind: "Items",
4593
+ issues: issues,
4594
+ },
4595
+ };
4596
+ };
4597
+ // Own keys other than indexes and `length` are excess properties.
4598
+ const getArrayExcessPropertyIssues = (value, options) => {
4599
+ let issues;
4600
+ const keys = Reflect.ownKeys(value);
4601
+ for (let position = 0; position < keys.length; position++) {
4602
+ const key = keys[position];
4603
+ if (key === "length")
4604
+ continue;
4605
+ if (typeof key === "string") {
4606
+ // Ordinary arrays list their indexes first, in ascending order. Comparing
4607
+ // the key first reads `length` only for an index key.
4608
+ if (key === globalThis.String(position) && position < value.length) {
4609
+ continue;
4610
+ }
4611
+ const index = globalThis.Number(key) >>> 0;
4612
+ if (index < value.length && globalThis.String(index) === key) {
4613
+ continue;
4614
+ }
4615
+ }
4616
+ (issues ??= []).push({ kind: "ExcessProperty", key });
4617
+ if (options.errors === "first")
4618
+ break;
4619
+ }
4620
+ return issues;
4621
+ };
4255
4622
  const copyArrayPrefix = (value, endIndex) => {
4256
4623
  const output = createMutableArray(value.length);
4257
4624
  for (let index = 0; index < endIndex; index++) {
@@ -4285,11 +4652,23 @@ export const set = (element) => createHomogeneousCollectionType(element, setRunt
4285
4652
  // Array(String) +82/+63 B gzip, while Array(String)+Set(String) is 2501/2492 B
4286
4653
  // and Set adds only +303/+304 B once Array is present. See TreeShaking.test.ts.
4287
4654
  const createHomogeneousCollectionType = (typeElement, config) => {
4288
- const cached = config.typeByElement.get(typeElement);
4655
+ const slot = typeElement[config.typeSymbol];
4656
+ // A copy of a Type, spread or with the Type as its prototype, has the slot
4657
+ // of the original.
4658
+ const cached = slot !== undefined && slot.element === typeElement
4659
+ ? slot
4660
+ : config.typeByElement.get(typeElement);
4289
4661
  if (cached)
4290
4662
  return cached;
4291
- const fromUnknown = (value, options = firstValidationOptions) => config.validate(value, typeElement.fromUnknown, options);
4292
- const validateOutput = (value, options = firstValidationOptions) => config.validate(value, typeElement[outputValidationSymbol], options);
4663
+ const checkElement = typeElement[checkSymbol];
4664
+ const check = checkElement &&
4665
+ ((value, options) => config.check(value, checkElement, options));
4666
+ const fromUnknown = check
4667
+ ? checkToFromUnknown(check)
4668
+ : (value, options = firstValidationOptions) => config.validate(value, typeElement.fromUnknown, options);
4669
+ const validateOutput = check
4670
+ ? fromUnknown
4671
+ : (value, options = firstValidationOptions) => config.validate(value, typeElement[outputValidationSymbol], options);
4293
4672
  const parent = typeElement.parent
4294
4673
  ? createHomogeneousCollectionType(typeElement.parent, config)
4295
4674
  : null;
@@ -4304,16 +4683,20 @@ const createHomogeneousCollectionType = (typeElement, config) => {
4304
4683
  ? identity
4305
4684
  : (value) => config.encode(value, encodeElement);
4306
4685
  const getTypeIssues = createCollectionRuntimeTypeIssues(config.name, "Items", config.formatError, () => typeElement);
4307
- const type = createTypeNode(config.name, parent, fromUnknown, (value) => config.is(value, typeElement.is), validateOutput, from, to, getTypeIssues, { element: typeElement });
4308
- config.typeByElement.set(typeElement, type);
4686
+ const type = createTypeNode(config.name, parent, fromUnknown, (value) => config.is(value, typeElement.is), validateOutput, from, to, getTypeIssues, { additionalProperties: { element: typeElement }, check });
4687
+ // A frozen element Type keeps its Collection Type in the WeakMap.
4688
+ if (!Reflect.set(typeElement, config.typeSymbol, type)) {
4689
+ config.typeByElement.set(typeElement, type);
4690
+ }
4309
4691
  return type;
4310
4692
  };
4311
4693
  const setTypeByElement = /*#__PURE__*/ new WeakMap();
4312
4694
  const setRuntimeConfig = {
4313
4695
  name: "Set",
4696
+ typeSymbol: setTypeSymbol,
4314
4697
  typeByElement: setTypeByElement,
4315
4698
  validate: (value, validateElement, options) => {
4316
- if (!hasObjectTag(value, "Set")) {
4699
+ if (!hasObjectTag(value, "[object Set]")) {
4317
4700
  return err({
4318
4701
  type: "Set",
4319
4702
  reason: { kind: "NotSet", value },
@@ -4322,6 +4705,37 @@ const setRuntimeConfig = {
4322
4705
  return validateSetItems(value, validateElement, options, true);
4323
4706
  },
4324
4707
  validateItems: (value, validateElement, options) => validateSetItems(value, validateElement, options, false),
4708
+ check: (value, checkElement, options) => {
4709
+ if (!hasObjectTag(value, "[object Set]")) {
4710
+ return { type: "Set", reason: { kind: "NotSet", value } };
4711
+ }
4712
+ // The same issues as validateSetItems, without building an Output.
4713
+ let issues;
4714
+ for (const key of Reflect.ownKeys(value)) {
4715
+ (issues ??= []).push({ kind: "ExcessProperty", key });
4716
+ if (options.errors === "first")
4717
+ break;
4718
+ }
4719
+ let index = 0;
4720
+ for (const item of value) {
4721
+ if (issues !== undefined && options.errors === "first")
4722
+ break;
4723
+ const error = checkElement(item, options);
4724
+ if (error !== undefined) {
4725
+ (issues ??= []).push({ kind: "Element", index, error });
4726
+ }
4727
+ index++;
4728
+ }
4729
+ return issues === undefined
4730
+ ? undefined
4731
+ : {
4732
+ type: "Set",
4733
+ reason: {
4734
+ kind: "Items",
4735
+ issues: issues,
4736
+ },
4737
+ };
4738
+ },
4325
4739
  encode: (value, encodeElement) => {
4326
4740
  let changed = false;
4327
4741
  const output = new Set();
@@ -4334,7 +4748,7 @@ const setRuntimeConfig = {
4334
4748
  return changed ? output : value;
4335
4749
  },
4336
4750
  is: (value, isElement) => {
4337
- if (!hasObjectTag(value, "Set") ||
4751
+ if (!hasObjectTag(value, "[object Set]") ||
4338
4752
  Reflect.ownKeys(value).length !== 0) {
4339
4753
  return false;
4340
4754
  }
@@ -4425,7 +4839,7 @@ export const map = (key, value) => {
4425
4839
  if (cached)
4426
4840
  return cached;
4427
4841
  const validate = (input, validateKey, validateValue, options) => {
4428
- if (!hasObjectTag(input, "Map")) {
4842
+ if (!hasObjectTag(input, "[object Map]")) {
4429
4843
  return err({
4430
4844
  type: "Map",
4431
4845
  reason: { kind: "NotMap", value: input },
@@ -4433,8 +4847,90 @@ export const map = (key, value) => {
4433
4847
  }
4434
4848
  return validateMapEntries(input, validateKey, validateValue, options, true);
4435
4849
  };
4436
- const fromUnknown = (input, options = firstValidationOptions) => validate(input, typeKey.fromUnknown, typeValue.fromUnknown, options);
4437
- const validateOutput = (input, options = firstValidationOptions) => validate(input, typeKey[outputValidationSymbol], typeValue[outputValidationSymbol], options);
4850
+ const checkKey = typeKey[checkSymbol];
4851
+ const checkValue = typeValue[checkSymbol];
4852
+ // Like validate for identity keys and values, which leave the Map unchanged,
4853
+ // so it returns only the error. A custom iterator can repeat a key, so
4854
+ // collisions are still detected.
4855
+ const check = checkKey &&
4856
+ checkValue &&
4857
+ ((input, options) => {
4858
+ if (!hasObjectTag(input, "[object Map]")) {
4859
+ return {
4860
+ type: "Map",
4861
+ reason: { kind: "NotMap", value: input },
4862
+ };
4863
+ }
4864
+ const entries = input;
4865
+ let issues;
4866
+ const entryByKey = new Map();
4867
+ for (const key of Reflect.ownKeys(entries)) {
4868
+ (issues ??= []).push({ kind: "ExcessProperty", key });
4869
+ if (options.errors === "first")
4870
+ break;
4871
+ }
4872
+ let index = 0;
4873
+ for (const [inputKey, inputValue] of entries) {
4874
+ if (issues !== undefined && options.errors === "first")
4875
+ break;
4876
+ const keyError = checkKey(inputKey, options);
4877
+ if (keyError !== undefined) {
4878
+ (issues ??= []).push({
4879
+ kind: "Key",
4880
+ index,
4881
+ key: inputKey,
4882
+ error: keyError,
4883
+ });
4884
+ if (options.errors === "first")
4885
+ break;
4886
+ }
4887
+ const valueError = checkValue(inputValue, options);
4888
+ if (valueError !== undefined) {
4889
+ (issues ??= []).push({
4890
+ kind: "Value",
4891
+ index,
4892
+ key: inputKey,
4893
+ error: valueError,
4894
+ });
4895
+ if (options.errors === "first")
4896
+ break;
4897
+ }
4898
+ if (keyError === undefined) {
4899
+ const previous = entryByKey.get(inputKey);
4900
+ if (previous === undefined) {
4901
+ entryByKey.set(inputKey, { index, key: inputKey });
4902
+ }
4903
+ else {
4904
+ (issues ??= []).push({
4905
+ kind: "Collision",
4906
+ index,
4907
+ key: inputKey,
4908
+ previousIndex: previous.index,
4909
+ previousKey: previous.key,
4910
+ outputKey: inputKey,
4911
+ });
4912
+ if (options.errors === "first")
4913
+ break;
4914
+ }
4915
+ }
4916
+ index++;
4917
+ }
4918
+ return issues === undefined
4919
+ ? undefined
4920
+ : {
4921
+ type: "Map",
4922
+ reason: {
4923
+ kind: "Entries",
4924
+ issues: issues,
4925
+ },
4926
+ };
4927
+ });
4928
+ const fromUnknown = check
4929
+ ? checkToFromUnknown(check)
4930
+ : (input, options = firstValidationOptions) => validate(input, typeKey.fromUnknown, typeValue.fromUnknown, options);
4931
+ const validateOutput = check
4932
+ ? fromUnknown
4933
+ : (input, options = firstValidationOptions) => validate(input, typeKey[outputValidationSymbol], typeValue[outputValidationSymbol], options);
4438
4934
  const formatError = (error) => {
4439
4935
  if (error.reason.kind === "NotMap")
4440
4936
  return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not a Map.`;
@@ -4475,7 +4971,7 @@ export const map = (key, value) => {
4475
4971
  return changed ? output : input;
4476
4972
  };
4477
4973
  const is = (input) => {
4478
- if (!hasObjectTag(input, "Map")) {
4974
+ if (!hasObjectTag(input, "[object Map]")) {
4479
4975
  return false;
4480
4976
  }
4481
4977
  if (Reflect.ownKeys(input).length !== 0)
@@ -4486,26 +4982,36 @@ export const map = (key, value) => {
4486
4982
  }
4487
4983
  return true;
4488
4984
  };
4489
- const getTypeIssues = (error, mode) => {
4985
+ const getTypeIssues = (error, mode, path) => {
4490
4986
  const mapError = error;
4491
4987
  if (mapError.reason.kind !== "Entries") {
4492
- return singleRuntimeTypeIssue("Map", error, formatError);
4988
+ return singleRuntimeTypeIssue("Map", error, formatError, path);
4493
4989
  }
4494
4990
  const allIssues = mapError.reason.issues;
4495
4991
  const issues = mode === "first" ? [allIssues[0]] : allIssues;
4496
- return issues.flatMap((issue) => {
4992
+ const result = [];
4993
+ for (const issue of issues) {
4497
4994
  if (issue.kind === "Key" || issue.kind === "Value") {
4498
- return prependRuntimeTypeIssuePath(issue.index, prependRuntimeTypeIssuePath(issue.kind === "Key" ? "key" : "value", (issue.kind === "Key" ? typeKey : typeValue)[getRuntimeTypeIssuesSymbol](issue.error, mode)));
4995
+ for (const nestedIssue of (issue.kind === "Key" ? typeKey : typeValue)[getRuntimeTypeIssuesSymbol](issue.error, mode, appendIssuePath(appendIssuePath(path, issue.index), issue.kind === "Key" ? "key" : "value"))) {
4996
+ result.push(nestedIssue);
4997
+ }
4998
+ continue;
4499
4999
  }
4500
- return singleRuntimeTypeIssue("Map", mode === "first"
4501
- ? error
4502
- : {
4503
- type: "Map",
4504
- reason: { kind: "Entries", issues: [issue] },
4505
- }, formatError, [issue.kind === "ExcessProperty" ? issue.key : issue.index]);
4506
- });
5000
+ result.push({
5001
+ name: "Map",
5002
+ error: mode === "first"
5003
+ ? error
5004
+ : {
5005
+ type: "Map",
5006
+ reason: { kind: "Entries", issues: [issue] },
5007
+ },
5008
+ path: appendIssuePath(path, issue.kind === "ExcessProperty" ? issue.key : issue.index),
5009
+ formatError: formatError,
5010
+ });
5011
+ }
5012
+ return result;
4507
5013
  };
4508
- const type = createTypeNode("Map", parent, fromUnknown, is, validateOutput, from, to, getTypeIssues, { key: typeKey, value: typeValue });
5014
+ const type = createTypeNode("Map", parent, fromUnknown, is, validateOutput, from, to, getTypeIssues, { additionalProperties: { key: typeKey, value: typeValue }, check });
4509
5015
  if (typeByValue === undefined) {
4510
5016
  typeByValue = new WeakMap();
4511
5017
  mapTypeByValueByKey.set(typeKey, typeByValue);
@@ -4618,8 +5124,33 @@ const createTupleType = (typeElements) => {
4618
5124
  }
4619
5125
  return validateTupleItems(value, typeElements, validateElement, options, true);
4620
5126
  };
4621
- const fromUnknown = (value, options = firstValidationOptions) => validate(value, (element, item, elementOptions) => element.fromUnknown(item, elementOptions), options);
4622
- const validateOutput = (value, options = firstValidationOptions) => validate(value, (element, item, elementOptions) => element[outputValidationSymbol](item, elementOptions), options);
5127
+ const elementChecks = getRuntimeChecks(typeElements);
5128
+ const checkElement = elementChecks &&
5129
+ ((item, options, index) => elementChecks[index](item, options));
5130
+ // The same errors as validate, for identity elements.
5131
+ const check = checkElement &&
5132
+ ((value, options) => {
5133
+ if (!Array.isArray(value)) {
5134
+ return { type: "Tuple", reason: { kind: "NotArray", value } };
5135
+ }
5136
+ if (value.length !== expectedLength) {
5137
+ return {
5138
+ type: "Tuple",
5139
+ reason: {
5140
+ kind: "InvalidLength",
5141
+ expected: expectedLength,
5142
+ actual: value.length,
5143
+ },
5144
+ };
5145
+ }
5146
+ return checkIndexedArrayItems("Tuple", value, checkElement, options);
5147
+ });
5148
+ const fromUnknown = check
5149
+ ? checkToFromUnknown(check)
5150
+ : (value, options = firstValidationOptions) => validate(value, (element, item, elementOptions) => element.fromUnknown(item, elementOptions), options);
5151
+ const validateOutput = check
5152
+ ? fromUnknown
5153
+ : (value, options = firstValidationOptions) => validate(value, (element, item, elementOptions) => element[outputValidationSymbol](item, elementOptions), options);
4623
5154
  const formatError = (error) => {
4624
5155
  if (error.reason.kind === "NotArray")
4625
5156
  return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not a tuple.`;
@@ -4678,7 +5209,7 @@ const createTupleType = (typeElements) => {
4678
5209
  return true;
4679
5210
  };
4680
5211
  const getTypeIssues = createCollectionRuntimeTypeIssues("Tuple", "Items", formatError, (issue) => typeElements[issue.index]);
4681
- return createTypeNode("Tuple", parent, fromUnknown, is, validateOutput, from, to, getTypeIssues, { elements: typeElements });
5212
+ return createTypeNode("Tuple", parent, fromUnknown, is, validateOutput, from, to, getTypeIssues, { additionalProperties: { elements: typeElements }, check });
4682
5213
  };
4683
5214
  const validateTupleItems = (value, elements, validate, options, checkStructure) => validateIndexedArrayItems("Tuple", value, (value, elementOptions, index) => validate(elements[index], value, elementOptions, index), options, checkStructure);
4684
5215
  /**
@@ -4730,17 +5261,18 @@ export const Digit1To99 = /*#__PURE__*/ union(Digit1To9,
4730
5261
  export const Digit1To59 = /*#__PURE__*/ union(Digit1To9,
4731
5262
  /*#__PURE__*/ templateLiteral(
4732
5263
  /*#__PURE__*/ union("1", "2", "3", "4", "5"), Digit));
4733
- const createObjectRuntimeTypeIssues = (defaultFormatter, props, recordType) => (error, mode) => {
5264
+ const createObjectRuntimeTypeIssues = (props, recordType) => (error, mode, path) => {
4734
5265
  const objectError = error;
4735
5266
  if (objectError.reason.kind !== "Properties") {
4736
- return singleRuntimeTypeIssue("Object", error, defaultFormatter);
5267
+ return singleRuntimeTypeIssue("Object", error, formatObjectError, path);
4737
5268
  }
4738
5269
  const propertyErrors = objectError.reason.errors;
4739
5270
  const keys = Reflect.ownKeys(propertyErrors);
4740
- const firstKey = keys[0];
4741
- assertNonNullable(firstKey);
4742
- const keysToVisit = mode === "first" ? [firstKey] : keys;
4743
- return keysToVisit.flatMap((key) => {
5271
+ assertNonNullable(keys[0]);
5272
+ const visitCount = mode === "first" ? 1 : keys.length;
5273
+ const result = [];
5274
+ for (let index = 0; index < visitCount; index++) {
5275
+ const key = keys[index];
4744
5276
  const propertyError = propertyErrors[key];
4745
5277
  const property = typeof key === "string" &&
4746
5278
  props !== undefined &&
@@ -4760,51 +5292,86 @@ const createObjectRuntimeTypeIssues = (defaultFormatter, props, recordType) => (
4760
5292
  reason: { kind: "Properties", errors },
4761
5293
  };
4762
5294
  }
4763
- return singleRuntimeTypeIssue("Object", ownError, defaultFormatter, [
4764
- key,
4765
- ]);
5295
+ result.push({
5296
+ name: "Object",
5297
+ error: ownError,
5298
+ path: appendIssuePath(path, key),
5299
+ // The issue error has this first key, so its message needs no key
5300
+ // enumeration of the error record.
5301
+ formatError: () => formatObjectPropertyError(key, propertyError),
5302
+ });
5303
+ continue;
4766
5304
  }
4767
- if (property !== undefined) {
4768
- return prependRuntimeTypeIssuePath(key, objectPropertyToType(property)[getRuntimeTypeIssuesSymbol](propertyError, mode));
5305
+ // A record property error adds its own key.
5306
+ const nestedIssues = property === undefined
5307
+ ? recordType[getRuntimeTypeIssuesSymbol](propertyError, mode, path.slice())
5308
+ : objectPropertyToType(property)[getRuntimeTypeIssuesSymbol](propertyError, mode, appendIssuePath(path, key));
5309
+ // Nested issues are a new array, so one visited issue returns them.
5310
+ if (visitCount === 1)
5311
+ return nestedIssues;
5312
+ for (const issue of nestedIssues)
5313
+ result.push(issue);
5314
+ }
5315
+ return result;
5316
+ };
5317
+ const formatObjectError = (error) => {
5318
+ if (error.reason.kind !== "Properties")
5319
+ return formatPlainObjectRootError(error.reason);
5320
+ const key = Reflect.ownKeys(error.reason.errors).at(0);
5321
+ assertNonNullable(key);
5322
+ return formatObjectPropertyError(key, error.reason.errors[key]);
5323
+ };
5324
+ const formatObjectPropertyError = (key, propertyError) => {
5325
+ assertNonNullable(propertyError);
5326
+ if (propertyError.type === "ObjectPropertyAccess") {
5327
+ switch (propertyError.reason) {
5328
+ case "Accessor":
5329
+ return "An Object property must be a data property. Materialize accessor values into plain data before using this Type or use a different Type.";
5330
+ case "NonEnumerable":
5331
+ return "An Object property must be enumerable. Make it enumerable or use a different Type.";
4769
5332
  }
4770
- return recordType[getRuntimeTypeIssuesSymbol](propertyError, mode);
4771
- });
5333
+ }
5334
+ if (propertyError.type === "ObjectMissingProperty")
5335
+ return `The required property ${safelyStringifyUnknownValue(key)} is missing.`;
5336
+ if (typeof key === "symbol")
5337
+ return "An Object property key must be a string. Remove the symbol property or use a different Type.";
5338
+ if (propertyError.type === "ObjectExcessProperty")
5339
+ return `The property ${safelyStringifyUnknownValue(key)} is not allowed. Remove it or use a different Type.`;
5340
+ return `The property ${safelyStringifyUnknownValue(key)} is invalid.`;
4772
5341
  };
4773
5342
  const formatPlainObjectRootError = (reason) => reason.kind === "NotObject"
4774
5343
  ? `A value ${safelyStringifyUnknownValue(reason.value)} is not an object.`
4775
5344
  : "The value is an object, but an Object Output must be a plain object or have a null prototype.";
4776
- /**
4777
- * A {@link Type} for readonly plain objects with unknown property values.
4778
- *
4779
- * `Object` is the runtime counterpart of a `Readonly<Record<string, unknown>>`
4780
- * data boundary. Its prototype rule uses the realm-neutral structural heuristic
4781
- * described by {@link isPlainObject}. A matching custom root prototype can be
4782
- * classified as plain; other custom prototypes and class instances are
4783
- * rejected. Every own property must have a string key and be an enumerable data
4784
- * property. Accessors, non-enumerable properties, and symbol properties are
4785
- * rejected without reading their values.
4786
- *
4787
- * Use {@link object} when property names are fixed, {@link record} when keys and
4788
- * values have their own Types, and {@link instanceOf} when an instance belongs
4789
- * to the domain.
4790
- *
4791
- * @group Base
4792
- */
4793
- const _Object = /*#__PURE__*/ createRootType("Object", (value, options = firstValidationOptions) => {
5345
+ // Ordinary objects list string keys before symbols, so without symbols the
5346
+ // names are the own keys, and cheaper than Reflect.ownKeys. A Proxy may list
5347
+ // symbols first, and Reflect.ownKeys keeps that order.
5348
+ const getOwnKeys = (value) => {
5349
+ const names = globalThis.Object.getOwnPropertyNames(value);
5350
+ return globalThis.Object.getOwnPropertySymbols(value).length === 0
5351
+ ? names
5352
+ : Reflect.ownKeys(value);
5353
+ };
5354
+ // An `is` walk in own-key order fails at the first symbol key, after checking
5355
+ // the names before it. Ordinary objects list symbols last, but a Proxy may
5356
+ // list one first. Walking names is faster than walking mixed own keys.
5357
+ const getNamesBeforeSymbol = (value) => {
5358
+ const names = [];
5359
+ for (const key of Reflect.ownKeys(value)) {
5360
+ if (typeof key !== "string")
5361
+ break;
5362
+ names.push(key);
5363
+ }
5364
+ return names;
5365
+ };
5366
+ const checkPlainObject = (value, options) => {
4794
5367
  if (value === null || typeof value !== "object") {
4795
- return err({
4796
- type: "Object",
4797
- reason: { kind: "NotObject", value },
4798
- });
5368
+ return { type: "Object", reason: { kind: "NotObject", value } };
4799
5369
  }
4800
5370
  if (!isPlainObject(value)) {
4801
- return err({
4802
- type: "Object",
4803
- reason: { kind: "UnexpectedPrototype", value },
4804
- });
5371
+ return { type: "Object", reason: { kind: "UnexpectedPrototype", value } };
4805
5372
  }
4806
5373
  let errors;
4807
- for (const key of Reflect.ownKeys(value)) {
5374
+ for (const key of getOwnKeys(value)) {
4808
5375
  let propertyError;
4809
5376
  if (typeof key !== "string") {
4810
5377
  propertyError = { type: "ObjectExcessProperty" };
@@ -4833,57 +5400,38 @@ const _Object = /*#__PURE__*/ createRootType("Object", (value, options = firstVa
4833
5400
  break;
4834
5401
  }
4835
5402
  return errors === undefined
4836
- ? ok(value)
4837
- : err({
5403
+ ? undefined
5404
+ : {
4838
5405
  type: "Object",
4839
5406
  reason: { kind: "Properties", errors },
4840
- });
4841
- }, (error) => {
4842
- if (error.reason.kind !== "Properties")
4843
- return formatPlainObjectRootError(error.reason);
4844
- const key = Reflect.ownKeys(error.reason.errors).at(0);
4845
- assertNonNullable(key);
4846
- const propertyError = error.reason.errors[key];
4847
- assertNonNullable(propertyError);
4848
- if (propertyError.type === "ObjectPropertyAccess") {
4849
- switch (propertyError.reason) {
4850
- case "Accessor":
4851
- return "An Object property must be a data property. Materialize accessor values into plain data before using this Type or use a different Type.";
4852
- case "NonEnumerable":
4853
- return "An Object property must be enumerable. Make it enumerable or use a different Type.";
4854
- }
4855
- }
4856
- if (propertyError.type === "ObjectMissingProperty")
4857
- return `The required property ${safelyStringifyUnknownValue(key)} is missing.`;
4858
- if (typeof key === "symbol")
4859
- return "An Object property key must be a string. Remove the symbol property or use a different Type.";
4860
- if (propertyError.type === "ObjectExcessProperty")
4861
- return `The property ${safelyStringifyUnknownValue(key)} is not allowed. Remove it or use a different Type.`;
4862
- return `The property ${safelyStringifyUnknownValue(key)} is invalid.`;
4863
- },
4864
- /*#__PURE__*/ createObjectRuntimeTypeIssues(((error) => {
4865
- if (error.reason.kind !== "Properties")
4866
- return formatPlainObjectRootError(error.reason);
4867
- const key = Reflect.ownKeys(error.reason.errors).at(0);
4868
- assertNonNullable(key);
4869
- const propertyError = error.reason.errors[key];
4870
- assertNonNullable(propertyError);
4871
- if (propertyError.type === "ObjectPropertyAccess") {
4872
- switch (propertyError.reason) {
4873
- case "Accessor":
4874
- return "An Object property must be a data property. Materialize accessor values into plain data before using this Type or use a different Type.";
4875
- case "NonEnumerable":
4876
- return "An Object property must be enumerable. Make it enumerable or use a different Type.";
4877
- }
4878
- }
4879
- if (propertyError.type === "ObjectMissingProperty")
4880
- return `The required property ${safelyStringifyUnknownValue(key)} is missing.`;
4881
- if (typeof key === "symbol")
4882
- return "An Object property key must be a string. Remove the symbol property or use a different Type.";
4883
- if (propertyError.type === "ObjectExcessProperty")
4884
- return `The property ${safelyStringifyUnknownValue(key)} is not allowed. Remove it or use a different Type.`;
4885
- return `The property ${safelyStringifyUnknownValue(key)} is invalid.`;
4886
- })));
5407
+ };
5408
+ };
5409
+ /**
5410
+ * A {@link Type} for readonly plain objects with unknown property values.
5411
+ *
5412
+ * `Object` is the runtime counterpart of a `Readonly<Record<string, unknown>>`
5413
+ * data boundary. Its prototype rule uses the realm-neutral structural heuristic
5414
+ * described by {@link isPlainObject}. A matching custom root prototype can be
5415
+ * classified as plain; other custom prototypes and class instances are
5416
+ * rejected. Every own property must have a string key and be an enumerable data
5417
+ * property. Accessors, non-enumerable properties, and symbol properties are
5418
+ * rejected without reading their values.
5419
+ *
5420
+ * Use {@link object} when property names are fixed, {@link record} when keys and
5421
+ * values have their own Types, and {@link instanceOf} when an instance belongs
5422
+ * to the domain.
5423
+ *
5424
+ * @group Base
5425
+ */
5426
+ const _Object = /*#__PURE__*/ createRootType("Object", (value, options = firstValidationOptions) => {
5427
+ const error = checkPlainObject(value, options);
5428
+ return error === undefined
5429
+ ? ok(value)
5430
+ : err(error);
5431
+ }, formatObjectError, {
5432
+ getTypeIssues: /*#__PURE__*/ createObjectRuntimeTypeIssues(),
5433
+ check: checkPlainObject,
5434
+ });
4887
5435
  // Avoid a local `Object` binding because Babel's CommonJS transform injects
4888
5436
  // `Object.defineProperty` before it is initialized:
4889
5437
  // https://github.com/babel/babel/issues/16943
@@ -5047,8 +5595,71 @@ export const record = (key, value) => {
5047
5595
  }
5048
5596
  return validateRecordEntries(input, validateKey, validateValue, options);
5049
5597
  };
5050
- const fromUnknown = (input, options = firstValidationOptions) => validate(input, typeKey.fromUnknown, typeValue.fromUnknown, options);
5051
- const validateOutput = (input, options = firstValidationOptions) => validate(input, typeKey[outputValidationSymbol], typeValue[outputValidationSymbol], options);
5598
+ const checkKey = typeKey[checkSymbol];
5599
+ const checkValue = typeValue[checkSymbol];
5600
+ // The same errors as validate for identity keys and values. Own keys are
5601
+ // unique, so identity keys never collide.
5602
+ const check = checkKey &&
5603
+ checkValue &&
5604
+ ((input, options) => {
5605
+ if (input === null || typeof input !== "object") {
5606
+ return { type: "Record", reason: { kind: "NotRecord", value: input } };
5607
+ }
5608
+ if (!isPlainObject(input)) {
5609
+ return {
5610
+ type: "Record",
5611
+ reason: { kind: "NotPlainRecord", value: input },
5612
+ };
5613
+ }
5614
+ let issues;
5615
+ for (const inputKey of getOwnKeys(input)) {
5616
+ const keyError = checkKey(inputKey, options);
5617
+ if (keyError !== undefined) {
5618
+ (issues ??= []).push({ kind: "Key", key: inputKey, error: keyError });
5619
+ if (options.errors === "first")
5620
+ break;
5621
+ }
5622
+ const descriptor = globalThis.Object.getOwnPropertyDescriptor(input, inputKey);
5623
+ assert(descriptor !== undefined, "Record property descriptor is missing.");
5624
+ if (!("value" in descriptor)) {
5625
+ (issues ??= []).push({ kind: "Accessor", key: inputKey });
5626
+ if (options.errors === "first")
5627
+ break;
5628
+ }
5629
+ else if (!descriptor.enumerable) {
5630
+ (issues ??= []).push({ kind: "NonEnumerable", key: inputKey });
5631
+ if (options.errors === "first")
5632
+ break;
5633
+ }
5634
+ else {
5635
+ const valueError = checkValue(descriptor.value, options);
5636
+ if (valueError !== undefined) {
5637
+ (issues ??= []).push({
5638
+ kind: "Value",
5639
+ key: inputKey,
5640
+ error: valueError,
5641
+ });
5642
+ if (options.errors === "first")
5643
+ break;
5644
+ }
5645
+ }
5646
+ }
5647
+ return issues === undefined
5648
+ ? undefined
5649
+ : {
5650
+ type: "Record",
5651
+ reason: {
5652
+ kind: "Entries",
5653
+ issues: issues,
5654
+ },
5655
+ };
5656
+ });
5657
+ const fromUnknown = check
5658
+ ? checkToFromUnknown(check)
5659
+ : (input, options = firstValidationOptions) => validate(input, typeKey.fromUnknown, typeValue.fromUnknown, options);
5660
+ const validateOutput = check
5661
+ ? fromUnknown
5662
+ : (input, options = firstValidationOptions) => validate(input, typeKey[outputValidationSymbol], typeValue[outputValidationSymbol], options);
5052
5663
  const formatError = (error) => {
5053
5664
  if (error.reason.kind === "NotRecord")
5054
5665
  return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not a Record.`;
@@ -5102,8 +5713,16 @@ export const record = (key, value) => {
5102
5713
  return false;
5103
5714
  if (!isPlainObject(input))
5104
5715
  return false;
5105
- for (const key of Reflect.ownKeys(input)) {
5106
- if (typeof key !== "string" || !typeKey.is(key))
5716
+ // Separate name and symbol enumerations cost dictionary-mode inputs, such
5717
+ // as null-prototype records, about 10% over one Reflect.ownKeys, which is
5718
+ // much slower for ordinary objects.
5719
+ const hasSymbols = globalThis.Object.getOwnPropertySymbols(input).length !== 0;
5720
+ const names = hasSymbols
5721
+ ? getNamesBeforeSymbol(input)
5722
+ : globalThis.Object.getOwnPropertyNames(input);
5723
+ for (let index = 0; index < names.length; index++) {
5724
+ const key = names[index];
5725
+ if (!typeKey.is(key))
5107
5726
  return false;
5108
5727
  const descriptor = globalThis.Object.getOwnPropertyDescriptor(input, key);
5109
5728
  if (descriptor === undefined ||
@@ -5114,17 +5733,17 @@ export const record = (key, value) => {
5114
5733
  if (!typeValue.is(descriptor.value))
5115
5734
  return false;
5116
5735
  }
5117
- return true;
5736
+ return !hasSymbols;
5118
5737
  };
5119
5738
  const getTypeIssues = createCollectionRuntimeTypeIssues("Record", "Entries", formatError, (issue) => (issue.kind === "Key" ? typeKey : typeValue));
5120
- return createTypeNode("Record", parent, fromUnknown, is, validateOutput, from, to, getTypeIssues, { key: typeKey, value: typeValue });
5739
+ return createTypeNode("Record", parent, fromUnknown, is, validateOutput, from, to, getTypeIssues, { additionalProperties: { key: typeKey, value: typeValue }, check });
5121
5740
  };
5122
5741
  const validateRecordEntries = (input, validateKey, validateValue, options) => {
5123
5742
  let issues;
5124
5743
  const output = createMutableRecord();
5125
5744
  const inputKeyByOutputKey = createMutableRecord();
5126
5745
  let changed = false;
5127
- for (const inputKey of Reflect.ownKeys(input)) {
5746
+ for (const inputKey of getOwnKeys(input)) {
5128
5747
  const keyResult = validateKey(inputKey, options);
5129
5748
  if (!keyResult.ok) {
5130
5749
  (issues ??= []).push({
@@ -5275,9 +5894,12 @@ export function withDefault(property, value, { strategy } = {}) {
5275
5894
  ? result
5276
5895
  : err({ type: "WithDefault", outputError: result.error });
5277
5896
  },
5278
- [getRuntimeTypeIssuesSymbol]: (error, mode) => error.type === "WithDefault" && "outputError" in error
5279
- ? ownGetTypeIssues(error.outputError, mode)
5280
- : type[getRuntimeTypeIssuesSymbol](error, mode),
5897
+ [getRuntimeTypeIssuesSymbol]: (error, mode, path) => error.type === "WithDefault" && "outputError" in error
5898
+ ? ownGetTypeIssues(error.outputError, mode, path)
5899
+ : type[getRuntimeTypeIssuesSymbol](error, mode, path),
5900
+ // Output validation wraps errors, so a copied check would not match it.
5901
+ [checkSymbol]: undefined,
5902
+ [refinementsSymbol]: undefined,
5281
5903
  };
5282
5904
  const operations = {
5283
5905
  output: outputWithIssues,
@@ -5336,13 +5958,32 @@ const missingDefaultValue = /*#__PURE__*/ globalThis.Symbol();
5336
5958
  export function object(props, recordType) {
5337
5959
  return createObjectType(snapshotObjectProps(props), recordType);
5338
5960
  }
5339
- const createObjectType = (props, recordType) => {
5961
+ const createObjectType = (props, recordType,
5962
+ // A parent Object Type reuses the key order of its child.
5963
+ keys = globalThis.Object.keys(props)) => {
5340
5964
  const runtimeProps = props;
5341
- const defaultProperty = globalThis.Object.values(runtimeProps).find(isDefaultProperty);
5342
- if (defaultProperty) {
5343
- return defaultProperty[defaultPropertySymbol].createObject(props, recordType);
5965
+ const keyCount = keys.length;
5966
+ const propertyTypes = createMutableArray(keyCount);
5967
+ const optionalFlags = createMutableArray(keyCount);
5968
+ for (let index = 0; index < keyCount; index++) {
5969
+ const property = runtimeProps[keys[index]];
5970
+ if (isDefaultProperty(property)) {
5971
+ return property[defaultPropertySymbol].createObject(props, recordType);
5972
+ }
5973
+ const isOptional = isOptionalProperty(property);
5974
+ const type = isOptional ? property.type : property;
5975
+ propertyTypes[index] = type;
5976
+ optionalFlags[index] = isOptional;
5344
5977
  }
5345
- const keys = globalThis.Object.keys(runtimeProps);
5978
+ const propertyChecks = propertyTypes.map((type) => type[checkSymbol]);
5979
+ const recordCheck = recordType?.value[checkSymbol];
5980
+ // When every property and Record value Type is an identity Type, validation
5981
+ // never changes a value.
5982
+ const isIdentity = !propertyChecks.includes(undefined) &&
5983
+ (recordType === undefined || recordCheck !== undefined);
5984
+ let isOutputs;
5985
+ // Returns `undefined` for a valid input it did not change, so a valid
5986
+ // identity Object allocates no Result.
5346
5987
  const validate = (value, options, exactOutput) => {
5347
5988
  if (value === null || typeof value !== "object") {
5348
5989
  return err({
@@ -5357,116 +5998,158 @@ const createObjectType = (props, recordType) => {
5357
5998
  reason: { kind: "UnexpectedPrototype", value: input },
5358
5999
  });
5359
6000
  }
6001
+ const ownKeys = getOwnKeys(input);
6002
+ // Descriptors by visit index let a copy-on-write Output reuse the values
6003
+ // already read. Only a property without a check can change its value.
6004
+ const descriptors = isIdentity
6005
+ ? undefined
6006
+ : createMutableArray(keyCount + ownKeys.length);
5360
6007
  let errors;
5361
6008
  let output;
5362
- const inputDescriptorByKey = new Map();
5363
- for (const key of keys)
5364
- inputDescriptorByKey.set(key, undefined);
5365
- for (const key of Reflect.ownKeys(input)) {
5366
- inputDescriptorByKey.set(key, undefined);
5367
- }
5368
- const setError = (key, error) => {
5369
- errors ??= createMutableRecord();
5370
- errors[key] = error;
5371
- };
5372
- for (const [key] of inputDescriptorByKey) {
5373
- const property = typeof key === "string" && globalThis.Object.hasOwn(runtimeProps, key)
5374
- ? runtimeProps[key]
5375
- : undefined;
6009
+ // Own keys that are exactly the declared keys in their order leave the
6010
+ // undeclared-key loop nothing to visit. A first-mode stop leaves the flag
6011
+ // unchecked, but then that loop does not run either.
6012
+ let ownKeysAreDeclared = ownKeys.length === keyCount;
6013
+ for (let index = 0; (errors === undefined || options.errors !== "first") && index < keyCount; index++) {
6014
+ const key = keys[index];
6015
+ if (ownKeysAreDeclared && ownKeys[index] !== key) {
6016
+ ownKeysAreDeclared = false;
6017
+ }
5376
6018
  const descriptor = globalThis.Object.getOwnPropertyDescriptor(input, key);
5377
- inputDescriptorByKey.set(key, descriptor);
6019
+ if (descriptors !== undefined)
6020
+ descriptors[index] = descriptor;
6021
+ let error;
5378
6022
  if (descriptor === undefined) {
5379
- assert(property !== undefined, "Object property is missing.");
5380
- if (isOptionalProperty(property)) {
6023
+ if (optionalFlags[index])
5381
6024
  continue;
6025
+ error = { type: "ObjectMissingProperty" };
6026
+ }
6027
+ else if (!("value" in descriptor)) {
6028
+ error = {
6029
+ type: "ObjectPropertyAccess",
6030
+ reason: "Accessor",
6031
+ };
6032
+ }
6033
+ else if (!descriptor.enumerable) {
6034
+ error = {
6035
+ type: "ObjectPropertyAccess",
6036
+ reason: "NonEnumerable",
6037
+ };
6038
+ }
6039
+ else {
6040
+ const propertyValue = descriptor.value;
6041
+ const check = propertyChecks[index];
6042
+ if (check !== undefined) {
6043
+ const propertyError = check(propertyValue, options);
6044
+ if (propertyError === undefined) {
6045
+ if (output !== undefined && errors === undefined) {
6046
+ output[key] = propertyValue;
6047
+ }
6048
+ continue;
6049
+ }
6050
+ error = propertyError;
5382
6051
  }
5383
- setError(key, { type: "ObjectMissingProperty" });
5384
- if (options.errors === "first")
5385
- break;
6052
+ else {
6053
+ const result = exactOutput
6054
+ ? propertyTypes[index][outputValidationSymbol](propertyValue, options)
6055
+ : propertyTypes[index].fromUnknown(propertyValue, options);
6056
+ if (result.ok) {
6057
+ if (errors !== undefined || exactOutput)
6058
+ continue;
6059
+ if (output === undefined) {
6060
+ if (globalThis.Object.is(result.value, propertyValue))
6061
+ continue;
6062
+ output = copyVisitedProperties(descriptors, ownKeys, index);
6063
+ }
6064
+ output[key] = result.value;
6065
+ continue;
6066
+ }
6067
+ error = result.error;
6068
+ }
6069
+ }
6070
+ errors ??= createMutableRecord();
6071
+ errors[key] = error;
6072
+ }
6073
+ const ownKeyEnd = ownKeysAreDeclared ? 0 : ownKeys.length;
6074
+ for (let position = 0; (errors === undefined || options.errors !== "first") &&
6075
+ position < ownKeyEnd; position++) {
6076
+ const key = ownKeys[position];
6077
+ const index = keyCount + position;
6078
+ if (typeof key === "string" &&
6079
+ // Own keys usually follow the declared order.
6080
+ ((position < keyCount && key === keys[position]) ||
6081
+ globalThis.Object.hasOwn(runtimeProps, key))) {
6082
+ if (descriptors !== undefined)
6083
+ descriptors[index] = undefined;
5386
6084
  continue;
5387
6085
  }
5388
- if (property === undefined && recordType === undefined) {
5389
- setError(key, {
6086
+ const descriptor = globalThis.Object.getOwnPropertyDescriptor(input, key);
6087
+ assert(descriptor !== undefined, "Object property is missing.");
6088
+ if (descriptors !== undefined)
6089
+ descriptors[index] = descriptor;
6090
+ let error;
6091
+ if (recordType === undefined) {
6092
+ error = {
5390
6093
  type: "ObjectExcessProperty",
5391
- });
5392
- if (options.errors === "first")
5393
- break;
5394
- continue;
6094
+ };
5395
6095
  }
5396
- if (property === undefined && typeof key !== "string") {
5397
- setError(key, createRecordPropertyError({
6096
+ else if (typeof key !== "string") {
6097
+ error = createRecordPropertyError({
5398
6098
  kind: "Key",
5399
6099
  key,
5400
- error: {
5401
- type: "TypeOf",
5402
- expected: "String",
5403
- value: key,
5404
- },
5405
- }));
5406
- if (options.errors === "first")
5407
- break;
5408
- continue;
6100
+ error: { type: "TypeOf", expected: "String", value: key },
6101
+ });
5409
6102
  }
5410
- if (!("value" in descriptor)) {
5411
- const propertyError = {
6103
+ else if (!("value" in descriptor)) {
6104
+ error = {
5412
6105
  type: "ObjectPropertyAccess",
5413
6106
  reason: "Accessor",
5414
6107
  };
5415
- setError(key, propertyError);
5416
- if (options.errors === "first")
5417
- break;
5418
- continue;
5419
6108
  }
5420
- if (!descriptor.enumerable) {
5421
- const propertyError = {
6109
+ else if (!descriptor.enumerable) {
6110
+ error = {
5422
6111
  type: "ObjectPropertyAccess",
5423
6112
  reason: "NonEnumerable",
5424
6113
  };
5425
- setError(key, propertyError);
5426
- if (options.errors === "first")
5427
- break;
5428
- continue;
5429
- }
5430
- const propertyValue = descriptor.value;
5431
- const propertyType = property === undefined
5432
- ? recordType.value
5433
- : objectPropertyToType(property);
5434
- const result = exactOutput
5435
- ? propertyType[outputValidationSymbol](propertyValue, options)
5436
- : propertyType.fromUnknown(propertyValue, options);
5437
- if (!result.ok) {
5438
- setError(key, property === undefined
5439
- ? createRecordPropertyError({
5440
- kind: "Value",
5441
- key,
5442
- error: result.error,
5443
- })
5444
- : result.error);
5445
- if (options.errors === "first")
5446
- break;
5447
- continue;
5448
6114
  }
5449
- if (errors !== undefined)
5450
- continue;
5451
- if (exactOutput)
5452
- continue;
5453
- if (output === undefined &&
5454
- globalThis.Object.is(result.value, propertyValue)) {
5455
- continue;
5456
- }
5457
- if (output === undefined) {
5458
- output = globalThis.Object.create(null);
5459
- for (const [previousKey, previousDescriptor] of inputDescriptorByKey) {
5460
- if (previousKey === key)
5461
- break;
5462
- if (previousDescriptor !== undefined &&
5463
- "value" in previousDescriptor &&
5464
- previousDescriptor.enumerable) {
5465
- output[previousKey] = previousDescriptor.value;
6115
+ else {
6116
+ const propertyValue = descriptor.value;
6117
+ let valueError;
6118
+ if (recordCheck !== undefined) {
6119
+ const checkError = recordCheck(propertyValue, options);
6120
+ if (checkError === undefined) {
6121
+ if (output !== undefined && errors === undefined) {
6122
+ output[key] = propertyValue;
6123
+ }
6124
+ continue;
5466
6125
  }
6126
+ valueError = checkError;
5467
6127
  }
6128
+ else {
6129
+ const result = exactOutput
6130
+ ? recordType.value[outputValidationSymbol](propertyValue, options)
6131
+ : recordType.value.fromUnknown(propertyValue, options);
6132
+ if (result.ok) {
6133
+ if (errors !== undefined || exactOutput)
6134
+ continue;
6135
+ if (output === undefined) {
6136
+ if (globalThis.Object.is(result.value, propertyValue))
6137
+ continue;
6138
+ output = copyVisitedProperties(descriptors, ownKeys, index);
6139
+ }
6140
+ output[key] = result.value;
6141
+ continue;
6142
+ }
6143
+ valueError = result.error;
6144
+ }
6145
+ error = createRecordPropertyError({
6146
+ kind: "Value",
6147
+ key,
6148
+ error: valueError,
6149
+ });
5468
6150
  }
5469
- output[key] = result.value;
6151
+ errors ??= createMutableRecord();
6152
+ errors[key] = error;
5470
6153
  }
5471
6154
  if (errors !== undefined) {
5472
6155
  return err({
@@ -5474,69 +6157,70 @@ const createObjectType = (props, recordType) => {
5474
6157
  reason: { kind: "Properties", errors },
5475
6158
  });
5476
6159
  }
5477
- if (output === undefined)
5478
- return ok(input);
5479
- return ok(output);
6160
+ return output === undefined ? undefined : ok(output);
5480
6161
  };
5481
- const fromUnknown = (value, options = firstValidationOptions) => validate(value, options, false);
5482
- const validateOutput = (value, options = firstValidationOptions) => validate(value, options, true);
5483
- const formatError = (error) => {
5484
- if (error.reason.kind !== "Properties")
5485
- return formatPlainObjectRootError(error.reason);
5486
- const key = Reflect.ownKeys(error.reason.errors).at(0);
5487
- assertNonNullable(key);
5488
- const propertyError = error.reason.errors[key];
5489
- assertNonNullable(propertyError);
5490
- if (propertyError.type === "ObjectPropertyAccess") {
5491
- switch (propertyError.reason) {
5492
- case "Accessor":
5493
- return "An Object property must be a data property. Materialize accessor values into plain data before using this Type or use a different Type.";
5494
- case "NonEnumerable":
5495
- return "An Object property must be enumerable. Make it enumerable or use a different Type.";
5496
- }
6162
+ // Properties visited before `end` are valid and unchanged, so a new Output
6163
+ // starts with their values.
6164
+ const copyVisitedProperties = (descriptors, ownKeys, end) => {
6165
+ const output = globalThis.Object.create(null);
6166
+ for (let index = 0; index < end; index++) {
6167
+ const descriptor = descriptors[index];
6168
+ if (descriptor === undefined)
6169
+ continue;
6170
+ output[index < keyCount ? keys[index] : ownKeys[index - keyCount]] =
6171
+ descriptor.value;
5497
6172
  }
5498
- if (propertyError.type === "ObjectMissingProperty")
5499
- return `The required property ${safelyStringifyUnknownValue(key)} is missing.`;
5500
- if (typeof key === "symbol")
5501
- return "An Object property key must be a string. Remove the symbol property or use a different Type.";
5502
- if (propertyError.type === "ObjectExcessProperty")
5503
- return `The property ${safelyStringifyUnknownValue(key)} is not allowed. Remove it or use a different Type.`;
5504
- return `The property ${safelyStringifyUnknownValue(key)} is invalid.`;
6173
+ return output;
5505
6174
  };
5506
- const rootProps = createMutableRecord();
6175
+ // An identity Object builds no Output, so its validation returns an Err or
6176
+ // `undefined`.
6177
+ const check = isIdentity
6178
+ ? (value, options) => {
6179
+ const result = validate(value, options, false);
6180
+ return result?.ok === false ? result.error : undefined;
6181
+ }
6182
+ : undefined;
6183
+ const fromUnknown = (value, options = firstValidationOptions) => validate(value, options, false) ??
6184
+ ok(value);
6185
+ const validateOutput = isIdentity
6186
+ ? fromUnknown
6187
+ : (value, options = firstValidationOptions) => validate(value, options, true) ??
6188
+ ok(value);
5507
6189
  let hasNonRootType = false;
5508
6190
  let canSkipTo = recordType === undefined || recordType.value[encoderSymbol] === identity;
5509
- for (const key of keys) {
5510
- const property = runtimeProps[key];
5511
- const type = objectPropertyToType(property);
6191
+ for (let index = 0; index < keyCount; index++) {
6192
+ const type = propertyTypes[index];
5512
6193
  if (type[encoderSymbol] !== identity)
5513
6194
  canSkipTo = false;
5514
- const rootType = getTerminalRuntimeNode(type);
5515
- if (rootType !== type)
6195
+ if (type.parent != null)
5516
6196
  hasNonRootType = true;
5517
- rootProps[key] = isOptionalProperty(property)
5518
- ? optional(rootType)
5519
- : rootType;
5520
6197
  }
5521
- const parent = hasNonRootType || recordType?.parent
5522
- ? createObjectType(rootProps, (recordType?.parent ?? recordType))
5523
- : null;
5524
- const inputFromByKey = createMutableRecord();
5525
- for (const key of keys) {
5526
- inputFromByKey[key] = getTerminalRuntimeNode(objectPropertyToType(runtimeProps[key])[fromSymbol]);
6198
+ let parent = null;
6199
+ if (hasNonRootType || recordType?.parent) {
6200
+ const rootProps = createMutableRecord();
6201
+ for (let index = 0; index < keyCount; index++) {
6202
+ const rootType = getTerminalRuntimeNode(propertyTypes[index]);
6203
+ rootProps[keys[index]] = optionalFlags[index]
6204
+ ? optional(rootType)
6205
+ : rootType;
6206
+ }
6207
+ parent = createObjectType(rootProps, (recordType?.parent ?? recordType), keys);
5527
6208
  }
6209
+ let inputFroms;
5528
6210
  const recordValueFromInput = recordType
5529
6211
  ? getTerminalRuntimeNode(recordType.value[fromSymbol])
5530
6212
  : undefined;
5531
6213
  const fromParent = parent
5532
6214
  ? (value, options = firstValidationOptions) => {
6215
+ const propertyFroms = (inputFroms ??= propertyTypes.map((type) => getTerminalRuntimeNode(type[fromSymbol])));
5533
6216
  let errors;
5534
6217
  let output;
5535
- for (const key of keys) {
6218
+ for (let index = 0; index < keyCount; index++) {
6219
+ const key = keys[index];
5536
6220
  if (!globalThis.Object.hasOwn(value, key))
5537
6221
  continue;
5538
6222
  const propertyValue = value[key];
5539
- const result = inputFromByKey[key](propertyValue, options);
6223
+ const result = propertyFroms[index](propertyValue, options);
5540
6224
  if (result.ok) {
5541
6225
  if (!globalThis.Object.is(result.value, propertyValue)) {
5542
6226
  (output ??= createMutableRecord(value))[key] = result.value;
@@ -5584,11 +6268,12 @@ const createObjectType = (props, recordType) => {
5584
6268
  ? identity
5585
6269
  : (value) => {
5586
6270
  let output;
5587
- for (const key of keys) {
6271
+ for (let index = 0; index < keyCount; index++) {
6272
+ const key = keys[index];
5588
6273
  if (!globalThis.Object.hasOwn(value, key))
5589
6274
  continue;
5590
6275
  const propertyValue = value[key];
5591
- const encoded = objectPropertyToType(runtimeProps[key])[encoderSymbol](propertyValue);
6276
+ const encoded = propertyTypes[index][encoderSymbol](propertyValue);
5592
6277
  if (!globalThis.Object.is(encoded, propertyValue)) {
5593
6278
  (output ??= createMutableRecord(value))[key] = encoded;
5594
6279
  }
@@ -5606,50 +6291,79 @@ const createObjectType = (props, recordType) => {
5606
6291
  }
5607
6292
  return output ?? value;
5608
6293
  };
5609
- const is = (value) => {
5610
- if (value === null || typeof value !== "object")
5611
- return false;
5612
- if (!isPlainObject(value))
5613
- return false;
5614
- for (const key of keys) {
5615
- const property = runtimeProps[key];
5616
- const descriptor = globalThis.Object.getOwnPropertyDescriptor(value, key);
6294
+ const isDeclared = (value) => {
6295
+ const isProperties = (isOutputs ??= propertyTypes.map(getIs));
6296
+ for (let index = 0; index < keyCount; index++) {
6297
+ const descriptor = globalThis.Object.getOwnPropertyDescriptor(value, keys[index]);
5617
6298
  if (descriptor === undefined) {
5618
- if (!isOptionalProperty(property))
6299
+ if (!optionalFlags[index])
5619
6300
  return false;
5620
6301
  continue;
5621
6302
  }
5622
6303
  if (!("value" in descriptor) ||
5623
6304
  !descriptor.enumerable ||
5624
- !objectPropertyToType(property).is(descriptor.value)) {
6305
+ !isProperties[index](descriptor.value)) {
5625
6306
  return false;
5626
6307
  }
5627
6308
  }
5628
- for (const key of Reflect.ownKeys(value)) {
5629
- if (typeof key === "string" &&
5630
- globalThis.Object.hasOwn(runtimeProps, key)) {
5631
- continue;
5632
- }
5633
- if (recordType === undefined || typeof key !== "string")
6309
+ return true;
6310
+ };
6311
+ // Separate predicates keep each small; one shared by both cases was up to
6312
+ // 5% slower in either.
6313
+ const is = recordType === undefined
6314
+ ? (value) => {
6315
+ if (value === null || typeof value !== "object")
5634
6316
  return false;
5635
- const descriptor = globalThis.Object.getOwnPropertyDescriptor(value, key);
5636
- if (descriptor === undefined ||
5637
- !("value" in descriptor) ||
5638
- !descriptor.enumerable) {
6317
+ if (!isPlainObject(value) || !isDeclared(value))
5639
6318
  return false;
6319
+ // Checking names has no effects without a Record, so symbols, which
6320
+ // fail, are counted after them.
6321
+ const names = globalThis.Object.getOwnPropertyNames(value);
6322
+ for (let position = 0; position < names.length; position++) {
6323
+ const key = names[position];
6324
+ if (!(position < keyCount && key === keys[position]) &&
6325
+ !globalThis.Object.hasOwn(runtimeProps, key)) {
6326
+ return false;
6327
+ }
5640
6328
  }
5641
- if (!recordType.value.is(descriptor.value))
5642
- return false;
6329
+ return globalThis.Object.getOwnPropertySymbols(value).length === 0;
5643
6330
  }
5644
- return true;
5645
- };
5646
- const getTypeIssues = createObjectRuntimeTypeIssues(formatError, runtimeProps, recordType);
5647
- return createTypeNode("Object", parent, fromUnknown, is, validateOutput, from, to, getTypeIssues, recordType
5648
- ? {
5649
- props: runtimeProps,
5650
- record: recordType,
5651
- }
5652
- : { props: runtimeProps });
6331
+ : (value) => {
6332
+ if (value === null || typeof value !== "object")
6333
+ return false;
6334
+ if (!isPlainObject(value) || !isDeclared(value))
6335
+ return false;
6336
+ const hasSymbols = globalThis.Object.getOwnPropertySymbols(value).length !== 0;
6337
+ const names = hasSymbols
6338
+ ? getNamesBeforeSymbol(value)
6339
+ : globalThis.Object.getOwnPropertyNames(value);
6340
+ for (let position = 0; position < names.length; position++) {
6341
+ const key = names[position];
6342
+ if ((position < keyCount && key === keys[position]) ||
6343
+ globalThis.Object.hasOwn(runtimeProps, key)) {
6344
+ continue;
6345
+ }
6346
+ const descriptor = globalThis.Object.getOwnPropertyDescriptor(value, key);
6347
+ if (descriptor === undefined ||
6348
+ !("value" in descriptor) ||
6349
+ !descriptor.enumerable) {
6350
+ return false;
6351
+ }
6352
+ if (!recordType.value.is(descriptor.value))
6353
+ return false;
6354
+ }
6355
+ return !hasSymbols;
6356
+ };
6357
+ const getTypeIssues = createObjectRuntimeTypeIssues(runtimeProps, recordType);
6358
+ return createTypeNode("Object", parent, fromUnknown, is, validateOutput, from, to, getTypeIssues, {
6359
+ additionalProperties: recordType
6360
+ ? {
6361
+ props: runtimeProps,
6362
+ record: recordType,
6363
+ }
6364
+ : { props: runtimeProps },
6365
+ check,
6366
+ });
5653
6367
  };
5654
6368
  const createDefaultObjectType = (props, recordType) => {
5655
6369
  const inputProps = createMutableRecord();
@@ -5665,7 +6379,7 @@ const createDefaultObjectType = (props, recordType) => {
5665
6379
  else {
5666
6380
  inputProps[key] = property;
5667
6381
  const type = objectPropertyToType(property);
5668
- const output = createRootType(type.name, type[outputValidationSymbol], type.formatError, type[getRuntimeTypeIssuesSymbol]);
6382
+ const output = createRootType(type.name, type[outputValidationSymbol], type.formatError, { getTypeIssues: type[getRuntimeTypeIssuesSymbol] });
5669
6383
  outputProps[key] =
5670
6384
  optionalPropertySymbol in property
5671
6385
  ? createOptionalProperty(output)
@@ -5698,16 +6412,18 @@ const createDefaultObjectType = (props, recordType) => {
5698
6412
  }
5699
6413
  return source[encoderSymbol](result);
5700
6414
  };
5701
- return createTypeNode("Object", parent, (value, options) => flatMapResult(source.fromUnknown(value, options), finish), output.is, output[outputValidationSymbol], createFromOperation(fromParent), to, createObjectRuntimeTypeIssues(source.formatError, props, recordType), recordType
5702
- ? { props, record: recordType }
5703
- : { props });
6415
+ return createTypeNode("Object", parent, (value, options) => flatMapResult(source.fromUnknown(value, options), finish), output.is, output[outputValidationSymbol], createFromOperation(fromParent), to, createObjectRuntimeTypeIssues(props, recordType), {
6416
+ additionalProperties: recordType
6417
+ ? { props, record: recordType }
6418
+ : { props },
6419
+ });
5704
6420
  };
5705
6421
  // Read descriptors instead of spreading so accessors are not invoked,
5706
6422
  // non-enumerable declarations are retained, and later mutations are isolated.
5707
6423
  const snapshotObjectProps = (props, runtimeProps = createMutableRecord()) => {
5708
6424
  const errorMessage = "Object schema properties must be own string-keyed data properties.";
5709
6425
  assert(isPlainObject(props), errorMessage);
5710
- for (const key of Reflect.ownKeys(props)) {
6426
+ for (const key of getOwnKeys(props)) {
5711
6427
  const descriptor = globalThis.Object.getOwnPropertyDescriptor(props, key);
5712
6428
  assert(typeof key === "string" &&
5713
6429
  descriptor !== undefined &&
@@ -5931,13 +6647,13 @@ export const objectKeys = (key) => (type) => {
5931
6647
  ? result
5932
6648
  : err({ type: "ObjectKeys", outputError: result.error });
5933
6649
  };
5934
- const getTypeIssues = (error, mode) => {
6650
+ const getTypeIssues = (error, mode, path) => {
5935
6651
  if ("outputError" in error) {
5936
- return runtimeOutput[getRuntimeTypeIssuesSymbol](error.outputError, mode);
6652
+ return runtimeOutput[getRuntimeTypeIssuesSymbol](error.outputError, mode, path);
5937
6653
  }
5938
- return input[getRuntimeTypeIssuesSymbol](error.error, mode);
6654
+ return input[getRuntimeTypeIssuesSymbol](error.error, mode, path);
5939
6655
  };
5940
- return createTypeNode("ObjectKeys", Unknown, fromUnknown, runtimeOutput.is, validateOutput, createFromOperation(fromUnknown), to, getTypeIssues, { key, output: type });
6656
+ return createTypeNode("ObjectKeys", Unknown, fromUnknown, runtimeOutput.is, validateOutput, createFromOperation(fromUnknown), to, getTypeIssues, { additionalProperties: { key, output: type } });
5941
6657
  };
5942
6658
  export function result(okType, errorType) {
5943
6659
  return discriminatedUnion("ok", createObjectType({
@@ -5995,21 +6711,28 @@ export function discriminatedUnion(...keyOrMembers) {
5995
6711
  membersByDiscriminator.set(value, member);
5996
6712
  }
5997
6713
  const routeUnknown = (value) => {
5998
- const objectResult = value === null || typeof value !== "object"
5999
- ? err({ type: "Object", reason: { kind: "NotObject", value } })
6000
- : !isPlainObject(value)
6001
- ? err({
6002
- type: "Object",
6003
- reason: { kind: "UnexpectedPrototype", value },
6004
- })
6005
- : ok(value);
6006
- if (!objectResult.ok) {
6714
+ if (value === null || typeof value !== "object") {
6007
6715
  return err({
6008
6716
  type: "DiscriminatedUnion",
6009
- reason: { kind: "Object", error: objectResult.error },
6717
+ reason: {
6718
+ kind: "Object",
6719
+ error: { type: "Object", reason: { kind: "NotObject", value } },
6720
+ },
6010
6721
  });
6011
6722
  }
6012
- const input = objectResult.value;
6723
+ if (!isPlainObject(value)) {
6724
+ return err({
6725
+ type: "DiscriminatedUnion",
6726
+ reason: {
6727
+ kind: "Object",
6728
+ error: {
6729
+ type: "Object",
6730
+ reason: { kind: "UnexpectedPrototype", value },
6731
+ },
6732
+ },
6733
+ });
6734
+ }
6735
+ const input = value;
6013
6736
  const descriptor = globalThis.Object.getOwnPropertyDescriptor(input, key);
6014
6737
  let discriminator;
6015
6738
  if (descriptor === undefined) {
@@ -6050,16 +6773,15 @@ export function discriminatedUnion(...keyOrMembers) {
6050
6773
  }
6051
6774
  return ok(member);
6052
6775
  };
6053
- const wrapMemberResult = (member, result) => result.ok
6054
- ? result
6055
- : err({
6056
- type: "DiscriminatedUnion",
6057
- reason: {
6058
- kind: "Member",
6059
- discriminator: member.props[key].expected,
6060
- error: result.error,
6061
- },
6062
- });
6776
+ const createMemberError = (member, error) => ({
6777
+ type: "DiscriminatedUnion",
6778
+ reason: {
6779
+ kind: "Member",
6780
+ discriminator: member.props[key].expected,
6781
+ error,
6782
+ },
6783
+ });
6784
+ const wrapMemberResult = (member, result) => result.ok ? result : err(createMemberError(member, result.error));
6063
6785
  const validateUnknown = (value, options = firstValidationOptions, useParent, exactOutput) => {
6064
6786
  const routeResult = routeUnknown(value);
6065
6787
  if (!routeResult.ok)
@@ -6098,24 +6820,34 @@ export function discriminatedUnion(...keyOrMembers) {
6098
6820
  }
6099
6821
  };
6100
6822
  const defaultFormatter = formatError;
6101
- const getTypeIssues = (error, mode) => {
6823
+ const getTypeIssues = (error, mode, path) => {
6102
6824
  const discriminatedUnionError = error;
6103
6825
  if (discriminatedUnionError.reason.kind === "Member") {
6104
6826
  const reason = discriminatedUnionError.reason;
6105
6827
  const member = membersByDiscriminator.get(reason.discriminator);
6106
6828
  assertNonNullable(member);
6107
- return member[getRuntimeTypeIssuesSymbol](reason.error, mode);
6829
+ return member[getRuntimeTypeIssuesSymbol](reason.error, mode, path);
6108
6830
  }
6109
6831
  if (discriminatedUnionError.reason.kind === "PropertyAccess" ||
6110
6832
  discriminatedUnionError.reason.kind === "Discriminator") {
6111
- return singleRuntimeTypeIssue("DiscriminatedUnion", discriminatedUnionError, defaultFormatter, [discriminatedUnionError.reason.key]);
6833
+ return singleRuntimeTypeIssue("DiscriminatedUnion", discriminatedUnionError, defaultFormatter, appendIssuePath(path, discriminatedUnionError.reason.key));
6112
6834
  }
6113
- return singleRuntimeTypeIssue("DiscriminatedUnion", error, defaultFormatter);
6835
+ return singleRuntimeTypeIssue("DiscriminatedUnion", error, defaultFormatter, path);
6114
6836
  };
6115
6837
  const parentFromUnknown = (value, options) => validateUnknown(value, options, true, false);
6116
6838
  const parentValidateOutput = (value, options) => validateUnknown(value, options, true, true);
6117
6839
  const parentTo = (value) => route(value).parent[encoderSymbol](value);
6118
- const parent = createTypeNode("DiscriminatedUnion", null, parentFromUnknown, (value) => getOutputMember(value)?.parent.is(value) ?? false, parentValidateOutput, ok, parentTo, getTypeIssues, { key, members });
6840
+ const parent = createTypeNode("DiscriminatedUnion", null, parentFromUnknown, (value) => getOutputMember(value)?.parent.is(value) ?? false, parentValidateOutput, ok, parentTo, getTypeIssues, { additionalProperties: { key, members } });
6841
+ // Identity members return the input itself, and so does routing.
6842
+ const check = getRuntimeChecks(members) &&
6843
+ ((value, options) => {
6844
+ const routeResult = routeUnknown(value);
6845
+ if (!routeResult.ok)
6846
+ return routeResult.error;
6847
+ const member = routeResult.value;
6848
+ const error = member[checkSymbol](value, options);
6849
+ return error === undefined ? undefined : createMemberError(member, error);
6850
+ });
6119
6851
  const fromUnknown = (value, options) => validateUnknown(value, options, false, false);
6120
6852
  const validateOutput = (value, options) => validateUnknown(value, options, false, true);
6121
6853
  const fromParent = (value, options = firstValidationOptions) => {
@@ -6125,7 +6857,7 @@ export function discriminatedUnion(...keyOrMembers) {
6125
6857
  };
6126
6858
  const from = createFromOperation(fromParent);
6127
6859
  const to = (value) => route(value)[encoderSymbol](value);
6128
- return createTypeNode("DiscriminatedUnion", parent, fromUnknown, (value) => getOutputMember(value)?.is(value) ?? false, validateOutput, from, to, getTypeIssues, { key, members });
6860
+ return createTypeNode("DiscriminatedUnion", parent, fromUnknown, (value) => getOutputMember(value)?.is(value) ?? false, validateOutput, from, to, getTypeIssues, { additionalProperties: { key, members }, check });
6129
6861
  }
6130
6862
  export function lazy(getType) {
6131
6863
  let resolution = { state: "unresolved" };
@@ -6167,11 +6899,11 @@ export function lazy(getType) {
6167
6899
  };
6168
6900
  const parentFromUnknown = (value, options = firstValidationOptions) => resolve().root.fromUnknown(value, options);
6169
6901
  const parentTo = (value) => resolve().root[encoderSymbol](value);
6170
- const parent = createTypeNode("Lazy", null, parentFromUnknown, (value) => resolve().root.is(value), (value, options) => resolve().root[outputValidationSymbol](value, options), ok, parentTo, (error, mode) => resolve().root[getRuntimeTypeIssuesSymbol](error, mode));
6902
+ const parent = createTypeNode("Lazy", null, parentFromUnknown, (value) => resolve().root.is(value), (value, options) => resolve().root[outputValidationSymbol](value, options), ok, parentTo, (error, mode, path) => resolve().root[getRuntimeTypeIssuesSymbol](error, mode, path));
6171
6903
  const fromUnknown = (value, options = firstValidationOptions) => resolve().target.fromUnknown(value, options);
6172
6904
  const fromParent = (value, options = firstValidationOptions) => resolve().targetFromInput(value, options);
6173
6905
  const from = createFromOperation(fromParent);
6174
- const type = createTypeNode("Lazy", parent, fromUnknown, (value) => resolve().target.is(value), (value, options) => resolve().target[outputValidationSymbol](value, options), from, (value) => resolve().target[encoderSymbol](value), (error, mode) => resolve().target[getRuntimeTypeIssuesSymbol](error, mode));
6906
+ const type = createTypeNode("Lazy", parent, fromUnknown, (value) => resolve().target.is(value), (value, options) => resolve().target[outputValidationSymbol](value, options), from, (value) => resolve().target[encoderSymbol](value), (error, mode, path) => resolve().target[getRuntimeTypeIssuesSymbol](error, mode, path));
6175
6907
  lazyTypeNodes.add(parent);
6176
6908
  lazyTypeNodes.add(type);
6177
6909
  return type;
@@ -6242,10 +6974,17 @@ const validateData = (value, options = firstValidationOptions) => {
6242
6974
  const kind = getObjectKind(value);
6243
6975
  if (kind === "Array") {
6244
6976
  const array = value;
6245
- for (const key of Reflect.ownKeys(array)) {
6977
+ const keys = Reflect.ownKeys(array);
6978
+ for (let position = 0; position < keys.length; position++) {
6979
+ const key = keys[position];
6246
6980
  if (key === "length")
6247
6981
  continue;
6248
6982
  if (typeof key === "string") {
6983
+ // Ordinary arrays list their indexes first, in ascending order.
6984
+ // Comparing the key first reads `length` only for an index key.
6985
+ if (key === globalThis.String(position) && position < array.length) {
6986
+ continue;
6987
+ }
6249
6988
  const index = globalThis.Number(key) >>> 0;
6250
6989
  if (index < array.length && globalThis.String(index) === key) {
6251
6990
  continue;
@@ -6280,7 +7019,7 @@ const validateData = (value, options = firstValidationOptions) => {
6280
7019
  }
6281
7020
  }
6282
7021
  else if (kind === "Object") {
6283
- for (const key of Reflect.ownKeys(value)) {
7022
+ for (const key of getOwnKeys(value)) {
6284
7023
  const childPath = dataChildPath(path, key);
6285
7024
  if (typeof key === "symbol") {
6286
7025
  if (addIssue({
@@ -6394,7 +7133,7 @@ const validateData = (value, options = firstValidationOptions) => {
6394
7133
  },
6395
7134
  });
6396
7135
  };
6397
- const getDataRuntimeTypeIssues = (error, mode) => {
7136
+ const getDataRuntimeTypeIssues = (error, mode, path) => {
6398
7137
  const dataError = error;
6399
7138
  const issues = mode === "first"
6400
7139
  ? [dataError.reason.issues[0]]
@@ -6407,7 +7146,8 @@ const getDataRuntimeTypeIssues = (error, mode) => {
6407
7146
  type: "Data",
6408
7147
  reason: { kind: "Issues", issues: [issue] },
6409
7148
  },
6410
- path: issue.path,
7149
+ // A root issue shares the path array of its error.
7150
+ path: path.length === 0 ? issue.path : [...path, ...issue.path],
6411
7151
  formatError: ((error) => {
6412
7152
  const issue = error.reason.issues[0];
6413
7153
  switch (issue.kind) {
@@ -6451,7 +7191,12 @@ const getDataRuntimeTypeIssues = (error, mode) => {
6451
7191
  *
6452
7192
  * @group Base
6453
7193
  */
6454
- export const Data = /*#__PURE__*/ createTypeNode("Data", null, validateData, (value) => validateData(value).ok, validateData, ok, identity, getDataRuntimeTypeIssues);
7194
+ export const Data = /*#__PURE__*/ createTypeNode("Data", null, validateData, (value) => validateData(value).ok, validateData, ok, identity, getDataRuntimeTypeIssues, {
7195
+ check: (value, options) => {
7196
+ const result = validateData(value, options);
7197
+ return result.ok ? undefined : result.error;
7198
+ },
7199
+ });
6455
7200
  const emptyJsonValuePath =
6456
7201
  /*#__PURE__*/ globalThis.Object.freeze([]);
6457
7202
  const jsonValuePathToArray = (path) => {
@@ -6534,10 +7279,17 @@ const validateJsonValue = (value, options = firstValidationOptions) => {
6534
7279
  }
6535
7280
  const children = [];
6536
7281
  if (isArray) {
6537
- for (const key of Reflect.ownKeys(value)) {
7282
+ const keys = Reflect.ownKeys(value);
7283
+ for (let position = 0; position < keys.length; position++) {
7284
+ const key = keys[position];
6538
7285
  if (key === "length")
6539
7286
  continue;
6540
7287
  if (typeof key === "string") {
7288
+ // Ordinary arrays list their indexes first, in ascending order.
7289
+ // Comparing the key first reads `length` only for an index key.
7290
+ if (key === globalThis.String(position) && position < value.length) {
7291
+ continue;
7292
+ }
6541
7293
  const index = globalThis.Number(key) >>> 0;
6542
7294
  if (index < value.length && globalThis.String(index) === key) {
6543
7295
  continue;
@@ -6581,7 +7333,7 @@ const validateJsonValue = (value, options = firstValidationOptions) => {
6581
7333
  }
6582
7334
  }
6583
7335
  else {
6584
- for (const key of Reflect.ownKeys(value)) {
7336
+ for (const key of getOwnKeys(value)) {
6585
7337
  const childPath = jsonValueChildPath(path, key);
6586
7338
  if (typeof key === "symbol") {
6587
7339
  if (addIssue({
@@ -6636,7 +7388,7 @@ const validateJsonValue = (value, options = firstValidationOptions) => {
6636
7388
  },
6637
7389
  });
6638
7390
  };
6639
- const getJsonValueRuntimeTypeIssues = (error, mode) => {
7391
+ const getJsonValueRuntimeTypeIssues = (error, mode, path) => {
6640
7392
  const jsonValueError = error;
6641
7393
  const issues = mode === "first"
6642
7394
  ? [jsonValueError.reason.issues[0]]
@@ -6649,7 +7401,7 @@ const getJsonValueRuntimeTypeIssues = (error, mode) => {
6649
7401
  type: "JsonValue",
6650
7402
  reason: { kind: "Issues", issues: [issue] },
6651
7403
  },
6652
- path: issue.path,
7404
+ path: path.length === 0 ? issue.path : [...path, ...issue.path],
6653
7405
  formatError: ((error) => {
6654
7406
  const issue = error.reason.issues[0];
6655
7407
  switch (issue.kind) {
@@ -6766,7 +7518,12 @@ const stringifyJsonValue = (value) => {
6766
7518
  * @group JSON
6767
7519
  */
6768
7520
  export const JsonValue =
6769
- /*#__PURE__*/ createTypeNode("JsonValue", null, validateJsonValue, (value) => validateJsonValue(value).ok, validateJsonValue, ok, identity, getJsonValueRuntimeTypeIssues);
7521
+ /*#__PURE__*/ createTypeNode("JsonValue", null, validateJsonValue, (value) => validateJsonValue(value).ok, validateJsonValue, ok, identity, getJsonValueRuntimeTypeIssues, {
7522
+ check: (value, options) => {
7523
+ const result = validateJsonValue(value, options);
7524
+ return result.ok ? undefined : result.error;
7525
+ },
7526
+ });
6770
7527
  /**
6771
7528
  * Exact top-level JSON array {@link Type}.
6772
7529
  *
@@ -6902,9 +7659,9 @@ export const JsonValueFromJson = /*#__PURE__*/ transform("JsonValueFromJson", Js
6902
7659
  */
6903
7660
  export const json = (type, name, ..._validation) => {
6904
7661
  const runtimeType = type;
6905
- const getTypeIssues = (error, mode) => error.type === name
6906
- ? runtimeType[getRuntimeTypeIssuesSymbol](error.error, mode)
6907
- : Json[getRuntimeTypeIssuesSymbol](error, mode);
7662
+ const getTypeIssues = (error, mode, path) => error.type === name
7663
+ ? runtimeType[getRuntimeTypeIssuesSymbol](error.error, mode, path)
7664
+ : Json[getRuntimeTypeIssuesSymbol](error, mode, path);
6908
7665
  const jsonToTypeOutput = (value, options = firstValidationOptions) => runtimeType.fromUnknown(parseJson(value), options);
6909
7666
  const jsonStringToBrandedJson = (value, options) => {
6910
7667
  const jsonValueResult = jsonToJsonValueResult(value);