@kubb/plugin-zod 5.0.0-beta.10 → 5.0.0-beta.100

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.cjs CHANGED
@@ -9,9 +9,171 @@ var __name = (target, value) => __defProp(target, "name", {
9
9
  configurable: true
10
10
  });
11
11
  //#endregion
12
- let _kubb_core = require("@kubb/core");
13
- let _kubb_renderer_jsx = require("@kubb/renderer-jsx");
14
- let _kubb_renderer_jsx_jsx_runtime = require("@kubb/renderer-jsx/jsx-runtime");
12
+ let kubb_kit = require("kubb/kit");
13
+ let kubb_jsx = require("kubb/jsx");
14
+ let kubb_jsx_jsx_runtime = require("kubb/jsx/jsx-runtime");
15
+ //#region ../../internals/shared/src/params.ts
16
+ /**
17
+ * Drops parameters that share the same name, keeping the first.
18
+ *
19
+ * A malformed spec can declare the same parameter name twice within one `in` location. Both would
20
+ * resolve to the same output property, so emitting both would yield an object type with a duplicate
21
+ * member, which TypeScript rejects. This is a defensive guard against that case, not a casing guard:
22
+ * parameter names flow through unchanged, so no two distinct names ever collide here anymore.
23
+ */
24
+ function dedupeParams(params) {
25
+ const seen = /* @__PURE__ */ new Set();
26
+ return params.filter((param) => {
27
+ if (seen.has(param.name)) return false;
28
+ seen.add(param.name);
29
+ return true;
30
+ });
31
+ }
32
+ //#endregion
33
+ //#region ../../internals/shared/src/operation.ts
34
+ /**
35
+ * Maps a content type to the PascalCase suffix used to name per-content-type variants
36
+ * (e.g. `application/json` → `Json`, `application/xml` → `Xml`, `multipart/form-data` → `FormData`).
37
+ */
38
+ function getContentTypeSuffix(contentType) {
39
+ const baseType = contentType.split(";")[0].trim();
40
+ if (baseType === "application/json") return "Json";
41
+ if (baseType === "multipart/form-data") return "FormData";
42
+ if (baseType === "application/x-www-form-urlencoded") return "FormUrlEncoded";
43
+ const parts = (baseType.split("/").pop() ?? baseType).split(/[^a-zA-Z0-9]+/).filter(Boolean);
44
+ if (parts.length === 0) return "Unknown";
45
+ return parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
46
+ }
47
+ /**
48
+ * Appends a content-type suffix to a base name, keeping a trailing `Data` segment last
49
+ * (e.g. `AddPetData` + `Json` → `AddPetJsonData`, `AddPetStatus200` + `Xml` → `AddPetStatus200Xml`).
50
+ */
51
+ function getPerContentTypeName(baseName, suffix) {
52
+ if (baseName.endsWith("Data")) return suffix.endsWith("Data") ? baseName.slice(0, -4) + suffix : `${baseName.slice(0, -4)}${suffix}Data`;
53
+ return baseName + suffix;
54
+ }
55
+ /**
56
+ * Resolves per-content-type variant names for a set of content entries, deduplicating suffix
57
+ * collisions with a numeric counter. Entries without a schema are skipped. The returned `suffix` is
58
+ * the final (possibly counter-augmented) value, so callers can derive parallel names in another
59
+ * namespace (e.g. plugin-faker deriving the matching plugin-ts type name).
60
+ */
61
+ function resolveContentTypeVariants(entries, baseName) {
62
+ const usedNames = /* @__PURE__ */ new Set();
63
+ return entries.filter((entry) => entry.schema).map((entry) => {
64
+ const baseSuffix = getContentTypeSuffix(entry.contentType);
65
+ let suffix = baseSuffix;
66
+ let name = getPerContentTypeName(baseName, suffix);
67
+ let counter = 2;
68
+ while (usedNames.has(name)) {
69
+ suffix = `${baseSuffix}${counter++}`;
70
+ name = getPerContentTypeName(baseName, suffix);
71
+ }
72
+ usedNames.add(name);
73
+ return {
74
+ name,
75
+ suffix,
76
+ schema: entry.schema,
77
+ keysToOmit: entry.keysToOmit,
78
+ contentType: entry.contentType
79
+ };
80
+ });
81
+ }
82
+ function getOperationParameters(node) {
83
+ return {
84
+ path: dedupeParams(node.parameters.filter((param) => param.in === "path")),
85
+ query: dedupeParams(node.parameters.filter((param) => param.in === "query")),
86
+ header: dedupeParams(node.parameters.filter((param) => param.in === "header")),
87
+ cookie: dedupeParams(node.parameters.filter((param) => param.in === "cookie"))
88
+ };
89
+ }
90
+ /**
91
+ * Builds the combined `{ body, path, query, headers }` options object schema for an operation,
92
+ * referencing the already-resolved body and grouped param names. Shared by `@kubb/plugin-ts`'s
93
+ * `Options` type and `@kubb/plugin-zod`'s inferred options schema, so both printers emit the same
94
+ * shape from the same inputs. `primitive: 'object'` is a no-op for the TS printer and tells the Zod
95
+ * printer to emit `z.object(…)` rather than a record.
96
+ */
97
+ function buildOptionsSchema(node, resolver) {
98
+ const { path, query, header } = getOperationParameters(node);
99
+ const hasBody = Boolean(node.requestBody?.content?.[0]?.schema);
100
+ const createNever = () => kubb_kit.ast.factory.createSchema({
101
+ type: "never",
102
+ primitive: void 0,
103
+ optional: true
104
+ });
105
+ const groups = [
106
+ {
107
+ name: "path",
108
+ params: path,
109
+ resolve: resolver.param.path
110
+ },
111
+ {
112
+ name: "query",
113
+ params: query,
114
+ resolve: resolver.param.query
115
+ },
116
+ {
117
+ name: "headers",
118
+ params: header,
119
+ resolve: resolver.param.headers
120
+ }
121
+ ];
122
+ return kubb_kit.ast.factory.createSchema({
123
+ type: "object",
124
+ primitive: "object",
125
+ deprecated: node.deprecated,
126
+ properties: [kubb_kit.ast.factory.createProperty({
127
+ name: "body",
128
+ required: hasBody,
129
+ schema: hasBody ? kubb_kit.ast.factory.createSchema({
130
+ type: "ref",
131
+ name: resolver.response.body(node)
132
+ }) : createNever()
133
+ }), ...groups.map(({ name, params, resolve }) => {
134
+ const required = params.some((param) => param.required);
135
+ return kubb_kit.ast.factory.createProperty({
136
+ name,
137
+ required,
138
+ schema: params.length > 0 ? kubb_kit.ast.factory.createSchema({
139
+ type: "ref",
140
+ name: resolve.call(resolver.param, node, params[0]),
141
+ optional: !required
142
+ }) : createNever()
143
+ });
144
+ })]
145
+ });
146
+ }
147
+ function getStatusCodeNumber(statusCode) {
148
+ const code = Number(statusCode);
149
+ return Number.isNaN(code) ? null : code;
150
+ }
151
+ function isSuccessStatusCode(statusCode) {
152
+ const code = getStatusCodeNumber(statusCode);
153
+ return code !== null && code >= 200 && code < 300;
154
+ }
155
+ function getSuccessResponses(responses) {
156
+ return responses.filter((response) => isSuccessStatusCode(response.statusCode));
157
+ }
158
+ //#endregion
159
+ //#region ../../internals/shared/src/adapter.ts
160
+ /**
161
+ * Narrows the generic `Adapter` from a generator context to the OpenAPI adapter,
162
+ * so OAS-only options (`dateType`, `enums`) and the parsed `document` are typed.
163
+ *
164
+ * Throws when a non-OAS adapter is configured, turning a silently wrong cast into a
165
+ * clear, actionable error at the point of use.
166
+ *
167
+ * @example
168
+ * ```ts
169
+ * const { dateType } = getOasAdapter(ctx.adapter).options
170
+ * ```
171
+ */
172
+ function getOasAdapter(adapter) {
173
+ if (adapter.name !== "oas") throw new Error(`Expected the OpenAPI adapter (adapterOas), but received "${adapter.name}". Configure \`adapter: adapterOas()\` in your Kubb config.`);
174
+ return adapter;
175
+ }
176
+ //#endregion
15
177
  //#region ../../internals/utils/src/casing.ts
16
178
  /**
17
179
  * Shared implementation for camelCase and PascalCase conversion.
@@ -23,61 +185,197 @@ let _kubb_renderer_jsx_jsx_runtime = require("@kubb/renderer-jsx/jsx-runtime");
23
185
  function toCamelOrPascal(text, pascal) {
24
186
  return text.trim().replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/(\d)([a-z])/g, "$1 $2").split(/[\s\-_./\\:]+/).filter(Boolean).map((word, i) => {
25
187
  if (word.length > 1 && word === word.toUpperCase()) return word;
26
- if (i === 0 && !pascal) return word.charAt(0).toLowerCase() + word.slice(1);
27
- return word.charAt(0).toUpperCase() + word.slice(1);
188
+ return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
28
189
  }).join("").replace(/[^a-zA-Z0-9]/g, "");
29
190
  }
30
191
  /**
31
- * Splits `text` on `.` and applies `transformPart` to each segment.
32
- * The last segment receives `isLast = true`, all earlier segments receive `false`.
33
- * Segments are joined with `/` to form a file path.
192
+ * Converts `text` to camelCase.
193
+ *
194
+ * @example Word boundaries
195
+ * `camelCase('hello-world') // 'helloWorld'`
34
196
  *
35
- * Only splits on dots followed by a letter so that version numbers
36
- * embedded in operationIds (e.g. `v2025.0`) are kept intact.
197
+ * @example With a prefix
198
+ * `camelCase('tag', { prefix: 'create' }) // 'createTag'`
37
199
  */
38
- function applyToFileParts(text, transformPart) {
39
- const parts = text.split(/\.(?=[a-zA-Z])/);
40
- return parts.map((part, i) => transformPart(part, i === parts.length - 1)).join("/");
200
+ function camelCase(text, { prefix = "", suffix = "" } = {}) {
201
+ return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
41
202
  }
42
203
  /**
43
- * Converts `text` to camelCase.
44
- * When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
204
+ * Converts `text` to PascalCase.
205
+ *
206
+ * @example Word boundaries
207
+ * `pascalCase('hello-world') // 'HelloWorld'`
208
+ *
209
+ * @example With a suffix
210
+ * `pascalCase('tag', { suffix: 'schema' }) // 'TagSchema'`
211
+ */
212
+ function pascalCase(text, { prefix = "", suffix = "" } = {}) {
213
+ return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
214
+ }
215
+ //#endregion
216
+ //#region ../../internals/utils/src/reserved.ts
217
+ /**
218
+ * JavaScript and Java reserved words.
219
+ * @link https://github.com/jonschlinkert/reserved/blob/master/index.js
220
+ */
221
+ const reservedWords = /* @__PURE__ */ new Set([
222
+ "abstract",
223
+ "arguments",
224
+ "boolean",
225
+ "break",
226
+ "byte",
227
+ "case",
228
+ "catch",
229
+ "char",
230
+ "class",
231
+ "const",
232
+ "continue",
233
+ "debugger",
234
+ "default",
235
+ "delete",
236
+ "do",
237
+ "double",
238
+ "else",
239
+ "enum",
240
+ "eval",
241
+ "export",
242
+ "extends",
243
+ "false",
244
+ "final",
245
+ "finally",
246
+ "float",
247
+ "for",
248
+ "function",
249
+ "goto",
250
+ "if",
251
+ "implements",
252
+ "import",
253
+ "in",
254
+ "instanceof",
255
+ "int",
256
+ "interface",
257
+ "let",
258
+ "long",
259
+ "native",
260
+ "new",
261
+ "null",
262
+ "package",
263
+ "private",
264
+ "protected",
265
+ "public",
266
+ "return",
267
+ "short",
268
+ "static",
269
+ "super",
270
+ "switch",
271
+ "synchronized",
272
+ "this",
273
+ "throw",
274
+ "throws",
275
+ "transient",
276
+ "true",
277
+ "try",
278
+ "typeof",
279
+ "var",
280
+ "void",
281
+ "volatile",
282
+ "while",
283
+ "with",
284
+ "yield",
285
+ "Array",
286
+ "Date",
287
+ "hasOwnProperty",
288
+ "Infinity",
289
+ "isFinite",
290
+ "isNaN",
291
+ "isPrototypeOf",
292
+ "length",
293
+ "Math",
294
+ "name",
295
+ "NaN",
296
+ "Number",
297
+ "Object",
298
+ "prototype",
299
+ "String",
300
+ "toString",
301
+ "undefined",
302
+ "valueOf"
303
+ ]);
304
+ /**
305
+ * Returns `true` when `name` is a syntactically valid JavaScript variable name.
45
306
  *
46
307
  * @example
47
- * camelCase('hello-world') // 'helloWorld'
48
- * camelCase('pet.petId', { isFile: true }) // 'pet/petId'
49
- */
50
- function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) {
51
- if (isFile) return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? {
52
- prefix,
53
- suffix
54
- } : {}));
55
- return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
308
+ * ```ts
309
+ * isValidVarName('status') // true
310
+ * isValidVarName('class') // false (reserved word)
311
+ * isValidVarName('42foo') // false (starts with digit)
312
+ * ```
313
+ */
314
+ function isValidVarName(name) {
315
+ if (!name || reservedWords.has(name)) return false;
316
+ return isIdentifier(name);
56
317
  }
57
318
  /**
58
- * Converts `text` to PascalCase.
59
- * When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.
319
+ * Returns `name` when it's a syntactically valid JavaScript variable name,
320
+ * otherwise prefixes it with `_` so the result is a valid identifier.
321
+ *
322
+ * Useful for sanitizing OpenAPI schema names or operation IDs that start with
323
+ * a digit (e.g. `409`, `504AccountCancel`) before using them as exported
324
+ * variable, type, or function names.
60
325
  *
61
326
  * @example
62
- * pascalCase('hello-world') // 'HelloWorld'
63
- * pascalCase('pet.petId', { isFile: true }) // 'pet/PetId'
64
- */
65
- function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
66
- if (isFile) return applyToFileParts(text, (part, isLast) => isLast ? pascalCase(part, {
67
- prefix,
68
- suffix
69
- }) : camelCase(part));
70
- return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
327
+ * ```ts
328
+ * ensureValidVarName('409') // '_409'
329
+ * ensureValidVarName('504AccountCancel') // '_504AccountCancel'
330
+ * ensureValidVarName('Pet') // 'Pet'
331
+ * ensureValidVarName('class') // '_class'
332
+ * ```
333
+ */
334
+ function ensureValidVarName(name) {
335
+ if (!name || isValidVarName(name)) return name;
336
+ return `_${name}`;
337
+ }
338
+ /**
339
+ * Returns `true` when `name` is syntactically a valid identifier, ignoring reserved words.
340
+ *
341
+ * Reserved words and globals (`class`, `name`, `Date`, …) are valid as bare object-literal keys
342
+ * even though they are not valid variable names, so use this (not {@link isValidVarName}) when
343
+ * deciding whether an object key needs quoting.
344
+ *
345
+ * @example
346
+ * ```ts
347
+ * isIdentifier('name') // true
348
+ * isIdentifier('x-total')// false
349
+ * ```
350
+ */
351
+ function isIdentifier(name) {
352
+ return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
71
353
  }
