@kubb/plugin-zod 5.0.0-alpha.23 → 5.0.0-alpha.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/index.cjs +1673 -88
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.ts +317 -2
  4. package/dist/index.js +1646 -88
  5. package/dist/index.js.map +1 -1
  6. package/package.json +5 -33
  7. package/src/components/Operations.tsx +22 -15
  8. package/src/components/Zod.tsx +18 -118
  9. package/src/components/ZodMini.tsx +41 -0
  10. package/src/constants.ts +5 -0
  11. package/src/generators/zodGenerator.tsx +165 -158
  12. package/src/generators/zodGeneratorLegacy.tsx +401 -0
  13. package/src/index.ts +11 -1
  14. package/src/plugin.ts +105 -129
  15. package/src/presets.ts +25 -0
  16. package/src/printers/printerZod.ts +271 -0
  17. package/src/printers/printerZodMini.ts +246 -0
  18. package/src/resolvers/resolverZod.ts +71 -0
  19. package/src/resolvers/resolverZodLegacy.ts +60 -0
  20. package/src/types.ts +121 -92
  21. package/src/utils.ts +248 -0
  22. package/dist/components-DW4Q2yVq.js +0 -868
  23. package/dist/components-DW4Q2yVq.js.map +0 -1
  24. package/dist/components-qFUGs0vU.cjs +0 -916
  25. package/dist/components-qFUGs0vU.cjs.map +0 -1
  26. package/dist/components.cjs +0 -4
  27. package/dist/components.d.ts +0 -56
  28. package/dist/components.js +0 -2
  29. package/dist/generators-C9BCTLXg.cjs +0 -301
  30. package/dist/generators-C9BCTLXg.cjs.map +0 -1
  31. package/dist/generators-maqx12yN.js +0 -290
  32. package/dist/generators-maqx12yN.js.map +0 -1
  33. package/dist/generators.cjs +0 -4
  34. package/dist/generators.d.ts +0 -12
  35. package/dist/generators.js +0 -2
  36. package/dist/templates/ToZod.source.cjs +0 -7
  37. package/dist/templates/ToZod.source.cjs.map +0 -1
  38. package/dist/templates/ToZod.source.d.ts +0 -7
  39. package/dist/templates/ToZod.source.js +0 -6
  40. package/dist/templates/ToZod.source.js.map +0 -1
  41. package/dist/types-CClg-ikj.d.ts +0 -172
  42. package/src/components/index.ts +0 -2
  43. package/src/generators/index.ts +0 -2
  44. package/src/generators/operationsGenerator.tsx +0 -50
  45. package/src/parser.ts +0 -952
  46. package/src/templates/ToZod.source.ts +0 -4
  47. package/templates/ToZod.ts +0 -61
