@kubb/plugin-zod 5.0.0-alpha.3 → 5.0.0-alpha.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/index.cjs +1619 -100
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.ts +418 -4
  4. package/dist/index.js +1614 -100
  5. package/dist/index.js.map +1 -1
  6. package/package.json +6 -34
  7. package/src/components/Operations.tsx +22 -15
  8. package/src/components/Zod.tsx +20 -119
  9. package/src/constants.ts +5 -0
  10. package/src/generators/zodGenerator.tsx +129 -159
  11. package/src/generators/zodGeneratorLegacy.tsx +365 -0
  12. package/src/index.ts +12 -1
  13. package/src/plugin.ts +102 -148
  14. package/src/presets.ts +30 -0
  15. package/src/printers/printerZod.ts +298 -0
  16. package/src/printers/printerZodMini.ts +273 -0
  17. package/src/resolvers/resolverZod.ts +61 -0
  18. package/src/resolvers/resolverZodLegacy.ts +60 -0
  19. package/src/types.ts +172 -93
  20. package/src/utils.ts +248 -0
  21. package/dist/components-B7zUFnAm.cjs +0 -890
  22. package/dist/components-B7zUFnAm.cjs.map +0 -1
  23. package/dist/components-eECfXVou.js +0 -842
  24. package/dist/components-eECfXVou.js.map +0 -1
  25. package/dist/components.cjs +0 -4
  26. package/dist/components.d.ts +0 -56
  27. package/dist/components.js +0 -2
  28. package/dist/generators-CRKtFRi1.js +0 -290
  29. package/dist/generators-CRKtFRi1.js.map +0 -1
  30. package/dist/generators-CzSLRVqQ.cjs +0 -301
  31. package/dist/generators-CzSLRVqQ.cjs.map +0 -1
  32. package/dist/generators.cjs +0 -4
  33. package/dist/generators.d.ts +0 -503
  34. package/dist/generators.js +0 -2
  35. package/dist/templates/ToZod.source.cjs +0 -7
  36. package/dist/templates/ToZod.source.cjs.map +0 -1
  37. package/dist/templates/ToZod.source.d.ts +0 -7
  38. package/dist/templates/ToZod.source.js +0 -6
  39. package/dist/templates/ToZod.source.js.map +0 -1
  40. package/dist/types-D0wsPC6Y.d.ts +0 -172
  41. package/src/components/index.ts +0 -2
  42. package/src/generators/index.ts +0 -2
  43. package/src/generators/operationsGenerator.tsx +0 -50
  44. package/src/parser.ts +0 -909
  45. package/src/templates/ToZod.source.ts +0 -4
  46. package/templates/ToZod.ts +0 -61