72
354
  //#endregion
73
- //#region ../../internals/utils/src/string.ts
355
+ //#region ../../internals/utils/src/strings.ts
356
+ /**
357
+ * Wraps a value in single quotes for emitting a single-quoted JavaScript string literal, escaping
358
+ * any backslash or single quote in the content.
359
+ *
360
+ * @example
361
+ * ```ts
362
+ * singleQuote('foo') // "'foo'"
363
+ * singleQuote("o'clock") // "'o\\'clock'"
364
+ * ```
365
+ */
366
+ function singleQuote(value) {
367
+ if (value === void 0 || value === null) return "''";
368
+ return `'${String(value).replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
369
+ }
74
370
  /**
75
371
  * Strips a single matching pair of `"..."`, `'...'`, or `` `...` `` from both ends of `text`.
76
372
  * Returns the string unchanged when no balanced quote pair is found.
77
373
  *
78
374
  * @example
375
+ * ```ts
79
376
  * trimQuotes('"hello"') // 'hello'
80
377
  * trimQuotes('hello') // 'hello'
378
+ * ```
81
379
  */
82
380
  function trimQuotes(text) {
83
381
  if (text.length >= 2) {
@@ -87,43 +385,32 @@ function trimQuotes(text) {
87
385
  }
88
386
  return text;
89
387
  }
90
- //#endregion
91
- //#region ../../internals/utils/src/object.ts
92
388
  /**
93
- * Serializes a primitive value to a JSON string literal, stripping any surrounding quote characters first.
389
+ * Serializes a primitive to a single-quoted string literal, stripping any surrounding quotes first.
94
390
  *
95
- * @example
96
- * stringify('hello') // '"hello"'
97
- * stringify('"hello"') // '"hello"'
98
- */
99
- function stringify(value) {
100
- if (value === void 0 || value === null) return "\"\"";
101
- return JSON.stringify(trimQuotes(value.toString()));
102
- }
103
- /**
104
- * Converts a plain object into a multiline key-value string suitable for embedding in generated code.
105
- * Nested objects are recursively stringified with indentation.
391
+ * Escaping runs through `JSON.stringify`, then the result switches to single quotes so the generated
392
+ * code matches the repo style without a formatter.
106
393
  *
107
394
  * @example
108
- * stringifyObject({ foo: 'bar', nested: { a: 1 } })
109
- * // 'foo: bar,\nnested: {\n a: 1\n }'
395
+ * ```ts
396
+ * stringify('hello') // "'hello'"
397
+ * stringify('"hello"') // "'hello'"
398
+ * ```
110
399
  */
111
- function stringifyObject(value) {
112
- return Object.entries(value).map(([key, val]) => {
113
- if (val !== null && typeof val === "object") return `${key}: {\n ${stringifyObject(val)}\n }`;
114
- return `${key}: ${val}`;
115
- }).filter(Boolean).join(",\n");
400
+ function stringify(value) {
401
+ if (value === void 0 || value === null) return "''";
402
+ return `'${JSON.stringify(trimQuotes(value.toString())).slice(1, -1).replace(/\\"/g, "\"").replace(/'/g, "\\'")}'`;
116
403
  }
117
- //#endregion
118
- //#region ../../internals/utils/src/regexp.ts
119
404
  /**
120
405
  * Converts a pattern string into a `new RegExp(...)` constructor call or a regex literal string.
121
- * Inline flags expressed as `^(?im)` prefixes are extracted and applied to the resulting expression.
406
+ * Inline flags expressed as a `^(?im)` prefix are extracted and applied to the resulting expression.
122
407
  * Pass `null` as the second argument to emit a `/pattern/flags` literal instead.
123
408
  *
124
409
  * @example
125
- * toRegExpString('^(?im)foo') // → 'new RegExp("foo", "im")'
126
- * toRegExpString('^(?im)foo', null) // '/foo/im'
410
+ * ```ts
411
+ * toRegExpString('^(?im)foo') // 'new RegExp("^foo", "im")'
412
+ * toRegExpString('^(?im)foo', null) // '/^foo/im'
413
+ * ```
127
414
  */
128
415
  function toRegExpString(text, func = "RegExp") {
129
416
  const raw = trimQuotes(text);
@@ -136,98 +423,281 @@ function toRegExpString(text, func = "RegExp") {
136
423
  return `new ${func}(${JSON.stringify(source)}${flags ? `, ${JSON.stringify(flags)}` : ""})`;
137
424
  }
138
425
  //#endregion
139
- //#region src/components/Operations.tsx
140
- function Operations({ name, operations }) {
141
- const operationsJSON = operations.reduce((prev, acc) => {
142
- prev[`"${acc.node.operationId}"`] = acc.data;
143
- return prev;
144
- }, {});
145
- const pathsJSON = operations.reduce((prev, acc) => {
146
- prev[`"${acc.node.path}"`] = {
147
- ...prev[`"${acc.node.path}"`] ?? {},
148
- [acc.node.method]: `operations["${acc.node.operationId}"]`
149
- };
150
- return prev;
151
- }, {});
152
- return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx_jsx_runtime.Fragment, { children: [
153
- /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Source, {
154
- name: "OperationSchema",
155
- isExportable: true,
156
- isIndexable: true,
157
- children: /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.Type, {
158
- name: "OperationSchema",
159
- export: true,
160
- children: `{
161
- readonly request: z.ZodTypeAny | undefined;
162
- readonly parameters: {
163
- readonly path: z.ZodTypeAny | undefined;
164
- readonly query: z.ZodTypeAny | undefined;
165
- readonly header: z.ZodTypeAny | undefined;
166
- };
167
- readonly responses: {
168
- readonly [status: number]: z.ZodTypeAny;
169
- readonly default: z.ZodTypeAny;
170
- };
171
- readonly errors: {
172
- readonly [status: number]: z.ZodTypeAny;
173
- };
174
- }`
175
- })
176
- }),
177
- /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Source, {
178
- name: "OperationsMap",
179
- isExportable: true,
180
- isIndexable: true,
181
- children: /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.Type, {
182
- name: "OperationsMap",
183
- export: true,
184
- children: "Record<string, OperationSchema>"
185
- })
186
- }),
187
- /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Source, {
188
- name,
189
- isExportable: true,
190
- isIndexable: true,
191
- children: /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.Const, {
192
- export: true,
193
- name,
194
- asConst: true,
195
- children: `{${stringifyObject(operationsJSON)}}`
196
- })
197
- }),
198
- /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Source, {
199
- name: "paths",
200
- isExportable: true,
201
- isIndexable: true,
202
- children: /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.Const, {
203
- export: true,
204
- name: "paths",
205
- asConst: true,
206
- children: `{${stringifyObject(pathsJSON)}}`
207
- })
208
- })
209
- ] });
426
+ //#region ../../internals/utils/src/codegen.ts
427
+ const INDENT = " ";
428
+ /**
429
+ * Indents every non-empty line of `text` by one indent level, leaving blank lines empty.
430
+ */
431
+ function indentLines(text) {
432
+ if (!text) return "";
433
+ return text.split("\n").map((line) => line.trim() ? `${INDENT}${line}` : "").join("\n");
434
+ }
435
+ /**
436
+ * Renders an object key, quoting it with single quotes only when it is not a valid identifier.
437
+ * Reserved words and globals (`name`, `class`, …) are valid bare keys and stay unquoted.
438
+ *
439
+ * @example
440
+ * ```ts
441
+ * objectKey('name') // 'name'
442
+ * objectKey('x-total') // "'x-total'"
443
+ * ```
444
+ */
445
+ function objectKey(name) {
446
+ return isIdentifier(name) ? name : singleQuote(name);
447
+ }
448
+ /**
449
+ * Assembles a multi-line object literal from already-rendered `entries`, indenting each entry one
450
+ * level and closing the brace at column zero. Entries that are themselves multi-line objects indent
451
+ * cumulatively. Each entry ends with a trailing comma to match the formatter's multi-line style.
452
+ *
453
+ * @example
454
+ * ```ts
455
+ * buildObject(['id: z.number()', 'name: z.string()'])
456
+ * // '{\n id: z.number(),\n name: z.string(),\n}'
457
+ * ```
458
+ */
459
+ function buildObject(entries) {
460
+ if (entries.length === 0) return "{}";
461
+ return `{\n${entries.map((entry) => `${indentLines(entry)},`).join("\n")}\n}`;
462
+ }
463
+ /**
464
+ * Assembles a bracketed list (array by default) from already-rendered `items`. Keeps everything on
465
+ * one line when no item spans multiple lines, and otherwise puts each item on its own line, indented
466
+ * one level with a trailing comma and the closing bracket at column zero. Used for member lists such
467
+ * as `z.union([…])` and `z.array([…])`.
468
+ *
469
+ * @example
470
+ * ```ts
471
+ * buildList(['z.string()', 'z.number()'])
472
+ * // '[z.string(), z.number()]'
473
+ * ```
474
+ */
475
+ function buildList(items, brackets = ["[", "]"]) {
476
+ const [open, close] = brackets;
477
+ if (items.length === 0) return `${open}${close}`;
478
+ if (!items.some((item) => item.includes("\n"))) return `${open}${items.join(", ")}${close}`;
479
+ return `${open}\n${items.map((item) => `${indentLines(item)},`).join("\n")}\n${close}`;
480
+ }
481
+ /**
482
+ * Emits a lazy getter for a circular-ref property position, `get name() { return body }`. The key
483
+ * is quoted only when it is not a valid identifier. Used by the string printers to defer evaluation
484
+ * of a recursive schema until first access.
485
+ *
486
+ * @example
487
+ * ```ts
488
+ * lazyGetter({ name: 'parent', body: 'z.lazy(() => Pet)' })
489
+ * // "get parent() { return z.lazy(() => Pet) }"
490
+ * ```
491
+ */
492
+ function lazyGetter({ name, body }) {
493
+ return `get ${objectKey(name)}() { return ${body} }`;
494
+ }
495
+ //#endregion
496
+ //#region ../../internals/utils/src/fs.ts
497
+ /**
498
+ * Builds a nested file path from a dotted name. Splits on dots that precede a letter
499
+ * (so version numbers embedded in operationIds like `v2025.0` stay intact), camelCases
500
+ * every earlier segment, applies `caseLast` to the final segment, and joins with `/`.
501
+ *
502
+ * Empty segments are dropped before joining. They arise when the name starts with a dot
503
+ * followed by a letter (e.g. `..Schema` splits into `['..', 'Schema']` and `'..'` cases to
504
+ * an empty string). Without this a leading `/` would form, which `path.resolve` reads as an
505
+ * absolute path, letting generated files escape the configured output directory.
506
+ *
507
+ * @example Nested path from a dotted name
508
+ * `toFilePath('pet.petId') // 'pet/petId'`
509
+ *
510
+ * @example PascalCase the final segment
511
+ * `toFilePath('pet.Pet', pascalCase) // 'pet/Pet'`
512
+ *
513
+ * @example Suffix applied to the final segment only
514
+ * `toFilePath('tag.tag', (part) => camelCase(part, { suffix: 'schema' })) // 'tag/tagSchema'`
515
+ */
516
+ function toFilePath(name, caseLast = camelCase) {
517
+ const parts = name.split(/\.(?=[a-zA-Z])/);
518
+ return parts.map((part, i) => i === parts.length - 1 ? caseLast(part) : camelCase(part)).filter(Boolean).join("/");
519
+ }
520
+ //#endregion
521
+ //#region ../../internals/shared/src/resolver.ts
522
+ /**
523
+ * Resolves a single operation parameter name with the
524
+ * `<operationId> <in> <name>` template.
525
+ *
526
+ * @example
527
+ * `operationParamName.call(resolver, node, param) // → 'DeletePetPathPetId'`
528
+ */
529
+ function operationParamName(node, param) {
530
+ return this.name(`${node.operationId} ${param.in} ${param.name}`);
531
+ }
532
+ /**
533
+ * Builds the shared `param` namespace. Spread the result into `createResolver`
534
+ * and override individual methods next to it when a plugin deviates.
535
+ *
536
+ * @example
537
+ * ```ts
538
+ * createResolver<PluginTs>({ param: createOperationParamResolver(), ... })
539
+ * ```
540
+ */
541
+ function createOperationParamResolver() {
542
+ return {
543
+ name: operationParamName,
544
+ path(node) {
545
+ return this.name(`${node.operationId} Path`);
546
+ },
547
+ query(node) {
548
+ return this.name(`${node.operationId} Query`);
549
+ },
550
+ headers(node) {
551
+ return this.name(`${node.operationId} Headers`);
552
+ }
553
+ };
554
+ }
555
+ /**
556
+ * Builds the shared `response` namespace. Spread the result into
557
+ * `createResolver` and add plugin-specific methods (`options`, `error`) next
558
+ * to it.
559
+ *
560
+ * @example
561
+ * ```ts
562
+ * createResolver<PluginTs>({ response: { ...createOperationResponseResolver(), options(node) {...} }, ... })
563
+ * ```
564
+ */
565
+ function createOperationResponseResolver() {
566
+ return {
567
+ status(node, statusCode) {
568
+ return this.name(`${node.operationId} Status ${statusCode}`);
569
+ },
570
+ body(node) {
571
+ return this.name(`${node.operationId} Body`);
572
+ },
573
+ responses(node) {
574
+ return this.name(`${node.operationId} Responses`);
575
+ },
576
+ response(node) {
577
+ return this.name(`${node.operationId} Response`);
578
+ }
579
+ };
580
+ }
581
+ /**
582
+ * Builds a resolver `file` override whose base name runs every path segment
583
+ * through `toFilePath`, casing the final segment with `caseLast`.
584
+ *
585
+ * @example
586
+ * ```ts
587
+ * createResolver<PluginTs>({ file: createCasedFile(pascalCase), ... })
588
+ * ```
589
+ */
590
+ function createCasedFile(caseLast) {
591
+ return { baseName({ name, extname }) {
592
+ return `${toFilePath(name, caseLast)}${extname}`;
593
+ } };
594
+ }
595
+ //#endregion
596
+ //#region ../../internals/shared/src/refs.ts
597
+ /**
598
+ * Collects the resolved target name of every pointer-carrying ref in a schema tree, in
599
+ * first-occurrence order. Use this for name-only checks (e.g. redeclaration detection) where
600
+ * `resolver.imports` would resolve file paths that are then discarded.
601
+ */
602
+ function collectRefNames(schema) {
603
+ return kubb_kit.ast.collectSync(schema, { schema: (node) => {
604
+ const refNode = kubb_kit.ast.narrowSchema(node, "ref");
605
+ if (!refNode?.ref) return null;
606
+ return kubb_kit.ast.resolveRefName(refNode);
607
+ } });
608
+ }
609
+ //#endregion
610
+ //#region ../../internals/shared/src/group.ts
611
+ /**
612
+ * Builds the `group` config a Kubb plugin passes to `ctx.setOptions`, applying the
613
+ * shared default naming so every plugin groups output consistently:
614
+ *
615
+ * - `path` groups use the second path segment (`/pet/findByStatus` → `pet`).
616
+ * - other groups use the camelCased group (`pet store` → `petStore`).
617
+ *
618
+ * A user-provided `group.name` always wins over the default namer, so callers stay in
619
+ * control of their output folders. Returns `null` when grouping is disabled, matching the
620
+ * per-plugin convention.
621
+ *
622
+ * @param group - The user-supplied group option, or `undefined` to disable grouping.
623
+ *
624
+ * @example
625
+ * ```ts
626
+ * createGroupConfig(group) // shared across every plugin
627
+ * ```
628
+ */
629
+ function createGroupConfig(group) {
630
+ if (!group) return null;
631
+ const defaultName = (ctx) => {
632
+ if (group.type === "path") return `${ctx.group.split("/")[1]}`;
633
+ return camelCase(ctx.group);
634
+ };
635
+ return {
636
+ ...group,
637
+ name: group.name ? group.name : defaultName
638
+ };
639
+ }
640
+ //#endregion
641
+ //#region ../../internals/shared/src/schemaTraversal.ts
642
+ /**
643
+ * Maps each property of an object schema to its transformed output. Pairs every result with the
644
+ * original property so the printer keeps full control over modifiers, getters, and key syntax.
645
+ *
646
+ * @example
647
+ * ```ts
648
+ * const entries = mapSchemaProperties(node, (schema) => this.transform(schema))
649
+ * // entries: [{ name: 'id', property, output: 'z.number()' }, ...]
650
+ * ```
651
+ */
652
+ function mapSchemaProperties(node, transform) {
653
+ return node.properties.map((property) => ({
654
+ name: property.name,
655
+ property,
656
+ output: transform(property.schema)
657
+ }));
658
+ }
659
+ /**
660
+ * Maps each member of a union or intersection schema to its transformed output, pairing every
661
+ * result with the original member.
662
+ */
663
+ function mapSchemaMembers(node, transform) {
664
+ return (node.members ?? []).map((schema) => ({
665
+ schema,
666
+ output: transform(schema)
667
+ }));
668
+ }
669
+ /**
670
+ * Maps each item of an array or tuple schema to its transformed output, pairing every result with
671
+ * the original item.
672
+ */
673
+ function mapSchemaItems(node, transform) {
674
+ return (node.items ?? []).map((schema) => ({
675
+ schema,
676
+ output: transform(schema)
677
+ }));
210
678
  }
