zod-compiler 1.26.4 → 1.28.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.
Files changed (43) hide show
  1. package/README.md +77 -1
  2. package/dist/jit.d.ts +2 -0
  3. package/dist/jit.d.ts.map +1 -1
  4. package/dist/jit.js +9 -6
  5. package/dist/jit.js.map +1 -1
  6. package/dist/register/config.d.ts +20 -0
  7. package/dist/register/config.d.ts.map +1 -0
  8. package/dist/register/config.js +1 -0
  9. package/dist/register/index.d.ts +1 -0
  10. package/dist/register/index.js +68 -0
  11. package/dist/register/index.js.map +1 -0
  12. package/dist/register/transform.d.ts +16 -0
  13. package/dist/register/transform.d.ts.map +1 -0
  14. package/dist/register/transform.js +148 -0
  15. package/dist/register/transform.js.map +1 -0
  16. package/dist/swc.d.ts +5 -1
  17. package/dist/swc.d.ts.map +1 -1
  18. package/dist/swc.js.map +1 -1
  19. package/dist/turbopack.d.ts +6 -1
  20. package/dist/turbopack.d.ts.map +1 -1
  21. package/dist/turbopack.js.map +1 -1
  22. package/dist/unplugin/disk-cache.d.ts.map +1 -1
  23. package/dist/unplugin/disk-cache.js +1 -0
  24. package/dist/unplugin/disk-cache.js.map +1 -1
  25. package/dist/unplugin/index.d.ts.map +1 -1
  26. package/dist/unplugin/index.js +56 -9
  27. package/dist/unplugin/index.js.map +1 -1
  28. package/dist/unplugin/pool-worker.d.ts +1 -0
  29. package/dist/unplugin/pool-worker.js +116 -0
  30. package/dist/unplugin/pool-worker.js.map +1 -0
  31. package/dist/unplugin/pool.d.ts +162 -0
  32. package/dist/unplugin/pool.d.ts.map +1 -0
  33. package/dist/unplugin/pool.js +445 -0
  34. package/dist/unplugin/pool.js.map +1 -0
  35. package/dist/unplugin/transform.d.ts +2 -1
  36. package/dist/unplugin/transform.d.ts.map +1 -1
  37. package/dist/unplugin/transform.js +1 -1
  38. package/dist/unplugin/transform.js.map +1 -1
  39. package/dist/unplugin/types.d.ts +36 -1
  40. package/dist/unplugin/types.d.ts.map +1 -1
  41. package/dist/unplugin/types.js.map +1 -1
  42. package/package.json +12 -1
  43. package/schema.json +80 -0
package/README.md CHANGED
@@ -14,7 +14,7 @@ input. No code changes required.
14
14
 
15
15
  ## Usage
16
16
 
17
- Four ways to use zod-compiler — pick one:
17
+ Five ways to use zod-compiler — pick one:
18
18
 
19
19
  ### 1. Automatic Mode (Default)
20
20
 
@@ -114,6 +114,46 @@ Libraries should ship plain Zod and let the app decide.
114
114
  Needs `new Function`, as Zod's own object fast-path does. `z.config({ jitless: true })` and a CSP
115
115
  that blocks eval both leave a working plain-Zod schema.
116
116
 
117
+ ### 5. Node.js Register Hook
118
+
119
+ Node.js 22.15+ can automatically insert the equivalent of `jit()` for exported schemas as modules
120
+ load, with no source changes and no bundler:
121
+
122
+ ```bash
123
+ node --import zod-compiler/register src/server.js
124
+ ```
125
+
126
+ The same preload handles ESM imports, CommonJS `require()`, and Node's native TypeScript formats. It
127
+ also chains with TypeScript runners:
128
+
129
+ ```bash
130
+ node --import zod-compiler/register --import tsx src/server.ts
131
+ ```
132
+
133
+ This is runtime JIT instrumentation, not the AOT source rewriting performed by the Vite, Rsbuild, and
134
+ other build plugins. The hook identifies exported schema bindings and registers their live Zod objects;
135
+ validators are generated in-process, lazily on first use. It does not execute modules twice and adds no
136
+ transform cache beyond Node's module cache. Use a build plugin or the CLI when generated validator code
137
+ must exist before Node starts or runtime `new Function` is unavailable.
138
+
139
+ Optional settings come from `zod-compiler.json` in the working directory:
140
+
141
+ ```json
142
+ {
143
+ "$schema": "./node_modules/zod-compiler/schema.json",
144
+ "include": ["src/**"],
145
+ "exclude": ["**/*.test.ts"],
146
+ "schemas": "auto",
147
+ "eager": false,
148
+ "output": "schema",
149
+ "hoist": true
150
+ }
151
+ ```
152
+
153
+ `output: "compact"` preserves the Zod schema and compiled valid-input fast path while delegating cold
154
+ error production to Zod. Full `"schema"` output remains the default. `"bag"` is unavailable because a
155
+ load hook cannot replace already-linked ESM export bindings safely.
156
+
117
157
  ## Build Plugin
118
158
 
119
159
  ### Supported Build Tools
