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.
- package/README.md +59 -53
- package/dist/core/codegen/build-path.d.ts +12 -1
- package/dist/core/codegen/build-path.d.ts.map +1 -1
- package/dist/core/codegen/build-path.js +47 -9
- package/dist/core/codegen/build-path.js.map +1 -1
- package/dist/core/codegen/context.d.ts +31 -16
- package/dist/core/codegen/context.d.ts.map +1 -1
- package/dist/core/codegen/context.js +43 -18
- package/dist/core/codegen/context.js.map +1 -1
- package/dist/core/codegen/emit-issue.d.ts +17 -20
- package/dist/core/codegen/emit-issue.d.ts.map +1 -1
- package/dist/core/codegen/emit-issue.js +13 -13
- package/dist/core/codegen/emit-issue.js.map +1 -1
- package/dist/core/codegen/index.d.ts.map +1 -1
- package/dist/core/codegen/index.js +9 -7
- package/dist/core/codegen/index.js.map +1 -1
- package/dist/core/codegen/issue-decls.d.ts +41 -7
- package/dist/core/codegen/issue-decls.d.ts.map +1 -1
- package/dist/core/codegen/issue-decls.js +49 -9
- package/dist/core/codegen/issue-decls.js.map +1 -1
- package/dist/core/codegen/schemas/array.js +1 -1
- package/dist/core/codegen/schemas/array.js.map +1 -1
- package/dist/core/codegen/schemas/bigint.d.ts.map +1 -1
- package/dist/core/codegen/schemas/bigint.js +15 -4
- package/dist/core/codegen/schemas/bigint.js.map +1 -1
- package/dist/core/codegen/schemas/catch.d.ts.map +1 -1
- package/dist/core/codegen/schemas/catch.js +3 -2
- package/dist/core/codegen/schemas/catch.js.map +1 -1
- package/dist/core/codegen/schemas/discriminated-union.d.ts.map +1 -1
- package/dist/core/codegen/schemas/discriminated-union.js +2 -1
- package/dist/core/codegen/schemas/discriminated-union.js.map +1 -1
- package/dist/core/codegen/schemas/effect.d.ts.map +1 -1
- package/dist/core/codegen/schemas/effect.js +4 -2
- package/dist/core/codegen/schemas/effect.js.map +1 -1
- package/dist/core/codegen/schemas/file.js +2 -8
- package/dist/core/codegen/schemas/file.js.map +1 -1
- package/dist/core/codegen/schemas/number.js +4 -3
- package/dist/core/codegen/schemas/number.js.map +1 -1
- package/dist/core/codegen/schemas/object.d.ts +11 -1
- package/dist/core/codegen/schemas/object.d.ts.map +1 -1
- package/dist/core/codegen/schemas/object.js +58 -14
- package/dist/core/codegen/schemas/object.js.map +1 -1
- package/dist/core/codegen/schemas/readonly.d.ts.map +1 -1
- package/dist/core/codegen/schemas/readonly.js.map +1 -1
- package/dist/core/codegen/schemas/record.d.ts.map +1 -1
- package/dist/core/codegen/schemas/record.js +8 -3
- package/dist/core/codegen/schemas/record.js.map +1 -1
- package/dist/core/codegen/schemas/sizeable.d.ts +49 -3
- package/dist/core/codegen/schemas/sizeable.d.ts.map +1 -1
- package/dist/core/codegen/schemas/sizeable.js +101 -8
- package/dist/core/codegen/schemas/sizeable.js.map +1 -1
- package/dist/core/codegen/schemas/string.d.ts.map +1 -1
- package/dist/core/codegen/schemas/string.js +45 -16
- package/dist/core/codegen/schemas/string.js.map +1 -1
- package/dist/core/codegen/schemas/tuple.d.ts +14 -11
- package/dist/core/codegen/schemas/tuple.d.ts.map +1 -1
- package/dist/core/codegen/schemas/tuple.js +99 -49
- package/dist/core/codegen/schemas/tuple.js.map +1 -1
- package/dist/core/codegen/well-known-regex.d.ts +7 -0
- package/dist/core/codegen/well-known-regex.d.ts.map +1 -1
- package/dist/core/codegen/well-known-regex.js +10 -3
- package/dist/core/codegen/well-known-regex.js.map +1 -1
- package/dist/core/extract/checks.d.ts.map +1 -1
- package/dist/core/extract/checks.js +1 -0
- package/dist/core/extract/checks.js.map +1 -1
- package/dist/core/extract/extractors/intersection.js +21 -6
- package/dist/core/extract/extractors/intersection.js.map +1 -1
- package/dist/core/extract/extractors/object.d.ts.map +1 -1
- package/dist/core/extract/extractors/object.js +25 -9
- package/dist/core/extract/extractors/object.js.map +1 -1
- package/dist/core/extract/extractors/optional.d.ts.map +1 -1
- package/dist/core/extract/extractors/optional.js +1 -28
- package/dist/core/extract/extractors/optional.js.map +1 -1
- package/dist/core/extract/extractors/record.js +9 -3
- package/dist/core/extract/extractors/record.js.map +1 -1
- package/dist/core/extract/extractors/tuple.d.ts.map +1 -1
- package/dist/core/extract/extractors/tuple.js +16 -20
- package/dist/core/extract/extractors/tuple.js.map +1 -1
- package/dist/core/extract/types.d.ts +16 -8
- package/dist/core/extract/types.d.ts.map +1 -1
- package/dist/core/iife.d.ts +2 -1
- package/dist/core/iife.d.ts.map +1 -1
- package/dist/core/iife.js +2 -1
- package/dist/core/iife.js.map +1 -1
- package/dist/core/types.d.ts +70 -21
- package/dist/core/types.d.ts.map +1 -1
- package/dist/jit.d.ts.map +1 -1
- package/dist/jit.js +47 -12
- package/dist/jit.js.map +1 -1
- package/dist/runtime.d.ts +3 -1
- package/dist/runtime.js +12 -10
- 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 →
|
|
15
|
-
*
|
|
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
|
|
19
|
-
*
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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(${
|
|
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
|
|
187
|
-
case "max_length": return
|
|
188
|
-
case "length_equals": return
|
|
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
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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":"
|
|
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,
|
|
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
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
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
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
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
|
-
|
|
28
|
-
if (
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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 = `${
|
|
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
|
-
|
|
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 = `${
|
|
93
|
+
const restExpr = `${x}[${idxVar}]`;
|
|
53
94
|
const restPath = extendPath(g.path, idxVar);
|
|
54
95
|
itemsCode += emit`
|
|
55
|
-
for(var ${idxVar}=${len};${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
|
-
|
|
64
|
-
|
|
65
|
-
code +=
|
|
66
|
-
|
|
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
|
-
}
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
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
|
|
88
|
-
if (ir.rest === null) if (
|
|
89
|
-
else
|
|
90
|
-
|
|
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
|
-
|
|
99
|
-
parts.push(
|
|
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":"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"}
|
|
@@ -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":"
|
|
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][
|
|
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]{
|
|
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
|
|
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",
|