zod-compiler 1.28.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/README.md +59 -53
  2. package/dist/core/codegen/build-path.d.ts +12 -1
  3. package/dist/core/codegen/build-path.d.ts.map +1 -1
  4. package/dist/core/codegen/build-path.js +47 -9
  5. package/dist/core/codegen/build-path.js.map +1 -1
  6. package/dist/core/codegen/context.d.ts +31 -16
  7. package/dist/core/codegen/context.d.ts.map +1 -1
  8. package/dist/core/codegen/context.js +43 -18
  9. package/dist/core/codegen/context.js.map +1 -1
  10. package/dist/core/codegen/emit-issue.d.ts +17 -20
  11. package/dist/core/codegen/emit-issue.d.ts.map +1 -1
  12. package/dist/core/codegen/emit-issue.js +13 -13
  13. package/dist/core/codegen/emit-issue.js.map +1 -1
  14. package/dist/core/codegen/index.d.ts.map +1 -1
  15. package/dist/core/codegen/index.js +9 -7
  16. package/dist/core/codegen/index.js.map +1 -1
  17. package/dist/core/codegen/issue-decls.d.ts +41 -7
  18. package/dist/core/codegen/issue-decls.d.ts.map +1 -1
  19. package/dist/core/codegen/issue-decls.js +49 -9
  20. package/dist/core/codegen/issue-decls.js.map +1 -1
  21. package/dist/core/codegen/schemas/array.js +1 -1
  22. package/dist/core/codegen/schemas/array.js.map +1 -1
  23. package/dist/core/codegen/schemas/bigint.d.ts.map +1 -1
  24. package/dist/core/codegen/schemas/bigint.js +15 -4
  25. package/dist/core/codegen/schemas/bigint.js.map +1 -1
  26. package/dist/core/codegen/schemas/catch.d.ts.map +1 -1
  27. package/dist/core/codegen/schemas/catch.js +3 -2
  28. package/dist/core/codegen/schemas/catch.js.map +1 -1
  29. package/dist/core/codegen/schemas/discriminated-union.d.ts.map +1 -1
  30. package/dist/core/codegen/schemas/discriminated-union.js +2 -1
  31. package/dist/core/codegen/schemas/discriminated-union.js.map +1 -1
  32. package/dist/core/codegen/schemas/effect.d.ts.map +1 -1
  33. package/dist/core/codegen/schemas/effect.js +4 -2
  34. package/dist/core/codegen/schemas/effect.js.map +1 -1
  35. package/dist/core/codegen/schemas/file.js +2 -8
  36. package/dist/core/codegen/schemas/file.js.map +1 -1
  37. package/dist/core/codegen/schemas/number.js +4 -3
  38. package/dist/core/codegen/schemas/number.js.map +1 -1
  39. package/dist/core/codegen/schemas/object.d.ts +11 -1
  40. package/dist/core/codegen/schemas/object.d.ts.map +1 -1
  41. package/dist/core/codegen/schemas/object.js +58 -14
  42. package/dist/core/codegen/schemas/object.js.map +1 -1
  43. package/dist/core/codegen/schemas/readonly.d.ts.map +1 -1
  44. package/dist/core/codegen/schemas/readonly.js.map +1 -1
  45. package/dist/core/codegen/schemas/record.d.ts.map +1 -1
  46. package/dist/core/codegen/schemas/record.js +8 -3
  47. package/dist/core/codegen/schemas/record.js.map +1 -1
  48. package/dist/core/codegen/schemas/sizeable.d.ts +49 -3
  49. package/dist/core/codegen/schemas/sizeable.d.ts.map +1 -1
  50. package/dist/core/codegen/schemas/sizeable.js +101 -8
  51. package/dist/core/codegen/schemas/sizeable.js.map +1 -1
  52. package/dist/core/codegen/schemas/string.d.ts.map +1 -1
  53. package/dist/core/codegen/schemas/string.js +45 -16
  54. package/dist/core/codegen/schemas/string.js.map +1 -1
  55. package/dist/core/codegen/schemas/tuple.d.ts +14 -11
  56. package/dist/core/codegen/schemas/tuple.d.ts.map +1 -1
  57. package/dist/core/codegen/schemas/tuple.js +99 -49
  58. package/dist/core/codegen/schemas/tuple.js.map +1 -1
  59. package/dist/core/codegen/well-known-regex.d.ts +7 -0
  60. package/dist/core/codegen/well-known-regex.d.ts.map +1 -1
  61. package/dist/core/codegen/well-known-regex.js +10 -3
  62. package/dist/core/codegen/well-known-regex.js.map +1 -1
  63. package/dist/core/extract/checks.d.ts.map +1 -1
  64. package/dist/core/extract/checks.js +1 -0
  65. package/dist/core/extract/checks.js.map +1 -1
  66. package/dist/core/extract/extractors/intersection.js +21 -6
  67. package/dist/core/extract/extractors/intersection.js.map +1 -1
  68. package/dist/core/extract/extractors/object.d.ts.map +1 -1
  69. package/dist/core/extract/extractors/object.js +25 -9
  70. package/dist/core/extract/extractors/object.js.map +1 -1
  71. package/dist/core/extract/extractors/optional.d.ts.map +1 -1
  72. package/dist/core/extract/extractors/optional.js +1 -28
  73. package/dist/core/extract/extractors/optional.js.map +1 -1
  74. package/dist/core/extract/extractors/record.js +9 -3
  75. package/dist/core/extract/extractors/record.js.map +1 -1
  76. package/dist/core/extract/extractors/tuple.d.ts.map +1 -1
  77. package/dist/core/extract/extractors/tuple.js +16 -20
  78. package/dist/core/extract/extractors/tuple.js.map +1 -1
  79. package/dist/core/extract/types.d.ts +16 -8
  80. package/dist/core/extract/types.d.ts.map +1 -1
  81. package/dist/core/iife.d.ts +2 -1
  82. package/dist/core/iife.d.ts.map +1 -1
  83. package/dist/core/iife.js +2 -1
  84. package/dist/core/iife.js.map +1 -1
  85. package/dist/core/types.d.ts +70 -21
  86. package/dist/core/types.d.ts.map +1 -1
  87. package/dist/jit.d.ts.map +1 -1
  88. package/dist/jit.js +47 -12
  89. package/dist/jit.js.map +1 -1
  90. package/dist/runtime.d.ts +3 -1
  91. package/dist/runtime.js +12 -10
  92. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"object.js","names":[],"sources":["../../../../src/core/codegen/schemas/object.ts"],"sourcesContent":["import type { ObjectIR, SchemaIR } from \"../../types.js\";\nimport type { FastGen, SlowGen } from \"../context.js\";\nimport {\n declareFastTemps,\n emitEffectCallable,\n emitRuntimeHelper,\n escapeString,\n extendPath,\n extendStaticPath,\n hasMutation,\n keyMembershipTest,\n outputAlwaysDefined,\n} from \"../context.js\";\nimport { emit } from \"../emit.js\";\nimport { invalidType, unrecognizedKeys } from \"../emit-issue.js\";\nimport { ZC_AB_DECL } from \"../issue-decls.js\";\nimport { orderByRuntimeCost } from \"../fast-size.js\";\nimport { refineCheck, superRefineCheck, superRefineFastTest } from \"./effect.js\";\n\nexport function slowObject(ir: SchemaIR & { type: \"object\" }, g: SlowGen): string {\n let code = emit`\n if(typeof ${g.input}!==\"object\"||${g.input}===null||Array.isArray(${g.input})){\n ${invalidType(g, \"object\")}\n }else{`;\n\n // Strip mode (zod's default z.object() output) rebuilds a FRESH object from\n // the declared keys, so it always writes back. Otherwise clone only when a\n // property mutates the value.\n const strip = ir.stripUnknownKeys === true;\n const needsClone =\n strip ||\n (ir.catchall !== undefined && hasMutation(ir.catchall)) ||\n Object.values(ir.properties).some(hasMutation);\n const objVar = g.temp(\"o\");\n if (!strip) {\n // Spread, not Object.assign: V8's CloneObjectIC makes `{...x}` ~25% faster\n // on the whole safeParse call for mutation-bearing schemas.\n code += needsClone ? `var ${objVar}={...${g.input}};` : `var ${objVar}=${g.input};`;\n }\n\n // Object-level refines are gated on zod's ABORT rule, not on \"did anything\n // fail\": zod parses the properties (plus the strict/catchall passes) into the\n // payload and then skips its check chain only when `util.aborted` holds — i.e.\n // when one of those issues is non-continuable. A property that failed its own\n // `min`/format check reports a CONTINUABLE issue, so the outer refine still\n // runs and both messages surface; a property that failed to parse at all\n // (`invalid_type`, an unrecognized key, a bad record key) aborts and\n // suppresses it. Snapshot the issue count before the properties so the scan\n // covers exactly this node's own parse, as zod's fresh sub-payload does.\n const refineMark = ir.checks && ir.checks.length > 0 ? g.temp(\"rm\") : \"\";\n if (refineMark) code += `var ${refineMark}=${g.issues}.length;`;\n\n const suppressAbsent = new Set(ir.suppressAbsentKeys ?? []);\n /** Strip only: per-property output slot + whether it is always in the result. */\n const slots: { always: boolean; keyStr: string; value: string }[] = [];\n\n for (const [key, propIR] of Object.entries(ir.properties)) {\n const keyStr = escapeString(key);\n const propPath = extendStaticPath(g.path, key);\n // Strip validates the value read from the INPUT, held in a local, and\n // assembles the result afterwards. Reading through the half-built copy (the\n // previous shape) both cost a megamorphic access per key — the copy's map\n // changes with every key added — and diverged from zod, which parses\n // `input[key]` and so accepts a value found on the prototype.\n const propExpr = strip ? g.temp(\"sv\") : `${objVar}[${keyStr}]`;\n if (strip) {\n code += `var ${propExpr}=${g.input}[${keyStr}];`;\n slots.push({ always: outputAlwaysDefined(propIR), keyStr, value: propExpr });\n }\n const propCode = g.visit(propIR, { input: propExpr, output: propExpr, path: propPath });\n if (suppressAbsent.has(key)) {\n // Mirrors zod's handlePropertyResult: optional-out fallback props run,\n // but their issues are discarded when the key is absent from the input.\n const beforeVar = g.temp(\"ob\");\n code += emit`\n var ${beforeVar}=${g.issues}.length;\n ${propCode}\n if(!(${keyStr} in ${strip ? g.input : objVar})&&${g.issues}.length>${beforeVar}){\n ${g.issues}.length=${beforeVar};\n }`;\n } else {\n code += propCode;\n }\n }\n\n if (strip) {\n // Assemble the result in shape order (zod's key order), taking as long a\n // LEADING run of always-present keys as possible into one object literal:\n // V8 stamps a literal out of a cached boilerplate map in a single\n // allocation, where adding keys one at a time walks a transition chain and\n // re-checks the map on every store — measured 8.1x (20 keys) / 2.8x (7\n // keys, one optional) on the whole safeParse.\n //\n // Everything after the first conditional key is appended so insertion order\n // still matches zod. The per-key test is zod's own: keep the key when the\n // parsed value is defined, or when it was present on the input at all\n // (`key in input`, prototype included — an own `k: undefined` survives).\n const literal: string[] = [];\n let appends = \"\";\n let leading = true;\n for (const slot of slots) {\n if (leading && slot.always) {\n literal.push(`${slot.keyStr}:${slot.value}`);\n continue;\n }\n leading = false;\n appends += slot.always\n ? `${objVar}[${slot.keyStr}]=${slot.value};`\n : `if(${slot.value}!==undefined||(${slot.keyStr} in ${g.input})){${objVar}[${slot.keyStr}]=${slot.value};}`;\n }\n code += `var ${objVar}={${literal.join(\",\")}};${appends}`;\n }\n\n // Strict unknown-key pass — zod's handleCatchall, byte-exact: for-in over\n // the ORIGINAL input (inherited enumerable keys count, no hasOwnProperty),\n // ALL unknown keys collected into one issue, pushed AFTER property issues\n // and before object-level refines.\n if (ir.strict) {\n const keys = Object.keys(ir.properties);\n const ukVar = g.temp(\"uk\");\n const kVar = g.temp(\"k\");\n const test = keyMembershipTest(g.ctx, keys, kVar);\n code += emit`\n var ${ukVar}=null;\n for(var ${kVar} in ${g.input}){\n if(!(${test})){(${ukVar}=${ukVar}||[]).push(${kVar});}\n }\n if(${ukVar}!==null){\n ${unrecognizedKeys(g, ukVar)}\n }`;\n }\n\n // .catchall(schema): validate every key NOT in the shape, mirroring zod's\n // handleCatchall — same bare for-in over the ORIGINAL input as the strict\n // pass (inherited enumerable keys count, no hasOwnProperty guard), each\n // issue reported at the key. Runs after the properties, as zod does.\n //\n // The key's slot is BOTH the input and the output, exactly as a shape\n // property's is: a value-rewriting catchall (coerce, .trim(), a default)\n // writes through the same expression it later re-reads, so splitting them\n // makes the rewrite invisible to its own checks.\n //\n // When the catchall mutates, objVar is a clone, and each unknown key is\n // copied into it before validation — which also reproduces zod, whose fresh\n // output object gains an OWN key for every for-in key it saw, inherited ones\n // included. A non-mutating catchall writes nothing, so objVar is the input\n // and the object stays pass-through by reference.\n if (ir.catchall) {\n const keys = Object.keys(ir.properties);\n const kVar = g.temp(\"ck\");\n const test = keyMembershipTest(g.ctx, keys, kVar);\n const slot = `${objVar}[${kVar}]`;\n const seed = needsClone ? `${slot}=${g.input}[${kVar}];` : \"\";\n code += emit`\n for(var ${kVar} in ${g.input}){\n if(!(${test})){\n ${seed}\n ${g.visit(ir.catchall, {\n input: slot,\n output: slot,\n path: extendPath(g.path, kVar),\n })}\n }\n }`;\n }\n\n if (needsClone) {\n code += `${g.output}=${objVar};`;\n }\n\n // Object-level refine effects: z.object({...}).refine(fn), suppressed when the\n // parse phase aborted (see refineMark). One gate covers every effect: each of\n // them can only add a continuable `custom` issue, so zod's per-check\n // re-evaluation of `isAborted` can never flip between them.\n if (ir.checks && ir.checks.length > 0) {\n let refines = \"\";\n for (const check of ir.checks) {\n refines +=\n check.kind === \"super_refine_effect\"\n ? superRefineCheck(check, objVar, g)\n : refineCheck(check, objVar, g);\n }\n const aborted = emitRuntimeHelper(g.ctx, \"__zcAb\", ZC_AB_DECL);\n code += `if(!${aborted}(${g.issues},${refineMark})){${refines}}`;\n }\n\n code += `}\\n`;\n return code;\n}\n\n/**\n * Property entries in the order their fast-checks should be `&&`-chained:\n * cheapest first, declaration order preserved among equals (Array#sort is\n * stable). Valid input runs every conjunct whatever the order, so this costs\n * nothing on the hot path; a REJECT stops at the first false conjunct, so\n * pricing a `z.email()` behind the `kind` literal that actually discriminates\n * is what makes union probing and `.is()` misses cheap (see\n * estimateRuntimeCost). The SLOW path keeps declaration order — that one's\n * output is the issue list, whose order is part of zod parity.\n */\nfunction orderedProperties(ir: ObjectIR, g: FastGen): [string, SchemaIR][] {\n return orderByRuntimeCost(Object.entries(ir.properties), ([, propIR]) => propIR, g.ctx);\n}\n\n/**\n * Property/strict/refine fast-checks for an object, WITHOUT the leading\n * `typeof===object && !==null && !Array.isArray` type-guard. Returns the\n * conjunct parts (joinable with `&&`), or null if any child is fast-ineligible.\n *\n * `skipKey`, when given, omits that one property's check. Used by the\n * discriminated-union fast path (via `g.discSkipKey`): the enclosing `switch`\n * has already matched the discriminator's value, so re-checking it is redundant.\n */\nfunction fastObjectBody(ir: ObjectIR, g: FastGen, skipKey?: string): string[] | null {\n const x = g.input;\n const parts: string[] = [];\n\n for (const [key, propIR] of orderedProperties(ir, g)) {\n if (key === skipKey) continue;\n const propExpr = `${x}[${escapeString(key)}]`;\n const propCheck = g.visit(propIR, { input: propExpr });\n if (propCheck === null) return null; // All-or-nothing\n parts.push(propCheck);\n }\n\n // Strict unknown-key pass: hosted boolean helper (a for-in loop cannot live\n // in the && chain). Same for-in iteration as the slow path — fast/slow\n // agreement is load-bearing under the __zcFinD deferral. The membership set is\n // the FULL key list (the discriminator is a recognized key), independent of\n // skipKey, which only suppresses re-validating the discriminator's value.\n if (ir.strict) {\n const keys = Object.keys(ir.properties);\n const fnName = g.temp(\"so\");\n const test = keyMembershipTest(g.ctx, keys, \"k\");\n g.ctx.preamble.push(\n `function ${fnName}(o){for(var k in o){if(!(${test}))return false;}return true;}`,\n );\n parts.push(`${fnName}(${x})`);\n }\n\n // .catchall(schema): same hosted for-in as strict, but each unrecognized key\n // is checked against the catchall rather than rejected outright. The value is\n // hoisted into a local for the same reason fastRecord does it — repeated\n // `o[k]` computed loads are not eliminated across check boundaries.\n if (ir.catchall) {\n const kv = g.temp(\"cak\");\n const vv = g.temp(\"cav\");\n const catchallGen = g.scoped(vv);\n const valCheck = catchallGen.visit(ir.catchall, { input: vv });\n if (valCheck === null) return null;\n if (valCheck !== \"true\") {\n const fnName = g.temp(\"co\");\n const test = keyMembershipTest(g.ctx, Object.keys(ir.properties), kv);\n g.ctx.preamble.push(\n `function ${fnName}(o){${declareFastTemps(catchallGen.scope)}var ${kv},${vv};for(${kv} in o){if(!(${test})){${vv}=o[${kv}];if(!(${valCheck}))return false;}}return true;}`,\n );\n parts.push(`${fnName}(${x})`);\n }\n }\n\n // Object-level refine effects (appended last — run after property checks short-circuit)\n if (ir.checks) {\n for (const check of ir.checks) {\n if (check.kind === \"refine_effect\") {\n parts.push(`${emitEffectCallable(g.ctx, check)}(${x})`);\n } else if (check.kind === \"super_refine_effect\") {\n parts.push(superRefineFastTest(check, x, g));\n }\n }\n }\n\n return parts;\n}\n\nexport function fastObject(ir: ObjectIR, g: FastGen): string | null {\n // A strip object still gets an expression. Stripping reshapes the OUTPUT, not\n // the verdict, so this remains an exact acceptance predicate — which is what\n // `.is()` installs, and what the build path reuses to validate the subtrees it\n // passes through by reference. What it must NOT do is stand in for the parse\n // result: generateValidator withholds the `data:input` shortcut whenever the\n // schema rebuilds (see rebuildsOutput).\n const x = g.input;\n const body = fastObjectBody(ir, g, g.discSkipKey);\n if (body === null) return null;\n // Discriminated-union option: the enclosing switch (and the caller's guard)\n // already established object-ness and the discriminator value, so emit only\n // the remaining checks — no leading type-guard. An option with nothing left\n // to check accepts unconditionally (\"true\").\n if (g.discSkipKey !== undefined) {\n return body.length > 0 ? body.join(\"&&\") : \"true\";\n }\n return [`typeof ${x}===\"object\"`, `${x}!==null`, `!Array.isArray(${x})`, ...body].join(\"&&\");\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"object.js","names":[],"sources":["../../../../src/core/codegen/schemas/object.ts"],"sourcesContent":["import type { ObjectIR, SchemaIR } from \"../../types.js\";\nimport type { FastGen, SlowGen } from \"../context.js\";\nimport {\n declareFastTemps,\n emitEffectCallable,\n emitRuntimeHelper,\n escapeString,\n extendPath,\n extendStaticPath,\n hasMutation,\n keyMembershipTest,\n outputAlwaysDefined,\n} from \"../context.js\";\nimport { emit } from \"../emit.js\";\nimport { invalidType, unrecognizedKeys } from \"../emit-issue.js\";\nimport { ZC_AB_DECL, ZC_PROTO_SCRUB_DECL } from \"../issue-decls.js\";\nimport { orderByRuntimeCost } from \"../fast-size.js\";\nimport { refineCheck, superRefineCheck, superRefineFastTest } from \"./effect.js\";\n\n/**\n * The shape entries whose schemas actually RUN. `$ZodObject` skips a declared\n * `__proto__` key in its shape loop (`if (key === \"__proto__\") continue`), so\n * that property is never validated and never written to the output — it is\n * only a recognized name for the strict/catchall passes, which read\n * `Object.keys(ir.properties)` directly. Writing it would be worse than\n * pointless: `{__proto__: v}` in an object literal, and `o[\"__proto__\"] = v`\n * on a plain object, both SET THE PROTOTYPE rather than define a key.\n */\nexport function parsedProperties(ir: ObjectIR): [string, SchemaIR][] {\n return Object.entries(ir.properties).filter(([key]) => key !== \"__proto__\");\n}\n\nexport function slowObject(ir: SchemaIR & { type: \"object\" }, g: SlowGen): string {\n let code = emit`\n if(typeof ${g.input}!==\"object\"||${g.input}===null||Array.isArray(${g.input})){\n ${invalidType(g, \"object\")}\n }else{`;\n\n // Strip mode (zod's default z.object() output) rebuilds a FRESH object from\n // the declared keys, so it always writes back. Otherwise clone only when a\n // property mutates the value.\n const strip = ir.stripUnknownKeys === true;\n const needsClone =\n strip ||\n (ir.catchall !== undefined && hasMutation(ir.catchall)) ||\n Object.values(ir.properties).some(hasMutation);\n const objVar = g.temp(\"o\");\n if (!strip) {\n // Spread, not Object.assign: V8's CloneObjectIC makes `{...x}` ~25% faster\n // on the whole safeParse call for mutation-bearing schemas.\n code += needsClone ? `var ${objVar}={...${g.input}};` : `var ${objVar}=${g.input};`;\n // A loose or catchall output is the INPUT, or a spread of it — and spread\n // copies an own `__proto__` as a plain data property — where zod's fresh\n // `{}` never receives the key at all. Scrub it (see ZC_PROTO_SCRUB_DECL); a\n // strip object rebuilds and needs nothing here.\n const scrub = emitRuntimeHelper(g.ctx, \"__zcPs\", ZC_PROTO_SCRUB_DECL);\n code += `${objVar}=${scrub}(${objVar});`;\n }\n\n // Object-level refines are gated on zod's ABORT rule, not on \"did anything\n // fail\": zod parses the properties (plus the strict/catchall passes) into the\n // payload and then skips its check chain only when `util.aborted` holds — i.e.\n // when one of those issues is non-continuable. A property that failed its own\n // `min`/format check reports a CONTINUABLE issue, so the outer refine still\n // runs and both messages surface — and so does an unrecognized key, which zod\n // pushes with `continue: true`; a property that failed to parse at all\n // (`invalid_type`, a bad record key) aborts and suppresses it. Snapshot the\n // issue count before the properties so the scan covers exactly this node's\n // own parse, as zod's fresh sub-payload does.\n const refineMark = ir.checks && ir.checks.length > 0 ? g.temp(\"rm\") : \"\";\n if (refineMark) code += `var ${refineMark}=${g.issues}.length;`;\n\n const skipAbsent = new Set(ir.skipAbsentKeys ?? []);\n const suppressAbsent = new Set(ir.suppressAbsentKeys ?? []);\n const nonoptional = new Set(ir.nonoptionalKeys ?? []);\n /** Strip only: per-property output slot + whether it is always in the result. */\n const slots: { always: boolean; keyStr: string; value: string }[] = [];\n\n for (const [key, propIR] of parsedProperties(ir)) {\n const keyStr = escapeString(key);\n const propPath = extendStaticPath(g.path, key);\n // Strip validates the value read from the INPUT, held in a local, and\n // assembles the result afterwards. Reading through the half-built copy (the\n // previous shape) both cost a megamorphic access per key — the copy's map\n // changes with every key added — and diverged from zod, which parses\n // `input[key]` and so accepts a value found on the prototype.\n const propExpr = strip ? g.temp(\"sv\") : `${objVar}[${keyStr}]`;\n if (strip) {\n code += `var ${propExpr}=${g.input}[${keyStr}];`;\n slots.push({ always: outputAlwaysDefined(propIR), keyStr, value: propExpr });\n }\n const propCode = g.visit(propIR, { input: propExpr, output: propExpr, path: propPath });\n // The three absent-key rules of zod's handlePropertyResult (see ObjectIR).\n // Presence is tested on the ORIGINAL input, as zod's `key in input` is —\n // a clone would hide an inherited key.\n //\n // The two rules that test presence AFTER running the property snapshot it\n // FIRST. Running the property can create the very key the test asks about:\n // where nothing mutates, `objVar` IS `g.input`, so the write-back of a\n // property that accepted `undefined` (`o[key]=result`) adds an own `key`\n // and the guard then reads it as present. `z.looseObject({a: z.custom()})`\n // on `{}` lost its `nonoptional` issue entirely that way — the fast path\n // still rejected on `\"a\" in x`, so `safeParse` failed with an EMPTY issue\n // array — and left the caller's object carrying an `a` zod never writes.\n const dropValue = strip ? `${propExpr}=undefined;` : `delete ${objVar}[${keyStr}];`;\n if (skipAbsent.has(key)) {\n code += emit`if(${keyStr} in ${g.input}){${propCode}}`;\n } else if (suppressAbsent.has(key)) {\n // A defaulted optional-out property runs, but a failure on an absent key\n // is discarded whole: issues and whatever value it wrote before failing.\n const beforeVar = g.temp(\"ob\");\n const presentVar = g.temp(\"op\");\n code += emit`\n var ${presentVar}=${keyStr} in ${g.input};\n var ${beforeVar}=${g.issues}.length;\n ${propCode}\n if(!${presentVar}&&${g.issues}.length>${beforeVar}){\n ${g.issues}.length=${beforeVar};${dropValue}\n }`;\n } else if (nonoptional.has(key)) {\n // A required key that is absent fails as such when its schema raised\n // nothing for the `undefined` it saw. zod pushes this one without an\n // `inst`, so no schema-level message reaches it — and it returns without\n // assigning, so whatever the property made of `undefined` is dropped.\n const beforeVar = g.temp(\"ob\");\n const presentVar = g.temp(\"op\");\n code += emit`\n var ${presentVar}=${keyStr} in ${g.input};\n var ${beforeVar}=${g.issues}.length;\n ${propCode}\n if(!${presentVar}){\n ${dropValue}\n if(${g.issues}.length===${beforeVar}){\n ${invalidType(g, \"nonoptional\", { input: \"undefined\", path: propPath, codeFirst: true, useTypeMsg: false })}\n }\n }`;\n } else {\n code += propCode;\n }\n }\n\n if (strip) {\n // Assemble the result in shape order (zod's key order), taking as long a\n // LEADING run of always-present keys as possible into one object literal:\n // V8 stamps a literal out of a cached boilerplate map in a single\n // allocation, where adding keys one at a time walks a transition chain and\n // re-checks the map on every store — measured 8.1x (20 keys) / 2.8x (7\n // keys, one optional) on the whole safeParse.\n //\n // Everything after the first conditional key is appended so insertion order\n // still matches zod. The per-key test is zod's own: keep the key when the\n // parsed value is defined, or when it was present on the input at all\n // (`key in input`, prototype included — an own `k: undefined` survives).\n const literal: string[] = [];\n let appends = \"\";\n let leading = true;\n for (const slot of slots) {\n if (leading && slot.always) {\n literal.push(`${slot.keyStr}:${slot.value}`);\n continue;\n }\n leading = false;\n appends += slot.always\n ? `${objVar}[${slot.keyStr}]=${slot.value};`\n : `if(${slot.value}!==undefined||(${slot.keyStr} in ${g.input})){${objVar}[${slot.keyStr}]=${slot.value};}`;\n }\n code += `var ${objVar}={${literal.join(\",\")}};${appends}`;\n }\n\n // Strict unknown-key pass — zod's handleCatchall, byte-exact: for-in over\n // the ORIGINAL input (inherited enumerable keys count, no hasOwnProperty),\n // ALL unknown keys collected into one issue, pushed AFTER property issues\n // and before object-level refines.\n if (ir.strict) {\n const keys = Object.keys(ir.properties);\n const ukVar = g.temp(\"uk\");\n const kVar = g.temp(\"k\");\n const test = keyMembershipTest(g.ctx, keys, kVar);\n code += emit`\n var ${ukVar}=null;\n for(var ${kVar} in ${g.input}){\n if(!(${test})){(${ukVar}=${ukVar}||[]).push(${kVar});}\n }\n if(${ukVar}!==null){\n ${unrecognizedKeys(g, ukVar)}\n }`;\n }\n\n // .catchall(schema): validate every key NOT in the shape, mirroring zod's\n // handleCatchall — same bare for-in over the ORIGINAL input as the strict\n // pass (inherited enumerable keys count, no hasOwnProperty guard), each\n // issue reported at the key. Runs after the properties, as zod does. An\n // undeclared `__proto__` is skipped like zod skips it: never validated, and\n // never assigned, since `o[\"__proto__\"]=v` on the clone would replace its\n // prototype instead of adding a key. (The strict pass above still REPORTS\n // it — there it is an unknown key like any other.)\n //\n // The key's slot is BOTH the input and the output, exactly as a shape\n // property's is: a value-rewriting catchall (coerce, .trim(), a default)\n // writes through the same expression it later re-reads, so splitting them\n // makes the rewrite invisible to its own checks.\n //\n // When the catchall mutates, objVar is a clone, and each unknown key is\n // copied into it before validation — which also reproduces zod, whose fresh\n // output object gains an OWN key for every for-in key it saw, inherited ones\n // included. A non-mutating catchall writes nothing, so objVar is the input\n // and the object stays pass-through by reference.\n if (ir.catchall) {\n const keys = Object.keys(ir.properties);\n const kVar = g.temp(\"ck\");\n const test = keyMembershipTest(g.ctx, keys, kVar);\n const slot = `${objVar}[${kVar}]`;\n const seed = needsClone ? `${slot}=${g.input}[${kVar}];` : \"\";\n code += emit`\n for(var ${kVar} in ${g.input}){\n if(!(${test})&&${kVar}!==\"__proto__\"){\n ${seed}\n ${g.visit(ir.catchall, {\n input: slot,\n output: slot,\n path: extendPath(g.path, kVar),\n })}\n }\n }`;\n }\n\n // `!strip` also writes back: the scrub above may have re-pointed `objVar` at\n // a copy even where nothing else mutates.\n if (needsClone || !strip) {\n code += `${g.output}=${objVar};`;\n }\n\n // Object-level refine effects: z.object({...}).refine(fn), suppressed when the\n // parse phase aborted (see refineMark). One gate covers every effect: each of\n // them can only add a continuable `custom` issue, so zod's per-check\n // re-evaluation of `isAborted` can never flip between them.\n if (ir.checks && ir.checks.length > 0) {\n let refines = \"\";\n for (const check of ir.checks) {\n refines +=\n check.kind === \"super_refine_effect\"\n ? superRefineCheck(check, objVar, g)\n : refineCheck(check, objVar, g);\n }\n const aborted = emitRuntimeHelper(g.ctx, \"__zcAb\", ZC_AB_DECL);\n code += `if(!${aborted}(${g.issues},${refineMark})){${refines}}`;\n }\n\n code += `}\\n`;\n return code;\n}\n\n/**\n * Property entries in the order their fast-checks should be `&&`-chained:\n * cheapest first, declaration order preserved among equals (Array#sort is\n * stable). Valid input runs every conjunct whatever the order, so this costs\n * nothing on the hot path; a REJECT stops at the first false conjunct, so\n * pricing a `z.email()` behind the `kind` literal that actually discriminates\n * is what makes union probing and `.is()` misses cheap (see\n * estimateRuntimeCost). The SLOW path keeps declaration order — that one's\n * output is the issue list, whose order is part of zod parity.\n */\nfunction orderedProperties(ir: ObjectIR, g: FastGen): [string, SchemaIR][] {\n return orderByRuntimeCost(parsedProperties(ir), ([, propIR]) => propIR, g.ctx);\n}\n\n/**\n * Property/strict/refine fast-checks for an object, WITHOUT the leading\n * `typeof===object && !==null && !Array.isArray` type-guard. Returns the\n * conjunct parts (joinable with `&&`), or null if any child is fast-ineligible.\n *\n * `skipKey`, when given, omits that one property's check. Used by the\n * discriminated-union fast path (via `g.discSkipKey`): the enclosing `switch`\n * has already matched the discriminator's value, so re-checking it is redundant.\n */\nfunction fastObjectBody(ir: ObjectIR, g: FastGen, skipKey?: string): string[] | null {\n const x = g.input;\n const parts: string[] = [];\n const absentAccepts = new Set([...(ir.skipAbsentKeys ?? []), ...(ir.suppressAbsentKeys ?? [])]);\n const nonoptional = new Set(ir.nonoptionalKeys ?? []);\n\n for (const [key, propIR] of orderedProperties(ir, g)) {\n if (key === skipKey) continue;\n const keyStr = escapeString(key);\n const propCheck = g.visit(propIR, { input: `${x}[${keyStr}]` });\n if (propCheck === null) return null; // All-or-nothing\n // Presence rules keyed on the schema's `optin`/`optout` (see ObjectIR): a\n // required key must be there whatever its schema makes of `undefined`, and\n // an optional-out key on the \"optional\"/\"defaulted\" rungs is accepted when\n // it is not, whatever the property would say.\n if (nonoptional.has(key)) {\n parts.push(propCheck === \"true\" ? `${keyStr} in ${x}` : `${keyStr} in ${x}&&${propCheck}`);\n } else if (absentAccepts.has(key)) {\n parts.push(`(!(${keyStr} in ${x})||${propCheck})`);\n } else {\n parts.push(propCheck);\n }\n }\n\n // Strict unknown-key pass: hosted boolean helper (a for-in loop cannot live\n // in the && chain). Same for-in iteration as the slow path — fast/slow\n // agreement is load-bearing under the __zcFinD deferral. The membership set is\n // the FULL key list (the discriminator is a recognized key), independent of\n // skipKey, which only suppresses re-validating the discriminator's value.\n if (ir.strict) {\n const keys = Object.keys(ir.properties);\n const fnName = g.temp(\"so\");\n const test = keyMembershipTest(g.ctx, keys, \"k\");\n g.ctx.preamble.push(\n `function ${fnName}(o){for(var k in o){if(!(${test}))return false;}return true;}`,\n );\n parts.push(`${fnName}(${x})`);\n }\n\n // .catchall(schema): same hosted for-in as strict, but each unrecognized key\n // is checked against the catchall rather than rejected outright. The value is\n // hoisted into a local for the same reason fastRecord does it — repeated\n // `o[k]` computed loads are not eliminated across check boundaries.\n if (ir.catchall) {\n const kv = g.temp(\"cak\");\n const vv = g.temp(\"cav\");\n const catchallGen = g.scoped(vv);\n const valCheck = catchallGen.visit(ir.catchall, { input: vv });\n if (valCheck === null) return null;\n if (valCheck !== \"true\") {\n const fnName = g.temp(\"co\");\n const test = keyMembershipTest(g.ctx, Object.keys(ir.properties), kv);\n g.ctx.preamble.push(\n `function ${fnName}(o){${declareFastTemps(catchallGen.scope)}var ${kv},${vv};for(${kv} in o){if(!(${test})&&${kv}!==\"__proto__\"){${vv}=o[${kv}];if(!(${valCheck}))return false;}}return true;}`,\n );\n parts.push(`${fnName}(${x})`);\n }\n }\n\n // Object-level refine effects (appended last — run after property checks short-circuit)\n if (ir.checks) {\n for (const check of ir.checks) {\n if (check.kind === \"refine_effect\") {\n parts.push(`${emitEffectCallable(g.ctx, check)}(${x})`);\n } else if (check.kind === \"super_refine_effect\") {\n parts.push(superRefineFastTest(check, x, g));\n }\n }\n }\n\n return parts;\n}\n\nexport function fastObject(ir: ObjectIR, g: FastGen): string | null {\n // A strip object still gets an expression. Stripping reshapes the OUTPUT, not\n // the verdict, so this remains an exact acceptance predicate — which is what\n // `.is()` installs, and what the build path reuses to validate the subtrees it\n // passes through by reference. What it must NOT do is stand in for the parse\n // result: generateValidator withholds the `data:input` shortcut whenever the\n // schema rebuilds (see rebuildsOutput).\n const x = g.input;\n const body = fastObjectBody(ir, g, g.discSkipKey);\n if (body === null) return null;\n // Discriminated-union option: the enclosing switch (and the caller's guard)\n // already established object-ness and the discriminator value, so emit only\n // the remaining checks — no leading type-guard. An option with nothing left\n // to check accepts unconditionally (\"true\").\n if (g.discSkipKey !== undefined) {\n return body.length > 0 ? body.join(\"&&\") : \"true\";\n }\n return [`typeof ${x}===\"object\"`, `${x}!==null`, `!Array.isArray(${x})`, ...body].join(\"&&\");\n}\n"],"mappings":";;;;;;;;;;;;;;;;AA4BA,SAAgB,iBAAiB,IAAoC;CACnE,OAAO,OAAO,QAAQ,GAAG,UAAU,CAAC,CAAC,QAAQ,CAAC,SAAS,QAAQ,WAAW;AAC5E;AAEA,SAAgB,WAAW,IAAmC,GAAoB;CAChF,IAAI,OAAO,IAAI;gBACD,EAAE,MAAM,eAAe,EAAE,MAAM,yBAAyB,EAAE,MAAM;QACxE,YAAY,GAAG,QAAQ,EAAE;;CAM/B,MAAM,QAAQ,GAAG,qBAAqB;CACtC,MAAM,aACJ,SACC,GAAG,aAAa,KAAA,KAAa,YAAY,GAAG,QAAQ,KACrD,OAAO,OAAO,GAAG,UAAU,CAAC,CAAC,KAAK,WAAW;CAC/C,MAAM,SAAS,EAAE,KAAK,GAAG;CACzB,IAAI,CAAC,OAAO;EAGV,QAAQ,aAAa,OAAO,OAAO,OAAO,EAAE,MAAM,MAAM,OAAO,OAAO,GAAG,EAAE,MAAM;EAKjF,MAAM,QAAQ,kBAAkB,EAAE,KAAK,UAAU,mBAAmB;EACpE,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO;CACvC;CAYA,MAAM,aAAa,GAAG,UAAU,GAAG,OAAO,SAAS,IAAI,EAAE,KAAK,IAAI,IAAI;CACtE,IAAI,YAAY,QAAQ,OAAO,WAAW,GAAG,EAAE,OAAO;CAEtD,MAAM,aAAa,IAAI,IAAI,GAAG,kBAAkB,CAAC,CAAC;CAClD,MAAM,iBAAiB,IAAI,IAAI,GAAG,sBAAsB,CAAC,CAAC;CAC1D,MAAM,cAAc,IAAI,IAAI,GAAG,mBAAmB,CAAC,CAAC;;CAEpD,MAAM,QAA8D,CAAC;CAErE,KAAK,MAAM,CAAC,KAAK,WAAW,iBAAiB,EAAE,GAAG;EAChD,MAAM,SAAS,aAAa,GAAG;EAC/B,MAAM,WAAW,iBAAiB,EAAE,MAAM,GAAG;EAM7C,MAAM,WAAW,QAAQ,EAAE,KAAK,IAAI,IAAI,GAAG,OAAO,GAAG,OAAO;EAC5D,IAAI,OAAO;GACT,QAAQ,OAAO,SAAS,GAAG,EAAE,MAAM,GAAG,OAAO;GAC7C,MAAM,KAAK;IAAE,QAAQ,oBAAoB,MAAM;IAAG;IAAQ,OAAO;GAAS,CAAC;EAC7E;EACA,MAAM,WAAW,EAAE,MAAM,QAAQ;GAAE,OAAO;GAAU,QAAQ;GAAU,MAAM;EAAS,CAAC;EAatF,MAAM,YAAY,QAAQ,GAAG,SAAS,eAAe,UAAU,OAAO,GAAG,OAAO;EAChF,IAAI,WAAW,IAAI,GAAG,GACpB,QAAQ,IAAI,MAAM,OAAO,MAAM,EAAE,MAAM,IAAI,SAAS;OAC/C,IAAI,eAAe,IAAI,GAAG,GAAG;GAGlC,MAAM,YAAY,EAAE,KAAK,IAAI;GAC7B,MAAM,aAAa,EAAE,KAAK,IAAI;GAC9B,QAAQ,IAAI;cACJ,WAAW,GAAG,OAAO,MAAM,EAAE,MAAM;cACnC,UAAU,GAAG,EAAE,OAAO;UAC1B,SAAS;cACL,WAAW,IAAI,EAAE,OAAO,UAAU,UAAU;YAC9C,EAAE,OAAO,UAAU,UAAU,GAAG,UAAU;;EAElD,OAAO,IAAI,YAAY,IAAI,GAAG,GAAG;GAK/B,MAAM,YAAY,EAAE,KAAK,IAAI;GAC7B,MAAM,aAAa,EAAE,KAAK,IAAI;GAC9B,QAAQ,IAAI;cACJ,WAAW,GAAG,OAAO,MAAM,EAAE,MAAM;cACnC,UAAU,GAAG,EAAE,OAAO;UAC1B,SAAS;cACL,WAAW;YACb,UAAU;eACP,EAAE,OAAO,YAAY,UAAU;cAChC,YAAY,GAAG,eAAe;IAAE,OAAO;IAAa,MAAM;IAAU,WAAW;IAAM,YAAY;GAAM,CAAC,EAAE;;;EAGpH,OACE,QAAQ;CAEZ;CAEA,IAAI,OAAO;EAYT,MAAM,UAAoB,CAAC;EAC3B,IAAI,UAAU;EACd,IAAI,UAAU;EACd,KAAK,MAAM,QAAQ,OAAO;GACxB,IAAI,WAAW,KAAK,QAAQ;IAC1B,QAAQ,KAAK,GAAG,KAAK,OAAO,GAAG,KAAK,OAAO;IAC3C;GACF;GACA,UAAU;GACV,WAAW,KAAK,SACZ,GAAG,OAAO,GAAG,KAAK,OAAO,IAAI,KAAK,MAAM,KACxC,MAAM,KAAK,MAAM,iBAAiB,KAAK,OAAO,MAAM,EAAE,MAAM,KAAK,OAAO,GAAG,KAAK,OAAO,IAAI,KAAK,MAAM;EAC5G;EACA,QAAQ,OAAO,OAAO,IAAI,QAAQ,KAAK,GAAG,EAAE,IAAI;CAClD;CAMA,IAAI,GAAG,QAAQ;EACb,MAAM,OAAO,OAAO,KAAK,GAAG,UAAU;EACtC,MAAM,QAAQ,EAAE,KAAK,IAAI;EACzB,MAAM,OAAO,EAAE,KAAK,GAAG;EACvB,MAAM,OAAO,kBAAkB,EAAE,KAAK,MAAM,IAAI;EAChD,QAAQ,IAAI;YACJ,MAAM;gBACF,KAAK,MAAM,EAAE,MAAM;eACpB,KAAK,MAAM,MAAM,GAAG,MAAM,aAAa,KAAK;;WAEhD,MAAM;UACP,iBAAiB,GAAG,KAAK,EAAE;;CAEnC;CAqBA,IAAI,GAAG,UAAU;EACf,MAAM,OAAO,OAAO,KAAK,GAAG,UAAU;EACtC,MAAM,OAAO,EAAE,KAAK,IAAI;EACxB,MAAM,OAAO,kBAAkB,EAAE,KAAK,MAAM,IAAI;EAChD,MAAM,OAAO,GAAG,OAAO,GAAG,KAAK;EAC/B,MAAM,OAAO,aAAa,GAAG,KAAK,GAAG,EAAE,MAAM,GAAG,KAAK,MAAM;EAC3D,QAAQ,IAAI;gBACA,KAAK,MAAM,EAAE,MAAM;eACpB,KAAK,KAAK,KAAK;YAClB,KAAK;YACL,EAAE,MAAM,GAAG,UAAU;GACrB,OAAO;GACP,QAAQ;GACR,MAAM,WAAW,EAAE,MAAM,IAAI;EAC/B,CAAC,EAAE;;;CAGX;CAIA,IAAI,cAAc,CAAC,OACjB,QAAQ,GAAG,EAAE,OAAO,GAAG,OAAO;CAOhC,IAAI,GAAG,UAAU,GAAG,OAAO,SAAS,GAAG;EACrC,IAAI,UAAU;EACd,KAAK,MAAM,SAAS,GAAG,QACrB,WACE,MAAM,SAAS,wBACX,iBAAiB,OAAO,QAAQ,CAAC,IACjC,YAAY,OAAO,QAAQ,CAAC;EAEpC,MAAM,UAAU,kBAAkB,EAAE,KAAK,UAAU,UAAU;EAC7D,QAAQ,OAAO,QAAQ,GAAG,EAAE,OAAO,GAAG,WAAW,KAAK,QAAQ;CAChE;CAEA,QAAQ;CACR,OAAO;AACT;;;;;;;;;;;AAYA,SAAS,kBAAkB,IAAc,GAAkC;CACzE,OAAO,mBAAmB,iBAAiB,EAAE,IAAI,GAAG,YAAY,QAAQ,EAAE,GAAG;AAC/E;;;;;;;;;;AAWA,SAAS,eAAe,IAAc,GAAY,SAAmC;CACnF,MAAM,IAAI,EAAE;CACZ,MAAM,QAAkB,CAAC;CACzB,MAAM,gCAAgB,IAAI,IAAI,CAAC,GAAI,GAAG,kBAAkB,CAAC,GAAI,GAAI,GAAG,sBAAsB,CAAC,CAAE,CAAC;CAC9F,MAAM,cAAc,IAAI,IAAI,GAAG,mBAAmB,CAAC,CAAC;CAEpD,KAAK,MAAM,CAAC,KAAK,WAAW,kBAAkB,IAAI,CAAC,GAAG;EACpD,IAAI,QAAQ,SAAS;EACrB,MAAM,SAAS,aAAa,GAAG;EAC/B,MAAM,YAAY,EAAE,MAAM,QAAQ,EAAE,OAAO,GAAG,EAAE,GAAG,OAAO,GAAG,CAAC;EAC9D,IAAI,cAAc,MAAM,OAAO;EAK/B,IAAI,YAAY,IAAI,GAAG,GACrB,MAAM,KAAK,cAAc,SAAS,GAAG,OAAO,MAAM,MAAM,GAAG,OAAO,MAAM,EAAE,IAAI,WAAW;OACpF,IAAI,cAAc,IAAI,GAAG,GAC9B,MAAM,KAAK,MAAM,OAAO,MAAM,EAAE,KAAK,UAAU,EAAE;OAEjD,MAAM,KAAK,SAAS;CAExB;CAOA,IAAI,GAAG,QAAQ;EACb,MAAM,OAAO,OAAO,KAAK,GAAG,UAAU;EACtC,MAAM,SAAS,EAAE,KAAK,IAAI;EAC1B,MAAM,OAAO,kBAAkB,EAAE,KAAK,MAAM,GAAG;EAC/C,EAAE,IAAI,SAAS,KACb,YAAY,OAAO,2BAA2B,KAAK,8BACrD;EACA,MAAM,KAAK,GAAG,OAAO,GAAG,EAAE,EAAE;CAC9B;CAMA,IAAI,GAAG,UAAU;EACf,MAAM,KAAK,EAAE,KAAK,KAAK;EACvB,MAAM,KAAK,EAAE,KAAK,KAAK;EACvB,MAAM,cAAc,EAAE,OAAO,EAAE;EAC/B,MAAM,WAAW,YAAY,MAAM,GAAG,UAAU,EAAE,OAAO,GAAG,CAAC;EAC7D,IAAI,aAAa,MAAM,OAAO;EAC9B,IAAI,aAAa,QAAQ;GACvB,MAAM,SAAS,EAAE,KAAK,IAAI;GAC1B,MAAM,OAAO,kBAAkB,EAAE,KAAK,OAAO,KAAK,GAAG,UAAU,GAAG,EAAE;GACpE,EAAE,IAAI,SAAS,KACb,YAAY,OAAO,MAAM,iBAAiB,YAAY,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,OAAO,GAAG,cAAc,KAAK,KAAK,GAAG,kBAAkB,GAAG,KAAK,GAAG,SAAS,SAAS,+BAClK;GACA,MAAM,KAAK,GAAG,OAAO,GAAG,EAAE,EAAE;EAC9B;CACF;CAGA,IAAI,GAAG,QACA;OAAA,MAAM,SAAS,GAAG,QACrB,IAAI,MAAM,SAAS,iBACjB,MAAM,KAAK,GAAG,mBAAmB,EAAE,KAAK,KAAK,EAAE,GAAG,EAAE,EAAE;OACjD,IAAI,MAAM,SAAS,uBACxB,MAAM,KAAK,oBAAoB,OAAO,GAAG,CAAC,CAAC;CAAA;CAKjD,OAAO;AACT;AAEA,SAAgB,WAAW,IAAc,GAA2B;CAOlE,MAAM,IAAI,EAAE;CACZ,MAAM,OAAO,eAAe,IAAI,GAAG,EAAE,WAAW;CAChD,IAAI,SAAS,MAAM,OAAO;CAK1B,IAAI,EAAE,gBAAgB,KAAA,GACpB,OAAO,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI;CAE7C,OAAO;EAAC,UAAU,EAAE;EAAc,GAAG,EAAE;EAAU,kBAAkB,EAAE;EAAI,GAAG;CAAI,CAAC,CAAC,KAAK,IAAI;AAC7F"}
