@kubb/plugin-zod 5.0.0-beta.15 → 5.0.0-beta.22

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.
@@ -53,7 +53,7 @@ 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(() => …)`.
@@ -90,6 +90,13 @@ function strictOneOfMember(member: string, node: ast.SchemaNode): string {
90
90
  return member
91
91
  }
92
92
 
93
+ function getMemberConstraint(member: ast.SchemaNode): string | undefined {
94
+ if (member.primitive === 'string') return lengthConstraints(ast.narrowSchema(member, 'string') ?? {}) || undefined
95
+ if (member.primitive === 'number' || member.primitive === 'integer')
96
+ return numberConstraints(ast.narrowSchema(member, 'number') ?? ast.narrowSchema(member, 'integer') ?? {}) || undefined
97
+ if (member.primitive === 'array') return lengthConstraints(ast.narrowSchema(member, 'array') ?? {}) || undefined
98
+ }
99
+
93
100
  /**
94
101
  * Zod v4 printer built with `definePrinter`.
95
102
  *
@@ -184,7 +191,7 @@ export const printerZod = ast.definePrinter<PrinterZodFactory>((options) => {
184
191
  return `z.enum([${nonNullValues.map(formatLiteral).join(', ')}])`
185
192
  },
186
193
  ref(node) {
187
- if (!node.name) return undefined
194
+ if (!node.name) return null
188
195
  const refName = node.ref ? (ast.extractRefName(node.ref) ?? node.name) : node.name
189
196
  const resolvedName = node.ref ? (this.options.resolver?.default(refName, 'function') ?? refName) : node.name
190
197
 
@@ -220,10 +227,7 @@ export const printerZod = ast.definePrinter<PrinterZodFactory>((options) => {
220
227
  // When a property schema is not a ref but the metadata is from a ref (e.g., discriminator
221
228
  // property override), skip applying the description from the ref target to avoid applying
222
229
  // metadata from a replaced schema.
223
- let descriptionToApply = meta.description
224
- if (schema.type !== 'ref' && meta.type === 'ref') {
225
- descriptionToApply = undefined
226
- }
230
+ const descriptionToApply = schema.type !== 'ref' && meta.type === 'ref' ? undefined : meta.description
227
231
 
228
232
  const value = applyModifiers({
229
233
  value: wrappedOutput,
@@ -241,32 +245,27 @@ export const printerZod = ast.definePrinter<PrinterZodFactory>((options) => {
241
245
  })
242
246
  .join(',\n ')
243
247
 
244
- let result = `z.object({\n ${properties}\n })`
248
+ const objectBase = `z.object({\n ${properties}\n })`
245
249
 
246
250
  // Handle additionalProperties as .catchall() or .strict()
247
- if (node.additionalProperties && node.additionalProperties !== true) {
248
- const catchallType = this.transform(node.additionalProperties)
249
- if (catchallType) {
250
- result += `.catchall(${catchallType})`
251
+ const result = (() => {
252
+ if (node.additionalProperties && node.additionalProperties !== true) {
253
+ const catchallType = this.transform(node.additionalProperties)
254
+ return catchallType ? `${objectBase}.catchall(${catchallType})` : objectBase
251
255
  }
252
- } else if (node.additionalProperties === true) {
253
- result += `.catchall(${this.transform(ast.createSchema({ type: 'unknown' }))})`
254
- } else if (node.additionalProperties === false) {
255
- result += '.strict()'
256
- }
256
+ if (node.additionalProperties === true) return `${objectBase}.catchall(${this.transform(ast.createSchema({ type: 'unknown' }))})`
257
+ if (node.additionalProperties === false) return `${objectBase}.strict()`
258
+ return objectBase
259
+ })()
257
260
 
258
261
  return result
259
262
  },
260
263
  array(node) {
261
264
  const items = (node.items ?? []).map((item) => this.transform(item)).filter(Boolean)
262
265
  const inner = items.join(', ') || this.transform(ast.createSchema({ type: 'unknown' }))!
263
- let result = `z.array(${inner})${lengthConstraints(node)}`
266
+ const base = `z.array(${inner})${lengthConstraints(node)}`
264
267
 
265
- if (node.unique) {
266
- result += `.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })`
267
- }
268
-
269
- return result
268
+ return node.unique ? `${base}.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })` : base
270
269
  },
271
270
  tuple(node) {
272
271
  const items = (node.items ?? []).map((item) => this.transform(item)).filter(Boolean)
@@ -299,61 +298,37 @@ export const printerZod = ast.definePrinter<PrinterZodFactory>((options) => {
299
298
  const [first, ...rest] = members
300
299
  if (!first) return ''
301
300
 
302
- let base = this.transform(first)
303
- if (!base) return ''
301
+ const firstBase = this.transform(first)
302
+ if (!firstBase) return ''
304
303
 
305
- for (const member of rest) {
306
- if (member.primitive === 'string') {
307
- const s = ast.narrowSchema(member, 'string')
308
- const c = lengthConstraints(s ?? {})
309
- if (c) {
310
- base += c
311
- continue
312
- }
313
- } else if (member.primitive === 'number' || member.primitive === 'integer') {
314
- const n = ast.narrowSchema(member, 'number') ?? ast.narrowSchema(member, 'integer')
315
- const c = numberConstraints(n ?? {})
316
- if (c) {
317
- base += c
318
- continue
319
- }
320
- } else if (member.primitive === 'array') {
321
- const a = ast.narrowSchema(member, 'array')
322
- const c = lengthConstraints(a ?? {})
323
- if (c) {
324
- base += c
325
- continue
326
- }
327
- }
304
+ return rest.reduce((acc, member) => {
305
+ const constraint = getMemberConstraint(member)
306
+ if (constraint) return acc + constraint
328
307
  const transformed = this.transform(member)
329
- if (transformed) base = `${base}.and(${transformed})`
330
- }
331
-
332
- return base
308
+ return transformed ? `${acc}.and(${transformed})` : acc
309
+ }, firstBase)
333
310
  },
334
311
  ...options.nodes,
335
312
  },
336
313
  print(node) {
337
314
  const { keysToOmit } = this.options
338
315
 
339
- let base = this.transform(node)
340
- if (!base) return null
316
+ const transformed = this.transform(node)
317
+ if (!transformed) return null
341
318
 
342
319
  const meta = ast.syncSchemaRef(node)
343
320
 
344
- if (keysToOmit?.length && meta.primitive === 'object' && !(meta.type === 'union' && meta.discriminatorPropertyName)) {
321
+ const base = (() => {
322
+ if (!keysToOmit?.length || meta.primitive !== 'object' || (meta.type === 'union' && meta.discriminatorPropertyName)) return transformed
345
323
  // Mirror printerTs `nonNullable: true`: when omitting keys, the resulting
346
324
  // schema is a new non-nullable object type — skip optional/nullable/nullish.
347
325
  // Discriminated unions (z.discriminatedUnion) do not support .omit(), so skip them.
348
326
 
349
327
  // If this is a lazy reference, apply omit inside the lazy function
350
- const lazyMatch = base.match(/^z\.lazy\(\(\)\s*=>\s*(.+)\)$/)
351
- if (lazyMatch) {
352
- base = `z.lazy(() => ${lazyMatch[1]}.omit({ ${keysToOmit.map((k: string) => `"${k}": true`).join(', ')} }))`
353
- } else {
354
- base = `${base}.omit({ ${keysToOmit.map((k: string) => `"${k}": true`).join(', ')} })`
355
- }
356
- }
328
+ const lazyMatch = transformed.match(/^z\.lazy\(\(\)\s*=>\s*(.+)\)$/)
329
+ if (lazyMatch) return `z.lazy(() => ${lazyMatch[1]}.omit({ ${keysToOmit.map((k: string) => `"${k}": true`).join(', ')} }))`
330
+ return `${transformed}.omit({ ${keysToOmit.map((k: string) => `"${k}": true`).join(', ')} })`
331
+ })()
357
332
 
358
333
  return applyModifiers({
359
334
  value: base,
@@ -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(() => …)`.
@@ -70,6 +70,13 @@ function strictOneOfMember(member: string, node: ast.SchemaNode): string {
70
70
  return member
71
71
  }
