zod-compiler 1.26.2 → 1.26.4

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.
@@ -1 +1 @@
1
- {"version":3,"file":"iife.js","names":[],"sources":["../../src/core/iife.ts"],"sourcesContent":["/**\n * Shared CompiledSchema<T> IIFE generation.\n * Used by both CLI emitter and unplugin transform.\n */\n\nimport { RETAINED_SCHEMA_VAR } from \"./codegen/context.js\";\nimport type { CompiledSchemaInfo } from \"./pipeline.js\";\n\n/**\n * Import statement required by generateIIFE output (references\n * __zodCompilerConfig). `core` is bound for $ZodAsyncError: superRefine and\n * custom callbacks may only reveal that they are async through the promise\n * they return, at which point zod's own synchronous parse raises.\n */\nexport const ZOD_CONFIG_IMPORT =\n 'import { config as __zodCompilerConfig, core as __zcCore, ZodRealError as __zcZodError } from \"zod\";';\n\n/**\n * File-level `__zcMsg` declaration (must appear once after ZOD_CONFIG_IMPORT):\n * the message an issue gets when nothing was baked into it at build time.\n *\n * Resolves zod's tail of `finalizeIssue` — `config.customError` then\n * `config.localeError` then \"Invalid input\" — and does it PER CALL, because the\n * config is mutable: `z.config({ localeError })` in an entry point runs after\n * the schema modules it imports, so a value snapshotted at module init misses\n * it. Reading a captured `localeError` alone also dropped `customError`\n * outright, silently ignoring the global map most i18n setups install.\n *\n * The head of zod's chain — the schema's own `error` option — is baked into the\n * issue at build time and short-circuits this. The one link that cannot be\n * reproduced is a per-CALL `ctx.error`, which would have to travel through\n * `safeParse`; that entry point sits at V8's inlining budget, where even an\n * unused extra parameter measured ~12% on every parse.\n *\n * Only ever called while building an error, never on a successful parse.\n */\nexport const ZOD_MSG_DECLARATION =\n 'function __zcUw(m){return typeof m===\"string\"?m:(m===undefined||m===null?undefined:m.message);}' +\n \"var __zcMsg=function(iss){var c=__zodCompilerConfig(),m;\" +\n \"if(c.customError){m=__zcUw(c.customError(iss));if(m!==undefined&&m!==null)return m;}\" +\n \"if(c.localeError){m=__zcUw(c.localeError(iss));if(m!==undefined&&m!==null)return m;}\" +\n 'return \"Invalid input\";};';\n\n/**\n * Shared failure-result for __zcFin / __zcFinD. Inline mode (CLI emitter)\n * declares it once per compiled file; lean mode (all unplugin bundlers) declares\n * it once per bundle in the plugin-materialized runtime module (module-local —\n * generated code only ever references __zcFin/__zcFinD, never __ZcFail).\n *\n * Why a prototype getter and not `{success:false, get error(){...}}`: an object\n * literal with an inline accessor forces V8 down its slow accessor-defining\n * allocation path — ~110ns per failure, measured — which dominates the entire\n * invalid-input cost whenever callers never read `.error`. Hosting `error` on\n * the prototype turns each failure into a plain field-only instantiation (~2ns,\n * ~13x), with the lazy-cache semantics intact. (Trade-off: `error` is a\n * prototype accessor, so it no longer shows up in `Object.keys(result)` / spread\n * / JSON.stringify of the result wrapper — `.success`/`.error`/`.data` access,\n * destructuring, and `in` are unaffected.)\n *\n * One class serves both finalizers, so the instances share one hidden class:\n * __zcFin passes pre-collected issues in `_e` (with `_f===null`); __zcFinD\n * passes the hosted slow-walk in `_f` plus the input in `_i`, and the getter\n * runs the walk on first `.error` read. The whole finalization — locale fill\n * (__zcMsg applied ONLY when an issue carries no message, never overwriting a\n * baked-in custom/fallback message), input strip, and ZodError construction\n * (zod v4 JSON.stringifies every issue into `message` and captures a stack\n * trace) — stays deferred inside the cached accessor exactly as before, since\n * the issues array is observable solely through `.error`.\n *\n * `input` is `delete`d, not assigned `undefined`. Key PRESENCE is observable —\n * `\"input\" in issue`, `Object.keys(issue)`, object spread, `toStrictEqual`\n * against a zod issue — and zod's `util.finalizeIssue` does `delete full.input`\n * whenever `reportInput` is off, so assignment left every compiled issue one\n * enumerable key wider than zod's. The delete's dictionary-mode transition is\n * affordable precisely BECAUSE of the deferral above: it runs only after a\n * caller asks for `.error` on a failed parse, and is memoised in `_c`. Neither\n * a successful parse nor a `.success`/`.is()` check on a rejected one reaches\n * it — measured, the two are unchanged, while the `.error` read itself goes\n * ~3.95us -> ~4.19us per failure, i.e. ~6% of a path whose cost is already\n * dominated by the ZodError construction on the next line (stack capture plus a\n * JSON.stringify of every issue).\n */\nexport const FAIL_CLASS_DECL =\n \"function __ZcFail(e,f,i){this.success=false;this._e=e;this._f=f;this._i=i;this._c=undefined;}\" +\n 'Object.defineProperty(__ZcFail.prototype,\"error\",{configurable:true,get:function(){' +\n \"if(this._c)return this._c;\" +\n \"var e=this._f!==null?this._f(this._i):this._e;\" +\n 'for(var i=0;i<e.length;i++){if(e[i].message===undefined&&typeof __zcMsg===\"function\")e[i].message=__zcMsg(e[i]);delete e[i].input;delete e[i].continue;}' +\n \"return this._c=new __zcZodError(e);}});\";\n\n/** Eager finalizer (mutation / partial-fast-path schemas): issues already\n * collected in `e`; success short-circuits to a plain result literal. */\nexport const FIN_DECL =\n \"function __zcFin(e,d){if(!e.length)return{success:true,data:d};return new __ZcFail(e,null,null);}\";\n\n/**\n * Deferred-collection finalizer for Fast-Path-eligible schemas. When the\n * fast check fails, the ENTIRE slow path (the issue-collecting re-walk) is\n * pushed into the cached `.error` accessor instead of running eagerly:\n * fast-eligible schemas never mutate, so the walk's only output is the\n * issues array, which is observable solely through `.error` — one step\n * further along the same lazy boundary `__zcFin` already established (locale\n * fill, input strip, ZodError construction). A failed safeParse whose\n * `.error` is never read costs the fast check alone.\n *\n * Takes the schema's HOSTED slow-walk function plus the input — NOT a\n * per-call closure: `__zcFinD(__sw_N, input)` allocates only the result\n * object, where `__zcFinD(function(){...})` paid a closure environment and\n * function object per failure. Hosting the walk also shrinks safeParse to\n * two statements, within V8's inlining budget (the success-path result\n * literal becomes escape-analyzable at monomorphic call sites).\n *\n * The walk re-reads `input` at `.error`-read time; a caller that mutates\n * the input between safeParse and reading `.error` sees issues for the\n * mutated value (zod materializes at parse time). Same caveat class as the\n * documented __zcFin deferral.\n */\nexport const FIN_DEFERRED_DECL = \"function __zcFinD(f,inp){return new __ZcFail(null,f,inp);}\";\n\n/**\n * Compact-mode failure class — a lazy failure that delegates error reporting to\n * the ORIGINAL Zod schema's `safeParse`. Used by `output: \"compact\"`, where the\n * compiled slow walk is dropped entirely: a mutation-free schema's fast check\n * is the only generated validation, and on a fast-check failure the cold error\n * path is produced by the retained Zod schema itself (`zod` is the source of\n * truth, so the issues are byte-identical — no second validation engine).\n *\n * `_z` is the schema's PRISTINE safeParse method, captured by\n * emitRetainedMethod (see context.ts), and `_r` is its receiver — the `__zs`\n * binding generateIIFE places above that capture. Both are read before the\n * trailing `__zcMkv` call installs anything, so the method is zod's own\n * implementation, never the compiled delegate, avoiding infinite recursion\n * without allocating a bound function. The zod parse is deferred until `.error`\n * is read and cached, so\n * the common `safeParse(x).success`/`.is(x)` checks on invalid input cost only\n * the fast check (zod never runs) — the same deferral boundary `__zcFinD`\n * establishes for the compiled slow walk. Sound because compact mode is gated\n * on a TOTAL fast path: `fc(input) === false` ⟹ zod rejects, so `success:false`\n * holds without consulting zod.\n *\n * The getter returns zod's OWN ZodError verbatim (no locale fill / input strip /\n * re-wrap — zod already finalized it), so a delegated failure is exactly what\n * the unaltered schema would have produced.\n */\nexport const FAILZ_CLASS_DECL =\n \"function __ZcFailZ(z,r,i){this.success=false;this._z=z;this._r=r;this._i=i;this._c=undefined;}\" +\n 'Object.defineProperty(__ZcFailZ.prototype,\"error\",{configurable:true,get:function(){' +\n \"return this._c||(this._c=this._z.call(this._r,this._i).error);}});\";\n\n/** Compact-mode finalizer: retain a pristine safeParse, its receiver, and input lazily. */\nexport const FINZ_DECL = \"function __zcFinZ(z,r,i){return new __ZcFailZ(z,r,i);}\";\n\n/**\n * Validator factory. Inline mode (CLI emitter) declares it once per compiled\n * file; lean mode (all unplugin bundlers) exports it once per bundle from the\n * plugin-materialized runtime module — generated code never imports it from\n * the zod-compiler package itself, so zod-compiler stays a devDependency and\n * the helper set is always version-locked to the codegen that calls it.\n * Wraps a safeParse function into the CompiledSchema interface.\n *\n * IDENTITY-PRESERVING: with zodCompat (schema != null) the compiled\n * parse/safeParse/parseAsync/safeParseAsync are installed as OWN properties\n * on the original schema object, which is returned as-is. zod v4 keys\n * several APIs on object identity — toJSONSchema's ctx.seen registers the\n * object it is handed while each processor closure captures the original\n * inst (a wrapper crashes `optionalProcessor` with \"Cannot set properties\n * of undefined (setting 'ref')\" the moment a compiled schema is composed\n * into another schema), and globalRegistry/.meta() is a WeakMap keyed by\n * the schema instance (a wrapper silently loses OpenAPI titles/ids). An\n * Object.create wrapper breaks both; mutating the original breaks neither\n * (zod's internal parsing flows through _zod.run, never the public\n * methods, and derived schemas — .optional(), .extend() — are fresh\n * instances that fall back to plain zod). schema=null (zodCompat: false)\n * still produces a plain method-bag object.\n *\n * fc is the schema's hosted fast-check boolean function (null when no Fast\n * Path exists). parse()/parseAsync() try it first and return the input\n * directly on success: fc is small enough for V8 to inline, so the hot parse\n * path runs with zero allocations — calling fn would allocate an intermediate\n * SafeParseResult that escape analysis cannot remove (fn never inlines).\n * Fast-path-eligible schemas never mutate, so fc(input) ⟹ data === input.\n *\n * is is the TOTAL fast-check predicate (fc when the fast path is total, else\n * null). Installed as `.is()` — a zero-allocation boolean type guard. When\n * null (partial fast path or none) `.is()` derives from fn(input).success: a\n * partial fc can pass-through valid input but its `false` does not imply\n * rejection (a default/catch may still succeed), so it would be unsound as a\n * standalone guard.\n *\n * parseAsync/safeParseAsync wrap the SYNC validator, which is right for every\n * schema the compiler can reproduce — none of them are async. It is wrong for\n * the ones it cannot: an `async` refinement or a `z.promise()` extracts to a\n * Zod delegate, and delegating means calling Zod's SYNCHRONOUS safeParse, which\n * raises `$ZodAsyncError` by design. Wrapping that gave the compiled schema an\n * async pair that rejected with `$ZodAsyncError` for EVERY input — including\n * valid ones — where Zod resolves normally, so `await UserSchema.parseAsync(x)`\n * stopped working the moment any part of the schema went async.\n *\n * So both are guarded: a synchronous throw hands off to the schema's ORIGINAL\n * async method, captured before these are installed — the same escape hatch\n * `~standard` already uses for its throw path, and for the same reason (the\n * compiled validator has no async mode to offer, and Zod's is exact). It also\n * fixes the smaller wart that a throwing `fn` made `parseAsync` throw\n * SYNCHRONOUSLY rather than return a rejected promise. With `zodCompat: false`\n * there is no schema to delegate to and the throw propagates as before.\n *\n * `~standard` is REPLACED, not merely preserved. Zod builds it lazily as\n * `validate: (v) => safeParse(inst, v)` — the core FUNCTION, which goes straight\n * to `inst._zod.run`. It never reads the schema's own `safeParse` property, so\n * installing the compiled one leaves this route entirely uncompiled: measured\n * 271.7 ns against the compiled 26.6 ns on the same schema, i.e. Standard Schema\n * consumers (tRPC, Hono, TanStack) were getting plain Zod.\n *\n * Zod's own `~standard` is never READ, only overwritten. Zod installs the slot\n * with `util.defineLazy` — commented there as \"avoid creating objects for every\n * schema\" — so it is an accessor that builds `{version, vendor, validate}` plus\n * its closure on first touch. Reading it to capture a fallback fired that getter\n * for every compiled schema while the module was still initializing.\n *\n * How much that costs depends on the entry point, and only one of them is free:\n * classic `zod` forces the slot itself during `ZodType.init` (it does\n * `Object.assign(inst[\"~standard\"], { jsonSchema })`), so there the read hit an\n * already-built object and cost only an accessor call. `zod/mini` and raw\n * `zod/v4/core` never touch it, so for those the read built — and retained — an\n * object and a closure per schema, purely to capture a fallback the schema will\n * most likely never expose to a Standard Schema consumer.\n *\n * The throw path is rebuilt instead of captured. Zod's validate catches a\n * synchronous throw and retries through `safeParseAsync` — that is how an async\n * refinement resolves and how a throwing check surfaces as a rejected promise\n * rather than a synchronous throw — so calling the already-captured `zspa` and\n * mapping its result is the same route to the same result. `vendor` is likewise\n * a constant: schema discovery only ever admits zod schemas, and classic, mini\n * and core all hardcode `vendor: \"zod\"` themselves.\n *\n * Not carried over (unchanged by this, and pre-dating it): classic's\n * `~standard.jsonSchema` extension, which the replacement object has never\n * reproduced.\n *\n * Installed with defineProperty rather than assignment: Zod's lazy setter\n * redefines the slot as non-writable, so a second `__zcMkv` on the same schema\n * object — two exports aliasing one schema — would throw under ESM strict mode.\n */\nexport const MK_VALIDATOR_DECL =\n \"function __zcMkv(fn,schema,fc,is){var w=schema||{};var zpa=w.parseAsync,zspa=w.safeParseAsync;w.parse=fc?function(input){if(fc(input))return input;var r=fn(input);if(r.success)return r.data;throw r.error;}:function(input){var r=fn(input);if(r.success)return r.data;throw r.error;};w.safeParse=fn;w.safeParseAsync=function(input){try{return Promise.resolve(fn(input));}catch(e){if(zspa)return zspa(input);throw e;}};w.parseAsync=fc?function(input){try{if(fc(input))return Promise.resolve(input);var r=fn(input);if(r.success)return Promise.resolve(r.data);return Promise.reject(r.error);}catch(e){if(zpa)return zpa(input);throw e;}}:function(input){try{var r=fn(input);if(r.success)return Promise.resolve(r.data);return Promise.reject(r.error);}catch(e){if(zpa)return zpa(input);throw e;}};w.is=is||function(input){return fn(input).success;};\" +\n 'Object.defineProperty(w,\"~standard\",{configurable:true,value:{version:1,vendor:\"zod\",validate:function(input){var r;try{if(fc&&fc(input))return{value:input};r=fn(input);}catch(e){if(zspa)return zspa(input).then(function(q){return q.success?{value:q.data}:{issues:q.error.issues};});throw e;}return r.success?{value:r.data}:{issues:r.error.issues};}}});' +\n \"return w;}\";\n\nfunction extractFunctionName(functionDef: string): string {\n const match = /^function\\s+(\\w+)\\s*\\(/.exec(functionDef);\n if (!match?.[1]) {\n throw new Error(\"Cannot extract function name from generated code\");\n }\n return match[1];\n}\n\n/**\n * Generate a `/* @__PURE__ * /` IIFE wrapping a compiled validator.\n *\n * @param schemaExpr - Expression resolving to the original Zod schema\n * (e.g. `\"UserSchema\"` in unplugin, `\"(__src_X as any).schema\"` in CLI)\n * @param schema\n * @param options\n */\nexport function generateIIFE(\n schemaExpr: string,\n schema: CompiledSchemaInfo,\n options?: { zodCompat?: boolean | undefined },\n): string {\n const { codegenResult, refEntries } = schema;\n const fnName = extractFunctionName(codegenResult.functionDef);\n const zodCompat = options?.zodCompat !== false;\n // Every fallback access starts from the same source schema, and compact\n // delegation names it outright. Capture it once whenever either needs it, so\n // an inline initializer is not reconstructed for each path and again for the\n // identity-preserving __zcMkv target.\n const bindsSchema = refEntries.length > 0 || codegenResult.usesRetainedSchema === true;\n const retainedSchema = bindsSchema ? RETAINED_SCHEMA_VAR : schemaExpr;\n const schemaArg = zodCompat ? retainedSchema : \"null\";\n const fcArg = codegenResult.fastFnName ?? \"null\";\n // `.is()` gets the fast-check directly only when it is a total predicate;\n // partial/none falls back to safeParse().success inside __zcMkv. A rebuilding\n // schema has no by-reference `fc` but still names its predicate separately.\n const isArg = codegenResult.isFnName ?? (codegenResult.fastTotal ? fcArg : \"null\");\n\n return [\n \"/* @__PURE__ */ (() => {\",\n ...(bindsSchema ? [`var ${RETAINED_SCHEMA_VAR}=${schemaExpr};`] : []),\n // Only fallback refs need the array; a compact validator with none of its\n // own reads `__zs` directly rather than allocating `[__zs]` to index into.\n ...(refEntries.length > 0\n ? [`var __rf=[${refEntries.map((fb) => `${retainedSchema}${fb.accessPath}`).join(\",\")}];`]\n : []),\n ...codegenResult.code\n .split(\"\\n\")\n .filter((l) => l.trim() !== \"\" && l.trim() !== \"/* zod-compiler */\"),\n codegenResult.functionDef,\n `return __zcMkv(${fnName},${schemaArg},${fcArg},${isArg});`,\n \"})()\",\n ].join(\"\\n\");\n}\n"],"mappings":";;;;;;;;;;;;AAcA,MAAa,oBACX;;;;;;;;;;;;;;;;;;;;AAqBF,MAAa,sBACX;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6CF,MAAa,kBACX;;;AASF,MAAa,WACX;;;;;;;;;;;;;;;;;;;;;;;AAwBF,MAAa,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BjC,MAAa,mBACX;;AAKF,MAAa,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6FzB,MAAa,oBACX;AAIF,SAAS,oBAAoB,aAA6B;CACxD,MAAM,QAAQ,yBAAyB,KAAK,WAAW;CACvD,IAAI,CAAC,QAAQ,IACX,MAAM,IAAI,MAAM,kDAAkD;CAEpE,OAAO,MAAM;AACf;;;;;;;;;AAUA,SAAgB,aACd,YACA,QACA,SACQ;CACR,MAAM,EAAE,eAAe,eAAe;CACtC,MAAM,SAAS,oBAAoB,cAAc,WAAW;CAC5D,MAAM,YAAY,SAAS,cAAc;CAKzC,MAAM,cAAc,WAAW,SAAS,KAAK,cAAc,uBAAuB;CAClF,MAAM,iBAAiB,cAAc,sBAAsB;CAC3D,MAAM,YAAY,YAAY,iBAAiB;CAC/C,MAAM,QAAQ,cAAc,cAAc;CAI1C,MAAM,QAAQ,cAAc,aAAa,cAAc,YAAY,QAAQ;CAE3E,OAAO;EACL;EACA,GAAI,cAAc,CAAC,OAAO,oBAAoB,GAAG,WAAW,EAAE,IAAI,CAAC;EAGnE,GAAI,WAAW,SAAS,IACpB,CAAC,aAAa,WAAW,KAAK,OAAO,GAAG,iBAAiB,GAAG,YAAY,CAAC,CAAC,KAAK,GAAG,EAAE,GAAG,IACvF,CAAC;EACL,GAAG,cAAc,KACd,MAAM,IAAI,CAAC,CACX,QAAQ,MAAM,EAAE,KAAK,MAAM,MAAM,EAAE,KAAK,MAAM,oBAAoB;EACrE,cAAc;EACd,kBAAkB,OAAO,GAAG,UAAU,GAAG,MAAM,GAAG,MAAM;EACxD;CACF,CAAC,CAAC,KAAK,IAAI;AACb"}
1
+ {"version":3,"file":"iife.js","names":[],"sources":["../../src/core/iife.ts"],"sourcesContent":["/**\n * Shared CompiledSchema<T> IIFE generation.\n * Used by both CLI emitter and unplugin transform.\n */\n\nimport { RETAINED_SCHEMA_VAR } from \"./codegen/context.js\";\nimport type { CompiledSchemaInfo } from \"./pipeline.js\";\n\n/**\n * Import statement required by generateIIFE output (references\n * __zodCompilerConfig). `core` is bound for $ZodAsyncError: superRefine and\n * custom callbacks may only reveal that they are async through the promise\n * they return, at which point zod's own synchronous parse raises.\n */\nexport const ZOD_CONFIG_IMPORT =\n 'import { config as __zodCompilerConfig, core as __zcCore, ZodRealError as __zcZodError } from \"zod\";';\n\n/**\n * File-level `__zcMsg` declaration (must appear once after ZOD_CONFIG_IMPORT):\n * the message an issue gets when nothing was baked into it at build time.\n *\n * Resolves zod's tail of `finalizeIssue` — `config.customError` then\n * `config.localeError` then \"Invalid input\" — and does it PER CALL, because the\n * config is mutable: `z.config({ localeError })` in an entry point runs after\n * the schema modules it imports, so a value snapshotted at module init misses\n * it. Reading a captured `localeError` alone also dropped `customError`\n * outright, silently ignoring the global map most i18n setups install.\n *\n * The head of zod's chain — the schema's own `error` option — is baked into the\n * issue at build time and short-circuits this. The one link that cannot be\n * reproduced is a per-CALL `ctx.error`, which would have to travel through\n * `safeParse`; that entry point sits at V8's inlining budget, where even an\n * unused extra parameter measured ~12% on every parse.\n *\n * Only ever called while building an error, never on a successful parse.\n */\nexport const ZOD_MSG_DECLARATION =\n 'function __zcUw(m){return typeof m===\"string\"?m:(m===undefined||m===null?undefined:m.message);}' +\n \"var __zcMsg=function(iss){var c=__zodCompilerConfig(),m;\" +\n \"if(c.customError){m=__zcUw(c.customError(iss));if(m!==undefined&&m!==null)return m;}\" +\n \"if(c.localeError){m=__zcUw(c.localeError(iss));if(m!==undefined&&m!==null)return m;}\" +\n 'return \"Invalid input\";};';\n\n/**\n * Shared failure-result for __zcFin / __zcFinD. Inline mode (CLI emitter)\n * declares it once per compiled file; lean mode (all unplugin bundlers) declares\n * it once per bundle in the plugin-materialized runtime module (module-local —\n * generated code only ever references __zcFin/__zcFinD, never __ZcFail).\n *\n * Why a prototype getter and not `{success:false, get error(){...}}`: an object\n * literal with an inline accessor forces V8 down its slow accessor-defining\n * allocation path — ~110ns per failure, measured — which dominates the entire\n * invalid-input cost whenever callers never read `.error`. Hosting `error` on\n * the prototype turns each failure into a plain field-only instantiation (~2ns,\n * ~13x), with the lazy-cache semantics intact. (Trade-off: `error` is a\n * prototype accessor, so it no longer shows up in `Object.keys(result)` / spread\n * / JSON.stringify of the result wrapper — `.success`/`.error`/`.data` access,\n * destructuring, and `in` are unaffected.)\n *\n * One class serves both finalizers, so the instances share one hidden class:\n * __zcFin passes pre-collected issues in `_e` (with `_f===null`); __zcFinD\n * passes the hosted slow-walk in `_f` plus the input in `_i`, and the getter\n * runs the walk on first `.error` read. The whole finalization — locale fill\n * (__zcMsg applied ONLY when an issue carries no message, never overwriting a\n * baked-in custom/fallback message), input strip, and ZodError construction\n * (zod v4 JSON.stringifies every issue into `message` and captures a stack\n * trace) — stays deferred inside the cached accessor exactly as before, since\n * the issues array is observable solely through `.error`.\n *\n * `input` is `delete`d, not assigned `undefined`. Key PRESENCE is observable —\n * `\"input\" in issue`, `Object.keys(issue)`, object spread, `toStrictEqual`\n * against a zod issue — and zod's `util.finalizeIssue` does `delete full.input`\n * whenever `reportInput` is off, so assignment left every compiled issue one\n * enumerable key wider than zod's. The delete's dictionary-mode transition is\n * affordable precisely BECAUSE of the deferral above: it runs only after a\n * caller asks for `.error` on a failed parse, and is memoised in `_c`. Neither\n * a successful parse nor a `.success`/`.is()` check on a rejected one reaches\n * it — measured, the two are unchanged, while the `.error` read itself goes\n * ~3.95us -> ~4.19us per failure, i.e. ~6% of a path whose cost is already\n * dominated by the ZodError construction on the next line (stack capture plus a\n * JSON.stringify of every issue).\n */\nexport const FAIL_CLASS_DECL =\n \"function __ZcFail(e,f,i){this.success=false;this._e=e;this._f=f;this._i=i;this._c=undefined;}\" +\n 'Object.defineProperty(__ZcFail.prototype,\"error\",{configurable:true,get:function(){' +\n \"if(this._c)return this._c;\" +\n \"var e=this._f!==null?this._f(this._i):this._e;\" +\n 'for(var i=0;i<e.length;i++){if(e[i].message===undefined&&typeof __zcMsg===\"function\")e[i].message=__zcMsg(e[i]);delete e[i].input;delete e[i].continue;}' +\n \"return this._c=new __zcZodError(e);}});\";\n\n/** Eager finalizer (mutation / partial-fast-path schemas): issues already\n * collected in `e`; success short-circuits to a plain result literal. */\nexport const FIN_DECL =\n \"function __zcFin(e,d){if(!e.length)return{success:true,data:d};return new __ZcFail(e,null,null);}\";\n\n/**\n * Deferred-collection finalizer for Fast-Path-eligible schemas. When the\n * fast check fails, the ENTIRE slow path (the issue-collecting re-walk) is\n * pushed into the cached `.error` accessor instead of running eagerly:\n * fast-eligible schemas never mutate, so the walk's only output is the\n * issues array, which is observable solely through `.error` — one step\n * further along the same lazy boundary `__zcFin` already established (locale\n * fill, input strip, ZodError construction). A failed safeParse whose\n * `.error` is never read costs the fast check alone.\n *\n * Takes the schema's HOSTED slow-walk function plus the input — NOT a\n * per-call closure: `__zcFinD(__sw_N, input)` allocates only the result\n * object, where `__zcFinD(function(){...})` paid a closure environment and\n * function object per failure. Hosting the walk also shrinks safeParse to\n * two statements, within V8's inlining budget (the success-path result\n * literal becomes escape-analyzable at monomorphic call sites).\n *\n * The walk re-reads `input` at `.error`-read time; a caller that mutates\n * the input between safeParse and reading `.error` sees issues for the\n * mutated value (zod materializes at parse time). Same caveat class as the\n * documented __zcFin deferral.\n */\nexport const FIN_DEFERRED_DECL = \"function __zcFinD(f,inp){return new __ZcFail(null,f,inp);}\";\n\n/**\n * Compact-mode failure class — a lazy failure that delegates error reporting to\n * the ORIGINAL Zod schema's `safeParse`. Used by `output: \"compact\"`, where the\n * compiled slow walk is dropped entirely: a mutation-free schema's fast check\n * is the only generated validation, and on a fast-check failure the cold error\n * path is produced by the retained Zod schema itself (`zod` is the source of\n * truth, so the issues are byte-identical — no second validation engine).\n *\n * `_z` is the schema's PRISTINE safeParse method, captured by\n * emitRetainedMethod (see context.ts), and `_r` is its receiver — the `__zs`\n * binding generateIIFE places above that capture. Both are read before the\n * trailing `__zcMkv` call installs anything, so the method is zod's own\n * implementation, never the compiled delegate, avoiding infinite recursion\n * without allocating a bound function. The zod parse is deferred until `.error`\n * is read and cached, so\n * the common `safeParse(x).success`/`.is(x)` checks on invalid input cost only\n * the fast check (zod never runs) — the same deferral boundary `__zcFinD`\n * establishes for the compiled slow walk. Sound because compact mode is gated\n * on a TOTAL fast path: `fc(input) === false` ⟹ zod rejects, so `success:false`\n * holds without consulting zod.\n *\n * The getter returns zod's OWN ZodError verbatim (no locale fill / input strip /\n * re-wrap — zod already finalized it), so a delegated failure is exactly what\n * the unaltered schema would have produced.\n */\nexport const FAILZ_CLASS_DECL =\n \"function __ZcFailZ(z,r,i){this.success=false;this._z=z;this._r=r;this._i=i;this._c=undefined;}\" +\n 'Object.defineProperty(__ZcFailZ.prototype,\"error\",{configurable:true,get:function(){' +\n \"return this._c||(this._c=this._z.call(this._r,this._i).error);}});\";\n\n/** Compact-mode finalizer: retain a pristine safeParse, its receiver, and input lazily. */\nexport const FINZ_DECL = \"function __zcFinZ(z,r,i){return new __ZcFailZ(z,r,i);}\";\n\n/**\n * Validator factory. Inline mode (CLI emitter) declares it once per compiled\n * file; lean mode (all unplugin bundlers) exports it once per bundle from the\n * plugin-materialized runtime module — generated code never imports it from\n * the zod-compiler package itself, so zod-compiler stays a devDependency and\n * the helper set is always version-locked to the codegen that calls it.\n * Wraps a safeParse function into the CompiledSchema interface.\n *\n * IDENTITY-PRESERVING: with zodCompat (schema != null) the compiled\n * parse/safeParse/parseAsync/safeParseAsync are installed as OWN properties\n * on the original schema object, which is returned as-is. zod v4 keys\n * several APIs on object identity — toJSONSchema's ctx.seen registers the\n * object it is handed while each processor closure captures the original\n * inst (a wrapper crashes `optionalProcessor` with \"Cannot set properties\n * of undefined (setting 'ref')\" the moment a compiled schema is composed\n * into another schema), and globalRegistry/.meta() is a WeakMap keyed by\n * the schema instance (a wrapper silently loses OpenAPI titles/ids). An\n * Object.create wrapper breaks both; mutating the original breaks neither\n * (zod's internal parsing flows through _zod.run, never the public\n * methods, and derived schemas — .optional(), .extend() — are fresh\n * instances that fall back to plain zod). schema=null (zodCompat: false)\n * still produces a plain method-bag object.\n *\n * fc is the schema's hosted fast-check boolean function (null when no Fast\n * Path exists). parse()/parseAsync() try it first and return the input\n * directly on success: fc is small enough for V8 to inline, so the hot parse\n * path runs with zero allocations — calling fn would allocate an intermediate\n * SafeParseResult that escape analysis cannot remove (fn never inlines).\n * Fast-path-eligible schemas never mutate, so fc(input) ⟹ data === input.\n *\n * is is the TOTAL fast-check predicate (fc when the fast path is total, else\n * null). Installed as `.is()` — a zero-allocation boolean type guard. When\n * null (partial fast path or none) `.is()` derives from fn(input).success: a\n * partial fc can pass-through valid input but its `false` does not imply\n * rejection (a default/catch may still succeed), so it would be unsound as a\n * standalone guard.\n *\n * parseAsync/safeParseAsync wrap the SYNC validator, which is right for every\n * schema the compiler can reproduce — none of them are async. It is wrong for\n * the ones it cannot: an `async` refinement or a `z.promise()` extracts to a\n * Zod delegate, and delegating means calling Zod's SYNCHRONOUS safeParse, which\n * raises `$ZodAsyncError` by design. Wrapping that gave the compiled schema an\n * async pair that rejected with `$ZodAsyncError` for EVERY input — including\n * valid ones — where Zod resolves normally, so `await UserSchema.parseAsync(x)`\n * stopped working the moment any part of the schema went async.\n *\n * So both are guarded: a synchronous throw hands off to the schema's ORIGINAL\n * async method, captured before these are installed — the same escape hatch\n * `~standard` already uses for its throw path, and for the same reason (the\n * compiled validator has no async mode to offer, and Zod's is exact). It also\n * fixes the smaller wart that a throwing `fn` made `parseAsync` throw\n * SYNCHRONOUSLY rather than return a rejected promise. With `zodCompat: false`\n * there is no schema to delegate to and the throw propagates as before.\n *\n * `~standard` is REPLACED, not merely preserved. Zod builds it lazily as\n * `validate: (v) => safeParse(inst, v)` — the core FUNCTION, which goes straight\n * to `inst._zod.run`. It never reads the schema's own `safeParse` property, so\n * installing the compiled one leaves this route entirely uncompiled: measured\n * 271.7 ns against the compiled 26.6 ns on the same schema, i.e. Standard Schema\n * consumers (tRPC, Hono, TanStack) were getting plain Zod.\n *\n * Zod's own `~standard` is never READ, only overwritten. Zod installs the slot\n * with `util.defineLazy` — commented there as \"avoid creating objects for every\n * schema\" — so it is an accessor that builds `{version, vendor, validate}` plus\n * its closure on first touch. Reading it to capture a fallback fired that getter\n * for every compiled schema while the module was still initializing.\n *\n * How much that costs depends on the entry point, and only one of them is free:\n * classic `zod` forces the slot itself during `ZodType.init` (it does\n * `Object.assign(inst[\"~standard\"], { jsonSchema })`), so there the read hit an\n * already-built object and cost only an accessor call. `zod/mini` and raw\n * `zod/v4/core` never touch it, so for those the read built — and retained — an\n * object and a closure per schema, purely to capture a fallback the schema will\n * most likely never expose to a Standard Schema consumer.\n *\n * The throw path is rebuilt instead of captured. Zod's validate catches a\n * synchronous throw and retries through `safeParseAsync` — that is how an async\n * refinement resolves and how a throwing check surfaces as a rejected promise\n * rather than a synchronous throw — so calling the already-captured `zspa` and\n * mapping its result is the same route to the same result. `vendor` is likewise\n * a constant: schema discovery only ever admits zod schemas, and classic, mini\n * and core all hardcode `vendor: \"zod\"` themselves.\n *\n * Not carried over (unchanged by this, and pre-dating it): classic's\n * `~standard.jsonSchema` extension, which the replacement object has never\n * reproduced.\n *\n * Installed with defineProperty rather than assignment: Zod's lazy setter\n * redefines the slot as non-writable, so a second `__zcMkv` on the same schema\n * object — two exports aliasing one schema — would throw under ESM strict mode.\n */\nexport const MK_VALIDATOR_DECL =\n \"function __zcMkv(fn,schema,fc,is){var w=schema||{};var zpa=w.parseAsync,zspa=w.safeParseAsync;w.parse=fc?function(input){if(fc(input))return input;var r=fn(input);if(r.success)return r.data;throw r.error;}:function(input){var r=fn(input);if(r.success)return r.data;throw r.error;};w.safeParse=fn;w.safeParseAsync=function(input){try{return Promise.resolve(fn(input));}catch(e){if(zspa)return zspa(input);throw e;}};w.parseAsync=fc?function(input){try{if(fc(input))return Promise.resolve(input);var r=fn(input);if(r.success)return Promise.resolve(r.data);return Promise.reject(r.error);}catch(e){if(zpa)return zpa(input);throw e;}}:function(input){try{var r=fn(input);if(r.success)return Promise.resolve(r.data);return Promise.reject(r.error);}catch(e){if(zpa)return zpa(input);throw e;}};w.is=is||function(input){return fn(input).success;};\" +\n 'Object.defineProperty(w,\"~standard\",{configurable:true,value:{version:1,vendor:\"zod\",validate:function(input){var r;try{if(fc&&fc(input))return{value:input};r=fn(input);}catch(e){if(zspa)return zspa(input).then(function(q){return q.success?{value:q.data}:{issues:q.error.issues};});throw e;}return r.success?{value:r.data}:{issues:r.error.issues};}}});' +\n \"return w;}\";\n\nfunction extractFunctionName(functionDef: string): string {\n const match = /^function\\s+(\\w+)\\s*\\(/.exec(functionDef);\n if (!match?.[1]) {\n throw new Error(\"Cannot extract function name from generated code\");\n }\n return match[1];\n}\n\n/**\n * Does the IIFE's preamble DEREFERENCE the retained schema at evaluation time?\n *\n * Only `__rf` does: every entry is `__zs<accessPath>`, and an access path walks\n * the schema's structure — `._zod.innerType` fires a `z.lazy()` getter,\n * `.shape` materializes a `z.object()` shape (zod v4 reads every property\n * descriptor, so ONE `.shape` read fires ALL of an object's recursion getters).\n * Binding `__zs` itself does not: constructing the schema leaves deferred\n * callbacks unforced, and `usesRetainedSchema`'s only read is `__zs.safeParse`,\n * an own property zod assigns during `ZodType.init`.\n *\n * The distinction decides whether the IIFE may be evaluated inside the\n * INITIALIZER of the binding the schema's own deferred callbacks close over —\n * see the self-referential path in the unplugin's autoDiscover rewrite.\n */\nexport function iifeDerefsSchema(schema: CompiledSchemaInfo): boolean {\n return schema.refEntries.length > 0;\n}\n\n/**\n * Generate a `/* @__PURE__ * /` IIFE wrapping a compiled validator.\n *\n * @param schemaExpr - Expression resolving to the original Zod schema\n * (e.g. `\"UserSchema\"` in unplugin, `\"(__src_X as any).schema\"` in CLI)\n * @param schema\n * @param options - `pure: false` drops the `/* @__PURE__ * /` annotation, for\n * the one caller that emits the IIFE as a STATEMENT following the\n * declaration rather than as its initializer: there the call's whole point is\n * its side effect (`__zcMkv` installing the compiled methods on the schema),\n * and a bundler that believed it pure would drop the compilation outright.\n */\nexport function generateIIFE(\n schemaExpr: string,\n schema: CompiledSchemaInfo,\n options?: { zodCompat?: boolean | undefined; pure?: boolean | undefined },\n): string {\n const { codegenResult, refEntries } = schema;\n const fnName = extractFunctionName(codegenResult.functionDef);\n const zodCompat = options?.zodCompat !== false;\n // Every fallback access starts from the same source schema, and compact\n // delegation names it outright. Capture it once whenever either needs it, so\n // an inline initializer is not reconstructed for each path and again for the\n // identity-preserving __zcMkv target.\n const bindsSchema = refEntries.length > 0 || codegenResult.usesRetainedSchema === true;\n const retainedSchema = bindsSchema ? RETAINED_SCHEMA_VAR : schemaExpr;\n const schemaArg = zodCompat ? retainedSchema : \"null\";\n const fcArg = codegenResult.fastFnName ?? \"null\";\n // `.is()` gets the fast-check directly only when it is a total predicate;\n // partial/none falls back to safeParse().success inside __zcMkv. A rebuilding\n // schema has no by-reference `fc` but still names its predicate separately.\n const isArg = codegenResult.isFnName ?? (codegenResult.fastTotal ? fcArg : \"null\");\n\n return [\n options?.pure === false ? \"(() => {\" : \"/* @__PURE__ */ (() => {\",\n ...(bindsSchema ? [`var ${RETAINED_SCHEMA_VAR}=${schemaExpr};`] : []),\n // Only fallback refs need the array; a compact validator with none of its\n // own reads `__zs` directly rather than allocating `[__zs]` to index into.\n ...(refEntries.length > 0\n ? [`var __rf=[${refEntries.map((fb) => `${retainedSchema}${fb.accessPath}`).join(\",\")}];`]\n : []),\n ...codegenResult.code\n .split(\"\\n\")\n .filter((l) => l.trim() !== \"\" && l.trim() !== \"/* zod-compiler */\"),\n codegenResult.functionDef,\n `return __zcMkv(${fnName},${schemaArg},${fcArg},${isArg});`,\n \"})()\",\n ].join(\"\\n\");\n}\n"],"mappings":";;;;;;;;;;;;AAcA,MAAa,oBACX;;;;;;;;;;;;;;;;;;;;AAqBF,MAAa,sBACX;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6CF,MAAa,kBACX;;;AASF,MAAa,WACX;;;;;;;;;;;;;;;;;;;;;;;AAwBF,MAAa,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BjC,MAAa,mBACX;;AAKF,MAAa,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6FzB,MAAa,oBACX;AAIF,SAAS,oBAAoB,aAA6B;CACxD,MAAM,QAAQ,yBAAyB,KAAK,WAAW;CACvD,IAAI,CAAC,QAAQ,IACX,MAAM,IAAI,MAAM,kDAAkD;CAEpE,OAAO,MAAM;AACf;;;;;;;;;;;;;;;;AAiBA,SAAgB,iBAAiB,QAAqC;CACpE,OAAO,OAAO,WAAW,SAAS;AACpC;;;;;;;;;;;;;AAcA,SAAgB,aACd,YACA,QACA,SACQ;CACR,MAAM,EAAE,eAAe,eAAe;CACtC,MAAM,SAAS,oBAAoB,cAAc,WAAW;CAC5D,MAAM,YAAY,SAAS,cAAc;CAKzC,MAAM,cAAc,WAAW,SAAS,KAAK,cAAc,uBAAuB;CAClF,MAAM,iBAAiB,cAAc,sBAAsB;CAC3D,MAAM,YAAY,YAAY,iBAAiB;CAC/C,MAAM,QAAQ,cAAc,cAAc;CAI1C,MAAM,QAAQ,cAAc,aAAa,cAAc,YAAY,QAAQ;CAE3E,OAAO;EACL,SAAS,SAAS,QAAQ,aAAa;EACvC,GAAI,cAAc,CAAC,OAAO,oBAAoB,GAAG,WAAW,EAAE,IAAI,CAAC;EAGnE,GAAI,WAAW,SAAS,IACpB,CAAC,aAAa,WAAW,KAAK,OAAO,GAAG,iBAAiB,GAAG,YAAY,CAAC,CAAC,KAAK,GAAG,EAAE,GAAG,IACvF,CAAC;EACL,GAAG,cAAc,KACd,MAAM,IAAI,CAAC,CACX,QAAQ,MAAM,EAAE,KAAK,MAAM,MAAM,EAAE,KAAK,MAAM,oBAAoB;EACrE,cAAc;EACd,kBAAkB,OAAO,GAAG,UAAU,GAAG,MAAM,GAAG,MAAM;EACxD;CACF,CAAC,CAAC,KAAK,IAAI;AACb"}
@@ -1 +1 @@
1
- {"version":3,"file":"discovery.d.ts","names":[],"sources":["../src/discovery.ts"],"mappings":";;UAIiB;;EAEf;;;;;;;;;iBAoBoB,gBACpB,kBACA,UAAU,kBACT,QAAQ"}
1
+ {"version":3,"file":"discovery.d.ts","names":[],"sources":["../src/discovery.ts"],"mappings":";;UAKiB;;EAEf;;;;;;;;;iBAUoB,gBACpB,kBACA,UAAU,kBACT,QAAQ"}
package/dist/discovery.js CHANGED
@@ -1,16 +1,8 @@
1
1
  import { isCompiledSchema } from "./core/compile.js";