@@ -1 +1 @@
1
- {"version":3,"file":"readonly.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/readonly.ts"],"mappings":";;;iBAGgB,aAAa,IAAI;EAAa;GAAoB,GAAG;iBAiBrD,aAAa,IAAI,YAAY,GAAG"}
1
+ {"version":3,"file":"readonly.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/readonly.ts"],"mappings":";;;iBAGgB,aAAa,IAAI;EAAa;GAAoB,GAAG;iBAkBrD,aAAa,IAAI,YAAY,GAAG"}
@@ -1 +1 @@
1
- {"version":3,"file":"readonly.js","names":[],"sources":["../../../../src/core/codegen/schemas/readonly.ts"],"sourcesContent":["import type { ReadonlyIR, SchemaIR } from \"../../types.js\";\nimport type { FastGen, SlowGen } from \"../context.js\";\n\nexport function slowReadonly(ir: SchemaIR & { type: \"readonly\" }, g: SlowGen): string {\n // Pass-through wrapper: forward the union abort flag (see slowOptional).\n const inner = g.visit(ir.inner, { aborted: g.aborted });\n if (ir.freeze !== true) return inner;\n // Guarded because the walk only reassigns `output` to its rebuilt object once\n // the type check passed — a failed one leaves the CALLER's input sitting\n // there, and freezing that is the side effect zod avoids.\n //\n // The mark is RELATIVE to the issues already collected, never against zero: a\n // `.catch()` above lets the parse succeed with a sibling's issues still in the\n // array, and an absolute test would silently skip the freeze there while zod\n // still applied it. Same relative-mark discipline as slowObject's refineMark,\n // slowPipe's lenVar and slowEffect's beforeVar.\n const mark = g.temp(\"rf\");\n return `var ${mark}=${g.issues}.length;\\n${inner}if(${g.issues}.length===${mark}){${g.output}=Object.freeze(${g.output});}\\n`;\n}\n\nexport function fastReadonly(ir: ReadonlyIR, g: FastGen): string | null {\n return g.visit(ir.inner);\n}\n"],"mappings":";AAGA,SAAgB,aAAa,IAAqC,GAAoB;CAEpF,MAAM,QAAQ,EAAE,MAAM,GAAG,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAC;CACtD,IAAI,GAAG,WAAW,MAAM,OAAO;CAU/B,MAAM,OAAO,EAAE,KAAK,IAAI;CACxB,OAAO,OAAO,KAAK,GAAG,EAAE,OAAO,YAAY,MAAM,KAAK,EAAE,OAAO,YAAY,KAAK,IAAI,EAAE,OAAO,iBAAiB,EAAE,OAAO;AACzH;AAEA,SAAgB,aAAa,IAAgB,GAA2B;CACtE,OAAO,EAAE,MAAM,GAAG,KAAK;AACzB"}
1
+ {"version":3,"file":"readonly.js","names":[],"sources":["../../../../src/core/codegen/schemas/readonly.ts"],"sourcesContent":["import type { ReadonlyIR, SchemaIR } from \"../../types.js\";\nimport type { FastGen, SlowGen } from \"../context.js\";\n\nexport function slowReadonly(ir: SchemaIR & { type: \"readonly\" }, g: SlowGen): string {\n // Pass-through wrapper: forward the union abort flag (see slowOptional).\n const inner = g.visit(ir.inner, { aborted: g.aborted });\n if (ir.freeze !== true) return inner;\n // Guarded because the walk only reassigns `output` to its rebuilt object once\n // the type check passed — a failed one leaves the CALLER's input sitting\n // there, and freezing that is the side effect zod avoids.\n //\n // The mark is RELATIVE to the issues already collected, never against zero:\n // zod runs every node on a fresh payload and freezes whenever THAT node's\n // parse is clean, while the compiled walk shares one issues array across\n // siblings, so an absolute test would skip the freeze as soon as an earlier\n // sibling had failed. Same relative-mark discipline as slowObject's\n // refineMark, slowPipe's lenVar and slowEffect's beforeVar.\n const mark = g.temp(\"rf\");\n return `var ${mark}=${g.issues}.length;\\n${inner}if(${g.issues}.length===${mark}){${g.output}=Object.freeze(${g.output});}\\n`;\n}\n\nexport function fastReadonly(ir: ReadonlyIR, g: FastGen): string | null {\n return g.visit(ir.inner);\n}\n"],"mappings":";AAGA,SAAgB,aAAa,IAAqC,GAAoB;CAEpF,MAAM,QAAQ,EAAE,MAAM,GAAG,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAC;CACtD,IAAI,GAAG,WAAW,MAAM,OAAO;CAW/B,MAAM,OAAO,EAAE,KAAK,IAAI;CACxB,OAAO,OAAO,KAAK,GAAG,EAAE,OAAO,YAAY,MAAM,KAAK,EAAE,OAAO,YAAY,KAAK,IAAI,EAAE,OAAO,iBAAiB,EAAE,OAAO;AACzH;AAEA,SAAgB,aAAa,IAAgB,GAA2B;CACtE,OAAO,EAAE,MAAM,GAAG,KAAK;AACzB"}
@@ -1 +1 @@
1
- {"version":3,"file":"record.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/record.ts"],"mappings":";;;iBA0BgB,WAAW,IAAI;EAAa;GAAkB,GAAG;iBA8CjD,WAAW,IAAI,UAAU,GAAG"}
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,7 +1,7 @@
1
1
  import { declareFastTemps, emitRuntimeHelper, extendPath, hasMutation } from "../context.js";