72
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
+
73
80
  /**
74
81
  * Zod v4 **Mini** printer built with `definePrinter`.
75
82
  *
@@ -153,7 +160,7 @@ export const printerZodMini = ast.definePrinter<PrinterZodMiniFactory>((options)
153
160
  },
154
161
 
155
162
  ref(node) {
156
- if (!node.name) return undefined
163
+ if (!node.name) return null
157
164
  const refName = node.ref ? (ast.extractRefName(node.ref) ?? node.name) : node.name
158
165
  const resolvedName = node.ref ? (this.options.resolver?.default(refName, 'function') ?? refName) : node.name
159
166
 
@@ -206,13 +213,9 @@ export const printerZodMini = ast.definePrinter<PrinterZodMiniFactory>((options)
206
213
  array(node) {
207
214
  const items = (node.items ?? []).map((item) => this.transform(item)).filter(Boolean)
208
215
  const inner = items.join(', ') || this.transform(ast.createSchema({ type: 'unknown' }))!
209
- let result = `z.array(${inner})${lengthChecksMini(node)}`
216
+ const base = `z.array(${inner})${lengthChecksMini(node)}`
210
217
 
211
- if (node.unique) {
212
- result += `.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })`
213
- }
214
-
215
- return result
218
+ return node.unique ? `${base}.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })` : base
216
219
  },
217
220
  tuple(node) {
218
221
  const items = (node.items ?? []).map((item) => this.transform(item)).filter(Boolean)
@@ -245,61 +248,37 @@ export const printerZodMini = ast.definePrinter<PrinterZodMiniFactory>((options)
245
248
  const [first, ...rest] = members
246
249
  if (!first) return ''
247
250
 
248
- let base = this.transform(first)
249
- if (!base) return ''
251
+ const firstBase = this.transform(first)
252
+ if (!firstBase) return ''
250
253
 
251
- for (const member of rest) {
252
- if (member.primitive === 'string') {
253
- const s = ast.narrowSchema(member, 'string')
254
- const c = lengthChecksMini(s ?? {})
255
- if (c) {
256
- base += c
257
- continue
258
- }
259
- } else if (member.primitive === 'number' || member.primitive === 'integer') {
260
- const n = ast.narrowSchema(member, 'number') ?? ast.narrowSchema(member, 'integer')
261
- const c = numberChecksMini(n ?? {})
262
- if (c) {
263
- base += c
264
- continue
265
- }
266
- } else if (member.primitive === 'array') {
267
- const a = ast.narrowSchema(member, 'array')
268
- const c = lengthChecksMini(a ?? {})
269
- if (c) {
270
- base += c
271
- continue
272
- }
273
- }
254
+ return rest.reduce((acc, member) => {
255
+ const constraint = getMemberConstraintMini(member)
256
+ if (constraint) return acc + constraint
274
257
  const transformed = this.transform(member)
275
- if (transformed) base = `z.intersection(${base}, ${transformed})`
276
- }
277
-
278
- return base
258
+ return transformed ? `z.intersection(${acc}, ${transformed})` : acc
259
+ }, firstBase)
279
260
  },
280
261
  ...options.nodes,
281
262
  },
282
263
  print(node) {
283
264
  const { keysToOmit } = this.options
284
265
 
285
- let base = this.transform(node)
286
- if (!base) return null
266
+ const transformed = this.transform(node)
267
+ if (!transformed) return null
287
268
 
288
269
  const meta = ast.syncSchemaRef(node)
289
270
 
290
- if (keysToOmit?.length && meta.primitive === 'object' && !(meta.type === 'union' && meta.discriminatorPropertyName)) {
271
+ const base = (() => {
272
+ if (!keysToOmit?.length || meta.primitive !== 'object' || (meta.type === 'union' && meta.discriminatorPropertyName)) return transformed
291
273
  // Mirror printerTs `nonNullable: true`: when omitting keys, the resulting
292
274
  // schema is a new non-nullable object type — skip optional/nullable/nullish.
293
275
  // Discriminated unions (z.discriminatedUnion) do not support .omit(), so skip them.
294
276
 
295
277
  // If this is a lazy reference, apply omit inside the lazy function
296
- const lazyMatch = base.match(/^z\.lazy\(\(\)\s*=>\s*(.+)\)$/)
297
- if (lazyMatch) {
298
- base = `z.lazy(() => ${lazyMatch[1]}.omit({ ${keysToOmit.map((k: string) => `"${k}": true`).join(', ')} }))`
299
- } else {
300
- base = `${base}.omit({ ${keysToOmit.map((k: string) => `"${k}": true`).join(', ')} })`
301
- }
302
- }
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
+ })()
303
282
 
304
283
  return applyMiniModifiers({
305
284
  value: base,
package/src/utils.ts CHANGED
@@ -158,21 +158,14 @@ export function lengthChecksMini({ min, max, pattern }: LengthConstraints): stri
158
158
  * to a schema value string using the chainable Zod v4 API.
159
159
  */
