zod-compiler 1.27.0 → 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.
@@ -0,0 +1,116 @@
1
+ import { getFirstPartyModulePaths, invalidateModuleCache } from "../loader.js";
2
+ import { transformCodeWithMap } from "./transform.js";
3
+ import { parentPort } from "node:worker_threads";
4
+ //#region src/unplugin/pool-worker.ts
5
+ /**
6
+ * Worker entry for the transform pool (see ./pool.ts).
7
+ *
8
+ * One transform at a time, on its own module graph. The worker owns a private
9
+ * loader — its own jiti instance, its own zod, its own execution cache — which
10
+ * is what makes running several of these concurrently sound: the serialization
11
+ * inside ../loader.ts exists to stop concurrent transforms from double-
12
+ * executing a SHARED dependency, and nothing is shared across threads.
13
+ *
14
+ * The protocol deliberately carries no callbacks. In-process, the transform
15
+ * reports progress through `onDiscovery`/`onSubstantialWork`/
16
+ * `onUncacheableResult`/`onBuildStats`, all of which the disk cache reads to
17
+ * decide whether and how to persist. Functions do not survive structured
18
+ * cloning, so they are collapsed here into flags on a single reply — the main
19
+ * thread reconstructs exactly the decisions it would have made.
20
+ */
21
+ const port = parentPort;
22
+ if (port === null) throw new Error("zod-compiler pool worker was started outside a worker thread");
23
+ /**
24
+ * Paths already reported to the pool. The executed-module list grows into the
25
+ * thousands on a real project and the main thread only needs the union, so
26
+ * each reply carries the delta since the last one rather than the whole set.
27
+ */
28
+ let reported = /* @__PURE__ */ new Set();
29
+ /**
30
+ * Modules executed since the previous report, or null when this runtime cannot
31
+ * track them at all (Bun/Deno use native import, whose cache cannot be
32
+ * enumerated or evicted). Null propagates: the pool stops offering a superset
33
+ * and the disk cache declines to persist deferred entries, which is what the
34
+ * in-process path already does on those runtimes.
35
+ */
36
+ function moduleDelta() {
37
+ const all = getFirstPartyModulePaths();
38
+ if (all === null) return null;
39
+ const delta = [];
40
+ for (const p of all) if (!reported.has(p)) {
41
+ reported.add(p);
42
+ delta.push(p);
43
+ }
44
+ return delta;
45
+ }
46
+ async function handleTransform(request) {
47
+ let discoveryRan = false;
48
+ let substantialWork = false;
49
+ let uncacheable = false;
50
+ let stats = null;
51
+ try {
52
+ const result = {
53
+ output: await transformCodeWithMap(request.code, request.id, {
54
+ ...request.options,
55
+ onDiscovery() {
56
+ discoveryRan = true;
57
+ },
58
+ onSubstantialWork() {
59
+ substantialWork = true;
60
+ },
61
+ onUncacheableResult() {
62
+ uncacheable = true;
63
+ },
64
+ onBuildStats(s) {
65
+ stats = s;
66
+ }
67
+ }),
68
+ discoveryRan,
69
+ substantialWork,
70
+ uncacheable,
71
+ stats,
72
+ newModulePaths: moduleDelta()
73
+ };
74
+ return {
75
+ type: "result",
76
+ seq: request.seq,
77
+ result
78
+ };
79
+ } catch (error) {
80
+ const e = error instanceof Error ? error : new Error(String(error));
81
+ return {
82
+ type: "error",
83
+ seq: request.seq,
84
+ name: e.name,
85
+ message: e.message,
86
+ stack: e.stack
87
+ };
88
+ }
89
+ }
90
+ port.on("message", (request) => {
91
+ if (request.type === "invalidate") {
92
+ invalidateModuleCache();
93
+ reported = /* @__PURE__ */ new Set();
94
+ port.postMessage({
95
+ type: "invalidated",
96
+ seq: request.seq
97
+ });
98
+ return;
99
+ }
100
+ handleTransform(request).then((response) => {
101
+ port.postMessage(response);
102
+ }, (error) => {
103
+ const e = error instanceof Error ? error : new Error(String(error));
104
+ port.postMessage({
105
+ type: "error",
106
+ seq: request.seq,
107
+ name: e.name,
108
+ message: e.message,
109
+ stack: e.stack
110
+ });
111
+ });
112
+ });
113
+ //#endregion
114
+ export {};
115
+
116
+ //# sourceMappingURL=pool-worker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pool-worker.js","names":[],"sources":["../../src/unplugin/pool-worker.ts"],"sourcesContent":["/**\n * Worker entry for the transform pool (see ./pool.ts).\n *\n * One transform at a time, on its own module graph. The worker owns a private\n * loader — its own jiti instance, its own zod, its own execution cache — which\n * is what makes running several of these concurrently sound: the serialization\n * inside ../loader.ts exists to stop concurrent transforms from double-\n * executing a SHARED dependency, and nothing is shared across threads.\n *\n * The protocol deliberately carries no callbacks. In-process, the transform\n * reports progress through `onDiscovery`/`onSubstantialWork`/\n * `onUncacheableResult`/`onBuildStats`, all of which the disk cache reads to\n * decide whether and how to persist. Functions do not survive structured\n * cloning, so they are collapsed here into flags on a single reply — the main\n * thread reconstructs exactly the decisions it would have made.\n */\n\nimport { parentPort } from \"node:worker_threads\";\nimport { getFirstPartyModulePaths, invalidateModuleCache } from \"../loader.js\";\nimport type { PoolRequest, PoolResponse, PoolTransformResult } from \"./pool.js\";\nimport { transformCodeWithMap } from \"./transform.js\";\nimport type { BuildStats } from \"./types.js\";\n\nconst port = parentPort;\nif (port === null) {\n throw new Error(\"zod-compiler pool worker was started outside a worker thread\");\n}\n\n/**\n * Paths already reported to the pool. The executed-module list grows into the\n * thousands on a real project and the main thread only needs the union, so\n * each reply carries the delta since the last one rather than the whole set.\n */\nlet reported = new Set<string>();\n\n/**\n * Modules executed since the previous report, or null when this runtime cannot\n * track them at all (Bun/Deno use native import, whose cache cannot be\n * enumerated or evicted). Null propagates: the pool stops offering a superset\n * and the disk cache declines to persist deferred entries, which is what the\n * in-process path already does on those runtimes.\n */\nfunction moduleDelta(): string[] | null {\n const all = getFirstPartyModulePaths();\n if (all === null) return null;\n const delta: string[] = [];\n for (const p of all) {\n if (!reported.has(p)) {\n reported.add(p);\n delta.push(p);\n }\n }\n return delta;\n}\n\nasync function handleTransform(\n request: Extract<PoolRequest, { type: \"transform\" }>,\n): Promise<PoolResponse> {\n let discoveryRan = false;\n let substantialWork = false;\n let uncacheable = false;\n let stats: BuildStats | null = null;\n\n try {\n const output = await transformCodeWithMap(request.code, request.id, {\n ...request.options,\n onDiscovery() {\n discoveryRan = true;\n },\n onSubstantialWork() {\n substantialWork = true;\n },\n onUncacheableResult() {\n uncacheable = true;\n },\n onBuildStats(s) {\n stats = s;\n },\n });\n const result: PoolTransformResult = {\n output,\n discoveryRan,\n substantialWork,\n uncacheable,\n // TS cannot see the assignment inside the callback above.\n stats: stats as BuildStats | null,\n newModulePaths: moduleDelta(),\n };\n return { type: \"result\", seq: request.seq, result };\n } catch (error) {\n // The transform's own failures — a schema file that will not load in\n // `schemas: \"explicit\"` mode — must reach the bundler with their original\n // message, so they are forwarded rather than swallowed. The pool rebuilds\n // the Error on the other side; only these three fields survive cloning.\n const e = error instanceof Error ? error : new Error(String(error));\n return { type: \"error\", seq: request.seq, name: e.name, message: e.message, stack: e.stack };\n }\n}\n\nport.on(\"message\", (request: PoolRequest) => {\n if (request.type === \"invalidate\") {\n invalidateModuleCache();\n // The loader just dropped every first-party module, so the next discovery\n // re-executes and must re-report them: a delta against the pre-change set\n // would report nothing and the pool's union would keep paths whose entries\n // are gone. Harmless for cache soundness (a superset only over-\n // invalidates) but it would grow without bound across a long watch session.\n reported = new Set<string>();\n port.postMessage({ type: \"invalidated\", seq: request.seq } satisfies PoolResponse);\n return;\n }\n\n void handleTransform(request).then(\n (response) => {\n port.postMessage(response);\n },\n (error: unknown) => {\n // handleTransform catches its own failures; this is the last resort for\n // a fault in the reply path itself (an output that will not clone).\n const e = error instanceof Error ? error : new Error(String(error));\n port.postMessage({\n type: \"error\",\n seq: request.seq,\n name: e.name,\n message: e.message,\n stack: e.stack,\n } satisfies PoolResponse);\n },\n );\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAuBA,MAAM,OAAO;AACb,IAAI,SAAS,MACX,MAAM,IAAI,MAAM,8DAA8D;;;;;;AAQhF,IAAI,2BAAW,IAAI,IAAY;;;;;;;;AAS/B,SAAS,cAA+B;CACtC,MAAM,MAAM,yBAAyB;CACrC,IAAI,QAAQ,MAAM,OAAO;CACzB,MAAM,QAAkB,CAAC;CACzB,KAAK,MAAM,KAAK,KACd,IAAI,CAAC,SAAS,IAAI,CAAC,GAAG;EACpB,SAAS,IAAI,CAAC;EACd,MAAM,KAAK,CAAC;CACd;CAEF,OAAO;AACT;AAEA,eAAe,gBACb,SACuB;CACvB,IAAI,eAAe;CACnB,IAAI,kBAAkB;CACtB,IAAI,cAAc;CAClB,IAAI,QAA2B;CAE/B,IAAI;EAgBF,MAAM,SAA8B;GAClC,QAAA,MAhBmB,qBAAqB,QAAQ,MAAM,QAAQ,IAAI;IAClE,GAAG,QAAQ;IACX,cAAc;KACZ,eAAe;IACjB;IACA,oBAAoB;KAClB,kBAAkB;IACpB;IACA,sBAAsB;KACpB,cAAc;IAChB;IACA,aAAa,GAAG;KACd,QAAQ;IACV;GACF,CAAC;GAGC;GACA;GACA;GAEO;GACP,gBAAgB,YAAY;EAC9B;EACA,OAAO;GAAE,MAAM;GAAU,KAAK,QAAQ;GAAK;EAAO;CACpD,SAAS,OAAO;EAKd,MAAM,IAAI,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;EAClE,OAAO;GAAE,MAAM;GAAS,KAAK,QAAQ;GAAK,MAAM,EAAE;GAAM,SAAS,EAAE;GAAS,OAAO,EAAE;EAAM;CAC7F;AACF;AAEA,KAAK,GAAG,YAAY,YAAyB;CAC3C,IAAI,QAAQ,SAAS,cAAc;EACjC,sBAAsB;EAMtB,2BAAW,IAAI,IAAY;EAC3B,KAAK,YAAY;GAAE,MAAM;GAAe,KAAK,QAAQ;EAAI,CAAwB;EACjF;CACF;CAEA,gBAAqB,OAAO,CAAC,CAAC,MAC3B,aAAa;EACZ,KAAK,YAAY,QAAQ;CAC3B,IACC,UAAmB;EAGlB,MAAM,IAAI,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;EAClE,KAAK,YAAY;GACf,MAAM;GACN,KAAK,QAAQ;GACb,MAAM,EAAE;GACR,SAAS,EAAE;GACX,OAAO,EAAE;EACX,CAAwB;CAC1B,CACF;AACF,CAAC"}
@@ -0,0 +1,162 @@
1
+ import { CodegenMode } from "../core/codegen/context.js";
2
+ import { HoistOptions } from "./hoist.js";
3
+ import { BuildStats, TransformOptions } from "./types.js";
4
+ import { TransformSourceMap } from "./transform.js";
5
+ //#region src/unplugin/pool.d.ts
6
+ /** The subset of TransformOptions that survives structured cloning. */
7
+ interface PoolTransformOptions {
8
+ mode: CodegenMode;
9
+ runtimeId?: string | undefined;
10
+ zodCompat?: boolean | undefined;
11
+ compact?: boolean | undefined;
12
+ verbose?: boolean | undefined;
13
+ autoDiscover?: boolean | undefined;
14
+ hoist?: boolean | HoistOptions | undefined;
15
+ }
16
+ /** Main → worker. */
17
+ type PoolRequest = {
18
+ type: "transform";
19
+ seq: number;
20
+ code: string;
21
+ id: string;
22
+ options: PoolTransformOptions;
23
+ } | {
24
+ type: "invalidate";
25
+ seq: number;
26
+ };
27
+ /**
28
+ * What a worker observed while transforming one file: the output plus the
29
+ * signals the main thread's disk-cache bookkeeping needs, which arrive as
30
+ * callbacks in the in-process path and cannot cross a thread boundary.
31
+ */
32
+ interface PoolTransformResult {
33
+ output: {
34
+ code: string;
35
+ map: TransformSourceMap | null;
36
+ } | null;
37
+ discoveryRan: boolean;
38
+ substantialWork: boolean;
39
+ uncacheable: boolean;
40
+ stats: BuildStats | null;
41
+ /**
42
+ * First-party modules this worker executed since its last report, or `null`
43
+ * when the worker cannot track them (Bun/Deno native import).
44
+ */
45
+ newModulePaths: string[] | null;
46
+ }
47
+ /** Worker → main. */
48
+ type PoolResponse = {
49
+ type: "result";
50
+ seq: number;
51
+ result: PoolTransformResult;
52
+ } | {
53
+ type: "error";
54
+ seq: number;
55
+ name: string;
56
+ message: string;
57
+ stack?: string | undefined;
58
+ } | {
59
+ type: "invalidated";
60
+ seq: number;
61
+ };
62
+ /**
63
+ * Raised when the pool itself failed — a worker that would not spawn, or one
64
+ * that died mid-task (OOM is the realistic case; each worker holds a full
65
+ * module graph). Distinct from a transform's own error, which is rethrown with
66
+ * its original identity, because the caller's response differs: an
67
+ * infrastructure failure is worth retrying on the bundler thread, a schema
68
+ * that cannot compile is not.
69
+ */
70
+ declare class PoolUnavailableError extends Error {
71
+ constructor(message: string, options?: {
72
+ cause?: unknown;
73
+ });
74
+ }
75
+ /**
76
+ * Resolve `parallel` to a worker count. `false`/`undefined` disable the pool.
77
+ *
78
+ * `true` leaves one core for the bundler itself — it is still parsing,
79
+ * resolving and generating chunks while transforms run — and then caps at
80
+ * {@link MAX_AUTO_WORKERS}.
81
+ */
82
+ declare function resolvePoolSize(parallel: boolean | number | undefined): number;
83
+ /** Strip the callbacks from TransformOptions; what remains is cloneable. */
84
+ declare function poolTransformOptions(options: TransformOptions): PoolTransformOptions;
85
+ /** Terminate every live transform pool. */
86
+ declare function disposeAllPools(): Promise<void>;
87
+ /**
88
+ * A lazily grown pool of transform workers.
89
+ *
90
+ * Not a general-purpose executor: it knows the transform protocol, and it
91
+ * knows that a broadcast invalidation has to be ordered ahead of every
92
+ * subsequent transform on every worker (per-worker message delivery is FIFO,
93
+ * so posting the broadcast before any later task is sufficient — and the only
94
+ * ordering guarantee available, since a worker mid-transform cannot be
95
+ * interrupted).
96
+ */
97
+ declare class TransformPool {
98
+ private readonly size;
99
+ private readonly entry;
100
+ private readonly workers;
101
+ private readonly queue;
102
+ /** In-flight requests by seq, so a worker exit can reject exactly its own. */
103
+ private readonly inflight;
104
+ private readonly executedModules;
105
+ /** A worker reported it cannot track executed modules — the union is unusable. */
106
+ private moduleTrackingUnavailable;
107
+ private seq;
108
+ private disposed;
109
+ /** Worker deaths since the last successful result; trips MAX_CONSECUTIVE_FAILURES. */
110
+ private consecutiveFailures;
111
+ /** Set once the breaker trips: every later run() rejects without spawning. */
112
+ private brokenPool;
113
+ private constructor();
114
+ /**
115
+ * Build a pool of `size` workers, or return null when workers are
116
+ * unavailable in this installation (the entry could not be located). Callers
117
+ * treat null as "run in-process".
118
+ */
119
+ static create(size: number): TransformPool | null;
120
+ /** Transform one file on a worker. Rejects with the transform's own error, or PoolUnavailableError. */
121
+ run(code: string, id: string, options: PoolTransformOptions): Promise<PoolTransformResult>;
122
+ /**
123
+ * Drop every worker's executed-module cache, so the next discovery re-runs
124
+ * changed schema graphs. Mirrors the loader's invalidateModuleCache() for
125
+ * the in-process path; called from watchChange.
126
+ *
127
+ * Fire-and-forget: the broadcast is posted to every live worker before any
128
+ * later transform can be, and per-worker FIFO delivery does the rest. A
129
+ * transform ALREADY executing in a worker keeps its pre-change module cache
130
+ * — the same window the in-process path has, where invalidation lands
131
+ * between awaits — and the main thread's own result cache is cleared
132
+ * alongside, so the file is recomputed either way.
133
+ */
134
+ invalidate(): void;
135
+ /**
136
+ * Union of the first-party modules every worker has executed, or null when
137
+ * any worker cannot track them. Synchronous, because DiskCache.flushDeferred
138
+ * runs from `buildEnd` and from a process 'exit' hook, where only
139
+ * synchronous work is allowed — which is why workers push deltas rather than
140
+ * answering a query.
141
+ */
142
+ firstPartyModulePaths(): string[] | null;
143
+ /** Terminate every worker. Pending and queued tasks reject. */
144
+ dispose(): Promise<void>;
145
+ /** Hand a task to an idle worker, grow the pool, or queue it. */
146
+ private dispatch;
147
+ private send;
148
+ private spawn;
149
+ private onMessage;
150
+ /**
151
+ * A worker died — OOM while holding a large module graph is the realistic
152
+ * cause. Its task fails with PoolUnavailableError so the caller can retry
153
+ * in-process, and the worker is dropped from the pool; the next dispatch
154
+ * spawns a replacement.
155
+ */
156
+ private onWorkerGone;
157
+ private drain;
158
+ private recordModulePaths;
159
+ }
160
+ //#endregion
161
+ export { PoolRequest, PoolResponse, PoolTransformOptions, PoolTransformResult, PoolUnavailableError, TransformPool, disposeAllPools, poolTransformOptions, resolvePoolSize };
162
+ //# sourceMappingURL=pool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pool.d.ts","names":[],"sources":["../../src/unplugin/pool.ts"],"mappings":";;;;;;UA8GiB;EACf,MAAM;EACN;EACA;EACA;EACA;EACA;EACA,kBAAkB;;;KAIR;EACN;EAAmB;EAAa;EAAc;EAAY,SAAS;;EACnE;EAAoB;;;;;;;UAOT;EACf;IAAU;IAAc,KAAK;;EAC7B;EACA;EACA;EACA,OAAO;;;;;EAKP;;;KAIU;EACN;EAAgB;EAAa,QAAQ;;EACrC;EAAe;EAAa;EAAc;EAAiB;;EAC3D;EAAqB;;;;;;;;;;cAUd,6BAA6B;EAC5B,YAAA,iBAAiB;IAAY;;;;;;;;;;iBAa3B,gBAAgB;;iBAUhB,qBAAqB,SAAS,mBAAmB;;iBA4F3C,mBAAmB;;;;;;;;;;;cA0B5B;mBACM;mBACA;mBACA;mBACA;;mBAEA;mBACA;;UAET;UACA;UACA;;UAEA;;UAEA;UAED;;;;;;SAUA,OAAO,eAAe;;EAU7B,IAAI,cAAc,YAAY,SAAS,uBAAuB,QAAQ;;;;;;;;;;;;;EA6BtE;;;;;;;;EAgBA;;EAUM,WAAW;;UAYT;UA0BA;UAUA;UAwBA;;;;;;;UAmCA;UAwBA;UA2BA"}