2
2
  import { emit } from "../emit.js";
3
- import { invalidType } from "../emit-issue.js";
4
- import { ZC_FZ_DECL, ZC_HOP_DECL, ZC_PLAIN_DECL } from "../issue-decls.js";
3
+ import { invalidType, unrecognizedKeys } from "../emit-issue.js";
4
+ import { ZC_FZ_DECL, ZC_HOP_DECL, ZC_PLAIN_DECL, ZC_PROTO_SCRUB_DECL } from "../issue-decls.js";
5
5
  //#region src/core/codegen/schemas/record.ts
6
6
  /**
7
7
  * `$ZodRecord` gates on `util.isPlainObject`, NOT the looser `util.isObject`
@@ -26,13 +26,17 @@ function slowRecord(ir, g) {
26
26
  ${invalidType(g, "record")}
27
27
  }else{`;
28
28
  if (hasMutation(ir.valueType)) code += `${g.output}={...${g.input}};`;
29
+ code += `${g.output}=${emitRuntimeHelper(g.ctx, "__zcPs", ZC_PROTO_SCRUB_DECL)}(${g.input});`;
29
30
  const keyVar = g.temp("rkey");
30
31
  const keyIssuesVar = g.temp("rki");
31
32
  const keyPath = extendPath(g.path, keyVar);
32
33
  const valExpr = `${g.input}[${keyVar}]`;
33
34
  const hop = emitRuntimeHelper(g.ctx, "__zcHop", ZC_HOP_DECL);
34
35
  const fz = emitRuntimeHelper(g.ctx, "__zcFz", ZC_FZ_DECL);
36
+ const unrecognizedVar = ir.enumerableKeys ? g.temp("ruk") : null;
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});`;
35
38
  code += emit`
39
+ ${unrecognizedVar === null ? "" : `var ${unrecognizedVar}=null;`}
36
40
  for(var ${keyVar} in ${g.input}){
37
41
  if(!${hop}.call(${g.input},${keyVar}))continue;
38
42
  ${PROTO_SKIP(keyVar)}
@@ -44,7 +48,7 @@ function slowRecord(ir, g) {
44
48
  issues: keyIssuesVar
45
49
  })}
46
50
  if(${keyIssuesVar}.length>0){
47
- ${g.issues}.push({code:"invalid_key",origin:"record",issues:${fz}(${keyIssuesVar}),input:${keyVar},path:${keyPath}${g.typeMsg === void 0 ? "" : `,message:${JSON.stringify(g.typeMsg)}`}});
51
+ ${onKeyFailure}
48
52
  }else{
49
53
  ${g.visit(ir.valueType, {
50
54
  input: valExpr,
@@ -53,6 +57,7 @@ function slowRecord(ir, g) {
53
57
  })}
54
58
  }
55
59
  }
60
+ ${unrecognizedVar === null ? "" : `if(${unrecognizedVar}!==null){${unrecognizedKeys(g, unrecognizedVar)}}`}
56
61
  }`;
57
62
  return `${code}\n`;
58
63
  }
@@ -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 } from \"../emit-issue.js\";\nimport { ZC_FZ_DECL, ZC_HOP_DECL, ZC_PLAIN_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\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 code += emit`\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 ${g.issues}.push({code:\"invalid_key\",origin:\"record\",issues:${fz}(${keyIssuesVar}),input:${keyVar},path:${keyPath}${g.typeMsg === undefined ? \"\" : `,message:${JSON.stringify(g.typeMsg)}`}});\n }else{\n ${g.visit(ir.valueType, { input: valExpr, output: valExpr, path: keyPath })}\n }\n }\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":";;;;;;;;;;AAYA,SAAS,gBAAgB,GAAsB,OAAuB;CACpE,OAAO,GAAG,kBAAkB,EAAE,KAAK,aAAa,aAAa,EAAE,GAAG,MAAM;AAC1E;;;;;;;;;AAUA,MAAM,cAAc,WAA2B,MAAM,OAAO;AAE5D,SAAgB,WAAW,IAAmC,GAAoB;CAChF,IAAI,OAAO,IAAI;UACP,gBAAgB,GAAG,EAAE,KAAK,EAAE;QAC9B,YAAY,GAAG,QAAQ,EAAE;;CAG/B,IAAI,YAAY,GAAG,SAAS,GAC1B,QAAQ,GAAG,EAAE,OAAO,OAAO,EAAE,MAAM;CAGrC,MAAM,SAAS,EAAE,KAAK,MAAM;CAC5B,MAAM,eAAe,EAAE,KAAK,KAAK;CACjC,MAAM,UAAU,WAAW,EAAE,MAAM,MAAM;CACzC,MAAM,UAAU,GAAG,EAAE,MAAM,GAAG,OAAO;CAQrC,MAAM,MAAM,kBAAkB,EAAE,KAAK,WAAW,WAAW;CAO3D,MAAM,KAAK,kBAAkB,EAAE,KAAK,UAAU,UAAU;CAExD,QAAQ,IAAI;cACA,OAAO,MAAM,EAAE,MAAM;YACvB,IAAI,QAAQ,EAAE,MAAM,GAAG,OAAO;QAClC,WAAW,MAAM,EAAE;YACf,aAAa;QACjB,EAAE,MAAM,GAAG,SAAS;EAAE,OAAO;EAAQ,QAAQ;EAAQ,MAAM;EAAM,QAAQ;CAAa,CAAC,EAAE;WACtF,aAAa;UACd,EAAE,OAAO,mDAAmD,GAAG,GAAG,aAAa,UAAU,OAAO,QAAQ,UAAU,EAAE,YAAY,KAAA,IAAY,KAAK,YAAY,KAAK,UAAU,EAAE,OAAO,IAAI;;UAEzL,EAAE,MAAM,GAAG,WAAW;EAAE,OAAO;EAAS,QAAQ;EAAS,MAAM;CAAQ,CAAC,EAAE;;;;CAIlF,OAAO,GAAG,KAAK;AACjB;AAEA,SAAgB,WAAW,IAAc,GAA2B;CAClE,MAAM,IAAI,EAAE;CACZ,MAAM,QAAkB,CAAC,gBAAgB,GAAG,CAAC,CAAC;CAI9C,MAAM,iBACJ,GAAG,QAAQ,SAAS,YAAY,GAAG,QAAQ,OAAO,WAAW,KAAK,GAAG,QAAQ,WAAW;CAE1F,MAAM,KAAK,EAAE,KAAK,IAAI;CACtB,MAAM,KAAK,EAAE,KAAK,IAAI;CAGtB,MAAM,OAAO,EAAE,OAAO,EAAE;CACxB,MAAM,WAAW,iBAAiB,SAAS,KAAK,MAAM,GAAG,SAAS,EAAE,OAAO,GAAG,CAAC;CAI/E,MAAM,WAAW,KAAK,MAAM,GAAG,WAAW,EAAE,OAAO,GAAG,CAAC;CACvD,IAAI,aAAa,QAAQ,aAAa,MAAM,OAAO;CAEnD,MAAM,aAAuB,CAAC;CAC9B,IAAI,aAAa,QAAQ,WAAW,KAAK,QAAQ;CACjD,IAAI,aAAa,QAAQ,WAAW,KAAK,QAAQ;CAEjD,IAAI,WAAW,SAAS,GAAG;EACzB,MAAM,aAAa,EAAE,KAAK,IAAI;EAC9B,MAAM,YAAY,aAAa,SAAS,GAAG,GAAG,KAAK,GAAG,MAAM;EAO5D,MAAM,MAAM,kBAAkB,EAAE,KAAK,WAAW,WAAW;EAC3D,EAAE,IAAI,SAAS,KACb,YAAY,WAAW,MAAM,iBAAiB,KAAK,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,OAAO,GAAG,SAAS,WAAW,EAAE,EAAE,KAAK,IAAI,UAAU,GAAG,KAAK,UAAU,OAAO,WAAW,KAAK,IAAI,EAAE,iCAC/K;EACA,MAAM,KAAK,GAAG,WAAW,GAAG,EAAE,EAAE;CAClC;CAEA,OAAO,MAAM,KAAK,IAAI;AACxB"}
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,6 +1,46 @@
1
1
  import { CheckOrEffectIR, FileCheckIR, SetCheckIR } from "../../types.js";
2
- import { SlowGen } from "../context.js";
2
+ import { CodeGenContext, SlowGen } from "../context.js";
3
3
  //#region src/core/codegen/schemas/sizeable.d.ts
4
+ /**
5
+ * String length tests over Unicode CODE POINTS, gated so the count is only
6
+ * paid for when the UTF-16 unit count leaves the verdict in doubt.
7
+ *
8
+ * zod measures `z.string().min()/.max()/.length()` in code points
9
+ * (`util.codePointLength`), so `"😀"` has length 1, and it computes that count
10
+ * only when the unit count could sit on the other side of the bound. A code
11
+ * point is one or two units, so `units/2 <= codePoints <= units` — which pins
12
+ * the doubtful band tighter than zod's own gate without changing a verdict:
13
+ * `min(N)` is settled outside `N <= units < 2*ceil(N)-1` (so `min(1)` stays a
14
+ * plain `length>=1`; see {@link certainlyAtLeast} for the fractional case),
15
+ * `max(N)` outside `N < units <= 2N`, and `length(N)` outside `N <= units <=
16
+ * 2N`. Every test leads with the plain unit comparison, so an ASCII string of
17
+ * ordinary length never reaches the helper.
18
+ *
19
+ * `mayNotBeString` adds the `typeof` guard zod applies (`typeof input ===
20
+ * "string" && …`) for a site whose input is not statically a string — a
21
+ * length check firing on the wrong type through its `when` predicate; an array
22
+ * measures elements, never code points. Only the failure-direction forms take
23
+ * it: `min`/`max`/`equals` are reached solely from `fastStringCheck`, whose
24
+ * input is statically a string.
25
+ */
26
+ declare const stringLengthTests: {
27
+ min(x: string, min: number, ctx: CodeGenContext): string;
28
+ max(x: string, max: number, ctx: CodeGenContext): string;
29
+ equals(x: string, length: number, ctx: CodeGenContext): string;
30
+ /**
31
+ * The FAILING condition of `min`/`max`, with the negation pushed inward so a
32
+ * check with no doubtful band emits the plain comparison the slow path always
33
+ * used (`x.length<1`, not `!(x.length>=1)`).
34
+ */
35
+ minFails(x: string, min: number, ctx: CodeGenContext, mayNotBeString?: boolean): string;
36
+ maxFails(x: string, max: number, ctx: CodeGenContext, mayNotBeString?: boolean): string;
37
+ /**
38
+ * The measured length zod compares in `$ZodCheckLengthEquals` — code points
39
+ * inside the doubtful band, units outside it — for the slow path, which has
40
+ * to know WHICH side of the bound the value fell on.
41
+ */
42
+ measure(x: string, length: number, ctx: CodeGenContext, mayNotBeString?: boolean): string;
43
+ };
4
44
  /**
5
45
  * Length/size checks re-emitted for the branch where the node's TYPE CHECK
6
46
  * ALREADY FAILED.
@@ -30,8 +70,14 @@ import { SlowGen } from "../context.js";
30
70
  *
31
71
  * `origin` is computed at runtime here (see ZC_LENGTH_ORIGIN_DECL), because the
32
72
  * value that reached this branch is by definition not the schema's own type.
73
+ *
74
+ * `mayBeString` is set by a non-string node whose length checks can fire on a
75
+ * string (`z.array(…).min(2)` over `"😀"`): zod then measures it in code
76
+ * points, so those tests take the guarded form of {@link stringLengthTests}. A
77
+ * string node's own failure branch never holds a string and keeps the plain
78
+ * unit comparison.
33
79
  */