160
160
  export function applyModifiers({ value, nullable, optional, nullish, defaultValue, description }: ModifierOptions): string {
161
- let result = value
162
- if (nullish || (nullable && optional)) {
163
- result = `${result}.nullish()`
164
- } else if (optional) {
165
- result = `${result}.optional()`
166
- } else if (nullable) {
167
- result = `${result}.nullable()`
168
- }
169
- if (defaultValue !== undefined) {
170
- result = `${result}.default(${formatDefault(defaultValue)})`
171
- }
172
- if (description) {
173
- result = `${result}.describe(${stringify(description)})`
174
- }
175
- return result
161
+ const withModifier = (() => {
162
+ if (nullish || (nullable && optional)) return `${value}.nullish()`
163
+ if (optional) return `${value}.optional()`
164
+ if (nullable) return `${value}.nullable()`
165
+ return value
166
+ })()
167
+ const withDefault = defaultValue !== undefined ? `${withModifier}.default(${formatDefault(defaultValue)})` : withModifier
168
+ return description ? `${withDefault}.describe(${stringify(description)})` : withDefault
176
169
  }
177
170
 
178
171
  /**
@@ -180,21 +173,12 @@ export function applyModifiers({ value, nullable, optional, nullish, defaultValu
180
173
  * (`z.nullable()`, `z.optional()`, `z.nullish()`).
181
174
  */
182
175
  export function applyMiniModifiers({ value, nullable, optional, nullish, defaultValue }: Omit<ModifierOptions, 'description'>): string {
183
- let result = value
184
- if (nullish) {
185
- result = `z.nullish(${result})`
186
- } else {
187
- if (nullable) {
188
- result = `z.nullable(${result})`
189
- }
190
- if (optional) {
191
- result = `z.optional(${result})`
192
- }
193
- }
194
- if (defaultValue !== undefined) {
195
- result = `z._default(${result}, ${formatDefault(defaultValue)})`
196
- }
197
- return result
176
+ const withModifier = (() => {
177
+ if (nullish) return `z.nullish(${value})`
178
+ const withNullable = nullable ? `z.nullable(${value})` : value
179
+ return optional ? `z.optional(${withNullable})` : withNullable
180
+ })()
181
+ return defaultValue !== undefined ? `z._default(${withModifier}, ${formatDefault(defaultValue)})` : withModifier
198
182
  }
199
183
 
200
184
  type BuildGroupedParamsSchemaOptions = {