zod-compiler 2.0.3 → 2.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/core/codegen/context.d.ts +40 -1
  2. package/dist/core/codegen/context.d.ts.map +1 -1
  3. package/dist/core/codegen/context.js +51 -1
  4. package/dist/core/codegen/context.js.map +1 -1
  5. package/dist/core/codegen/index.js +1 -0
  6. package/dist/core/codegen/index.js.map +1 -1
  7. package/dist/core/codegen/schemas/array.d.ts.map +1 -1
  8. package/dist/core/codegen/schemas/array.js +23 -17
  9. package/dist/core/codegen/schemas/array.js.map +1 -1
  10. package/dist/core/codegen/schemas/discriminated-union.d.ts.map +1 -1
  11. package/dist/core/codegen/schemas/discriminated-union.js +2 -2
  12. package/dist/core/codegen/schemas/discriminated-union.js.map +1 -1
  13. package/dist/core/codegen/schemas/effect.d.ts.map +1 -1
  14. package/dist/core/codegen/schemas/effect.js +1 -1
  15. package/dist/core/codegen/schemas/effect.js.map +1 -1
  16. package/dist/core/codegen/schemas/map.d.ts.map +1 -1
  17. package/dist/core/codegen/schemas/map.js +70 -17
  18. package/dist/core/codegen/schemas/map.js.map +1 -1
  19. package/dist/core/codegen/schemas/record.d.ts.map +1 -1
  20. package/dist/core/codegen/schemas/record.js +23 -17
  21. package/dist/core/codegen/schemas/record.js.map +1 -1
  22. package/dist/core/codegen/schemas/recursive-ref.d.ts.map +1 -1
  23. package/dist/core/codegen/schemas/recursive-ref.js +1 -0
  24. package/dist/core/codegen/schemas/recursive-ref.js.map +1 -1
  25. package/dist/core/codegen/schemas/set.d.ts.map +1 -1
  26. package/dist/core/codegen/schemas/set.js +51 -13
  27. package/dist/core/codegen/schemas/set.js.map +1 -1
  28. package/dist/core/codegen/schemas/tuple.d.ts.map +1 -1
  29. package/dist/core/codegen/schemas/tuple.js +16 -13
  30. package/dist/core/codegen/schemas/tuple.js.map +1 -1
  31. package/dist/core/codegen/schemas/union.d.ts.map +1 -1
  32. package/dist/core/codegen/schemas/union.js +21 -15
  33. package/dist/core/codegen/schemas/union.js.map +1 -1
  34. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { checkPriority, declareFastTemps, emitRuntimeHelper, extendPath, hasMutation } from "../context.js";
1
+ import { checkPriority, declareFastTemps, emitRuntimeHelper, extendPath, hasMutation, visitMember } from "../context.js";
2
2
  import { emit } from "../emit.js";
3
3
  import { invalidType, tooBig, tooSmall } from "../emit-issue.js";
4
4
  import { ZC_AB_DECL } from "../issue-decls.js";