34
- declare function whenGatedSizeChecks(checks: readonly (CheckOrEffectIR | SetCheckIR | FileCheckIR)[], g: SlowGen, family: "length" | "size"): string;
80
+ declare function whenGatedSizeChecks(checks: readonly (CheckOrEffectIR | SetCheckIR | FileCheckIR)[], g: SlowGen, family: "length" | "size", mayBeString?: boolean): string;
35
81
  //#endregion
36
- export { whenGatedSizeChecks };
82
+ export { stringLengthTests, whenGatedSizeChecks };
37
83
  //# sourceMappingURL=sizeable.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sizeable.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/sizeable.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAqCgB,oBACd,kBAAkB,kBAAkB,aAAa,gBACjD,GAAG,SACH"}
1
+ {"version":3,"file":"sizeable.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/sizeable.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;cA4Ca;EACJ,IAAA,WAAM,aAAa,KAAO;EAM1B,IAAA,WAAM,aAAa,KAAO;EAMvB,OAAA,WAAM,gBAAgB,KAAO;;;;;;EAe3B,SAAA,WAAM,aAAa,KAAO,gBAAc;EAOxC,SAAA,WAAM,aAAa,KAAO,gBAAc;;;;;;EAYzC,QAAA,WAAM,gBAAgB,KAAO,gBAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA8CxC,oBACd,kBAAkB,kBAAkB,aAAa,gBACjD,GAAG,SACH,2BACA"}
@@ -1,9 +1,94 @@
1
1
  import { emitRuntimeHelper } from "../context.js";
