zod-compiler 1.23.7 → 1.23.8
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/dist/core/codegen/build-path.d.ts +31 -1
- package/dist/core/codegen/build-path.d.ts.map +1 -1
- package/dist/core/codegen/build-path.js +44 -4
- package/dist/core/codegen/build-path.js.map +1 -1
- package/dist/core/codegen/context.d.ts +17 -1
- package/dist/core/codegen/context.d.ts.map +1 -1
- package/dist/core/codegen/context.js +18 -2
- package/dist/core/codegen/context.js.map +1 -1
- package/dist/core/codegen/emit-issue.d.ts +46 -6
- package/dist/core/codegen/emit-issue.d.ts.map +1 -1
- package/dist/core/codegen/emit-issue.js +48 -18
- package/dist/core/codegen/emit-issue.js.map +1 -1
- package/dist/core/codegen/index.js +3 -3
- package/dist/core/codegen/index.js.map +1 -1
- package/dist/core/codegen/issue-decls.d.ts +62 -2
- package/dist/core/codegen/issue-decls.d.ts.map +1 -1
- package/dist/core/codegen/issue-decls.js +64 -11
- package/dist/core/codegen/issue-decls.js.map +1 -1
- package/dist/core/codegen/schemas/array.d.ts.map +1 -1
- package/dist/core/codegen/schemas/array.js +2 -0
- package/dist/core/codegen/schemas/array.js.map +1 -1
- package/dist/core/codegen/schemas/bigint.js +1 -1
- package/dist/core/codegen/schemas/bigint.js.map +1 -1
- package/dist/core/codegen/schemas/discriminated-union.js +2 -2
- package/dist/core/codegen/schemas/discriminated-union.js.map +1 -1
- package/dist/core/codegen/schemas/effect.js +1 -1
- package/dist/core/codegen/schemas/effect.js.map +1 -1
- package/dist/core/codegen/schemas/file.d.ts.map +1 -1
- package/dist/core/codegen/schemas/file.js +2 -0
- package/dist/core/codegen/schemas/file.js.map +1 -1
- package/dist/core/codegen/schemas/map.js +2 -2
- package/dist/core/codegen/schemas/map.js.map +1 -1
- package/dist/core/codegen/schemas/number.js +6 -3
- package/dist/core/codegen/schemas/number.js.map +1 -1
- package/dist/core/codegen/schemas/record.d.ts.map +1 -1
- package/dist/core/codegen/schemas/record.js +23 -9
- package/dist/core/codegen/schemas/record.js.map +1 -1
- package/dist/core/codegen/schemas/set.d.ts.map +1 -1
- package/dist/core/codegen/schemas/set.js +2 -0
- package/dist/core/codegen/schemas/set.js.map +1 -1
- package/dist/core/codegen/schemas/sizeable.d.ts +37 -0
- package/dist/core/codegen/schemas/sizeable.d.ts.map +1 -0
- package/dist/core/codegen/schemas/sizeable.js +115 -0
- package/dist/core/codegen/schemas/sizeable.js.map +1 -0
- package/dist/core/codegen/schemas/string.d.ts.map +1 -1
- package/dist/core/codegen/schemas/string.js +10 -4
- package/dist/core/codegen/schemas/string.js.map +1 -1
- package/dist/core/codegen/schemas/tuple.d.ts.map +1 -1
- package/dist/core/codegen/schemas/tuple.js +25 -20
- package/dist/core/codegen/schemas/tuple.js.map +1 -1
- package/dist/core/codegen/schemas/union.d.ts.map +1 -1
- package/dist/core/codegen/schemas/union.js +5 -2
- package/dist/core/codegen/schemas/union.js.map +1 -1
- package/dist/core/extract/extractors/object.d.ts.map +1 -1
- package/dist/core/extract/extractors/object.js +21 -1
- 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 +31 -1
- package/dist/core/extract/extractors/optional.js.map +1 -1
- package/dist/core/extract/extractors/tuple.d.ts.map +1 -1
- package/dist/core/extract/extractors/tuple.js +23 -1
- package/dist/core/extract/extractors/tuple.js.map +1 -1
- package/dist/core/extract/types.d.ts +9 -0
- package/dist/core/extract/types.d.ts.map +1 -1
- package/dist/core/iife.d.ts +17 -0
- package/dist/core/iife.d.ts.map +1 -1
- package/dist/core/iife.js +19 -2
- package/dist/core/iife.js.map +1 -1
- package/dist/core/types.d.ts +27 -0
- package/dist/core/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { emitRuntimeHelper } from "../context.js";
|
|
2
|
+
import { emit } from "../emit.js";
|
|
3
|
+
import { tooBig, tooSmall } from "../emit-issue.js";
|
|
4
|
+
import { ZC_LENGTH_ORIGIN_DECL, ZC_SIZE_ORIGIN_DECL } from "../issue-decls.js";
|
|
5
|
+
//#region src/core/codegen/schemas/sizeable.ts
|
|
6
|
+
/**
|
|
7
|
+
* Length/size checks re-emitted for the branch where the node's TYPE CHECK
|
|
8
|
+
* ALREADY FAILED.
|
|
9
|
+
*
|
|
10
|
+
* zod skips a schema's checks once its parse aborted — except the ones carrying
|
|
11
|
+
* a `when` predicate, which `runChecks` consults instead of the abort flag:
|
|
12
|
+
*
|
|
13
|
+
* ```js
|
|
14
|
+
* if (ch._zod.def.when) { if (!ch._zod.def.when(payload)) continue; }
|
|
15
|
+
* else if (isAborted) continue;
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* and `$ZodCheckMinLength` & co. install exactly such a predicate —
|
|
19
|
+
* `!nullish(value) && value.length !== undefined` (`.size` for the size family)
|
|
20
|
+
* — so they run on ANY input carrying that property, of any type. Hence
|
|
21
|
+
* `z.string().min(2).safeParse([])` reports TWO issues in zod: the
|
|
22
|
+
* `invalid_type`, and a `too_small` whose `origin` is `"array"`, because the
|
|
23
|
+
* empty array satisfied the `when`. Compiled output kept every check inside the
|
|
24
|
+
* matched-type branch and reported only the first. Same for
|
|
25
|
+
* `z.array(…).min(3)` over a short string, `z.set(…).min(2)` over a `Map`, and
|
|
26
|
+
* `z.file().min(2)` over a `Set`.
|
|
27
|
+
*
|
|
28
|
+
* Emitted as a SEPARATE copy in the failure branch rather than hoisted out of
|
|
29
|
+
* both, so the success path — where the origin is statically known and the guard
|
|
30
|
+
* is trivially true — stays byte-for-byte what it was. This is cold code: it
|
|
31
|
+
* runs only for input the node has already rejected.
|
|
32
|
+
*
|
|
33
|
+
* `origin` is computed at runtime here (see ZC_LENGTH_ORIGIN_DECL), because the
|
|
34
|
+
* value that reached this branch is by definition not the schema's own type.
|
|
35
|
+
*/
|
|
36
|
+
function whenGatedSizeChecks(checks, g, family) {
|
|
37
|
+
const KINDS = family === "length" ? /* @__PURE__ */ new Set([
|
|
38
|
+
"min_length",
|
|
39
|
+
"max_length",
|
|
40
|
+
"length_equals"
|
|
41
|
+
]) : /* @__PURE__ */ new Set([
|
|
42
|
+
"min_size",
|
|
43
|
+
"max_size",
|
|
44
|
+
"size_equals"
|
|
45
|
+
]);
|
|
46
|
+
if (!checks.some((check) => KINDS.has(check.kind))) return "";
|
|
47
|
+
const property = family === "length" ? "length" : "size";
|
|
48
|
+
const origin = { expr: `${family === "length" ? emitRuntimeHelper(g.ctx, "__zcLo", ZC_LENGTH_ORIGIN_DECL) : emitRuntimeHelper(g.ctx, "__zcSo", ZC_SIZE_ORIGIN_DECL)}(${g.input})` };
|
|
49
|
+
const measure = `${g.input}.${property}`;
|
|
50
|
+
let body = "";
|
|
51
|
+
for (const check of checks) switch (check.kind) {
|
|
52
|
+
case "min_length":
|
|
53
|
+
body += emit`
|
|
54
|
+
if(${measure}<${check.minimum}){
|
|
55
|
+
${tooSmall(g, check.minimum, origin, true, { message: check.message })}
|
|
56
|
+
}`;
|
|
57
|
+
break;
|
|
58
|
+
case "max_length":
|
|
59
|
+
body += emit`
|
|
60
|
+
if(${measure}>${check.maximum}){
|
|
61
|
+
${tooBig(g, check.maximum, origin, true, { message: check.message })}
|
|
62
|
+
}`;
|
|
63
|
+
break;
|
|
64
|
+
case "length_equals":
|
|
65
|
+
body += emit`
|
|
66
|
+
if(${measure}<${check.length}){
|
|
67
|
+
${tooSmall(g, check.length, origin, true, {
|
|
68
|
+
exact: true,
|
|
69
|
+
message: check.message
|
|
70
|
+
})}
|
|
71
|
+
}else if(${measure}>${check.length}){
|
|
72
|
+
${tooBig(g, check.length, origin, true, {
|
|
73
|
+
exact: true,
|
|
74
|
+
message: check.message
|
|
75
|
+
})}
|
|
76
|
+
}`;
|
|
77
|
+
break;
|
|
78
|
+
case "min_size":
|
|
79
|
+
body += emit`
|
|
80
|
+
if(${measure}<${check.minimum}){
|
|
81
|
+
${tooSmall(g, check.minimum, origin, true, { message: check.message })}
|
|
82
|
+
}`;
|
|
83
|
+
break;
|
|
84
|
+
case "max_size":
|
|
85
|
+
body += emit`
|
|
86
|
+
if(${measure}>${check.maximum}){
|
|
87
|
+
${tooBig(g, check.maximum, origin, true, { message: check.message })}
|
|
88
|
+
}`;
|
|
89
|
+
break;
|
|
90
|
+
case "size_equals":
|
|
91
|
+
body += emit`
|
|
92
|
+
if(${measure}<${check.size}){
|
|
93
|
+
${tooSmall(g, check.size, origin, true, {
|
|
94
|
+
exact: true,
|
|
95
|
+
message: check.message
|
|
96
|
+
})}
|
|
97
|
+
}else if(${measure}>${check.size}){
|
|
98
|
+
${tooBig(g, check.size, origin, true, {
|
|
99
|
+
exact: true,
|
|
100
|
+
message: check.message
|
|
101
|
+
})}
|
|
102
|
+
}`;
|
|
103
|
+
break;
|
|
104
|
+
default: break;
|
|
105
|
+
}
|
|
106
|
+
if (body === "") return "";
|
|
107
|
+
return emit`
|
|
108
|
+
if(${g.input}!==undefined&&${g.input}!==null&&${measure}!==undefined){
|
|
109
|
+
${body}
|
|
110
|
+
}`;
|
|
111
|
+
}
|
|
112
|
+
//#endregion
|
|
113
|
+
export { whenGatedSizeChecks };
|
|
114
|
+
|
|
115
|
+
//# sourceMappingURL=sizeable.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sizeable.js","names":[],"sources":["../../../../src/core/codegen/schemas/sizeable.ts"],"sourcesContent":["import type { CheckOrEffectIR, FileCheckIR, SetCheckIR } from \"../../types.js\";\nimport type { SlowGen } from \"../context.js\";\nimport { emitRuntimeHelper } from \"../context.js\";\nimport { emit } from \"../emit.js\";\nimport { tooBig, tooSmall } from \"../emit-issue.js\";\nimport { ZC_LENGTH_ORIGIN_DECL, ZC_SIZE_ORIGIN_DECL } from \"../issue-decls.js\";\n\n/**\n * Length/size checks re-emitted for the branch where the node's TYPE CHECK\n * ALREADY FAILED.\n *\n * zod skips a schema's checks once its parse aborted — except the ones carrying\n * a `when` predicate, which `runChecks` consults instead of the abort flag:\n *\n * ```js\n * if (ch._zod.def.when) { if (!ch._zod.def.when(payload)) continue; }\n * else if (isAborted) continue;\n * ```\n *\n * and `$ZodCheckMinLength` & co. install exactly such a predicate —\n * `!nullish(value) && value.length !== undefined` (`.size` for the size family)\n * — so they run on ANY input carrying that property, of any type. Hence\n * `z.string().min(2).safeParse([])` reports TWO issues in zod: the\n * `invalid_type`, and a `too_small` whose `origin` is `\"array\"`, because the\n * empty array satisfied the `when`. Compiled output kept every check inside the\n * matched-type branch and reported only the first. Same for\n * `z.array(…).min(3)` over a short string, `z.set(…).min(2)` over a `Map`, and\n * `z.file().min(2)` over a `Set`.\n *\n * Emitted as a SEPARATE copy in the failure branch rather than hoisted out of\n * both, so the success path — where the origin is statically known and the guard\n * is trivially true — stays byte-for-byte what it was. This is cold code: it\n * runs only for input the node has already rejected.\n *\n * `origin` is computed at runtime here (see ZC_LENGTH_ORIGIN_DECL), because the\n * value that reached this branch is by definition not the schema's own type.\n */\nexport function whenGatedSizeChecks(\n checks: readonly (CheckOrEffectIR | SetCheckIR | FileCheckIR)[],\n g: SlowGen,\n family: \"length\" | \"size\",\n): string {\n const KINDS =\n family === \"length\"\n ? new Set([\"min_length\", \"max_length\", \"length_equals\"])\n : new Set([\"min_size\", \"max_size\", \"size_equals\"]);\n // Checked BEFORE touching the context: `emitRuntimeHelper` pushes its\n // declaration on sight, so asking for the origin helper up front left a dead\n // `__zcLo` in the preamble of every schema with a plain `z.string()` in it.\n if (!checks.some((check) => KINDS.has(check.kind))) return \"\";\n\n const property = family === \"length\" ? \"length\" : \"size\";\n const originHelper =\n family === \"length\"\n ? emitRuntimeHelper(g.ctx, \"__zcLo\", ZC_LENGTH_ORIGIN_DECL)\n : emitRuntimeHelper(g.ctx, \"__zcSo\", ZC_SIZE_ORIGIN_DECL);\n const origin = { expr: `${originHelper}(${g.input})` };\n const measure = `${g.input}.${property}`;\n\n let body = \"\";\n for (const check of checks) {\n // Kept in DECLARATION order: `runChecks` walks the check array, so two\n // failing length checks report in the order they were declared.\n switch (check.kind) {\n case \"min_length\":\n body += emit`\n if(${measure}<${check.minimum}){\n ${tooSmall(g, check.minimum, origin, true, { message: check.message })}\n }`;\n break;\n case \"max_length\":\n body += emit`\n if(${measure}>${check.maximum}){\n ${tooBig(g, check.maximum, origin, true, { message: check.message })}\n }`;\n break;\n case \"length_equals\":\n body += emit`\n if(${measure}<${check.length}){\n ${tooSmall(g, check.length, origin, true, { exact: true, message: check.message })}\n }else if(${measure}>${check.length}){\n ${tooBig(g, check.length, origin, true, { exact: true, message: check.message })}\n }`;\n break;\n case \"min_size\":\n body += emit`\n if(${measure}<${check.minimum}){\n ${tooSmall(g, check.minimum, origin, true, { message: check.message })}\n }`;\n break;\n case \"max_size\":\n body += emit`\n if(${measure}>${check.maximum}){\n ${tooBig(g, check.maximum, origin, true, { message: check.message })}\n }`;\n break;\n case \"size_equals\":\n body += emit`\n if(${measure}<${check.size}){\n ${tooSmall(g, check.size, origin, true, { exact: true, message: check.message })}\n }else if(${measure}>${check.size}){\n ${tooBig(g, check.size, origin, true, { exact: true, message: check.message })}\n }`;\n break;\n default:\n break;\n }\n }\n if (body === \"\") return \"\";\n // zod's own `when`, verbatim: `!util.nullish(value) && value.<prop> !== undefined`.\n return emit`\n if(${g.input}!==undefined&&${g.input}!==null&&${measure}!==undefined){\n ${body}\n }`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCA,SAAgB,oBACd,QACA,GACA,QACQ;CACR,MAAM,QACJ,WAAW,2BACP,IAAI,IAAI;EAAC;EAAc;EAAc;CAAe,CAAC,oBACrD,IAAI,IAAI;EAAC;EAAY;EAAY;CAAa,CAAC;CAIrD,IAAI,CAAC,OAAO,MAAM,UAAU,MAAM,IAAI,MAAM,IAAI,CAAC,GAAG,OAAO;CAE3D,MAAM,WAAW,WAAW,WAAW,WAAW;CAKlD,MAAM,SAAS,EAAE,MAAM,GAHrB,WAAW,WACP,kBAAkB,EAAE,KAAK,UAAU,qBAAqB,IACxD,kBAAkB,EAAE,KAAK,UAAU,mBAAmB,EACrB,GAAG,EAAE,MAAM,GAAG;CACrD,MAAM,UAAU,GAAG,EAAE,MAAM,GAAG;CAE9B,IAAI,OAAO;CACX,KAAK,MAAM,SAAS,QAGlB,QAAQ,MAAM,MAAd;EACE,KAAK;GACH,QAAQ,IAAI;eACL,QAAQ,GAAG,MAAM,QAAQ;cAC1B,SAAS,GAAG,MAAM,SAAS,QAAQ,MAAM,EAAE,SAAS,MAAM,QAAQ,CAAC,EAAE;;GAE3E;EACF,KAAK;GACH,QAAQ,IAAI;eACL,QAAQ,GAAG,MAAM,QAAQ;cAC1B,OAAO,GAAG,MAAM,SAAS,QAAQ,MAAM,EAAE,SAAS,MAAM,QAAQ,CAAC,EAAE;;GAEzE;EACF,KAAK;GACH,QAAQ,IAAI;eACL,QAAQ,GAAG,MAAM,OAAO;cACzB,SAAS,GAAG,MAAM,QAAQ,QAAQ,MAAM;IAAE,OAAO;IAAM,SAAS,MAAM;GAAQ,CAAC,EAAE;qBAC1E,QAAQ,GAAG,MAAM,OAAO;cAC/B,OAAO,GAAG,MAAM,QAAQ,QAAQ,MAAM;IAAE,OAAO;IAAM,SAAS,MAAM;GAAQ,CAAC,EAAE;;GAErF;EACF,KAAK;GACH,QAAQ,IAAI;eACL,QAAQ,GAAG,MAAM,QAAQ;cAC1B,SAAS,GAAG,MAAM,SAAS,QAAQ,MAAM,EAAE,SAAS,MAAM,QAAQ,CAAC,EAAE;;GAE3E;EACF,KAAK;GACH,QAAQ,IAAI;eACL,QAAQ,GAAG,MAAM,QAAQ;cAC1B,OAAO,GAAG,MAAM,SAAS,QAAQ,MAAM,EAAE,SAAS,MAAM,QAAQ,CAAC,EAAE;;GAEzE;EACF,KAAK;GACH,QAAQ,IAAI;eACL,QAAQ,GAAG,MAAM,KAAK;cACvB,SAAS,GAAG,MAAM,MAAM,QAAQ,MAAM;IAAE,OAAO;IAAM,SAAS,MAAM;GAAQ,CAAC,EAAE;qBACxE,QAAQ,GAAG,MAAM,KAAK;cAC7B,OAAO,GAAG,MAAM,MAAM,QAAQ,MAAM;IAAE,OAAO;IAAM,SAAS,MAAM;GAAQ,CAAC,EAAE;;GAEnF;EACF,SACE;CACJ;CAEF,IAAI,SAAS,IAAI,OAAO;CAExB,OAAO,IAAI;SACJ,EAAE,MAAM,gBAAgB,EAAE,MAAM,WAAW,QAAQ;QACpD,KAAK;;AAEb"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"string.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/string.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"string.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/string.ts"],"mappings":";;;iBAuEgB,WAAW,IAAI,UAAU,GAAG;;;;;;;;;;;iBAmJ5B,gBAAgB,OAAO,SAAS,WAAW,KAAK;iBAkDhD,WAAW,IAAI,UAAU,GAAG"}
|
|
@@ -3,6 +3,7 @@ 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
7
|
//#region src/core/codegen/schemas/string.ts
|
|
7
8
|
/** `re.lastIndex=0;` reset statement for stateful (g/y-flagged) regexes. */
|
|
8
9
|
function lastIndexReset(regexVar, flags) {
|
|
@@ -62,6 +63,7 @@ function slowString(ir, g) {
|
|
|
62
63
|
code += emit`
|
|
63
64
|
if(typeof ${g.input}!=="string"){
|
|
64
65
|
${invalidType(g, "string")}
|
|
66
|
+
${whenGatedSizeChecks(ir.checks, g, "length")}
|
|
65
67
|
}`;
|
|
66
68
|
if (ir.checks.length > 0) {
|
|
67
69
|
code += `else{`;
|
|
@@ -96,7 +98,8 @@ function slowString(ir, g) {
|
|
|
96
98
|
code += emit`
|
|
97
99
|
if(!${g.input}.includes(${escapeString(check.includes)}${check.position !== void 0 ? `,${check.position}` : ""})){
|
|
98
100
|
${invalidFormat(g, "includes", {
|
|
99
|
-
|
|
101
|
+
origin: "string",
|
|
102
|
+
extra: `includes:${escapeString(check.includes)}`,
|
|
100
103
|
message: check.message
|
|
101
104
|
})}
|
|
102
105
|
}`;
|
|
@@ -105,7 +108,8 @@ function slowString(ir, g) {
|
|
|
105
108
|
code += emit`
|
|
106
109
|
if(!${g.input}.startsWith(${escapeString(check.prefix)})){
|
|
107
110
|
${invalidFormat(g, "starts_with", {
|
|
108
|
-
|
|
111
|
+
origin: "string",
|
|
112
|
+
extra: `prefix:${escapeString(check.prefix)}`,
|
|
109
113
|
message: check.message
|
|
110
114
|
})}
|
|
111
115
|
}`;
|
|
@@ -114,7 +118,8 @@ function slowString(ir, g) {
|
|
|
114
118
|
code += emit`
|
|
115
119
|
if(!${g.input}.endsWith(${escapeString(check.suffix)})){
|
|
116
120
|
${invalidFormat(g, "ends_with", {
|
|
117
|
-
|
|
121
|
+
origin: "string",
|
|
122
|
+
extra: `suffix:${escapeString(check.suffix)}`,
|
|
118
123
|
message: check.message
|
|
119
124
|
})}
|
|
120
125
|
}`;
|
|
@@ -149,11 +154,12 @@ function slowString(ir, g) {
|
|
|
149
154
|
regexVar = g.regex("str", pattern, check.patternFlags);
|
|
150
155
|
} else continue;
|
|
151
156
|
let extra;
|
|
152
|
-
if (!check.bareIssue) extra = `pattern:${!check.patternFlags && fastTestSource(pattern) !== null ? emitRegexSourceString(g.ctx, pattern) : `${regexVar}.toString()`}
|
|
157
|
+
if (!check.bareIssue) extra = `pattern:${!check.patternFlags && fastTestSource(pattern) !== null ? emitRegexSourceString(g.ctx, pattern) : `${regexVar}.toString()`}`;
|
|
153
158
|
code += emit`
|
|
154
159
|
${lastIndexReset(regexVar, check.patternFlags)}
|
|
155
160
|
if(!${regexVar}.test(${g.input})){
|
|
156
161
|
${invalidFormat(g, { expr: escapeString(check.format) }, {
|
|
162
|
+
origin: check.bareIssue ? void 0 : "string",
|
|
157
163
|
extra,
|
|
158
164
|
message: check.message
|
|
159
165
|
})}
|
|
@@ -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\";\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 }`;\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\", { extra: `includes:${escapeString(check.includes)},origin:\"string\"`, 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\", { extra: `prefix:${escapeString(check.prefix)},origin:\"string\"`, 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\", { extra: `suffix:${escapeString(check.suffix)},origin:\"string\"`, 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},origin:\"string\"`;\n }\n code += emit`\n ${lastIndexReset(regexVar, check.patternFlags)}\n if(!${regexVar}.test(${g.input})){\n ${invalidFormat(g, { expr: escapeString(check.format) }, { 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":";;;;;;;AAgBA,SAAS,eAAe,UAAkB,OAAmC;CAC3E,OAAO,SAAS,OAAO,KAAK,KAAK,IAAI,GAAG,SAAS,iBAAiB;AACpE;;;;;;;;;;;AAYA,SAAS,aAAa,OAA0B,GAAoB;CAClE,MAAM,aAAa,EAAE,KAAK,IAAI;CAC9B,MAAM,SAAS,EAAE,KAAK,GAAG;CACzB,IAAI,QAAQ;CACZ,IAAI,MAAM,UAAU;EAClB,MAAM,KAAK,EAAE,MAAM,QAAQ,MAAM,UAAU,MAAM,aAAa;EAC9D,SAAS,IAAI;QACT,eAAe,IAAI,MAAM,aAAa,EAAE;YACpC,GAAG,QAAQ,OAAO;UACpB,cAAc,GAAG,OAAO;GACxB,OAAO,mCAAmC,aAAa,MAAM,QAAQ;GACrE,SAAS,MAAM;EACjB,CAAC,EAAE;;CAET;CACA,IAAI,MAAM,UAAU;EAClB,MAAM,KAAK,EAAE,MAAM,SAAS,MAAM,UAAU,MAAM,aAAa;EAC/D,MAAM,YAAY,IAAI,OAAO,0BAA0B,OAAO,wBAAwB,OAAO;EAC7F,SAAS,IAAI;QACT,eAAe,IAAI,MAAM,aAAa,EAAE;YACpC,GAAG,QAAQ,UAAU;UACvB,cAAc,GAAG,OAAO;GACxB,OAAO,mCAAmC,aAAa,MAAM,QAAQ;GACrE,SAAS,MAAM;EACjB,CAAC,EAAE;;CAET;CAEA,SAAS,GAAG,EAAE,OAAO,GAAG,MAAM,YAAY,GAAG,OAAO,SAAS,WAAW;CACxE,OAAO,IAAI;UACH,WAAW,GAAG,EAAE,MAAM;UACtB,OAAO;UACP,OAAO,WAAW,WAAW;SAC9B,OAAO;QACR,cAAc,GAAG,OAAO,EAAE,SAAS,MAAM,QAAQ,CAAC,EAAE;;QAEpD,MAAM;;AAEd;AAEA,SAAgB,WAAW,IAAc,GAAoB;CAC3D,IAAI,OAAO;CACX,IAAI,GAAG,QACL,QAAQ,IAAI,OAAO,EAAE,OAAO,UAAU,EAAE,MAAM;CAEhD,QAAQ,IAAI;gBACE,EAAE,MAAM;QAChB,YAAY,GAAG,QAAQ,EAAE;;CAG/B,IAAI,GAAG,OAAO,SAAS,GAAG;EACxB,QAAQ;EAGR,KAAK,MAAM,SAAS,GAAG,QACrB,QAAQ,MAAM,MAAd;GACE,KAAK;IACH,QAAQ,IAAI;iBACL,EAAE,MAAM,UAAU,MAAM,QAAQ;gBACjC,SAAS,GAAG,MAAM,SAAS,UAAU,MAAM,EAAE,SAAS,MAAM,QAAQ,CAAC,EAAE;;IAE7E;GACF,KAAK;IACH,QAAQ,IAAI;iBACL,EAAE,MAAM,UAAU,MAAM,QAAQ;gBACjC,OAAO,GAAG,MAAM,SAAS,UAAU,MAAM,EAAE,SAAS,MAAM,QAAQ,CAAC,EAAE;;IAE3E;GACF,KAAK;IACH,QAAQ,IAAI;iBACL,EAAE,MAAM,UAAU,MAAM,OAAO;gBAChC,SAAS,GAAG,MAAM,QAAQ,UAAU,MAAM;KAAE,OAAO;KAAM,SAAS,MAAM;IAAQ,CAAC,EAAE;uBAC5E,EAAE,MAAM,UAAU,MAAM,OAAO;gBACtC,OAAO,GAAG,MAAM,QAAQ,UAAU,MAAM;KAAE,OAAO;KAAM,SAAS,MAAM;IAAQ,CAAC,EAAE;;IAEvF;GAMF,KAAK;IACH,QAAQ,IAAI;kBACJ,EAAE,MAAM,YAAY,aAAa,MAAM,QAAQ,IAAI,MAAM,aAAa,KAAA,IAAY,IAAI,MAAM,aAAa,GAAG;gBAC9G,cAAc,GAAG,YAAY;KAAE,OAAO,YAAY,aAAa,MAAM,QAAQ,EAAE;KAAmB,SAAS,MAAM;IAAQ,CAAC,EAAE;;IAElI;GACF,KAAK;IACH,QAAQ,IAAI;kBACJ,EAAE,MAAM,cAAc,aAAa,MAAM,MAAM,EAAE;gBACnD,cAAc,GAAG,eAAe;KAAE,OAAO,UAAU,aAAa,MAAM,MAAM,EAAE;KAAmB,SAAS,MAAM;IAAQ,CAAC,EAAE;;IAEjI;GACF,KAAK;IACH,QAAQ,IAAI;kBACJ,EAAE,MAAM,YAAY,aAAa,MAAM,MAAM,EAAE;gBACjD,cAAc,GAAG,aAAa;KAAE,OAAO,UAAU,aAAa,MAAM,MAAM,EAAE;KAAmB,SAAS,MAAM;IAAQ,CAAC,EAAE;;IAE/H;GACF,KAAK;IACH,QAAQ,YAAY,OAAO,EAAE,OAAO,CAAC;IACrC;GACF,KAAK;IACH,QAAQ,iBAAiB,OAAO,EAAE,OAAO,CAAC;IAC1C;GACF,KAAK;IAGH,QAAQ,IAAI,GAAG,EAAE,OAAO,GAAG,aAAa,EAAE,KAAK,MAAM,MAAM,EAAE,GAAG,EAAE,MAAM;IACxE;GACF,KAAK,iBAAiB;IACpB,IAAI;IACJ,IAAI;IAMJ,IAAI,MAAM,WAAW,SAAS,CAAC,MAAM,SAAS;KAC5C,QAAQ,aAAa,OAAO,CAAC;KAC7B;IACF;IACA,IAAI,MAAM,WAAW,SAAS;KAC5B,UAAU,MAAM,WAAW;KAC3B,WAAW,EAAE,MAAM,SAAS,SAAS,MAAM,YAAY;IACzD,OAAO,IAAI,MAAM,WAAW,WAAW,MAAM,SAAS;KACpD,UAAU,MAAM;KAChB,WAAW,EAAE,MAAM,OAAO,SAAS,MAAM,YAAY;IACvD,OAAO,IAAI,MAAM,WAAW,QAAQ;KAClC,UAAU,MAAM,WAAA;KAChB,WAAW,EAAE,MAAM,QAAQ,SAAS,MAAM,YAAY;IACxD,OACE,IAAI,MAAM,SAAS;KACjB,UAAU,MAAM;KAChB,WAAW,EAAE,MAAM,OAAO,SAAS,MAAM,YAAY;IACvD,OAGE;IAWJ,IAAI;IACJ,IAAI,CAAC,MAAM,WAST,QAAQ,WAJU,CAAC,MAAM,gBAAgB,eAAe,OAAO,MAAM,OAEjE,sBAAsB,EAAE,KAAK,OAAO,IACpC,GAAG,SAAS,aACe;IAEjC,QAAQ,IAAI;cACR,eAAe,UAAU,MAAM,YAAY,EAAE;kBACzC,SAAS,QAAQ,EAAE,MAAM;gBAC3B,cAAc,GAAG,EAAE,MAAM,aAAa,MAAM,MAAM,EAAE,GAAG;KAAE;KAAO,SAAS,MAAM;IAAQ,CAAC,EAAE;;IAEhG;GACF;EACF;EAEF,QAAQ;CACV;CAEA,OAAO,GAAG,KAAK;AACjB;;;;;;;;;;;AAYA,SAAgB,gBAAgB,OAAgB,GAAW,KAAoC;CAC7F,QAAQ,MAAM,MAAd;EACE,KAAK,cACH,OAAO,GAAG,EAAE,WAAW,MAAM;EAC/B,KAAK,cACH,OAAO,GAAG,EAAE,WAAW,MAAM;EAC/B,KAAK,iBACH,OAAO,GAAG,EAAE,YAAY,MAAM;EAChC,KAAK,YACH,OAAO,MAAM,aAAa,KAAA,IACtB,GAAG,EAAE,YAAY,aAAa,MAAM,QAAQ,EAAE,GAAG,MAAM,SAAS,KAChE,GAAG,EAAE,YAAY,aAAa,MAAM,QAAQ,EAAE;EACpD,KAAK,eACH,OAAO,GAAG,EAAE,cAAc,aAAa,MAAM,MAAM,EAAE;EACvD,KAAK,aACH,OAAO,GAAG,EAAE,YAAY,aAAa,MAAM,MAAM,EAAE;EACrD,KAAK,iBAAiB;GAIpB,IAAI,MAAM,WAAW,SAAS,CAAC,MAAM,SAAS,OAAO;GACrD,IAAI;GACJ,IAAI;GACJ,IAAI,MAAM,WAAW,SAAS;IAC5B,SAAS;IACT,UAAU,MAAM,WAAW;GAC7B,OAAO,IAAI,MAAM,WAAW,QAAQ;IAClC,SAAS;IACT,UAAU,MAAM,WAAA;GAClB,OAAO,IAAI,MAAM,SAAS;IACxB,SAAS;IACT,UAAU,MAAM;GAClB,OAEE,OAAO;GAET,MAAM,IAAI,UAAU,KAAK,QAAQ,SAAS,MAAM,YAAY;GAG5D,OAAO,MAAM,gBAAgB,OAAO,KAAK,MAAM,YAAY,IACvD,KAAK,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MACnC,GAAG,EAAE,QAAQ,EAAE;EACrB;EACA,SAGE,OAAO;CACX;AACF;AAEA,SAAgB,WAAW,IAAc,GAA2B;CAClE,IAAI,GAAG,QAAQ,OAAO;CAGtB,IAAI,GAAG,OAAO,MAAM,MAAM,EAAE,SAAS,kBAAkB,GAAG,OAAO;CAEjE,MAAM,IAAI,EAAE;CACZ,MAAM,QAAkB,CAAC,UAAU,EAAE,YAAY;CACjD,MAAM,SAAS,GAAG,OAAO,QACtB,MAAoB,EAAE,SAAS,mBAAmB,EAAE,SAAS,kBAChE;CAEA,KAAK,MAAM,SAAS,OAAO,KAAK,aAAa,GAAG;EAC9C,MAAM,OAAO,gBAAgB,OAAO,GAAG,EAAE,GAAG;EAC5C,IAAI,SAAS,MAAM,OAAO;EAC1B,MAAM,KAAK,IAAI;CACjB;CAGA,KAAK,MAAM,SAAS,GAAG,QACrB,IAAI,MAAM,SAAS,iBACjB,MAAM,KAAK,GAAG,mBAAmB,EAAE,KAAK,KAAK,EAAE,GAAG,EAAE,EAAE;MACjD,IAAI,MAAM,SAAS,uBACxB,MAAM,KAAK,oBAAoB,OAAO,GAAG,CAAC,CAAC;CAI/C,OAAO,MAAM,KAAK,IAAI;AACxB"}
|
|
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 +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":";;;;;;;;;;;;;;;;;iBA2BgB,UAAU,IAAI;EAAa;GAAiB,GAAG;iBAsE/C,UAAU,IAAI,SAAS,GAAG"}
|
|
@@ -1,22 +1,9 @@
|
|
|
1
|
-
import { declareFastTemps, extendPath, extendStaticPathIndex, hasMutation } from "../context.js";
|
|
1
|
+
import { declareFastTemps, extendPath, extendStaticPathIndex, hasMutation, tuplePadsShortInput } 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
6
|
/**
|
|
7
|
-
* Index where the omittable tail begins (zod's `optStart`): trailing
|
|
8
|
-
* optional/default items have `optin === "optional"` and may be absent.
|
|
9
|
-
*/
|
|
10
|
-
function optStart(ir) {
|
|
11
|
-
let start = ir.items.length;
|
|
12
|
-
for (let i = ir.items.length - 1; i >= 0; i--) {
|
|
13
|
-
const itemType = ir.items[i].type;
|
|
14
|
-
if (itemType !== "optional" && itemType !== "default") break;
|
|
15
|
-
start--;
|
|
16
|
-
}
|
|
17
|
-
return start;
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
7
|
* Mirrors $ZodTuple: without rest, over-length input emits a single too_big
|
|
21
8
|
* and `length < optStart - 1` a single too_small (minimum = items.length) —
|
|
22
9
|
* both created by the tuple node (schema error applies) and both skip item
|
|
@@ -38,11 +25,21 @@ function slowTuple(ir, g) {
|
|
|
38
25
|
}else{`;
|
|
39
26
|
let itemsCode = "";
|
|
40
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
|
+
}
|
|
41
38
|
for (let i = 0; i < len; i++) {
|
|
42
39
|
const itemIR = ir.items[i];
|
|
43
40
|
const elemExpr = `${g.input}[${i}]`;
|
|
44
41
|
const elemPath = extendStaticPathIndex(g.path, i);
|
|
45
|
-
const skipMissingOptional = i >= optStart
|
|
42
|
+
const skipMissingOptional = i >= ir.optStart;
|
|
46
43
|
const itemCode = g.visit(itemIR, {
|
|
47
44
|
input: elemExpr,
|
|
48
45
|
output: elemExpr,
|
|
@@ -64,12 +61,19 @@ function slowTuple(ir, g) {
|
|
|
64
61
|
}`;
|
|
65
62
|
}
|
|
66
63
|
if (ir.rest === null) {
|
|
67
|
-
const start = optStart
|
|
64
|
+
const start = ir.optStart;
|
|
68
65
|
code += emit`
|
|
69
66
|
if(${g.input}.length>${len}){
|
|
70
|
-
${tooBig(g, len, "array", true, {
|
|
67
|
+
${tooBig(g, len, "array", true, {
|
|
68
|
+
useTypeMsg: true,
|
|
69
|
+
layout: "tuple",
|
|
70
|
+
aborts: true
|
|
71
|
+
})}
|
|
71
72
|
}else if(${g.input}.length<${start - 1}){
|
|
72
|
-
${tooSmall(g, len, "array", "omit", {
|
|
73
|
+
${tooSmall(g, len, "array", "omit", {
|
|
74
|
+
useTypeMsg: true,
|
|
75
|
+
aborts: true
|
|
76
|
+
})}
|
|
73
77
|
}else{
|
|
74
78
|
${itemsCode}
|
|
75
79
|
}`;
|
|
@@ -80,7 +84,7 @@ function slowTuple(ir, g) {
|
|
|
80
84
|
function fastTuple(ir, g) {
|
|
81
85
|
const x = g.input;
|
|
82
86
|
const parts = [`Array.isArray(${x})`];
|
|
83
|
-
const required = optStart
|
|
87
|
+
const required = ir.optStart;
|
|
84
88
|
if (ir.rest === null) if (required === ir.items.length) parts.push(`${x}.length===${ir.items.length}`);
|
|
85
89
|
else parts.push(`${x}.length>=${required}`, `${x}.length<=${ir.items.length}`);
|
|
86
90
|
else if (required > 0) parts.push(`${x}.length>=${required}`);
|
|
@@ -91,7 +95,8 @@ function fastTuple(ir, g) {
|
|
|
91
95
|
for (const { index, itemIR } of orderByRuntimeCost(indexed, (e) => e.itemIR, g.ctx)) {
|
|
92
96
|
const itemCheck = g.visit(itemIR, { input: `${x}[${index}]` });
|
|
93
97
|
if (itemCheck === null) return null;
|
|
94
|
-
if (itemCheck
|
|
98
|
+
if (itemCheck === "true") continue;
|
|
99
|
+
parts.push(index >= ir.optStart ? `(${x}.length<=${index}||${itemCheck})` : itemCheck);
|
|
95
100
|
}
|
|
96
101
|
if (ir.rest !== null) {
|
|
97
102
|
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 {
|
|
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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"union.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/union.ts"],"mappings":";;;iBAQgB,UAAU,IAAI;EAAa;GAAiB,GAAG;
|
|
1
|
+
{"version":3,"file":"union.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/union.ts"],"mappings":";;;iBAQgB,UAAU,IAAI;EAAa;GAAiB,GAAG;iBA+I/C,UAAU,IAAI,SAAS,GAAG"}
|
|
@@ -62,6 +62,7 @@ function slowUnion(ir, g) {
|
|
|
62
62
|
const ojVar = g.temp("uoj");
|
|
63
63
|
const abVar = g.temp("uab");
|
|
64
64
|
const ocVar = g.temp("uoc");
|
|
65
|
+
const oiIssue = g.temp("uoq");
|
|
65
66
|
const okVar = g.temp("uok");
|
|
66
67
|
const ofVar = g.temp("uof");
|
|
67
68
|
const surfaced = `${naVar}[0][${okVar}]`;
|
|
@@ -73,7 +74,9 @@ function slowUnion(ir, g) {
|
|
|
73
74
|
var ${abVar}=${abortedVar}[${oiVar}]===true;
|
|
74
75
|
if(!${abVar}){
|
|
75
76
|
for(var ${ojVar}=0;${ojVar}<${errorsVar}[${oiVar}].length;${ojVar}++){
|
|
76
|
-
var ${
|
|
77
|
+
var ${oiIssue}=${errorsVar}[${oiVar}][${ojVar}];
|
|
78
|
+
if(${oiIssue}.continue===false){${abVar}=true;break;}
|
|
79
|
+
var ${ocVar}=${oiIssue}.code;
|
|
77
80
|
if(${abortingCodeTest(ocVar)}){${abVar}=true;break;}
|
|
78
81
|
}
|
|
79
82
|
}
|
|
@@ -86,7 +89,7 @@ function slowUnion(ir, g) {
|
|
|
86
89
|
}
|
|
87
90
|
}else{
|
|
88
91
|
for(var ${ofVar}=0;${ofVar}<${errorsVar}.length;${ofVar}++){${fz}(${errorsVar}[${ofVar}]);}
|
|
89
|
-
${g.issues}.push({code:"invalid_union",errors:${errorsVar}
|
|
92
|
+
${g.issues}.push({code:"invalid_union",errors:${errorsVar},input:${g.input},path:${unionPath}${msgProp}});
|
|
90
93
|
}
|
|
91
94
|
}`;
|
|
92
95
|
return `${code}\n`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"union.js","names":[],"sources":["../../../../src/core/codegen/schemas/union.ts"],"sourcesContent":["import type { SchemaIR, UnionIR } from \"../../types.js\";\nimport type { FastGen, SlowGen } from \"../context.js\";\nimport { emitRuntimeHelper, hasMutation } from \"../context.js\";\nimport { emit } from \"../emit.js\";\nimport { orderByRuntimeCost } from \"../fast-size.js\";\nimport { abortingCodeTest, ZC_FZ_DECL } from \"../issue-decls.js\";\nimport { detectUnionDiscriminator, emitFastDiscriminatedSwitch } from \"./discriminated-union.js\";\n\nexport function slowUnion(ir: SchemaIR & { type: \"union\" }, g: SlowGen): string {\n const resultVar = g.temp(\"u\");\n const errorsVar = g.temp(\"ue\");\n // Parallel to errorsVar: zod's `payload.aborted` for each failed option. Set\n // by a pipe/codec option whose `in` failed (see slowPipe); read during pruning\n // alongside the per-issue code check.\n const abortedVar = g.temp(\"uea\");\n let code = `var ${resultVar}=false;var ${errorsVar}=[];var ${abortedVar}=[];`;\n\n // Options run at a path RELATIVE to the union, not at the union's own path.\n //\n // $ZodUnion hands each option a FRESH payload — `{ value: payload.value,\n // issues: [] }` — so every issue an option raises is numbered from the union's\n // position, and handleUnionResults copies those relative paths into the\n // `invalid_union` groups unchanged. Walking options at the union's absolute\n // path instead leaked it into all of them: `{a: z.union([...])}` reported the\n // nested errors at `[\"a\"]` where zod reports `[]`. Same model record/map\n // already use for their key and value schemas.\n //\n // The union's own path is re-applied in exactly one place, mirroring the one\n // place zod re-applies it: handleUnionResults' shortcut for a single\n // non-aborted option returns THAT OPTION'S payload, whose issues are still\n // relative, and the PARENT (an object's handlePropertyResult, say) prefixes\n // them exactly once. Compiled parents bake their path into each issue at\n // creation, so the union has to do that prefixing itself.\n //\n // That path expression is read ONCE, into `pathVar`, rather than at each issue\n // below — it may allocate a fresh array per evaluation (`[\"items\",__i_7]`).\n // The binding is emitted inside the failure block further down, not here: both\n // readers live there, so a union that matches an option never evaluates it at\n // all, and the whole of this generator's output is one contiguous block at the\n // union's site, so any enclosing loop variable the expression names is equally\n // in scope there. A union already at `[]` prefixes nothing (concat would only\n // copy the array), so it skips the binding entirely.\n const atPathRoot = g.path === \"[]\";\n const pathVar = atPathRoot ? null : g.temp(\"up\");\n const unionPath = pathVar ?? \"[]\";\n\n // If any option can mutate output (default, catch, coerce, effect),\n // each branch gets its own temp output to prevent cross-branch leaks.\n const needsOutputIsolation = ir.options.some(hasMutation);\n\n for (const option of ir.options) {\n const tmpIssues = g.temp(\"ui\");\n // This option's abort flag, forwarded into the option so a pipe `in` failure\n // can raise it (zod's handlePipeResult). Stays false for every other shape.\n const optAborted = g.temp(\"uoa\");\n\n if (needsOutputIsolation) {\n const tmpOutput = g.temp(\"uo\");\n code += emit`\n if(!${resultVar}){\n var ${tmpIssues}=[];\n var ${optAborted}=false;\n var ${tmpOutput}=${g.input};\n ${g.visit(option, { issues: tmpIssues, input: tmpOutput, output: tmpOutput, path: \"[]\", aborted: optAborted })}\n if(${tmpIssues}.length===0){\n ${resultVar}=true;\n ${g.output}=${tmpOutput};\n }else{\n ${errorsVar}.push(${tmpIssues});\n ${abortedVar}.push(${optAborted});\n }\n }`;\n } else {\n code += emit`\n if(!${resultVar}){\n var ${tmpIssues}=[];\n var ${optAborted}=false;\n ${g.visit(option, { issues: tmpIssues, path: \"[]\", aborted: optAborted })}\n if(${tmpIssues}.length===0){\n ${resultVar}=true;\n }else{\n ${errorsVar}.push(${tmpIssues});\n ${abortedVar}.push(${optAborted});\n }\n }`;\n }\n }\n\n // Mirrors zod's handleUnionResults pruning (`util.aborted`): an option is\n // \"aborted\" when its result carries `payload.aborted` (a pipe whose `in`\n // failed — tracked in abortedVar) OR it produced a parse-level issue\n // (continue !== true in zod — invalid_type and friends). Check-level issues\n // (too_small, invalid_format, custom, ...) alone don't abort. If exactly ONE\n // option is non-aborted, its issues are surfaced directly instead of an\n // invalid_union wrapper.\n const msgProp = g.typeMsg === undefined ? \"\" : `,message:${JSON.stringify(g.typeMsg)}`;\n const fz = emitRuntimeHelper(g.ctx, \"__zcFz\", ZC_FZ_DECL);\n const naVar = g.temp(\"una\");\n const oiVar = g.temp(\"uoi\");\n const ojVar = g.temp(\"uoj\");\n const abVar = g.temp(\"uab\");\n const ocVar = g.temp(\"uoc\");\n const okVar = g.temp(\"uok\");\n const ofVar = g.temp(\"uof\");\n const surfaced = `${naVar}[0][${okVar}]`;\n // Which branch fires decides where an option's issues are finalized, because\n // the two branches finalize at DIFFERENT paths — so neither can be done up\n // front, in the option loop, without being wrong for the other half:\n //\n // - invalid_union: zod maps each group through `finalizeIssue` while the paths\n // are still relative, so an error map reading `issue.path` sees `[]`.\n // __zcFz is that same locale-fill + input-strip, and is what record/map\n // already use for the issues they nest.\n // - sole non-aborted: zod surfaces the option's own issues, which reach the\n // top-level finalizer with the FULL path, so the message is computed from\n // the absolute path. Prefix here and leave them unfinalized — they land in\n // the parent's issue array, which is finalized at the top exactly once.\n code += emit`\n if(!${resultVar}){\n ${pathVar === null ? \"\" : `var ${pathVar}=${g.path};`}\n var ${naVar}=[];\n for(var ${oiVar}=0;${oiVar}<${errorsVar}.length;${oiVar}++){\n var ${abVar}=${abortedVar}[${oiVar}]===true;\n if(!${abVar}){\n for(var ${ojVar}=0;${ojVar}<${errorsVar}[${oiVar}].length;${ojVar}++){\n var ${ocVar}=${errorsVar}[${oiVar}][${ojVar}].code;\n if(${abortingCodeTest(ocVar)}){${abVar}=true;break;}\n }\n }\n if(!${abVar}){${naVar}.push(${errorsVar}[${oiVar}]);}\n }\n if(${naVar}.length===1){\n for(var ${okVar}=0;${okVar}<${naVar}[0].length;${okVar}++){\n ${atPathRoot ? \"\" : `${surfaced}.path=${unionPath}.concat(${surfaced}.path);`}\n ${g.issues}.push(${surfaced});\n }\n }else{\n for(var ${ofVar}=0;${ofVar}<${errorsVar}.length;${ofVar}++){${fz}(${errorsVar}[${ofVar}]);}\n ${g.issues}.push({code:\"invalid_union\",errors:${errorsVar}${msgProp},input:${g.input},path:${unionPath}});\n }\n }`;\n return `${code}\\n`;\n}\n\nexport function fastUnion(ir: UnionIR, g: FastGen): string | null {\n // A plain `z.union` of objects that all pin a shared key to disjoint literals\n // is structurally a discriminated union: dispatch on that key with an O(1)\n // switch instead of probing every arm in sequence. detectUnionDiscriminator\n // returns non-null only when the switch provably accepts exactly what the\n // ||-chain would (disjoint required literals). The slow path is untouched, so\n // error output stays identical to Zod's plain-union behavior.\n const discriminated = detectUnionDiscriminator(ir.options);\n if (discriminated !== null) {\n return emitFastDiscriminatedSwitch(\n g,\n discriminated.discriminator,\n discriminated.cases,\n ir.options,\n );\n }\n\n // Probe cheap options first: the ||-chain stops at the first match, and an\n // option that cannot match is rejected by its own cheapest-first conjunct\n // chain. Which option matches is unobservable here (the result is a single\n // boolean), and the slow path — where option order IS observable, through\n // zod's first-match output and invalid_union issue order — is untouched.\n const optionChecks: string[] = [];\n for (const option of orderByRuntimeCost(ir.options, (o) => o, g.ctx)) {\n const check = g.visit(option);\n if (check === null) return null;\n optionChecks.push(`(${check})`);\n }\n // Wrap in parens — || has lower precedence than && in parent expressions\n return `(${optionChecks.join(\"||\")})`;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"union.js","names":[],"sources":["../../../../src/core/codegen/schemas/union.ts"],"sourcesContent":["import type { SchemaIR, UnionIR } from \"../../types.js\";\nimport type { FastGen, SlowGen } from \"../context.js\";\nimport { emitRuntimeHelper, hasMutation } from \"../context.js\";\nimport { emit } from \"../emit.js\";\nimport { orderByRuntimeCost } from \"../fast-size.js\";\nimport { abortingCodeTest, ZC_FZ_DECL } from \"../issue-decls.js\";\nimport { detectUnionDiscriminator, emitFastDiscriminatedSwitch } from \"./discriminated-union.js\";\n\nexport function slowUnion(ir: SchemaIR & { type: \"union\" }, g: SlowGen): string {\n const resultVar = g.temp(\"u\");\n const errorsVar = g.temp(\"ue\");\n // Parallel to errorsVar: zod's `payload.aborted` for each failed option. Set\n // by a pipe/codec option whose `in` failed (see slowPipe); read during pruning\n // alongside the per-issue code check.\n const abortedVar = g.temp(\"uea\");\n let code = `var ${resultVar}=false;var ${errorsVar}=[];var ${abortedVar}=[];`;\n\n // Options run at a path RELATIVE to the union, not at the union's own path.\n //\n // $ZodUnion hands each option a FRESH payload — `{ value: payload.value,\n // issues: [] }` — so every issue an option raises is numbered from the union's\n // position, and handleUnionResults copies those relative paths into the\n // `invalid_union` groups unchanged. Walking options at the union's absolute\n // path instead leaked it into all of them: `{a: z.union([...])}` reported the\n // nested errors at `[\"a\"]` where zod reports `[]`. Same model record/map\n // already use for their key and value schemas.\n //\n // The union's own path is re-applied in exactly one place, mirroring the one\n // place zod re-applies it: handleUnionResults' shortcut for a single\n // non-aborted option returns THAT OPTION'S payload, whose issues are still\n // relative, and the PARENT (an object's handlePropertyResult, say) prefixes\n // them exactly once. Compiled parents bake their path into each issue at\n // creation, so the union has to do that prefixing itself.\n //\n // That path expression is read ONCE, into `pathVar`, rather than at each issue\n // below — it may allocate a fresh array per evaluation (`[\"items\",__i_7]`).\n // The binding is emitted inside the failure block further down, not here: both\n // readers live there, so a union that matches an option never evaluates it at\n // all, and the whole of this generator's output is one contiguous block at the\n // union's site, so any enclosing loop variable the expression names is equally\n // in scope there. A union already at `[]` prefixes nothing (concat would only\n // copy the array), so it skips the binding entirely.\n const atPathRoot = g.path === \"[]\";\n const pathVar = atPathRoot ? null : g.temp(\"up\");\n const unionPath = pathVar ?? \"[]\";\n\n // If any option can mutate output (default, catch, coerce, effect),\n // each branch gets its own temp output to prevent cross-branch leaks.\n const needsOutputIsolation = ir.options.some(hasMutation);\n\n for (const option of ir.options) {\n const tmpIssues = g.temp(\"ui\");\n // This option's abort flag, forwarded into the option so a pipe `in` failure\n // can raise it (zod's handlePipeResult). Stays false for every other shape.\n const optAborted = g.temp(\"uoa\");\n\n if (needsOutputIsolation) {\n const tmpOutput = g.temp(\"uo\");\n code += emit`\n if(!${resultVar}){\n var ${tmpIssues}=[];\n var ${optAborted}=false;\n var ${tmpOutput}=${g.input};\n ${g.visit(option, { issues: tmpIssues, input: tmpOutput, output: tmpOutput, path: \"[]\", aborted: optAborted })}\n if(${tmpIssues}.length===0){\n ${resultVar}=true;\n ${g.output}=${tmpOutput};\n }else{\n ${errorsVar}.push(${tmpIssues});\n ${abortedVar}.push(${optAborted});\n }\n }`;\n } else {\n code += emit`\n if(!${resultVar}){\n var ${tmpIssues}=[];\n var ${optAborted}=false;\n ${g.visit(option, { issues: tmpIssues, path: \"[]\", aborted: optAborted })}\n if(${tmpIssues}.length===0){\n ${resultVar}=true;\n }else{\n ${errorsVar}.push(${tmpIssues});\n ${abortedVar}.push(${optAborted});\n }\n }`;\n }\n }\n\n // Mirrors zod's handleUnionResults pruning (`util.aborted`): an option is\n // \"aborted\" when its result carries `payload.aborted` (a pipe whose `in`\n // failed — tracked in abortedVar) OR it produced a parse-level issue\n // (continue !== true in zod — invalid_type and friends). Check-level issues\n // (too_small, invalid_format, custom, ...) alone don't abort. If exactly ONE\n // option is non-aborted, its issues are surfaced directly instead of an\n // invalid_union wrapper.\n const msgProp = g.typeMsg === undefined ? \"\" : `,message:${JSON.stringify(g.typeMsg)}`;\n const fz = emitRuntimeHelper(g.ctx, \"__zcFz\", ZC_FZ_DECL);\n const naVar = g.temp(\"una\");\n const oiVar = g.temp(\"uoi\");\n const ojVar = g.temp(\"uoj\");\n const abVar = g.temp(\"uab\");\n const ocVar = g.temp(\"uoc\");\n // zod prunes with `util.aborted`, i.e. `continue !== true` on ANY issue — a\n // superset of the code test below, and the only thing that catches a tuple's\n // length issue (schema-created, same code a continuable check uses). See\n // `abortsProp` in emit-issue.ts.\n const oiIssue = g.temp(\"uoq\");\n const okVar = g.temp(\"uok\");\n const ofVar = g.temp(\"uof\");\n const surfaced = `${naVar}[0][${okVar}]`;\n // Which branch fires decides where an option's issues are finalized, because\n // the two branches finalize at DIFFERENT paths — so neither can be done up\n // front, in the option loop, without being wrong for the other half:\n //\n // - invalid_union: zod maps each group through `finalizeIssue` while the paths\n // are still relative, so an error map reading `issue.path` sees `[]`.\n // __zcFz is that same locale-fill + input-strip, and is what record/map\n // already use for the issues they nest.\n // - sole non-aborted: zod surfaces the option's own issues, which reach the\n // top-level finalizer with the FULL path, so the message is computed from\n // the absolute path. Prefix here and leave them unfinalized — they land in\n // the parent's issue array, which is finalized at the top exactly once.\n code += emit`\n if(!${resultVar}){\n ${pathVar === null ? \"\" : `var ${pathVar}=${g.path};`}\n var ${naVar}=[];\n for(var ${oiVar}=0;${oiVar}<${errorsVar}.length;${oiVar}++){\n var ${abVar}=${abortedVar}[${oiVar}]===true;\n if(!${abVar}){\n for(var ${ojVar}=0;${ojVar}<${errorsVar}[${oiVar}].length;${ojVar}++){\n var ${oiIssue}=${errorsVar}[${oiVar}][${ojVar}];\n if(${oiIssue}.continue===false){${abVar}=true;break;}\n var ${ocVar}=${oiIssue}.code;\n if(${abortingCodeTest(ocVar)}){${abVar}=true;break;}\n }\n }\n if(!${abVar}){${naVar}.push(${errorsVar}[${oiVar}]);}\n }\n if(${naVar}.length===1){\n for(var ${okVar}=0;${okVar}<${naVar}[0].length;${okVar}++){\n ${atPathRoot ? \"\" : `${surfaced}.path=${unionPath}.concat(${surfaced}.path);`}\n ${g.issues}.push(${surfaced});\n }\n }else{\n for(var ${ofVar}=0;${ofVar}<${errorsVar}.length;${ofVar}++){${fz}(${errorsVar}[${ofVar}]);}\n ${g.issues}.push({code:\"invalid_union\",errors:${errorsVar},input:${g.input},path:${unionPath}${msgProp}});\n }\n }`;\n return `${code}\\n`;\n}\n\nexport function fastUnion(ir: UnionIR, g: FastGen): string | null {\n // A plain `z.union` of objects that all pin a shared key to disjoint literals\n // is structurally a discriminated union: dispatch on that key with an O(1)\n // switch instead of probing every arm in sequence. detectUnionDiscriminator\n // returns non-null only when the switch provably accepts exactly what the\n // ||-chain would (disjoint required literals). The slow path is untouched, so\n // error output stays identical to Zod's plain-union behavior.\n const discriminated = detectUnionDiscriminator(ir.options);\n if (discriminated !== null) {\n return emitFastDiscriminatedSwitch(\n g,\n discriminated.discriminator,\n discriminated.cases,\n ir.options,\n );\n }\n\n // Probe cheap options first: the ||-chain stops at the first match, and an\n // option that cannot match is rejected by its own cheapest-first conjunct\n // chain. Which option matches is unobservable here (the result is a single\n // boolean), and the slow path — where option order IS observable, through\n // zod's first-match output and invalid_union issue order — is untouched.\n //\n // That \"unobservable\" holds for the VERDICT this expression reports, which is\n // all a nested conjunct or a `.is()` guard consumes. It does NOT hold for the\n // by-reference shortcut a root schema takes on a passing check, where WHICH\n // option zod picked decides the output value — see `fastResultIsInput`, which\n // withholds that shortcut rather than costing every union its fast check.\n const optionChecks: string[] = [];\n for (const option of orderByRuntimeCost(ir.options, (o) => o, g.ctx)) {\n const check = g.visit(option);\n if (check === null) return null;\n optionChecks.push(`(${check})`);\n }\n // Wrap in parens — || has lower precedence than && in parent expressions\n return `(${optionChecks.join(\"||\")})`;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"object.d.ts","names":[],"sources":["../../../../src/core/extract/extractors/object.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"object.d.ts","names":[],"sources":["../../../../src/core/extract/extractors/object.ts"],"mappings":";;;iBAiCgB,cAAc,KAAK,QAAQ,KAAK,mBAAmB"}
|
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
import { customParamsRefRegistrar, extractChecks, payloadCheckRef, refineRefRegistrar } from "../checks.js";
|
|
2
2
|
//#region src/core/extract/extractors/object.ts
|
|
3
|
+
/**
|
|
4
|
+
* Can this property IR raise an issue when its key is ABSENT (so the value it
|
|
5
|
+
* sees is `undefined`)? Only those need zod's absent-key issue suppression
|
|
6
|
+
* wrapped around them.
|
|
7
|
+
*
|
|
8
|
+
* `optional` and `undefined` cannot: the first short-circuits `undefined`
|
|
9
|
+
* (or forwards it into a `default`, which substitutes without complaint), and
|
|
10
|
+
* the second accepts it. `nullable`/`readonly` are transparent to `undefined`,
|
|
11
|
+
* so they inherit the answer. Everything else — a `fallback` delegate above all
|
|
12
|
+
* — is assumed to report.
|
|
13
|
+
*/
|
|
14
|
+
function reportsOnAbsentKey(ir) {
|
|
15
|
+
switch (ir.type) {
|
|
16
|
+
case "optional":
|
|
17
|
+
case "undefined": return false;
|
|
18
|
+
case "nullable":
|
|
19
|
+
case "readonly": return reportsOnAbsentKey(ir.inner);
|
|
20
|
+
default: return true;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
3
23
|
function extractObject(def, ctx) {
|
|
4
24
|
const catchallType = def.catchall?._zod?.def?.type;
|
|
5
25
|
const strict = catchallType === "never";
|
|
@@ -14,7 +34,7 @@ function extractObject(def, ctx) {
|
|
|
14
34
|
for (const [key, value] of Object.entries(def.shape)) {
|
|
15
35
|
const propIR = ctx.visit(value, `.shape[${JSON.stringify(key)}]`);
|
|
16
36
|
properties[key] = propIR;
|
|
17
|
-
if (
|
|
37
|
+
if (value._zod.optout === "optional" && reportsOnAbsentKey(propIR)) suppressAbsentKeys.push(key);
|
|
18
38
|
}
|
|
19
39
|
const propIRs = Object.values(properties);
|
|
20
40
|
if (propIRs.length > 0 && propIRs.every((p) => p.type === "fallback")) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"object.js","names":[],"sources":["../../../../src/core/extract/extractors/object.ts"],"sourcesContent":["import type { RefineEffectCheckIR, SchemaIR, SuperRefineEffectCheckIR } from \"../../types.js\";\nimport {\n customParamsRefRegistrar,\n extractChecks,\n payloadCheckRef,\n refineRefRegistrar,\n} from \"../checks.js\";\nimport type { ExtractorContext, ZodDef } from \"../types.js\";\n\nexport function extractObject(def: ZodDef, ctx: ExtractorContext): SchemaIR {\n // Unknown-key policies:\n // - z.strictObject / .strict() / .catchall(z.never()) reject unknown keys —\n // compiled as a for-in membership pass (ObjectIR.strict). Pass-through is\n // preserved: valid strict data has no extras, so no clone is needed.\n // - z.looseObject (catchall: unknown/any) matches compiled pass-through.\n // - .catchall(schema) validates unknown keys against a schema (ObjectIR.catchall).\n // - default z.object() (no catchall): STRIPS unknown keys, as zod does\n // (ObjectIR.stripUnknownKeys). looseObject/strictObject are unaffected —\n // the first keeps extras by design, the second rejects them outright.\n const catchallType = def.catchall?._zod?.def?.type;\n const strict = catchallType === \"never\";\n const valueCatchall =\n def.catchall && !strict && catchallType !== \"unknown\" && catchallType !== \"any\"\n ? ctx.visit(def.catchall, \"._zod.def.catchall\")\n : undefined;\n // A delegating catchall would need zod per unknown key; hand it the object.\n if (valueCatchall?.type === \"fallback\") return ctx.fallback(\"unsupported\");\n const catchallFlag = valueCatchall ? { catchall: valueCatchall } : {};\n const strictFlag = strict ? { strict: true } : {};\n const stripFlag = def.catchall ? {} : { stripUnknownKeys: true };\n\n // Null-prototype: on a normal object `properties[\"__proto__\"] = ir` sets the\n // prototype instead of defining a key, so a shape declaring `__proto__` (only\n // reachable via a computed key or a dynamically built shape — an object\n // literal's `__proto__:` is the same setter) silently lost that property.\n // Every consumer reads this through Object.keys/values/entries, so dropping\n // the prototype is invisible to them.\n const properties: Record<string, SchemaIR> = Object.create(null) as Record<string, SchemaIR>;\n //
|
|
1
|
+
{"version":3,"file":"object.js","names":[],"sources":["../../../../src/core/extract/extractors/object.ts"],"sourcesContent":["import type { RefineEffectCheckIR, SchemaIR, SuperRefineEffectCheckIR } from \"../../types.js\";\nimport {\n customParamsRefRegistrar,\n extractChecks,\n payloadCheckRef,\n refineRefRegistrar,\n} from \"../checks.js\";\nimport type { ExtractorContext, ZodDef } from \"../types.js\";\n\n/**\n * Can this property IR raise an issue when its key is ABSENT (so the value it\n * sees is `undefined`)? Only those need zod's absent-key issue suppression\n * wrapped around them.\n *\n * `optional` and `undefined` cannot: the first short-circuits `undefined`\n * (or forwards it into a `default`, which substitutes without complaint), and\n * the second accepts it. `nullable`/`readonly` are transparent to `undefined`,\n * so they inherit the answer. Everything else — a `fallback` delegate above all\n * — is assumed to report.\n */\nfunction reportsOnAbsentKey(ir: SchemaIR): boolean {\n switch (ir.type) {\n case \"optional\":\n case \"undefined\":\n return false;\n case \"nullable\":\n case \"readonly\":\n return reportsOnAbsentKey(ir.inner);\n default:\n return true;\n }\n}\n\nexport function extractObject(def: ZodDef, ctx: ExtractorContext): SchemaIR {\n // Unknown-key policies:\n // - z.strictObject / .strict() / .catchall(z.never()) reject unknown keys —\n // compiled as a for-in membership pass (ObjectIR.strict). Pass-through is\n // preserved: valid strict data has no extras, so no clone is needed.\n // - z.looseObject (catchall: unknown/any) matches compiled pass-through.\n // - .catchall(schema) validates unknown keys against a schema (ObjectIR.catchall).\n // - default z.object() (no catchall): STRIPS unknown keys, as zod does\n // (ObjectIR.stripUnknownKeys). looseObject/strictObject are unaffected —\n // the first keeps extras by design, the second rejects them outright.\n const catchallType = def.catchall?._zod?.def?.type;\n const strict = catchallType === \"never\";\n const valueCatchall =\n def.catchall && !strict && catchallType !== \"unknown\" && catchallType !== \"any\"\n ? ctx.visit(def.catchall, \"._zod.def.catchall\")\n : undefined;\n // A delegating catchall would need zod per unknown key; hand it the object.\n if (valueCatchall?.type === \"fallback\") return ctx.fallback(\"unsupported\");\n const catchallFlag = valueCatchall ? { catchall: valueCatchall } : {};\n const strictFlag = strict ? { strict: true } : {};\n const stripFlag = def.catchall ? {} : { stripUnknownKeys: true };\n\n // Null-prototype: on a normal object `properties[\"__proto__\"] = ir` sets the\n // prototype instead of defining a key, so a shape declaring `__proto__` (only\n // reachable via a computed key or a dynamically built shape — an object\n // literal's `__proto__:` is the same setter) silently lost that property.\n // Every consumer reads this through Object.keys/values/entries, so dropping\n // the prototype is invisible to them.\n const properties: Record<string, SchemaIR> = Object.create(null) as Record<string, SchemaIR>;\n // Optional-OUT props: mirror zod's handlePropertyResult, which suppresses\n // issues for ABSENT keys (`if (isOptionalOut && !(key in input)) return`).\n // That is how `z.exactOptional()` accepts a missing key while rejecting an\n // explicit `undefined`.\n //\n // Gated on the property IR still being able to REPORT for an absent key, so\n // the common `.optional()` field pays nothing: its compiled form short-\n // circuits `undefined` and produces no issue to suppress. Testing\n // `propIR.type === \"fallback\"` — as this once did — was too narrow by exactly\n // one wrapper: `z.exactOptional(z.string()).nullable()` extracts to\n // `nullable(fallback)`, keeps `optout === \"optional\"` (nullable propagates\n // it), and delegated `undefined` straight into zod's exactOptional, which\n // rejects it. The object then reported an `invalid_type` for a key zod does\n // not look at.\n const suppressAbsentKeys: string[] = [];\n const refMark = ctx.refs?.length ?? 0;\n for (const [key, value] of Object.entries(def.shape)) {\n const propIR = ctx.visit(value, `.shape[${JSON.stringify(key)}]`);\n properties[key] = propIR;\n if (value._zod.optout === \"optional\" && reportsOnAbsentKey(propIR)) {\n suppressAbsentKeys.push(key);\n }\n }\n\n // Fallback coalescing: when EVERY property delegates to Zod, the per-field\n // wrapper (clone + N safeParse calls + issue path rewrites) is pure overhead\n // — measured ~1.4x slower than letting Zod validate the object in one pass.\n // Delegate the whole object instead. The discarded property fallbacks'\n // ref-table entries are rolled back so __rf[] holds only live schemas.\n const propIRs = Object.values(properties);\n if (propIRs.length > 0 && propIRs.every((p) => p.type === \"fallback\")) {\n if (ctx.refs) ctx.refs.length = refMark;\n return ctx.fallback(\"coalesced\");\n }\n const suppress = suppressAbsentKeys.length > 0 ? { suppressAbsentKeys } : {};\n\n if (def.checks && def.checks.length > 0) {\n const { checkIRs, hasFallback } = extractChecks(\n def.checks,\n refineRefRegistrar(ctx, def.checks),\n (index) => payloadCheckRef(ctx, def.checks, index),\n customParamsRefRegistrar(ctx, def.checks),\n );\n if (hasFallback) return ctx.fallback(\"refine\");\n // Object codegen supports the two callback kinds; anything else (overwrite,\n // exotic .check() entries) must not be dropped.\n if (checkIRs.some((c) => c.kind !== \"refine_effect\" && c.kind !== \"super_refine_effect\")) {\n return ctx.fallback(\"unsupported\");\n }\n const refineChecks = checkIRs.filter(\n (c): c is RefineEffectCheckIR | SuperRefineEffectCheckIR =>\n c.kind === \"refine_effect\" || c.kind === \"super_refine_effect\",\n );\n if (refineChecks.length > 0) {\n return {\n type: \"object\",\n properties,\n checks: refineChecks,\n ...strictFlag,\n ...stripFlag,\n ...catchallFlag,\n ...suppress,\n };\n }\n }\n return { type: \"object\", properties, ...strictFlag, ...stripFlag, ...catchallFlag, ...suppress };\n}\n"],"mappings":";;;;;;;;;;;;;AAoBA,SAAS,mBAAmB,IAAuB;CACjD,QAAQ,GAAG,MAAX;EACE,KAAK;EACL,KAAK,aACH,OAAO;EACT,KAAK;EACL,KAAK,YACH,OAAO,mBAAmB,GAAG,KAAK;EACpC,SACE,OAAO;CACX;AACF;AAEA,SAAgB,cAAc,KAAa,KAAiC;CAU1E,MAAM,eAAe,IAAI,UAAU,MAAM,KAAK;CAC9C,MAAM,SAAS,iBAAiB;CAChC,MAAM,gBACJ,IAAI,YAAY,CAAC,UAAU,iBAAiB,aAAa,iBAAiB,QACtE,IAAI,MAAM,IAAI,UAAU,oBAAoB,IAC5C,KAAA;CAEN,IAAI,eAAe,SAAS,YAAY,OAAO,IAAI,SAAS,aAAa;CACzE,MAAM,eAAe,gBAAgB,EAAE,UAAU,cAAc,IAAI,CAAC;CACpE,MAAM,aAAa,SAAS,EAAE,QAAQ,KAAK,IAAI,CAAC;CAChD,MAAM,YAAY,IAAI,WAAW,CAAC,IAAI,EAAE,kBAAkB,KAAK;CAQ/D,MAAM,aAAuC,OAAO,OAAO,IAAI;CAe/D,MAAM,qBAA+B,CAAC;CACtC,MAAM,UAAU,IAAI,MAAM,UAAU;CACpC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,IAAI,KAAK,GAAG;EACpD,MAAM,SAAS,IAAI,MAAM,OAAO,UAAU,KAAK,UAAU,GAAG,EAAE,EAAE;EAChE,WAAW,OAAO;EAClB,IAAI,MAAM,KAAK,WAAW,cAAc,mBAAmB,MAAM,GAC/D,mBAAmB,KAAK,GAAG;CAE/B;CAOA,MAAM,UAAU,OAAO,OAAO,UAAU;CACxC,IAAI,QAAQ,SAAS,KAAK,QAAQ,OAAO,MAAM,EAAE,SAAS,UAAU,GAAG;EACrE,IAAI,IAAI,MAAM,IAAI,KAAK,SAAS;EAChC,OAAO,IAAI,SAAS,WAAW;CACjC;CACA,MAAM,WAAW,mBAAmB,SAAS,IAAI,EAAE,mBAAmB,IAAI,CAAC;CAE3E,IAAI,IAAI,UAAU,IAAI,OAAO,SAAS,GAAG;EACvC,MAAM,EAAE,UAAU,gBAAgB,cAChC,IAAI,QACJ,mBAAmB,KAAK,IAAI,MAAM,IACjC,UAAU,gBAAgB,KAAK,IAAI,QAAQ,KAAK,GACjD,yBAAyB,KAAK,IAAI,MAAM,CAC1C;EACA,IAAI,aAAa,OAAO,IAAI,SAAS,QAAQ;EAG7C,IAAI,SAAS,MAAM,MAAM,EAAE,SAAS,mBAAmB,EAAE,SAAS,qBAAqB,GACrF,OAAO,IAAI,SAAS,aAAa;EAEnC,MAAM,eAAe,SAAS,QAC3B,MACC,EAAE,SAAS,mBAAmB,EAAE,SAAS,qBAC7C;EACA,IAAI,aAAa,SAAS,GACxB,OAAO;GACL,MAAM;GACN;GACA,QAAQ;GACR,GAAG;GACH,GAAG;GACH,GAAG;GACH,GAAG;EACL;CAEJ;CACA,OAAO;EAAE,MAAM;EAAU;EAAY,GAAG;EAAY,GAAG;EAAW,GAAG;EAAc,GAAG;CAAS;AACjG"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"optional.d.ts","names":[],"sources":["../../../../src/core/extract/extractors/optional.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"optional.d.ts","names":[],"sources":["../../../../src/core/extract/extractors/optional.ts"],"mappings":";;cAkCa,iBAAiB"}
|