zod-compiler 1.26.3 → 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.
@@ -16,6 +16,11 @@ declare function compile<T extends ZodType>(zodSchema: T): T & CompiledSchema<ou
16
16
  /**
17
17
  * Check if a value is a CompiledSchema created by compile().
18
18
  * Used by the CLI to discover schemas in source files.
19
+ *
20
+ * Like {@link isZodSchema}, this probes values the caller does not control —
21
+ * every export of every candidate file — and `COMPILED_MARKER in value` fires a
22
+ * Proxy's `has` trap. A trap that throws (an ORM model, a strict test double)
23
+ * must not fail the build over an export that was never a candidate.
19
24
  */
20
25
  declare function isCompiledSchema(value: unknown): value is CompiledSchema<unknown>;
21
26
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"compile.d.ts","names":[],"sources":["../../src/core/compile.ts"],"mappings":";;;;;;;;;;;;;;iBA+CgB,QAAQ,UAAU,SAAS,WAAW,IAAI,IAAI,eAAe,OAAO;;;;;iBAYpE,iBAAiB,iBAAiB,SAAS"}
1
+ {"version":3,"file":"compile.d.ts","names":[],"sources":["../../src/core/compile.ts"],"mappings":";;;;;;;;;;;;;;iBA+CgB,QAAQ,UAAU,SAAS,WAAW,IAAI,IAAI,eAAe,OAAO;;;;;;;;;;iBAiBpE,iBAAiB,iBAAiB,SAAS"}
@@ -47,9 +47,19 @@ function compile(zodSchema) {
47
47
  /**
48
48
  * Check if a value is a CompiledSchema created by compile().
49
49
  * Used by the CLI to discover schemas in source files.
50
+ *
51
+ * Like {@link isZodSchema}, this probes values the caller does not control —
52
+ * every export of every candidate file — and `COMPILED_MARKER in value` fires a
53
+ * Proxy's `has` trap. A trap that throws (an ORM model, a strict test double)
54
+ * must not fail the build over an export that was never a candidate.
50
55
  */
51
56
  function isCompiledSchema(value) {
52
- return typeof value === "object" && value !== null && COMPILED_MARKER in value;
57
+ if (typeof value !== "object" || value === null) return false;
58
+ try {
59
+ return COMPILED_MARKER in value;
60
+ } catch {
61
+ return false;
62
+ }
53
63
  }
54
64
  //#endregion
55
65
  export { compile, isCompiledSchema };
@@ -1 +1 @@
1
- {"version":3,"file":"compile.js","names":[],"sources":["../../src/core/compile.ts"],"sourcesContent":["import type { output, ZodType } from \"zod\";\nimport type { CompiledSchema } from \"./types.js\";\n\nconst COMPILED_MARKER = Symbol.for(\"zod-compiler:compiled\");\n\n/**\n * Dev-time fallback: returns the ORIGINAL schema object, augmented with a\n * non-enumerable `schema` self-reference (CLI emitter discovery reads\n * `(__src_X as any).schema`) and the compiled marker.\n *\n * Identity preservation is load-bearing: zod v4 keys toJSONSchema's\n * processing context and globalRegistry/.meta() on schema object identity —\n * an Object.create facade crashes toJSONSchema the moment the compiled\n * schema is composed into another schema, and silently loses .meta()\n * metadata. parse/safeParse/... remain the schema's own zod methods.\n */\nfunction createFallback<T>(zodSchema: unknown): CompiledSchema<T> {\n const facade = zodSchema as CompiledSchema<T>;\n if (!(\"schema\" in (facade as object))) {\n Object.defineProperty(facade, \"schema\", { value: zodSchema, enumerable: false });\n }\n // Dev-time `.is`: delegates to zod's own safeParse so the boolean guard works\n // before `zod-compiler generate` runs. The build replaces this with the\n // compiled fast-check (zero-allocation) via __zcMkv. Non-enumerable to keep\n // the augmentation invisible on the identity-preserved schema object.\n if (!(\"is\" in (facade as object))) {\n Object.defineProperty(facade, \"is\", {\n value: (input: unknown): boolean => facade.safeParse(input).success,\n enumerable: false,\n configurable: true,\n writable: true,\n });\n }\n return facade;\n}\n\n/**\n * Compile a Zod schema into an optimized validator.\n *\n * At dev-time, falls back to Zod's runtime validation: the schema itself is\n * returned (identity-preserving for toJSONSchema/.meta()), tagged with the\n * compiled marker so build-time discovery can find it.\n * After `npx zod-compiler generate`, import from the `.compiled.ts` file instead.\n *\n * The return type is `T & CompiledSchema<output<T>>`, preserving the original\n * Zod schema type for compatibility with libraries like `@hono/zod-validator`.\n */\nexport function compile<T extends ZodType>(zodSchema: T): T & CompiledSchema<output<T>> {\n const result = createFallback<output<T>>(zodSchema);\n if (!(COMPILED_MARKER in (result as object))) {\n Object.defineProperty(result, COMPILED_MARKER, { value: true, enumerable: false });\n }\n return result as T & CompiledSchema<output<T>>;\n}\n\n/**\n * Check if a value is a CompiledSchema created by compile().\n * Used by the CLI to discover schemas in source files.\n */\nexport function isCompiledSchema(value: unknown): value is CompiledSchema<unknown> {\n return typeof value === \"object\" && value !== null && COMPILED_MARKER in value;\n}\n"],"mappings":";AAGA,MAAM,kBAAkB,OAAO,IAAI,uBAAuB;;;;;;;;;;;;AAa1D,SAAS,eAAkB,WAAuC;CAChE,MAAM,SAAS;CACf,IAAI,EAAE,YAAa,SACjB,OAAO,eAAe,QAAQ,UAAU;EAAE,OAAO;EAAW,YAAY;CAAM,CAAC;CAMjF,IAAI,EAAE,QAAS,SACb,OAAO,eAAe,QAAQ,MAAM;EAClC,QAAQ,UAA4B,OAAO,UAAU,KAAK,CAAC,CAAC;EAC5D,YAAY;EACZ,cAAc;EACd,UAAU;CACZ,CAAC;CAEH,OAAO;AACT;;;;;;;;;;;;AAaA,SAAgB,QAA2B,WAA6C;CACtF,MAAM,SAAS,eAA0B,SAAS;CAClD,IAAI,EAAE,mBAAoB,SACxB,OAAO,eAAe,QAAQ,iBAAiB;EAAE,OAAO;EAAM,YAAY;CAAM,CAAC;CAEnF,OAAO;AACT;;;;;AAMA,SAAgB,iBAAiB,OAAkD;CACjF,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,mBAAmB;AAC3E"}
1
+ {"version":3,"file":"compile.js","names":[],"sources":["../../src/core/compile.ts"],"sourcesContent":["import type { output, ZodType } from \"zod\";\nimport type { CompiledSchema } from \"./types.js\";\n\nconst COMPILED_MARKER = Symbol.for(\"zod-compiler:compiled\");\n\n/**\n * Dev-time fallback: returns the ORIGINAL schema object, augmented with a\n * non-enumerable `schema` self-reference (CLI emitter discovery reads\n * `(__src_X as any).schema`) and the compiled marker.\n *\n * Identity preservation is load-bearing: zod v4 keys toJSONSchema's\n * processing context and globalRegistry/.meta() on schema object identity —\n * an Object.create facade crashes toJSONSchema the moment the compiled\n * schema is composed into another schema, and silently loses .meta()\n * metadata. parse/safeParse/... remain the schema's own zod methods.\n */\nfunction createFallback<T>(zodSchema: unknown): CompiledSchema<T> {\n const facade = zodSchema as CompiledSchema<T>;\n if (!(\"schema\" in (facade as object))) {\n Object.defineProperty(facade, \"schema\", { value: zodSchema, enumerable: false });\n }\n // Dev-time `.is`: delegates to zod's own safeParse so the boolean guard works\n // before `zod-compiler generate` runs. The build replaces this with the\n // compiled fast-check (zero-allocation) via __zcMkv. Non-enumerable to keep\n // the augmentation invisible on the identity-preserved schema object.\n if (!(\"is\" in (facade as object))) {\n Object.defineProperty(facade, \"is\", {\n value: (input: unknown): boolean => facade.safeParse(input).success,\n enumerable: false,\n configurable: true,\n writable: true,\n });\n }\n return facade;\n}\n\n/**\n * Compile a Zod schema into an optimized validator.\n *\n * At dev-time, falls back to Zod's runtime validation: the schema itself is\n * returned (identity-preserving for toJSONSchema/.meta()), tagged with the\n * compiled marker so build-time discovery can find it.\n * After `npx zod-compiler generate`, import from the `.compiled.ts` file instead.\n *\n * The return type is `T & CompiledSchema<output<T>>`, preserving the original\n * Zod schema type for compatibility with libraries like `@hono/zod-validator`.\n */\nexport function compile<T extends ZodType>(zodSchema: T): T & CompiledSchema<output<T>> {\n const result = createFallback<output<T>>(zodSchema);\n if (!(COMPILED_MARKER in (result as object))) {\n Object.defineProperty(result, COMPILED_MARKER, { value: true, enumerable: false });\n }\n return result as T & CompiledSchema<output<T>>;\n}\n\n/**\n * Check if a value is a CompiledSchema created by compile().\n * Used by the CLI to discover schemas in source files.\n *\n * Like {@link isZodSchema}, this probes values the caller does not control —\n * every export of every candidate file — and `COMPILED_MARKER in value` fires a\n * Proxy's `has` trap. A trap that throws (an ORM model, a strict test double)\n * must not fail the build over an export that was never a candidate.\n */\nexport function isCompiledSchema(value: unknown): value is CompiledSchema<unknown> {\n if (typeof value !== \"object\" || value === null) return false;\n try {\n return COMPILED_MARKER in value;\n } catch {\n return false;\n }\n}\n"],"mappings":";AAGA,MAAM,kBAAkB,OAAO,IAAI,uBAAuB;;;;;;;;;;;;AAa1D,SAAS,eAAkB,WAAuC;CAChE,MAAM,SAAS;CACf,IAAI,EAAE,YAAa,SACjB,OAAO,eAAe,QAAQ,UAAU;EAAE,OAAO;EAAW,YAAY;CAAM,CAAC;CAMjF,IAAI,EAAE,QAAS,SACb,OAAO,eAAe,QAAQ,MAAM;EAClC,QAAQ,UAA4B,OAAO,UAAU,KAAK,CAAC,CAAC;EAC5D,YAAY;EACZ,cAAc;EACd,UAAU;CACZ,CAAC;CAEH,OAAO;AACT;;;;;;;;;;;;AAaA,SAAgB,QAA2B,WAA6C;CACtF,MAAM,SAAS,eAA0B,SAAS;CAClD,IAAI,EAAE,mBAAoB,SACxB,OAAO,eAAe,QAAQ,iBAAiB;EAAE,OAAO;EAAM,YAAY;CAAM,CAAC;CAEnF,OAAO;AACT;;;;;;;;;;AAWA,SAAgB,iBAAiB,OAAkD;CACjF,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM,OAAO;CACxD,IAAI;EACF,OAAO,mBAAmB;CAC5B,QAAQ;EACN,OAAO;CACT;AACF"}
@@ -14,6 +14,22 @@
14
14
  * a promise where zod's synchronous parse raises $ZodAsyncError.
15
15
  */
16
16
  declare function isReferenceablePredicate(fn: unknown): boolean;
17
+ /**
18
+ * Does this callback DEMONSTRABLY observe zod's second (parse-context) argument?
19
+ *
20
+ * Only a positive answer is trustworthy. Zod calls `transform(value, payload)`
21
+ * while every compiled route passes the value alone, so a callback that reads
22
+ * the second argument must be left to zod — but a callback whose signature
23
+ * cannot be read (a native like `Number`, a bound function) is NOT thereby
24
+ * suspect, and refusing those costs a very common idiom its compiled path for
25
+ * no correctness gain: `.transform(Number)` delegating measured SLOWER than not
26
+ * compiling at all. So this reports only what the parsed parameter list proves.
27
+ *
28
+ * `fn.length` cannot answer this at all: it stops counting at the first default
29
+ * and ignores a rest element, so `(...args) => args.length` reports 0 and
30
+ * `(v, ctx = null) => …` reports 1.
31
+ */
32
+ declare function observesSecondArgument(fn: unknown): boolean;
17
33
  /**
18
34
  * Can a callback be invoked with the value alone without hiding Zod's second
19
35
  * parse-context argument? `fn.length` is insufficient here: default and rest
@@ -50,5 +66,5 @@ declare function isPayloadCheck(check: {
50
66
  */
51
67
  declare function tryCompileEffect(fn: unknown): string | undefined;
52
68
  //#endregion
53
- export { isContextFreeUnaryCallback, isPayloadCheck, isReferenceablePredicate, tryCompileEffect };
69
+ export { isContextFreeUnaryCallback, isPayloadCheck, isReferenceablePredicate, observesSecondArgument, tryCompileEffect };
54
70
  //# sourceMappingURL=effects.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"effects.d.ts","names":[],"sources":["../../../src/core/extract/effects.ts"],"mappings":";;;;;;;;;;;;;;;iBAwDgB,yBAAyB;;;;;;iBAczB,2BAA2B;;;;;;;;;;;;;;iBAgC3B,eAAe;EAC7B;IAAS;IAAiB;MAAQ;MAAc;;IAAkB,SAAS;;;;;;;;iBAe7D,iBAAiB"}
1
+ {"version":3,"file":"effects.d.ts","names":[],"sources":["../../../src/core/extract/effects.ts"],"mappings":";;;;;;;;;;;;;;;iBAwDgB,yBAAyB;;;;;;;;;;;;;;;;iBA8IzB,uBAAuB;;;;;;iBAWvB,2BAA2B;;;;;;;;;;;;;;iBA8B3B,eAAe;EAC7B;IAAS;IAAiB;MAAQ;MAAc;;IAAkB,SAAS;;;;;;;;iBAe7D,iBAAiB"}
@@ -52,27 +52,148 @@ function isReferenceablePredicate(fn) {
52
52
  return kind !== "AsyncFunction" && kind !== "GeneratorFunction" && kind !== "AsyncGeneratorFunction";
53
53
  }
54
54
  /**
55
+ * Not every callable stringifies to an expression, and acorn parses what it can
56
+ * rather than refusing. A method shorthand lifted off an object literal —
57
+ * `normalize(v) { … }` — parses as the CALL `normalize(v)` and stops at the
58
+ * brace; a getter's source yields a bare Identifier; a class yields a
59
+ * ClassExpression. None of those carry `params`, so every arity guard below
60
+ * passes vacuously and `tryCompileEffect` hands back the whole source including
61
+ * the trailing block, which lands in generated code as
62
+ * `var __ef_2=(normalize(v) { … });` — a SyntaxError that makes the emitted
63
+ * module unparseable while the CLI still reports success.
64
+ *
65
+ * Only an arrow or a function expression is safe to treat as an inlineable
66
+ * callback. Rejecting is the conservative answer rather than merely the safe
67
+ * one: with no parsed params there is nothing to prove the callback ignores
68
+ * zod's second (parse-context) argument, so a refine/transform degrades to a
69
+ * call through `__rf[N]` and a preprocess degrades further, to full zod
70
+ * delegation. Every shape above keeps zod's own behaviour either way — a class
71
+ * still throws on a call-without-new exactly as it does under zod.
72
+ *
73
+ * The parse must also have consumed the whole source. Nothing here can prove
74
+ * `toString()` returned real source text, and a partial parse would re-open
75
+ * this same bug class by re-emitting the unconsumed tail.
76
+ */
77
+ function asFunctionNode(ast, source) {
78
+ if (!(ast.type === "ArrowFunctionExpression" || ast.type === "FunctionExpression")) return null;
79
+ return source.slice(ast.end).trim() === "" ? ast : null;
80
+ }
81
+ /**
82
+ * Recover a callback's parameter list from its source, or null when the source
83
+ * does not reveal one. An arrow or function expression parses directly; a method
84
+ * shorthand lifted off an object literal (`normalize(v) { … }`) is not an
85
+ * expression on its own but becomes one wrapped back in the braces it came from.
86
+ * A native or bound function reveals nothing — `[native code]` has no parameter
87
+ * list to read, and skipping it before either parse keeps two thrown acorn
88
+ * exceptions off the hot path for `.transform(Number)` and friends.
89
+ */
90
+ function recoverSignature(source) {
91
+ if (source.includes("[native code]")) return null;
92
+ try {
93
+ const fnNode = asFunctionNode(parseExpressionAt(source, 0, {
94
+ ecmaVersion: "latest",
95
+ sourceType: "module"
96
+ }), source);
97
+ if (fnNode !== null) return fnNode;
98
+ } catch {}
99
+ try {
100
+ const wrapped = `({${source}})`;
101
+ const ast = parseExpressionAt(wrapped, 0, {
102
+ ecmaVersion: "latest",
103
+ sourceType: "module"
104
+ });
105
+ if (wrapped.slice(ast.end).trim() !== ")") return null;
106
+ const properties = ast.properties;
107
+ if (properties?.length !== 1) return null;
108
+ const value = properties[0]?.value;
109
+ return value !== void 0 && value.type === "FunctionExpression" ? value : null;
110
+ } catch {
111
+ return null;
112
+ }
113
+ }
114
+ /**
115
+ * Does a body reference ITS OWN `arguments`?
116
+ *
117
+ * A nested non-arrow function gets a fresh `arguments` binding, so its use of
118
+ * the name says nothing about the callback being classified — descending into
119
+ * one would fail a perfectly safe callback and cost it its compiled path. An
120
+ * arrow has no `arguments` of its own and so is walked.
121
+ */
122
+ function referencesOwnArguments(node) {
123
+ if (node.type === "Identifier") return node.name === "arguments";
124
+ if (node.type === "FunctionExpression" || node.type === "FunctionDeclaration") return false;
125
+ if (node.type === "MemberExpression") {
126
+ const member = node;
127
+ if (referencesOwnArguments(member.object)) return true;
128
+ return member.computed && referencesOwnArguments(member.property);
129
+ }
130
+ if (node.type === "Property") {
131
+ const property = node;
132
+ if (property.computed && referencesOwnArguments(property.key)) return true;
133
+ return referencesOwnArguments(property.value);
134
+ }
135
+ for (const key of Object.keys(node)) {
136
+ if (key === "type" || key === "start" || key === "end") continue;
137
+ const child = node[key];
138
+ if (!child || typeof child !== "object") continue;
139
+ const children = Array.isArray(child) ? child : [child];
140
+ for (const item of children) if (item && typeof item === "object" && "type" in item) {
141
+ if (referencesOwnArguments(item)) return true;
142
+ }
143
+ }
144
+ return false;
145
+ }
146
+ /**
147
+ * The three ways a parsed signature can betray zod's second argument. Shared so
148
+ * that {@link observesSecondArgument} and {@link isContextFreeUnaryCallback},
149
+ * which are exact inverses of it, cannot drift apart.
150
+ */
151
+ function signatureObservesSecondArgument(fnNode) {
152
+ const params = fnNode.params ?? [];
153
+ if (params.length > 1) return true;
154
+ if (params.some((param) => param.type === "RestElement")) return true;
155
+ return fnNode.body !== void 0 && referencesOwnArguments(fnNode.body);
156
+ }
157
+ /**
158
+ * Does this callback DEMONSTRABLY observe zod's second (parse-context) argument?
159
+ *
160
+ * Only a positive answer is trustworthy. Zod calls `transform(value, payload)`
161
+ * while every compiled route passes the value alone, so a callback that reads
162
+ * the second argument must be left to zod — but a callback whose signature
163
+ * cannot be read (a native like `Number`, a bound function) is NOT thereby
164
+ * suspect, and refusing those costs a very common idiom its compiled path for
165
+ * no correctness gain: `.transform(Number)` delegating measured SLOWER than not
166
+ * compiling at all. So this reports only what the parsed parameter list proves.
167
+ *
168
+ * `fn.length` cannot answer this at all: it stops counting at the first default
169
+ * and ignores a rest element, so `(...args) => args.length` reports 0 and
170
+ * `(v, ctx = null) => …` reports 1.
171
+ */
172
+ function observesSecondArgument(fn) {
173
+ if (typeof fn !== "function") return false;
174
+ const fnNode = recoverSignature(fn.toString());
175
+ return fnNode !== null && signatureObservesSecondArgument(fnNode);
176
+ }
177
+ /**
55
178
  * Can a callback be invoked with the value alone without hiding Zod's second
56
179
  * parse-context argument? `fn.length` is insufficient here: default and rest
57
180
  * parameters can observe that argument while still reporting length 0 or 1.
58
181
  */
59
182
  function isContextFreeUnaryCallback(fn) {
60
183
  if (!isReferenceablePredicate(fn)) return false;
184
+ const source = fn.toString();
61
185
  let ast;
62
186
  try {
63
- ast = parseExpressionAt(fn.toString(), 0, {
187
+ ast = parseExpressionAt(source, 0, {
64
188
  ecmaVersion: "latest",
65
189
  sourceType: "module"
66
190
  });
67
191
  } catch {
68
192
  return false;
69
193
  }
70
- const fnNode = ast;
71
- if ((fnNode.params?.length ?? 0) > 1) return false;
72
- if (fnNode.params?.some((param) => param.type === "RestElement")) return false;
73
- const refs = /* @__PURE__ */ new Set();
74
- if (fnNode.body) collectIdentifierRefs(fnNode.body, refs);
75
- return !refs.has("arguments");
194
+ const fnNode = asFunctionNode(ast, source);
195
+ if (fnNode === null) return false;
196
+ return !signatureObservesSecondArgument(fnNode);
76
197
  }
77
198
  /**
78
199
  * A `superRefine`: a `custom` check whose callback takes zod's PAYLOAD rather
@@ -108,10 +229,11 @@ function tryCompileEffect(fn) {
108
229
  } catch {
109
230
  return;
110
231
  }
232
+ const fnNode = asFunctionNode(ast, source);
233
+ if (fnNode === null) return void 0;
111
234
  if ("async" in ast && ast.async) return void 0;
112
235
  if ("generator" in ast && ast.generator) return void 0;
113
236
  const params = /* @__PURE__ */ new Set();
114
- const fnNode = ast;
115
237
  if (fn.length >= 2) return void 0;
116
238
  if (fnNode.params) for (const param of fnNode.params) collectBindingNames(param, params);
117
239
  const locals = /* @__PURE__ */ new Set();
@@ -189,6 +311,6 @@ function collectIdentifierRefs(node, refs) {
189
311
  }
190
312
  }
191
313
  //#endregion
192
- export { isContextFreeUnaryCallback, isPayloadCheck, isReferenceablePredicate, tryCompileEffect };
314
+ export { isContextFreeUnaryCallback, isPayloadCheck, isReferenceablePredicate, observesSecondArgument, tryCompileEffect };
193
315
 
194
316
  //# sourceMappingURL=effects.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"effects.js","names":[],"sources":["../../../src/core/extract/effects.ts"],"sourcesContent":["/**\n * Effect compilation utilities.\n * Uses fn.toString() to extract function source text from live Zod schema references,\n * then classifies whether the function can be safely inlined (zero external captures).\n */\n\nimport type { Node } from \"acorn\";\nimport { parseExpressionAt } from \"acorn\";\n\n// Well-known globals that are safe to reference in inlined functions\nconst SAFE_GLOBALS = new Set([\n \"undefined\",\n \"null\",\n \"NaN\",\n \"Infinity\",\n \"Math\",\n \"Number\",\n \"String\",\n \"Boolean\",\n \"Array\",\n \"Object\",\n \"JSON\",\n \"Date\",\n \"RegExp\",\n \"parseInt\",\n \"parseFloat\",\n \"isNaN\",\n \"isFinite\",\n \"encodeURI\",\n \"decodeURI\",\n \"encodeURIComponent\",\n \"decodeURIComponent\",\n \"BigInt\",\n \"Symbol\",\n \"Map\",\n \"Set\",\n \"Error\",\n \"TypeError\",\n \"RangeError\",\n \"SyntaxError\",\n \"ReferenceError\",\n \"Promise\",\n \"globalThis\",\n \"true\",\n \"false\",\n]);\n\n/**\n * Can this refine predicate be CALLED by reference from generated code?\n *\n * Weaker than {@link tryCompileEffect}: the callback keeps its own closure, so\n * captures are fine — only shapes whose semantics the generated call could not\n * reproduce are rejected. A second parameter means the zod `ctx` protocol\n * (superRefine-style issue collection), and an async/generator function returns\n * a promise where zod's synchronous parse raises $ZodAsyncError.\n */\nexport function isReferenceablePredicate(fn: unknown): boolean {\n if (typeof fn !== \"function\") return false;\n if (fn.length >= 2) return false;\n const kind = fn.constructor?.name;\n return (\n kind !== \"AsyncFunction\" && kind !== \"GeneratorFunction\" && kind !== \"AsyncGeneratorFunction\"\n );\n}\n\n/**\n * Can a callback be invoked with the value alone without hiding Zod's second\n * parse-context argument? `fn.length` is insufficient here: default and rest\n * parameters can observe that argument while still reporting length 0 or 1.\n */\nexport function isContextFreeUnaryCallback(fn: unknown): boolean {\n if (!isReferenceablePredicate(fn)) return false;\n let ast: Node;\n try {\n ast = parseExpressionAt((fn as Function).toString(), 0, {\n ecmaVersion: \"latest\",\n sourceType: \"module\",\n });\n } catch {\n return false;\n }\n const fnNode = ast as { params?: Node[]; body?: Node };\n if ((fnNode.params?.length ?? 0) > 1) return false;\n if (fnNode.params?.some((param) => param.type === \"RestElement\")) return false;\n const refs = new Set<string>();\n if (fnNode.body) collectIdentifierRefs(fnNode.body, refs);\n return !refs.has(\"arguments\");\n}\n\n/**\n * A `superRefine`: a `custom` check whose callback takes zod's PAYLOAD rather\n * than the value, collecting issues instead of returning a verdict. Zod stores\n * it on the check instance (`_zod.check`) rather than `def.fn`.\n *\n * Narrowed to zod's OWN wrapper, which is what `_superRefine` builds and the\n * only thing whose payload handling is modelled here: it installs `addIssue`,\n * so every issue the user adds has been through `util.issue`. The trait marks a\n * genuine `$ZodCheck` instance and so excludes a raw `.check(fn)` — shape-\n * identical, but there the callback IS the user's, holding the payload\n * unmediated. A `when` predicate is likewise refused: it makes zod run the\n * check conditionally, where generated code runs it always.\n */\nexport function isPayloadCheck(check: {\n _zod?: { check?: unknown; def?: { fn?: unknown; when?: unknown }; traits?: Set<string> };\n}): boolean {\n return (\n check._zod?.def?.fn === undefined &&\n check._zod?.def?.when === undefined &&\n typeof check._zod?.check === \"function\" &&\n check._zod?.traits?.has(\"$ZodCheck\") === true\n );\n}\n\n/**\n * Try to compile a function into an inlineable source string.\n * Returns the function source if it's a zero-capture function (safe to inline),\n * or undefined if it cannot be compiled (async, has captures, or parse failure).\n */\nexport function tryCompileEffect(fn: unknown): string | undefined {\n if (typeof fn !== \"function\") return undefined;\n\n const source = fn.toString();\n\n // Quick reject: native functions\n if (source.includes(\"[native code]\")) return undefined;\n\n let ast: Node;\n try {\n ast = parseExpressionAt(source, 0, {\n ecmaVersion: \"latest\",\n sourceType: \"module\",\n });\n } catch {\n // Parse failed — likely TypeScript annotations or unsupported syntax\n return undefined;\n }\n\n // Reject async and generator functions\n if (\"async\" in ast && (ast as { async?: boolean }).async) return undefined;\n if (\"generator\" in ast && (ast as { generator?: boolean }).generator) return undefined;\n\n // Collect parameter names\n const params = new Set<string>();\n const fnNode = ast as {\n type: string;\n params?: Node[];\n body?: Node;\n };\n\n // Reject functions with 2+ required parameters (uses Zod ctx argument).\n // transform(value, ctx) and superRefine(value, ctx) rely on ctx for\n // issue collection, which cannot be reproduced in compiled output.\n // fn.length counts parameters before the first default, so (v, base=10)\n // has length 1 (allowed) while (v, ctx) has length 2 (rejected).\n if ((fn as { length: number }).length >= 2) return undefined;\n\n if (fnNode.params) {\n for (const param of fnNode.params) {\n collectBindingNames(param, params);\n }\n }\n\n // Collect local variable declarations in the body\n const locals = new Set<string>();\n if (fnNode.body && fnNode.body.type === \"BlockStatement\") {\n collectLocals(fnNode.body, locals);\n }\n\n // Collect all identifier references in the body\n const refs = new Set<string>();\n if (fnNode.body) {\n collectIdentifierRefs(fnNode.body, refs);\n }\n\n // Check for external captures\n for (const ref of refs) {\n if (!params.has(ref) && !locals.has(ref) && !SAFE_GLOBALS.has(ref)) {\n return undefined; // Has external capture\n }\n }\n\n return source;\n}\n\n/** Collect binding names from a parameter pattern (handles destructuring). */\nfunction collectBindingNames(node: Node, names: Set<string>): void {\n switch (node.type) {\n case \"Identifier\":\n names.add((node as Node & { name: string }).name);\n break;\n case \"AssignmentPattern\":\n collectBindingNames((node as Node & { left: Node }).left, names);\n break;\n case \"ObjectPattern\":\n for (const prop of (node as Node & { properties: Node[] }).properties) {\n if (prop.type === \"RestElement\") {\n collectBindingNames((prop as Node & { argument: Node }).argument, names);\n } else {\n collectBindingNames((prop as Node & { value: Node }).value, names);\n }\n }\n break;\n case \"ArrayPattern\":\n for (const elem of (node as Node & { elements: (Node | null)[] }).elements) {\n if (elem) collectBindingNames(elem, names);\n }\n break;\n case \"RestElement\":\n collectBindingNames((node as Node & { argument: Node }).argument, names);\n break;\n }\n}\n\n/** Collect locally declared variable names from a block body. */\nfunction collectLocals(body: Node, locals: Set<string>): void {\n const block = body as Node & { body: Node[] };\n if (!block.body) return;\n for (const stmt of block.body) {\n if (stmt.type === \"VariableDeclaration\") {\n for (const decl of (stmt as Node & { declarations: Node[] }).declarations) {\n const id = (decl as Node & { id: Node }).id;\n collectBindingNames(id, locals);\n }\n }\n }\n}\n\n/** Collect all identifier references in an AST subtree. */\nfunction collectIdentifierRefs(node: Node, refs: Set<string>): void {\n if (node.type === \"Identifier\") {\n refs.add((node as Node & { name: string }).name);\n return;\n }\n\n // `this` in arrow functions captures from enclosing scope — treat as external capture\n if (node.type === \"ThisExpression\") {\n refs.add(\"this\");\n return;\n }\n\n // Skip property access identifiers (obj.prop — \"prop\" is not a reference)\n if (node.type === \"MemberExpression\") {\n const member = node as Node & { object: Node; property: Node; computed: boolean };\n collectIdentifierRefs(member.object, refs);\n if (member.computed) {\n collectIdentifierRefs(member.property, refs);\n }\n return;\n }\n\n // Object literal properties: skip non-computed keys, only walk values\n if (node.type === \"Property\") {\n const prop = node as Node & { key: Node; value: Node; computed: boolean };\n if (prop.computed) {\n collectIdentifierRefs(prop.key, refs);\n }\n collectIdentifierRefs(prop.value, refs);\n return;\n }\n\n // Skip binding positions in variable declarations, function params, etc.\n if (node.type === \"VariableDeclarator\") {\n const decl = node as Node & { init: Node | null };\n if (decl.init) collectIdentifierRefs(decl.init, refs);\n return;\n }\n\n // Recursively walk all child nodes\n for (const key of Object.keys(node)) {\n if (key === \"type\" || key === \"start\" || key === \"end\") continue;\n const child = (node as unknown as Record<string, unknown>)[key];\n if (child && typeof child === \"object\") {\n if (Array.isArray(child)) {\n for (const item of child) {\n if (item && typeof item === \"object\" && \"type\" in item) {\n collectIdentifierRefs(item as Node, refs);\n }\n }\n } else if (\"type\" in child) {\n collectIdentifierRefs(child as Node, refs);\n }\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"effects.js","names":[],"sources":["../../../src/core/extract/effects.ts"],"sourcesContent":["/**\n * Effect compilation utilities.\n * Uses fn.toString() to extract function source text from live Zod schema references,\n * then classifies whether the function can be safely inlined (zero external captures).\n */\n\nimport type { Node } from \"acorn\";\nimport { parseExpressionAt } from \"acorn\";\n\n// Well-known globals that are safe to reference in inlined functions\nconst SAFE_GLOBALS = new Set([\n \"undefined\",\n \"null\",\n \"NaN\",\n \"Infinity\",\n \"Math\",\n \"Number\",\n \"String\",\n \"Boolean\",\n \"Array\",\n \"Object\",\n \"JSON\",\n \"Date\",\n \"RegExp\",\n \"parseInt\",\n \"parseFloat\",\n \"isNaN\",\n \"isFinite\",\n \"encodeURI\",\n \"decodeURI\",\n \"encodeURIComponent\",\n \"decodeURIComponent\",\n \"BigInt\",\n \"Symbol\",\n \"Map\",\n \"Set\",\n \"Error\",\n \"TypeError\",\n \"RangeError\",\n \"SyntaxError\",\n \"ReferenceError\",\n \"Promise\",\n \"globalThis\",\n \"true\",\n \"false\",\n]);\n\n/**\n * Can this refine predicate be CALLED by reference from generated code?\n *\n * Weaker than {@link tryCompileEffect}: the callback keeps its own closure, so\n * captures are fine — only shapes whose semantics the generated call could not\n * reproduce are rejected. A second parameter means the zod `ctx` protocol\n * (superRefine-style issue collection), and an async/generator function returns\n * a promise where zod's synchronous parse raises $ZodAsyncError.\n */\nexport function isReferenceablePredicate(fn: unknown): boolean {\n if (typeof fn !== \"function\") return false;\n if (fn.length >= 2) return false;\n const kind = fn.constructor?.name;\n return (\n kind !== \"AsyncFunction\" && kind !== \"GeneratorFunction\" && kind !== \"AsyncGeneratorFunction\"\n );\n}\n\n/** A parsed callback: the node types whose SOURCE TEXT is a usable expression. */\ntype FunctionNode = Node & { params?: Node[]; body?: Node };\n\n/**\n * Not every callable stringifies to an expression, and acorn parses what it can\n * rather than refusing. A method shorthand lifted off an object literal —\n * `normalize(v) { … }` — parses as the CALL `normalize(v)` and stops at the\n * brace; a getter's source yields a bare Identifier; a class yields a\n * ClassExpression. None of those carry `params`, so every arity guard below\n * passes vacuously and `tryCompileEffect` hands back the whole source including\n * the trailing block, which lands in generated code as\n * `var __ef_2=(normalize(v) { … });` — a SyntaxError that makes the emitted\n * module unparseable while the CLI still reports success.\n *\n * Only an arrow or a function expression is safe to treat as an inlineable\n * callback. Rejecting is the conservative answer rather than merely the safe\n * one: with no parsed params there is nothing to prove the callback ignores\n * zod's second (parse-context) argument, so a refine/transform degrades to a\n * call through `__rf[N]` and a preprocess degrades further, to full zod\n * delegation. Every shape above keeps zod's own behaviour either way — a class\n * still throws on a call-without-new exactly as it does under zod.\n *\n * The parse must also have consumed the whole source. Nothing here can prove\n * `toString()` returned real source text, and a partial parse would re-open\n * this same bug class by re-emitting the unconsumed tail.\n */\nfunction asFunctionNode(ast: Node, source: string): FunctionNode | null {\n const isFunction = ast.type === \"ArrowFunctionExpression\" || ast.type === \"FunctionExpression\";\n if (!isFunction) return null;\n return source.slice(ast.end).trim() === \"\" ? (ast as FunctionNode) : null;\n}\n\n/**\n * Recover a callback's parameter list from its source, or null when the source\n * does not reveal one. An arrow or function expression parses directly; a method\n * shorthand lifted off an object literal (`normalize(v) { … }`) is not an\n * expression on its own but becomes one wrapped back in the braces it came from.\n * A native or bound function reveals nothing — `[native code]` has no parameter\n * list to read, and skipping it before either parse keeps two thrown acorn\n * exceptions off the hot path for `.transform(Number)` and friends.\n */\nfunction recoverSignature(source: string): FunctionNode | null {\n if (source.includes(\"[native code]\")) return null;\n try {\n const ast = parseExpressionAt(source, 0, { ecmaVersion: \"latest\", sourceType: \"module\" });\n const fnNode = asFunctionNode(ast, source);\n if (fnNode !== null) return fnNode;\n } catch {\n // Not an expression on its own; try the object-literal form below.\n }\n try {\n const wrapped = `({${source}})`;\n const ast = parseExpressionAt(wrapped, 0, { ecmaVersion: \"latest\", sourceType: \"module\" });\n // Same full-consumption rule asFunctionNode applies: a source that closes\n // the wrapper early and reopens it would otherwise yield the WRONG method's\n // parameter list. Acorn returns the inner object expression, so the only\n // text that may remain is the wrapper's own closing paren.\n if (wrapped.slice(ast.end).trim() !== \")\") return null;\n const properties = (ast as Node & { properties?: (Node & { value?: Node })[] }).properties;\n if (properties?.length !== 1) return null;\n const value = properties[0]?.value;\n return value !== undefined && value.type === \"FunctionExpression\"\n ? (value as FunctionNode)\n : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Does a body reference ITS OWN `arguments`?\n *\n * A nested non-arrow function gets a fresh `arguments` binding, so its use of\n * the name says nothing about the callback being classified — descending into\n * one would fail a perfectly safe callback and cost it its compiled path. An\n * arrow has no `arguments` of its own and so is walked.\n */\nfunction referencesOwnArguments(node: Node): boolean {\n if (node.type === \"Identifier\") return (node as Node & { name: string }).name === \"arguments\";\n if (node.type === \"FunctionExpression\" || node.type === \"FunctionDeclaration\") return false;\n // `obj.arguments` and `{ arguments: v }` name a property, not the binding —\n // the same positions collectIdentifierRefs skips.\n if (node.type === \"MemberExpression\") {\n const member = node as Node & { object: Node; property: Node; computed: boolean };\n if (referencesOwnArguments(member.object)) return true;\n return member.computed && referencesOwnArguments(member.property);\n }\n if (node.type === \"Property\") {\n const property = node as Node & { key: Node; value: Node; computed: boolean };\n if (property.computed && referencesOwnArguments(property.key)) return true;\n return referencesOwnArguments(property.value);\n }\n for (const key of Object.keys(node)) {\n if (key === \"type\" || key === \"start\" || key === \"end\") continue;\n const child = (node as unknown as Record<string, unknown>)[key];\n if (!child || typeof child !== \"object\") continue;\n const children = Array.isArray(child) ? child : [child];\n for (const item of children) {\n if (item && typeof item === \"object\" && \"type\" in item) {\n if (referencesOwnArguments(item as Node)) return true;\n }\n }\n }\n return false;\n}\n\n/**\n * The three ways a parsed signature can betray zod's second argument. Shared so\n * that {@link observesSecondArgument} and {@link isContextFreeUnaryCallback},\n * which are exact inverses of it, cannot drift apart.\n */\nfunction signatureObservesSecondArgument(fnNode: FunctionNode): boolean {\n const params = fnNode.params ?? [];\n if (params.length > 1) return true;\n if (params.some((param) => param.type === \"RestElement\")) return true;\n return fnNode.body !== undefined && referencesOwnArguments(fnNode.body);\n}\n\n/**\n * Does this callback DEMONSTRABLY observe zod's second (parse-context) argument?\n *\n * Only a positive answer is trustworthy. Zod calls `transform(value, payload)`\n * while every compiled route passes the value alone, so a callback that reads\n * the second argument must be left to zod — but a callback whose signature\n * cannot be read (a native like `Number`, a bound function) is NOT thereby\n * suspect, and refusing those costs a very common idiom its compiled path for\n * no correctness gain: `.transform(Number)` delegating measured SLOWER than not\n * compiling at all. So this reports only what the parsed parameter list proves.\n *\n * `fn.length` cannot answer this at all: it stops counting at the first default\n * and ignores a rest element, so `(...args) => args.length` reports 0 and\n * `(v, ctx = null) => …` reports 1.\n */\nexport function observesSecondArgument(fn: unknown): boolean {\n if (typeof fn !== \"function\") return false;\n const fnNode = recoverSignature(fn.toString());\n return fnNode !== null && signatureObservesSecondArgument(fnNode);\n}\n\n/**\n * Can a callback be invoked with the value alone without hiding Zod's second\n * parse-context argument? `fn.length` is insufficient here: default and rest\n * parameters can observe that argument while still reporting length 0 or 1.\n */\nexport function isContextFreeUnaryCallback(fn: unknown): boolean {\n if (!isReferenceablePredicate(fn)) return false;\n const source = (fn as Function).toString();\n let ast: Node;\n try {\n ast = parseExpressionAt(source, 0, {\n ecmaVersion: \"latest\",\n sourceType: \"module\",\n });\n } catch {\n return false;\n }\n const fnNode = asFunctionNode(ast, source);\n if (fnNode === null) return false;\n return !signatureObservesSecondArgument(fnNode);\n}\n\n/**\n * A `superRefine`: a `custom` check whose callback takes zod's PAYLOAD rather\n * than the value, collecting issues instead of returning a verdict. Zod stores\n * it on the check instance (`_zod.check`) rather than `def.fn`.\n *\n * Narrowed to zod's OWN wrapper, which is what `_superRefine` builds and the\n * only thing whose payload handling is modelled here: it installs `addIssue`,\n * so every issue the user adds has been through `util.issue`. The trait marks a\n * genuine `$ZodCheck` instance and so excludes a raw `.check(fn)` — shape-\n * identical, but there the callback IS the user's, holding the payload\n * unmediated. A `when` predicate is likewise refused: it makes zod run the\n * check conditionally, where generated code runs it always.\n */\nexport function isPayloadCheck(check: {\n _zod?: { check?: unknown; def?: { fn?: unknown; when?: unknown }; traits?: Set<string> };\n}): boolean {\n return (\n check._zod?.def?.fn === undefined &&\n check._zod?.def?.when === undefined &&\n typeof check._zod?.check === \"function\" &&\n check._zod?.traits?.has(\"$ZodCheck\") === true\n );\n}\n\n/**\n * Try to compile a function into an inlineable source string.\n * Returns the function source if it's a zero-capture function (safe to inline),\n * or undefined if it cannot be compiled (async, has captures, or parse failure).\n */\nexport function tryCompileEffect(fn: unknown): string | undefined {\n if (typeof fn !== \"function\") return undefined;\n\n const source = fn.toString();\n\n // Quick reject: native functions\n if (source.includes(\"[native code]\")) return undefined;\n\n let ast: Node;\n try {\n ast = parseExpressionAt(source, 0, {\n ecmaVersion: \"latest\",\n sourceType: \"module\",\n });\n } catch {\n // Parse failed — likely TypeScript annotations or unsupported syntax\n return undefined;\n }\n\n // Only a fully-consumed arrow or function expression can be re-emitted as\n // source; see asFunctionNode for the shapes acorn otherwise accepts.\n const fnNode = asFunctionNode(ast, source);\n if (fnNode === null) return undefined;\n\n // Reject async and generator functions\n if (\"async\" in ast && (ast as { async?: boolean }).async) return undefined;\n if (\"generator\" in ast && (ast as { generator?: boolean }).generator) return undefined;\n\n // Collect parameter names\n const params = new Set<string>();\n\n // Reject functions with 2+ required parameters (uses Zod ctx argument).\n // transform(value, ctx) and superRefine(value, ctx) rely on ctx for\n // issue collection, which cannot be reproduced in compiled output.\n // fn.length counts parameters before the first default, so (v, base=10)\n // has length 1 (allowed) while (v, ctx) has length 2 (rejected).\n if ((fn as { length: number }).length >= 2) return undefined;\n\n if (fnNode.params) {\n for (const param of fnNode.params) {\n collectBindingNames(param, params);\n }\n }\n\n // Collect local variable declarations in the body\n const locals = new Set<string>();\n if (fnNode.body && fnNode.body.type === \"BlockStatement\") {\n collectLocals(fnNode.body, locals);\n }\n\n // Collect all identifier references in the body\n const refs = new Set<string>();\n if (fnNode.body) {\n collectIdentifierRefs(fnNode.body, refs);\n }\n\n // Check for external captures\n for (const ref of refs) {\n if (!params.has(ref) && !locals.has(ref) && !SAFE_GLOBALS.has(ref)) {\n return undefined; // Has external capture\n }\n }\n\n return source;\n}\n\n/** Collect binding names from a parameter pattern (handles destructuring). */\nfunction collectBindingNames(node: Node, names: Set<string>): void {\n switch (node.type) {\n case \"Identifier\":\n names.add((node as Node & { name: string }).name);\n break;\n case \"AssignmentPattern\":\n collectBindingNames((node as Node & { left: Node }).left, names);\n break;\n case \"ObjectPattern\":\n for (const prop of (node as Node & { properties: Node[] }).properties) {\n if (prop.type === \"RestElement\") {\n collectBindingNames((prop as Node & { argument: Node }).argument, names);\n } else {\n collectBindingNames((prop as Node & { value: Node }).value, names);\n }\n }\n break;\n case \"ArrayPattern\":\n for (const elem of (node as Node & { elements: (Node | null)[] }).elements) {\n if (elem) collectBindingNames(elem, names);\n }\n break;\n case \"RestElement\":\n collectBindingNames((node as Node & { argument: Node }).argument, names);\n break;\n }\n}\n\n/** Collect locally declared variable names from a block body. */\nfunction collectLocals(body: Node, locals: Set<string>): void {\n const block = body as Node & { body: Node[] };\n if (!block.body) return;\n for (const stmt of block.body) {\n if (stmt.type === \"VariableDeclaration\") {\n for (const decl of (stmt as Node & { declarations: Node[] }).declarations) {\n const id = (decl as Node & { id: Node }).id;\n collectBindingNames(id, locals);\n }\n }\n }\n}\n\n/** Collect all identifier references in an AST subtree. */\nfunction collectIdentifierRefs(node: Node, refs: Set<string>): void {\n if (node.type === \"Identifier\") {\n refs.add((node as Node & { name: string }).name);\n return;\n }\n\n // `this` in arrow functions captures from enclosing scope — treat as external capture\n if (node.type === \"ThisExpression\") {\n refs.add(\"this\");\n return;\n }\n\n // Skip property access identifiers (obj.prop — \"prop\" is not a reference)\n if (node.type === \"MemberExpression\") {\n const member = node as Node & { object: Node; property: Node; computed: boolean };\n collectIdentifierRefs(member.object, refs);\n if (member.computed) {\n collectIdentifierRefs(member.property, refs);\n }\n return;\n }\n\n // Object literal properties: skip non-computed keys, only walk values\n if (node.type === \"Property\") {\n const prop = node as Node & { key: Node; value: Node; computed: boolean };\n if (prop.computed) {\n collectIdentifierRefs(prop.key, refs);\n }\n collectIdentifierRefs(prop.value, refs);\n return;\n }\n\n // Skip binding positions in variable declarations, function params, etc.\n if (node.type === \"VariableDeclarator\") {\n const decl = node as Node & { init: Node | null };\n if (decl.init) collectIdentifierRefs(decl.init, refs);\n return;\n }\n\n // Recursively walk all child nodes\n for (const key of Object.keys(node)) {\n if (key === \"type\" || key === \"start\" || key === \"end\") continue;\n const child = (node as unknown as Record<string, unknown>)[key];\n if (child && typeof child === \"object\") {\n if (Array.isArray(child)) {\n for (const item of child) {\n if (item && typeof item === \"object\" && \"type\" in item) {\n collectIdentifierRefs(item as Node, refs);\n }\n }\n } else if (\"type\" in child) {\n collectIdentifierRefs(child as Node, refs);\n }\n }\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"pipe.d.ts","names":[],"sources":["../../../../src/core/extract/extractors/pipe.ts"],"mappings":";;;iBASgB,YAAY,KAAK,QAAQ,KAAK,mBAAmB"}
1
+ {"version":3,"file":"pipe.d.ts","names":[],"sources":["../../../../src/core/extract/extractors/pipe.ts"],"mappings":";;;iBAUgB,YAAY,KAAK,QAAQ,KAAK,mBAAmB"}
@@ -1,4 +1,4 @@
1
- import { isContextFreeUnaryCallback, isReferenceablePredicate, tryCompileEffect } from "../effects.js";
1
+ import { isContextFreeUnaryCallback, isReferenceablePredicate, observesSecondArgument, tryCompileEffect } from "../effects.js";
2
2
  import { extractStringBool, isStringBoolCodec } from "./string-bool.js";
3
3
  //#region src/core/extract/extractors/pipe.ts
4
4
  function extractPipe(def, ctx) {
@@ -33,25 +33,27 @@ function extractPipe(def, ctx) {
33
33
  }
34
34
  const outDef = def.out?._zod?.def;
35
35
  if (outDef && outDef.type === "transform") {
36
- const source = tryCompileEffect(outDef.transform);
37
- if (source) return {
38
- type: "effect",
39
- effectKind: "transform",
40
- source,
41
- inner: ctx.visit(def.in, "._zod.def.in")
42
- };
43
- if (ctx.refs && isReferenceablePredicate(outDef.transform)) {
44
- const refIndex = ctx.refs.length;
45
- ctx.refs.push({
46
- schema: outDef.transform,
47
- accessPath: `${ctx.path}._zod.def.out._zod.def.transform`
48
- });
49
- return {
36
+ if (!observesSecondArgument(outDef.transform)) {
37
+ const source = tryCompileEffect(outDef.transform);
38
+ if (source) return {
50
39
  type: "effect",
51
40
  effectKind: "transform",
52
- refIndex,
41
+ source,
53
42
  inner: ctx.visit(def.in, "._zod.def.in")
54
43
  };
44
+ if (ctx.refs && isReferenceablePredicate(outDef.transform)) {
45
+ const refIndex = ctx.refs.length;
46
+ ctx.refs.push({
47
+ schema: outDef.transform,
48
+ accessPath: `${ctx.path}._zod.def.out._zod.def.transform`
49
+ });
50
+ return {
51
+ type: "effect",
52
+ effectKind: "transform",
53
+ refIndex,
54
+ inner: ctx.visit(def.in, "._zod.def.in")
55
+ };
56
+ }
55
57
  }
56
58
  return ctx.fallback("transform");
57
59
  }
@@ -1 +1 @@
1
- {"version":3,"file":"pipe.js","names":[],"sources":["../../../../src/core/extract/extractors/pipe.ts"],"sourcesContent":["import type { SchemaIR } from \"../../types.js\";\nimport {\n isContextFreeUnaryCallback,\n isReferenceablePredicate,\n tryCompileEffect,\n} from \"../effects.js\";\nimport type { ExtractorContext, ZodDef } from \"../types.js\";\nimport { extractStringBool, isStringBoolCodec } from \"./string-bool.js\";\n\nexport function extractPipe(def: ZodDef, ctx: ExtractorContext): SchemaIR {\n // Detect stringbool Codec (string→boolean with transform + reverseTransform)\n if (isStringBoolCodec(def)) {\n const ir = extractStringBool(def, ctx);\n if (ir) return ir;\n // Probing failed — fall back to Zod\n return ctx.fallback(\"transform\");\n }\n\n // Other Codecs (z.codec): the decode transform lives on the pipe def itself.\n // Compiling in/out as a plain pipe would validate the untransformed value\n // against the output schema — delegate to Zod.\n if (def.transform !== undefined) {\n return ctx.fallback(\"transform\");\n }\n\n // z.preprocess(fn, schema) is represented as pipe(transform(fn), schema):\n // run the callback before validating the output schema. A synchronous\n // single-argument callback needs no Zod parse context, so it can use the\n // same inline-or-reference machinery as a regular transform.\n const inDef = def.in?._zod?.def;\n if (inDef && inDef.type === \"transform\" && isContextFreeUnaryCallback(inDef.transform)) {\n const source = tryCompileEffect(inDef.transform);\n if (source) {\n return {\n type: \"effect\",\n effectKind: \"preprocess\",\n source,\n inner: ctx.visit(def.out, \"._zod.def.out\"),\n };\n }\n if (ctx.refs) {\n const refIndex = ctx.refs.length;\n ctx.refs.push({\n schema: inDef.transform,\n accessPath: `${ctx.path}._zod.def.in._zod.def.transform`,\n });\n return {\n type: \"effect\",\n effectKind: \"preprocess\",\n refIndex,\n inner: ctx.visit(def.out, \"._zod.def.out\"),\n };\n }\n }\n\n const outDef = def.out?._zod?.def;\n if (outDef && outDef.type === \"transform\") {\n const source = tryCompileEffect(outDef.transform);\n if (source) {\n const inIR = ctx.visit(def.in, \"._zod.def.in\");\n return { type: \"effect\", effectKind: \"transform\", source, inner: inIR };\n }\n // Not inlineable (the callback captures). Call the user's own function\n // through a schema reference rather than delegating: falling back costs the\n // schema its compiled path AND adds the delegate wrapper on top, which\n // measured SLOWER than plain zod. A two-argument transform is zod's `ctx`\n // protocol and an async one returns a promise, so both still delegate.\n if (ctx.refs && isReferenceablePredicate(outDef.transform)) {\n const refIndex = ctx.refs.length;\n ctx.refs.push({\n schema: outDef.transform,\n accessPath: `${ctx.path}._zod.def.out._zod.def.transform`,\n });\n const inIR = ctx.visit(def.in, \"._zod.def.in\");\n return { type: \"effect\", effectKind: \"transform\", refIndex, inner: inIR };\n }\n return ctx.fallback(\"transform\");\n }\n const inIR = ctx.visit(def.in, \"._zod.def.in\");\n const outIR = ctx.visit(def.out, \"._zod.def.out\");\n return { type: \"pipe\", in: inIR, out: outIR };\n}\n"],"mappings":";;;AASA,SAAgB,YAAY,KAAa,KAAiC;CAExE,IAAI,kBAAkB,GAAG,GAAG;EAC1B,MAAM,KAAK,kBAAkB,KAAK,GAAG;EACrC,IAAI,IAAI,OAAO;EAEf,OAAO,IAAI,SAAS,WAAW;CACjC;CAKA,IAAI,IAAI,cAAc,KAAA,GACpB,OAAO,IAAI,SAAS,WAAW;CAOjC,MAAM,QAAQ,IAAI,IAAI,MAAM;CAC5B,IAAI,SAAS,MAAM,SAAS,eAAe,2BAA2B,MAAM,SAAS,GAAG;EACtF,MAAM,SAAS,iBAAiB,MAAM,SAAS;EAC/C,IAAI,QACF,OAAO;GACL,MAAM;GACN,YAAY;GACZ;GACA,OAAO,IAAI,MAAM,IAAI,KAAK,eAAe;EAC3C;EAEF,IAAI,IAAI,MAAM;GACZ,MAAM,WAAW,IAAI,KAAK;GAC1B,IAAI,KAAK,KAAK;IACZ,QAAQ,MAAM;IACd,YAAY,GAAG,IAAI,KAAK;GAC1B,CAAC;GACD,OAAO;IACL,MAAM;IACN,YAAY;IACZ;IACA,OAAO,IAAI,MAAM,IAAI,KAAK,eAAe;GAC3C;EACF;CACF;CAEA,MAAM,SAAS,IAAI,KAAK,MAAM;CAC9B,IAAI,UAAU,OAAO,SAAS,aAAa;EACzC,MAAM,SAAS,iBAAiB,OAAO,SAAS;EAChD,IAAI,QAEF,OAAO;GAAE,MAAM;GAAU,YAAY;GAAa;GAAQ,OAD7C,IAAI,MAAM,IAAI,IAAI,cACqC;EAAE;EAOxE,IAAI,IAAI,QAAQ,yBAAyB,OAAO,SAAS,GAAG;GAC1D,MAAM,WAAW,IAAI,KAAK;GAC1B,IAAI,KAAK,KAAK;IACZ,QAAQ,OAAO;IACf,YAAY,GAAG,IAAI,KAAK;GAC1B,CAAC;GAED,OAAO;IAAE,MAAM;IAAU,YAAY;IAAa;IAAU,OAD/C,IAAI,MAAM,IAAI,IAAI,cACuC;GAAE;EAC1E;EACA,OAAO,IAAI,SAAS,WAAW;CACjC;CAGA,OAAO;EAAE,MAAM;EAAQ,IAFV,IAAI,MAAM,IAAI,IAAI,cAED;EAAG,KADnB,IAAI,MAAM,IAAI,KAAK,eACS;CAAE;AAC9C"}
1
+ {"version":3,"file":"pipe.js","names":[],"sources":["../../../../src/core/extract/extractors/pipe.ts"],"sourcesContent":["import type { SchemaIR } from \"../../types.js\";\nimport {\n isContextFreeUnaryCallback,\n isReferenceablePredicate,\n observesSecondArgument,\n tryCompileEffect,\n} from \"../effects.js\";\nimport type { ExtractorContext, ZodDef } from \"../types.js\";\nimport { extractStringBool, isStringBoolCodec } from \"./string-bool.js\";\n\nexport function extractPipe(def: ZodDef, ctx: ExtractorContext): SchemaIR {\n // Detect stringbool Codec (string→boolean with transform + reverseTransform)\n if (isStringBoolCodec(def)) {\n const ir = extractStringBool(def, ctx);\n if (ir) return ir;\n // Probing failed — fall back to Zod\n return ctx.fallback(\"transform\");\n }\n\n // Other Codecs (z.codec): the decode transform lives on the pipe def itself.\n // Compiling in/out as a plain pipe would validate the untransformed value\n // against the output schema — delegate to Zod.\n if (def.transform !== undefined) {\n return ctx.fallback(\"transform\");\n }\n\n // z.preprocess(fn, schema) is represented as pipe(transform(fn), schema):\n // run the callback before validating the output schema. A synchronous\n // single-argument callback needs no Zod parse context, so it can use the\n // same inline-or-reference machinery as a regular transform.\n const inDef = def.in?._zod?.def;\n if (inDef && inDef.type === \"transform\" && isContextFreeUnaryCallback(inDef.transform)) {\n const source = tryCompileEffect(inDef.transform);\n if (source) {\n return {\n type: \"effect\",\n effectKind: \"preprocess\",\n source,\n inner: ctx.visit(def.out, \"._zod.def.out\"),\n };\n }\n if (ctx.refs) {\n const refIndex = ctx.refs.length;\n ctx.refs.push({\n schema: inDef.transform,\n accessPath: `${ctx.path}._zod.def.in._zod.def.transform`,\n });\n return {\n type: \"effect\",\n effectKind: \"preprocess\",\n refIndex,\n inner: ctx.visit(def.out, \"._zod.def.out\"),\n };\n }\n }\n\n const outDef = def.out?._zod?.def;\n if (outDef && outDef.type === \"transform\") {\n // Zod calls `transform(payload.value, payload)`; both compiled routes below\n // pass the value alone. A callback whose parameter list PROVES it reads that\n // second argument has to stay with zod — `fn.length` cannot see it, since it\n // stops at the first default and ignores a rest element, so\n // `(...args) => args.length` and `(v, ctx = null) => …` both slipped through\n // and silently computed a different result compiled than under zod.\n //\n // A signature that cannot be READ (a native, a bound function) is not\n // thereby suspect and keeps the reference route. That knowingly retains a\n // narrow pre-existing hole — a variadic native like `String.fromCharCode`,\n // or `((...args) => …).bind(null)`, reports length 0 and still sees only one\n // argument compiled — because closing it would make the far more common\n // `.transform(Number)` delegate, which measures slower than not compiling.\n if (!observesSecondArgument(outDef.transform)) {\n const source = tryCompileEffect(outDef.transform);\n if (source) {\n const inIR = ctx.visit(def.in, \"._zod.def.in\");\n return { type: \"effect\", effectKind: \"transform\", source, inner: inIR };\n }\n // Not inlineable (the callback captures, or its source is not an\n // expression). Call the user's own function through a schema reference\n // rather than delegating: falling back costs the schema its compiled path\n // AND adds the delegate wrapper on top, which measured SLOWER than plain\n // zod.\n if (ctx.refs && isReferenceablePredicate(outDef.transform)) {\n const refIndex = ctx.refs.length;\n ctx.refs.push({\n schema: outDef.transform,\n accessPath: `${ctx.path}._zod.def.out._zod.def.transform`,\n });\n const inIR = ctx.visit(def.in, \"._zod.def.in\");\n return { type: \"effect\", effectKind: \"transform\", refIndex, inner: inIR };\n }\n }\n // A ctx-taking transform is zod's issue-collection protocol and an async one\n // returns a promise, so both delegate.\n return ctx.fallback(\"transform\");\n }\n const inIR = ctx.visit(def.in, \"._zod.def.in\");\n const outIR = ctx.visit(def.out, \"._zod.def.out\");\n return { type: \"pipe\", in: inIR, out: outIR };\n}\n"],"mappings":";;;AAUA,SAAgB,YAAY,KAAa,KAAiC;CAExE,IAAI,kBAAkB,GAAG,GAAG;EAC1B,MAAM,KAAK,kBAAkB,KAAK,GAAG;EACrC,IAAI,IAAI,OAAO;EAEf,OAAO,IAAI,SAAS,WAAW;CACjC;CAKA,IAAI,IAAI,cAAc,KAAA,GACpB,OAAO,IAAI,SAAS,WAAW;CAOjC,MAAM,QAAQ,IAAI,IAAI,MAAM;CAC5B,IAAI,SAAS,MAAM,SAAS,eAAe,2BAA2B,MAAM,SAAS,GAAG;EACtF,MAAM,SAAS,iBAAiB,MAAM,SAAS;EAC/C,IAAI,QACF,OAAO;GACL,MAAM;GACN,YAAY;GACZ;GACA,OAAO,IAAI,MAAM,IAAI,KAAK,eAAe;EAC3C;EAEF,IAAI,IAAI,MAAM;GACZ,MAAM,WAAW,IAAI,KAAK;GAC1B,IAAI,KAAK,KAAK;IACZ,QAAQ,MAAM;IACd,YAAY,GAAG,IAAI,KAAK;GAC1B,CAAC;GACD,OAAO;IACL,MAAM;IACN,YAAY;IACZ;IACA,OAAO,IAAI,MAAM,IAAI,KAAK,eAAe;GAC3C;EACF;CACF;CAEA,MAAM,SAAS,IAAI,KAAK,MAAM;CAC9B,IAAI,UAAU,OAAO,SAAS,aAAa;EAczC,IAAI,CAAC,uBAAuB,OAAO,SAAS,GAAG;GAC7C,MAAM,SAAS,iBAAiB,OAAO,SAAS;GAChD,IAAI,QAEF,OAAO;IAAE,MAAM;IAAU,YAAY;IAAa;IAAQ,OAD7C,IAAI,MAAM,IAAI,IAAI,cACqC;GAAE;GAOxE,IAAI,IAAI,QAAQ,yBAAyB,OAAO,SAAS,GAAG;IAC1D,MAAM,WAAW,IAAI,KAAK;IAC1B,IAAI,KAAK,KAAK;KACZ,QAAQ,OAAO;KACf,YAAY,GAAG,IAAI,KAAK;IAC1B,CAAC;IAED,OAAO;KAAE,MAAM;KAAU,YAAY;KAAa;KAAU,OAD/C,IAAI,MAAM,IAAI,IAAI,cACuC;IAAE;GAC1E;EACF;EAGA,OAAO,IAAI,SAAS,WAAW;CACjC;CAGA,OAAO;EAAE,MAAM;EAAQ,IAFV,IAAI,MAAM,IAAI,IAAI,cAED;EAAG,KADnB,IAAI,MAAM,IAAI,KAAK,eACS;CAAE;AAC9C"}
@@ -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":"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"}
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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zod-compiler",
3
- "version": "1.26.3",
3
+ "version": "1.26.4",
4
4
  "description": "Compile Zod schemas into zero-overhead validation functions",
5
5
  "keywords": [
6
6
  "aot",