2
2
  import { emit } from "../emit.js";
3
3
  import { tooBig, tooSmall } from "../emit-issue.js";
4
- import { ZC_LENGTH_ORIGIN_DECL, ZC_SIZE_ORIGIN_DECL } from "../issue-decls.js";
4
+ import { ZC_CPL_DECL, ZC_LENGTH_ORIGIN_DECL, ZC_SIZE_ORIGIN_DECL } from "../issue-decls.js";
5
5
  //#region src/core/codegen/schemas/sizeable.ts
6
6
  /**
7
+ * The UTF-16 unit count from which a string is CERTAINLY at least `min` code
8
+ * points, so the count can be skipped.
9
+ *
10
+ * A code point is one or two units, so `codePoints >= ceil(units / 2)`, and
11
+ * that ceiling is an integer: `ceil(units/2) >= min` holds exactly when
12
+ * `ceil(units/2) >= Math.ceil(min)`, i.e. `units >= 2 * ceil(min) - 1`.
13
+ * Rounding `min` up is what makes a FRACTIONAL bound safe — `min(2.5)` is
14
+ * settled at 5 units, not the 4 that `2 * min - 1` would have claimed, where
15
+ * two astral characters are only 2 code points and zod reports `too_small`.
16
+ */
17
+ function certainlyAtLeast(min) {
18
+ return 2 * Math.ceil(min) - 1;
19
+ }
20
+ /**
21
+ * String length tests over Unicode CODE POINTS, gated so the count is only
22
+ * paid for when the UTF-16 unit count leaves the verdict in doubt.
23
+ *
24
+ * zod measures `z.string().min()/.max()/.length()` in code points
25
+ * (`util.codePointLength`), so `"😀"` has length 1, and it computes that count
26
+ * only when the unit count could sit on the other side of the bound. A code
27
+ * point is one or two units, so `units/2 <= codePoints <= units` — which pins
28
+ * the doubtful band tighter than zod's own gate without changing a verdict:
29
+ * `min(N)` is settled outside `N <= units < 2*ceil(N)-1` (so `min(1)` stays a
30
+ * plain `length>=1`; see {@link certainlyAtLeast} for the fractional case),
31
+ * `max(N)` outside `N < units <= 2N`, and `length(N)` outside `N <= units <=
32
+ * 2N`. Every test leads with the plain unit comparison, so an ASCII string of
33
+ * ordinary length never reaches the helper.
34
+ *
35
+ * `mayNotBeString` adds the `typeof` guard zod applies (`typeof input ===
36
+ * "string" && …`) for a site whose input is not statically a string — a
37
+ * length check firing on the wrong type through its `when` predicate; an array
38
+ * measures elements, never code points. Only the failure-direction forms take
39
+ * it: `min`/`max`/`equals` are reached solely from `fastStringCheck`, whose
40
+ * input is statically a string.
41
+ */
42
+ const stringLengthTests = {
43
+ min(x, min, ctx) {
44
+ const units = `${x}.length>=${min}`;
45
+ if (min <= 1) return units;
46
+ const cpl = emitRuntimeHelper(ctx, "__zcCpl", ZC_CPL_DECL);
47
+ return `${units}&&(${x}.length>=${certainlyAtLeast(min)}||${cpl}(${x})>=${min})`;
48
+ },
49
+ max(x, max, ctx) {
50
+ const units = `${x}.length<=${max}`;
51
+ if (max <= 0) return units;
52
+ const cpl = emitRuntimeHelper(ctx, "__zcCpl", ZC_CPL_DECL);
53
+ return `(${units}||(${x}.length<=${2 * max}&&${cpl}(${x})<=${max}))`;
54
+ },
55
+ equals(x, length, ctx) {
56
+ if (length <= 0) return `${x}.length===${length}`;
57
+ const cpl = emitRuntimeHelper(ctx, "__zcCpl", ZC_CPL_DECL);
58
+ if (length === 1) return `(${x}.length===1||(${x}.length===2&&${cpl}(${x})===1))`;
59
+ return `(${x}.length>=${length}&&${x}.length<=${2 * length}&&${cpl}(${x})===${length})`;
60
+ },
61
+ /**
62
+ * The FAILING condition of `min`/`max`, with the negation pushed inward so a
63
+ * check with no doubtful band emits the plain comparison the slow path always
64
+ * used (`x.length<1`, not `!(x.length>=1)`).
65
+ */
66
+ minFails(x, min, ctx, mayNotBeString = false) {
67
+ const units = `${x}.length<${min}`;
68
+ if (min <= 1) return units;
69
+ const cpl = emitRuntimeHelper(ctx, "__zcCpl", ZC_CPL_DECL);
70
+ const guard = mayNotBeString ? `typeof ${x}==="string"&&` : "";
71
+ return `${units}||(${x}.length<${certainlyAtLeast(min)}&&${guard}${cpl}(${x})<${min})`;
72
+ },
73
+ maxFails(x, max, ctx, mayNotBeString = false) {
74
+ const units = `${x}.length>${max}`;
75
+ if (max <= 0) return units;
76
+ const cpl = emitRuntimeHelper(ctx, "__zcCpl", ZC_CPL_DECL);
77
+ const guard = mayNotBeString ? `typeof ${x}!=="string"||` : "";
78
+ return `${units}&&(${x}.length>${2 * max}||${guard}${cpl}(${x})>${max})`;
79
+ },
80
+ /**
81
+ * The measured length zod compares in `$ZodCheckLengthEquals` — code points
82
+ * inside the doubtful band, units outside it — for the slow path, which has
83
+ * to know WHICH side of the bound the value fell on.
84
+ */
85
+ measure(x, length, ctx, mayNotBeString = false) {
86
+ if (length <= 0) return `${x}.length`;
87
+ const cpl = emitRuntimeHelper(ctx, "__zcCpl", ZC_CPL_DECL);
88
+ return `(${mayNotBeString ? `typeof ${x}==="string"&&` : ""}${length === 1 ? `${x}.length===2` : `${x}.length>=${length}&&${x}.length<=${2 * length}`}?${cpl}(${x}):${x}.length)`;
89
+ }
90
+ };
91
+ /**
7
92
  * Length/size checks re-emitted for the branch where the node's TYPE CHECK
8
93
  * ALREADY FAILED.
9
94
  *
@@ -32,8 +117,14 @@ import { ZC_LENGTH_ORIGIN_DECL, ZC_SIZE_ORIGIN_DECL } from "../issue-decls.js";
32
117
  *
33
118
  * `origin` is computed at runtime here (see ZC_LENGTH_ORIGIN_DECL), because the
34
119
  * value that reached this branch is by definition not the schema's own type.
120
+ *
121
+ * `mayBeString` is set by a non-string node whose length checks can fire on a
122
+ * string (`z.array(…).min(2)` over `"😀"`): zod then measures it in code
123
+ * points, so those tests take the guarded form of {@link stringLengthTests}. A
124
+ * string node's own failure branch never holds a string and keeps the plain
125
+ * unit comparison.
35
126
  */
