@kubb/plugin-zod 5.0.0-beta.42 → 5.0.0-beta.64
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 +123 -182
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +18 -20
- package/dist/index.js +117 -176
- package/dist/index.js.map +1 -1
- package/package.json +7 -15
- package/extension.yaml +0 -970
- package/src/components/Operations.tsx +0 -78
- package/src/components/Zod.tsx +0 -42
- package/src/constants.ts +0 -5
- package/src/generators/zodGenerator.tsx +0 -366
- package/src/index.ts +0 -11
- package/src/plugin.ts +0 -97
- package/src/printers/printerZod.ts +0 -362
- package/src/printers/printerZodMini.ts +0 -292
- package/src/resolvers/resolverZod.ts +0 -69
- package/src/types.ts +0 -223
- package/src/utils.ts +0 -299
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { stringifyObject } from '@internals/utils'
|
|
2
|
-
import { ast } from '@kubb/core'
|
|
3
|
-
import { Const, File, Type } from '@kubb/renderer-jsx'
|
|
4
|
-
import type { KubbReactNode } from '@kubb/renderer-jsx/types'
|
|
5
|
-
|
|
6
|
-
type SchemaNames = {
|
|
7
|
-
request: string | null
|
|
8
|
-
parameters: {
|
|
9
|
-
path: string | null
|
|
10
|
-
query: string | null
|
|
11
|
-
header: string | null
|
|
12
|
-
}
|
|
13
|
-
responses: { default?: string } & Record<number | string, string>
|
|
14
|
-
errors: Record<number | string, string>
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
type Props = {
|
|
18
|
-
name: string
|
|
19
|
-
operations: Array<{ node: ast.OperationNode; data: SchemaNames }>
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function Operations({ name, operations }: Props): KubbReactNode {
|
|
23
|
-
const operationsJSON = operations.reduce<Record<string, unknown>>(
|
|
24
|
-
(prev, acc) => {
|
|
25
|
-
prev[`"${acc.node.operationId}"`] = acc.data
|
|
26
|
-
|
|
27
|
-
return prev
|
|
28
|
-
},
|
|
29
|
-
{} as Record<string, unknown>,
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
const pathsJSON = operations.reduce<Record<string, Record<string, string>>>((prev, acc) => {
|
|
33
|
-
if (!ast.isHttpOperationNode(acc.node)) return prev
|
|
34
|
-
prev[`"${acc.node.path}"`] = {
|
|
35
|
-
...(prev[`"${acc.node.path}"`] ?? {}),
|
|
36
|
-
[acc.node.method]: `operations["${acc.node.operationId}"]`,
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return prev
|
|
40
|
-
}, {})
|
|
41
|
-
|
|
42
|
-
return (
|
|
43
|
-
<>
|
|
44
|
-
<File.Source name="OperationSchema" isExportable isIndexable>
|
|
45
|
-
<Type name="OperationSchema" export>{`{
|
|
46
|
-
readonly request: z.ZodTypeAny | undefined;
|
|
47
|
-
readonly parameters: {
|
|
48
|
-
readonly path: z.ZodTypeAny | undefined;
|
|
49
|
-
readonly query: z.ZodTypeAny | undefined;
|
|
50
|
-
readonly header: z.ZodTypeAny | undefined;
|
|
51
|
-
};
|
|
52
|
-
readonly responses: {
|
|
53
|
-
readonly [status: number]: z.ZodTypeAny;
|
|
54
|
-
readonly default: z.ZodTypeAny;
|
|
55
|
-
};
|
|
56
|
-
readonly errors: {
|
|
57
|
-
readonly [status: number]: z.ZodTypeAny;
|
|
58
|
-
};
|
|
59
|
-
}`}</Type>
|
|
60
|
-
</File.Source>
|
|
61
|
-
<File.Source name="OperationsMap" isExportable isIndexable>
|
|
62
|
-
<Type name="OperationsMap" export>
|
|
63
|
-
{'Record<string, OperationSchema>'}
|
|
64
|
-
</Type>
|
|
65
|
-
</File.Source>
|
|
66
|
-
<File.Source name={name} isExportable isIndexable>
|
|
67
|
-
<Const export name={name} asConst>
|
|
68
|
-
{`{${stringifyObject(operationsJSON)}}`}
|
|
69
|
-
</Const>
|
|
70
|
-
</File.Source>
|
|
71
|
-
<File.Source name={'paths'} isExportable isIndexable>
|
|
72
|
-
<Const export name={'paths'} asConst>
|
|
73
|
-
{`{${stringifyObject(pathsJSON)}}`}
|
|
74
|
-
</Const>
|
|
75
|
-
</File.Source>
|
|
76
|
-
</>
|
|
77
|
-
)
|
|
78
|
-
}
|
package/src/components/Zod.tsx
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import type { ast } from '@kubb/core'
|
|
2
|
-
import { Const, File, Type } from '@kubb/renderer-jsx'
|
|
3
|
-
import type { KubbReactNode } from '@kubb/renderer-jsx/types'
|
|
4
|
-
import type { PrinterZodFactory } from '../printers/printerZod.ts'
|
|
5
|
-
import type { PrinterZodMiniFactory } from '../printers/printerZodMini.ts'
|
|
6
|
-
|
|
7
|
-
type Props = {
|
|
8
|
-
name: string
|
|
9
|
-
node: ast.SchemaNode
|
|
10
|
-
/**
|
|
11
|
-
* Pre-configured printer instance created by the generator.
|
|
12
|
-
* The generator selects `printerZod` or `printerZodMini` based on the `mini` option,
|
|
13
|
-
* then merges in any user-supplied `printer.nodes` overrides.
|
|
14
|
-
*/
|
|
15
|
-
printer: ast.Printer<PrinterZodFactory> | ast.Printer<PrinterZodMiniFactory>
|
|
16
|
-
inferTypeName?: string | null
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function Zod({ name, node, printer, inferTypeName }: Props): KubbReactNode {
|
|
20
|
-
const output = printer.print(node)
|
|
21
|
-
|
|
22
|
-
if (!output) {
|
|
23
|
-
return
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
return (
|
|
27
|
-
<>
|
|
28
|
-
<File.Source name={name} isExportable isIndexable>
|
|
29
|
-
<Const export name={name}>
|
|
30
|
-
{output}
|
|
31
|
-
</Const>
|
|
32
|
-
</File.Source>
|
|
33
|
-
{inferTypeName && (
|
|
34
|
-
<File.Source name={inferTypeName} isExportable isIndexable isTypeOnly>
|
|
35
|
-
<Type export name={inferTypeName}>
|
|
36
|
-
{`z.infer<typeof ${name}>`}
|
|
37
|
-
</Type>
|
|
38
|
-
</File.Source>
|
|
39
|
-
)}
|
|
40
|
-
</>
|
|
41
|
-
)
|
|
42
|
-
}
|
package/src/constants.ts
DELETED
|
@@ -1,366 +0,0 @@
|
|
|
1
|
-
import { resolveContentTypeVariants } from '@internals/shared'
|
|
2
|
-
import type { Adapter } from '@kubb/core'
|
|
3
|
-
import { ast, defineGenerator } from '@kubb/core'
|
|
4
|
-
import type { AdapterOas } from '@kubb/adapter-oas'
|
|
5
|
-
import { File, jsxRendererSync } from '@kubb/renderer-jsx'
|
|
6
|
-
import { Operations } from '../components/Operations.tsx'
|
|
7
|
-
import { Zod } from '../components/Zod.tsx'
|
|
8
|
-
import { ZOD_NAMESPACE_IMPORTS } from '../constants.ts'
|
|
9
|
-
import { printerZod } from '../printers/printerZod.ts'
|
|
10
|
-
import { printerZodMini } from '../printers/printerZodMini.ts'
|
|
11
|
-
import type { PluginZod, ResolverZod } from '../types'
|
|
12
|
-
import { buildSchemaNames, containsCodec } from '../utils.ts'
|
|
13
|
-
|
|
14
|
-
type StdPrinters = { output: ReturnType<typeof printerZod>; input: ReturnType<typeof printerZod> }
|
|
15
|
-
type ZodPrinterEntry = StdPrinters & { coercion: unknown; guidType: unknown; dateType: unknown }
|
|
16
|
-
type ZodMiniPrinterEntry = { printer: ReturnType<typeof printerZodMini>; guidType: unknown }
|
|
17
|
-
|
|
18
|
-
// Per-build caches: keyed on resolver (unique per plugin instance per build, GC'd when released)
|
|
19
|
-
const zodPrinterCache = new WeakMap<ResolverZod, ZodPrinterEntry>()
|
|
20
|
-
const zodMiniPrinterCache = new WeakMap<ResolverZod, ZodMiniPrinterEntry>()
|
|
21
|
-
|
|
22
|
-
type StdPrinterParams = { coercion: unknown; guidType: unknown; dateType: unknown; wrapOutput: unknown; cyclicSchemas: ReadonlySet<string>; nodes: unknown }
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Returns the cached `output`/`input` direction printers for a resolver, building them on
|
|
26
|
-
* first use. The `input` printer encodes `Date → string` for request bodies; `output` decodes
|
|
27
|
-
* `string → Date` for responses. Schemas without `dateType: 'date'` fields print identically.
|
|
28
|
-
*/
|
|
29
|
-
function getStdPrinters(resolver: ResolverZod, params: StdPrinterParams): StdPrinters {
|
|
30
|
-
const cached = zodPrinterCache.get(resolver)
|
|
31
|
-
if (cached && cached.coercion === params.coercion && cached.guidType === params.guidType && cached.dateType === params.dateType) {
|
|
32
|
-
return { output: cached.output, input: cached.input }
|
|
33
|
-
}
|
|
34
|
-
const base = { ...params, resolver } as Parameters<typeof printerZod>[0]
|
|
35
|
-
const output = printerZod({ ...base, direction: 'output' })
|
|
36
|
-
const input = printerZod({ ...base, direction: 'input' })
|
|
37
|
-
zodPrinterCache.set(resolver, { output, input, coercion: params.coercion, guidType: params.guidType, dateType: params.dateType })
|
|
38
|
-
return { output, input }
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function getMiniPrinter(resolver: ResolverZod, params: { guidType: unknown; wrapOutput: unknown; cyclicSchemas: ReadonlySet<string>; nodes: unknown }) {
|
|
42
|
-
const cached = zodMiniPrinterCache.get(resolver)
|
|
43
|
-
if (cached && cached.guidType === params.guidType) return cached.printer
|
|
44
|
-
const p = printerZodMini({ ...params, resolver } as Parameters<typeof printerZodMini>[0])
|
|
45
|
-
zodMiniPrinterCache.set(resolver, { printer: p, guidType: params.guidType })
|
|
46
|
-
return p
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Built-in generator for `@kubb/plugin-zod`. Emits one Zod schema per
|
|
51
|
-
* schema in the spec plus per-operation request/response/parameter schemas.
|
|
52
|
-
* When `mini: true`, schemas use the Zod Mini functional API instead of
|
|
53
|
-
* chainable methods.
|
|
54
|
-
*/
|
|
55
|
-
export const zodGenerator = defineGenerator<PluginZod>({
|
|
56
|
-
name: 'zod',
|
|
57
|
-
renderer: jsxRendererSync,
|
|
58
|
-
schema(node, ctx) {
|
|
59
|
-
const { adapter, config, resolver, root } = ctx
|
|
60
|
-
const { output, coercion, guidType, mini, wrapOutput, inferred, importPath, group, printer } = ctx.options
|
|
61
|
-
const dateType = (adapter as Adapter<AdapterOas>).options.dateType
|
|
62
|
-
|
|
63
|
-
if (!node.name) {
|
|
64
|
-
return
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const mode = ctx.getMode(output)
|
|
68
|
-
const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath as 'zod' | 'zod/mini')
|
|
69
|
-
const cyclicSchemas = new Set<string>(ctx.meta.circularNames)
|
|
70
|
-
|
|
71
|
-
// A codec component is rendered twice: the canonical (output) schema decodes
|
|
72
|
-
// `string → Date`, and an `${name}InputSchema` variant encodes `Date → string` for requests.
|
|
73
|
-
const hasCodec = !mini && containsCodec(node)
|
|
74
|
-
|
|
75
|
-
const codecRefNames = new Set(
|
|
76
|
-
hasCodec
|
|
77
|
-
? ast.collect<string>(node, {
|
|
78
|
-
schema: (n) => (n.type === 'ref' && n.ref && containsCodec(n) ? (ast.extractRefName(n.ref) ?? undefined) : undefined),
|
|
79
|
-
})
|
|
80
|
-
: [],
|
|
81
|
-
)
|
|
82
|
-
const importEntries = adapter.getImports(node, (schemaName) => ({
|
|
83
|
-
name: resolver.resolveSchemaName(schemaName),
|
|
84
|
-
path: resolver.resolveFile({ name: schemaName, extname: '.ts' }, { root, output, group: group ?? undefined }).path,
|
|
85
|
-
}))
|
|
86
|
-
const inputImportEntries = hasCodec
|
|
87
|
-
? [...codecRefNames].map((schemaName) => ({
|
|
88
|
-
name: resolver.resolveInputSchemaName(schemaName),
|
|
89
|
-
path: resolver.resolveFile({ name: schemaName, extname: '.ts' }, { root, output, group: group ?? undefined }).path,
|
|
90
|
-
}))
|
|
91
|
-
: []
|
|
92
|
-
const seenImports = new Set<string>()
|
|
93
|
-
const imports = [...importEntries, ...inputImportEntries].filter((imp) => {
|
|
94
|
-
const key = `${Array.isArray(imp.name) ? imp.name.join(',') : imp.name}|${imp.path}`
|
|
95
|
-
if (seenImports.has(key)) return false
|
|
96
|
-
seenImports.add(key)
|
|
97
|
-
return true
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
const meta = {
|
|
101
|
-
name: resolver.resolveSchemaName(node.name),
|
|
102
|
-
file: resolver.resolveFile({ name: node.name, extname: '.ts' }, { root, output, group: group ?? undefined }),
|
|
103
|
-
} as const
|
|
104
|
-
|
|
105
|
-
const inferTypeName = inferred ? resolver.resolveSchemaTypeName(node.name) : null
|
|
106
|
-
|
|
107
|
-
const stdPrinters = mini ? null : getStdPrinters(resolver, { coercion, guidType, dateType, wrapOutput, cyclicSchemas, nodes: printer?.nodes })
|
|
108
|
-
const schemaPrinter = mini ? getMiniPrinter(resolver, { guidType, wrapOutput, cyclicSchemas, nodes: printer?.nodes }) : stdPrinters!.output
|
|
109
|
-
|
|
110
|
-
return (
|
|
111
|
-
<File
|
|
112
|
-
baseName={meta.file.baseName}
|
|
113
|
-
path={meta.file.path}
|
|
114
|
-
meta={meta.file.meta}
|
|
115
|
-
banner={resolver.resolveBanner(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
|
|
116
|
-
footer={resolver.resolveFooter(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
|
|
117
|
-
>
|
|
118
|
-
<File.Import name={isZodImport ? 'z' : ['z']} path={importPath} isNameSpace={isZodImport} />
|
|
119
|
-
{mode === 'split' &&
|
|
120
|
-
imports.map((imp) => <File.Import key={[node.name, imp.path, imp.name].join('-')} root={meta.file.path} path={imp.path} name={imp.name} />)}
|
|
121
|
-
|
|
122
|
-
<Zod name={meta.name} node={node} printer={schemaPrinter} inferTypeName={inferTypeName} />
|
|
123
|
-
{hasCodec && stdPrinters && (
|
|
124
|
-
<Zod
|
|
125
|
-
name={resolver.resolveInputSchemaName(node.name)}
|
|
126
|
-
node={node}
|
|
127
|
-
printer={stdPrinters.input}
|
|
128
|
-
inferTypeName={inferred ? resolver.resolveInputSchemaTypeName(node.name) : null}
|
|
129
|
-
/>
|
|
130
|
-
)}
|
|
131
|
-
</File>
|
|
132
|
-
)
|
|
133
|
-
},
|
|
134
|
-
operation(node, ctx) {
|
|
135
|
-
if (!ast.isHttpOperationNode(node)) return null
|
|
136
|
-
const { adapter, config, resolver, root } = ctx
|
|
137
|
-
const { output, coercion, guidType, mini, wrapOutput, inferred, importPath, group, paramsCasing, printer } = ctx.options
|
|
138
|
-
const dateType = (adapter as Adapter<AdapterOas>).options.dateType
|
|
139
|
-
|
|
140
|
-
const mode = ctx.getMode(output)
|
|
141
|
-
const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath as 'zod' | 'zod/mini')
|
|
142
|
-
|
|
143
|
-
const params = ast.caseParams(node.parameters, paramsCasing)
|
|
144
|
-
|
|
145
|
-
const meta = {
|
|
146
|
-
file: resolver.resolveFile(
|
|
147
|
-
{ name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
|
|
148
|
-
{ root, output, group: group ?? undefined },
|
|
149
|
-
),
|
|
150
|
-
} as const
|
|
151
|
-
|
|
152
|
-
const cyclicSchemas = new Set<string>(ctx.meta.circularNames)
|
|
153
|
-
|
|
154
|
-
function renderSchemaEntry({
|
|
155
|
-
schema,
|
|
156
|
-
name,
|
|
157
|
-
keysToOmit,
|
|
158
|
-
direction = 'output',
|
|
159
|
-
}: {
|
|
160
|
-
schema: ast.SchemaNode | null
|
|
161
|
-
name: string
|
|
162
|
-
keysToOmit?: Array<string> | null
|
|
163
|
-
direction?: 'input' | 'output'
|
|
164
|
-
}) {
|
|
165
|
-
if (!schema) return null
|
|
166
|
-
|
|
167
|
-
const inferTypeName = inferred ? resolver.resolveTypeName(name) : null
|
|
168
|
-
|
|
169
|
-
// In the input direction, refs to codec components resolve to their input variant.
|
|
170
|
-
const codecRefNames =
|
|
171
|
-
direction === 'input' && !mini
|
|
172
|
-
? new Set(
|
|
173
|
-
ast.collect<string>(schema, {
|
|
174
|
-
schema: (n) => (n.type === 'ref' && n.ref && containsCodec(n) ? (ast.extractRefName(n.ref) ?? undefined) : undefined),
|
|
175
|
-
}),
|
|
176
|
-
)
|
|
177
|
-
: null
|
|
178
|
-
const imports = adapter.getImports(schema, (schemaName) => ({
|
|
179
|
-
name: codecRefNames?.has(schemaName) ? resolver.resolveInputSchemaName(schemaName) : resolver.resolveSchemaName(schemaName),
|
|
180
|
-
path: resolver.resolveFile({ name: schemaName, extname: '.ts' }, { root, output, group: group ?? undefined }).path,
|
|
181
|
-
}))
|
|
182
|
-
|
|
183
|
-
const schemaPrinter = mini
|
|
184
|
-
? keysToOmit?.length
|
|
185
|
-
? printerZodMini({ guidType, wrapOutput, resolver, keysToOmit, cyclicSchemas, nodes: printer?.nodes })
|
|
186
|
-
: getMiniPrinter(resolver, { guidType, wrapOutput, cyclicSchemas, nodes: printer?.nodes })
|
|
187
|
-
: keysToOmit?.length
|
|
188
|
-
? printerZod({ coercion, guidType, dateType, wrapOutput, resolver, keysToOmit, cyclicSchemas, nodes: printer?.nodes, direction })
|
|
189
|
-
: getStdPrinters(resolver, { coercion, guidType, dateType, wrapOutput, cyclicSchemas, nodes: printer?.nodes })[direction]
|
|
190
|
-
|
|
191
|
-
return (
|
|
192
|
-
<>
|
|
193
|
-
{mode === 'split' &&
|
|
194
|
-
imports.map((imp) => <File.Import key={[name, imp.path, imp.name].join('-')} root={meta.file.path} path={imp.path} name={imp.name} />)}
|
|
195
|
-
<Zod name={name} node={schema} printer={schemaPrinter} inferTypeName={inferTypeName} />
|
|
196
|
-
</>
|
|
197
|
-
)
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// Multiple content types for a single name — emit one schema per content type plus a union alias.
|
|
201
|
-
function buildContentTypeVariants(
|
|
202
|
-
entries: Array<{ contentType: string; schema?: ast.SchemaNode | null; keysToOmit?: Array<string> | null }>,
|
|
203
|
-
baseName: string,
|
|
204
|
-
decorate?: (schema: ast.SchemaNode) => ast.SchemaNode,
|
|
205
|
-
direction?: 'input' | 'output',
|
|
206
|
-
) {
|
|
207
|
-
const variants = resolveContentTypeVariants(entries, baseName)
|
|
208
|
-
const unionSchema = ast.createSchema({
|
|
209
|
-
type: 'union',
|
|
210
|
-
members: variants.map((variant) => ast.createSchema({ type: 'ref', name: variant.name })),
|
|
211
|
-
})
|
|
212
|
-
return (
|
|
213
|
-
<>
|
|
214
|
-
{variants.map((variant) =>
|
|
215
|
-
renderSchemaEntry({
|
|
216
|
-
schema: decorate ? decorate(variant.schema) : variant.schema,
|
|
217
|
-
name: variant.name,
|
|
218
|
-
keysToOmit: variant.keysToOmit,
|
|
219
|
-
direction,
|
|
220
|
-
}),
|
|
221
|
-
)}
|
|
222
|
-
{renderSchemaEntry({ schema: unionSchema, name: baseName, direction })}
|
|
223
|
-
</>
|
|
224
|
-
)
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
const paramSchemas = params.map((param) => renderSchemaEntry({ schema: param.schema, name: resolver.resolveParamName(node, param), direction: 'input' }))
|
|
228
|
-
|
|
229
|
-
const responseSchemas = node.responses.map((res) => {
|
|
230
|
-
const variants = (res.content ?? []).filter((entry) => entry.schema)
|
|
231
|
-
if (variants.length > 1) {
|
|
232
|
-
return buildContentTypeVariants(res.content!, resolver.resolveResponseStatusName(node, res.statusCode))
|
|
233
|
-
}
|
|
234
|
-
const primary = variants[0] ?? res.content?.[0]
|
|
235
|
-
return renderSchemaEntry({
|
|
236
|
-
schema: primary?.schema ?? null,
|
|
237
|
-
name: resolver.resolveResponseStatusName(node, res.statusCode),
|
|
238
|
-
keysToOmit: primary?.keysToOmit,
|
|
239
|
-
})
|
|
240
|
-
})
|
|
241
|
-
|
|
242
|
-
const responsesWithSchema = node.responses.filter((res) => res.content?.some((entry) => entry.schema))
|
|
243
|
-
const responseUnionSchema =
|
|
244
|
-
responsesWithSchema.length > 0
|
|
245
|
-
? (() => {
|
|
246
|
-
const responseUnionName = resolver.resolveResponseName(node)
|
|
247
|
-
|
|
248
|
-
// Collect all import names from response schemas to detect naming collisions.
|
|
249
|
-
// When a response is a $ref to a component schema whose resolved name matches
|
|
250
|
-
// the response union name, skip generation to avoid redeclaration errors.
|
|
251
|
-
const importedNames = new Set(
|
|
252
|
-
responsesWithSchema.flatMap((res) =>
|
|
253
|
-
(res.content ?? []).flatMap((entry) =>
|
|
254
|
-
entry.schema
|
|
255
|
-
? adapter
|
|
256
|
-
.getImports(entry.schema, (schemaName) => ({
|
|
257
|
-
name: resolver.resolveSchemaName(schemaName),
|
|
258
|
-
path: '',
|
|
259
|
-
}))
|
|
260
|
-
.flatMap((imp) => (Array.isArray(imp.name) ? imp.name : [imp.name]))
|
|
261
|
-
: [],
|
|
262
|
-
),
|
|
263
|
-
),
|
|
264
|
-
)
|
|
265
|
-
|
|
266
|
-
if (importedNames.has(responseUnionName)) {
|
|
267
|
-
return null
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
const members = responsesWithSchema.map((res) => ast.createSchema({ type: 'ref', name: resolver.resolveResponseStatusName(node, res.statusCode) }))
|
|
271
|
-
const unionNode = members.length === 1 ? members[0]! : ast.createSchema({ type: 'union', members })
|
|
272
|
-
|
|
273
|
-
return renderSchemaEntry({
|
|
274
|
-
schema: unionNode,
|
|
275
|
-
name: responseUnionName,
|
|
276
|
-
})
|
|
277
|
-
})()
|
|
278
|
-
: null
|
|
279
|
-
|
|
280
|
-
const requestBodyContent = node.requestBody?.content ?? []
|
|
281
|
-
const requestSchema = (() => {
|
|
282
|
-
if (requestBodyContent.length === 0) return null
|
|
283
|
-
if (requestBodyContent.length === 1) {
|
|
284
|
-
const entry = requestBodyContent[0]!
|
|
285
|
-
if (!entry.schema) return null
|
|
286
|
-
return renderSchemaEntry({
|
|
287
|
-
schema: { ...entry.schema, description: node.requestBody!.description ?? entry.schema.description },
|
|
288
|
-
name: resolver.resolveDataName(node),
|
|
289
|
-
keysToOmit: entry.keysToOmit,
|
|
290
|
-
direction: 'input',
|
|
291
|
-
})
|
|
292
|
-
}
|
|
293
|
-
return buildContentTypeVariants(
|
|
294
|
-
requestBodyContent,
|
|
295
|
-
resolver.resolveDataName(node),
|
|
296
|
-
(schema) => ({
|
|
297
|
-
...schema,
|
|
298
|
-
description: node.requestBody!.description ?? schema.description,
|
|
299
|
-
}),
|
|
300
|
-
'input',
|
|
301
|
-
)
|
|
302
|
-
})()
|
|
303
|
-
|
|
304
|
-
return (
|
|
305
|
-
<File
|
|
306
|
-
baseName={meta.file.baseName}
|
|
307
|
-
path={meta.file.path}
|
|
308
|
-
meta={meta.file.meta}
|
|
309
|
-
banner={resolver.resolveBanner(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
|
|
310
|
-
footer={resolver.resolveFooter(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
|
|
311
|
-
>
|
|
312
|
-
<File.Import name={isZodImport ? 'z' : ['z']} path={importPath} isNameSpace={isZodImport} />
|
|
313
|
-
{paramSchemas}
|
|
314
|
-
{responseSchemas}
|
|
315
|
-
{responseUnionSchema}
|
|
316
|
-
{requestSchema}
|
|
317
|
-
</File>
|
|
318
|
-
)
|
|
319
|
-
},
|
|
320
|
-
operations(nodes, ctx) {
|
|
321
|
-
const { config, resolver, root } = ctx
|
|
322
|
-
const { output, importPath, group, operations, paramsCasing } = ctx.options
|
|
323
|
-
|
|
324
|
-
if (!operations) {
|
|
325
|
-
return
|
|
326
|
-
}
|
|
327
|
-
const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath as 'zod' | 'zod/mini')
|
|
328
|
-
|
|
329
|
-
const meta = {
|
|
330
|
-
file: resolver.resolveFile({ name: 'operations', extname: '.ts' }, { root, output, group: group ?? undefined }),
|
|
331
|
-
} as const
|
|
332
|
-
|
|
333
|
-
const transformedOperations = nodes.filter(ast.isHttpOperationNode).map((node) => {
|
|
334
|
-
const params = ast.caseParams(node.parameters, paramsCasing)
|
|
335
|
-
|
|
336
|
-
return {
|
|
337
|
-
node,
|
|
338
|
-
data: buildSchemaNames(node, { params, resolver }),
|
|
339
|
-
}
|
|
340
|
-
})
|
|
341
|
-
|
|
342
|
-
const imports = transformedOperations.flatMap(({ node, data }) => {
|
|
343
|
-
const names = [data.request, ...Object.values(data.responses), ...Object.values(data.parameters)].filter(Boolean) as Array<string>
|
|
344
|
-
const opFile = resolver.resolveFile(
|
|
345
|
-
{ name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
|
|
346
|
-
{ root, output, group: group ?? undefined },
|
|
347
|
-
)
|
|
348
|
-
|
|
349
|
-
return names.map((name) => <File.Import key={[name, opFile.path].join('-')} name={[name]} root={meta.file.path} path={opFile.path} />)
|
|
350
|
-
})
|
|
351
|
-
|
|
352
|
-
return (
|
|
353
|
-
<File
|
|
354
|
-
baseName={meta.file.baseName}
|
|
355
|
-
path={meta.file.path}
|
|
356
|
-
meta={meta.file.meta}
|
|
357
|
-
banner={resolver.resolveBanner(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
|
|
358
|
-
footer={resolver.resolveFooter(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
|
|
359
|
-
>
|
|
360
|
-
<File.Import isTypeOnly name={isZodImport ? 'z' : ['z']} path={importPath} isNameSpace={isZodImport} />
|
|
361
|
-
{imports}
|
|
362
|
-
<Operations name="operations" operations={transformedOperations} />
|
|
363
|
-
</File>
|
|
364
|
-
)
|
|
365
|
-
},
|
|
366
|
-
})
|
package/src/index.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export { zodGenerator } from './generators/zodGenerator.tsx'
|
|
2
|
-
|
|
3
|
-
export { default, pluginZod, pluginZodName } from './plugin.ts'
|
|
4
|
-
export type { PrinterZodFactory, PrinterZodNodes, PrinterZodOptions } from './printers/printerZod.ts'
|
|
5
|
-
export { printerZod } from './printers/printerZod.ts'
|
|
6
|
-
export type { PrinterZodMiniFactory, PrinterZodMiniNodes, PrinterZodMiniOptions } from './printers/printerZodMini.ts'
|
|
7
|
-
export { printerZodMini } from './printers/printerZodMini.ts'
|
|
8
|
-
|
|
9
|
-
export { resolverZod } from './resolvers/resolverZod.ts'
|
|
10
|
-
|
|
11
|
-
export type { PluginZod, ResolverZod } from './types.ts'
|
package/src/plugin.ts
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { createGroupConfig } from '@internals/shared'
|
|
2
|
-
import { definePlugin } from '@kubb/core'
|
|
3
|
-
import { zodGenerator } from './generators/zodGenerator.tsx'
|
|
4
|
-
import { resolverZod } from './resolvers/resolverZod.ts'
|
|
5
|
-
import type { PluginZod } from './types.ts'
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Canonical plugin name for `@kubb/plugin-zod`. Used for driver lookups and
|
|
9
|
-
* cross-plugin dependency references.
|
|
10
|
-
*/
|
|
11
|
-
export const pluginZodName = 'plugin-zod' satisfies PluginZod['name']
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Generates Zod v4 schemas from an OpenAPI spec. Use them to validate API
|
|
15
|
-
* responses at runtime, build form schemas, or feed back into router libraries
|
|
16
|
-
* that consume Zod (tRPC, Hono, Elysia). Pair with `@kubb/plugin-client` and
|
|
17
|
-
* set the client's `parser: 'zod'` to validate every response automatically.
|
|
18
|
-
*
|
|
19
|
-
* @example
|
|
20
|
-
* ```ts
|
|
21
|
-
* import { defineConfig } from 'kubb'
|
|
22
|
-
* import { pluginTs } from '@kubb/plugin-ts'
|
|
23
|
-
* import { pluginZod } from '@kubb/plugin-zod'
|
|
24
|
-
*
|
|
25
|
-
* export default defineConfig({
|
|
26
|
-
* input: { path: './petStore.yaml' },
|
|
27
|
-
* output: { path: './src/gen' },
|
|
28
|
-
* plugins: [
|
|
29
|
-
* pluginTs(),
|
|
30
|
-
* pluginZod({
|
|
31
|
-
* output: { path: './zod' },
|
|
32
|
-
* typed: true,
|
|
33
|
-
* }),
|
|
34
|
-
* ],
|
|
35
|
-
* })
|
|
36
|
-
* ```
|
|
37
|
-
*/
|
|
38
|
-
export const pluginZod = definePlugin<PluginZod>((options) => {
|
|
39
|
-
const {
|
|
40
|
-
output = { path: 'zod', barrelType: 'named' },
|
|
41
|
-
group,
|
|
42
|
-
exclude = [],
|
|
43
|
-
include,
|
|
44
|
-
override = [],
|
|
45
|
-
typed = false,
|
|
46
|
-
operations = false,
|
|
47
|
-
mini = false,
|
|
48
|
-
guidType = 'uuid',
|
|
49
|
-
importPath = mini ? 'zod/mini' : 'zod',
|
|
50
|
-
coercion = false,
|
|
51
|
-
inferred = false,
|
|
52
|
-
wrapOutput = undefined,
|
|
53
|
-
paramsCasing,
|
|
54
|
-
printer,
|
|
55
|
-
resolver: userResolver,
|
|
56
|
-
transformer: userTransformer,
|
|
57
|
-
generators: userGenerators = [],
|
|
58
|
-
} = options
|
|
59
|
-
|
|
60
|
-
const groupConfig = createGroupConfig(group, { suffix: 'Controller' })
|
|
61
|
-
|
|
62
|
-
return {
|
|
63
|
-
name: pluginZodName,
|
|
64
|
-
options,
|
|
65
|
-
hooks: {
|
|
66
|
-
'kubb:plugin:setup'(ctx) {
|
|
67
|
-
ctx.setOptions({
|
|
68
|
-
output,
|
|
69
|
-
exclude,
|
|
70
|
-
include,
|
|
71
|
-
override,
|
|
72
|
-
group: groupConfig,
|
|
73
|
-
typed,
|
|
74
|
-
importPath,
|
|
75
|
-
coercion,
|
|
76
|
-
operations,
|
|
77
|
-
inferred,
|
|
78
|
-
guidType,
|
|
79
|
-
mini,
|
|
80
|
-
wrapOutput,
|
|
81
|
-
paramsCasing,
|
|
82
|
-
printer,
|
|
83
|
-
})
|
|
84
|
-
ctx.setResolver(userResolver ? { ...resolverZod, ...userResolver } : resolverZod)
|
|
85
|
-
if (userTransformer) {
|
|
86
|
-
ctx.setTransformer(userTransformer)
|
|
87
|
-
}
|
|
88
|
-
ctx.addGenerator(zodGenerator)
|
|
89
|
-
for (const gen of userGenerators) {
|
|
90
|
-
ctx.addGenerator(gen)
|
|
91
|
-
}
|
|
92
|
-
},
|
|
93
|
-
},
|
|
94
|
-
}
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
export default pluginZod
|