@@ -11,45 +11,51 @@ function slowArray(ir, g) {
11
11
  ${invalidType(g, "array")}
12
12
  ${whenGatedSizeChecks(ir.checks, g, "length", true)}
13
13
  }else{`;
14
- if (hasMutation(ir.element)) code += `${g.output}=${g.input}.slice();`;
14
+ const arr = g.temp("ar");
15
+ code += `var ${arr}=${g.input};`;
16
+ if (hasMutation(ir.element)) code += `${arr}=${arr}.slice();`;
15
17
  const refineMark = ir.checks.some((c) => c.kind === "refine_effect" || c.kind === "super_refine_effect") ? g.temp("rm") : "";
16
18
  if (refineMark) code += `var ${refineMark}=${g.issues}.length;`;
17
19
  const idxVar = g.temp("i");
18
- const elemExpr = `${g.input}[${idxVar}]`;
19
- const elemPath = extendPath(g.path, idxVar);
20
+ const element = visitMember(g, ir.element, {
21
+ container: arr,
22
+ original: g.input,
23
+ copy: `${arr}.slice()`,
24
+ key: idxVar,
25
+ path: extendPath(g.path, idxVar),
26
+ issues: g.issues
27
+ });
20
28
  code += emit`
21
- for(var ${idxVar}=0;${idxVar}<${g.input}.length;${idxVar}++){
22
- ${g.visit(ir.element, {
23
- input: elemExpr,
24
- output: elemExpr,
25
- path: elemPath
26
- })}
27
- }`;
29
+ for(var ${idxVar}=0;${idxVar}<${arr}.length;${idxVar}++){
30
+ ${element}
31
+ }
32
+ ${g.output}=${arr};`;
28
33
  /** Wrap one refine effect in zod's abort gate, preserving declaration order. */
29
34
  const gated = (body) => {
30
35
  return `if(!${emitRuntimeHelper(g.ctx, "__zcAb", ZC_AB_DECL)}(${g.issues},${refineMark})){${body}}`;
31
36
  };
37
+ const parsed = g.output;
32
38
  for (const check of ir.checks) switch (check.kind) {
33
39
  case "min_length":
34
40
  code += emit`
35
- if(${g.input}.length<${check.minimum}){
41
+ if(${parsed}.length<${check.minimum}){
36
42
  ${tooSmall(g, check.minimum, "array", true, { message: check.message })}
37
43
  }`;
38
44
  break;
39
45
  case "max_length":
40
46
  code += emit`
41
- if(${g.input}.length>${check.maximum}){
47
+ if(${parsed}.length>${check.maximum}){
42
48
  ${tooBig(g, check.maximum, "array", true, { message: check.message })}
43
49
  }`;
44
50
  break;
45
51
  case "length_equals":
46
52
  code += emit`
47
- if(${g.input}.length<${check.length}){
53
+ if(${parsed}.length<${check.length}){
48
54
  ${tooSmall(g, check.length, "array", true, {
49
55
  exact: true,
50
56
  message: check.message
51
57
  })}
52
- }else if(${g.input}.length>${check.length}){
58
+ }else if(${parsed}.length>${check.length}){
53
59
  ${tooBig(g, check.length, "array", true, {
54
60
  exact: true,
55
61
  message: check.message
@@ -57,10 +63,10 @@ function slowArray(ir, g) {
57
63
  }`;
58
64
  break;
59
65
  case "refine_effect":
60
- code += gated(refineCheck(check, g.input, g));
66
+ code += gated(refineCheck(check, parsed, g));
61
67
  break;
62
68
  case "super_refine_effect":
63
- code += gated(superRefineCheck(check, g.input, g));
69
+ code += gated(superRefineCheck(check, parsed, g));
64
70
  break;
65
71
  }
66
72
  code += `}`;
@@ -1 +1 @@
1
- {"version":3,"file":"array.js","names":[],"sources":["../../../../src/core/codegen/schemas/array.ts"],"sourcesContent":["import type { ArrayIR, CheckIR, SchemaIR } from \"../../types.js\";\nimport type { FastGen, SlowGen } from \"../context.js\";\nimport {\n checkPriority,\n declareFastTemps,\n emitRuntimeHelper,\n extendPath,\n hasMutation,\n} from \"../context.js\";\nimport { emit } from \"../emit.js\";\nimport { invalidType, tooBig, tooSmall } from \"../emit-issue.js\";\nimport { ZC_AB_DECL } from \"../issue-decls.js\";\nimport { fastRefineTest, refineCheck, superRefineCheck, superRefineFastTest } from \"./effect.js\";\nimport { whenGatedSizeChecks } from \"./sizeable.js\";\n\nexport function slowArray(ir: SchemaIR & { type: \"array\" }, g: SlowGen): string {\n let code = emit`\n if(!Array.isArray(${g.input})){\n ${invalidType(g, \"array\")}\n ${whenGatedSizeChecks(ir.checks, g, \"length\", true)}\n }else{`;\n\n if (hasMutation(ir.element)) {\n code += `${g.output}=${g.input}.slice();`;\n }\n\n // Element validation precedes the size/refine checks: Zod parses elements\n // (the base type) first and only then runs its checks, so for an input that\n // both has an invalid element AND fails a size check, the per-element issue\n // is surfaced before too_small/too_big. Emitting the checks first would\n // reverse that order (the slow path collects all issues with no\n // short-circuit, so insertion order IS issue order).\n\n // Snapshot before the elements, for the same reason slowObject does: the\n // array's refine effects are gated on zod's abort rule over the issues THIS\n // node's parse produced. Size checks are exempt — zod's length checks declare\n // a `when` predicate, which bypasses the abort gate, so\n // `z.array(z.string()).min(2)` still reports too_small next to a bad element's\n // invalid_type.\n const hasRefine = ir.checks.some(\n (c) => c.kind === \"refine_effect\" || c.kind === \"super_refine_effect\",\n );\n const refineMark = hasRefine ? g.temp(\"rm\") : \"\";\n if (refineMark) code += `var ${refineMark}=${g.issues}.length;`;\n\n const idxVar = g.temp(\"i\");\n const elemExpr = `${g.input}[${idxVar}]`;\n const elemPath = extendPath(g.path, idxVar);\n code += emit`\n for(var ${idxVar}=0;${idxVar}<${g.input}.length;${idxVar}++){\n ${g.visit(ir.element, { input: elemExpr, output: elemExpr, path: elemPath })}\n }`;\n\n /** Wrap one refine effect in zod's abort gate, preserving declaration order. */\n const gated = (body: string): string => {\n const aborted = emitRuntimeHelper(g.ctx, \"__zcAb\", ZC_AB_DECL);\n return `if(!${aborted}(${g.issues},${refineMark})){${body}}`;\n };\n\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, \"array\", 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, \"array\", 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, \"array\", true, { exact: true, message: check.message })}\n }else if(${g.input}.length>${check.length}){\n ${tooBig(g, check.length, \"array\", true, { exact: true, message: check.message })}\n }`;\n break;\n case \"refine_effect\":\n code += gated(refineCheck(check, g.input, g));\n break;\n case \"super_refine_effect\":\n code += gated(superRefineCheck(check, g.input, g));\n break;\n }\n }\n\n code += `}`;\n return `${code}\\n`;\n}\n\nexport function fastArray(ir: ArrayIR, g: FastGen): string | null {\n const x = g.input;\n const parts: string[] = [`Array.isArray(${x})`];\n const checks = ir.checks.filter((c): c is CheckIR => c.kind !== \"refine_effect\");\n\n // Size checks\n for (const check of checks.sort(checkPriority)) {\n switch (check.kind) {\n case \"min_length\":\n parts.push(`${x}.length>=${check.minimum}`);\n break;\n case \"max_length\":\n parts.push(`${x}.length<=${check.maximum}`);\n break;\n case \"length_equals\":\n parts.push(`${x}.length===${check.length}`);\n break;\n }\n }\n\n // Element validation via preamble helper (avoids .every() closure allocation).\n // Fresh scope: the helper is its own function, size-gated independently.\n const elemVar = g.temp(\"ae\");\n const elemGen = g.scoped(elemVar);\n const elemCheck = elemGen.visit(ir.element);\n if (elemCheck === null) return null;\n if (elemCheck !== \"true\") {\n const helperName = g.temp(\"af\");\n g.ctx.preamble.push(\n `function ${helperName}(a){${declareFastTemps(elemGen.scope)}for(var ${elemVar},i=0;i<a.length;i++){${elemVar}=a[i];if(!(${elemCheck})){return false;}}return true;}`,\n );\n parts.push(`${helperName}(${x})`);\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(fastRefineTest(check, x, g));\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":"array.js","names":[],"sources":["../../../../src/core/codegen/schemas/array.ts"],"sourcesContent":["import type { ArrayIR, CheckIR, SchemaIR } from \"../../types.js\";\nimport type { FastGen, SlowGen } from \"../context.js\";\nimport {\n checkPriority,\n declareFastTemps,\n emitRuntimeHelper,\n extendPath,\n hasMutation,\n visitMember,\n} from \"../context.js\";\nimport { emit } from \"../emit.js\";\nimport { invalidType, tooBig, tooSmall } from \"../emit-issue.js\";\nimport { ZC_AB_DECL } from \"../issue-decls.js\";\nimport { fastRefineTest, refineCheck, superRefineCheck, superRefineFastTest } from \"./effect.js\";\nimport { whenGatedSizeChecks } from \"./sizeable.js\";\n\nexport function slowArray(ir: SchemaIR & { type: \"array\" }, g: SlowGen): string {\n let code = emit`\n if(!Array.isArray(${g.input})){\n ${invalidType(g, \"array\")}\n ${whenGatedSizeChecks(ir.checks, g, \"length\", true)}\n }else{`;\n\n // Every element is read and written through ONE binding, handed back as the\n // output after the loop — slowTuple's discipline, for slowTuple's reason: a\n // parent can hand this node `g.input` and `g.output` as two different\n // bindings, and copying into `g.output` while the elements went on being\n // written through `g.input` returned the ORIGINAL elements and rewrote the\n // caller's array. A mutating element schema rewrites values as a matter of\n // course, so its copy is taken up front; anything else copies only once an\n // element hands back a replacement (see visitMember).\n const arr = g.temp(\"ar\");\n code += `var ${arr}=${g.input};`;\n if (hasMutation(ir.element)) {\n code += `${arr}=${arr}.slice();`;\n }\n\n // Element validation precedes the size/refine checks: Zod parses elements\n // (the base type) first and only then runs its checks, so for an input that\n // both has an invalid element AND fails a size check, the per-element issue\n // is surfaced before too_small/too_big. Emitting the checks first would\n // reverse that order (the slow path collects all issues with no\n // short-circuit, so insertion order IS issue order).\n\n // Snapshot before the elements, for the same reason slowObject does: the\n // array's refine effects are gated on zod's abort rule over the issues THIS\n // node's parse produced. Size checks are exempt — zod's length checks declare\n // a `when` predicate, which bypasses the abort gate, so\n // `z.array(z.string()).min(2)` still reports too_small next to a bad element's\n // invalid_type.\n const hasRefine = ir.checks.some(\n (c) => c.kind === \"refine_effect\" || c.kind === \"super_refine_effect\",\n );\n const refineMark = hasRefine ? g.temp(\"rm\") : \"\";\n if (refineMark) code += `var ${refineMark}=${g.issues}.length;`;\n\n const idxVar = g.temp(\"i\");\n const element = visitMember(g, ir.element, {\n container: arr,\n original: g.input,\n copy: `${arr}.slice()`,\n key: idxVar,\n path: extendPath(g.path, idxVar),\n issues: g.issues,\n });\n code += emit`\n for(var ${idxVar}=0;${idxVar}<${arr}.length;${idxVar}++){\n ${element}\n }\n ${g.output}=${arr};`;\n\n /** Wrap one refine effect in zod's abort gate, preserving declaration order. */\n const gated = (body: string): string => {\n const aborted = emitRuntimeHelper(g.ctx, \"__zcAb\", ZC_AB_DECL);\n return `if(!${aborted}(${g.issues},${refineMark})){${body}}`;\n };\n\n // The checks run on the parsed array, as zod's run on `payload.value`: the\n // output, which carries any replacement an element handed back, and is what\n // a superRefine rewrites for the checks after it.\n const parsed = g.output;\n for (const check of ir.checks) {\n switch (check.kind) {\n case \"min_length\":\n code += emit`\n if(${parsed}.length<${check.minimum}){\n ${tooSmall(g, check.minimum, \"array\", true, { message: check.message })}\n }`;\n break;\n case \"max_length\":\n code += emit`\n if(${parsed}.length>${check.maximum}){\n ${tooBig(g, check.maximum, \"array\", true, { message: check.message })}\n }`;\n break;\n case \"length_equals\":\n code += emit`\n if(${parsed}.length<${check.length}){\n ${tooSmall(g, check.length, \"array\", true, { exact: true, message: check.message })}\n }else if(${parsed}.length>${check.length}){\n ${tooBig(g, check.length, \"array\", true, { exact: true, message: check.message })}\n }`;\n break;\n case \"refine_effect\":\n code += gated(refineCheck(check, parsed, g));\n break;\n case \"super_refine_effect\":\n code += gated(superRefineCheck(check, parsed, g));\n break;\n }\n }\n\n code += `}`;\n return `${code}\\n`;\n}\n\nexport function fastArray(ir: ArrayIR, g: FastGen): string | null {\n const x = g.input;\n const parts: string[] = [`Array.isArray(${x})`];\n const checks = ir.checks.filter((c): c is CheckIR => c.kind !== \"refine_effect\");\n\n // Size checks\n for (const check of checks.sort(checkPriority)) {\n switch (check.kind) {\n case \"min_length\":\n parts.push(`${x}.length>=${check.minimum}`);\n break;\n case \"max_length\":\n parts.push(`${x}.length<=${check.maximum}`);\n break;\n case \"length_equals\":\n parts.push(`${x}.length===${check.length}`);\n break;\n }\n }\n\n // Element validation via preamble helper (avoids .every() closure allocation).\n // Fresh scope: the helper is its own function, size-gated independently.\n const elemVar = g.temp(\"ae\");\n const elemGen = g.scoped(elemVar);\n const elemCheck = elemGen.visit(ir.element);\n if (elemCheck === null) return null;\n if (elemCheck !== \"true\") {\n const helperName = g.temp(\"af\");\n g.ctx.preamble.push(\n `function ${helperName}(a){${declareFastTemps(elemGen.scope)}for(var ${elemVar},i=0;i<a.length;i++){${elemVar}=a[i];if(!(${elemCheck})){return false;}}return true;}`,\n );\n parts.push(`${helperName}(${x})`);\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(fastRefineTest(check, x, g));\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":"discriminated-union.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/discriminated-union.ts"],"mappings":";;;;KAaK,oBAAoB;iBAET,uBACd,IAAI;EAAa;GACjB,GAAG;;;;;;;;;;;;;;;;;;iBAoEW,4BACd,GAAG,SACH,uBACA,gBAAgB,qBAChB,kBAAkB;iBAmCJ,uBAAuB,IAAI,sBAAsB,GAAG;;;;;;;;;;;;;;;;;;;;;;;iBAyDpD,yBACd,kBAAkB,YAClB;EACG;EAAuB,OAAO"}
1
+ {"version":3,"file":"discriminated-union.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/discriminated-union.ts"],"mappings":";;;;KAOK,oBAAoB;iBAET,uBACd,IAAI;EAAa;GACjB,GAAG;;;;;;;;;;;;;;;;;;iBAmEW,4BACd,GAAG,SACH,uBACA,gBAAgB,qBAChB,kBAAkB;iBAmCJ,uBAAuB,IAAI,sBAAsB,GAAG;;;;;;;;;;;;;;;;;;;;;;;iBAyDpD,yBACd,kBAAkB,YAClB;EACG;EAAuB,OAAO"}
@@ -1,4 +1,4 @@
1
- import { declareFastTemps, escapeString, extendPath, hasMutation, literalToJs } from "../context.js";
1
+ import { declareFastTemps, escapeString, extendPath, literalToJs } from "../context.js";
2
2
  import { emit } from "../emit.js";
3
3
  import { invalidType } from "../emit-issue.js";
4
4
  //#region src/core/codegen/schemas/discriminated-union.ts
@@ -26,7 +26,7 @@ function slowDiscriminatedUnion(ir, g) {
26
26
  default:
27
27
  ${g.issues}.push({code:"invalid_union",errors:[],note:"No matching discriminator",discriminator:${discKey},options:[${optionsList}],input:${g.input},path:${extendPath(g.path, discKey)}${msgProp}});
28
28
  }`;
29
- if (ir.options.some(hasMutation)) code += `${g.output}=${objVar};`;
29
+ code += `${g.output}=${objVar};`;
30
30
  code += `}`;
31
31
  return `${code}\n`;
32
32
  }
@@ -1 +1 @@
1
- {"version":3,"file":"discriminated-union.js","names":[],"sources":["../../../../src/core/codegen/schemas/discriminated-union.ts"],"sourcesContent":["import type { DiscriminatedUnionIR, LiteralValue, ObjectIR, SchemaIR } from \"../../types.js\";\nimport type { FastGen, SlowGen } from \"../context.js\";\nimport {\n declareFastTemps,\n escapeString,\n extendPath,\n hasMutation,\n literalToJs,\n} from \"../context.js\";\nimport { emit } from \"../emit.js\";\nimport { invalidType } from \"../emit-issue.js\";\n\n/** One `discriminator value → option index` dispatch entry. */\ntype DiscriminatorCase = DiscriminatedUnionIR[\"cases\"][number];\n\nexport function slowDiscriminatedUnion(\n ir: SchemaIR & { type: \"discriminatedUnion\" },\n g: SlowGen,\n): string {\n const discKey = escapeString(ir.discriminator);\n\n let code = emit`\n if(typeof ${g.input}!==\"object\"||${g.input}===null||Array.isArray(${g.input})){\n ${invalidType(g, \"object\", { codeFirst: true })}\n }else{`;\n\n const objVar = g.temp(\"du\");\n code += `var ${objVar}=${g.input};switch(${objVar}[${discKey}]){`;\n\n for (const { value, option: index } of ir.cases) {\n const option = ir.options[index] as SchemaIR;\n code += emit`\n case ${literalToJs(value)}:\n ${g.visit(option, { input: objVar, output: objVar })}\n break;`;\n }\n\n const msgProp = g.typeMsg === undefined ? \"\" : `,message:${JSON.stringify(g.typeMsg)}`;\n // Field for field what $ZodDiscriminatedUnion pushes when no option matches:\n // `{ code, errors: [], note: \"No matching discriminator\", discriminator,\n // options, input, path: [def.discriminator] }`. `options` is\n // `Array.from(disc.value.keys())` — every dispatch value in map insertion\n // order, which is exactly `ir.cases` (option order, then each option's\n // `propValues[discriminator]` in ITS order, so an omittable discriminator\n // lists `undefined` right after its own values). The locale reads the list\n // for \"Invalid discriminator value. Expected 'a' | 'b'\". A fresh array\n // literal per push, as zod allocates one, so consumers never share or\n // mutate a hoisted table.\n const optionsList = ir.cases.map(({ value }) => literalToJs(value)).join(\",\");\n code += emit`\n default:\n ${g.issues}.push({code:\"invalid_union\",errors:[],note:\"No matching discriminator\",discriminator:${discKey},options:[${optionsList}],input:${g.input},path:${extendPath(g.path, discKey)}${msgProp}});\n }`;\n // Propagate option-applied mutations (defaults, coercions, transforms,\n // overwrite checks, stringbool) back to the output location. Each option is\n // visited with output:objVar — a fresh local — so a mutating option's clone is\n // reassigned into objVar and stranded there; without this write-back the caller\n // returns the ORIGINAL input by reference and the mutation is silently lost.\n // Gated on mutation so a pure-validation union stays a zero-write pass-through\n // (objVar still aliases the input). On the no-match/failure paths objVar equals\n // the input, so the write is a harmless self-assignment.\n if (ir.options.some(hasMutation)) {\n code += `${g.output}=${objVar};`;\n }\n code += `}`;\n return `${code}\\n`;\n}\n\n/**\n * Emit an O(1) switch-dispatch fast-check for a discriminated union — real\n * (`z.discriminatedUnion`) or one detected inside a plain `z.union`\n * (see {@link detectUnionDiscriminator}). Both share this so the detected case\n * inherits the size-gating and the per-case guard strip.\n *\n * `discSkipKey` tells each object option to drop its own type-guard and\n * discriminator re-check: the caller's guard (`typeof x===\"object\"&&…` below)\n * already proved object-ness, and the matched switch case has fixed the\n * discriminator value, so re-emitting either is pure redundancy the optimizer\n * only removes when it inlines this helper — which a union large enough to\n * matter won't. Routed through the normal `visit` so size-gated extraction\n * still bounds the switch (the strip survives into any hoisted helper);\n * non-object options ignore the hint and keep their own guard.\n *\n * Returns null if any option is fast-path-ineligible.\n */\nexport function emitFastDiscriminatedSwitch(\n g: FastGen,\n discriminator: string,\n cases: readonly DiscriminatorCase[],\n options: readonly SchemaIR[],\n): string | null {\n const x = g.input;\n const discKey = escapeString(discriminator);\n const helperName = g.temp(\"du\");\n const helperParam = g.temp(\"dx\");\n\n // The switch body is its own function: size-gate the options against the cap\n // in a fresh scope, otherwise many small options accumulate into the caller's\n // scope while this helper itself grows unbounded past the TurboFan budget.\n //\n // The switch is over the literal labels themselves. An earlier revision\n // routed three or more string labels through a `{value: ordinal}` table and\n // switched on the ordinal, on the theory that V8 compiles a string switch as\n // sequential comparisons and a dense integer one as a jump table. Measured on\n // V8 13.x (node 24) that is a loss at every size: the table's `t[v]` is a\n // keyed load whose key varies per parse, which goes megamorphic the moment\n // the input rotates through the options, while the string switch stays\n // nearly flat (8.6 ns at 8 cases, 10.1 at 32, 17.3 at 80, rotating input)\n // against the table's 12.9, 15.8 and 20.8. The build path switches the same\n // way (see `buildDispatch`).\n const body = g.scoped(helperParam);\n const caseStrs: string[] = [];\n for (const { value, option: index } of cases) {\n const check = body.visit(options[index] as SchemaIR, { discSkipKey: discriminator });\n if (check === null) return null;\n caseStrs.push(`case ${literalToJs(value)}:return ${check};`);\n }\n g.ctx.preamble.push(\n `function ${helperName}(${helperParam}){${declareFastTemps(body.scope)}switch(${helperParam}[${discKey}]){${caseStrs.join(\"\")}default:return false;}}`,\n );\n\n return `typeof ${x}===\"object\"&&${x}!==null&&!Array.isArray(${x})&&${helperName}(${x})`;\n}\n\nexport function fastDiscriminatedUnion(ir: DiscriminatedUnionIR, g: FastGen): string | null {\n return emitFastDiscriminatedSwitch(g, ir.discriminator, ir.cases, ir.options);\n}\n\n/**\n * Minimum option count for rewriting a plain `z.union` to switch dispatch. Below\n * this the switch helper's fixed call overhead loses to a fully-inlined\n * `||`-chain that V8 keeps flat: measured crossover is ~4 options (n=3 can hit\n * 0.6x — a regression — while n=5 is 1.27x and n=32 is 2.34x), so 5 captures the\n * stable wins with margin and never regresses a small union. Real\n * `z.discriminatedUnion` is unaffected — it dispatches via switch by construction\n * regardless of size.\n */\nconst MIN_AUTO_DISCRIMINATE_OPTIONS = 5;\n\n/**\n * Values that switch correctly under `===` (excludes `undefined` and `NaN`).\n *\n * Also excludes a SYMBOL — now that `LiteralIR.values` admits one, this is the\n * guard that keeps it out. A symbol discriminant has no source form, so there\n * is no `case` label to emit for it (`literalToJs` refuses it by type), and the\n * ordinal-table variant is string-keyed besides. Detection bails to the\n * `||`-chain, where the option's own literal check does the right thing by\n * reading the value list off the retained schema.\n */\nfunction isSwitchableDiscriminant(v: LiteralValue): v is string | number | boolean | bigint | null {\n return (\n v === null ||\n typeof v === \"string\" ||\n typeof v === \"boolean\" ||\n typeof v === \"bigint\" ||\n (typeof v === \"number\" && !Number.isNaN(v))\n );\n}\n\n/**\n * Detect whether a plain (untagged) `z.union` is *structurally* a discriminated\n * union, so its fast path can use O(1) switch dispatch instead of probing every\n * arm. Returns the discriminator + dispatch table, or null to keep the\n * `||`-chain.\n *\n * Requires (proving the switch accepts exactly what the `||`-chain would): every\n * option is a plain object that pins one shared key to a REQUIRED literal\n * (`prop.type === \"literal\"` — an optional/non-literal key is rejected), and the\n * literal values are pairwise DISJOINT across options. Disjointness is the crux:\n * it guarantees at most one option can accept any given input, so dispatching to\n * that single option is equivalent to trying them all. Any value shared by two\n * options (ambiguous), a non-switchable value (`undefined`/`NaN`), or a\n * non-object option makes detection bail to the safe `||`-chain.\n *\n * The slow path is untouched: it keeps `z.union`'s sequential trial and its\n * `invalid_union` error shape, so failure output stays byte-identical to Zod.\n * The fast path and the build path both dispatch on the result — the build\n * path from two options up (`minOptions`), since its options are hosted calls\n * either way and a switch only ever replaces probes with one call; see\n * `buildUnion`.\n */\nexport function detectUnionDiscriminator(\n options: readonly SchemaIR[],\n minOptions: number = MIN_AUTO_DISCRIMINATE_OPTIONS,\n): { discriminator: string; cases: DiscriminatorCase[] } | null {\n if (options.length < minOptions) return null;\n const objects: ObjectIR[] = [];\n for (const option of options) {\n if (option.type !== \"object\") return null;\n objects.push(option);\n }\n const first = objects[0];\n if (first === undefined) return null; // unreachable (length checked above)\n\n // Only keys present in the first option can be shared by all; try each.\n candidate: for (const key of Object.keys(first.properties)) {\n const seen = new Set<string | number | boolean | bigint | null>();\n const cases: DiscriminatorCase[] = [];\n for (const [i, object] of objects.entries()) {\n const prop = object.properties[key];\n if (prop === undefined || prop.type !== \"literal\") continue candidate;\n for (const value of prop.values) {\n if (!isSwitchableDiscriminant(value)) continue candidate;\n if (seen.has(value)) continue candidate; // shared value → ambiguous dispatch\n seen.add(value);\n cases.push({ value, option: i });\n }\n }\n return { discriminator: key, cases };\n }\n return null;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"discriminated-union.js","names":[],"sources":["../../../../src/core/codegen/schemas/discriminated-union.ts"],"sourcesContent":["import type { DiscriminatedUnionIR, LiteralValue, ObjectIR, SchemaIR } from \"../../types.js\";\nimport type { FastGen, SlowGen } from \"../context.js\";\nimport { declareFastTemps, escapeString, extendPath, literalToJs } from \"../context.js\";\nimport { emit } from \"../emit.js\";\nimport { invalidType } from \"../emit-issue.js\";\n\n/** One `discriminator value → option index` dispatch entry. */\ntype DiscriminatorCase = DiscriminatedUnionIR[\"cases\"][number];\n\nexport function slowDiscriminatedUnion(\n ir: SchemaIR & { type: \"discriminatedUnion\" },\n g: SlowGen,\n): string {\n const discKey = escapeString(ir.discriminator);\n\n let code = emit`\n if(typeof ${g.input}!==\"object\"||${g.input}===null||Array.isArray(${g.input})){\n ${invalidType(g, \"object\", { codeFirst: true })}\n }else{`;\n\n const objVar = g.temp(\"du\");\n code += `var ${objVar}=${g.input};switch(${objVar}[${discKey}]){`;\n\n for (const { value, option: index } of ir.cases) {\n const option = ir.options[index] as SchemaIR;\n code += emit`\n case ${literalToJs(value)}:\n ${g.visit(option, { input: objVar, output: objVar })}\n break;`;\n }\n\n const msgProp = g.typeMsg === undefined ? \"\" : `,message:${JSON.stringify(g.typeMsg)}`;\n // Field for field what $ZodDiscriminatedUnion pushes when no option matches:\n // `{ code, errors: [], note: \"No matching discriminator\", discriminator,\n // options, input, path: [def.discriminator] }`. `options` is\n // `Array.from(disc.value.keys())` — every dispatch value in map insertion\n // order, which is exactly `ir.cases` (option order, then each option's\n // `propValues[discriminator]` in ITS order, so an omittable discriminator\n // lists `undefined` right after its own values). The locale reads the list\n // for \"Invalid discriminator value. Expected 'a' | 'b'\". A fresh array\n // literal per push, as zod allocates one, so consumers never share or\n // mutate a hoisted table.\n const optionsList = ir.cases.map(({ value }) => literalToJs(value)).join(\",\");\n code += emit`\n default:\n ${g.issues}.push({code:\"invalid_union\",errors:[],note:\"No matching discriminator\",discriminator:${discKey},options:[${optionsList}],input:${g.input},path:${extendPath(g.path, discKey)}${msgProp}});\n }`;\n // Propagate what the matched option produced back to the output location.\n // Each option is visited with output:objVar — a fresh local — so its result is\n // reassigned into objVar and stranded there; without this write-back the\n // caller gets the ORIGINAL input by reference and the result is silently lost.\n // That is as true of an option that rewrites nothing as of a mutating one\n // (defaults, coercions, transforms, overwrite checks, stringbool): it can\n // still hand back a replacement — a `__proto__`-scrubbed copy, or a copy a\n // nested replacement landed on — so the write is not gated on mutation. Where\n // nothing was replaced, and on the no-match path, objVar is still the input.\n code += `${g.output}=${objVar};`;\n code += `}`;\n return `${code}\\n`;\n}\n\n/**\n * Emit an O(1) switch-dispatch fast-check for a discriminated union — real\n * (`z.discriminatedUnion`) or one detected inside a plain `z.union`\n * (see {@link detectUnionDiscriminator}). Both share this so the detected case\n * inherits the size-gating and the per-case guard strip.\n *\n * `discSkipKey` tells each object option to drop its own type-guard and\n * discriminator re-check: the caller's guard (`typeof x===\"object\"&&…` below)\n * already proved object-ness, and the matched switch case has fixed the\n * discriminator value, so re-emitting either is pure redundancy the optimizer\n * only removes when it inlines this helper — which a union large enough to\n * matter won't. Routed through the normal `visit` so size-gated extraction\n * still bounds the switch (the strip survives into any hoisted helper);\n * non-object options ignore the hint and keep their own guard.\n *\n * Returns null if any option is fast-path-ineligible.\n */\nexport function emitFastDiscriminatedSwitch(\n g: FastGen,\n discriminator: string,\n cases: readonly DiscriminatorCase[],\n options: readonly SchemaIR[],\n): string | null {\n const x = g.input;\n const discKey = escapeString(discriminator);\n const helperName = g.temp(\"du\");\n const helperParam = g.temp(\"dx\");\n\n // The switch body is its own function: size-gate the options against the cap\n // in a fresh scope, otherwise many small options accumulate into the caller's\n // scope while this helper itself grows unbounded past the TurboFan budget.\n //\n // The switch is over the literal labels themselves. An earlier revision\n // routed three or more string labels through a `{value: ordinal}` table and\n // switched on the ordinal, on the theory that V8 compiles a string switch as\n // sequential comparisons and a dense integer one as a jump table. Measured on\n // V8 13.x (node 24) that is a loss at every size: the table's `t[v]` is a\n // keyed load whose key varies per parse, which goes megamorphic the moment\n // the input rotates through the options, while the string switch stays\n // nearly flat (8.6 ns at 8 cases, 10.1 at 32, 17.3 at 80, rotating input)\n // against the table's 12.9, 15.8 and 20.8. The build path switches the same\n // way (see `buildDispatch`).\n const body = g.scoped(helperParam);\n const caseStrs: string[] = [];\n for (const { value, option: index } of cases) {\n const check = body.visit(options[index] as SchemaIR, { discSkipKey: discriminator });\n if (check === null) return null;\n caseStrs.push(`case ${literalToJs(value)}:return ${check};`);\n }\n g.ctx.preamble.push(\n `function ${helperName}(${helperParam}){${declareFastTemps(body.scope)}switch(${helperParam}[${discKey}]){${caseStrs.join(\"\")}default:return false;}}`,\n );\n\n return `typeof ${x}===\"object\"&&${x}!==null&&!Array.isArray(${x})&&${helperName}(${x})`;\n}\n\nexport function fastDiscriminatedUnion(ir: DiscriminatedUnionIR, g: FastGen): string | null {\n return emitFastDiscriminatedSwitch(g, ir.discriminator, ir.cases, ir.options);\n}\n\n/**\n * Minimum option count for rewriting a plain `z.union` to switch dispatch. Below\n * this the switch helper's fixed call overhead loses to a fully-inlined\n * `||`-chain that V8 keeps flat: measured crossover is ~4 options (n=3 can hit\n * 0.6x — a regression — while n=5 is 1.27x and n=32 is 2.34x), so 5 captures the\n * stable wins with margin and never regresses a small union. Real\n * `z.discriminatedUnion` is unaffected — it dispatches via switch by construction\n * regardless of size.\n */\nconst MIN_AUTO_DISCRIMINATE_OPTIONS = 5;\n\n/**\n * Values that switch correctly under `===` (excludes `undefined` and `NaN`).\n *\n * Also excludes a SYMBOL — now that `LiteralIR.values` admits one, this is the\n * guard that keeps it out. A symbol discriminant has no source form, so there\n * is no `case` label to emit for it (`literalToJs` refuses it by type), and the\n * ordinal-table variant is string-keyed besides. Detection bails to the\n * `||`-chain, where the option's own literal check does the right thing by\n * reading the value list off the retained schema.\n */\nfunction isSwitchableDiscriminant(v: LiteralValue): v is string | number | boolean | bigint | null {\n return (\n v === null ||\n typeof v === \"string\" ||\n typeof v === \"boolean\" ||\n typeof v === \"bigint\" ||\n (typeof v === \"number\" && !Number.isNaN(v))\n );\n}\n\n/**\n * Detect whether a plain (untagged) `z.union` is *structurally* a discriminated\n * union, so its fast path can use O(1) switch dispatch instead of probing every\n * arm. Returns the discriminator + dispatch table, or null to keep the\n * `||`-chain.\n *\n * Requires (proving the switch accepts exactly what the `||`-chain would): every\n * option is a plain object that pins one shared key to a REQUIRED literal\n * (`prop.type === \"literal\"` — an optional/non-literal key is rejected), and the\n * literal values are pairwise DISJOINT across options. Disjointness is the crux:\n * it guarantees at most one option can accept any given input, so dispatching to\n * that single option is equivalent to trying them all. Any value shared by two\n * options (ambiguous), a non-switchable value (`undefined`/`NaN`), or a\n * non-object option makes detection bail to the safe `||`-chain.\n *\n * The slow path is untouched: it keeps `z.union`'s sequential trial and its\n * `invalid_union` error shape, so failure output stays byte-identical to Zod.\n * The fast path and the build path both dispatch on the result — the build\n * path from two options up (`minOptions`), since its options are hosted calls\n * either way and a switch only ever replaces probes with one call; see\n * `buildUnion`.\n */\nexport function detectUnionDiscriminator(\n options: readonly SchemaIR[],\n minOptions: number = MIN_AUTO_DISCRIMINATE_OPTIONS,\n): { discriminator: string; cases: DiscriminatorCase[] } | null {\n if (options.length < minOptions) return null;\n const objects: ObjectIR[] = [];\n for (const option of options) {\n if (option.type !== \"object\") return null;\n objects.push(option);\n }\n const first = objects[0];\n if (first === undefined) return null; // unreachable (length checked above)\n\n // Only keys present in the first option can be shared by all; try each.\n candidate: for (const key of Object.keys(first.properties)) {\n const seen = new Set<string | number | boolean | bigint | null>();\n const cases: DiscriminatorCase[] = [];\n for (const [i, object] of objects.entries()) {\n const prop = object.properties[key];\n if (prop === undefined || prop.type !== \"literal\") continue candidate;\n for (const value of prop.values) {\n if (!isSwitchableDiscriminant(value)) continue candidate;\n if (seen.has(value)) continue candidate; // shared value → ambiguous dispatch\n seen.add(value);\n cases.push({ value, option: i });\n }\n }\n return { discriminator: key, cases };\n }\n return null;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"effect.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/effect.ts"],"mappings":";;;;;;;iBAqBgB,WAAW,IAAI,oBAAoB,oBAAoB,GAAG;;;;;;;;;iBAyC1D,YAAY,OAAO,qBAAqB,cAAc,GAAG;;;;;;;;;;;;iBAkDzD,eAAe,OAAO,qBAAqB,cAAc,GAAG;;;;;iBAqB5D,oBACd,OAAO,0BACP,cACA,GAAG;;;;;;;;;;;;;iBAmBW,iBACd,OAAO,0BACP,cACA,GAAG"}
1
+ {"version":3,"file":"effect.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/effect.ts"],"mappings":";;;;;;;iBAqBgB,WAAW,IAAI,oBAAoB,oBAAoB,GAAG;;;;;;;;;iBAgD1D,YAAY,OAAO,qBAAqB,cAAc,GAAG;;;;;;;;;;;;iBAkDzD,eAAe,OAAO,qBAAqB,cAAc,GAAG;;;;;iBAqB5D,oBACd,OAAO,0BACP,cACA,GAAG;;;;;;;;;;;;;iBAmBW,iBACd,OAAO,0BACP,cACA,GAAG"}
@@ -15,7 +15,7 @@ function slowEffect(ir, g) {
15
15
  if(${valueVar} instanceof Promise)${asy}();
16
16
  ${g.output}=${valueVar};
17
17
  ${g.visit(ir.inner, {
18
- input: valueVar,
18
+ input: g.output,
19
19
  output: g.output,
20
20
  aborted: g.aborted
21
21
  })}
@@ -1 +1 @@
1
- {"version":3,"file":"effect.js","names":[],"sources":["../../../../src/core/codegen/schemas/effect.ts"],"sourcesContent":["import type {\n PreprocessEffectIR,\n RefineEffectCheckIR,\n SuperRefineEffectCheckIR,\n TransformEffectIR,\n} from \"../../types.js\";\nimport type { FastGen, SlowGen } from \"../context.js\";\nimport {\n emitConstant,\n emitEffectCallable,\n emitRuntimeHelper,\n extendStaticPath,\n extendStaticPathIndex,\n} from \"../context.js\";\nimport { emit } from \"../emit.js\";\nimport { ZC_ASYNC_DECL, ZC_SR_DECL, ZC_SR_OK_DECL, ZC_SR_RUN_DECL } from \"../issue-decls.js\";\n\n/**\n * Generate code for a TransformEffectIR node.\n * Validates the inner schema, then applies the transform function and writes back the result.\n */\nexport function slowEffect(ir: TransformEffectIR | PreprocessEffectIR, g: SlowGen): string {\n // A callback that returns a Promise is zod's synchronous-parse error, not a\n // value (see ZC_ASYNC_DECL); zod tests `_out instanceof Promise` right after\n // the call, so the guard sits at the same point.\n const asy = emitRuntimeHelper(g.ctx, \"__zcAsy\", ZC_ASYNC_DECL);\n if (ir.effectKind === \"preprocess\") {\n const valueVar = g.temp(\"pv\");\n return `${emit`\n var ${valueVar}=${emitEffectCallable(g.ctx, ir)}(${g.input});\n if(${valueVar} instanceof Promise)${asy}();\n ${g.output}=${valueVar};\n ${g.visit(ir.inner, { input: valueVar, output: g.output, aborted: g.aborted })}\n `}\\n`;\n }\n\n const beforeVar = g.temp(\"ib\");\n const innerCode = g.visit(ir.inner);\n // A transform is `inner.transform(fn)` = a pipe(inner, transform): zod's\n // handlePipeResult aborts when `inner` produces any issue. Inside a union the\n // option must therefore count as aborted even if `inner`'s only issue is a\n // non-aborting `custom`/check-level code (mirrors slowPipe's abort branch).\n const abortBranch = g.aborted ? `else{${g.aborted}=true;}` : \"\";\n\n return `${emit`\n var ${beforeVar}=${g.issues}.length;\n ${innerCode}\n if(${g.issues}.length===${beforeVar}){\n ${g.output}=${emitEffectCallable(g.ctx, ir)}(${g.output});\n if(${g.output} instanceof Promise)${asy}();\n }${abortBranch}\n `}\\n`;\n}\n\n/**\n * Generate code for a RefineEffectCheckIR (inline refine function call).\n * Called from string/number/object check loops when a refine_effect is encountered.\n *\n * @param check - The refine effect check IR\n * @param expr - The expression to validate (may differ from g.input, e.g. objVar in object generators)\n * @param g - SlowGen context (provides path, issues)\n */\nexport function refineCheck(check: RefineEffectCheckIR, expr: string, g: SlowGen): string {\n // The refine's own message wins; without one, zod's finalizeIssue falls\n // through to the error map of the schema the check is attached to (the check\n // stamps its owner onto the issue), so the node's schema-level message is the\n // next rung. With neither, __zcFin applies the locale default.\n const message = check.message ?? g.typeMsg;\n const messageProp = message === undefined ? \"\" : `,message:${JSON.stringify(message)}`;\n // `.refine(fn, { path })` reports against a member of the refined value, so\n // the configured segments extend this node's path.\n const path = (check.path ?? []).reduce<string>(\n (acc, segment) =>\n typeof segment === \"number\"\n ? extendStaticPathIndex(acc, segment)\n : extendStaticPath(acc, segment),\n g.path,\n );\n // `.refine(fn, { params })`: $ZodCustom's check does\n // `if (def.params) _iss.params = def.params`, so the key is present only when\n // the schema declared one, and holds the ORIGINAL object — the extractor\n // parked it in `__rf` precisely so the reference survives (see\n // RefineEffectCheckIR.paramsRefIndex). Placed before `message` to keep the\n // key order zod produces.\n const paramsProp =\n check.paramsRefIndex === undefined\n ? \"\"\n : `,params:${emitConstant(g.ctx, \"rpa\", `__rf[${check.paramsRefIndex}]`)}`;\n // The predicate's return value is tested for a Promise before its truthiness\n // (see ZC_ASYNC_DECL): a Promise is truthy, so without the guard a plain\n // function returning one would ACCEPT every input where zod throws.\n const asy = emitRuntimeHelper(g.ctx, \"__zcAsy\", ZC_ASYNC_DECL);\n const result = g.temp(\"rr\");\n return emit`\n var ${result}=${emitEffectCallable(g.ctx, check)}(${expr});\n if(${result} instanceof Promise)${asy}();\n if(!${result}){\n ${g.issues}.push({code:\"custom\",path:${path}${paramsProp},input:${expr}${messageProp}});\n }`;\n}\n\n/**\n * Fast-path test for a RefineEffectCheckIR: the predicate's verdict, bound to a\n * local so a Promise can be told apart from a truthy value. A Promise throws\n * zod's `$ZodAsyncError` right here, EAGERLY: zod's `runChecks` throws the\n * moment a check returns a Promise under a synchronous parse — before any later\n * check, and even inside a union whose next option would have matched — so\n * deferring the throw to the slow walk (which a fast-eligible schema only runs\n * when `.error` is read) would turn zod's throw into a failure result. The\n * throw itself lives in a hosted helper, so the hot expression only pays the\n * `instanceof` test.\n */\nexport function fastRefineTest(check: RefineEffectCheckIR, expr: string, g: FastGen): string {\n const asy = emitRuntimeHelper(g.ctx, \"__zcAsy\", ZC_ASYNC_DECL);\n const verdict = g.local(\"fr\");\n return `((${verdict}=${emitEffectCallable(g.ctx, check)}(${expr})) instanceof Promise?${asy}():${verdict})`;\n}\n\n/**\n * Declare the shared invoker both superRefine helpers call. It is module-local\n * rather than an imported helper (lean mode declares it in the runtime module\n * beside them), so inline mode must place it in the preamble itself.\n */\nfunction emitSuperRefineRunner(ctx: SlowGen[\"ctx\"]): void {\n if (ctx.mode !== \"lean\" && !ctx.preamble.includes(ZC_SR_RUN_DECL)) {\n ctx.preamble.push(ZC_SR_RUN_DECL);\n }\n}\n\n/**\n * Fast-path test for a SuperRefineEffectCheckIR: a boolean term reporting that\n * the callback added no issue and left the value alone (see ZC_SR_OK_DECL).\n */\nexport function superRefineFastTest(\n check: SuperRefineEffectCheckIR,\n expr: string,\n g: FastGen,\n): string {\n emitSuperRefineRunner(g.ctx);\n const ok = emitRuntimeHelper(g.ctx, \"__zcSrOk\", ZC_SR_OK_DECL);\n return `${ok}(${emitEffectCallable(g.ctx, check)},${expr})`;\n}\n\n/**\n * Generate code for a SuperRefineEffectCheckIR: call the payload-taking\n * callback with a synthesized `{ value, issues }` and merge what it collected.\n * zod's own wrapper installs `addIssue` and normalizes the result, so the\n * issues are zod's — the helper only reprojects them onto this node's path and\n * strips the internal bookkeeping zod deletes before they become visible.\n *\n * The payload's `value` is writable public API, so it is written back to the\n * output slot the way an overwrite effect is. This walk is always the eager one\n * (the node reports as mutating, see hasMutation), so the write-back lands on\n * every parse that reaches it — not only on failures.\n */\nexport function superRefineCheck(\n check: SuperRefineEffectCheckIR,\n expr: string,\n g: SlowGen,\n): string {\n emitSuperRefineRunner(g.ctx);\n const helper = emitRuntimeHelper(g.ctx, \"__zcSr\", ZC_SR_DECL);\n const fn = emitEffectCallable(g.ctx, check);\n const p = g.temp(\"sp\");\n // The owning schema's message covers an added issue that carries none of its\n // own, as it does for a refine (see refineCheck); the helper applies it.\n const msgArg = g.typeMsg === undefined ? \"\" : `,${JSON.stringify(g.typeMsg)}`;\n let code = `var ${p}=${helper}(${fn},${expr},${g.path},${g.issues}${msgArg});${g.output}=${p}.value;`;\n // Inside a union option, an aborting issue must mark the option aborted so\n // pruning matches zod (see ZC_SR_DECL); elsewhere the flag is unobserved.\n if (g.aborted) code += `if(${p}.aborted){${g.aborted}=true;}`;\n return code;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"effect.js","names":[],"sources":["../../../../src/core/codegen/schemas/effect.ts"],"sourcesContent":["import type {\n PreprocessEffectIR,\n RefineEffectCheckIR,\n SuperRefineEffectCheckIR,\n TransformEffectIR,\n} from \"../../types.js\";\nimport type { FastGen, SlowGen } from \"../context.js\";\nimport {\n emitConstant,\n emitEffectCallable,\n emitRuntimeHelper,\n extendStaticPath,\n extendStaticPathIndex,\n} from \"../context.js\";\nimport { emit } from \"../emit.js\";\nimport { ZC_ASYNC_DECL, ZC_SR_DECL, ZC_SR_OK_DECL, ZC_SR_RUN_DECL } from \"../issue-decls.js\";\n\n/**\n * Generate code for a TransformEffectIR node.\n * Validates the inner schema, then applies the transform function and writes back the result.\n */\nexport function slowEffect(ir: TransformEffectIR | PreprocessEffectIR, g: SlowGen): string {\n // A callback that returns a Promise is zod's synchronous-parse error, not a\n // value (see ZC_ASYNC_DECL); zod tests `_out instanceof Promise` right after\n // the call, so the guard sits at the same point.\n const asy = emitRuntimeHelper(g.ctx, \"__zcAsy\", ZC_ASYNC_DECL);\n if (ir.effectKind === \"preprocess\") {\n const valueVar = g.temp(\"pv\");\n // The callback's result is written to the output first, and the inner\n // schema then reads and writes it through that ONE expression, as a root\n // does. A rewriting inner reads back what it wrote — `.trim().min(1)`\n // measures the trimmed string, a coercion checks the converted value — so\n // handing it the result as its input and the output as its output made those\n // checks see the value from before the rewrite: nested in a walk that runs\n // eagerly, `z.preprocess(fn, z.string().trim().min(1))` accepted `\" \"`.\n return `${emit`\n var ${valueVar}=${emitEffectCallable(g.ctx, ir)}(${g.input});\n if(${valueVar} instanceof Promise)${asy}();\n ${g.output}=${valueVar};\n ${g.visit(ir.inner, { input: g.output, output: g.output, aborted: g.aborted })}\n `}\\n`;\n }\n\n const beforeVar = g.temp(\"ib\");\n const innerCode = g.visit(ir.inner);\n // A transform is `inner.transform(fn)` = a pipe(inner, transform): zod's\n // handlePipeResult aborts when `inner` produces any issue. Inside a union the\n // option must therefore count as aborted even if `inner`'s only issue is a\n // non-aborting `custom`/check-level code (mirrors slowPipe's abort branch).\n const abortBranch = g.aborted ? `else{${g.aborted}=true;}` : \"\";\n\n return `${emit`\n var ${beforeVar}=${g.issues}.length;\n ${innerCode}\n if(${g.issues}.length===${beforeVar}){\n ${g.output}=${emitEffectCallable(g.ctx, ir)}(${g.output});\n if(${g.output} instanceof Promise)${asy}();\n }${abortBranch}\n `}\\n`;\n}\n\n/**\n * Generate code for a RefineEffectCheckIR (inline refine function call).\n * Called from string/number/object check loops when a refine_effect is encountered.\n *\n * @param check - The refine effect check IR\n * @param expr - The expression to validate (may differ from g.input, e.g. objVar in object generators)\n * @param g - SlowGen context (provides path, issues)\n */\nexport function refineCheck(check: RefineEffectCheckIR, expr: string, g: SlowGen): string {\n // The refine's own message wins; without one, zod's finalizeIssue falls\n // through to the error map of the schema the check is attached to (the check\n // stamps its owner onto the issue), so the node's schema-level message is the\n // next rung. With neither, __zcFin applies the locale default.\n const message = check.message ?? g.typeMsg;\n const messageProp = message === undefined ? \"\" : `,message:${JSON.stringify(message)}`;\n // `.refine(fn, { path })` reports against a member of the refined value, so\n // the configured segments extend this node's path.\n const path = (check.path ?? []).reduce<string>(\n (acc, segment) =>\n typeof segment === \"number\"\n ? extendStaticPathIndex(acc, segment)\n : extendStaticPath(acc, segment),\n g.path,\n );\n // `.refine(fn, { params })`: $ZodCustom's check does\n // `if (def.params) _iss.params = def.params`, so the key is present only when\n // the schema declared one, and holds the ORIGINAL object — the extractor\n // parked it in `__rf` precisely so the reference survives (see\n // RefineEffectCheckIR.paramsRefIndex). Placed before `message` to keep the\n // key order zod produces.\n const paramsProp =\n check.paramsRefIndex === undefined\n ? \"\"\n : `,params:${emitConstant(g.ctx, \"rpa\", `__rf[${check.paramsRefIndex}]`)}`;\n // The predicate's return value is tested for a Promise before its truthiness\n // (see ZC_ASYNC_DECL): a Promise is truthy, so without the guard a plain\n // function returning one would ACCEPT every input where zod throws.\n const asy = emitRuntimeHelper(g.ctx, \"__zcAsy\", ZC_ASYNC_DECL);\n const result = g.temp(\"rr\");\n return emit`\n var ${result}=${emitEffectCallable(g.ctx, check)}(${expr});\n if(${result} instanceof Promise)${asy}();\n if(!${result}){\n ${g.issues}.push({code:\"custom\",path:${path}${paramsProp},input:${expr}${messageProp}});\n }`;\n}\n\n/**\n * Fast-path test for a RefineEffectCheckIR: the predicate's verdict, bound to a\n * local so a Promise can be told apart from a truthy value. A Promise throws\n * zod's `$ZodAsyncError` right here, EAGERLY: zod's `runChecks` throws the\n * moment a check returns a Promise under a synchronous parse — before any later\n * check, and even inside a union whose next option would have matched — so\n * deferring the throw to the slow walk (which a fast-eligible schema only runs\n * when `.error` is read) would turn zod's throw into a failure result. The\n * throw itself lives in a hosted helper, so the hot expression only pays the\n * `instanceof` test.\n */\nexport function fastRefineTest(check: RefineEffectCheckIR, expr: string, g: FastGen): string {\n const asy = emitRuntimeHelper(g.ctx, \"__zcAsy\", ZC_ASYNC_DECL);\n const verdict = g.local(\"fr\");\n return `((${verdict}=${emitEffectCallable(g.ctx, check)}(${expr})) instanceof Promise?${asy}():${verdict})`;\n}\n\n/**\n * Declare the shared invoker both superRefine helpers call. It is module-local\n * rather than an imported helper (lean mode declares it in the runtime module\n * beside them), so inline mode must place it in the preamble itself.\n */\nfunction emitSuperRefineRunner(ctx: SlowGen[\"ctx\"]): void {\n if (ctx.mode !== \"lean\" && !ctx.preamble.includes(ZC_SR_RUN_DECL)) {\n ctx.preamble.push(ZC_SR_RUN_DECL);\n }\n}\n\n/**\n * Fast-path test for a SuperRefineEffectCheckIR: a boolean term reporting that\n * the callback added no issue and left the value alone (see ZC_SR_OK_DECL).\n */\nexport function superRefineFastTest(\n check: SuperRefineEffectCheckIR,\n expr: string,\n g: FastGen,\n): string {\n emitSuperRefineRunner(g.ctx);\n const ok = emitRuntimeHelper(g.ctx, \"__zcSrOk\", ZC_SR_OK_DECL);\n return `${ok}(${emitEffectCallable(g.ctx, check)},${expr})`;\n}\n\n/**\n * Generate code for a SuperRefineEffectCheckIR: call the payload-taking\n * callback with a synthesized `{ value, issues }` and merge what it collected.\n * zod's own wrapper installs `addIssue` and normalizes the result, so the\n * issues are zod's — the helper only reprojects them onto this node's path and\n * strips the internal bookkeeping zod deletes before they become visible.\n *\n * The payload's `value` is writable public API, so it is written back to the\n * output slot the way an overwrite effect is. This walk is always the eager one\n * (the node reports as mutating, see hasMutation), so the write-back lands on\n * every parse that reaches it — not only on failures.\n */\nexport function superRefineCheck(\n check: SuperRefineEffectCheckIR,\n expr: string,\n g: SlowGen,\n): string {\n emitSuperRefineRunner(g.ctx);\n const helper = emitRuntimeHelper(g.ctx, \"__zcSr\", ZC_SR_DECL);\n const fn = emitEffectCallable(g.ctx, check);\n const p = g.temp(\"sp\");\n // The owning schema's message covers an added issue that carries none of its\n // own, as it does for a refine (see refineCheck); the helper applies it.\n const msgArg = g.typeMsg === undefined ? \"\" : `,${JSON.stringify(g.typeMsg)}`;\n let code = `var ${p}=${helper}(${fn},${expr},${g.path},${g.issues}${msgArg});${g.output}=${p}.value;`;\n // Inside a union option, an aborting issue must mark the option aborted so\n // pruning matches zod (see ZC_SR_DECL); elsewhere the flag is unobserved.\n if (g.aborted) code += `if(${p}.aborted){${g.aborted}=true;}`;\n return code;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"map.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/map.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;iBAwBgB,QAAQ,IAAI;EAAa;GAAe,GAAG;iBA+D3C,QAAQ,IAAI,OAAO,GAAG"}
1
+ {"version":3,"file":"map.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/map.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;iBAwBgB,QAAQ,IAAI;EAAa;GAAe,GAAG;iBA6H3C,QAAQ,IAAI,OAAO,GAAG"}
@@ -26,28 +26,81 @@ function slowMap(ir, g) {
26
26
  const pkVar = g.temp("map_pk");
27
27
  const keyIssues = g.temp("map_ki");
28
28
  const valIssues = g.temp("map_vi");
29
- const mutates = hasMutation(ir.keyType) || hasMutation(ir.valueType);
30
- const rebuiltVar = mutates ? g.temp("map_n") : "";
31
29
  const msgProp = g.typeMsg === void 0 ? "" : `,message:${JSON.stringify(g.typeMsg)}`;
32
30
  const fz = emitRuntimeHelper(g.ctx, "__zcFz", ZC_FZ_DECL);
33
31
  const pfx = emitRuntimeHelper(g.ctx, "__zcPfx", ZC_PFX_DECL);
34
- const keyCode = g.visit(ir.keyType, {
35
- input: `${entryVar}[0]`,
36
- output: `${entryVar}[0]`,
37
- path: "[]",
38
- issues: keyIssues
39
- });
40
- const valCode = g.visit(ir.valueType, {
41
- input: `${entryVar}[1]`,
42
- output: `${entryVar}[1]`,
43
- path: "[]",
44
- issues: valIssues
45
- });
32
+ const keyOut = g.temp("map_ko");
33
+ const valueOut = g.temp("map_vo");
34
+ let keyCode;
35
+ let valCode;
36
+ let before = "";
37
+ let collect = "";
38
+ let after = "";
39
+ if (hasMutation(ir.keyType) || hasMutation(ir.valueType)) {
40
+ const rebuiltVar = g.temp("map_n");
41
+ keyCode = `var ${keyOut}=${keyVar};` + g.visit(ir.keyType, {
42
+ input: keyOut,
43
+ output: keyOut,
44
+ path: "[]",
45
+ issues: keyIssues
46
+ });
47
+ valCode = `var ${valueOut}=${entryVar}[1];` + g.visit(ir.valueType, {
48
+ input: valueOut,
49
+ output: valueOut,
50
+ path: "[]",
51
+ issues: valIssues
52
+ });
53
+ before = `var ${rebuiltVar}=new Map();`;
54
+ collect = `${rebuiltVar}.set(${keyOut},${valueOut});`;
55
+ after = `${g.output}=${rebuiltVar};`;
56
+ } else {
57
+ const valueVar = g.temp("map_v");
58
+ const keyVisit = g.visit(ir.keyType, {
59
+ input: keyVar,
60
+ output: keyOut,
61
+ path: "[]",
62
+ issues: keyIssues
63
+ });
64
+ const valueVisit = g.visit(ir.valueType, {
65
+ input: valueVar,
66
+ output: valueOut,
67
+ path: "[]",
68
+ issues: valIssues
69
+ });
70
+ const keyWrites = keyVisit.includes(keyOut);
71
+ const valueWrites = valueVisit.includes(valueOut);
72
+ keyCode = `${keyWrites ? `var ${keyOut}=${keyVar};` : ""}${keyVisit}`;
73
+ valCode = `var ${valueVar}=${entryVar}[1];${valueWrites ? `var ${valueOut}=${valueVar};` : ""}${valueVisit}`;
74
+ if (keyWrites || valueWrites) {
75
+ const rebuiltVar = g.temp("map_n");
76
+ const countVar = g.temp("map_c");
77
+ const skipVar = g.temp("map_j");
78
+ const earlierVar = g.temp("map_p");
79
+ const key = keyWrites ? keyOut : keyVar;
80
+ const value = valueWrites ? valueOut : valueVar;
81
+ const replaced = [...keyWrites ? [`${keyOut}!==${keyVar}`] : [], ...valueWrites ? [`${valueOut}!==${valueVar}`] : []].join("||");
82
+ before = `var ${rebuiltVar}=null;var ${countVar}=0;`;
83
+ collect = emit`
84
+ if(${rebuiltVar}!==null){
85
+ ${rebuiltVar}.set(${key},${value});
86
+ }else if(${replaced}){
87
+ ${rebuiltVar}=new Map();
88
+ var ${skipVar}=0;
89
+ for(var ${earlierVar} of ${g.input}){
90
+ if(${skipVar}++===${countVar})break;
91
+ ${rebuiltVar}.set(${earlierVar}[0],${earlierVar}[1]);
92
+ }
93
+ ${rebuiltVar}.set(${key},${value});
94
+ }
95
+ ${countVar}++;`;
96
+ after = `if(${rebuiltVar}!==null){${g.output}=${rebuiltVar};}`;
97
+ }
98
+ }
46
99
  return `${emit`
47
100
  if(!(${g.input} instanceof Map)){
48
101
  ${invalidType(g, "map")}
49
102
  }else{
50
- ${mutates ? `var ${rebuiltVar}=new Map();` : ""}
103
+ ${before}
51
104
  for(var ${entryVar} of ${g.input}){
52
105
  var ${keyVar}=${entryVar}[0];
53
106
  var ${pkVar}=${propertyKeyTest(keyVar)};
@@ -69,9 +122,9 @@ function slowMap(ir, g) {
69
122
  ${g.issues}.push({origin:"map",code:"invalid_element",key:${keyVar},issues:${fz}(${valIssues}),input:${g.input},path:${g.path}${msgProp}});
70
123
  }
71
124
  }
72
- ${mutates ? `${rebuiltVar}.set(${entryVar}[0],${entryVar}[1]);` : ""}
125
+ ${collect}
73
126
  }
74
- ${mutates ? `${g.output}=${rebuiltVar};` : ""}
127
+ ${after}
75
128
  }
76
129
  `}\n`;
77
130
  }
@@ -1 +1 @@
1
- {"version":3,"file":"map.js","names":[],"sources":["../../../../src/core/codegen/schemas/map.ts"],"sourcesContent":["import type { MapIR, SchemaIR } from \"../../types.js\";\nimport type { FastGen, SlowGen } from \"../context.js\";\nimport { declareFastTemps, emitRuntimeHelper, hasMutation } from \"../context.js\";\nimport { emit } from \"../emit.js\";\nimport { invalidType } from \"../emit-issue.js\";\nimport { propertyKeyTest, ZC_FZ_DECL, ZC_PFX_DECL } from \"../issue-decls.js\";\n\n/**\n * Map entries report through zod's `handleMapResult`, which branches on the\n * RUNTIME type of the key rather than on the key schema:\n *\n * - a property-key type (string | number | symbol) addresses the entry, so key\n * AND value issues are prefixed with the key itself → `[...mapPath, key, …]`.\n * This holds even when the key is the wrong type for the schema — a `number`\n * key under `z.map(z.string(), …)` still reports at `[5]`.\n * - anything else (boolean, bigint, object, …) cannot be a path segment, so the\n * issues are WRAPPED: key issues into one `invalid_key`, value issues into one\n * `invalid_element` carrying the offending `key`, both at the map's own path\n * with the originals nested and finalized.\n *\n * The key is snapshotted before validation because zod passes the ORIGINAL key\n * to both the type test and the prefix, and a mutating key schema (`.trim()`)\n * would otherwise have rewritten it by then.\n */\nexport function slowMap(ir: SchemaIR & { type: \"map\" }, g: SlowGen): string {\n const entryVar = g.temp(\"map_e\");\n const keyVar = g.temp(\"map_k\");\n const pkVar = g.temp(\"map_pk\");\n const keyIssues = g.temp(\"map_ki\");\n const valIssues = g.temp(\"map_vi\");\n // Mutating key/value schemas (coerce, .trim(), url) rewrite the entry tuple,\n // which a Map cannot reflect — rebuild into a fresh Map (mirrors Zod).\n const mutates = hasMutation(ir.keyType) || hasMutation(ir.valueType);\n const rebuiltVar = mutates ? g.temp(\"map_n\") : \"\";\n const msgProp = g.typeMsg === undefined ? \"\" : `,message:${JSON.stringify(g.typeMsg)}`;\n const fz = emitRuntimeHelper(g.ctx, \"__zcFz\", ZC_FZ_DECL);\n const pfx = emitRuntimeHelper(g.ctx, \"__zcPfx\", ZC_PFX_DECL);\n\n // Key and value are validated into scratch arrays at a path RELATIVE to the\n // entry, exactly as zod runs them on a fresh payload — whichever branch the\n // key's type selects then decides where those paths get rooted.\n const keyCode = g.visit(ir.keyType, {\n input: `${entryVar}[0]`,\n output: `${entryVar}[0]`,\n path: \"[]\",\n issues: keyIssues,\n });\n const valCode = g.visit(ir.valueType, {\n input: `${entryVar}[1]`,\n output: `${entryVar}[1]`,\n path: \"[]\",\n issues: valIssues,\n });\n\n return `${emit`\n if(!(${g.input} instanceof Map)){\n ${invalidType(g, \"map\")}\n }else{\n ${mutates ? `var ${rebuiltVar}=new Map();` : \"\"}\n for(var ${entryVar} of ${g.input}){\n var ${keyVar}=${entryVar}[0];\n var ${pkVar}=${propertyKeyTest(keyVar)};\n var ${keyIssues}=[];\n ${keyCode}\n if(${keyIssues}.length>0){\n if(${pkVar}){\n ${pfx}(${g.issues},${keyIssues},${g.path},${keyVar});\n }else{\n ${g.issues}.push({code:\"invalid_key\",origin:\"map\",issues:${fz}(${keyIssues}),input:${g.input},path:${g.path}${msgProp}});\n }\n }\n var ${valIssues}=[];\n ${valCode}\n if(${valIssues}.length>0){\n if(${pkVar}){\n ${pfx}(${g.issues},${valIssues},${g.path},${keyVar});\n }else{\n ${g.issues}.push({origin:\"map\",code:\"invalid_element\",key:${keyVar},issues:${fz}(${valIssues}),input:${g.input},path:${g.path}${msgProp}});\n }\n }\n ${mutates ? `${rebuiltVar}.set(${entryVar}[0],${entryVar}[1]);` : \"\"}\n }\n ${mutates ? `${g.output}=${rebuiltVar};` : \"\"}\n }\n `}\\n`;\n}\n\nexport function fastMap(ir: MapIR, g: FastGen): string | null {\n const x = g.input;\n const parts: string[] = [`${x} instanceof Map`];\n\n // Key/value validation via preamble helper (Map has no .every()).\n // Key + value share one fresh scope — same emitted helper, size-gated\n // independently of the caller.\n const entryVar = g.temp(\"me\");\n const body = g.scoped(`${entryVar}[0]`);\n const keyCheck = body.visit(ir.keyType, { input: `${entryVar}[0]` });\n if (keyCheck === null) return null;\n const valCheck = body.visit(ir.valueType, { input: `${entryVar}[1]` });\n if (valCheck === null) return null;\n\n if (keyCheck !== \"true\" || valCheck !== \"true\") {\n const combined = [keyCheck, valCheck].filter((c) => c !== \"true\").join(\"&&\");\n const helperName = g.temp(\"mh\");\n g.ctx.preamble.push(\n `function ${helperName}(m){${declareFastTemps(body.scope)}for(var ${entryVar} of m){if(!(${combined})){return false;}}return true;}`,\n );\n parts.push(`${helperName}(${x})`);\n }\n\n return parts.join(\"&&\");\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAwBA,SAAgB,QAAQ,IAAgC,GAAoB;CAC1E,MAAM,WAAW,EAAE,KAAK,OAAO;CAC/B,MAAM,SAAS,EAAE,KAAK,OAAO;CAC7B,MAAM,QAAQ,EAAE,KAAK,QAAQ;CAC7B,MAAM,YAAY,EAAE,KAAK,QAAQ;CACjC,MAAM,YAAY,EAAE,KAAK,QAAQ;CAGjC,MAAM,UAAU,YAAY,GAAG,OAAO,KAAK,YAAY,GAAG,SAAS;CACnE,MAAM,aAAa,UAAU,EAAE,KAAK,OAAO,IAAI;CAC/C,MAAM,UAAU,EAAE,YAAY,KAAA,IAAY,KAAK,YAAY,KAAK,UAAU,EAAE,OAAO;CACnF,MAAM,KAAK,kBAAkB,EAAE,KAAK,UAAU,UAAU;CACxD,MAAM,MAAM,kBAAkB,EAAE,KAAK,WAAW,WAAW;CAK3D,MAAM,UAAU,EAAE,MAAM,GAAG,SAAS;EAClC,OAAO,GAAG,SAAS;EACnB,QAAQ,GAAG,SAAS;EACpB,MAAM;EACN,QAAQ;CACV,CAAC;CACD,MAAM,UAAU,EAAE,MAAM,GAAG,WAAW;EACpC,OAAO,GAAG,SAAS;EACnB,QAAQ,GAAG,SAAS;EACpB,MAAM;EACN,QAAQ;CACV,CAAC;CAED,OAAO,GAAG,IAAI;WACL,EAAE,MAAM;QACX,YAAY,GAAG,KAAK,EAAE;;QAEtB,UAAU,OAAO,WAAW,eAAe,GAAG;gBACtC,SAAS,MAAM,EAAE,MAAM;cACzB,OAAO,GAAG,SAAS;cACnB,MAAM,GAAG,gBAAgB,MAAM,EAAE;cACjC,UAAU;UACd,QAAQ;aACL,UAAU;eACR,MAAM;cACP,IAAI,GAAG,EAAE,OAAO,GAAG,UAAU,GAAG,EAAE,KAAK,GAAG,OAAO;;cAEjD,EAAE,OAAO,gDAAgD,GAAG,GAAG,UAAU,UAAU,EAAE,MAAM,QAAQ,EAAE,OAAO,QAAQ;;;cAGpH,UAAU;UACd,QAAQ;aACL,UAAU;eACR,MAAM;cACP,IAAI,GAAG,EAAE,OAAO,GAAG,UAAU,GAAG,EAAE,KAAK,GAAG,OAAO;;cAEjD,EAAE,OAAO,iDAAiD,OAAO,UAAU,GAAG,GAAG,UAAU,UAAU,EAAE,MAAM,QAAQ,EAAE,OAAO,QAAQ;;;UAG1I,UAAU,GAAG,WAAW,OAAO,SAAS,MAAM,SAAS,SAAS,GAAG;;QAErE,UAAU,GAAG,EAAE,OAAO,GAAG,WAAW,KAAK,GAAG;;IAEhD;AACJ;AAEA,SAAgB,QAAQ,IAAW,GAA2B;CAC5D,MAAM,IAAI,EAAE;CACZ,MAAM,QAAkB,CAAC,GAAG,EAAE,gBAAgB;CAK9C,MAAM,WAAW,EAAE,KAAK,IAAI;CAC5B,MAAM,OAAO,EAAE,OAAO,GAAG,SAAS,IAAI;CACtC,MAAM,WAAW,KAAK,MAAM,GAAG,SAAS,EAAE,OAAO,GAAG,SAAS,KAAK,CAAC;CACnE,IAAI,aAAa,MAAM,OAAO;CAC9B,MAAM,WAAW,KAAK,MAAM,GAAG,WAAW,EAAE,OAAO,GAAG,SAAS,KAAK,CAAC;CACrE,IAAI,aAAa,MAAM,OAAO;CAE9B,IAAI,aAAa,UAAU,aAAa,QAAQ;EAC9C,MAAM,WAAW,CAAC,UAAU,QAAQ,CAAC,CAAC,QAAQ,MAAM,MAAM,MAAM,CAAC,CAAC,KAAK,IAAI;EAC3E,MAAM,aAAa,EAAE,KAAK,IAAI;EAC9B,EAAE,IAAI,SAAS,KACb,YAAY,WAAW,MAAM,iBAAiB,KAAK,KAAK,EAAE,UAAU,SAAS,cAAc,SAAS,gCACtG;EACA,MAAM,KAAK,GAAG,WAAW,GAAG,EAAE,EAAE;CAClC;CAEA,OAAO,MAAM,KAAK,IAAI;AACxB"}
1
+ {"version":3,"file":"map.js","names":[],"sources":["../../../../src/core/codegen/schemas/map.ts"],"sourcesContent":["import type { MapIR, SchemaIR } from \"../../types.js\";\nimport type { FastGen, SlowGen } from \"../context.js\";\nimport { declareFastTemps, emitRuntimeHelper, hasMutation } from \"../context.js\";\nimport { emit } from \"../emit.js\";\nimport { invalidType } from \"../emit-issue.js\";\nimport { propertyKeyTest, ZC_FZ_DECL, ZC_PFX_DECL } from \"../issue-decls.js\";\n\n/**\n * Map entries report through zod's `handleMapResult`, which branches on the\n * RUNTIME type of the key rather than on the key schema:\n *\n * - a property-key type (string | number | symbol) addresses the entry, so key\n * AND value issues are prefixed with the key itself → `[...mapPath, key, …]`.\n * This holds even when the key is the wrong type for the schema — a `number`\n * key under `z.map(z.string(), …)` still reports at `[5]`.\n * - anything else (boolean, bigint, object, …) cannot be a path segment, so the\n * issues are WRAPPED: key issues into one `invalid_key`, value issues into one\n * `invalid_element` carrying the offending `key`, both at the map's own path\n * with the originals nested and finalized.\n *\n * The key is snapshotted before validation because zod passes the ORIGINAL key\n * to both the type test and the prefix, and a mutating key schema (`.trim()`)\n * would otherwise have rewritten it by then.\n */\nexport function slowMap(ir: SchemaIR & { type: \"map\" }, g: SlowGen): string {\n const entryVar = g.temp(\"map_e\");\n const keyVar = g.temp(\"map_k\");\n const pkVar = g.temp(\"map_pk\");\n const keyIssues = g.temp(\"map_ki\");\n const valIssues = g.temp(\"map_vi\");\n const msgProp = g.typeMsg === undefined ? \"\" : `,message:${JSON.stringify(g.typeMsg)}`;\n const fz = emitRuntimeHelper(g.ctx, \"__zcFz\", ZC_FZ_DECL);\n const pfx = emitRuntimeHelper(g.ctx, \"__zcPfx\", ZC_PFX_DECL);\n\n // Key and value are validated into scratch arrays at a path RELATIVE to the\n // entry, exactly as zod runs them on a fresh payload — whichever branch the\n // key's type selects then decides where those paths get rooted.\n //\n // Both are read out of the entry into locals and never written back into it:\n // the entry array is the iterator's, and a Map subclass can hand out its own.\n // What they produce reaches the output through the rebuilt Map instead.\n const keyOut = g.temp(\"map_ko\");\n const valueOut = g.temp(\"map_vo\");\n let keyCode: string;\n let valCode: string;\n let before = \"\";\n let collect = \"\";\n let after = \"\";\n if (hasMutation(ir.keyType) || hasMutation(ir.valueType)) {\n // A mutating key or value (coerce, .trim(), url) reads back what it wrote,\n // so each keeps one local for both, and every entry is rebuilt into a fresh\n // Map (mirrors Zod).\n const rebuiltVar = g.temp(\"map_n\");\n keyCode =\n `var ${keyOut}=${keyVar};` +\n g.visit(ir.keyType, { input: keyOut, output: keyOut, path: \"[]\", issues: keyIssues });\n valCode =\n `var ${valueOut}=${entryVar}[1];` +\n g.visit(ir.valueType, { input: valueOut, output: valueOut, path: \"[]\", issues: valIssues });\n before = `var ${rebuiltVar}=new Map();`;\n collect = `${rebuiltVar}.set(${keyOut},${valueOut});`;\n after = `${g.output}=${rebuiltVar};`;\n } else {\n // A key or value that rewrites nothing can still hand back a REPLACEMENT —\n // a `__proto__`-scrubbed copy, a recursive value rebuilt by its own\n // validator — which zod's output Map holds in its place. Each is given an\n // output local of its own (see visitMember), and the first replacement\n // starts the rebuilt Map: the entries before it, in order, then every\n // entry's output from there on. A Map nothing replaces still comes back by\n // reference, and a side that never names its output is never compared.\n const valueVar = g.temp(\"map_v\");\n const keyVisit = g.visit(ir.keyType, {\n input: keyVar,\n output: keyOut,\n path: \"[]\",\n issues: keyIssues,\n });\n const valueVisit = g.visit(ir.valueType, {\n input: valueVar,\n output: valueOut,\n path: \"[]\",\n issues: valIssues,\n });\n const keyWrites = keyVisit.includes(keyOut);\n const valueWrites = valueVisit.includes(valueOut);\n keyCode = `${keyWrites ? `var ${keyOut}=${keyVar};` : \"\"}${keyVisit}`;\n valCode = `var ${valueVar}=${entryVar}[1];${valueWrites ? `var ${valueOut}=${valueVar};` : \"\"}${valueVisit}`;\n if (keyWrites || valueWrites) {\n const rebuiltVar = g.temp(\"map_n\");\n const countVar = g.temp(\"map_c\");\n const skipVar = g.temp(\"map_j\");\n const earlierVar = g.temp(\"map_p\");\n const key = keyWrites ? keyOut : keyVar;\n const value = valueWrites ? valueOut : valueVar;\n const replaced = [\n ...(keyWrites ? [`${keyOut}!==${keyVar}`] : []),\n ...(valueWrites ? [`${valueOut}!==${valueVar}`] : []),\n ].join(\"||\");\n before = `var ${rebuiltVar}=null;var ${countVar}=0;`;\n collect = emit`\n if(${rebuiltVar}!==null){\n ${rebuiltVar}.set(${key},${value});\n }else if(${replaced}){\n ${rebuiltVar}=new Map();\n var ${skipVar}=0;\n for(var ${earlierVar} of ${g.input}){\n if(${skipVar}++===${countVar})break;\n ${rebuiltVar}.set(${earlierVar}[0],${earlierVar}[1]);\n }\n ${rebuiltVar}.set(${key},${value});\n }\n ${countVar}++;`;\n after = `if(${rebuiltVar}!==null){${g.output}=${rebuiltVar};}`;\n }\n }\n\n return `${emit`\n if(!(${g.input} instanceof Map)){\n ${invalidType(g, \"map\")}\n }else{\n ${before}\n for(var ${entryVar} of ${g.input}){\n var ${keyVar}=${entryVar}[0];\n var ${pkVar}=${propertyKeyTest(keyVar)};\n var ${keyIssues}=[];\n ${keyCode}\n if(${keyIssues}.length>0){\n if(${pkVar}){\n ${pfx}(${g.issues},${keyIssues},${g.path},${keyVar});\n }else{\n ${g.issues}.push({code:\"invalid_key\",origin:\"map\",issues:${fz}(${keyIssues}),input:${g.input},path:${g.path}${msgProp}});\n }\n }\n var ${valIssues}=[];\n ${valCode}\n if(${valIssues}.length>0){\n if(${pkVar}){\n ${pfx}(${g.issues},${valIssues},${g.path},${keyVar});\n }else{\n ${g.issues}.push({origin:\"map\",code:\"invalid_element\",key:${keyVar},issues:${fz}(${valIssues}),input:${g.input},path:${g.path}${msgProp}});\n }\n }\n ${collect}\n }\n ${after}\n }\n `}\\n`;\n}\n\nexport function fastMap(ir: MapIR, g: FastGen): string | null {\n const x = g.input;\n const parts: string[] = [`${x} instanceof Map`];\n\n // Key/value validation via preamble helper (Map has no .every()).\n // Key + value share one fresh scope — same emitted helper, size-gated\n // independently of the caller.\n const entryVar = g.temp(\"me\");\n const body = g.scoped(`${entryVar}[0]`);\n const keyCheck = body.visit(ir.keyType, { input: `${entryVar}[0]` });\n if (keyCheck === null) return null;\n const valCheck = body.visit(ir.valueType, { input: `${entryVar}[1]` });\n if (valCheck === null) return null;\n\n if (keyCheck !== \"true\" || valCheck !== \"true\") {\n const combined = [keyCheck, valCheck].filter((c) => c !== \"true\").join(\"&&\");\n const helperName = g.temp(\"mh\");\n g.ctx.preamble.push(\n `function ${helperName}(m){${declareFastTemps(body.scope)}for(var ${entryVar} of m){if(!(${combined})){return false;}}return true;}`,\n );\n parts.push(`${helperName}(${x})`);\n }\n\n return parts.join(\"&&\");\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"record.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/record.ts"],"mappings":";;;iBA0BgB,WAAW,IAAI;EAAa;GAAkB,GAAG;iBA8DjD,WAAW,IAAI,UAAU,GAAG"}
1
+ {"version":3,"file":"record.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/record.ts"],"mappings":";;;iBAgCgB,WAAW,IAAI;EAAa;GAAkB,GAAG;iBAoFjD,WAAW,IAAI,UAAU,GAAG"}
@@ -1,4 +1,4 @@
1
- import { declareFastTemps, emitRuntimeHelper, extendPath, hasMutation } from "../context.js";
1
+ import { declareFastTemps, emitRuntimeHelper, extendPath, hasMutation, visitMember } from "../context.js";
2
2
  import { emit } from "../emit.js";
3
3
  import { invalidType, unrecognizedKeys } from "../emit-issue.js";
4
4
  import { ZC_FZ_DECL, ZC_HOP_DECL, ZC_PLAIN_DECL, ZC_PROTO_SCRUB_DECL } from "../issue-decls.js";
@@ -25,39 +25,45 @@ function slowRecord(ir, g) {
25
25
  if(!${plainObjectTest(g, g.input)}){
26
26
  ${invalidType(g, "record")}
27
27
  }else{`;
28
- if (hasMutation(ir.valueType)) code += `${g.output}={...${g.input}};`;
29
- code += `${g.output}=${emitRuntimeHelper(g.ctx, "__zcPs", ZC_PROTO_SCRUB_DECL)}(${g.input});`;
28
+ const rec = g.temp("rc");
29
+ code += `var ${rec}=${emitRuntimeHelper(g.ctx, "__zcPs", ZC_PROTO_SCRUB_DECL)}(${g.input});`;
30
+ if (hasMutation(ir.valueType)) code += `if(${rec}===${g.input}){${rec}={...${g.input}};}`;
30
31
  const keyVar = g.temp("rkey");
31
32
  const keyIssuesVar = g.temp("rki");
32
33
  const keyPath = extendPath(g.path, keyVar);
33
- const valExpr = `${g.input}[${keyVar}]`;
34
34
  const hop = emitRuntimeHelper(g.ctx, "__zcHop", ZC_HOP_DECL);
35
35
  const fz = emitRuntimeHelper(g.ctx, "__zcFz", ZC_FZ_DECL);
36
36
  const unrecognizedVar = ir.enumerableKeys ? g.temp("ruk") : null;
37
37
  const onKeyFailure = unrecognizedVar === null ? `${g.issues}.push({code:"invalid_key",origin:"record",issues:${fz}(${keyIssuesVar}),input:${keyVar},path:${keyPath}${g.typeMsg === void 0 ? "" : `,message:${JSON.stringify(g.typeMsg)}`}});` : `(${unrecognizedVar}=${unrecognizedVar}||[]).push(${keyVar});`;
38
- code += emit`
39
- ${unrecognizedVar === null ? "" : `var ${unrecognizedVar}=null;`}
40
- for(var ${keyVar} in ${g.input}){
41
- if(!${hop}.call(${g.input},${keyVar}))continue;
42
- ${PROTO_SKIP(keyVar)}
43
- var ${keyIssuesVar}=[];
44
- ${g.visit(ir.keyType, {
38
+ const keyCode = g.visit(ir.keyType, {
45
39
  input: keyVar,
46
40
  output: keyVar,
47
41
  path: "[]",
48
42
  issues: keyIssuesVar
49
- })}
43
+ });
44
+ const valueCode = visitMember(g, ir.valueType, {
45
+ container: rec,
46
+ original: g.input,
47
+ copy: `{...${g.input}}`,
48
+ key: keyVar,
49
+ path: keyPath,
50
+ issues: g.issues
51
+ });
52
+ code += emit`
53
+ ${unrecognizedVar === null ? "" : `var ${unrecognizedVar}=null;`}
54
+ for(var ${keyVar} in ${rec}){
55
+ if(!${hop}.call(${rec},${keyVar}))continue;
56
+ ${PROTO_SKIP(keyVar)}
57
+ var ${keyIssuesVar}=[];
58
+ ${keyCode}
50
59
  if(${keyIssuesVar}.length>0){
51
60
  ${onKeyFailure}
52
61
  }else{
53
- ${g.visit(ir.valueType, {
54
- input: valExpr,
55
- output: valExpr,
56
- path: keyPath
57
- })}
62
+ ${valueCode}
58
63
  }
59
64
  }
60
65
  ${unrecognizedVar === null ? "" : `if(${unrecognizedVar}!==null){${unrecognizedKeys(g, unrecognizedVar)}}`}
66
+ ${g.output}=${rec};
61
67
  }`;
62
68
  return `${code}\n`;
63
69
  }
@@ -1 +1 @@
1
- {"version":3,"file":"record.js","names":[],"sources":["../../../../src/core/codegen/schemas/record.ts"],"sourcesContent":["import type { RecordIR, SchemaIR } from \"../../types.js\";\nimport type { FastGen, SlowGen } from \"../context.js\";\nimport { declareFastTemps, emitRuntimeHelper, extendPath, hasMutation } from \"../context.js\";\nimport { emit } from \"../emit.js\";\nimport { invalidType, unrecognizedKeys } from \"../emit-issue.js\";\nimport { ZC_FZ_DECL, ZC_HOP_DECL, ZC_PLAIN_DECL, ZC_PROTO_SCRUB_DECL } from \"../issue-decls.js\";\n\n/**\n * `$ZodRecord` gates on `util.isPlainObject`, NOT the looser `util.isObject`\n * that `$ZodObject` uses — see {@link ZC_PLAIN_DECL} for why the two differ and\n * what accepting the wrong one lets through.\n */\nfunction plainObjectTest(g: SlowGen | FastGen, input: string): string {\n return `${emitRuntimeHelper(g.ctx, \"__zcPlain\", ZC_PLAIN_DECL)}(${input})`;\n}\n\n/**\n * Zod's record walk opens with `if (key === \"__proto__\") continue`, so an own\n * `__proto__` data property (which `JSON.parse('{\"__proto__\":…}')` creates) is\n * neither key-validated nor value-validated nor copied. Without this the\n * compiled walk reported issues zod does not: `z.record(z.string(), z.number())`\n * over such an object raised a SECOND `invalid_type`, and a constrained key\n * schema a second `invalid_key`, for a key zod never looks at.\n */\nconst PROTO_SKIP = (keyVar: string): string => `if(${keyVar}===\"__proto__\")continue;`;\n\nexport function slowRecord(ir: SchemaIR & { type: \"record\" }, g: SlowGen): string {\n let code = emit`\n if(!${plainObjectTest(g, g.input)}){\n ${invalidType(g, \"record\")}\n }else{`;\n\n if (hasMutation(ir.valueType)) {\n code += `${g.output}={...${g.input}};`;\n }\n // PROTO_SKIP keeps `__proto__` out of the WALK; this keeps it out of the\n // OUTPUT, which zod also does — it copies into a fresh `{}` and skips the key\n // (see ZC_PROTO_SCRUB_DECL). Runs before the walk so the loop iterates the\n // same container the caller gets back.\n code += `${g.output}=${emitRuntimeHelper(g.ctx, \"__zcPs\", ZC_PROTO_SCRUB_DECL)}(${g.input});`;\n\n const keyVar = g.temp(\"rkey\");\n const keyIssuesVar = g.temp(\"rki\");\n const keyPath = extendPath(g.path, keyVar);\n const valExpr = `${g.input}[${keyVar}]`;\n // for-in + hasOwnProperty guard instead of Object.keys(): identical\n // own-enumerable string-key set and iteration order, no keys-array\n // allocation. When the value type mutates (coerce/default/.trim()) the clone\n // above has already replaced g.input, so this iterates the clone exactly as\n // the Object.keys form did — the key set is stable (values change, keys\n // don't). Records whose values mutate run this path eagerly, so they get the\n // same speedup the fast path does.\n const hop = emitRuntimeHelper(g.ctx, \"__zcHop\", ZC_HOP_DECL);\n\n // The key is validated at a RELATIVE path and its issues are finalized before\n // being nested, mirroring zod: `issues: keyResult.issues.map(finalizeIssue)`\n // over a payload zod ran fresh. Nested issues never reach the top-level\n // finalization loop (it walks only the outer array), so an unfinalized nest\n // leaked absolute paths and the raw `input` into the reported error.\n const fz = emitRuntimeHelper(g.ctx, \"__zcFz\", ZC_FZ_DECL);\n\n // A key the enumerable key schema rejects is unrecognized, not invalid: zod\n // collects those and pushes ONE `unrecognized_keys` after the walk, so it\n // trails every value issue (see RecordIR.enumerableKeys).\n const unrecognizedVar = ir.enumerableKeys ? g.temp(\"ruk\") : null;\n const onKeyFailure =\n unrecognizedVar === null\n ? `${g.issues}.push({code:\"invalid_key\",origin:\"record\",issues:${fz}(${keyIssuesVar}),input:${keyVar},path:${keyPath}${g.typeMsg === undefined ? \"\" : `,message:${JSON.stringify(g.typeMsg)}`}});`\n : `(${unrecognizedVar}=${unrecognizedVar}||[]).push(${keyVar});`;\n\n code += emit`\n ${unrecognizedVar === null ? \"\" : `var ${unrecognizedVar}=null;`}\n for(var ${keyVar} in ${g.input}){\n if(!${hop}.call(${g.input},${keyVar}))continue;\n ${PROTO_SKIP(keyVar)}\n var ${keyIssuesVar}=[];\n ${g.visit(ir.keyType, { input: keyVar, output: keyVar, path: \"[]\", issues: keyIssuesVar })}\n if(${keyIssuesVar}.length>0){\n ${onKeyFailure}\n }else{\n ${g.visit(ir.valueType, { input: valExpr, output: valExpr, path: keyPath })}\n }\n }\n ${unrecognizedVar === null ? \"\" : `if(${unrecognizedVar}!==null){${unrecognizedKeys(g, unrecognizedVar)}}`}\n }`;\n return `${code}\\n`;\n}\n\nexport function fastRecord(ir: RecordIR, g: FastGen): string | null {\n const x = g.input;\n const parts: string[] = [plainObjectTest(g, x)];\n\n // Object.keys only yields strings — a plain unconstrained string key schema\n // is always satisfied, so skip generating its check entirely.\n const plainStringKey =\n ir.keyType.type === \"string\" && ir.keyType.checks.length === 0 && ir.keyType.coerce !== true;\n\n const kv = g.temp(\"rk\");\n const vv = g.temp(\"rv\");\n // Key + value checks share one fresh scope — they live in the same emitted\n // helper function, size-gated independently of the caller.\n const body = g.scoped(kv);\n const keyCheck = plainStringKey ? \"true\" : body.visit(ir.keyType, { input: kv });\n // Hoist o[k] into a loop variable: computed-key lookups don't get V8's load\n // elimination across check boundaries, so each repeated o[k] would re-walk\n // the (often dictionary-mode) object.\n const valCheck = body.visit(ir.valueType, { input: vv });\n if (keyCheck === null || valCheck === null) return null;\n\n const conditions: string[] = [];\n if (keyCheck !== \"true\") conditions.push(keyCheck);\n if (valCheck !== \"true\") conditions.push(valCheck);\n\n if (conditions.length > 0) {\n const helperName = g.temp(\"rf\");\n const valAssign = valCheck !== \"true\" ? `${vv}=o[${kv}];` : \"\";\n // for-in (no Object.keys array allocation) + hasOwnProperty guard. The\n // guard restricts iteration to own enumerable string keys — the exact set\n // Object.keys/the slow path produce — so inherited enumerable props can't\n // make the fast check disagree with the deferred slow walk. Measured 2.9x\n // (5 keys) to 5.8x (20 keys) faster than the Object.keys form; the hoisted\n // __zcHop.call inlines, making the guard ~free vs an unguarded for-in.\n const hop = emitRuntimeHelper(g.ctx, \"__zcHop\", ZC_HOP_DECL);\n g.ctx.preamble.push(\n `function ${helperName}(o){${declareFastTemps(body.scope)}var ${kv},${vv};for(${kv} in o){${PROTO_SKIP(kv)}if(${hop}.call(o,${kv})){${valAssign}if(!(${conditions.join(\"&&\")})){return false;}}}return true;}`,\n );\n parts.push(`${helperName}(${x})`);\n }\n\n return parts.join(\"&&\");\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"record.js","names":[],"sources":["../../../../src/core/codegen/schemas/record.ts"],"sourcesContent":["import type { RecordIR, SchemaIR } from \"../../types.js\";\nimport type { FastGen, SlowGen } from \"../context.js\";\nimport {\n declareFastTemps,\n emitRuntimeHelper,\n extendPath,\n hasMutation,\n visitMember,\n} from \"../context.js\";\nimport { emit } from \"../emit.js\";\nimport { invalidType, unrecognizedKeys } from \"../emit-issue.js\";\nimport { ZC_FZ_DECL, ZC_HOP_DECL, ZC_PLAIN_DECL, ZC_PROTO_SCRUB_DECL } from \"../issue-decls.js\";\n\n/**\n * `$ZodRecord` gates on `util.isPlainObject`, NOT the looser `util.isObject`\n * that `$ZodObject` uses — see {@link ZC_PLAIN_DECL} for why the two differ and\n * what accepting the wrong one lets through.\n */\nfunction plainObjectTest(g: SlowGen | FastGen, input: string): string {\n return `${emitRuntimeHelper(g.ctx, \"__zcPlain\", ZC_PLAIN_DECL)}(${input})`;\n}\n\n/**\n * Zod's record walk opens with `if (key === \"__proto__\") continue`, so an own\n * `__proto__` data property (which `JSON.parse('{\"__proto__\":…}')` creates) is\n * neither key-validated nor value-validated nor copied. Without this the\n * compiled walk reported issues zod does not: `z.record(z.string(), z.number())`\n * over such an object raised a SECOND `invalid_type`, and a constrained key\n * schema a second `invalid_key`, for a key zod never looks at.\n */\nconst PROTO_SKIP = (keyVar: string): string => `if(${keyVar}===\"__proto__\")continue;`;\n\nexport function slowRecord(ir: SchemaIR & { type: \"record\" }, g: SlowGen): string {\n let code = emit`\n if(!${plainObjectTest(g, g.input)}){\n ${invalidType(g, \"record\")}\n }else{`;\n\n // One binding for every read and write, handed back as the output after the\n // walk: a parent can hand this node `g.input` and `g.output` as two different\n // bindings (see slowTuple).\n //\n // PROTO_SKIP keeps `__proto__` out of the WALK; the scrub keeps it out of the\n // OUTPUT, which zod also does — it copies into a fresh `{}` and skips the key\n // (see ZC_PROTO_SCRUB_DECL). Runs before the walk so the loop iterates the\n // same container the caller gets back. A mutating value schema rewrites values\n // as a matter of course, so it takes a copy up front unless the scrub already\n // made one; anything else copies only once a value hands back a replacement\n // (see visitMember).\n const rec = g.temp(\"rc\");\n code += `var ${rec}=${emitRuntimeHelper(g.ctx, \"__zcPs\", ZC_PROTO_SCRUB_DECL)}(${g.input});`;\n if (hasMutation(ir.valueType)) {\n code += `if(${rec}===${g.input}){${rec}={...${g.input}};}`;\n }\n\n const keyVar = g.temp(\"rkey\");\n const keyIssuesVar = g.temp(\"rki\");\n const keyPath = extendPath(g.path, keyVar);\n // for-in + hasOwnProperty guard instead of Object.keys(): identical\n // own-enumerable string-key set and iteration order, no keys-array\n // allocation. The loop iterates the binding as the walk found it — the clone,\n // when one was taken above — and a copy taken mid-walk for a replacement has\n // the same keys, so the key set is stable (values change, keys don't).\n // Records whose values mutate run this path eagerly, so they get the same\n // speedup the fast path does.\n const hop = emitRuntimeHelper(g.ctx, \"__zcHop\", ZC_HOP_DECL);\n\n // The key is validated at a RELATIVE path and its issues are finalized before\n // being nested, mirroring zod: `issues: keyResult.issues.map(finalizeIssue)`\n // over a payload zod ran fresh. Nested issues never reach the top-level\n // finalization loop (it walks only the outer array), so an unfinalized nest\n // leaked absolute paths and the raw `input` into the reported error.\n const fz = emitRuntimeHelper(g.ctx, \"__zcFz\", ZC_FZ_DECL);\n\n // A key the enumerable key schema rejects is unrecognized, not invalid: zod\n // collects those and pushes ONE `unrecognized_keys` after the walk, so it\n // trails every value issue (see RecordIR.enumerableKeys).\n const unrecognizedVar = ir.enumerableKeys ? g.temp(\"ruk\") : null;\n const onKeyFailure =\n unrecognizedVar === null\n ? `${g.issues}.push({code:\"invalid_key\",origin:\"record\",issues:${fz}(${keyIssuesVar}),input:${keyVar},path:${keyPath}${g.typeMsg === undefined ? \"\" : `,message:${JSON.stringify(g.typeMsg)}`}});`\n : `(${unrecognizedVar}=${unrecognizedVar}||[]).push(${keyVar});`;\n\n const keyCode = g.visit(ir.keyType, {\n input: keyVar,\n output: keyVar,\n path: \"[]\",\n issues: keyIssuesVar,\n });\n const valueCode = visitMember(g, ir.valueType, {\n container: rec,\n original: g.input,\n copy: `{...${g.input}}`,\n key: keyVar,\n path: keyPath,\n issues: g.issues,\n });\n code += emit`\n ${unrecognizedVar === null ? \"\" : `var ${unrecognizedVar}=null;`}\n for(var ${keyVar} in ${rec}){\n if(!${hop}.call(${rec},${keyVar}))continue;\n ${PROTO_SKIP(keyVar)}\n var ${keyIssuesVar}=[];\n ${keyCode}\n if(${keyIssuesVar}.length>0){\n ${onKeyFailure}\n }else{\n ${valueCode}\n }\n }\n ${unrecognizedVar === null ? \"\" : `if(${unrecognizedVar}!==null){${unrecognizedKeys(g, unrecognizedVar)}}`}\n ${g.output}=${rec};\n }`;\n return `${code}\\n`;\n}\n\nexport function fastRecord(ir: RecordIR, g: FastGen): string | null {\n const x = g.input;\n const parts: string[] = [plainObjectTest(g, x)];\n\n // Object.keys only yields strings — a plain unconstrained string key schema\n // is always satisfied, so skip generating its check entirely.\n const plainStringKey =\n ir.keyType.type === \"string\" && ir.keyType.checks.length === 0 && ir.keyType.coerce !== true;\n\n const kv = g.temp(\"rk\");\n const vv = g.temp(\"rv\");\n // Key + value checks share one fresh scope — they live in the same emitted\n // helper function, size-gated independently of the caller.\n const body = g.scoped(kv);\n const keyCheck = plainStringKey ? \"true\" : body.visit(ir.keyType, { input: kv });\n // Hoist o[k] into a loop variable: computed-key lookups don't get V8's load\n // elimination across check boundaries, so each repeated o[k] would re-walk\n // the (often dictionary-mode) object.\n const valCheck = body.visit(ir.valueType, { input: vv });\n if (keyCheck === null || valCheck === null) return null;\n\n const conditions: string[] = [];\n if (keyCheck !== \"true\") conditions.push(keyCheck);\n if (valCheck !== \"true\") conditions.push(valCheck);\n\n if (conditions.length > 0) {\n const helperName = g.temp(\"rf\");\n const valAssign = valCheck !== \"true\" ? `${vv}=o[${kv}];` : \"\";\n // for-in (no Object.keys array allocation) + hasOwnProperty guard. The\n // guard restricts iteration to own enumerable string keys — the exact set\n // Object.keys/the slow path produce — so inherited enumerable props can't\n // make the fast check disagree with the deferred slow walk. Measured 2.9x\n // (5 keys) to 5.8x (20 keys) faster than the Object.keys form; the hoisted\n // __zcHop.call inlines, making the guard ~free vs an unguarded for-in.\n const hop = emitRuntimeHelper(g.ctx, \"__zcHop\", ZC_HOP_DECL);\n g.ctx.preamble.push(\n `function ${helperName}(o){${declareFastTemps(body.scope)}var ${kv},${vv};for(${kv} in o){${PROTO_SKIP(kv)}if(${hop}.call(o,${kv})){${valAssign}if(!(${conditions.join(\"&&\")})){return false;}}}return true;}`,\n );\n parts.push(`${helperName}(${x})`);\n }\n\n return parts.join(\"&&\");\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"recursive-ref.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/recursive-ref.ts"],"mappings":";;;iBA0DgB,iBAAiB,IAAI,gBAAgB,GAAG;iBAIxC,iBAAiB,IAAI,gBAAgB,GAAG;;;;;;iBASxC,oBAAoB,IAAI,mBAAmB,GAAG;iBAI9C,oBAAoB,IAAI,mBAAmB,GAAG"}
1
+ {"version":3,"file":"recursive-ref.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/recursive-ref.ts"],"mappings":";;;iBA2DgB,iBAAiB,IAAI,gBAAgB,GAAG;iBAIxC,iBAAiB,IAAI,gBAAgB,GAAG;;;;;;iBASxC,oBAAoB,IAAI,mBAAmB,GAAG;iBAI9C,oBAAoB,IAAI,mBAAmB,GAAG"}
@@ -40,6 +40,7 @@ function slowRecursionCall(refId, g) {
40
40
  */
41
41
  function fastRecursionCall(refId, g) {
42
42
  const ctx = g.ctx;
43
+ if (ctx.fastRecursionDisabled) return null;
43
44
  const t = ctx.recTargets?.get(refId ?? 0);
44
45
  if (!t || t.isRoot) {
45
46
  ctx.recFastName ??= `__fcr_${ctx.counter++}`;