36
- function whenGatedSizeChecks(checks, g, family) {
127
+ function whenGatedSizeChecks(checks, g, family, mayBeString = false) {
37
128
  const KINDS = family === "length" ? /* @__PURE__ */ new Set([
38
129
  "min_length",
39
130
  "max_length",
@@ -51,30 +142,32 @@ function whenGatedSizeChecks(checks, g, family) {
51
142
  for (const check of checks) switch (check.kind) {
52
143
  case "min_length":
53
144
  body += emit`
54
- if(${measure}<${check.minimum}){
145
+ if(${mayBeString ? stringLengthTests.minFails(g.input, check.minimum, g.ctx, true) : `${measure}<${check.minimum}`}){
55
146
  ${tooSmall(g, check.minimum, origin, true, { message: check.message })}
56
147
  }`;
57
148
  break;
58
149
  case "max_length":
59
150
  body += emit`
60
- if(${measure}>${check.maximum}){
151
+ if(${mayBeString ? stringLengthTests.maxFails(g.input, check.maximum, g.ctx, true) : `${measure}>${check.maximum}`}){
61
152
  ${tooBig(g, check.maximum, origin, true, { message: check.message })}
62
153
  }`;
63
154
  break;
64
- case "length_equals":
155
+ case "length_equals": {
156
+ const length = mayBeString ? stringLengthTests.measure(g.input, check.length, g.ctx, true) : measure;
65
157
  body += emit`
66
- if(${measure}<${check.length}){
158
+ if(${length}<${check.length}){
67
159
  ${tooSmall(g, check.length, origin, true, {
68
160
  exact: true,
69
161
  message: check.message
70
162
  })}
71
- }else if(${measure}>${check.length}){
163
+ }else if(${length}>${check.length}){
72
164
  ${tooBig(g, check.length, origin, true, {
73
165
  exact: true,
74
166
  message: check.message
75
167
  })}
76
168
  }`;
77
169
  break;
170
+ }
78
171
  case "min_size":
79
172
  body += emit`
80
173
  if(${measure}<${check.minimum}){
@@ -110,6 +203,6 @@ function whenGatedSizeChecks(checks, g, family) {
110
203
  }`;
111
204
  }
112
205
  //#endregion
113
- export { whenGatedSizeChecks };
206
+ export { stringLengthTests, whenGatedSizeChecks };
114
207
 
115
208
  //# sourceMappingURL=sizeable.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sizeable.js","names":[],"sources":["../../../../src/core/codegen/schemas/sizeable.ts"],"sourcesContent":["import type { CheckOrEffectIR, FileCheckIR, SetCheckIR } from \"../../types.js\";\nimport type { SlowGen } from \"../context.js\";\nimport { emitRuntimeHelper } from \"../context.js\";\nimport { emit } from \"../emit.js\";\nimport { tooBig, tooSmall } from \"../emit-issue.js\";\nimport { ZC_LENGTH_ORIGIN_DECL, ZC_SIZE_ORIGIN_DECL } from \"../issue-decls.js\";\n\n/**\n * Length/size checks re-emitted for the branch where the node's TYPE CHECK\n * ALREADY FAILED.\n *\n * zod skips a schema's checks once its parse aborted — except the ones carrying\n * a `when` predicate, which `runChecks` consults instead of the abort flag:\n *\n * ```js\n * if (ch._zod.def.when) { if (!ch._zod.def.when(payload)) continue; }\n * else if (isAborted) continue;\n * ```\n *\n * and `$ZodCheckMinLength` & co. install exactly such a predicate —\n * `!nullish(value) && value.length !== undefined` (`.size` for the size family)\n * — so they run on ANY input carrying that property, of any type. Hence\n * `z.string().min(2).safeParse([])` reports TWO issues in zod: the\n * `invalid_type`, and a `too_small` whose `origin` is `\"array\"`, because the\n * empty array satisfied the `when`. Compiled output kept every check inside the\n * matched-type branch and reported only the first. Same for\n * `z.array(…).min(3)` over a short string, `z.set(…).min(2)` over a `Map`, and\n * `z.file().min(2)` over a `Set`.\n *\n * Emitted as a SEPARATE copy in the failure branch rather than hoisted out of\n * both, so the success path — where the origin is statically known and the guard\n * is trivially true — stays byte-for-byte what it was. This is cold code: it\n * runs only for input the node has already rejected.\n *\n * `origin` is computed at runtime here (see ZC_LENGTH_ORIGIN_DECL), because the\n * value that reached this branch is by definition not the schema's own type.\n */\nexport function whenGatedSizeChecks(\n checks: readonly (CheckOrEffectIR | SetCheckIR | FileCheckIR)[],\n g: SlowGen,\n family: \"length\" | \"size\",\n): string {\n const KINDS =\n family === \"length\"\n ? new Set([\"min_length\", \"max_length\", \"length_equals\"])\n : new Set([\"min_size\", \"max_size\", \"size_equals\"]);\n // Checked BEFORE touching the context: `emitRuntimeHelper` pushes its\n // declaration on sight, so asking for the origin helper up front left a dead\n // `__zcLo` in the preamble of every schema with a plain `z.string()` in it.\n if (!checks.some((check) => KINDS.has(check.kind))) return \"\";\n\n const property = family === \"length\" ? \"length\" : \"size\";\n const originHelper =\n family === \"length\"\n ? emitRuntimeHelper(g.ctx, \"__zcLo\", ZC_LENGTH_ORIGIN_DECL)\n : emitRuntimeHelper(g.ctx, \"__zcSo\", ZC_SIZE_ORIGIN_DECL);\n const origin = { expr: `${originHelper}(${g.input})` };\n const measure = `${g.input}.${property}`;\n\n let body = \"\";\n for (const check of checks) {\n // Kept in DECLARATION order: `runChecks` walks the check array, so two\n // failing length checks report in the order they were declared.\n switch (check.kind) {\n case \"min_length\":\n body += emit`\n if(${measure}<${check.minimum}){\n ${tooSmall(g, check.minimum, origin, true, { message: check.message })}\n }`;\n break;\n case \"max_length\":\n body += emit`\n if(${measure}>${check.maximum}){\n ${tooBig(g, check.maximum, origin, true, { message: check.message })}\n }`;\n break;\n case \"length_equals\":\n body += emit`\n if(${measure}<${check.length}){\n ${tooSmall(g, check.length, origin, true, { exact: true, message: check.message })}\n }else if(${measure}>${check.length}){\n ${tooBig(g, check.length, origin, true, { exact: true, message: check.message })}\n }`;\n break;\n case \"min_size\":\n body += emit`\n if(${measure}<${check.minimum}){\n ${tooSmall(g, check.minimum, origin, true, { message: check.message })}\n }`;\n break;\n case \"max_size\":\n body += emit`\n if(${measure}>${check.maximum}){\n ${tooBig(g, check.maximum, origin, true, { message: check.message })}\n }`;\n break;\n case \"size_equals\":\n body += emit`\n if(${measure}<${check.size}){\n ${tooSmall(g, check.size, origin, true, { exact: true, message: check.message })}\n }else if(${measure}>${check.size}){\n ${tooBig(g, check.size, origin, true, { exact: true, message: check.message })}\n }`;\n break;\n default:\n break;\n }\n }\n if (body === \"\") return \"\";\n // zod's own `when`, verbatim: `!util.nullish(value) && value.<prop> !== undefined`.\n return emit`\n if(${g.input}!==undefined&&${g.input}!==null&&${measure}!==undefined){\n ${body}\n }`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCA,SAAgB,oBACd,QACA,GACA,QACQ;CACR,MAAM,QACJ,WAAW,2BACP,IAAI,IAAI;EAAC;EAAc;EAAc;CAAe,CAAC,oBACrD,IAAI,IAAI;EAAC;EAAY;EAAY;CAAa,CAAC;CAIrD,IAAI,CAAC,OAAO,MAAM,UAAU,MAAM,IAAI,MAAM,IAAI,CAAC,GAAG,OAAO;CAE3D,MAAM,WAAW,WAAW,WAAW,WAAW;CAKlD,MAAM,SAAS,EAAE,MAAM,GAHrB,WAAW,WACP,kBAAkB,EAAE,KAAK,UAAU,qBAAqB,IACxD,kBAAkB,EAAE,KAAK,UAAU,mBAAmB,EACrB,GAAG,EAAE,MAAM,GAAG;CACrD,MAAM,UAAU,GAAG,EAAE,MAAM,GAAG;CAE9B,IAAI,OAAO;CACX,KAAK,MAAM,SAAS,QAGlB,QAAQ,MAAM,MAAd;EACE,KAAK;GACH,QAAQ,IAAI;eACL,QAAQ,GAAG,MAAM,QAAQ;cAC1B,SAAS,GAAG,MAAM,SAAS,QAAQ,MAAM,EAAE,SAAS,MAAM,QAAQ,CAAC,EAAE;;GAE3E;EACF,KAAK;GACH,QAAQ,IAAI;eACL,QAAQ,GAAG,MAAM,QAAQ;cAC1B,OAAO,GAAG,MAAM,SAAS,QAAQ,MAAM,EAAE,SAAS,MAAM,QAAQ,CAAC,EAAE;;GAEzE;EACF,KAAK;GACH,QAAQ,IAAI;eACL,QAAQ,GAAG,MAAM,OAAO;cACzB,SAAS,GAAG,MAAM,QAAQ,QAAQ,MAAM;IAAE,OAAO;IAAM,SAAS,MAAM;GAAQ,CAAC,EAAE;qBAC1E,QAAQ,GAAG,MAAM,OAAO;cAC/B,OAAO,GAAG,MAAM,QAAQ,QAAQ,MAAM;IAAE,OAAO;IAAM,SAAS,MAAM;GAAQ,CAAC,EAAE;;GAErF;EACF,KAAK;GACH,QAAQ,IAAI;eACL,QAAQ,GAAG,MAAM,QAAQ;cAC1B,SAAS,GAAG,MAAM,SAAS,QAAQ,MAAM,EAAE,SAAS,MAAM,QAAQ,CAAC,EAAE;;GAE3E;EACF,KAAK;GACH,QAAQ,IAAI;eACL,QAAQ,GAAG,MAAM,QAAQ;cAC1B,OAAO,GAAG,MAAM,SAAS,QAAQ,MAAM,EAAE,SAAS,MAAM,QAAQ,CAAC,EAAE;;GAEzE;EACF,KAAK;GACH,QAAQ,IAAI;eACL,QAAQ,GAAG,MAAM,KAAK;cACvB,SAAS,GAAG,MAAM,MAAM,QAAQ,MAAM;IAAE,OAAO;IAAM,SAAS,MAAM;GAAQ,CAAC,EAAE;qBACxE,QAAQ,GAAG,MAAM,KAAK;cAC7B,OAAO,GAAG,MAAM,MAAM,QAAQ,MAAM;IAAE,OAAO;IAAM,SAAS,MAAM;GAAQ,CAAC,EAAE;;GAEnF;EACF,SACE;CACJ;CAEF,IAAI,SAAS,IAAI,OAAO;CAExB,OAAO,IAAI;SACJ,EAAE,MAAM,gBAAgB,EAAE,MAAM,WAAW,QAAQ;QACpD,KAAK;;AAEb"}
1
+ {"version":3,"file":"sizeable.js","names":[],"sources":["../../../../src/core/codegen/schemas/sizeable.ts"],"sourcesContent":["import type { CheckOrEffectIR, FileCheckIR, SetCheckIR } from \"../../types.js\";\nimport type { CodeGenContext, SlowGen } from \"../context.js\";\nimport { emitRuntimeHelper } from \"../context.js\";\nimport { emit } from \"../emit.js\";\nimport { tooBig, tooSmall } from \"../emit-issue.js\";\nimport { ZC_CPL_DECL, ZC_LENGTH_ORIGIN_DECL, ZC_SIZE_ORIGIN_DECL } from \"../issue-decls.js\";\n\n/**\n * The UTF-16 unit count from which a string is CERTAINLY at least `min` code\n * points, so the count can be skipped.\n *\n * A code point is one or two units, so `codePoints >= ceil(units / 2)`, and\n * that ceiling is an integer: `ceil(units/2) >= min` holds exactly when\n * `ceil(units/2) >= Math.ceil(min)`, i.e. `units >= 2 * ceil(min) - 1`.\n * Rounding `min` up is what makes a FRACTIONAL bound safe — `min(2.5)` is\n * settled at 5 units, not the 4 that `2 * min - 1` would have claimed, where\n * two astral characters are only 2 code points and zod reports `too_small`.\n */\nfunction certainlyAtLeast(min: number): number {\n return 2 * Math.ceil(min) - 1;\n}\n\n/**\n * String length tests over Unicode CODE POINTS, gated so the count is only\n * paid for when the UTF-16 unit count leaves the verdict in doubt.\n *\n * zod measures `z.string().min()/.max()/.length()` in code points\n * (`util.codePointLength`), so `\"😀\"` has length 1, and it computes that count\n * only when the unit count could sit on the other side of the bound. A code\n * point is one or two units, so `units/2 <= codePoints <= units` — which pins\n * the doubtful band tighter than zod's own gate without changing a verdict:\n * `min(N)` is settled outside `N <= units < 2*ceil(N)-1` (so `min(1)` stays a\n * plain `length>=1`; see {@link certainlyAtLeast} for the fractional case),\n * `max(N)` outside `N < units <= 2N`, and `length(N)` outside `N <= units <=\n * 2N`. Every test leads with the plain unit comparison, so an ASCII string of\n * ordinary length never reaches the helper.\n *\n * `mayNotBeString` adds the `typeof` guard zod applies (`typeof input ===\n * \"string\" && …`) for a site whose input is not statically a string — a\n * length check firing on the wrong type through its `when` predicate; an array\n * measures elements, never code points. Only the failure-direction forms take\n * it: `min`/`max`/`equals` are reached solely from `fastStringCheck`, whose\n * input is statically a string.\n */\nexport const stringLengthTests = {\n min(x: string, min: number, ctx: CodeGenContext): string {\n const units = `${x}.length>=${min}`;\n if (min <= 1) return units;\n const cpl = emitRuntimeHelper(ctx, \"__zcCpl\", ZC_CPL_DECL);\n return `${units}&&(${x}.length>=${certainlyAtLeast(min)}||${cpl}(${x})>=${min})`;\n },\n max(x: string, max: number, ctx: CodeGenContext): string {\n const units = `${x}.length<=${max}`;\n if (max <= 0) return units;\n const cpl = emitRuntimeHelper(ctx, \"__zcCpl\", ZC_CPL_DECL);\n return `(${units}||(${x}.length<=${2 * max}&&${cpl}(${x})<=${max}))`;\n },\n equals(x: string, length: number, ctx: CodeGenContext): string {\n if (length <= 0) return `${x}.length===${length}`;\n const cpl = emitRuntimeHelper(ctx, \"__zcCpl\", ZC_CPL_DECL);\n // A single unit is always one code point, so `length(1)` is only in doubt\n // at exactly two units.\n if (length === 1) {\n return `(${x}.length===1||(${x}.length===2&&${cpl}(${x})===1))`;\n }\n return `(${x}.length>=${length}&&${x}.length<=${2 * length}&&${cpl}(${x})===${length})`;\n },\n /**\n * The FAILING condition of `min`/`max`, with the negation pushed inward so a\n * check with no doubtful band emits the plain comparison the slow path always\n * used (`x.length<1`, not `!(x.length>=1)`).\n */\n minFails(x: string, min: number, ctx: CodeGenContext, mayNotBeString = false): string {\n const units = `${x}.length<${min}`;\n if (min <= 1) return units;\n const cpl = emitRuntimeHelper(ctx, \"__zcCpl\", ZC_CPL_DECL);\n const guard = mayNotBeString ? `typeof ${x}===\"string\"&&` : \"\";\n return `${units}||(${x}.length<${certainlyAtLeast(min)}&&${guard}${cpl}(${x})<${min})`;\n },\n maxFails(x: string, max: number, ctx: CodeGenContext, mayNotBeString = false): string {\n const units = `${x}.length>${max}`;\n if (max <= 0) return units;\n const cpl = emitRuntimeHelper(ctx, \"__zcCpl\", ZC_CPL_DECL);\n const guard = mayNotBeString ? `typeof ${x}!==\"string\"||` : \"\";\n return `${units}&&(${x}.length>${2 * max}||${guard}${cpl}(${x})>${max})`;\n },\n /**\n * The measured length zod compares in `$ZodCheckLengthEquals` — code points\n * inside the doubtful band, units outside it — for the slow path, which has\n * to know WHICH side of the bound the value fell on.\n */\n measure(x: string, length: number, ctx: CodeGenContext, mayNotBeString = false): string {\n if (length <= 0) return `${x}.length`;\n const cpl = emitRuntimeHelper(ctx, \"__zcCpl\", ZC_CPL_DECL);\n const guard = mayNotBeString ? `typeof ${x}===\"string\"&&` : \"\";\n const band =\n length === 1 ? `${x}.length===2` : `${x}.length>=${length}&&${x}.length<=${2 * length}`;\n return `(${guard}${band}?${cpl}(${x}):${x}.length)`;\n },\n};\n\n/**\n * Length/size checks re-emitted for the branch where the node's TYPE CHECK\n * ALREADY FAILED.\n *\n * zod skips a schema's checks once its parse aborted — except the ones carrying\n * a `when` predicate, which `runChecks` consults instead of the abort flag:\n *\n * ```js\n * if (ch._zod.def.when) { if (!ch._zod.def.when(payload)) continue; }\n * else if (isAborted) continue;\n * ```\n *\n * and `$ZodCheckMinLength` & co. install exactly such a predicate —\n * `!nullish(value) && value.length !== undefined` (`.size` for the size family)\n * — so they run on ANY input carrying that property, of any type. Hence\n * `z.string().min(2).safeParse([])` reports TWO issues in zod: the\n * `invalid_type`, and a `too_small` whose `origin` is `\"array\"`, because the\n * empty array satisfied the `when`. Compiled output kept every check inside the\n * matched-type branch and reported only the first. Same for\n * `z.array(…).min(3)` over a short string, `z.set(…).min(2)` over a `Map`, and\n * `z.file().min(2)` over a `Set`.\n *\n * Emitted as a SEPARATE copy in the failure branch rather than hoisted out of\n * both, so the success path — where the origin is statically known and the guard\n * is trivially true — stays byte-for-byte what it was. This is cold code: it\n * runs only for input the node has already rejected.\n *\n * `origin` is computed at runtime here (see ZC_LENGTH_ORIGIN_DECL), because the\n * value that reached this branch is by definition not the schema's own type.\n *\n * `mayBeString` is set by a non-string node whose length checks can fire on a\n * string (`z.array(…).min(2)` over `\"😀\"`): zod then measures it in code\n * points, so those tests take the guarded form of {@link stringLengthTests}. A\n * string node's own failure branch never holds a string and keeps the plain\n * unit comparison.\n */\nexport function whenGatedSizeChecks(\n checks: readonly (CheckOrEffectIR | SetCheckIR | FileCheckIR)[],\n g: SlowGen,\n family: \"length\" | \"size\",\n mayBeString = false,\n): string {\n const KINDS =\n family === \"length\"\n ? new Set([\"min_length\", \"max_length\", \"length_equals\"])\n : new Set([\"min_size\", \"max_size\", \"size_equals\"]);\n // Checked BEFORE touching the context: `emitRuntimeHelper` pushes its\n // declaration on sight, so asking for the origin helper up front left a dead\n // `__zcLo` in the preamble of every schema with a plain `z.string()` in it.\n if (!checks.some((check) => KINDS.has(check.kind))) return \"\";\n\n const property = family === \"length\" ? \"length\" : \"size\";\n const originHelper =\n family === \"length\"\n ? emitRuntimeHelper(g.ctx, \"__zcLo\", ZC_LENGTH_ORIGIN_DECL)\n : emitRuntimeHelper(g.ctx, \"__zcSo\", ZC_SIZE_ORIGIN_DECL);\n const origin = { expr: `${originHelper}(${g.input})` };\n const measure = `${g.input}.${property}`;\n\n let body = \"\";\n for (const check of checks) {\n // Kept in DECLARATION order: `runChecks` walks the check array, so two\n // failing length checks report in the order they were declared.\n switch (check.kind) {\n case \"min_length\":\n body += emit`\n if(${mayBeString ? stringLengthTests.minFails(g.input, check.minimum, g.ctx, true) : `${measure}<${check.minimum}`}){\n ${tooSmall(g, check.minimum, origin, true, { message: check.message })}\n }`;\n break;\n case \"max_length\":\n body += emit`\n if(${mayBeString ? stringLengthTests.maxFails(g.input, check.maximum, g.ctx, true) : `${measure}>${check.maximum}`}){\n ${tooBig(g, check.maximum, origin, true, { message: check.message })}\n }`;\n break;\n case \"length_equals\": {\n const length = mayBeString\n ? stringLengthTests.measure(g.input, check.length, g.ctx, true)\n : measure;\n body += emit`\n if(${length}<${check.length}){\n ${tooSmall(g, check.length, origin, true, { exact: true, message: check.message })}\n }else if(${length}>${check.length}){\n ${tooBig(g, check.length, origin, true, { exact: true, message: check.message })}\n }`;\n break;\n }\n case \"min_size\":\n body += emit`\n if(${measure}<${check.minimum}){\n ${tooSmall(g, check.minimum, origin, true, { message: check.message })}\n }`;\n break;\n case \"max_size\":\n body += emit`\n if(${measure}>${check.maximum}){\n ${tooBig(g, check.maximum, origin, true, { message: check.message })}\n }`;\n break;\n case \"size_equals\":\n body += emit`\n if(${measure}<${check.size}){\n ${tooSmall(g, check.size, origin, true, { exact: true, message: check.message })}\n }else if(${measure}>${check.size}){\n ${tooBig(g, check.size, origin, true, { exact: true, message: check.message })}\n }`;\n break;\n default:\n break;\n }\n }\n if (body === \"\") return \"\";\n // zod's own `when`, verbatim: `!util.nullish(value) && value.<prop> !== undefined`.\n return emit`\n if(${g.input}!==undefined&&${g.input}!==null&&${measure}!==undefined){\n ${body}\n }`;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"string.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/string.ts"],"mappings":";;;iBAuEgB,WAAW,IAAI,UAAU,GAAG;;;;;;;;;;;iBAmJ5B,gBAAgB,OAAO,SAAS,WAAW,KAAK;iBAkDhD,WAAW,IAAI,UAAU,GAAG"}
1
+ {"version":3,"file":"string.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/string.ts"],"mappings":";;;iBAoGgB,WAAW,IAAI,UAAU,GAAG;;;;;;;;;;;iBAwJ5B,gBAAgB,OAAO,SAAS,WAAW,KAAK;iBAkDhD,WAAW,IAAI,UAAU,GAAG"}