@kubb/plugin-zod 5.0.0-beta.4 → 5.0.0-beta.56
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/README.md +39 -22
- package/dist/index.cjs +684 -301
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +115 -49
- package/dist/index.js +673 -296
- package/dist/index.js.map +1 -1
- package/package.json +13 -23
- package/src/components/Operations.tsx +7 -6
- package/src/components/Zod.tsx +1 -1
- package/src/generators/zodGenerator.tsx +213 -62
- package/src/plugin.ts +24 -21
- package/src/printers/printerZod.ts +131 -110
- package/src/printers/printerZodMini.ts +82 -87
- package/src/resolvers/resolverZod.ts +33 -19
- package/src/types.ts +64 -32
- package/src/utils.ts +114 -37
- package/extension.yaml +0 -502
- /package/dist/{chunk--u3MIqq1.js → chunk-C0LytTxp.js} +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { stringify } from '@
|
|
1
|
+
import { buildList, buildObject, extractRefName, objectKey, stringify } from '@kubb/ast/utils'
|
|
2
2
|
|
|
3
3
|
import { ast } from '@kubb/core'
|
|
4
4
|
import type { PluginZod, ResolverZod } from '../types.ts'
|
|
5
|
-
import { applyModifiers, formatLiteral, lengthConstraints, numberConstraints, shouldCoerce } from '../utils.ts'
|
|
5
|
+
import { applyModifiers, containsCodec, formatLiteral, getCodec, lengthConstraints, numberConstraints, shouldCoerce } from '../utils.ts'
|
|
6
6
|
import type { AdapterOas } from '@kubb/adapter-oas'
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -53,12 +53,21 @@ export type PrinterZodOptions = {
|
|
|
53
53
|
/**
|
|
54
54
|
* Properties to exclude using `.omit({ key: true })`.
|
|
55
55
|
*/
|
|
56
|
-
keysToOmit?: Array<string>
|
|
56
|
+
keysToOmit?: Array<string> | null
|
|
57
57
|
/**
|
|
58
58
|
* Schema names that form circular dependency chains.
|
|
59
59
|
* Properties referencing these emit lazy getters wrapping refs in `z.lazy(() => …)`.
|
|
60
60
|
*/
|
|
61
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'
|
|
62
71
|
/**
|
|
63
72
|
* Custom handler map for node type overrides.
|
|
64
73
|
*/
|
|
@@ -70,6 +79,33 @@ export type PrinterZodOptions = {
|
|
|
70
79
|
*/
|
|
71
80
|
export type PrinterZodFactory = ast.PrinterFactoryOptions<'zod', PrinterZodOptions, string, string>
|
|
72
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
|
+
|
|
73
109
|
/**
|
|
74
110
|
* Zod v4 printer built with `definePrinter`.
|
|
75
111
|
*
|
|
@@ -112,11 +148,13 @@ export const printerZod = ast.definePrinter<PrinterZodFactory>((options) => {
|
|
|
112
148
|
return shouldCoerce(this.options.coercion, 'numbers') ? 'z.coerce.bigint()' : 'z.bigint()'
|
|
113
149
|
},
|
|
114
150
|
date(node) {
|
|
115
|
-
|
|
116
|
-
|
|
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)
|
|
117
155
|
}
|
|
118
156
|
|
|
119
|
-
return
|
|
157
|
+
return 'z.iso.date()'
|
|
120
158
|
},
|
|
121
159
|
datetime(node) {
|
|
122
160
|
const offset = node.offset || this.options.dateType === 'stringOffset'
|
|
@@ -164,9 +202,17 @@ export const printerZod = ast.definePrinter<PrinterZodFactory>((options) => {
|
|
|
164
202
|
return `z.enum([${nonNullValues.map(formatLiteral).join(', ')}])`
|
|
165
203
|
},
|
|
166
204
|
ref(node) {
|
|
167
|
-
if (!node.name) return
|
|
168
|
-
const refName = node.ref ? (
|
|
169
|
-
|
|
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
|
|
170
216
|
|
|
171
217
|
if (node.ref && this.options.cyclicSchemas?.has(refName)) {
|
|
172
218
|
return `z.lazy(() => ${resolvedName})`
|
|
@@ -175,93 +221,92 @@ export const printerZod = ast.definePrinter<PrinterZodFactory>((options) => {
|
|
|
175
221
|
return resolvedName
|
|
176
222
|
},
|
|
177
223
|
object(node) {
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
description: descriptionToApply,
|
|
212
|
-
})
|
|
213
|
-
|
|
214
|
-
if (hasSelfRef) {
|
|
215
|
-
return `get "${propName}"() { return ${value} }`
|
|
216
|
-
}
|
|
217
|
-
return `"${propName}": ${value}`
|
|
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,
|
|
218
257
|
})
|
|
219
|
-
.join(',\n ')
|
|
220
258
|
|
|
221
|
-
|
|
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)})`
|
|
222
266
|
|
|
223
267
|
// Handle additionalProperties as .catchall() or .strict()
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
268
|
+
const result = (() => {
|
|
269
|
+
if (node.additionalProperties && node.additionalProperties !== true) {
|
|
270
|
+
const catchallType = this.transform(node.additionalProperties)
|
|
271
|
+
return catchallType ? `${objectBase}.catchall(${catchallType})` : objectBase
|
|
228
272
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
}
|
|
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
|
+
})()
|
|
234
277
|
|
|
235
278
|
return result
|
|
236
279
|
},
|
|
237
280
|
array(node) {
|
|
238
281
|
const items = (node.items ?? []).map((item) => this.transform(item)).filter(Boolean)
|
|
239
282
|
const inner = items.join(', ') || this.transform(ast.createSchema({ type: 'unknown' }))!
|
|
240
|
-
|
|
283
|
+
const base = `z.array(${inner})${lengthConstraints(node)}`
|
|
241
284
|
|
|
242
|
-
|
|
243
|
-
result += `.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })`
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
return result
|
|
285
|
+
return node.unique ? `${base}.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })` : base
|
|
247
286
|
},
|
|
248
287
|
tuple(node) {
|
|
249
288
|
const items = (node.items ?? []).map((item) => this.transform(item)).filter(Boolean)
|
|
250
289
|
|
|
251
|
-
return `z.tuple(
|
|
290
|
+
return `z.tuple(${buildList(items)})`
|
|
252
291
|
},
|
|
253
292
|
union(node) {
|
|
254
293
|
const nodeMembers = node.members ?? []
|
|
255
|
-
const members = nodeMembers
|
|
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)
|
|
256
301
|
if (members.length === 0) return ''
|
|
257
302
|
if (members.length === 1) return members[0]!
|
|
258
303
|
if (node.discriminatorPropertyName && !nodeMembers.some((m) => m.type === 'intersection')) {
|
|
259
304
|
// z.discriminatedUnion requires ZodObject members; intersections (ZodIntersection) are not
|
|
260
305
|
// assignable to $ZodDiscriminant, so fall back to z.union when any member is an intersection.
|
|
261
|
-
return `z.discriminatedUnion(${stringify(node.discriminatorPropertyName)},
|
|
306
|
+
return `z.discriminatedUnion(${stringify(node.discriminatorPropertyName)}, ${buildList(members)})`
|
|
262
307
|
}
|
|
263
308
|
|
|
264
|
-
return `z.union(
|
|
309
|
+
return `z.union(${buildList(members)})`
|
|
265
310
|
},
|
|
266
311
|
intersection(node) {
|
|
267
312
|
const members = node.members ?? []
|
|
@@ -270,61 +315,37 @@ export const printerZod = ast.definePrinter<PrinterZodFactory>((options) => {
|
|
|
270
315
|
const [first, ...rest] = members
|
|
271
316
|
if (!first) return ''
|
|
272
317
|
|
|
273
|
-
|
|
274
|
-
if (!
|
|
275
|
-
|
|
276
|
-
for (const member of rest) {
|
|
277
|
-
if (member.primitive === 'string') {
|
|
278
|
-
const s = ast.narrowSchema(member, 'string')
|
|
279
|
-
const c = lengthConstraints(s ?? {})
|
|
280
|
-
if (c) {
|
|
281
|
-
base += c
|
|
282
|
-
continue
|
|
283
|
-
}
|
|
284
|
-
} else if (member.primitive === 'number' || member.primitive === 'integer') {
|
|
285
|
-
const n = ast.narrowSchema(member, 'number') ?? ast.narrowSchema(member, 'integer')
|
|
286
|
-
const c = numberConstraints(n ?? {})
|
|
287
|
-
if (c) {
|
|
288
|
-
base += c
|
|
289
|
-
continue
|
|
290
|
-
}
|
|
291
|
-
} else if (member.primitive === 'array') {
|
|
292
|
-
const a = ast.narrowSchema(member, 'array')
|
|
293
|
-
const c = lengthConstraints(a ?? {})
|
|
294
|
-
if (c) {
|
|
295
|
-
base += c
|
|
296
|
-
continue
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
const transformed = this.transform(member)
|
|
300
|
-
if (transformed) base = `${base}.and(${transformed})`
|
|
301
|
-
}
|
|
318
|
+
const firstBase = this.transform(first)
|
|
319
|
+
if (!firstBase) return ''
|
|
302
320
|
|
|
303
|
-
return
|
|
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)
|
|
304
327
|
},
|
|
305
328
|
...options.nodes,
|
|
306
329
|
},
|
|
307
330
|
print(node) {
|
|
308
331
|
const { keysToOmit } = this.options
|
|
309
332
|
|
|
310
|
-
|
|
311
|
-
if (!
|
|
333
|
+
const transformed = this.transform(node)
|
|
334
|
+
if (!transformed) return null
|
|
312
335
|
|
|
313
336
|
const meta = ast.syncSchemaRef(node)
|
|
314
337
|
|
|
315
|
-
|
|
338
|
+
const base = (() => {
|
|
339
|
+
if (!keysToOmit?.length || meta.primitive !== 'object' || (meta.type === 'union' && meta.discriminatorPropertyName)) return transformed
|
|
316
340
|
// Mirror printerTs `nonNullable: true`: when omitting keys, the resulting
|
|
317
341
|
// schema is a new non-nullable object type — skip optional/nullable/nullish.
|
|
318
342
|
// Discriminated unions (z.discriminatedUnion) do not support .omit(), so skip them.
|
|
319
343
|
|
|
320
344
|
// If this is a lazy reference, apply omit inside the lazy function
|
|
321
|
-
const lazyMatch =
|
|
322
|
-
if (lazyMatch) {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
base = `${base}.omit({ ${keysToOmit.map((k: string) => `"${k}": true`).join(', ')} })`
|
|
326
|
-
}
|
|
327
|
-
}
|
|
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
|
+
})()
|
|
328
349
|
|
|
329
350
|
return applyModifiers({
|
|
330
351
|
value: base,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { stringify } from '@
|
|
1
|
+
import { buildList, buildObject, extractRefName, objectKey, stringify } from '@kubb/ast/utils'
|
|
2
2
|
|
|
3
3
|
import { ast } from '@kubb/core'
|
|
4
4
|
import type { PluginZod, ResolverZod } from '../types.ts'
|
|
@@ -45,7 +45,7 @@ export type PrinterZodMiniOptions = {
|
|
|
45
45
|
/**
|
|
46
46
|
* Properties to exclude using `.omit({ key: true })`.
|
|
47
47
|
*/
|
|
48
|
-
keysToOmit?: Array<string>
|
|
48
|
+
keysToOmit?: Array<string> | null
|
|
49
49
|
/**
|
|
50
50
|
* Schema names that form circular dependency chains.
|
|
51
51
|
* Properties referencing these emit lazy getters wrapping refs in `z.lazy(() => …)`.
|
|
@@ -61,6 +61,22 @@ export type PrinterZodMiniOptions = {
|
|
|
61
61
|
* Factory options for the Zod Mini printer, defining input/output types and configuration.
|
|
62
62
|
*/
|
|
63
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
|
+
|
|
64
80
|
/**
|
|
65
81
|
* Zod v4 **Mini** printer built with `definePrinter`.
|
|
66
82
|
*
|
|
@@ -144,8 +160,8 @@ export const printerZodMini = ast.definePrinter<PrinterZodMiniFactory>((options)
|
|
|
144
160
|
},
|
|
145
161
|
|
|
146
162
|
ref(node) {
|
|
147
|
-
if (!node.name) return
|
|
148
|
-
const refName = node.ref ? (
|
|
163
|
+
if (!node.name) return null
|
|
164
|
+
const refName = node.ref ? (extractRefName(node.ref) ?? node.name) : node.name
|
|
149
165
|
const resolvedName = node.ref ? (this.options.resolver?.default(refName, 'function') ?? refName) : node.name
|
|
150
166
|
|
|
151
167
|
if (node.ref && this.options.cyclicSchemas?.has(refName)) {
|
|
@@ -155,70 +171,73 @@ export const printerZodMini = ast.definePrinter<PrinterZodMiniFactory>((options)
|
|
|
155
171
|
return resolvedName
|
|
156
172
|
},
|
|
157
173
|
object(node) {
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
if (hasSelfRef) {
|
|
186
|
-
return `get "${propName}"() { return ${value} }`
|
|
187
|
-
}
|
|
188
|
-
return `"${propName}": ${value}`
|
|
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,
|
|
189
201
|
})
|
|
190
|
-
.join(',\n ')
|
|
191
202
|
|
|
192
|
-
|
|
203
|
+
if (hasSelfRef) {
|
|
204
|
+
return `get ${objectKey(propName)}() { return ${value} }`
|
|
205
|
+
}
|
|
206
|
+
return `${objectKey(propName)}: ${value}`
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
return `z.object(${buildObject(entries)})`
|
|
193
210
|
},
|
|
194
211
|
array(node) {
|
|
195
212
|
const items = (node.items ?? []).map((item) => this.transform(item)).filter(Boolean)
|
|
196
213
|
const inner = items.join(', ') || this.transform(ast.createSchema({ type: 'unknown' }))!
|
|
197
|
-
|
|
214
|
+
const base = `z.array(${inner})${lengthChecksMini(node)}`
|
|
198
215
|
|
|
199
|
-
|
|
200
|
-
result += `.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })`
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
return result
|
|
216
|
+
return node.unique ? `${base}.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })` : base
|
|
204
217
|
},
|
|
205
218
|
tuple(node) {
|
|
206
219
|
const items = (node.items ?? []).map((item) => this.transform(item)).filter(Boolean)
|
|
207
220
|
|
|
208
|
-
return `z.tuple(
|
|
221
|
+
return `z.tuple(${buildList(items)})`
|
|
209
222
|
},
|
|
210
223
|
union(node) {
|
|
211
224
|
const nodeMembers = node.members ?? []
|
|
212
|
-
const members = nodeMembers
|
|
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)
|
|
213
232
|
if (members.length === 0) return ''
|
|
214
233
|
if (members.length === 1) return members[0]!
|
|
215
234
|
if (node.discriminatorPropertyName && !nodeMembers.some((m) => m.type === 'intersection')) {
|
|
216
235
|
// z.discriminatedUnion requires ZodObject members; intersections (ZodIntersection) are not
|
|
217
236
|
// assignable to $ZodDiscriminant, so fall back to z.union when any member is an intersection.
|
|
218
|
-
return `z.discriminatedUnion(${stringify(node.discriminatorPropertyName)},
|
|
237
|
+
return `z.discriminatedUnion(${stringify(node.discriminatorPropertyName)}, ${buildList(members)})`
|
|
219
238
|
}
|
|
220
239
|
|
|
221
|
-
return `z.union(
|
|
240
|
+
return `z.union(${buildList(members)})`
|
|
222
241
|
},
|
|
223
242
|
intersection(node) {
|
|
224
243
|
const members = node.members ?? []
|
|
@@ -227,61 +246,37 @@ export const printerZodMini = ast.definePrinter<PrinterZodMiniFactory>((options)
|
|
|
227
246
|
const [first, ...rest] = members
|
|
228
247
|
if (!first) return ''
|
|
229
248
|
|
|
230
|
-
|
|
231
|
-
if (!
|
|
232
|
-
|
|
233
|
-
for (const member of rest) {
|
|
234
|
-
if (member.primitive === 'string') {
|
|
235
|
-
const s = ast.narrowSchema(member, 'string')
|
|
236
|
-
const c = lengthChecksMini(s ?? {})
|
|
237
|
-
if (c) {
|
|
238
|
-
base += c
|
|
239
|
-
continue
|
|
240
|
-
}
|
|
241
|
-
} else if (member.primitive === 'number' || member.primitive === 'integer') {
|
|
242
|
-
const n = ast.narrowSchema(member, 'number') ?? ast.narrowSchema(member, 'integer')
|
|
243
|
-
const c = numberChecksMini(n ?? {})
|
|
244
|
-
if (c) {
|
|
245
|
-
base += c
|
|
246
|
-
continue
|
|
247
|
-
}
|
|
248
|
-
} else if (member.primitive === 'array') {
|
|
249
|
-
const a = ast.narrowSchema(member, 'array')
|
|
250
|
-
const c = lengthChecksMini(a ?? {})
|
|
251
|
-
if (c) {
|
|
252
|
-
base += c
|
|
253
|
-
continue
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
const transformed = this.transform(member)
|
|
257
|
-
if (transformed) base = `z.intersection(${base}, ${transformed})`
|
|
258
|
-
}
|
|
249
|
+
const firstBase = this.transform(first)
|
|
250
|
+
if (!firstBase) return ''
|
|
259
251
|
|
|
260
|
-
return
|
|
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)
|
|
261
258
|
},
|
|
262
259
|
...options.nodes,
|
|
263
260
|
},
|
|
264
261
|
print(node) {
|
|
265
262
|
const { keysToOmit } = this.options
|
|
266
263
|
|
|
267
|
-
|
|
268
|
-
if (!
|
|
264
|
+
const transformed = this.transform(node)
|
|
265
|
+
if (!transformed) return null
|
|
269
266
|
|
|
270
267
|
const meta = ast.syncSchemaRef(node)
|
|
271
268
|
|
|
272
|
-
|
|
269
|
+
const base = (() => {
|
|
270
|
+
if (!keysToOmit?.length || meta.primitive !== 'object' || (meta.type === 'union' && meta.discriminatorPropertyName)) return transformed
|
|
273
271
|
// Mirror printerTs `nonNullable: true`: when omitting keys, the resulting
|
|
274
272
|
// schema is a new non-nullable object type — skip optional/nullable/nullish.
|
|
275
273
|
// Discriminated unions (z.discriminatedUnion) do not support .omit(), so skip them.
|
|
276
274
|
|
|
277
275
|
// If this is a lazy reference, apply omit inside the lazy function
|
|
278
|
-
const lazyMatch =
|
|
279
|
-
if (lazyMatch) {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
base = `${base}.omit({ ${keysToOmit.map((k: string) => `"${k}": true`).join(', ')} })`
|
|
283
|
-
}
|
|
284
|
-
}
|
|
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
|
+
})()
|
|
285
280
|
|
|
286
281
|
return applyMiniModifiers({
|
|
287
282
|
value: base,
|