@kubb/plugin-zod 5.0.0-alpha.3 → 5.0.0-alpha.31

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 (46) hide show
  1. package/dist/index.cjs +1619 -100
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.ts +418 -4
  4. package/dist/index.js +1614 -100
  5. package/dist/index.js.map +1 -1
  6. package/package.json +6 -34
  7. package/src/components/Operations.tsx +22 -15
  8. package/src/components/Zod.tsx +20 -119
  9. package/src/constants.ts +5 -0
  10. package/src/generators/zodGenerator.tsx +129 -159
  11. package/src/generators/zodGeneratorLegacy.tsx +365 -0
  12. package/src/index.ts +12 -1
  13. package/src/plugin.ts +102 -148
  14. package/src/presets.ts +30 -0
  15. package/src/printers/printerZod.ts +298 -0
  16. package/src/printers/printerZodMini.ts +273 -0
  17. package/src/resolvers/resolverZod.ts +61 -0
  18. package/src/resolvers/resolverZodLegacy.ts +60 -0
  19. package/src/types.ts +172 -93
  20. package/src/utils.ts +248 -0
  21. package/dist/components-B7zUFnAm.cjs +0 -890
  22. package/dist/components-B7zUFnAm.cjs.map +0 -1
  23. package/dist/components-eECfXVou.js +0 -842
  24. package/dist/components-eECfXVou.js.map +0 -1
  25. package/dist/components.cjs +0 -4
  26. package/dist/components.d.ts +0 -56
  27. package/dist/components.js +0 -2
  28. package/dist/generators-CRKtFRi1.js +0 -290
  29. package/dist/generators-CRKtFRi1.js.map +0 -1
  30. package/dist/generators-CzSLRVqQ.cjs +0 -301
  31. package/dist/generators-CzSLRVqQ.cjs.map +0 -1
  32. package/dist/generators.cjs +0 -4
  33. package/dist/generators.d.ts +0 -503
  34. package/dist/generators.js +0 -2
  35. package/dist/templates/ToZod.source.cjs +0 -7
  36. package/dist/templates/ToZod.source.cjs.map +0 -1
  37. package/dist/templates/ToZod.source.d.ts +0 -7
  38. package/dist/templates/ToZod.source.js +0 -6
  39. package/dist/templates/ToZod.source.js.map +0 -1
  40. package/dist/types-D0wsPC6Y.d.ts +0 -172
  41. package/src/components/index.ts +0 -2
  42. package/src/generators/index.ts +0 -2
  43. package/src/generators/operationsGenerator.tsx +0 -50
  44. package/src/parser.ts +0 -909
  45. package/src/templates/ToZod.source.ts +0 -4
  46. package/templates/ToZod.ts +0 -61
