zod-compiler 1.23.8 → 1.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +116 -12
- package/dist/runtime.d.ts +53 -0
- package/dist/runtime.js +55 -0
- package/dist/turbopack.d.ts +56 -0
- package/dist/turbopack.d.ts.map +1 -0
- package/dist/turbopack.js +200 -0
- package/dist/turbopack.js.map +1 -0
- package/dist/unplugin/dep-graph.d.ts +45 -2
- package/dist/unplugin/dep-graph.d.ts.map +1 -1
- package/dist/unplugin/dep-graph.js +42 -2
- package/dist/unplugin/dep-graph.js.map +1 -1
- package/dist/unplugin/edits.d.ts +13 -1
- package/dist/unplugin/edits.d.ts.map +1 -1
- package/dist/unplugin/edits.js +35 -1
- package/dist/unplugin/edits.js.map +1 -1
- package/dist/unplugin/hoist.d.ts.map +1 -1
- package/dist/unplugin/hoist.js +2 -26
- package/dist/unplugin/hoist.js.map +1 -1
- package/dist/unplugin/transform.d.ts.map +1 -1
- package/dist/unplugin/transform.js +38 -19
- package/dist/unplugin/transform.js.map +1 -1
- package/dist/unplugin/virtual.d.ts +28 -1
- package/dist/unplugin/virtual.d.ts.map +1 -1
- package/dist/unplugin/virtual.js +27 -1
- package/dist/unplugin/virtual.js.map +1 -1
- package/package.json +12 -1
package/README.md
CHANGED
|
@@ -118,18 +118,21 @@ that blocks eval both leave a working plain-Zod schema.
|
|
|
118
118
|
|
|
119
119
|
### Supported Build Tools
|
|
120
120
|
|
|
121
|
-
| Build Tool
|
|
122
|
-
|
|
|
123
|
-
| Vite
|
|
124
|
-
| webpack
|
|
125
|
-
|
|
|
126
|
-
|
|
|
127
|
-
|
|
|
128
|
-
|
|
|
129
|
-
|
|
|
130
|
-
|
|
|
131
|
-
|
|
|
132
|
-
|
|
|
121
|
+
| Build Tool | Import |
|
|
122
|
+
| ------------------- | ------------------------------------------------- |
|
|
123
|
+
| Vite | `import zodCompiler from "zod-compiler/vite"` |
|
|
124
|
+
| webpack | `import zodCompiler from "zod-compiler/webpack"` |
|
|
125
|
+
| Turbopack / Next.js | `loaders: ["zod-compiler/turbopack"]` |
|
|
126
|
+
| esbuild | `import zodCompiler from "zod-compiler/esbuild"` |
|
|
127
|
+
| SWC | `import zodCompiler from "zod-compiler/swc"` |
|
|
128
|
+
| Rollup | `import zodCompiler from "zod-compiler/rollup"` |
|
|
129
|
+
| Rolldown | `import zodCompiler from "zod-compiler/rolldown"` |
|
|
130
|
+
| Rsbuild | `import zodCompiler from "zod-compiler/rsbuild"` |
|
|
131
|
+
| rspack | `import zodCompiler from "zod-compiler/rspack"` |
|
|
132
|
+
| Bun | `import zodCompiler from "zod-compiler/bun"` |
|
|
133
|
+
| Farm | `import zodCompiler from "zod-compiler/farm"` |
|
|
134
|
+
|
|
135
|
+
Turbopack takes a loader rather than a plugin — see [Next.js (Turbopack)](#nextjs-turbopack).
|
|
133
136
|
|
|
134
137
|
### Options
|
|
135
138
|
|
|
@@ -210,6 +213,13 @@ Validators share a runtime helper layer imported from one module, so each helper
|
|
|
210
213
|
bundle. Schemas in a file sharing a structurally identical sub-shape emit its error walk once —
|
|
211
214
|
**19-28% raw / 10-18% gzipped**, scaling with how much the file repeats.
|
|
212
215
|
|
|
216
|
+
Build plugins serve that module from their own resolve hook — as `virtual:zod-compiler/runtime`, or
|
|
217
|
+
`__zod-compiler-runtime__` on webpack and rspack, which reject the `virtual:` scheme. A host without
|
|
218
|
+
hooks — a webpack loader such as [Turbopack](#nextjs-turbopack) — can instead import the same code
|
|
219
|
+
from `zod-compiler/runtime`, a real package subpath that plain module resolution finds. That only
|
|
220
|
+
pays off where the host bundles the import rather than leaving it external, which is why it is
|
|
221
|
+
opt-in there.
|
|
222
|
+
|
|
213
223
|
**Transpile-only esbuild builds** (no `--bundle`) never fire the bundler's resolve hooks, so the
|
|
214
224
|
`virtual:` specifier would survive into `dist/` and fail at runtime. Set `codegenMode: "inline"` to emit
|
|
215
225
|
helpers per file instead:
|
|
@@ -220,6 +230,100 @@ export default [zodCompiler({ schemas: "explicit", codegenMode: "inline" })];
|
|
|
220
230
|
|
|
221
231
|
Set `output: "bag"` to also drop the retained Zod schema when you don't need `.shape` / `instanceof`.
|
|
222
232
|
|
|
233
|
+
### Next.js (Turbopack)
|
|
234
|
+
|
|
235
|
+
Turbopack — the default bundler since Next.js 16 — [runs webpack loaders but no webpack
|
|
236
|
+
plugins](https://nextjs.org/docs/app/api-reference/turbopack#webpack-plugins), so it needs the
|
|
237
|
+
loader entry point rather than `zod-compiler/webpack`:
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
// next.config.ts
|
|
241
|
+
import type { NextConfig } from "next";
|
|
242
|
+
|
|
243
|
+
const nextConfig: NextConfig = {
|
|
244
|
+
turbopack: {
|
|
245
|
+
rules: {
|
|
246
|
+
"*.{ts,tsx}": {
|
|
247
|
+
condition: {
|
|
248
|
+
all: [
|
|
249
|
+
{ not: "foreign" }, // skip node_modules
|
|
250
|
+
{ content: /[Zz]od/ }, // skip files that cannot contain a schema
|
|
251
|
+
],
|
|
252
|
+
},
|
|
253
|
+
loaders: ["zod-compiler/turbopack"],
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
export default nextConfig;
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Pass options with the object form — `loaders: [{ loader: "zod-compiler/turbopack", options: { verbose: true } }]`.
|
|
263
|
+
Next.js serializes them into its config, so they have to be plain JSON. That rules out a RegExp
|
|
264
|
+
`hoist.schemaNamePattern` (pass the pattern as a string instead), and `apply` is Vite-only as always.
|
|
265
|
+
There is no `cache` option either: the loader keeps no disk cache of its own, because
|
|
266
|
+
Turbopack already caches loader results persistently, keyed on file content plus the dependencies
|
|
267
|
+
the loader declares. Nothing lands in `node_modules/.cache/zod-compiler`, so the CI cache step under
|
|
268
|
+
[Large projects and CI](#large-projects-and-ci) does not apply — cache `.next/cache` instead.
|
|
269
|
+
|
|
270
|
+
The two `condition` clauses are the Turbopack equivalent of the plugin's own file filters — without
|
|
271
|
+
them the loader is invoked on every `.ts` in the project. Keep the `content` pattern this loose:
|
|
272
|
+
narrowing it to `"zod"` silently skips `zod/v4`, `zod/mini` and the `zod-compiler` import behind
|
|
273
|
+
`schemas: "explicit"`, and skipped files just quietly stay uncompiled. Drop the `content` clause
|
|
274
|
+
entirely if you set a custom `hoist.schemaNamePattern`, which makes schema roots out of identifiers
|
|
275
|
+
(`UserModel`) in files that need never mention zod at all.
|
|
276
|
+
|
|
277
|
+
Set no `as` or `type` on the rule. The loader emits TypeScript and Turbopack's own SWC pass handles
|
|
278
|
+
it, so there is no second transpile and no `@swc/core` dependency.
|
|
279
|
+
|
|
280
|
+
Helpers are emitted per file by default. `codegenMode: "lean"` imports them from
|
|
281
|
+
`zod-compiler/runtime` instead — a real package subpath, resolvable without the hook a loader
|
|
282
|
+
doesn't have — so one copy is shared across every transformed file:
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
loaders: [{ loader: "zod-compiler/turbopack", options: { codegenMode: "lean" } }],
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
It is opt-in because it only holds where the host **bundles** that import. Next.js does for client
|
|
289
|
+
and App Router server code. Pages Router server code externalizes `node_modules` imports unless
|
|
290
|
+
[`bundlePagesRouterDependencies`](https://nextjs.org/docs/pages/api-reference/config/next-config-js/bundlePagesRouterDependencies)
|
|
291
|
+
is on — and `zod-compiler` is usually a devDependency, so a production install prunes it and the
|
|
292
|
+
route throws `ERR_MODULE_NOT_FOUND` on the first request, with nothing failing at build time. Use
|
|
293
|
+
lean for an App-Router-only app, or move `zod-compiler` to `dependencies`.
|
|
294
|
+
|
|
295
|
+
Expect a file reachable from both client and server components to be transformed more than once —
|
|
296
|
+
Turbopack applies loaders per output environment, and runs them in a worker pool, so the cache of
|
|
297
|
+
executed modules that makes discovery cheap is only shared within a worker. The first build of a
|
|
298
|
+
large schema set is the expensive one.
|
|
299
|
+
|
|
300
|
+
`next dev --webpack` / `next build --webpack` remain available, where `zod-compiler/webpack` applies
|
|
301
|
+
unchanged:
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
// next.config.ts — webpack only
|
|
305
|
+
import type { NextConfig } from "next";
|
|
306
|
+
import zodCompiler from "zod-compiler/webpack";
|
|
307
|
+
|
|
308
|
+
const nextConfig: NextConfig = {
|
|
309
|
+
webpack: (config) => {
|
|
310
|
+
config.plugins?.push(zodCompiler({ verbose: true }));
|
|
311
|
+
return config;
|
|
312
|
+
},
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
export default nextConfig;
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
Schemas exported from a `"use client"` module compile like any other — the generated runtime is
|
|
319
|
+
emitted below the directive so it stays the first statement. A `"use server"` file is different, and
|
|
320
|
+
not because of zod-compiler: Next.js only allows async function exports there, so a schema in one
|
|
321
|
+
has to stay inside a function, where [hoisting](#schema-hoisting) still lifts and compiles it.
|
|
322
|
+
|
|
323
|
+
Note also the React Server Components rule that a server component may not import a _value_ from a
|
|
324
|
+
`"use client"` module — that yields a client reference rather than the schema, with or without
|
|
325
|
+
zod-compiler.
|
|
326
|
+
|
|
223
327
|
### SWC
|
|
224
328
|
|
|
225
329
|
A programmatic `@swc/core` bridge wrapping `transform()`, not a `.swcrc` plugin. Install
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Generated at build time from zod-compiler's helper registries.
|
|
2
|
+
// Imported by lean-mode generated code, never written by hand — `any`
|
|
3
|
+
// because a caller would otherwise have to cast every helper to call it.
|
|
4
|
+
export declare const __zcMkv: any;
|
|
5
|
+
export declare const __zcFin: any;
|
|
6
|
+
export declare const __zcFinD: any;
|
|
7
|
+
export declare const __zcFinZ: any;
|
|
8
|
+
export declare const __zcTS: any;
|
|
9
|
+
export declare const __zcTSn: any;
|
|
10
|
+
export declare const __zcTSx: any;
|
|
11
|
+
export declare const __zcTB: any;
|
|
12
|
+
export declare const __zcTBt: any;
|
|
13
|
+
export declare const __zcTBx: any;
|
|
14
|
+
export declare const __zcIT: any;
|
|
15
|
+
export declare const __zcITc: any;
|
|
16
|
+
export declare const __zcIF: any;
|
|
17
|
+
export declare const __zcIV: any;
|
|
18
|
+
export declare const __zcUK: any;
|
|
19
|
+
export declare const __zcAb: any;
|
|
20
|
+
export declare const __zcFsr: any;
|
|
21
|
+
export declare const __zcFz: any;
|
|
22
|
+
export declare const __zcHop: any;
|
|
23
|
+
export declare const __zcLo: any;
|
|
24
|
+
export declare const __zcSo: any;
|
|
25
|
+
export declare const __zcPlain: any;
|
|
26
|
+
export declare const __zcPfx: any;
|
|
27
|
+
export declare const __zcCu: any;
|
|
28
|
+
export declare const __zcSr: any;
|
|
29
|
+
export declare const __zcSrOk: any;
|
|
30
|
+
export declare const __zcReEmail: any;
|
|
31
|
+
export declare const __zcReEmailSrc: any;
|
|
32
|
+
export declare const __zcReUuid: any;
|
|
33
|
+
export declare const __zcReUuidSrc: any;
|
|
34
|
+
export declare const __zcReCuid: any;
|
|
35
|
+
export declare const __zcReCuidSrc: any;
|
|
36
|
+
export declare const __zcReCuid2: any;
|
|
37
|
+
export declare const __zcReUlid: any;
|
|
38
|
+
export declare const __zcReUlidSrc: any;
|
|
39
|
+
export declare const __zcReNanoid: any;
|
|
40
|
+
export declare const __zcReNanoidSrc: any;
|
|
41
|
+
export declare const __zcReXid: any;
|
|
42
|
+
export declare const __zcReXidSrc: any;
|
|
43
|
+
export declare const __zcReKsuid: any;
|
|
44
|
+
export declare const __zcReKsuidSrc: any;
|
|
45
|
+
export declare const __zcReIpv4: any;
|
|
46
|
+
export declare const __zcReIpv6: any;
|
|
47
|
+
export declare const __zcReBase64: any;
|
|
48
|
+
export declare const __zcReBase64Src: any;
|
|
49
|
+
export declare const __zcReBase64Url: any;
|
|
50
|
+
export declare const __zcReE164: any;
|
|
51
|
+
export declare const __zcReE164Src: any;
|
|
52
|
+
export declare const __zcReGuid: any;
|
|
53
|
+
export declare const __zcReGuidSrc: any;
|
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { config as __zodCompilerConfig, core as __zcCore, ZodRealError as __zcZodError } from "zod";
|
|
2
|
+
function __zcUw(m){return typeof m==="string"?m:(m===undefined||m===null?undefined:m.message);}var __zcMsg=function(iss){var c=__zodCompilerConfig(),m;if(c.customError){m=__zcUw(c.customError(iss));if(m!==undefined&&m!==null)return m;}if(c.localeError){m=__zcUw(c.localeError(iss));if(m!==undefined&&m!==null)return m;}return "Invalid input";};
|
|
3
|
+
function __ZcFail(e,f,i){this.success=false;this._e=e;this._f=f;this._i=i;this._c=undefined;}Object.defineProperty(__ZcFail.prototype,"error",{configurable:true,get:function(){if(this._c)return this._c;var e=this._f!==null?this._f(this._i):this._e;for(var i=0;i<e.length;i++){if(e[i].message===undefined&&typeof __zcMsg==="function")e[i].message=__zcMsg(e[i]);delete e[i].input;delete e[i].continue;}return this._c=new __zcZodError(e);}});
|
|
4
|
+
function __ZcFailZ(z,i){this.success=false;this._z=z;this._i=i;this._c=undefined;}Object.defineProperty(__ZcFailZ.prototype,"error",{configurable:true,get:function(){return this._c||(this._c=this._z(this._i).error);}});
|
|
5
|
+
export function __zcMkv(fn,schema,fc,is){var w=schema||{};var zpa=w.parseAsync,zspa=w.safeParseAsync;w.parse=fc?function(input){if(fc(input))return input;var r=fn(input);if(r.success)return r.data;throw r.error;}:function(input){var r=fn(input);if(r.success)return r.data;throw r.error;};w.safeParse=fn;w.safeParseAsync=function(input){try{return Promise.resolve(fn(input));}catch(e){if(zspa)return zspa(input);throw e;}};w.parseAsync=fc?function(input){try{if(fc(input))return Promise.resolve(input);var r=fn(input);if(r.success)return Promise.resolve(r.data);return Promise.reject(r.error);}catch(e){if(zpa)return zpa(input);throw e;}}:function(input){try{var r=fn(input);if(r.success)return Promise.resolve(r.data);return Promise.reject(r.error);}catch(e){if(zpa)return zpa(input);throw e;}};w.is=is||function(input){return fn(input).success;};var s=w["~standard"],zv=s&&s.validate;Object.defineProperty(w,"~standard",{configurable:true,value:{version:1,vendor:(s&&s.vendor)||"zod",validate:function(input){var r;try{if(fc&&fc(input))return{value:input};r=fn(input);}catch(e){if(zv)return zv(input);throw e;}return r.success?{value:r.data}:{issues:r.error.issues};}}});return w;}
|
|
6
|
+
export function __zcFin(e,d){if(!e.length)return{success:true,data:d};return new __ZcFail(e,null,null);}
|
|
7
|
+
export function __zcFinD(f,inp){return new __ZcFail(null,f,inp);}
|
|
8
|
+
export function __zcFinZ(z,i){return new __ZcFailZ(z,i);}
|
|
9
|
+
function __zcSrRun(f,p){var r=f(p);if(r&&typeof r.then==="function"){throw new __zcCore.$ZodAsyncError();}}
|
|
10
|
+
export function __zcTS(m,o,i,inp,p,msg){var r={origin:o,code:"too_small",minimum:m,inclusive:i,input:inp,path:p};if(msg!==undefined)r.message=msg;return r;}
|
|
11
|
+
export function __zcTSn(m,o,inp,p,msg){var r={code:"too_small",minimum:m,origin:o,input:inp,path:p,continue:false};if(msg!==undefined)r.message=msg;return r;}
|
|
12
|
+
export function __zcTSx(m,o,inp,p,msg){var r={origin:o,code:"too_small",minimum:m,inclusive:true,exact:true,input:inp,path:p};if(msg!==undefined)r.message=msg;return r;}
|
|
13
|
+
export function __zcTB(m,o,i,inp,p,msg){var r={origin:o,code:"too_big",maximum:m,inclusive:i,input:inp,path:p};if(msg!==undefined)r.message=msg;return r;}
|
|
14
|
+
export function __zcTBt(m,o,inp,p,msg){var r={code:"too_big",maximum:m,inclusive:true,origin:o,input:inp,path:p,continue:false};if(msg!==undefined)r.message=msg;return r;}
|
|
15
|
+
export function __zcTBx(m,o,inp,p,msg){var r={origin:o,code:"too_big",maximum:m,inclusive:true,exact:true,input:inp,path:p};if(msg!==undefined)r.message=msg;return r;}
|
|
16
|
+
export function __zcIT(e,inp,p,msg){var r={expected:e,code:"invalid_type",input:inp,path:p};if(msg!==undefined)r.message=msg;return r;}
|
|
17
|
+
export function __zcITc(e,inp,p,msg){var r={code:"invalid_type",expected:e,input:inp,path:p};if(msg!==undefined)r.message=msg;return r;}
|
|
18
|
+
export function __zcIF(o,f,inp,p,extra,msg){var r=o===undefined?{code:"invalid_format",format:f}:{origin:o,code:"invalid_format",format:f};if(extra)Object.assign(r,extra);r.input=inp;r.path=p;if(msg!==undefined)r.message=msg;return r;}
|
|
19
|
+
export function __zcIV(values,inp,p,extra,msg){var r={code:"invalid_value"};if(extra)Object.assign(r,extra);r.values=values;r.input=inp;r.path=p;if(msg!==undefined)r.message=msg;return r;}
|
|
20
|
+
export function __zcUK(k,inp,p,msg){var r={code:"unrecognized_keys",keys:k,input:inp,path:p};if(msg!==undefined)r.message=msg;return r;}
|
|
21
|
+
export function __zcAb(e,i){for(;i<e.length;i++){var s=e[i];if(s.continue===false)return true;var c=s.code;if(c==="invalid_type"||c==="invalid_value"||c==="invalid_union"||c==="unrecognized_keys"||c==="invalid_key"||c==="invalid_element")return true;}return false;}
|
|
22
|
+
export function __zcFsr(v,s){var vd=((""+v).split(".")[1]||"").length;var ss=""+s;var sd=(ss.split(".")[1]||"").length;if(sd===0&&/\d?e-\d?/.test(ss)){var m=ss.match(/\d?e-(\d?)/);if(m&&m[1]){sd=parseInt(m[1],10);}}var d=vd>sd?vd:sd;var vi=parseInt(v.toFixed(d).replace(".",""),10);var si=parseInt(s.toFixed(d).replace(".",""),10);return (vi%si)/Math.pow(10,d);}
|
|
23
|
+
export function __zcFz(e){for(var i=0;i<e.length;i++){var s=e[i];if(s.message===undefined&&typeof __zcMsg==="function")s.message=__zcMsg(s);delete s.input;delete s.continue;}return e;}
|
|
24
|
+
export const __zcHop=Object.prototype.hasOwnProperty;
|
|
25
|
+
export function __zcLo(v){return Array.isArray(v)?"array":typeof v==="string"?"string":"unknown";}
|
|
26
|
+
export function __zcSo(v){return v instanceof Set?"set":v instanceof Map?"map":(typeof File!=="undefined"&&v instanceof File)?"file":"unknown";}
|
|
27
|
+
export function __zcPlain(o){if(typeof o!=="object"||o===null||Array.isArray(o))return false;var c=o.constructor;if(c===undefined||typeof c!=="function")return true;var p=c.prototype;if(typeof p!=="object"||p===null||Array.isArray(p))return false;return Object.prototype.hasOwnProperty.call(p,"isPrototypeOf");}
|
|
28
|
+
export function __zcPfx(d,s,b,k){for(var i=0;i<s.length;i++){var x=s[i];x.path=b.concat(k,x.path);d.push(x);}}
|
|
29
|
+
export function __zcCu(f,v){var r=f(v);if(r&&typeof r.then==="function"){throw new __zcCore.$ZodAsyncError();}return !!r;}
|
|
30
|
+
export function __zcSr(f,v,p,e){var q={value:v,issues:[]};__zcSrRun(f,q);for(var i=0;i<q.issues.length;i++){var s=q.issues[i],t={};for(var k in s){if(k!=="inst"&&k!=="continue")t[k]=s[k];}if(s.continue!==true)q.aborted=true;t.path=s.path&&s.path.length?p.concat(s.path):p;e.push(t);}return q;}
|
|
31
|
+
export function __zcSrOk(f,v){var p={value:v,issues:[]};__zcSrRun(f,p);return p.issues.length===0&&p.value===v;}
|
|
32
|
+
export const __zcReEmail=new RegExp("^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$");
|
|
33
|
+
export const __zcReEmailSrc="/^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$/";
|
|
34
|
+
export const __zcReUuid=new RegExp("^([0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]-[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$");
|
|
35
|
+
export const __zcReUuidSrc="/^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/";
|
|
36
|
+
export const __zcReCuid=new RegExp("^[cC][^\\s-][^\\s-][^\\s-][^\\s-][^\\s-][^\\s-][^\\s-][^\\s-][^\\s-]*$");
|
|
37
|
+
export const __zcReCuidSrc="/^[cC][^\\s-]{8,}$/";
|
|
38
|
+
export const __zcReCuid2=new RegExp("^[0-9a-z]+$");
|
|
39
|
+
export const __zcReUlid=new RegExp("^[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]$");
|
|
40
|
+
export const __zcReUlidSrc="/^[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$/";
|
|
41
|
+
export const __zcReNanoid=new RegExp("^[a-zA-Z0-9_-][a-zA-Z0-9_-][a-zA-Z0-9_-][a-zA-Z0-9_-][a-zA-Z0-9_-][a-zA-Z0-9_-][a-zA-Z0-9_-][a-zA-Z0-9_-][a-zA-Z0-9_-][a-zA-Z0-9_-][a-zA-Z0-9_-][a-zA-Z0-9_-][a-zA-Z0-9_-][a-zA-Z0-9_-][a-zA-Z0-9_-][a-zA-Z0-9_-][a-zA-Z0-9_-][a-zA-Z0-9_-][a-zA-Z0-9_-][a-zA-Z0-9_-][a-zA-Z0-9_-]$");
|
|
42
|
+
export const __zcReNanoidSrc="/^[a-zA-Z0-9_-]{21}$/";
|
|
43
|
+
export const __zcReXid=new RegExp("^[0-9a-vA-V][0-9a-vA-V][0-9a-vA-V][0-9a-vA-V][0-9a-vA-V][0-9a-vA-V][0-9a-vA-V][0-9a-vA-V][0-9a-vA-V][0-9a-vA-V][0-9a-vA-V][0-9a-vA-V][0-9a-vA-V][0-9a-vA-V][0-9a-vA-V][0-9a-vA-V][0-9a-vA-V][0-9a-vA-V][0-9a-vA-V][0-9a-vA-V]$");
|
|
44
|
+
export const __zcReXidSrc="/^[0-9a-vA-V]{20}$/";
|
|
45
|
+
export const __zcReKsuid=new RegExp("^[A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9][A-Za-z0-9]$");
|
|
46
|
+
export const __zcReKsuidSrc="/^[A-Za-z0-9]{27}$/";
|
|
47
|
+
export const __zcReIpv4=new RegExp("^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$");
|
|
48
|
+
export const __zcReIpv6=new RegExp("^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$");
|
|
49
|
+
export const __zcReBase64=new RegExp("^$|^(?:[0-9a-zA-Z+/][0-9a-zA-Z+/][0-9a-zA-Z+/][0-9a-zA-Z+/])*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$");
|
|
50
|
+
export const __zcReBase64Src="/^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/";
|
|
51
|
+
export const __zcReBase64Url=new RegExp("^[A-Za-z0-9_-]*$");
|
|
52
|
+
export const __zcReE164=new RegExp("^\\+[1-9]\\d\\d\\d\\d\\d\\d\\d?\\d?\\d?\\d?\\d?\\d?\\d?\\d?$");
|
|
53
|
+
export const __zcReE164Src="/^\\+[1-9]\\d{6,14}$/";
|
|
54
|
+
export const __zcReGuid=new RegExp("^([0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]-[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]-[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]-[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]-[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])$");
|
|
55
|
+
export const __zcReGuidSrc="/^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$/";
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { ZodCompilerPluginOptions } from "./unplugin/types.js";
|
|
2
|
+
import { TransformSourceMap } from "./unplugin/transform.js";
|
|
3
|
+
//#region src/turbopack.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Plugin options minus the ones a loader host cannot express.
|
|
6
|
+
*
|
|
7
|
+
* Turbopack serializes loader options through `next.config`, so they have to be
|
|
8
|
+
* plain JSON — hence no `apply` (a Vite lifecycle function) and a string-only
|
|
9
|
+
* `schemaNamePattern` where the plugin also accepts a RegExp.
|
|
10
|
+
*
|
|
11
|
+
* No `cache` either: the disk cache's dependency bookkeeping leans on a
|
|
12
|
+
* `buildEnd` flush that a loader has no equivalent for, and loader hosts keep
|
|
13
|
+
* their own persistent result cache — Turbopack's is keyed on content plus the
|
|
14
|
+
* dependencies declared below, which is what this would have re-implemented.
|
|
15
|
+
*/
|
|
16
|
+
type ZodCompilerTurbopackOptions = Omit<ZodCompilerPluginOptions, "apply" | "cache" | "codegenMode" | "hoist"> & {
|
|
17
|
+
hoist?: boolean | {
|
|
18
|
+
schemaNamePattern?: string | null | undefined;
|
|
19
|
+
} | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* `"inline"` (default) emits the shared helpers into every transformed file.
|
|
22
|
+
*
|
|
23
|
+
* `"lean"` imports them from `zod-compiler/runtime` instead, so a bundle
|
|
24
|
+
* carries one copy however many files were transformed. That specifier is a
|
|
25
|
+
* real package subpath, which is what makes it usable from a loader at all —
|
|
26
|
+
* the `virtual:` id the build plugins emit needs a `resolveId` hook, and a
|
|
27
|
+
* loader has none.
|
|
28
|
+
*
|
|
29
|
+
* Opt-in rather than the default because it only holds when the host BUNDLES
|
|
30
|
+
* the import. Next.js does for client and App Router server code, but Pages
|
|
31
|
+
* Router server code externalizes node_modules imports unless
|
|
32
|
+
* `bundlePagesRouterDependencies` is set — and `zod-compiler` is normally a
|
|
33
|
+
* devDependency, so a production install prunes it and the route throws
|
|
34
|
+
* ERR_MODULE_NOT_FOUND on the first request. A bigger bundle is the better
|
|
35
|
+
* default than a runtime failure that no build step reports.
|
|
36
|
+
* @default "inline"
|
|
37
|
+
*/
|
|
38
|
+
codegenMode?: "lean" | "inline" | undefined;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* The slice of webpack's loader context this uses. Structural rather than
|
|
42
|
+
* imported from webpack: the package must not take a webpack dependency to
|
|
43
|
+
* serve a host that is not webpack.
|
|
44
|
+
*/
|
|
45
|
+
interface ZodCompilerLoaderContext {
|
|
46
|
+
resourcePath: string;
|
|
47
|
+
async(): (error: Error | null, code?: string, map?: TransformSourceMap) => void;
|
|
48
|
+
getOptions?(): ZodCompilerTurbopackOptions | undefined;
|
|
49
|
+
addDependency?(file: string): void;
|
|
50
|
+
cacheable?(flag: boolean): void;
|
|
51
|
+
query?: unknown;
|
|
52
|
+
}
|
|
53
|
+
declare function zodCompilerLoader(this: ZodCompilerLoaderContext, source: string, inputMap?: TransformSourceMap): void;
|
|
54
|
+
//#endregion
|
|
55
|
+
export { ZodCompilerLoaderContext, ZodCompilerTurbopackOptions, zodCompilerLoader as default };
|
|
56
|
+
//# sourceMappingURL=turbopack.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"turbopack.d.ts","names":[],"sources":["../src/turbopack.ts"],"mappings":";;;;;;;;;;;;;;;KA+DY,8BAA8B,KACxC;EAGA;IAAoB;;;;;;;;;;;;;;;;;;;;EAmBpB;;;;;;;UAQe;EACf;EACA,UAAU,OAAO,cAAc,eAAe,MAAM;EACpD,eAAe;EACf,eAAe;EACf,WAAW;EACX;;iBAwMsB,kBACtB,MAAM,0BACN,gBACA,WAAW"}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { getFirstPartyModulePaths, invalidateModuleCache } from "./loader.js";
|
|
2
|
+
import { RUNTIME_PACKAGE_ID } from "./unplugin/virtual.js";
|
|
3
|
+
import { log, shouldTransform, transformCodeWithMap } from "./unplugin/transform.js";
|
|
4
|
+
import { resetDepGraphMemo, transformDependencies } from "./unplugin/dep-graph.js";
|
|
5
|
+
import remapping from "@jridgewell/remapping";
|
|
6
|
+
import fs from "node:fs";
|
|
7
|
+
//#region src/turbopack.ts
|
|
8
|
+
/**
|
|
9
|
+
* Webpack-loader entry point, for Turbopack (Next.js) and any other host that
|
|
10
|
+
* runs webpack loaders but not webpack plugins.
|
|
11
|
+
*
|
|
12
|
+
* Turbopack deliberately supports no webpack plugins, so the unplugin build
|
|
13
|
+
* plugins cannot reach it. Loaders it does run, through the real `loader-runner`
|
|
14
|
+
* library, and that is enough: this module is the whole zod-compiler transform
|
|
15
|
+
* behind `this.async()`.
|
|
16
|
+
*
|
|
17
|
+
* It emits TypeScript, not JavaScript. A `turbopack.rules` entry that sets no
|
|
18
|
+
* `as`/`type` leaves the loader's output to be parsed as whatever the file
|
|
19
|
+
* already was, so Turbopack's own SWC pass handles the syntax — there is no
|
|
20
|
+
* second transpile here and no @swc/core dependency.
|
|
21
|
+
*
|
|
22
|
+
* `codegenMode: "lean"` is available here — it imports the shared helpers from
|
|
23
|
+
* `zod-compiler/runtime`, a real package subpath, rather than the `virtual:` id
|
|
24
|
+
* the build plugins answer from a resolve hook that a loader does not have. It
|
|
25
|
+
* is opt-in; see the option's doc for why.
|
|
26
|
+
*
|
|
27
|
+
* // next.config.ts
|
|
28
|
+
* export default {
|
|
29
|
+
* turbopack: {
|
|
30
|
+
* rules: {
|
|
31
|
+
* "*.{ts,tsx}": {
|
|
32
|
+
* condition: { all: [{ not: "foreign" }, { content: /[Zz]od/ }] },
|
|
33
|
+
* loaders: ["zod-compiler/turbopack"],
|
|
34
|
+
* },
|
|
35
|
+
* },
|
|
36
|
+
* },
|
|
37
|
+
* };
|
|
38
|
+
*
|
|
39
|
+
* The `content` condition is the Turbopack equivalent of the plugins' own code
|
|
40
|
+
* filter and must stay as loose as ZOD_MENTION: narrowing it to the literal
|
|
41
|
+
* specifier `"zod"` would silently skip `zod/v4`, `zod/mini` and the
|
|
42
|
+
* `zod-compiler` import that drives `schemas: "explicit"` — no error, those
|
|
43
|
+
* schemas just quietly stay uncompiled.
|
|
44
|
+
*/
|
|
45
|
+
/**
|
|
46
|
+
* Disk stamps of every module the shared execution cache is currently holding.
|
|
47
|
+
*
|
|
48
|
+
* Discovery executes schema files from DISK through a module cache that outlives
|
|
49
|
+
* loader calls. The bundler plugins evict it from `watchChange`; a loader has no
|
|
50
|
+
* such hook, so staleness has to be detected here or compiled validators keep
|
|
51
|
+
* reflecting whatever the files said when they were first executed.
|
|
52
|
+
*
|
|
53
|
+
* Diffing the entry's own content is NOT enough, and the gap is exactly the case
|
|
54
|
+
* `addDependency` exists to handle: when an imported constant changes, the host
|
|
55
|
+
* re-runs the loader for a schema file whose content is UNCHANGED.
|
|
56
|
+
*
|
|
57
|
+
* Tracked GLOBALLY rather than per file, because that is what it describes: one
|
|
58
|
+
* process-wide module cache, stale the moment any file behind it changes. A
|
|
59
|
+
* per-file dependency list cannot express that — a file being transformed for
|
|
60
|
+
* the FIRST time has no list yet, but the cache it is about to read from is
|
|
61
|
+
* already warm and may already be stale. Keying on the cache's own inventory
|
|
62
|
+
* also means one edit costs one eviction rather than one per dependent file.
|
|
63
|
+
*
|
|
64
|
+
* `getFirstPartyModulePaths()` is that inventory, which is why it is sound here
|
|
65
|
+
* while being unsound as a per-file dependency list (see transformDependencies):
|
|
66
|
+
* the question is "what is cached", not "what does this file need". It covers
|
|
67
|
+
* dependencies the host never feeds through this loader at all — a constants
|
|
68
|
+
* file that never mentions zod, or one a rule's `exclude` skips — but only the
|
|
69
|
+
* ones jiti holds, so the two known gaps are its gaps:
|
|
70
|
+
*
|
|
71
|
+
* - `.js`/`.mjs` deps go through native `import()` (see loader.ts), which has no
|
|
72
|
+
* evictable cache. The build plugins are equally stale there; on Bun and Deno
|
|
73
|
+
* that is every module, and this whole mechanism is inert.
|
|
74
|
+
* - A file being CREATED changes nothing's stamp, so a new module that shadows
|
|
75
|
+
* an existing resolution (`limits.ts` beside `limits/index.ts`) keeps serving
|
|
76
|
+
* the old one until some stamped file also changes. `watchChange` gives the
|
|
77
|
+
* plugins a signal for this that a loader host has no equivalent of.
|
|
78
|
+
*
|
|
79
|
+
* mtime+size like the disk cache's fast path, minus its content hashes: those
|
|
80
|
+
* exist to survive checkouts across processes, and this map dies with the
|
|
81
|
+
* process.
|
|
82
|
+
*/
|
|
83
|
+
const executedModuleStamps = /* @__PURE__ */ new Map();
|
|
84
|
+
function stampOf(file) {
|
|
85
|
+
try {
|
|
86
|
+
const stat = fs.statSync(file, { throwIfNoEntry: false });
|
|
87
|
+
return stat === void 0 ? "" : `${stat.mtimeMs}:${stat.size}`;
|
|
88
|
+
} catch {
|
|
89
|
+
return "";
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Drop cached module executions when any file behind them changed on disk.
|
|
94
|
+
*
|
|
95
|
+
* Eviction is global (see invalidateModuleCache), so it is deliberately paired
|
|
96
|
+
* with the dep-graph memos the plugin also resets in `watchChange`: a resolution
|
|
97
|
+
* memoized before a file existed would otherwise pin that importer to an
|
|
98
|
+
* unanalyzable graph for the life of the process.
|
|
99
|
+
*
|
|
100
|
+
* Costs one stat per executed module, paid by every file that reaches the
|
|
101
|
+
* transform — ~3 ms per file against the 1,900-module project dep-graph.ts
|
|
102
|
+
* cites, the same order as the closure walk `transformDependencies` does a few
|
|
103
|
+
* lines later, and far below the discovery it is protecting.
|
|
104
|
+
*/
|
|
105
|
+
function invalidateStaleExecutions() {
|
|
106
|
+
let stale = false;
|
|
107
|
+
for (const [file, stamp] of executedModuleStamps) if (stampOf(file) !== stamp) {
|
|
108
|
+
stale = true;
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
if (!stale) return;
|
|
112
|
+
invalidateModuleCache();
|
|
113
|
+
resetDepGraphMemo();
|
|
114
|
+
executedModuleStamps.clear();
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Stamp whatever discovery just executed.
|
|
118
|
+
*
|
|
119
|
+
* Keeps the FIRST stamp for a file rather than refreshing: a file edited between
|
|
120
|
+
* its execution and this call must keep its pre-edit stamp, or the next run
|
|
121
|
+
* would see it as fresh and pin the stale execution permanently.
|
|
122
|
+
*
|
|
123
|
+
* `null` means no jiti instance exists — a runtime whose module cache cannot be
|
|
124
|
+
* evicted at all (loader.ts), so there is nothing to track and nothing to fix.
|
|
125
|
+
*/
|
|
126
|
+
function recordExecutedModules() {
|
|
127
|
+
for (const file of getFirstPartyModulePaths() ?? []) if (!executedModuleStamps.has(file)) executedModuleStamps.set(file, stampOf(file));
|
|
128
|
+
}
|
|
129
|
+
function readOptions(context) {
|
|
130
|
+
const options = context.getOptions?.();
|
|
131
|
+
if (options !== void 0) return options;
|
|
132
|
+
return typeof context.query === "object" && context.query !== null ? context.query : {};
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Declare what a rebuild must watch. The host caches this file's output keyed on
|
|
136
|
+
* its own content, but discovery executed the whole import graph — without these
|
|
137
|
+
* an edit to an imported constant leaves a stale validator in the bundle.
|
|
138
|
+
*/
|
|
139
|
+
function declareDependencies(context, id, verbose) {
|
|
140
|
+
const { files, complete } = transformDependencies(id);
|
|
141
|
+
for (const file of files) context.addDependency?.(file);
|
|
142
|
+
if (complete) return;
|
|
143
|
+
context.cacheable?.(false);
|
|
144
|
+
if (verbose) log(`Cannot analyze the import graph of ${id} — re-running its transform every build`);
|
|
145
|
+
}
|
|
146
|
+
/** The transform itself, split out so the loader shell stays callback-only. */
|
|
147
|
+
async function run(context, source, inputMap) {
|
|
148
|
+
const id = context.resourcePath;
|
|
149
|
+
const options = readOptions(context);
|
|
150
|
+
if (!shouldTransform(id, options)) return {
|
|
151
|
+
code: source,
|
|
152
|
+
map: inputMap
|
|
153
|
+
};
|
|
154
|
+
invalidateStaleExecutions();
|
|
155
|
+
const output = options.output ?? "schema";
|
|
156
|
+
let discoveryRan = false;
|
|
157
|
+
const result = await transformCodeWithMap(source, id, {
|
|
158
|
+
mode: options.codegenMode ?? "inline",
|
|
159
|
+
runtimeId: RUNTIME_PACKAGE_ID,
|
|
160
|
+
verbose: options.verbose,
|
|
161
|
+
zodCompat: output === "schema" || output === "compact",
|
|
162
|
+
compact: output === "compact",
|
|
163
|
+
autoDiscover: (options.schemas ?? "auto") === "auto",
|
|
164
|
+
hoist: options.hoist,
|
|
165
|
+
onDiscovery: () => {
|
|
166
|
+
discoveryRan = true;
|
|
167
|
+
},
|
|
168
|
+
onUncacheableResult: () => {
|
|
169
|
+
context.cacheable?.(false);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
if (discoveryRan) {
|
|
173
|
+
recordExecutedModules();
|
|
174
|
+
declareDependencies(context, id, options.verbose === true);
|
|
175
|
+
}
|
|
176
|
+
if (result === null) return {
|
|
177
|
+
code: source,
|
|
178
|
+
map: inputMap
|
|
179
|
+
};
|
|
180
|
+
return {
|
|
181
|
+
code: result.code,
|
|
182
|
+
map: composeMaps(result.map, inputMap)
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/** Chain this transform's map onto an earlier loader's, newest first. */
|
|
186
|
+
function composeMaps(map, inputMap) {
|
|
187
|
+
if (map === null) return inputMap;
|
|
188
|
+
if (inputMap === void 0) return map;
|
|
189
|
+
return remapping([map, inputMap], () => null);
|
|
190
|
+
}
|
|
191
|
+
function zodCompilerLoader(source, inputMap) {
|
|
192
|
+
const callback = this.async();
|
|
193
|
+
const succeed = (result) => callback(null, result.code, result.map);
|
|
194
|
+
const fail = (error) => callback(error instanceof Error ? error : new Error(String(error)));
|
|
195
|
+
run(this, source, inputMap).then(succeed, fail);
|
|
196
|
+
}
|
|
197
|
+
//#endregion
|
|
198
|
+
export { zodCompilerLoader as default };
|
|
199
|
+
|
|
200
|
+
//# sourceMappingURL=turbopack.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"turbopack.js","names":[],"sources":["../src/turbopack.ts"],"sourcesContent":["/**\n * Webpack-loader entry point, for Turbopack (Next.js) and any other host that\n * runs webpack loaders but not webpack plugins.\n *\n * Turbopack deliberately supports no webpack plugins, so the unplugin build\n * plugins cannot reach it. Loaders it does run, through the real `loader-runner`\n * library, and that is enough: this module is the whole zod-compiler transform\n * behind `this.async()`.\n *\n * It emits TypeScript, not JavaScript. A `turbopack.rules` entry that sets no\n * `as`/`type` leaves the loader's output to be parsed as whatever the file\n * already was, so Turbopack's own SWC pass handles the syntax — there is no\n * second transpile here and no @swc/core dependency.\n *\n * `codegenMode: \"lean\"` is available here — it imports the shared helpers from\n * `zod-compiler/runtime`, a real package subpath, rather than the `virtual:` id\n * the build plugins answer from a resolve hook that a loader does not have. It\n * is opt-in; see the option's doc for why.\n *\n * // next.config.ts\n * export default {\n * turbopack: {\n * rules: {\n * \"*.{ts,tsx}\": {\n * condition: { all: [{ not: \"foreign\" }, { content: /[Zz]od/ }] },\n * loaders: [\"zod-compiler/turbopack\"],\n * },\n * },\n * },\n * };\n *\n * The `content` condition is the Turbopack equivalent of the plugins' own code\n * filter and must stay as loose as ZOD_MENTION: narrowing it to the literal\n * specifier `\"zod\"` would silently skip `zod/v4`, `zod/mini` and the\n * `zod-compiler` import that drives `schemas: \"explicit\"` — no error, those\n * schemas just quietly stay uncompiled.\n */\n\nimport fs from \"node:fs\";\nimport remapping from \"@jridgewell/remapping\";\nimport { getFirstPartyModulePaths, invalidateModuleCache } from \"./loader.js\";\nimport { resetDepGraphMemo, transformDependencies } from \"./unplugin/dep-graph.js\";\nimport {\n log,\n shouldTransform,\n type TransformSourceMap,\n transformCodeWithMap,\n} from \"./unplugin/transform.js\";\nimport type { ZodCompilerPluginOptions } from \"./unplugin/types.js\";\nimport { RUNTIME_PACKAGE_ID } from \"./unplugin/virtual.js\";\n\n/**\n * Plugin options minus the ones a loader host cannot express.\n *\n * Turbopack serializes loader options through `next.config`, so they have to be\n * plain JSON — hence no `apply` (a Vite lifecycle function) and a string-only\n * `schemaNamePattern` where the plugin also accepts a RegExp.\n *\n * No `cache` either: the disk cache's dependency bookkeeping leans on a\n * `buildEnd` flush that a loader has no equivalent for, and loader hosts keep\n * their own persistent result cache — Turbopack's is keyed on content plus the\n * dependencies declared below, which is what this would have re-implemented.\n */\nexport type ZodCompilerTurbopackOptions = Omit<\n ZodCompilerPluginOptions,\n \"apply\" | \"cache\" | \"codegenMode\" | \"hoist\"\n> & {\n hoist?: boolean | { schemaNamePattern?: string | null | undefined } | undefined;\n /**\n * `\"inline\"` (default) emits the shared helpers into every transformed file.\n *\n * `\"lean\"` imports them from `zod-compiler/runtime` instead, so a bundle\n * carries one copy however many files were transformed. That specifier is a\n * real package subpath, which is what makes it usable from a loader at all —\n * the `virtual:` id the build plugins emit needs a `resolveId` hook, and a\n * loader has none.\n *\n * Opt-in rather than the default because it only holds when the host BUNDLES\n * the import. Next.js does for client and App Router server code, but Pages\n * Router server code externalizes node_modules imports unless\n * `bundlePagesRouterDependencies` is set — and `zod-compiler` is normally a\n * devDependency, so a production install prunes it and the route throws\n * ERR_MODULE_NOT_FOUND on the first request. A bigger bundle is the better\n * default than a runtime failure that no build step reports.\n * @default \"inline\"\n */\n codegenMode?: \"lean\" | \"inline\" | undefined;\n};\n\n/**\n * The slice of webpack's loader context this uses. Structural rather than\n * imported from webpack: the package must not take a webpack dependency to\n * serve a host that is not webpack.\n */\nexport interface ZodCompilerLoaderContext {\n resourcePath: string;\n async(): (error: Error | null, code?: string, map?: TransformSourceMap) => void;\n getOptions?(): ZodCompilerTurbopackOptions | undefined;\n addDependency?(file: string): void;\n cacheable?(flag: boolean): void;\n query?: unknown;\n}\n\n/**\n * Disk stamps of every module the shared execution cache is currently holding.\n *\n * Discovery executes schema files from DISK through a module cache that outlives\n * loader calls. The bundler plugins evict it from `watchChange`; a loader has no\n * such hook, so staleness has to be detected here or compiled validators keep\n * reflecting whatever the files said when they were first executed.\n *\n * Diffing the entry's own content is NOT enough, and the gap is exactly the case\n * `addDependency` exists to handle: when an imported constant changes, the host\n * re-runs the loader for a schema file whose content is UNCHANGED.\n *\n * Tracked GLOBALLY rather than per file, because that is what it describes: one\n * process-wide module cache, stale the moment any file behind it changes. A\n * per-file dependency list cannot express that — a file being transformed for\n * the FIRST time has no list yet, but the cache it is about to read from is\n * already warm and may already be stale. Keying on the cache's own inventory\n * also means one edit costs one eviction rather than one per dependent file.\n *\n * `getFirstPartyModulePaths()` is that inventory, which is why it is sound here\n * while being unsound as a per-file dependency list (see transformDependencies):\n * the question is \"what is cached\", not \"what does this file need\". It covers\n * dependencies the host never feeds through this loader at all — a constants\n * file that never mentions zod, or one a rule's `exclude` skips — but only the\n * ones jiti holds, so the two known gaps are its gaps:\n *\n * - `.js`/`.mjs` deps go through native `import()` (see loader.ts), which has no\n * evictable cache. The build plugins are equally stale there; on Bun and Deno\n * that is every module, and this whole mechanism is inert.\n * - A file being CREATED changes nothing's stamp, so a new module that shadows\n * an existing resolution (`limits.ts` beside `limits/index.ts`) keeps serving\n * the old one until some stamped file also changes. `watchChange` gives the\n * plugins a signal for this that a loader host has no equivalent of.\n *\n * mtime+size like the disk cache's fast path, minus its content hashes: those\n * exist to survive checkouts across processes, and this map dies with the\n * process.\n */\nconst executedModuleStamps = new Map<string, string>();\n\nfunction stampOf(file: string): string {\n try {\n const stat = fs.statSync(file, { throwIfNoEntry: false });\n return stat === undefined ? \"\" : `${stat.mtimeMs}:${stat.size}`;\n } catch {\n // Non-ENOENT (EACCES, ELOOP, ENOTDIR): unreadable is indistinguishable from\n // changed, and must never fail the build.\n return \"\";\n }\n}\n\n/**\n * Drop cached module executions when any file behind them changed on disk.\n *\n * Eviction is global (see invalidateModuleCache), so it is deliberately paired\n * with the dep-graph memos the plugin also resets in `watchChange`: a resolution\n * memoized before a file existed would otherwise pin that importer to an\n * unanalyzable graph for the life of the process.\n *\n * Costs one stat per executed module, paid by every file that reaches the\n * transform — ~3 ms per file against the 1,900-module project dep-graph.ts\n * cites, the same order as the closure walk `transformDependencies` does a few\n * lines later, and far below the discovery it is protecting.\n */\nfunction invalidateStaleExecutions(): void {\n let stale = false;\n for (const [file, stamp] of executedModuleStamps) {\n if (stampOf(file) !== stamp) {\n stale = true;\n break;\n }\n }\n if (!stale) return;\n invalidateModuleCache();\n resetDepGraphMemo();\n executedModuleStamps.clear();\n}\n\n/**\n * Stamp whatever discovery just executed.\n *\n * Keeps the FIRST stamp for a file rather than refreshing: a file edited between\n * its execution and this call must keep its pre-edit stamp, or the next run\n * would see it as fresh and pin the stale execution permanently.\n *\n * `null` means no jiti instance exists — a runtime whose module cache cannot be\n * evicted at all (loader.ts), so there is nothing to track and nothing to fix.\n */\nfunction recordExecutedModules(): void {\n for (const file of getFirstPartyModulePaths() ?? []) {\n if (!executedModuleStamps.has(file)) executedModuleStamps.set(file, stampOf(file));\n }\n}\n\nfunction readOptions(context: ZodCompilerLoaderContext): ZodCompilerTurbopackOptions {\n const options = context.getOptions?.();\n if (options !== undefined) return options;\n // `getOptions` is standard but not universal; `query` is the older shape.\n return typeof context.query === \"object\" && context.query !== null\n ? (context.query as ZodCompilerTurbopackOptions)\n : {};\n}\n\n/**\n * Declare what a rebuild must watch. The host caches this file's output keyed on\n * its own content, but discovery executed the whole import graph — without these\n * an edit to an imported constant leaves a stale validator in the bundle.\n */\nfunction declareDependencies(\n context: ZodCompilerLoaderContext,\n id: string,\n verbose: boolean,\n): void {\n const { files, complete } = transformDependencies(id);\n for (const file of files) context.addDependency?.(file);\n if (complete) return;\n // The graph could not be analyzed (a non-literal dynamic import anywhere in\n // the closure), so the list above is just this file — not enough for the host\n // to know when to re-run us. Asking to be re-run every build is the only\n // answer left. Freshness itself does not depend on this: whether the loader\n // is re-invoked once or every time, invalidateStaleExecutions decides what\n // discovery may reuse.\n context.cacheable?.(false);\n if (verbose) {\n log(`Cannot analyze the import graph of ${id} — re-running its transform every build`);\n }\n}\n\ninterface LoaderResult {\n code: string;\n map?: TransformSourceMap | undefined;\n}\n\n/** The transform itself, split out so the loader shell stays callback-only. */\nasync function run(\n context: ZodCompilerLoaderContext,\n source: string,\n inputMap: TransformSourceMap | undefined,\n): Promise<LoaderResult> {\n const id = context.resourcePath;\n const options = readOptions(context);\n\n if (!shouldTransform(id, options)) return { code: source, map: inputMap };\n\n // Before discovery can read a stale execution. Note this covers an edit to\n // THIS file too: discovery executed it, so it carries a stamp of its own —\n // there is no separate host-content diff, which would only ever fire for\n // content that differs from the disk discovery actually reads.\n invalidateStaleExecutions();\n\n const output = options.output ?? \"schema\";\n let discoveryRan = false;\n const result = await transformCodeWithMap(source, id, {\n mode: options.codegenMode ?? \"inline\",\n runtimeId: RUNTIME_PACKAGE_ID,\n verbose: options.verbose,\n // \"compact\" keeps the Zod schema (its safeParse IS the cold error path), so\n // only \"bag\" drops Zod compatibility.\n zodCompat: output === \"schema\" || output === \"compact\",\n compact: output === \"compact\",\n autoDiscover: (options.schemas ?? \"auto\") === \"auto\",\n hoist: options.hoist,\n onDiscovery: () => {\n discoveryRan = true;\n },\n onUncacheableResult: () => {\n // Discovery recovered from a process.exit (an env guard in a secret-less\n // build): the result is a function of the ENVIRONMENT, not the file, so it\n // must not be cached against this content.\n context.cacheable?.(false);\n },\n });\n\n // Only discovery reads other files. A hoist-only or bailed-out transform is a\n // pure function of this file's content, which the host already keys on.\n if (discoveryRan) {\n recordExecutedModules();\n declareDependencies(context, id, options.verbose === true);\n }\n\n if (result === null) return { code: source, map: inputMap };\n return { code: result.code, map: composeMaps(result.map, inputMap) };\n}\n\n/** Chain this transform's map onto an earlier loader's, newest first. */\nfunction composeMaps(\n map: TransformSourceMap | null,\n inputMap: TransformSourceMap | undefined,\n): TransformSourceMap | undefined {\n if (map === null) return inputMap;\n if (inputMap === undefined) return map;\n return remapping(\n [map, inputMap] as Parameters<typeof remapping>[0],\n () => null,\n ) as unknown as TransformSourceMap;\n}\n\nexport default function zodCompilerLoader(\n this: ZodCompilerLoaderContext,\n source: string,\n inputMap?: TransformSourceMap,\n): void {\n const callback = this.async();\n const succeed = (result: LoaderResult): void => callback(null, result.code, result.map);\n const fail = (error: unknown): void =>\n callback(error instanceof Error ? error : new Error(String(error)));\n\n // Two `then` handlers rather than one plus a try/catch: loader-runner invokes\n // the host's continuation synchronously from `callback`, so a throw inside it\n // would re-enter a catch and call back a second time. As rejection handlers of\n // the SAME promise, exactly one of these can ever run.\n run(this, source, inputMap).then(succeed, fail);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6IA,MAAM,uCAAuB,IAAI,IAAoB;AAErD,SAAS,QAAQ,MAAsB;CACrC,IAAI;EACF,MAAM,OAAO,GAAG,SAAS,MAAM,EAAE,gBAAgB,MAAM,CAAC;EACxD,OAAO,SAAS,KAAA,IAAY,KAAK,GAAG,KAAK,QAAQ,GAAG,KAAK;CAC3D,QAAQ;EAGN,OAAO;CACT;AACF;;;;;;;;;;;;;;AAeA,SAAS,4BAAkC;CACzC,IAAI,QAAQ;CACZ,KAAK,MAAM,CAAC,MAAM,UAAU,sBAC1B,IAAI,QAAQ,IAAI,MAAM,OAAO;EAC3B,QAAQ;EACR;CACF;CAEF,IAAI,CAAC,OAAO;CACZ,sBAAsB;CACtB,kBAAkB;CAClB,qBAAqB,MAAM;AAC7B;;;;;;;;;;;AAYA,SAAS,wBAA8B;CACrC,KAAK,MAAM,QAAQ,yBAAyB,KAAK,CAAC,GAChD,IAAI,CAAC,qBAAqB,IAAI,IAAI,GAAG,qBAAqB,IAAI,MAAM,QAAQ,IAAI,CAAC;AAErF;AAEA,SAAS,YAAY,SAAgE;CACnF,MAAM,UAAU,QAAQ,aAAa;CACrC,IAAI,YAAY,KAAA,GAAW,OAAO;CAElC,OAAO,OAAO,QAAQ,UAAU,YAAY,QAAQ,UAAU,OACzD,QAAQ,QACT,CAAC;AACP;;;;;;AAOA,SAAS,oBACP,SACA,IACA,SACM;CACN,MAAM,EAAE,OAAO,aAAa,sBAAsB,EAAE;CACpD,KAAK,MAAM,QAAQ,OAAO,QAAQ,gBAAgB,IAAI;CACtD,IAAI,UAAU;CAOd,QAAQ,YAAY,KAAK;CACzB,IAAI,SACF,IAAI,sCAAsC,GAAG,wCAAwC;AAEzF;;AAQA,eAAe,IACb,SACA,QACA,UACuB;CACvB,MAAM,KAAK,QAAQ;CACnB,MAAM,UAAU,YAAY,OAAO;CAEnC,IAAI,CAAC,gBAAgB,IAAI,OAAO,GAAG,OAAO;EAAE,MAAM;EAAQ,KAAK;CAAS;CAMxE,0BAA0B;CAE1B,MAAM,SAAS,QAAQ,UAAU;CACjC,IAAI,eAAe;CACnB,MAAM,SAAS,MAAM,qBAAqB,QAAQ,IAAI;EACpD,MAAM,QAAQ,eAAe;EAC7B,WAAW;EACX,SAAS,QAAQ;EAGjB,WAAW,WAAW,YAAY,WAAW;EAC7C,SAAS,WAAW;EACpB,eAAe,QAAQ,WAAW,YAAY;EAC9C,OAAO,QAAQ;EACf,mBAAmB;GACjB,eAAe;EACjB;EACA,2BAA2B;GAIzB,QAAQ,YAAY,KAAK;EAC3B;CACF,CAAC;CAID,IAAI,cAAc;EAChB,sBAAsB;EACtB,oBAAoB,SAAS,IAAI,QAAQ,YAAY,IAAI;CAC3D;CAEA,IAAI,WAAW,MAAM,OAAO;EAAE,MAAM;EAAQ,KAAK;CAAS;CAC1D,OAAO;EAAE,MAAM,OAAO;EAAM,KAAK,YAAY,OAAO,KAAK,QAAQ;CAAE;AACrE;;AAGA,SAAS,YACP,KACA,UACgC;CAChC,IAAI,QAAQ,MAAM,OAAO;CACzB,IAAI,aAAa,KAAA,GAAW,OAAO;CACnC,OAAO,UACL,CAAC,KAAK,QAAQ,SACR,IACR;AACF;AAEA,SAAwB,kBAEtB,QACA,UACM;CACN,MAAM,WAAW,KAAK,MAAM;CAC5B,MAAM,WAAW,WAA+B,SAAS,MAAM,OAAO,MAAM,OAAO,GAAG;CACtF,MAAM,QAAQ,UACZ,SAAS,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC,CAAC;CAMpE,IAAI,MAAM,QAAQ,QAAQ,CAAC,CAAC,KAAK,SAAS,IAAI;AAChD"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
//#region src/unplugin/dep-graph.d.ts
|
|
2
2
|
/**
|
|
3
|
-
* Static per-file dependency graphs for disk-cache invalidation
|
|
3
|
+
* Static per-file dependency graphs, for disk-cache invalidation and for the
|
|
4
|
+
* watch dependencies a loader host has to declare (see transformDependencies).
|
|
4
5
|
*
|
|
5
6
|
* The disk cache used to record `getFirstPartyModulePaths()` — the superset
|
|
6
7
|
* of EVERY first-party module the loader had executed so far — as each
|
|
@@ -50,6 +51,48 @@ declare function resetDepGraphMemo(): void;
|
|
|
50
51
|
* The entry itself is excluded (its content is part of the cache key).
|
|
51
52
|
*/
|
|
52
53
|
declare function collectStaticDeps(entryFile: string): StaticDeps;
|
|
54
|
+
interface TransformDependencies {
|
|
55
|
+
/**
|
|
56
|
+
* Files to declare as watch dependencies, entry first and deduped. Complete
|
|
57
|
+
* only when `complete` is true — otherwise it is just the entry.
|
|
58
|
+
*/
|
|
59
|
+
files: string[];
|
|
60
|
+
/**
|
|
61
|
+
* False when the import graph could not be fully analyzed. `files` is then
|
|
62
|
+
* NOT sufficient, and the host has to reach for a coarser signal (a context
|
|
63
|
+
* dependency over the project, or refusing to cache the result) instead of
|
|
64
|
+
* trusting it.
|
|
65
|
+
*/
|
|
66
|
+
complete: boolean;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Files whose contents can change what `id`'s transform produces — the set a
|
|
70
|
+
* host must declare as watch dependencies.
|
|
71
|
+
*
|
|
72
|
+
* Discovery EXECUTES the file's import graph, so a compiled validator reflects
|
|
73
|
+
* every constant, enum and helper that graph contributed. A host that keys its
|
|
74
|
+
* cache on `id`'s own content alone therefore serves a stale validator after an
|
|
75
|
+
* edit to any of them.
|
|
76
|
+
*
|
|
77
|
+
* Bundler plugins do not need this: their `watchChange` hook fires for every
|
|
78
|
+
* file in the project, so the plugin invalidates wholesale. Loader hosts
|
|
79
|
+
* (Turbopack, plain webpack loaders) have no such hook — their only lever is
|
|
80
|
+
* declaring dependencies per file, which is what this returns.
|
|
81
|
+
*
|
|
82
|
+
* The entry is always included, for the reason the disk cache records it too: a
|
|
83
|
+
* host keys on the content it PASSED, but discovery executed the file from disk.
|
|
84
|
+
*
|
|
85
|
+
* Deliberately NOT backed by `getFirstPartyModulePaths()` when the crawl comes
|
|
86
|
+
* up incomplete, unlike the disk cache's fallback. That superset is
|
|
87
|
+
* point-in-time, not cumulative — `invalidateModuleCache()` empties it — so
|
|
88
|
+
* after an unrelated file's discovery repopulates it, it describes THAT file's
|
|
89
|
+
* graph and can silently omit this one's. The disk cache survives that because
|
|
90
|
+
* `watchChange` invalidates wholesale and deferred entries flush against one
|
|
91
|
+
* end-of-build snapshot; a loader host has neither backstop, so a plausible but
|
|
92
|
+
* wrong list would go undetected. Reporting `complete: false` lets the caller
|
|
93
|
+
* do the sound thing instead.
|
|
94
|
+
*/
|
|
95
|
+
declare function transformDependencies(id: string): TransformDependencies;
|
|
53
96
|
//#endregion
|
|
54
|
-
export { StaticDeps, collectStaticDeps, resetDepGraphMemo };
|
|
97
|
+
export { StaticDeps, TransformDependencies, collectStaticDeps, resetDepGraphMemo, transformDependencies };
|
|
55
98
|
//# sourceMappingURL=dep-graph.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dep-graph.d.ts","names":[],"sources":["../../src/unplugin/dep-graph.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"dep-graph.d.ts","names":[],"sources":["../../src/unplugin/dep-graph.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UA6CiB;;EAEf;;EAEA;;;iBA0Cc;;;;;iBAsMA,kBAAkB,oBAAoB;UA0BrC;;;;;EAKf;;;;;;;EAOA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA8Bc,sBAAsB,aAAa"}
|