zod-compiler 1.26.2 → 1.26.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/compile.d.ts +5 -0
- package/dist/core/compile.d.ts.map +1 -1
- package/dist/core/compile.js +11 -1
- package/dist/core/compile.js.map +1 -1
- package/dist/core/extract/effects.d.ts +17 -1
- package/dist/core/extract/effects.d.ts.map +1 -1
- package/dist/core/extract/effects.js +131 -9
- package/dist/core/extract/effects.js.map +1 -1
- package/dist/core/extract/extractors/pipe.d.ts.map +1 -1
- package/dist/core/extract/extractors/pipe.js +18 -16
- package/dist/core/extract/extractors/pipe.js.map +1 -1
- package/dist/core/iife.d.ts +23 -2
- package/dist/core/iife.d.ts.map +1 -1
- package/dist/core/iife.js +25 -3
- package/dist/core/iife.js.map +1 -1
- package/dist/discovery.d.ts.map +1 -1
- package/dist/discovery.js +14 -18
- package/dist/discovery.js.map +1 -1
- package/dist/is-zod-schema.d.ts +23 -0
- package/dist/is-zod-schema.d.ts.map +1 -0
- package/dist/is-zod-schema.js +32 -0
- package/dist/is-zod-schema.js.map +1 -0
- package/dist/jit.d.ts.map +1 -1
- package/dist/jit.js +63 -28
- package/dist/jit.js.map +1 -1
- package/dist/unplugin/hoist-compile.d.ts.map +1 -1
- package/dist/unplugin/hoist-compile.js +1 -4
- package/dist/unplugin/hoist-compile.js.map +1 -1
- package/dist/unplugin/transform.d.ts.map +1 -1
- package/dist/unplugin/transform.js +29 -1
- package/dist/unplugin/transform.js.map +1 -1
- package/package.json +1 -1
package/dist/core/compile.d.ts
CHANGED
|
@@ -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
|
|
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"}
|
package/dist/core/compile.js
CHANGED
|
@@ -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
|
-
|
|
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 };
|
package/dist/core/compile.js.map
CHANGED
|
@@ -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
|
|
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;;;;;;
|
|
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(
|
|
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 (
|
|
72
|
-
|
|
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":";;;
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
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
|
|
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"}
|
package/dist/core/iife.d.ts
CHANGED
|
@@ -213,17 +213,38 @@ declare const FINZ_DECL = "function __zcFinZ(z,r,i){return new __ZcFailZ(z,r,i);
|
|
|
213
213
|
* object — two exports aliasing one schema — would throw under ESM strict mode.
|
|
214
214
|
*/
|
|
215
215
|
declare const MK_VALIDATOR_DECL: string;
|
|
216
|
+
/**
|
|
217
|
+
* Does the IIFE's preamble DEREFERENCE the retained schema at evaluation time?
|
|
218
|
+
*
|
|
219
|
+
* Only `__rf` does: every entry is `__zs<accessPath>`, and an access path walks
|
|
220
|
+
* the schema's structure — `._zod.innerType` fires a `z.lazy()` getter,
|
|
221
|
+
* `.shape` materializes a `z.object()` shape (zod v4 reads every property
|
|
222
|
+
* descriptor, so ONE `.shape` read fires ALL of an object's recursion getters).
|
|
223
|
+
* Binding `__zs` itself does not: constructing the schema leaves deferred
|
|
224
|
+
* callbacks unforced, and `usesRetainedSchema`'s only read is `__zs.safeParse`,
|
|
225
|
+
* an own property zod assigns during `ZodType.init`.
|
|
226
|
+
*
|
|
227
|
+
* The distinction decides whether the IIFE may be evaluated inside the
|
|
228
|
+
* INITIALIZER of the binding the schema's own deferred callbacks close over —
|
|
229
|
+
* see the self-referential path in the unplugin's autoDiscover rewrite.
|
|
230
|
+
*/
|
|
231
|
+
declare function iifeDerefsSchema(schema: CompiledSchemaInfo): boolean;
|
|
216
232
|
/**
|
|
217
233
|
* Generate a `/* @__PURE__ * /` IIFE wrapping a compiled validator.
|
|
218
234
|
*
|
|
219
235
|
* @param schemaExpr - Expression resolving to the original Zod schema
|
|
220
236
|
* (e.g. `"UserSchema"` in unplugin, `"(__src_X as any).schema"` in CLI)
|
|
221
237
|
* @param schema
|
|
222
|
-
* @param options
|
|
238
|
+
* @param options - `pure: false` drops the `/* @__PURE__ * /` annotation, for
|
|
239
|
+
* the one caller that emits the IIFE as a STATEMENT following the
|
|
240
|
+
* declaration rather than as its initializer: there the call's whole point is
|
|
241
|
+
* its side effect (`__zcMkv` installing the compiled methods on the schema),
|
|
242
|
+
* and a bundler that believed it pure would drop the compilation outright.
|
|
223
243
|
*/
|
|
224
244
|
declare function generateIIFE(schemaExpr: string, schema: CompiledSchemaInfo, options?: {
|
|
225
245
|
zodCompat?: boolean | undefined;
|
|
246
|
+
pure?: boolean | undefined;
|
|
226
247
|
}): string;
|
|
227
248
|
//#endregion
|
|
228
|
-
export { FAILZ_CLASS_DECL, FAIL_CLASS_DECL, FINZ_DECL, FIN_DECL, FIN_DEFERRED_DECL, MK_VALIDATOR_DECL, ZOD_CONFIG_IMPORT, ZOD_MSG_DECLARATION, generateIIFE };
|
|
249
|
+
export { FAILZ_CLASS_DECL, FAIL_CLASS_DECL, FINZ_DECL, FIN_DECL, FIN_DEFERRED_DECL, MK_VALIDATOR_DECL, ZOD_CONFIG_IMPORT, ZOD_MSG_DECLARATION, generateIIFE, iifeDerefsSchema };
|
|
229
250
|
//# sourceMappingURL=iife.d.ts.map
|
package/dist/core/iife.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"iife.d.ts","names":[],"sources":["../../src/core/iife.ts"],"mappings":";;;;;;;;cAca;;;;;;;;;;;;;;;;;;;;cAsBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA8CA;;;cAUA;;;;;;;;;;;;;;;;;;;;;;;cAyBA;;;;;;;;;;;;;;;;;;;;;;;;;;cA2BA;;cAMA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA6FA
|
|
1
|
+
{"version":3,"file":"iife.d.ts","names":[],"sources":["../../src/core/iife.ts"],"mappings":";;;;;;;;cAca;;;;;;;;;;;;;;;;;;;;cAsBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA8CA;;;cAUA;;;;;;;;;;;;;;;;;;;;;;;cAyBA;;;;;;;;;;;;;;;;;;;;;;;;;;cA2BA;;cAMA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA6FA;;;;;;;;;;;;;;;;iBA4BG,iBAAiB,QAAQ;;;;;;;;;;;;;iBAgBzB,aACd,oBACA,QAAQ,oBACR;EAAY;EAAiC"}
|
package/dist/core/iife.js
CHANGED
|
@@ -223,12 +223,34 @@ function extractFunctionName(functionDef) {
|
|
|
223
223
|
return match[1];
|
|
224
224
|
}
|
|
225
225
|
/**
|
|
226
|
+
* Does the IIFE's preamble DEREFERENCE the retained schema at evaluation time?
|
|
227
|
+
*
|
|
228
|
+
* Only `__rf` does: every entry is `__zs<accessPath>`, and an access path walks
|
|
229
|
+
* the schema's structure — `._zod.innerType` fires a `z.lazy()` getter,
|
|
230
|
+
* `.shape` materializes a `z.object()` shape (zod v4 reads every property
|
|
231
|
+
* descriptor, so ONE `.shape` read fires ALL of an object's recursion getters).
|
|
232
|
+
* Binding `__zs` itself does not: constructing the schema leaves deferred
|
|
233
|
+
* callbacks unforced, and `usesRetainedSchema`'s only read is `__zs.safeParse`,
|
|
234
|
+
* an own property zod assigns during `ZodType.init`.
|
|
235
|
+
*
|
|
236
|
+
* The distinction decides whether the IIFE may be evaluated inside the
|
|
237
|
+
* INITIALIZER of the binding the schema's own deferred callbacks close over —
|
|
238
|
+
* see the self-referential path in the unplugin's autoDiscover rewrite.
|
|
239
|
+
*/
|
|
240
|
+
function iifeDerefsSchema(schema) {
|
|
241
|
+
return schema.refEntries.length > 0;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
226
244
|
* Generate a `/* @__PURE__ * /` IIFE wrapping a compiled validator.
|
|
227
245
|
*
|
|
228
246
|
* @param schemaExpr - Expression resolving to the original Zod schema
|
|
229
247
|
* (e.g. `"UserSchema"` in unplugin, `"(__src_X as any).schema"` in CLI)
|
|
230
248
|
* @param schema
|
|
231
|
-
* @param options
|
|
249
|
+
* @param options - `pure: false` drops the `/* @__PURE__ * /` annotation, for
|
|
250
|
+
* the one caller that emits the IIFE as a STATEMENT following the
|
|
251
|
+
* declaration rather than as its initializer: there the call's whole point is
|
|
252
|
+
* its side effect (`__zcMkv` installing the compiled methods on the schema),
|
|
253
|
+
* and a bundler that believed it pure would drop the compilation outright.
|
|
232
254
|
*/
|
|
233
255
|
function generateIIFE(schemaExpr, schema, options) {
|
|
234
256
|
const { codegenResult, refEntries } = schema;
|
|
@@ -240,7 +262,7 @@ function generateIIFE(schemaExpr, schema, options) {
|
|
|
240
262
|
const fcArg = codegenResult.fastFnName ?? "null";
|
|
241
263
|
const isArg = codegenResult.isFnName ?? (codegenResult.fastTotal ? fcArg : "null");
|
|
242
264
|
return [
|
|
243
|
-
"/* @__PURE__ */ (() => {",
|
|
265
|
+
options?.pure === false ? "(() => {" : "/* @__PURE__ */ (() => {",
|
|
244
266
|
...bindsSchema ? [`var ${RETAINED_SCHEMA_VAR}=${schemaExpr};`] : [],
|
|
245
267
|
...refEntries.length > 0 ? [`var __rf=[${refEntries.map((fb) => `${retainedSchema}${fb.accessPath}`).join(",")}];`] : [],
|
|
246
268
|
...codegenResult.code.split("\n").filter((l) => l.trim() !== "" && l.trim() !== "/* zod-compiler */"),
|
|
@@ -250,6 +272,6 @@ function generateIIFE(schemaExpr, schema, options) {
|
|
|
250
272
|
].join("\n");
|
|
251
273
|
}
|
|
252
274
|
//#endregion
|
|
253
|
-
export { FAILZ_CLASS_DECL, FAIL_CLASS_DECL, FINZ_DECL, FIN_DECL, FIN_DEFERRED_DECL, MK_VALIDATOR_DECL, ZOD_CONFIG_IMPORT, ZOD_MSG_DECLARATION, generateIIFE };
|
|
275
|
+
export { FAILZ_CLASS_DECL, FAIL_CLASS_DECL, FINZ_DECL, FIN_DECL, FIN_DEFERRED_DECL, MK_VALIDATOR_DECL, ZOD_CONFIG_IMPORT, ZOD_MSG_DECLARATION, generateIIFE, iifeDerefsSchema };
|
|
254
276
|
|
|
255
277
|
//# sourceMappingURL=iife.js.map
|