@kubb/plugin-zod 5.9.0-canary.20261111T122006 → 5.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,143 +1,407 @@
1
- import { Diagnostics, Resolver, addGenerator, ast, camelCase, containsCircularRef, createResolver, defineExtension, defineGenerator, pascalCase, setMacros, setResolver, syncSchemaRef } from "@kubb/kit";
1
+ import { Diagnostics, Resolver, addGenerator, ast, casing, createPrinter, createResolver, defineExtension, defineGenerator, setMacros, setResolver, text } from "@kubb/kit";
2
2
  import { Const, File, Function, Type, jsxRenderer } from "@kubb/renderer-jsx";
3
3
  import { Fragment, jsx, jsxs } from "@kubb/renderer-jsx/jsx-runtime";
4
- // ../../internals/shared/src/operation.ts
4
+ // ../../internals/utils/src/reserved.ts
5
5
  /**
6
- * Builds the `ResolverFileParams` every operation generator passes to
7
- * `resolver.file`: a file named `name`, tagged by the operation's first
8
- * tag (or `'default'`), at the operation's path. Centralizes the entry object
9
- * that was repeated at dozens of call sites across the client and query plugins.
6
+ * JavaScript and Java reserved words.
7
+ * @link https://github.com/jonschlinkert/reserved/blob/master/index.js
8
+ */
9
+ const reservedWords = /* @__PURE__ */ new Set([
10
+ "abstract",
11
+ "arguments",
12
+ "await",
13
+ "boolean",
14
+ "break",
15
+ "byte",
16
+ "case",
17
+ "catch",
18
+ "char",
19
+ "class",
20
+ "const",
21
+ "continue",
22
+ "debugger",
23
+ "default",
24
+ "delete",
25
+ "do",
26
+ "double",
27
+ "else",
28
+ "enum",
29
+ "eval",
30
+ "export",
31
+ "extends",
32
+ "false",
33
+ "final",
34
+ "finally",
35
+ "float",
36
+ "for",
37
+ "function",
38
+ "goto",
39
+ "if",
40
+ "implements",
41
+ "import",
42
+ "in",
43
+ "instanceof",
44
+ "int",
45
+ "interface",
46
+ "let",
47
+ "long",
48
+ "native",
49
+ "new",
50
+ "null",
51
+ "package",
52
+ "private",
53
+ "protected",
54
+ "public",
55
+ "return",
56
+ "short",
57
+ "static",
58
+ "super",
59
+ "switch",
60
+ "synchronized",
61
+ "this",
62
+ "throw",
63
+ "throws",
64
+ "transient",
65
+ "true",
66
+ "try",
67
+ "typeof",
68
+ "var",
69
+ "void",
70
+ "volatile",
71
+ "while",
72
+ "with",
73
+ "yield",
74
+ "Array",
75
+ "Date",
76
+ "hasOwnProperty",
77
+ "Infinity",
78
+ "isFinite",
79
+ "isNaN",
80
+ "isPrototypeOf",
81
+ "length",
82
+ "Math",
83
+ "name",
84
+ "NaN",
85
+ "Number",
86
+ "Object",
87
+ "prototype",
88
+ "String",
89
+ "toString",
90
+ "undefined",
91
+ "valueOf"
92
+ ]);
93
+ /**
94
+ * Returns `true` when `name` is a syntactically valid JavaScript variable name.
10
95
  *
11
96
  * @example
12
97
  * ```ts
13
- * resolver.file(operationFileEntry(node, node.operationId), { root, output, group })
98
+ * isValidVarName('status') // true
99
+ * isValidVarName('class') // false (reserved word)
100
+ * isValidVarName('42foo') // false (starts with digit)
14
101
  * ```
15
102
  */
