zod-compiler 1.28.0 → 2.0.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 (92) hide show
  1. package/README.md +59 -53
  2. package/dist/core/codegen/build-path.d.ts +12 -1
  3. package/dist/core/codegen/build-path.d.ts.map +1 -1
  4. package/dist/core/codegen/build-path.js +47 -9
  5. package/dist/core/codegen/build-path.js.map +1 -1
  6. package/dist/core/codegen/context.d.ts +31 -16
  7. package/dist/core/codegen/context.d.ts.map +1 -1
  8. package/dist/core/codegen/context.js +43 -18
  9. package/dist/core/codegen/context.js.map +1 -1
  10. package/dist/core/codegen/emit-issue.d.ts +17 -20
  11. package/dist/core/codegen/emit-issue.d.ts.map +1 -1
  12. package/dist/core/codegen/emit-issue.js +13 -13
  13. package/dist/core/codegen/emit-issue.js.map +1 -1
  14. package/dist/core/codegen/index.d.ts.map +1 -1
  15. package/dist/core/codegen/index.js +9 -7
  16. package/dist/core/codegen/index.js.map +1 -1
  17. package/dist/core/codegen/issue-decls.d.ts +41 -7
  18. package/dist/core/codegen/issue-decls.d.ts.map +1 -1
  19. package/dist/core/codegen/issue-decls.js +49 -9
  20. package/dist/core/codegen/issue-decls.js.map +1 -1
  21. package/dist/core/codegen/schemas/array.js +1 -1
  22. package/dist/core/codegen/schemas/array.js.map +1 -1
  23. package/dist/core/codegen/schemas/bigint.d.ts.map +1 -1
  24. package/dist/core/codegen/schemas/bigint.js +15 -4
  25. package/dist/core/codegen/schemas/bigint.js.map +1 -1
  26. package/dist/core/codegen/schemas/catch.d.ts.map +1 -1
  27. package/dist/core/codegen/schemas/catch.js +3 -2
  28. package/dist/core/codegen/schemas/catch.js.map +1 -1
  29. package/dist/core/codegen/schemas/discriminated-union.d.ts.map +1 -1
  30. package/dist/core/codegen/schemas/discriminated-union.js +2 -1
  31. package/dist/core/codegen/schemas/discriminated-union.js.map +1 -1
  32. package/dist/core/codegen/schemas/effect.d.ts.map +1 -1
  33. package/dist/core/codegen/schemas/effect.js +4 -2
  34. package/dist/core/codegen/schemas/effect.js.map +1 -1
  35. package/dist/core/codegen/schemas/file.js +2 -8
  36. package/dist/core/codegen/schemas/file.js.map +1 -1
  37. package/dist/core/codegen/schemas/number.js +4 -3
  38. package/dist/core/codegen/schemas/number.js.map +1 -1
  39. package/dist/core/codegen/schemas/object.d.ts +11 -1
  40. package/dist/core/codegen/schemas/object.d.ts.map +1 -1
  41. package/dist/core/codegen/schemas/object.js +58 -14
  42. package/dist/core/codegen/schemas/object.js.map +1 -1
  43. package/dist/core/codegen/schemas/readonly.d.ts.map +1 -1
  44. package/dist/core/codegen/schemas/readonly.js.map +1 -1
  45. package/dist/core/codegen/schemas/record.d.ts.map +1 -1
  46. package/dist/core/codegen/schemas/record.js +8 -3
  47. package/dist/core/codegen/schemas/record.js.map +1 -1
  48. package/dist/core/codegen/schemas/sizeable.d.ts +49 -3
  49. package/dist/core/codegen/schemas/sizeable.d.ts.map +1 -1
  50. package/dist/core/codegen/schemas/sizeable.js +101 -8
  51. package/dist/core/codegen/schemas/sizeable.js.map +1 -1
  52. package/dist/core/codegen/schemas/string.d.ts.map +1 -1
  53. package/dist/core/codegen/schemas/string.js +45 -16
  54. package/dist/core/codegen/schemas/string.js.map +1 -1
  55. package/dist/core/codegen/schemas/tuple.d.ts +14 -11
  56. package/dist/core/codegen/schemas/tuple.d.ts.map +1 -1
  57. package/dist/core/codegen/schemas/tuple.js +99 -49
  58. package/dist/core/codegen/schemas/tuple.js.map +1 -1
  59. package/dist/core/codegen/well-known-regex.d.ts +7 -0
  60. package/dist/core/codegen/well-known-regex.d.ts.map +1 -1
  61. package/dist/core/codegen/well-known-regex.js +10 -3
  62. package/dist/core/codegen/well-known-regex.js.map +1 -1
  63. package/dist/core/extract/checks.d.ts.map +1 -1
  64. package/dist/core/extract/checks.js +1 -0
  65. package/dist/core/extract/checks.js.map +1 -1
  66. package/dist/core/extract/extractors/intersection.js +21 -6
  67. package/dist/core/extract/extractors/intersection.js.map +1 -1
  68. package/dist/core/extract/extractors/object.d.ts.map +1 -1
  69. package/dist/core/extract/extractors/object.js +25 -9
  70. package/dist/core/extract/extractors/object.js.map +1 -1
  71. package/dist/core/extract/extractors/optional.d.ts.map +1 -1
  72. package/dist/core/extract/extractors/optional.js +1 -28
  73. package/dist/core/extract/extractors/optional.js.map +1 -1
  74. package/dist/core/extract/extractors/record.js +9 -3
  75. package/dist/core/extract/extractors/record.js.map +1 -1
  76. package/dist/core/extract/extractors/tuple.d.ts.map +1 -1
  77. package/dist/core/extract/extractors/tuple.js +16 -20
  78. package/dist/core/extract/extractors/tuple.js.map +1 -1
  79. package/dist/core/extract/types.d.ts +16 -8
  80. package/dist/core/extract/types.d.ts.map +1 -1
  81. package/dist/core/iife.d.ts +2 -1
  82. package/dist/core/iife.d.ts.map +1 -1
  83. package/dist/core/iife.js +2 -1
  84. package/dist/core/iife.js.map +1 -1
  85. package/dist/core/types.d.ts +70 -21
  86. package/dist/core/types.d.ts.map +1 -1
  87. package/dist/jit.d.ts.map +1 -1
  88. package/dist/jit.js +47 -12
  89. package/dist/jit.js.map +1 -1
  90. package/dist/runtime.d.ts +3 -1
  91. package/dist/runtime.js +12 -10
  92. package/package.json +2 -2
@@ -3,20 +3,34 @@ import { checkPriority, emitEffectCallable, emitEffectFn, emitRegex, emitRegexSo
3
3
  import { emit } from "../emit.js";
4
4
  import { invalidFormat, invalidType, tooBig, tooSmall } from "../emit-issue.js";
5
5
  import { refineCheck, superRefineCheck, superRefineFastTest } from "./effect.js";
6
- import { whenGatedSizeChecks } from "./sizeable.js";
6
+ import { stringLengthTests, whenGatedSizeChecks } from "./sizeable.js";
7
7
  //#region src/core/codegen/schemas/string.ts
8
8
  /** `re.lastIndex=0;` reset statement for stateful (g/y-flagged) regexes. */
9
9
  function lastIndexReset(regexVar, flags) {
10
10
  return flags && /[gy]/.test(flags) ? `${regexVar}.lastIndex=0;` : "";
11
11
  }
12
12
  /**
13
+ * `regexes.httpProtocol.source`. `parseURLObject` compares a url check's
14
+ * protocol SOURCE against it — not the schema constructor — so any check whose
15
+ * protocol is spelled this way gets the guard, `z.httpUrl()` or not.
16
+ */
17
+ const HTTP_PROTOCOL_SOURCE = "^https?$";
18
+ /**
13
19
  * Generate the url check, mirroring $ZodURL semantics:
14
- * trim → new URL(trimmed) optional hostname/protocol regex tests
15
- * write back url.href (normalize) or the trimmed input.
20
+ * trim → (for an http(s)-protocol check without normalize) require `://`
21
+ * new URL(trimmed) optional hostname/protocol regex tests → write back
22
+ * url.href (normalize) or the trimmed input with its tabs and newlines deleted.
23
+ *
24
+ * The `://` guard is `parseURLObject`'s: without it the URL parser accepts
25
+ * `http:example.com`, and its rejection is a distinct issue (`note: "Invalid
26
+ * URL format"`, no `pattern`) pushed BEFORE the parser runs. The deletion of
27
+ * `\t`, `\n` and `\r` (`stripTabAndNewline`) matches what the parser itself
28
+ * drops before it reads the host, so the returned value names the host that was
29
+ * validated.
16
30
  *
17
31
  * $ZodURL is another constructor that OVERRIDES the `??=`-installed default
18
- * check, so none of the three issues below carries `origin` — and the two that
19
- * do carry a `pattern` use `regex.source`, not the default check's
32
+ * check, so none of the issues below carries `origin` — and the two that do
33
+ * carry a `pattern` use `regex.source`, not the default check's
20
34
  * `regex.toString()` (no delimiters, no flags). Both are reproduced verbatim.
21
35
  */
22
36
  function slowUrlCheck(check, g) {
@@ -46,9 +60,9 @@ function slowUrlCheck(check, g) {
46
60
  })}
47
61
  }`;
48
62
  }
49
- inner += `${g.output}=${check.normalize ? `${urlVar}.href` : trimmedVar};`;
50
- return emit`
51
- var ${trimmedVar}=${g.input}.trim();
63
+ const stripped = check.normalize ? `${urlVar}.href` : `${trimmedVar}.replace(${g.regex("tnl", "[\\t\\n\\r]", "g")},"")`;
64
+ inner += `${g.output}=${stripped};`;
65
+ let parse = emit`
52
66
  var ${urlVar}=null;
53
67
  try{${urlVar}=new URL(${trimmedVar});}catch(_){}
54
68
  if(${urlVar}===null){
@@ -56,6 +70,18 @@ function slowUrlCheck(check, g) {
56
70
  }else{
57
71
  ${inner}
58
72
  }`;
73
+ if (!check.normalize && check.protocol === HTTP_PROTOCOL_SOURCE) parse = emit`
74
+ if(!${g.regex("httpUrl", "^https?:\\/\\/", "i")}.test(${trimmedVar})){
75
+ ${invalidFormat(g, "url", {
76
+ extra: `note:"Invalid URL format"`,
77
+ message: check.message
78
+ })}
79
+ }else{
80
+ ${parse}
81
+ }`;
82
+ return emit`
83
+ var ${trimmedVar}=${g.input}.trim();
84
+ ${parse}`;
59
85
  }