package/src/parser.ts DELETED
@@ -1,909 +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
- return {
455
- hasOptional: schemas.some((item) => isKeyword(item, schemaKeywords.optional)),
456
- hasNullable: schemas.some((item) => isKeyword(item, schemaKeywords.nullable)),
457
- hasNullish: schemas.some((item) => isKeyword(item, schemaKeywords.nullish)),
458
- defaultValue: defaultSchema?.args as string | number | true | object | undefined,
459
- isBigInt,
460
- }
461
- }
462
-
463
- /**
464
- * Filters out modifier keywords from schemas for mini mode base schema parsing
465
- * This can be reused by other parsers (e.g., valibot) that need similar functionality
466
- */
467
- export function filterMiniModifiers(schemas: Schema[]): Schema[] {
468
- return schemas.filter((item) => !miniModifierKeywords.some((keyword) => isKeyword(item, keyword)))
469
- }
470
-
471
- /**
472
- * Wraps an output string with Zod Mini functional modifiers
473
- * Order: default (innermost) -> nullable -> optional (outermost)
474
- * OR: default -> nullish
475
- * Note: describe is not supported in Zod Mini and is skipped
476
- */
477
- export function wrapWithMiniModifiers(output: string, modifiers: MiniModifiers): string {
478
- let result = output
479
-
480
- // Apply default first (innermost wrapper)
481
- if (modifiers.defaultValue !== undefined) {
482
- result = zodKeywordMapper.default(modifiers.defaultValue, result, true, modifiers.isBigInt)!
483
- }
484
-
485
- // Apply nullish, nullable, or optional (outer wrappers for optionality)
486
- if (modifiers.hasNullish) {
487
- result = zodKeywordMapper.nullish(result)!
488
- } else {
489
- if (modifiers.hasNullable) {
490
- result = zodKeywordMapper.nullable(result)!
491
- }
492
- if (modifiers.hasOptional) {
493
- result = zodKeywordMapper.optional(result)!
494
- }
495
- }
496
-
497
- return result
498
- }
499
-
500
- const shouldCoerce = (coercion: ParserOptions['coercion'] | undefined, type: 'dates' | 'strings' | 'numbers'): boolean => {
501
- if (coercion === undefined) {
502
- return false
503
- }
504
- if (typeof coercion === 'boolean') {
505
- return coercion
506
- }
507
-
508
- return !!coercion[type]
509
- }
510
-
511
- type ParserOptions = {
512
- mapper?: Record<string, string>
513
- coercion?: boolean | { dates?: boolean; strings?: boolean; numbers?: boolean }
514
- wrapOutput?: (opts: { output: string; schema: any }) => string | undefined
515
- version: '3' | '4'
516
- guidType?: 'uuid' | 'guid'
517
- skipLazyForRefs?: boolean
518
- mini?: boolean
519
- }
520
-
521
- // Create the parser using createParser
522
- export const parse = createParser<string, ParserOptions>({
523
- mapper: zodKeywordMapper,
524
- handlers: {
525
- union(tree, options) {
526
- const { current, schema, parent, name, siblings } = tree
527
-
528
- // zod union type needs at least 2 items
529
- if (Array.isArray(current.args) && current.args.length === 1) {
530
- return this.parse({ schema, parent, name, current: current.args[0] as Schema, siblings }, options)
531
- }
532
- if (Array.isArray(current.args) && !current.args.length) {
533
- return ''
534
- }
535
-
536
- return zodKeywordMapper.union(
537
- sort(current.args)
538
- .map((it, _index, siblings) => this.parse({ schema, parent: current, name, current: it, siblings }, options))
539
- .filter(Boolean),
540
- )
541
- },
542
- and(tree, options) {
543
- const { current, schema, name } = tree
544
-
545
- const items = sort(current.args)
546
- .filter((schema: Schema) => {
547
- return ![schemaKeywords.optional, schemaKeywords.describe].includes(schema.keyword as typeof schemaKeywords.describe)
548
- })
549
- .map((it: Schema, _index, siblings) => this.parse({ schema, parent: current, name, current: it, siblings }, options))
550
- .filter(Boolean)
551
-
552
- return `${items.slice(0, 1)}${zodKeywordMapper.and(items.slice(1), options.mini)}`
553
- },
554
- array(tree, options) {
555
- const { current, schema, name } = tree
556
-
557
- return zodKeywordMapper.array(
558
- sort(current.args.items)
559
- .map((it, _index, siblings) => {
560
- return this.parse({ schema, parent: current, name, current: it, siblings }, options)
561
- })
562
- .filter(Boolean),
563
- current.args.min,
564
- current.args.max,
565
- current.args.unique,
566
- options.mini,
567
- )
568
- },
569
- enum(tree, options) {
570
- const { current, schema, name } = tree
571
-
572
- if (current.args.asConst) {
573
- if (current.args.items.length === 1) {
574
- const child = {
575
- keyword: schemaKeywords.const,
576
- args: current.args.items[0],
577
- }
578
- return this.parse({ schema, parent: current, name, current: child, siblings: [child] }, options)
579
- }
580
-
581
- return zodKeywordMapper.union(
582
- current.args.items
583
- .map((schema) => ({
584
- keyword: schemaKeywords.const,
585
- args: schema,
586
- }))
587
- .map((it, _index, siblings) => {
588
- return this.parse({ schema, parent: current, name, current: it, siblings }, options)
589
- })
590
- .filter(Boolean),
591
- )
592
- }
593
-
594
- return zodKeywordMapper.enum(
595
- current.args.items.map((schema) => {
596
- if (schema.format === 'boolean') {
597
- return stringify(schema.value)
598
- }
599
-
600
- if (schema.format === 'number') {
601
- return stringify(schema.value)
602
- }
603
- return stringify(schema.value)
604
- }),
605
- )
606
- },
607
- ref(tree, options) {
608
- const { current } = tree
609
-
610
- // Skip z.lazy wrapper if skipLazyForRefs is true (e.g., inside v4 getters)
611
- if (options.skipLazyForRefs) {
612
- return current.args?.name
613
- }
614
- return zodKeywordMapper.ref(current.args?.name)
615
- },
616
- object(tree, options) {
617
- const { current, schema, name } = tree
618
-
619
- const propertyEntries = Object.entries(current.args?.properties || {}).filter((item) => {
620
- const schema = item[1]
621
- return schema && typeof schema.map === 'function'
622
- })
623
-
624
- const properties = propertyEntries
625
- .map(([propertyName, schemas]) => {
626
- const nameSchema = schemas.find((it) => it.keyword === schemaKeywords.name) as SchemaKeywordMapper['name']
627
- const isNullable = schemas.some((it) => isKeyword(it, schemaKeywords.nullable))
628
- const isNullish = schemas.some((it) => isKeyword(it, schemaKeywords.nullish))
629
- const isOptional = schemas.some((it) => isKeyword(it, schemaKeywords.optional))
630
- const hasRef = !!SchemaGenerator.find(schemas, schemaKeywords.ref)
631
-
632
- const mappedName = nameSchema?.args || propertyName
633
-
634
- // custom mapper(pluginOptions)
635
- // Use Object.hasOwn to avoid matching inherited properties like 'toString', 'valueOf', etc.
636
- if (options.mapper && Object.hasOwn(options.mapper, mappedName)) {
637
- return `"${propertyName}": ${options.mapper?.[mappedName]}`
638
- }
639
-
640
- const baseSchemaOutput = sort(schemas)
641
- .filter((schema) => {
642
- return !isKeyword(schema, schemaKeywords.optional) && !isKeyword(schema, schemaKeywords.nullable) && !isKeyword(schema, schemaKeywords.nullish)
643
- })
644
- .map((it) => {
645
- // For v4 with refs, skip z.lazy wrapper since the getter provides lazy evaluation
646
- const skipLazyForRefs = options.version === '4' && hasRef
647
- return this.parse({ schema, parent: current, name, current: it, siblings: schemas }, { ...options, skipLazyForRefs })
648
- })
649
- .filter(Boolean)
650
- .join('')
651
-
652
- const objectValue = options.wrapOutput
653
- ? options.wrapOutput({ output: baseSchemaOutput, schema: schema?.properties?.[propertyName] }) || baseSchemaOutput
654
- : baseSchemaOutput
655
-
656
- if (options.version === '4' && hasRef) {
657
- // In mini mode, use functional wrappers instead of chainable methods
658
- if (options.mini) {
659
- // both optional and nullable
660
- if (isNullish) {
661
- return `get "${propertyName}"(){
662
- return ${zodKeywordMapper.nullish(objectValue)}
663
- }`
664
- }
665
-
666
- // undefined
667
- if (isOptional) {
668
- return `get "${propertyName}"(){
669
- return ${zodKeywordMapper.optional(objectValue)}
670
- }`
671
- }
672
-
673
- // null
674
- if (isNullable) {
675
- return `get "${propertyName}"(){
676
- return ${zodKeywordMapper.nullable(objectValue)}
677
- }`
678
- }
679
-
680
- return `get "${propertyName}"(){
681
- return ${objectValue}
682
- }`
683
- }
684
-
685
- // Non-mini mode uses chainable methods
686
- // both optional and nullable
687
- if (isNullish) {
688
- return `get "${propertyName}"(){
689
- return ${objectValue}${zodKeywordMapper.nullish()}
690
- }`
691
- }
692
-
693
- // undefined
694
- if (isOptional) {
695
- return `get "${propertyName}"(){
696
- return ${objectValue}${zodKeywordMapper.optional()}
697
- }`
698
- }
699
-
700
- // null
701
- if (isNullable) {
702
- return `get "${propertyName}"(){
703
- return ${objectValue}${zodKeywordMapper.nullable()}
704
- }`
705
- }
706
-
707
- return `get "${propertyName}"(){
708
- return ${objectValue}
709
- }`
710
- }
711
-
712
- // both optional and nullable
713
- if (isNullish && options.mini) {
714
- return `"${propertyName}": ${zodKeywordMapper.nullish(objectValue)}`
715
- }
716
-
717
- if (isNullish && !options.mini) {
718
- return `"${propertyName}": ${objectValue}${zodKeywordMapper.nullish()}`
719
- }
720
-
721
- // undefined
722
- if (isOptional) {
723
- return `"${propertyName}": ${zodKeywordMapper.optional(objectValue)}`
724
- }
725
-
726
- // null
727
- if (isNullable) {
728
- return `"${propertyName}": ${zodKeywordMapper.nullable(objectValue)}`
729
- }
730
-
731
- return `"${propertyName}": ${objectValue}`
732
- })
733
- .join(',\n')
734
-
735
- const additionalProperties = current.args?.additionalProperties?.length
736
- ? current.args.additionalProperties
737
- .map((it, _index, siblings) => this.parse({ schema, parent: current, name, current: it, siblings }, options))
738
- .filter(Boolean)
739
- .join('')
740
- : undefined
741
-
742
- const text = [
743
- zodKeywordMapper.object(properties, current.args?.strict, options.version),
744
- additionalProperties ? zodKeywordMapper.catchall(additionalProperties, options.mini) : undefined,
745
- ].filter(Boolean)
746
-
747
- return text.join('')
748
- },
749
- tuple(tree, options) {
750
- const { current, schema, name } = tree
751
-
752
- return zodKeywordMapper.tuple(
753
- current.args.items.map((it, _index, siblings) => this.parse({ schema, parent: current, name, current: it, siblings }, options)).filter(Boolean),
754
- )
755
- },
756
- const(tree, _options) {
757
- const { current } = tree
758
-
759
- if (current.args.format === 'number' && current.args.value !== undefined) {
760
- return zodKeywordMapper.const(Number(current.args.value))
761
- }
762
-
763
- if (current.args.format === 'boolean' && current.args.value !== undefined) {
764
- return zodKeywordMapper.const(typeof current.args.value === 'boolean' ? current.args.value : undefined)
765
- }
766
- return zodKeywordMapper.const(stringify(current.args.value))
767
- },
768
- matches(tree, options) {
769
- const { current, siblings } = tree
770
-
771
- // Early exit: if siblings contain both matches and ref → skip matches entirely
772
- const hasRef = siblings.some((it) => isKeyword(it, schemaKeywords.ref))
773
- if (hasRef) {
774
- return undefined // strip matches
775
- }
776
-
777
- const minSchema = findSchemaKeyword(siblings, 'min')
778
- const maxSchema = findSchemaKeyword(siblings, 'max')
779
-
780
- if (current.args) {
781
- return zodKeywordMapper.matches(
782
- toRegExpString(current.args, null),
783
- shouldCoerce(options.coercion, 'strings'),
784
- options.mini,
785
- minSchema?.args,
786
- maxSchema?.args,
787
- )
788
- }
789
- return undefined
790
- },
791
- default(tree, options) {
792
- const { current, siblings } = tree
793
-
794
- // In mini mode, default is handled by wrapWithMiniModifiers
795
- if (options.mini) {
796
- return undefined
797
- }
798
-
799
- // Check if this is a bigint type by looking at siblings
800
- const isBigInt = siblings.some((it) => isKeyword(it, schemaKeywords.bigint))
801
-
802
- if (current.args !== undefined) {
803
- return zodKeywordMapper.default(current.args, undefined, undefined, isBigInt)
804
- }
805
- // When args is undefined, call the mapper without arguments
806
- return zodKeywordMapper.default()
807
- },
808
- describe(tree, options) {
809
- const { current } = tree
810
-
811
- if (current.args) {
812
- return zodKeywordMapper.describe(stringify(current.args.toString()), undefined, options.mini)
813
- }
814
- return undefined
815
- },
816
- string(tree, options) {
817
- const { siblings } = tree
818
-
819
- const minSchema = findSchemaKeyword(siblings, 'min')
820
- const maxSchema = findSchemaKeyword(siblings, 'max')
821
-
822
- return zodKeywordMapper.string(shouldCoerce(options.coercion, 'strings'), minSchema?.args, maxSchema?.args, options.mini)
823
- },
824
- uuid(tree, options) {
825
- const { siblings } = tree
826
-
827
- const minSchema = findSchemaKeyword(siblings, 'min')
828
- const maxSchema = findSchemaKeyword(siblings, 'max')
829
-
830
- return zodKeywordMapper.uuid({
831
- coercion: shouldCoerce(options.coercion, 'strings'),
832
- version: options.version,
833
- guidType: options.guidType,
834
- min: minSchema?.args,
835
- max: maxSchema?.args,
836
- mini: options.mini,
837
- })
838
- },
839
- email(tree, options) {
840
- const { siblings } = tree
841
-
842
- const minSchema = findSchemaKeyword(siblings, 'min')
843
- const maxSchema = findSchemaKeyword(siblings, 'max')
844
-
845
- return zodKeywordMapper.email(shouldCoerce(options.coercion, 'strings'), options.version, minSchema?.args, maxSchema?.args, options.mini)
846
- },
847
- url(tree, options) {
848
- const { siblings } = tree
849
-
850
- const minSchema = findSchemaKeyword(siblings, 'min')
851
- const maxSchema = findSchemaKeyword(siblings, 'max')
852
-
853
- return zodKeywordMapper.url(shouldCoerce(options.coercion, 'strings'), options.version, minSchema?.args, maxSchema?.args, options.mini)
854
- },
855
- number(tree, options) {
856
- const { siblings } = tree
857
-
858
- const minSchema = findSchemaKeyword(siblings, 'min')
859
- const maxSchema = findSchemaKeyword(siblings, 'max')
860
- const exclusiveMinimumSchema = findSchemaKeyword(siblings, 'exclusiveMinimum')
861
- const exclusiveMaximumSchema = findSchemaKeyword(siblings, 'exclusiveMaximum')
862
-
863
- return zodKeywordMapper.number(
864
- shouldCoerce(options.coercion, 'numbers'),
865
- minSchema?.args,
866
- maxSchema?.args,
867
- exclusiveMinimumSchema?.args,
868
- exclusiveMaximumSchema?.args,
869
- options.mini,
870
- )
871
- },
872
- integer(tree, options) {
873
- const { siblings } = tree
874
-
875
- const minSchema = findSchemaKeyword(siblings, 'min')
876
- const maxSchema = findSchemaKeyword(siblings, 'max')
877
- const exclusiveMinimumSchema = findSchemaKeyword(siblings, 'exclusiveMinimum')
878
- const exclusiveMaximumSchema = findSchemaKeyword(siblings, 'exclusiveMaximum')
879
-
880
- return zodKeywordMapper.integer(
881
- shouldCoerce(options.coercion, 'numbers'),
882
- minSchema?.args,
883
- maxSchema?.args,
884
- options.version,
885
- exclusiveMinimumSchema?.args,
886
- exclusiveMaximumSchema?.args,
887
- options.mini,
888
- )
889
- },
890
- bigint(_tree, options) {
891
- return zodKeywordMapper.bigint(shouldCoerce(options.coercion, 'numbers'))
892
- },
893
- datetime(tree, options) {
894
- const { current } = tree
895
-
896
- return zodKeywordMapper.datetime(current.args.offset, current.args.local, options.version, options.mini)
897
- },
898
- date(tree, options) {
899
- const { current } = tree
900
-
901
- return zodKeywordMapper.date(current.args.type, shouldCoerce(options.coercion, 'dates'), options.version)
902
- },
903
- time(tree, options) {
904
- const { current } = tree
905
-
906
- return zodKeywordMapper.time(current.args.type, shouldCoerce(options.coercion, 'dates'), options.version)
907
- },
908
- },
909
- })