2
+ import { isZodSchema } from "./is-zod-schema.js";
2
3
  import { loadSourceFile } from "./loader.js";
3
4
  //#region src/discovery.ts
4
5
  /**
5
- * Check if a value is a Zod schema by detecting the _zod.def structure.
6
- * Used by autoDiscover to find plain Zod exports.
7
- */
8
- function isZodSchema(value) {
9
- if (typeof value !== "object" || value === null || !("_zod" in value)) return false;
10
- const zod = value["_zod"];
11
- return typeof zod === "object" && zod !== null && "def" in zod;
12
- }
13
- /**
14
6
  * Discover schemas in a source file by importing it and scanning exports.
15
7
  *
16
8
  * - Default mode: finds compile() calls via CompiledSchema marker.
@@ -22,15 +14,19 @@ async function discoverSchemas(filePath, options) {
22
14
  const schemas = [];
23
15
  const targets = { ...mod };
24
16
  const defaultExport = mod["default"];
25
- if (defaultExport != null && typeof defaultExport === "object" && !isCompiledSchema(defaultExport)) Object.assign(targets, defaultExport);
26
- for (const [exportName, value] of Object.entries(targets)) if (isCompiledSchema(value)) schemas.push({
27
- exportName,
28
- schema: value["schema"]
29
- });
30
- else if (options?.autoDiscover && isZodSchema(value)) schemas.push({
31
- exportName,
32
- schema: value
33
- });
17
+ if (defaultExport != null && typeof defaultExport === "object" && !isCompiledSchema(defaultExport)) try {
18
+ Object.assign(targets, defaultExport);
19
+ } catch {}
20
+ for (const [exportName, value] of Object.entries(targets)) try {
21
+ if (isCompiledSchema(value)) schemas.push({
22
+ exportName,
23
+ schema: value["schema"]
24
+ });
25
+ else if (options?.autoDiscover && isZodSchema(value)) schemas.push({
26
+ exportName,
27
+ schema: value
28
+ });
29
+ } catch {}
34
30
  return schemas;
35
31
  }
36
32
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"discovery.js","names":[],"sources":["../src/discovery.ts"],"sourcesContent":["import { isCompiledSchema } from \"./core/compile.js\";\nimport type { DiscoveredSchema } from \"./core/types.js\";\nimport { loadSourceFile } from \"./loader.js\";\n\nexport interface DiscoverOptions {\n /** Auto-detect all exported Zod schemas without requiring compile() wrappers. */\n autoDiscover?: boolean | undefined;\n}\n\n/**\n * Check if a value is a Zod schema by detecting the _zod.def structure.\n * Used by autoDiscover to find plain Zod exports.\n */\nfunction isZodSchema(value: unknown): boolean {\n if (typeof value !== \"object\" || value === null || !(\"_zod\" in value)) return false;\n const zod = (value as Record<string, unknown>)[\"_zod\"];\n return typeof zod === \"object\" && zod !== null && \"def\" in zod;\n}\n\n/**\n * Discover schemas in a source file by importing it and scanning exports.\n *\n * - Default mode: finds compile() calls via CompiledSchema marker.\n * - autoDiscover mode: also finds plain Zod schema exports via _zod.def detection.\n * compile() schemas take priority (isCompiledSchema checked first).\n */\nexport async function discoverSchemas(\n filePath: string,\n options?: DiscoverOptions,\n): Promise<DiscoveredSchema[]> {\n const mod = await loadSourceFile(filePath);\n const schemas: DiscoveredSchema[] = [];\n const targets: Record<string, unknown> = { ...mod };\n const defaultExport = mod[\"default\"];\n if (\n defaultExport != null &&\n typeof defaultExport === \"object\" &&\n !isCompiledSchema(defaultExport)\n ) {\n Object.assign(targets, defaultExport as Record<string, unknown>);\n }\n\n for (const [exportName, value] of Object.entries(targets)) {\n if (isCompiledSchema(value)) {\n schemas.push({ exportName, schema: (value as unknown as Record<string, unknown>)[\"schema\"] });\n } else if (options?.autoDiscover && isZodSchema(value)) {\n schemas.push({ exportName, schema: value });\n }\n }\n\n return schemas;\n}\n"],"mappings":";;;;;;;AAaA,SAAS,YAAY,OAAyB;CAC5C,IAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,EAAE,UAAU,QAAQ,OAAO;CAC9E,MAAM,MAAO,MAAkC;CAC/C,OAAO,OAAO,QAAQ,YAAY,QAAQ,QAAQ,SAAS;AAC7D;;;;;;;;AASA,eAAsB,gBACpB,UACA,SAC6B;CAC7B,MAAM,MAAM,MAAM,eAAe,QAAQ;CACzC,MAAM,UAA8B,CAAC;CACrC,MAAM,UAAmC,EAAE,GAAG,IAAI;CAClD,MAAM,gBAAgB,IAAI;CAC1B,IACE,iBAAiB,QACjB,OAAO,kBAAkB,YACzB,CAAC,iBAAiB,aAAa,GAE/B,OAAO,OAAO,SAAS,aAAwC;CAGjE,KAAK,MAAM,CAAC,YAAY,UAAU,OAAO,QAAQ,OAAO,GACtD,IAAI,iBAAiB,KAAK,GACxB,QAAQ,KAAK;EAAE;EAAY,QAAS,MAA6C;CAAU,CAAC;MACvF,IAAI,SAAS,gBAAgB,YAAY,KAAK,GACnD,QAAQ,KAAK;EAAE;EAAY,QAAQ;CAAM,CAAC;CAI9C,OAAO;AACT"}
1
+ {"version":3,"file":"discovery.js","names":[],"sources":["../src/discovery.ts"],"sourcesContent":["import { isCompiledSchema } from \"./core/compile.js\";\nimport type { DiscoveredSchema } from \"./core/types.js\";\nimport { isZodSchema } from \"./is-zod-schema.js\";\nimport { loadSourceFile } from \"./loader.js\";\n\nexport interface DiscoverOptions {\n /** Auto-detect all exported Zod schemas without requiring compile() wrappers. */\n autoDiscover?: boolean | undefined;\n}\n\n/**\n * Discover schemas in a source file by importing it and scanning exports.\n *\n * - Default mode: finds compile() calls via CompiledSchema marker.\n * - autoDiscover mode: also finds plain Zod schema exports via _zod.def detection.\n * compile() schemas take priority (isCompiledSchema checked first).\n */\nexport async function discoverSchemas(\n filePath: string,\n options?: DiscoverOptions,\n): Promise<DiscoveredSchema[]> {\n const mod = await loadSourceFile(filePath);\n const schemas: DiscoveredSchema[] = [];\n const targets: Record<string, unknown> = { ...mod };\n const defaultExport = mod[\"default\"];\n if (\n defaultExport != null &&\n typeof defaultExport === \"object\" &&\n !isCompiledSchema(defaultExport)\n ) {\n try {\n Object.assign(targets, defaultExport as Record<string, unknown>);\n } catch {\n // A default export that resists enumeration — a Proxy trapping `ownKeys`,\n // a getter that throws — is not a bag of schemas. The named exports of\n // this file still stand on their own.\n }\n }\n\n for (const [exportName, value] of Object.entries(targets)) {\n // Probing touches values the user never offered as candidates, and a Proxy\n // can answer `has` with true and then throw from `get` (an ORM model, an\n // i18n catch-all). Skipping that one export keeps the rest of the file's\n // schemas compiling, where letting it escape fails the whole build.\n try {\n if (isCompiledSchema(value)) {\n schemas.push({\n exportName,\n schema: (value as unknown as Record<string, unknown>)[\"schema\"],\n });\n } else if (options?.autoDiscover && isZodSchema(value)) {\n schemas.push({ exportName, schema: value });\n }\n } catch {\n // Not a schema; leave it alone.\n }\n }\n\n return schemas;\n}\n"],"mappings":";;;;;;;;;;;AAiBA,eAAsB,gBACpB,UACA,SAC6B;CAC7B,MAAM,MAAM,MAAM,eAAe,QAAQ;CACzC,MAAM,UAA8B,CAAC;CACrC,MAAM,UAAmC,EAAE,GAAG,IAAI;CAClD,MAAM,gBAAgB,IAAI;CAC1B,IACE,iBAAiB,QACjB,OAAO,kBAAkB,YACzB,CAAC,iBAAiB,aAAa,GAE/B,IAAI;EACF,OAAO,OAAO,SAAS,aAAwC;CACjE,QAAQ,CAIR;CAGF,KAAK,MAAM,CAAC,YAAY,UAAU,OAAO,QAAQ,OAAO,GAKtD,IAAI;EACF,IAAI,iBAAiB,KAAK,GACxB,QAAQ,KAAK;GACX;GACA,QAAS,MAA6C;EACxD,CAAC;OACI,IAAI,SAAS,gBAAgB,YAAY,KAAK,GACnD,QAAQ,KAAK;GAAE;GAAY,QAAQ;EAAM,CAAC;CAE9C,QAAQ,CAER;CAGF,OAAO;AACT"}
@@ -0,0 +1,23 @@
1
+ import { ZodType } from "zod";
2
+ //#region src/is-zod-schema.d.ts
3
+ /**
4
+ * Is this value a Zod schema? Zod schemas carry `_zod.def`, so that is the
5
+ * probe — the same one auto-discovery uses at build time and `jitAll` uses at
6
+ * runtime.
7
+ *
8
+ * The probe is applied to values the caller does not control: every own value
9
+ * of a module namespace under `jitAll`, every export of a candidate file under
10
+ * discovery. Some of those are not plain objects. A Proxy can trap `has` and
11
+ * `get` and throw — an ORM model, a strict test double, an i18n catch-all — and
12
+ * `"_zod" in value` fires the first trap while reading `_zod` fires the second.
13
+ * Whatever such a value is, it is not a Zod schema, and asking must not take
14
+ * down the caller: an uncaught throw here aborts the importing app at boot, or
15
+ * the build, over an object that was never a candidate.
16
+ *
17
+ * Kept as a leaf module with no runtime imports: `jit`/`jitAll` reach it on the
18
+ * startup path, where pulling in the build-time graph would be pure cost.
19
+ */
20
+ declare function isZodSchema(value: unknown): value is ZodType;
21
+ //#endregion
22
+ export { isZodSchema };
23
+ //# sourceMappingURL=is-zod-schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"is-zod-schema.d.ts","names":[],"sources":["../src/is-zod-schema.ts"],"mappings":";;;;;;;;;;;;;;;;;;;iBAmBgB,YAAY,iBAAiB,SAAS"}
@@ -0,0 +1,32 @@
1
+ //#region src/is-zod-schema.ts
2
+ /**
3
+ * Is this value a Zod schema? Zod schemas carry `_zod.def`, so that is the
4
+ * probe — the same one auto-discovery uses at build time and `jitAll` uses at
5
+ * runtime.
6
+ *
7
+ * The probe is applied to values the caller does not control: every own value
8
+ * of a module namespace under `jitAll`, every export of a candidate file under
9
+ * discovery. Some of those are not plain objects. A Proxy can trap `has` and
10
+ * `get` and throw — an ORM model, a strict test double, an i18n catch-all — and
11
+ * `"_zod" in value` fires the first trap while reading `_zod` fires the second.
12
+ * Whatever such a value is, it is not a Zod schema, and asking must not take
13
+ * down the caller: an uncaught throw here aborts the importing app at boot, or
14
+ * the build, over an object that was never a candidate.
15
+ *
16
+ * Kept as a leaf module with no runtime imports: `jit`/`jitAll` reach it on the
17
+ * startup path, where pulling in the build-time graph would be pure cost.
18
+ */
19
+ function isZodSchema(value) {
20
+ if (typeof value !== "object" || value === null) return false;
21
+ try {
22
+ if (!("_zod" in value)) return false;
23
+ const internal = value["_zod"];
24
+ return typeof internal === "object" && internal !== null && "def" in internal;
25
+ } catch {
26
+ return false;
27
+ }
28
+ }
29
+ //#endregion
30
+ export { isZodSchema };
31
+
32
+ //# sourceMappingURL=is-zod-schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"is-zod-schema.js","names":[],"sources":["../src/is-zod-schema.ts"],"sourcesContent":["import type { ZodType } from \"zod\";\n\n/**\n * Is this value a Zod schema? Zod schemas carry `_zod.def`, so that is the\n * probe — the same one auto-discovery uses at build time and `jitAll` uses at\n * runtime.\n *\n * The probe is applied to values the caller does not control: every own value\n * of a module namespace under `jitAll`, every export of a candidate file under\n * discovery. Some of those are not plain objects. A Proxy can trap `has` and\n * `get` and throw — an ORM model, a strict test double, an i18n catch-all — and\n * `\"_zod\" in value` fires the first trap while reading `_zod` fires the second.\n * Whatever such a value is, it is not a Zod schema, and asking must not take\n * down the caller: an uncaught throw here aborts the importing app at boot, or\n * the build, over an object that was never a candidate.\n *\n * Kept as a leaf module with no runtime imports: `jit`/`jitAll` reach it on the\n * startup path, where pulling in the build-time graph would be pure cost.\n */\nexport function isZodSchema(value: unknown): value is ZodType {\n if (typeof value !== \"object\" || value === null) return false;\n try {\n if (!(\"_zod\" in value)) return false;\n const internal = (value as Record<string, unknown>)[\"_zod\"];\n return typeof internal === \"object\" && internal !== null && \"def\" in internal;\n } catch {\n return false;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAmBA,SAAgB,YAAY,OAAkC;CAC5D,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM,OAAO;CACxD,IAAI;EACF,IAAI,EAAE,UAAU,QAAQ,OAAO;EAC/B,MAAM,WAAY,MAAkC;EACpD,OAAO,OAAO,aAAa,YAAY,aAAa,QAAQ,SAAS;CACvE,QAAQ;EACN,OAAO;CACT;AACF"}
package/dist/jit.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"jit.d.ts","names":[],"sources":["../src/jit.ts"],"mappings":";;;UA4EiB;;;;;;;EAOf;;;;;;;;;;;;;;;;;;;;;;;iBAwBc,IAAI,UAAU,SAC5B,QAAQ,GACR,UAAU,aACT,IAAI,eAAe,OAAO;;;;;;;;;;;;;;iBAkHb,OAAO,iBAAiB,UAAU"}
1
+ {"version":3,"file":"jit.d.ts","names":[],"sources":["../src/jit.ts"],"mappings":";;;UA6EiB;;;;;;;EAOf;;;;;;;;;;;;;;;;;;;;;;;iBAwBc,IAAI,UAAU,SAC5B,QAAQ,GACR,UAAU,aACT,IAAI,eAAe,OAAO;;;;;;;;;;;;;;iBA8Kb,OAAO,iBAAiB,UAAU"}
package/dist/jit.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { isZodSchema } from "./is-zod-schema.js";
1
2
  import { FAILZ_CLASS_DECL, FAIL_CLASS_DECL, FINZ_DECL, FIN_DECL, FIN_DEFERRED_DECL, MK_VALIDATOR_DECL, ZOD_MSG_DECLARATION, generateIIFE } from "./core/iife.js";
