@kubb/plugin-zod 5.0.0-beta.42 → 5.0.0-beta.64

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.
@@ -1,362 +0,0 @@
1
- import { stringify } from '@internals/utils'
2
-
3
- import { ast } from '@kubb/core'
4
- import type { PluginZod, ResolverZod } from '../types.ts'
5
- import { applyModifiers, containsCodec, formatLiteral, getCodec, lengthConstraints, numberConstraints, shouldCoerce } from '../utils.ts'
6
- import type { AdapterOas } from '@kubb/adapter-oas'
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 = ast.PrinterPartial<string, PrinterZodOptions>
29
-
30
- export type PrinterZodOptions = {
31
- /**
32
- * Enable automatic type coercion for strings, numbers, and dates.
33
- */
34
- coercion?: PluginZod['resolvedOptions']['coercion']
35
- /**
36
- * Use `z.guid()` or `z.uuid()` for UUID/GUID validation.
37
- *
38
- * @default 'uuid'
39
- */
40
- guidType?: PluginZod['resolvedOptions']['guidType']
41
- /**
42
- * Date format in the OpenAPI spec (`'date'` or `'date-time'`).
43
- */
44
- dateType?: AdapterOas['resolvedOptions']['dateType']
45
- /**
46
- * Hook to transform generated Zod schema before output.
47
- */
48
- wrapOutput?: PluginZod['resolvedOptions']['wrapOutput']
49
- /**
50
- * Transforms raw schema names into valid JavaScript identifiers.
51
- */
52
- resolver?: ResolverZod
53
- /**
54
- * Properties to exclude using `.omit({ key: true })`.
55
- */
56
- keysToOmit?: Array<string> | null
57
- /**
58
- * Schema names that form circular dependency chains.
59
- * Properties referencing these emit lazy getters wrapping refs in `z.lazy(() => …)`.
60
- */
61
- cyclicSchemas?: ReadonlySet<string>
62
- /**
63
- * Print direction for `dateType: 'date'` fields (`Date` in TypeScript):
64
- * - `'output'` (default) — decode the wire `string` into a `Date` (response bodies).
65
- * - `'input'` — encode a `Date` back into the wire `string` (request bodies/params).
66
- *
67
- * Diverging the directions requires the generator to emit an `${name}InputSchema`
68
- * variant for each date-bearing component.
69
- */
70
- direction?: 'input' | 'output'
71
- /**
72
- * Custom handler map for node type overrides.
73
- */
74
- nodes?: PrinterZodNodes
75
- }
76
-
77
- /**
78
- * Factory options for the Zod printer, defining input/output types and configuration.
79
- */
80
- export type PrinterZodFactory = ast.PrinterFactoryOptions<'zod', PrinterZodOptions, string, string>
81
-
82
- function strictOneOfMember(member: string, node: ast.SchemaNode): string {
83
- if (node.type === 'object' && node.additionalProperties === undefined) {
84
- return `${member}.strict()`
85
- }
86
-
87
- if (node.type === 'ref') {
88
- if (member.startsWith('z.lazy(')) {
89
- return member
90
- }
91
-
92
- const schema = ast.syncSchemaRef(node)
93
-
94
- if (schema.type === 'object' && (schema.additionalProperties === undefined || schema.additionalProperties === false)) {
95
- return `${member}.strict()`
96
- }
97
- }
98
-
99
- return member
100
- }
101
-
102
- function getMemberConstraint(member: ast.SchemaNode): string | undefined {
103
- if (member.primitive === 'string') return lengthConstraints(ast.narrowSchema(member, 'string') ?? {}) || undefined
104
- if (member.primitive === 'number' || member.primitive === 'integer')
105
- return numberConstraints(ast.narrowSchema(member, 'number') ?? ast.narrowSchema(member, 'integer') ?? {}) || undefined
106
- if (member.primitive === 'array') return lengthConstraints(ast.narrowSchema(member, 'array') ?? {}) || undefined
107
- }
108
-
109
- /**
110
- * Zod v4 printer built with `definePrinter`.
111
- *
112
- * Converts a `SchemaNode` AST into a Zod v4 code string using the chainable API
113
- * (`.optional()`, `.nullable()`, `.omit()`, etc.). For improved tree-shaking, see {@link printerZodMini}.
114
- *
115
- * @example Chainable API
116
- * ```ts
117
- * const printer = printerZod({ coercion: false })
118
- * const code = printer.print(stringNode) // "z.string()"
119
- * ```
120
- */
121
- export const printerZod = ast.definePrinter<PrinterZodFactory>((options) => {
122
- return {
123
- name: 'zod',
124
- options,
125
- nodes: {
126
- any: () => 'z.any()',
127
- unknown: () => 'z.unknown()',
128
- void: () => 'z.void()',
129
- never: () => 'z.never()',
130
- boolean: () => 'z.boolean()',
131
- null: () => 'z.null()',
132
- string(node) {
133
- const base = shouldCoerce(this.options.coercion, 'strings') ? 'z.coerce.string()' : 'z.string()'
134
-
135
- return `${base}${lengthConstraints(node)}`
136
- },
137
- number(node) {
138
- const base = shouldCoerce(this.options.coercion, 'numbers') ? 'z.coerce.number()' : 'z.number()'
139
-
140
- return `${base}${numberConstraints(node)}`
141
- },
142
- integer(node) {
143
- const base = shouldCoerce(this.options.coercion, 'numbers') ? 'z.coerce.number().int()' : 'z.int()'
144
-
145
- return `${base}${numberConstraints(node)}`
146
- },
147
- bigint() {
148
- return shouldCoerce(this.options.coercion, 'numbers') ? 'z.coerce.bigint()' : 'z.bigint()'
149
- },
150
- date(node) {
151
- // representation: 'date' → typed as `Date`; decode/encode at the boundary.
152
- const codec = getCodec(node)
153
- if (codec) {
154
- return this.options.direction === 'input' ? codec.encode(node) : codec.decode(node)
155
- }
156
-
157
- return 'z.iso.date()'
158
- },
159
- datetime(node) {
160
- const offset = node.offset || this.options.dateType === 'stringOffset'
161
- const local = node.local || this.options.dateType === 'stringLocal'
162
-
163
- if (offset) return 'z.iso.datetime({ offset: true })'
164
- if (local) return 'z.iso.datetime({ local: true })'
165
-
166
- return 'z.iso.datetime()'
167
- },
168
- time(node) {
169
- if (node.representation === 'string') {
170
- return 'z.iso.time()'
171
- }
172
-
173
- return shouldCoerce(this.options.coercion, 'dates') ? 'z.coerce.date()' : 'z.date()'
174
- },
175
- uuid(node) {
176
- const base = this.options.guidType === 'guid' ? 'z.guid()' : 'z.uuid()'
177
-
178
- return `${base}${lengthConstraints(node)}`
179
- },
180
- email(node) {
181
- return `z.email()${lengthConstraints(node)}`
182
- },
183
- url(node) {
184
- return `z.url()${lengthConstraints(node)}`
185
- },
186
- ipv4: () => 'z.ipv4()',
187
- ipv6: () => 'z.ipv6()',
188
- blob: () => 'z.instanceof(File)',
189
- enum(node) {
190
- const values = node.namedEnumValues?.map((v) => v.value) ?? node.enumValues ?? []
191
- const nonNullValues = values.filter((v): v is string | number | boolean => v !== null)
192
-
193
- // asConst-style enum: use z.union([z.literal(…), …])
194
- if (node.namedEnumValues?.length) {
195
- const literals = nonNullValues.map((v) => `z.literal(${formatLiteral(v)})`)
196
-
197
- if (literals.length === 1) return literals[0]!
198
- return `z.union([${literals.join(', ')}])`
199
- }
200
-
201
- // Regular enum: use z.enum([…])
202
- return `z.enum([${nonNullValues.map(formatLiteral).join(', ')}])`
203
- },
204
- ref(node) {
205
- if (!node.name) return null
206
- const refName = node.ref ? (ast.extractRefName(node.ref) ?? node.name) : node.name
207
-
208
- // In the input direction, a date-bearing component resolves to its `${name}InputSchema`
209
- // variant so request bodies encode `Date → string` instead of decoding.
210
- const useInputVariant = node.ref != null && this.options.direction === 'input' && containsCodec(node)
211
- const resolvedName = node.ref
212
- ? useInputVariant
213
- ? (this.options.resolver?.resolveInputSchemaName(refName) ?? refName)
214
- : (this.options.resolver?.default(refName, 'function') ?? refName)
215
- : node.name
216
-
217
- if (node.ref && this.options.cyclicSchemas?.has(refName)) {
218
- return `z.lazy(() => ${resolvedName})`
219
- }
220
-
221
- return resolvedName
222
- },
223
- object(node) {
224
- const properties = node.properties
225
- .map((prop) => {
226
- const { name: propName, schema } = prop
227
-
228
- const meta = ast.syncSchemaRef(schema)
229
-
230
- const isNullable = meta.nullable
231
- const isOptional = schema.optional
232
- const isNullish = schema.nullish
233
-
234
- const hasSelfRef = this.options.cyclicSchemas != null && ast.containsCircularRef(schema, { circularSchemas: this.options.cyclicSchemas })
235
- // Inside a getter the getter itself defers evaluation, so suppress
236
- // z.lazy() wrapping on nested refs by temporarily clearing cyclicSchemas.
237
- // Save before clearing: this.options === options (same reference via definePrinter),
238
- // so reading options.cyclicSchemas after mutation would return undefined.
239
- const savedCyclicSchemas = this.options.cyclicSchemas
240
- if (hasSelfRef) this.options.cyclicSchemas = undefined
241
- const baseOutput = this.transform(schema) ?? this.transform(ast.createSchema({ type: 'unknown' }))!
242
- if (hasSelfRef) this.options.cyclicSchemas = savedCyclicSchemas
243
-
244
- const wrappedOutput = this.options.wrapOutput ? this.options.wrapOutput({ output: baseOutput, schema }) || baseOutput : baseOutput
245
-
246
- // When a property schema is not a ref but the metadata is from a ref (e.g., discriminator
247
- // property override), skip applying the description from the ref target to avoid applying
248
- // metadata from a replaced schema.
249
- const descriptionToApply = schema.type !== 'ref' && meta.type === 'ref' ? undefined : meta.description
250
-
251
- const value = applyModifiers({
252
- value: wrappedOutput,
253
- nullable: isNullable,
254
- optional: isOptional,
255
- nullish: isNullish,
256
- defaultValue: meta.default,
257
- description: descriptionToApply,
258
- })
259
-
260
- if (hasSelfRef) {
261
- return `get "${propName}"() { return ${value} }`
262
- }
263
- return `"${propName}": ${value}`
264
- })
265
- .join(',\n ')
266
-
267
- const objectBase = `z.object({\n ${properties}\n })`
268
-
269
- // Handle additionalProperties as .catchall() or .strict()
270
- const result = (() => {
271
- if (node.additionalProperties && node.additionalProperties !== true) {
272
- const catchallType = this.transform(node.additionalProperties)
273
- return catchallType ? `${objectBase}.catchall(${catchallType})` : objectBase
274
- }
275
- if (node.additionalProperties === true) return `${objectBase}.catchall(${this.transform(ast.createSchema({ type: 'unknown' }))})`
276
- if (node.additionalProperties === false) return `${objectBase}.strict()`
277
- return objectBase
278
- })()
279
-
280
- return result
281
- },
282
- array(node) {
283
- const items = (node.items ?? []).map((item) => this.transform(item)).filter(Boolean)
284
- const inner = items.join(', ') || this.transform(ast.createSchema({ type: 'unknown' }))!
285
- const base = `z.array(${inner})${lengthConstraints(node)}`
286
-
287
- return node.unique ? `${base}.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })` : base
288
- },
289
- tuple(node) {
290
- const items = (node.items ?? []).map((item) => this.transform(item)).filter(Boolean)
291
-
292
- return `z.tuple([${items.join(', ')}])`
293
- },
294
- union(node) {
295
- const nodeMembers = node.members ?? []
296
- const members = nodeMembers
297
- .map((memberNode) => {
298
- const member = this.transform(memberNode)
299
-
300
- return member && node.strategy === 'one' ? strictOneOfMember(member, memberNode) : member
301
- })
302
- .filter(Boolean)
303
- if (members.length === 0) return ''
304
- if (members.length === 1) return members[0]!
305
- if (node.discriminatorPropertyName && !nodeMembers.some((m) => m.type === 'intersection')) {
306
- // z.discriminatedUnion requires ZodObject members; intersections (ZodIntersection) are not
307
- // assignable to $ZodDiscriminant, so fall back to z.union when any member is an intersection.
308
- return `z.discriminatedUnion(${stringify(node.discriminatorPropertyName)}, [${members.join(', ')}])`
309
- }
310
-
311
- return `z.union([${members.join(', ')}])`
312
- },
313
- intersection(node) {
314
- const members = node.members ?? []
315
- if (members.length === 0) return ''
316
-
317
- const [first, ...rest] = members
318
- if (!first) return ''
319
-
320
- const firstBase = this.transform(first)
321
- if (!firstBase) return ''
322
-
323
- return rest.reduce((acc, member) => {
324
- const constraint = getMemberConstraint(member)
325
- if (constraint) return acc + constraint
326
- const transformed = this.transform(member)
327
- return transformed ? `${acc}.and(${transformed})` : acc
328
- }, firstBase)
329
- },
330
- ...options.nodes,
331
- },
332
- print(node) {
333
- const { keysToOmit } = this.options
334
-
335
- const transformed = this.transform(node)
336
- if (!transformed) return null
337
-
338
- const meta = ast.syncSchemaRef(node)
339
-
340
- const base = (() => {
341
- if (!keysToOmit?.length || meta.primitive !== 'object' || (meta.type === 'union' && meta.discriminatorPropertyName)) return transformed
342
- // Mirror printerTs `nonNullable: true`: when omitting keys, the resulting
343
- // schema is a new non-nullable object type — skip optional/nullable/nullish.
344
- // Discriminated unions (z.discriminatedUnion) do not support .omit(), so skip them.
345
-
346
- // If this is a lazy reference, apply omit inside the lazy function
347
- const lazyMatch = transformed.match(/^z\.lazy\(\(\)\s*=>\s*(.+)\)$/)
348
- if (lazyMatch) return `z.lazy(() => ${lazyMatch[1]}.omit({ ${keysToOmit.map((k: string) => `"${k}": true`).join(', ')} }))`
349
- return `${transformed}.omit({ ${keysToOmit.map((k: string) => `"${k}": true`).join(', ')} })`
350
- })()
351
-
352
- return applyModifiers({
353
- value: base,
354
- nullable: meta.nullable,
355
- optional: meta.optional,
356
- nullish: meta.nullish,
357
- defaultValue: meta.default,
358
- description: meta.description,
359
- })
360
- },
361
- }
362
- })
@@ -1,292 +0,0 @@
1
- import { stringify } from '@internals/utils'
2
-
3
- import { ast } from '@kubb/core'
4
- import type { PluginZod, ResolverZod } from '../types.ts'
5
- import { applyMiniModifiers, formatLiteral, lengthChecksMini, numberChecksMini } from '../utils.ts'
6
-
7
- /**
8
- * Partial map of node-type overrides for the Zod Mini printer.
9
- *
10
- * Each key is a `SchemaType` string (e.g. `'date'`, `'string'`). The function
11
- * replaces the built-in handler for that node type. Use `this.transform` to
12
- * recurse into nested schema nodes, and `this.options` to read printer options.
13
- *
14
- * @example Override the `date` handler
15
- * ```ts
16
- * pluginZod({
17
- * mini: true,
18
- * printer: {
19
- * nodes: {
20
- * date(node) {
21
- * return 'z.iso.date()'
22
- * },
23
- * },
24
- * },
25
- * })
26
- * ```
27
- */
28
- export type PrinterZodMiniNodes = ast.PrinterPartial<string, PrinterZodMiniOptions>
29
-
30
- export type PrinterZodMiniOptions = {
31
- /**
32
- * Use `z.guid()` or `z.uuid()` for UUID/GUID validation.
33
- *
34
- * @default 'uuid'
35
- */
36
- guidType?: PluginZod['resolvedOptions']['guidType']
37
- /**
38
- * Hook to transform generated Zod schema before output.
39
- */
40
- wrapOutput?: PluginZod['resolvedOptions']['wrapOutput']
41
- /**
42
- * Transforms raw schema names into valid JavaScript identifiers.
43
- */
44
- resolver?: ResolverZod
45
- /**
46
- * Properties to exclude using `.omit({ key: true })`.
47
- */
48
- keysToOmit?: Array<string> | null
49
- /**
50
- * Schema names that form circular dependency chains.
51
- * Properties referencing these emit lazy getters wrapping refs in `z.lazy(() => …)`.
52
- */
53
- cyclicSchemas?: ReadonlySet<string>
54
- /**
55
- * Custom handler map for node type overrides.
56
- */
57
- nodes?: PrinterZodMiniNodes
58
- }
59
-
60
- /**
61
- * Factory options for the Zod Mini printer, defining input/output types and configuration.
62
- */
63
- export type PrinterZodMiniFactory = ast.PrinterFactoryOptions<'zod-mini', PrinterZodMiniOptions, string, string>
64
-
65
- function strictOneOfMember(member: string, node: ast.SchemaNode): string {
66
- if (node.type === 'object' && (node.additionalProperties === undefined || node.additionalProperties === false)) {
67
- return member.replace(/^z\.object\(/, 'z.strictObject(')
68
- }
69
-
70
- return member
71
- }
72
-
73
- function getMemberConstraintMini(member: ast.SchemaNode): string | undefined {
74
- if (member.primitive === 'string') return lengthChecksMini(ast.narrowSchema(member, 'string') ?? {}) || undefined
75
- if (member.primitive === 'number' || member.primitive === 'integer')
76
- return numberChecksMini(ast.narrowSchema(member, 'number') ?? ast.narrowSchema(member, 'integer') ?? {}) || undefined
77
- if (member.primitive === 'array') return lengthChecksMini(ast.narrowSchema(member, 'array') ?? {}) || undefined
78
- }
79
-
80
- /**
81
- * Zod v4 **Mini** printer built with `definePrinter`.
82
- *
83
- * Converts a `SchemaNode` AST into a Zod v4 code string using the functional API
84
- * (`z.optional(z.string())`) for improved tree-shaking. See {@link printerZod} for the chainable API.
85
- *
86
- * @example Functional Mini API
87
- * ```ts
88
- * const printer = printerZodMini({})
89
- * const code = printer.print(optionalStringNode) // "z.optional(z.string())"
90
- * ```
91
- */
92
- export const printerZodMini = ast.definePrinter<PrinterZodMiniFactory>((options) => {
93
- return {
94
- name: 'zod-mini',
95
- options,
96
- nodes: {
97
- any: () => 'z.any()',
98
- unknown: () => 'z.unknown()',
99
- void: () => 'z.void()',
100
- never: () => 'z.never()',
101
- boolean: () => 'z.boolean()',
102
- null: () => 'z.null()',
103
- string(node) {
104
- return `z.string()${lengthChecksMini(node)}`
105
- },
106
- number(node) {
107
- return `z.number()${numberChecksMini(node)}`
108
- },
109
- integer(node) {
110
- return `z.int()${numberChecksMini(node)}`
111
- },
112
- bigint(node) {
113
- return `z.bigint()${numberChecksMini(node)}`
114
- },
115
- date(node) {
116
- if (node.representation === 'string') {
117
- return 'z.iso.date()'
118
- }
119
-
120
- return 'z.date()'
121
- },
122
- datetime() {
123
- // Mini mode: datetime validation via z.string() (z.iso.datetime not available in mini)
124
- return 'z.string()'
125
- },
126
- time(node) {
127
- if (node.representation === 'string') {
128
- return 'z.iso.time()'
129
- }
130
-
131
- return 'z.date()'
132
- },
133
- uuid(node) {
134
- const base = this.options.guidType === 'guid' ? 'z.guid()' : 'z.uuid()'
135
-
136
- return `${base}${lengthChecksMini(node)}`
137
- },
138
- email(node) {
139
- return `z.email()${lengthChecksMini(node)}`
140
- },
141
- url(node) {
142
- return `z.url()${lengthChecksMini(node)}`
143
- },
144
- ipv4: () => 'z.ipv4()',
145
- ipv6: () => 'z.ipv6()',
146
- blob: () => 'z.instanceof(File)',
147
- enum(node) {
148
- const values = node.namedEnumValues?.map((v) => v.value) ?? node.enumValues ?? []
149
- const nonNullValues = values.filter((v): v is string | number | boolean => v !== null)
150
-
151
- // asConst-style enum: use z.union([z.literal(…), …])
152
- if (node.namedEnumValues?.length) {
153
- const literals = nonNullValues.map((v) => `z.literal(${formatLiteral(v)})`)
154
- if (literals.length === 1) return literals[0]!
155
- return `z.union([${literals.join(', ')}])`
156
- }
157
-
158
- // Regular enum: use z.enum([…])
159
- return `z.enum([${nonNullValues.map(formatLiteral).join(', ')}])`
160
- },
161
-
162
- ref(node) {
163
- if (!node.name) return null
164
- const refName = node.ref ? (ast.extractRefName(node.ref) ?? node.name) : node.name
165
- const resolvedName = node.ref ? (this.options.resolver?.default(refName, 'function') ?? refName) : node.name
166
-
167
- if (node.ref && this.options.cyclicSchemas?.has(refName)) {
168
- return `z.lazy(() => ${resolvedName})`
169
- }
170
-
171
- return resolvedName
172
- },
173
- object(node) {
174
- const properties = node.properties
175
- .map((prop) => {
176
- const { name: propName, schema } = prop
177
-
178
- const meta = ast.syncSchemaRef(schema)
179
-
180
- const isNullable = meta.nullable
181
- const isOptional = schema.optional
182
- const isNullish = schema.nullish
183
-
184
- const hasSelfRef = this.options.cyclicSchemas != null && ast.containsCircularRef(schema, { circularSchemas: this.options.cyclicSchemas })
185
- // Inside a getter the getter itself defers evaluation, so suppress
186
- // z.lazy() wrapping on nested refs by temporarily clearing cyclicSchemas.
187
- // Save before clearing: this.options === options (same reference via definePrinter),
188
- // so reading options.cyclicSchemas after mutation would return undefined.
189
- const savedCyclicSchemas = this.options.cyclicSchemas
190
- if (hasSelfRef) this.options.cyclicSchemas = undefined
191
- const baseOutput = this.transform(schema) ?? this.transform(ast.createSchema({ type: 'unknown' }))!
192
- if (hasSelfRef) this.options.cyclicSchemas = savedCyclicSchemas
193
-
194
- const wrappedOutput = this.options.wrapOutput ? this.options.wrapOutput({ output: baseOutput, schema }) || baseOutput : baseOutput
195
-
196
- const value = applyMiniModifiers({
197
- value: wrappedOutput,
198
- nullable: isNullable,
199
- optional: isOptional,
200
- nullish: isNullish,
201
- defaultValue: meta.default,
202
- })
203
-
204
- if (hasSelfRef) {
205
- return `get "${propName}"() { return ${value} }`
206
- }
207
- return `"${propName}": ${value}`
208
- })
209
- .join(',\n ')
210
-
211
- return `z.object({\n ${properties}\n })`
212
- },
213
- array(node) {
214
- const items = (node.items ?? []).map((item) => this.transform(item)).filter(Boolean)
215
- const inner = items.join(', ') || this.transform(ast.createSchema({ type: 'unknown' }))!
216
- const base = `z.array(${inner})${lengthChecksMini(node)}`
217
-
218
- return node.unique ? `${base}.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })` : base
219
- },
220
- tuple(node) {
221
- const items = (node.items ?? []).map((item) => this.transform(item)).filter(Boolean)
222
-
223
- return `z.tuple([${items.join(', ')}])`
224
- },
225
- union(node) {
226
- const nodeMembers = node.members ?? []
227
- const members = nodeMembers
228
- .map((memberNode) => {
229
- const member = this.transform(memberNode)
230
-
231
- return member && node.strategy === 'one' ? strictOneOfMember(member, memberNode) : member
232
- })
233
- .filter(Boolean)
234
- if (members.length === 0) return ''
235
- if (members.length === 1) return members[0]!
236
- if (node.discriminatorPropertyName && !nodeMembers.some((m) => m.type === 'intersection')) {
237
- // z.discriminatedUnion requires ZodObject members; intersections (ZodIntersection) are not
238
- // assignable to $ZodDiscriminant, so fall back to z.union when any member is an intersection.
239
- return `z.discriminatedUnion(${stringify(node.discriminatorPropertyName)}, [${members.join(', ')}])`
240
- }
241
-
242
- return `z.union([${members.join(', ')}])`
243
- },
244
- intersection(node) {
245
- const members = node.members ?? []
246
- if (members.length === 0) return ''
247
-
248
- const [first, ...rest] = members
249
- if (!first) return ''
250
-
251
- const firstBase = this.transform(first)
252
- if (!firstBase) return ''
253
-
254
- return rest.reduce((acc, member) => {
255
- const constraint = getMemberConstraintMini(member)
256
- if (constraint) return acc + constraint
257
- const transformed = this.transform(member)
258
- return transformed ? `z.intersection(${acc}, ${transformed})` : acc
259
- }, firstBase)
260
- },
261
- ...options.nodes,
262
- },
263
- print(node) {
264
- const { keysToOmit } = this.options
265
-
266
- const transformed = this.transform(node)
267
- if (!transformed) return null
268
-
269
- const meta = ast.syncSchemaRef(node)
270
-
271
- const base = (() => {
272
- if (!keysToOmit?.length || meta.primitive !== 'object' || (meta.type === 'union' && meta.discriminatorPropertyName)) return transformed
273
- // Mirror printerTs `nonNullable: true`: when omitting keys, the resulting
274
- // schema is a new non-nullable object type — skip optional/nullable/nullish.
275
- // Discriminated unions (z.discriminatedUnion) do not support .omit(), so skip them.
276
-
277
- // If this is a lazy reference, apply omit inside the lazy function
278
- const lazyMatch = transformed.match(/^z\.lazy\(\(\)\s*=>\s*(.+)\)$/)
279
- if (lazyMatch) return `z.lazy(() => ${lazyMatch[1]}.omit({ ${keysToOmit.map((k: string) => `"${k}": true`).join(', ')} }))`
280
- return `${transformed}.omit({ ${keysToOmit.map((k: string) => `"${k}": true`).join(', ')} })`
281
- })()
282
-
283
- return applyMiniModifiers({
284
- value: base,
285
- nullable: meta.nullable,
286
- optional: meta.optional,
287
- nullish: meta.nullish,
288
- defaultValue: meta.default,
289
- })
290
- },
291
- }
292
- })