zod-compiler 1.23.6 → 1.23.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/codegen/build-path.d.ts.map +1 -1
- package/dist/core/codegen/build-path.js +57 -6
- package/dist/core/codegen/build-path.js.map +1 -1
- package/dist/core/codegen/context.d.ts +23 -3
- package/dist/core/codegen/context.d.ts.map +1 -1
- package/dist/core/codegen/context.js +23 -1
- package/dist/core/codegen/context.js.map +1 -1
- package/dist/core/codegen/dedupe.js +4 -1
- package/dist/core/codegen/dedupe.js.map +1 -1
- package/dist/core/codegen/emit-issue.d.ts +27 -2
- package/dist/core/codegen/emit-issue.d.ts.map +1 -1
- package/dist/core/codegen/emit-issue.js +8 -3
- package/dist/core/codegen/emit-issue.js.map +1 -1
- package/dist/core/codegen/issue-decls.d.ts +9 -3
- package/dist/core/codegen/issue-decls.d.ts.map +1 -1
- package/dist/core/codegen/issue-decls.js +10 -4
- package/dist/core/codegen/issue-decls.js.map +1 -1
- package/dist/core/codegen/schemas/catch.d.ts.map +1 -1
- package/dist/core/codegen/schemas/catch.js +1 -1
- package/dist/core/codegen/schemas/catch.js.map +1 -1
- package/dist/core/codegen/schemas/discriminated-union.d.ts.map +1 -1
- package/dist/core/codegen/schemas/discriminated-union.js +11 -3
- package/dist/core/codegen/schemas/discriminated-union.js.map +1 -1
- package/dist/core/codegen/schemas/effect.d.ts.map +1 -1
- package/dist/core/codegen/schemas/effect.js +3 -2
- package/dist/core/codegen/schemas/effect.js.map +1 -1
- package/dist/core/codegen/schemas/enum.d.ts.map +1 -1
- package/dist/core/codegen/schemas/enum.js +25 -0
- package/dist/core/codegen/schemas/enum.js.map +1 -1
- package/dist/core/codegen/schemas/literal.d.ts.map +1 -1
- package/dist/core/codegen/schemas/literal.js +37 -3
- package/dist/core/codegen/schemas/literal.js.map +1 -1
- package/dist/core/codegen/schemas/string-bool.d.ts.map +1 -1
- package/dist/core/codegen/schemas/string-bool.js +3 -2
- package/dist/core/codegen/schemas/string-bool.js.map +1 -1
- package/dist/core/codegen/schemas/string.d.ts.map +1 -1
- package/dist/core/codegen/schemas/string.js +13 -7
- package/dist/core/codegen/schemas/string.js.map +1 -1
- package/dist/core/codegen/schemas/template-literal.d.ts.map +1 -1
- package/dist/core/codegen/schemas/template-literal.js +2 -2
- package/dist/core/codegen/schemas/template-literal.js.map +1 -1
- package/dist/core/codegen/schemas/tuple.d.ts +11 -5
- package/dist/core/codegen/schemas/tuple.d.ts.map +1 -1
- package/dist/core/codegen/schemas/tuple.js +12 -6
- package/dist/core/codegen/schemas/tuple.js.map +1 -1
- package/dist/core/codegen/schemas/union.d.ts.map +1 -1
- package/dist/core/codegen/schemas/union.js +17 -17
- package/dist/core/codegen/schemas/union.js.map +1 -1
- package/dist/core/extract/checks.d.ts +18 -2
- package/dist/core/extract/checks.d.ts.map +1 -1
- package/dist/core/extract/checks.js +138 -11
- package/dist/core/extract/checks.js.map +1 -1
- package/dist/core/extract/extractors/array.d.ts.map +1 -1
- package/dist/core/extract/extractors/array.js +2 -2
- package/dist/core/extract/extractors/array.js.map +1 -1
- package/dist/core/extract/extractors/intersection.js +44 -0
- package/dist/core/extract/extractors/intersection.js.map +1 -1
- package/dist/core/extract/extractors/lazy.d.ts.map +1 -1
- package/dist/core/extract/extractors/lazy.js +2 -13
- package/dist/core/extract/extractors/lazy.js.map +1 -1
- package/dist/core/extract/extractors/literal.d.ts +14 -0
- package/dist/core/extract/extractors/literal.d.ts.map +1 -1
- package/dist/core/extract/extractors/literal.js +32 -4
- package/dist/core/extract/extractors/literal.js.map +1 -1
- package/dist/core/extract/extractors/number.d.ts.map +1 -1
- package/dist/core/extract/extractors/number.js +2 -2
- package/dist/core/extract/extractors/number.js.map +1 -1
- package/dist/core/extract/extractors/object.d.ts.map +1 -1
- package/dist/core/extract/extractors/object.js +2 -2
- package/dist/core/extract/extractors/object.js.map +1 -1
- package/dist/core/extract/extractors/record.d.ts.map +1 -1
- package/dist/core/extract/extractors/record.js +2 -0
- package/dist/core/extract/extractors/record.js.map +1 -1
- package/dist/core/extract/extractors/string.d.ts.map +1 -1
- package/dist/core/extract/extractors/string.js +2 -2
- package/dist/core/extract/extractors/string.js.map +1 -1
- package/dist/core/extract/extractors/union.js +2 -0
- package/dist/core/extract/extractors/union.js.map +1 -1
- package/dist/core/extract/recursion.d.ts +26 -0
- package/dist/core/extract/recursion.d.ts.map +1 -0
- package/dist/core/extract/recursion.js +37 -0
- package/dist/core/extract/recursion.js.map +1 -0
- package/dist/core/extract/registry.d.ts.map +1 -1
- package/dist/core/extract/registry.js +2 -0
- package/dist/core/extract/registry.js.map +1 -1
- package/dist/core/extract/types.d.ts +21 -6
- package/dist/core/extract/types.d.ts.map +1 -1
- package/dist/core/iife.d.ts +13 -0
- package/dist/core/iife.d.ts.map +1 -1
- package/dist/core/iife.js +14 -1
- package/dist/core/iife.js.map +1 -1
- package/dist/core/types.d.ts +49 -2
- package/dist/core/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"issue-decls.js","names":[],"sources":["../../../src/core/codegen/issue-decls.ts"],"sourcesContent":["/**\n * Issue factory function bodies (statement form).\n *\n * These functions produce the same `{code, ...}` shapes that lean-mode\n * generated code would otherwise inline at every check site.\n * Hosted in \"virtual:zod-compiler/runtime\" and called as `__zcTS(...)` etc.\n *\n * Argument convention (positional, kept short to minimize call-site bytes):\n * __zcTS(minimum, origin, inclusive, input, path, msg?) — too_small\n * __zcTB(maximum, origin, inclusive, input, path, msg?) — too_big\n * __zcIT(expected, input, path, msg?) — invalid_type\n * __zcIF(format, input, path, extra?, msg?) — invalid_format (extra merged into result)\n * __zcIV(values, input, path, msg?) — invalid_value\n * __zcUK(keys, input, path, msg?) — unrecognized_keys\n *\n * The trailing msg argument carries a static custom error message; when\n * absent, the __zcFin finalizer applies the configured locale default.\n */\n\nconst ZC_TS_DECL =\n 'function __zcTS(m,o,i,inp,p,msg){var r={code:\"too_small\",minimum:m,origin:o,inclusive:i,input:inp,path:p};if(msg!==undefined)r.message=msg;return r;}';\n\nconst ZC_TS_EXACT_DECL =\n 'function __zcTSx(m,o,inp,p,msg){var r={code:\"too_small\",minimum:m,origin:o,inclusive:true,exact:true,input:inp,path:p};if(msg!==undefined)r.message=msg;return r;}';\n\nconst ZC_TB_DECL =\n 'function __zcTB(m,o,i,inp,p,msg){var r={code:\"too_big\",maximum:m,origin:o,inclusive:i,input:inp,path:p};if(msg!==undefined)r.message=msg;return r;}';\n\nconst ZC_TB_EXACT_DECL =\n 'function __zcTBx(m,o,inp,p,msg){var r={code:\"too_big\",maximum:m,origin:o,inclusive:true,exact:true,input:inp,path:p};if(msg!==undefined)r.message=msg;return r;}';\n\nconst ZC_IT_DECL =\n 'function __zcIT(e,inp,p,msg){var r={code:\"invalid_type\",expected:e,input:inp,path:p};if(msg!==undefined)r.message=msg;return r;}';\n\nconst ZC_IF_DECL =\n 'function __zcIF(f,inp,p,extra,msg){var r={code:\"invalid_format\",format:f,input:inp,path:p};if(extra)Object.assign(r,extra);if(msg!==undefined)r.message=msg;return r;}';\n\nconst ZC_IV_DECL =\n 'function __zcIV(values,inp,p,msg){var r={code:\"invalid_value\",values:values,input:inp,path:p};if(msg!==undefined)r.message=msg;return r;}';\n\nconst ZC_UK_DECL =\n 'function __zcUK(k,inp,p,msg){var r={code:\"unrecognized_keys\",keys:k,input:inp,path:p};if(msg!==undefined)r.message=msg;return r;}';\n\n/** All issue factory declarations indexed by helper name. */\nexport const ISSUE_DECLS: Readonly<Record<string, string>> = {\n __zcTS: ZC_TS_DECL,\n __zcTSx: ZC_TS_EXACT_DECL,\n __zcTB: ZC_TB_DECL,\n __zcTBx: ZC_TB_EXACT_DECL,\n __zcIT: ZC_IT_DECL,\n __zcIF: ZC_IF_DECL,\n __zcIV: ZC_IV_DECL,\n __zcUK: ZC_UK_DECL,\n};\n\n/**\n * Float-safe remainder — byte-for-byte port of zod's util.floatSafeRemainder.\n * Raw `%` mis-rejects valid multiples of decimal steps (0.3 % 0.1 !== 0);\n * zod scales both operands to integers by their decimal-place count first.\n */\nexport const ZC_FSR_DECL =\n 'function __zcFsr(v,s){var vd=((\"\"+v).split(\".\")[1]||\"\").length;var ss=\"\"+s;var sd=(ss.split(\".\")[1]||\"\").length;if(sd===0&&/\\\\d?e-\\\\d?/.test(ss)){var m=ss.match(/\\\\d?e-(\\\\d?)/);if(m&&m[1]){sd=parseInt(m[1],10);}}var d=vd>sd?vd:sd;var vi=parseInt(v.toFixed(d).replace(\".\",\"\"),10);var si=parseInt(s.toFixed(d).replace(\".\",\"\"),10);return (vi%si)/Math.pow(10,d);}';\n\n/**\n * Hoisted `Object.prototype.hasOwnProperty` reference. Record fast/slow paths\n * iterate keys with `for(k in o)` (no `Object.keys` array allocation) and guard\n * each key with `__zcHop.call(o,k)` to skip inherited enumerable properties —\n * yielding the exact own-enumerable string-key set `Object.keys` would, so\n * fast/slow stay in agreement and parity with zod's own-key record semantics is\n * preserved. The hoisted reference inlines in V8; reading the prototype property\n * per call would not.\n */\nexport const ZC_HOP_DECL = \"const __zcHop=Object.prototype.hasOwnProperty;\";\n\n/**\n * Issue codes zod raises from a schema's `_zod.parse` rather than from a check,\n * and which therefore carry `continue !== true` — what `util.aborted` looks for.\n *\n * Everything a CHECK produces (`too_small`, `too_big`, `invalid_format`,\n * `not_multiple_of`, a refine's `custom`) is continuable, because `$ZodCheck`\n * sets `continue: !def.abort` and an `abort: true` check costs the schema its\n * compiled path anyway — so classifying by code alone is exact for generated\n * issues.\n *\n * Read by {@link ZC_AB_DECL} and by the union's option-pruning loop, which\n * applies the same rule inline over a per-option issue array.\n */\nconst ABORTING_ISSUE_CODES: readonly string[] = [\n \"invalid_type\",\n \"invalid_value\",\n \"invalid_union\",\n \"unrecognized_keys\",\n \"invalid_key\",\n \"invalid_element\",\n];\n\n/** `c===\"invalid_type\"||c===\"invalid_value\"||…` over the code held in `codeExpr`. */\nexport function abortingCodeTest(codeExpr: string): string {\n return ABORTING_ISSUE_CODES.map((code) => `${codeExpr}===${JSON.stringify(code)}`).join(\"||\");\n}\n\n/**\n * Port of zod's `util.aborted(payload, startIndex)`: has anything since\n * `startIndex` produced a NON-continuable issue?\n *\n * zod gates a schema's check chain on this (`runChecks`: `else if (isAborted)\n * continue`), which is what makes a container's `.refine()` still run after a\n * property or element failed its own `min`/`max`/format check, and stop running\n * once one failed to parse at all. The `startIndex` is the issue count when the\n * node was entered, mirroring the fresh payload zod hands each sub-schema.\n *\n * Size/length checks are exempt in zod — they declare a `when` predicate, which\n * bypasses the abort gate — so generated code leaves those ungated and only\n * wraps the refine/superRefine effects.\n */\nexport const ZC_AB_DECL = `function __zcAb(e,i){for(;i<e.length;i++){var c=e[i].code;if(${abortingCodeTest(\"c\")})return true;}return false;}`;\n\n/**\n * Port of zod's `util.finalizeIssue` for issues NESTED inside an\n * `invalid_key` / `invalid_element` wrapper. Those never reach the top-level\n * finalization loop (which walks only the outer array), so zod finalizes them\n * where it builds the wrapper: locale message applied when none was baked in,\n * `input` cleared. Their `path` stays RELATIVE to the key or value schema — zod\n * ran it on a fresh payload — so nothing rewrites it.\n *\n * `input` is cleared by assignment rather than `delete`, matching the top-level\n * finalizer and the union's per-option loop.\n */\nexport const ZC_FZ_DECL =\n \"function __zcFz(e){for(var i=0;i<e.length;i++){var s=e[i];\" +\n 'if(s.message===undefined&&typeof __zcMsg===\"function\")s.message=__zcMsg(s);' +\n \"s.input=undefined;}return e;}\";\n\n/**\n * Port of zod's `util.prefixIssues(key, issues)` for a map entry whose key is a\n * property-key type: each issue's RELATIVE path is spliced onto the map's own\n * path plus the key, and the issue moves into the parent's array.\n *\n * `b.concat(k,x.path)` flattens `x.path` one level, giving exactly\n * `[...base, key, ...relative]`.\n */\nexport const ZC_PFX_DECL =\n \"function __zcPfx(d,s,b,k){for(var i=0;i<s.length;i++){var x=s[i];x.path=b.concat(k,x.path);d.push(x);}}\";\n\n/** Does `keyExpr` hold one of zod's `util.propertyKeyTypes` (string|number|symbol)? */\nexport function propertyKeyTest(keyExpr: string): string {\n return `typeof ${keyExpr}===\"string\"||typeof ${keyExpr}===\"number\"||typeof ${keyExpr}===\"symbol\"`;\n}\n\n/**\n * superRefine fast-check: run the payload callback on a throwaway payload and\n * report whether it both added nothing AND left the value alone. zod's own\n * wrapper (the referenced function) installs `addIssue` and normalizes what the\n * user adds, so the verdict is zod's; the issues themselves are re-collected by\n * the slow walk.\n *\n * The `p.value===v` half is what lets a superRefine node keep a fast path at\n * all. `value` is writable public API ($RefinementCtx extends ParsePayload), so\n * a callback may rewrite it, and the fast path's caller returns the ORIGINAL\n * input on success — which would then be stale. Reporting false when the value\n * moved routes those parses into the slow walk, which propagates the new value\n * (see ZC_SR_DECL). Callbacks that only validate — effectively all of them —\n * still take the fast exit.\n */\nexport const ZC_SR_OK_DECL =\n \"function __zcSrOk(f,v){var p={value:v,issues:[]};__zcSrRun(f,p);\" +\n \"return p.issues.length===0&&p.value===v;}\";\n\n/**\n * Module-local (never imported by generated code) — __zcSr/__zcSrOk call it.\n * Lean mode declares it in the runtime module beside them; inline mode pushes it\n * into the preamble alongside whichever of the two is used.\n *\n * Invoke the referenced wrapper, reproducing zod's synchronous-parse contract:\n * a callback that returns a promise makes zod raise $ZodAsyncError rather than\n * silently accepting. Because the ref points at zod's superRefine WRAPPER, not\n * the user's function, an async callback cannot be detected while extracting —\n * the returned thenable is the only evidence, so the test lives here. Sync\n * callbacks return undefined, so the guard short-circuits on the first operand.\n */\nexport const ZC_SR_RUN_DECL =\n \"function __zcSrRun(f,p){var r=f(p);\" +\n 'if(r&&typeof r.then===\"function\"){throw new __zcCore.$ZodAsyncError();}}';\n\n/**\n * z.custom()/z.instanceof() fast verdict. Zod treats truthy predicate returns\n * as success and raises $ZodAsyncError when a synchronous parse encounters a\n * thenable, including a non-async function that happens to return a Promise.\n */\nexport const ZC_CUSTOM_OK_DECL =\n \"function __zcCu(f,v){var r=f(v);\" +\n 'if(r&&typeof r.then===\"function\"){throw new __zcCore.$ZodAsyncError();}return !!r;}';\n\n/**\n * superRefine slow-path merge: run the callback, then move its issues onto the\n * validator's list the way zod's finalizeIssue does — the node's path prefixed\n * onto any path the user supplied, and the internal `inst`/`continue` fields\n * dropped (they are zod bookkeeping, deleted before the issue is user-visible).\n *\n * Returns the payload, so the caller can write `.value` back (the callback may\n * have rewritten it) and read `.aborted`. Aborted is set when any issue aborts\n * in zod's sense (`continue !== true`, which covers `fatal: true` and the string\n * shorthand, whose issue carries no `continue` at all) — or when the callback\n * set it directly, also public payload API. A union option uses it to mark\n * itself aborted, matching how zod prunes option errors; without it an option\n * failing only through superRefine would be surfaced directly instead of inside\n * `invalid_union`.\n */\nexport const ZC_SR_DECL =\n \"function __zcSr(f,v,p,e){var q={value:v,issues:[]};__zcSrRun(f,q);\" +\n \"for(var i=0;i<q.issues.length;i++){var s=q.issues[i],t={};\" +\n 'for(var k in s){if(k!==\"inst\"&&k!==\"continue\")t[k]=s[k];}' +\n \"if(s.continue!==true)q.aborted=true;\" +\n \"t.path=s.path&&s.path.length?p.concat(s.path):p;e.push(t);}return q;}\";\n\n/** Non-issue runtime helper declarations hosted in the virtual module. */\nexport const RUNTIME_HELPER_DECLS: Readonly<Record<string, string>> = {\n __zcAb: ZC_AB_DECL,\n __zcFsr: ZC_FSR_DECL,\n __zcFz: ZC_FZ_DECL,\n __zcHop: ZC_HOP_DECL,\n __zcPfx: ZC_PFX_DECL,\n __zcCu: ZC_CUSTOM_OK_DECL,\n __zcSr: ZC_SR_DECL,\n __zcSrOk: ZC_SR_OK_DECL,\n};\n"],"mappings":";AA4CA,MAAa,cAAgD;CAC3D,QAAQ;CACR,SAAS;CACT,QAAQ;CACR,SAAS;CACT,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;AACV;;;;;;AAOA,MAAa,cACX;;;;;;;;;;AAWF,MAAa,cAAc;;;;;;;;;;;;;;AAe3B,MAAM,uBAA0C;CAC9C;CACA;CACA;CACA;CACA;CACA;AACF;;AAGA,SAAgB,iBAAiB,UAA0B;CACzD,OAAO,qBAAqB,KAAK,SAAS,GAAG,SAAS,KAAK,KAAK,UAAU,IAAI,GAAG,CAAC,CAAC,KAAK,IAAI;AAC9F;;;;;;;;;;;;;;;AAgBA,MAAa,aAAa,gEAAgE,iBAAiB,GAAG,EAAE;;;;;;;;;;;;AAahH,MAAa,aACX;;;;;;;;;AAYF,MAAa,cACX;;AAGF,SAAgB,gBAAgB,SAAyB;CACvD,OAAO,UAAU,QAAQ,sBAAsB,QAAQ,sBAAsB,QAAQ;AACvF;;;;;;;;;;;;;;;;AAiBA,MAAa,gBACX;;;;;;;;;;;;;AAeF,MAAa,iBACX;;;;;;AAQF,MAAa,oBACX;;;;;;;;;;;;;;;;AAkBF,MAAa,aACX;;AAOF,MAAa,uBAAyD;CACpE,QAAQ;CACR,SAAS;CACT,QAAQ;CACR,SAAS;CACT,SAAS;CACT,QAAQ;CACR,QAAQ;CACR,UAAU;AACZ"}
|
|
1
|
+
{"version":3,"file":"issue-decls.js","names":[],"sources":["../../../src/core/codegen/issue-decls.ts"],"sourcesContent":["/**\n * Issue factory function bodies (statement form).\n *\n * These functions produce the same `{code, ...}` shapes that lean-mode\n * generated code would otherwise inline at every check site.\n * Hosted in \"virtual:zod-compiler/runtime\" and called as `__zcTS(...)` etc.\n *\n * Argument convention (positional, kept short to minimize call-site bytes):\n * __zcTS(minimum, origin, inclusive, input, path, msg?) — too_small\n * __zcTSn(minimum, origin, input, path, msg?) — too_small with NO `inclusive` key\n * __zcTB(maximum, origin, inclusive, input, path, msg?) — too_big\n * __zcIT(expected, input, path, msg?) — invalid_type\n * __zcIF(format, input, path, extra?, msg?) — invalid_format (extra merged into result)\n * __zcIV(values, input, path, extra?, msg?) — invalid_value (extra merged into result)\n * __zcUK(keys, input, path, msg?) — unrecognized_keys\n *\n * The trailing msg argument carries a static custom error message; when\n * absent, the __zcFin finalizer applies the configured locale default.\n */\n\nconst ZC_TS_DECL =\n 'function __zcTS(m,o,i,inp,p,msg){var r={code:\"too_small\",minimum:m,origin:o,inclusive:i,input:inp,path:p};if(msg!==undefined)r.message=msg;return r;}';\n\n/**\n * too_small with the `inclusive` key ABSENT, not false. $ZodTuple's under-length\n * branch pushes `{ code: \"too_small\", minimum: items.length }` and nothing else\n * — the over-length branch of the same ternary is the one that spells out\n * `inclusive: true` — so the tuple's issue must not carry the key at all.\n * Separate from __zcTS because the value cannot express absence.\n */\nconst ZC_TS_NO_INCLUSIVE_DECL =\n 'function __zcTSn(m,o,inp,p,msg){var r={code:\"too_small\",minimum:m,origin:o,input:inp,path:p};if(msg!==undefined)r.message=msg;return r;}';\n\nconst ZC_TS_EXACT_DECL =\n 'function __zcTSx(m,o,inp,p,msg){var r={code:\"too_small\",minimum:m,origin:o,inclusive:true,exact:true,input:inp,path:p};if(msg!==undefined)r.message=msg;return r;}';\n\nconst ZC_TB_DECL =\n 'function __zcTB(m,o,i,inp,p,msg){var r={code:\"too_big\",maximum:m,origin:o,inclusive:i,input:inp,path:p};if(msg!==undefined)r.message=msg;return r;}';\n\nconst ZC_TB_EXACT_DECL =\n 'function __zcTBx(m,o,inp,p,msg){var r={code:\"too_big\",maximum:m,origin:o,inclusive:true,exact:true,input:inp,path:p};if(msg!==undefined)r.message=msg;return r;}';\n\nconst ZC_IT_DECL =\n 'function __zcIT(e,inp,p,msg){var r={code:\"invalid_type\",expected:e,input:inp,path:p};if(msg!==undefined)r.message=msg;return r;}';\n\nconst ZC_IF_DECL =\n 'function __zcIF(f,inp,p,extra,msg){var r={code:\"invalid_format\",format:f,input:inp,path:p};if(extra)Object.assign(r,extra);if(msg!==undefined)r.message=msg;return r;}';\n\n/**\n * `extra` carries the per-producer fields: enum and literal push `values` only,\n * while z.stringbool()'s codec transform also pushes `expected: \"stringbool\"`.\n * Merged the same way __zcIF merges its own.\n */\nconst ZC_IV_DECL =\n 'function __zcIV(values,inp,p,extra,msg){var r={code:\"invalid_value\",values:values,input:inp,path:p};if(extra)Object.assign(r,extra);if(msg!==undefined)r.message=msg;return r;}';\n\nconst ZC_UK_DECL =\n 'function __zcUK(k,inp,p,msg){var r={code:\"unrecognized_keys\",keys:k,input:inp,path:p};if(msg!==undefined)r.message=msg;return r;}';\n\n/** All issue factory declarations indexed by helper name. */\nexport const ISSUE_DECLS: Readonly<Record<string, string>> = {\n __zcTS: ZC_TS_DECL,\n __zcTSn: ZC_TS_NO_INCLUSIVE_DECL,\n __zcTSx: ZC_TS_EXACT_DECL,\n __zcTB: ZC_TB_DECL,\n __zcTBx: ZC_TB_EXACT_DECL,\n __zcIT: ZC_IT_DECL,\n __zcIF: ZC_IF_DECL,\n __zcIV: ZC_IV_DECL,\n __zcUK: ZC_UK_DECL,\n};\n\n/**\n * Float-safe remainder — byte-for-byte port of zod's util.floatSafeRemainder.\n * Raw `%` mis-rejects valid multiples of decimal steps (0.3 % 0.1 !== 0);\n * zod scales both operands to integers by their decimal-place count first.\n */\nexport const ZC_FSR_DECL =\n 'function __zcFsr(v,s){var vd=((\"\"+v).split(\".\")[1]||\"\").length;var ss=\"\"+s;var sd=(ss.split(\".\")[1]||\"\").length;if(sd===0&&/\\\\d?e-\\\\d?/.test(ss)){var m=ss.match(/\\\\d?e-(\\\\d?)/);if(m&&m[1]){sd=parseInt(m[1],10);}}var d=vd>sd?vd:sd;var vi=parseInt(v.toFixed(d).replace(\".\",\"\"),10);var si=parseInt(s.toFixed(d).replace(\".\",\"\"),10);return (vi%si)/Math.pow(10,d);}';\n\n/**\n * Hoisted `Object.prototype.hasOwnProperty` reference. Record fast/slow paths\n * iterate keys with `for(k in o)` (no `Object.keys` array allocation) and guard\n * each key with `__zcHop.call(o,k)` to skip inherited enumerable properties —\n * yielding the exact own-enumerable string-key set `Object.keys` would, so\n * fast/slow stay in agreement and parity with zod's own-key record semantics is\n * preserved. The hoisted reference inlines in V8; reading the prototype property\n * per call would not.\n */\nexport const ZC_HOP_DECL = \"const __zcHop=Object.prototype.hasOwnProperty;\";\n\n/**\n * Issue codes zod raises from a schema's `_zod.parse` rather than from a check,\n * and which therefore carry `continue !== true` — what `util.aborted` looks for.\n *\n * Everything a CHECK produces (`too_small`, `too_big`, `invalid_format`,\n * `not_multiple_of`, a refine's `custom`) is continuable, because `$ZodCheck`\n * sets `continue: !def.abort` and an `abort: true` check costs the schema its\n * compiled path anyway — so classifying by code alone is exact for generated\n * issues.\n *\n * Read by {@link ZC_AB_DECL} and by the union's option-pruning loop, which\n * applies the same rule inline over a per-option issue array.\n */\nconst ABORTING_ISSUE_CODES: readonly string[] = [\n \"invalid_type\",\n \"invalid_value\",\n \"invalid_union\",\n \"unrecognized_keys\",\n \"invalid_key\",\n \"invalid_element\",\n];\n\n/** `c===\"invalid_type\"||c===\"invalid_value\"||…` over the code held in `codeExpr`. */\nexport function abortingCodeTest(codeExpr: string): string {\n return ABORTING_ISSUE_CODES.map((code) => `${codeExpr}===${JSON.stringify(code)}`).join(\"||\");\n}\n\n/**\n * Port of zod's `util.aborted(payload, startIndex)`: has anything since\n * `startIndex` produced a NON-continuable issue?\n *\n * zod gates a schema's check chain on this (`runChecks`: `else if (isAborted)\n * continue`), which is what makes a container's `.refine()` still run after a\n * property or element failed its own `min`/`max`/format check, and stop running\n * once one failed to parse at all. The `startIndex` is the issue count when the\n * node was entered, mirroring the fresh payload zod hands each sub-schema.\n *\n * Size/length checks are exempt in zod — they declare a `when` predicate, which\n * bypasses the abort gate — so generated code leaves those ungated and only\n * wraps the refine/superRefine effects.\n */\nexport const ZC_AB_DECL = `function __zcAb(e,i){for(;i<e.length;i++){var c=e[i].code;if(${abortingCodeTest(\"c\")})return true;}return false;}`;\n\n/**\n * Port of zod's `util.finalizeIssue` for issues NESTED inside an\n * `invalid_key` / `invalid_element` wrapper. Those never reach the top-level\n * finalization loop (which walks only the outer array), so zod finalizes them\n * where it builds the wrapper: locale message applied when none was baked in,\n * `input` cleared. Their `path` stays RELATIVE to the key or value schema — zod\n * ran it on a fresh payload — so nothing rewrites it.\n *\n * `input` is `delete`d rather than assigned `undefined`, matching zod's own\n * `delete full.input` and the top-level finalizer: the key's PRESENCE is\n * observable (`\"input\" in issue`, `Object.keys`, spread, a strict deep-equal\n * against a zod issue), so assigning left a nested issue one key wider than\n * zod's. Only reached while an error is being built — never on a successful\n * parse — so the dictionary-mode transition it costs is confined to the path\n * that then constructs a ZodError anyway.\n */\nexport const ZC_FZ_DECL =\n \"function __zcFz(e){for(var i=0;i<e.length;i++){var s=e[i];\" +\n 'if(s.message===undefined&&typeof __zcMsg===\"function\")s.message=__zcMsg(s);' +\n \"delete s.input;}return e;}\";\n\n/**\n * Port of zod's `util.prefixIssues(key, issues)` for a map entry whose key is a\n * property-key type: each issue's RELATIVE path is spliced onto the map's own\n * path plus the key, and the issue moves into the parent's array.\n *\n * `b.concat(k,x.path)` flattens `x.path` one level, giving exactly\n * `[...base, key, ...relative]`.\n */\nexport const ZC_PFX_DECL =\n \"function __zcPfx(d,s,b,k){for(var i=0;i<s.length;i++){var x=s[i];x.path=b.concat(k,x.path);d.push(x);}}\";\n\n/** Does `keyExpr` hold one of zod's `util.propertyKeyTypes` (string|number|symbol)? */\nexport function propertyKeyTest(keyExpr: string): string {\n return `typeof ${keyExpr}===\"string\"||typeof ${keyExpr}===\"number\"||typeof ${keyExpr}===\"symbol\"`;\n}\n\n/**\n * superRefine fast-check: run the payload callback on a throwaway payload and\n * report whether it both added nothing AND left the value alone. zod's own\n * wrapper (the referenced function) installs `addIssue` and normalizes what the\n * user adds, so the verdict is zod's; the issues themselves are re-collected by\n * the slow walk.\n *\n * The `p.value===v` half is what lets a superRefine node keep a fast path at\n * all. `value` is writable public API ($RefinementCtx extends ParsePayload), so\n * a callback may rewrite it, and the fast path's caller returns the ORIGINAL\n * input on success — which would then be stale. Reporting false when the value\n * moved routes those parses into the slow walk, which propagates the new value\n * (see ZC_SR_DECL). Callbacks that only validate — effectively all of them —\n * still take the fast exit.\n */\nexport const ZC_SR_OK_DECL =\n \"function __zcSrOk(f,v){var p={value:v,issues:[]};__zcSrRun(f,p);\" +\n \"return p.issues.length===0&&p.value===v;}\";\n\n/**\n * Module-local (never imported by generated code) — __zcSr/__zcSrOk call it.\n * Lean mode declares it in the runtime module beside them; inline mode pushes it\n * into the preamble alongside whichever of the two is used.\n *\n * Invoke the referenced wrapper, reproducing zod's synchronous-parse contract:\n * a callback that returns a promise makes zod raise $ZodAsyncError rather than\n * silently accepting. Because the ref points at zod's superRefine WRAPPER, not\n * the user's function, an async callback cannot be detected while extracting —\n * the returned thenable is the only evidence, so the test lives here. Sync\n * callbacks return undefined, so the guard short-circuits on the first operand.\n */\nexport const ZC_SR_RUN_DECL =\n \"function __zcSrRun(f,p){var r=f(p);\" +\n 'if(r&&typeof r.then===\"function\"){throw new __zcCore.$ZodAsyncError();}}';\n\n/**\n * z.custom()/z.instanceof() fast verdict. Zod treats truthy predicate returns\n * as success and raises $ZodAsyncError when a synchronous parse encounters a\n * thenable, including a non-async function that happens to return a Promise.\n */\nexport const ZC_CUSTOM_OK_DECL =\n \"function __zcCu(f,v){var r=f(v);\" +\n 'if(r&&typeof r.then===\"function\"){throw new __zcCore.$ZodAsyncError();}return !!r;}';\n\n/**\n * superRefine slow-path merge: run the callback, then move its issues onto the\n * validator's list the way zod's finalizeIssue does — the node's path prefixed\n * onto any path the user supplied, and the internal `inst`/`continue` fields\n * dropped (they are zod bookkeeping, deleted before the issue is user-visible).\n *\n * Returns the payload, so the caller can write `.value` back (the callback may\n * have rewritten it) and read `.aborted`. Aborted is set when any issue aborts\n * in zod's sense (`continue !== true`, which covers `fatal: true` and the string\n * shorthand, whose issue carries no `continue` at all) — or when the callback\n * set it directly, also public payload API. A union option uses it to mark\n * itself aborted, matching how zod prunes option errors; without it an option\n * failing only through superRefine would be surfaced directly instead of inside\n * `invalid_union`.\n */\nexport const ZC_SR_DECL =\n \"function __zcSr(f,v,p,e){var q={value:v,issues:[]};__zcSrRun(f,q);\" +\n \"for(var i=0;i<q.issues.length;i++){var s=q.issues[i],t={};\" +\n 'for(var k in s){if(k!==\"inst\"&&k!==\"continue\")t[k]=s[k];}' +\n \"if(s.continue!==true)q.aborted=true;\" +\n \"t.path=s.path&&s.path.length?p.concat(s.path):p;e.push(t);}return q;}\";\n\n/** Non-issue runtime helper declarations hosted in the virtual module. */\nexport const RUNTIME_HELPER_DECLS: Readonly<Record<string, string>> = {\n __zcAb: ZC_AB_DECL,\n __zcFsr: ZC_FSR_DECL,\n __zcFz: ZC_FZ_DECL,\n __zcHop: ZC_HOP_DECL,\n __zcPfx: ZC_PFX_DECL,\n __zcCu: ZC_CUSTOM_OK_DECL,\n __zcSr: ZC_SR_DECL,\n __zcSrOk: ZC_SR_OK_DECL,\n};\n"],"mappings":";AA4DA,MAAa,cAAgD;CAC3D,QAAQ;CACR,SAAS;CACT,SAAS;CACT,QAAQ;CACR,SAAS;CACT,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;AACV;;;;;;AAOA,MAAa,cACX;;;;;;;;;;AAWF,MAAa,cAAc;;;;;;;;;;;;;;AAe3B,MAAM,uBAA0C;CAC9C;CACA;CACA;CACA;CACA;CACA;AACF;;AAGA,SAAgB,iBAAiB,UAA0B;CACzD,OAAO,qBAAqB,KAAK,SAAS,GAAG,SAAS,KAAK,KAAK,UAAU,IAAI,GAAG,CAAC,CAAC,KAAK,IAAI;AAC9F;;;;;;;;;;;;;;;AAgBA,MAAa,aAAa,gEAAgE,iBAAiB,GAAG,EAAE;;;;;;;;;;;;;;;;;AAkBhH,MAAa,aACX;;;;;;;;;AAYF,MAAa,cACX;;AAGF,SAAgB,gBAAgB,SAAyB;CACvD,OAAO,UAAU,QAAQ,sBAAsB,QAAQ,sBAAsB,QAAQ;AACvF;;;;;;;;;;;;;;;;AAiBA,MAAa,gBACX;;;;;;;;;;;;;AAeF,MAAa,iBACX;;;;;;AAQF,MAAa,oBACX;;;;;;;;;;;;;;;;AAkBF,MAAa,aACX;;AAOF,MAAa,uBAAyD;CACpE,QAAQ;CACR,SAAS;CACT,QAAQ;CACR,SAAS;CACT,SAAS;CACT,QAAQ;CACR,QAAQ;CACR,UAAU;AACZ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"catch.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/catch.ts"],"mappings":";;;iBAGgB,UAAU,IAAI;EAAa;GAAiB,GAAG;
|
|
1
|
+
{"version":3,"file":"catch.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/catch.ts"],"mappings":";;;iBAGgB,UAAU,IAAI;EAAa;GAAiB,GAAG;iBAiC/C,UAAU,IAAI,SAAS,GAAG"}
|
|
@@ -9,7 +9,7 @@ function slowCatch(ir, g) {
|
|
|
9
9
|
`if(${tempIssues}.length>0){`,
|
|
10
10
|
`for(var ${idxVar}=0;${idxVar}<${tempIssues}.length;${idxVar}++){`,
|
|
11
11
|
`if(${tempIssues}[${idxVar}].message===undefined&&typeof __zcMsg==="function"){${tempIssues}[${idxVar}].message=__zcMsg(${tempIssues}[${idxVar}]);}`,
|
|
12
|
-
|
|
12
|
+
`delete ${tempIssues}[${idxVar}].input;`,
|
|
13
13
|
`}`,
|
|
14
14
|
`var ${cvVar}=__rf[${ir.refIndex}]._zod.def.catchValue;`,
|
|
15
15
|
`${g.output}=typeof ${cvVar}==="function"?${cvVar}({value:${g.input},issues:${tempIssues},error:{issues:${tempIssues}},input:${g.input}}):${cvVar};`,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"catch.js","names":[],"sources":["../../../../src/core/codegen/schemas/catch.ts"],"sourcesContent":["import type { CatchIR, SchemaIR } from \"../../types.js\";\nimport type { FastGen, SlowGen } from \"../context.js\";\n\nexport function slowCatch(ir: SchemaIR & { type: \"catch\" }, g: SlowGen): string {\n const tempIssues = g.temp(\"ci\");\n const idxVar = g.temp(\"ck\");\n const cvVar = g.temp(\"cv\");\n // Mirrors $ZodCatch: on inner failure, catchValue receives a ctx of\n // { value, issues, error: { issues }, input } and its result replaces the\n // value; the issues are swallowed. Finalize messages first so ctx readers\n // see zod-shaped issues (one finalized array serves both fields).\n return [\n `var ${tempIssues}=[];`,\n g.visit(ir.inner, { issues: tempIssues }),\n `if(${tempIssues}.length>0){`,\n `for(var ${idxVar}=0;${idxVar}<${tempIssues}.length;${idxVar}++){`,\n `if(${tempIssues}[${idxVar}].message===undefined&&typeof __zcMsg===\"function\"){${tempIssues}[${idxVar}].message=__zcMsg(${tempIssues}[${idxVar}]);}`,\n
|
|
1
|
+
{"version":3,"file":"catch.js","names":[],"sources":["../../../../src/core/codegen/schemas/catch.ts"],"sourcesContent":["import type { CatchIR, SchemaIR } from \"../../types.js\";\nimport type { FastGen, SlowGen } from \"../context.js\";\n\nexport function slowCatch(ir: SchemaIR & { type: \"catch\" }, g: SlowGen): string {\n const tempIssues = g.temp(\"ci\");\n const idxVar = g.temp(\"ck\");\n const cvVar = g.temp(\"cv\");\n // Mirrors $ZodCatch: on inner failure, catchValue receives a ctx of\n // { value, issues, error: { issues }, input } and its result replaces the\n // value; the issues are swallowed. Finalize messages first so ctx readers\n // see zod-shaped issues (one finalized array serves both fields).\n //\n // `input` is `delete`d, not assigned `undefined`, so `ctx.error.issues[n]` has\n // exactly zod's key set — zod's `finalizeIssue` deletes it, and the key's\n // presence shows through `Object.keys`/spread/strict deep-equal even when the\n // value reads as `undefined` either way. (`ctx.issues` is a documented\n // divergence: zod hands the callback the RAW payload array there, carrying\n // `inst` — the schema instance — which compiled code has no counterpart for.\n // See tests/known-divergences.test.ts.) Only reached on an inner failure, and\n // that failure is about to be swallowed by the catch value, so nothing on a\n // successful parse pays for the delete.\n return [\n `var ${tempIssues}=[];`,\n g.visit(ir.inner, { issues: tempIssues }),\n `if(${tempIssues}.length>0){`,\n `for(var ${idxVar}=0;${idxVar}<${tempIssues}.length;${idxVar}++){`,\n `if(${tempIssues}[${idxVar}].message===undefined&&typeof __zcMsg===\"function\"){${tempIssues}[${idxVar}].message=__zcMsg(${tempIssues}[${idxVar}]);}`,\n `delete ${tempIssues}[${idxVar}].input;`,\n `}`,\n `var ${cvVar}=__rf[${ir.refIndex}]._zod.def.catchValue;`,\n `${g.output}=typeof ${cvVar}===\"function\"?${cvVar}({value:${g.input},issues:${tempIssues},error:{issues:${tempIssues}},input:${g.input}}):${cvVar};`,\n `}`,\n \"\",\n ].join(\"\\n\");\n}\n\nexport function fastCatch(ir: CatchIR, g: FastGen): string | null {\n return g.visit(ir.inner);\n}\n"],"mappings":";AAGA,SAAgB,UAAU,IAAkC,GAAoB;CAC9E,MAAM,aAAa,EAAE,KAAK,IAAI;CAC9B,MAAM,SAAS,EAAE,KAAK,IAAI;CAC1B,MAAM,QAAQ,EAAE,KAAK,IAAI;CAezB,OAAO;EACL,OAAO,WAAW;EAClB,EAAE,MAAM,GAAG,OAAO,EAAE,QAAQ,WAAW,CAAC;EACxC,MAAM,WAAW;EACjB,WAAW,OAAO,KAAK,OAAO,GAAG,WAAW,UAAU,OAAO;EAC7D,MAAM,WAAW,GAAG,OAAO,sDAAsD,WAAW,GAAG,OAAO,oBAAoB,WAAW,GAAG,OAAO;EAC/I,UAAU,WAAW,GAAG,OAAO;EAC/B;EACA,OAAO,MAAM,QAAQ,GAAG,SAAS;EACjC,GAAG,EAAE,OAAO,UAAU,MAAM,gBAAgB,MAAM,UAAU,EAAE,MAAM,UAAU,WAAW,iBAAiB,WAAW,UAAU,EAAE,MAAM,KAAK,MAAM;EAClJ;EACA;CACF,CAAC,CAAC,KAAK,IAAI;AACb;AAEA,SAAgB,UAAU,IAAa,GAA2B;CAChE,OAAO,EAAE,MAAM,GAAG,KAAK;AACzB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discriminated-union.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/discriminated-union.ts"],"mappings":";;;;KAcK,oBAAoB;iBAET,uBACd,IAAI;EAAa;GACjB,GAAG;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"discriminated-union.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/discriminated-union.ts"],"mappings":";;;;KAcK,oBAAoB;iBAET,uBACd,IAAI;EAAa;GACjB,GAAG;;;;;;;;;;;;;;;;;;iBA8DW,4BACd,GAAG,SACH,uBACA,gBAAgB,qBAChB,kBAAkB;iBAsFJ,uBAAuB,IAAI,sBAAsB,GAAG;;;;;;;;;;;;;;;;;;;iBAqDpD,yBACd,kBAAkB;EACf;EAAuB,OAAO"}
|
|
@@ -20,11 +20,10 @@ function slowDiscriminatedUnion(ir, g) {
|
|
|
20
20
|
})}
|
|
21
21
|
break;`;
|
|
22
22
|
}
|
|
23
|
-
const validValues = ir.cases.map((c) => literalToJs(c.value)).join(",");
|
|
24
23
|
const msgProp = g.typeMsg === void 0 ? "" : `,message:${JSON.stringify(g.typeMsg)}`;
|
|
25
24
|
code += emit`
|
|
26
25
|
default:
|
|
27
|
-
${g.issues}.push({code:"invalid_union",errors:[],note:"No matching discriminator",discriminator:${discKey}
|
|
26
|
+
${g.issues}.push({code:"invalid_union",errors:[],note:"No matching discriminator",discriminator:${discKey}${msgProp},input:${g.input},path:${extendPath(g.path, discKey)}});
|
|
28
27
|
}`;
|
|
29
28
|
if (ir.options.some(hasMutation)) code += `${g.output}=${objVar};`;
|
|
30
29
|
code += `}`;
|
|
@@ -120,7 +119,16 @@ function fastDiscriminatedUnion(ir, g) {
|
|
|
120
119
|
* regardless of size.
|
|
121
120
|
*/
|
|
122
121
|
const MIN_AUTO_DISCRIMINATE_OPTIONS = 5;
|
|
123
|
-
/**
|
|
122
|
+
/**
|
|
123
|
+
* Values that switch correctly under `===` (excludes `undefined` and `NaN`).
|
|
124
|
+
*
|
|
125
|
+
* Also excludes a SYMBOL — now that `LiteralIR.values` admits one, this is the
|
|
126
|
+
* guard that keeps it out. A symbol discriminant has no source form, so there
|
|
127
|
+
* is no `case` label to emit for it (`literalToJs` refuses it by type), and the
|
|
128
|
+
* ordinal-table variant is string-keyed besides. Detection bails to the
|
|
129
|
+
* `||`-chain, where the option's own literal check does the right thing by
|
|
130
|
+
* reading the value list off the retained schema.
|
|
131
|
+
*/
|
|
124
132
|
function isSwitchableDiscriminant(v) {
|
|
125
133
|
return v === null || typeof v === "string" || typeof v === "boolean" || typeof v === "bigint" || typeof v === "number" && !Number.isNaN(v);
|
|
126
134
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discriminated-union.js","names":[],"sources":["../../../../src/core/codegen/schemas/discriminated-union.ts"],"sourcesContent":["import type { DiscriminatedUnionIR, ObjectIR, SchemaIR } from \"../../types.js\";\nimport type { FastGen, SlowGen } from \"../context.js\";\nimport {\n declareFastTemps,\n emitConstant,\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\")}\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 validValues = ir.cases.map((c) => literalToJs(c.value)).join(\",\");\n const msgProp = g.typeMsg === undefined ? \"\" : `,message:${JSON.stringify(g.typeMsg)}`;\n code += emit`\n default:\n ${g.issues}.push({code:\"invalid_union\",errors:[],note:\"No matching discriminator\",discriminator:${discKey},options:[${validValues}]${msgProp},input:${g.input},path:${extendPath(g.path, discKey)}});\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 const body = g.scoped(helperParam);\n const table = stringDispatchTable(cases);\n\n if (table === null) {\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 } else {\n // Dispatch through a string→ordinal table, then switch on the ordinal. V8\n // compiles a switch over STRING labels as sequential `===` comparisons, so\n // the plain form costs ~0.5 ns per preceding case: measured 4.1 ns (2\n // variants), 11.4 (8), 52.1 (80). A dense integer switch becomes a jump\n // table, leaving the lookup flat — 5.9 ns (8) and 15.1 (80), i.e. 1.9x to\n // 3.5x, for a table that adds ~1.3% to a union's generated bytes.\n //\n // `typeof t===\"string\"` is load-bearing: property access would coerce a\n // non-string discriminator (an object's toString, a number) into a key that\n // could hit a case whose own discriminator check the switch has stripped.\n // Strict equality never matched those, so the guard keeps the verdict.\n const caseStrs: string[] = [];\n for (const [optionIndex, ordinal] of table.ordinals) {\n const check = body.visit(options[optionIndex] as SchemaIR, { discSkipKey: discriminator });\n if (check === null) return null;\n caseStrs.push(`case ${ordinal}:return ${check};`);\n }\n const tableVar = emitConstant(g.ctx, \"dt\", table.initializer);\n const t = g.temp(\"dv\");\n g.ctx.preamble.push(\n `function ${helperName}(${helperParam}){${declareFastTemps(body.scope)}var ${t}=${helperParam}[${discKey}];` +\n `switch(typeof ${t}===\"string\"?${tableVar}[${t}]:0){${caseStrs.join(\"\")}default:return false;}}`,\n );\n }\n\n return `typeof ${x}===\"object\"&&${x}!==null&&!Array.isArray(${x})&&${helperName}(${x})`;\n}\n\n/**\n * Minimum case count for ordinal dispatch. Measured crossover is 3: at 2 cases\n * the string switch is one comparison and beats the extra table lookup\n * (4.1 ns vs 4.5), at 3 the table already wins (6.1 vs 4.6).\n */\nconst MIN_TABLE_DISPATCH = 3;\n\n/**\n * Build the `{value: ordinal}` dispatch table for a set of cases, or null when\n * the plain string switch should be kept. Requires every discriminator value to\n * be a string — mixed types would collide once coerced to property keys (`5`\n * and `\"5\"`) — and excludes `__proto__`, which an object literal cannot hold as\n * an own key. Values that select the SAME option share one ordinal, so a\n * multi-value literal emits its check once instead of per value.\n */\nfunction stringDispatchTable(\n cases: readonly DiscriminatorCase[],\n): { initializer: string; ordinals: Map<number, number> } | null {\n if (cases.length < MIN_TABLE_DISPATCH) return null;\n if (!cases.every((c) => typeof c.value === \"string\" && c.value !== \"__proto__\")) return null;\n\n const ordinals = new Map<number, number>();\n const entries: string[] = [];\n for (const { value, option } of cases) {\n let ordinal = ordinals.get(option);\n if (ordinal === undefined) {\n ordinal = ordinals.size + 1;\n ordinals.set(option, ordinal);\n }\n entries.push(`${escapeString(value as string)}:${ordinal}`);\n }\n return { initializer: `{${entries.join(\",\")}}`, ordinals };\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/** Values that switch correctly under `===` (excludes `undefined` and `NaN`). */\nfunction isSwitchableDiscriminant(\n v: string | number | boolean | null | bigint | undefined,\n): 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 * Fast-path only: the slow path keeps `z.union`'s sequential trial and its\n * `invalid_union` error shape, so failure output stays byte-identical to Zod.\n */\nexport function detectUnionDiscriminator(\n options: readonly SchemaIR[],\n): { discriminator: string; cases: DiscriminatorCase[] } | null {\n if (options.length < MIN_AUTO_DISCRIMINATE_OPTIONS) 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 {\n declareFastTemps,\n emitConstant,\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\")}\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 // input, path: [def.discriminator] }`. There is deliberately no `options`\n // key — zod never enumerates the valid discriminator values here, so adding\n // them invents a field consumers would find on no real zod issue.\n code += emit`\n default:\n ${g.issues}.push({code:\"invalid_union\",errors:[],note:\"No matching discriminator\",discriminator:${discKey}${msgProp},input:${g.input},path:${extendPath(g.path, discKey)}});\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 const body = g.scoped(helperParam);\n const table = stringDispatchTable(cases);\n\n if (table === null) {\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 } else {\n // Dispatch through a string→ordinal table, then switch on the ordinal. V8\n // compiles a switch over STRING labels as sequential `===` comparisons, so\n // the plain form costs ~0.5 ns per preceding case: measured 4.1 ns (2\n // variants), 11.4 (8), 52.1 (80). A dense integer switch becomes a jump\n // table, leaving the lookup flat — 5.9 ns (8) and 15.1 (80), i.e. 1.9x to\n // 3.5x, for a table that adds ~1.3% to a union's generated bytes.\n //\n // `typeof t===\"string\"` is load-bearing: property access would coerce a\n // non-string discriminator (an object's toString, a number) into a key that\n // could hit a case whose own discriminator check the switch has stripped.\n // Strict equality never matched those, so the guard keeps the verdict.\n const caseStrs: string[] = [];\n for (const [optionIndex, ordinal] of table.ordinals) {\n const check = body.visit(options[optionIndex] as SchemaIR, { discSkipKey: discriminator });\n if (check === null) return null;\n caseStrs.push(`case ${ordinal}:return ${check};`);\n }\n const tableVar = emitConstant(g.ctx, \"dt\", table.initializer);\n const t = g.temp(\"dv\");\n g.ctx.preamble.push(\n `function ${helperName}(${helperParam}){${declareFastTemps(body.scope)}var ${t}=${helperParam}[${discKey}];` +\n `switch(typeof ${t}===\"string\"?${tableVar}[${t}]:0){${caseStrs.join(\"\")}default:return false;}}`,\n );\n }\n\n return `typeof ${x}===\"object\"&&${x}!==null&&!Array.isArray(${x})&&${helperName}(${x})`;\n}\n\n/**\n * Minimum case count for ordinal dispatch. Measured crossover is 3: at 2 cases\n * the string switch is one comparison and beats the extra table lookup\n * (4.1 ns vs 4.5), at 3 the table already wins (6.1 vs 4.6).\n */\nconst MIN_TABLE_DISPATCH = 3;\n\n/**\n * Build the `{value: ordinal}` dispatch table for a set of cases, or null when\n * the plain string switch should be kept. Requires every discriminator value to\n * be a string — mixed types would collide once coerced to property keys (`5`\n * and `\"5\"`) — and excludes `__proto__`, which an object literal cannot hold as\n * an own key. Values that select the SAME option share one ordinal, so a\n * multi-value literal emits its check once instead of per value.\n */\nfunction stringDispatchTable(\n cases: readonly DiscriminatorCase[],\n): { initializer: string; ordinals: Map<number, number> } | null {\n if (cases.length < MIN_TABLE_DISPATCH) return null;\n if (!cases.every((c) => typeof c.value === \"string\" && c.value !== \"__proto__\")) return null;\n\n const ordinals = new Map<number, number>();\n const entries: string[] = [];\n for (const { value, option } of cases) {\n let ordinal = ordinals.get(option);\n if (ordinal === undefined) {\n ordinal = ordinals.size + 1;\n ordinals.set(option, ordinal);\n }\n entries.push(`${escapeString(value as string)}:${ordinal}`);\n }\n return { initializer: `{${entries.join(\",\")}}`, ordinals };\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 * Fast-path only: the slow path keeps `z.union`'s sequential trial and its\n * `invalid_union` error shape, so failure output stays byte-identical to Zod.\n */\nexport function detectUnionDiscriminator(\n options: readonly SchemaIR[],\n): { discriminator: string; cases: DiscriminatorCase[] } | null {\n if (options.length < MIN_AUTO_DISCRIMINATE_OPTIONS) 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":";;;;;;;
|
|
1
|
+
{"version":3,"file":"effect.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/effect.ts"],"mappings":";;;;;;;iBAqBgB,WAAW,IAAI,oBAAoB,oBAAoB,GAAG;;;;;;;;;iBAmC1D,YAAY,OAAO,qBAAqB,cAAc,GAAG;;;;;iBA8CzD,oBACd,OAAO,0BACP,cACA,GAAG;;;;;;;;;;;;;iBAmBW,iBACd,OAAO,0BACP,cACA,GAAG"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { emitEffectCallable, emitRuntimeHelper, extendStaticPath, extendStaticPathIndex } from "../context.js";
|
|
1
|
+
import { emitConstant, emitEffectCallable, emitRuntimeHelper, extendStaticPath, extendStaticPathIndex } from "../context.js";
|
|
2
2
|
import { emit } from "../emit.js";
|
|
3
3
|
import { ZC_SR_DECL, ZC_SR_OK_DECL, ZC_SR_RUN_DECL } from "../issue-decls.js";
|
|
4
4
|
//#region src/core/codegen/schemas/effect.ts
|
|
@@ -41,9 +41,10 @@ function slowEffect(ir, g) {
|
|
|
41
41
|
function refineCheck(check, expr, g) {
|
|
42
42
|
const messageProp = check.message === void 0 ? "" : `,message:${JSON.stringify(check.message)}`;
|
|
43
43
|
const path = (check.path ?? []).reduce((acc, segment) => typeof segment === "number" ? extendStaticPathIndex(acc, segment) : extendStaticPath(acc, segment), g.path);
|
|
44
|
+
const paramsProp = check.paramsRefIndex === void 0 ? "" : `,params:${emitConstant(g.ctx, "rpa", `__rf[${check.paramsRefIndex}]`)}`;
|
|
44
45
|
return emit`
|
|
45
46
|
if(!${emitEffectCallable(g.ctx, check)}(${expr})){
|
|
46
|
-
${g.issues}.push({code:"custom",path:${path}${messageProp},input:${expr}});
|
|
47
|
+
${g.issues}.push({code:"custom",path:${path}${paramsProp}${messageProp},input:${expr}});
|
|
47
48
|
}`;
|
|
48
49
|
}
|
|
49
50
|
/**
|
|
@@ -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 emitEffectCallable,\n emitRuntimeHelper,\n extendStaticPath,\n extendStaticPathIndex,\n} from \"../context.js\";\nimport { emit } from \"../emit.js\";\nimport { 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 if (ir.effectKind === \"preprocess\") {\n const valueVar = g.temp(\"pv\");\n return `${emit`\n var ${valueVar}=${emitEffectCallable(g.ctx, ir)}(${g.input});\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 }${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 // Custom issues are created by the refine check instance, so only the\n // refine's own message applies (never the schema-level error). With no\n // message baked in, __zcFin applies the locale default (\"Invalid input\").\n const messageProp =\n check.message === undefined ? \"\" : `,message:${JSON.stringify(check.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 return emit`\n if(!${emitEffectCallable(g.ctx, check)}(${expr})){\n ${g.issues}.push({code:\"custom\",path:${path}${messageProp},input:${expr}});\n }`;\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 let code = `var ${p}=${helper}(${fn},${expr},${g.path},${g.issues});${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":";;;;;;;;
|
|
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_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 if (ir.effectKind === \"preprocess\") {\n const valueVar = g.temp(\"pv\");\n return `${emit`\n var ${valueVar}=${emitEffectCallable(g.ctx, ir)}(${g.input});\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 }${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 // Custom issues are created by the refine check instance, so only the\n // refine's own message applies (never the schema-level error). With no\n // message baked in, __zcFin applies the locale default (\"Invalid input\").\n const messageProp =\n check.message === undefined ? \"\" : `,message:${JSON.stringify(check.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 return emit`\n if(!${emitEffectCallable(g.ctx, check)}(${expr})){\n ${g.issues}.push({code:\"custom\",path:${path}${paramsProp}${messageProp},input:${expr}});\n }`;\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 let code = `var ${p}=${helper}(${fn},${expr},${g.path},${g.issues});${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":";;;;;;;;AAqBA,SAAgB,WAAW,IAA4C,GAAoB;CACzF,IAAI,GAAG,eAAe,cAAc;EAClC,MAAM,WAAW,EAAE,KAAK,IAAI;EAC5B,OAAO,GAAG,IAAI;YACN,SAAS,GAAG,mBAAmB,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,MAAM;QACzD,EAAE,OAAO,GAAG,SAAS;QACrB,EAAE,MAAM,GAAG,OAAO;GAAE,OAAO;GAAU,QAAQ,EAAE;GAAQ,SAAS,EAAE;EAAQ,CAAC,EAAE;MAC/E;CACJ;CAEA,MAAM,YAAY,EAAE,KAAK,IAAI;CAC7B,MAAM,YAAY,EAAE,MAAM,GAAG,KAAK;CAKlC,MAAM,cAAc,EAAE,UAAU,QAAQ,EAAE,QAAQ,WAAW;CAE7D,OAAO,GAAG,IAAI;UACN,UAAU,GAAG,EAAE,OAAO;MAC1B,UAAU;SACP,EAAE,OAAO,YAAY,UAAU;QAChC,EAAE,OAAO,GAAG,mBAAmB,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,OAAO;OACvD,YAAY;IACf;AACJ;;;;;;;;;AAUA,SAAgB,YAAY,OAA4B,MAAc,GAAoB;CAIxF,MAAM,cACJ,MAAM,YAAY,KAAA,IAAY,KAAK,YAAY,KAAK,UAAU,MAAM,OAAO;CAG7E,MAAM,QAAQ,MAAM,QAAQ,CAAC,EAAA,CAAG,QAC7B,KAAK,YACJ,OAAO,YAAY,WACf,sBAAsB,KAAK,OAAO,IAClC,iBAAiB,KAAK,OAAO,GACnC,EAAE,IACJ;CAOA,MAAM,aACJ,MAAM,mBAAmB,KAAA,IACrB,KACA,WAAW,aAAa,EAAE,KAAK,OAAO,QAAQ,MAAM,eAAe,EAAE;CAC3E,OAAO,IAAI;UACH,mBAAmB,EAAE,KAAK,KAAK,EAAE,GAAG,KAAK;QAC3C,EAAE,OAAO,4BAA4B,OAAO,aAAa,YAAY,SAAS,KAAK;;AAE3F;;;;;;AAOA,SAAS,sBAAsB,KAA2B;CACxD,IAAI,IAAI,SAAS,UAAU,CAAC,IAAI,SAAS,SAAA,+GAAuB,GAC9D,IAAI,SAAS,KAAK,cAAc;AAEpC;;;;;AAMA,SAAgB,oBACd,OACA,MACA,GACQ;CACR,sBAAsB,EAAE,GAAG;CAE3B,OAAO,GADI,kBAAkB,EAAE,KAAK,YAAY,aACrC,EAAE,GAAG,mBAAmB,EAAE,KAAK,KAAK,EAAE,GAAG,KAAK;AAC3D;;;;;;;;;;;;;AAcA,SAAgB,iBACd,OACA,MACA,GACQ;CACR,sBAAsB,EAAE,GAAG;CAC3B,MAAM,SAAS,kBAAkB,EAAE,KAAK,UAAU,UAAU;CAC5D,MAAM,KAAK,mBAAmB,EAAE,KAAK,KAAK;CAC1C,MAAM,IAAI,EAAE,KAAK,IAAI;CACrB,IAAI,OAAO,OAAO,EAAE,GAAG,OAAO,GAAG,GAAG,GAAG,KAAK,GAAG,EAAE,KAAK,GAAG,EAAE,OAAO,IAAI,EAAE,OAAO,GAAG,EAAE;CAGpF,IAAI,EAAE,SAAS,QAAQ,MAAM,EAAE,YAAY,EAAE,QAAQ;CACrD,OAAO;AACT"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"enum.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/enum.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"enum.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/enum.ts"],"mappings":";;;iBA4BgB,SAAS,IAAI,QAAQ,GAAG;iBA0BxB,SAAS,IAAI,QAAQ,GAAG"}
|
|
@@ -2,8 +2,32 @@ import { emitSet, escapeString } from "../context.js";
|
|
|
2
2
|
import { emit } from "../emit.js";
|
|
3
3
|
import { invalidValue } from "../emit-issue.js";
|
|
4
4
|
//#region src/core/codegen/schemas/enum.ts
|
|
5
|
+
/**
|
|
6
|
+
* An enum can legitimately accept NOTHING, and it is reachable three ways
|
|
7
|
+
* through the public API: `z.enum([])`, `z.enum({})`, and — the non-obvious one
|
|
8
|
+
* — `z.enum([1, 2])`, a NUMERIC ARRAY. Zod turns that array into the entries
|
|
9
|
+
* `{1: 1, 2: 2}`, which is indistinguishable from a TS numeric enum's reverse
|
|
10
|
+
* mapping, so its reverse-mapping filter strips every entry back out and leaves
|
|
11
|
+
* `_zod.values` empty. Zod then rejects every input with `invalid_value` and
|
|
12
|
+
* `values: []`, and the compiled validator must do the same.
|
|
13
|
+
*
|
|
14
|
+
* Zero values must be special-cased because both code paths build their test by
|
|
15
|
+
* JOINING per-value comparisons: an empty join yields an empty STRING, emitting
|
|
16
|
+
* `if(){...}` and `return ();` — not a wrong validator but an unparseable one,
|
|
17
|
+
* so the whole compile died with a SyntaxError.
|
|
18
|
+
*
|
|
19
|
+
* (`z.literal()` joins the same way but needs no such guard: zod throws
|
|
20
|
+
* "Cannot create literal schema with no valid values" at construction, so an
|
|
21
|
+
* empty literal never reaches codegen.)
|
|
22
|
+
*/
|
|
23
|
+
function isEmpty(ir) {
|
|
24
|
+
return ir.values.length === 0;
|
|
25
|
+
}
|
|
5
26
|
function slowEnum(ir, g) {
|
|
6
27
|
const valuesExpr = JSON.stringify(ir.values);
|
|
28
|
+
if (isEmpty(ir)) return `${emit`
|
|
29
|
+
${invalidValue(g, valuesExpr)}
|
|
30
|
+
`}\n`;
|
|
7
31
|
if (ir.values.length <= 5) return `${emit`
|
|
8
32
|
if(${ir.values.map((v) => `${g.input}!==${escapeString(v)}`).join("&&")}){
|
|
9
33
|
${invalidValue(g, valuesExpr)}
|
|
@@ -17,6 +41,7 @@ function slowEnum(ir, g) {
|
|
|
17
41
|
}
|
|
18
42
|
function fastEnum(ir, g) {
|
|
19
43
|
const x = g.input;
|
|
44
|
+
if (isEmpty(ir)) return "false";
|
|
20
45
|
if (ir.values.length <= 5) return `(${ir.values.map((v) => `${x}===${escapeString(v)}`).join("||")})`;
|
|
21
46
|
return `${emitSet(g.ctx, "enum", ir.values)}.has(${x})`;
|
|
22
47
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"enum.js","names":[],"sources":["../../../../src/core/codegen/schemas/enum.ts"],"sourcesContent":["import type { EnumIR } from \"../../types.js\";\nimport type { FastGen, SlowGen } from \"../context.js\";\nimport { emitSet, ENUM_INLINE_THRESHOLD, escapeString } from \"../context.js\";\nimport { emit } from \"../emit.js\";\nimport { invalidValue } from \"../emit-issue.js\";\n\nexport function slowEnum(ir: EnumIR, g: SlowGen): string {\n const valuesExpr = JSON.stringify(ir.values);\n if (ir.values.length <= ENUM_INLINE_THRESHOLD) {\n // Inline equality checks for small enums (avoids Set allocation in preamble)\n const condition = ir.values.map((v) => `${g.input}!==${escapeString(v)}`).join(\"&&\");\n return `${emit`\n if(${condition}){\n ${invalidValue(g, valuesExpr)}\n }\n `}\\n`;\n }\n const setVar = g.set(\"enum\", ir.values);\n return `${emit`\n if(!${setVar}.has(${g.input})){\n ${invalidValue(g, valuesExpr)}\n }\n `}\\n`;\n}\n\nexport function fastEnum(ir: EnumIR, g: FastGen): string {\n const x = g.input;\n if (ir.values.length <= ENUM_INLINE_THRESHOLD) {\n // Inline equality checks for small enums, wrapped in parens for precedence safety\n return `(${ir.values.map((v) => `${x}===${escapeString(v)}`).join(\"||\")})`;\n }\n // Use Set for larger enums. Routed through emitSet so the fast check and the\n // slow walk share one declaration instead of emitting the value list twice.\n return `${emitSet(g.ctx, \"enum\", ir.values)}.has(${x})`;\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"enum.js","names":[],"sources":["../../../../src/core/codegen/schemas/enum.ts"],"sourcesContent":["import type { EnumIR } from \"../../types.js\";\nimport type { FastGen, SlowGen } from \"../context.js\";\nimport { emitSet, ENUM_INLINE_THRESHOLD, escapeString } from \"../context.js\";\nimport { emit } from \"../emit.js\";\nimport { invalidValue } from \"../emit-issue.js\";\n\n/**\n * An enum can legitimately accept NOTHING, and it is reachable three ways\n * through the public API: `z.enum([])`, `z.enum({})`, and — the non-obvious one\n * — `z.enum([1, 2])`, a NUMERIC ARRAY. Zod turns that array into the entries\n * `{1: 1, 2: 2}`, which is indistinguishable from a TS numeric enum's reverse\n * mapping, so its reverse-mapping filter strips every entry back out and leaves\n * `_zod.values` empty. Zod then rejects every input with `invalid_value` and\n * `values: []`, and the compiled validator must do the same.\n *\n * Zero values must be special-cased because both code paths build their test by\n * JOINING per-value comparisons: an empty join yields an empty STRING, emitting\n * `if(){...}` and `return ();` — not a wrong validator but an unparseable one,\n * so the whole compile died with a SyntaxError.\n *\n * (`z.literal()` joins the same way but needs no such guard: zod throws\n * \"Cannot create literal schema with no valid values\" at construction, so an\n * empty literal never reaches codegen.)\n */\nfunction isEmpty(ir: EnumIR): boolean {\n return ir.values.length === 0;\n}\n\nexport function slowEnum(ir: EnumIR, g: SlowGen): string {\n const valuesExpr = JSON.stringify(ir.values);\n if (isEmpty(ir)) {\n // Nothing to compare against — every input is invalid, so push the issue\n // unconditionally rather than guarding it with an empty condition.\n return `${emit`\n ${invalidValue(g, valuesExpr)}\n `}\\n`;\n }\n if (ir.values.length <= ENUM_INLINE_THRESHOLD) {\n // Inline equality checks for small enums (avoids Set allocation in preamble)\n const condition = ir.values.map((v) => `${g.input}!==${escapeString(v)}`).join(\"&&\");\n return `${emit`\n if(${condition}){\n ${invalidValue(g, valuesExpr)}\n }\n `}\\n`;\n }\n const setVar = g.set(\"enum\", ir.values);\n return `${emit`\n if(!${setVar}.has(${g.input})){\n ${invalidValue(g, valuesExpr)}\n }\n `}\\n`;\n}\n\nexport function fastEnum(ir: EnumIR, g: FastGen): string {\n const x = g.input;\n // See isEmpty: an accepted-value set can be empty, and an empty `||` join\n // would emit `()`. Nothing is ever accepted, so the fast check is constant.\n if (isEmpty(ir)) return \"false\";\n if (ir.values.length <= ENUM_INLINE_THRESHOLD) {\n // Inline equality checks for small enums, wrapped in parens for precedence safety\n return `(${ir.values.map((v) => `${x}===${escapeString(v)}`).join(\"||\")})`;\n }\n // Use Set for larger enums. Routed through emitSet so the fast check and the\n // slow walk share one declaration instead of emitting the value list twice.\n return `${emitSet(g.ctx, \"enum\", ir.values)}.has(${x})`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAwBA,SAAS,QAAQ,IAAqB;CACpC,OAAO,GAAG,OAAO,WAAW;AAC9B;AAEA,SAAgB,SAAS,IAAY,GAAoB;CACvD,MAAM,aAAa,KAAK,UAAU,GAAG,MAAM;CAC3C,IAAI,QAAQ,EAAE,GAGZ,OAAO,GAAG,IAAI;QACV,aAAa,GAAG,UAAU,EAAE;MAC9B;CAEJ,IAAI,GAAG,OAAO,UAAA,GAGZ,OAAO,GAAG,IAAI;WADI,GAAG,OAAO,KAAK,MAAM,GAAG,EAAE,MAAM,KAAK,aAAa,CAAC,GAAG,CAAC,CAAC,KAAK,IAEhE,EAAE;UACX,aAAa,GAAG,UAAU,EAAE;;MAEhC;CAGJ,OAAO,GAAG,IAAI;UADC,EAAE,IAAI,QAAQ,GAAG,MAEnB,EAAE,OAAO,EAAE,MAAM;QACxB,aAAa,GAAG,UAAU,EAAE;;IAEhC;AACJ;AAEA,SAAgB,SAAS,IAAY,GAAoB;CACvD,MAAM,IAAI,EAAE;CAGZ,IAAI,QAAQ,EAAE,GAAG,OAAO;CACxB,IAAI,GAAG,OAAO,UAAA,GAEZ,OAAO,IAAI,GAAG,OAAO,KAAK,MAAM,GAAG,EAAE,KAAK,aAAa,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,EAAE;CAI1E,OAAO,GAAG,QAAQ,EAAE,KAAK,QAAQ,GAAG,MAAM,EAAE,OAAO,EAAE;AACvD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"literal.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/literal.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"literal.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/literal.ts"],"mappings":";;;iBAoDgB,YAAY,IAAI,WAAW,GAAG;iBA2B9B,YAAY,IAAI,WAAW,GAAG"}
|
|
@@ -1,10 +1,34 @@
|
|
|
1
|
-
import { literalToJs } from "../context.js";
|
|
1
|
+
import { emitConstant, hasSourceForm, literalToJs } from "../context.js";
|
|
2
2
|
import { emit } from "../emit.js";
|
|
3
3
|
import { invalidValue } from "../emit-issue.js";
|
|
4
4
|
//#region src/core/codegen/schemas/literal.ts
|
|
5
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* The literal schema's OWN `def.values` array, aliased into a preamble binding.
|
|
7
|
+
*
|
|
8
|
+
* Used when at least one value has no JS source form (a symbol, an object): the
|
|
9
|
+
* accepted set cannot be written into the generated source, so it is READ from
|
|
10
|
+
* the retained schema instead — the same `__rf[N]` route `.default()` uses for
|
|
11
|
+
* its default value.
|
|
12
|
+
*
|
|
13
|
+
* `Array.prototype.includes` compares with SameValueZero, which is precisely
|
|
14
|
+
* what zod's `new Set(def.values).has(input)` does — NaN matches NaN, `-0`
|
|
15
|
+
* matches `+0` — so the verdict is identical without materializing a Set. The
|
|
16
|
+
* same binding also feeds the issue's `values` field, where zod likewise
|
|
17
|
+
* reports `def.values` itself.
|
|
18
|
+
*/
|
|
19
|
+
function runtimeValues(ctx, refIndex) {
|
|
20
|
+
return emitConstant(ctx, "lv", `__rf[${refIndex}]._zod.def.values`);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* `[v1,v2,...]` source for the invalid_value issue's `values` field. Only
|
|
24
|
+
* reached on the all-spellable branch (an unspellable value routes through
|
|
25
|
+
* {@link runtimeValues}), so the filter drops nothing; it is what lets
|
|
26
|
+
* `literalToJs` keep its narrow parameter type. Without a guard here `join`
|
|
27
|
+
* rendered an unspellable value as an ARRAY HOLE — `values:[,"b"]` — which is
|
|
28
|
+
* neither what zod reports nor a well-formed value list.
|
|
29
|
+
*/
|
|
6
30
|
function valuesJs(values) {
|
|
7
|
-
return `[${values.map(literalToJs).join(",")}]`;
|
|
31
|
+
return `[${values.filter(hasSourceForm).map(literalToJs).join(",")}]`;
|
|
8
32
|
}
|
|
9
33
|
/**
|
|
10
34
|
* Equality test `x === value`, except for the literal NaN value: `NaN === NaN`
|
|
@@ -14,9 +38,18 @@ function valuesJs(values) {
|
|
|
14
38
|
*/
|
|
15
39
|
function literalEq(x, v) {
|
|
16
40
|
if (typeof v === "number" && Number.isNaN(v)) return `Number.isNaN(${x})`;
|
|
41
|
+
if (!hasSourceForm(v)) throw new Error("literal value has no JS source form and no runtime ref");
|
|
17
42
|
return `${x}===${literalToJs(v)}`;
|
|
18
43
|
}
|
|
19
44
|
function slowLiteral(ir, g) {
|
|
45
|
+
if (ir.refIndex !== void 0) {
|
|
46
|
+
const values = runtimeValues(g.ctx, ir.refIndex);
|
|
47
|
+
return emit`
|
|
48
|
+
if(!${values}.includes(${g.input})){
|
|
49
|
+
${invalidValue(g, values)}
|
|
50
|
+
}
|
|
51
|
+
`;
|
|
52
|
+
}
|
|
20
53
|
if (ir.values.length === 1) return emit`
|
|
21
54
|
if(!(${literalEq(g.input, ir.values[0])})){
|
|
22
55
|
${invalidValue(g, valuesJs(ir.values))}
|
|
@@ -30,6 +63,7 @@ function slowLiteral(ir, g) {
|
|
|
30
63
|
}
|
|
31
64
|
function fastLiteral(ir, g) {
|
|
32
65
|
const x = g.input;
|
|
66
|
+
if (ir.refIndex !== void 0) return `${runtimeValues(g.ctx, ir.refIndex)}.includes(${x})`;
|
|
33
67
|
if (ir.values.length === 1) return literalEq(x, ir.values[0]);
|
|
34
68
|
return `(${ir.values.map((v) => literalEq(x, v)).join("||")})`;
|
|
35
69
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"literal.js","names":[],"sources":["../../../../src/core/codegen/schemas/literal.ts"],"sourcesContent":["import type { LiteralIR } from \"../../types.js\";\nimport type { FastGen, SlowGen } from \"../context.js\";\nimport { literalToJs } from \"../context.js\";\nimport { emit } from \"../emit.js\";\nimport { invalidValue } from \"../emit-issue.js\";\n\n
|
|
1
|
+
{"version":3,"file":"literal.js","names":[],"sources":["../../../../src/core/codegen/schemas/literal.ts"],"sourcesContent":["import type { LiteralIR, LiteralValue } from \"../../types.js\";\nimport type { CodeGenContext, FastGen, SlowGen } from \"../context.js\";\nimport { emitConstant, hasSourceForm, literalToJs } from \"../context.js\";\nimport { emit } from \"../emit.js\";\nimport { invalidValue } from \"../emit-issue.js\";\n\n/**\n * The literal schema's OWN `def.values` array, aliased into a preamble binding.\n *\n * Used when at least one value has no JS source form (a symbol, an object): the\n * accepted set cannot be written into the generated source, so it is READ from\n * the retained schema instead — the same `__rf[N]` route `.default()` uses for\n * its default value.\n *\n * `Array.prototype.includes` compares with SameValueZero, which is precisely\n * what zod's `new Set(def.values).has(input)` does — NaN matches NaN, `-0`\n * matches `+0` — so the verdict is identical without materializing a Set. The\n * same binding also feeds the issue's `values` field, where zod likewise\n * reports `def.values` itself.\n */\nfunction runtimeValues(ctx: CodeGenContext, refIndex: number): string {\n return emitConstant(ctx, \"lv\", `__rf[${refIndex}]._zod.def.values`);\n}\n\n/**\n * `[v1,v2,...]` source for the invalid_value issue's `values` field. Only\n * reached on the all-spellable branch (an unspellable value routes through\n * {@link runtimeValues}), so the filter drops nothing; it is what lets\n * `literalToJs` keep its narrow parameter type. Without a guard here `join`\n * rendered an unspellable value as an ARRAY HOLE — `values:[,\"b\"]` — which is\n * neither what zod reports nor a well-formed value list.\n */\nfunction valuesJs(values: readonly LiteralValue[]): string {\n return `[${values.filter(hasSourceForm).map(literalToJs).join(\",\")}]`;\n}\n\n/**\n * Equality test `x === value`, except for the literal NaN value: `NaN === NaN`\n * is false under `===`, but zod accepts NaN against `z.literal(NaN)` by value, so\n * a NaN literal compares via `Number.isNaN`. (Infinity/-Infinity compare fine\n * under `===` once literalToJs emits them as the right expression.)\n */\nfunction literalEq(x: string, v: LiteralValue): string {\n if (typeof v === \"number\" && Number.isNaN(v)) return `Number.isNaN(${x})`;\n if (!hasSourceForm(v)) {\n // Unreachable: a node holding an unspellable value carries `refIndex`, and\n // both generators take the runtime-values branch before reaching here.\n throw new Error(\"literal value has no JS source form and no runtime ref\");\n }\n return `${x}===${literalToJs(v)}`;\n}\n\nexport function slowLiteral(ir: LiteralIR, g: SlowGen): string {\n if (ir.refIndex !== undefined) {\n const values = runtimeValues(g.ctx, ir.refIndex);\n return emit`\n if(!${values}.includes(${g.input})){\n ${invalidValue(g, values)}\n }\n `;\n }\n\n if (ir.values.length === 1) {\n return emit`\n if(!(${literalEq(g.input, ir.values[0])})){\n ${invalidValue(g, valuesJs(ir.values))}\n }\n `;\n }\n\n const valueChecks = ir.values.map((v) => literalEq(g.input, v)).join(\"||\");\n\n return emit`\n if(!(${valueChecks})){\n ${invalidValue(g, valuesJs(ir.values))}\n }\n `;\n}\n\nexport function fastLiteral(ir: LiteralIR, g: FastGen): string {\n const x = g.input;\n // A call expression binds tighter than any operator it gets spliced into, so\n // this needs no parens of its own.\n if (ir.refIndex !== undefined) return `${runtimeValues(g.ctx, ir.refIndex)}.includes(${x})`;\n if (ir.values.length === 1) {\n return literalEq(x, ir.values[0]);\n }\n // Wrap in parens — || has lower precedence than && in parent expressions\n return `(${ir.values.map((v) => literalEq(x, v)).join(\"||\")})`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAoBA,SAAS,cAAc,KAAqB,UAA0B;CACpE,OAAO,aAAa,KAAK,MAAM,QAAQ,SAAS,kBAAkB;AACpE;;;;;;;;;AAUA,SAAS,SAAS,QAAyC;CACzD,OAAO,IAAI,OAAO,OAAO,aAAa,CAAC,CAAC,IAAI,WAAW,CAAC,CAAC,KAAK,GAAG,EAAE;AACrE;;;;;;;AAQA,SAAS,UAAU,GAAW,GAAyB;CACrD,IAAI,OAAO,MAAM,YAAY,OAAO,MAAM,CAAC,GAAG,OAAO,gBAAgB,EAAE;CACvE,IAAI,CAAC,cAAc,CAAC,GAGlB,MAAM,IAAI,MAAM,wDAAwD;CAE1E,OAAO,GAAG,EAAE,KAAK,YAAY,CAAC;AAChC;AAEA,SAAgB,YAAY,IAAe,GAAoB;CAC7D,IAAI,GAAG,aAAa,KAAA,GAAW;EAC7B,MAAM,SAAS,cAAc,EAAE,KAAK,GAAG,QAAQ;EAC/C,OAAO,IAAI;YACH,OAAO,YAAY,EAAE,MAAM;UAC7B,aAAa,GAAG,MAAM,EAAE;;;CAGhC;CAEA,IAAI,GAAG,OAAO,WAAW,GACvB,OAAO,IAAI;aACF,UAAU,EAAE,OAAO,GAAG,OAAO,EAAE,EAAE;UACpC,aAAa,GAAG,SAAS,GAAG,MAAM,CAAC,EAAE;;;CAO7C,OAAO,IAAI;WAFS,GAAG,OAAO,KAAK,MAAM,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,IAGlD,EAAE;QACf,aAAa,GAAG,SAAS,GAAG,MAAM,CAAC,EAAE;;;AAG7C;AAEA,SAAgB,YAAY,IAAe,GAAoB;CAC7D,MAAM,IAAI,EAAE;CAGZ,IAAI,GAAG,aAAa,KAAA,GAAW,OAAO,GAAG,cAAc,EAAE,KAAK,GAAG,QAAQ,EAAE,YAAY,EAAE;CACzF,IAAI,GAAG,OAAO,WAAW,GACvB,OAAO,UAAU,GAAG,GAAG,OAAO,EAAE;CAGlC,OAAO,IAAI,GAAG,OAAO,KAAK,MAAM,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE;AAC9D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"string-bool.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/string-bool.ts"],"mappings":";;;iBAMgB,eAAe,IAAI,cAAc,GAAG;
|
|
1
|
+
{"version":3,"file":"string-bool.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/string-bool.ts"],"mappings":";;;iBAMgB,eAAe,IAAI,cAAc,GAAG;iBA+CpC,qBAAqB,IAAI;;iBAKzB,kBAAkB,IAAI,cAAc,KAAK"}
|
|
@@ -13,20 +13,21 @@ function slowStringBool(ir, g) {
|
|
|
13
13
|
if (!ir.caseSensitive) code += `var ${normalized}=${g.input}.toLowerCase();`;
|
|
14
14
|
const allValues = [...ir.truthy, ...ir.falsy];
|
|
15
15
|
const valuesExpr = JSON.stringify(allValues);
|
|
16
|
+
const expectedExtra = "expected:\"stringbool\"";
|
|
16
17
|
if (stringBoolUsesInline(ir)) {
|
|
17
18
|
const truthyCondition = ir.truthy.map((v) => `${normalized}===${escapeString(v)}`).join("||");
|
|
18
19
|
const falsyCondition = ir.falsy.map((v) => `${normalized}===${escapeString(v)}`).join("||");
|
|
19
20
|
code += emit`
|
|
20
21
|
if(${truthyCondition}){${g.output}=true;}
|
|
21
22
|
else if(${falsyCondition}){${g.output}=false;}
|
|
22
|
-
else{${invalidValue(g, valuesExpr)}}
|
|
23
|
+
else{${invalidValue(g, valuesExpr, { extra: expectedExtra })}}
|
|
23
24
|
`;
|
|
24
25
|
} else {
|
|
25
26
|
const value = g.temp("sbv");
|
|
26
27
|
const lookup = emitStringBoolMap(ir, g.ctx);
|
|
27
28
|
code += emit`
|
|
28
29
|
var ${value}=${lookup}.get(${normalized});
|
|
29
|
-
if(${value}===undefined){${invalidValue(g, valuesExpr)}}
|
|
30
|
+
if(${value}===undefined){${invalidValue(g, valuesExpr, { extra: expectedExtra })}}
|
|
30
31
|
else{${g.output}=${value};}
|
|
31
32
|
`;
|
|
32
33
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"string-bool.js","names":[],"sources":["../../../../src/core/codegen/schemas/string-bool.ts"],"sourcesContent":["import type { StringBoolIR } from \"../../types.js\";\nimport type { CodeGenContext, SlowGen } from \"../context.js\";\nimport { ENUM_INLINE_THRESHOLD, emitConstant, escapeString } from \"../context.js\";\nimport { emit } from \"../emit.js\";\nimport { invalidType, invalidValue } from \"../emit-issue.js\";\n\nexport function slowStringBool(ir: StringBoolIR, g: SlowGen): string {\n let code = \"\";\n\n // Type check: input must be a string\n code += emit`\n if(typeof ${g.input}!==\"string\"){\n ${invalidType(g, \"string\")}\n }else{\n `;\n\n // Normalize input for case-insensitive matching\n const normalized = ir.caseSensitive ? g.input : g.temp(\"sbn\");\n if (!ir.caseSensitive) code += `var ${normalized}=${g.input}.toLowerCase();`;\n const allValues = [...ir.truthy, ...ir.falsy];\n const valuesExpr = JSON.stringify(allValues);\n\n // Compare per-side counts against threshold (not the combined total)\n const useInline = stringBoolUsesInline(ir);\n\n if (useInline) {\n const truthyCondition = ir.truthy.map((v) => `${normalized}===${escapeString(v)}`).join(\"||\");\n const falsyCondition = ir.falsy.map((v) => `${normalized}===${escapeString(v)}`).join(\"||\");\n code += emit`\n if(${truthyCondition}){${g.output}=true;}\n else if(${falsyCondition}){${g.output}=false;}\n else{${invalidValue(g, valuesExpr)}}\n `;\n } else {\n const value = g.temp(\"sbv\");\n const lookup = emitStringBoolMap(ir, g.ctx);\n code += emit`\n var ${value}=${lookup}.get(${normalized});\n if(${value}===undefined){${invalidValue(g, valuesExpr)}}\n else{${g.output}=${value};}\n `;\n }\n\n code += emit`}`;\n return `${code}\\n`;\n}\n\nexport function stringBoolUsesInline(ir: StringBoolIR): boolean {\n return ir.truthy.length <= ENUM_INLINE_THRESHOLD && ir.falsy.length <= ENUM_INLINE_THRESHOLD;\n}\n\n/** One lookup distinguishes true, false and absent; shared by hot and issue walks. */\nexport function emitStringBoolMap(ir: StringBoolIR, ctx: CodeGenContext): string {\n const pairs = [\n ...ir.truthy.map((value) => [value, true]),\n ...ir.falsy.map((value) => [value, false]),\n ];\n return emitConstant(ctx, \"map_sb\", `new Map(${JSON.stringify(pairs)})`);\n}\n"],"mappings":";;;;AAMA,SAAgB,eAAe,IAAkB,GAAoB;CACnE,IAAI,OAAO;CAGX,QAAQ,IAAI;gBACE,EAAE,MAAM;QAChB,YAAY,GAAG,QAAQ,EAAE;;;CAK/B,MAAM,aAAa,GAAG,gBAAgB,EAAE,QAAQ,EAAE,KAAK,KAAK;CAC5D,IAAI,CAAC,GAAG,eAAe,QAAQ,OAAO,WAAW,GAAG,EAAE,MAAM;CAC5D,MAAM,YAAY,CAAC,GAAG,GAAG,QAAQ,GAAG,GAAG,KAAK;CAC5C,MAAM,aAAa,KAAK,UAAU,SAAS;
|
|
1
|
+
{"version":3,"file":"string-bool.js","names":[],"sources":["../../../../src/core/codegen/schemas/string-bool.ts"],"sourcesContent":["import type { StringBoolIR } from \"../../types.js\";\nimport type { CodeGenContext, SlowGen } from \"../context.js\";\nimport { ENUM_INLINE_THRESHOLD, emitConstant, escapeString } from \"../context.js\";\nimport { emit } from \"../emit.js\";\nimport { invalidType, invalidValue } from \"../emit-issue.js\";\n\nexport function slowStringBool(ir: StringBoolIR, g: SlowGen): string {\n let code = \"\";\n\n // Type check: input must be a string\n code += emit`\n if(typeof ${g.input}!==\"string\"){\n ${invalidType(g, \"string\")}\n }else{\n `;\n\n // Normalize input for case-insensitive matching\n const normalized = ir.caseSensitive ? g.input : g.temp(\"sbn\");\n if (!ir.caseSensitive) code += `var ${normalized}=${g.input}.toLowerCase();`;\n const allValues = [...ir.truthy, ...ir.falsy];\n const valuesExpr = JSON.stringify(allValues);\n // z.stringbool() is a Codec whose transform pushes\n // `{ code: \"invalid_value\", expected: \"stringbool\", values: [...] }` — the\n // only invalid_value producer that carries an `expected` (enum and literal\n // push `values` alone), and zod's locale keys off it. Both emitted lookups\n // below share this so the two code paths cannot drift apart.\n const expectedExtra = 'expected:\"stringbool\"';\n\n // Compare per-side counts against threshold (not the combined total)\n const useInline = stringBoolUsesInline(ir);\n\n if (useInline) {\n const truthyCondition = ir.truthy.map((v) => `${normalized}===${escapeString(v)}`).join(\"||\");\n const falsyCondition = ir.falsy.map((v) => `${normalized}===${escapeString(v)}`).join(\"||\");\n code += emit`\n if(${truthyCondition}){${g.output}=true;}\n else if(${falsyCondition}){${g.output}=false;}\n else{${invalidValue(g, valuesExpr, { extra: expectedExtra })}}\n `;\n } else {\n const value = g.temp(\"sbv\");\n const lookup = emitStringBoolMap(ir, g.ctx);\n code += emit`\n var ${value}=${lookup}.get(${normalized});\n if(${value}===undefined){${invalidValue(g, valuesExpr, { extra: expectedExtra })}}\n else{${g.output}=${value};}\n `;\n }\n\n code += emit`}`;\n return `${code}\\n`;\n}\n\nexport function stringBoolUsesInline(ir: StringBoolIR): boolean {\n return ir.truthy.length <= ENUM_INLINE_THRESHOLD && ir.falsy.length <= ENUM_INLINE_THRESHOLD;\n}\n\n/** One lookup distinguishes true, false and absent; shared by hot and issue walks. */\nexport function emitStringBoolMap(ir: StringBoolIR, ctx: CodeGenContext): string {\n const pairs = [\n ...ir.truthy.map((value) => [value, true]),\n ...ir.falsy.map((value) => [value, false]),\n ];\n return emitConstant(ctx, \"map_sb\", `new Map(${JSON.stringify(pairs)})`);\n}\n"],"mappings":";;;;AAMA,SAAgB,eAAe,IAAkB,GAAoB;CACnE,IAAI,OAAO;CAGX,QAAQ,IAAI;gBACE,EAAE,MAAM;QAChB,YAAY,GAAG,QAAQ,EAAE;;;CAK/B,MAAM,aAAa,GAAG,gBAAgB,EAAE,QAAQ,EAAE,KAAK,KAAK;CAC5D,IAAI,CAAC,GAAG,eAAe,QAAQ,OAAO,WAAW,GAAG,EAAE,MAAM;CAC5D,MAAM,YAAY,CAAC,GAAG,GAAG,QAAQ,GAAG,GAAG,KAAK;CAC5C,MAAM,aAAa,KAAK,UAAU,SAAS;CAM3C,MAAM,gBAAgB;CAKtB,IAFkB,qBAAqB,EAE3B,GAAG;EACb,MAAM,kBAAkB,GAAG,OAAO,KAAK,MAAM,GAAG,WAAW,KAAK,aAAa,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI;EAC5F,MAAM,iBAAiB,GAAG,MAAM,KAAK,MAAM,GAAG,WAAW,KAAK,aAAa,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI;EAC1F,QAAQ,IAAI;WACL,gBAAgB,IAAI,EAAE,OAAO;gBACxB,eAAe,IAAI,EAAE,OAAO;aAC/B,aAAa,GAAG,YAAY,EAAE,OAAO,cAAc,CAAC,EAAE;;CAEjE,OAAO;EACL,MAAM,QAAQ,EAAE,KAAK,KAAK;EAC1B,MAAM,SAAS,kBAAkB,IAAI,EAAE,GAAG;EAC1C,QAAQ,IAAI;YACJ,MAAM,GAAG,OAAO,OAAO,WAAW;WACnC,MAAM,gBAAgB,aAAa,GAAG,YAAY,EAAE,OAAO,cAAc,CAAC,EAAE;aAC1E,EAAE,OAAO,GAAG,MAAM;;CAE7B;CAEA,QAAQ,IAAI;CACZ,OAAO,GAAG,KAAK;AACjB;AAEA,SAAgB,qBAAqB,IAA2B;CAC9D,OAAO,GAAG,OAAO,UAAA,KAAmC,GAAG,MAAM,UAAA;AAC/D;;AAGA,SAAgB,kBAAkB,IAAkB,KAA6B;CAC/E,MAAM,QAAQ,CACZ,GAAG,GAAG,OAAO,KAAK,UAAU,CAAC,OAAO,IAAI,CAAC,GACzC,GAAG,GAAG,MAAM,KAAK,UAAU,CAAC,OAAO,KAAK,CAAC,CAC3C;CACA,OAAO,aAAa,KAAK,UAAU,WAAW,KAAK,UAAU,KAAK,EAAE,EAAE;AACxE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"string.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/string.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"string.d.ts","names":[],"sources":["../../../../src/core/codegen/schemas/string.ts"],"mappings":";;;iBAsEgB,WAAW,IAAI,UAAU,GAAG;;;;;;;;;;;iBAkJ5B,gBAAgB,OAAO,SAAS,WAAW,KAAK;iBAkDhD,WAAW,IAAI,UAAU,GAAG"}
|
|
@@ -12,6 +12,11 @@ function lastIndexReset(regexVar, flags) {
|
|
|
12
12
|
* Generate the url check, mirroring $ZodURL semantics:
|
|
13
13
|
* trim → new URL(trimmed) → optional hostname/protocol regex tests →
|
|
14
14
|
* write back url.href (normalize) or the trimmed input.
|
|
15
|
+
*
|
|
16
|
+
* $ZodURL is another constructor that OVERRIDES the `??=`-installed default
|
|
17
|
+
* check, so none of the three issues below carries `origin` — and the two that
|
|
18
|
+
* do carry a `pattern` use `regex.source`, not the default check's
|
|
19
|
+
* `regex.toString()` (no delimiters, no flags). Both are reproduced verbatim.
|
|
15
20
|
*/
|
|
16
21
|
function slowUrlCheck(check, g) {
|
|
17
22
|
const trimmedVar = g.temp("ut");
|
|
@@ -91,7 +96,7 @@ function slowString(ir, g) {
|
|
|
91
96
|
code += emit`
|
|
92
97
|
if(!${g.input}.includes(${escapeString(check.includes)}${check.position !== void 0 ? `,${check.position}` : ""})){
|
|
93
98
|
${invalidFormat(g, "includes", {
|
|
94
|
-
extra: `includes:${escapeString(check.includes)}`,
|
|
99
|
+
extra: `includes:${escapeString(check.includes)},origin:"string"`,
|
|
95
100
|
message: check.message
|
|
96
101
|
})}
|
|
97
102
|
}`;
|
|
@@ -100,7 +105,7 @@ function slowString(ir, g) {
|
|
|
100
105
|
code += emit`
|
|
101
106
|
if(!${g.input}.startsWith(${escapeString(check.prefix)})){
|
|
102
107
|
${invalidFormat(g, "starts_with", {
|
|
103
|
-
extra: `prefix:${escapeString(check.prefix)}`,
|
|
108
|
+
extra: `prefix:${escapeString(check.prefix)},origin:"string"`,
|
|
104
109
|
message: check.message
|
|
105
110
|
})}
|
|
106
111
|
}`;
|
|
@@ -109,7 +114,7 @@ function slowString(ir, g) {
|
|
|
109
114
|
code += emit`
|
|
110
115
|
if(!${g.input}.endsWith(${escapeString(check.suffix)})){
|
|
111
116
|
${invalidFormat(g, "ends_with", {
|
|
112
|
-
extra: `suffix:${escapeString(check.suffix)}`,
|
|
117
|
+
extra: `suffix:${escapeString(check.suffix)},origin:"string"`,
|
|
113
118
|
message: check.message
|
|
114
119
|
})}
|
|
115
120
|
}`;
|
|
@@ -126,7 +131,7 @@ function slowString(ir, g) {
|
|
|
126
131
|
case "string_format": {
|
|
127
132
|
let regexVar;
|
|
128
133
|
let pattern;
|
|
129
|
-
if (check.format === "url") {
|
|
134
|
+
if (check.format === "url" && !check.pattern) {
|
|
130
135
|
code += slowUrlCheck(check, g);
|
|
131
136
|
continue;
|
|
132
137
|
}
|
|
@@ -143,12 +148,13 @@ function slowString(ir, g) {
|
|
|
143
148
|
pattern = check.pattern;
|
|
144
149
|
regexVar = g.regex("str", pattern, check.patternFlags);
|
|
145
150
|
} else continue;
|
|
146
|
-
|
|
151
|
+
let extra;
|
|
152
|
+
if (!check.bareIssue) extra = `pattern:${!check.patternFlags && fastTestSource(pattern) !== null ? emitRegexSourceString(g.ctx, pattern) : `${regexVar}.toString()`},origin:"string"`;
|
|
147
153
|
code += emit`
|
|
148
154
|
${lastIndexReset(regexVar, check.patternFlags)}
|
|
149
155
|
if(!${regexVar}.test(${g.input})){
|
|
150
156
|
${invalidFormat(g, { expr: escapeString(check.format) }, {
|
|
151
|
-
extra
|
|
157
|
+
extra,
|
|
152
158
|
message: check.message
|
|
153
159
|
})}
|
|
154
160
|
}`;
|
|
@@ -178,7 +184,7 @@ function fastStringCheck(check, x, ctx) {
|
|
|
178
184
|
case "starts_with": return `${x}.startsWith(${escapeString(check.prefix)})`;
|
|
179
185
|
case "ends_with": return `${x}.endsWith(${escapeString(check.suffix)})`;
|
|
180
186
|
case "string_format": {
|
|
181
|
-
if (check.format === "url") return null;
|
|
187
|
+
if (check.format === "url" && !check.pattern) return null;
|
|
182
188
|
let pattern;
|
|
183
189
|
let prefix;
|
|
184
190
|
if (check.format === "email") {
|