2
3
  import { compileSchemas } from "./core/pipeline.js";
3
4
  import { ZodRealError, config, core } from "zod";
@@ -97,7 +98,11 @@ function jit(schema, options) {
97
98
  return schema;
98
99
  }
99
100
  const original = /* @__PURE__ */ new Map();
100
- for (const slot of SLOTS) original.set(slot, Object.getOwnPropertyDescriptor(target, slot));
101
+ try {
102
+ for (const slot of SLOTS) original.set(slot, Object.getOwnPropertyDescriptor(target, slot));
103
+ } catch {
104
+ return schema;
105
+ }
101
106
  let pending = true;
102
107
  try {
103
108
  installAccessors(target, original, () => {
@@ -118,27 +123,63 @@ function jit(schema, options) {
118
123
  }
119
124
  /**
120
125
  * Front every installed method with a compile-on-read accessor. `trigger`
121
- * materializes the schema, which replaces these accessors with the compiled
122
- * methods (or restores Zod's own), so the read that follows never re-enters.
126
+ * materializes the schema, which normally replaces these accessors with the
127
+ * compiled methods (or restores Zod's own). When that replacement does not take,
128
+ * the getter falls back to the snapshot rather than re-reading the slot — see
129
+ * the re-entrancy note in the body.
123
130
  */
124
131
  function installAccessors(target, original, trigger, cancel) {
125
- for (const slot of SLOTS) Object.defineProperty(target, slot, {
126
- configurable: true,
127
- enumerable: original.get(slot)?.enumerable ?? false,
128
- get() {
129
- trigger();
130
- return target[slot];
131
- },
132
- set(value) {
133
- cancel();
134
- Object.defineProperty(target, slot, {
135
- configurable: true,
136
- enumerable: original.get(slot)?.enumerable ?? false,
137
- value,
138
- writable: true
139
- });
140
- }
141
- });
132
+ for (const slot of SLOTS) {
133
+ let reading = false;
134
+ const read = function() {
135
+ if (reading) return fromSnapshot(target, original, slot);
136
+ reading = true;
137
+ try {
138
+ trigger();
139
+ if (stillFrontedBy(target, slot, read)) return fromSnapshot(target, original, slot);
140
+ return target[slot];
141
+ } finally {
142
+ reading = false;
143
+ }
144
+ };
145
+ Object.defineProperty(target, slot, {
146
+ configurable: true,
147
+ enumerable: original.get(slot)?.enumerable ?? false,
148
+ get: read,
149
+ set(value) {
150
+ cancel();
151
+ Object.defineProperty(target, slot, {
152
+ configurable: true,
153
+ enumerable: original.get(slot)?.enumerable ?? false,
154
+ value,
155
+ writable: true
156
+ });
157
+ }
158
+ });
159
+ }
160
+ }
161
+ /**
162
+ * Is `slot` still fronted by this very accessor — i.e. did the replacement that
163
+ * `trigger()` was supposed to perform not take? A target that will not answer
164
+ * the question is assumed to still hold it, since reading the slot to find out
165
+ * is the recursion being avoided.
166
+ */
167
+ function stillFrontedBy(target, slot, getter) {
168
+ try {
169
+ return Object.getOwnPropertyDescriptor(target, slot)?.get === getter;
170
+ } catch {
171
+ return true;
172
+ }
173
+ }
174
+ /**
175
+ * What Zod had in `slot`, taken from the snapshot made before installation.
176
+ * `undefined` for `is`, which no plain Zod schema carries — the same thing every
177
+ * other degradation path leaves there.
178
+ */
179
+ function fromSnapshot(target, original, slot) {
180
+ const descriptor = original.get(slot);
181
+ if (descriptor === void 0) return void 0;
182
+ return descriptor.get === void 0 ? descriptor.value : descriptor.get.call(target);
142
183
  }
143
184
  /**
144
185
  * Compile every Zod schema found among an object's own values — typically a
@@ -156,19 +197,13 @@ function installAccessors(target, original, trigger, cancel) {
156
197
  function jitAll(schemas, options) {
157
198
  for (const value of Object.values(schemas)) if (isZodSchema(value)) jit(value, options);
158
199
  }
159
- /** Zod schemas carry `_zod.def`; the same probe auto-discovery uses at build time. */
160
- function isZodSchema(value) {
161
- if (typeof value !== "object" || value === null || !("_zod" in value)) return false;
162
- const internal = value["_zod"];
163
- return typeof internal === "object" && internal !== null && "def" in internal;
164
- }
165
200
  /** Put Zod's own descriptors back, dropping the compile-on-read accessors. */
166
201
  function restore(target, original) {
167
- for (const slot of SLOTS) {
202
+ for (const slot of SLOTS) try {
168
203
  const descriptor = original.get(slot);
169
204
  if (descriptor === void 0) delete target[slot];
170
205
  else Object.defineProperty(target, slot, descriptor);
171
- }
206
+ } catch {}
172
207
  }
173
208
  /**
174
209
  * Whether runtime code generation is permitted here. Read per call, never
package/dist/jit.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"jit.js","names":["zodCore","zodConfig"],"sources":["../src/jit.ts"],"sourcesContent":["/**\n * Runtime compilation — the same extract → codegen pipeline the build plugin\n * runs, executed in-process and evaluated through `new Function`.\n *\n * The AOT paths (unplugin, CLI) need a build step to fire. Plenty of everyday\n * code has none: `tsx server.ts`, `node --experimental-strip-types`, a Jest\n * suite, a serverless handler bundled by someone else's toolchain, a library\n * that ships schemas to consumers. There `compile()` is a no-op and every parse\n * runs plain Zod. `jit()` closes that gap — one call, no build integration,\n * measured 3-25x on everyday schemas at ~0.1-0.2 ms of one-time compilation.\n *\n * Nothing here re-implements validation: {@link compileSchemas} and\n * {@link generateIIFE} are the exact modules the plugin and CLI use, so the\n * generated validator, its Zod parity and its performance are identical to what\n * a build would have emitted. The only difference is *when* the code is\n * produced.\n *\n * Compilation is LAZY by default: `jit()` installs accessors that compile on\n * the first read of a parse method and replace themselves with the compiled\n * ones. Importing a module of 500 schemas therefore costs nothing, and a\n * serverless invocation touching three of them pays for three.\n *\n * Runtime code generation is not always permitted — a strict CSP without\n * `unsafe-eval`, some edge runtimes. Zod v4 has the same constraint (its object\n * fast-pass is itself a `new Function`) and already exposes the two switches\n * for it: the `core.util.allowsEval` probe and `z.config({ jitless: true })`.\n * `jit()` honours both and degrades to plain Zod, so one setting governs both\n * compilers. Those targets are where the build plugin belongs anyway — it emits\n * the same validator with no runtime evaluation at all.\n */\n\nimport { config as zodConfig, core as zodCore, ZodRealError, type output, type ZodType } from \"zod\";\nimport {\n FAIL_CLASS_DECL,\n FAILZ_CLASS_DECL,\n FIN_DECL,\n FIN_DEFERRED_DECL,\n FINZ_DECL,\n generateIIFE,\n MK_VALIDATOR_DECL,\n ZOD_MSG_DECLARATION,\n} from \"./core/iife.js\";\nimport { compileSchemas } from \"./core/pipeline.js\";\nimport type { CompiledSchema } from \"./core/types.js\";\n\n/**\n * The declarations `ZOD_CONFIG_IMPORT` supplies to an emitted module, minus the\n * import itself — `zod`'s three bindings arrive as parameters instead, so the\n * evaluated code has no module scope to resolve. Byte-for-byte the same helper\n * source the CLI emitter writes into a `.compiled.ts`, so a JIT validator and\n * an AOT one share their entire runtime layer.\n */\nconst RUNTIME_PRELUDE = [\n ZOD_MSG_DECLARATION,\n FAIL_CLASS_DECL,\n MK_VALIDATOR_DECL,\n FIN_DECL,\n FIN_DEFERRED_DECL,\n FAILZ_CLASS_DECL,\n FINZ_DECL,\n].join(\"\\n\");\n\n/**\n * Methods `__zcMkv` installs. Each is fronted by a compile-on-read accessor\n * until the schema materializes.\n *\n * `~standard` earns its place: Zod builds it as a closure over `_zod.run`, not\n * over the schema's `safeParse` property, so a Standard Schema consumer (tRPC,\n * Hono, TanStack Form) that never touches `safeParse` would otherwise keep\n * running plain Zod forever behind a \"compiled\" schema.\n */\nconst SLOTS = [\"parse\", \"safeParse\", \"parseAsync\", \"safeParseAsync\", \"is\", \"~standard\"] as const;\n\n/** Schemas already handed to `jit()`, so a second call is a no-op rather than a recompile. */\nconst seen = new WeakSet<object>();\n\nexport interface JitOptions {\n /**\n * Compile immediately instead of on first use. Costs ~0.1-0.2 ms per schema\n * at import time; useful for a long-lived server that would rather pay during\n * startup than on the first request, or to surface a compilation failure\n * eagerly. Default `false`.\n */\n eager?: boolean | undefined;\n}\n\n/**\n * Compile `schema` in-process and install the compiled `parse` / `safeParse` /\n * `parseAsync` / `safeParseAsync` / `is` / `~standard` on it.\n *\n * Returns the SAME object — identity-preserving exactly as the build plugin is,\n * so `.shape`, `_zod`, `instanceof`, `z.toJSONSchema()`, `.meta()` and\n * composition into a larger schema all keep working, and every existing\n * reference to the schema picks the compiled methods up.\n *\n * ```ts\n * import { z } from \"zod\";\n * import { jit } from \"zod-compiler/jit\";\n *\n * export const UserSchema = jit(z.object({ name: z.string(), email: z.email() }));\n * UserSchema.safeParse(input); // compiled on this first call\n * ```\n *\n * Schemas the compiler cannot reproduce fall back to Zod per sub-schema, the\n * same way they do at build time; a schema that cannot be compiled at all is\n * left as plain Zod.\n */\nexport function jit<T extends ZodType>(\n schema: T,\n options?: JitOptions,\n): T & CompiledSchema<output<T>> {\n const target = schema as unknown as Record<string, unknown>;\n if (seen.has(target)) return schema as T & CompiledSchema<output<T>>;\n seen.add(target);\n\n if (options?.eager === true) {\n materialize(schema);\n return schema as T & CompiledSchema<output<T>>;\n }\n\n // Snapshot Zod's own descriptors first: materialize() restores them before\n // handing the object to `__zcMkv`, so the generated code sees a pristine\n // schema — it captures the original `parseAsync` / `safeParseAsync` as its\n // throw paths, and capturing a stub there would loop back into itself.\n const original = new Map<string, PropertyDescriptor | undefined>();\n for (const slot of SLOTS) {\n original.set(slot, Object.getOwnPropertyDescriptor(target, slot));\n }\n\n // Installing the accessors is the one step that can throw rather than degrade:\n // a slot locked non-configurable (a future Zod, another wrapper) makes\n // defineProperty raise, and `jit()` is called at module scope — so an\n // unhandled throw here takes down the importing app at boot. Roll back to\n // whatever Zod had and leave the schema alone instead.\n let pending = true;\n try {\n installAccessors(\n target,\n original,\n () => {\n if (!pending) return;\n pending = false;\n restore(target, original);\n materialize(schema);\n },\n () => {\n if (!pending) return;\n pending = false;\n // Restore EVERY slot, not just the one being written. A left-behind\n // accessor whose trigger has been cancelled would read `target[slot]`\n // and re-enter itself — unbounded recursion, which is what a later read\n // of an untouched slot (`~standard`, from a Standard Schema consumer)\n // would otherwise hit.\n //\n // Reached only when something WRITES a slot before anything reads one: a\n // test double, another wrapper, or an AOT `safeParse` assigned directly.\n // The build plugin's own `__zcMkv` does not land here — its first\n // statement READS `parseAsync`/`safeParseAsync` to capture their\n // originals, so it triggers materialization and then overwrites the\n // compiled-by-jit methods with the compiled-by-plugin ones.\n restore(target, original);\n },\n );\n } catch {\n pending = false;\n restore(target, original);\n }\n\n return schema as T & CompiledSchema<output<T>>;\n}\n\n/**\n * Front every installed method with a compile-on-read accessor. `trigger`\n * materializes the schema, which replaces these accessors with the compiled\n * methods (or restores Zod's own), so the read that follows never re-enters.\n */\nfunction installAccessors(\n target: Record<string, unknown>,\n original: ReadonlyMap<string, PropertyDescriptor | undefined>,\n trigger: () => void,\n cancel: () => void,\n): void {\n for (const slot of SLOTS) {\n Object.defineProperty(target, slot, {\n configurable: true,\n // Preserve Zod's own visibility: parse/safeParse/... are enumerable own\n // properties, `~standard` is not. `is` does not exist on a Zod schema, so\n // it follows the non-enumerable convention `compile()` already uses.\n enumerable: original.get(slot)?.enumerable ?? false,\n get() {\n trigger();\n // Whatever now occupies the slot: the compiled method, or — if\n // compilation was impossible — Zod's own, put back by restore().\n return target[slot];\n },\n set(value: unknown) {\n // Someone overwrote a method before first use (a test double, another\n // wrapper). Their value wins, and compilation is cancelled outright —\n // materializing later would restore Zod's descriptors over it.\n cancel();\n Object.defineProperty(target, slot, {\n configurable: true,\n enumerable: original.get(slot)?.enumerable ?? false,\n value,\n writable: true,\n });\n },\n });\n }\n}\n\n/**\n * Compile every Zod schema found among an object's own values — typically a\n * module namespace, so a whole schema file opts in with one call:\n *\n * ```ts\n * import * as schemas from \"./schemas.js\";\n * jitAll(schemas);\n * ```\n *\n * The namespace object itself is never written to (a module namespace is\n * read-only); `jit()` mutates the schema objects it holds, which is what every\n * importer of that module already references.\n */\nexport function jitAll(schemas: object, options?: JitOptions): void {\n for (const value of Object.values(schemas)) {\n if (isZodSchema(value)) jit(value, options);\n }\n}\n\n/** Zod schemas carry `_zod.def`; the same probe auto-discovery uses at build time. */\nfunction isZodSchema(value: unknown): value is ZodType {\n if (typeof value !== \"object\" || value === null || !(\"_zod\" in value)) return false;\n const internal = (value as Record<string, unknown>)[\"_zod\"];\n return typeof internal === \"object\" && internal !== null && \"def\" in internal;\n}\n\n/** Put Zod's own descriptors back, dropping the compile-on-read accessors. */\nfunction restore(\n target: Record<string, unknown>,\n original: ReadonlyMap<string, PropertyDescriptor | undefined>,\n): void {\n for (const slot of SLOTS) {\n const descriptor = original.get(slot);\n if (descriptor === undefined) delete target[slot];\n else Object.defineProperty(target, slot, descriptor);\n }\n}\n\n/**\n * Whether runtime code generation is permitted here. Read per call, never\n * snapshotted: `z.config({ jitless: true })` runs in an entry point, after the\n * schema modules it imports have already been evaluated.\n */\nfunction codegenAllowed(): boolean {\n return zodCore.globalConfig.jitless !== true && zodCore.util.allowsEval.value;\n}\n\n/**\n * Run the pipeline and let the generated IIFE install its methods on `schema`.\n * Swallows failure: a schema that cannot be compiled keeps Zod's own methods,\n * which the caller already has, so there is nothing to report and nothing to\n * break.\n */\nfunction materialize(schema: unknown): void {\n if (!codegenAllowed()) return;\n try {\n buildValidator(schema);\n } catch {\n // Left as plain Zod. Deliberately silent: `jit()` is an optimization, and a\n // schema using a construct the compiler declines is a supported outcome,\n // not an error.\n }\n}\n\n/**\n * Generate the validator and evaluate it, reproducing the module a\n * `.compiled.ts` would have been: helper preamble, the file-level shared block,\n * then the `__zcMkv` IIFE whose `__rf[]` bases and install target are the live\n * schema object passed in as `__schema`.\n */\nfunction buildValidator(schema: unknown): void {\n const { schemas, shared } = compileSchemas([{ exportName: \"jit\", schema }], { mode: \"inline\" });\n const compiled = schemas[0];\n if (compiled === undefined) throw new Error(\"zod-compiler: schema produced no validator\");\n\n const body = [RUNTIME_PRELUDE, shared.code, `return ${generateIIFE(\"__schema\", compiled)};`].join(\n \"\\n\",\n );\n\n // The three bindings ZOD_CONFIG_IMPORT would have imported, passed in so the\n // evaluated code needs no module resolution of its own.\n // oxlint-disable-next-line no-new-func -- generating the validator IS the feature\n const factory = new Function(\n \"__zodCompilerConfig\",\n \"__zcCore\",\n \"__zcZodError\",\n \"__schema\",\n body,\n ) as (\n zodConfigFn: typeof zodConfig,\n zodCoreNs: typeof zodCore,\n zodErrorCtor: typeof ZodRealError,\n target: unknown,\n ) => unknown;\n\n factory(zodConfig, zodCore, ZodRealError, schema);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoDA,MAAM,kBAAkB;CACtB;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC,CAAC,KAAK,IAAI;;;;;;;;;;AAWX,MAAM,QAAQ;CAAC;CAAS;CAAa;CAAc;CAAkB;CAAM;AAAW;;AAGtF,MAAM,uBAAO,IAAI,QAAgB;;;;;;;;;;;;;;;;;;;;;;AAiCjC,SAAgB,IACd,QACA,SAC+B;CAC/B,MAAM,SAAS;CACf,IAAI,KAAK,IAAI,MAAM,GAAG,OAAO;CAC7B,KAAK,IAAI,MAAM;CAEf,IAAI,SAAS,UAAU,MAAM;EAC3B,YAAY,MAAM;EAClB,OAAO;CACT;CAMA,MAAM,2BAAW,IAAI,IAA4C;CACjE,KAAK,MAAM,QAAQ,OACjB,SAAS,IAAI,MAAM,OAAO,yBAAyB,QAAQ,IAAI,CAAC;CAQlE,IAAI,UAAU;CACd,IAAI;EACF,iBACE,QACA,gBACM;GACJ,IAAI,CAAC,SAAS;GACd,UAAU;GACV,QAAQ,QAAQ,QAAQ;GACxB,YAAY,MAAM;EACpB,SACM;GACJ,IAAI,CAAC,SAAS;GACd,UAAU;GAaV,QAAQ,QAAQ,QAAQ;EAC1B,CACF;CACF,QAAQ;EACN,UAAU;EACV,QAAQ,QAAQ,QAAQ;CAC1B;CAEA,OAAO;AACT;;;;;;AAOA,SAAS,iBACP,QACA,UACA,SACA,QACM;CACN,KAAK,MAAM,QAAQ,OACjB,OAAO,eAAe,QAAQ,MAAM;EAClC,cAAc;EAId,YAAY,SAAS,IAAI,IAAI,CAAC,EAAE,cAAc;EAC9C,MAAM;GACJ,QAAQ;GAGR,OAAO,OAAO;EAChB;EACA,IAAI,OAAgB;GAIlB,OAAO;GACP,OAAO,eAAe,QAAQ,MAAM;IAClC,cAAc;IACd,YAAY,SAAS,IAAI,IAAI,CAAC,EAAE,cAAc;IAC9C;IACA,UAAU;GACZ,CAAC;EACH;CACF,CAAC;AAEL;;;;;;;;;;;;;;AAeA,SAAgB,OAAO,SAAiB,SAA4B;CAClE,KAAK,MAAM,SAAS,OAAO,OAAO,OAAO,GACvC,IAAI,YAAY,KAAK,GAAG,IAAI,OAAO,OAAO;AAE9C;;AAGA,SAAS,YAAY,OAAkC;CACrD,IAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,EAAE,UAAU,QAAQ,OAAO;CAC9E,MAAM,WAAY,MAAkC;CACpD,OAAO,OAAO,aAAa,YAAY,aAAa,QAAQ,SAAS;AACvE;;AAGA,SAAS,QACP,QACA,UACM;CACN,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,aAAa,SAAS,IAAI,IAAI;EACpC,IAAI,eAAe,KAAA,GAAW,OAAO,OAAO;OACvC,OAAO,eAAe,QAAQ,MAAM,UAAU;CACrD;AACF;;;;;;AAOA,SAAS,iBAA0B;CACjC,OAAOA,KAAQ,aAAa,YAAY,QAAQA,KAAQ,KAAK,WAAW;AAC1E;;;;;;;AAQA,SAAS,YAAY,QAAuB;CAC1C,IAAI,CAAC,eAAe,GAAG;CACvB,IAAI;EACF,eAAe,MAAM;CACvB,QAAQ,CAIR;AACF;;;;;;;AAQA,SAAS,eAAe,QAAuB;CAC7C,MAAM,EAAE,SAAS,WAAW,eAAe,CAAC;EAAE,YAAY;EAAO;CAAO,CAAC,GAAG,EAAE,MAAM,SAAS,CAAC;CAC9F,MAAM,WAAW,QAAQ;CACzB,IAAI,aAAa,KAAA,GAAW,MAAM,IAAI,MAAM,4CAA4C;CAExF,MAAM,OAAO;EAAC;EAAiB,OAAO;EAAM,UAAU,aAAa,YAAY,QAAQ,EAAE;CAAE,CAAC,CAAC,KAC3F,IACF;CAkBA,IAboB,SAClB,uBACA,YACA,gBACA,YACA,IAQI,CAAC,CAACC,QAAWD,MAAS,cAAc,MAAM;AAClD"}
1
+ {"version":3,"file":"jit.js","names":["zodCore","zodConfig"],"sources":["../src/jit.ts"],"sourcesContent":["/**\n * Runtime compilation — the same extract → codegen pipeline the build plugin\n * runs, executed in-process and evaluated through `new Function`.\n *\n * The AOT paths (unplugin, CLI) need a build step to fire. Plenty of everyday\n * code has none: `tsx server.ts`, `node --experimental-strip-types`, a Jest\n * suite, a serverless handler bundled by someone else's toolchain, a library\n * that ships schemas to consumers. There `compile()` is a no-op and every parse\n * runs plain Zod. `jit()` closes that gap — one call, no build integration,\n * measured 3-25x on everyday schemas at ~0.1-0.2 ms of one-time compilation.\n *\n * Nothing here re-implements validation: {@link compileSchemas} and\n * {@link generateIIFE} are the exact modules the plugin and CLI use, so the\n * generated validator, its Zod parity and its performance are identical to what\n * a build would have emitted. The only difference is *when* the code is\n * produced.\n *\n * Compilation is LAZY by default: `jit()` installs accessors that compile on\n * the first read of a parse method and replace themselves with the compiled\n * ones. Importing a module of 500 schemas therefore costs nothing, and a\n * serverless invocation touching three of them pays for three.\n *\n * Runtime code generation is not always permitted — a strict CSP without\n * `unsafe-eval`, some edge runtimes. Zod v4 has the same constraint (its object\n * fast-pass is itself a `new Function`) and already exposes the two switches\n * for it: the `core.util.allowsEval` probe and `z.config({ jitless: true })`.\n * `jit()` honours both and degrades to plain Zod, so one setting governs both\n * compilers. Those targets are where the build plugin belongs anyway — it emits\n * the same validator with no runtime evaluation at all.\n */\n\nimport { config as zodConfig, core as zodCore, ZodRealError, type output, type ZodType } from \"zod\";\nimport {\n FAIL_CLASS_DECL,\n FAILZ_CLASS_DECL,\n FIN_DECL,\n FIN_DEFERRED_DECL,\n FINZ_DECL,\n generateIIFE,\n MK_VALIDATOR_DECL,\n ZOD_MSG_DECLARATION,\n} from \"./core/iife.js\";\nimport { compileSchemas } from \"./core/pipeline.js\";\nimport type { CompiledSchema } from \"./core/types.js\";\nimport { isZodSchema } from \"./is-zod-schema.js\";\n\n/**\n * The declarations `ZOD_CONFIG_IMPORT` supplies to an emitted module, minus the\n * import itself — `zod`'s three bindings arrive as parameters instead, so the\n * evaluated code has no module scope to resolve. Byte-for-byte the same helper\n * source the CLI emitter writes into a `.compiled.ts`, so a JIT validator and\n * an AOT one share their entire runtime layer.\n */\nconst RUNTIME_PRELUDE = [\n ZOD_MSG_DECLARATION,\n FAIL_CLASS_DECL,\n MK_VALIDATOR_DECL,\n FIN_DECL,\n FIN_DEFERRED_DECL,\n FAILZ_CLASS_DECL,\n FINZ_DECL,\n].join(\"\\n\");\n\n/**\n * Methods `__zcMkv` installs. Each is fronted by a compile-on-read accessor\n * until the schema materializes.\n *\n * `~standard` earns its place: Zod builds it as a closure over `_zod.run`, not\n * over the schema's `safeParse` property, so a Standard Schema consumer (tRPC,\n * Hono, TanStack Form) that never touches `safeParse` would otherwise keep\n * running plain Zod forever behind a \"compiled\" schema.\n */\nconst SLOTS = [\"parse\", \"safeParse\", \"parseAsync\", \"safeParseAsync\", \"is\", \"~standard\"] as const;\n\n/** Schemas already handed to `jit()`, so a second call is a no-op rather than a recompile. */\nconst seen = new WeakSet<object>();\n\nexport interface JitOptions {\n /**\n * Compile immediately instead of on first use. Costs ~0.1-0.2 ms per schema\n * at import time; useful for a long-lived server that would rather pay during\n * startup than on the first request, or to surface a compilation failure\n * eagerly. Default `false`.\n */\n eager?: boolean | undefined;\n}\n\n/**\n * Compile `schema` in-process and install the compiled `parse` / `safeParse` /\n * `parseAsync` / `safeParseAsync` / `is` / `~standard` on it.\n *\n * Returns the SAME object — identity-preserving exactly as the build plugin is,\n * so `.shape`, `_zod`, `instanceof`, `z.toJSONSchema()`, `.meta()` and\n * composition into a larger schema all keep working, and every existing\n * reference to the schema picks the compiled methods up.\n *\n * ```ts\n * import { z } from \"zod\";\n * import { jit } from \"zod-compiler/jit\";\n *\n * export const UserSchema = jit(z.object({ name: z.string(), email: z.email() }));\n * UserSchema.safeParse(input); // compiled on this first call\n * ```\n *\n * Schemas the compiler cannot reproduce fall back to Zod per sub-schema, the\n * same way they do at build time; a schema that cannot be compiled at all is\n * left as plain Zod.\n */\nexport function jit<T extends ZodType>(\n schema: T,\n options?: JitOptions,\n): T & CompiledSchema<output<T>> {\n const target = schema as unknown as Record<string, unknown>;\n if (seen.has(target)) return schema as T & CompiledSchema<output<T>>;\n seen.add(target);\n\n if (options?.eager === true) {\n materialize(schema);\n return schema as T & CompiledSchema<output<T>>;\n }\n\n // Snapshot Zod's own descriptors first: materialize() restores them before\n // handing the object to `__zcMkv`, so the generated code sees a pristine\n // schema — it captures the original `parseAsync` / `safeParseAsync` as its\n // throw paths, and capturing a stub there would loop back into itself.\n const original = new Map<string, PropertyDescriptor | undefined>();\n try {\n for (const slot of SLOTS) {\n original.set(slot, Object.getOwnPropertyDescriptor(target, slot));\n }\n } catch {\n // The target answers a descriptor query with a throw — an exotic wrapper,\n // not anything Zod built. Without a snapshot there is nothing to roll back\n // to, so install nothing and hand back the schema exactly as it came.\n return schema as T & CompiledSchema<output<T>>;\n }\n\n // Installing the accessors is the step most likely to throw rather than\n // degrade: a slot locked non-configurable (a future Zod, another wrapper) makes\n // defineProperty raise, and `jit()` is called at module scope — so an\n // unhandled throw here takes down the importing app at boot. Roll back to\n // whatever Zod had and leave the schema alone instead.\n let pending = true;\n try {\n installAccessors(\n target,\n original,\n () => {\n if (!pending) return;\n pending = false;\n restore(target, original);\n materialize(schema);\n },\n () => {\n if (!pending) return;\n pending = false;\n // Restore EVERY slot, not just the one being written. A left-behind\n // accessor whose trigger has been cancelled would read `target[slot]`\n // and re-enter itself — unbounded recursion, which is what a later read\n // of an untouched slot (`~standard`, from a Standard Schema consumer)\n // would otherwise hit.\n //\n // Reached only when something WRITES a slot before anything reads one: a\n // test double, another wrapper, or an AOT `safeParse` assigned directly.\n // The build plugin's own `__zcMkv` does not land here — its first\n // statement READS `parseAsync`/`safeParseAsync` to capture their\n // originals, so it triggers materialization and then overwrites the\n // compiled-by-jit methods with the compiled-by-plugin ones.\n restore(target, original);\n },\n );\n } catch {\n pending = false;\n restore(target, original);\n }\n\n return schema as T & CompiledSchema<output<T>>;\n}\n\n/**\n * Front every installed method with a compile-on-read accessor. `trigger`\n * materializes the schema, which normally replaces these accessors with the\n * compiled methods (or restores Zod's own). When that replacement does not take,\n * the getter falls back to the snapshot rather than re-reading the slot — see\n * the re-entrancy note in the body.\n */\nfunction installAccessors(\n target: Record<string, unknown>,\n original: ReadonlyMap<string, PropertyDescriptor | undefined>,\n trigger: () => void,\n cancel: () => void,\n): void {\n for (const slot of SLOTS) {\n // Re-entrancy is settled structurally rather than by inspection. Reading the\n // slot again is how this getter normally hands over — to the compiled method\n // materialize() installed, or to Zod's own that restore() put back — but the\n // handover can fail to take: a target frozen after `jit()` refuses both, and\n // a second copy of this module in the graph leaves ITS accessor on the slot,\n // so the two bounce reads between them. `trigger()` is spent by then, so\n // either way the read recurses until the stack blows. While a read is already\n // in flight, serve Zod's own method from the snapshot: a schema that cannot\n // be compiled still parses.\n let reading = false;\n const read = function (): unknown {\n if (reading) return fromSnapshot(target, original, slot);\n reading = true;\n try {\n trigger();\n if (stillFrontedBy(target, slot, read)) return fromSnapshot(target, original, slot);\n return target[slot];\n } finally {\n reading = false;\n }\n };\n Object.defineProperty(target, slot, {\n configurable: true,\n // Preserve Zod's own visibility: parse/safeParse/... are enumerable own\n // properties, `~standard` is not. `is` does not exist on a Zod schema, so\n // it follows the non-enumerable convention `compile()` already uses.\n enumerable: original.get(slot)?.enumerable ?? false,\n get: read,\n set(value: unknown) {\n // Someone overwrote a method before first use (a test double, another\n // wrapper). Their value wins, and compilation is cancelled outright —\n // materializing later would restore Zod's descriptors over it.\n cancel();\n Object.defineProperty(target, slot, {\n configurable: true,\n enumerable: original.get(slot)?.enumerable ?? false,\n value,\n writable: true,\n });\n },\n });\n }\n}\n\n/**\n * Is `slot` still fronted by this very accessor — i.e. did the replacement that\n * `trigger()` was supposed to perform not take? A target that will not answer\n * the question is assumed to still hold it, since reading the slot to find out\n * is the recursion being avoided.\n */\nfunction stillFrontedBy(\n target: Record<string, unknown>,\n slot: string,\n getter: () => unknown,\n): boolean {\n try {\n return Object.getOwnPropertyDescriptor(target, slot)?.get === getter;\n } catch {\n return true;\n }\n}\n\n/**\n * What Zod had in `slot`, taken from the snapshot made before installation.\n * `undefined` for `is`, which no plain Zod schema carries — the same thing every\n * other degradation path leaves there.\n */\nfunction fromSnapshot(\n target: Record<string, unknown>,\n original: ReadonlyMap<string, PropertyDescriptor | undefined>,\n slot: string,\n): unknown {\n const descriptor = original.get(slot);\n if (descriptor === undefined) return undefined;\n // `~standard` is a lazy getter on a Zod schema, so invoke it rather than\n // reading a `value` it does not have.\n return descriptor.get === undefined ? descriptor.value : descriptor.get.call(target);\n}\n\n/**\n * Compile every Zod schema found among an object's own values — typically a\n * module namespace, so a whole schema file opts in with one call:\n *\n * ```ts\n * import * as schemas from \"./schemas.js\";\n * jitAll(schemas);\n * ```\n *\n * The namespace object itself is never written to (a module namespace is\n * read-only); `jit()` mutates the schema objects it holds, which is what every\n * importer of that module already references.\n */\nexport function jitAll(schemas: object, options?: JitOptions): void {\n for (const value of Object.values(schemas)) {\n if (isZodSchema(value)) jit(value, options);\n }\n}\n\n/** Put Zod's own descriptors back, dropping the compile-on-read accessors. */\nfunction restore(\n target: Record<string, unknown>,\n original: ReadonlyMap<string, PropertyDescriptor | undefined>,\n): void {\n for (const slot of SLOTS) {\n // Per slot, because this also runs as the rollback for a failed install: a\n // target that refuses one slot must not cost the others their restoration.\n // A slot left fronted by its accessor still reads correctly — the getter\n // serves Zod's own method from the snapshot — but it keeps a redundant\n // indirection, so restoring what can be restored is worth the try/catch.\n try {\n const descriptor = original.get(slot);\n if (descriptor === undefined) delete target[slot];\n else Object.defineProperty(target, slot, descriptor);\n } catch {\n // Nothing further to try for this slot.\n }\n }\n}\n\n/**\n * Whether runtime code generation is permitted here. Read per call, never\n * snapshotted: `z.config({ jitless: true })` runs in an entry point, after the\n * schema modules it imports have already been evaluated.\n */\nfunction codegenAllowed(): boolean {\n return zodCore.globalConfig.jitless !== true && zodCore.util.allowsEval.value;\n}\n\n/**\n * Run the pipeline and let the generated IIFE install its methods on `schema`.\n * Swallows failure: a schema that cannot be compiled keeps Zod's own methods,\n * which the caller already has, so there is nothing to report and nothing to\n * break.\n */\nfunction materialize(schema: unknown): void {\n if (!codegenAllowed()) return;\n try {\n buildValidator(schema);\n } catch {\n // Left as plain Zod. Deliberately silent: `jit()` is an optimization, and a\n // schema using a construct the compiler declines is a supported outcome,\n // not an error.\n }\n}\n\n/**\n * Generate the validator and evaluate it, reproducing the module a\n * `.compiled.ts` would have been: helper preamble, the file-level shared block,\n * then the `__zcMkv` IIFE whose `__rf[]` bases and install target are the live\n * schema object passed in as `__schema`.\n */\nfunction buildValidator(schema: unknown): void {\n const { schemas, shared } = compileSchemas([{ exportName: \"jit\", schema }], { mode: \"inline\" });\n const compiled = schemas[0];\n if (compiled === undefined) throw new Error(\"zod-compiler: schema produced no validator\");\n\n const body = [RUNTIME_PRELUDE, shared.code, `return ${generateIIFE(\"__schema\", compiled)};`].join(\n \"\\n\",\n );\n\n // The three bindings ZOD_CONFIG_IMPORT would have imported, passed in so the\n // evaluated code needs no module resolution of its own.\n // oxlint-disable-next-line no-new-func -- generating the validator IS the feature\n const factory = new Function(\n \"__zodCompilerConfig\",\n \"__zcCore\",\n \"__zcZodError\",\n \"__schema\",\n body,\n ) as (\n zodConfigFn: typeof zodConfig,\n zodCoreNs: typeof zodCore,\n zodErrorCtor: typeof ZodRealError,\n target: unknown,\n ) => unknown;\n\n factory(zodConfig, zodCore, ZodRealError, schema);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqDA,MAAM,kBAAkB;CACtB;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC,CAAC,KAAK,IAAI;;;;;;;;;;AAWX,MAAM,QAAQ;CAAC;CAAS;CAAa;CAAc;CAAkB;CAAM;AAAW;;AAGtF,MAAM,uBAAO,IAAI,QAAgB;;;;;;;;;;;;;;;;;;;;;;AAiCjC,SAAgB,IACd,QACA,SAC+B;CAC/B,MAAM,SAAS;CACf,IAAI,KAAK,IAAI,MAAM,GAAG,OAAO;CAC7B,KAAK,IAAI,MAAM;CAEf,IAAI,SAAS,UAAU,MAAM;EAC3B,YAAY,MAAM;EAClB,OAAO;CACT;CAMA,MAAM,2BAAW,IAAI,IAA4C;CACjE,IAAI;EACF,KAAK,MAAM,QAAQ,OACjB,SAAS,IAAI,MAAM,OAAO,yBAAyB,QAAQ,IAAI,CAAC;CAEpE,QAAQ;EAIN,OAAO;CACT;CAOA,IAAI,UAAU;CACd,IAAI;EACF,iBACE,QACA,gBACM;GACJ,IAAI,CAAC,SAAS;GACd,UAAU;GACV,QAAQ,QAAQ,QAAQ;GACxB,YAAY,MAAM;EACpB,SACM;GACJ,IAAI,CAAC,SAAS;GACd,UAAU;GAaV,QAAQ,QAAQ,QAAQ;EAC1B,CACF;CACF,QAAQ;EACN,UAAU;EACV,QAAQ,QAAQ,QAAQ;CAC1B;CAEA,OAAO;AACT;;;;;;;;AASA,SAAS,iBACP,QACA,UACA,SACA,QACM;CACN,KAAK,MAAM,QAAQ,OAAO;EAUxB,IAAI,UAAU;EACd,MAAM,OAAO,WAAqB;GAChC,IAAI,SAAS,OAAO,aAAa,QAAQ,UAAU,IAAI;GACvD,UAAU;GACV,IAAI;IACF,QAAQ;IACR,IAAI,eAAe,QAAQ,MAAM,IAAI,GAAG,OAAO,aAAa,QAAQ,UAAU,IAAI;IAClF,OAAO,OAAO;GAChB,UAAU;IACR,UAAU;GACZ;EACF;EACA,OAAO,eAAe,QAAQ,MAAM;GAClC,cAAc;GAId,YAAY,SAAS,IAAI,IAAI,CAAC,EAAE,cAAc;GAC9C,KAAK;GACL,IAAI,OAAgB;IAIlB,OAAO;IACP,OAAO,eAAe,QAAQ,MAAM;KAClC,cAAc;KACd,YAAY,SAAS,IAAI,IAAI,CAAC,EAAE,cAAc;KAC9C;KACA,UAAU;IACZ,CAAC;GACH;EACF,CAAC;CACH;AACF;;;;;;;AAQA,SAAS,eACP,QACA,MACA,QACS;CACT,IAAI;EACF,OAAO,OAAO,yBAAyB,QAAQ,IAAI,CAAC,EAAE,QAAQ;CAChE,QAAQ;EACN,OAAO;CACT;AACF;;;;;;AAOA,SAAS,aACP,QACA,UACA,MACS;CACT,MAAM,aAAa,SAAS,IAAI,IAAI;CACpC,IAAI,eAAe,KAAA,GAAW,OAAO,KAAA;CAGrC,OAAO,WAAW,QAAQ,KAAA,IAAY,WAAW,QAAQ,WAAW,IAAI,KAAK,MAAM;AACrF;;;;;;;;;;;;;;AAeA,SAAgB,OAAO,SAAiB,SAA4B;CAClE,KAAK,MAAM,SAAS,OAAO,OAAO,OAAO,GACvC,IAAI,YAAY,KAAK,GAAG,IAAI,OAAO,OAAO;AAE9C;;AAGA,SAAS,QACP,QACA,UACM;CACN,KAAK,MAAM,QAAQ,OAMjB,IAAI;EACF,MAAM,aAAa,SAAS,IAAI,IAAI;EACpC,IAAI,eAAe,KAAA,GAAW,OAAO,OAAO;OACvC,OAAO,eAAe,QAAQ,MAAM,UAAU;CACrD,QAAQ,CAER;AAEJ;;;;;;AAOA,SAAS,iBAA0B;CACjC,OAAOA,KAAQ,aAAa,YAAY,QAAQA,KAAQ,KAAK,WAAW;AAC1E;;;;;;;AAQA,SAAS,YAAY,QAAuB;CAC1C,IAAI,CAAC,eAAe,GAAG;CACvB,IAAI;EACF,eAAe,MAAM;CACvB,QAAQ,CAIR;AACF;;;;;;;AAQA,SAAS,eAAe,QAAuB;CAC7C,MAAM,EAAE,SAAS,WAAW,eAAe,CAAC;EAAE,YAAY;EAAO;CAAO,CAAC,GAAG,EAAE,MAAM,SAAS,CAAC;CAC9F,MAAM,WAAW,QAAQ;CACzB,IAAI,aAAa,KAAA,GAAW,MAAM,IAAI,MAAM,4CAA4C;CAExF,MAAM,OAAO;EAAC;EAAiB,OAAO;EAAM,UAAU,aAAa,YAAY,QAAQ,EAAE;CAAE,CAAC,CAAC,KAC3F,IACF;CAkBA,IAboB,SAClB,uBACA,YACA,gBACA,YACA,IAQI,CAAC,CAACC,QAAWD,MAAS,cAAc,MAAM;AAClD"}
@@ -1 +1 @@
1
- {"version":3,"file":"hoist-compile.d.ts","names":[],"sources":["../../src/unplugin/hoist-compile.ts"],"mappings":";;;;;UA6CiB;;EAEf;;EAEA;;EAEA,MAAM;;;;;;iBAmGc,sBACpB,kBAAkB,iBAClB,cACA,YACA,MAAM,cACL,QAAQ"}
1
+ {"version":3,"file":"hoist-compile.d.ts","names":[],"sources":["../../src/unplugin/hoist-compile.ts"],"mappings":";;;;;UA8CiB;;EAEf;;EAEA;;EAEA,MAAM;;;;;;iBAyFc,sBACpB,kBAAkB,iBAClB,cACA,YACA,MAAM,cACL,QAAQ"}
@@ -1,12 +1,9 @@
1
+ import { isZodSchema } from "../is-zod-schema.js";
1
2
  import { loadModule } from "../loader.js";
2
3
  import { generateValidator } from "../core/codegen/index.js";
3
4
  import { extractSchema } from "../core/extract/index.js";
4
5
  import { analyzeHoistedExpression, collectImportBindings } from "./hoist.js";
5
6
  //#region src/unplugin/hoist-compile.ts
6
- /** Duck-type check mirroring discovery's isZodSchema. */
7
- function isZodSchema(value) {
8
- return typeof value === "object" && value !== null && "_zod" in value && typeof value._zod === "object";
9
- }
10
7
  /**
11
8
  * Evaluate a hoisted expression with its zod bindings and compile it.
12
9
  * Returns null when the schema is ineligible or anything fails.
@@ -1 +1 @@
1
- {"version":3,"file":"hoist-compile.js","names":[],"sources":["../../src/unplugin/hoist-compile.ts"],"sourcesContent":["/**\n * Build-time compilation of hoisted schemas.\n *\n * After hoistZodSchemasMeta() lifts `z.object({...})` constructions to\n * module-scope `_zh_*` declarations, this step evaluates each hoisted\n * expression with the project's real zod module, runs the regular\n * extract → codegen pipeline on the resulting schema object, and hands the\n * transform a compiled IIFE to splice in as the declaration initializer:\n *\n * const _zh_x = z.object({ id: z.number() });\n * ⇣\n * const _zh_x = /* @__PURE__ *\\/ (() => { ... return __zcMkv(...); })();\n *\n * Eligibility is STRICTER than hoist eligibility. Hoisting only moves an\n * expression; compiling it bakes build-time evaluation results into\n * generated checks, so the construction must be deterministic:\n *\n * - Every EAGER free identifier must be a zod-package binding. Anything\n * else (other imports: `getLimit()`, globals: `new Date()`,\n * `Math.random()`) could evaluate differently at build time vs module\n * load — those schemas stay plainly hoisted.\n * - DEFERRED references (inside refine/transform/default callbacks) are\n * unrestricted: callbacks reach generated code via fn.toString() or stay\n * on the runtime-constructed schema (`__rf` delegation), never via their\n * build-time closure values. If extraction itself needs a deferred value\n * it cannot have (z.lazy(() => ImportedChild)), evaluation throws and the\n * schema falls back to a plain hoist.\n *\n * Every failure path is graceful: the declaration keeps its original zod\n * expression and runtime behavior is unchanged.\n */\n\nimport type { CodegenMode } from \"../core/codegen/context.js\";\nimport { generateValidator } from \"../core/codegen/index.js\";\nimport { extractSchema, type RefEntry } from \"../core/extract/index.js\";\nimport type { CompiledSchemaInfo } from \"../core/pipeline.js\";\nimport { loadModule } from \"../loader.js\";\nimport {\n analyzeHoistedExpression,\n collectImportBindings,\n type HoistedSchema,\n type ImportDetail,\n} from \"./hoist.js\";\n\n/** A hoisted declaration whose initializer can be replaced with a compiled IIFE. */\nexport interface CompiledHoistedSchema {\n /** The `_zh_*` declaration name. */\n name: string;\n /** Original construction expression text (becomes the IIFE's schema expression). */\n text: string;\n /** Compiled validator for the schema. */\n info: CompiledSchemaInfo;\n}\n\n/** Duck-type check mirroring discovery's isZodSchema. */\nfunction isZodSchema(value: unknown): boolean {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"_zod\" in value &&\n typeof (value as { _zod: unknown })._zod === \"object\"\n );\n}\n\n/**\n * Evaluate a hoisted expression with its zod bindings and compile it.\n * Returns null when the schema is ineligible or anything fails.\n */\nasync function compileOne(\n schema: HoistedSchema,\n importDetails: Map<string, ImportDetail>,\n id: string,\n mode: CodegenMode,\n moduleCache: Map<string, Promise<Record<string, unknown>>>,\n): Promise<CompiledHoistedSchema | null> {\n const analysis = analyzeHoistedExpression(schema.text);\n if (analysis === null) return null;\n\n // Determinism gate: eager evaluation may only touch zod bindings.\n const free = new Set([...analysis.eagerFree, ...analysis.deferredFree]);\n const bindings: Array<{ name: string; detail: ImportDetail }> = [];\n for (const name of analysis.eagerFree) {\n const detail = importDetails.get(name);\n if (!detail || !isZodSpecifier(detail.specifier)) return null;\n }\n // Inject every free import binding we can resolve (eager ones are all zod\n // by the gate above; deferred ones are best-effort — extraction only\n // dereferences them for build-time-invoked callbacks like z.lazy getters).\n for (const name of free) {\n const detail = importDetails.get(name);\n if (detail && isZodSpecifier(detail.specifier)) {\n bindings.push({ name, detail });\n } else if (analysis.eagerFree.has(name)) {\n return null;\n }\n // deferred non-zod names stay unbound: the evaluated closure would throw\n // if invoked at build time, which the try/catch below converts to a skip.\n }\n\n try {\n const values = await Promise.all(\n bindings.map(async ({ detail }) => {\n let loading = moduleCache.get(detail.specifier);\n if (!loading) {\n loading = loadModule(detail.specifier, id);\n moduleCache.set(detail.specifier, loading);\n }\n const mod = await loading;\n return detail.imported === \"*\" ? mod : mod[detail.imported];\n }),\n );\n\n const evaluate = new Function(\n ...bindings.map((b) => b.name),\n `\"use strict\"; return (${schema.text});`,\n );\n const value: unknown = evaluate(...values);\n if (!isZodSchema(value)) return null;\n\n const refEntries: RefEntry[] = [];\n const ir = extractSchema(value, refEntries);\n // A root fallback compiles to a pure delegation wrapper — strictly worse\n // than leaving the plain hoisted construction in place.\n if (ir.type === \"fallback\") return null;\n\n const codegenResult = generateValidator(ir, schema.name, { mode });\n return {\n name: schema.name,\n text: schema.text,\n info: { exportName: schema.name, codegenResult, refEntries },\n };\n } catch {\n return null;\n }\n}\n\nfunction isZodSpecifier(specifier: string): boolean {\n return (\n specifier === \"zod\" ||\n specifier === \"zod/v4\" ||\n specifier === \"zod/mini\" ||\n specifier === \"zod/v4/mini\" ||\n specifier === \"zod/v4-mini\"\n );\n}\n\n/**\n * Compile every eligible hoisted schema. Failures are silent per schema —\n * the caller leaves ineligible declarations as plain hoists.\n */\nexport async function compileHoistedSchemas(\n schemas: readonly HoistedSchema[],\n code: string,\n id: string,\n mode: CodegenMode,\n): Promise<CompiledHoistedSchema[]> {\n const { details } = collectImportBindings(code);\n const moduleCache = new Map<string, Promise<Record<string, unknown>>>();\n const compiled: CompiledHoistedSchema[] = [];\n for (const schema of schemas) {\n const result = await compileOne(schema, details, id, mode, moduleCache);\n if (result !== null) compiled.push(result);\n }\n return compiled;\n}\n"],"mappings":";;;;;;AAuDA,SAAS,YAAY,OAAyB;CAC5C,OACE,OAAO,UAAU,YACjB,UAAU,QACV,UAAU,SACV,OAAQ,MAA4B,SAAS;AAEjD;;;;;AAMA,eAAe,WACb,QACA,eACA,IACA,MACA,aACuC;CACvC,MAAM,WAAW,yBAAyB,OAAO,IAAI;CACrD,IAAI,aAAa,MAAM,OAAO;CAG9B,MAAM,uBAAO,IAAI,IAAI,CAAC,GAAG,SAAS,WAAW,GAAG,SAAS,YAAY,CAAC;CACtE,MAAM,WAA0D,CAAC;CACjE,KAAK,MAAM,QAAQ,SAAS,WAAW;EACrC,MAAM,SAAS,cAAc,IAAI,IAAI;EACrC,IAAI,CAAC,UAAU,CAAC,eAAe,OAAO,SAAS,GAAG,OAAO;CAC3D;CAIA,KAAK,MAAM,QAAQ,MAAM;EACvB,MAAM,SAAS,cAAc,IAAI,IAAI;EACrC,IAAI,UAAU,eAAe,OAAO,SAAS,GAC3C,SAAS,KAAK;GAAE;GAAM;EAAO,CAAC;OACzB,IAAI,SAAS,UAAU,IAAI,IAAI,GACpC,OAAO;CAIX;CAEA,IAAI;EACF,MAAM,SAAS,MAAM,QAAQ,IAC3B,SAAS,IAAI,OAAO,EAAE,aAAa;GACjC,IAAI,UAAU,YAAY,IAAI,OAAO,SAAS;GAC9C,IAAI,CAAC,SAAS;IACZ,UAAU,WAAW,OAAO,WAAW,EAAE;IACzC,YAAY,IAAI,OAAO,WAAW,OAAO;GAC3C;GACA,MAAM,MAAM,MAAM;GAClB,OAAO,OAAO,aAAa,MAAM,MAAM,IAAI,OAAO;EACpD,CAAC,CACH;EAMA,MAAM,QAAiB,IAJF,SACnB,GAAG,SAAS,KAAK,MAAM,EAAE,IAAI,GAC7B,yBAAyB,OAAO,KAAK,GAET,CAAC,CAAC,GAAG,MAAM;EACzC,IAAI,CAAC,YAAY,KAAK,GAAG,OAAO;EAEhC,MAAM,aAAyB,CAAC;EAChC,MAAM,KAAK,cAAc,OAAO,UAAU;EAG1C,IAAI,GAAG,SAAS,YAAY,OAAO;EAEnC,MAAM,gBAAgB,kBAAkB,IAAI,OAAO,MAAM,EAAE,KAAK,CAAC;EACjE,OAAO;GACL,MAAM,OAAO;GACb,MAAM,OAAO;GACb,MAAM;IAAE,YAAY,OAAO;IAAM;IAAe;GAAW;EAC7D;CACF,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,eAAe,WAA4B;CAClD,OACE,cAAc,SACd,cAAc,YACd,cAAc,cACd,cAAc,iBACd,cAAc;AAElB;;;;;AAMA,eAAsB,sBACpB,SACA,MACA,IACA,MACkC;CAClC,MAAM,EAAE,YAAY,sBAAsB,IAAI;CAC9C,MAAM,8BAAc,IAAI,IAA8C;CACtE,MAAM,WAAoC,CAAC;CAC3C,KAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,SAAS,MAAM,WAAW,QAAQ,SAAS,IAAI,MAAM,WAAW;EACtE,IAAI,WAAW,MAAM,SAAS,KAAK,MAAM;CAC3C;CACA,OAAO;AACT"}
1
+ {"version":3,"file":"hoist-compile.js","names":[],"sources":["../../src/unplugin/hoist-compile.ts"],"sourcesContent":["/**\n * Build-time compilation of hoisted schemas.\n *\n * After hoistZodSchemasMeta() lifts `z.object({...})` constructions to\n * module-scope `_zh_*` declarations, this step evaluates each hoisted\n * expression with the project's real zod module, runs the regular\n * extract → codegen pipeline on the resulting schema object, and hands the\n * transform a compiled IIFE to splice in as the declaration initializer:\n *\n * const _zh_x = z.object({ id: z.number() });\n * ⇣\n * const _zh_x = /* @__PURE__ *\\/ (() => { ... return __zcMkv(...); })();\n *\n * Eligibility is STRICTER than hoist eligibility. Hoisting only moves an\n * expression; compiling it bakes build-time evaluation results into\n * generated checks, so the construction must be deterministic:\n *\n * - Every EAGER free identifier must be a zod-package binding. Anything\n * else (other imports: `getLimit()`, globals: `new Date()`,\n * `Math.random()`) could evaluate differently at build time vs module\n * load — those schemas stay plainly hoisted.\n * - DEFERRED references (inside refine/transform/default callbacks) are\n * unrestricted: callbacks reach generated code via fn.toString() or stay\n * on the runtime-constructed schema (`__rf` delegation), never via their\n * build-time closure values. If extraction itself needs a deferred value\n * it cannot have (z.lazy(() => ImportedChild)), evaluation throws and the\n * schema falls back to a plain hoist.\n *\n * Every failure path is graceful: the declaration keeps its original zod\n * expression and runtime behavior is unchanged.\n */\n\nimport type { CodegenMode } from \"../core/codegen/context.js\";\nimport { generateValidator } from \"../core/codegen/index.js\";\nimport { extractSchema, type RefEntry } from \"../core/extract/index.js\";\nimport type { CompiledSchemaInfo } from \"../core/pipeline.js\";\nimport { isZodSchema } from \"../is-zod-schema.js\";\nimport { loadModule } from \"../loader.js\";\nimport {\n analyzeHoistedExpression,\n collectImportBindings,\n type HoistedSchema,\n type ImportDetail,\n} from \"./hoist.js\";\n\n/** A hoisted declaration whose initializer can be replaced with a compiled IIFE. */\nexport interface CompiledHoistedSchema {\n /** The `_zh_*` declaration name. */\n name: string;\n /** Original construction expression text (becomes the IIFE's schema expression). */\n text: string;\n /** Compiled validator for the schema. */\n info: CompiledSchemaInfo;\n}\n\n/**\n * Evaluate a hoisted expression with its zod bindings and compile it.\n * Returns null when the schema is ineligible or anything fails.\n */\nasync function compileOne(\n schema: HoistedSchema,\n importDetails: Map<string, ImportDetail>,\n id: string,\n mode: CodegenMode,\n moduleCache: Map<string, Promise<Record<string, unknown>>>,\n): Promise<CompiledHoistedSchema | null> {\n const analysis = analyzeHoistedExpression(schema.text);\n if (analysis === null) return null;\n\n // Determinism gate: eager evaluation may only touch zod bindings.\n const free = new Set([...analysis.eagerFree, ...analysis.deferredFree]);\n const bindings: Array<{ name: string; detail: ImportDetail }> = [];\n for (const name of analysis.eagerFree) {\n const detail = importDetails.get(name);\n if (!detail || !isZodSpecifier(detail.specifier)) return null;\n }\n // Inject every free import binding we can resolve (eager ones are all zod\n // by the gate above; deferred ones are best-effort — extraction only\n // dereferences them for build-time-invoked callbacks like z.lazy getters).\n for (const name of free) {\n const detail = importDetails.get(name);\n if (detail && isZodSpecifier(detail.specifier)) {\n bindings.push({ name, detail });\n } else if (analysis.eagerFree.has(name)) {\n return null;\n }\n // deferred non-zod names stay unbound: the evaluated closure would throw\n // if invoked at build time, which the try/catch below converts to a skip.\n }\n\n try {\n const values = await Promise.all(\n bindings.map(async ({ detail }) => {\n let loading = moduleCache.get(detail.specifier);\n if (!loading) {\n loading = loadModule(detail.specifier, id);\n moduleCache.set(detail.specifier, loading);\n }\n const mod = await loading;\n return detail.imported === \"*\" ? mod : mod[detail.imported];\n }),\n );\n\n const evaluate = new Function(\n ...bindings.map((b) => b.name),\n `\"use strict\"; return (${schema.text});`,\n );\n const value: unknown = evaluate(...values);\n if (!isZodSchema(value)) return null;\n\n const refEntries: RefEntry[] = [];\n const ir = extractSchema(value, refEntries);\n // A root fallback compiles to a pure delegation wrapper — strictly worse\n // than leaving the plain hoisted construction in place.\n if (ir.type === \"fallback\") return null;\n\n const codegenResult = generateValidator(ir, schema.name, { mode });\n return {\n name: schema.name,\n text: schema.text,\n info: { exportName: schema.name, codegenResult, refEntries },\n };\n } catch {\n return null;\n }\n}\n\nfunction isZodSpecifier(specifier: string): boolean {\n return (\n specifier === \"zod\" ||\n specifier === \"zod/v4\" ||\n specifier === \"zod/mini\" ||\n specifier === \"zod/v4/mini\" ||\n specifier === \"zod/v4-mini\"\n );\n}\n\n/**\n * Compile every eligible hoisted schema. Failures are silent per schema —\n * the caller leaves ineligible declarations as plain hoists.\n */\nexport async function compileHoistedSchemas(\n schemas: readonly HoistedSchema[],\n code: string,\n id: string,\n mode: CodegenMode,\n): Promise<CompiledHoistedSchema[]> {\n const { details } = collectImportBindings(code);\n const moduleCache = new Map<string, Promise<Record<string, unknown>>>();\n const compiled: CompiledHoistedSchema[] = [];\n for (const schema of schemas) {\n const result = await compileOne(schema, details, id, mode, moduleCache);\n if (result !== null) compiled.push(result);\n }\n return compiled;\n}\n"],"mappings":";;;;;;;;;;AA2DA,eAAe,WACb,QACA,eACA,IACA,MACA,aACuC;CACvC,MAAM,WAAW,yBAAyB,OAAO,IAAI;CACrD,IAAI,aAAa,MAAM,OAAO;CAG9B,MAAM,uBAAO,IAAI,IAAI,CAAC,GAAG,SAAS,WAAW,GAAG,SAAS,YAAY,CAAC;CACtE,MAAM,WAA0D,CAAC;CACjE,KAAK,MAAM,QAAQ,SAAS,WAAW;EACrC,MAAM,SAAS,cAAc,IAAI,IAAI;EACrC,IAAI,CAAC,UAAU,CAAC,eAAe,OAAO,SAAS,GAAG,OAAO;CAC3D;CAIA,KAAK,MAAM,QAAQ,MAAM;EACvB,MAAM,SAAS,cAAc,IAAI,IAAI;EACrC,IAAI,UAAU,eAAe,OAAO,SAAS,GAC3C,SAAS,KAAK;GAAE;GAAM;EAAO,CAAC;OACzB,IAAI,SAAS,UAAU,IAAI,IAAI,GACpC,OAAO;CAIX;CAEA,IAAI;EACF,MAAM,SAAS,MAAM,QAAQ,IAC3B,SAAS,IAAI,OAAO,EAAE,aAAa;GACjC,IAAI,UAAU,YAAY,IAAI,OAAO,SAAS;GAC9C,IAAI,CAAC,SAAS;IACZ,UAAU,WAAW,OAAO,WAAW,EAAE;IACzC,YAAY,IAAI,OAAO,WAAW,OAAO;GAC3C;GACA,MAAM,MAAM,MAAM;GAClB,OAAO,OAAO,aAAa,MAAM,MAAM,IAAI,OAAO;EACpD,CAAC,CACH;EAMA,MAAM,QAAiB,IAJF,SACnB,GAAG,SAAS,KAAK,MAAM,EAAE,IAAI,GAC7B,yBAAyB,OAAO,KAAK,GAET,CAAC,CAAC,GAAG,MAAM;EACzC,IAAI,CAAC,YAAY,KAAK,GAAG,OAAO;EAEhC,MAAM,aAAyB,CAAC;EAChC,MAAM,KAAK,cAAc,OAAO,UAAU;EAG1C,IAAI,GAAG,SAAS,YAAY,OAAO;EAEnC,MAAM,gBAAgB,kBAAkB,IAAI,OAAO,MAAM,EAAE,KAAK,CAAC;EACjE,OAAO;GACL,MAAM,OAAO;GACb,MAAM,OAAO;GACb,MAAM;IAAE,YAAY,OAAO;IAAM;IAAe;GAAW;EAC7D;CACF,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,eAAe,WAA4B;CAClD,OACE,cAAc,SACd,cAAc,YACd,cAAc,cACd,cAAc,iBACd,cAAc;AAElB;;;;;AAMA,eAAsB,sBACpB,SACA,MACA,IACA,MACkC;CAClC,MAAM,EAAE,YAAY,sBAAsB,IAAI;CAC9C,MAAM,8BAAc,IAAI,IAA8C;CACtE,MAAM,WAAoC,CAAC;CAC3C,KAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,SAAS,MAAM,WAAW,QAAQ,SAAS,IAAI,MAAM,WAAW;EACtE,IAAI,WAAW,MAAM,SAAS,KAAK,MAAM;CAC3C;CACA,OAAO;AACT"}
@@ -1 +1 @@
1
- {"version":3,"file":"transform.d.ts","names":[],"sources":["../../src/unplugin/transform.ts"],"mappings":";;;;UA6BiB;EACf;EACA;EACA;EACA;EACA;EACA;;;;;;;;;;cAsHW,wBAAsB;;;;iBAgDnB,gBAAgB,YAAY,UAAU;;;;;;;;;;;;;cA6BzC;EAAuB,SAAS;EAAU,SAAS;;;;;;;;iBAgChD,oBAAoB,UAAU,2BAA2B;iBAOzD,IAAI;UAUH;EACf;EACA,KAAK;;;;;;;iBAQe,cACpB,cACA,YACA,SAAS,mBACR;;;;;;iBAUmB,qBACpB,cACA,YACA,SAAS,mBACR,QAAQ;;;;iBAqXK,cACd,cACA,SAAS,sBACT;EAAY;;;;;;iBAiDE,kBAAkB,cAAc;;;;;iBAgBhC,0BACd,cACA,SAAS,sBACT;EAAY;;;;;;iBAuCE,oBAAoB"}
1
+ {"version":3,"file":"transform.d.ts","names":[],"sources":["../../src/unplugin/transform.ts"],"mappings":";;;;UA8BiB;EACf;EACA;EACA;EACA;EACA;EACA;;;;;;;;;;cAsHW,wBAAsB;;;;iBAgDnB,gBAAgB,YAAY,UAAU;;;;;;;;;;;;;cA6BzC;EAAuB,SAAS;EAAU,SAAS;;;;;;;;iBAgChD,oBAAoB,UAAU,2BAA2B;iBAOzD,IAAI;UAUH;EACf;EACA,KAAK;;;;;;;iBAQe,cACpB,cACA,YACA,SAAS,mBACR;;;;;;iBAUmB,qBACpB,cACA,YACA,SAAS,mBACR,QAAQ;;;;iBAqXK,cACd,cACA,SAAS,sBACT;EAAY;;;;;;iBAiDE,kBAAkB,cAAc;;;;;iBAgBhC,0BACd,cACA,SAAS,sBACT;EAAY;;;;;;iBAgGE,oBAAoB"}
@@ -1,6 +1,6 @@
1
1
  import { ProcessExitDuringLoadError } from "../loader.js";
2
2
  import { discoverSchemas } from "../discovery.js";
3
- import { FAILZ_CLASS_DECL, FAIL_CLASS_DECL, FINZ_DECL, FIN_DECL, FIN_DEFERRED_DECL, MK_VALIDATOR_DECL, ZOD_CONFIG_IMPORT, ZOD_MSG_DECLARATION, generateIIFE } from "../core/iife.js";
3
+ import { FAILZ_CLASS_DECL, FAIL_CLASS_DECL, FINZ_DECL, FIN_DECL, FIN_DEFERRED_DECL, MK_VALIDATOR_DECL, ZOD_CONFIG_IMPORT, ZOD_MSG_DECLARATION, generateIIFE, iifeDerefsSchema } from "../core/iife.js";
4
4
  import "../core/codegen/dedupe.js";
5
5
  import { aggregateUsedHelpers, compileSchemas } from "../core/pipeline.js";
6
6
  import { mayExportSchemas } from "../static-filter.js";
@@ -461,6 +461,19 @@ function findExpressionEnd(code, start) {
461
461
  function rewriteSourceAutoDiscover(code, schemas, options) {
462
462
  return applyEdits(code, collectAutoDiscoverEdits(code, schemas, options));
463
463
  }
464
+ /**
465
+ * Does `expr` mention `name` as an identifier?
466
+ *
467
+ * Deliberately lexical, and deliberately biased toward YES. A false positive
468
+ * costs one export its `@__PURE__` annotation; a false negative emits an IIFE
469
+ * that dereferences a binding still under initialization. The expression text
470
+ * is often TypeScript (`z.custom<T>(...)`), which no JS parser here can be
471
+ * trusted to walk, so a word-boundary scan — which cannot miss a real
472
+ * identifier reference — is the sound direction to be wrong in.
473
+ */
474
+ function mentionsIdentifier(expr, name) {
475
+ return new RegExp(`\\b${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`).test(expr);
476
+ }
464
477
  /** Edits for rewriteSourceAutoDiscover, collected against pristine `code`. */
465
478
  function collectAutoDiscoverEdits(code, schemas, options) {
466
479
  const edits = [];
@@ -472,6 +485,21 @@ function collectAutoDiscoverEdits(code, schemas, options) {
472
485
  const rhsEnd = findExpressionEnd(code, rhsStart);
473
486
  if (rhsEnd === -1) continue;
474
487
  const originalExpr = code.slice(rhsStart, rhsEnd).trim();
488
+ if (iifeDerefsSchema(schema) && mentionsIdentifier(originalExpr, schema.exportName)) {
489
+ if (options?.zodCompat === false) {
490
+ warn(`Skipping self-referential export "${schema.exportName}": output "bag" cannot preserve its recursive reference. Keeping the original schema.`);
491
+ continue;
492
+ }
493
+ edits.push({
494
+ start: rhsStart,
495
+ end: rhsEnd,
496
+ text: `${originalExpr};\n${generateIIFE(schema.exportName, schema, {
497
+ ...options,
498
+ pure: false
499
+ })};`
500
+ });
501
+ continue;
502
+ }
475
503
  edits.push({
476
504
  start: rhsStart,
477
505
  end: rhsEnd,