211
679
  //#endregion
212
680
  //#region src/components/Zod.tsx
213
- function Zod({ name, node, printer, inferTypeName }) {
681
+ function Zod({ name, node, printer, inferTypeName, cyclic }) {
214
682
  const output = printer.print(node);
215
683
  if (!output) return;
216
- return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Source, {
684
+ const needsAnnotation = cyclic && node.type !== "object";
685
+ return /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsxs)(kubb_jsx_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsx)(kubb_jsx.File.Source, {
217
686
  name,
218
687
  isExportable: true,
219
688
  isIndexable: true,
220
- children: /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.Const, {
689
+ children: /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsx)(kubb_jsx.Const, {
221
690
  export: true,
222
691
  name,
692
+ type: needsAnnotation ? "z.ZodType" : void 0,
223
693
  children: output
224
694
  })
225
- }), inferTypeName && /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Source, {
695
+ }), inferTypeName && /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsx)(kubb_jsx.File.Source, {
226
696
  name: inferTypeName,
227
697
  isExportable: true,
228
698
  isIndexable: true,
229
699
  isTypeOnly: true,
230
- children: /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.Type, {
700
+ children: /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsx)(kubb_jsx.Type, {
231
701
  export: true,
232
702
  name: inferTypeName,
233
703
  children: `z.infer<typeof ${name}>`
@@ -240,7 +710,7 @@ function Zod({ name, node, printer, inferTypeName }) {
240
710
  * Import paths that use a namespace import (`import * as z from '...'`).
241
711
  * All other import paths use a named import (`import { z } from '...'`).
242
712
  */
243
- const ZOD_NAMESPACE_IMPORTS = new Set(["zod", "zod/mini"]);
713
+ const ZOD_NAMESPACE_IMPORTS = /* @__PURE__ */ new Set(["zod", "zod/mini"]);
244
714
  //#endregion
245
715
  //#region src/utils.ts
246
716
  /**
@@ -252,34 +722,106 @@ function shouldCoerce(coercion, type) {
252
722
  return !!coercion[type];
253
723
  }
254
724
  /**
255
- * Collects all resolved schema names for an operation's parameters and responses
256
- * into a single lookup object, useful for building imports and type references.
257
- */
258
- function buildSchemaNames(node, { params, resolver }) {
259
- const pathParam = params.find((p) => p.in === "path");
260
- const queryParam = params.find((p) => p.in === "query");
261
- const headerParam = params.find((p) => p.in === "header");
262
- const responses = {};
263
- const errors = {};
264
- for (const res of node.responses) {
265
- const name = resolver.resolveResponseStatusName(node, res.statusCode);
266
- const statusNum = Number(res.statusCode);
267
- if (!Number.isNaN(statusNum)) {
268
- responses[statusNum] = name;
269
- if (statusNum >= 400) errors[statusNum] = name;
725
+ * Registered codecs, checked in order.
726
+ */
727
+ const codecs = [{
728
+ matches(node) {
729
+ return node.type === "date" && node.representation === "date";
730
+ },
731
+ decode(node) {
732
+ return node.format === "date" ? "z.iso.date().transform((value) => new Date(value))" : "z.iso.datetime().transform((value) => new Date(value))";
733
+ },
734
+ encode(node) {
735
+ return node.format === "date" ? "z.date().transform((value) => value.toISOString().slice(0, 10))" : "z.date().transform((value) => value.toISOString())";
736
+ }
737
+ }];
738
+ /**
739
+ * Returns the codec for this node, or `undefined` when the node needs no
740
+ * encode/decode (its wire and runtime types match).
741
+ */
742
+ function getCodec(node) {
743
+ if (!node) return void 0;
744
+ return codecs.find((codec) => codec.matches(node));
745
+ }
746
+ /**
747
+ * Returns `true` when the node itself is encoded/decoded by a codec.
748
+ */
749
+ function hasCodec(node) {
750
+ return getCodec(node) !== void 0;
751
+ }
752
+ /**
753
+ * Returns `true` when the schema transitively contains a codec node —
754
+ * a value whose runtime type differs from its wire type (see {@link hasCodec}),
755
+ * so it must be decoded (response) or encoded (request) at the validation boundary.
756
+ * `$ref`s are followed via their resolved schema; a `seen` set guards cycles.
757
+ */
758
+ function containsCodec(node, seen = /* @__PURE__ */ new Set()) {
759
+ if (!node) return false;
760
+ if (hasCodec(node)) return true;
761
+ if (node.type === "ref") {
762
+ if (!node.ref) return false;
763
+ const refName = (0, kubb_kit.extractRefName)(node.ref);
764
+ if (refName) {
765
+ if (seen.has(refName)) return false;
766
+ seen.add(refName);
270
767
  }
768
+ const resolved = (0, kubb_kit.syncSchemaRef)(node);
769
+ if (resolved.type === "ref") return false;
770
+ return containsCodec(resolved, seen);
271
771
  }
272
- responses["default"] = resolver.resolveResponseName(node);
273
- return {
274
- request: node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : void 0,
275
- parameters: {
276
- path: pathParam ? resolver.resolvePathParamsName(node, pathParam) : void 0,
277
- query: queryParam ? resolver.resolveQueryParamsName(node, queryParam) : void 0,
278
- header: headerParam ? resolver.resolveHeaderParamsName(node, headerParam) : void 0
279
- },
280
- responses,
281
- errors
282
- };
772
+ const children = [];
773
+ if ("properties" in node && node.properties) children.push(...node.properties.map((prop) => prop.schema));
774
+ if ("items" in node && node.items) children.push(...node.items);
775
+ if ("members" in node && node.members) children.push(...node.members);
776
+ if ("additionalProperties" in node && node.additionalProperties && node.additionalProperties !== true) children.push(node.additionalProperties);
777
+ return children.some((child) => containsCodec(child, seen));
778
+ }
779
+ /**
780
+ * Collects the names of `$ref` schemas that transitively contain a codec, so the generator can route
781
+ * them to their input (encode) variant.
782
+ */
783
+ function collectCodecRefNames(node) {
784
+ return kubb_kit.ast.collectSync(node, { schema: (n) => n.type === "ref" && n.ref && containsCodec(n) ? kubb_kit.ast.resolveRefName(n) ?? void 0 : void 0 });
785
+ }
786
+ /**
787
+ * Whether the node is a plain inline object whose shape can be lifted into an `.extend({ … })`
788
+ * argument. A catchall, `patternProperties`, or a nullable/optional wrapper cannot, so those stay
789
+ * on `.and(…)`.
790
+ */
791
+ function isPlainInlineObject(node) {
792
+ return node.type === "object" && !node.nullable && !node.optional && !node.nullish && node.additionalProperties === void 0 && !node.patternProperties;
793
+ }
794
+ /**
795
+ * Whether a node renders as a bare Zod object — one that accepts `.extend(…)` and can be a
796
+ * `z.discriminatedUnion` option. Covers object nodes, `$ref`s that resolve to (or are still
797
+ * unresolved) objects, single-member unions of an object, and object-composable `allOf`.
798
+ *
799
+ * `cyclicSchemas` bounds the recursion: a ref into a cycle renders as `z.lazy(…)`, not an object.
800
+ */
801
+ function isObjectSchemaNode(node, cyclicSchemas) {
802
+ if (node.nullable || node.optional || node.nullish) return false;
803
+ if (node.type === "object") return true;
804
+ if (node.type === "ref") {
805
+ const refName = kubb_kit.ast.resolveRefName(node);
806
+ if (refName && cyclicSchemas?.has(refName)) return false;
807
+ const resolved = (0, kubb_kit.syncSchemaRef)(node);
808
+ return resolved.type === "ref" || isObjectSchemaNode(resolved, cyclicSchemas);
809
+ }
810
+ if (node.type === "union") {
811
+ const members = node.members ?? [];
812
+ return members.length === 1 && isObjectSchemaNode(members[0], cyclicSchemas);
813
+ }
814
+ return isObjectComposableIntersection(node, cyclicSchemas);
815
+ }
816
+ /**
817
+ * Whether an `allOf` is a pure object composition — an object base plus inline object members whose
818
+ * shapes merge with `.extend(…)`. These stay a Zod object instead of a `ZodIntersection`, which
819
+ * `z.discriminatedUnion` rejects.
820
+ */
821
+ function isObjectComposableIntersection(node, cyclicSchemas) {
822
+ if (node.type !== "intersection") return false;
823
+ const [first, ...rest] = node.members ?? [];
824
+ return !!first && isObjectSchemaNode(first, cyclicSchemas) && rest.every(isPlainInlineObject);
283
825
  }
284
826
  /**
285
827
  * Format a default value as a code-level literal.
@@ -291,6 +833,33 @@ function formatDefault(value) {
291
833
  return String(value ?? "");
292
834
  }
293
835
  /**
836
+ * Format a default for `.default(...)` so the literal matches the generated schema's type.
837
+ *
838
+ * An OpenAPI `default` does not always agree with the schema it sits on: a `bigint` field
839
+ * (`format: int64`) carries a `number` default, and a spec may put `default: {}` on an `array`.
840
+ * Emitting those verbatim produces a Zod schema that does not typecheck, so coerce the bigint case
841
+ * to a `BigInt(...)` literal and emit an array literal for arrays (dropping a non-array default).
842
+ * Returns `null` when no `.default(...)` should be emitted. Keys off the schema node's type.
843
+ */
844
+ function defaultLiteral(node, value) {
845
+ if (value === null) return null;
846
+ if (node && kubb_kit.ast.narrowSchema(node, "bigint")) {
847
+ if (typeof value === "bigint") return `BigInt(${value})`;
848
+ if (typeof value === "number" && Number.isInteger(value)) return `BigInt(${value})`;
849
+ return null;
850
+ }
851
+ if (node && kubb_kit.ast.narrowSchema(node, "array")) return Array.isArray(value) ? JSON.stringify(value) : null;
852
+ const enumNode = node ? kubb_kit.ast.narrowSchema(node, "enum") : void 0;
853
+ if (enumNode) {
854
+ const values = enumNode.namedEnumValues?.map((member) => member.value) ?? enumNode.enumValues ?? [];
855
+ if (values.length) {
856
+ const match = values.find((member) => member === value || String(member) === String(value));
857
+ return match != null ? formatLiteral(match) : null;
858
+ }
859
+ }
860
+ return formatDefault(value);
861
+ }
862
+ /**
294
863
  * Format a primitive enum/literal value.
295
864
  * Strings are quoted; numbers and booleans are emitted raw.
296
865
  */
@@ -299,6 +868,47 @@ function formatLiteral(v) {
299
868
  return String(v);
300
869
  }
301
870
  /**
871
+ * Build the Zod schema for a set of literal enum values.
872
+ * `z.enum()` only accepts string members in Zod v4, so a numeric, boolean, or
873
+ * mixed set is emitted as a single `z.literal(…)` or a `z.union([z.literal(…), …])`.
874
+ * An all-string set keeps the more compact `z.enum([…])`.
875
+ */
876
+ function buildEnum(values) {
877
+ if (values.every((v) => typeof v === "string")) return `z.enum([${values.map(formatLiteral).join(", ")}])`;
878
+ const literals = values.map((v) => `z.literal(${formatLiteral(v)})`);
879
+ if (literals.length === 1) return literals[0];
880
+ return `z.union([${literals.join(", ")}])`;
881
+ }
882
+ /**
883
+ * Map a `regexType` to the `func` argument of `toRegExpString`: `'constructor'` emits
884
+ * `new RegExp(...)`, while `'literal'` (the default) emits a regex literal.
885
+ */
886
+ function regexFunc(regexType) {
887
+ return regexType === "constructor" ? "RegExp" : null;
888
+ }
889
+ /**
890
+ * Builds a Zod record key schema that enforces the `patternProperties` regexes a plain
891
+ * `.catchall()` would drop. Several patterns combine into one alternation (`(^a)|(^b)`).
892
+ */
893
+ function patternKeySchema({ patterns, regexType }) {
894
+ return `z.string().regex(${patternKeySource({
895
+ patterns,
896
+ regexType
897
+ })})`;
898
+ }
899
+ /**
900
+ * `zod/mini` variant of {@link patternKeySchema}, wrapping the regex in `.check(z.regex(...))`.
901
+ */
902
+ function patternKeySchemaMini({ patterns, regexType }) {
903
+ return `z.string().check(z.regex(${patternKeySource({
904
+ patterns,
905
+ regexType
906
+ })}))`;
907
+ }
908
+ function patternKeySource({ patterns, regexType }) {
909
+ return toRegExpString(patterns.length === 1 ? patterns[0] : patterns.map((pattern) => `(${pattern})`).join("|"), regexFunc(regexType));
910
+ }
911
+ /**
302
912
  * Build `.min()` / `.max()` / `.gt()` / `.lt()` constraint chains for numbers
303
913
  * using the standard chainable Zod v4 API.
304
914
  */
@@ -315,11 +925,11 @@ function numberConstraints({ min, max, exclusiveMinimum, exclusiveMaximum, multi
315
925
  * Build `.min()` / `.max()` / `.regex()` chains for strings/arrays
316
926
  * using the standard chainable Zod v4 API.
317
927
  */
318
- function lengthConstraints({ min, max, pattern }) {
928
+ function lengthConstraints({ min, max, pattern, regexType }) {
319
929
  return [
320
930
  min !== void 0 ? `.min(${min})` : "",
321
931
  max !== void 0 ? `.max(${max})` : "",
322
- pattern !== void 0 ? `.regex(${toRegExpString(pattern, null)})` : ""
932
+ pattern !== void 0 ? `.regex(${toRegExpString(pattern, regexFunc(regexType))})` : ""
323
933
  ].join("");
324
934
  }
325
935
  /**
@@ -337,52 +947,140 @@ function numberChecksMini({ min, max, exclusiveMinimum, exclusiveMaximum, multip
337
947
  /**
338
948
  * Build `.check(z.minLength(), z.maxLength(), z.regex())` for `zod/mini` length constraints.
339
949
  */
340
- function lengthChecksMini({ min, max, pattern }) {
950
+ function lengthChecksMini({ min, max, pattern, regexType }) {
341
951
  const checks = [];
342
952
  if (min !== void 0) checks.push(`z.minLength(${min})`);
343
953
  if (max !== void 0) checks.push(`z.maxLength(${max})`);
344
- if (pattern !== void 0) checks.push(`z.regex(${toRegExpString(pattern, null)})`);
954
+ if (pattern !== void 0) checks.push(`z.regex(${toRegExpString(pattern, regexFunc(regexType))})`);
345
955
  return checks.length ? `.check(${checks.join(", ")})` : "";
346
956
  }
347
957
  /**
348
- * Apply nullable / optional / nullish modifiers and an optional `.describe()` call
349
- * to a schema value string using the chainable Zod v4 API.
958
+ * Apply nullable / optional / nullish modifiers, an optional `.describe()` call, and an
959
+ * optional `.meta({ examples })` call to a schema value string using the chainable Zod v4 API.
350
960
  */
351
- function applyModifiers({ value, nullable, optional, nullish, defaultValue, description }) {
352
- let result = value;
353
- if (nullish || nullable && optional) result = `${result}.nullish()`;
354
- else if (optional) result = `${result}.optional()`;
355
- else if (nullable) result = `${result}.nullable()`;
356
- if (defaultValue !== void 0) result = `${result}.default(${formatDefault(defaultValue)})`;
357
- if (description) result = `${result}.describe(${stringify(description)})`;
358
- return result;
961
+ function applyModifiers({ value, schema, nullable, optional, nullish, defaultValue, description, examples }) {
962
+ const withModifier = (() => {
963
+ if (nullish || nullable && optional) return `${value}.nullish()`;
964
+ if (optional) return `${value}.optional()`;
965
+ if (nullable) return `${value}.nullable()`;
966
+ return value;
967
+ })();
968
+ const literal = defaultValue !== void 0 ? defaultLiteral(schema, defaultValue) : null;
969
+ const withDefault = literal !== null ? `${withModifier}.default(${literal})` : withModifier;
970
+ const withDescription = description ? `${withDefault}.describe(${stringify(description)})` : withDefault;
971
+ return examples?.length ? `${withDescription}.meta({ examples: [${examples.map(formatDefault).join(", ")}] })` : withDescription;
972
+ }
973
+ function modifierDepth(schema) {
974
+ if (schema.nullish || schema.nullable && schema.optional) return 2;
975
+ if (schema.nullable || schema.optional) return 1;
976
+ return 0;
977
+ }
978
+ /**
979
+ * Build the `.unwrap()` chain to insert before `.omit()` when the schema is a `$ref`.
980
+ *
981
+ * A `$ref` resolves to a named schema variable that already carries its own `.nullable()` /
982
+ * `.optional()` / `.nullish()` / `.default()` wrappers. `.omit()` lives on the inner `ZodObject`,
983
+ * not on those `ZodNullable` / `ZodOptional` / `ZodDefault` wrappers, so the omit has to unwrap
984
+ * down to the object first. This mirrors plugin-ts emitting `Omit<NonNullable<T>, …>`. Inline
985
+ * objects need no unwrap because the printer adds their modifiers after `.omit()`.
986
+ */
987
+ function omitUnwrapChain(node) {
988
+ const ref = kubb_kit.ast.narrowSchema(node, "ref");
989
+ if (!ref) return "";
990
+ const target = ref.schema ?? ref;
991
+ const depth = modifierDepth(target) + (target.default !== void 0 ? 1 : 0);
992
+ return ".unwrap()".repeat(depth);
359
993
  }
360
994
  /**
361
995
  * Apply nullable / optional / nullish modifiers using the functional `zod/mini` API
362
996
  * (`z.nullable()`, `z.optional()`, `z.nullish()`).
363
997
  */
364
- function applyMiniModifiers({ value, nullable, optional, nullish, defaultValue }) {
365
- let result = value;
366
- if (nullish) result = `z.nullish(${result})`;
367
- else {
368
- if (nullable) result = `z.nullable(${result})`;
369
- if (optional) result = `z.optional(${result})`;
370
- }
371
- if (defaultValue !== void 0) result = `z._default(${result}, ${formatDefault(defaultValue)})`;
372
- return result;
998
+ function applyMiniModifiers({ value, schema, nullable, optional, nullish, defaultValue }) {
999
+ const withModifier = (() => {
1000
+ if (nullish) return `z.nullish(${value})`;
1001
+ const withNullable = nullable ? `z.nullable(${value})` : value;
1002
+ return optional ? `z.optional(${withNullable})` : withNullable;
1003
+ })();
1004
+ const literal = defaultValue !== void 0 ? defaultLiteral(schema, defaultValue) : null;
1005
+ return literal !== null ? `z._default(${withModifier}, ${literal})` : withModifier;
1006
+ }
1007
+ /**
1008
+ * Builds an `object` schema node grouping the given parameter nodes.
1009
+ * The `primitive: 'object'` marker ensures the Zod printer emits `z.object(…)` rather than a record.
1010
+ */
1011
+ function buildGroupedParamsSchema({ params, optional }) {
1012
+ return kubb_kit.ast.factory.createSchema({
1013
+ type: "object",
1014
+ optional,
1015
+ primitive: "object",
1016
+ properties: params.map((param) => kubb_kit.ast.factory.createProperty({
1017
+ name: param.name,
1018
+ required: param.required,
1019
+ schema: param.schema
1020
+ }))
1021
+ });
373
1022
  }
374
1023
  //#endregion
375
1024
  //#region src/printers/printerZod.ts
376
- function strictOneOfMember$1(member, node) {
1025
+ function strictOneOfMember$1(member, node, cyclicSchemas) {
377
1026
  if (node.type === "object" && node.additionalProperties === void 0) return `${member}.strict()`;
378
1027
  if (node.type === "ref") {
379
1028
  if (member.startsWith("z.lazy(")) return member;
380
- const schema = _kubb_core.ast.syncSchemaRef(node);
1029
+ const refName = kubb_kit.ast.resolveRefName(node);
1030
+ if (refName && cyclicSchemas?.has(refName)) return member;
1031
+ const schema = (0, kubb_kit.syncSchemaRef)(node);
1032
+ if (schema.nullable || schema.optional || node.nullable || node.optional) return member;
381
1033
  if (schema.type === "object" && (schema.additionalProperties === void 0 || schema.additionalProperties === false)) return `${member}.strict()`;
382
1034
  }
383
1035
  return member;
384
1036
  }
385
1037
  __name(strictOneOfMember$1, "strictOneOfMember");
1038
+ function getMemberConstraint({ member, regexType }) {
1039
+ if (member.primitive === "string") return lengthConstraints({
1040
+ ...kubb_kit.ast.narrowSchema(member, "string") ?? {},
1041
+ regexType
1042
+ }) || void 0;
1043
+ if (member.primitive === "number" || member.primitive === "integer") return numberConstraints(kubb_kit.ast.narrowSchema(member, "number") ?? kubb_kit.ast.narrowSchema(member, "integer") ?? {}) || void 0;
1044
+ if (member.primitive === "array") return lengthConstraints({
1045
+ ...kubb_kit.ast.narrowSchema(member, "array") ?? {},
1046
+ regexType
1047
+ }) || void 0;
1048
+ }
1049
+ /**
1050
+ * Builds the `{ key: value, … }` shape for an object node, shared by the `z.object(...)` and
1051
+ * `.extend(...)` renderings so they stay in lockstep.
1052
+ */
1053
+ function buildZodObjectShape(ctx, node) {
1054
+ const objectNode = kubb_kit.ast.narrowSchema(node, "object");
1055
+ if (!objectNode) return "{}";
1056
+ const isCyclic = (schema) => ctx.options.cyclicSchemas != null && (0, kubb_kit.containsCircularRef)(schema, { circularSchemas: ctx.options.cyclicSchemas });
1057
+ return buildObject(mapSchemaProperties(objectNode, (schema) => {
1058
+ const hasSelfRef = isCyclic(schema);
1059
+ const savedCyclicSchemas = ctx.options.cyclicSchemas;
1060
+ if (hasSelfRef) ctx.options.cyclicSchemas = void 0;
1061
+ const baseOutput = ctx.transform(schema) ?? ctx.transform(kubb_kit.ast.factory.createSchema({ type: "unknown" }));
1062
+ if (hasSelfRef) ctx.options.cyclicSchemas = savedCyclicSchemas;
1063
+ return baseOutput;
1064
+ }).map(({ name: propName, property, output: baseOutput }) => {
1065
+ const { schema } = property;
1066
+ const meta = (0, kubb_kit.syncSchemaRef)(schema);
1067
+ const descriptionToApply = schema.type !== "ref" && meta.type === "ref" ? void 0 : meta.description;
1068
+ const value = applyModifiers({
1069
+ value: baseOutput,
1070
+ schema,
1071
+ nullable: meta.nullable,
1072
+ optional: schema.optional || property.required === false,
1073
+ nullish: schema.nullish,
1074
+ defaultValue: meta.default,
1075
+ description: descriptionToApply,
1076
+ examples: meta.examples
1077
+ });
1078
+ return isCyclic(schema) ? lazyGetter({
1079
+ name: propName,
1080
+ body: value
1081
+ }) : `${objectKey(propName)}: ${value}`;
1082
+ }));
1083
+ }
386
1084
  /**
387
1085
  * Zod v4 printer built with `definePrinter`.
388
1086
  *
@@ -395,7 +1093,8 @@ __name(strictOneOfMember$1, "strictOneOfMember");
395
1093
  * const code = printer.print(stringNode) // "z.string()"
396
1094
  * ```
397
1095
  */
398
- const printerZod = _kubb_core.ast.definePrinter((options) => {
1096
+ const printerZod = kubb_kit.ast.createPrinter((options) => {
1097
+ const cyclicSchemaNames = options.cyclicSchemas;
399
1098
  return {
400
1099
  name: "zod",
401
1100
  options,
@@ -407,7 +1106,10 @@ const printerZod = _kubb_core.ast.definePrinter((options) => {
407
1106
  boolean: () => "z.boolean()",
408
1107
  null: () => "z.null()",
409
1108
  string(node) {
410
- return `${shouldCoerce(this.options.coercion, "strings") ? "z.coerce.string()" : "z.string()"}${lengthConstraints(node)}`;
1109
+ return `${shouldCoerce(this.options.coercion, "strings") ? "z.coerce.string()" : "z.string()"}${lengthConstraints({
1110
+ ...node,
1111
+ regexType: this.options.regexType
1112
+ })}`;
411
1113
  },
412
1114
  number(node) {
413
1115
  return `${shouldCoerce(this.options.coercion, "numbers") ? "z.coerce.number()" : "z.number()"}${numberConstraints(node)}`;
@@ -419,8 +1121,12 @@ const printerZod = _kubb_core.ast.definePrinter((options) => {
419
1121
  return shouldCoerce(this.options.coercion, "numbers") ? "z.coerce.bigint()" : "z.bigint()";
420
1122
  },
421
1123
  date(node) {
422
- if (node.representation === "string") return "z.iso.date()";
423
- return shouldCoerce(this.options.coercion, "dates") ? "z.coerce.date()" : "z.date()";
1124
+ const codec = getCodec(node);
1125
+ if (codec) {
1126
+ if (this.options.direction === "input") return codec.encode(node);
1127
+ return shouldCoerce(this.options.coercion, "dates") ? "z.coerce.date()" : codec.decode(node);
1128
+ }
1129
+ return "z.iso.date()";
424
1130
  },
425
1131
  datetime(node) {
426
1132
  const offset = node.offset || this.options.dateType === "stringOffset";
@@ -434,13 +1140,22 @@ const printerZod = _kubb_core.ast.definePrinter((options) => {
434
1140
  return shouldCoerce(this.options.coercion, "dates") ? "z.coerce.date()" : "z.date()";
435
1141
  },
436
1142
  uuid(node) {
437
- return `${this.options.guidType === "guid" ? "z.guid()" : "z.uuid()"}${lengthConstraints(node)}`;
1143
+ return `${this.options.guidType === "guid" ? "z.guid()" : "z.uuid()"}${lengthConstraints({
1144
+ ...node,
1145
+ regexType: this.options.regexType
1146
+ })}`;
438
1147
  },
439
1148
  email(node) {
440
- return `z.email()${lengthConstraints(node)}`;
1149
+ return `z.email()${lengthConstraints({
1150
+ ...node,
1151
+ regexType: this.options.regexType
1152
+ })}`;
441
1153
  },
442
1154
  url(node) {
443
- return `z.url()${lengthConstraints(node)}`;
1155
+ return `z.url()${lengthConstraints({
1156
+ ...node,
1157
+ regexType: this.options.regexType
1158
+ })}`;
444
1159
  },
445
1160
  ipv4: () => "z.ipv4()",
446
1161
  ipv6: () => "z.ipv6()",
@@ -452,120 +1167,104 @@ const printerZod = _kubb_core.ast.definePrinter((options) => {
452
1167
  if (literals.length === 1) return literals[0];
453
1168
  return `z.union([${literals.join(", ")}])`;
454
1169
  }
455
- return `z.enum([${nonNullValues.map(formatLiteral).join(", ")}])`;
1170
+ return buildEnum(nonNullValues);
456
1171
  },
457
1172
  ref(node) {
458
- if (!node.name) return void 0;
459
- const refName = node.ref ? _kubb_core.ast.extractRefName(node.ref) ?? node.name : node.name;
460
- const resolvedName = node.ref ? this.options.resolver?.default(refName, "function") ?? refName : node.name;
1173
+ if (!node.name) return null;
1174
+ const refName = kubb_kit.ast.resolveRefName(node);
1175
+ if (!refName) return null;
1176
+ const useInputVariant = node.ref != null && this.options.direction === "input" && containsCodec(node);
1177
+ const resolvedName = node.ref ? useInputVariant ? this.options.resolver?.schema.inputName(refName) ?? refName : this.options.resolver?.name(refName) ?? refName : node.name;
461
1178
  if (node.ref && this.options.cyclicSchemas?.has(refName)) return `z.lazy(() => ${resolvedName})`;
462
1179
  return resolvedName;
463
1180
  },
464
1181
  object(node) {
465
- let result = `z.object({\n ${node.properties.map((prop) => {
466
- const { name: propName, schema } = prop;
467
- const meta = _kubb_core.ast.syncSchemaRef(schema);
468
- const isNullable = meta.nullable;
469
- const isOptional = schema.optional;
470
- const isNullish = schema.nullish;
471
- const hasSelfRef = this.options.cyclicSchemas != null && _kubb_core.ast.containsCircularRef(schema, { circularSchemas: this.options.cyclicSchemas });
472
- if (hasSelfRef) this.options.cyclicSchemas = void 0;
473
- const baseOutput = this.transform(schema) ?? this.transform(_kubb_core.ast.createSchema({ type: "unknown" }));
474
- if (hasSelfRef) this.options.cyclicSchemas = options.cyclicSchemas;
475
- const wrappedOutput = this.options.wrapOutput ? this.options.wrapOutput({
476
- output: baseOutput,
477
- schema
478
- }) || baseOutput : baseOutput;
479
- let descriptionToApply = meta.description;
480
- if (schema.type !== "ref" && meta.type === "ref") descriptionToApply = void 0;
481
- const value = applyModifiers({
482
- value: wrappedOutput,
483
- nullable: isNullable,
484
- optional: isOptional,
485
- nullish: isNullish,
486
- defaultValue: meta.default,
487
- description: descriptionToApply
488
- });
489
- if (hasSelfRef) return `get "${propName}"() { return ${value} }`;
490
- return `"${propName}": ${value}`;
491
- }).join(",\n ")}\n })`;
492
- if (node.additionalProperties && node.additionalProperties !== true) {
493
- const catchallType = this.transform(node.additionalProperties);
494
- if (catchallType) result += `.catchall(${catchallType})`;
495
- } else if (node.additionalProperties === true) result += `.catchall(${this.transform(_kubb_core.ast.createSchema({ type: "unknown" }))})`;
496
- else if (node.additionalProperties === false) result += ".strict()";
497
- return result;
1182
+ const entries = node.properties ?? [];
1183
+ const objectBase = `z.object(${buildZodObjectShape(this, node)})`;
1184
+ return (() => {
1185
+ const patterns = node.patternProperties ? Object.entries(node.patternProperties) : [];
1186
+ if (node.additionalProperties && node.additionalProperties !== true) {
1187
+ const catchallType = this.transform(node.additionalProperties);
1188
+ return catchallType ? `${objectBase}.catchall(${catchallType})` : objectBase;
1189
+ }
1190
+ if (node.additionalProperties === true) return `${objectBase}.catchall(${this.transform(kubb_kit.ast.factory.createSchema({ type: "unknown" }))})`;
1191
+ if (node.additionalProperties === false && patterns.length === 0) return `${objectBase}.strict()`;
1192
+ if (patterns.length > 0) {
1193
+ const values = patterns.map(([, valueSchema]) => {
1194
+ const valueType = this.transform(valueSchema) ?? this.transform(kubb_kit.ast.factory.createSchema({ type: "unknown" }));
1195
+ return valueSchema.nullable ? `${valueType}.nullable()` : valueType;
1196
+ });
1197
+ const distinct = [...new Set(values)];
1198
+ const value = distinct.length === 1 ? distinct[0] : `z.union([${distinct.join(", ")}])`;
1199
+ if (entries.length > 0) return `${objectBase}.catchall(${value})`;
1200
+ return `z.record(${patternKeySchema({
1201
+ patterns: patterns.map(([pattern]) => pattern),
1202
+ regexType: this.options.regexType
1203
+ })}, ${value})`;
1204
+ }
1205
+ return objectBase;
1206
+ })();
498
1207
  },
499
1208
  array(node) {
500
- let result = `z.array(${(node.items ?? []).map((item) => this.transform(item)).filter(Boolean).join(", ") || this.transform(_kubb_core.ast.createSchema({ type: "unknown" }))})${lengthConstraints(node)}`;
501
- if (node.unique) result += `.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })`;
502
- return result;
1209
+ const base = `z.array(${mapSchemaItems(node, (item) => this.transform(item)).map(({ output }) => output).filter(Boolean).join(", ") || this.transform(kubb_kit.ast.factory.createSchema({ type: "unknown" }))})${lengthConstraints({
1210
+ ...node,
1211
+ regexType: this.options.regexType
1212
+ })}`;
1213
+ return node.unique ? `${base}.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })` : base;
503
1214
  },
504
1215
  tuple(node) {
505
- return `z.tuple([${(node.items ?? []).map((item) => this.transform(item)).filter(Boolean).join(", ")}])`;
1216
+ return `z.tuple(${buildList(mapSchemaItems(node, (item) => this.transform(item)).map(({ output }) => output).filter(Boolean))})`;
506
1217
  },
507
1218
  union(node) {
508
1219
  const nodeMembers = node.members ?? [];
509
- const members = nodeMembers.map((memberNode) => {
510
- const member = this.transform(memberNode);
511
- return member && node.strategy === "one" ? strictOneOfMember$1(member, memberNode) : member;
512
- }).filter(Boolean);
1220
+ const members = mapSchemaMembers(node, (memberNode) => this.transform(memberNode)).map(({ schema, output }) => output && node.strategy === "one" ? strictOneOfMember$1(output, schema, cyclicSchemaNames) : output).filter(Boolean);
513
1221
  if (members.length === 0) return "";
514
1222
  if (members.length === 1) return members[0];
515
- if (node.discriminatorPropertyName && !nodeMembers.some((m) => m.type === "intersection")) return `z.discriminatedUnion(${stringify(node.discriminatorPropertyName)}, [${members.join(", ")}])`;
516
- return `z.union([${members.join(", ")}])`;
1223
+ const allDiscriminable = nodeMembers.every((m) => isObjectSchemaNode(m, cyclicSchemaNames));
1224
+ if (node.discriminatorPropertyName && allDiscriminable) return `z.discriminatedUnion(${stringify(node.discriminatorPropertyName)}, ${buildList(members)})`;
1225
+ return `z.union(${buildList(members)})`;
517
1226
  },
518
1227
  intersection(node) {
519
1228
  const members = node.members ?? [];
520
1229
  if (members.length === 0) return "";
521
1230
  const [first, ...rest] = members;
522
1231
  if (!first) return "";
523
- let base = this.transform(first);
524
- if (!base) return "";
525
- for (const member of rest) {
526
- if (member.primitive === "string") {
527
- const c = lengthConstraints(_kubb_core.ast.narrowSchema(member, "string") ?? {});
528
- if (c) {
529
- base += c;
530
- continue;
531
- }
532
- } else if (member.primitive === "number" || member.primitive === "integer") {
533
- const c = numberConstraints(_kubb_core.ast.narrowSchema(member, "number") ?? _kubb_core.ast.narrowSchema(member, "integer") ?? {});
534
- if (c) {
535
- base += c;
536
- continue;
537
- }
538
- } else if (member.primitive === "array") {
539
- const c = lengthConstraints(_kubb_core.ast.narrowSchema(member, "array") ?? {});
540
- if (c) {
541
- base += c;
542
- continue;
543
- }
544
- }
1232
+ const firstBase = this.transform(first);
1233
+ if (!firstBase) return "";
1234
+ if (rest.length > 0 && isObjectComposableIntersection(node, cyclicSchemaNames)) return rest.reduce((acc, member) => `${acc}.extend(${buildZodObjectShape(this, member)})`, firstBase);
1235
+ return rest.reduce((acc, member) => {
1236
+ const constraint = getMemberConstraint({
1237
+ member,
1238
+ regexType: this.options.regexType
1239
+ });
1240
+ if (constraint) return acc + constraint;
545
1241
  const transformed = this.transform(member);
546
- if (transformed) base = `${base}.and(${transformed})`;
547
- }
548
- return base;
549
- },
550
- ...options.nodes
1242
+ return transformed ? `${acc}.and(${transformed})` : acc;
1243
+ }, firstBase);
1244
+ }
551
1245
  },
1246
+ overrides: options.nodes,
552
1247
  print(node) {
553
1248
  const { keysToOmit } = this.options;
554
- let base = this.transform(node);
555
- if (!base) return null;
556
- const meta = _kubb_core.ast.syncSchemaRef(node);
557
- if (keysToOmit?.length && meta.primitive === "object" && !(meta.type === "union" && meta.discriminatorPropertyName)) {
558
- const lazyMatch = base.match(/^z\.lazy\(\(\)\s*=>\s*(.+)\)$/);
559
- if (lazyMatch) base = `z.lazy(() => ${lazyMatch[1]}.omit({ ${keysToOmit.map((k) => `"${k}": true`).join(", ")} }))`;
560
- else base = `${base}.omit({ ${keysToOmit.map((k) => `"${k}": true`).join(", ")} })`;
561
- }
1249
+ const transformed = this.transform(node);
1250
+ if (!transformed) return null;
1251
+ const meta = (0, kubb_kit.syncSchemaRef)(node);
562
1252
  return applyModifiers({
563
- value: base,
1253
+ value: (() => {
1254
+ if (!keysToOmit?.length || meta.primitive !== "object" || meta.type === "union" && meta.discriminatorPropertyName) return transformed;
1255
+ const unwrap = omitUnwrapChain(node);
1256
+ const omit = `.omit({ ${keysToOmit.map((k) => `"${k}": true`).join(", ")} })`;
1257
+ const lazyMatch = transformed.match(/^z\.lazy\(\(\)\s*=>\s*(.+)\)$/);
1258
+ if (lazyMatch) return `z.lazy(() => ${lazyMatch[1]}${unwrap}${omit})`;
1259
+ return `${transformed}${unwrap}${omit}`;
1260
+ })(),
1261
+ schema: node,
564
1262
  nullable: meta.nullable,
565
1263
  optional: meta.optional,
566
1264
  nullish: meta.nullish,
567
1265
  defaultValue: meta.default,
568
- description: meta.description
1266
+ description: meta.description,
1267
+ examples: meta.examples
569
1268
  });
570
1269
  }
571
1270
  };
@@ -576,8 +1275,51 @@ function strictOneOfMember(member, node) {
576
1275
  if (node.type === "object" && (node.additionalProperties === void 0 || node.additionalProperties === false)) return member.replace(/^z\.object\(/, "z.strictObject(");
577
1276
  return member;
578
1277
  }
1278
+ function getMemberConstraintMini({ member, regexType }) {
1279
+ if (member.primitive === "string") return lengthChecksMini({
1280
+ ...kubb_kit.ast.narrowSchema(member, "string") ?? {},
1281
+ regexType
1282
+ }) || void 0;
1283
+ if (member.primitive === "number" || member.primitive === "integer") return numberChecksMini(kubb_kit.ast.narrowSchema(member, "number") ?? kubb_kit.ast.narrowSchema(member, "integer") ?? {}) || void 0;
1284
+ if (member.primitive === "array") return lengthChecksMini({
1285
+ ...kubb_kit.ast.narrowSchema(member, "array") ?? {},
1286
+ regexType
1287
+ }) || void 0;
1288
+ }
579
1289
  /**
580
- * Zod v4 **Mini** printer built with `definePrinter`.
1290
+ * Builds the `{ key: value, … }` shape for an object node with the functional `zod/mini` modifiers,
1291
+ * shared by the `z.object(...)` and `z.extend(...)` renderings so they stay in lockstep.
1292
+ */
1293
+ function buildZodMiniObjectShape(ctx, node) {
1294
+ const objectNode = kubb_kit.ast.narrowSchema(node, "object");
1295
+ if (!objectNode) return "{}";
1296
+ const isCyclic = (schema) => ctx.options.cyclicSchemas != null && (0, kubb_kit.containsCircularRef)(schema, { circularSchemas: ctx.options.cyclicSchemas });
1297
+ return buildObject(mapSchemaProperties(objectNode, (schema) => {
1298
+ const hasSelfRef = isCyclic(schema);
1299
+ const savedCyclicSchemas = ctx.options.cyclicSchemas;
1300
+ if (hasSelfRef) ctx.options.cyclicSchemas = void 0;
1301
+ const baseOutput = ctx.transform(schema) ?? ctx.transform(kubb_kit.ast.factory.createSchema({ type: "unknown" }));
1302
+ if (hasSelfRef) ctx.options.cyclicSchemas = savedCyclicSchemas;
1303
+ return baseOutput;
1304
+ }).map(({ name: propName, property, output: baseOutput }) => {
1305
+ const { schema } = property;
1306
+ const meta = (0, kubb_kit.syncSchemaRef)(schema);
1307
+ const value = applyMiniModifiers({
1308
+ value: baseOutput,
1309
+ schema,
1310
+ nullable: meta.nullable,
1311
+ optional: schema.optional || property.required === false,
1312
+ nullish: schema.nullish,
1313
+ defaultValue: meta.default
1314
+ });
1315
+ return isCyclic(schema) ? lazyGetter({
1316
+ name: propName,
1317
+ body: value
1318
+ }) : `${objectKey(propName)}: ${value}`;
1319
+ }));
1320
+ }
1321
+ /**
1322
+ * Zod v4 Mini printer built with `definePrinter`.
581
1323
  *
582
1324
  * Converts a `SchemaNode` AST into a Zod v4 code string using the functional API
583
1325
  * (`z.optional(z.string())`) for improved tree-shaking. See {@link printerZod} for the chainable API.
@@ -588,7 +1330,8 @@ function strictOneOfMember(member, node) {
588
1330
  * const code = printer.print(optionalStringNode) // "z.optional(z.string())"
589
1331
  * ```
590
1332
  */
591
- const printerZodMini = _kubb_core.ast.definePrinter((options) => {
1333
+ const printerZodMini = kubb_kit.ast.createPrinter((options) => {
1334
+ const cyclicSchemaNames = options.cyclicSchemas;
592
1335
  return {
593
1336
  name: "zod-mini",
594
1337
  options,
@@ -600,7 +1343,10 @@ const printerZodMini = _kubb_core.ast.definePrinter((options) => {
600
1343
  boolean: () => "z.boolean()",
601
1344
  null: () => "z.null()",
602
1345
  string(node) {
603
- return `z.string()${lengthChecksMini(node)}`;
1346
+ return `z.string()${lengthChecksMini({
1347
+ ...node,
1348
+ regexType: this.options.regexType
1349
+ })}`;
604
1350
  },
605
1351
  number(node) {
606
1352
  return `z.number()${numberChecksMini(node)}`;
@@ -623,13 +1369,22 @@ const printerZodMini = _kubb_core.ast.definePrinter((options) => {
623
1369
  return "z.date()";
624
1370
  },
625
1371
  uuid(node) {
626
- return `${this.options.guidType === "guid" ? "z.guid()" : "z.uuid()"}${lengthChecksMini(node)}`;
1372
+ return `${this.options.guidType === "guid" ? "z.guid()" : "z.uuid()"}${lengthChecksMini({
1373
+ ...node,
1374
+ regexType: this.options.regexType
1375
+ })}`;
627
1376
  },
628
1377
  email(node) {
629
- return `z.email()${lengthChecksMini(node)}`;
1378
+ return `z.email()${lengthChecksMini({
1379
+ ...node,
1380
+ regexType: this.options.regexType
1381
+ })}`;
630
1382
  },
631
1383
  url(node) {
632
- return `z.url()${lengthChecksMini(node)}`;
1384
+ return `z.url()${lengthChecksMini({
1385
+ ...node,
1386
+ regexType: this.options.regexType
1387
+ })}`;
633
1388
  },
634
1389
  ipv4: () => "z.ipv4()",
635
1390
  ipv6: () => "z.ipv6()",
@@ -641,105 +1396,95 @@ const printerZodMini = _kubb_core.ast.definePrinter((options) => {
641
1396
  if (literals.length === 1) return literals[0];
642
1397
  return `z.union([${literals.join(", ")}])`;
643
1398
  }
644
- return `z.enum([${nonNullValues.map(formatLiteral).join(", ")}])`;
1399
+ return buildEnum(nonNullValues);
645
1400
  },
646
1401
  ref(node) {
647
- if (!node.name) return void 0;
648
- const refName = node.ref ? _kubb_core.ast.extractRefName(node.ref) ?? node.name : node.name;
649
- const resolvedName = node.ref ? this.options.resolver?.default(refName, "function") ?? refName : node.name;
1402
+ if (!node.name) return null;
1403
+ const refName = kubb_kit.ast.resolveRefName(node);
1404
+ if (!refName) return null;
1405
+ const resolvedName = node.ref ? this.options.resolver?.name(refName) ?? refName : node.name;
650
1406
  if (node.ref && this.options.cyclicSchemas?.has(refName)) return `z.lazy(() => ${resolvedName})`;
651
1407
  return resolvedName;
652
1408
  },
653
1409
  object(node) {
654
- return `z.object({\n ${node.properties.map((prop) => {
655
- const { name: propName, schema } = prop;
656
- const meta = _kubb_core.ast.syncSchemaRef(schema);
657
- const isNullable = meta.nullable;
658
- const isOptional = schema.optional;
659
- const isNullish = schema.nullish;
660
- const hasSelfRef = this.options.cyclicSchemas != null && _kubb_core.ast.containsCircularRef(schema, { circularSchemas: this.options.cyclicSchemas });
661
- if (hasSelfRef) this.options.cyclicSchemas = void 0;
662
- const baseOutput = this.transform(schema) ?? this.transform(_kubb_core.ast.createSchema({ type: "unknown" }));
663
- if (hasSelfRef) this.options.cyclicSchemas = options.cyclicSchemas;
664
- const value = applyMiniModifiers({
665
- value: this.options.wrapOutput ? this.options.wrapOutput({
666
- output: baseOutput,
667
- schema
668
- }) || baseOutput : baseOutput,
669
- nullable: isNullable,
670
- optional: isOptional,
671
- nullish: isNullish,
672
- defaultValue: meta.default
1410
+ const entries = node.properties ?? [];
1411
+ const objectBase = `z.object(${buildZodMiniObjectShape(this, node)})`;
1412
+ const patterns = node.patternProperties ? Object.entries(node.patternProperties) : [];
1413
+ if (node.additionalProperties && node.additionalProperties !== true) {
1414
+ const catchallType = this.transform(node.additionalProperties);
1415
+ return catchallType ? `z.catchall(${objectBase}, ${catchallType})` : objectBase;
1416
+ }
1417
+ if (node.additionalProperties === true) return `z.catchall(${objectBase}, ${this.transform(kubb_kit.ast.factory.createSchema({ type: "unknown" }))})`;
1418
+ if (node.additionalProperties === false && patterns.length === 0) return objectBase.replace(/^z\.object\(/, "z.strictObject(");
1419
+ if (patterns.length > 0) {
1420
+ const values = patterns.map(([, valueSchema]) => {
1421
+ const valueType = this.transform(valueSchema) ?? this.transform(kubb_kit.ast.factory.createSchema({ type: "unknown" }));
1422
+ return valueSchema.nullable ? `z.nullable(${valueType})` : valueType;
673
1423
  });
674
- if (hasSelfRef) return `get "${propName}"() { return ${value} }`;
675
- return `"${propName}": ${value}`;
676
- }).join(",\n ")}\n })`;
1424
+ const distinct = [...new Set(values)];
1425
+ const value = distinct.length === 1 ? distinct[0] : `z.union([${distinct.join(", ")}])`;
1426
+ if (entries.length > 0) return `z.catchall(${objectBase}, ${value})`;
1427
+ return `z.record(${patternKeySchemaMini({
1428
+ patterns: patterns.map(([pattern]) => pattern),
1429
+ regexType: this.options.regexType
1430
+ })}, ${value})`;
1431
+ }
1432
+ return objectBase;
677
1433
  },
678
1434
  array(node) {
679
- let result = `z.array(${(node.items ?? []).map((item) => this.transform(item)).filter(Boolean).join(", ") || this.transform(_kubb_core.ast.createSchema({ type: "unknown" }))})${lengthChecksMini(node)}`;
680
- if (node.unique) result += `.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })`;
681
- return result;
1435
+ const base = `z.array(${mapSchemaItems(node, (item) => this.transform(item)).map(({ output }) => output).filter(Boolean).join(", ") || this.transform(kubb_kit.ast.factory.createSchema({ type: "unknown" }))})${lengthChecksMini({
1436
+ ...node,
1437
+ regexType: this.options.regexType
1438
+ })}`;
1439
+ return node.unique ? `${base}.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })` : base;
682
1440
  },
683
1441
  tuple(node) {
684
- return `z.tuple([${(node.items ?? []).map((item) => this.transform(item)).filter(Boolean).join(", ")}])`;
1442
+ return `z.tuple(${buildList(mapSchemaItems(node, (item) => this.transform(item)).map(({ output }) => output).filter(Boolean))})`;
685
1443
  },
686
1444
  union(node) {
687
1445
  const nodeMembers = node.members ?? [];
688
- const members = nodeMembers.map((memberNode) => {
689
- const member = this.transform(memberNode);
690
- return member && node.strategy === "one" ? strictOneOfMember(member, memberNode) : member;
691
- }).filter(Boolean);
1446
+ const members = mapSchemaMembers(node, (memberNode) => this.transform(memberNode)).map(({ schema, output }) => output && node.strategy === "one" ? strictOneOfMember(output, schema) : output).filter(Boolean);
692
1447
  if (members.length === 0) return "";
693
1448
  if (members.length === 1) return members[0];
694
- if (node.discriminatorPropertyName && !nodeMembers.some((m) => m.type === "intersection")) return `z.discriminatedUnion(${stringify(node.discriminatorPropertyName)}, [${members.join(", ")}])`;
695
- return `z.union([${members.join(", ")}])`;
1449
+ const allDiscriminable = nodeMembers.every((m) => isObjectSchemaNode(m, cyclicSchemaNames));
1450
+ if (node.discriminatorPropertyName && allDiscriminable) return `z.discriminatedUnion(${stringify(node.discriminatorPropertyName)}, ${buildList(members)})`;
1451
+ return `z.union(${buildList(members)})`;
696
1452
  },
697
1453
  intersection(node) {
698
1454
  const members = node.members ?? [];
699
1455
  if (members.length === 0) return "";
700
1456
  const [first, ...rest] = members;
701
1457
  if (!first) return "";
702
- let base = this.transform(first);
703
- if (!base) return "";
704
- for (const member of rest) {
705
- if (member.primitive === "string") {
706
- const c = lengthChecksMini(_kubb_core.ast.narrowSchema(member, "string") ?? {});
707
- if (c) {
708
- base += c;
709
- continue;
710
- }
711
- } else if (member.primitive === "number" || member.primitive === "integer") {
712
- const c = numberChecksMini(_kubb_core.ast.narrowSchema(member, "number") ?? _kubb_core.ast.narrowSchema(member, "integer") ?? {});
713
- if (c) {
714
- base += c;
715
- continue;
716
- }
717
- } else if (member.primitive === "array") {
718
- const c = lengthChecksMini(_kubb_core.ast.narrowSchema(member, "array") ?? {});
719
- if (c) {
720
- base += c;
721
- continue;
722
- }
723
- }
1458
+ const firstBase = this.transform(first);
1459
+ if (!firstBase) return "";
1460
+ if (rest.length > 0 && isObjectComposableIntersection(node, cyclicSchemaNames)) return rest.reduce((acc, member) => `z.extend(${acc}, ${buildZodMiniObjectShape(this, member)})`, firstBase);
1461
+ return rest.reduce((acc, member) => {
1462
+ const constraint = getMemberConstraintMini({
1463
+ member,
1464
+ regexType: this.options.regexType
1465
+ });
1466
+ if (constraint) return acc + constraint;
724
1467
  const transformed = this.transform(member);
725
- if (transformed) base = `z.intersection(${base}, ${transformed})`;
726
- }
727
- return base;
728
- },
729
- ...options.nodes
1468
+ return transformed ? `z.intersection(${acc}, ${transformed})` : acc;
1469
+ }, firstBase);
1470
+ }
730
1471
  },
1472
+ overrides: options.nodes,
731
1473
  print(node) {
732
1474
  const { keysToOmit } = this.options;
733
- let base = this.transform(node);
734
- if (!base) return null;
735
- const meta = _kubb_core.ast.syncSchemaRef(node);
736
- if (keysToOmit?.length && meta.primitive === "object" && !(meta.type === "union" && meta.discriminatorPropertyName)) {
737
- const lazyMatch = base.match(/^z\.lazy\(\(\)\s*=>\s*(.+)\)$/);
738
- if (lazyMatch) base = `z.lazy(() => ${lazyMatch[1]}.omit({ ${keysToOmit.map((k) => `"${k}": true`).join(", ")} }))`;
739
- else base = `${base}.omit({ ${keysToOmit.map((k) => `"${k}": true`).join(", ")} })`;
740
- }
1475
+ const transformed = this.transform(node);
1476
+ if (!transformed) return null;
1477
+ const meta = (0, kubb_kit.syncSchemaRef)(node);
741
1478
  return applyMiniModifiers({
742
- value: base,
1479
+ value: (() => {
1480
+ if (!keysToOmit?.length || meta.primitive !== "object" || meta.type === "union" && meta.discriminatorPropertyName) return transformed;
1481
+ const unwrap = omitUnwrapChain(node);
1482
+ const omit = `.omit({ ${keysToOmit.map((k) => `"${k}": true`).join(", ")} })`;
1483
+ const lazyMatch = transformed.match(/^z\.lazy\(\(\)\s*=>\s*(.+)\)$/);
1484
+ if (lazyMatch) return `z.lazy(() => ${lazyMatch[1]}${unwrap}${omit})`;
1485
+ return `${transformed}${unwrap}${omit}`;
1486
+ })(),
1487
+ schema: node,
743
1488
  nullable: meta.nullable,
744
1489
  optional: meta.optional,
745
1490
  nullish: meta.nullish,
@@ -750,137 +1495,234 @@ const printerZodMini = _kubb_core.ast.definePrinter((options) => {
750
1495
  });
751
1496
  //#endregion
752
1497
  //#region src/generators/zodGenerator.tsx
753
- const zodGenerator = (0, _kubb_core.defineGenerator)({
1498
+ const zodPrinterCache = /* @__PURE__ */ new WeakMap();
1499
+ const zodMiniPrinterCache = /* @__PURE__ */ new WeakMap();
1500
+ /**
1501
+ * Returns the cached `output`/`input` direction printers for a resolver, building them on
1502
+ * first use. The `input` printer encodes `Date → string` for request bodies, and `output` decodes
1503
+ * `string → Date` for responses. Schemas without `dateType: 'date'` fields print identically.
1504
+ */
1505
+ function getStdPrinters(resolver, params) {
1506
+ const cached = zodPrinterCache.get(resolver);
1507
+ if (cached && cached.coercion === params.coercion && cached.guidType === params.guidType && cached.regexType === params.regexType && cached.dateType === params.dateType && cached.nodes === params.nodes) return {
1508
+ output: cached.output,
1509
+ input: cached.input
1510
+ };
1511
+ const output = printerZod({
1512
+ ...params,
1513
+ resolver,
1514
+ direction: "output"
1515
+ });
1516
+ const input = printerZod({
1517
+ ...params,
1518
+ resolver,
1519
+ direction: "input"
1520
+ });
1521
+ zodPrinterCache.set(resolver, {
1522
+ output,
1523
+ input,
1524
+ coercion: params.coercion,
1525
+ guidType: params.guidType,
1526
+ regexType: params.regexType,
1527
+ dateType: params.dateType,
1528
+ nodes: params.nodes
1529
+ });
1530
+ return {
1531
+ output,
1532
+ input
1533
+ };
1534
+ }
1535
+ function getMiniPrinter(resolver, params) {
1536
+ const cached = zodMiniPrinterCache.get(resolver);
1537
+ if (cached && cached.guidType === params.guidType && cached.regexType === params.regexType && cached.nodes === params.nodes) return cached.printer;
1538
+ const p = printerZodMini({
1539
+ ...params,
1540
+ resolver
1541
+ });
1542
+ zodMiniPrinterCache.set(resolver, {
1543
+ printer: p,
1544
+ guidType: params.guidType,
1545
+ regexType: params.regexType,
1546
+ nodes: params.nodes
1547
+ });
1548
+ return p;
1549
+ }
1550
+ /**
1551
+ * Built-in generator for `@kubb/plugin-zod`. Emits one Zod schema per
1552
+ * schema in the spec plus per-operation request/response/parameter schemas.
1553
+ * When `mini: true`, schemas use the Zod Mini functional API instead of
1554
+ * chainable methods.
1555
+ */
1556
+ const zodGenerator = (0, kubb_kit.defineGenerator)({
754
1557
  name: "zod",
755
- renderer: _kubb_renderer_jsx.jsxRenderer,
1558
+ renderer: kubb_jsx.jsxRenderer,
756
1559
  schema(node, ctx) {
757
1560
  const { adapter, config, resolver, root } = ctx;
758
- const { output, coercion, guidType, mini, wrapOutput, inferred, importPath, group, printer } = ctx.options;
759
- const dateType = adapter.options.dateType;
1561
+ const { output, coercion, guidType, regexType, mini, inferred, importPath, group, printer } = ctx.options;
1562
+ const dateType = getOasAdapter(adapter).options.dateType;
760
1563
  if (!node.name) return;
761
- const mode = ctx.getMode(output);
762
1564
  const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath);
763
- const imports = adapter.getImports(node, (schemaName) => ({
764
- name: resolver.resolveSchemaName(schemaName),
765
- path: resolver.resolveFile({
1565
+ const cyclicSchemas = new Set(ctx.meta.circularNames);
1566
+ const hasCodec = !mini && containsCodec(node);
1567
+ const codecRefNames = new Set(hasCodec ? collectCodecRefNames(node) : []);
1568
+ const importEntries = resolver.imports({
1569
+ node,
1570
+ root,
1571
+ output,
1572
+ group: group ?? void 0
1573
+ });
1574
+ const inputImportEntries = hasCodec ? [...codecRefNames].map((schemaName) => ({
1575
+ name: [resolver.schema.inputName(schemaName)],
1576
+ path: resolver.file({
766
1577
  name: schemaName,
767
- extname: ".ts"
768
- }, {
1578
+ extname: ".ts",
769
1579
  root,
770
1580
  output,
771
- group
1581
+ group: group ?? void 0
772
1582
  }).path
773
- }));
1583
+ })) : [];
1584
+ const seenImports = /* @__PURE__ */ new Set();
1585
+ const imports = [...importEntries, ...inputImportEntries].filter((imp) => {
1586
+ const key = `${Array.isArray(imp.name) ? imp.name.join(",") : imp.name}|${imp.path}`;
1587
+ if (seenImports.has(key)) return false;
1588
+ seenImports.add(key);
1589
+ return true;
1590
+ });
774
1591
  const meta = {
775
- name: resolver.resolveSchemaName(node.name),
776
- file: resolver.resolveFile({
1592
+ name: resolver.name(node.name),
1593
+ file: resolver.file({
777
1594
  name: node.name,
778
- extname: ".ts"
779
- }, {
1595
+ extname: ".ts",
780
1596
  root,
781
1597
  output,
782
- group
1598
+ group: group ?? void 0
783
1599
  })
784
1600
  };
785
- const inferTypeName = inferred ? resolver.resolveSchemaTypeName(node.name) : void 0;
786
- const cyclicSchemas = adapter.inputNode ? _kubb_core.ast.findCircularSchemas(adapter.inputNode.schemas) : void 0;
787
- const schemaPrinter = mini ? printerZodMini({
788
- guidType,
789
- wrapOutput,
790
- resolver,
791
- cyclicSchemas,
792
- nodes: printer?.nodes
793
- }) : printerZod({
1601
+ const inferTypeName = inferred ? resolver.schema.typeName(node.name) : null;
1602
+ const stdPrinters = mini ? null : getStdPrinters(resolver, {
794
1603
  coercion,
795
1604
  guidType,
1605
+ regexType,
796
1606
  dateType,
797
- wrapOutput,
798
- resolver,
799
1607
  cyclicSchemas,
800
1608
  nodes: printer?.nodes
801
1609
  });
802
- return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx.File, {
1610
+ const schemaPrinter = mini ? getMiniPrinter(resolver, {
1611
+ guidType,
1612
+ regexType,
1613
+ cyclicSchemas,
1614
+ nodes: printer?.nodes
1615
+ }) : stdPrinters.output;
1616
+ return /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsxs)(kubb_jsx.File, {
803
1617
  baseName: meta.file.baseName,
804
1618
  path: meta.file.path,
805
1619
  meta: meta.file.meta,
806
- banner: resolver.resolveBanner(adapter.inputNode, {
1620
+ banner: resolver.default.banner(ctx.meta, {
807
1621
  output,
808
- config
1622
+ config,
1623
+ file: {
1624
+ path: meta.file.path,
1625
+ baseName: meta.file.baseName
1626
+ }
809
1627
  }),
810
- footer: resolver.resolveFooter(adapter.inputNode, {
1628
+ footer: resolver.default.footer(ctx.meta, {
811
1629
  output,
812
- config
1630
+ config,
1631
+ file: {
1632
+ path: meta.file.path,
1633
+ baseName: meta.file.baseName
1634
+ }
813
1635
  }),
814
1636
  children: [
815
- /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
1637
+ /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsx)(kubb_jsx.File.Import, {
816
1638
  name: isZodImport ? "z" : ["z"],
817
1639
  path: importPath,
818
1640
  isNameSpace: isZodImport
819
1641
  }),
820
- mode === "split" && imports.map((imp) => /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
1642
+ imports.map((imp) => /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsx)(kubb_jsx.File.Import, {
821
1643
  root: meta.file.path,
822
1644
  path: imp.path,
823
1645
  name: imp.name
824
- }, [node.name, imp.path].join("-"))),
825
- /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(Zod, {
1646
+ }, [
1647
+ node.name,
1648
+ imp.path,
1649
+ imp.name
1650
+ ].join("-"))),
1651
+ /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsx)(Zod, {
826
1652
  name: meta.name,
827
1653
  node,
828
1654
  printer: schemaPrinter,
829
- inferTypeName
1655
+ inferTypeName,
1656
+ cyclic: cyclicSchemas.has(node.name)
1657
+ }),
1658
+ hasCodec && stdPrinters && /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsx)(Zod, {
1659
+ name: resolver.schema.inputName(node.name),
1660
+ node,
1661
+ printer: stdPrinters.input,
1662
+ inferTypeName: inferred ? resolver.schema.inputTypeName(node.name) : null,
1663
+ cyclic: cyclicSchemas.has(node.name)
830
1664
  })
831
1665
  ]
832
1666
  });
833
1667
  },
834
1668
  operation(node, ctx) {
1669
+ if (!kubb_kit.ast.isHttpOperationNode(node)) return null;
835
1670
  const { adapter, config, resolver, root } = ctx;
836
- const { output, coercion, guidType, mini, wrapOutput, inferred, importPath, group, paramsCasing, printer } = ctx.options;
837
- const dateType = adapter.options.dateType;
838
- const mode = ctx.getMode(output);
1671
+ const { output, coercion, guidType, regexType, mini, inferred, importPath, group, printer } = ctx.options;
1672
+ const dateType = getOasAdapter(adapter).options.dateType;
839
1673
  const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath);
840
- const params = _kubb_core.ast.caseParams(node.parameters, paramsCasing);
841
- const meta = { file: resolver.resolveFile({
1674
+ const meta = { file: resolver.file({
842
1675
  name: node.operationId,
843
1676
  extname: ".ts",
844
1677
  tag: node.tags[0] ?? "default",
845
- path: node.path
846
- }, {
1678
+ path: node.path,
847
1679
  root,
848
1680
  output,
849
- group
1681
+ group: group ?? void 0
850
1682
  }) };
851
- const cyclicSchemas = adapter.inputNode ? _kubb_core.ast.findCircularSchemas(adapter.inputNode.schemas) : void 0;
852
- function renderSchemaEntry({ schema, name, keysToOmit }) {
1683
+ const cyclicSchemas = new Set(ctx.meta.circularNames);
1684
+ function renderSchemaEntry({ schema, name, keysToOmit, direction = "output" }) {
853
1685
  if (!schema) return null;
854
- const inferTypeName = inferred ? resolver.resolveTypeName(name) : void 0;
855
- const imports = adapter.getImports(schema, (schemaName) => ({
856
- name: resolver.resolveSchemaName(schemaName),
857
- path: resolver.resolveFile({
858
- name: schemaName,
859
- extname: ".ts"
860
- }, {
861
- root,
862
- output,
863
- group
864
- }).path
865
- }));
866
- const schemaPrinter = mini ? printerZodMini({
1686
+ const inferTypeName = inferred ? resolver.schema.type(name) : null;
1687
+ const codecRefNames = direction === "input" && !mini ? new Set(collectCodecRefNames(schema)) : null;
1688
+ const imports = resolver.imports({
1689
+ node: schema,
1690
+ root,
1691
+ output,
1692
+ group: group ?? void 0,
1693
+ name: (schemaName) => codecRefNames?.has(schemaName) ? resolver.schema.inputName(schemaName) : resolver.name(schemaName)
1694
+ });
1695
+ const schemaPrinter = mini ? keysToOmit?.length ? printerZodMini({
867
1696
  guidType,
868
- wrapOutput,
1697
+ regexType,
869
1698
  resolver,
870
1699
  keysToOmit,
871
1700
  cyclicSchemas,
872
1701
  nodes: printer?.nodes
873
- }) : printerZod({
1702
+ }) : getMiniPrinter(resolver, {
1703
+ guidType,
1704
+ regexType,
1705
+ cyclicSchemas,
1706
+ nodes: printer?.nodes
1707
+ }) : keysToOmit?.length ? printerZod({
874
1708
  coercion,
875
1709
  guidType,
1710
+ regexType,
876
1711
  dateType,
877
- wrapOutput,
878
1712
  resolver,
879
1713
  keysToOmit,
880
1714
  cyclicSchemas,
1715
+ nodes: printer?.nodes,
1716
+ direction
1717
+ }) : getStdPrinters(resolver, {
1718
+ coercion,
1719
+ guidType,
1720
+ regexType,
1721
+ dateType,
1722
+ cyclicSchemas,
881
1723
  nodes: printer?.nodes
882
- });
883
- return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx_jsx_runtime.Fragment, { children: [mode === "split" && imports.map((imp) => /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
1724
+ })[direction];
1725
+ return /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsxs)(kubb_jsx_jsx_runtime.Fragment, { children: [imports.map((imp) => /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsx)(kubb_jsx.File.Import, {
884
1726
  root: meta.file.path,
885
1727
  path: imp.path,
886
1728
  name: imp.name
@@ -888,63 +1730,147 @@ const zodGenerator = (0, _kubb_core.defineGenerator)({
888
1730
  name,
889
1731
  imp.path,
890
1732
  imp.name
891
- ].join("-"))), /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(Zod, {
1733
+ ].join("-"))), /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsx)(Zod, {
892
1734
  name,
893
1735
  node: schema,
894
1736
  printer: schemaPrinter,
895
- inferTypeName
1737
+ inferTypeName,
1738
+ cyclic: false
896
1739
  })] });
897
1740
  }
898
- const paramSchemas = params.map((param) => renderSchemaEntry({
899
- schema: param.schema,
900
- name: resolver.resolveParamName(node, param)
901
- }));
902
- const responseSchemas = node.responses.map((res) => renderSchemaEntry({
903
- schema: res.schema,
904
- name: resolver.resolveResponseStatusName(node, res.statusCode),
905
- keysToOmit: res.keysToOmit
906
- }));
907
- const responsesWithSchema = node.responses.filter((res) => res.schema);
908
- const responseUnionSchema = responsesWithSchema.length > 0 ? (() => {
909
- const responseUnionName = resolver.resolveResponseName(node);
910
- if (new Set(responsesWithSchema.flatMap((res) => res.schema ? adapter.getImports(res.schema, (schemaName) => ({
911
- name: resolver.resolveSchemaName(schemaName),
912
- path: ""
913
- })).flatMap((imp) => Array.isArray(imp.name) ? imp.name : [imp.name]) : [])).has(responseUnionName)) return null;
914
- const members = responsesWithSchema.map((res) => _kubb_core.ast.createSchema({
1741
+ function buildContentTypeVariants(entries, baseName, decorate, direction) {
1742
+ const variants = resolveContentTypeVariants(entries, baseName);
1743
+ const unionSchema = kubb_kit.ast.factory.createSchema({
1744
+ type: "union",
1745
+ members: variants.map((variant) => kubb_kit.ast.factory.createSchema({
1746
+ type: "ref",
1747
+ name: variant.name
1748
+ }))
1749
+ });
1750
+ return /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsxs)(kubb_jsx_jsx_runtime.Fragment, { children: [variants.map((variant) => renderSchemaEntry({
1751
+ schema: decorate ? decorate(variant.schema) : variant.schema,
1752
+ name: variant.name,
1753
+ keysToOmit: variant.keysToOmit,
1754
+ direction
1755
+ })), renderSchemaEntry({
1756
+ schema: unionSchema,
1757
+ name: baseName,
1758
+ direction
1759
+ })] });
1760
+ }
1761
+ function buildResponseUnion({ responses, name, fallbackUnknown }) {
1762
+ if (new Set(responses.flatMap((res) => (res.content ?? []).flatMap((entry) => entry.schema ? collectRefNames(entry.schema).map((refName) => resolver.name(refName)) : []))).has(name)) return null;
1763
+ const members = responses.map((res) => kubb_kit.ast.factory.createSchema({
915
1764
  type: "ref",
916
- name: resolver.resolveResponseStatusName(node, res.statusCode)
1765
+ name: resolver.response.status(node, res.statusCode)
917
1766
  }));
1767
+ if (fallbackUnknown && members.length === 0) return renderSchemaEntry({
1768
+ schema: kubb_kit.ast.factory.createSchema({ type: "unknown" }),
1769
+ name
1770
+ });
918
1771
  return renderSchemaEntry({
919
- schema: members.length === 1 ? members[0] : _kubb_core.ast.createSchema({
1772
+ schema: members.length === 1 ? members[0] : kubb_kit.ast.factory.createSchema({
920
1773
  type: "union",
921
1774
  members
922
1775
  }),
923
- name: responseUnionName
1776
+ name
924
1777
  });
925
- })() : null;
926
- const requestSchema = node.requestBody?.content?.[0]?.schema ? renderSchemaEntry({
927
- schema: {
928
- ...node.requestBody.content[0].schema,
929
- description: node.requestBody.description ?? node.requestBody.content[0].schema.description
1778
+ }
1779
+ const paramSchemas = node.parameters.map((param) => renderSchemaEntry({
1780
+ schema: param.schema,
1781
+ name: resolver.param.name(node, param),
1782
+ direction: "input"
1783
+ }));
1784
+ const responseSchemas = node.responses.map((res) => {
1785
+ const variants = (res.content ?? []).filter((entry) => entry.schema);
1786
+ if (variants.length > 1) return buildContentTypeVariants(res.content, resolver.response.status(node, res.statusCode));
1787
+ const primary = variants[0] ?? res.content?.[0];
1788
+ return renderSchemaEntry({
1789
+ schema: primary?.schema ?? null,
1790
+ name: resolver.response.status(node, res.statusCode),
1791
+ keysToOmit: primary?.keysToOmit
1792
+ });
1793
+ });
1794
+ const responsesWithSchema = node.responses.filter((res) => res.content?.some((entry) => entry.schema));
1795
+ const successResponsesWithSchema = getSuccessResponses(responsesWithSchema);
1796
+ const responseUnionSchema = responsesWithSchema.length > 0 ? buildResponseUnion({
1797
+ responses: successResponsesWithSchema,
1798
+ name: resolver.response.response(node),
1799
+ fallbackUnknown: true
1800
+ }) : null;
1801
+ const errorResponsesWithSchema = responsesWithSchema.filter((res) => !isSuccessStatusCode(res.statusCode));
1802
+ const errorUnionSchema = errorResponsesWithSchema.length > 0 ? buildResponseUnion({
1803
+ responses: errorResponsesWithSchema,
1804
+ name: resolver.response.error(node),
1805
+ fallbackUnknown: false
1806
+ }) : null;
1807
+ const requestBodyContent = node.requestBody?.content ?? [];
1808
+ const requestSchema = (() => {
1809
+ if (requestBodyContent.length === 0) return null;
1810
+ if (requestBodyContent.length === 1) {
1811
+ const entry = requestBodyContent[0];
1812
+ if (!entry.schema) return null;
1813
+ return renderSchemaEntry({
1814
+ schema: {
1815
+ ...entry.schema,
1816
+ description: node.requestBody.description ?? entry.schema.description
1817
+ },
1818
+ name: resolver.response.body(node),
1819
+ keysToOmit: entry.keysToOmit,
1820
+ direction: "input"
1821
+ });
1822
+ }
1823
+ return buildContentTypeVariants(requestBodyContent, resolver.response.body(node), (schema) => ({
1824
+ ...schema,
1825
+ description: node.requestBody.description ?? schema.description
1826
+ }), "input");
1827
+ })();
1828
+ const { path, query, header } = getOperationParameters(node);
1829
+ const paramGroupSchemas = inferred ? [
1830
+ {
1831
+ kind: "path",
1832
+ params: path
930
1833
  },
931
- name: resolver.resolveDataName(node),
932
- keysToOmit: node.requestBody.content[0].keysToOmit
1834
+ {
1835
+ kind: "query",
1836
+ params: query
1837
+ },
1838
+ {
1839
+ kind: "headers",
1840
+ params: header
1841
+ }
1842
+ ].filter(({ params }) => params.length > 0).map(({ kind, params }) => renderSchemaEntry({
1843
+ schema: buildGroupedParamsSchema({ params }),
1844
+ name: resolver.param[kind](node, params[0]),
1845
+ direction: "input"
1846
+ })) : [];
1847
+ const optionsSchema = inferred ? renderSchemaEntry({
1848
+ schema: buildOptionsSchema(node, resolver),
1849
+ name: resolver.name(`${node.operationId} Options`),
1850
+ direction: "input"
933
1851
  }) : null;
934
- return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx.File, {
1852
+ return /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsxs)(kubb_jsx.File, {
935
1853
  baseName: meta.file.baseName,
936
1854
  path: meta.file.path,
937
1855
  meta: meta.file.meta,
938
- banner: resolver.resolveBanner(adapter.inputNode, {
1856
+ banner: resolver.default.banner(ctx.meta, {
939
1857
  output,
940
- config
1858
+ config,
1859
+ file: {
1860
+ path: meta.file.path,
1861
+ baseName: meta.file.baseName
1862
+ }
941
1863
  }),
942
- footer: resolver.resolveFooter(adapter.inputNode, {
1864
+ footer: resolver.default.footer(ctx.meta, {
943
1865
  output,
944
- config
1866
+ config,
1867
+ file: {
1868
+ path: meta.file.path,
1869
+ baseName: meta.file.baseName
1870
+ }
945
1871
  }),
946
1872
  children: [
947
- /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
1873
+ /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsx)(kubb_jsx.File.Import, {
948
1874
  name: isZodImport ? "z" : ["z"],
949
1875
  path: importPath,
950
1876
  isNameSpace: isZodImport
@@ -952,78 +1878,10 @@ const zodGenerator = (0, _kubb_core.defineGenerator)({
952
1878
  paramSchemas,
953
1879
  responseSchemas,
954
1880
  responseUnionSchema,
955
- requestSchema
956
- ]
957
- });
958
- },
959
- operations(nodes, ctx) {
960
- const { adapter, config, resolver, root } = ctx;
961
- const { output, importPath, group, operations, paramsCasing } = ctx.options;
962
- if (!operations) return;
963
- const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath);
964
- const meta = { file: resolver.resolveFile({
965
- name: "operations",
966
- extname: ".ts"
967
- }, {
968
- root,
969
- output,
970
- group
971
- }) };
972
- const transformedOperations = nodes.map((node) => {
973
- return {
974
- node,
975
- data: buildSchemaNames(node, {
976
- params: _kubb_core.ast.caseParams(node.parameters, paramsCasing),
977
- resolver
978
- })
979
- };
980
- });
981
- const imports = transformedOperations.flatMap(({ node, data }) => {
982
- const names = [
983
- data.request,
984
- ...Object.values(data.responses),
985
- ...Object.values(data.parameters)
986
- ].filter(Boolean);
987
- const opFile = resolver.resolveFile({
988
- name: node.operationId,
989
- extname: ".ts",
990
- tag: node.tags[0] ?? "default",
991
- path: node.path
992
- }, {
993
- root,
994
- output,
995
- group
996
- });
997
- return names.map((name) => /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
998
- name: [name],
999
- root: meta.file.path,
1000
- path: opFile.path
1001
- }, [name, opFile.path].join("-")));
1002
- });
1003
- return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx.File, {
1004
- baseName: meta.file.baseName,
1005
- path: meta.file.path,
1006
- meta: meta.file.meta,
1007
- banner: resolver.resolveBanner(adapter.inputNode, {
1008
- output,
1009
- config
1010
- }),
1011
- footer: resolver.resolveFooter(adapter.inputNode, {
1012
- output,
1013
- config
1014
- }),
1015
- children: [
1016
- /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Import, {
1017
- isTypeOnly: true,
1018
- name: isZodImport ? "z" : ["z"],
1019
- path: importPath,
1020
- isNameSpace: isZodImport
1021
- }),
1022
- imports,
1023
- /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(Operations, {
1024
- name: "operations",
1025
- operations: transformedOperations
1026
- })
1881
+ errorUnionSchema,
1882
+ requestSchema,
1883
+ paramGroupSchemas,
1884
+ optionsSchema
1027
1885
  ]
1028
1886
  });
1029
1887
  }
@@ -1031,92 +1889,90 @@ const zodGenerator = (0, _kubb_core.defineGenerator)({
1031
1889
  //#endregion
1032
1890
  //#region src/resolvers/resolverZod.ts
1033
1891
  /**
1034
- * Naming convention resolver for Zod plugin.
1892
+ * Default resolver used by `@kubb/plugin-zod`. Decides the names and file
1893
+ * paths for every generated Zod schema. Schemas use camelCase with a
1894
+ * `Schema` suffix (`listPetsSchema`); their inferred types use PascalCase
1895
+ * with a `SchemaType` suffix (`PetSchemaType`), so the value and the type
1896
+ * never share an identifier even when the schema name is all-uppercase.
1035
1897
  *
1036
- * Provides default naming helpers using camelCase with a `Schema` suffix for schemas.
1898
+ * @example Resolve schema and type names
1899
+ * ```ts
1900
+ * import { resolverZod } from '@kubb/plugin-zod'
1037
1901
  *
1038
- * @example
1039
- * `resolverZod.default('list pets', 'function') // 'listPetsSchema'`
1902
+ * resolverZod.name('list pets') // 'listPetsSchema'
1903
+ * resolverZod.schema.typeName('pet') // 'PetSchemaType'
1904
+ * ```
1040
1905
  */
1041
- const resolverZod = (0, _kubb_core.defineResolver)(() => {
1042
- return {
1043
- name: "default",
1044
- pluginName: "plugin-zod",
1045
- default(name, type) {
1046
- return camelCase(name, {
1047
- isFile: type === "file",
1048
- suffix: type ? "schema" : void 0
1049
- });
1050
- },
1051
- resolveSchemaName(name) {
1052
- return camelCase(name, { suffix: "schema" });
1053
- },
1054
- resolveSchemaTypeName(name) {
1055
- return pascalCase(name, { suffix: "schema" });
1056
- },
1057
- resolveTypeName(name) {
1058
- return pascalCase(name);
1059
- },
1060
- resolvePathName(name, type) {
1061
- return this.default(name, type);
1062
- },
1063
- resolveParamName(node, param) {
1064
- return this.resolveSchemaName(`${node.operationId} ${param.in} ${param.name}`);
1065
- },
1066
- resolveResponseStatusName(node, statusCode) {
1067
- return this.resolveSchemaName(`${node.operationId} Status ${statusCode}`);
1068
- },
1069
- resolveDataName(node) {
1070
- return this.resolveSchemaName(`${node.operationId} Data`);
1071
- },
1072
- resolveResponsesName(node) {
1073
- return this.resolveSchemaName(`${node.operationId} Responses`);
1906
+ const resolverZod = (0, kubb_kit.createResolver)({
1907
+ pluginName: "plugin-zod",
1908
+ name(name) {
1909
+ return ensureValidVarName(camelCase(name, { suffix: "schema" }));
1910
+ },
1911
+ file: createCasedFile((part) => camelCase(part, { suffix: "schema" })),
1912
+ schema: {
1913
+ typeName(name) {
1914
+ return ensureValidVarName(pascalCase(name, { suffix: "schema type" }));
1074
1915
  },
1075
- resolveResponseName(node) {
1076
- return this.resolveSchemaName(`${node.operationId} Response`);
1916
+ type(name) {
1917
+ return ensureValidVarName(pascalCase(name, { suffix: "type" }));
1077
1918
  },
1078
- resolvePathParamsName(node, param) {
1079
- return this.resolveParamName(node, param);
1919
+ inputName(name) {
1920
+ return this.name(`${name} input`);
1080
1921
  },
1081
- resolveQueryParamsName(node, param) {
1082
- return this.resolveParamName(node, param);
1922
+ inputTypeName(name) {
1923
+ return this.schema.typeName(`${name} input`);
1924
+ }
1925
+ },
1926
+ param: createOperationParamResolver(),
1927
+ response: {
1928
+ ...createOperationResponseResolver(),
1929
+ error(node) {
1930
+ return this.name(`${node.operationId} Error`);
1083
1931
  },
1084
- resolveHeaderParamsName(node, param) {
1085
- return this.resolveParamName(node, param);
1932
+ options(node) {
1933
+ return this.schema.type(this.name(`${node.operationId} Options`));
1086
1934
  }
1087
- };
1935
+ }
1088
1936
  });
1089
1937
  //#endregion
1090
1938
  //#region src/plugin.ts
1091
1939
  /**
1092
- * Canonical plugin name for `@kubb/plugin-zod`, used in driver lookups and warnings.
1940
+ * Canonical plugin name for `@kubb/plugin-zod`. Used for driver lookups and
1941
+ * cross-plugin dependency references.
1093
1942
  */
1094
1943
  const pluginZodName = "plugin-zod";
1095
1944
  /**
1096
- * Generates Zod validation schemas from an OpenAPI specification.
1097
- * Walks schemas and operations, delegates to generators, and writes barrel files
1098
- * based on the configured `barrelType`.
1945
+ * Generates Zod v4 schemas from an OpenAPI spec. Use them to validate API
1946
+ * responses at runtime, build form schemas, or feed back into router libraries
1947
+ * that consume Zod (tRPC, Hono, Elysia). Pair with `@kubb/plugin-axios` or
1948
+ * `@kubb/plugin-fetch` and set the client's `validator: 'zod'` to validate every response
1949
+ * automatically.
1099
1950
  *
1100
- * @example Zod schema generator
1951
+ * @example
1101
1952
  * ```ts
1102
- * import pluginZod from '@kubb/plugin-zod'
1953
+ * import { defineConfig } from 'kubb/config'
1954
+ * import { pluginTs } from '@kubb/plugin-ts'
1955
+ * import { pluginZod } from '@kubb/plugin-zod'
1956
+ *
1103
1957
  * export default defineConfig({
1104
- * plugins: [pluginZod({ output: { path: 'zod' } })]
1958
+ * input: './petStore.yaml',
1959
+ * output: { path: './src/gen' },
1960
+ * plugins: [
1961
+ * pluginTs(),
1962
+ * pluginZod({
1963
+ * output: { path: './zod' },
1964
+ * inferred: true,
1965
+ * }),
1966
+ * ],
1105
1967
  * })
1106
1968
  * ```
1107
1969
  */
1108
- const pluginZod = (0, _kubb_core.definePlugin)((options) => {
1970
+ const pluginZod = (0, kubb_kit.definePlugin)((options) => {
1109
1971
  const { output = {
1110
1972
  path: "zod",
1111
- barrelType: "named"
1112
- }, group, exclude = [], include, override = [], typed = false, operations = false, mini = false, guidType = "uuid", importPath = mini ? "zod/mini" : "zod", coercion = false, inferred = false, wrapOutput = void 0, paramsCasing, printer, resolver: userResolver, transformer: userTransformer, generators: userGenerators = [] } = options;
1113
- const groupConfig = group ? {
1114
- ...group,
1115
- name: (ctx) => {
1116
- if (group.type === "path") return `${ctx.group.split("/")[1]}`;
1117
- return `${camelCase(ctx.group)}Controller`;
1118
- }
1119
- } : void 0;
1973
+ barrel: { type: "named" }
1974
+ }, group, exclude = [], include, override = [], mini = false, guidType = "uuid", regexType = "literal", importPath = mini ? "zod/mini" : "zod", coercion = false, inferred = false, printer, resolver: userResolver, macros: userMacros } = options;
1975
+ const groupConfig = createGroupConfig(group);
1120
1976
  return {
1121
1977
  name: pluginZodName,
1122
1978
  options,
@@ -1127,24 +1983,17 @@ const pluginZod = (0, _kubb_core.definePlugin)((options) => {
1127
1983
  include,
1128
1984
  override,
1129
1985
  group: groupConfig,
1130
- typed,
1131
1986
  importPath,
1132
1987
  coercion,
1133
- operations,
1134
1988
  inferred,
1135
1989
  guidType,
1990
+ regexType,
1136
1991
  mini,
1137
- wrapOutput,
1138
- paramsCasing,
1139
1992
  printer
1140
1993
  });
1141
- ctx.setResolver(userResolver ? {
1142
- ...resolverZod,
1143
- ...userResolver
1144
- } : resolverZod);
1145
- if (userTransformer) ctx.setTransformer(userTransformer);
1994
+ ctx.setResolver(userResolver ? kubb_kit.Resolver.merge(resolverZod, userResolver) : resolverZod);
1995
+ if (userMacros?.length) ctx.setMacros(userMacros);
1146
1996
  ctx.addGenerator(zodGenerator);
1147
- for (const gen of userGenerators) ctx.addGenerator(gen);
1148
1997
  } }
1149
1998
  };
1150
1999
  });