@@ -148,6 +188,7 @@ has neither — see [React Native / Expo](#react-native--expo).
148
188
  | `apply` | `"build" \| "serve" \| "all"` | builds + Vitest | **Vite only**: when the plugin runs |
149
189
  | `codegenMode` | `"lean" \| "inline"` | auto | `"inline"` emits helpers per file; needed for transpile-only esbuild — see [SWC](#swc) |
150
190
  | `cache` | `boolean \| string` | `true` | Persistent transform cache in `node_modules/.cache/zod-compiler` |
191
+ | `parallel` | `boolean \| number` | `false` | Run transforms on worker threads — see [Parallel Transforms](#parallel-transforms) |
151
192
 
152
193
  ```typescript
153
194
  zodCompiler({
@@ -372,6 +413,41 @@ expensive one — later runs hit the persistent cache.
372
413
  Scope discovery with `include`; set `ZOD_COMPILER_TIMING=1` for a per-phase breakdown. Files that
373
414
  never mention `zod` cost nothing.
374
415
 
416
+ ### Parallel Transforms
417
+
418
+ Discovery runs one file at a time on the bundler's own thread — executions are serialized so
419
+ concurrent transforms cannot double-execute a shared dependency. `parallel` moves whole transforms
420
+ onto worker threads instead, each with its own loader and module cache, which is what makes running
421
+ them at the same time sound.
422
+
423
+ ```typescript
424
+ zodCompiler({ parallel: true }); // one worker per core, less one, capped at 4
425
+ zodCompiler({ parallel: 2 }); // or pick the count yourself
426
+ ```
427
+
428
+ **Whether it pays depends on your import graph, not your core count.** A module shared by many
429
+ schema files is executed once in-process and once _per worker_ here. Files with independent graphs
430
+ win; files chained through each other can lose. Both rows below are 120 files of 8 schemas each, on
431
+ 12 performance cores — the only difference is whether the files import one another:
432
+
433
+ | Transform (120 files) | in-process | n=2 | n=4 | n=8 | n=12 |
434
+ | --------------------- | ---------: | -------: | -------: | -------: | -------: |
435
+ | independent graphs | 3,633 ms | 2,263 ms | 1,508 ms | 1,786 ms | 2,119 ms |
436
+ | 120-deep import chain | 945 ms | 977 ms | 1,045 ms | 1,796 ms | 3,332 ms |
437
+
438
+ So measure before adopting it — `ZOD_COMPILER_TIMING=1` prints the per-phase breakdown, and the
439
+ `discover` line is the one workers move. Throughput peaks around four workers and declines past it:
440
+ beyond that point every extra worker re-executes more graph, holds another copy of it in memory, and
441
+ adds to the generated source that the single receiving thread has to deserialize.
442
+
443
+ Emitted code, sourcemaps and cache entries are identical either way — `parallel` is not part of the
444
+ cache key, so a parallel build and a serial one share the same cache. The disk cache and dependency
445
+ crawling stay on the bundler thread, and if a worker cannot start or dies mid-build its file is
446
+ retried in-process rather than failing the build.
447
+
448
+ A **warm cache still beats parallelism**, and costs no memory — reach for `parallel` for the cold
449
+ runs the cache cannot help with.
450
+
375
451
  ## Framework Examples
376
452
 
377
453
  Nothing framework-specific is needed — exported schemas are compiled in place, so anything accepting
package/dist/jit.d.ts CHANGED
@@ -9,6 +9,8 @@ interface JitOptions {
9
9
  * eagerly. Default `false`.
10
10
  */
11
11
  eager?: boolean | undefined;
12
+ /** Use compact codegen and delegate cold error production to Zod. @default "schema" */
13
+ output?: "schema" | "compact" | undefined;
12
14
  }
13
15
  /**
14
16
  * Compile `schema` in-process and install the compiled `parse` / `safeParse` /
package/dist/jit.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"jit.d.ts","names":[],"sources":["../src/jit.ts"],"mappings":";;;UA6EiB;;;;;;;EAOf;;;;;;;;;;;;;;;;;;;;;;;iBAwBc,IAAI,UAAU,SAC5B,QAAQ,GACR,UAAU,aACT,IAAI,eAAe,OAAO;;;;;;;;;;;;;;iBA8Kb,OAAO,iBAAiB,UAAU"}
1
+ {"version":3,"file":"jit.d.ts","names":[],"sources":["../src/jit.ts"],"mappings":";;;UA6EiB;;;;;;;EAOf;;EAEA;;;;;;;;;;;;;;;;;;;;;;;iBAwBc,IAAI,UAAU,SAC5B,QAAQ,GACR,UAAU,aACT,IAAI,eAAe,OAAO;;;;;;;;;;;;;;iBA8Kb,OAAO,iBAAiB,UAAU"}
package/dist/jit.js CHANGED
@@ -94,7 +94,7 @@ function jit(schema, options) {
94
94
  if (seen.has(target)) return schema;
95
95
  seen.add(target);
96
96
  if (options?.eager === true) {
97
- materialize(schema);
97
+ materialize(schema, options);
98
98
  return schema;
99
99
  }
100
100
  const original = /* @__PURE__ */ new Map();
@@ -109,7 +109,7 @@ function jit(schema, options) {
109
109
  if (!pending) return;
110
110
  pending = false;
111
111
  restore(target, original);
112
- materialize(schema);
112
+ materialize(schema, options);
113
113
  }, () => {
114
114
  if (!pending) return;
115
115
  pending = false;
@@ -219,10 +219,10 @@ function codegenAllowed() {
219
219
  * which the caller already has, so there is nothing to report and nothing to
220
220
  * break.
221
221
  */
222
- function materialize(schema) {
222
+ function materialize(schema, options) {
223
223
  if (!codegenAllowed()) return;
224
224
  try {
225
- buildValidator(schema);
225
+ buildValidator(schema, options);
226
226
  } catch {}
227
227
  }
228
228
  /**
@@ -231,11 +231,14 @@ function materialize(schema) {
231
231
  * then the `__zcMkv` IIFE whose `__rf[]` bases and install target are the live
232
232
  * schema object passed in as `__schema`.
233
233
  */
234
- function buildValidator(schema) {
234
+ function buildValidator(schema, options) {
235
235
  const { schemas, shared } = compileSchemas([{
236
236
  exportName: "jit",
237
237
  schema
238
- }], { mode: "inline" });
238
+ }], {
239
+ compact: options?.output === "compact",
240
+ mode: "inline"
241
+ });
239
242
  const compiled = schemas[0];
240
243
  if (compiled === void 0) throw new Error("zod-compiler: schema produced no validator");
241
244
  const body = [
package/dist/jit.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"jit.js","names":["zodCore","zodConfig"],"sources":["../src/jit.ts"],"sourcesContent":["/**\n * Runtime compilation — the same extract → codegen pipeline the build plugin\n * runs, executed in-process and evaluated through `new Function`.\n *\n * The AOT paths (unplugin, CLI) need a build step to fire. Plenty of everyday\n * code has none: `tsx server.ts`, `node --experimental-strip-types`, a Jest\n * suite, a serverless handler bundled by someone else's toolchain, a library\n * that ships schemas to consumers. There `compile()` is a no-op and every parse\n * runs plain Zod. `jit()` closes that gap — one call, no build integration,\n * measured 3-25x on everyday schemas at ~0.1-0.2 ms of one-time compilation.\n *\n * Nothing here re-implements validation: {@link compileSchemas} and\n * {@link generateIIFE} are the exact modules the plugin and CLI use, so the\n * generated validator, its Zod parity and its performance are identical to what\n * a build would have emitted. The only difference is *when* the code is\n * produced.\n *\n * Compilation is LAZY by default: `jit()` installs accessors that compile on\n * the first read of a parse method and replace themselves with the compiled\n * ones. Importing a module of 500 schemas therefore costs nothing, and a\n * serverless invocation touching three of them pays for three.\n *\n * Runtime code generation is not always permitted — a strict CSP without\n * `unsafe-eval`, some edge runtimes. Zod v4 has the same constraint (its object\n * fast-pass is itself a `new Function`) and already exposes the two switches\n * for it: the `core.util.allowsEval` probe and `z.config({ jitless: true })`.\n * `jit()` honours both and degrades to plain Zod, so one setting governs both\n * compilers. Those targets are where the build plugin belongs anyway — it emits\n * the same validator with no runtime evaluation at all.\n */\n\nimport { config as zodConfig, core as zodCore, ZodRealError, type output, type ZodType } from \"zod\";\nimport {\n FAIL_CLASS_DECL,\n FAILZ_CLASS_DECL,\n FIN_DECL,\n FIN_DEFERRED_DECL,\n FINZ_DECL,\n generateIIFE,\n MK_VALIDATOR_DECL,\n ZOD_MSG_DECLARATION,\n} from \"./core/iife.js\";\nimport { compileSchemas } from \"./core/pipeline.js\";\nimport type { CompiledSchema } from \"./core/types.js\";\nimport { isZodSchema } from \"./is-zod-schema.js\";\n\n/**\n * The declarations `ZOD_CONFIG_IMPORT` supplies to an emitted module, minus the\n * import itself — `zod`'s three bindings arrive as parameters instead, so the\n * evaluated code has no module scope to resolve. Byte-for-byte the same helper\n * source the CLI emitter writes into a `.compiled.ts`, so a JIT validator and\n * an AOT one share their entire runtime layer.\n */\nconst RUNTIME_PRELUDE = [\n ZOD_MSG_DECLARATION,\n FAIL_CLASS_DECL,\n MK_VALIDATOR_DECL,\n FIN_DECL,\n FIN_DEFERRED_DECL,\n FAILZ_CLASS_DECL,\n FINZ_DECL,\n].join(\"\\n\");\n\n/**\n * Methods `__zcMkv` installs. Each is fronted by a compile-on-read accessor\n * until the schema materializes.\n *\n * `~standard` earns its place: Zod builds it as a closure over `_zod.run`, not\n * over the schema's `safeParse` property, so a Standard Schema consumer (tRPC,\n * Hono, TanStack Form) that never touches `safeParse` would otherwise keep\n * running plain Zod forever behind a \"compiled\" schema.\n */\nconst SLOTS = [\"parse\", \"safeParse\", \"parseAsync\", \"safeParseAsync\", \"is\", \"~standard\"] as const;\n\n/** Schemas already handed to `jit()`, so a second call is a no-op rather than a recompile. */\nconst seen = new WeakSet<object>();\n\nexport interface JitOptions {\n /**\n * Compile immediately instead of on first use. Costs ~0.1-0.2 ms per schema\n * at import time; useful for a long-lived server that would rather pay during\n * startup than on the first request, or to surface a compilation failure\n * eagerly. Default `false`.\n */\n eager?: boolean | undefined;\n}\n\n/**\n * Compile `schema` in-process and install the compiled `parse` / `safeParse` /\n * `parseAsync` / `safeParseAsync` / `is` / `~standard` on it.\n *\n * Returns the SAME object — identity-preserving exactly as the build plugin is,\n * so `.shape`, `_zod`, `instanceof`, `z.toJSONSchema()`, `.meta()` and\n * composition into a larger schema all keep working, and every existing\n * reference to the schema picks the compiled methods up.\n *\n * ```ts\n * import { z } from \"zod\";\n * import { jit } from \"zod-compiler/jit\";\n *\n * export const UserSchema = jit(z.object({ name: z.string(), email: z.email() }));\n * UserSchema.safeParse(input); // compiled on this first call\n * ```\n *\n * Schemas the compiler cannot reproduce fall back to Zod per sub-schema, the\n * same way they do at build time; a schema that cannot be compiled at all is\n * left as plain Zod.\n */\nexport function jit<T extends ZodType>(\n schema: T,\n options?: JitOptions,\n): T & CompiledSchema<output<T>> {\n const target = schema as unknown as Record<string, unknown>;\n if (seen.has(target)) return schema as T & CompiledSchema<output<T>>;\n seen.add(target);\n\n if (options?.eager === true) {\n materialize(schema);\n return schema as T & CompiledSchema<output<T>>;\n }\n\n // Snapshot Zod's own descriptors first: materialize() restores them before\n // handing the object to `__zcMkv`, so the generated code sees a pristine\n // schema — it captures the original `parseAsync` / `safeParseAsync` as its\n // throw paths, and capturing a stub there would loop back into itself.\n const original = new Map<string, PropertyDescriptor | undefined>();\n try {\n for (const slot of SLOTS) {\n original.set(slot, Object.getOwnPropertyDescriptor(target, slot));\n }\n } catch {\n // The target answers a descriptor query with a throw — an exotic wrapper,\n // not anything Zod built. Without a snapshot there is nothing to roll back\n // to, so install nothing and hand back the schema exactly as it came.\n return schema as T & CompiledSchema<output<T>>;\n }\n\n // Installing the accessors is the step most likely to throw rather than\n // degrade: a slot locked non-configurable (a future Zod, another wrapper) makes\n // defineProperty raise, and `jit()` is called at module scope — so an\n // unhandled throw here takes down the importing app at boot. Roll back to\n // whatever Zod had and leave the schema alone instead.\n let pending = true;\n try {\n installAccessors(\n target,\n original,\n () => {\n if (!pending) return;\n pending = false;\n restore(target, original);\n materialize(schema);\n },\n () => {\n if (!pending) return;\n pending = false;\n // Restore EVERY slot, not just the one being written. A left-behind\n // accessor whose trigger has been cancelled would read `target[slot]`\n // and re-enter itself — unbounded recursion, which is what a later read\n // of an untouched slot (`~standard`, from a Standard Schema consumer)\n // would otherwise hit.\n //\n // Reached only when something WRITES a slot before anything reads one: a\n // test double, another wrapper, or an AOT `safeParse` assigned directly.\n // The build plugin's own `__zcMkv` does not land here — its first\n // statement READS `parseAsync`/`safeParseAsync` to capture their\n // originals, so it triggers materialization and then overwrites the\n // compiled-by-jit methods with the compiled-by-plugin ones.\n restore(target, original);\n },\n );\n } catch {\n pending = false;\n restore(target, original);\n }\n\n return schema as T & CompiledSchema<output<T>>;\n}\n\n/**\n * Front every installed method with a compile-on-read accessor. `trigger`\n * materializes the schema, which normally replaces these accessors with the\n * compiled methods (or restores Zod's own). When that replacement does not take,\n * the getter falls back to the snapshot rather than re-reading the slot — see\n * the re-entrancy note in the body.\n */\nfunction installAccessors(\n target: Record<string, unknown>,\n original: ReadonlyMap<string, PropertyDescriptor | undefined>,\n trigger: () => void,\n cancel: () => void,\n): void {\n for (const slot of SLOTS) {\n // Re-entrancy is settled structurally rather than by inspection. Reading the\n // slot again is how this getter normally hands over — to the compiled method\n // materialize() installed, or to Zod's own that restore() put back — but the\n // handover can fail to take: a target frozen after `jit()` refuses both, and\n // a second copy of this module in the graph leaves ITS accessor on the slot,\n // so the two bounce reads between them. `trigger()` is spent by then, so\n // either way the read recurses until the stack blows. While a read is already\n // in flight, serve Zod's own method from the snapshot: a schema that cannot\n // be compiled still parses.\n let reading = false;\n const read = function (): unknown {\n if (reading) return fromSnapshot(target, original, slot);\n reading = true;\n try {\n trigger();\n if (stillFrontedBy(target, slot, read)) return fromSnapshot(target, original, slot);\n return target[slot];\n } finally {\n reading = false;\n }\n };\n Object.defineProperty(target, slot, {\n configurable: true,\n // Preserve Zod's own visibility: parse/safeParse/... are enumerable own\n // properties, `~standard` is not. `is` does not exist on a Zod schema, so\n // it follows the non-enumerable convention `compile()` already uses.\n enumerable: original.get(slot)?.enumerable ?? false,\n get: read,\n set(value: unknown) {\n // Someone overwrote a method before first use (a test double, another\n // wrapper). Their value wins, and compilation is cancelled outright —\n // materializing later would restore Zod's descriptors over it.\n cancel();\n Object.defineProperty(target, slot, {\n configurable: true,\n enumerable: original.get(slot)?.enumerable ?? false,\n value,\n writable: true,\n });\n },\n });\n }\n}\n\n/**\n * Is `slot` still fronted by this very accessor — i.e. did the replacement that\n * `trigger()` was supposed to perform not take? A target that will not answer\n * the question is assumed to still hold it, since reading the slot to find out\n * is the recursion being avoided.\n */\nfunction stillFrontedBy(\n target: Record<string, unknown>,\n slot: string,\n getter: () => unknown,\n): boolean {\n try {\n return Object.getOwnPropertyDescriptor(target, slot)?.get === getter;\n } catch {\n return true;\n }\n}\n\n/**\n * What Zod had in `slot`, taken from the snapshot made before installation.\n * `undefined` for `is`, which no plain Zod schema carries — the same thing every\n * other degradation path leaves there.\n */\nfunction fromSnapshot(\n target: Record<string, unknown>,\n original: ReadonlyMap<string, PropertyDescriptor | undefined>,\n slot: string,\n): unknown {\n const descriptor = original.get(slot);\n if (descriptor === undefined) return undefined;\n // `~standard` is a lazy getter on a Zod schema, so invoke it rather than\n // reading a `value` it does not have.\n return descriptor.get === undefined ? descriptor.value : descriptor.get.call(target);\n}\n\n/**\n * Compile every Zod schema found among an object's own values — typically a\n * module namespace, so a whole schema file opts in with one call:\n *\n * ```ts\n * import * as schemas from \"./schemas.js\";\n * jitAll(schemas);\n * ```\n *\n * The namespace object itself is never written to (a module namespace is\n * read-only); `jit()` mutates the schema objects it holds, which is what every\n * importer of that module already references.\n */\nexport function jitAll(schemas: object, options?: JitOptions): void {\n for (const value of Object.values(schemas)) {\n if (isZodSchema(value)) jit(value, options);\n }\n}\n\n/** Put Zod's own descriptors back, dropping the compile-on-read accessors. */\nfunction restore(\n target: Record<string, unknown>,\n original: ReadonlyMap<string, PropertyDescriptor | undefined>,\n): void {\n for (const slot of SLOTS) {\n // Per slot, because this also runs as the rollback for a failed install: a\n // target that refuses one slot must not cost the others their restoration.\n // A slot left fronted by its accessor still reads correctly — the getter\n // serves Zod's own method from the snapshot — but it keeps a redundant\n // indirection, so restoring what can be restored is worth the try/catch.\n try {\n const descriptor = original.get(slot);\n if (descriptor === undefined) delete target[slot];\n else Object.defineProperty(target, slot, descriptor);\n } catch {\n // Nothing further to try for this slot.\n }\n }\n}\n\n/**\n * Whether runtime code generation is permitted here. Read per call, never\n * snapshotted: `z.config({ jitless: true })` runs in an entry point, after the\n * schema modules it imports have already been evaluated.\n */\nfunction codegenAllowed(): boolean {\n return zodCore.globalConfig.jitless !== true && zodCore.util.allowsEval.value;\n}\n\n/**\n * Run the pipeline and let the generated IIFE install its methods on `schema`.\n * Swallows failure: a schema that cannot be compiled keeps Zod's own methods,\n * which the caller already has, so there is nothing to report and nothing to\n * break.\n */\nfunction materialize(schema: unknown): void {\n if (!codegenAllowed()) return;\n try {\n buildValidator(schema);\n } catch {\n // Left as plain Zod. Deliberately silent: `jit()` is an optimization, and a\n // schema using a construct the compiler declines is a supported outcome,\n // not an error.\n }\n}\n\n/**\n * Generate the validator and evaluate it, reproducing the module a\n * `.compiled.ts` would have been: helper preamble, the file-level shared block,\n * then the `__zcMkv` IIFE whose `__rf[]` bases and install target are the live\n * schema object passed in as `__schema`.\n */\nfunction buildValidator(schema: unknown): void {\n const { schemas, shared } = compileSchemas([{ exportName: \"jit\", schema }], { mode: \"inline\" });\n const compiled = schemas[0];\n if (compiled === undefined) throw new Error(\"zod-compiler: schema produced no validator\");\n\n const body = [RUNTIME_PRELUDE, shared.code, `return ${generateIIFE(\"__schema\", compiled)};`].join(\n \"\\n\",\n );\n\n // The three bindings ZOD_CONFIG_IMPORT would have imported, passed in so the\n // evaluated code needs no module resolution of its own.\n // oxlint-disable-next-line no-new-func -- generating the validator IS the feature\n const factory = new Function(\n \"__zodCompilerConfig\",\n \"__zcCore\",\n \"__zcZodError\",\n \"__schema\",\n body,\n ) as (\n zodConfigFn: typeof zodConfig,\n zodCoreNs: typeof zodCore,\n zodErrorCtor: typeof ZodRealError,\n target: unknown,\n ) => unknown;\n\n factory(zodConfig, zodCore, ZodRealError, schema);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"jit.js","names":["zodCore","zodConfig"],"sources":["../src/jit.ts"],"sourcesContent":["/**\n * Runtime compilation — the same extract → codegen pipeline the build plugin\n * runs, executed in-process and evaluated through `new Function`.\n *\n * The AOT paths (unplugin, CLI) need a build step to fire. Plenty of everyday\n * code has none: `tsx server.ts`, `node --experimental-strip-types`, a Jest\n * suite, a serverless handler bundled by someone else's toolchain, a library\n * that ships schemas to consumers. There `compile()` is a no-op and every parse\n * runs plain Zod. `jit()` closes that gap — one call, no build integration,\n * measured 3-25x on everyday schemas at ~0.1-0.2 ms of one-time compilation.\n *\n * Nothing here re-implements validation: {@link compileSchemas} and\n * {@link generateIIFE} are the exact modules the plugin and CLI use, so the\n * generated validator, its Zod parity and its performance are identical to what\n * a build would have emitted. The only difference is *when* the code is\n * produced.\n *\n * Compilation is LAZY by default: `jit()` installs accessors that compile on\n * the first read of a parse method and replace themselves with the compiled\n * ones. Importing a module of 500 schemas therefore costs nothing, and a\n * serverless invocation touching three of them pays for three.\n *\n * Runtime code generation is not always permitted — a strict CSP without\n * `unsafe-eval`, some edge runtimes. Zod v4 has the same constraint (its object\n * fast-pass is itself a `new Function`) and already exposes the two switches\n * for it: the `core.util.allowsEval` probe and `z.config({ jitless: true })`.\n * `jit()` honours both and degrades to plain Zod, so one setting governs both\n * compilers. Those targets are where the build plugin belongs anyway — it emits\n * the same validator with no runtime evaluation at all.\n */\n\nimport { config as zodConfig, core as zodCore, ZodRealError, type output, type ZodType } from \"zod\";\nimport {\n FAIL_CLASS_DECL,\n FAILZ_CLASS_DECL,\n FIN_DECL,\n FIN_DEFERRED_DECL,\n FINZ_DECL,\n generateIIFE,\n MK_VALIDATOR_DECL,\n ZOD_MSG_DECLARATION,\n} from \"./core/iife.js\";\nimport { compileSchemas } from \"./core/pipeline.js\";\nimport type { CompiledSchema } from \"./core/types.js\";\nimport { isZodSchema } from \"./is-zod-schema.js\";\n\n/**\n * The declarations `ZOD_CONFIG_IMPORT` supplies to an emitted module, minus the\n * import itself — `zod`'s three bindings arrive as parameters instead, so the\n * evaluated code has no module scope to resolve. Byte-for-byte the same helper\n * source the CLI emitter writes into a `.compiled.ts`, so a JIT validator and\n * an AOT one share their entire runtime layer.\n */\nconst RUNTIME_PRELUDE = [\n ZOD_MSG_DECLARATION,\n FAIL_CLASS_DECL,\n MK_VALIDATOR_DECL,\n FIN_DECL,\n FIN_DEFERRED_DECL,\n FAILZ_CLASS_DECL,\n FINZ_DECL,\n].join(\"\\n\");\n\n/**\n * Methods `__zcMkv` installs. Each is fronted by a compile-on-read accessor\n * until the schema materializes.\n *\n * `~standard` earns its place: Zod builds it as a closure over `_zod.run`, not\n * over the schema's `safeParse` property, so a Standard Schema consumer (tRPC,\n * Hono, TanStack Form) that never touches `safeParse` would otherwise keep\n * running plain Zod forever behind a \"compiled\" schema.\n */\nconst SLOTS = [\"parse\", \"safeParse\", \"parseAsync\", \"safeParseAsync\", \"is\", \"~standard\"] as const;\n\n/** Schemas already handed to `jit()`, so a second call is a no-op rather than a recompile. */\nconst seen = new WeakSet<object>();\n\nexport interface JitOptions {\n /**\n * Compile immediately instead of on first use. Costs ~0.1-0.2 ms per schema\n * at import time; useful for a long-lived server that would rather pay during\n * startup than on the first request, or to surface a compilation failure\n * eagerly. Default `false`.\n */\n eager?: boolean | undefined;\n /** Use compact codegen and delegate cold error production to Zod. @default \"schema\" */\n output?: \"schema\" | \"compact\" | undefined;\n}\n\n/**\n * Compile `schema` in-process and install the compiled `parse` / `safeParse` /\n * `parseAsync` / `safeParseAsync` / `is` / `~standard` on it.\n *\n * Returns the SAME object — identity-preserving exactly as the build plugin is,\n * so `.shape`, `_zod`, `instanceof`, `z.toJSONSchema()`, `.meta()` and\n * composition into a larger schema all keep working, and every existing\n * reference to the schema picks the compiled methods up.\n *\n * ```ts\n * import { z } from \"zod\";\n * import { jit } from \"zod-compiler/jit\";\n *\n * export const UserSchema = jit(z.object({ name: z.string(), email: z.email() }));\n * UserSchema.safeParse(input); // compiled on this first call\n * ```\n *\n * Schemas the compiler cannot reproduce fall back to Zod per sub-schema, the\n * same way they do at build time; a schema that cannot be compiled at all is\n * left as plain Zod.\n */\nexport function jit<T extends ZodType>(\n schema: T,\n options?: JitOptions,\n): T & CompiledSchema<output<T>> {\n const target = schema as unknown as Record<string, unknown>;\n if (seen.has(target)) return schema as T & CompiledSchema<output<T>>;\n seen.add(target);\n\n if (options?.eager === true) {\n materialize(schema, options);\n return schema as T & CompiledSchema<output<T>>;\n }\n\n // Snapshot Zod's own descriptors first: materialize() restores them before\n // handing the object to `__zcMkv`, so the generated code sees a pristine\n // schema — it captures the original `parseAsync` / `safeParseAsync` as its\n // throw paths, and capturing a stub there would loop back into itself.\n const original = new Map<string, PropertyDescriptor | undefined>();\n try {\n for (const slot of SLOTS) {\n original.set(slot, Object.getOwnPropertyDescriptor(target, slot));\n }\n } catch {\n // The target answers a descriptor query with a throw — an exotic wrapper,\n // not anything Zod built. Without a snapshot there is nothing to roll back\n // to, so install nothing and hand back the schema exactly as it came.\n return schema as T & CompiledSchema<output<T>>;\n }\n\n // Installing the accessors is the step most likely to throw rather than\n // degrade: a slot locked non-configurable (a future Zod, another wrapper) makes\n // defineProperty raise, and `jit()` is called at module scope — so an\n // unhandled throw here takes down the importing app at boot. Roll back to\n // whatever Zod had and leave the schema alone instead.\n let pending = true;\n try {\n installAccessors(\n target,\n original,\n () => {\n if (!pending) return;\n pending = false;\n restore(target, original);\n materialize(schema, options);\n },\n () => {\n if (!pending) return;\n pending = false;\n // Restore EVERY slot, not just the one being written. A left-behind\n // accessor whose trigger has been cancelled would read `target[slot]`\n // and re-enter itself — unbounded recursion, which is what a later read\n // of an untouched slot (`~standard`, from a Standard Schema consumer)\n // would otherwise hit.\n //\n // Reached only when something WRITES a slot before anything reads one: a\n // test double, another wrapper, or an AOT `safeParse` assigned directly.\n // The build plugin's own `__zcMkv` does not land here — its first\n // statement READS `parseAsync`/`safeParseAsync` to capture their\n // originals, so it triggers materialization and then overwrites the\n // compiled-by-jit methods with the compiled-by-plugin ones.\n restore(target, original);\n },\n );\n } catch {\n pending = false;\n restore(target, original);\n }\n\n return schema as T & CompiledSchema<output<T>>;\n}\n\n/**\n * Front every installed method with a compile-on-read accessor. `trigger`\n * materializes the schema, which normally replaces these accessors with the\n * compiled methods (or restores Zod's own). When that replacement does not take,\n * the getter falls back to the snapshot rather than re-reading the slot — see\n * the re-entrancy note in the body.\n */\nfunction installAccessors(\n target: Record<string, unknown>,\n original: ReadonlyMap<string, PropertyDescriptor | undefined>,\n trigger: () => void,\n cancel: () => void,\n): void {\n for (const slot of SLOTS) {\n // Re-entrancy is settled structurally rather than by inspection. Reading the\n // slot again is how this getter normally hands over — to the compiled method\n // materialize() installed, or to Zod's own that restore() put back — but the\n // handover can fail to take: a target frozen after `jit()` refuses both, and\n // a second copy of this module in the graph leaves ITS accessor on the slot,\n // so the two bounce reads between them. `trigger()` is spent by then, so\n // either way the read recurses until the stack blows. While a read is already\n // in flight, serve Zod's own method from the snapshot: a schema that cannot\n // be compiled still parses.\n let reading = false;\n const read = function (): unknown {\n if (reading) return fromSnapshot(target, original, slot);\n reading = true;\n try {\n trigger();\n if (stillFrontedBy(target, slot, read)) return fromSnapshot(target, original, slot);\n return target[slot];\n } finally {\n reading = false;\n }\n };\n Object.defineProperty(target, slot, {\n configurable: true,\n // Preserve Zod's own visibility: parse/safeParse/... are enumerable own\n // properties, `~standard` is not. `is` does not exist on a Zod schema, so\n // it follows the non-enumerable convention `compile()` already uses.\n enumerable: original.get(slot)?.enumerable ?? false,\n get: read,\n set(value: unknown) {\n // Someone overwrote a method before first use (a test double, another\n // wrapper). Their value wins, and compilation is cancelled outright —\n // materializing later would restore Zod's descriptors over it.\n cancel();\n Object.defineProperty(target, slot, {\n configurable: true,\n enumerable: original.get(slot)?.enumerable ?? false,\n value,\n writable: true,\n });\n },\n });\n }\n}\n\n/**\n * Is `slot` still fronted by this very accessor — i.e. did the replacement that\n * `trigger()` was supposed to perform not take? A target that will not answer\n * the question is assumed to still hold it, since reading the slot to find out\n * is the recursion being avoided.\n */\nfunction stillFrontedBy(\n target: Record<string, unknown>,\n slot: string,\n getter: () => unknown,\n): boolean {\n try {\n return Object.getOwnPropertyDescriptor(target, slot)?.get === getter;\n } catch {\n return true;\n }\n}\n\n/**\n * What Zod had in `slot`, taken from the snapshot made before installation.\n * `undefined` for `is`, which no plain Zod schema carries — the same thing every\n * other degradation path leaves there.\n */\nfunction fromSnapshot(\n target: Record<string, unknown>,\n original: ReadonlyMap<string, PropertyDescriptor | undefined>,\n slot: string,\n): unknown {\n const descriptor = original.get(slot);\n if (descriptor === undefined) return undefined;\n // `~standard` is a lazy getter on a Zod schema, so invoke it rather than\n // reading a `value` it does not have.\n return descriptor.get === undefined ? descriptor.value : descriptor.get.call(target);\n}\n\n/**\n * Compile every Zod schema found among an object's own values — typically a\n * module namespace, so a whole schema file opts in with one call:\n *\n * ```ts\n * import * as schemas from \"./schemas.js\";\n * jitAll(schemas);\n * ```\n *\n * The namespace object itself is never written to (a module namespace is\n * read-only); `jit()` mutates the schema objects it holds, which is what every\n * importer of that module already references.\n */\nexport function jitAll(schemas: object, options?: JitOptions): void {\n for (const value of Object.values(schemas)) {\n if (isZodSchema(value)) jit(value, options);\n }\n}\n\n/** Put Zod's own descriptors back, dropping the compile-on-read accessors. */\nfunction restore(\n target: Record<string, unknown>,\n original: ReadonlyMap<string, PropertyDescriptor | undefined>,\n): void {\n for (const slot of SLOTS) {\n // Per slot, because this also runs as the rollback for a failed install: a\n // target that refuses one slot must not cost the others their restoration.\n // A slot left fronted by its accessor still reads correctly — the getter\n // serves Zod's own method from the snapshot — but it keeps a redundant\n // indirection, so restoring what can be restored is worth the try/catch.\n try {\n const descriptor = original.get(slot);\n if (descriptor === undefined) delete target[slot];\n else Object.defineProperty(target, slot, descriptor);\n } catch {\n // Nothing further to try for this slot.\n }\n }\n}\n\n/**\n * Whether runtime code generation is permitted here. Read per call, never\n * snapshotted: `z.config({ jitless: true })` runs in an entry point, after the\n * schema modules it imports have already been evaluated.\n */\nfunction codegenAllowed(): boolean {\n return zodCore.globalConfig.jitless !== true && zodCore.util.allowsEval.value;\n}\n\n/**\n * Run the pipeline and let the generated IIFE install its methods on `schema`.\n * Swallows failure: a schema that cannot be compiled keeps Zod's own methods,\n * which the caller already has, so there is nothing to report and nothing to\n * break.\n */\nfunction materialize(schema: unknown, options?: JitOptions): void {\n if (!codegenAllowed()) return;\n try {\n buildValidator(schema, options);\n } catch {\n // Left as plain Zod. Deliberately silent: `jit()` is an optimization, and a\n // schema using a construct the compiler declines is a supported outcome,\n // not an error.\n }\n}\n\n/**\n * Generate the validator and evaluate it, reproducing the module a\n * `.compiled.ts` would have been: helper preamble, the file-level shared block,\n * then the `__zcMkv` IIFE whose `__rf[]` bases and install target are the live\n * schema object passed in as `__schema`.\n */\nfunction buildValidator(schema: unknown, options?: JitOptions): void {\n const { schemas, shared } = compileSchemas([{ exportName: \"jit\", schema }], {\n compact: options?.output === \"compact\",\n mode: \"inline\",\n });\n const compiled = schemas[0];\n if (compiled === undefined) throw new Error(\"zod-compiler: schema produced no validator\");\n\n const body = [RUNTIME_PRELUDE, shared.code, `return ${generateIIFE(\"__schema\", compiled)};`].join(\n \"\\n\",\n );\n\n // The three bindings ZOD_CONFIG_IMPORT would have imported, passed in so the\n // evaluated code needs no module resolution of its own.\n // oxlint-disable-next-line no-new-func -- generating the validator IS the feature\n const factory = new Function(\n \"__zodCompilerConfig\",\n \"__zcCore\",\n \"__zcZodError\",\n \"__schema\",\n body,\n ) as (\n zodConfigFn: typeof zodConfig,\n zodCoreNs: typeof zodCore,\n zodErrorCtor: typeof ZodRealError,\n target: unknown,\n ) => unknown;\n\n factory(zodConfig, zodCore, ZodRealError, schema);\n}\n"],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import { ZodCompilerPluginOptions } from "../unplugin/types.js";
2
+ //#region src/register/config.d.ts
3
+ type RegisterOutput = Extract<ZodCompilerPluginOptions["output"], "schema" | "compact">;
4
+ type SharedRegisterOptions = Pick<ZodCompilerPluginOptions, "include" | "exclude" | "schemas" | "hoist">;
5
+ /** Configuration loaded by `zod-compiler/register` from the current working directory. */
6
+ interface ZodCompilerRegisterConfig extends SharedRegisterOptions {
7
+ /** JSON Schema used by editors. @default "./node_modules/zod-compiler/schema.json" */
8
+ $schema?: string | undefined;
9
+ /** Compile immediately instead of when a validation method is first read. @default false */
10
+ eager?: boolean | undefined;
11
+ /**
12
+ * Preserve the complete compiled validator, or delegate cold error production
13
+ * to the retained Zod schema in compact mode.
14
+ * @default "schema"
15
+ */
16
+ output?: RegisterOutput | undefined;
17
+ }
18
+ //#endregion
19
+ export { ZodCompilerRegisterConfig };
20
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","names":[],"sources":["../../src/register/config.ts"],"mappings":";;KAEK,iBAAiB,QAAQ;KACzB,wBAAwB,KAC3B;;UAKe,kCAAkC;;EAEjD;;EAEA;;;;;;EAMA,SAAS"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,68 @@
1
+ import { isCompiledSchema } from "../core/compile.js";
2
+ import { jit, jitAll } from "../jit.js";
3
+ import { decodeModuleSource, instrumentModule, isRegisterFormat } from "./transform.js";
4
+ import { registerHooks } from "node:module";
5
+ import path from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ import fs from "node:fs";
8
+ import { init } from "es-module-lexer";
9
+ //#region src/register/index.ts
10
+ const CONFIG_FILE = "zod-compiler.json";
11
+ const REGISTER_SYMBOL = Symbol.for("zod-compiler:register");
12
+ await init;
13
+ const config = loadConfig();
14
+ const jitOptions = {
15
+ eager: config.eager,
16
+ output: config.output
17
+ };
18
+ Object.defineProperty(globalThis, REGISTER_SYMBOL, {
19
+ configurable: true,
20
+ value(value, flatten = false) {
21
+ registerValue(value);
22
+ if (flatten && typeof value === "object" && value !== null) for (const exported of Object.values(value)) registerValue(exported);
23
+ return value;
24
+ }
25
+ });
26
+ registerHooks({ load(url, context, nextLoad) {
27
+ const loaded = nextLoad(url, context);
28
+ if (!url.startsWith("file:") || !isRegisterFormat(loaded.format) || loaded.source == null) return loaded;
29
+ const filename = fileURLToPath(url).replaceAll("\\", "/");
30
+ const source = decodeModuleSource(loaded.source);
31
+ let transformed;
32
+ try {
33
+ transformed = instrumentModule(source, filename, loaded.format, config);
34
+ } catch {
35
+ return loaded;
36
+ }
37
+ return transformed === null ? loaded : {
38
+ ...loaded,
39
+ source: transformed
40
+ };
41
+ } });
42
+ function registerValue(value) {
43
+ if (config.schemas === "explicit") {
44
+ if (isCompiledSchema(value)) jit(value, jitOptions);
45
+ } else jitAll({ value }, jitOptions);
46
+ }
47
+ function loadConfig() {
48
+ const filename = path.resolve(CONFIG_FILE);
49
+ let source;
50
+ try {
51
+ source = fs.readFileSync(filename, "utf8");
52
+ } catch (error) {
53
+ if (error.code === "ENOENT") return {};
54
+ throw error;
55
+ }
56
+ let value;
57
+ try {
58
+ value = JSON.parse(source);
59
+ } catch (error) {
60
+ throw new SyntaxError(`Cannot parse ${filename}: ${error.message}`, { cause: error });
61
+ }
62
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError(`${filename} must contain a JSON object`);
63
+ return value;
64
+ }
65
+ //#endregion
66
+ export {};
67
+
68
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/register/index.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport { registerHooks } from \"node:module\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { init } from \"es-module-lexer\";\nimport type { ZodType } from \"zod\";\nimport { isCompiledSchema } from \"../core/compile.js\";\nimport { jit, jitAll, type JitOptions } from \"../jit.js\";\nimport type { ZodCompilerRegisterConfig } from \"./config.js\";\nimport { decodeModuleSource, instrumentModule, isRegisterFormat } from \"./transform.js\";\n\nconst CONFIG_FILE = \"zod-compiler.json\";\nconst REGISTER_SYMBOL = Symbol.for(\"zod-compiler:register\");\n\nawait init;\n\nconst config = loadConfig();\nconst jitOptions: JitOptions = {\n eager: config.eager,\n output: config.output,\n};\n\nObject.defineProperty(globalThis, REGISTER_SYMBOL, {\n configurable: true,\n value(value: unknown, flatten = false): unknown {\n registerValue(value);\n if (flatten && typeof value === \"object\" && value !== null) {\n for (const exported of Object.values(value)) registerValue(exported);\n }\n return value;\n },\n});\n\nregisterHooks({\n load(url, context, nextLoad) {\n const loaded = nextLoad(url, context);\n if (!url.startsWith(\"file:\") || !isRegisterFormat(loaded.format) || loaded.source == null) {\n return loaded;\n }\n\n // Posix separators, because the include/exclude globs are matched with\n // picomatch and the plugin paths it was written against are bundler ids,\n // which are already normalized. `fileURLToPath` is the only caller that\n // yields native separators, so on Windows `include: [\"src/**\"]` matched\n // nothing and the whole feature went silently inert.\n const filename = fileURLToPath(url).replaceAll(\"\\\\\", \"/\");\n const source = decodeModuleSource(loaded.source);\n // This hook runs for EVERY module the process loads, and what it adds is\n // only an optimization. So nothing it does may fail a load: a config value\n // of the wrong shape, a lexer that chokes on a dialect it half-supports, a\n // bad `hoist.schemaNamePattern` — each would otherwise surface as a crash\n // at startup, in someone else's file, naming zod-compiler internals. Ship\n // the module unchanged instead and leave its schemas uncompiled.\n let transformed: string | null;\n try {\n transformed = instrumentModule(source, filename, loaded.format, config);\n } catch {\n return loaded;\n }\n return transformed === null ? loaded : { ...loaded, source: transformed };\n },\n});\n\nfunction registerValue(value: unknown): void {\n if (config.schemas === \"explicit\") {\n if (isCompiledSchema(value)) jit(value as unknown as ZodType, jitOptions);\n } else {\n jitAll({ value }, jitOptions);\n }\n}\n\nfunction loadConfig(): ZodCompilerRegisterConfig {\n const filename = path.resolve(CONFIG_FILE);\n let source: string;\n try {\n source = fs.readFileSync(filename, \"utf8\");\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return {};\n throw error;\n }\n\n let value: unknown;\n try {\n value = JSON.parse(source);\n } catch (error) {\n throw new SyntaxError(`Cannot parse ${filename}: ${(error as Error).message}`, {\n cause: error,\n });\n }\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new TypeError(`${filename} must contain a JSON object`);\n }\n return value as ZodCompilerRegisterConfig;\n}\n"],"mappings":";;;;;;;;;AAWA,MAAM,cAAc;AACpB,MAAM,kBAAkB,OAAO,IAAI,uBAAuB;AAE1D,MAAM;AAEN,MAAM,SAAS,WAAW;AAC1B,MAAM,aAAyB;CAC7B,OAAO,OAAO;CACd,QAAQ,OAAO;AACjB;AAEA,OAAO,eAAe,YAAY,iBAAiB;CACjD,cAAc;CACd,MAAM,OAAgB,UAAU,OAAgB;EAC9C,cAAc,KAAK;EACnB,IAAI,WAAW,OAAO,UAAU,YAAY,UAAU,MACpD,KAAK,MAAM,YAAY,OAAO,OAAO,KAAK,GAAG,cAAc,QAAQ;EAErE,OAAO;CACT;AACF,CAAC;AAED,cAAc,EACZ,KAAK,KAAK,SAAS,UAAU;CAC3B,MAAM,SAAS,SAAS,KAAK,OAAO;CACpC,IAAI,CAAC,IAAI,WAAW,OAAO,KAAK,CAAC,iBAAiB,OAAO,MAAM,KAAK,OAAO,UAAU,MACnF,OAAO;CAQT,MAAM,WAAW,cAAc,GAAG,CAAC,CAAC,WAAW,MAAM,GAAG;CACxD,MAAM,SAAS,mBAAmB,OAAO,MAAM;CAO/C,IAAI;CACJ,IAAI;EACF,cAAc,iBAAiB,QAAQ,UAAU,OAAO,QAAQ,MAAM;CACxE,QAAQ;EACN,OAAO;CACT;CACA,OAAO,gBAAgB,OAAO,SAAS;EAAE,GAAG;EAAQ,QAAQ;CAAY;AAC1E,EACF,CAAC;AAED,SAAS,cAAc,OAAsB;CAC3C,IAAI,OAAO,YAAY,YACjB;MAAA,iBAAiB,KAAK,GAAG,IAAI,OAA6B,UAAU;CAAA,OAExE,OAAO,EAAE,MAAM,GAAG,UAAU;AAEhC;AAEA,SAAS,aAAwC;CAC/C,MAAM,WAAW,KAAK,QAAQ,WAAW;CACzC,IAAI;CACJ,IAAI;EACF,SAAS,GAAG,aAAa,UAAU,MAAM;CAC3C,SAAS,OAAO;EACd,IAAK,MAAgC,SAAS,UAAU,OAAO,CAAC;EAChE,MAAM;CACR;CAEA,IAAI;CACJ,IAAI;EACF,QAAQ,KAAK,MAAM,MAAM;CAC3B,SAAS,OAAO;EACd,MAAM,IAAI,YAAY,gBAAgB,SAAS,IAAK,MAAgB,WAAW,EAC7E,OAAO,MACT,CAAC;CACH;CACA,IAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,MAAM,QAAQ,KAAK,GACpE,MAAM,IAAI,UAAU,GAAG,SAAS,4BAA4B;CAE9D,OAAO;AACT"}
@@ -0,0 +1,16 @@
1
+ import { ZodCompilerRegisterConfig } from "./config.js";
2
+ //#region src/register/transform.d.ts
3
+ type RegisterFormat = "commonjs" | "commonjs-typescript" | "module" | "module-typescript";
4
+ /** Formats whose source Node can execute after a synchronous load hook returns it. */
5
+ declare function isRegisterFormat(format: string | null | undefined): format is RegisterFormat;
6
+ /** Decode the textual module formats accepted by Node's load hook. */
7
+ declare function decodeModuleSource(source: string | ArrayBuffer | NodeJS.TypedArray): string;
8
+ /**
9
+ * Add lazy JIT registration to a module without executing it during the load hook.
10
+ * Named ESM exports are referenced by their real local binding, so aliases and
11
+ * multiple declarations work without rewriting their initializers.
12
+ */
13
+ declare function instrumentModule(source: string, filename: string, format: RegisterFormat, config: ZodCompilerRegisterConfig): string | null;
14
+ //#endregion
15
+ export { decodeModuleSource, instrumentModule, isRegisterFormat };
16
+ //# sourceMappingURL=transform.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transform.d.ts","names":[],"sources":["../../src/register/transform.ts"],"mappings":";;KAMK;;iBASW,iBAAiB,oCAAoC,UAAU;;iBAK/D,mBAAmB,iBAAiB,cAAc,OAAO;;;;;;iBAazD,iBACd,gBACA,kBACA,QAAQ,gBACR,QAAQ"}
@@ -0,0 +1,148 @@
1
+ import { hoistZodSchemasMeta } from "../unplugin/hoist.js";
2
+ import { shouldTransform } from "../unplugin/transform.js";
3
+ import { parse } from "es-module-lexer";
4
+ //#region src/register/transform.ts
5
+ const REGISTER_CALL = "globalThis[Symbol.for(\"zod-compiler:register\")]";
6
+ const SUPPORTED_FORMATS = /* @__PURE__ */ new Set([
7
+ "commonjs",
8
+ "commonjs-typescript",
9
+ "module",
10
+ "module-typescript"
11
+ ]);
12
+ /** Formats whose source Node can execute after a synchronous load hook returns it. */
13
+ function isRegisterFormat(format) {
14
+ return format !== null && format !== void 0 && SUPPORTED_FORMATS.has(format);
15
+ }
16
+ /** Decode the textual module formats accepted by Node's load hook. */
17
+ function decodeModuleSource(source) {
18
+ if (typeof source === "string") return source;
19
+ if (source instanceof ArrayBuffer) return new TextDecoder().decode(source);
20
+ return new TextDecoder().decode(new Uint8Array(source.buffer, source.byteOffset, source.byteLength));
21
+ }
22
+ /**
23
+ * Add lazy JIT registration to a module without executing it during the load hook.
24
+ * Named ESM exports are referenced by their real local binding, so aliases and
25
+ * multiple declarations work without rewriting their initializers.
26
+ */
27
+ function instrumentModule(source, filename, format, config) {
28
+ if (!shouldTransform(filename, {
29
+ ...config.include === void 0 ? {} : { include: config.include },
30
+ ...config.exclude === void 0 ? {} : { exclude: config.exclude }
31
+ })) return null;
32
+ if (!/[Zz]od/.test(source)) return null;
33
+ const hoisted = config.hoist === false ? null : hoistZodSchemasMeta(source, hoistOptions(config));
34
+ const code = hoisted?.code ?? source;
35
+ const names = new Set(hoisted?.schemas.map((schema) => schema.name) ?? []);
36
+ if (format === "commonjs" || format === "commonjs-typescript") return appendRegistrations(code, [...names], true);
37
+ let exports;
38
+ try {
39
+ [, exports] = parse(code);
40
+ } catch {
41
+ return hoisted?.code ?? null;
42
+ }
43
+ const typeOnlyNames = collectTypeOnlyExportNames(code, exports);
44
+ for (const exported of exports) {
45
+ if (exported.ls < 0 || exported.le < 0) continue;
46
+ const localName = code.slice(exported.ls, exported.le);
47
+ if (typeOnlyNames.has(localName)) continue;
48
+ names.add(localName);
49
+ }
50
+ return names.size === 0 ? hoisted?.code ?? null : appendRegistrations(code, [...names], false);
51
+ }
52
+ function hoistOptions(config) {
53
+ return typeof config.hoist === "object" ? config.hoist : void 0;
54
+ }
55
+ /**
56
+ * Reserved words es-module-lexer can hand back as a "local name". It reports
57
+ * byte offsets into TypeScript it only half-understands, so `export const enum
58
+ * Level` yields `enum` and `export default class extends Error {}` yields
59
+ * `extends`. Emitting either as an expression is a SyntaxError, which no
60
+ * try/catch can contain — the module never compiles.
61
+ */
62
+ const RESERVED_WORDS = /* @__PURE__ */ new Set([
63
+ "await",
64
+ "break",
65
+ "case",
66
+ "catch",
67
+ "class",
68
+ "const",
69
+ "continue",
70
+ "debugger",
71
+ "default",
72
+ "delete",
73
+ "do",
74
+ "else",
75
+ "enum",
76
+ "export",
77
+ "extends",
78
+ "false",
79
+ "finally",
80
+ "for",
81
+ "function",
82
+ "if",
83
+ "import",
84
+ "in",
85
+ "instanceof",
86
+ "new",
87
+ "null",
88
+ "return",
89
+ "super",
90
+ "switch",
91
+ "this",
92
+ "throw",
93
+ "true",
94
+ "try",
95
+ "typeof",
96
+ "var",
97
+ "void",
98
+ "while",
99
+ "with",
100
+ "yield"
101
+ ]);
102
+ /** Can this text be emitted as a bare identifier reference? */
103
+ function isEmittableIdentifier(name) {
104
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) && !RESERVED_WORDS.has(name);
105
+ }
106
+ /**
107
+ * Registration is an optimization, so a registration that cannot run must cost
108
+ * nothing but the optimization. Two things make one fail at runtime, and both
109
+ * are unavoidable when the name comes from a JS lexer reading TS source:
110
+ *
111
+ * - the binding does not exist — `export declare class`/`function` are erased
112
+ * by type stripping, and `export abstract class Base {}` is misreported as
113
+ * the local name `s` (the tail of the `class` keyword);
114
+ * - the binding exists but is not initialized yet — a re-exported import in a
115
+ * circular barrel is in its TDZ when the appended read runs.
116
+ *
117
+ * Both throw at the READ, so a per-call catch turns each from an app-down boot
118
+ * failure into a schema that merely stays uncompiled. Per call rather than one
119
+ * block around all of them, so one bad export does not cost its file-mates
120
+ * their registration. The `globalThis[Symbol.for(...)]` lookup sits inside the
121
+ * try too: a module that shadows `Symbol` would otherwise throw there instead.
122
+ */
123
+ function appendRegistrations(code, names, commonjs) {
124
+ const calls = names.filter(isEmittableIdentifier).map((name) => `try{${REGISTER_CALL}(${name})}catch{}`);
125
+ if (commonjs) calls.push(`try{${REGISTER_CALL}(module.exports, true)}catch{}`);
126
+ if (calls.length === 0) return code;
127
+ return `${code}\n;${calls.join("\n")}\n`;
128
+ }
129
+ /** es-module-lexer deliberately accepts TS syntax but reports `type` list entries as values. */
130
+ function collectTypeOnlyExportNames(code, exports) {
131
+ const names = /* @__PURE__ */ new Set();
132
+ for (const exported of exports) {
133
+ const statement = code.slice(exported.ss, exported.le < 0 ? exported.e : exported.le);
134
+ if (/^export\s+type\b/.test(statement)) {
135
+ if (exported.ln) names.add(exported.ln);
136
+ continue;
137
+ }
138
+ for (const match of statement.matchAll(/(?:^|[{,])\s*type\s+([A-Za-z_$][\w$]*)/g)) {
139
+ names.add("type");
140
+ if (match[1]) names.add(match[1]);
141
+ }
142
+ }
143
+ return names;
144
+ }
145
+ //#endregion
146
+ export { decodeModuleSource, instrumentModule, isRegisterFormat };
147
+
148
+ //# sourceMappingURL=transform.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transform.js","names":[],"sources":["../../src/register/transform.ts"],"sourcesContent":["import { parse, type ExportSpecifier } from \"es-module-lexer\";\nimport { hoistZodSchemasMeta, type HoistOptions } from \"../unplugin/hoist.js\";\nimport { shouldTransform } from \"../unplugin/transform.js\";\nimport type { ZodCompilerRegisterConfig } from \"./config.js\";\n\nconst REGISTER_CALL = 'globalThis[Symbol.for(\"zod-compiler:register\")]';\ntype RegisterFormat = \"commonjs\" | \"commonjs-typescript\" | \"module\" | \"module-typescript\";\nconst SUPPORTED_FORMATS = new Set([\n \"commonjs\",\n \"commonjs-typescript\",\n \"module\",\n \"module-typescript\",\n]);\n\n/** Formats whose source Node can execute after a synchronous load hook returns it. */\nexport function isRegisterFormat(format: string | null | undefined): format is RegisterFormat {\n return format !== null && format !== undefined && SUPPORTED_FORMATS.has(format);\n}\n\n/** Decode the textual module formats accepted by Node's load hook. */\nexport function decodeModuleSource(source: string | ArrayBuffer | NodeJS.TypedArray): string {\n if (typeof source === \"string\") return source;\n if (source instanceof ArrayBuffer) return new TextDecoder().decode(source);\n return new TextDecoder().decode(\n new Uint8Array(source.buffer, source.byteOffset, source.byteLength),\n );\n}\n\n/**\n * Add lazy JIT registration to a module without executing it during the load hook.\n * Named ESM exports are referenced by their real local binding, so aliases and\n * multiple declarations work without rewriting their initializers.\n */\nexport function instrumentModule(\n source: string,\n filename: string,\n format: RegisterFormat,\n config: ZodCompilerRegisterConfig,\n): string | null {\n const filter = {\n ...(config.include === undefined ? {} : { include: config.include }),\n ...(config.exclude === undefined ? {} : { exclude: config.exclude }),\n };\n if (!shouldTransform(filename, filter)) return null;\n if (!/[Zz]od/.test(source)) return null;\n\n const hoisted = config.hoist === false ? null : hoistZodSchemasMeta(source, hoistOptions(config));\n const code = hoisted?.code ?? source;\n const names = new Set(hoisted?.schemas.map((schema) => schema.name) ?? []);\n\n if (format === \"commonjs\" || format === \"commonjs-typescript\") {\n return appendRegistrations(code, [...names], true);\n }\n\n let exports: readonly ExportSpecifier[];\n try {\n [, exports] = parse(code);\n } catch {\n return hoisted?.code ?? null;\n }\n\n const typeOnlyNames = collectTypeOnlyExportNames(code, exports);\n for (const exported of exports) {\n if (exported.ls < 0 || exported.le < 0) continue;\n const localName = code.slice(exported.ls, exported.le);\n if (typeOnlyNames.has(localName)) continue;\n names.add(localName);\n }\n\n return names.size === 0 ? (hoisted?.code ?? null) : appendRegistrations(code, [...names], false);\n}\n\nfunction hoistOptions(config: ZodCompilerRegisterConfig): HoistOptions | undefined {\n return typeof config.hoist === \"object\" ? config.hoist : undefined;\n}\n\n/**\n * Reserved words es-module-lexer can hand back as a \"local name\". It reports\n * byte offsets into TypeScript it only half-understands, so `export const enum\n * Level` yields `enum` and `export default class extends Error {}` yields\n * `extends`. Emitting either as an expression is a SyntaxError, which no\n * try/catch can contain — the module never compiles.\n */\nconst RESERVED_WORDS = new Set([\n \"await\",\n \"break\",\n \"case\",\n \"catch\",\n \"class\",\n \"const\",\n \"continue\",\n \"debugger\",\n \"default\",\n \"delete\",\n \"do\",\n \"else\",\n \"enum\",\n \"export\",\n \"extends\",\n \"false\",\n \"finally\",\n \"for\",\n \"function\",\n \"if\",\n \"import\",\n \"in\",\n \"instanceof\",\n \"new\",\n \"null\",\n \"return\",\n \"super\",\n \"switch\",\n \"this\",\n \"throw\",\n \"true\",\n \"try\",\n \"typeof\",\n \"var\",\n \"void\",\n \"while\",\n \"with\",\n \"yield\",\n]);\n\n/** Can this text be emitted as a bare identifier reference? */\nfunction isEmittableIdentifier(name: string): boolean {\n return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) && !RESERVED_WORDS.has(name);\n}\n\n/**\n * Registration is an optimization, so a registration that cannot run must cost\n * nothing but the optimization. Two things make one fail at runtime, and both\n * are unavoidable when the name comes from a JS lexer reading TS source:\n *\n * - the binding does not exist — `export declare class`/`function` are erased\n * by type stripping, and `export abstract class Base {}` is misreported as\n * the local name `s` (the tail of the `class` keyword);\n * - the binding exists but is not initialized yet — a re-exported import in a\n * circular barrel is in its TDZ when the appended read runs.\n *\n * Both throw at the READ, so a per-call catch turns each from an app-down boot\n * failure into a schema that merely stays uncompiled. Per call rather than one\n * block around all of them, so one bad export does not cost its file-mates\n * their registration. The `globalThis[Symbol.for(...)]` lookup sits inside the\n * try too: a module that shadows `Symbol` would otherwise throw there instead.\n */\nfunction appendRegistrations(code: string, names: readonly string[], commonjs: boolean): string {\n const calls = names\n .filter(isEmittableIdentifier)\n .map((name) => `try{${REGISTER_CALL}(${name})}catch{}`);\n if (commonjs) calls.push(`try{${REGISTER_CALL}(module.exports, true)}catch{}`);\n if (calls.length === 0) return code;\n return `${code}\\n;${calls.join(\"\\n\")}\\n`;\n}\n\n/** es-module-lexer deliberately accepts TS syntax but reports `type` list entries as values. */\nfunction collectTypeOnlyExportNames(\n code: string,\n exports: readonly ExportSpecifier[],\n): ReadonlySet<string> {\n const names = new Set<string>();\n for (const exported of exports) {\n const statement = code.slice(exported.ss, exported.le < 0 ? exported.e : exported.le);\n if (/^export\\s+type\\b/.test(statement)) {\n if (exported.ln) names.add(exported.ln);\n continue;\n }\n for (const match of statement.matchAll(/(?:^|[{,])\\s*type\\s+([A-Za-z_$][\\w$]*)/g)) {\n names.add(\"type\");\n if (match[1]) names.add(match[1]);\n }\n }\n return names;\n}\n"],"mappings":"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"}
package/dist/swc.d.ts CHANGED
@@ -26,8 +26,12 @@ interface SwcCoreLike {
26
26
  * persistent cache, so hosts that need one must key transform results on
27
27
  * content themselves. `include`/`exclude` are honored: files they reject
28
28
  * pass through to SWC without the zod-compiler step.
29
+ *
30
+ * No `parallel` either: this is a per-file transformer, so whatever drives it
31
+ * decides how many files run at once. A pool owned by a single `transform()`
32
+ * call would compete with that instead of adding to it.
29
33
  */
30
- type ZodCompilerSwcOptions = Omit<ZodCompilerPluginOptions, "apply" | "cache" | "codegenMode"> & {
34
+ type ZodCompilerSwcOptions = Omit<ZodCompilerPluginOptions, "apply" | "cache" | "codegenMode" | "parallel"> & {
31
35
  /**
32
36
  * SWC is a transformer, not a bundler plugin host, so inline is the safe
33
37
  * default. Lean mode may emit virtual runtime imports that SWC cannot resolve
package/dist/swc.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"swc.d.ts","names":[],"sources":["../src/swc.ts"],"mappings":";;;;;;;;UAYiB;EACf;EACA;EACA;GACC;;UAGc;EACf;EACA;GACC;;UAGc;EACf,UAAU,cAAc,UAAU,aAAa,QAAQ;;;;;;;;;KAU7C,wBAAwB,KAClC;;;;;;EAQA;;UAGe;;EAEf,MAAM;;EAEN,cAAc;;UAGC,kCAAkC;;EAEjD;;UAGe;EACf,UAAU,cAAc,SAAS,4BAA4B,QAAQ;EACrE,cAAc,kBAAkB,UAAU,oBAAoB,QAAQ;;iBAoFlD,UACpB,cACA,SAAS,4BACR,QAAQ;iBAIW,cACpB,kBACA,UAAU,oBACT,QAAQ;iBAKK,kBAAkB,WAAW,oBAAoB;;;;;iBAgB3C,iBACpB,KAAK,aACL,cACA,SAAS,4BACR,QAAQ;iBAIa,YAAY,WAAW,oBAAoB"}
1
+ {"version":3,"file":"swc.d.ts","names":[],"sources":["../src/swc.ts"],"mappings":";;;;;;;;UAYiB;EACf;EACA;EACA;GACC;;UAGc;EACf;EACA;GACC;;UAGc;EACf,UAAU,cAAc,UAAU,aAAa,QAAQ;;;;;;;;;;;;;KAc7C,wBAAwB,KAClC;;;;;;EAQA;;UAGe;;EAEf,MAAM;;EAEN,cAAc;;UAGC,kCAAkC;;EAEjD;;UAGe;EACf,UAAU,cAAc,SAAS,4BAA4B,QAAQ;EACrE,cAAc,kBAAkB,UAAU,oBAAoB,QAAQ;;iBAoFlD,UACpB,cACA,SAAS,4BACR,QAAQ;iBAIW,cACpB,kBACA,UAAU,oBACT,QAAQ;iBAKK,kBAAkB,WAAW,oBAAoB;;;;;iBAgB3C,iBACpB,KAAK,aACL,cACA,SAAS,4BACR,QAAQ;iBAIa,YAAY,WAAW,oBAAoB"}
package/dist/swc.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"swc.js","names":[],"sources":["../src/swc.ts"],"sourcesContent":["import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { invalidateModuleCache } from \"./loader.js\";\nimport { shouldTransform, transformCodeWithMap } from \"./unplugin/transform.js\";\nimport type { TransformOptions, ZodCompilerPluginOptions } from \"./unplugin/types.js\";\n\n/**\n * Minimal @swc/core option shape. zod-compiler keeps @swc/core optional so\n * non-SWC users do not install a native dependency just by using the package.\n * `inputSourceMap` matches @swc/core: a JSON string or a boolean — swc does\n * not accept map objects.\n */\nexport interface SwcOptions {\n filename?: string | undefined;\n inputSourceMap?: boolean | string | undefined;\n sourceMaps?: boolean | \"inline\" | undefined;\n [key: string]: unknown;\n}\n\nexport interface SwcOutput {\n code: string;\n map?: string | undefined;\n [key: string]: unknown;\n}\n\nexport interface SwcCoreLike {\n transform(code: string, options?: SwcOptions): Promise<SwcOutput>;\n}\n\n/**\n * Plugin options that make sense for a transformer host. `apply` is Vite\n * lifecycle and `cache` is the bundler disk cache — the bridge keeps no\n * persistent cache, so hosts that need one must key transform results on\n * content themselves. `include`/`exclude` are honored: files they reject\n * pass through to SWC without the zod-compiler step.\n */\nexport type ZodCompilerSwcOptions = Omit<\n ZodCompilerPluginOptions,\n \"apply\" | \"cache\" | \"codegenMode\"\n> & {\n /**\n * SWC is a transformer, not a bundler plugin host, so inline is the safe\n * default. Lean mode may emit virtual runtime imports that SWC cannot resolve\n * unless another tool handles them after SWC.\n */\n codegenMode?: \"inline\" | \"lean\" | undefined;\n};\n\nexport interface SwcBridgeDefaults {\n /** Options passed through to @swc/core.transform. */\n swc?: SwcOptions | undefined;\n /** zod-compiler options. Defaults match the build plugins except codegenMode is \"inline\". */\n zodCompiler?: ZodCompilerSwcOptions | undefined;\n}\n\nexport interface SwcBridgeTransformOptions extends SwcBridgeDefaults {\n /** Absolute or project-relative filename for schema discovery and SWC config resolution. */\n filename: string;\n}\n\nexport interface SwcBridge {\n transform(code: string, options: SwcBridgeTransformOptions): Promise<SwcOutput>;\n transformFile(filename: string, options?: SwcBridgeDefaults): Promise<SwcOutput>;\n}\n\nfunction toTransformOptions(options?: ZodCompilerSwcOptions): TransformOptions {\n const output = options?.output ?? \"schema\";\n return {\n mode: options?.codegenMode ?? \"inline\",\n verbose: options?.verbose,\n zodCompat: output === \"schema\" || output === \"compact\",\n compact: output === \"compact\",\n autoDiscover: (options?.schemas ?? \"auto\") === \"auto\",\n hoist: options?.hoist,\n };\n}\n\n/**\n * Per-call options win key-by-key over factory defaults. The merge is\n * shallow: a per-call `swc.jsc` replaces the default `jsc` wholesale rather\n * than deep-merging parser/target settings.\n */\nfunction mergeOptions(\n defaults: SwcBridgeDefaults | undefined,\n options: SwcBridgeTransformOptions,\n): SwcBridgeTransformOptions {\n return {\n filename: options.filename,\n swc: { ...defaults?.swc, ...options.swc },\n zodCompiler: { ...defaults?.zodCompiler, ...options.zodCompiler },\n };\n}\n\nasync function loadSwc(): Promise<SwcCoreLike> {\n try {\n return (await import(\"@swc/core\")) as unknown as SwcCoreLike;\n } catch (error) {\n const cause = error instanceof Error ? `: ${error.message}` : \"\";\n throw new Error(\n `zod-compiler/swc requires @swc/core to be installed by the consuming project${cause}`,\n );\n }\n}\n\n/**\n * Last content seen per filename. Discovery executes schema files from DISK\n * through a module cache that outlives transform calls, so when a host (dev\n * server, watch-mode test runner) re-transforms a file with new content, the\n * stale executions must be dropped or the compiled validators keep\n * reflecting the old schema. Same content-diff scheme as the unplugin\n * transform hook; tracked for every file fed to the bridge — an excluded\n * file can still be a dependency a schema file executed.\n */\nconst lastSeenCode = new Map<string, string>();\n\nfunction invalidateOnContentChange(filename: string, code: string): void {\n const key = path.resolve(filename);\n const previous = lastSeenCode.get(key);\n if (previous !== undefined && previous !== code) {\n invalidateModuleCache();\n }\n lastSeenCode.set(key, code);\n}\n\nasync function transformWith(\n swc: SwcCoreLike,\n code: string,\n options: SwcBridgeTransformOptions,\n): Promise<SwcOutput> {\n invalidateOnContentChange(options.filename, code);\n\n const zodResult = shouldTransform(options.filename, options.zodCompiler)\n ? await transformCodeWithMap(code, options.filename, toTransformOptions(options.zodCompiler))\n : null;\n\n const swcOptions: SwcOptions = {\n ...options.swc,\n filename: options.swc?.filename ?? options.filename,\n };\n if (zodResult?.map && swcOptions.inputSourceMap === undefined) {\n swcOptions.inputSourceMap = JSON.stringify(zodResult.map);\n }\n\n return swc.transform(zodResult?.code ?? code, swcOptions);\n}\n\nexport async function transform(\n code: string,\n options: SwcBridgeTransformOptions,\n): Promise<SwcOutput> {\n return transformWith(await loadSwc(), code, options);\n}\n\nexport async function transformFile(\n filename: string,\n options?: SwcBridgeDefaults,\n): Promise<SwcOutput> {\n const code = await fs.readFile(filename, \"utf8\");\n return transform(code, { ...options, filename });\n}\n\nexport function createSwcCompiler(defaults?: SwcBridgeDefaults): SwcBridge {\n return {\n transform(code, options) {\n return transform(code, mergeOptions(defaults, options));\n },\n async transformFile(filename, options) {\n const code = await fs.readFile(filename, \"utf8\");\n return transform(code, mergeOptions(defaults, { ...options, filename }));\n },\n };\n}\n\n/**\n * Test seam for the SWC bridge. It is exported because it is also useful for\n * custom hosts that already own a @swc/core-compatible transform function.\n */\nexport async function transformWithSwc(\n swc: SwcCoreLike,\n code: string,\n options: SwcBridgeTransformOptions,\n): Promise<SwcOutput> {\n return transformWith(swc, code, options);\n}\n\nexport default function zodCompiler(defaults?: SwcBridgeDefaults): SwcBridge {\n return createSwcCompiler(defaults);\n}\n"],"mappings":";;;;;AAiEA,SAAS,mBAAmB,SAAmD;CAC7E,MAAM,SAAS,SAAS,UAAU;CAClC,OAAO;EACL,MAAM,SAAS,eAAe;EAC9B,SAAS,SAAS;EAClB,WAAW,WAAW,YAAY,WAAW;EAC7C,SAAS,WAAW;EACpB,eAAe,SAAS,WAAW,YAAY;EAC/C,OAAO,SAAS;CAClB;AACF;;;;;;AAOA,SAAS,aACP,UACA,SAC2B;CAC3B,OAAO;EACL,UAAU,QAAQ;EAClB,KAAK;GAAE,GAAG,UAAU;GAAK,GAAG,QAAQ;EAAI;EACxC,aAAa;GAAE,GAAG,UAAU;GAAa,GAAG,QAAQ;EAAY;CAClE;AACF;AAEA,eAAe,UAAgC;CAC7C,IAAI;EACF,OAAQ,MAAM,OAAO;CACvB,SAAS,OAAO;EACd,MAAM,QAAQ,iBAAiB,QAAQ,KAAK,MAAM,YAAY;EAC9D,MAAM,IAAI,MACR,+EAA+E,OACjF;CACF;AACF;;;;;;;;;;AAWA,MAAM,+BAAe,IAAI,IAAoB;AAE7C,SAAS,0BAA0B,UAAkB,MAAoB;CACvE,MAAM,MAAM,KAAK,QAAQ,QAAQ;CACjC,MAAM,WAAW,aAAa,IAAI,GAAG;CACrC,IAAI,aAAa,KAAA,KAAa,aAAa,MACzC,sBAAsB;CAExB,aAAa,IAAI,KAAK,IAAI;AAC5B;AAEA,eAAe,cACb,KACA,MACA,SACoB;CACpB,0BAA0B,QAAQ,UAAU,IAAI;CAEhD,MAAM,YAAY,gBAAgB,QAAQ,UAAU,QAAQ,WAAW,IACnE,MAAM,qBAAqB,MAAM,QAAQ,UAAU,mBAAmB,QAAQ,WAAW,CAAC,IAC1F;CAEJ,MAAM,aAAyB;EAC7B,GAAG,QAAQ;EACX,UAAU,QAAQ,KAAK,YAAY,QAAQ;CAC7C;CACA,IAAI,WAAW,OAAO,WAAW,mBAAmB,KAAA,GAClD,WAAW,iBAAiB,KAAK,UAAU,UAAU,GAAG;CAG1D,OAAO,IAAI,UAAU,WAAW,QAAQ,MAAM,UAAU;AAC1D;AAEA,eAAsB,UACpB,MACA,SACoB;CACpB,OAAO,cAAc,MAAM,QAAQ,GAAG,MAAM,OAAO;AACrD;AAEA,eAAsB,cACpB,UACA,SACoB;CAEpB,OAAO,UAAU,MADE,GAAG,SAAS,UAAU,MAAM,GACxB;EAAE,GAAG;EAAS;CAAS,CAAC;AACjD;AAEA,SAAgB,kBAAkB,UAAyC;CACzE,OAAO;EACL,UAAU,MAAM,SAAS;GACvB,OAAO,UAAU,MAAM,aAAa,UAAU,OAAO,CAAC;EACxD;EACA,MAAM,cAAc,UAAU,SAAS;GAErC,OAAO,UAAU,MADE,GAAG,SAAS,UAAU,MAAM,GACxB,aAAa,UAAU;IAAE,GAAG;IAAS;GAAS,CAAC,CAAC;EACzE;CACF;AACF;;;;;AAMA,eAAsB,iBACpB,KACA,MACA,SACoB;CACpB,OAAO,cAAc,KAAK,MAAM,OAAO;AACzC;AAEA,SAAwB,YAAY,UAAyC;CAC3E,OAAO,kBAAkB,QAAQ;AACnC"}
1
+ {"version":3,"file":"swc.js","names":[],"sources":["../src/swc.ts"],"sourcesContent":["import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { invalidateModuleCache } from \"./loader.js\";\nimport { shouldTransform, transformCodeWithMap } from \"./unplugin/transform.js\";\nimport type { TransformOptions, ZodCompilerPluginOptions } from \"./unplugin/types.js\";\n\n/**\n * Minimal @swc/core option shape. zod-compiler keeps @swc/core optional so\n * non-SWC users do not install a native dependency just by using the package.\n * `inputSourceMap` matches @swc/core: a JSON string or a boolean — swc does\n * not accept map objects.\n */\nexport interface SwcOptions {\n filename?: string | undefined;\n inputSourceMap?: boolean | string | undefined;\n sourceMaps?: boolean | \"inline\" | undefined;\n [key: string]: unknown;\n}\n\nexport interface SwcOutput {\n code: string;\n map?: string | undefined;\n [key: string]: unknown;\n}\n\nexport interface SwcCoreLike {\n transform(code: string, options?: SwcOptions): Promise<SwcOutput>;\n}\n\n/**\n * Plugin options that make sense for a transformer host. `apply` is Vite\n * lifecycle and `cache` is the bundler disk cache — the bridge keeps no\n * persistent cache, so hosts that need one must key transform results on\n * content themselves. `include`/`exclude` are honored: files they reject\n * pass through to SWC without the zod-compiler step.\n *\n * No `parallel` either: this is a per-file transformer, so whatever drives it\n * decides how many files run at once. A pool owned by a single `transform()`\n * call would compete with that instead of adding to it.\n */\nexport type ZodCompilerSwcOptions = Omit<\n ZodCompilerPluginOptions,\n \"apply\" | \"cache\" | \"codegenMode\" | \"parallel\"\n> & {\n /**\n * SWC is a transformer, not a bundler plugin host, so inline is the safe\n * default. Lean mode may emit virtual runtime imports that SWC cannot resolve\n * unless another tool handles them after SWC.\n */\n codegenMode?: \"inline\" | \"lean\" | undefined;\n};\n\nexport interface SwcBridgeDefaults {\n /** Options passed through to @swc/core.transform. */\n swc?: SwcOptions | undefined;\n /** zod-compiler options. Defaults match the build plugins except codegenMode is \"inline\". */\n zodCompiler?: ZodCompilerSwcOptions | undefined;\n}\n\nexport interface SwcBridgeTransformOptions extends SwcBridgeDefaults {\n /** Absolute or project-relative filename for schema discovery and SWC config resolution. */\n filename: string;\n}\n\nexport interface SwcBridge {\n transform(code: string, options: SwcBridgeTransformOptions): Promise<SwcOutput>;\n transformFile(filename: string, options?: SwcBridgeDefaults): Promise<SwcOutput>;\n}\n\nfunction toTransformOptions(options?: ZodCompilerSwcOptions): TransformOptions {\n const output = options?.output ?? \"schema\";\n return {\n mode: options?.codegenMode ?? \"inline\",\n verbose: options?.verbose,\n zodCompat: output === \"schema\" || output === \"compact\",\n compact: output === \"compact\",\n autoDiscover: (options?.schemas ?? \"auto\") === \"auto\",\n hoist: options?.hoist,\n };\n}\n\n/**\n * Per-call options win key-by-key over factory defaults. The merge is\n * shallow: a per-call `swc.jsc` replaces the default `jsc` wholesale rather\n * than deep-merging parser/target settings.\n */\nfunction mergeOptions(\n defaults: SwcBridgeDefaults | undefined,\n options: SwcBridgeTransformOptions,\n): SwcBridgeTransformOptions {\n return {\n filename: options.filename,\n swc: { ...defaults?.swc, ...options.swc },\n zodCompiler: { ...defaults?.zodCompiler, ...options.zodCompiler },\n };\n}\n\nasync function loadSwc(): Promise<SwcCoreLike> {\n try {\n return (await import(\"@swc/core\")) as unknown as SwcCoreLike;\n } catch (error) {\n const cause = error instanceof Error ? `: ${error.message}` : \"\";\n throw new Error(\n `zod-compiler/swc requires @swc/core to be installed by the consuming project${cause}`,\n );\n }\n}\n\n/**\n * Last content seen per filename. Discovery executes schema files from DISK\n * through a module cache that outlives transform calls, so when a host (dev\n * server, watch-mode test runner) re-transforms a file with new content, the\n * stale executions must be dropped or the compiled validators keep\n * reflecting the old schema. Same content-diff scheme as the unplugin\n * transform hook; tracked for every file fed to the bridge — an excluded\n * file can still be a dependency a schema file executed.\n */\nconst lastSeenCode = new Map<string, string>();\n\nfunction invalidateOnContentChange(filename: string, code: string): void {\n const key = path.resolve(filename);\n const previous = lastSeenCode.get(key);\n if (previous !== undefined && previous !== code) {\n invalidateModuleCache();\n }\n lastSeenCode.set(key, code);\n}\n\nasync function transformWith(\n swc: SwcCoreLike,\n code: string,\n options: SwcBridgeTransformOptions,\n): Promise<SwcOutput> {\n invalidateOnContentChange(options.filename, code);\n\n const zodResult = shouldTransform(options.filename, options.zodCompiler)\n ? await transformCodeWithMap(code, options.filename, toTransformOptions(options.zodCompiler))\n : null;\n\n const swcOptions: SwcOptions = {\n ...options.swc,\n filename: options.swc?.filename ?? options.filename,\n };\n if (zodResult?.map && swcOptions.inputSourceMap === undefined) {\n swcOptions.inputSourceMap = JSON.stringify(zodResult.map);\n }\n\n return swc.transform(zodResult?.code ?? code, swcOptions);\n}\n\nexport async function transform(\n code: string,\n options: SwcBridgeTransformOptions,\n): Promise<SwcOutput> {\n return transformWith(await loadSwc(), code, options);\n}\n\nexport async function transformFile(\n filename: string,\n options?: SwcBridgeDefaults,\n): Promise<SwcOutput> {\n const code = await fs.readFile(filename, \"utf8\");\n return transform(code, { ...options, filename });\n}\n\nexport function createSwcCompiler(defaults?: SwcBridgeDefaults): SwcBridge {\n return {\n transform(code, options) {\n return transform(code, mergeOptions(defaults, options));\n },\n async transformFile(filename, options) {\n const code = await fs.readFile(filename, \"utf8\");\n return transform(code, mergeOptions(defaults, { ...options, filename }));\n },\n };\n}\n\n/**\n * Test seam for the SWC bridge. It is exported because it is also useful for\n * custom hosts that already own a @swc/core-compatible transform function.\n */\nexport async function transformWithSwc(\n swc: SwcCoreLike,\n code: string,\n options: SwcBridgeTransformOptions,\n): Promise<SwcOutput> {\n return transformWith(swc, code, options);\n}\n\nexport default function zodCompiler(defaults?: SwcBridgeDefaults): SwcBridge {\n return createSwcCompiler(defaults);\n}\n"],"mappings":";;;;;AAqEA,SAAS,mBAAmB,SAAmD;CAC7E,MAAM,SAAS,SAAS,UAAU;CAClC,OAAO;EACL,MAAM,SAAS,eAAe;EAC9B,SAAS,SAAS;EAClB,WAAW,WAAW,YAAY,WAAW;EAC7C,SAAS,WAAW;EACpB,eAAe,SAAS,WAAW,YAAY;EAC/C,OAAO,SAAS;CAClB;AACF;;;;;;AAOA,SAAS,aACP,UACA,SAC2B;CAC3B,OAAO;EACL,UAAU,QAAQ;EAClB,KAAK;GAAE,GAAG,UAAU;GAAK,GAAG,QAAQ;EAAI;EACxC,aAAa;GAAE,GAAG,UAAU;GAAa,GAAG,QAAQ;EAAY;CAClE;AACF;AAEA,eAAe,UAAgC;CAC7C,IAAI;EACF,OAAQ,MAAM,OAAO;CACvB,SAAS,OAAO;EACd,MAAM,QAAQ,iBAAiB,QAAQ,KAAK,MAAM,YAAY;EAC9D,MAAM,IAAI,MACR,+EAA+E,OACjF;CACF;AACF;;;;;;;;;;AAWA,MAAM,+BAAe,IAAI,IAAoB;AAE7C,SAAS,0BAA0B,UAAkB,MAAoB;CACvE,MAAM,MAAM,KAAK,QAAQ,QAAQ;CACjC,MAAM,WAAW,aAAa,IAAI,GAAG;CACrC,IAAI,aAAa,KAAA,KAAa,aAAa,MACzC,sBAAsB;CAExB,aAAa,IAAI,KAAK,IAAI;AAC5B;AAEA,eAAe,cACb,KACA,MACA,SACoB;CACpB,0BAA0B,QAAQ,UAAU,IAAI;CAEhD,MAAM,YAAY,gBAAgB,QAAQ,UAAU,QAAQ,WAAW,IACnE,MAAM,qBAAqB,MAAM,QAAQ,UAAU,mBAAmB,QAAQ,WAAW,CAAC,IAC1F;CAEJ,MAAM,aAAyB;EAC7B,GAAG,QAAQ;EACX,UAAU,QAAQ,KAAK,YAAY,QAAQ;CAC7C;CACA,IAAI,WAAW,OAAO,WAAW,mBAAmB,KAAA,GAClD,WAAW,iBAAiB,KAAK,UAAU,UAAU,GAAG;CAG1D,OAAO,IAAI,UAAU,WAAW,QAAQ,MAAM,UAAU;AAC1D;AAEA,eAAsB,UACpB,MACA,SACoB;CACpB,OAAO,cAAc,MAAM,QAAQ,GAAG,MAAM,OAAO;AACrD;AAEA,eAAsB,cACpB,UACA,SACoB;CAEpB,OAAO,UAAU,MADE,GAAG,SAAS,UAAU,MAAM,GACxB;EAAE,GAAG;EAAS;CAAS,CAAC;AACjD;AAEA,SAAgB,kBAAkB,UAAyC;CACzE,OAAO;EACL,UAAU,MAAM,SAAS;GACvB,OAAO,UAAU,MAAM,aAAa,UAAU,OAAO,CAAC;EACxD;EACA,MAAM,cAAc,UAAU,SAAS;GAErC,OAAO,UAAU,MADE,GAAG,SAAS,UAAU,MAAM,GACxB,aAAa,UAAU;IAAE,GAAG;IAAS;GAAS,CAAC,CAAC;EACzE;CACF;AACF;;;;;AAMA,eAAsB,iBACpB,KACA,MACA,SACoB;CACpB,OAAO,cAAc,KAAK,MAAM,OAAO;AACzC;AAEA,SAAwB,YAAY,UAAyC;CAC3E,OAAO,kBAAkB,QAAQ;AACnC"}