@kubb/plugin-zod 5.0.0-beta.10 → 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.
- package/dist/index.cjs +142 -127
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +143 -128
- package/dist/index.js.map +1 -1
- package/extension.yaml +21 -1
- package/package.json +5 -5
- package/src/generators/zodGenerator.tsx +51 -18
- package/src/printers/printerZod.ts +40 -62
- package/src/printers/printerZodMini.ts +30 -48
- package/src/utils.ts +14 -30
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
import type { Adapter } from '@kubb/core'
|
|
2
2
|
import { ast, defineGenerator } from '@kubb/core'
|
|
3
3
|
import type { AdapterOas } from '@kubb/adapter-oas'
|
|
4
|
-
import { File,
|
|
4
|
+
import { File, jsxRendererSync } from '@kubb/renderer-jsx'
|
|
5
5
|
import { Operations } from '../components/Operations.tsx'
|
|
6
6
|
import { Zod } from '../components/Zod.tsx'
|
|
7
7
|
import { ZOD_NAMESPACE_IMPORTS } from '../constants.ts'
|
|
8
8
|
import { printerZod } from '../printers/printerZod.ts'
|
|
9
9
|
import { printerZodMini } from '../printers/printerZodMini.ts'
|
|
10
|
-
import type { PluginZod } from '../types'
|
|
10
|
+
import type { PluginZod, ResolverZod } from '../types'
|
|
11
11
|
import { buildSchemaNames } from '../utils.ts'
|
|
12
12
|
|
|
13
|
+
type ZodPrinterEntry = { printer: ReturnType<typeof printerZod>; coercion: unknown; guidType: unknown; dateType: unknown }
|
|
14
|
+
type ZodMiniPrinterEntry = { printer: ReturnType<typeof printerZodMini>; guidType: unknown }
|
|
15
|
+
|
|
16
|
+
// Per-build caches: keyed on resolver (unique per plugin instance per build, GC'd when released)
|
|
17
|
+
const zodPrinterCache = new WeakMap<ResolverZod, ZodPrinterEntry>()
|
|
18
|
+
const zodMiniPrinterCache = new WeakMap<ResolverZod, ZodMiniPrinterEntry>()
|
|
19
|
+
|
|
13
20
|
export const zodGenerator = defineGenerator<PluginZod>({
|
|
14
21
|
name: 'zod',
|
|
15
|
-
renderer:
|
|
22
|
+
renderer: jsxRendererSync,
|
|
16
23
|
schema(node, ctx) {
|
|
17
24
|
const { adapter, config, resolver, root } = ctx
|
|
18
25
|
const { output, coercion, guidType, mini, wrapOutput, inferred, importPath, group, printer } = ctx.options
|
|
@@ -37,19 +44,35 @@ export const zodGenerator = defineGenerator<PluginZod>({
|
|
|
37
44
|
|
|
38
45
|
const inferTypeName = inferred ? resolver.resolveSchemaTypeName(node.name) : undefined
|
|
39
46
|
|
|
40
|
-
const cyclicSchemas =
|
|
47
|
+
const cyclicSchemas = new Set<string>(ctx.meta.circularNames)
|
|
41
48
|
|
|
42
|
-
const schemaPrinter = mini
|
|
43
|
-
|
|
44
|
-
|
|
49
|
+
const schemaPrinter = mini ? getCachedMiniPrinter() : getCachedStdPrinter()
|
|
50
|
+
function getCachedStdPrinter() {
|
|
51
|
+
const cached = zodPrinterCache.get(resolver)
|
|
52
|
+
if (cached && cached.coercion === coercion && cached.guidType === guidType && cached.dateType === dateType) {
|
|
53
|
+
return cached.printer
|
|
54
|
+
}
|
|
55
|
+
const p = printerZod({ coercion, guidType, dateType, wrapOutput, resolver, cyclicSchemas, nodes: printer?.nodes })
|
|
56
|
+
zodPrinterCache.set(resolver, { printer: p, coercion, guidType, dateType })
|
|
57
|
+
return p
|
|
58
|
+
}
|
|
59
|
+
function getCachedMiniPrinter() {
|
|
60
|
+
const cached = zodMiniPrinterCache.get(resolver)
|
|
61
|
+
if (cached && cached.guidType === guidType) {
|
|
62
|
+
return cached.printer
|
|
63
|
+
}
|
|
64
|
+
const p = printerZodMini({ guidType, wrapOutput, resolver, cyclicSchemas, nodes: printer?.nodes })
|
|
65
|
+
zodMiniPrinterCache.set(resolver, { printer: p, guidType })
|
|
66
|
+
return p
|
|
67
|
+
}
|
|
45
68
|
|
|
46
69
|
return (
|
|
47
70
|
<File
|
|
48
71
|
baseName={meta.file.baseName}
|
|
49
72
|
path={meta.file.path}
|
|
50
73
|
meta={meta.file.meta}
|
|
51
|
-
banner={resolver.resolveBanner(
|
|
52
|
-
footer={resolver.resolveFooter(
|
|
74
|
+
banner={resolver.resolveBanner(ctx.meta, { output, config })}
|
|
75
|
+
footer={resolver.resolveFooter(ctx.meta, { output, config })}
|
|
53
76
|
>
|
|
54
77
|
<File.Import name={isZodImport ? 'z' : ['z']} path={importPath} isNameSpace={isZodImport} />
|
|
55
78
|
{mode === 'split' && imports.map((imp) => <File.Import key={[node.name, imp.path].join('-')} root={meta.file.path} path={imp.path} name={imp.name} />)}
|
|
@@ -72,9 +95,9 @@ export const zodGenerator = defineGenerator<PluginZod>({
|
|
|
72
95
|
file: resolver.resolveFile({ name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path }, { root, output, group }),
|
|
73
96
|
} as const
|
|
74
97
|
|
|
75
|
-
const cyclicSchemas =
|
|
98
|
+
const cyclicSchemas = new Set<string>(ctx.meta.circularNames)
|
|
76
99
|
|
|
77
|
-
function renderSchemaEntry({ schema, name, keysToOmit }: { schema: ast.SchemaNode | null; name: string; keysToOmit?: Array<string> }) {
|
|
100
|
+
function renderSchemaEntry({ schema, name, keysToOmit }: { schema: ast.SchemaNode | null; name: string; keysToOmit?: Array<string> | null }) {
|
|
78
101
|
if (!schema) return null
|
|
79
102
|
|
|
80
103
|
const inferTypeName = inferred ? resolver.resolveTypeName(name) : undefined
|
|
@@ -84,9 +107,19 @@ export const zodGenerator = defineGenerator<PluginZod>({
|
|
|
84
107
|
path: resolver.resolveFile({ name: schemaName, extname: '.ts' }, { root, output, group }).path,
|
|
85
108
|
}))
|
|
86
109
|
|
|
110
|
+
const cachedStd = zodPrinterCache.get(resolver)
|
|
111
|
+
const cachedMini = zodMiniPrinterCache.get(resolver)
|
|
87
112
|
const schemaPrinter = mini
|
|
88
|
-
?
|
|
89
|
-
|
|
113
|
+
? keysToOmit?.length
|
|
114
|
+
? printerZodMini({ guidType, wrapOutput, resolver, keysToOmit, cyclicSchemas, nodes: printer?.nodes })
|
|
115
|
+
: cachedMini?.guidType === guidType
|
|
116
|
+
? cachedMini.printer
|
|
117
|
+
: printerZodMini({ guidType, wrapOutput, resolver, cyclicSchemas, nodes: printer?.nodes })
|
|
118
|
+
: keysToOmit?.length
|
|
119
|
+
? printerZod({ coercion, guidType, dateType, wrapOutput, resolver, keysToOmit, cyclicSchemas, nodes: printer?.nodes })
|
|
120
|
+
: cachedStd?.coercion === coercion && cachedStd?.guidType === guidType && cachedStd?.dateType === dateType
|
|
121
|
+
? cachedStd.printer
|
|
122
|
+
: printerZod({ coercion, guidType, dateType, wrapOutput, resolver, cyclicSchemas, nodes: printer?.nodes })
|
|
90
123
|
|
|
91
124
|
return (
|
|
92
125
|
<>
|
|
@@ -159,8 +192,8 @@ export const zodGenerator = defineGenerator<PluginZod>({
|
|
|
159
192
|
baseName={meta.file.baseName}
|
|
160
193
|
path={meta.file.path}
|
|
161
194
|
meta={meta.file.meta}
|
|
162
|
-
banner={resolver.resolveBanner(
|
|
163
|
-
footer={resolver.resolveFooter(
|
|
195
|
+
banner={resolver.resolveBanner(ctx.meta, { output, config })}
|
|
196
|
+
footer={resolver.resolveFooter(ctx.meta, { output, config })}
|
|
164
197
|
>
|
|
165
198
|
<File.Import name={isZodImport ? 'z' : ['z']} path={importPath} isNameSpace={isZodImport} />
|
|
166
199
|
{paramSchemas}
|
|
@@ -171,7 +204,7 @@ export const zodGenerator = defineGenerator<PluginZod>({
|
|
|
171
204
|
)
|
|
172
205
|
},
|
|
173
206
|
operations(nodes, ctx) {
|
|
174
|
-
const {
|
|
207
|
+
const { config, resolver, root } = ctx
|
|
175
208
|
const { output, importPath, group, operations, paramsCasing } = ctx.options
|
|
176
209
|
|
|
177
210
|
if (!operations) {
|
|
@@ -204,8 +237,8 @@ export const zodGenerator = defineGenerator<PluginZod>({
|
|
|
204
237
|
baseName={meta.file.baseName}
|
|
205
238
|
path={meta.file.path}
|
|
206
239
|
meta={meta.file.meta}
|
|
207
|
-
banner={resolver.resolveBanner(
|
|
208
|
-
footer={resolver.resolveFooter(
|
|
240
|
+
banner={resolver.resolveBanner(ctx.meta, { output, config })}
|
|
241
|
+
footer={resolver.resolveFooter(ctx.meta, { output, config })}
|
|
209
242
|
>
|
|
210
243
|
<File.Import isTypeOnly name={isZodImport ? 'z' : ['z']} path={importPath} isNameSpace={isZodImport} />
|
|
211
244
|
{imports}
|
|
@@ -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
|
|
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
|
|
|
@@ -208,19 +215,19 @@ export const printerZod = ast.definePrinter<PrinterZodFactory>((options) => {
|
|
|
208
215
|
const hasSelfRef = this.options.cyclicSchemas != null && ast.containsCircularRef(schema, { circularSchemas: this.options.cyclicSchemas })
|
|
209
216
|
// Inside a getter the getter itself defers evaluation, so suppress
|
|
210
217
|
// z.lazy() wrapping on nested refs by temporarily clearing cyclicSchemas.
|
|
218
|
+
// Save before clearing: this.options === options (same reference via definePrinter),
|
|
219
|
+
// so reading options.cyclicSchemas after mutation would return undefined.
|
|
220
|
+
const savedCyclicSchemas = this.options.cyclicSchemas
|
|
211
221
|
if (hasSelfRef) this.options.cyclicSchemas = undefined
|
|
212
222
|
const baseOutput = this.transform(schema) ?? this.transform(ast.createSchema({ type: 'unknown' }))!
|
|
213
|
-
if (hasSelfRef) this.options.cyclicSchemas =
|
|
223
|
+
if (hasSelfRef) this.options.cyclicSchemas = savedCyclicSchemas
|
|
214
224
|
|
|
215
225
|
const wrappedOutput = this.options.wrapOutput ? this.options.wrapOutput({ output: baseOutput, schema }) || baseOutput : baseOutput
|
|
216
226
|
|
|
217
227
|
// When a property schema is not a ref but the metadata is from a ref (e.g., discriminator
|
|
218
228
|
// property override), skip applying the description from the ref target to avoid applying
|
|
219
229
|
// metadata from a replaced schema.
|
|
220
|
-
|
|
221
|
-
if (schema.type !== 'ref' && meta.type === 'ref') {
|
|
222
|
-
descriptionToApply = undefined
|
|
223
|
-
}
|
|
230
|
+
const descriptionToApply = schema.type !== 'ref' && meta.type === 'ref' ? undefined : meta.description
|
|
224
231
|
|
|
225
232
|
const value = applyModifiers({
|
|
226
233
|
value: wrappedOutput,
|
|
@@ -238,32 +245,27 @@ export const printerZod = ast.definePrinter<PrinterZodFactory>((options) => {
|
|
|
238
245
|
})
|
|
239
246
|
.join(',\n ')
|
|
240
247
|
|
|
241
|
-
|
|
248
|
+
const objectBase = `z.object({\n ${properties}\n })`
|
|
242
249
|
|
|
243
250
|
// Handle additionalProperties as .catchall() or .strict()
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
251
|
+
const result = (() => {
|
|
252
|
+
if (node.additionalProperties && node.additionalProperties !== true) {
|
|
253
|
+
const catchallType = this.transform(node.additionalProperties)
|
|
254
|
+
return catchallType ? `${objectBase}.catchall(${catchallType})` : objectBase
|
|
248
255
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
}
|
|
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
|
+
})()
|
|
254
260
|
|
|
255
261
|
return result
|
|
256
262
|
},
|
|
257
263
|
array(node) {
|
|
258
264
|
const items = (node.items ?? []).map((item) => this.transform(item)).filter(Boolean)
|
|
259
265
|
const inner = items.join(', ') || this.transform(ast.createSchema({ type: 'unknown' }))!
|
|
260
|
-
|
|
266
|
+
const base = `z.array(${inner})${lengthConstraints(node)}`
|
|
261
267
|
|
|
262
|
-
|
|
263
|
-
result += `.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })`
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
return result
|
|
268
|
+
return node.unique ? `${base}.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })` : base
|
|
267
269
|
},
|
|
268
270
|
tuple(node) {
|
|
269
271
|
const items = (node.items ?? []).map((item) => this.transform(item)).filter(Boolean)
|
|
@@ -296,61 +298,37 @@ export const printerZod = ast.definePrinter<PrinterZodFactory>((options) => {
|
|
|
296
298
|
const [first, ...rest] = members
|
|
297
299
|
if (!first) return ''
|
|
298
300
|
|
|
299
|
-
|
|
300
|
-
if (!
|
|
301
|
+
const firstBase = this.transform(first)
|
|
302
|
+
if (!firstBase) return ''
|
|
301
303
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
const c = lengthConstraints(s ?? {})
|
|
306
|
-
if (c) {
|
|
307
|
-
base += c
|
|
308
|
-
continue
|
|
309
|
-
}
|
|
310
|
-
} else if (member.primitive === 'number' || member.primitive === 'integer') {
|
|
311
|
-
const n = ast.narrowSchema(member, 'number') ?? ast.narrowSchema(member, 'integer')
|
|
312
|
-
const c = numberConstraints(n ?? {})
|
|
313
|
-
if (c) {
|
|
314
|
-
base += c
|
|
315
|
-
continue
|
|
316
|
-
}
|
|
317
|
-
} else if (member.primitive === 'array') {
|
|
318
|
-
const a = ast.narrowSchema(member, 'array')
|
|
319
|
-
const c = lengthConstraints(a ?? {})
|
|
320
|
-
if (c) {
|
|
321
|
-
base += c
|
|
322
|
-
continue
|
|
323
|
-
}
|
|
324
|
-
}
|
|
304
|
+
return rest.reduce((acc, member) => {
|
|
305
|
+
const constraint = getMemberConstraint(member)
|
|
306
|
+
if (constraint) return acc + constraint
|
|
325
307
|
const transformed = this.transform(member)
|
|
326
|
-
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
return base
|
|
308
|
+
return transformed ? `${acc}.and(${transformed})` : acc
|
|
309
|
+
}, firstBase)
|
|
330
310
|
},
|
|
331
311
|
...options.nodes,
|
|
332
312
|
},
|
|
333
313
|
print(node) {
|
|
334
314
|
const { keysToOmit } = this.options
|
|
335
315
|
|
|
336
|
-
|
|
337
|
-
if (!
|
|
316
|
+
const transformed = this.transform(node)
|
|
317
|
+
if (!transformed) return null
|
|
338
318
|
|
|
339
319
|
const meta = ast.syncSchemaRef(node)
|
|
340
320
|
|
|
341
|
-
|
|
321
|
+
const base = (() => {
|
|
322
|
+
if (!keysToOmit?.length || meta.primitive !== 'object' || (meta.type === 'union' && meta.discriminatorPropertyName)) return transformed
|
|
342
323
|
// Mirror printerTs `nonNullable: true`: when omitting keys, the resulting
|
|
343
324
|
// schema is a new non-nullable object type — skip optional/nullable/nullish.
|
|
344
325
|
// Discriminated unions (z.discriminatedUnion) do not support .omit(), so skip them.
|
|
345
326
|
|
|
346
327
|
// If this is a lazy reference, apply omit inside the lazy function
|
|
347
|
-
const lazyMatch =
|
|
348
|
-
if (lazyMatch) {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
base = `${base}.omit({ ${keysToOmit.map((k: string) => `"${k}": true`).join(', ')} })`
|
|
352
|
-
}
|
|
353
|
-
}
|
|
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
|
+
})()
|
|
354
332
|
|
|
355
333
|
return applyModifiers({
|
|
356
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
|
|
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
|
|
|
@@ -177,9 +184,12 @@ export const printerZodMini = ast.definePrinter<PrinterZodMiniFactory>((options)
|
|
|
177
184
|
const hasSelfRef = this.options.cyclicSchemas != null && ast.containsCircularRef(schema, { circularSchemas: this.options.cyclicSchemas })
|
|
178
185
|
// Inside a getter the getter itself defers evaluation, so suppress
|
|
179
186
|
// z.lazy() wrapping on nested refs by temporarily clearing cyclicSchemas.
|
|
187
|
+
// Save before clearing: this.options === options (same reference via definePrinter),
|
|
188
|
+
// so reading options.cyclicSchemas after mutation would return undefined.
|
|
189
|
+
const savedCyclicSchemas = this.options.cyclicSchemas
|
|
180
190
|
if (hasSelfRef) this.options.cyclicSchemas = undefined
|
|
181
191
|
const baseOutput = this.transform(schema) ?? this.transform(ast.createSchema({ type: 'unknown' }))!
|
|
182
|
-
if (hasSelfRef) this.options.cyclicSchemas =
|
|
192
|
+
if (hasSelfRef) this.options.cyclicSchemas = savedCyclicSchemas
|
|
183
193
|
|
|
184
194
|
const wrappedOutput = this.options.wrapOutput ? this.options.wrapOutput({ output: baseOutput, schema }) || baseOutput : baseOutput
|
|
185
195
|
|
|
@@ -203,13 +213,9 @@ export const printerZodMini = ast.definePrinter<PrinterZodMiniFactory>((options)
|
|
|
203
213
|
array(node) {
|
|
204
214
|
const items = (node.items ?? []).map((item) => this.transform(item)).filter(Boolean)
|
|
205
215
|
const inner = items.join(', ') || this.transform(ast.createSchema({ type: 'unknown' }))!
|
|
206
|
-
|
|
216
|
+
const base = `z.array(${inner})${lengthChecksMini(node)}`
|
|
207
217
|
|
|
208
|
-
|
|
209
|
-
result += `.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })`
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
return result
|
|
218
|
+
return node.unique ? `${base}.refine(items => new Set(items).size === items.length, { message: "Array entries must be unique" })` : base
|
|
213
219
|
},
|
|
214
220
|
tuple(node) {
|
|
215
221
|
const items = (node.items ?? []).map((item) => this.transform(item)).filter(Boolean)
|
|
@@ -242,61 +248,37 @@ export const printerZodMini = ast.definePrinter<PrinterZodMiniFactory>((options)
|
|
|
242
248
|
const [first, ...rest] = members
|
|
243
249
|
if (!first) return ''
|
|
244
250
|
|
|
245
|
-
|
|
246
|
-
if (!
|
|
251
|
+
const firstBase = this.transform(first)
|
|
252
|
+
if (!firstBase) return ''
|
|
247
253
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
const c = lengthChecksMini(s ?? {})
|
|
252
|
-
if (c) {
|
|
253
|
-
base += c
|
|
254
|
-
continue
|
|
255
|
-
}
|
|
256
|
-
} else if (member.primitive === 'number' || member.primitive === 'integer') {
|
|
257
|
-
const n = ast.narrowSchema(member, 'number') ?? ast.narrowSchema(member, 'integer')
|
|
258
|
-
const c = numberChecksMini(n ?? {})
|
|
259
|
-
if (c) {
|
|
260
|
-
base += c
|
|
261
|
-
continue
|
|
262
|
-
}
|
|
263
|
-
} else if (member.primitive === 'array') {
|
|
264
|
-
const a = ast.narrowSchema(member, 'array')
|
|
265
|
-
const c = lengthChecksMini(a ?? {})
|
|
266
|
-
if (c) {
|
|
267
|
-
base += c
|
|
268
|
-
continue
|
|
269
|
-
}
|
|
270
|
-
}
|
|
254
|
+
return rest.reduce((acc, member) => {
|
|
255
|
+
const constraint = getMemberConstraintMini(member)
|
|
256
|
+
if (constraint) return acc + constraint
|
|
271
257
|
const transformed = this.transform(member)
|
|
272
|
-
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
return base
|
|
258
|
+
return transformed ? `z.intersection(${acc}, ${transformed})` : acc
|
|
259
|
+
}, firstBase)
|
|
276
260
|
},
|
|
277
261
|
...options.nodes,
|
|
278
262
|
},
|
|
279
263
|
print(node) {
|
|
280
264
|
const { keysToOmit } = this.options
|
|
281
265
|
|
|
282
|
-
|
|
283
|
-
if (!
|
|
266
|
+
const transformed = this.transform(node)
|
|
267
|
+
if (!transformed) return null
|
|
284
268
|
|
|
285
269
|
const meta = ast.syncSchemaRef(node)
|
|
286
270
|
|
|
287
|
-
|
|
271
|
+
const base = (() => {
|
|
272
|
+
if (!keysToOmit?.length || meta.primitive !== 'object' || (meta.type === 'union' && meta.discriminatorPropertyName)) return transformed
|
|
288
273
|
// Mirror printerTs `nonNullable: true`: when omitting keys, the resulting
|
|
289
274
|
// schema is a new non-nullable object type — skip optional/nullable/nullish.
|
|
290
275
|
// Discriminated unions (z.discriminatedUnion) do not support .omit(), so skip them.
|
|
291
276
|
|
|
292
277
|
// If this is a lazy reference, apply omit inside the lazy function
|
|
293
|
-
const lazyMatch =
|
|
294
|
-
if (lazyMatch) {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
base = `${base}.omit({ ${keysToOmit.map((k: string) => `"${k}": true`).join(', ')} })`
|
|
298
|
-
}
|
|
299
|
-
}
|
|
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
|
+
})()
|
|
300
282
|
|
|
301
283
|
return applyMiniModifiers({
|
|
302
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
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
167
|
-
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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 = {
|