60
86
  function slowString(ir, g) {
61
87
  let code = "";
@@ -70,30 +96,33 @@ function slowString(ir, g) {
70
96
  for (const check of ir.checks) switch (check.kind) {
71
97
  case "min_length":
72
98
  code += emit`
73
- if(${g.input}.length<${check.minimum}){
99
+ if(${stringLengthTests.minFails(g.input, check.minimum, g.ctx)}){
74
100
  ${tooSmall(g, check.minimum, "string", true, { message: check.message })}
75
101
  }`;
76
102
  break;
77
103
  case "max_length":
78
104
  code += emit`
79
- if(${g.input}.length>${check.maximum}){
105
+ if(${stringLengthTests.maxFails(g.input, check.maximum, g.ctx)}){
80
106
  ${tooBig(g, check.maximum, "string", true, { message: check.message })}
81
107
  }`;
82
108
  break;
83
- case "length_equals":
109
+ case "length_equals": {
110
+ const length = g.temp("cl");
84
111
  code += emit`
85
- if(${g.input}.length<${check.length}){
112
+ var ${length}=${stringLengthTests.measure(g.input, check.length, g.ctx)};
113
+ if(${length}<${check.length}){
86
114
  ${tooSmall(g, check.length, "string", true, {
87
115
  exact: true,
88
116
  message: check.message
89
117
  })}
90
- }else if(${g.input}.length>${check.length}){
118
+ }else if(${length}>${check.length}){
91
119
  ${tooBig(g, check.length, "string", true, {
92
120
  exact: true,
93
121
  message: check.message
94
122
  })}
95
123
  }`;
96
124
  break;
125
+ }
97
126
  case "includes":
98
127
  code += emit`
99
128
  if(!${g.input}.includes(${escapeString(check.includes)}${check.position !== void 0 ? `,${check.position}` : ""})){
@@ -183,9 +212,9 @@ function slowString(ir, g) {
183
212
  */
184
213
  function fastStringCheck(check, x, ctx) {
185
214
  switch (check.kind) {
186
- case "min_length": return `${x}.length>=${check.minimum}`;
187
- case "max_length": return `${x}.length<=${check.maximum}`;
188
- case "length_equals": return `${x}.length===${check.length}`;
215
+ case "min_length": return stringLengthTests.min(x, check.minimum, ctx);
216
+ case "max_length": return stringLengthTests.max(x, check.maximum, ctx);
217
+ case "length_equals": return stringLengthTests.equals(x, check.length, ctx);
189
218
  case "includes": return check.position !== void 0 ? `${x}.includes(${escapeString(check.includes)},${check.position})` : `${x}.includes(${escapeString(check.includes)})`;
190
219
  case "starts_with": return `${x}.startsWith(${escapeString(check.prefix)})`;
191
220
  case "ends_with": return `${x}.endsWith(${escapeString(check.suffix)})`;
@@ -1 +1 @@
1
- {"version":3,"file":"string.js","names":[],"sources":["../../../../src/core/codegen/schemas/string.ts"],"sourcesContent":["import type { CheckIR, CheckStringFormat, StringIR } from \"../../types.js\";\nimport type { CodeGenContext, FastGen, SlowGen } from \"../context.js\";\nimport {\n checkPriority,\n emitEffectCallable,\n emitEffectFn,\n emitRegex,\n emitRegexSourceString,\n escapeString,\n} from \"../context.js\";\nimport { emit } from \"../emit.js\";\nimport { invalidFormat, invalidType, tooBig, tooSmall } from \"../emit-issue.js\";\nimport { EMAIL_REGEX_SOURCE, fastTestSource, UUID_REGEX_SOURCE } from \"../well-known-regex.js\";\nimport { refineCheck, superRefineCheck, superRefineFastTest } from \"./effect.js\";\nimport { whenGatedSizeChecks } from \"./sizeable.js\";\n\n/** `re.lastIndex=0;` reset statement for stateful (g/y-flagged) regexes. */\nfunction lastIndexReset(regexVar: string, flags: string | undefined): string {\n return flags && /[gy]/.test(flags) ? `${regexVar}.lastIndex=0;` : \"\";\n}\n\n/**\n * Generate the url check, mirroring $ZodURL semantics:\n * trim → new URL(trimmed) → optional hostname/protocol regex tests →\n * write back url.href (normalize) or the trimmed input.\n *\n * $ZodURL is another constructor that OVERRIDES the `??=`-installed default\n * check, so none of the three issues below carries `origin` — and the two that\n * do carry a `pattern` use `regex.source`, not the default check's\n * `regex.toString()` (no delimiters, no flags). Both are reproduced verbatim.\n */\nfunction slowUrlCheck(check: CheckStringFormat, g: SlowGen): string {\n const trimmedVar = g.temp(\"ut\");\n const urlVar = g.temp(\"u\");\n let inner = \"\";\n if (check.hostname) {\n const re = g.regex(\"host\", check.hostname, check.hostnameFlags);\n inner += emit`\n ${lastIndexReset(re, check.hostnameFlags)}\n if(!${re}.test(${urlVar}.hostname)){\n ${invalidFormat(g, \"url\", {\n extra: `note:\"Invalid hostname\",pattern:${escapeString(check.hostname)}`,\n message: check.message,\n })}\n }`;\n }\n if (check.protocol) {\n const re = g.regex(\"proto\", check.protocol, check.protocolFlags);\n const protoExpr = `(${urlVar}.protocol.endsWith(\":\")?${urlVar}.protocol.slice(0,-1):${urlVar}.protocol)`;\n inner += emit`\n ${lastIndexReset(re, check.protocolFlags)}\n if(!${re}.test(${protoExpr})){\n ${invalidFormat(g, \"url\", {\n extra: `note:\"Invalid protocol\",pattern:${escapeString(check.protocol)}`,\n message: check.message,\n })}\n }`;\n }\n // Zod writes the value back even when hostname/protocol issues were pushed.\n inner += `${g.output}=${check.normalize ? `${urlVar}.href` : trimmedVar};`;\n return emit`\n var ${trimmedVar}=${g.input}.trim();\n var ${urlVar}=null;\n try{${urlVar}=new URL(${trimmedVar});}catch(_){}\n if(${urlVar}===null){\n ${invalidFormat(g, \"url\", { message: check.message })}\n }else{\n ${inner}\n }`;\n}\n\nexport function slowString(ir: StringIR, g: SlowGen): string {\n let code = \"\";\n if (ir.coerce) {\n code += emit`try{${g.output}=String(${g.input});}catch(_){}`;\n }\n code += emit`\n if(typeof ${g.input}!==\"string\"){\n ${invalidType(g, \"string\")}\n ${whenGatedSizeChecks(ir.checks, g, \"length\")}\n }`;\n\n if (ir.checks.length > 0) {\n code += `else{`;\n // Insertion order mirrors zod's issue order for multi-failure inputs;\n // the slow path collects all issues with no short-circuit.\n for (const check of ir.checks) {\n switch (check.kind) {\n case \"min_length\":\n code += emit`\n if(${g.input}.length<${check.minimum}){\n ${tooSmall(g, check.minimum, \"string\", true, { message: check.message })}\n }`;\n break;\n case \"max_length\":\n code += emit`\n if(${g.input}.length>${check.maximum}){\n ${tooBig(g, check.maximum, \"string\", true, { message: check.message })}\n }`;\n break;\n case \"length_equals\":\n code += emit`\n if(${g.input}.length<${check.length}){\n ${tooSmall(g, check.length, \"string\", true, { exact: true, message: check.message })}\n }else if(${g.input}.length>${check.length}){\n ${tooBig(g, check.length, \"string\", true, { exact: true, message: check.message })}\n }`;\n break;\n // includes/starts_with/ends_with each carry `origin:\"string\"` but NO\n // `pattern`: $ZodCheckIncludes/StartsWith/EndsWith bypass\n // $ZodCheckStringFormat entirely (they init from $ZodCheck and assign\n // `inst._zod.check` directly), and the pattern they build is registered\n // in the bag for JSON Schema only, never put on the issue.\n case \"includes\":\n code += emit`\n if(!${g.input}.includes(${escapeString(check.includes)}${check.position !== undefined ? `,${check.position}` : \"\"})){\n ${invalidFormat(g, \"includes\", { origin: \"string\", extra: `includes:${escapeString(check.includes)}`, message: check.message })}\n }`;\n break;\n case \"starts_with\":\n code += emit`\n if(!${g.input}.startsWith(${escapeString(check.prefix)})){\n ${invalidFormat(g, \"starts_with\", { origin: \"string\", extra: `prefix:${escapeString(check.prefix)}`, message: check.message })}\n }`;\n break;\n case \"ends_with\":\n code += emit`\n if(!${g.input}.endsWith(${escapeString(check.suffix)})){\n ${invalidFormat(g, \"ends_with\", { origin: \"string\", extra: `suffix:${escapeString(check.suffix)}`, message: check.message })}\n }`;\n break;\n case \"refine_effect\":\n code += refineCheck(check, g.input, g);\n break;\n case \"super_refine_effect\":\n code += superRefineCheck(check, g.input, g);\n break;\n case \"overwrite_effect\":\n // $ZodCheckOverwrite: value = tx(value). Later checks read the\n // rewritten value because input aliases the output location.\n code += emit`${g.output}=${emitEffectFn(g.ctx, check.source)}(${g.input});`;\n break;\n case \"string_format\": {\n let regexVar: string;\n let pattern: string;\n // Only the BUILT-IN z.url() gets the URL-parser check, and extraction\n // never gives that one a pattern. A `pattern` on a \"url\"-named check\n // therefore marks a custom format that merely borrowed the name\n // (`z.stringFormat(\"url\", /re/)`); it validates through its own regex,\n // so fall through and compile that instead of the URL parser.\n if (check.format === \"url\" && !check.pattern) {\n code += slowUrlCheck(check, g);\n continue;\n }\n if (check.format === \"email\") {\n pattern = check.pattern ?? EMAIL_REGEX_SOURCE;\n regexVar = g.regex(\"email\", pattern, check.patternFlags);\n } else if (check.format === \"regex\" && check.pattern) {\n pattern = check.pattern;\n regexVar = g.regex(\"str\", pattern, check.patternFlags);\n } else if (check.format === \"uuid\") {\n pattern = check.pattern ?? UUID_REGEX_SOURCE;\n regexVar = g.regex(\"uuid\", pattern, check.patternFlags);\n } else {\n if (check.pattern) {\n pattern = check.pattern;\n regexVar = g.regex(\"str\", pattern, check.patternFlags);\n } else {\n // Extraction guarantees a pattern for non-special formats;\n // defensive skip kept for hand-built IR.\n continue;\n }\n }\n // Zod's invalid_format shape depends on WHICH check instance ran.\n // `$ZodCheckStringFormat.init` installs the default pattern check with\n // `??=`, and that default pushes `origin:\"string\"` + `pattern`. A\n // constructor that OVERRIDES `inst._zod.check` pushes its own issue\n // instead — `$ZodCustomStringFormat` (z.stringFormat/z.hex/z.hostname/\n // z.hash) pushes a bare `{code, format, input}` because it validates\n // through `def.fn` and never reads `def.pattern`. We test the pattern\n // either way, so the issue shape is driven off the extracted flag.\n let extra: string | undefined;\n if (!check.bareIssue) {\n // When emitRegex swapped in a faster equivalent pattern, the runtime\n // regex's toString() would leak the rewrite into the issue. Reference\n // the shared original-pattern string instead (pattern came from\n // RegExp.source, so it matches zod's `.toString()` byte-for-byte).\n const rewritten = !check.patternFlags && fastTestSource(pattern) !== null;\n const patternExpr = rewritten\n ? emitRegexSourceString(g.ctx, pattern)\n : `${regexVar}.toString()`;\n extra = `pattern:${patternExpr}`;\n }\n code += emit`\n ${lastIndexReset(regexVar, check.patternFlags)}\n if(!${regexVar}.test(${g.input})){\n ${invalidFormat(g, { expr: escapeString(check.format) }, { origin: check.bareIssue ? undefined : \"string\", extra, message: check.message })}\n }`;\n break;\n }\n }\n }\n code += `}`;\n }\n\n return `${code}\\n`;\n}\n\n/**\n * Boolean expression testing ONE compiled string check against `x`, or null when\n * the check is not expressible as a pure predicate (`z.url()`, which trims and\n * normalizes, and an unknown format with no pattern).\n *\n * Shared by the fast path — which sorts the checks cheapest-first and joins them\n * with `&&` — and by the build path, which emits them one statement at a time in\n * DECLARATION order so an interleaved `.trim()` rewrite is visible to the checks\n * that follow it (see buildString).\n */\nexport function fastStringCheck(check: CheckIR, x: string, ctx: CodeGenContext): string | null {\n switch (check.kind) {\n case \"min_length\":\n return `${x}.length>=${check.minimum}`;\n case \"max_length\":\n return `${x}.length<=${check.maximum}`;\n case \"length_equals\":\n return `${x}.length===${check.length}`;\n case \"includes\":\n return check.position !== undefined\n ? `${x}.includes(${escapeString(check.includes)},${check.position})`\n : `${x}.includes(${escapeString(check.includes)})`;\n case \"starts_with\":\n return `${x}.startsWith(${escapeString(check.prefix)})`;\n case \"ends_with\":\n return `${x}.endsWith(${escapeString(check.suffix)})`;\n case \"string_format\": {\n // URL validation mutates (trims) and uses try/catch — not a predicate.\n // A patterned \"url\" check is a custom format that borrowed the name (see\n // buildString), and its regex IS a predicate.\n if (check.format === \"url\" && !check.pattern) return null;\n let pattern: string;\n let prefix: string;\n if (check.format === \"email\") {\n prefix = \"email\";\n pattern = check.pattern ?? EMAIL_REGEX_SOURCE;\n } else if (check.format === \"uuid\") {\n prefix = \"uuid\";\n pattern = check.pattern ?? UUID_REGEX_SOURCE;\n } else if (check.pattern) {\n prefix = \"re\";\n pattern = check.pattern;\n } else {\n // Unknown format without pattern — can't generate a check\n return null;\n }\n const v = emitRegex(ctx, prefix, pattern, check.patternFlags);\n // Stateful (g/y) regexes need lastIndex reset; comma expression keeps\n // this usable inside the boolean chain.\n return check.patternFlags && /[gy]/.test(check.patternFlags)\n ? `((${v}.lastIndex=0),${v}.test(${x}))`\n : `${v}.test(${x})`;\n }\n default:\n // A check kind this generator does not model (number/bigint/date/set\n // shapes never reach here from a string node).\n return null;\n }\n}\n\nexport function fastString(ir: StringIR, g: FastGen): string | null {\n if (ir.coerce) return null;\n // Overwrite effects rewrite the value — the fast path returns input\n // unchanged, so any mutation makes it ineligible.\n if (ir.checks.some((c) => c.kind === \"overwrite_effect\")) return null;\n\n const x = g.input;\n const parts: string[] = [`typeof ${x}===\"string\"`];\n const checks = ir.checks.filter(\n (c): c is CheckIR => c.kind !== \"refine_effect\" && c.kind !== \"overwrite_effect\",\n );\n\n for (const check of checks.sort(checkPriority)) {\n const expr = fastStringCheck(check, x, g.ctx);\n if (expr === null) return null;\n parts.push(expr);\n }\n\n // Refine effect checks (appended last — run after cheap checks short-circuit)\n for (const check of ir.checks) {\n if (check.kind === \"refine_effect\") {\n parts.push(`${emitEffectCallable(g.ctx, check)}(${x})`);\n } else if (check.kind === \"super_refine_effect\") {\n parts.push(superRefineFastTest(check, x, g));\n }\n }\n\n return parts.join(\"&&\");\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"string.js","names":[],"sources":["../../../../src/core/codegen/schemas/string.ts"],"sourcesContent":["import type { CheckIR, CheckStringFormat, StringIR } from \"../../types.js\";\nimport type { CodeGenContext, FastGen, SlowGen } from \"../context.js\";\nimport {\n checkPriority,\n emitEffectCallable,\n emitEffectFn,\n emitRegex,\n emitRegexSourceString,\n escapeString,\n} from \"../context.js\";\nimport { emit } from \"../emit.js\";\nimport { invalidFormat, invalidType, tooBig, tooSmall } from \"../emit-issue.js\";\nimport { EMAIL_REGEX_SOURCE, fastTestSource, UUID_REGEX_SOURCE } from \"../well-known-regex.js\";\nimport { refineCheck, superRefineCheck, superRefineFastTest } from \"./effect.js\";\nimport { stringLengthTests, whenGatedSizeChecks } from \"./sizeable.js\";\n\n/** `re.lastIndex=0;` reset statement for stateful (g/y-flagged) regexes. */\nfunction lastIndexReset(regexVar: string, flags: string | undefined): string {\n return flags && /[gy]/.test(flags) ? `${regexVar}.lastIndex=0;` : \"\";\n}\n\n/**\n * `regexes.httpProtocol.source`. `parseURLObject` compares a url check's\n * protocol SOURCE against it — not the schema constructor — so any check whose\n * protocol is spelled this way gets the guard, `z.httpUrl()` or not.\n */\nconst HTTP_PROTOCOL_SOURCE = \"^https?$\";\n\n/**\n * Generate the url check, mirroring $ZodURL semantics:\n * trim → (for an http(s)-protocol check without normalize) require `://` →\n * new URL(trimmed) → optional hostname/protocol regex tests → write back\n * url.href (normalize) or the trimmed input with its tabs and newlines deleted.\n *\n * The `://` guard is `parseURLObject`'s: without it the URL parser accepts\n * `http:example.com`, and its rejection is a distinct issue (`note: \"Invalid\n * URL format\"`, no `pattern`) pushed BEFORE the parser runs. The deletion of\n * `\\t`, `\\n` and `\\r` (`stripTabAndNewline`) matches what the parser itself\n * drops before it reads the host, so the returned value names the host that was\n * validated.\n *\n * $ZodURL is another constructor that OVERRIDES the `??=`-installed default\n * check, so none of the issues below carries `origin` — and the two that do\n * carry a `pattern` use `regex.source`, not the default check's\n * `regex.toString()` (no delimiters, no flags). Both are reproduced verbatim.\n */\nfunction slowUrlCheck(check: CheckStringFormat, g: SlowGen): string {\n const trimmedVar = g.temp(\"ut\");\n const urlVar = g.temp(\"u\");\n let inner = \"\";\n if (check.hostname) {\n const re = g.regex(\"host\", check.hostname, check.hostnameFlags);\n inner += emit`\n ${lastIndexReset(re, check.hostnameFlags)}\n if(!${re}.test(${urlVar}.hostname)){\n ${invalidFormat(g, \"url\", {\n extra: `note:\"Invalid hostname\",pattern:${escapeString(check.hostname)}`,\n message: check.message,\n })}\n }`;\n }\n if (check.protocol) {\n const re = g.regex(\"proto\", check.protocol, check.protocolFlags);\n const protoExpr = `(${urlVar}.protocol.endsWith(\":\")?${urlVar}.protocol.slice(0,-1):${urlVar}.protocol)`;\n inner += emit`\n ${lastIndexReset(re, check.protocolFlags)}\n if(!${re}.test(${protoExpr})){\n ${invalidFormat(g, \"url\", {\n extra: `note:\"Invalid protocol\",pattern:${escapeString(check.protocol)}`,\n message: check.message,\n })}\n }`;\n }\n // Zod writes the value back even when hostname/protocol issues were pushed.\n const stripped = check.normalize\n ? `${urlVar}.href`\n : `${trimmedVar}.replace(${g.regex(\"tnl\", \"[\\\\t\\\\n\\\\r]\", \"g\")},\"\")`;\n inner += `${g.output}=${stripped};`;\n let parse = emit`\n var ${urlVar}=null;\n try{${urlVar}=new URL(${trimmedVar});}catch(_){}\n if(${urlVar}===null){\n ${invalidFormat(g, \"url\", { message: check.message })}\n }else{\n ${inner}\n }`;\n if (!check.normalize && check.protocol === HTTP_PROTOCOL_SOURCE) {\n const re = g.regex(\"httpUrl\", \"^https?:\\\\/\\\\/\", \"i\");\n parse = emit`\n if(!${re}.test(${trimmedVar})){\n ${invalidFormat(g, \"url\", { extra: `note:\"Invalid URL format\"`, message: check.message })}\n }else{\n ${parse}\n }`;\n }\n return emit`\n var ${trimmedVar}=${g.input}.trim();\n ${parse}`;\n}\n\nexport function slowString(ir: StringIR, g: SlowGen): string {\n let code = \"\";\n if (ir.coerce) {\n code += emit`try{${g.output}=String(${g.input});}catch(_){}`;\n }\n code += emit`\n if(typeof ${g.input}!==\"string\"){\n ${invalidType(g, \"string\")}\n ${whenGatedSizeChecks(ir.checks, g, \"length\")}\n }`;\n\n if (ir.checks.length > 0) {\n code += `else{`;\n // Insertion order mirrors zod's issue order for multi-failure inputs;\n // the slow path collects all issues with no short-circuit.\n for (const check of ir.checks) {\n switch (check.kind) {\n // Length is measured in code points where the unit count leaves the\n // verdict in doubt — see stringLengthTests.\n case \"min_length\":\n code += emit`\n if(${stringLengthTests.minFails(g.input, check.minimum, g.ctx)}){\n ${tooSmall(g, check.minimum, \"string\", true, { message: check.message })}\n }`;\n break;\n case \"max_length\":\n code += emit`\n if(${stringLengthTests.maxFails(g.input, check.maximum, g.ctx)}){\n ${tooBig(g, check.maximum, \"string\", true, { message: check.message })}\n }`;\n break;\n case \"length_equals\": {\n const length = g.temp(\"cl\");\n code += emit`\n var ${length}=${stringLengthTests.measure(g.input, check.length, g.ctx)};\n if(${length}<${check.length}){\n ${tooSmall(g, check.length, \"string\", true, { exact: true, message: check.message })}\n }else if(${length}>${check.length}){\n ${tooBig(g, check.length, \"string\", true, { exact: true, message: check.message })}\n }`;\n break;\n }\n // includes/starts_with/ends_with each carry `origin:\"string\"` but NO\n // `pattern`: $ZodCheckIncludes/StartsWith/EndsWith bypass\n // $ZodCheckStringFormat entirely (they init from $ZodCheck and assign\n // `inst._zod.check` directly), and the pattern they build is registered\n // in the bag for JSON Schema only, never put on the issue.\n case \"includes\":\n code += emit`\n if(!${g.input}.includes(${escapeString(check.includes)}${check.position !== undefined ? `,${check.position}` : \"\"})){\n ${invalidFormat(g, \"includes\", { origin: \"string\", extra: `includes:${escapeString(check.includes)}`, message: check.message })}\n }`;\n break;\n case \"starts_with\":\n code += emit`\n if(!${g.input}.startsWith(${escapeString(check.prefix)})){\n ${invalidFormat(g, \"starts_with\", { origin: \"string\", extra: `prefix:${escapeString(check.prefix)}`, message: check.message })}\n }`;\n break;\n case \"ends_with\":\n code += emit`\n if(!${g.input}.endsWith(${escapeString(check.suffix)})){\n ${invalidFormat(g, \"ends_with\", { origin: \"string\", extra: `suffix:${escapeString(check.suffix)}`, message: check.message })}\n }`;\n break;\n case \"refine_effect\":\n code += refineCheck(check, g.input, g);\n break;\n case \"super_refine_effect\":\n code += superRefineCheck(check, g.input, g);\n break;\n case \"overwrite_effect\":\n // $ZodCheckOverwrite: value = tx(value). Later checks read the\n // rewritten value because input aliases the output location.\n code += emit`${g.output}=${emitEffectFn(g.ctx, check.source)}(${g.input});`;\n break;\n case \"string_format\": {\n let regexVar: string;\n let pattern: string;\n // Only the BUILT-IN z.url() gets the URL-parser check, and extraction\n // never gives that one a pattern. A `pattern` on a \"url\"-named check\n // therefore marks a custom format that merely borrowed the name\n // (`z.stringFormat(\"url\", /re/)`); it validates through its own regex,\n // so fall through and compile that instead of the URL parser.\n if (check.format === \"url\" && !check.pattern) {\n code += slowUrlCheck(check, g);\n continue;\n }\n if (check.format === \"email\") {\n pattern = check.pattern ?? EMAIL_REGEX_SOURCE;\n regexVar = g.regex(\"email\", pattern, check.patternFlags);\n } else if (check.format === \"regex\" && check.pattern) {\n pattern = check.pattern;\n regexVar = g.regex(\"str\", pattern, check.patternFlags);\n } else if (check.format === \"uuid\") {\n pattern = check.pattern ?? UUID_REGEX_SOURCE;\n regexVar = g.regex(\"uuid\", pattern, check.patternFlags);\n } else {\n if (check.pattern) {\n pattern = check.pattern;\n regexVar = g.regex(\"str\", pattern, check.patternFlags);\n } else {\n // Extraction guarantees a pattern for non-special formats;\n // defensive skip kept for hand-built IR.\n continue;\n }\n }\n // Zod's invalid_format shape depends on WHICH check instance ran.\n // `$ZodCheckStringFormat.init` installs the default pattern check with\n // `??=`, and that default pushes `origin:\"string\"` + `pattern`. A\n // constructor that OVERRIDES `inst._zod.check` pushes its own issue\n // instead — `$ZodCustomStringFormat` (z.stringFormat/z.hex/z.hostname/\n // z.hash) pushes a bare `{code, format, input}` because it validates\n // through `def.fn` and never reads `def.pattern`. We test the pattern\n // either way, so the issue shape is driven off the extracted flag.\n let extra: string | undefined;\n if (!check.bareIssue) {\n // When emitRegex swapped in a faster equivalent pattern, the runtime\n // regex's toString() would leak the rewrite into the issue. Reference\n // the shared original-pattern string instead (pattern came from\n // RegExp.source, so it matches zod's `.toString()` byte-for-byte).\n const rewritten = !check.patternFlags && fastTestSource(pattern) !== null;\n const patternExpr = rewritten\n ? emitRegexSourceString(g.ctx, pattern)\n : `${regexVar}.toString()`;\n extra = `pattern:${patternExpr}`;\n }\n code += emit`\n ${lastIndexReset(regexVar, check.patternFlags)}\n if(!${regexVar}.test(${g.input})){\n ${invalidFormat(g, { expr: escapeString(check.format) }, { origin: check.bareIssue ? undefined : \"string\", extra, message: check.message })}\n }`;\n break;\n }\n }\n }\n code += `}`;\n }\n\n return `${code}\\n`;\n}\n\n/**\n * Boolean expression testing ONE compiled string check against `x`, or null when\n * the check is not expressible as a pure predicate (`z.url()`, which trims and\n * normalizes, and an unknown format with no pattern).\n *\n * Shared by the fast path — which sorts the checks cheapest-first and joins them\n * with `&&` — and by the build path, which emits them one statement at a time in\n * DECLARATION order so an interleaved `.trim()` rewrite is visible to the checks\n * that follow it (see buildString).\n */\nexport function fastStringCheck(check: CheckIR, x: string, ctx: CodeGenContext): string | null {\n switch (check.kind) {\n case \"min_length\":\n return stringLengthTests.min(x, check.minimum, ctx);\n case \"max_length\":\n return stringLengthTests.max(x, check.maximum, ctx);\n case \"length_equals\":\n return stringLengthTests.equals(x, check.length, ctx);\n case \"includes\":\n return check.position !== undefined\n ? `${x}.includes(${escapeString(check.includes)},${check.position})`\n : `${x}.includes(${escapeString(check.includes)})`;\n case \"starts_with\":\n return `${x}.startsWith(${escapeString(check.prefix)})`;\n case \"ends_with\":\n return `${x}.endsWith(${escapeString(check.suffix)})`;\n case \"string_format\": {\n // URL validation mutates (trims) and uses try/catch — not a predicate.\n // A patterned \"url\" check is a custom format that borrowed the name (see\n // buildString), and its regex IS a predicate.\n if (check.format === \"url\" && !check.pattern) return null;\n let pattern: string;\n let prefix: string;\n if (check.format === \"email\") {\n prefix = \"email\";\n pattern = check.pattern ?? EMAIL_REGEX_SOURCE;\n } else if (check.format === \"uuid\") {\n prefix = \"uuid\";\n pattern = check.pattern ?? UUID_REGEX_SOURCE;\n } else if (check.pattern) {\n prefix = \"re\";\n pattern = check.pattern;\n } else {\n // Unknown format without pattern — can't generate a check\n return null;\n }\n const v = emitRegex(ctx, prefix, pattern, check.patternFlags);\n // Stateful (g/y) regexes need lastIndex reset; comma expression keeps\n // this usable inside the boolean chain.\n return check.patternFlags && /[gy]/.test(check.patternFlags)\n ? `((${v}.lastIndex=0),${v}.test(${x}))`\n : `${v}.test(${x})`;\n }\n default:\n // A check kind this generator does not model (number/bigint/date/set\n // shapes never reach here from a string node).\n return null;\n }\n}\n\nexport function fastString(ir: StringIR, g: FastGen): string | null {\n if (ir.coerce) return null;\n // Overwrite effects rewrite the value — the fast path returns input\n // unchanged, so any mutation makes it ineligible.\n if (ir.checks.some((c) => c.kind === \"overwrite_effect\")) return null;\n\n const x = g.input;\n const parts: string[] = [`typeof ${x}===\"string\"`];\n const checks = ir.checks.filter(\n (c): c is CheckIR => c.kind !== \"refine_effect\" && c.kind !== \"overwrite_effect\",\n );\n\n for (const check of checks.sort(checkPriority)) {\n const expr = fastStringCheck(check, x, g.ctx);\n if (expr === null) return null;\n parts.push(expr);\n }\n\n // Refine effect checks (appended last — run after cheap checks short-circuit)\n for (const check of ir.checks) {\n if (check.kind === \"refine_effect\") {\n parts.push(`${emitEffectCallable(g.ctx, check)}(${x})`);\n } else if (check.kind === \"super_refine_effect\") {\n parts.push(superRefineFastTest(check, x, g));\n }\n }\n\n return parts.join(\"&&\");\n}\n"],"mappings":"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"}
@@ -2,18 +2,21 @@ import { SchemaIR, TupleIR } from "../../types.js";
2
2
  import { FastGen, SlowGen } from "../context.js";
3
3
  //#region src/core/codegen/schemas/tuple.d.ts
4
4
  /**
5
- * Mirrors $ZodTuple: without rest, over-length input emits a single too_big
6
- * and `length < optStart - 1` a single too_small (minimum = items.length) —
7
- * both created by the tuple node (schema error applies) and both skip item
8
- * validation. Anything else validates items; missing required items read as
9
- * undefined and fail their item schema's type check at the right path.
5
+ * Mirrors $ZodTuple. Without rest, an input shorter than `optStart` is a single
6
+ * `too_small` (`minimum: optStart`, `inclusive: true`) that skips item
7
+ * validation, and a longer one a single `too_big` that does NOT zod pushes it
8
+ * and runs the items anyway, so their issues follow it. Both are created by
9
+ * the tuple node (schema error applies) and both share the `code, minimum |
10
+ * maximum, inclusive, origin` key order that check-created size issues do not.
10
11
  *
11
- * The two halves of zod's ternary are NOT symmetric about `inclusive`:
12
- * `tooBig ? { code: "too_big", maximum: items.length, inclusive: true } :
13
- * { code: "too_small", minimum: items.length }`. The under-length issue has no
14
- * `inclusive` key at all, so it is emitted with `"omit"` rather than `false`
15
- * which would be an invented field. The locale phrases it ">N items" off the
16
- * key's absence, which is why the message matched even while the shape did not.
12
+ * Every item then runs, absent slots included, and the output is shaped by
13
+ * {@link slotKinds}. The generated code keeps zod's invariant that the output
14
+ * grows one slot at a time from the input's length: a slot that is about to be
15
+ * handled as absent is exactly the output's current length, and once a `skip`
16
+ * or a failed `tail` ends the output, every later absent slot sees a shorter
17
+ * array and falls through. Ending the output at slot `i` needs no truncation
18
+ * beyond what the failed item may have written, because nothing past `i` was
19
+ * ever assigned.
17
20
  */
18
21
  declare function slowTuple(ir: SchemaIR & {
19
22
  type: "tuple";
@@ -1 +1 @@
1
- {"version":3,"file":"tuple.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/tuple.ts"],"mappings":";;;;;;;;;;;;;;;;;iBA2BgB,UAAU,IAAI;EAAa;GAAiB,GAAG;iBAsE/C,UAAU,IAAI,SAAS,GAAG"}
1
+ {"version":3,"file":"tuple.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/tuple.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;iBA4DgB,UAAU,IAAI;EAAa;GAAiB,GAAG;iBAmI/C,UAAU,IAAI,SAAS,GAAG"}
@@ -1,93 +1,141 @@
1
- import { declareFastTemps, extendPath, extendStaticPathIndex, hasMutation, tuplePadsShortInput } from "../context.js";
1
+ import { declareFastTemps, extendPath, extendStaticPathIndex, hasMutation, outputAlwaysDefined, tupleRewritesShortInput } from "../context.js";
2
2
  import { emit } from "../emit.js";
3
3
  import { invalidType, tooBig, tooSmall } from "../emit-issue.js";
4
4
  import { orderByRuntimeCost } from "../fast-size.js";
5
5
  //#region src/core/codegen/schemas/tuple.ts
6
+ function slotKinds(ir) {
7
+ const len = ir.items.length;
8
+ const optoutStart = ir.optoutStart ?? len;
9
+ const optionalIn = new Set(ir.optionalIn ?? []);
10
+ const absentFrom = ir.rest === null ? ir.optStart : 0;
11
+ return ir.items.map((_, i) => {
12
+ if (i < absentFrom) return "present";
13
+ if (i < optoutStart) return "pad";
14
+ return optionalIn.has(i) ? "skip" : "tail";
15
+ });
16
+ }
6
17
  /**
7
- * Mirrors $ZodTuple: without rest, over-length input emits a single too_big
8
- * and `length < optStart - 1` a single too_small (minimum = items.length) —
9
- * both created by the tuple node (schema error applies) and both skip item
10
- * validation. Anything else validates items; missing required items read as
11
- * undefined and fail their item schema's type check at the right path.
18
+ * Mirrors $ZodTuple. Without rest, an input shorter than `optStart` is a single
19
+ * `too_small` (`minimum: optStart`, `inclusive: true`) that skips item
20
+ * validation, and a longer one a single `too_big` that does NOT zod pushes it
21
+ * and runs the items anyway, so their issues follow it. Both are created by
22
+ * the tuple node (schema error applies) and both share the `code, minimum |
23
+ * maximum, inclusive, origin` key order that check-created size issues do not.
12
24
  *
13
- * The two halves of zod's ternary are NOT symmetric about `inclusive`:
14
- * `tooBig ? { code: "too_big", maximum: items.length, inclusive: true } :
15
- * { code: "too_small", minimum: items.length }`. The under-length issue has no
16
- * `inclusive` key at all, so it is emitted with `"omit"` rather than `false`
17
- * which would be an invented field. The locale phrases it ">N items" off the
18
- * key's absence, which is why the message matched even while the shape did not.
25
+ * Every item then runs, absent slots included, and the output is shaped by
26
+ * {@link slotKinds}. The generated code keeps zod's invariant that the output
27
+ * grows one slot at a time from the input's length: a slot that is about to be
28
+ * handled as absent is exactly the output's current length, and once a `skip`
29
+ * or a failed `tail` ends the output, every later absent slot sees a shorter
30
+ * array and falls through. Ending the output at slot `i` needs no truncation
31
+ * beyond what the failed item may have written, because nothing past `i` was
32
+ * ever assigned.
19
33
  */
20
34
  function slowTuple(ir, g) {
21
35
  const len = ir.items.length;
36
+ const kinds = slotKinds(ir);
37
+ const optoutStart = ir.optoutStart ?? len;
38
+ const x = g.temp("ta");
39
+ const itemIssues = ir.rest === null ? g.issues : g.temp("tqi");
22
40
  let code = emit`
23
41
  if(!Array.isArray(${g.input})){
24
42
  ${invalidType(g, "tuple")}
25
- }else{`;
43
+ }else{
44
+ var ${x}=${g.input};
45
+ ${ir.rest === null ? "" : `var ${itemIssues}=[];`}`;
26
46
  let itemsCode = "";
27
- if (ir.items.some(hasMutation) || ir.rest !== null && hasMutation(ir.rest)) itemsCode += `${g.output}=${g.input}.slice();`;
28
- if (tuplePadsShortInput(ir)) {
29
- const padVar = g.temp("tp");
30
- itemsCode += emit`
31
- if(${g.input}.length<${ir.optStart}){
32
- ${g.output}=${g.input}.slice();
33
- for(var ${padVar}=${g.input}.length;${padVar}<${ir.optStart};${padVar}++){
34
- ${g.input}[${padVar}]=undefined;
35
- }
36
- }`;
37
- }
47
+ const mutates = ir.items.some(hasMutation) || ir.rest !== null && hasMutation(ir.rest);
48
+ if (mutates) itemsCode += `${x}=${x}.slice();`;
49
+ const lenVar = kinds.some((k) => k !== "present") ? g.temp("tl") : "";
50
+ if (lenVar) itemsCode += `var ${lenVar}=${x}.length;`;
51
+ if (!mutates && tupleRewritesShortInput(ir)) itemsCode += `if(${lenVar}<${len}){${x}=${x}.slice();}`;
38
52
  for (let i = 0; i < len; i++) {
39
53
  const itemIR = ir.items[i];
40
- const elemExpr = `${g.input}[${i}]`;
54
+ const elemExpr = `${x}[${i}]`;
41
55
  const elemPath = extendStaticPathIndex(g.path, i);
42
- const skipMissingOptional = i >= ir.optStart;
43
56
  const itemCode = g.visit(itemIR, {
44
57
  input: elemExpr,
45
58
  output: elemExpr,
46
- path: elemPath
59
+ path: elemPath,
60
+ issues: itemIssues
47
61
  });
48
- itemsCode += skipMissingOptional ? emit`if(${i}<${g.input}.length){${itemCode}}` : itemCode;
62
+ const absent = `${i}>=${lenVar}`;
63
+ switch (kinds[i]) {
64
+ case "present":
65
+ itemsCode += itemCode;
66
+ break;
67
+ case "skip":
68
+ itemsCode += emit`if(${i}<${lenVar}){${itemCode}}`;
69
+ break;
70
+ case "tail": {
71
+ const beforeVar = g.temp("tb");
72
+ itemsCode += emit`
73
+ if(${i}<${lenVar}||${x}.length===${i}){
74
+ var ${beforeVar}=${itemIssues}.length;
75
+ if(${absent}){${elemExpr}=undefined;}
76
+ ${itemCode}
77
+ if(${absent}&&${itemIssues}.length>${beforeVar}){${itemIssues}.length=${beforeVar};${x}.length=${i};}
78
+ }`;
79
+ break;
80
+ }
81
+ case "pad":
82
+ itemsCode += emit`if(${absent}){${elemExpr}=undefined;}${itemCode}`;
83
+ break;
84
+ }
85
+ }
86
+ if (kinds.some((k, i) => k === "tail" && !outputAlwaysDefined(ir.items[i]))) {
87
+ const tVar = g.temp("tt");
88
+ const floor = optoutStart > 0 ? `${tVar}>=${optoutStart}&&` : "";
89
+ itemsCode += `for(var ${tVar}=${x}.length-1;${floor}${tVar}>=${lenVar}&&${x}[${tVar}]===undefined;${tVar}--){${x}.length=${tVar};}`;
49
90
  }
50
91
  if (ir.rest !== null) {
51
92
  const idxVar = g.temp("ti");
52
- const restExpr = `${g.input}[${idxVar}]`;
93
+ const restExpr = `${x}[${idxVar}]`;
53
94
  const restPath = extendPath(g.path, idxVar);
54
95
  itemsCode += emit`
55
- for(var ${idxVar}=${len};${idxVar}<${g.input}.length;${idxVar}++){
96
+ for(var ${idxVar}=${len};${idxVar}<${x}.length;${idxVar}++){
56
97
  ${g.visit(ir.rest, {
57
98
  input: restExpr,
58
99
  output: restExpr,
59
100
  path: restPath
60
101
  })}
61
102
  }`;
62
- }
63
- if (ir.rest === null) {
64
- const start = ir.optStart;
65
- code += emit`
66
- if(${g.input}.length>${len}){
103
+ const flushVar = g.temp("tqj");
104
+ itemsCode += `for(var ${flushVar}=0;${flushVar}<${itemIssues}.length;${flushVar}++){${g.issues}.push(${itemIssues}[${flushVar}]);}`;
105
+ itemsCode += `${g.output}=${x};`;
106
+ code += itemsCode;
107
+ } else {
108
+ const body = emit`
109
+ if(${x}.length>${len}){
67
110
  ${tooBig(g, len, "array", true, {
68
- useTypeMsg: true,
69
111
  layout: "tuple",
70
112
  aborts: true
71
113
  })}
72
- }else if(${g.input}.length<${start - 1}){
73
- ${tooSmall(g, len, "array", "omit", {
74
- useTypeMsg: true,
114
+ }
115
+ ${itemsCode}
116
+ ${g.output}=${x};`;
117
+ code += ir.optStart > 0 ? emit`
118
+ if(${x}.length<${ir.optStart}){
119
+ ${tooSmall(g, ir.optStart, "array", true, {
120
+ layout: "tuple",
75
121
  aborts: true
76
122
  })}
77
- }else{
78
- ${itemsCode}
79
- }`;
80
- } else code += itemsCode;
123
+ }else{
124
+ ${body}
125
+ }` : body;
126
+ }
81
127
  code += `}\n`;
82
128
  return code;
83
129
  }
84
130
  function fastTuple(ir, g) {
85
131
  const x = g.input;
86
132
  const parts = [`Array.isArray(${x})`];
87
- const required = ir.optStart;
88
- if (ir.rest === null) if (required === ir.items.length) parts.push(`${x}.length===${ir.items.length}`);
89
- else parts.push(`${x}.length>=${required}`, `${x}.length<=${ir.items.length}`);
90
- else if (required > 0) parts.push(`${x}.length>=${required}`);
133
+ const kinds = slotKinds(ir);
134
+ if (ir.rest === null) if (ir.optStart === ir.items.length) parts.push(`${x}.length===${ir.items.length}`);
135
+ else {
136
+ if (ir.optStart > 0) parts.push(`${x}.length>=${ir.optStart}`);
137
+ parts.push(`${x}.length<=${ir.items.length}`);
138
+ }
91
139
  const indexed = ir.items.flatMap((itemIR, index) => itemIR ? [{
92
140
  index,
93
141
  itemIR
@@ -95,8 +143,10 @@ function fastTuple(ir, g) {
95
143
  for (const { index, itemIR } of orderByRuntimeCost(indexed, (e) => e.itemIR, g.ctx)) {
96
144
  const itemCheck = g.visit(itemIR, { input: `${x}[${index}]` });
97
145
  if (itemCheck === null) return null;
98
- if (itemCheck === "true") continue;
99
- parts.push(index >= ir.optStart ? `(${x}.length<=${index}||${itemCheck})` : itemCheck);
146
+ const kind = kinds[index];
147
+ if (kind === "pad" || kind === "tail") parts.push(itemCheck === "true" ? `${x}.length>${index}` : `${x}.length>${index}&&${itemCheck}`);
148
+ else if (itemCheck === "true") continue;
149
+ else parts.push(kind === "skip" ? `(${x}.length<=${index}||${itemCheck})` : itemCheck);
100
150
  }
101
151
  if (ir.rest !== null) {
102
152
  const rv = g.temp("tr");
@@ -1 +1 @@
1
- {"version":3,"file":"tuple.js","names":[],"sources":["../../../../src/core/codegen/schemas/tuple.ts"],"sourcesContent":["import type { SchemaIR, TupleIR } from \"../../types.js\";\nimport type { FastGen, SlowGen } from \"../context.js\";\nimport {\n declareFastTemps,\n extendPath,\n extendStaticPathIndex,\n hasMutation,\n tuplePadsShortInput,\n} from \"../context.js\";\nimport { orderByRuntimeCost } from \"../fast-size.js\";\nimport { emit } from \"../emit.js\";\nimport { invalidType, tooBig, tooSmall } from \"../emit-issue.js\";\n\n/**\n * Mirrors $ZodTuple: without rest, over-length input emits a single too_big\n * and `length < optStart - 1` a single too_small (minimum = items.length) —\n * both created by the tuple node (schema error applies) and both skip item\n * validation. Anything else validates items; missing required items read as\n * undefined and fail their item schema's type check at the right path.\n *\n * The two halves of zod's ternary are NOT symmetric about `inclusive`:\n * `tooBig ? { code: \"too_big\", maximum: items.length, inclusive: true } :\n * { code: \"too_small\", minimum: items.length }`. The under-length issue has no\n * `inclusive` key at all, so it is emitted with `\"omit\"` rather than `false` —\n * which would be an invented field. The locale phrases it \">N items\" off the\n * key's absence, which is why the message matched even while the shape did not.\n */\nexport function slowTuple(ir: SchemaIR & { type: \"tuple\" }, g: SlowGen): string {\n const len = ir.items.length;\n\n let code = emit`\n if(!Array.isArray(${g.input})){\n ${invalidType(g, \"tuple\")}\n }else{`;\n\n let itemsCode = \"\";\n if (ir.items.some(hasMutation) || (ir.rest !== null && hasMutation(ir.rest))) {\n itemsCode += `${g.output}=${g.input}.slice();`;\n }\n\n // Pad a short input up to `optStart`. zod runs every item below that index\n // whatever the input's length and writes each result back\n // (`final.value[i] = result.value`), so a required slot past the end lands as\n // an own `undefined` and the output array is LONGER than the input — visible\n // only when that item accepts undefined, which is what tuplePadsShortInput\n // tests (`z.tuple([z.any(), z.any()]).parse([\"x\"])` is `[\"x\", undefined]`).\n // Guarded on the length, so a well-formed input never allocates: this runs\n // only where the item loop is about to read past the end anyway.\n if (tuplePadsShortInput(ir)) {\n const padVar = g.temp(\"tp\");\n itemsCode += emit`\n if(${g.input}.length<${ir.optStart}){\n ${g.output}=${g.input}.slice();\n for(var ${padVar}=${g.input}.length;${padVar}<${ir.optStart};${padVar}++){\n ${g.input}[${padVar}]=undefined;\n }\n }`;\n }\n\n for (let i = 0; i < len; i++) {\n const itemIR = ir.items[i] as SchemaIR;\n const elemExpr = `${g.input}[${i}]`;\n const elemPath = extendStaticPathIndex(g.path, i);\n const skipMissingOptional = i >= ir.optStart;\n const itemCode = g.visit(itemIR, { input: elemExpr, output: elemExpr, path: elemPath });\n // Zod skips absent omittable items entirely (no default materialization).\n itemsCode += skipMissingOptional ? emit`if(${i}<${g.input}.length){${itemCode}}` : itemCode;\n }\n\n if (ir.rest !== null) {\n const idxVar = g.temp(\"ti\");\n const restExpr = `${g.input}[${idxVar}]`;\n const restPath = extendPath(g.path, idxVar);\n itemsCode += emit`\n for(var ${idxVar}=${len};${idxVar}<${g.input}.length;${idxVar}++){\n ${g.visit(ir.rest, { input: restExpr, output: restExpr, path: restPath })}\n }`;\n }\n\n if (ir.rest === null) {\n const start = ir.optStart;\n code += emit`\n if(${g.input}.length>${len}){\n ${tooBig(g, len, \"array\", true, { useTypeMsg: true, layout: \"tuple\", aborts: true })}\n }else if(${g.input}.length<${start - 1}){\n ${tooSmall(g, len, \"array\", \"omit\", { useTypeMsg: true, aborts: true })}\n }else{\n ${itemsCode}\n }`;\n } else {\n code += itemsCode;\n }\n\n code += `}\\n`;\n return code;\n}\n\nexport function fastTuple(ir: TupleIR, g: FastGen): string | null {\n const x = g.input;\n const parts: string[] = [`Array.isArray(${x})`];\n\n const required = ir.optStart;\n if (ir.rest === null) {\n if (required === ir.items.length) {\n parts.push(`${x}.length===${ir.items.length}`);\n } else {\n parts.push(`${x}.length>=${required}`, `${x}.length<=${ir.items.length}`);\n }\n } else if (required > 0) {\n parts.push(`${x}.length>=${required}`);\n }\n\n // Per-index checks, cheapest-first: positions are independent, so the emitted\n // order only decides which one a reject stops on (see estimateRuntimeCost).\n // The Array.isArray + length conjuncts above stay in front — the element\n // reads are only meaningful once they hold.\n const indexed = ir.items.flatMap((itemIR, index) => (itemIR ? [{ index, itemIR }] : []));\n for (const { index, itemIR } of orderByRuntimeCost(indexed, (e) => e.itemIR, g.ctx)) {\n const itemCheck = g.visit(itemIR, { input: `${x}[${index}]` });\n if (itemCheck === null) return null;\n if (itemCheck === \"true\") continue;\n // An item in the omittable tail is not merely allowed to be `undefined` —\n // zod does not run its schema AT ALL when the input is that short\n // (`if (i >= input.length) if (i >= optStart) continue`). Reading `x[i]` and\n // testing it would demand that the item's own check accept `undefined`,\n // which the fast forms of `z.undefined()` and `.optional()` happen to do but\n // a pipe or a union arm need not — so gate on presence the way zod does.\n parts.push(index >= ir.optStart ? `(${x}.length<=${index}||${itemCheck})` : itemCheck);\n }\n\n // Rest element validation via preamble helper (avoids .slice().every()\n // allocation). Fresh scope: the helper is its own function, size-gated\n // independently. (Fixed items above stay inline in the caller's && chain.)\n if (ir.rest !== null) {\n const rv = g.temp(\"tr\");\n const restGen = g.scoped(rv);\n const restCheck = restGen.visit(ir.rest);\n if (restCheck === null) return null;\n if (restCheck !== \"true\") {\n const helperName = g.temp(\"te\");\n g.ctx.preamble.push(\n `function ${helperName}(a,s){${declareFastTemps(restGen.scope)}for(var ${rv},i=s;i<a.length;i++){${rv}=a[i];if(!(${restCheck})){return false;}}return true;}`,\n );\n parts.push(`${helperName}(${x},${ir.items.length})`);\n }\n }\n\n return parts.join(\"&&\");\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"tuple.js","names":[],"sources":["../../../../src/core/codegen/schemas/tuple.ts"],"sourcesContent":["import type { SchemaIR, TupleIR } from \"../../types.js\";\nimport type { FastGen, SlowGen } from \"../context.js\";\nimport {\n declareFastTemps,\n extendPath,\n extendStaticPathIndex,\n hasMutation,\n outputAlwaysDefined,\n tupleRewritesShortInput,\n} from \"../context.js\";\nimport { orderByRuntimeCost } from \"../fast-size.js\";\nimport { emit } from \"../emit.js\";\nimport { invalidType, tooBig, tooSmall } from \"../emit-issue.js\";\n\n/**\n * What an ABSENT slot (index at or past the input's length) does to the output,\n * per zod's `handleTupleResults`:\n *\n * - `present`: can never be absent — below `optStart` without a rest element,\n * where a shorter input is already `too_small`.\n * - `skip`: at or past `optoutStart` and on the \"optional\" rung of `optin`.\n * The output ends here; the item is not consulted and nothing after it runs.\n * - `tail`: at or past `optoutStart` on any other rung. The item runs on\n * `undefined`; a failure ends the output here with its issues dropped, a\n * success is written back and may be trimmed later if it is `undefined`.\n * - `pad`: below `optoutStart`, so a later slot has to keep its index. The\n * item runs on `undefined`, its issues count, and its result — an own\n * `undefined` at the least — is written back.\n */\ntype SlotKind = \"present\" | \"skip\" | \"tail\" | \"pad\";\n\nfunction slotKinds(ir: TupleIR): SlotKind[] {\n const len = ir.items.length;\n const optoutStart = ir.optoutStart ?? len;\n const optionalIn = new Set(ir.optionalIn ?? []);\n const absentFrom = ir.rest === null ? ir.optStart : 0;\n return ir.items.map((_, i) => {\n if (i < absentFrom) return \"present\";\n if (i < optoutStart) return \"pad\";\n return optionalIn.has(i) ? \"skip\" : \"tail\";\n });\n}\n\n/**\n * Mirrors $ZodTuple. Without rest, an input shorter than `optStart` is a single\n * `too_small` (`minimum: optStart`, `inclusive: true`) that skips item\n * validation, and a longer one a single `too_big` that does NOT — zod pushes it\n * and runs the items anyway, so their issues follow it. Both are created by\n * the tuple node (schema error applies) and both share the `code, minimum |\n * maximum, inclusive, origin` key order that check-created size issues do not.\n *\n * Every item then runs, absent slots included, and the output is shaped by\n * {@link slotKinds}. The generated code keeps zod's invariant that the output\n * grows one slot at a time from the input's length: a slot that is about to be\n * handled as absent is exactly the output's current length, and once a `skip`\n * or a failed `tail` ends the output, every later absent slot sees a shorter\n * array and falls through. Ending the output at slot `i` needs no truncation\n * beyond what the failed item may have written, because nothing past `i` was\n * ever assigned.\n */\nexport function slowTuple(ir: SchemaIR & { type: \"tuple\" }, g: SlowGen): string {\n const len = ir.items.length;\n const kinds = slotKinds(ir);\n const optoutStart = ir.optoutStart ?? len;\n\n // Every read and write goes through ONE binding, seeded from the input and\n // re-pointed by each copy, with the result written to `g.output` at the end.\n // Reading `g.input` and writing `g.output` directly only worked while the two\n // were the same identifier — true at every `createSlowGen` root, but NOT under\n // `z.preprocess()`, which visits with `{ input: valueVar, output: g.output }`\n // (see slowEffect). There a copy landed in `g.output` while the item writes\n // went on hitting the original, so the output kept the pristine short array\n // and the caller's array collected the padding instead.\n const x = g.temp(\"ta\");\n\n // With a rest element the fixed items' issues are BUFFERED and flushed after\n // the rest loop, because that is the order zod reports them in: `$ZodTuple`\n // collects the fixed items into `itemResults` WITHOUT touching the payload,\n // runs the rest loop (which pushes through `handleTupleResult`), and only\n // then calls `handleTupleResults`, which pushes what it buffered. So\n // `z.tuple([z.string()]).rest(z.number())` on `[1,\"b\"]` reports the rest\n // element's issue first. `ZodError.message` is `JSON.stringify(issues)`, so\n // the order is user-visible, not just an array detail.\n const itemIssues = ir.rest === null ? g.issues : g.temp(\"tqi\");\n\n let code = emit`\n if(!Array.isArray(${g.input})){\n ${invalidType(g, \"tuple\")}\n }else{\n var ${x}=${g.input};\n ${ir.rest === null ? \"\" : `var ${itemIssues}=[];`}`;\n\n let itemsCode = \"\";\n const mutates = ir.items.some(hasMutation) || (ir.rest !== null && hasMutation(ir.rest));\n if (mutates) {\n itemsCode += `${x}=${x}.slice();`;\n }\n\n // The input's length, read once: absent slots are written back and would\n // otherwise shift the presence test for the slots after them.\n const lenVar = kinds.some((k) => k !== \"present\") ? g.temp(\"tl\") : \"\";\n if (lenVar) itemsCode += `var ${lenVar}=${x}.length;`;\n // A short input gains slots, so it is copied before the first write — the\n // caller's array must not grow. Guarded on the length so a well-formed input\n // never allocates; a mutating item has already paid for the copy.\n if (!mutates && tupleRewritesShortInput(ir)) {\n itemsCode += `if(${lenVar}<${len}){${x}=${x}.slice();}`;\n }\n\n for (let i = 0; i < len; i++) {\n const itemIR = ir.items[i] as SchemaIR;\n const elemExpr = `${x}[${i}]`;\n const elemPath = extendStaticPathIndex(g.path, i);\n const itemCode = g.visit(itemIR, {\n input: elemExpr,\n output: elemExpr,\n path: elemPath,\n issues: itemIssues,\n });\n // One copy of the item code per slot: an absent slot is materialized as an\n // own `undefined` first (`x[i] = undefined`, which is also what extends the\n // copied array), then validated exactly as a present one would be.\n const absent = `${i}>=${lenVar}`;\n switch (kinds[i]) {\n case \"present\":\n itemsCode += itemCode;\n break;\n case \"skip\":\n itemsCode += emit`if(${i}<${lenVar}){${itemCode}}`;\n break;\n case \"tail\": {\n const beforeVar = g.temp(\"tb\");\n itemsCode += emit`\n if(${i}<${lenVar}||${x}.length===${i}){\n var ${beforeVar}=${itemIssues}.length;\n if(${absent}){${elemExpr}=undefined;}\n ${itemCode}\n if(${absent}&&${itemIssues}.length>${beforeVar}){${itemIssues}.length=${beforeVar};${x}.length=${i};}\n }`;\n break;\n }\n case \"pad\":\n itemsCode += emit`if(${absent}){${elemExpr}=undefined;}${itemCode}`;\n break;\n }\n }\n\n // zod's trailing trim: absent slots that produced `undefined` are dropped\n // from the end while the item is optional-out. Only a `tail` slot can leave\n // one behind (a `pad` slot sits below `optoutStart`, a `skip` slot writes\n // nothing), so the loop is emitted only when some tail item might.\n if (kinds.some((k, i) => k === \"tail\" && !outputAlwaysDefined(ir.items[i] as SchemaIR))) {\n const tVar = g.temp(\"tt\");\n const floor = optoutStart > 0 ? `${tVar}>=${optoutStart}&&` : \"\";\n itemsCode += `for(var ${tVar}=${x}.length-1;${floor}${tVar}>=${lenVar}&&${x}[${tVar}]===undefined;${tVar}--){${x}.length=${tVar};}`;\n }\n\n if (ir.rest !== null) {\n const idxVar = g.temp(\"ti\");\n const restExpr = `${x}[${idxVar}]`;\n const restPath = extendPath(g.path, idxVar);\n itemsCode += emit`\n for(var ${idxVar}=${len};${idxVar}<${x}.length;${idxVar}++){\n ${g.visit(ir.rest, { input: restExpr, output: restExpr, path: restPath })}\n }`;\n const flushVar = g.temp(\"tqj\");\n itemsCode += `for(var ${flushVar}=0;${flushVar}<${itemIssues}.length;${flushVar}++){${g.issues}.push(${itemIssues}[${flushVar}]);}`;\n itemsCode += `${g.output}=${x};`;\n code += itemsCode;\n } else {\n const body = emit`\n if(${x}.length>${len}){\n ${tooBig(g, len, \"array\", true, { layout: \"tuple\", aborts: true })}\n }\n ${itemsCode}\n ${g.output}=${x};`;\n code +=\n ir.optStart > 0\n ? emit`\n if(${x}.length<${ir.optStart}){\n ${tooSmall(g, ir.optStart, \"array\", true, { layout: \"tuple\", aborts: true })}\n }else{\n ${body}\n }`\n : body;\n }\n\n code += `}\\n`;\n return code;\n}\n\nexport function fastTuple(ir: TupleIR, g: FastGen): string | null {\n const x = g.input;\n const parts: string[] = [`Array.isArray(${x})`];\n const kinds = slotKinds(ir);\n\n if (ir.rest === null) {\n if (ir.optStart === ir.items.length) {\n parts.push(`${x}.length===${ir.items.length}`);\n } else {\n if (ir.optStart > 0) parts.push(`${x}.length>=${ir.optStart}`);\n parts.push(`${x}.length<=${ir.items.length}`);\n }\n }\n\n // Per-index checks, cheapest-first: positions are independent, so the emitted\n // order only decides which one a reject stops on (see estimateRuntimeCost).\n // The Array.isArray + length conjuncts above stay in front — the element\n // reads are only meaningful once they hold.\n const indexed = ir.items.flatMap((itemIR, index) => (itemIR ? [{ index, itemIR }] : []));\n for (const { index, itemIR } of orderByRuntimeCost(indexed, (e) => e.itemIR, g.ctx)) {\n const itemCheck = g.visit(itemIR, { input: `${x}[${index}]` });\n if (itemCheck === null) return null;\n const kind = kinds[index];\n // The fast check answers true only where the output IS the input (see\n // fastResultIsInput), so a slot that would be written back when absent —\n // `pad` and `tail` alike — must be present here; the slow walk builds the\n // longer output. A `skip` slot is the reverse: absent, it is accepted\n // whatever its item says, since zod ends the output there without\n // consulting it.\n if (kind === \"pad\" || kind === \"tail\") {\n parts.push(\n itemCheck === \"true\" ? `${x}.length>${index}` : `${x}.length>${index}&&${itemCheck}`,\n );\n } else if (itemCheck === \"true\") {\n continue;\n } else {\n parts.push(kind === \"skip\" ? `(${x}.length<=${index}||${itemCheck})` : itemCheck);\n }\n }\n\n // Rest element validation via preamble helper (avoids .slice().every()\n // allocation). Fresh scope: the helper is its own function, size-gated\n // independently. (Fixed items above stay inline in the caller's && chain.)\n if (ir.rest !== null) {\n const rv = g.temp(\"tr\");\n const restGen = g.scoped(rv);\n const restCheck = restGen.visit(ir.rest);\n if (restCheck === null) return null;\n if (restCheck !== \"true\") {\n const helperName = g.temp(\"te\");\n g.ctx.preamble.push(\n `function ${helperName}(a,s){${declareFastTemps(restGen.scope)}for(var ${rv},i=s;i<a.length;i++){${rv}=a[i];if(!(${restCheck})){return false;}}return true;}`,\n );\n parts.push(`${helperName}(${x},${ir.items.length})`);\n }\n }\n\n return parts.join(\"&&\");\n}\n"],"mappings":";;;;;AA+BA,SAAS,UAAU,IAAyB;CAC1C,MAAM,MAAM,GAAG,MAAM;CACrB,MAAM,cAAc,GAAG,eAAe;CACtC,MAAM,aAAa,IAAI,IAAI,GAAG,cAAc,CAAC,CAAC;CAC9C,MAAM,aAAa,GAAG,SAAS,OAAO,GAAG,WAAW;CACpD,OAAO,GAAG,MAAM,KAAK,GAAG,MAAM;EAC5B,IAAI,IAAI,YAAY,OAAO;EAC3B,IAAI,IAAI,aAAa,OAAO;EAC5B,OAAO,WAAW,IAAI,CAAC,IAAI,SAAS;CACtC,CAAC;AACH;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,UAAU,IAAkC,GAAoB;CAC9E,MAAM,MAAM,GAAG,MAAM;CACrB,MAAM,QAAQ,UAAU,EAAE;CAC1B,MAAM,cAAc,GAAG,eAAe;CAUtC,MAAM,IAAI,EAAE,KAAK,IAAI;CAUrB,MAAM,aAAa,GAAG,SAAS,OAAO,EAAE,SAAS,EAAE,KAAK,KAAK;CAE7D,IAAI,OAAO,IAAI;wBACO,EAAE,MAAM;QACxB,YAAY,GAAG,OAAO,EAAE;;YAEpB,EAAE,GAAG,EAAE,MAAM;QACjB,GAAG,SAAS,OAAO,KAAK,OAAO,WAAW;CAEhD,IAAI,YAAY;CAChB,MAAM,UAAU,GAAG,MAAM,KAAK,WAAW,KAAM,GAAG,SAAS,QAAQ,YAAY,GAAG,IAAI;CACtF,IAAI,SACF,aAAa,GAAG,EAAE,GAAG,EAAE;CAKzB,MAAM,SAAS,MAAM,MAAM,MAAM,MAAM,SAAS,IAAI,EAAE,KAAK,IAAI,IAAI;CACnE,IAAI,QAAQ,aAAa,OAAO,OAAO,GAAG,EAAE;CAI5C,IAAI,CAAC,WAAW,wBAAwB,EAAE,GACxC,aAAa,MAAM,OAAO,GAAG,IAAI,IAAI,EAAE,GAAG,EAAE;CAG9C,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,KAAK;EAC5B,MAAM,SAAS,GAAG,MAAM;EACxB,MAAM,WAAW,GAAG,EAAE,GAAG,EAAE;EAC3B,MAAM,WAAW,sBAAsB,EAAE,MAAM,CAAC;EAChD,MAAM,WAAW,EAAE,MAAM,QAAQ;GAC/B,OAAO;GACP,QAAQ;GACR,MAAM;GACN,QAAQ;EACV,CAAC;EAID,MAAM,SAAS,GAAG,EAAE,IAAI;EACxB,QAAQ,MAAM,IAAd;GACE,KAAK;IACH,aAAa;IACb;GACF,KAAK;IACH,aAAa,IAAI,MAAM,EAAE,GAAG,OAAO,IAAI,SAAS;IAChD;GACF,KAAK,QAAQ;IACX,MAAM,YAAY,EAAE,KAAK,IAAI;IAC7B,aAAa,IAAI;eACV,EAAE,GAAG,OAAO,IAAI,EAAE,YAAY,EAAE;kBAC7B,UAAU,GAAG,WAAW;iBACzB,OAAO,IAAI,SAAS;cACvB,SAAS;iBACN,OAAO,IAAI,WAAW,UAAU,UAAU,IAAI,WAAW,UAAU,UAAU,GAAG,EAAE,UAAU,EAAE;;IAEvG;GACF;GACA,KAAK;IACH,aAAa,IAAI,MAAM,OAAO,IAAI,SAAS,cAAc;IACzD;EACJ;CACF;CAMA,IAAI,MAAM,MAAM,GAAG,MAAM,MAAM,UAAU,CAAC,oBAAoB,GAAG,MAAM,EAAc,CAAC,GAAG;EACvF,MAAM,OAAO,EAAE,KAAK,IAAI;EACxB,MAAM,QAAQ,cAAc,IAAI,GAAG,KAAK,IAAI,YAAY,MAAM;EAC9D,aAAa,WAAW,KAAK,GAAG,EAAE,YAAY,QAAQ,KAAK,IAAI,OAAO,IAAI,EAAE,GAAG,KAAK,gBAAgB,KAAK,MAAM,EAAE,UAAU,KAAK;CAClI;CAEA,IAAI,GAAG,SAAS,MAAM;EACpB,MAAM,SAAS,EAAE,KAAK,IAAI;EAC1B,MAAM,WAAW,GAAG,EAAE,GAAG,OAAO;EAChC,MAAM,WAAW,WAAW,EAAE,MAAM,MAAM;EAC1C,aAAa,IAAI;gBACL,OAAO,GAAG,IAAI,GAAG,OAAO,GAAG,EAAE,UAAU,OAAO;UACpD,EAAE,MAAM,GAAG,MAAM;GAAE,OAAO;GAAU,QAAQ;GAAU,MAAM;EAAS,CAAC,EAAE;;EAE9E,MAAM,WAAW,EAAE,KAAK,KAAK;EAC7B,aAAa,WAAW,SAAS,KAAK,SAAS,GAAG,WAAW,UAAU,SAAS,MAAM,EAAE,OAAO,QAAQ,WAAW,GAAG,SAAS;EAC9H,aAAa,GAAG,EAAE,OAAO,GAAG,EAAE;EAC9B,QAAQ;CACV,OAAO;EACL,MAAM,OAAO,IAAI;WACV,EAAE,UAAU,IAAI;UACjB,OAAO,GAAG,KAAK,SAAS,MAAM;GAAE,QAAQ;GAAS,QAAQ;EAAK,CAAC,EAAE;;QAEnE,UAAU;QACV,EAAE,OAAO,GAAG,EAAE;EAClB,QACE,GAAG,WAAW,IACV,IAAI;eACC,EAAE,UAAU,GAAG,SAAS;cACzB,SAAS,GAAG,GAAG,UAAU,SAAS,MAAM;GAAE,QAAQ;GAAS,QAAQ;EAAK,CAAC,EAAE;;cAE3E,KAAK;eAET;CACR;CAEA,QAAQ;CACR,OAAO;AACT;AAEA,SAAgB,UAAU,IAAa,GAA2B;CAChE,MAAM,IAAI,EAAE;CACZ,MAAM,QAAkB,CAAC,iBAAiB,EAAE,EAAE;CAC9C,MAAM,QAAQ,UAAU,EAAE;CAE1B,IAAI,GAAG,SAAS,MACd,IAAI,GAAG,aAAa,GAAG,MAAM,QAC3B,MAAM,KAAK,GAAG,EAAE,YAAY,GAAG,MAAM,QAAQ;MACxC;EACL,IAAI,GAAG,WAAW,GAAG,MAAM,KAAK,GAAG,EAAE,WAAW,GAAG,UAAU;EAC7D,MAAM,KAAK,GAAG,EAAE,WAAW,GAAG,MAAM,QAAQ;CAC9C;CAOF,MAAM,UAAU,GAAG,MAAM,SAAS,QAAQ,UAAW,SAAS,CAAC;EAAE;EAAO;CAAO,CAAC,IAAI,CAAC,CAAE;CACvF,KAAK,MAAM,EAAE,OAAO,YAAY,mBAAmB,UAAU,MAAM,EAAE,QAAQ,EAAE,GAAG,GAAG;EACnF,MAAM,YAAY,EAAE,MAAM,QAAQ,EAAE,OAAO,GAAG,EAAE,GAAG,MAAM,GAAG,CAAC;EAC7D,IAAI,cAAc,MAAM,OAAO;EAC/B,MAAM,OAAO,MAAM;EAOnB,IAAI,SAAS,SAAS,SAAS,QAC7B,MAAM,KACJ,cAAc,SAAS,GAAG,EAAE,UAAU,UAAU,GAAG,EAAE,UAAU,MAAM,IAAI,WAC3E;OACK,IAAI,cAAc,QACvB;OAEA,MAAM,KAAK,SAAS,SAAS,IAAI,EAAE,WAAW,MAAM,IAAI,UAAU,KAAK,SAAS;CAEpF;CAKA,IAAI,GAAG,SAAS,MAAM;EACpB,MAAM,KAAK,EAAE,KAAK,IAAI;EACtB,MAAM,UAAU,EAAE,OAAO,EAAE;EAC3B,MAAM,YAAY,QAAQ,MAAM,GAAG,IAAI;EACvC,IAAI,cAAc,MAAM,OAAO;EAC/B,IAAI,cAAc,QAAQ;GACxB,MAAM,aAAa,EAAE,KAAK,IAAI;GAC9B,EAAE,IAAI,SAAS,KACb,YAAY,WAAW,QAAQ,iBAAiB,QAAQ,KAAK,EAAE,UAAU,GAAG,uBAAuB,GAAG,aAAa,UAAU,gCAC/H;GACA,MAAM,KAAK,GAAG,WAAW,GAAG,EAAE,GAAG,GAAG,MAAM,OAAO,EAAE;EACrD;CACF;CAEA,OAAO,MAAM,KAAK,IAAI;AACxB"}
@@ -8,6 +8,13 @@
8
8
  *
9
9
  * Pattern sources are matched verbatim (string equality). Add new entries as Zod
10
10
  * exposes additional well-known formats and we want bundle-wide dedup.
11
+ *
12
+ * Verbatim matching means an entry goes STALE SILENTLY when Zod edits a pattern:
13
+ * nothing breaks, the lookup just stops hitting and every transformed file
14
+ * re-declares its own RegExp. The 4.5 bump did exactly that to `cuid`, `ulid`
15
+ * and `iso.datetime` — the last being the ~330-character source this table
16
+ * exists for. tests/core/codegen/well-known-regex.test.ts pins every entry
17
+ * against the live Zod pattern so the next upgrade fails loudly instead.
11
18
  */
12
19
  /** Zod v4's email regex source (string.ts uses this directly when format === "email"). */
13
20
  declare const EMAIL_REGEX_SOURCE: string;
@@ -1 +1 @@
1
- {"version":3,"file":"well-known-regex.d.ts","names":[],"sources":["../../../src/core/codegen/well-known-regex.ts"],"mappings":";;;;;;;;;;;;cAca;;;;;;;;;;;;;cAcA;;cAGA;UAGI;;EAEf;;EAEA;;;;;;EAMA;;cAGW,6BAA6B;;;;;iBAqE1B,qBAAqB;;;;;;;;;iBAYrB,sBAAsB;;;;;;;;;;;;;;;;iBAmBtB,eAAe;;;;;;;iBAWf,yBAAyB"}
1
+ {"version":3,"file":"well-known-regex.d.ts","names":[],"sources":["../../../src/core/codegen/well-known-regex.ts"],"mappings":";;;;;;;;;;;;;;;;;;;cAqBa;;;;;;;;;;;;;cAcA;;cAGA;UAGI;;EAEf;;EAEA;;;;;;EAMA;;cAGW,6BAA6B;;;;;iBAqE1B,qBAAqB;;;;;;;;;iBAYrB,sBAAsB;;;;;;;;;;;;;;;;iBAmBtB,eAAe;;;;;;;iBAWf,yBAAyB"}
@@ -9,6 +9,13 @@ import { unrollRepeats } from "./regex-unroll.js";
9
9
  *
10
10
  * Pattern sources are matched verbatim (string equality). Add new entries as Zod
11
11
  * exposes additional well-known formats and we want bundle-wide dedup.
12
+ *
13
+ * Verbatim matching means an entry goes STALE SILENTLY when Zod edits a pattern:
14
+ * nothing breaks, the lookup just stops hitting and every transformed file
15
+ * re-declares its own RegExp. The 4.5 bump did exactly that to `cuid`, `ulid`
16
+ * and `iso.datetime` — the last being the ~330-character source this table
17
+ * exists for. tests/core/codegen/well-known-regex.test.ts pins every entry
18
+ * against the live Zod pattern so the next upgrade fails loudly instead.
12
19
  */
13
20
  /** Zod v4's email regex source (string.ts uses this directly when format === "email"). */
14
21
  const EMAIL_REGEX_SOURCE = String.raw`^(?!\.)(?!.*\.\.)([A-Za-z0-9_'+\-\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$`;
@@ -39,7 +46,7 @@ const WELL_KNOWN_REGEXES = [
39
46
  },
40
47
  {
41
48
  name: "__zcReCuid",
42
- source: "^[cC][^\\s-]{8,}$"
49
+ source: "^[cC][0-9a-z]{6,}$"
43
50
  },
44
51
  {
45
52
  name: "__zcReCuid2",
@@ -47,7 +54,7 @@ const WELL_KNOWN_REGEXES = [
47
54
  },
48
55
  {
49
56
  name: "__zcReUlid",
50
- source: "^[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$"
57
+ source: "^[0-7][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{25}$"
51
58
  },
52
59
  {
53
60
  name: "__zcReNanoid",
@@ -95,7 +102,7 @@ const WELL_KNOWN_REGEXES = [
95
102
  },
96
103
  {
97
104
  name: "__zcReIsoDateTime",
98
- source: "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
105
+ source: "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"
99
106
  },
100
107
  {
101
108
  name: "__zcReIsoDuration",