16
- function operationFileEntry(node, name, extname = ".ts") {
17
- return {
18
- name,
19
- extname,
20
- tag: node.tags[0] ?? "default",
21
- path: node.path
22
- };
103
+ function isValidVarName(name) {
104
+ if (!name || reservedWords.has(name)) return false;
105
+ return isIdentifier(name);
23
106
  }
24
107
  /**
25
- * Resolves the file a schema-emitting plugin (`plugin-ts`, `plugin-zod`, `plugin-faker`) writes
26
- * `node` to, cached in `ctx.memo` under the resolver's plugin name and the
27
- * operation. The plugin itself and every plugin that imports from it call this, so they all get
28
- * the same path.
108
+ * Returns `name` when it's a syntactically valid JavaScript variable name,
109
+ * otherwise prefixes it with `_` so the result is a valid identifier.
29
110
  *
30
- * When that path matches the file of a component schema in `ctx.driver.inputNode`, ignoring case, the file name
31
- * gets an `Operation` suffix: operation `point` goes to `PointOperation.ts` next to schema `Point.ts`.
32
- * Otherwise the two merge into one file, or overwrite each other on a case-insensitive file system.
33
- * Only the file name changes. The declarations keep their names.
111
+ * Useful for sanitizing OpenAPI schema names or operation IDs that start with
112
+ * a digit (e.g. `409`, `504AccountCancel`) before using them as exported
113
+ * variable, type, or function names.
34
114
  *
35
- * @example Resolve `plugin-ts`'s file for the current operation
115
+ * @example
36
116
  * ```ts
37
- * const fileTs = resolveOperationFile({ ctx, node, resolver: tsResolver, root, output })
117
+ * ensureValidVarName('409') // '_409'
118
+ * ensureValidVarName('504AccountCancel') // '_504AccountCancel'
119
+ * ensureValidVarName('Pet') // 'Pet'
120
+ * ensureValidVarName('class') // '_class'
38
121
  * ```
39
122
  */
40
- function resolveOperationFile(options) {
41
- const { ctx, node, resolver, root, output } = options;
42
- const { memo } = ctx;
43
- const location = {
44
- root,
45
- output,
46
- group: options.group ?? void 0
47
- };
48
- return memo(`${resolver.pluginName}:operationFile:${node.operationId}`, () => {
49
- const schemaPaths = memo(`${resolver.pluginName}:schemaFilePaths`, () => new Set((ctx.driver.inputNode?.schemas ?? []).flatMap((schema) => schema.name ? [resolver.file({
50
- name: schema.name,
51
- extname: ".ts",
52
- ...location
53
- }).path.toLowerCase()] : [])));
54
- let file = resolver.file({
55
- ...operationFileEntry(node, node.operationId),
56
- ...location
57
- });
58
- for (let name = `${node.operationId}Operation`; schemaPaths.has(file.path.toLowerCase()); name += "Operation") {
59
- const next = resolver.file({
60
- ...operationFileEntry(node, name),
61
- ...location
62
- });
63
- if (next.path === file.path) break;
64
- file = next;
65
- }
66
- return file;
67
- });
68
- }
69
- function baseContentType(contentType) {
70
- return contentType.split(";")[0].trim().toLowerCase();
123
+ function ensureValidVarName(name) {
124
+ if (!name || isValidVarName(name)) return name;
125
+ return `_${name}`;
71
126
  }
72
127
  /**
73
- * Whether a content entry is a sequence of JSON values the adapter marks as a stream, such as `application/jsonl`,
74
- * `application/x-ndjson`, `application/json-seq`, or any media type with an OpenAPI 3.2 `itemSchema`.
128
+ * Returns `true` when `name` is syntactically a valid identifier, ignoring reserved words.
129
+ *
130
+ * Reserved words and globals (`class`, `name`, `Date`, …) are valid as bare object-literal keys
131
+ * even though they are not valid variable names, so use this (not {@link isValidVarName}) when
132
+ * deciding whether an object key needs quoting.
133
+ *
134
+ * @example
135
+ * ```ts
136
+ * isIdentifier('name') // true
137
+ * isIdentifier('x-total')// false
138
+ * ```
75
139
  */
76
- function isJsonLines(entry) {
77
- const baseType = baseContentType(entry.contentType);
78
- return Boolean(entry.stream) && baseType !== "text/event-stream" && !baseType.startsWith("multipart/");
79
- }
80
- function isJsonContentType(baseType) {
81
- return baseType === "application/json" || baseType.endsWith("+json") || baseType === "text/json";
140
+ function isIdentifier(name) {
141
+ return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
82
142
  }
143
+ // ../../internals/utils/src/codegen.ts
83
144
  /**
84
- * Parser for one response content entry.
145
+ * Assembles a multi-line object literal from already-rendered `entries`, indenting each entry one
146
+ * level and closing the brace at column zero. Entries that are themselves multi-line objects indent
147
+ * cumulatively. Each entry ends with a trailing comma to match the formatter's multi-line style.
85
148
  *
86
- * JSON stays unset so the runtime detects it. `text/event-stream` and JSON Lines are streams. A binary schema
87
- * (`format: binary` / `contentMediaType`) is a blob even when the media type is not a well-known
88
- * binary type. Other `text/*` is text, and `application/octet-stream`, `application/pdf`,
89
- * `image/*`, `audio/*`, and `video/*` are blobs.
149
+ * @example
150
+ * ```ts
151
+ * buildObject(['id: z.number()', 'name: z.string()'])
152
+ * // '{\n id: z.number(),\n name: z.string(),\n}'
153
+ * ```
90
154
  */
91
- function responseTypeForContent(entry) {
92
- const baseType = baseContentType(entry.contentType);
93
- if (!baseType) return void 0;
94
- if (baseType === "text/event-stream" || isJsonLines(entry)) return "stream";
95
- if (isJsonContentType(baseType)) return void 0;
96
- if (entry.schema?.type === "blob") return "blob";
97
- if (baseType.startsWith("text/")) return "text";
98
- if (isBinaryContentType(baseType)) return "blob";
99
- }
100
- function isBinaryContentType(baseType) {
101
- return baseType === "application/octet-stream" || baseType === "application/pdf" || /^(image|audio|video)\//.test(baseType);
155
+ function buildObject(entries) {
156
+ if (entries.length === 0) return "{}";
157
+ return `{\n${entries.map((entry) => `${text.indent({ text: entry })},`).join("\n")}\n}`;
102
158
  }
103
159
  /**
104
- * Maps a content type to the PascalCase suffix used to name per-content-type variants
105
- * (e.g. `application/json` → `Json`, `application/xml` → `Xml`, `multipart/form-data` → `FormData`).
160
+ * Assembles a bracketed list (array by default) from already-rendered `items`. Keeps everything on
161
+ * one line when no item spans multiple lines, and otherwise puts each item on its own line, indented
162
+ * one level with a trailing comma and the closing bracket at column zero. Used for member lists such
163
+ * as `z.union([…])` and `z.array([…])`.
164
+ *
165
+ * @example
166
+ * ```ts
167
+ * buildList(['z.string()', 'z.number()'])
168
+ * // '[z.string(), z.number()]'
169
+ * ```
106
170
  */
107
- function getContentTypeSuffix(contentType) {
108
- const baseType = contentType.split(";")[0].trim();
109
- if (baseType === "application/json") return "Json";
110
- if (baseType === "multipart/form-data") return "FormData";
111
- if (baseType === "application/x-www-form-urlencoded") return "FormUrlEncoded";
112
- const parts = (baseType.split("/").pop() ?? baseType).split(/[^a-zA-Z0-9]+/).filter(Boolean);
113
- if (parts.length === 0) return "Unknown";
114
- return parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
171
+ function buildList(items, brackets = ["[", "]"]) {
172
+ const [open, close] = brackets;
173
+ if (items.length === 0) return `${open}${close}`;
174
+ if (!items.some((item) => item.includes("\n"))) return `${open}${items.join(", ")}${close}`;
175
+ return `${open}\n${items.map((item) => `${text.indent({ text: item })},`).join("\n")}\n${close}`;
115
176
  }
116
177
  /**
117
- * Appends a content-type suffix to a base name, keeping a trailing `Data` segment last
118
- * (e.g. `AddPetData` + `Json` → `AddPetJsonData`, `AddPetStatus200` + `Xml` → `AddPetStatus200Xml`).
178
+ * Emits a lazy getter for a circular-ref property position, `get name() { return body }`. The key
179
+ * is quoted only when it is not a valid identifier. Used by the string printers to defer evaluation
180
+ * of a recursive schema until first access.
181
+ *
182
+ * @example
183
+ * ```ts
184
+ * lazyGetter({ name: 'parent', body: 'z.lazy(() => Pet)' })
185
+ * // "get parent() { return z.lazy(() => Pet) }"
186
+ * ```
119
187
  */
120
- function getPerContentTypeName(baseName, suffix) {
121
- if (baseName.endsWith("Data")) return suffix.endsWith("Data") ? baseName.slice(0, -4) + suffix : `${baseName.slice(0, -4)}${suffix}Data`;
122
- return baseName + suffix;
188
+ function lazyGetter({ name, body }) {
189
+ return `get ${objectKey(name)}() { return ${body} }`;
123
190
  }
124
191
  /**
125
- * Returns the entry with the schema of the value the client returns: `string` for a body read as text, such as `Pet` on
126
- * `application/xml`, and `blob` for `image/*`, `audio/*`, `video/*`, PDF, and octet-stream. A binary schema elsewhere is a `Blob`
127
- * only through `parseAs: 'blob'`, which needs every entry in `entries` binary. JSON, vendor (`application/vnd.*`), stream,
128
- * schemaless, and wildcard entries keep their schema. `entries` is the response's full list of content types.
192
+ * An object key, quoted only when it is not identifier-shaped. Reserved words such as `class` stay bare.
193
+ *
194
+ * @example
195
+ * ```ts
196
+ * objectKey('x-total') // "'x-total'"
197
+ * ```
129
198
  */
130
- function toResponseContent(entry, entries = [entry]) {
131
- const { contentType, schema } = entry;
132
- if (!schema || entry.stream) return entry;
133
- const baseType = baseContentType(contentType);
134
- const resolved = ast.resolveRef(schema);
135
- if (isBinaryContentType(baseType)) return resolved.type === "blob" ? entry : {
136
- ...entry,
137
- schema: ast.factory.createSchema({ type: "blob" }),
138
- keysToOmit: null
139
- };
140
- const isPlainMediaType = Boolean(baseType) && !isJsonContentType(baseType) && !baseType.includes("*");
199
+ function objectKey(name) {
200
+ return isIdentifier(name) ? name : text.quote(name);
201
+ }
202
+ // ../../internals/utils/src/fs.ts
203
+ /**
204
+ * Builds a nested file path from a dotted name. Splits on dots that precede a letter
205
+ * (so version numbers embedded in operationIds like `v2025.0` stay intact), camelCases
206
+ * every earlier segment, applies `caseLast` to the final segment, and joins with `/`.
207
+ *
208
+ * Empty segments are dropped before joining. They arise when the name starts with a dot
209
+ * followed by a letter (e.g. `..Schema` splits into `['..', 'Schema']` and `'..'` cases to
210
+ * an empty string). Without this a leading `/` would form, which `path.resolve` reads as an
211
+ * absolute path, letting generated files escape the configured output directory.
212
+ *
213
+ * @example Nested path from a dotted name
214
+ * `toFilePath('pet.petId') // 'pet/petId'`
215
+ *
216
+ * @example PascalCase the final segment
217
+ * `toFilePath('pet.Pet', pascalCase) // 'pet/Pet'`
218
+ *
219
+ * @example Suffix applied to the final segment only
220
+ * `toFilePath('tag.tag', (part) => camelCase(part, { suffix: 'schema' })) // 'tag/tagSchema'`
221
+ */
222
+ function toFilePath(name, caseLast = casing.camel) {
223
+ const parts = name.split(/\.(?=[a-zA-Z])/);
224
+ return parts.map((part, i) => i === parts.length - 1 ? caseLast(part) : casing.camel(part)).filter(Boolean).join("/");
225
+ }
226
+ // ../../internals/utils/src/strings.ts
227
+ const NEEDS_UNICODE = /(?<!\\)(?:\\\\)*\\[pPu]\{|[\u{10000}-\u{10FFFF}]/u;
228
+ const KEPT_ESCAPE = /[A-Za-z0-9^$\\.*+?()[\]{}|/]/;
229
+ /**
230
+ * Drops the backslash from escapes that the `u` flag rejects: an escaped punctuation character that is
231
+ * not a syntax character, and `\-` outside a class. Both match the same character without the backslash.
232
+ */
233
+ function dropIdentityEscapes(pattern) {
234
+ let inClass = false;
235
+ return pattern.replace(/\\(.)|[[\]]/gs, (match, escaped) => {
236
+ if (escaped === void 0) {
237
+ inClass = match === "[";
238
+ return match;
239
+ }
240
+ return KEPT_ESCAPE.test(escaped) || escaped === "-" && inClass ? match : escaped;
241
+ });
242
+ }
243
+ /**
244
+ * Builds the `RegExp` for a pattern, adding the `u` flag when it uses `\p{...}`, `\P{...}` or `\u{...}`, or
245
+ * holds a character outside the BMP. A pattern that is not valid under `u` keeps the flags it came with.
246
+ */
247
+ function toRegExp(pattern, flags) {
248
+ if (flags?.includes("u") || !NEEDS_UNICODE.test(pattern)) return new RegExp(pattern, flags);
249
+ try {
250
+ return new RegExp(dropIdentityEscapes(pattern), `${flags ?? ""}u`);
251
+ } catch {
252
+ return new RegExp(pattern, flags);
253
+ }
254
+ }
255
+ /**
256
+ * Converts a pattern string into a `new RegExp(...)` constructor call or a regex literal string.
257
+ * Inline flags expressed as a `^(?im)` prefix are extracted and applied to the resulting expression.
258
+ * Pass `null` as the second argument to emit a `/pattern/flags` literal instead.
259
+ *
260
+ * @example
261
+ * ```ts
262
+ * toRegExpString('^(?im)foo') // 'new RegExp("^foo", "im")'
263
+ * toRegExpString('^(?im)foo', null) // '/^foo/im'
264
+ * ```
265
+ */
266
+ function toRegExpString(pattern, func = "RegExp") {
267
+ const raw = text.unquote(pattern);
268
+ const match = raw.match(/^\^(\(\?([igmsuy]+)\))/i);
269
+ const replacementTarget = match?.[1] ?? "";
270
+ const matchedFlags = match?.[2];
271
+ const { source, flags } = toRegExp(raw.replace(/^\\?\//, "").replace(/\\?\/$/, "").replace(replacementTarget, ""), matchedFlags);
272
+ if (func === null) return `/${source}/${flags}`;
273
+ return `new ${func}(${JSON.stringify(source)}${flags ? `, ${JSON.stringify(flags)}` : ""})`;
274
+ }
275
+ // ../../internals/shared/src/operation.ts
276
+ /**
277
+ * Builds the `ResolverFileParams` every operation generator passes to
278
+ * `resolver.file`: a file named `name`, tagged by the operation's first
279
+ * tag (or `'default'`), at the operation's path. Centralizes the entry object
280
+ * that was repeated at dozens of call sites across the client and query plugins.
281
+ *
282
+ * @example
283
+ * ```ts
284
+ * resolver.file(operationFileEntry(node, node.operationId), { root, output, group })
285
+ * ```
286
+ */
287
+ function operationFileEntry(node, name, extname = ".ts") {
288
+ return {
289
+ name,
290
+ extname,
291
+ tag: node.tags[0] ?? "default",
292
+ path: node.path
293
+ };
294
+ }
295
+ /**
296
+ * Resolves the file a schema-emitting plugin (`plugin-ts`, `plugin-zod`, `plugin-faker`) writes
297
+ * `node` to, cached in `ctx.memo` under the resolver's plugin name and the
298
+ * operation. The plugin itself and every plugin that imports from it call this, so they all get
299
+ * the same path.
300
+ *
301
+ * When that path matches the file of a component schema in `ctx.driver.inputNode`, ignoring case, the file name
302
+ * gets an `Operation` suffix: operation `point` goes to `PointOperation.ts` next to schema `Point.ts`.
303
+ * Otherwise the two merge into one file, or overwrite each other on a case-insensitive file system.
304
+ * Only the file name changes. The declarations keep their names.
305
+ *
306
+ * @example Resolve `plugin-ts`'s file for the current operation
307
+ * ```ts
308
+ * const fileTs = resolveOperationFile({ ctx, node, resolver: tsResolver, root, output })
309
+ * ```
310
+ */
311
+ function resolveOperationFile(options) {
312
+ const { ctx, node, resolver, root, output } = options;
313
+ const { memo } = ctx;
314
+ const location = {
315
+ root,
316
+ output,
317
+ group: options.group ?? void 0
318
+ };
319
+ return memo(`${resolver.pluginName}:operationFile:${node.operationId}`, () => {
320
+ const schemaPaths = memo(`${resolver.pluginName}:schemaFilePaths`, () => new Set((ctx.driver.inputNode?.schemas ?? []).flatMap((schema) => schema.name ? [resolver.file({
321
+ name: schema.name,
322
+ extname: ".ts",
323
+ ...location
324
+ }).path.toLowerCase()] : [])));
325
+ let file = resolver.file({
326
+ ...operationFileEntry(node, node.operationId),
327
+ ...location
328
+ });
329
+ for (let name = `${node.operationId}Operation`; schemaPaths.has(file.path.toLowerCase()); name += "Operation") {
330
+ const next = resolver.file({
331
+ ...operationFileEntry(node, name),
332
+ ...location
333
+ });
334
+ if (next.path === file.path) break;
335
+ file = next;
336
+ }
337
+ return file;
338
+ });
339
+ }
340
+ /**
341
+ * How a content entry is framed as a stream: `'events'` for Server-Sent Events, `'lines'` for JSON Lines.
342
+ */
343
+ function getStreamFraming(entry) {
344
+ if (ast.mediaType.getBase(entry) === "text/event-stream") return "events";
345
+ return entry.stream && ast.mediaType.isStream(entry) ? "lines" : void 0;
346
+ }
347
+ /**
348
+ * Parser for one response content entry.
349
+ *
350
+ * JSON stays unset so the runtime detects it. `text/event-stream` and JSON Lines are streams. A binary schema
351
+ * (`format: binary` / `contentMediaType`) is a blob even when the media type is not a well-known
352
+ * binary type. Other `text/*` is text, and `application/octet-stream`, `application/pdf`,
353
+ * `image/*`, `audio/*`, and `video/*` are blobs.
354
+ */
355
+ function responseTypeForContent(entry) {
356
+ const baseType = ast.mediaType.getBase(entry);
357
+ if (!baseType) return void 0;
358
+ if (getStreamFraming(entry)) return "stream";
359
+ if (ast.mediaType.isJson(baseType)) return void 0;
360
+ if (entry.schema?.type === "blob") return "blob";
361
+ if (baseType.startsWith("text/")) return "text";
362
+ if (isBinaryContentType(baseType)) return "blob";
363
+ }
364
+ function isBinaryContentType(baseType) {
365
+ return baseType === "application/octet-stream" || baseType === "application/pdf" || /^(image|audio|video)\//.test(baseType);
366
+ }
367
+ /**
368
+ * Maps a content type to the PascalCase suffix used to name per-content-type variants
369
+ * (e.g. `application/json` → `Json`, `application/xml` → `Xml`, `multipart/form-data` → `FormData`).
370
+ */
371
+ function getContentTypeSuffix(contentType) {
372
+ const baseType = contentType.split(";")[0].trim();
373
+ if (baseType === "application/json") return "Json";
374
+ if (baseType === "multipart/form-data") return "FormData";
375
+ if (baseType === "application/x-www-form-urlencoded") return "FormUrlEncoded";
376
+ const parts = (baseType.split("/").pop() ?? baseType).split(/[^a-zA-Z0-9]+/).filter(Boolean);
377
+ if (parts.length === 0) return "Unknown";
378
+ return parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
379
+ }
380
+ /**
381
+ * Appends a content-type suffix to a base name, keeping a trailing `Data` segment last
382
+ * (e.g. `AddPetData` + `Json` → `AddPetJsonData`, `AddPetStatus200` + `Xml` → `AddPetStatus200Xml`).
383
+ */
384
+ function getPerContentTypeName(baseName, suffix) {
385
+ if (baseName.endsWith("Data")) return suffix.endsWith("Data") ? baseName.slice(0, -4) + suffix : `${baseName.slice(0, -4)}${suffix}Data`;
386
+ return baseName + suffix;
387
+ }
388
+ /**
389
+ * Returns the entry with the schema of the value the client returns: `string` for a body read as text, such as `Pet` on
390
+ * `application/xml`, and `blob` for `image/*`, `audio/*`, `video/*`, PDF, and octet-stream. A binary schema elsewhere is a `Blob`
391
+ * only through `parseAs: 'blob'`, which needs every entry in `entries` binary. JSON, vendor (`application/vnd.*`), stream,
392
+ * schemaless, and wildcard entries keep their schema. `entries` is the response's full list of content types.
393
+ */
394
+ function toResponseContent(entry, entries = [entry]) {
395
+ const { contentType, schema } = entry;
396
+ if (!schema || entry.stream) return entry;
397
+ const baseType = ast.mediaType.getBase(contentType);
398
+ const resolved = ast.ref.getSchema(schema);
399
+ if (isBinaryContentType(baseType)) return resolved.type === "blob" ? entry : {
400
+ ...entry,
401
+ schema: ast.factory.createSchema({ type: "blob" }),
402
+ keysToOmit: null
403
+ };
404
+ const isPlainMediaType = Boolean(baseType) && !ast.mediaType.isJson(baseType) && !baseType.includes("*");
141
405
  if (resolved.type === "blob") return isPlainMediaType && !entries.every((item) => item.schema?.type === "blob") ? {
142
406
  ...entry,
143
407
  schema: ast.factory.createSchema({ type: "string" }),
@@ -180,50 +444,6 @@ function resolveContentTypeVariants(entries, baseName) {
180
444
  };
181
445
  });
182
446
  }
183
- const operationParameterGroupsByNode = /* @__PURE__ */ new WeakMap();
184
- /**
185
- * An OpenAPI 3.2 `querystring` parameter is the whole query string, so a form-encoded object becomes one query parameter
186
- * per property, typed and serialized like any other.
187
- * Another content type, such as JSON, stays one parameter named after it, and the runtime sends its value as the whole query string.
188
- */
189
- function spreadQuerystring(param) {
190
- const schema = param.schema.type === "ref" ? param.schema.schema : param.schema;
191
- const formEncoded = !param.contentType || param.contentType.split(";")[0].trim().toLowerCase() === "application/x-www-form-urlencoded";
192
- if (schema?.type !== "object" || !formEncoded) return [param];
193
- return schema.properties.map((property) => ast.factory.createParameter({
194
- name: property.name,
195
- in: "query",
196
- schema: property.schema,
197
- required: param.required && property.required
198
- }));
199
- }
200
- /**
201
- * Groups an operation's parameters by location (`path`/`query`/`header`/`cookie`), deduping each
202
- * group by name. Every plugin generator visiting the same `OperationNode` shares one AST instance
203
- * (see `KubbDriver`), so the result is cached per node to avoid re-filtering and re-deduping the
204
- * same parameters once per plugin.
205
- */
206
- function getOperationParameters(node) {
207
- const cached = operationParameterGroupsByNode.get(node);
208
- if (cached) return cached;
209
- const dedupe = (params) => {
210
- const seen = /* @__PURE__ */ new Set();
211
- return params.filter((param) => {
212
- if (seen.has(param.name)) return false;
213
- seen.add(param.name);
214
- return true;
215
- });
216
- };
217
- const group = (location) => dedupe(node.parameters.filter((param) => param.in === location));
218
- const groups = {
219
- path: group("path"),
220
- query: dedupe([...group("query"), ...group("querystring").flatMap(spreadQuerystring)]),
221
- header: group("header"),
222
- cookie: group("cookie")
223
- };
224
- operationParameterGroupsByNode.set(node, groups);
225
- return groups;
226
- }
227
447
  /**
228
448
  * Whether the operation declares more than one request media type with a schema, so a caller must pick one on `contentType`.
229
449
  */
@@ -239,8 +459,8 @@ function hasRequestContentTypeChoice(node) {
239
459
  * than a record.
240
460
  */
241
461
  function buildOptionsSchema(node, resolver, { pairContentTypes = false } = {}) {
242
- const { path, query, header, cookie } = getOperationParameters(node);
243
- const hasBody = Boolean(node.requestBody?.content?.[0]?.schema);
462
+ const { path, query, header, cookie } = ast.http.groupParameters(node);
463
+ const hasBody = Boolean(ast.http.requestBody.getSchema(node));
244
464
  const createNever = () => ast.factory.createSchema({
245
465
  type: "never",
246
466
  primitive: void 0,
@@ -331,9 +551,10 @@ function buildOptionsSchema(node, resolver, { pairContentTypes = false } = {}) {
331
551
  * The schema a status occupies in the `<Name>Responses` record. A status that documents several
332
552
  * content types becomes a `{ contentType; data }` union so the runtime can surface the negotiated type
333
553
  * on `result.parsed`, while the standalone `<Name>StatusNNN` alias stays the plain body union that the
334
- * query hooks and `result.data` use.
554
+ * query hooks and `result.data` use. With `streamData`, the stream variant of a 2xx status holds that schema, since the
555
+ * client returns its body unread.
335
556
  */
336
- function buildResponseRecordEntry(node, res, resolver) {
557
+ function buildResponseRecordEntry(node, res, resolver, streamData) {
337
558
  const statusName = resolver.response.status(node, res.statusCode);
338
559
  const variants = (res.content ?? []).filter((entry) => entry.schema);
339
560
  if (variants.length <= 1) return ast.factory.createSchema({
@@ -350,12 +571,12 @@ function buildResponseRecordEntry(node, res, resolver) {
350
571
  required: true,
351
572
  schema: ast.factory.createSchema({
352
573
  type: "enum",
353
- enumValues: [baseContentType(variant.contentType)]
574
+ enumValues: [ast.mediaType.getBase(variant)]
354
575
  })
355
576
  }), ast.factory.createProperty({
356
577
  name: "data",
357
578
  required: true,
358
- schema: ast.factory.createSchema({
579
+ schema: streamData && ast.http.status.isSuccess(res) && ast.mediaType.isStream(variant) ? streamData : ast.factory.createSchema({
359
580
  type: "ref",
360
581
  name: variant.name
361
582
  })
@@ -371,29 +592,20 @@ function buildResponseRecordEntry(node, res, resolver) {
371
592
  * Always emits the keyed record, even when an operation declares no responses. An operation with no
372
593
  * responses renders as an empty object, which keeps every consumer's import (for example the axios
373
594
  * SDK's `RequestResult<XResponses>`) resolvable instead of pointing at a missing export.
595
+ *
596
+ * `streamData` is the type of a stream variant's `data`, such as a `ReadableStream<Uint8Array>` reference.
374
597
  */
375
- function buildResponses(node, resolver) {
598
+ function buildResponses(node, resolver, { streamData } = {}) {
376
599
  return ast.factory.createSchema({
377
600
  type: "object",
378
601
  primitive: "object",
379
602
  properties: node.responses.map((res) => ast.factory.createProperty({
380
603
  name: String(res.statusCode),
381
604
  required: true,
382
- schema: buildResponseRecordEntry(node, res, resolver)
605
+ schema: buildResponseRecordEntry(node, res, resolver, streamData)
383
606
  }))
384
607
  });
385
608
  }
386
- function getStatusCodeNumber(statusCode) {
387
- const code = Number(statusCode);
388
- return Number.isNaN(code) ? null : code;
389
- }
390
- function isSuccessStatusCode(statusCode) {
391
- const code = getStatusCodeNumber(statusCode);
392
- return code !== null && code >= 200 && code < 300;
393
- }
394
- function getSuccessResponses(responses) {
395
- return responses.filter((response) => isSuccessStatusCode(response.statusCode));
396
- }
397
609
  // ../../internals/shared/src/adapter.ts
398
610
  /**
399
611
  * Narrows the generic `Adapter` from a generator context to the OpenAPI adapter,
@@ -407,344 +619,15 @@ function getSuccessResponses(responses) {
407
619
  * const { dateType } = getOasAdapter(ctx.adapter).options
408
620
  * ```
409
621
  */
410
- function getOasAdapter(adapter) {
411
- if (adapter.name !== "oas") throw new Diagnostics.Error({
412
- code: Diagnostics.code.adapterRequired,
413
- severity: "error",
414
- message: `Expected the OpenAPI adapter (adapterOas), but received "${adapter.name}".`,
415
- help: "Set `adapter: adapterOas()` in kubb.config.ts.",
416
- location: { kind: "config" }
417
- });
418
- return adapter;
419
- }
420
- // ../../internals/utils/src/reserved.ts
421
- /**
422
- * JavaScript and Java reserved words.
423
- * @link https://github.com/jonschlinkert/reserved/blob/master/index.js
424
- */
425
- const reservedWords = /* @__PURE__ */ new Set([
426
- "abstract",
427
- "arguments",
428
- "await",
429
- "boolean",
430
- "break",
431
- "byte",
432
- "case",
433
- "catch",
434
- "char",
435
- "class",
436
- "const",
437
- "continue",
438
- "debugger",
439
- "default",
440
- "delete",
441
- "do",
442
- "double",
443
- "else",
444
- "enum",
445
- "eval",
446
- "export",
447
- "extends",
448
- "false",
449
- "final",
450
- "finally",
451
- "float",
452
- "for",
453
- "function",
454
- "goto",
455
- "if",
456
- "implements",
457
- "import",
458
- "in",
459
- "instanceof",
460
- "int",
461
- "interface",
462
- "let",
463
- "long",
464
- "native",
465
- "new",
466
- "null",
467
- "package",
468
- "private",
469
- "protected",
470
- "public",
471
- "return",
472
- "short",
473
- "static",
474
- "super",
475
- "switch",
476
- "synchronized",
477
- "this",
478
- "throw",
479
- "throws",
480
- "transient",
481
- "true",
482
- "try",
483
- "typeof",
484
- "var",
485
- "void",
486
- "volatile",
487
- "while",
488
- "with",
489
- "yield",
490
- "Array",
491
- "Date",
492
- "hasOwnProperty",
493
- "Infinity",
494
- "isFinite",
495
- "isNaN",
496
- "isPrototypeOf",
497
- "length",
498
- "Math",
499
- "name",
500
- "NaN",
501
- "Number",
502
- "Object",
503
- "prototype",
504
- "String",
505
- "toString",
506
- "undefined",
507
- "valueOf"
508
- ]);
509
- /**
510
- * Returns `true` when `name` is a syntactically valid JavaScript variable name.
511
- *
512
- * @example
513
- * ```ts
514
- * isValidVarName('status') // true
515
- * isValidVarName('class') // false (reserved word)
516
- * isValidVarName('42foo') // false (starts with digit)
517
- * ```
518
- */
519
- function isValidVarName(name) {
520
- if (!name || reservedWords.has(name)) return false;
521
- return isIdentifier(name);
522
- }
523
- /**
524
- * Returns `name` when it's a syntactically valid JavaScript variable name,
525
- * otherwise prefixes it with `_` so the result is a valid identifier.
526
- *
527
- * Useful for sanitizing OpenAPI schema names or operation IDs that start with
528
- * a digit (e.g. `409`, `504AccountCancel`) before using them as exported
529
- * variable, type, or function names.
530
- *
531
- * @example
532
- * ```ts
533
- * ensureValidVarName('409') // '_409'
534
- * ensureValidVarName('504AccountCancel') // '_504AccountCancel'
535
- * ensureValidVarName('Pet') // 'Pet'
536
- * ensureValidVarName('class') // '_class'
537
- * ```
538
- */
539
- function ensureValidVarName(name) {
540
- if (!name || isValidVarName(name)) return name;
541
- return `_${name}`;
542
- }
543
- /**
544
- * Returns `true` when `name` is syntactically a valid identifier, ignoring reserved words.
545
- *
546
- * Reserved words and globals (`class`, `name`, `Date`, …) are valid as bare object-literal keys
547
- * even though they are not valid variable names, so use this (not {@link isValidVarName}) when
548
- * deciding whether an object key needs quoting.
549
- *
550
- * @example
551
- * ```ts
552
- * isIdentifier('name') // true
553
- * isIdentifier('x-total')// false
554
- * ```
555
- */
556
- function isIdentifier(name) {
557
- return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
558
- }
559
- // ../../internals/utils/src/strings.ts
560
- /**
561
- * Wraps a value in single quotes for emitting a single-quoted JavaScript string literal, escaping
562
- * any backslash or single quote in the content.
563
- *
564
- * @example
565
- * ```ts
566
- * singleQuote('foo') // "'foo'"
567
- * singleQuote("o'clock") // "'o\\'clock'"
568
- * ```
569
- */
570
- function singleQuote(value) {
571
- if (value === void 0 || value === null) return "''";
572
- return `'${String(value).replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
573
- }
574
- /**
575
- * Strips a single matching pair of `"..."`, `'...'`, or `` `...` `` from both ends of `text`.
576
- * Returns the string unchanged when no balanced quote pair is found.
577
- *
578
- * @example
579
- * ```ts
580
- * trimQuotes('"hello"') // 'hello'
581
- * trimQuotes('hello') // 'hello'
582
- * ```
583
- */
584
- function trimQuotes(text) {
585
- if (text.length >= 2) {
586
- const first = text[0];
587
- const last = text[text.length - 1];
588
- if (first === "\"" && last === "\"" || first === "'" && last === "'" || first === "`" && last === "`") return text.slice(1, -1);
589
- }
590
- return text;
591
- }
592
- /**
593
- * Serializes a primitive to a single-quoted string literal, stripping any surrounding quotes first.
594
- *
595
- * Escaping runs through `JSON.stringify`, then the result switches to single quotes so the generated
596
- * code matches the repo style without a formatter.
597
- *
598
- * @example
599
- * ```ts
600
- * stringify('hello') // "'hello'"
601
- * stringify('"hello"') // "'hello'"
602
- * ```
603
- */
604
- function stringify(value) {
605
- if (value === void 0 || value === null) return "''";
606
- return `'${JSON.stringify(trimQuotes(value.toString())).slice(1, -1).replace(/\\"/g, "\"").replace(/'/g, "\\'")}'`;
607
- }
608
- const NEEDS_UNICODE = /(?<!\\)(?:\\\\)*\\[pPu]\{|[\u{10000}-\u{10FFFF}]/u;
609
- const KEPT_ESCAPE = /[A-Za-z0-9^$\\.*+?()[\]{}|/]/;
610
- /**
611
- * Drops the backslash from escapes that the `u` flag rejects: an escaped punctuation character that is
612
- * not a syntax character, and `\-` outside a class. Both match the same character without the backslash.
613
- */
614
- function dropIdentityEscapes(pattern) {
615
- let inClass = false;
616
- return pattern.replace(/\\(.)|[[\]]/gs, (match, escaped) => {
617
- if (escaped === void 0) {
618
- inClass = match === "[";
619
- return match;
620
- }
621
- return KEPT_ESCAPE.test(escaped) || escaped === "-" && inClass ? match : escaped;
622
- });
623
- }
624
- /**
625
- * Builds the `RegExp` for a pattern, adding the `u` flag when it uses `\p{...}`, `\P{...}` or `\u{...}`, or
626
- * holds a character outside the BMP. A pattern that is not valid under `u` keeps the flags it came with.
627
- */
628
- function toRegExp(pattern, flags) {
629
- if (flags?.includes("u") || !NEEDS_UNICODE.test(pattern)) return new RegExp(pattern, flags);
630
- try {
631
- return new RegExp(dropIdentityEscapes(pattern), `${flags ?? ""}u`);
632
- } catch {
633
- return new RegExp(pattern, flags);
634
- }
635
- }
636
- /**
637
- * Converts a pattern string into a `new RegExp(...)` constructor call or a regex literal string.
638
- * Inline flags expressed as a `^(?im)` prefix are extracted and applied to the resulting expression.
639
- * Pass `null` as the second argument to emit a `/pattern/flags` literal instead.
640
- *
641
- * @example
642
- * ```ts
643
- * toRegExpString('^(?im)foo') // 'new RegExp("^foo", "im")'
644
- * toRegExpString('^(?im)foo', null) // '/^foo/im'
645
- * ```
646
- */
647
- function toRegExpString(text, func = "RegExp") {
648
- const raw = trimQuotes(text);
649
- const match = raw.match(/^\^(\(\?([igmsuy]+)\))/i);
650
- const replacementTarget = match?.[1] ?? "";
651
- const matchedFlags = match?.[2];
652
- const { source, flags } = toRegExp(raw.replace(/^\\?\//, "").replace(/\\?\/$/, "").replace(replacementTarget, ""), matchedFlags);
653
- if (func === null) return `/${source}/${flags}`;
654
- return `new ${func}(${JSON.stringify(source)}${flags ? `, ${JSON.stringify(flags)}` : ""})`;
655
- }
656
- // ../../internals/utils/src/codegen.ts
657
- const INDENT = " ";
658
- /**
659
- * Indents every non-empty line of `text` by one indent level, leaving blank lines empty.
660
- */
661
- function indentLines(text) {
662
- if (!text) return "";
663
- return text.split("\n").map((line) => line.trim() ? `${INDENT}${line}` : "").join("\n");
664
- }
665
- /**
666
- * Renders an object key, quoting it with single quotes only when it is not a valid identifier.
667
- * Reserved words and globals (`name`, `class`, …) are valid bare keys and stay unquoted.
668
- *
669
- * @example
670
- * ```ts
671
- * objectKey('name') // 'name'
672
- * objectKey('x-total') // "'x-total'"
673
- * ```
674
- */
675
- function objectKey(name) {
676
- return isIdentifier(name) ? name : singleQuote(name);
677
- }
678
- /**
679
- * Assembles a multi-line object literal from already-rendered `entries`, indenting each entry one
680
- * level and closing the brace at column zero. Entries that are themselves multi-line objects indent
681
- * cumulatively. Each entry ends with a trailing comma to match the formatter's multi-line style.
682
- *
683
- * @example
684
- * ```ts
685
- * buildObject(['id: z.number()', 'name: z.string()'])
686
- * // '{\n id: z.number(),\n name: z.string(),\n}'
687
- * ```
688
- */
689
- function buildObject(entries) {
690
- if (entries.length === 0) return "{}";
691
- return `{\n${entries.map((entry) => `${indentLines(entry)},`).join("\n")}\n}`;
692
- }
693
- /**
694
- * Assembles a bracketed list (array by default) from already-rendered `items`. Keeps everything on
695
- * one line when no item spans multiple lines, and otherwise puts each item on its own line, indented
696
- * one level with a trailing comma and the closing bracket at column zero. Used for member lists such
697
- * as `z.union([…])` and `z.array([…])`.
698
- *
699
- * @example
700
- * ```ts
701
- * buildList(['z.string()', 'z.number()'])
702
- * // '[z.string(), z.number()]'
703
- * ```
704
- */
705
- function buildList(items, brackets = ["[", "]"]) {
706
- const [open, close] = brackets;
707
- if (items.length === 0) return `${open}${close}`;
708
- if (!items.some((item) => item.includes("\n"))) return `${open}${items.join(", ")}${close}`;
709
- return `${open}\n${items.map((item) => `${indentLines(item)},`).join("\n")}\n${close}`;
710
- }
711
- /**
712
- * Emits a lazy getter for a circular-ref property position, `get name() { return body }`. The key
713
- * is quoted only when it is not a valid identifier. Used by the string printers to defer evaluation
714
- * of a recursive schema until first access.
715
- *
716
- * @example
717
- * ```ts
718
- * lazyGetter({ name: 'parent', body: 'z.lazy(() => Pet)' })
719
- * // "get parent() { return z.lazy(() => Pet) }"
720
- * ```
721
- */
722
- function lazyGetter({ name, body }) {
723
- return `get ${objectKey(name)}() { return ${body} }`;
724
- }
725
- // ../../internals/utils/src/fs.ts
726
- /**
727
- * Builds a nested file path from a dotted name. Splits on dots that precede a letter
728
- * (so version numbers embedded in operationIds like `v2025.0` stay intact), camelCases
729
- * every earlier segment, applies `caseLast` to the final segment, and joins with `/`.
730
- *
731
- * Empty segments are dropped before joining. They arise when the name starts with a dot
732
- * followed by a letter (e.g. `..Schema` splits into `['..', 'Schema']` and `'..'` cases to
733
- * an empty string). Without this a leading `/` would form, which `path.resolve` reads as an
734
- * absolute path, letting generated files escape the configured output directory.
735
- *
736
- * @example Nested path from a dotted name
737
- * `toFilePath('pet.petId') // 'pet/petId'`
738
- *
739
- * @example PascalCase the final segment
740
- * `toFilePath('pet.Pet', pascalCase) // 'pet/Pet'`
741
- *
742
- * @example Suffix applied to the final segment only
743
- * `toFilePath('tag.tag', (part) => camelCase(part, { suffix: 'schema' })) // 'tag/tagSchema'`
744
- */
745
- function toFilePath(name, caseLast = camelCase) {
746
- const parts = name.split(/\.(?=[a-zA-Z])/);
747
- return parts.map((part, i) => i === parts.length - 1 ? caseLast(part) : camelCase(part)).filter(Boolean).join("/");
622
+ function getOasAdapter(adapter) {
623
+ if (adapter.name !== "oas") throw new Diagnostics.Error({
624
+ code: Diagnostics.code.adapterRequired,
625
+ severity: "error",
626
+ message: `Expected the OpenAPI adapter (adapterOas), but received "${adapter.name}".`,
627
+ help: "Set `adapter: adapterOas()` in kubb.config.ts.",
628
+ location: { kind: "config" }
629
+ });
630
+ return adapter;
748
631
  }
749
632
  // ../../internals/shared/src/resolver.ts
750
633
  /**
@@ -834,6 +717,13 @@ function createOperationResponseResolver() {
834
717
  name
835
718
  });
836
719
  },
720
+ headers(node, statusCode) {
721
+ const name = this.name(`${node.operationId} Status ${statusCode} Headers`);
722
+ return this.resolveName({
723
+ key: operationKey(node, "response", "headers", statusCode),
724
+ name
725
+ });
726
+ },
837
727
  body(node) {
838
728
  const name = this.name(`${node.operationId} Body`);
839
729
  return this.resolveName({
@@ -875,147 +765,7 @@ function createCasedFile(caseLast) {
875
765
  * Names of the schemas in a circular reference chain, computed once per build.
876
766
  */
877
767
  function getCircularNames(ctx) {
878
- return ctx.memo("circularNames:set", () => ast.findCircularSchemas(ctx.driver.inputNode?.schemas ?? []));
879
- }
880
- // ../../internals/shared/src/group.ts
881
- /**
882
- * Builds the `group` config a Kubb plugin passes to `ctx.setOptions`, applying the
883
- * shared default naming so every plugin groups output consistently:
884
- *
885
- * - `path` groups use the second path segment (`/pet/findByStatus` → `pet`).
886
- * - other groups use the camelCased group (`pet store` → `petStore`).
887
- *
888
- * A user-provided `group.name` always wins over the default namer, so callers stay in
889
- * control of their output folders. Returns `null` when grouping is disabled, matching the
890
- * per-plugin convention.
891
- *
892
- * @param group - The user-supplied group option, or `undefined` to disable grouping.
893
- *
894
- * @example
895
- * ```ts
896
- * createGroupConfig(group) // shared across every plugin
897
- * ```
898
- */
899
- function createGroupConfig(group) {
900
- if (!group) return null;
901
- const defaultName = (ctx) => {
902
- if (group.type === "path") return `${ctx.group.split("/")[1]}`;
903
- return camelCase(ctx.group);
904
- };
905
- return {
906
- ...group,
907
- name: group.name ? group.name : defaultName
908
- };
909
- }
910
- // ../../internals/shared/src/schemaTraversal.ts
911
- /**
912
- * Maps each property of an object schema to its transformed output. Pairs every result with the
913
- * original property so the printer keeps full control over modifiers, getters, and key syntax.
914
- *
915
- * @example
916
- * ```ts
917
- * const entries = mapSchemaProperties(node, (schema) => this.transform(schema))
918
- * // entries: [{ name: 'id', property, output: 'z.number()' }, ...]
919
- * ```
920
- */
921
- function mapSchemaProperties(node, transform) {
922
- return node.properties.map((property) => ({
923
- name: property.name,
924
- property,
925
- output: transform(property.schema)
926
- }));
927
- }
928
- /**
929
- * Maps each member of a union or intersection schema to its transformed output, pairing every
930
- * result with the original member.
931
- */
932
- function mapSchemaMembers(node, transform) {
933
- return (node.members ?? []).map((schema) => ({
934
- schema,
935
- output: transform(schema)
936
- }));
937
- }
938
- /**
939
- * Maps each item of an array or tuple schema to its transformed output, pairing every result with
940
- * the original item.
941
- */
942
- function mapSchemaItems(node, transform) {
943
- return (node.items ?? []).map((schema) => ({
944
- schema,
945
- output: transform(schema)
946
- }));
947
- }
948
- // src/components/Zod.tsx
949
- function Zod({ name, node, printer, inferTypeName, inferKind = "infer", typeGuards, isName, assertName, mini, cyclic, compile }) {
950
- const output = printer.print(node);
951
- const printerImports = printer.drainImports();
952
- if (!output) return;
953
- const needsAnnotation = cyclic && node.type !== "object";
954
- const isBare = ast.isBareRef(node, printer.options.keysToOmit);
955
- const isCyclic = Boolean(cyclic || printer.options.cyclicSchemas && containsCircularRef(node, { circularSchemas: printer.options.cyclicSchemas }));
956
- const value = Boolean(compile) && !isBare && !isCyclic ? typeof compile === "object" && compile.strict ? `z.compile(${output}, { strict: true })` : `z.compile(${output})` : output;
957
- const targetType = inferTypeName ?? `z.infer<typeof ${name}>`;
958
- const shouldGenerateIs = isName && (typeof typeGuards === "object" ? typeGuards.is ?? true : Boolean(typeGuards));
959
- const shouldGenerateAssert = assertName && (typeof typeGuards === "object" ? typeGuards.assert ?? true : Boolean(typeGuards));
960
- return /* @__PURE__ */ jsxs(Fragment, { children: [
961
- printerImports.map((imp) => /* @__PURE__ */ jsx(File.Import, {
962
- name: imp.name,
963
- path: imp.path,
964
- root: imp.root,
965
- isTypeOnly: imp.isTypeOnly,
966
- isNameSpace: imp.isNameSpace
967
- }, [
968
- name,
969
- imp.path,
970
- imp.name
971
- ].join("-"))),
972
- /* @__PURE__ */ jsx(File.Source, {
973
- name,
974
- isExportable: true,
975
- isIndexable: true,
976
- children: /* @__PURE__ */ jsx(Const, {
977
- export: true,
978
- name,
979
- type: needsAnnotation ? "z.ZodType" : void 0,
980
- children: value
981
- })
982
- }),
983
- inferTypeName && /* @__PURE__ */ jsx(File.Source, {
984
- name: inferTypeName,
985
- isExportable: true,
986
- isIndexable: true,
987
- isTypeOnly: true,
988
- children: /* @__PURE__ */ jsx(Type, {
989
- export: true,
990
- name: inferTypeName,
991
- children: `z.${inferKind}<typeof ${name}>`
992
- })
993
- }),
994
- shouldGenerateIs && /* @__PURE__ */ jsx(File.Source, {
995
- name: isName,
996
- isExportable: true,
997
- isIndexable: true,
998
- children: /* @__PURE__ */ jsx(Const, {
999
- export: true,
1000
- name: isName,
1001
- JSDoc: { comments: [`Type guard for {@link ${name}}`] },
1002
- children: mini ? `(data: unknown): data is ${targetType} => z.validate(${name}, data)` : `(data: unknown): data is ${targetType} => ${name}.validate(data)`
1003
- })
1004
- }),
1005
- shouldGenerateAssert && /* @__PURE__ */ jsx(File.Source, {
1006
- name: assertName,
1007
- isExportable: true,
1008
- isIndexable: true,
1009
- children: /* @__PURE__ */ jsx(Function, {
1010
- export: true,
1011
- name: assertName,
1012
- params: "data: unknown",
1013
- returnType: `asserts data is ${targetType}`,
1014
- JSDoc: { comments: [`Asserter for {@link ${name}}`, "@throws {z.ZodError} If data is invalid"] },
1015
- children: mini ? `if (!z.validate(${name}, data)) {\n z.parse(${name}, data)\n}` : `if (!${name}.validate(data)) {\n ${name}.parse(data)\n}`
1016
- })
1017
- })
1018
- ] });
768
+ return ctx.memo("circularNames:set", () => ast.graph.getCircularNames(ctx.driver.inputNode?.schemas ?? []));
1019
769
  }
1020
770
  // src/utils.ts
1021
771
  /**
@@ -1036,7 +786,7 @@ function shouldCoerce(coercion, type) {
1036
786
  */
1037
787
  function isClosedKeySchema(node) {
1038
788
  if (!node) return false;
1039
- const resolved = syncSchemaRef(node);
789
+ const resolved = ast.ref.mergeSchema(node);
1040
790
  if (resolved.type === "enum") return true;
1041
791
  if (resolved.type === "union") {
1042
792
  const members = resolved.members ?? [];
@@ -1046,12 +796,12 @@ function isClosedKeySchema(node) {
1046
796
  }
1047
797
  /**
1048
798
  * Whether the node is a plain inline object whose shape can be lifted into an `.extend({ … })`
1049
- * argument. Property counts, a catchall, `patternProperties`, or a nullable/optional wrapper cannot, so those stay
799
+ * argument. Input checks, a catchall, `patternProperties`, or a nullable/optional wrapper cannot, so those stay
1050
800
  * on `.and(…)`.
1051
801
  */
1052
802
  function isPlainInlineObject(node) {
1053
803
  if (node.type !== "object") return false;
1054
- if (node.minProperties !== void 0 || node.maxProperties !== void 0) return false;
804
+ if (hasInputChecks(node)) return false;
1055
805
  if (node.nullable || node.optional || node.nullish) return false;
1056
806
  const hasAdditionalProperties = node.additionalProperties !== void 0;
1057
807
  const hasPatternProperties = Boolean(node.patternProperties);
@@ -1062,21 +812,21 @@ function isPlainInlineObject(node) {
1062
812
  * `z.discriminatedUnion` option. Covers object nodes, `$ref`s that resolve to (or are still
1063
813
  * unresolved) objects, single-member unions of an object, and object-composable `allOf`.
1064
814
  *
1065
- * Property counts render as a pipe. A ref into `cyclicSchemas` renders as `z.lazy(…)`.
815
+ * Input checks, such as property counts and `conditionals`, render as a pipe. A ref into `cyclicSchemas` renders as `z.lazy(…)`.
1066
816
  */
1067
817
  function isObjectSchemaNode(node, cyclicSchemas) {
1068
818
  if (node.nullable || node.optional || node.nullish) return false;
819
+ if (hasInputChecks(node)) return false;
1069
820
  if (node.type === "object") {
1070
- if (node.minProperties !== void 0 || node.maxProperties !== void 0) return false;
1071
821
  const hasProperties = Boolean(node.properties?.length);
1072
822
  const hasTypedAdditionalProperties = Boolean(node.additionalProperties && node.additionalProperties !== true);
1073
823
  const hasPatternProperties = Boolean(node.patternProperties);
1074
824
  return !(!hasProperties && (hasTypedAdditionalProperties || hasPatternProperties || node.propertyNames));
1075
825
  }
1076
826
  if (node.type === "ref") {
1077
- const refName = ast.resolveRefName(node);
827
+ const refName = ast.ref.getName(node);
1078
828
  if (refName && cyclicSchemas?.has(refName)) return false;
1079
- const resolved = syncSchemaRef(node);
829
+ const resolved = ast.ref.mergeSchema(node);
1080
830
  return resolved.type === "ref" || isObjectSchemaNode(resolved, cyclicSchemas);
1081
831
  }
1082
832
  if (node.type === "union") {
@@ -1099,23 +849,23 @@ function preserveRequiredProperties(members) {
1099
849
  const hasRequiredProperty = ({ node, propertyName, seen = /* @__PURE__ */ new Set() }) => {
1100
850
  if (seen.has(node)) return false;
1101
851
  seen.add(node);
1102
- const resolved = syncSchemaRef(node);
1103
- const composition = ast.narrowSchema(resolved, ast.schemaTypes.intersection) ?? ast.narrowSchema(resolved, ast.schemaTypes.union);
852
+ const resolved = ast.ref.mergeSchema(node);
853
+ const composition = ast.schema.narrow(resolved, ast.schema.types.intersection) ?? ast.schema.narrow(resolved, ast.schema.types.union);
1104
854
  if (composition) {
1105
- if (composition.type === ast.schemaTypes.union && composition.members?.length !== 1) return false;
855
+ if (composition.type === ast.schema.types.union && composition.members?.length !== 1) return false;
1106
856
  return composition.members?.some((child) => hasRequiredProperty({
1107
857
  node: child,
1108
858
  propertyName,
1109
859
  seen
1110
860
  })) ?? false;
1111
861
  }
1112
- return ast.resolveSchemaProperties({
862
+ return ast.schema.findProperties({
1113
863
  node: resolved,
1114
864
  propertyName
1115
865
  }).some((property) => property.required !== false && !property.schema.optional && !property.schema.nullish);
1116
866
  };
1117
867
  return members.map((member, index) => {
1118
- const object = ast.narrowSchema(member, ast.schemaTypes.object);
868
+ const object = ast.schema.narrow(member, ast.schema.types.object);
1119
869
  if (!object || index === 0) return member;
1120
870
  return {
1121
871
  ...object,
@@ -1144,7 +894,7 @@ function preserveRequiredProperties(members) {
1144
894
  * representation, strings are quoted.
1145
895
  */
1146
896
  function formatDefault(value) {
1147
- if (typeof value === "string") return stringify(value);
897
+ if (typeof value === "string") return text.quote(value);
1148
898
  if (typeof value === "object" && value !== null) return JSON.stringify(value);
1149
899
  return String(value ?? "");
1150
900
  }
@@ -1159,16 +909,17 @@ function formatDefault(value) {
1159
909
  */
1160
910
  function defaultLiteral(node, value) {
1161
911
  if (value === null) return null;
1162
- const resolved = node ? syncSchemaRef(node) : void 0;
1163
- if (resolved && ast.narrowSchema(resolved, "bigint")) {
912
+ const resolved = node ? ast.ref.mergeSchema(node) : void 0;
913
+ if (resolved?.type === "blob") return null;
914
+ if (resolved && ast.schema.narrow(resolved, "bigint")) {
1164
915
  if (typeof value === "bigint") return `BigInt(${value})`;
1165
916
  if (typeof value === "number" && Number.isInteger(value)) return `BigInt(${value})`;
1166
917
  return null;
1167
918
  }
1168
- if (resolved && ast.narrowSchema(resolved, "array")) return Array.isArray(value) ? JSON.stringify(value) : null;
1169
- const enumNode = resolved ? ast.narrowSchema(resolved, "enum") : void 0;
919
+ if (resolved && ast.schema.narrow(resolved, "array")) return Array.isArray(value) ? JSON.stringify(value) : null;
920
+ const enumNode = resolved ? ast.schema.narrow(resolved, "enum") : void 0;
1170
921
  if (enumNode) {
1171
- const values = enumNode.namedEnumValues?.map((member) => member.value) ?? enumNode.enumValues ?? [];
922
+ const values = ast.schema.getLiterals(enumNode);
1172
923
  if (values.length) {
1173
924
  const match = values.find((member) => member === value || String(member) === String(value));
1174
925
  return match != null ? formatDefault(match) : null;
@@ -1237,13 +988,13 @@ function patternKeySchemaMini({ patterns, regexType }) {
1237
988
  * Build `.min()` / `.max()` / `.gt()` / `.lt()` constraint chains for numbers
1238
989
  * using the standard chainable Zod v4 API.
1239
990
  */
1240
- function numberConstraints({ min, max, exclusiveMinimum, exclusiveMaximum, multipleOf }) {
991
+ function numberConstraints({ min, max, exclusive, multipleOf }, suffix = "") {
1241
992
  return [
1242
- min !== void 0 ? `.min(${min})` : "",
1243
- max !== void 0 ? `.max(${max})` : "",
1244
- exclusiveMinimum !== void 0 ? `.gt(${exclusiveMinimum})` : "",
1245
- exclusiveMaximum !== void 0 ? `.lt(${exclusiveMaximum})` : "",
1246
- multipleOf !== void 0 ? `.multipleOf(${multipleOf})` : ""
993
+ min !== void 0 ? `.min(${min}${suffix})` : "",
994
+ max !== void 0 ? `.max(${max}${suffix})` : "",
995
+ exclusive?.min !== void 0 ? `.gt(${exclusive.min}${suffix})` : "",
996
+ exclusive?.max !== void 0 ? `.lt(${exclusive.max}${suffix})` : "",
997
+ multipleOf !== void 0 ? `.multipleOf(${multipleOf}${suffix})` : ""
1247
998
  ].join("");
1248
999
  }
1249
1000
  /**
@@ -1258,36 +1009,71 @@ function lengthConstraints({ min, max, pattern, regexType }) {
1258
1009
  ].join("");
1259
1010
  }
1260
1011
  /**
1012
+ * Numeric bounds for a bigint: the inclusive, whole, safe bounds faker mocks against, and a whole, safe `multipleOf`.
1013
+ */
1014
+ function bigintConstraints({ min, max, exclusive, multipleOf }) {
1015
+ return {
1016
+ ...ast.schema.getNumberBounds({
1017
+ min,
1018
+ max,
1019
+ exclusive,
1020
+ integer: true
1021
+ }),
1022
+ multipleOf: Number.isSafeInteger(multipleOf) ? multipleOf : void 0
1023
+ };
1024
+ }
1025
+ /**
1261
1026
  * Build `.check(z.minimum(), z.maximum())` for `zod/mini` numeric constraints.
1262
1027
  */
1263
- function numberChecksMini({ min, max, exclusiveMinimum, exclusiveMaximum, multipleOf }) {
1264
- const checks = [];
1265
- if (min !== void 0) checks.push(`z.minimum(${min})`);
1266
- if (max !== void 0) checks.push(`z.maximum(${max})`);
1267
- if (exclusiveMinimum !== void 0) checks.push(`z.minimum(${exclusiveMinimum}, { exclusive: true })`);
1268
- if (exclusiveMaximum !== void 0) checks.push(`z.maximum(${exclusiveMaximum}, { exclusive: true })`);
1269
- if (multipleOf !== void 0) checks.push(`z.multipleOf(${multipleOf})`);
1028
+ function numberChecksMini({ min, max, exclusive, multipleOf }, suffix = "") {
1029
+ const checks = [
1030
+ min !== void 0 && `z.minimum(${min}${suffix})`,
1031
+ max !== void 0 && `z.maximum(${max}${suffix})`,
1032
+ exclusive?.min !== void 0 && `z.gt(${exclusive.min}${suffix})`,
1033
+ exclusive?.max !== void 0 && `z.lt(${exclusive.max}${suffix})`,
1034
+ multipleOf !== void 0 && `z.multipleOf(${multipleOf}${suffix})`
1035
+ ].filter(Boolean);
1270
1036
  return checks.length ? `.check(${checks.join(", ")})` : "";
1271
1037
  }
1272
1038
  /**
1273
1039
  * Build `.check(z.minLength(), z.maxLength(), z.regex())` for `zod/mini` length constraints.
1274
1040
  */
1275
1041
  function lengthChecksMini({ min, max, pattern, regexType }) {
1276
- const checks = [];
1277
- if (min !== void 0) checks.push(`z.minLength(${min})`);
1278
- if (max !== void 0) checks.push(`z.maxLength(${max})`);
1279
- if (pattern !== void 0) checks.push(`z.regex(${regexSource(pattern, regexType)})`);
1042
+ const checks = [
1043
+ min !== void 0 && `z.minLength(${min})`,
1044
+ max !== void 0 && `z.maxLength(${max})`,
1045
+ pattern !== void 0 && `z.regex(${regexSource(pattern, regexType)})`
1046
+ ].filter(Boolean);
1280
1047
  return checks.length ? `.check(${checks.join(", ")})` : "";
1281
1048
  }
1282
1049
  /**
1283
- * Checks property counts before object parsing strips unknown keys or inserts defaults.
1050
+ * Whether `propertyNames` needs its own check: `z.record` checks the keys of a map, but `z.object` and a record keyed by
1051
+ * `patternProperties` do not.
1052
+ */
1053
+ function hasKeyCheck(node) {
1054
+ if (node.type !== "object" || !node.propertyNames) return false;
1055
+ const hasTypedAdditionalProperties = Boolean(node.additionalProperties && node.additionalProperties !== true);
1056
+ return node.properties.length > 0 || Boolean(node.patternProperties) && !hasTypedAdditionalProperties;
1057
+ }
1058
+ /**
1059
+ * Whether the node prints inside `z.pipe(z.unknown().check(…), …)`, see {@link inputChecks}.
1060
+ */
1061
+ function hasInputChecks(node) {
1062
+ return Boolean(node.conditionals?.length) || node.type === "object" && (node.min !== void 0 || node.max !== void 0) || hasKeyCheck(node);
1063
+ }
1064
+ /**
1065
+ * Checks the raw input, before parsing strips unknown keys or inserts defaults: property counts, key names, then the
1066
+ * printed `conditionals`.
1284
1067
  */
1285
- function objectConstraints({ value, node }) {
1286
- if (node.type !== "object") return value;
1287
- const checks = [];
1068
+ function inputChecks({ value, node, keySchema, conditionals }) {
1288
1069
  const skip = "typeof value !== \"object\" || value === null || Array.isArray(value)";
1289
- if (node.minProperties !== void 0) checks.push(`z.refine(value => ${skip} || Object.keys(value).length >= ${node.minProperties}, { message: "Object must have at least ${node.minProperties} properties" })`);
1290
- if (node.maxProperties !== void 0) checks.push(`z.refine(value => ${skip} || Object.keys(value).length <= ${node.maxProperties}, { message: "Object must have at most ${node.maxProperties} properties" })`);
1070
+ const { min, max } = node.type === "object" ? node : {};
1071
+ const checks = [
1072
+ min !== void 0 && `z.refine(value => ${skip} || Object.keys(value).length >= ${min}, { message: "Object must have at least ${min} properties" })`,
1073
+ max !== void 0 && `z.refine(value => ${skip} || Object.keys(value).length <= ${max}, { message: "Object must have at most ${max} properties" })`,
1074
+ keySchema && `z.refine(value => ${skip} || Object.keys(value).every((key) => ${keySchema}.safeParse(key).success), { message: "Object keys must match the property names schema" })`,
1075
+ ...conditionals
1076
+ ].filter(Boolean);
1291
1077
  return checks.length ? `z.pipe(z.unknown().check(${checks.join(", ")}), ${value})` : value;
1292
1078
  }
1293
1079
  /**
@@ -1303,8 +1089,8 @@ function applyModifiers({ value, schema, nullable, optional, nullish, defaultVal
1303
1089
  })();
1304
1090
  const literal = defaultValue !== void 0 ? defaultLiteral(schema, defaultValue) : null;
1305
1091
  const withDefault = literal !== null ? `${withModifier}.default(${literal})` : withModifier;
1306
- const withDescription = description ? `${withDefault}.describe(${stringify(description)})` : withDefault;
1307
- return examples?.length ? `${withDescription}.meta({ examples: [${examples.map(formatDefault).join(", ")}] })` : withDescription;
1092
+ const withDescription = description ? `${withDefault}.describe(${text.quote(description)})` : withDefault;
1093
+ return examples?.length ? `${withDescription}.meta({ examples: [${examples.map((example) => example === null ? "null" : formatDefault(example)).join(", ")}] })` : withDescription;
1308
1094
  }
1309
1095
  function modifierDepth(schema) {
1310
1096
  if (schema.nullish || schema.nullable && schema.optional) return 2;
@@ -1322,7 +1108,7 @@ function modifierDepth(schema) {
1322
1108
  * `.unwrap()`, so its printer passes `._zod.def.innerType` as the `step`.
1323
1109
  */
1324
1110
  function omitUnwrapChain(node, step = ".unwrap()") {
1325
- const ref = ast.narrowSchema(node, "ref");
1111
+ const ref = ast.schema.narrow(node, "ref");
1326
1112
  if (!ref) return "";
1327
1113
  const target = ref.schema ?? ref;
1328
1114
  const depth = modifierDepth(target) + (target.default !== void 0 ? 1 : 0);
@@ -1344,7 +1130,7 @@ function applyMiniModifiers({ value, schema, nullable, optional, nullish, defaul
1344
1130
  /**
1345
1131
  * Calls for string `format`s with a dedicated Zod constructor; a `Map` so `constructor` cannot hit `Object.prototype`.
1346
1132
  */
1347
- const STRING_FORMAT_CALLS = /* @__PURE__ */ new Map([
1133
+ const stringFormatCalls = /* @__PURE__ */ new Map([
1348
1134
  ["byte", "z.base64()"],
1349
1135
  ["base64", "z.base64()"],
1350
1136
  ["base64url", "z.base64url()"],
@@ -1355,6 +1141,16 @@ const STRING_FORMAT_CALLS = /* @__PURE__ */ new Map([
1355
1141
  ["hostname", "z.hostname()"]
1356
1142
  ]);
1357
1143
  /**
1144
+ * Calls for a string's `contentEncoding` with a dedicated Zod constructor.
1145
+ */
1146
+ const contentEncodingCalls = /* @__PURE__ */ new Map([["base64", "z.base64()"], ["base64url", "z.base64url()"]]);
1147
+ /**
1148
+ * The check for a string that holds a JSON document: it must parse, and the parsed value must pass `schema`.
1149
+ */
1150
+ function jsonContentCheck(schema) {
1151
+ return `(value) => { try { return ${schema}.safeParse(JSON.parse(value)).success } catch { return false } }, { message: "String must hold JSON that matches its content schema" }`;
1152
+ }
1153
+ /**
1358
1154
  * Marks an inline `oneOf` object strict, so `object()` builds `z.strictObject(...)` and no eager `.shape` read hits a deferred getter.
1359
1155
  */
1360
1156
  function strictOneOfNode(node) {
@@ -1367,13 +1163,14 @@ function strictOneOfNode(node) {
1367
1163
  * The constraint chain an `allOf` member adds to a bare string, number or array; each printer passes its own renderers.
1368
1164
  */
1369
1165
  function getMemberConstraint({ member, regexType, lengths, numbers }) {
1166
+ if (hasInputChecks(member) || ast.schema.narrow(member, "array")?.contains) return void 0;
1370
1167
  if (member.primitive === "string") return lengths({
1371
- ...ast.narrowSchema(member, "string") ?? {},
1168
+ ...ast.schema.narrow(member, "string") ?? {},
1372
1169
  regexType
1373
1170
  }) || void 0;
1374
- if (member.primitive === "number" || member.primitive === "integer") return numbers(ast.narrowSchema(member, "number") ?? ast.narrowSchema(member, "integer") ?? {}) || void 0;
1171
+ if (member.primitive === "number" || member.primitive === "integer") return numbers(ast.schema.narrow(member, "number") ?? ast.schema.narrow(member, "integer") ?? {}) || void 0;
1375
1172
  if (member.primitive === "array") return lengths({
1376
- ...ast.narrowSchema(member, "array") ?? {},
1173
+ ...ast.schema.narrow(member, "array") ?? {},
1377
1174
  regexType
1378
1175
  }) || void 0;
1379
1176
  }
@@ -1381,24 +1178,26 @@ function getMemberConstraint({ member, regexType, lengths, numbers }) {
1381
1178
  * Builds the `{ key: value, … }` shape shared by the `z.object(...)` and `.extend(...)` renderings.
1382
1179
  */
1383
1180
  function buildObjectShape(ctx, node, renderProperty) {
1384
- const objectNode = ast.narrowSchema(node, "object");
1181
+ const objectNode = ast.schema.narrow(node, "object");
1385
1182
  if (!objectNode) return "{}";
1386
- const isCyclic = (schema) => ctx.options.cyclicSchemas != null && containsCircularRef(schema, { circularSchemas: ctx.options.cyclicSchemas });
1387
- return buildObject(mapSchemaProperties(objectNode, (schema) => {
1183
+ const isCyclic = (schema) => ctx.options.cyclicSchemas != null && ast.graph.containsCircular(schema, { circularSchemas: ctx.options.cyclicSchemas });
1184
+ const transformed = objectNode.properties.map(({ schema }) => {
1388
1185
  const hasSelfRef = isCyclic(schema);
1389
1186
  const savedCyclicSchemas = ctx.options.cyclicSchemas;
1390
1187
  if (hasSelfRef) ctx.options.cyclicSchemas = void 0;
1391
1188
  const baseOutput = ctx.transform(schema) ?? ctx.transform(ast.factory.createSchema({ type: "unknown" }));
1392
1189
  if (hasSelfRef) ctx.options.cyclicSchemas = savedCyclicSchemas;
1393
1190
  return baseOutput;
1394
- }).map(({ name, property, output: value }) => {
1395
- const { schema } = property;
1191
+ });
1192
+ return buildObject(objectNode.properties.map((property, index) => {
1193
+ const { name, schema } = property;
1194
+ const value = transformed[index];
1396
1195
  return renderProperty({
1397
1196
  name,
1398
1197
  value,
1399
1198
  schema,
1400
1199
  required: property.required,
1401
- meta: syncSchemaRef(schema),
1200
+ meta: ast.ref.mergeSchema(schema),
1402
1201
  cyclic: isCyclic(schema)
1403
1202
  });
1404
1203
  }));
@@ -1410,14 +1209,14 @@ function printWithOmit(ctx, node, modify, mini = false) {
1410
1209
  const { keysToOmit } = ctx.options;
1411
1210
  const transformed = ctx.transform(node);
1412
1211
  if (!transformed) return null;
1413
- const meta = syncSchemaRef(node);
1212
+ const meta = ast.ref.mergeSchema(node);
1414
1213
  return modify((() => {
1415
1214
  if (!keysToOmit?.length || meta.primitive !== "object" || meta.type === "union" && meta.discriminatorPropertyName) return transformed;
1416
1215
  const unwrap = omitUnwrapChain(node, mini ? "._zod.def.innerType" : ".unwrap()");
1417
1216
  const mask = `{ ${keysToOmit.map((k) => `"${k}": true`).join(", ")} }`;
1418
1217
  const omit = (target) => {
1419
1218
  const inner = `${target}${unwrap}`;
1420
- const hasObjectConstraints = meta.type === "object" && (meta.minProperties !== void 0 || meta.maxProperties !== void 0);
1219
+ const hasObjectConstraints = hasInputChecks(meta);
1421
1220
  const pipe = mini ? `${inner}._zod.def` : inner;
1422
1221
  const object = hasObjectConstraints ? `${pipe}.out` : inner;
1423
1222
  const omitted = mini ? `z.omit(${object}, ${mask})` : `${object}.omit(${mask})`;
@@ -1436,7 +1235,7 @@ function printWithOmit(ctx, node, modify, mini = false) {
1436
1235
  function getLiteralUnion({ node, options }) {
1437
1236
  if (node.strategy === "one" && options.oneOfType === "xor" || options.nodes?.enum) return void 0;
1438
1237
  const values = (node.members ?? []).map((member) => {
1439
- const enumNode = ast.narrowSchema(member, ast.schemaTypes.enum);
1238
+ const enumNode = ast.schema.narrow(member, ast.schema.types.enum);
1440
1239
  return enumNode && !enumNode.name && !enumNode.nullable && !enumNode.namedEnumValues && enumNode.enumValues?.length === 1 ? enumNode.enumValues[0] : void 0;
1441
1240
  });
1442
1241
  if (!values.length || !values.every((value) => value !== void 0 && value !== null)) return void 0;
@@ -1449,11 +1248,11 @@ function getLiteralUnion({ node, options }) {
1449
1248
  * Whether a discriminated union member's discriminator property prints as a coerced literal union.
1450
1249
  */
1451
1250
  function hasCoercedDiscriminator({ member, propertyName, options }) {
1452
- return ast.resolveSchemaProperties({
1251
+ return ast.schema.findProperties({
1453
1252
  node: member,
1454
1253
  propertyName
1455
1254
  }).some(({ schema }) => {
1456
- const resolved = syncSchemaRef(schema);
1255
+ const resolved = ast.ref.mergeSchema(schema);
1457
1256
  return resolved.type === "union" && !!getLiteralUnion({
1458
1257
  node: resolved,
1459
1258
  options
@@ -1461,7 +1260,48 @@ function hasCoercedDiscriminator({ member, propertyName, options }) {
1461
1260
  });
1462
1261
  }
1463
1262
  /**
1464
- * Builds the scalar handlers both dialects share. They never recurse into children. The temporal
1263
+ * Whether `z.compile(schema, { strict: true })` throws for the node. Zod runs an unsupported schema below an object
1264
+ * property or an array, tuple or record item on its runtime parser, so only a coerced scalar or a `z.xor` that compiles
1265
+ * inline with the root throws: the node itself, or one reached through refs and union or intersection members.
1266
+ * `siblings` resolves a generated ref, which names another schema of the same file and carries no target.
1267
+ *
1268
+ * Mirrors the built-in handlers, so a type with a `printer.nodes` override is judged as if it printed the built-in output.
1269
+ */
1270
+ function throwsOnStrictCompile({ node, options, siblings, seen = /* @__PURE__ */ new Set() }) {
1271
+ if (!node || seen.has(node)) return false;
1272
+ seen.add(node);
1273
+ const throws = (child) => throwsOnStrictCompile({
1274
+ node: child,
1275
+ options,
1276
+ siblings,
1277
+ seen
1278
+ });
1279
+ switch (node.type) {
1280
+ case ast.schema.types.ref: return throws(node.schema ?? (node.ref || !node.name ? void 0 : siblings?.get(node.name)));
1281
+ case ast.schema.types.union:
1282
+ if (node.strategy === "one" && options.oneOfType === "xor") return true;
1283
+ return !!getLiteralUnion({
1284
+ node,
1285
+ options
1286
+ })?.coerce || (node.members ?? []).some(throws);
1287
+ case ast.schema.types.intersection: return (node.members ?? []).some(throws);
1288
+ case ast.schema.types.bigint: return true;
1289
+ case ast.schema.types.string: return shouldCoerce(options.coercion, "strings");
1290
+ case ast.schema.types.number:
1291
+ case ast.schema.types.integer: return shouldCoerce(options.coercion, "numbers");
1292
+ case ast.schema.types.date: return node.representation === "date" && options.direction !== "encode" && shouldCoerce(options.coercion, "dates");
1293
+ default: return false;
1294
+ }
1295
+ }
1296
+ function hasPlainDiscriminator({ member, propertyName }) {
1297
+ const schemas = ast.schema.findProperties({
1298
+ node: member,
1299
+ propertyName
1300
+ });
1301
+ return schemas.length > 0 && !schemas.some(({ schema }) => ast.schema.getLiterals(ast.ref.mergeSchema(schema)).length > 0);
1302
+ }
1303
+ /**
1304
+ * Builds the scalar handlers both dialects share. Only a string with a `contentSchema` recurses into a child. The temporal
1465
1305
  * scalars and `bigint` differ per dialect, so each printer adds its own.
1466
1306
  */
1467
1307
  function buildScalarNodes(dialect) {
@@ -1474,18 +1314,20 @@ function buildScalarNodes(dialect) {
1474
1314
  null: () => "z.null()",
1475
1315
  string(node) {
1476
1316
  const coerce = shouldCoerce(this.options.coercion, "strings");
1477
- const call = coerce ? void 0 : STRING_FORMAT_CALLS.get(node.format);
1478
- if (call) return `${call}${dialect.lengths({
1479
- ...node,
1480
- regexType: this.options.regexType
1481
- })}`;
1482
- const base = coerce ? "z.coerce.string()" : "z.string()";
1317
+ const base = (coerce ? void 0 : stringFormatCalls.get(node.format) ?? contentEncodingCalls.get(node.content?.encoding?.toLowerCase())) ?? (coerce ? "z.coerce.string()" : "z.string()");
1483
1318
  const pattern = node.pattern ?? integerFormatPattern(node.format);
1484
- return `${base}${dialect.lengths({
1319
+ const value = `${base}${dialect.lengths({
1485
1320
  ...node,
1486
1321
  pattern,
1487
1322
  regexType: this.options.regexType
1488
1323
  })}`;
1324
+ const contentNode = ast.schema.getEmbeddedJson(node);
1325
+ if (!contentNode) return value;
1326
+ const direction = this.options.direction;
1327
+ this.options.direction = "decode";
1328
+ const contentSchema = this.transform(contentNode);
1329
+ this.options.direction = direction;
1330
+ return contentSchema ? dialect.refine(value, jsonContentCheck(contentSchema)) : value;
1489
1331
  },
1490
1332
  number(node) {
1491
1333
  return `${shouldCoerce(this.options.coercion, "numbers") ? "z.coerce.number()" : "z.number()"}${dialect.numbers(node)}`;
@@ -1518,7 +1360,7 @@ function buildScalarNodes(dialect) {
1518
1360
  ipv6: () => "z.ipv6()",
1519
1361
  blob: () => "z.instanceof(Blob)",
1520
1362
  enum(node) {
1521
- const nonNullValues = (node.namedEnumValues?.map((v) => v.value) ?? node.enumValues ?? []).filter((v) => v !== null);
1363
+ const nonNullValues = ast.schema.getLiterals(node).filter((v) => v !== null);
1522
1364
  if (node.namedEnumValues?.length) {
1523
1365
  const literals = nonNullValues.map((v) => buildLiteral(v));
1524
1366
  if (literals.length === 1) return literals[0];
@@ -1537,11 +1379,12 @@ function buildScalarNodes(dialect) {
1537
1379
  */
1538
1380
  function buildZodNodes(dialect, cyclicSchemas) {
1539
1381
  const unionsWithSiblings = /* @__PURE__ */ new WeakSet();
1382
+ const printNullable = (schema, output) => output && schema.nullable ? dialect.nullable(output) : output;
1540
1383
  return {
1541
1384
  ...buildScalarNodes(dialect),
1542
1385
  ref(node) {
1543
1386
  if (!node.name) return null;
1544
- const refName = ast.resolveRefName(node);
1387
+ const refName = ast.ref.getName(node);
1545
1388
  if (!refName) return null;
1546
1389
  const useInputVariant = node.ref != null && (dialect.useInputVariant?.(node, this.options) ?? false);
1547
1390
  const resolvedName = node.ref ? useInputVariant ? this.options.resolver?.schema.inputName(refName) ?? refName : this.options.resolver?.name(refName) ?? refName : node.name;
@@ -1556,47 +1399,50 @@ function buildZodNodes(dialect, cyclicSchemas) {
1556
1399
  const isStrict = node.additionalProperties === false && patterns.length === 0;
1557
1400
  const isLoose = node.additionalProperties === true;
1558
1401
  const objectBase = `${isStrict ? "z.strictObject" : isLoose ? "z.looseObject" : "z.object"}(${dialect.objectShape(this, node)})`;
1559
- return objectConstraints({
1560
- value: (() => {
1561
- const recordFn = isClosedKeySchema(propertyNamesNode) ? "z.partialRecord" : "z.record";
1562
- if (node.additionalProperties && node.additionalProperties !== true) {
1563
- const catchallType = this.transform(node.additionalProperties);
1564
- if (entries.length === 0) return catchallType ? `${recordFn}(${propertyNamesKeySchema || "z.string()"}, ${catchallType})` : objectBase;
1565
- return catchallType ? dialect.catchall(objectBase, catchallType) : objectBase;
1566
- }
1567
- if (entries.length === 0 && patterns.length === 0 && propertyNamesKeySchema && !isStrict) {
1568
- const unknownType = this.transform(ast.factory.createSchema({ type: ast.schemaTypes.unknown }));
1569
- return `${recordFn}(${propertyNamesKeySchema}, ${unknownType})`;
1570
- }
1571
- if (node.additionalProperties === true) return objectBase;
1572
- if (isStrict) return objectBase;
1573
- if (patterns.length > 0) {
1574
- const values = patterns.map(([, valueSchema]) => {
1575
- const valueType = this.transform(valueSchema) ?? this.transform(ast.factory.createSchema({ type: ast.schemaTypes.unknown }));
1576
- return valueSchema.nullable ? dialect.nullable(valueType) : valueType;
1577
- });
1578
- const distinct = [...new Set(values)];
1579
- const value = distinct.length === 1 ? distinct[0] : `z.union([${distinct.join(", ")}])`;
1580
- if (entries.length > 0) return dialect.catchall(objectBase, value);
1581
- return `z.record(${dialect.patternKey({
1582
- patterns: patterns.map(([pattern]) => pattern),
1583
- regexType: this.options.regexType
1584
- })}, ${value})`;
1585
- }
1586
- return objectBase;
1587
- })(),
1588
- node
1589
- });
1402
+ return (() => {
1403
+ const recordFn = isClosedKeySchema(propertyNamesNode) ? "z.partialRecord" : "z.record";
1404
+ if (node.additionalProperties && node.additionalProperties !== true) {
1405
+ const catchallType = this.transform(node.additionalProperties);
1406
+ if (entries.length === 0) return catchallType ? `${recordFn}(${propertyNamesKeySchema || "z.string()"}, ${catchallType})` : objectBase;
1407
+ return catchallType ? dialect.catchall(objectBase, catchallType) : objectBase;
1408
+ }
1409
+ if (entries.length === 0 && patterns.length === 0 && propertyNamesKeySchema && !isStrict) {
1410
+ const unknownType = this.transform(ast.factory.createSchema({ type: ast.schema.types.unknown }));
1411
+ return `${recordFn}(${propertyNamesKeySchema}, ${unknownType})`;
1412
+ }
1413
+ if (node.additionalProperties === true) return objectBase;
1414
+ if (isStrict) return objectBase;
1415
+ if (patterns.length > 0) {
1416
+ const values = patterns.map(([, valueSchema]) => {
1417
+ const valueType = this.transform(valueSchema) ?? this.transform(ast.factory.createSchema({ type: ast.schema.types.unknown }));
1418
+ return valueSchema.nullable ? dialect.nullable(valueType) : valueType;
1419
+ });
1420
+ const distinct = [...new Set(values)];
1421
+ const value = distinct.length === 1 ? distinct[0] : `z.union([${distinct.join(", ")}])`;
1422
+ if (entries.length > 0) return dialect.catchall(objectBase, value);
1423
+ return `z.record(${dialect.patternKey({
1424
+ patterns: patterns.map(([pattern]) => pattern),
1425
+ regexType: this.options.regexType
1426
+ })}, ${value})`;
1427
+ }
1428
+ return objectBase;
1429
+ })();
1590
1430
  },
1591
1431
  array(node) {
1592
- const base = `z.array(${mapSchemaItems(node, (item) => this.transform(item)).map(({ output }) => output).filter(Boolean).join(", ") || this.transform(ast.factory.createSchema({ type: ast.schemaTypes.unknown }))})${dialect.lengths({
1432
+ const base = `z.array(${(node.items ?? []).map((item) => printNullable(item, this.transform(item))).filter(Boolean).join(", ") || this.transform(ast.factory.createSchema({ type: ast.schema.types.unknown }))})${dialect.lengths({
1593
1433
  ...node,
1594
1434
  regexType: this.options.regexType
1595
1435
  })}`;
1596
- return node.unique ? dialect.unique(base) : base;
1436
+ const unique = node.unique ? dialect.unique(base) : base;
1437
+ const contains = node.contains && containsCheck(this, node.contains);
1438
+ return contains ? dialect.refine(unique, contains) : unique;
1597
1439
  },
1598
1440
  tuple(node) {
1599
- return `z.tuple(${buildList(mapSchemaItems(node, (item) => this.transform(item)).map(({ output }) => output).filter(Boolean))})`;
1441
+ const items = (node.items ?? []).map((item) => printNullable(item, this.transform(item))).filter(Boolean);
1442
+ const rest = node.rest ? this.transform(node.rest) : void 0;
1443
+ const tuple = `z.tuple(${buildList(items)}${rest ? `, ${rest}` : ""})`;
1444
+ const contains = node.contains && containsCheck(this, node.contains);
1445
+ return contains ? dialect.refine(tuple, contains) : tuple;
1600
1446
  },
1601
1447
  union(node) {
1602
1448
  const literalUnion = getLiteralUnion({
@@ -1609,24 +1455,30 @@ function buildZodNodes(dialect, cyclicSchemas) {
1609
1455
  }
1610
1456
  const nodeMembers = node.members ?? [];
1611
1457
  const { discriminatorPropertyName } = node;
1612
- const isDiscriminated = !!discriminatorPropertyName && nodeMembers.every((m) => isObjectSchemaNode(m, cyclicSchemas)) && !nodeMembers.some((m) => hasCoercedDiscriminator({
1458
+ const isDiscriminated = !!discriminatorPropertyName && nodeMembers.every((m) => isObjectSchemaNode(m, cyclicSchemas) && !hasPlainDiscriminator({
1459
+ member: m,
1460
+ propertyName: discriminatorPropertyName
1461
+ })) && !nodeMembers.some((m) => hasCoercedDiscriminator({
1613
1462
  member: m,
1614
1463
  propertyName: discriminatorPropertyName,
1615
1464
  options: this.options
1616
1465
  }));
1617
1466
  const isXor = node.strategy === "one" && !isDiscriminated && this.options.oneOfType === "xor";
1618
- const isStrict = node.strategy === "one" && !isXor && !unionsWithSiblings.has(node);
1619
- const members = mapSchemaMembers(node, (memberNode) => this.transform(isStrict ? strictOneOfNode(memberNode) : memberNode)).map(({ schema, output }) => output && isStrict && dialect.strictMember ? dialect.strictMember(output, schema, cyclicSchemas) : output).filter(Boolean);
1467
+ const isStrict = node.strategy === "one" && !isXor && !isDiscriminated && !unionsWithSiblings.has(node);
1468
+ const members = (node.members ?? []).map((memberNode) => {
1469
+ const output = this.transform(isStrict ? strictOneOfNode(memberNode) : memberNode);
1470
+ return output && isStrict && dialect.strictMember ? dialect.strictMember(output, memberNode, cyclicSchemas) : output;
1471
+ }).map((output, index) => printNullable(nodeMembers[index], output)).filter(Boolean);
1620
1472
  if (members.length === 0) return "";
1621
1473
  if (members.length === 1) return members[0];
1622
- if (isDiscriminated) return `z.discriminatedUnion(${stringify(node.discriminatorPropertyName)}, ${buildList(members)})`;
1474
+ if (isDiscriminated) return `z.discriminatedUnion(${text.quote(node.discriminatorPropertyName)}, ${buildList(members)})`;
1623
1475
  if (isXor) return `z.xor(${buildList(members)})`;
1624
1476
  return `z.union(${buildList(members)})`;
1625
1477
  },
1626
1478
  intersection(node) {
1627
1479
  const members = node.members ?? [];
1628
1480
  if (members.length === 0) return "";
1629
- const hasSiblingProperties = members.some((member) => member.type === ast.schemaTypes.object && member.properties.length > 0);
1481
+ const hasSiblingProperties = members.some((member) => member.type === ast.schema.types.object && member.properties.length > 0);
1630
1482
  const transform = (member) => {
1631
1483
  if (!hasSiblingProperties) return this.transform(member);
1632
1484
  unionsWithSiblings.add(member);
@@ -1640,7 +1492,18 @@ function buildZodNodes(dialect, cyclicSchemas) {
1640
1492
  if (!first) return "";
1641
1493
  const firstBase = transform(first);
1642
1494
  if (!firstBase) return "";
1643
- if (rest.length > 0 && isObjectComposableIntersection(node, cyclicSchemas)) return preserveRequiredProperties(members).slice(1).reduce((acc, member) => dialect.extend(acc, dialect.objectShape(this, member)), firstBase);
1495
+ if (rest.length > 0 && isObjectComposableIntersection(node, cyclicSchemas)) {
1496
+ const discriminator = ast.schema.narrow(first, ast.schema.types.union)?.discriminatorPropertyName;
1497
+ return preserveRequiredProperties(members).slice(1).reduce((acc, member) => {
1498
+ const object = ast.schema.narrow(member, ast.schema.types.object);
1499
+ if (!discriminator || !object?.properties.some((property) => property.name === discriminator)) return dialect.extend(acc, dialect.objectShape(this, member));
1500
+ const properties = object.properties.filter((property) => property.name !== discriminator);
1501
+ return properties.length ? dialect.extend(acc, dialect.objectShape(this, {
1502
+ ...object,
1503
+ properties
1504
+ })) : acc;
1505
+ }, firstBase);
1506
+ }
1644
1507
  return rest.reduce((acc, member) => {
1645
1508
  const constraint = getMemberConstraint({
1646
1509
  member,
@@ -1655,15 +1518,162 @@ function buildZodNodes(dialect, cyclicSchemas) {
1655
1518
  }
1656
1519
  };
1657
1520
  }
1521
+ function containsCheck(ctx, { schema, min = 1, max }) {
1522
+ if (min === 0 && max === void 0) return null;
1523
+ const countBounds = [`count >= ${min}`, max !== void 0 && `count <= ${max}`].filter(Boolean).join(" && ");
1524
+ const message = max !== void 0 ? `Array must contain ${min} to ${max} matching items` : `Array must contain at least ${min} matching items`;
1525
+ return `(items) => { const count = items.filter((item) => ${ctx.transform(schema) ?? "z.unknown()"}.safeParse(item).success).length; return ${countBounds} }, { message: ${text.quote(message)} }`;
1526
+ }
1527
+ function conditionExpression(ctx, schema) {
1528
+ if (!schema) return "true";
1529
+ const object = ast.schema.narrow(schema, ast.schema.types.object);
1530
+ const keyOnlyNames = object?.properties.filter((property) => property.required && property.schema.type === ast.schema.types.unknown).map((property) => property.name) ?? [];
1531
+ const restSchema = object ? {
1532
+ ...object,
1533
+ properties: object.properties.filter((property) => !keyOnlyNames.includes(property.name))
1534
+ } : schema;
1535
+ const isOnlyKeys = keyOnlyNames.length > 0 && restSchema.type === "object" && restSchema.properties.length === 0 && restSchema.additionalProperties === void 0 && !restSchema.patternProperties && !restSchema.propertyNames && !hasInputChecks(restSchema);
1536
+ return [keyOnlyNames.length > 0 && `typeof value === "object" && value !== null && ${JSON.stringify(keyOnlyNames)}.every((key) => key in value)`, !isOnlyKeys && `${ctx.transform(restSchema) ?? "z.unknown()"}.safeParse(value).success`].filter(Boolean).join(" && ");
1537
+ }
1538
+ /**
1539
+ * Wraps each handler so its output first checks the raw input, see {@link inputChecks}.
1540
+ */
1541
+ function withInputChecks(nodes) {
1542
+ const wrapHandler = (type) => {
1543
+ const handler = nodes[type];
1544
+ if (!handler) return [];
1545
+ const checkedHandler = function(node) {
1546
+ const output = handler.call(this, node);
1547
+ if (!output || !hasInputChecks(node)) return output;
1548
+ return inputChecks({
1549
+ value: output,
1550
+ node,
1551
+ keySchema: hasKeyCheck(node) ? this.transform(node.propertyNames) : void 0,
1552
+ conditionals: (node.conditionals ?? []).map((entry) => `z.refine((value) => (${conditionExpression(this, entry.if)} ? ${conditionExpression(this, entry.then)} : ${conditionExpression(this, entry.else)}), { message: "Value does not meet a conditional schema" })`)
1553
+ });
1554
+ };
1555
+ return [[type, checkedHandler]];
1556
+ };
1557
+ return Object.fromEntries(Object.values(ast.schema.types).flatMap(wrapHandler));
1558
+ }
1559
+ // src/components/Zod.tsx
1560
+ /**
1561
+ * Whether the schema reaches a circular schema through its refs. `containsCircular` reads the refs the node holds,
1562
+ * while Zod refuses to compile a cycle anywhere below the root. A generated ref carries no target, so `siblings` resolves it.
1563
+ */
1564
+ function reachesCycle({ node, siblings, cyclicSchemas, seen = /* @__PURE__ */ new Set() }) {
1565
+ if (!cyclicSchemas?.size || seen.has(node)) return false;
1566
+ seen.add(node);
1567
+ return ast.schema.find(node, {
1568
+ followRefs: true,
1569
+ predicate(schema) {
1570
+ if (schema.type !== ast.schema.types.ref) return false;
1571
+ const name = ast.ref.getName(schema);
1572
+ if (name && cyclicSchemas.has(name)) return true;
1573
+ const sibling = schema.ref || !schema.name ? void 0 : siblings?.get(schema.name);
1574
+ return !!sibling && reachesCycle({
1575
+ node: sibling,
1576
+ siblings,
1577
+ cyclicSchemas,
1578
+ seen
1579
+ });
1580
+ }
1581
+ }) !== void 0;
1582
+ }
1583
+ function Zod({ name, node, printer, inferTypeName, inferKind = "infer", typeGuards, isName, assertName, mini, cyclic, compile, siblings }) {
1584
+ const output = printer.print(node);
1585
+ const printerImports = printer.drainImports();
1586
+ if (!output) return;
1587
+ const needsAnnotation = cyclic && node.type !== "object";
1588
+ const isAlias = ast.ref.isAlias(node, printer.options.keysToOmit);
1589
+ const isCyclic = Boolean(cyclic || printer.options.cyclicSchemas && ast.graph.containsCircular(node, { circularSchemas: printer.options.cyclicSchemas }));
1590
+ const isStrict = typeof compile === "object" && Boolean(compile.strict);
1591
+ const throwsOnCompile = isStrict && (throwsOnStrictCompile({
1592
+ node,
1593
+ options: printer.options,
1594
+ siblings
1595
+ }) || reachesCycle({
1596
+ node,
1597
+ siblings,
1598
+ cyclicSchemas: printer.options.cyclicSchemas
1599
+ }));
1600
+ const value = Boolean(compile) && !isAlias && !isCyclic && !throwsOnCompile ? isStrict ? `z.compile(${output}, { strict: true })` : `z.compile(${output})` : output;
1601
+ const targetType = inferTypeName ?? `z.infer<typeof ${name}>`;
1602
+ const shouldGenerateIs = isName && (typeof typeGuards === "object" ? typeGuards.is ?? true : Boolean(typeGuards));
1603
+ const shouldGenerateAssert = assertName && (typeof typeGuards === "object" ? typeGuards.assert ?? true : Boolean(typeGuards));
1604
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1605
+ printerImports.map((imp) => /* @__PURE__ */ jsx(File.Import, {
1606
+ name: imp.name,
1607
+ path: imp.path,
1608
+ root: imp.root,
1609
+ isTypeOnly: imp.isTypeOnly,
1610
+ isNameSpace: imp.isNameSpace
1611
+ }, [
1612
+ name,
1613
+ imp.path,
1614
+ imp.name
1615
+ ].join("-"))),
1616
+ /* @__PURE__ */ jsx(File.Source, {
1617
+ name,
1618
+ isExportable: true,
1619
+ isIndexable: true,
1620
+ children: /* @__PURE__ */ jsx(Const, {
1621
+ export: true,
1622
+ name,
1623
+ type: needsAnnotation ? mini ? "z.ZodMiniType" : "z.ZodType" : void 0,
1624
+ children: value
1625
+ })
1626
+ }),
1627
+ inferTypeName && /* @__PURE__ */ jsx(File.Source, {
1628
+ name: inferTypeName,
1629
+ isExportable: true,
1630
+ isIndexable: true,
1631
+ isTypeOnly: true,
1632
+ children: /* @__PURE__ */ jsx(Type, {
1633
+ export: true,
1634
+ name: inferTypeName,
1635
+ children: `z.${inferKind}<typeof ${name}>`
1636
+ })
1637
+ }),
1638
+ shouldGenerateIs && /* @__PURE__ */ jsx(File.Source, {
1639
+ name: isName,
1640
+ isExportable: true,
1641
+ isIndexable: true,
1642
+ children: /* @__PURE__ */ jsx(Const, {
1643
+ export: true,
1644
+ name: isName,
1645
+ JSDoc: { comments: [`Type guard for {@link ${name}}`] },
1646
+ children: mini ? `(data: unknown): data is ${targetType} => z.validate(${name}, data)` : `(data: unknown): data is ${targetType} => ${name}.validate(data)`
1647
+ })
1648
+ }),
1649
+ shouldGenerateAssert && /* @__PURE__ */ jsx(File.Source, {
1650
+ name: assertName,
1651
+ isExportable: true,
1652
+ isIndexable: true,
1653
+ children: /* @__PURE__ */ jsx(Function, {
1654
+ export: true,
1655
+ name: assertName,
1656
+ params: "data: unknown",
1657
+ returnType: `asserts data is ${targetType}`,
1658
+ JSDoc: { comments: [`Asserter for {@link ${name}}`, "@throws {z.ZodError} If data is invalid"] },
1659
+ children: mini ? `if (!z.validate(${name}, data)) {\n z.parse(${name}, data)\n}` : `if (!${name}.validate(data)) {\n ${name}.parse(data)\n}`
1660
+ })
1661
+ })
1662
+ ] });
1663
+ }
1658
1664
  // src/printers/printerZod.ts
1659
1665
  function strictOneOfMember(member, node, cyclicSchemas) {
1660
- if (node.type === ast.schemaTypes.ref) {
1666
+ if (node.type === ast.schema.types.ref) {
1661
1667
  if (member.startsWith("z.lazy(")) return member;
1662
- const refName = ast.resolveRefName(node);
1668
+ const refName = ast.ref.getName(node);
1663
1669
  if (refName && cyclicSchemas?.has(refName)) return member;
1664
- const schema = syncSchemaRef(node);
1670
+ const schema = ast.ref.mergeSchema(node);
1665
1671
  if (schema.nullable || schema.optional || node.nullable || node.optional) return member;
1666
- if (schema.type === ast.schemaTypes.object && (schema.additionalProperties === void 0 || schema.additionalProperties === false)) return `${member}.strict()`;
1672
+ if (schema.type === ast.schema.types.object && (schema.additionalProperties === void 0 || schema.additionalProperties === false)) {
1673
+ const checks = [node, node.schema].filter((entry) => entry && hasInputChecks(entry)).length;
1674
+ if (checks === 2) return member;
1675
+ return checks ? `z.pipe(${member}.in, ${member}.out.strict())` : `${member}.strict()`;
1676
+ }
1667
1677
  }
1668
1678
  return member;
1669
1679
  }
@@ -1679,7 +1689,7 @@ function buildZodObjectShape(ctx, node) {
1679
1689
  optional: schema.optional || required === false,
1680
1690
  nullish: schema.nullish,
1681
1691
  defaultValue: meta.default,
1682
- description: schema.type !== ast.schemaTypes.ref && meta.type === ast.schemaTypes.ref ? void 0 : meta.description,
1692
+ description: schema.type !== ast.schema.types.ref && meta.type === ast.schema.types.ref ? void 0 : meta.description,
1683
1693
  examples: meta.examples
1684
1694
  });
1685
1695
  if (!cyclic) return `${objectKey(name)}: ${body}`;
@@ -1700,21 +1710,22 @@ function buildZodObjectShape(ctx, node) {
1700
1710
  function recursiveGetterType({ ctx, schema, required }) {
1701
1711
  function baseType(schema) {
1702
1712
  if (ctx.options.nodes?.[schema.type]) return void 0;
1703
- if (schema.type === ast.schemaTypes.ref) {
1713
+ if (schema.type === ast.schema.types.ref) {
1704
1714
  const saved = ctx.options.cyclicSchemas;
1705
1715
  ctx.options.cyclicSchemas = void 0;
1706
1716
  const name = ctx.transform(schema);
1707
1717
  ctx.options.cyclicSchemas = saved;
1708
1718
  return name ? `typeof ${name}` : void 0;
1709
1719
  }
1710
- if (schema.type === ast.schemaTypes.array && schema.items?.length === 1) {
1720
+ if (schema.type === ast.schema.types.array && schema.items?.length === 1) {
1711
1721
  const item = baseType(schema.items[0]);
1712
- return item ? `z.ZodArray<${item}>` : void 0;
1722
+ if (!item) return void 0;
1723
+ return `z.ZodArray<${schema.items[0].nullable ? `z.ZodNullable<${item}>` : item}>`;
1713
1724
  }
1714
1725
  }
1715
1726
  const base = baseType(schema);
1716
1727
  if (!base) return void 0;
1717
- const meta = syncSchemaRef(schema);
1728
+ const meta = ast.ref.mergeSchema(schema);
1718
1729
  const optional = schema.optional || required === false;
1719
1730
  const wrapped = (() => {
1720
1731
  if (schema.nullish || meta.nullable && optional) return `z.ZodOptional<z.ZodNullable<${base}>>`;
@@ -1729,12 +1740,12 @@ function recursiveGetterType({ ctx, schema, required }) {
1729
1740
  * `direction` themselves, so {@link containsDirectionalNode} probes their children instead.
1730
1741
  */
1731
1742
  const CONTAINER_TYPES = /* @__PURE__ */ new Set([
1732
- ast.schemaTypes.object,
1733
- ast.schemaTypes.array,
1734
- ast.schemaTypes.tuple,
1735
- ast.schemaTypes.union,
1736
- ast.schemaTypes.intersection,
1737
- ast.schemaTypes.ref
1743
+ ast.schema.types.object,
1744
+ ast.schema.types.array,
1745
+ ast.schema.types.tuple,
1746
+ ast.schema.types.union,
1747
+ ast.schema.types.intersection,
1748
+ ast.schema.types.ref
1738
1749
  ]);
1739
1750
  /**
1740
1751
  * Runs the node's effective handler (a `printer.nodes` override, else the built-in) once per
@@ -1765,22 +1776,31 @@ function variesByDirection({ node, printerOptions }) {
1765
1776
  */
1766
1777
  function containsDirectionalNode({ node, printerOptions }) {
1767
1778
  if (!node) return false;
1768
- return ast.findSchema(node, {
1779
+ return ast.schema.find(node, {
1769
1780
  predicate: (schema) => variesByDirection({
1770
1781
  node: schema,
1771
1782
  printerOptions
1772
1783
  }),
1773
- followRefs: true
1784
+ followRefs: true,
1785
+ cache: getDirectionalCache(printerOptions)
1774
1786
  }) !== void 0;
1775
1787
  }
1776
1788
  /**
1789
+ * The `find` cache for {@link containsDirectionalNode}, one per plugin per build and per option set
1790
+ * the probe reads. A `printer.nodes` override has no string key, so it searches without a cache.
1791
+ */
1792
+ function getDirectionalCache({ resolver, nodes, coercion, dateType }) {
1793
+ if (!resolver || nodes) return void 0;
1794
+ return resolver.memo(`zodDirectionalNodes:${shouldCoerce(coercion, "dates")}:${dateType}`, () => /* @__PURE__ */ new WeakMap());
1795
+ }
1796
+ /**
1777
1797
  * Names of the `$ref` schemas the generator should route to their input (encode) variant.
1778
1798
  */
1779
1799
  function collectDirectionalRefNames({ node, printerOptions }) {
1780
- return ast.collectSync(node, { schema: (n) => n.type === ast.schemaTypes.ref && n.ref && containsDirectionalNode({
1800
+ return ast.node.collect(node, { schema: (n) => n.type === ast.schema.types.ref && n.ref && containsDirectionalNode({
1781
1801
  node: n,
1782
1802
  printerOptions
1783
- }) ? ast.resolveRefName(n) ?? void 0 : void 0 });
1803
+ }) ? ast.ref.getName(n) ?? void 0 : void 0 });
1784
1804
  }
1785
1805
  /**
1786
1806
  * Handlers that never recurse into children, so {@link variesByDirection} can call one directly
@@ -1802,6 +1822,7 @@ const standardDialect = {
1802
1822
  coercedNumber: (schema) => `z.coerce.number().pipe(${schema})`,
1803
1823
  catchall: (object, value) => `${object}.catchall(${value})`,
1804
1824
  nullable: (value) => `${value}.nullable()`,
1825
+ refine: (value, check) => `${value}.refine(${check})`,
1805
1826
  unique: (array) => `${array}.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })`,
1806
1827
  extend: (base, shape) => `${base}.extend(${shape})`,
1807
1828
  intersection: (left, right) => `${left}.and(${right})`,
@@ -1813,8 +1834,8 @@ const standardDialect = {
1813
1834
  };
1814
1835
  const scalarNodes = {
1815
1836
  ...buildScalarNodes(standardDialect),
1816
- bigint() {
1817
- return "z.coerce.bigint()";
1837
+ bigint(node) {
1838
+ return `z.coerce.bigint()${numberConstraints(bigintConstraints(node), "n")}`;
1818
1839
  },
1819
1840
  date(node) {
1820
1841
  if (node.representation !== "date") return "z.iso.date()";
@@ -1847,14 +1868,14 @@ const scalarNodes = {
1847
1868
  * const code = printer.print(stringNode) // "z.string()"
1848
1869
  * ```
1849
1870
  */
1850
- const printerZod = ast.createPrinter((options) => {
1871
+ const printerZod = createPrinter((options) => {
1851
1872
  return {
1852
1873
  name: "zod",
1853
1874
  options,
1854
- nodes: {
1875
+ nodes: withInputChecks({
1855
1876
  ...buildZodNodes(standardDialect, options.cyclicSchemas),
1856
1877
  ...scalarNodes
1857
- },
1878
+ }),
1858
1879
  overrides: options.nodes,
1859
1880
  print(node) {
1860
1881
  return printWithOmit(this, node, (value, meta) => applyModifiers({
@@ -1911,18 +1932,19 @@ const miniDialect = {
1911
1932
  coercedNumber: (schema) => `z.pipe(z.coerce.number(), ${schema})`,
1912
1933
  catchall: (object, value) => `z.catchall(${object}, ${value})`,
1913
1934
  nullable: (value) => `z.nullable(${value})`,
1935
+ refine: (value, check) => `${value}.check(z.refine(${check}))`,
1914
1936
  unique: (array) => `${array}.check(z.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" }))`,
1915
1937
  extend: (base, shape) => `z.extend(${base}, ${shape})`,
1916
1938
  intersection: (left, right) => `z.intersection(${left}, ${right})`
1917
1939
  };
1918
- const printerZodMini = ast.createPrinter((options) => {
1940
+ const printerZodMini = createPrinter((options) => {
1919
1941
  return {
1920
1942
  name: "zod-mini",
1921
1943
  options,
1922
- nodes: {
1944
+ nodes: withInputChecks({
1923
1945
  ...buildZodNodes(miniDialect, options.cyclicSchemas),
1924
1946
  bigint(node) {
1925
- return `z.coerce.bigint()${numberChecksMini(node)}`;
1947
+ return `z.coerce.bigint()${numberChecksMini(bigintConstraints(node), "n")}`;
1926
1948
  },
1927
1949
  date(node) {
1928
1950
  if (node.representation === "string") return "z.iso.date()";
@@ -1935,7 +1957,7 @@ const printerZodMini = ast.createPrinter((options) => {
1935
1957
  if (node.representation === "string") return "z.iso.time()";
1936
1958
  return "z.date()";
1937
1959
  }
1938
- },
1960
+ }),
1939
1961
  overrides: options.nodes,
1940
1962
  print(node) {
1941
1963
  return printWithOmit(this, node, (value, meta) => applyMiniModifiers({
@@ -2022,7 +2044,8 @@ const zodGenerator = defineGenerator({
2022
2044
  node,
2023
2045
  root,
2024
2046
  output,
2025
- group: group ?? void 0
2047
+ group: group ?? void 0,
2048
+ checks: true
2026
2049
  });
2027
2050
  const inputImportEntries = hasDirectionalNode ? [...directionalRefNames].map((schemaName) => ({
2028
2051
  name: [resolver.schema.inputName(schemaName)],
@@ -2113,7 +2136,7 @@ const zodGenerator = defineGenerator({
2113
2136
  });
2114
2137
  },
2115
2138
  operation(node, ctx) {
2116
- if (!ast.isHttpOperationNode(node)) return null;
2139
+ if (!ast.http.isOperation(node)) return null;
2117
2140
  const { adapter, config, resolver, root } = ctx;
2118
2141
  const { output, coercion, guidType, regexType, oneOfType, compile, mini, inferred, importPath, group, printer } = ctx.options;
2119
2142
  const dateType = getOasAdapter(adapter).options.dateType;
@@ -2136,23 +2159,26 @@ const zodGenerator = defineGenerator({
2136
2159
  cyclicSchemas: getCircularNames(ctx),
2137
2160
  nodes: printer?.nodes
2138
2161
  };
2162
+ const siblings = /* @__PURE__ */ new Map();
2139
2163
  function renderSchemaEntry({ schema, name, keysToOmit, direction = "decode" }) {
2140
2164
  if (!schema) return null;
2165
+ siblings.set(name, schema);
2141
2166
  const directionalRefNames = direction === "encode" && !mini ? new Set(collectDirectionalRefNames({
2142
2167
  node: schema,
2143
2168
  printerOptions
2144
2169
  })) : null;
2170
+ const schemaPrinter = getPrinter(resolver, {
2171
+ ...printerOptions,
2172
+ keysToOmit
2173
+ }, mini, direction);
2145
2174
  const imports = resolver.imports({
2146
2175
  node: schema,
2147
2176
  root,
2148
2177
  output,
2149
2178
  group: group ?? void 0,
2179
+ checks: true,
2150
2180
  name: (schemaName) => directionalRefNames?.has(schemaName) ? resolver.schema.inputName(schemaName) : resolver.name(schemaName)
2151
2181
  });
2152
- const schemaPrinter = getPrinter(resolver, {
2153
- ...printerOptions,
2154
- keysToOmit
2155
- }, mini, direction);
2156
2182
  return /* @__PURE__ */ jsxs(Fragment, { children: [imports.map((imp) => /* @__PURE__ */ jsx(File.Import, {
2157
2183
  root: meta.file.path,
2158
2184
  path: imp.path,
@@ -2168,7 +2194,8 @@ const zodGenerator = defineGenerator({
2168
2194
  inferTypeName: inferred ? resolver.schema.type(name) : null,
2169
2195
  inferKind: aliasKind(inferred, direction),
2170
2196
  cyclic: false,
2171
- compile
2197
+ compile,
2198
+ siblings
2172
2199
  })] });
2173
2200
  }
2174
2201
  function buildContentTypeVariants(entries, baseName, decorate, direction) {
@@ -2192,7 +2219,7 @@ const zodGenerator = defineGenerator({
2192
2219
  })] });
2193
2220
  }
2194
2221
  function buildResponseUnion({ responses, name, fallbackUnknown }) {
2195
- if (new Set(responses.flatMap((res) => (res.content ?? []).flatMap((entry) => entry.schema ? ast.collectImportedRefNames(entry.schema).map((refName) => resolver.name(refName)) : []))).has(name)) return null;
2222
+ if (new Set(ast.http.responses.getImportedNames(responses).map((refName) => resolver.name(refName))).has(name)) return null;
2196
2223
  const members = responses.map((res) => ast.factory.createSchema({
2197
2224
  type: "ref",
2198
2225
  name: resolver.response.status(node, res.statusCode)
@@ -2225,14 +2252,27 @@ const zodGenerator = defineGenerator({
2225
2252
  keysToOmit: primary?.keysToOmit
2226
2253
  });
2227
2254
  });
2255
+ const toParamsSchema = (params) => ast.factory.createSchema({
2256
+ type: "object",
2257
+ primitive: "object",
2258
+ properties: params.map((param) => ast.factory.createProperty({
2259
+ name: param.name,
2260
+ required: param.required,
2261
+ schema: param.schema
2262
+ }))
2263
+ });
2264
+ const responseHeaderSchemas = node.responses.map((res) => res.headers?.length ? renderSchemaEntry({
2265
+ schema: toParamsSchema(res.headers),
2266
+ name: resolver.response.headers(node, res.statusCode)
2267
+ }) : null);
2228
2268
  const responsesWithSchema = node.responses.filter((res) => res.content?.some((entry) => entry.schema));
2229
- const successResponsesWithSchema = getSuccessResponses(responsesWithSchema);
2269
+ const successResponsesWithSchema = responsesWithSchema.filter((res) => ast.http.status.isSuccess(res));
2230
2270
  const responseUnionSchema = responsesWithSchema.length > 0 ? buildResponseUnion({
2231
2271
  responses: successResponsesWithSchema,
2232
2272
  name: resolver.response.response(node),
2233
2273
  fallbackUnknown: true
2234
2274
  }) : null;
2235
- const errorResponsesWithSchema = responsesWithSchema.filter((res) => !isSuccessStatusCode(res.statusCode));
2275
+ const errorResponsesWithSchema = responsesWithSchema.filter((res) => !ast.http.status.isSuccess(res));
2236
2276
  const errorUnionSchema = errorResponsesWithSchema.length > 0 ? buildResponseUnion({
2237
2277
  responses: errorResponsesWithSchema,
2238
2278
  name: resolver.response.error(node),
@@ -2259,7 +2299,7 @@ const zodGenerator = defineGenerator({
2259
2299
  description: node.requestBody.description ?? schema.description
2260
2300
  }), "encode");
2261
2301
  })();
2262
- const { path, query, header, cookie } = getOperationParameters(node);
2302
+ const { path, query, header, cookie } = ast.http.groupParameters(node);
2263
2303
  const paramGroupSchemas = [
2264
2304
  {
2265
2305
  kind: "path",
@@ -2278,15 +2318,7 @@ const zodGenerator = defineGenerator({
2278
2318
  params: cookie
2279
2319
  }
2280
2320
  ].filter(({ params }) => params.length > 0).map(({ kind, params }) => renderSchemaEntry({
2281
- schema: ast.factory.createSchema({
2282
- type: "object",
2283
- primitive: "object",
2284
- properties: params.map((param) => ast.factory.createProperty({
2285
- name: param.name,
2286
- required: param.required,
2287
- schema: param.schema
2288
- }))
2289
- }),
2321
+ schema: toParamsSchema(params),
2290
2322
  name: resolver.param[kind](node, params[0]),
2291
2323
  direction: "encode"
2292
2324
  }));
@@ -2327,6 +2359,7 @@ const zodGenerator = defineGenerator({
2327
2359
  }),
2328
2360
  paramSchemas,
2329
2361
  responseSchemas,
2362
+ responseHeaderSchemas,
2330
2363
  responseUnionSchema,
2331
2364
  errorUnionSchema,
2332
2365
  requestSchema,
@@ -2339,11 +2372,11 @@ const zodGenerator = defineGenerator({
2339
2372
  });
2340
2373
  // kubb-extension.json
2341
2374
  var compatibility = {
2342
- "kubb": ">=5.16.0",
2375
+ "kubb": ">=5.18.4",
2343
2376
  "node": ">=22.22"
2344
2377
  };
2345
2378
  // package.json
2346
- var version = "5.9.0-canary.20261111T122006";
2379
+ var version = "5.9.1";
2347
2380
  // src/resolvers/resolverZod.ts
2348
2381
  /**
2349
2382
  * Default resolver used by `@kubb/plugin-zod`. Decides the names and file
@@ -2363,15 +2396,15 @@ var version = "5.9.0-canary.20261111T122006";
2363
2396
  const resolverZod = createResolver({
2364
2397
  pluginName: "plugin-zod",
2365
2398
  name(name) {
2366
- return this.memo(`name:${name}`, () => ensureValidVarName(camelCase(name, { suffix: "schema" })));
2399
+ return this.memo(`name:${name}`, () => ensureValidVarName(casing.camel(name, { suffix: "schema" })));
2367
2400
  },
2368
- file: createCasedFile((part) => camelCase(part, { suffix: "schema" })),
2401
+ file: createCasedFile((part) => casing.camel(part, { suffix: "schema" })),
2369
2402
  schema: {
2370
2403
  typeName(name) {
2371
- return this.memo(`typeName:${name}`, () => ensureValidVarName(pascalCase(name, { suffix: "schema type" })));
2404
+ return this.memo(`typeName:${name}`, () => ensureValidVarName(casing.pascal(name, { suffix: "schema type" })));
2372
2405
  },
2373
2406
  type(name) {
2374
- return this.memo(`type:${name}`, () => ensureValidVarName(pascalCase(name, { suffix: "type" })));
2407
+ return this.memo(`type:${name}`, () => ensureValidVarName(casing.pascal(name, { suffix: "type" })));
2375
2408
  },
2376
2409
  inputName(name) {
2377
2410
  return this.name(`${name} input`);
@@ -2380,10 +2413,10 @@ const resolverZod = createResolver({
2380
2413
  return this.schema.typeName(`${name} input`);
2381
2414
  },
2382
2415
  isName(name) {
2383
- return ensureValidVarName(camelCase(name, { prefix: "is" }));
2416
+ return ensureValidVarName(casing.camel(name, { prefix: "is" }));
2384
2417
  },
2385
2418
  assertName(name) {
2386
- return ensureValidVarName(camelCase(name, { prefix: "assert" }));
2419
+ return ensureValidVarName(casing.camel(name, { prefix: "assert" }));
2387
2420
  }
2388
2421
  },
2389
2422
  param: createOperationParamResolver(),
@@ -2457,7 +2490,7 @@ const pluginZod = defineExtension({
2457
2490
  exclude,
2458
2491
  include,
2459
2492
  override,
2460
- group: createGroupConfig(group),
2493
+ group: group ?? null,
2461
2494
  importPath,
2462
2495
  coercion,
2463
2496
  inferred: inferred === "direction" ? "codec" : inferred,