@kubb/plugin-zod 5.0.0-beta.56 → 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,360 +0,0 @@
1
- import { buildList, buildObject, extractRefName, objectKey, stringify } from '@kubb/ast/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 ? (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 entries = node.properties.map((prop) => {
225
- const { name: propName, schema } = prop
226
-
227
- const meta = ast.syncSchemaRef(schema)
228
-
229
- const isNullable = meta.nullable
230
- const isOptional = schema.optional
231
- const isNullish = schema.nullish
232
-
233
- const hasSelfRef = this.options.cyclicSchemas != null && ast.containsCircularRef(schema, { circularSchemas: this.options.cyclicSchemas })
234
- // Inside a getter the getter itself defers evaluation, so suppress
235
- // z.lazy() wrapping on nested refs by temporarily clearing cyclicSchemas.
236
- // Save before clearing: this.options === options (same reference via definePrinter),
237
- // so reading options.cyclicSchemas after mutation would return undefined.
238
- const savedCyclicSchemas = this.options.cyclicSchemas
239
- if (hasSelfRef) this.options.cyclicSchemas = undefined
240
- const baseOutput = this.transform(schema) ?? this.transform(ast.createSchema({ type: 'unknown' }))!
241
- if (hasSelfRef) this.options.cyclicSchemas = savedCyclicSchemas
242
-
243
- const wrappedOutput = this.options.wrapOutput ? this.options.wrapOutput({ output: baseOutput, schema }) || baseOutput : baseOutput
244
-
245
- // When a property schema is not a ref but the metadata is from a ref (e.g., discriminator
246
- // property override), skip applying the description from the ref target to avoid applying
247
- // metadata from a replaced schema.
248
- const descriptionToApply = schema.type !== 'ref' && meta.type === 'ref' ? undefined : meta.description
249
-
250
- const value = applyModifiers({
251
- value: wrappedOutput,
252
- nullable: isNullable,
253
- optional: isOptional,
254
- nullish: isNullish,
255
- defaultValue: meta.default,
256
- description: descriptionToApply,
257
- })
258
-
259
- if (hasSelfRef) {
260
- return `get ${objectKey(propName)}() { return ${value} }`
261
- }
262
- return `${objectKey(propName)}: ${value}`
263
- })
264
-
265
- const objectBase = `z.object(${buildObject(entries)})`
266
-
267
- // Handle additionalProperties as .catchall() or .strict()
268
- const result = (() => {
269
- if (node.additionalProperties && node.additionalProperties !== true) {
270
- const catchallType = this.transform(node.additionalProperties)
271
- return catchallType ? `${objectBase}.catchall(${catchallType})` : objectBase
272
- }
273
- if (node.additionalProperties === true) return `${objectBase}.catchall(${this.transform(ast.createSchema({ type: 'unknown' }))})`
274
- if (node.additionalProperties === false) return `${objectBase}.strict()`
275
- return objectBase
276
- })()
277
-
278
- return result
279
- },
280
- array(node) {
281
- const items = (node.items ?? []).map((item) => this.transform(item)).filter(Boolean)
282
- const inner = items.join(', ') || this.transform(ast.createSchema({ type: 'unknown' }))!
283
- const base = `z.array(${inner})${lengthConstraints(node)}`
284
-
285
- return node.unique ? `${base}.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })` : base
286
- },
287
- tuple(node) {
288
- const items = (node.items ?? []).map((item) => this.transform(item)).filter(Boolean)
289
-
290
- return `z.tuple(${buildList(items)})`
291
- },
292
- union(node) {
293
- const nodeMembers = node.members ?? []
294
- const members = nodeMembers
295
- .map((memberNode) => {
296
- const member = this.transform(memberNode)
297
-
298
- return member && node.strategy === 'one' ? strictOneOfMember(member, memberNode) : member
299
- })
300
- .filter(Boolean)
301
- if (members.length === 0) return ''
302
- if (members.length === 1) return members[0]!
303
- if (node.discriminatorPropertyName && !nodeMembers.some((m) => m.type === 'intersection')) {
304
- // z.discriminatedUnion requires ZodObject members; intersections (ZodIntersection) are not
305
- // assignable to $ZodDiscriminant, so fall back to z.union when any member is an intersection.
306
- return `z.discriminatedUnion(${stringify(node.discriminatorPropertyName)}, ${buildList(members)})`
307
- }
308
-
309
- return `z.union(${buildList(members)})`
310
- },
311
- intersection(node) {
312
- const members = node.members ?? []
313
- if (members.length === 0) return ''
314
-
315
- const [first, ...rest] = members
316
- if (!first) return ''
317
-
318
- const firstBase = this.transform(first)
319
- if (!firstBase) return ''
320
-
321
- return rest.reduce((acc, member) => {
322
- const constraint = getMemberConstraint(member)
323
- if (constraint) return acc + constraint
324
- const transformed = this.transform(member)
325
- return transformed ? `${acc}.and(${transformed})` : acc
326
- }, firstBase)
327
- },
328
- ...options.nodes,
329
- },
330
- print(node) {
331
- const { keysToOmit } = this.options
332
-
333
- const transformed = this.transform(node)
334
- if (!transformed) return null
335
-
336
- const meta = ast.syncSchemaRef(node)
337
-
338
- const base = (() => {
339
- if (!keysToOmit?.length || meta.primitive !== 'object' || (meta.type === 'union' && meta.discriminatorPropertyName)) return transformed
340
- // Mirror printerTs `nonNullable: true`: when omitting keys, the resulting
341
- // schema is a new non-nullable object type — skip optional/nullable/nullish.
342
- // Discriminated unions (z.discriminatedUnion) do not support .omit(), so skip them.
343
-
344
- // If this is a lazy reference, apply omit inside the lazy function
345
- const lazyMatch = transformed.match(/^z\.lazy\(\(\)\s*=>\s*(.+)\)$/)
346
- if (lazyMatch) return `z.lazy(() => ${lazyMatch[1]}.omit({ ${keysToOmit.map((k: string) => `"${k}": true`).join(', ')} }))`
347
- return `${transformed}.omit({ ${keysToOmit.map((k: string) => `"${k}": true`).join(', ')} })`
348
- })()
349
-
350
- return applyModifiers({
351
- value: base,
352
- nullable: meta.nullable,
353
- optional: meta.optional,
354
- nullish: meta.nullish,
355
- defaultValue: meta.default,
356
- description: meta.description,
357
- })
358
- },
359
- }
360
- })
@@ -1,290 +0,0 @@
1
- import { buildList, buildObject, extractRefName, objectKey, stringify } from '@kubb/ast/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 ? (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 entries = node.properties.map((prop) => {
175
- const { name: propName, schema } = prop
176
-
177
- const meta = ast.syncSchemaRef(schema)
178
-
179
- const isNullable = meta.nullable
180
- const isOptional = schema.optional
181
- const isNullish = schema.nullish
182
-
183
- const hasSelfRef = this.options.cyclicSchemas != null && ast.containsCircularRef(schema, { circularSchemas: this.options.cyclicSchemas })
184
- // Inside a getter the getter itself defers evaluation, so suppress
185
- // z.lazy() wrapping on nested refs by temporarily clearing cyclicSchemas.
186
- // Save before clearing: this.options === options (same reference via definePrinter),
187
- // so reading options.cyclicSchemas after mutation would return undefined.
188
- const savedCyclicSchemas = this.options.cyclicSchemas
189
- if (hasSelfRef) this.options.cyclicSchemas = undefined
190
- const baseOutput = this.transform(schema) ?? this.transform(ast.createSchema({ type: 'unknown' }))!
191
- if (hasSelfRef) this.options.cyclicSchemas = savedCyclicSchemas
192
-
193
- const wrappedOutput = this.options.wrapOutput ? this.options.wrapOutput({ output: baseOutput, schema }) || baseOutput : baseOutput
194
-
195
- const value = applyMiniModifiers({
196
- value: wrappedOutput,
197
- nullable: isNullable,
198
- optional: isOptional,
199
- nullish: isNullish,
200
- defaultValue: meta.default,
201
- })
202
-
203
- if (hasSelfRef) {
204
- return `get ${objectKey(propName)}() { return ${value} }`
205
- }
206
- return `${objectKey(propName)}: ${value}`
207
- })
208
-
209
- return `z.object(${buildObject(entries)})`
210
- },
211
- array(node) {
212
- const items = (node.items ?? []).map((item) => this.transform(item)).filter(Boolean)
213
- const inner = items.join(', ') || this.transform(ast.createSchema({ type: 'unknown' }))!
214
- const base = `z.array(${inner})${lengthChecksMini(node)}`
215
-
216
- return node.unique ? `${base}.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })` : base
217
- },
218
- tuple(node) {
219
- const items = (node.items ?? []).map((item) => this.transform(item)).filter(Boolean)
220
-
221
- return `z.tuple(${buildList(items)})`
222
- },
223
- union(node) {
224
- const nodeMembers = node.members ?? []
225
- const members = nodeMembers
226
- .map((memberNode) => {
227
- const member = this.transform(memberNode)
228
-
229
- return member && node.strategy === 'one' ? strictOneOfMember(member, memberNode) : member
230
- })
231
- .filter(Boolean)
232
- if (members.length === 0) return ''
233
- if (members.length === 1) return members[0]!
234
- if (node.discriminatorPropertyName && !nodeMembers.some((m) => m.type === 'intersection')) {
235
- // z.discriminatedUnion requires ZodObject members; intersections (ZodIntersection) are not
236
- // assignable to $ZodDiscriminant, so fall back to z.union when any member is an intersection.
237
- return `z.discriminatedUnion(${stringify(node.discriminatorPropertyName)}, ${buildList(members)})`
238
- }
239
-
240
- return `z.union(${buildList(members)})`
241
- },
242
- intersection(node) {
243
- const members = node.members ?? []
244
- if (members.length === 0) return ''
245
-
246
- const [first, ...rest] = members
247
- if (!first) return ''
248
-
249
- const firstBase = this.transform(first)
250
- if (!firstBase) return ''
251
-
252
- return rest.reduce((acc, member) => {
253
- const constraint = getMemberConstraintMini(member)
254
- if (constraint) return acc + constraint
255
- const transformed = this.transform(member)
256
- return transformed ? `z.intersection(${acc}, ${transformed})` : acc
257
- }, firstBase)
258
- },
259
- ...options.nodes,
260
- },
261
- print(node) {
262
- const { keysToOmit } = this.options
263
-
264
- const transformed = this.transform(node)
265
- if (!transformed) return null
266
-
267
- const meta = ast.syncSchemaRef(node)
268
-
269
- const base = (() => {
270
- if (!keysToOmit?.length || meta.primitive !== 'object' || (meta.type === 'union' && meta.discriminatorPropertyName)) return transformed
271
- // Mirror printerTs `nonNullable: true`: when omitting keys, the resulting
272
- // schema is a new non-nullable object type — skip optional/nullable/nullish.
273
- // Discriminated unions (z.discriminatedUnion) do not support .omit(), so skip them.
274
-
275
- // If this is a lazy reference, apply omit inside the lazy function
276
- const lazyMatch = transformed.match(/^z\.lazy\(\(\)\s*=>\s*(.+)\)$/)
277
- if (lazyMatch) return `z.lazy(() => ${lazyMatch[1]}.omit({ ${keysToOmit.map((k: string) => `"${k}": true`).join(', ')} }))`
278
- return `${transformed}.omit({ ${keysToOmit.map((k: string) => `"${k}": true`).join(', ')} })`
279
- })()
280
-
281
- return applyMiniModifiers({
282
- value: base,
283
- nullable: meta.nullable,
284
- optional: meta.optional,
285
- nullish: meta.nullish,
286
- defaultValue: meta.default,
287
- })
288
- },
289
- }
290
- })