package/src/parser.ts DELETED
@@ -1,952 +0,0 @@
1
- import { stringify, toRegExpString } from '@internals/utils'
2
- import type { Schema, SchemaMapper } from '@kubb/plugin-oas'
3
- import { createParser, findSchemaKeyword, isKeyword, SchemaGenerator, type SchemaKeywordMapper, schemaKeywords } from '@kubb/plugin-oas'
4
- import { sortBy } from 'remeda'
5
-
6
- //TODO add zodKeywordMapper as function that returns 3 versions: v3, v4 and v4 mini, this can also be used to have the custom mapping(see object type)
7
- // also include shouldCoerce
8
-
9
- /**
10
- * Helper to build string/array length constraint checks for Zod Mini mode
11
- */
12
- function buildLengthChecks(min?: number, max?: number): string[] {
13
- const checks: string[] = []
14
- if (min !== undefined) checks.push(`z.minLength(${min})`)
15
- if (max !== undefined) checks.push(`z.maxLength(${max})`)
16
- return checks
17
- }
18
-
19
- const zodKeywordMapper = {
20
- any: () => 'z.any()',
21
- unknown: () => 'z.unknown()',
22
- void: () => 'z.void()',
23
- number: (coercion?: boolean, min?: number, max?: number, exclusiveMinimum?: number, exclusiveMaximum?: number, mini?: boolean) => {
24
- if (mini) {
25
- const checks: string[] = []
26
- if (min !== undefined) checks.push(`z.minimum(${min})`)
27
- if (max !== undefined) checks.push(`z.maximum(${max})`)
28
- if (exclusiveMinimum !== undefined) checks.push(`z.minimum(${exclusiveMinimum}, { exclusive: true })`)
29
- if (exclusiveMaximum !== undefined) checks.push(`z.maximum(${exclusiveMaximum}, { exclusive: true })`)
30
- if (checks.length > 0) {
31
- return `z.number().check(${checks.join(', ')})`
32
- }
33
- return 'z.number()'
34
- }
35
- return [
36
- coercion ? 'z.coerce.number()' : 'z.number()',
37
- min !== undefined ? `.min(${min})` : undefined,
38
- max !== undefined ? `.max(${max})` : undefined,
39
- exclusiveMinimum !== undefined ? `.gt(${exclusiveMinimum})` : undefined,
40
- exclusiveMaximum !== undefined ? `.lt(${exclusiveMaximum})` : undefined,
41
- ]
42
- .filter(Boolean)
43
- .join('')
44
- },
45
- integer: (coercion?: boolean, min?: number, max?: number, version: '3' | '4' = '3', exclusiveMinimum?: number, exclusiveMaximum?: number, mini?: boolean) => {
46
- if (mini) {
47
- const checks: string[] = []
48
- if (min !== undefined) checks.push(`z.minimum(${min})`)
49
- if (max !== undefined) checks.push(`z.maximum(${max})`)
50
- if (exclusiveMinimum !== undefined) checks.push(`z.minimum(${exclusiveMinimum}, { exclusive: true })`)
51
- if (exclusiveMaximum !== undefined) checks.push(`z.maximum(${exclusiveMaximum}, { exclusive: true })`)
52
- if (checks.length > 0) {
53
- return `z.int().check(${checks.join(', ')})`
54
- }
55
- return 'z.int()'
56
- }
57
- return [
58
- coercion ? 'z.coerce.number().int()' : version === '4' ? 'z.int()' : 'z.number().int()',
59
- min !== undefined ? `.min(${min})` : undefined,
60
- max !== undefined ? `.max(${max})` : undefined,
61
- exclusiveMinimum !== undefined ? `.gt(${exclusiveMinimum})` : undefined,
62
- exclusiveMaximum !== undefined ? `.lt(${exclusiveMaximum})` : undefined,
63
- ]
64
- .filter(Boolean)
65
- .join('')
66
- },
67
- bigint: (coercion?: boolean) => (coercion ? 'z.coerce.bigint()' : 'z.bigint()'),
68
- interface: (value?: string, strict?: boolean) => {
69
- if (strict) {
70
- return `z.strictInterface({
71
- ${value}
72
- })`
73
- }
74
- return `z.interface({
75
- ${value}
76
- })`
77
- },
78
- object: (value?: string, strict?: boolean, version: '3' | '4' = '3') => {
79
- if (version === '4' && strict) {
80
- return `z.strictObject({
81
- ${value}
82
- })`
83
- }
84
-
85
- if (strict) {
86
- return `z.object({
87
- ${value}
88
- }).strict()`
89
- }
90
-
91
- return `z.object({
92
- ${value}
93
- })`
94
- },
95
- string: (coercion?: boolean, min?: number, max?: number, mini?: boolean) => {
96
- if (mini) {
97
- const checks = buildLengthChecks(min, max)
98
- if (checks.length > 0) {
99
- return `z.string().check(${checks.join(', ')})`
100
- }
101
- return 'z.string()'
102
- }
103
- return [coercion ? 'z.coerce.string()' : 'z.string()', min !== undefined ? `.min(${min})` : undefined, max !== undefined ? `.max(${max})` : undefined]
104
- .filter(Boolean)
105
- .join('')
106
- },
107
- //support for discriminatedUnion
108
- boolean: () => 'z.boolean()',
109
- undefined: () => 'z.undefined()',
110
- nullable: (value?: string) => {
111
- if (value) {
112
- return `z.nullable(${value})`
113
- }
114
- return '.nullable()'
115
- },
116
- null: () => 'z.null()',
117
- nullish: (value?: string) => {
118
- if (value) {
119
- return `z.nullish(${value})`
120
- }
121
- return '.nullish()'
122
- },
123
- array: (items: string[] = [], min?: number, max?: number, unique?: boolean, mini?: boolean) => {
124
- if (mini) {
125
- const checks = buildLengthChecks(min, max)
126
- if (unique) checks.push(`z.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })`)
127
- if (checks.length > 0) {
128
- return `z.array(${items?.join('')}).check(${checks.join(', ')})`
129
- }
130
- return `z.array(${items?.join('')})`
131
- }
132
- return [
133
- `z.array(${items?.join('')})`,
134
- min !== undefined ? `.min(${min})` : undefined,
135
- max !== undefined ? `.max(${max})` : undefined,
136
- unique ? `.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })` : undefined,
137
- ]
138
- .filter(Boolean)
139
- .join('')
140
- },
141
- tuple: (items: string[] = []) => `z.tuple([${items?.join(', ')}])`,
142
- enum: (items: string[] = []) => `z.enum([${items?.join(', ')}])`,
143
- union: (items: string[] = []) => `z.union([${items?.join(', ')}])`,
144
- const: (value?: string | number | boolean) => `z.literal(${value ?? ''})`,
145
- /**
146
- * ISO 8601
147
- */
148
- datetime: (offset = false, local = false, version: '3' | '4' = '3', mini?: boolean) => {
149
- // Zod Mini doesn't support .datetime() method, use plain string
150
- if (mini) {
151
- return 'z.string()'
152
- }
153
-
154
- if (offset) {
155
- return version === '4' ? `z.iso.datetime({ offset: ${offset} })` : `z.string().datetime({ offset: ${offset} })`
156
- }
157
-
158
- if (local) {
159
- return version === '4' ? `z.iso.datetime({ local: ${local} })` : `z.string().datetime({ local: ${local} })`
160
- }
161
-
162
- return version === '4' ? 'z.iso.datetime()' : 'z.string().datetime()'
163
- },
164
- /**
165
- * Type `'date'` Date
166
- * Type `'string'` ISO date format (YYYY-MM-DD)
167
- * @default ISO date format (YYYY-MM-DD)
168
- */
169
- date: (type: 'date' | 'string' = 'string', coercion?: boolean, version: '3' | '4' = '3') => {
170
- if (type === 'string') {
171
- return version === '4' ? 'z.iso.date()' : 'z.string().date()'
172
- }
173
-
174
- if (coercion) {
175
- return 'z.coerce.date()'
176
- }
177
-
178
- return 'z.date()'
179
- },
180
- /**
181
- * Type `'date'` Date
182
- * Type `'string'` ISO time format (HH:mm:ss[.SSSSSS])
183
- * @default ISO time format (HH:mm:ss[.SSSSSS])
184
- */
185
- time: (type: 'date' | 'string' = 'string', coercion?: boolean, version: '3' | '4' = '3') => {
186
- if (type === 'string') {
187
- return version === '4' ? 'z.iso.time()' : 'z.string().time()'
188
- }
189
-
190
- if (coercion) {
191
- return 'z.coerce.date()'
192
- }
193
-
194
- return 'z.date()'
195
- },
196
- uuid: ({
197
- coercion,
198
- version = '3',
199
- guidType = 'uuid',
200
- min,
201
- max,
202
- mini,
203
- }: {
204
- coercion?: boolean
205
- version?: '3' | '4'
206
- guidType?: 'uuid' | 'guid'
207
- min?: number
208
- max?: number
209
- mini?: boolean
210
- } = {}) => {
211
- const zodGuidType = version === '4' && guidType === 'guid' ? 'guid' : 'uuid'
212
-
213
- if (mini) {
214
- const checks = buildLengthChecks(min, max)
215
- if (checks.length > 0) {
216
- return `z.${zodGuidType}().check(${checks.join(', ')})`
217
- }
218
- return `z.${zodGuidType}()`
219
- }
220
-
221
- const zodV4UuidSchema = `z.${zodGuidType}()`
222
-
223
- return [
224
- coercion ? (version === '4' ? zodV4UuidSchema : 'z.coerce.string().uuid()') : version === '4' ? zodV4UuidSchema : 'z.string().uuid()',
225
- min !== undefined ? `.min(${min})` : undefined,
226
- max !== undefined ? `.max(${max})` : undefined,
227
- ]
228
- .filter(Boolean)
229
- .join('')
230
- },
231
- url: (coercion?: boolean, version: '3' | '4' = '3', min?: number, max?: number, mini?: boolean) => {
232
- if (mini) {
233
- const checks = buildLengthChecks(min, max)
234
- if (checks.length > 0) {
235
- return `z.url().check(${checks.join(', ')})`
236
- }
237
- return 'z.url()'
238
- }
239
- return [
240
- coercion ? (version === '4' ? 'z.url()' : 'z.coerce.string().url()') : version === '4' ? 'z.url()' : 'z.string().url()',
241
- min !== undefined ? `.min(${min})` : undefined,
242
- max !== undefined ? `.max(${max})` : undefined,
243
- ]
244
- .filter(Boolean)
245
- .join('')
246
- },
247
- default: (value?: string | number | boolean | object, innerSchema?: string, mini?: boolean, isBigInt?: boolean) => {
248
- if (mini && innerSchema) {
249
- // Wrap numeric values in BigInt() for bigint types in mini mode
250
- const defaultValue = isBigInt && typeof value === 'number' ? `BigInt(${value})` : typeof value === 'object' ? '{}' : (value ?? '')
251
- return `z._default(${innerSchema}, ${defaultValue})`
252
- }
253
-
254
- if (typeof value === 'object') {
255
- return '.default({})'
256
- }
257
-
258
- if (value === undefined) {
259
- return '.default()'
260
- }
261
-
262
- if (typeof value === 'string' && !value) {
263
- return `.default('')`
264
- }
265
-
266
- // Wrap numeric values in BigInt() for bigint types
267
- if (isBigInt && typeof value === 'number') {
268
- return `.default(BigInt(${value}))`
269
- }
270
-
271
- return `.default(${value ?? ''})`
272
- },
273
- and: (items: string[] = [], mini?: boolean) => {
274
- // zod/mini doesn't support .and() method, so we can't use intersection types
275
- // In mini mode, we try to extract and append .check() calls instead
276
- if (mini && items.length > 0) {
277
- // Try to extract check calls from additional items
278
- const checks: string[] = []
279
- for (const item of items) {
280
- // Extract .check(...) from patterns like "z.string().check(...)"
281
- // Need to handle nested parentheses properly
282
- const checkStart = item.indexOf('.check(')
283
- if (checkStart !== -1) {
284
- // Find the matching closing parenthesis
285
- let depth = 0
286
- let i = checkStart + 7 // length of '.check('
287
- let checkContent = ''
288
- while (i < item.length) {
289
- const char = item[i]
290
- if (char === '(') depth++
291
- else if (char === ')') {
292
- if (depth === 0) break
293
- depth--
294
- }
295
- checkContent += char
296
- i++
297
- }
298
- if (checkContent) {
299
- checks.push(checkContent)
300
- }
301
- }
302
- }
303
-
304
- if (checks.length > 0) {
305
- // Append checks to the base schema
306
- return `.check(${checks.join(', ')})`
307
- }
308
-
309
- // If we can't extract checks, just use the first schema (limitation)
310
- return ''
311
- }
312
- return items?.map((item) => `.and(${item})`).join('')
313
- },
314
- describe: (value = '', innerSchema?: string, mini?: boolean) => {
315
- if (mini) {
316
- return undefined
317
- }
318
-
319
- if (innerSchema) {
320
- return `z.describe(${innerSchema}, ${value})`
321
- }
322
- return `.describe(${value})`
323
- },
324
- max: undefined,
325
- min: undefined,
326
- optional: (value?: string) => {
327
- if (value) {
328
- return `z.optional(${value})`
329
- }
330
- return '.optional()'
331
- },
332
- matches: (value = '', coercion?: boolean, mini?: boolean, min?: number, max?: number) => {
333
- if (mini) {
334
- const checks = buildLengthChecks(min, max)
335
- checks.push(`z.regex(${value})`)
336
- return `z.string().check(${checks.join(', ')})`
337
- }
338
- return [
339
- coercion ? 'z.coerce.string()' : 'z.string()',
340
- min !== undefined ? `.min(${min})` : undefined,
341
- max !== undefined ? `.max(${max})` : undefined,
342
- `.regex(${value})`,
343
- ]
344
- .filter(Boolean)
345
- .join('')
346
- },
347
- email: (coercion?: boolean, version: '3' | '4' = '3', min?: number, max?: number, mini?: boolean) => {
348
- if (mini) {
349
- const checks = buildLengthChecks(min, max)
350
- if (checks.length > 0) {
351
- return `z.email().check(${checks.join(', ')})`
352
- }
353
- return 'z.email()'
354
- }
355
- return [
356
- coercion ? (version === '4' ? 'z.email()' : 'z.coerce.string().email()') : version === '4' ? 'z.email()' : 'z.string().email()',
357
- min !== undefined ? `.min(${min})` : undefined,
358
- max !== undefined ? `.max(${max})` : undefined,
359
- ]
360
- .filter(Boolean)
361
- .join('')
362
- },
363
- firstName: undefined,
364
- lastName: undefined,
365
- password: undefined,
366
- phone: undefined,
367
- readOnly: undefined,
368
- writeOnly: undefined,
369
- ref: (value?: string) => {
370
- if (!value) {
371
- return undefined
372
- }
373
-
374
- return `z.lazy(() => ${value})`
375
- },
376
- blob: () => 'z.instanceof(File)',
377
- deprecated: undefined,
378
- example: undefined,
379
- schema: undefined,
380
- catchall: (value?: string, mini?: boolean) => {
381
- // Zod Mini doesn't support .catchall() method
382
- if (mini) {
383
- return undefined
384
- }
385
- return value ? `.catchall(${value})` : undefined
386
- },
387
- name: undefined,
388
- exclusiveMinimum: undefined,
389
- exclusiveMaximum: undefined,
390
- } satisfies SchemaMapper<string | null | undefined>
391
-
392
- /**
393
- * @link based on https://github.com/cellular/oazapfts/blob/7ba226ebb15374e8483cc53e7532f1663179a22c/src/codegen/generate.ts#L398
394
- */
395
-
396
- export function sort(items?: Schema[]): Schema[] {
397
- const order: string[] = [
398
- schemaKeywords.string,
399
- schemaKeywords.datetime,
400
- schemaKeywords.date,
401
- schemaKeywords.time,
402
- schemaKeywords.tuple,
403
- schemaKeywords.number,
404
- schemaKeywords.object,
405
- schemaKeywords.enum,
406
- schemaKeywords.url,
407
- schemaKeywords.email,
408
- schemaKeywords.firstName,
409
- schemaKeywords.lastName,
410
- schemaKeywords.password,
411
- schemaKeywords.matches,
412
- schemaKeywords.uuid,
413
- schemaKeywords.null,
414
- schemaKeywords.min,
415
- schemaKeywords.max,
416
- schemaKeywords.default,
417
- schemaKeywords.describe,
418
- schemaKeywords.optional,
419
- schemaKeywords.nullable,
420
- schemaKeywords.nullish,
421
- ]
422
-
423
- if (!items) {
424
- return []
425
- }
426
-
427
- return sortBy(items, [(v) => order.indexOf(v.keyword), 'asc'])
428
- }
429
-
430
- type MiniModifiers = {
431
- hasOptional?: boolean
432
- hasNullable?: boolean
433
- hasNullish?: boolean
434
- defaultValue?: string | number | true | object
435
- isBigInt?: boolean
436
- }
437
-
438
- /**
439
- * Keywords that represent modifiers for mini mode
440
- * These are separated from the base schema and wrapped around it
441
- * Note: describe is included to filter it out, but won't be wrapped (Zod Mini doesn't support describe)
442
- */
443
- export const miniModifierKeywords = [schemaKeywords.optional, schemaKeywords.nullable, schemaKeywords.nullish, schemaKeywords.default, schemaKeywords.describe]
444
-
445
- /**
446
- * Extracts mini mode modifiers from a schemas array
447
- * This can be reused by other parsers (e.g., valibot) that need similar functionality
448
- * Note: describe is not included as Zod Mini doesn't support it
449
- */
450
- export function extractMiniModifiers(schemas: Schema[]): MiniModifiers {
451
- const defaultSchema = schemas.find((item) => isKeyword(item, schemaKeywords.default)) as { keyword: string; args: unknown } | undefined
452
- const isBigInt = schemas.some((item) => isKeyword(item, schemaKeywords.bigint))
453
-
454
- // When paired with an enum, skip the default if the value is not a valid enum member.
455
- // String default args are JSON-stringified (e.g. '"available"'), so parse them back
456
- // to the raw value before comparing against enum item values.
457
- let defaultValue = defaultSchema?.args as string | number | true | object | undefined
458
- if (defaultValue !== undefined) {
459
- const enumSchema = schemas.find((it) => isKeyword(it, schemaKeywords.enum)) as SchemaKeywordMapper['enum'] | undefined
460
- if (enumSchema) {
461
- let rawDefault: unknown = defaultValue
462
- if (typeof rawDefault === 'string') {
463
- try {
464
- rawDefault = JSON.parse(rawDefault)
465
- } catch {
466
- // leave as-is if not valid JSON
467
- }
468
- }
469
- const validValues = enumSchema.args.items.map((item) => item.value ?? item.name)
470
- if (!validValues.includes(rawDefault as string | number | boolean)) {
471
- defaultValue = undefined
472
- }
473
- }
474
- }
475
-
476
- return {
477
- hasOptional: schemas.some((item) => isKeyword(item, schemaKeywords.optional)),
478
- hasNullable: schemas.some((item) => isKeyword(item, schemaKeywords.nullable)),
479
- hasNullish: schemas.some((item) => isKeyword(item, schemaKeywords.nullish)),
480
- defaultValue,
481
- isBigInt,
482
- }
483
- }
484
-
485
- /**
486
- * Filters out modifier keywords from schemas for mini mode base schema parsing
487
- * This can be reused by other parsers (e.g., valibot) that need similar functionality
488
- */
489
- export function filterMiniModifiers(schemas: Schema[]): Schema[] {
490
- return schemas.filter((item) => !miniModifierKeywords.some((keyword) => isKeyword(item, keyword)))
491
- }
492
-
493
- /**
494
- * Wraps an output string with Zod Mini functional modifiers
495
- * Order: default (innermost) -> nullable -> optional (outermost)
496
- * OR: default -> nullish
497
- * Note: describe is not supported in Zod Mini and is skipped
498
- */
499
- export function wrapWithMiniModifiers(output: string, modifiers: MiniModifiers): string {
500
- let result = output
501
-
502
- // Apply default first (innermost wrapper)
503
- if (modifiers.defaultValue !== undefined) {
504
- result = zodKeywordMapper.default(modifiers.defaultValue, result, true, modifiers.isBigInt)!
505
- }
506
-
507
- // Apply nullish, nullable, or optional (outer wrappers for optionality)
508
- if (modifiers.hasNullish) {
509
- result = zodKeywordMapper.nullish(result)!
510
- } else {
511
- if (modifiers.hasNullable) {
512
- result = zodKeywordMapper.nullable(result)!
513
- }
514
- if (modifiers.hasOptional) {
515
- result = zodKeywordMapper.optional(result)!
516
- }
517
- }
518
-
519
- return result
520
- }
521
-
522
- const shouldCoerce = (coercion: ParserOptions['coercion'] | undefined, type: 'dates' | 'strings' | 'numbers'): boolean => {
523
- if (coercion === undefined) {
524
- return false
525
- }
526
- if (typeof coercion === 'boolean') {
527
- return coercion
528
- }
529
-
530
- return !!coercion[type]
531
- }
532
-
533
- type ParserOptions = {
534
- mapper?: Record<string, string>
535
- coercion?: boolean | { dates?: boolean; strings?: boolean; numbers?: boolean }
536
- wrapOutput?: (opts: { output: string; schema: any }) => string | undefined
537
- version: '3' | '4'
538
- guidType?: 'uuid' | 'guid'
539
- skipLazyForRefs?: boolean
540
- mini?: boolean
541
- }
542
-
543
- // Create the parser using createParser
544
- export const parse = createParser<string, ParserOptions>({
545
- mapper: zodKeywordMapper,
546
- handlers: {
547
- union(tree, options) {
548
- const { current, schema, parent, name, siblings } = tree
549
-
550
- // zod union type needs at least 2 items
551
- if (Array.isArray(current.args) && current.args.length === 1) {
552
- return this.parse({ schema, parent, name, current: current.args[0] as Schema, siblings }, options)
553
- }
554
- if (Array.isArray(current.args) && !current.args.length) {
555
- return ''
556
- }
557
-
558
- return zodKeywordMapper.union(
559
- sort(current.args)
560
- .map((it, _index, siblings) => this.parse({ schema, parent: current, name, current: it, siblings }, options))
561
- .filter(Boolean),
562
- )
563
- },
564
- and(tree, options) {
565
- const { current, schema, name } = tree
566
-
567
- const items = sort(current.args)
568
- .filter((schema: Schema) => {
569
- return ![schemaKeywords.optional, schemaKeywords.describe].includes(schema.keyword as typeof schemaKeywords.describe)
570
- })
571
- .map((it: Schema, _index, siblings) => this.parse({ schema, parent: current, name, current: it, siblings }, options))
572
- .filter(Boolean)
573
-
574
- return `${items.slice(0, 1)}${zodKeywordMapper.and(items.slice(1), options.mini)}`
575
- },
576
- array(tree, options) {
577
- const { current, schema, name } = tree
578
-
579
- return zodKeywordMapper.array(
580
- sort(current.args.items)
581
- .map((it, _index, siblings) => {
582
- return this.parse({ schema, parent: current, name, current: it, siblings }, options)
583
- })
584
- .filter(Boolean),
585
- current.args.min,
586
- current.args.max,
587
- current.args.unique,
588
- options.mini,
589
- )
590
- },
591
- enum(tree, options) {
592
- const { current, schema, name } = tree
593
-
594
- if (current.args.asConst) {
595
- if (current.args.items.length === 1) {
596
- const child = {
597
- keyword: schemaKeywords.const,
598
- args: current.args.items[0],
599
- }
600
- return this.parse({ schema, parent: current, name, current: child, siblings: [child] }, options)
601
- }
602
-
603
- return zodKeywordMapper.union(
604
- current.args.items
605
- .map((schema) => ({
606
- keyword: schemaKeywords.const,
607
- args: schema,
608
- }))
609
- .map((it, _index, siblings) => {
610
- return this.parse({ schema, parent: current, name, current: it, siblings }, options)
611
- })
612
- .filter(Boolean),
613
- )
614
- }
615
-
616
- return zodKeywordMapper.enum(
617
- current.args.items.map((schema) => {
618
- if (schema.format === 'boolean') {
619
- return stringify(schema.value)
620
- }
621
-
622
- if (schema.format === 'number') {
623
- return stringify(schema.value)
624
- }
625
- return stringify(schema.value)
626
- }),
627
- )
628
- },
629
- ref(tree, options) {
630
- const { current } = tree
631
-
632
- // Skip z.lazy wrapper if skipLazyForRefs is true (e.g., inside v4 getters)
633
- if (options.skipLazyForRefs) {
634
- return current.args?.name
635
- }
636
- return zodKeywordMapper.ref(current.args?.name)
637
- },
638
- object(tree, options) {
639
- const { current, schema, name } = tree
640
-
641
- const propertyEntries = Object.entries(current.args?.properties || {}).filter((item) => {
642
- const schema = item[1]
643
- return schema && typeof schema.map === 'function'
644
- })
645
-
646
- const properties = propertyEntries
647
- .map(([propertyName, schemas]) => {
648
- const nameSchema = schemas.find((it) => it.keyword === schemaKeywords.name) as SchemaKeywordMapper['name']
649
- const isNullable = schemas.some((it) => isKeyword(it, schemaKeywords.nullable))
650
- const isNullish = schemas.some((it) => isKeyword(it, schemaKeywords.nullish))
651
- const isOptional = schemas.some((it) => isKeyword(it, schemaKeywords.optional))
652
- const hasRef = !!SchemaGenerator.find(schemas, schemaKeywords.ref)
653
-
654
- const mappedName = nameSchema?.args || propertyName
655
-
656
- // custom mapper(pluginOptions)
657
- // Use Object.hasOwn to avoid matching inherited properties like 'toString', 'valueOf', etc.
658
- if (options.mapper && Object.hasOwn(options.mapper, mappedName)) {
659
- return `"${propertyName}": ${options.mapper?.[mappedName]}`
660
- }
661
-
662
- const baseSchemaOutput = sort(schemas)
663
- .filter((schema) => {
664
- return !isKeyword(schema, schemaKeywords.optional) && !isKeyword(schema, schemaKeywords.nullable) && !isKeyword(schema, schemaKeywords.nullish)
665
- })
666
- .map((it) => {
667
- // For v4 with refs, skip z.lazy wrapper since the getter provides lazy evaluation
668
- const skipLazyForRefs = options.version === '4' && hasRef
669
- return this.parse({ schema, parent: current, name, current: it, siblings: schemas }, { ...options, skipLazyForRefs })
670
- })
671
- .filter(Boolean)
672
- .join('')
673
-
674
- const objectValue = options.wrapOutput
675
- ? options.wrapOutput({ output: baseSchemaOutput, schema: schema?.properties?.[propertyName] }) || baseSchemaOutput
676
- : baseSchemaOutput
677
-
678
- if (options.version === '4' && hasRef) {
679
- // In mini mode, use functional wrappers instead of chainable methods
680
- if (options.mini) {
681
- // both optional and nullable
682
- if (isNullish) {
683
- return `get "${propertyName}"(){
684
- return ${zodKeywordMapper.nullish(objectValue)}
685
- }`
686
- }
687
-
688
- // undefined
689
- if (isOptional) {
690
- return `get "${propertyName}"(){
691
- return ${zodKeywordMapper.optional(objectValue)}
692
- }`
693
- }
694
-
695
- // null
696
- if (isNullable) {
697
- return `get "${propertyName}"(){
698
- return ${zodKeywordMapper.nullable(objectValue)}
699
- }`
700
- }
701
-
702
- return `get "${propertyName}"(){
703
- return ${objectValue}
704
- }`
705
- }
706
-
707
- // Non-mini mode uses chainable methods
708
- // both optional and nullable
709
- if (isNullish) {
710
- return `get "${propertyName}"(){
711
- return ${objectValue}${zodKeywordMapper.nullish()}
712
- }`
713
- }
714
-
715
- // undefined
716
- if (isOptional) {
717
- return `get "${propertyName}"(){
718
- return ${objectValue}${zodKeywordMapper.optional()}
719
- }`
720
- }
721
-
722
- // null
723
- if (isNullable) {
724
- return `get "${propertyName}"(){
725
- return ${objectValue}${zodKeywordMapper.nullable()}
726
- }`
727
- }
728
-
729
- return `get "${propertyName}"(){
730
- return ${objectValue}
731
- }`
732
- }
733
-
734
- // both optional and nullable
735
- if (isNullish && options.mini) {
736
- return `"${propertyName}": ${zodKeywordMapper.nullish(objectValue)}`
737
- }
738
-
739
- if (isNullish && !options.mini) {
740
- return `"${propertyName}": ${objectValue}${zodKeywordMapper.nullish()}`
741
- }
742
-
743
- // undefined
744
- if (isOptional) {
745
- return `"${propertyName}": ${zodKeywordMapper.optional(objectValue)}`
746
- }
747
-
748
- // null
749
- if (isNullable) {
750
- return `"${propertyName}": ${zodKeywordMapper.nullable(objectValue)}`
751
- }
752
-
753
- return `"${propertyName}": ${objectValue}`
754
- })
755
- .join(',\n')
756
-
757
- const additionalProperties = current.args?.additionalProperties?.length
758
- ? current.args.additionalProperties
759
- .map((it, _index, siblings) => this.parse({ schema, parent: current, name, current: it, siblings }, options))
760
- .filter(Boolean)
761
- .join('')
762
- : undefined
763
-
764
- const text = [
765
- zodKeywordMapper.object(properties, current.args?.strict, options.version),
766
- additionalProperties ? zodKeywordMapper.catchall(additionalProperties, options.mini) : undefined,
767
- ].filter(Boolean)
768
-
769
- return text.join('')
770
- },
771
- tuple(tree, options) {
772
- const { current, schema, name } = tree
773
-
774
- return zodKeywordMapper.tuple(
775
- current.args.items.map((it, _index, siblings) => this.parse({ schema, parent: current, name, current: it, siblings }, options)).filter(Boolean),
776
- )
777
- },
778
- const(tree, _options) {
779
- const { current } = tree
780
-
781
- if (current.args.format === 'number' && current.args.value !== undefined) {
782
- return zodKeywordMapper.const(Number(current.args.value))
783
- }
784
-
785
- if (current.args.format === 'boolean' && current.args.value !== undefined) {
786
- return zodKeywordMapper.const(typeof current.args.value === 'boolean' ? current.args.value : undefined)
787
- }
788
- return zodKeywordMapper.const(stringify(current.args.value))
789
- },
790
- matches(tree, options) {
791
- const { current, siblings } = tree
792
-
793
- // Early exit: if siblings contain both matches and ref → skip matches entirely
794
- const hasRef = siblings.some((it) => isKeyword(it, schemaKeywords.ref))
795
- if (hasRef) {
796
- return undefined // strip matches
797
- }
798
-
799
- const minSchema = findSchemaKeyword(siblings, 'min')
800
- const maxSchema = findSchemaKeyword(siblings, 'max')
801
-
802
- if (current.args) {
803
- return zodKeywordMapper.matches(
804
- toRegExpString(current.args, null),
805
- shouldCoerce(options.coercion, 'strings'),
806
- options.mini,
807
- minSchema?.args,
808
- maxSchema?.args,
809
- )
810
- }
811
- return undefined
812
- },
813
- default(tree, options) {
814
- const { current, siblings } = tree
815
-
816
- // In mini mode, default is handled by wrapWithMiniModifiers
817
- if (options.mini) {
818
- return undefined
819
- }
820
-
821
- // Check if this is a bigint type by looking at siblings
822
- const isBigInt = siblings.some((it) => isKeyword(it, schemaKeywords.bigint))
823
-
824
- // When a default is paired with an enum, skip the default if the value
825
- // is not one of the enum's valid values (e.g. an empty string "").
826
- // Zod's .default() for an enum must receive a value that is in the enum.
827
- if (current.args !== undefined) {
828
- const enumSchema = siblings.find((it) => isKeyword(it, schemaKeywords.enum)) as SchemaKeywordMapper['enum'] | undefined
829
- if (enumSchema) {
830
- // String default args are JSON-stringified (e.g. '"available"'), so parse
831
- // them back to the raw value before comparing against enum item values.
832
- let rawDefault: unknown = current.args
833
- if (typeof rawDefault === 'string') {
834
- try {
835
- rawDefault = JSON.parse(rawDefault)
836
- } catch {
837
- // leave as-is if not valid JSON
838
- }
839
- }
840
- const validValues = enumSchema.args.items.map((item) => item.value ?? item.name)
841
- if (!validValues.includes(rawDefault as string | number | boolean)) {
842
- return undefined
843
- }
844
- }
845
-
846
- return zodKeywordMapper.default(current.args, undefined, undefined, isBigInt)
847
- }
848
- // When args is undefined, call the mapper without arguments
849
- return zodKeywordMapper.default()
850
- },
851
- describe(tree, options) {
852
- const { current } = tree
853
-
854
- if (current.args) {
855
- return zodKeywordMapper.describe(stringify(current.args.toString()), undefined, options.mini)
856
- }
857
- return undefined
858
- },
859
- string(tree, options) {
860
- const { siblings } = tree
861
-
862
- const minSchema = findSchemaKeyword(siblings, 'min')
863
- const maxSchema = findSchemaKeyword(siblings, 'max')
864
-
865
- return zodKeywordMapper.string(shouldCoerce(options.coercion, 'strings'), minSchema?.args, maxSchema?.args, options.mini)
866
- },
867
- uuid(tree, options) {
868
- const { siblings } = tree
869
-
870
- const minSchema = findSchemaKeyword(siblings, 'min')
871
- const maxSchema = findSchemaKeyword(siblings, 'max')
872
-
873
- return zodKeywordMapper.uuid({
874
- coercion: shouldCoerce(options.coercion, 'strings'),
875
- version: options.version,
876
- guidType: options.guidType,
877
- min: minSchema?.args,
878
- max: maxSchema?.args,
879
- mini: options.mini,
880
- })
881
- },
882
- email(tree, options) {
883
- const { siblings } = tree
884
-
885
- const minSchema = findSchemaKeyword(siblings, 'min')
886
- const maxSchema = findSchemaKeyword(siblings, 'max')
887
-
888
- return zodKeywordMapper.email(shouldCoerce(options.coercion, 'strings'), options.version, minSchema?.args, maxSchema?.args, options.mini)
889
- },
890
- url(tree, options) {
891
- const { siblings } = tree
892
-
893
- const minSchema = findSchemaKeyword(siblings, 'min')
894
- const maxSchema = findSchemaKeyword(siblings, 'max')
895
-
896
- return zodKeywordMapper.url(shouldCoerce(options.coercion, 'strings'), options.version, minSchema?.args, maxSchema?.args, options.mini)
897
- },
898
- number(tree, options) {
899
- const { siblings } = tree
900
-
901
- const minSchema = findSchemaKeyword(siblings, 'min')
902
- const maxSchema = findSchemaKeyword(siblings, 'max')
903
- const exclusiveMinimumSchema = findSchemaKeyword(siblings, 'exclusiveMinimum')
904
- const exclusiveMaximumSchema = findSchemaKeyword(siblings, 'exclusiveMaximum')
905
-
906
- return zodKeywordMapper.number(
907
- shouldCoerce(options.coercion, 'numbers'),
908
- minSchema?.args,
909
- maxSchema?.args,
910
- exclusiveMinimumSchema?.args,
911
- exclusiveMaximumSchema?.args,
912
- options.mini,
913
- )
914
- },
915
- integer(tree, options) {
916
- const { siblings } = tree
917
-
918
- const minSchema = findSchemaKeyword(siblings, 'min')
919
- const maxSchema = findSchemaKeyword(siblings, 'max')
920
- const exclusiveMinimumSchema = findSchemaKeyword(siblings, 'exclusiveMinimum')
921
- const exclusiveMaximumSchema = findSchemaKeyword(siblings, 'exclusiveMaximum')
922
-
923
- return zodKeywordMapper.integer(
924
- shouldCoerce(options.coercion, 'numbers'),
925
- minSchema?.args,
926
- maxSchema?.args,
927
- options.version,
928
- exclusiveMinimumSchema?.args,
929
- exclusiveMaximumSchema?.args,
930
- options.mini,
931
- )
932
- },
933
- bigint(_tree, options) {
934
- return zodKeywordMapper.bigint(shouldCoerce(options.coercion, 'numbers'))
935
- },
936
- datetime(tree, options) {
937
- const { current } = tree
938
-
939
- return zodKeywordMapper.datetime(current.args.offset, current.args.local, options.version, options.mini)
940
- },
941
- date(tree, options) {
942
- const { current } = tree
943
-
944
- return zodKeywordMapper.date(current.args.type, shouldCoerce(options.coercion, 'dates'), options.version)
945
- },
946
- time(tree, options) {
947
- const { current } = tree
948
-
949
- return zodKeywordMapper.time(current.args.type, shouldCoerce(options.coercion, 'dates'), options.version)
950
- },
951
- },
952
- })