package/src/presets.ts ADDED
@@ -0,0 +1,30 @@
1
+ import { definePresets } from '@kubb/core'
2
+ import { zodGenerator } from './generators/zodGenerator.tsx'
3
+ import { zodGeneratorLegacy } from './generators/zodGeneratorLegacy.tsx'
4
+ import { printerZod } from './printers/printerZod.ts'
5
+ import { resolverZod } from './resolvers/resolverZod.ts'
6
+ import { resolverZodLegacy } from './resolvers/resolverZodLegacy.ts'
7
+ import type { ResolverZod } from './types.ts'
8
+
9
+ /**
10
+ * Built-in preset registry for `@kubb/plugin-zod`.
11
+ *
12
+ * - `default` — uses `resolverZod` and `zodGenerator` (current naming conventions).
13
+ * - `kubbV4` — uses `resolverZodLegacy` and `zodGeneratorLegacy` (Kubb v4 naming conventions).
14
+ *
15
+ * Note: `printerZodMini` is selected at runtime by the generator when `mini: true` is set.
16
+ */
17
+ export const presets = definePresets<ResolverZod>({
18
+ default: {
19
+ name: 'default',
20
+ resolver: resolverZod,
21
+ generators: [zodGenerator],
22
+ printer: printerZod,
23
+ },
24
+ kubbV4: {
25
+ name: 'kubbV4',
26
+ resolver: resolverZodLegacy,
27
+ generators: [zodGeneratorLegacy],
28
+ printer: printerZod,
29
+ },
30
+ })
@@ -0,0 +1,298 @@
1
+ import { stringify } from '@internals/utils'
2
+ import { createSchema, extractRefName, narrowSchema, syncSchemaRef } from '@kubb/ast'
3
+ import type { PrinterFactoryOptions, PrinterPartial } from '@kubb/core'
4
+ import { definePrinter } from '@kubb/core'
5
+ import type { PluginZod, ResolverZod } from '../types.ts'
6
+ import { applyModifiers, containsSelfRef, formatLiteral, lengthConstraints, numberConstraints, shouldCoerce } from '../utils.ts'
7
+
8
+ /**
9
+ * Partial map of node-type overrides for the Zod printer.
10
+ *
11
+ * Each key is a `SchemaType` string (e.g. `'date'`, `'string'`). The function
12
+ * replaces the built-in handler for that node type. Use `this.transform` to
13
+ * recurse into nested schema nodes, and `this.options` to read printer options.
14
+ *
15
+ * @example Override the `date` handler
16
+ * ```ts
17
+ * pluginZod({
18
+ * printer: {
19
+ * nodes: {
20
+ * date(node) {
21
+ * return 'z.string().date()'
22
+ * },
23
+ * },
24
+ * },
25
+ * })
26
+ * ```
27
+ */
28
+ export type PrinterZodNodes = PrinterPartial<string, PrinterZodOptions>
29
+
30
+ export type PrinterZodOptions = {
31
+ coercion?: PluginZod['resolvedOptions']['coercion']
32
+ guidType?: PluginZod['resolvedOptions']['guidType']
33
+ wrapOutput?: PluginZod['resolvedOptions']['wrapOutput']
34
+ resolver?: ResolverZod
35
+ schemaName?: string
36
+ /**
37
+ * Property keys to exclude from the generated object schema via `.omit({ key: true })`.
38
+ */
39
+ keysToOmit?: Array<string>
40
+ /**
41
+ * Partial map of node-type overrides. Each entry replaces the built-in handler for that node type.
42
+ */
43
+ nodes?: PrinterZodNodes
44
+ }
45
+
46
+ export type PrinterZodFactory = PrinterFactoryOptions<'zod', PrinterZodOptions, string, string>
47
+
48
+ /**
49
+ * Zod v4 printer built with `definePrinter`.
50
+ *
51
+ * Converts a `SchemaNode` AST into a **standard** Zod v4 code string
52
+ * using the chainable method API (`.optional()`, `.nullable()`, etc.).
53
+ *
54
+ * For the `zod/mini` functional API, see {@link printerZodMini}.
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * const printer = printerZod({ coercion: false })
59
+ * const code = printer.print(stringSchemaNode) // "z.string()"
60
+ * ```
61
+ */
62
+ export const printerZod = definePrinter<PrinterZodFactory>((options) => {
63
+ return {
64
+ name: 'zod',
65
+ options,
66
+ nodes: {
67
+ any: () => 'z.any()',
68
+ unknown: () => 'z.unknown()',
69
+ void: () => 'z.void()',
70
+ never: () => 'z.never()',
71
+ boolean: () => 'z.boolean()',
72
+ null: () => 'z.null()',
73
+ string(node) {
74
+ const base = shouldCoerce(this.options.coercion, 'strings') ? 'z.coerce.string()' : 'z.string()'
75
+
76
+ return `${base}${lengthConstraints(node)}`
77
+ },
78
+ number(node) {
79
+ const base = shouldCoerce(this.options.coercion, 'numbers') ? 'z.coerce.number()' : 'z.number()'
80
+
81
+ return `${base}${numberConstraints(node)}`
82
+ },
83
+ integer(node) {
84
+ const base = shouldCoerce(this.options.coercion, 'numbers') ? 'z.coerce.number().int()' : 'z.int()'
85
+
86
+ return `${base}${numberConstraints(node)}`
87
+ },
88
+ bigint() {
89
+ return shouldCoerce(this.options.coercion, 'numbers') ? 'z.coerce.bigint()' : 'z.bigint()'
90
+ },
91
+ date(node) {
92
+ if (node.representation === 'string') {
93
+ return 'z.iso.date()'
94
+ }
95
+
96
+ return shouldCoerce(this.options.coercion, 'dates') ? 'z.coerce.date()' : 'z.date()'
97
+ },
98
+ datetime(node) {
99
+ if (node.offset) return 'z.iso.datetime({ offset: true })'
100
+ if (node.local) return 'z.iso.datetime({ local: true })'
101
+
102
+ return 'z.iso.datetime()'
103
+ },
104
+ time(node) {
105
+ if (node.representation === 'string') {
106
+ return 'z.iso.time()'
107
+ }
108
+
109
+ return shouldCoerce(this.options.coercion, 'dates') ? 'z.coerce.date()' : 'z.date()'
110
+ },
111
+ uuid(node) {
112
+ const base = this.options.guidType === 'guid' ? 'z.guid()' : 'z.uuid()'
113
+
114
+ return `${base}${lengthConstraints(node)}`
115
+ },
116
+ email(node) {
117
+ return `z.email()${lengthConstraints(node)}`
118
+ },
119
+ url(node) {
120
+ return `z.url()${lengthConstraints(node)}`
121
+ },
122
+ ipv4: () => 'z.ipv4()',
123
+ ipv6: () => 'z.ipv6()',
124
+ blob: () => 'z.instanceof(File)',
125
+ enum(node) {
126
+ const values = node.namedEnumValues?.map((v) => v.value) ?? node.enumValues ?? []
127
+ const nonNullValues = values.filter((v): v is string | number | boolean => v !== null)
128
+
129
+ // asConst-style enum: use z.union([z.literal(…), …])
130
+ if (node.namedEnumValues?.length) {
131
+ const literals = nonNullValues.map((v) => `z.literal(${formatLiteral(v)})`)
132
+
133
+ if (literals.length === 1) return literals[0]!
134
+ return `z.union([${literals.join(', ')}])`
135
+ }
136
+
137
+ // Regular enum: use z.enum([…])
138
+ return `z.enum([${nonNullValues.map(formatLiteral).join(', ')}])`
139
+ },
140
+ ref(node) {
141
+ if (!node.name) return undefined
142
+ const refName = node.ref ? (extractRefName(node.ref) ?? node.name) : node.name
143
+ const resolvedName = node.ref ? (this.options.resolver?.default(refName, 'function') ?? refName) : node.name
144
+ const isSelfRef = node.ref && this.options.schemaName != null && resolvedName === this.options.schemaName
145
+
146
+ if (isSelfRef) {
147
+ return `z.lazy(() => ${resolvedName})`
148
+ }
149
+
150
+ return resolvedName
151
+ },
152
+ object(node) {
153
+ const properties = node.properties
154
+ .map((prop) => {
155
+ const { name: propName, schema } = prop
156
+
157
+ const meta = syncSchemaRef(schema)
158
+
159
+ const isNullable = meta.nullable
160
+ const isOptional = schema.optional
161
+ const isNullish = schema.nullish
162
+
163
+ const hasSelfRef =
164
+ this.options.schemaName != null && containsSelfRef(schema, { schemaName: this.options.schemaName, resolver: this.options.resolver })
165
+ const baseOutput = this.transform(schema) ?? this.transform(createSchema({ type: 'unknown' }))!
166
+ // Strip z.lazy() wrappers inside object getters — the getter itself provides deferred evaluation
167
+ const resolvedOutput = hasSelfRef ? baseOutput.replaceAll(`z.lazy(() => ${this.options.schemaName})`, this.options.schemaName!) : baseOutput
168
+
169
+ const wrappedOutput = this.options.wrapOutput ? this.options.wrapOutput({ output: resolvedOutput, schema }) || resolvedOutput : resolvedOutput
170
+
171
+ const value = applyModifiers({
172
+ value: wrappedOutput,
173
+ nullable: isNullable,
174
+ optional: isOptional,
175
+ nullish: isNullish,
176
+ defaultValue: meta.default,
177
+ description: meta.description,
178
+ })
179
+
180
+ if (hasSelfRef) {
181
+ return `get "${propName}"() { return ${value} }`
182
+ }
183
+ return `"${propName}": ${value}`
184
+ })
185
+ .join(',\n ')
186
+
187
+ let result = `z.object({\n ${properties}\n })`
188
+
189
+ // Handle additionalProperties as .catchall() or .strict()
190
+ if (node.additionalProperties && node.additionalProperties !== true) {
191
+ const catchallType = this.transform(node.additionalProperties)
192
+ if (catchallType) {
193
+ result += `.catchall(${catchallType})`
194
+ }
195
+ } else if (node.additionalProperties === true) {
196
+ result += `.catchall(${this.transform(createSchema({ type: 'unknown' }))})`
197
+ } else if (node.additionalProperties === false) {
198
+ result += '.strict()'
199
+ }
200
+
201
+ return result
202
+ },
203
+ array(node) {
204
+ const items = (node.items ?? []).map((item) => this.transform(item)).filter(Boolean)
205
+ const inner = items.join(', ') || this.transform(createSchema({ type: 'unknown' }))!
206
+ let result = `z.array(${inner})${lengthConstraints(node)}`
207
+
208
+ if (node.unique) {
209
+ result += `.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })`
210
+ }
211
+
212
+ return result
213
+ },
214
+ tuple(node) {
215
+ const items = (node.items ?? []).map((item) => this.transform(item)).filter(Boolean)
216
+
217
+ return `z.tuple([${items.join(', ')}])`
218
+ },
219
+ union(node) {
220
+ const nodeMembers = node.members ?? []
221
+ const members = nodeMembers.map((m) => this.transform(m)).filter(Boolean)
222
+ if (members.length === 0) return ''
223
+ if (members.length === 1) return members[0]!
224
+ if (node.discriminatorPropertyName && !nodeMembers.some((m) => m.type === 'intersection')) {
225
+ // z.discriminatedUnion requires ZodObject members; intersections (ZodIntersection) are not
226
+ // assignable to $ZodDiscriminant, so fall back to z.union when any member is an intersection.
227
+ return `z.discriminatedUnion(${stringify(node.discriminatorPropertyName)}, [${members.join(', ')}])`
228
+ }
229
+
230
+ return `z.union([${members.join(', ')}])`
231
+ },
232
+ intersection(node) {
233
+ const members = node.members ?? []
234
+ if (members.length === 0) return ''
235
+
236
+ const [first, ...rest] = members
237
+ if (!first) return ''
238
+
239
+ let base = this.transform(first)
240
+ if (!base) return ''
241
+
242
+ for (const member of rest) {
243
+ if (member.primitive === 'string') {
244
+ const s = narrowSchema(member, 'string')
245
+ const c = lengthConstraints(s ?? {})
246
+ if (c) {
247
+ base += c
248
+ continue
249
+ }
250
+ } else if (member.primitive === 'number' || member.primitive === 'integer') {
251
+ const n = narrowSchema(member, 'number') ?? narrowSchema(member, 'integer')
252
+ const c = numberConstraints(n ?? {})
253
+ if (c) {
254
+ base += c
255
+ continue
256
+ }
257
+ } else if (member.primitive === 'array') {
258
+ const a = narrowSchema(member, 'array')
259
+ const c = lengthConstraints(a ?? {})
260
+ if (c) {
261
+ base += c
262
+ continue
263
+ }
264
+ }
265
+ const transformed = this.transform(member)
266
+ if (transformed) base = `${base}.and(${transformed})`
267
+ }
268
+
269
+ return base
270
+ },
271
+ ...options.nodes,
272
+ },
273
+ print(node) {
274
+ const { keysToOmit } = this.options
275
+
276
+ let base = this.transform(node)
277
+ if (!base) return null
278
+
279
+ const meta = syncSchemaRef(node)
280
+
281
+ if (keysToOmit?.length && meta.primitive === 'object' && !(meta.type === 'union' && meta.discriminatorPropertyName)) {
282
+ // Mirror printerTs `nonNullable: true`: when omitting keys, the resulting
283
+ // schema is a new non-nullable object type — skip optional/nullable/nullish.
284
+ // Discriminated unions (z.discriminatedUnion) do not support .omit(), so skip them.
285
+ base = `${base}.omit({ ${keysToOmit.map((k) => `"${k}": true`).join(', ')} })`
286
+ }
287
+
288
+ return applyModifiers({
289
+ value: base,
290
+ nullable: meta.nullable,
291
+ optional: meta.optional,
292
+ nullish: meta.nullish,
293
+ defaultValue: meta.default,
294
+ description: meta.description,
295
+ })
296
+ },
297
+ }
298
+ })
@@ -0,0 +1,273 @@
1
+ import { stringify } from '@internals/utils'
2
+ import { createSchema, extractRefName, narrowSchema, syncSchemaRef } from '@kubb/ast'
3
+ import type { PrinterFactoryOptions, PrinterPartial } from '@kubb/core'
4
+ import { definePrinter } from '@kubb/core'
5
+ import type { PluginZod, ResolverZod } from '../types.ts'
6
+ import { applyMiniModifiers, containsSelfRef, formatLiteral, lengthChecksMini, numberChecksMini } from '../utils.ts'
7
+
8
+ /**
9
+ * Partial map of node-type overrides for the Zod Mini printer.
10
+ *
11
+ * Each key is a `SchemaType` string (e.g. `'date'`, `'string'`). The function
12
+ * replaces the built-in handler for that node type. Use `this.transform` to
13
+ * recurse into nested schema nodes, and `this.options` to read printer options.
14
+ *
15
+ * @example Override the `date` handler
16
+ * ```ts
17
+ * pluginZod({
18
+ * mini: true,
19
+ * printer: {
20
+ * nodes: {
21
+ * date(node) {
22
+ * return 'z.iso.date()'
23
+ * },
24
+ * },
25
+ * },
26
+ * })
27
+ * ```
28
+ */
29
+ export type PrinterZodMiniNodes = PrinterPartial<string, PrinterZodMiniOptions>
30
+
31
+ export type PrinterZodMiniOptions = {
32
+ guidType?: PluginZod['resolvedOptions']['guidType']
33
+ wrapOutput?: PluginZod['resolvedOptions']['wrapOutput']
34
+ resolver?: ResolverZod
35
+ schemaName?: string
36
+ /**
37
+ * Property keys to exclude from the generated object schema via `.omit({ key: true })`.
38
+ */
39
+ keysToOmit?: Array<string>
40
+ /**
41
+ * Partial map of node-type overrides. Each entry replaces the built-in handler for that node type.
42
+ */
43
+ nodes?: PrinterZodMiniNodes
44
+ }
45
+
46
+ export type PrinterZodMiniFactory = PrinterFactoryOptions<'zod-mini', PrinterZodMiniOptions, string, string>
47
+ /**
48
+ * Zod v4 **Mini** printer built with `definePrinter`.
49
+ *
50
+ * Converts a `SchemaNode` AST into a Zod v4 Mini code string using the
51
+ * functional API (`z.optional(z.string())`) for better tree-shaking.
52
+ *
53
+ * For the standard chainable API, see {@link printerZod}.
54
+ *
55
+ * @example
56
+ * ```ts
57
+ * const printer = printerZodMini({})
58
+ * const code = printer.print(optionalStringNode) // "z.optional(z.string())"
59
+ * ```
60
+ */
61
+ export const printerZodMini = definePrinter<PrinterZodMiniFactory>((options) => {
62
+ return {
63
+ name: 'zod-mini',
64
+ options,
65
+ nodes: {
66
+ any: () => 'z.any()',
67
+ unknown: () => 'z.unknown()',
68
+ void: () => 'z.void()',
69
+ never: () => 'z.never()',
70
+ boolean: () => 'z.boolean()',
71
+ null: () => 'z.null()',
72
+ string(node) {
73
+ return `z.string()${lengthChecksMini(node)}`
74
+ },
75
+ number(node) {
76
+ return `z.number()${numberChecksMini(node)}`
77
+ },
78
+ integer(node) {
79
+ return `z.int()${numberChecksMini(node)}`
80
+ },
81
+ bigint(node) {
82
+ return `z.bigint()${numberChecksMini(node)}`
83
+ },
84
+ date(node) {
85
+ if (node.representation === 'string') {
86
+ return 'z.iso.date()'
87
+ }
88
+
89
+ return 'z.date()'
90
+ },
91
+ datetime() {
92
+ // Mini mode: datetime validation via z.string() (z.iso.datetime not available in mini)
93
+ return 'z.string()'
94
+ },
95
+ time(node) {
96
+ if (node.representation === 'string') {
97
+ return 'z.iso.time()'
98
+ }
99
+
100
+ return 'z.date()'
101
+ },
102
+ uuid(node) {
103
+ const base = this.options.guidType === 'guid' ? 'z.guid()' : 'z.uuid()'
104
+
105
+ return `${base}${lengthChecksMini(node)}`
106
+ },
107
+ email(node) {
108
+ return `z.email()${lengthChecksMini(node)}`
109
+ },
110
+ url(node) {
111
+ return `z.url()${lengthChecksMini(node)}`
112
+ },
113
+ ipv4: () => 'z.ipv4()',
114
+ ipv6: () => 'z.ipv6()',
115
+ blob: () => 'z.instanceof(File)',
116
+ enum(node) {
117
+ const values = node.namedEnumValues?.map((v) => v.value) ?? node.enumValues ?? []
118
+ const nonNullValues = values.filter((v): v is string | number | boolean => v !== null)
119
+
120
+ // asConst-style enum: use z.union([z.literal(…), …])
121
+ if (node.namedEnumValues?.length) {
122
+ const literals = nonNullValues.map((v) => `z.literal(${formatLiteral(v)})`)
123
+ if (literals.length === 1) return literals[0]!
124
+ return `z.union([${literals.join(', ')}])`
125
+ }
126
+
127
+ // Regular enum: use z.enum([…])
128
+ return `z.enum([${nonNullValues.map(formatLiteral).join(', ')}])`
129
+ },
130
+
131
+ ref(node) {
132
+ if (!node.name) return undefined
133
+ const refName = node.ref ? (extractRefName(node.ref) ?? node.name) : node.name
134
+ const resolvedName = node.ref ? (this.options.resolver?.default(refName, 'function') ?? refName) : node.name
135
+ const isSelfRef = node.ref && this.options.schemaName != null && resolvedName === this.options.schemaName
136
+
137
+ if (isSelfRef) {
138
+ return `z.lazy(() => ${resolvedName})`
139
+ }
140
+
141
+ return resolvedName
142
+ },
143
+ object(node) {
144
+ const properties = node.properties
145
+ .map((prop) => {
146
+ const { name: propName, schema } = prop
147
+
148
+ const meta = syncSchemaRef(schema)
149
+
150
+ const isNullable = meta.nullable
151
+ const isOptional = schema.optional
152
+ const isNullish = schema.nullish
153
+
154
+ const hasSelfRef =
155
+ this.options.schemaName != null && containsSelfRef(schema, { schemaName: this.options.schemaName, resolver: this.options.resolver })
156
+ const baseOutput = this.transform(schema) ?? this.transform(createSchema({ type: 'unknown' }))!
157
+ // Strip z.lazy() wrappers inside object getters — the getter itself provides deferred evaluation
158
+ const resolvedOutput = hasSelfRef ? baseOutput.replaceAll(`z.lazy(() => ${this.options.schemaName})`, this.options.schemaName!) : baseOutput
159
+
160
+ const wrappedOutput = this.options.wrapOutput ? this.options.wrapOutput({ output: resolvedOutput, schema }) || resolvedOutput : resolvedOutput
161
+
162
+ const value = applyMiniModifiers({
163
+ value: wrappedOutput,
164
+ nullable: isNullable,
165
+ optional: isOptional,
166
+ nullish: isNullish,
167
+ defaultValue: meta.default,
168
+ })
169
+
170
+ if (hasSelfRef) {
171
+ return `get "${propName}"() { return ${value} }`
172
+ }
173
+ return `"${propName}": ${value}`
174
+ })
175
+ .join(',\n ')
176
+
177
+ return `z.object({\n ${properties}\n })`
178
+ },
179
+ array(node) {
180
+ const items = (node.items ?? []).map((item) => this.transform(item)).filter(Boolean)
181
+ const inner = items.join(', ') || this.transform(createSchema({ type: 'unknown' }))!
182
+ let result = `z.array(${inner})${lengthChecksMini(node)}`
183
+
184
+ if (node.unique) {
185
+ result += `.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })`
186
+ }
187
+
188
+ return result
189
+ },
190
+ tuple(node) {
191
+ const items = (node.items ?? []).map((item) => this.transform(item)).filter(Boolean)
192
+
193
+ return `z.tuple([${items.join(', ')}])`
194
+ },
195
+ union(node) {
196
+ const nodeMembers = node.members ?? []
197
+ const members = nodeMembers.map((m) => this.transform(m)).filter(Boolean)
198
+ if (members.length === 0) return ''
199
+ if (members.length === 1) return members[0]!
200
+ if (node.discriminatorPropertyName && !nodeMembers.some((m) => m.type === 'intersection')) {
201
+ // z.discriminatedUnion requires ZodObject members; intersections (ZodIntersection) are not
202
+ // assignable to $ZodDiscriminant, so fall back to z.union when any member is an intersection.
203
+ return `z.discriminatedUnion(${stringify(node.discriminatorPropertyName)}, [${members.join(', ')}])`
204
+ }
205
+
206
+ return `z.union([${members.join(', ')}])`
207
+ },
208
+ intersection(node) {
209
+ const members = node.members ?? []
210
+ if (members.length === 0) return ''
211
+
212
+ const [first, ...rest] = members
213
+ if (!first) return ''
214
+
215
+ let base = this.transform(first)
216
+ if (!base) return ''
217
+
218
+ for (const member of rest) {
219
+ if (member.primitive === 'string') {
220
+ const s = narrowSchema(member, 'string')
221
+ const c = lengthChecksMini(s ?? {})
222
+ if (c) {
223
+ base += c
224
+ continue
225
+ }
226
+ } else if (member.primitive === 'number' || member.primitive === 'integer') {
227
+ const n = narrowSchema(member, 'number') ?? narrowSchema(member, 'integer')
228
+ const c = numberChecksMini(n ?? {})
229
+ if (c) {
230
+ base += c
231
+ continue
232
+ }
233
+ } else if (member.primitive === 'array') {
234
+ const a = narrowSchema(member, 'array')
235
+ const c = lengthChecksMini(a ?? {})
236
+ if (c) {
237
+ base += c
238
+ continue
239
+ }
240
+ }
241
+ const transformed = this.transform(member)
242
+ if (transformed) base = `z.intersection(${base}, ${transformed})`
243
+ }
244
+
245
+ return base
246
+ },
247
+ ...options.nodes,
248
+ },
249
+ print(node) {
250
+ const { keysToOmit } = this.options
251
+
252
+ let base = this.transform(node)
253
+ if (!base) return null
254
+
255
+ const meta = syncSchemaRef(node)
256
+
257
+ if (keysToOmit?.length && meta.primitive === 'object' && !(meta.type === 'union' && meta.discriminatorPropertyName)) {
258
+ // Mirror printerTs `nonNullable: true`: when omitting keys, the resulting
259
+ // schema is a new non-nullable object type — skip optional/nullable/nullish.
260
+ // Discriminated unions (z.discriminatedUnion) do not support .omit(), so skip them.
261
+ base = `${base}.omit({ ${keysToOmit.map((k) => `"${k}": true`).join(', ')} })`
262
+ }
263
+
264
+ return applyMiniModifiers({
265
+ value: base,
266
+ nullable: meta.nullable,
267
+ optional: meta.optional,
268
+ nullish: meta.nullish,
269
+ defaultValue: meta.default,
270
+ })
271
+ },
272
+ }
273
+ })
@@ -0,0 +1,61 @@
1
+ import { camelCase, pascalCase } from '@internals/utils'
2
+ import { defineResolver } from '@kubb/core'
3
+ import type { PluginZod } from '../types.ts'
4
+
5
+ /**
6
+ * Default resolver for `@kubb/plugin-zod`.
7
+ *
8
+ * Uses `camelCase` naming with a `Schema` suffix for function/type/const names.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * resolverZod.default('list pets', 'function') // → 'listPetsSchema'
13
+ * resolverZod.default('Pet', 'file') // → 'pet'
14
+ * resolverZod.resolveName('list pets') // → 'listPetsSchema'
15
+ * ```
16
+ */
17
+ export const resolverZod = defineResolver<PluginZod>(() => {
18
+ return {
19
+ name: 'default',
20
+ pluginName: 'plugin-zod',
21
+ default(name, type) {
22
+ return camelCase(name, { isFile: type === 'file', suffix: type ? 'schema' : undefined })
23
+ },
24
+ resolveSchemaName(name) {
25
+ return camelCase(name, { suffix: 'schema' })
26
+ },
27
+ resolveSchemaTypeName(name) {
28
+ return pascalCase(name, { suffix: 'schema' })
29
+ },
30
+ resolveTypeName(name) {
31
+ return pascalCase(name)
32
+ },
33
+ resolvePathName(name, type) {
34
+ return this.default(name, type)
35
+ },
36
+ resolveParamName(node, param) {
37
+ return this.resolveSchemaName(`${node.operationId} ${param.in} ${param.name}`)
38
+ },
39
+ resolveResponseStatusName(node, statusCode) {
40
+ return this.resolveSchemaName(`${node.operationId} Status ${statusCode}`)
41
+ },
42
+ resolveDataName(node) {
43
+ return this.resolveSchemaName(`${node.operationId} Data`)
44
+ },
45
+ resolveResponsesName(node) {
46
+ return this.resolveSchemaName(`${node.operationId} Responses`)
47
+ },
48
+ resolveResponseName(node) {
49
+ return this.resolveSchemaName(`${node.operationId} Response`)
50
+ },
51
+ resolvePathParamsName(node, param) {
52
+ return this.resolveParamName(node, param)
53
+ },
54
+ resolveQueryParamsName(node, param) {
55
+ return this.resolveParamName(node, param)
56
+ },
57
+ resolveHeaderParamsName(node, param) {
58
+ return this.resolveParamName(node, param)
59
+ },
60
+ }
61
+ })