@kubb/plugin-zod 5.0.0-beta.22 → 5.0.0-beta.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.
- package/dist/index.cjs +48 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +77 -32
- package/dist/index.js +48 -24
- package/dist/index.js.map +1 -1
- package/extension.yaml +646 -185
- package/package.json +4 -4
- package/src/components/Operations.tsx +4 -4
- package/src/components/Zod.tsx +1 -1
- package/src/generators/zodGenerator.tsx +21 -9
- package/src/plugin.ts +21 -8
- package/src/resolvers/resolverZod.ts +9 -4
- package/src/types.ts +42 -21
- package/src/utils.ts +6 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubb/plugin-zod",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.25",
|
|
4
4
|
"description": "Generate Zod validation schemas from your OpenAPI specification for runtime data parsing and type safety. Pairs perfectly with @kubb/plugin-ts for end-to-end type coverage.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"code-generation",
|
|
@@ -48,15 +48,15 @@
|
|
|
48
48
|
"registry": "https://registry.npmjs.org/"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@kubb/core": "5.0.0-beta.
|
|
52
|
-
"@kubb/renderer-jsx": "5.0.0-beta.
|
|
51
|
+
"@kubb/core": "5.0.0-beta.25",
|
|
52
|
+
"@kubb/renderer-jsx": "5.0.0-beta.25",
|
|
53
53
|
"remeda": "^2.34.1"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@internals/utils": "0.0.0"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
|
-
"@kubb/renderer-jsx": "5.0.0-beta.
|
|
59
|
+
"@kubb/renderer-jsx": "5.0.0-beta.25"
|
|
60
60
|
},
|
|
61
61
|
"size-limit": [
|
|
62
62
|
{
|
|
@@ -4,11 +4,11 @@ import { Const, File, Type } from '@kubb/renderer-jsx'
|
|
|
4
4
|
import type { KubbReactNode } from '@kubb/renderer-jsx/types'
|
|
5
5
|
|
|
6
6
|
type SchemaNames = {
|
|
7
|
-
request: string |
|
|
7
|
+
request: string | null
|
|
8
8
|
parameters: {
|
|
9
|
-
path: string |
|
|
10
|
-
query: string |
|
|
11
|
-
header: string |
|
|
9
|
+
path: string | null
|
|
10
|
+
query: string | null
|
|
11
|
+
header: string | null
|
|
12
12
|
}
|
|
13
13
|
responses: { default?: string } & Record<number | string, string>
|
|
14
14
|
errors: Record<number | string, string>
|
package/src/components/Zod.tsx
CHANGED
|
@@ -13,7 +13,7 @@ type Props = {
|
|
|
13
13
|
* then merges in any user-supplied `printer.nodes` overrides.
|
|
14
14
|
*/
|
|
15
15
|
printer: ast.Printer<PrinterZodFactory> | ast.Printer<PrinterZodMiniFactory>
|
|
16
|
-
inferTypeName?: string
|
|
16
|
+
inferTypeName?: string | null
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export function Zod({ name, node, printer, inferTypeName }: Props): KubbReactNode {
|
|
@@ -17,6 +17,12 @@ type ZodMiniPrinterEntry = { printer: ReturnType<typeof printerZodMini>; guidTyp
|
|
|
17
17
|
const zodPrinterCache = new WeakMap<ResolverZod, ZodPrinterEntry>()
|
|
18
18
|
const zodMiniPrinterCache = new WeakMap<ResolverZod, ZodMiniPrinterEntry>()
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Built-in generator for `@kubb/plugin-zod`. Emits one Zod schema per
|
|
22
|
+
* schema in the spec plus per-operation request/response/parameter schemas.
|
|
23
|
+
* When `mini: true`, schemas use the Zod Mini functional API instead of
|
|
24
|
+
* chainable methods.
|
|
25
|
+
*/
|
|
20
26
|
export const zodGenerator = defineGenerator<PluginZod>({
|
|
21
27
|
name: 'zod',
|
|
22
28
|
renderer: jsxRendererSync,
|
|
@@ -34,15 +40,15 @@ export const zodGenerator = defineGenerator<PluginZod>({
|
|
|
34
40
|
|
|
35
41
|
const imports = adapter.getImports(node, (schemaName) => ({
|
|
36
42
|
name: resolver.resolveSchemaName(schemaName),
|
|
37
|
-
path: resolver.resolveFile({ name: schemaName, extname: '.ts' }, { root, output, group }).path,
|
|
43
|
+
path: resolver.resolveFile({ name: schemaName, extname: '.ts' }, { root, output, group: group ?? undefined }).path,
|
|
38
44
|
}))
|
|
39
45
|
|
|
40
46
|
const meta = {
|
|
41
47
|
name: resolver.resolveSchemaName(node.name),
|
|
42
|
-
file: resolver.resolveFile({ name: node.name, extname: '.ts' }, { root, output, group }),
|
|
48
|
+
file: resolver.resolveFile({ name: node.name, extname: '.ts' }, { root, output, group: group ?? undefined }),
|
|
43
49
|
} as const
|
|
44
50
|
|
|
45
|
-
const inferTypeName = inferred ? resolver.resolveSchemaTypeName(node.name) :
|
|
51
|
+
const inferTypeName = inferred ? resolver.resolveSchemaTypeName(node.name) : null
|
|
46
52
|
|
|
47
53
|
const cyclicSchemas = new Set<string>(ctx.meta.circularNames)
|
|
48
54
|
|
|
@@ -92,7 +98,10 @@ export const zodGenerator = defineGenerator<PluginZod>({
|
|
|
92
98
|
const params = ast.caseParams(node.parameters, paramsCasing)
|
|
93
99
|
|
|
94
100
|
const meta = {
|
|
95
|
-
file: resolver.resolveFile(
|
|
101
|
+
file: resolver.resolveFile(
|
|
102
|
+
{ name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
|
|
103
|
+
{ root, output, group: group ?? undefined },
|
|
104
|
+
),
|
|
96
105
|
} as const
|
|
97
106
|
|
|
98
107
|
const cyclicSchemas = new Set<string>(ctx.meta.circularNames)
|
|
@@ -100,11 +109,11 @@ export const zodGenerator = defineGenerator<PluginZod>({
|
|
|
100
109
|
function renderSchemaEntry({ schema, name, keysToOmit }: { schema: ast.SchemaNode | null; name: string; keysToOmit?: Array<string> | null }) {
|
|
101
110
|
if (!schema) return null
|
|
102
111
|
|
|
103
|
-
const inferTypeName = inferred ? resolver.resolveTypeName(name) :
|
|
112
|
+
const inferTypeName = inferred ? resolver.resolveTypeName(name) : null
|
|
104
113
|
|
|
105
114
|
const imports = adapter.getImports(schema, (schemaName) => ({
|
|
106
115
|
name: resolver.resolveSchemaName(schemaName),
|
|
107
|
-
path: resolver.resolveFile({ name: schemaName, extname: '.ts' }, { root, output, group }).path,
|
|
116
|
+
path: resolver.resolveFile({ name: schemaName, extname: '.ts' }, { root, output, group: group ?? undefined }).path,
|
|
108
117
|
}))
|
|
109
118
|
|
|
110
119
|
const cachedStd = zodPrinterCache.get(resolver)
|
|
@@ -213,7 +222,7 @@ export const zodGenerator = defineGenerator<PluginZod>({
|
|
|
213
222
|
const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath as 'zod' | 'zod/mini')
|
|
214
223
|
|
|
215
224
|
const meta = {
|
|
216
|
-
file: resolver.resolveFile({ name: 'operations', extname: '.ts' }, { root, output, group }),
|
|
225
|
+
file: resolver.resolveFile({ name: 'operations', extname: '.ts' }, { root, output, group: group ?? undefined }),
|
|
217
226
|
} as const
|
|
218
227
|
|
|
219
228
|
const transformedOperations = nodes.map((node) => {
|
|
@@ -226,8 +235,11 @@ export const zodGenerator = defineGenerator<PluginZod>({
|
|
|
226
235
|
})
|
|
227
236
|
|
|
228
237
|
const imports = transformedOperations.flatMap(({ node, data }) => {
|
|
229
|
-
const names = [data.request, ...Object.values(data.responses), ...Object.values(data.parameters)].filter(Boolean) as string
|
|
230
|
-
const opFile = resolver.resolveFile(
|
|
238
|
+
const names = [data.request, ...Object.values(data.responses), ...Object.values(data.parameters)].filter(Boolean) as Array<string>
|
|
239
|
+
const opFile = resolver.resolveFile(
|
|
240
|
+
{ name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
|
|
241
|
+
{ root, output, group: group ?? undefined },
|
|
242
|
+
)
|
|
231
243
|
|
|
232
244
|
return names.map((name) => <File.Import key={[name, opFile.path].join('-')} name={[name]} root={meta.file.path} path={opFile.path} />)
|
|
233
245
|
})
|
package/src/plugin.ts
CHANGED
|
@@ -5,20 +5,33 @@ import { resolverZod } from './resolvers/resolverZod.ts'
|
|
|
5
5
|
import type { PluginZod } from './types.ts'
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Canonical plugin name for `@kubb/plugin-zod
|
|
8
|
+
* Canonical plugin name for `@kubb/plugin-zod`. Used for driver lookups and
|
|
9
|
+
* cross-plugin dependency references.
|
|
9
10
|
*/
|
|
10
11
|
export const pluginZodName = 'plugin-zod' satisfies PluginZod['name']
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
|
-
* Generates Zod
|
|
14
|
-
*
|
|
15
|
-
*
|
|
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.
|
|
16
18
|
*
|
|
17
|
-
* @example
|
|
19
|
+
* @example
|
|
18
20
|
* ```ts
|
|
19
|
-
* import
|
|
21
|
+
* import { defineConfig } from 'kubb'
|
|
22
|
+
* import { pluginTs } from '@kubb/plugin-ts'
|
|
23
|
+
* import { pluginZod } from '@kubb/plugin-zod'
|
|
24
|
+
*
|
|
20
25
|
* export default defineConfig({
|
|
21
|
-
*
|
|
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
|
+
* ],
|
|
22
35
|
* })
|
|
23
36
|
* ```
|
|
24
37
|
*/
|
|
@@ -54,7 +67,7 @@ export const pluginZod = definePlugin<PluginZod>((options) => {
|
|
|
54
67
|
return `${camelCase(ctx.group)}Controller`
|
|
55
68
|
},
|
|
56
69
|
} satisfies Group)
|
|
57
|
-
:
|
|
70
|
+
: null
|
|
58
71
|
|
|
59
72
|
return {
|
|
60
73
|
name: pluginZodName,
|
|
@@ -3,12 +3,17 @@ import { defineResolver } from '@kubb/core'
|
|
|
3
3
|
import type { PluginZod } from '../types.ts'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Default resolver used by `@kubb/plugin-zod`. Decides the names and file
|
|
7
|
+
* paths for every generated Zod schema. Schemas use camelCase with a
|
|
8
|
+
* `Schema` suffix (`listPetsSchema`); their inferred types use PascalCase.
|
|
7
9
|
*
|
|
8
|
-
*
|
|
10
|
+
* @example Resolve schema and type names
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { resolverZod } from '@kubb/plugin-zod'
|
|
9
13
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
14
|
+
* resolverZod.default('list pets', 'function') // 'listPetsSchema'
|
|
15
|
+
* resolverZod.resolveSchemaTypeName('pet') // 'PetSchema'
|
|
16
|
+
* ```
|
|
12
17
|
*/
|
|
13
18
|
export const resolverZod = defineResolver<PluginZod>(() => {
|
|
14
19
|
return {
|
package/src/types.ts
CHANGED
|
@@ -75,85 +75,106 @@ export type ResolverZod = Resolver &
|
|
|
75
75
|
|
|
76
76
|
export type Options = {
|
|
77
77
|
/**
|
|
78
|
-
*
|
|
78
|
+
* Where the generated Zod schemas are written and how they are exported.
|
|
79
|
+
*
|
|
80
|
+
* @default { path: 'zod', barrel: { type: 'named' } }
|
|
79
81
|
*/
|
|
80
82
|
output?: Output
|
|
81
83
|
/**
|
|
82
|
-
*
|
|
84
|
+
* Split generated files into subfolders based on the operation's tag.
|
|
83
85
|
*/
|
|
84
86
|
group?: Group
|
|
85
87
|
/**
|
|
86
|
-
*
|
|
88
|
+
* Skip operations matching at least one entry in the list.
|
|
87
89
|
*/
|
|
88
90
|
exclude?: Array<Exclude>
|
|
89
91
|
/**
|
|
90
|
-
*
|
|
92
|
+
* Restrict generation to operations matching at least one entry in the list.
|
|
91
93
|
*/
|
|
92
94
|
include?: Array<Include>
|
|
93
95
|
/**
|
|
94
|
-
*
|
|
96
|
+
* Apply a different options object to operations matching a pattern.
|
|
95
97
|
*/
|
|
96
98
|
override?: Array<Override<ResolvedOptions>>
|
|
97
99
|
/**
|
|
98
|
-
*
|
|
100
|
+
* Module specifier used in the `import { z } from '...'` statement.
|
|
101
|
+
* Use `'zod/mini'` for the tree-shakeable bundle.
|
|
99
102
|
*
|
|
100
103
|
* @default 'zod'
|
|
101
104
|
*/
|
|
102
105
|
importPath?: 'zod' | 'zod/mini' | (string & {})
|
|
103
106
|
/**
|
|
104
|
-
*
|
|
107
|
+
* Tie each Zod schema to its TypeScript type from `@kubb/plugin-ts`. Requires
|
|
108
|
+
* `@kubb/plugin-ts` in the plugins list. TypeScript fails compilation when the
|
|
109
|
+
* schema drifts from the type.
|
|
105
110
|
*/
|
|
106
111
|
typed?: boolean
|
|
107
112
|
/**
|
|
108
|
-
*
|
|
113
|
+
* Export a `z.infer<typeof schema>` type alias next to every generated schema.
|
|
114
|
+
* Lets the Zod schema act as the single source of truth.
|
|
109
115
|
*/
|
|
110
116
|
inferred?: boolean
|
|
111
117
|
/**
|
|
112
|
-
*
|
|
118
|
+
* Wrap schemas in `z.coerce` so input is coerced before validation. Useful for
|
|
119
|
+
* form data and query params where everything arrives as a string.
|
|
120
|
+
* - `true` coerces strings, numbers, and dates.
|
|
121
|
+
* - Object form picks per-primitive coercion.
|
|
122
|
+
*
|
|
123
|
+
* @default false
|
|
124
|
+
* @see https://zod.dev/?id=coercion-for-primitives
|
|
113
125
|
*/
|
|
114
126
|
coercion?: boolean | { dates?: boolean; strings?: boolean; numbers?: boolean }
|
|
115
127
|
/**
|
|
116
|
-
*
|
|
128
|
+
* Emit an `operations.ts` file with request body, query/path params, and per-status
|
|
129
|
+
* response schemas grouped by operation.
|
|
117
130
|
*/
|
|
118
131
|
operations?: boolean
|
|
119
132
|
/**
|
|
120
|
-
* Validator
|
|
133
|
+
* Validator for `format: uuid` properties.
|
|
134
|
+
* - `'uuid'` — `z.uuid()`. Standard RFC 4122.
|
|
135
|
+
* - `'guid'` — `z.guid()`. Accepts Microsoft-style GUIDs.
|
|
121
136
|
*
|
|
122
137
|
* @default 'uuid'
|
|
123
138
|
*/
|
|
124
139
|
guidType?: 'uuid' | 'guid'
|
|
125
140
|
/**
|
|
126
|
-
*
|
|
141
|
+
* Switch to Zod Mini's functional API for better tree-shaking. Also defaults
|
|
142
|
+
* `importPath` to `'zod/mini'`.
|
|
127
143
|
*
|
|
128
144
|
* @default false
|
|
145
|
+
* @beta
|
|
129
146
|
*/
|
|
130
147
|
mini?: boolean
|
|
131
148
|
/**
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
149
|
+
* Wrap the generated Zod schema string with extra calls. Receives the raw output
|
|
150
|
+
* and the originating `SchemaNode`. Useful for round-tripping OpenAPI metadata
|
|
151
|
+
* back into Zod (e.g. `.openapi(...)`).
|
|
135
152
|
*/
|
|
136
153
|
wrapOutput?: (arg: { output: string; schema: ast.SchemaNode }) => string | undefined
|
|
137
154
|
/**
|
|
138
|
-
*
|
|
155
|
+
* Rename properties inside path/query/header schemas. Body schemas are unaffected.
|
|
156
|
+
*
|
|
157
|
+
* @note Must match the value of `paramsCasing` on `@kubb/plugin-ts`.
|
|
139
158
|
*/
|
|
140
159
|
paramsCasing?: 'camelcase'
|
|
141
160
|
/**
|
|
142
|
-
*
|
|
161
|
+
* Custom generators that run alongside the built-in Zod generators.
|
|
143
162
|
*/
|
|
144
163
|
generators?: Array<Generator<PluginZod>>
|
|
145
164
|
/**
|
|
146
|
-
* Override
|
|
165
|
+
* Override how schema and operation names are built. Methods you omit fall back
|
|
166
|
+
* to the default `resolverZod`.
|
|
147
167
|
*/
|
|
148
168
|
resolver?: Partial<ResolverZod> & ThisType<ResolverZod>
|
|
149
169
|
/**
|
|
150
|
-
*
|
|
170
|
+
* Replace the Zod handler for a specific schema type (`'integer'`, `'date'`, ...).
|
|
171
|
+
* When `mini: true`, overrides target the Zod Mini printer instead.
|
|
151
172
|
*/
|
|
152
173
|
printer?: {
|
|
153
174
|
nodes?: PrinterZodNodes | PrinterZodMiniNodes
|
|
154
175
|
}
|
|
155
176
|
/**
|
|
156
|
-
* AST visitor to
|
|
177
|
+
* AST visitor applied to each schema or operation node before printing.
|
|
157
178
|
*/
|
|
158
179
|
transformer?: ast.Visitor
|
|
159
180
|
}
|
|
@@ -163,7 +184,7 @@ type ResolvedOptions = {
|
|
|
163
184
|
exclude: Array<Exclude>
|
|
164
185
|
include: Array<Include> | undefined
|
|
165
186
|
override: Array<Override<ResolvedOptions>>
|
|
166
|
-
group: Group |
|
|
187
|
+
group: Group | null
|
|
167
188
|
typed: NonNullable<Options['typed']>
|
|
168
189
|
inferred: NonNullable<Options['inferred']>
|
|
169
190
|
importPath: NonNullable<Options['importPath']>
|
package/src/utils.ts
CHANGED
|
@@ -39,11 +39,11 @@ export function buildSchemaNames(node: ast.OperationNode, { params, resolver }:
|
|
|
39
39
|
responses['default'] = resolver.resolveResponseName(node)
|
|
40
40
|
|
|
41
41
|
return {
|
|
42
|
-
request: node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) :
|
|
42
|
+
request: node.requestBody?.content?.[0]?.schema ? resolver.resolveDataName(node) : null,
|
|
43
43
|
parameters: {
|
|
44
|
-
path: pathParam ? resolver.resolvePathParamsName(node, pathParam) :
|
|
45
|
-
query: queryParam ? resolver.resolveQueryParamsName(node, queryParam) :
|
|
46
|
-
header: headerParam ? resolver.resolveHeaderParamsName(node, headerParam) :
|
|
44
|
+
path: pathParam ? resolver.resolvePathParamsName(node, pathParam) : null,
|
|
45
|
+
query: queryParam ? resolver.resolveQueryParamsName(node, queryParam) : null,
|
|
46
|
+
header: headerParam ? resolver.resolveHeaderParamsName(node, headerParam) : null,
|
|
47
47
|
},
|
|
48
48
|
responses,
|
|
49
49
|
errors,
|
|
@@ -133,7 +133,7 @@ export function lengthConstraints({ min, max, pattern }: LengthConstraints): str
|
|
|
133
133
|
* Build `.check(z.minimum(), z.maximum())` for `zod/mini` numeric constraints.
|
|
134
134
|
*/
|
|
135
135
|
export function numberChecksMini({ min, max, exclusiveMinimum, exclusiveMaximum, multipleOf }: NumericConstraints): string {
|
|
136
|
-
const checks: string
|
|
136
|
+
const checks: Array<string> = []
|
|
137
137
|
if (min !== undefined) checks.push(`z.minimum(${min})`)
|
|
138
138
|
if (max !== undefined) checks.push(`z.maximum(${max})`)
|
|
139
139
|
if (exclusiveMinimum !== undefined) checks.push(`z.minimum(${exclusiveMinimum}, { exclusive: true })`)
|
|
@@ -146,7 +146,7 @@ export function numberChecksMini({ min, max, exclusiveMinimum, exclusiveMaximum,
|
|
|
146
146
|
* Build `.check(z.minLength(), z.maxLength(), z.regex())` for `zod/mini` length constraints.
|
|
147
147
|
*/
|
|
148
148
|
export function lengthChecksMini({ min, max, pattern }: LengthConstraints): string {
|
|
149
|
-
const checks: string
|
|
149
|
+
const checks: Array<string> = []
|
|
150
150
|
if (min !== undefined) checks.push(`z.minLength(${min})`)
|
|
151
151
|
if (max !== undefined) checks.push(`z.maxLength(${max})`)
|
|
152
152
|
if (pattern !== undefined) checks.push(`z.regex(${toRegExpString(pattern, null)})`)
|