@kubb/plugin-zod 5.0.0-beta.4 → 5.0.0-beta.56

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.
@@ -1,3 +1,5 @@
1
+ import { resolveContentTypeVariants } from '@internals/shared'
2
+ import { extractRefName } from '@kubb/ast/utils'
1
3
  import type { Adapter } from '@kubb/core'
2
4
  import { ast, defineGenerator } from '@kubb/core'
3
5
  import type { AdapterOas } from '@kubb/adapter-oas'
@@ -7,9 +9,50 @@ import { Zod } from '../components/Zod.tsx'
7
9
  import { ZOD_NAMESPACE_IMPORTS } from '../constants.ts'
8
10
  import { printerZod } from '../printers/printerZod.ts'
9
11
  import { printerZodMini } from '../printers/printerZodMini.ts'
10
- import type { PluginZod } from '../types'
11
- import { buildSchemaNames } from '../utils.ts'
12
-
12
+ import type { PluginZod, ResolverZod } from '../types'
13
+ import { buildSchemaNames, containsCodec } from '../utils.ts'
14
+
15
+ type StdPrinters = { output: ReturnType<typeof printerZod>; input: ReturnType<typeof printerZod> }
16
+ type ZodPrinterEntry = StdPrinters & { coercion: unknown; guidType: unknown; dateType: unknown }
17
+ type ZodMiniPrinterEntry = { printer: ReturnType<typeof printerZodMini>; guidType: unknown }
18
+
19
+ // Per-build caches: keyed on resolver (unique per plugin instance per build, GC'd when released)
20
+ const zodPrinterCache = new WeakMap<ResolverZod, ZodPrinterEntry>()
21
+ const zodMiniPrinterCache = new WeakMap<ResolverZod, ZodMiniPrinterEntry>()
22
+
23
+ type StdPrinterParams = { coercion: unknown; guidType: unknown; dateType: unknown; wrapOutput: unknown; cyclicSchemas: ReadonlySet<string>; nodes: unknown }
24
+
25
+ /**
26
+ * Returns the cached `output`/`input` direction printers for a resolver, building them on
27
+ * first use. The `input` printer encodes `Date → string` for request bodies; `output` decodes
28
+ * `string → Date` for responses. Schemas without `dateType: 'date'` fields print identically.
29
+ */
30
+ function getStdPrinters(resolver: ResolverZod, params: StdPrinterParams): StdPrinters {
31
+ const cached = zodPrinterCache.get(resolver)
32
+ if (cached && cached.coercion === params.coercion && cached.guidType === params.guidType && cached.dateType === params.dateType) {
33
+ return { output: cached.output, input: cached.input }
34
+ }
35
+ const base = { ...params, resolver } as Parameters<typeof printerZod>[0]
36
+ const output = printerZod({ ...base, direction: 'output' })
37
+ const input = printerZod({ ...base, direction: 'input' })
38
+ zodPrinterCache.set(resolver, { output, input, coercion: params.coercion, guidType: params.guidType, dateType: params.dateType })
39
+ return { output, input }
40
+ }
41
+
42
+ function getMiniPrinter(resolver: ResolverZod, params: { guidType: unknown; wrapOutput: unknown; cyclicSchemas: ReadonlySet<string>; nodes: unknown }) {
43
+ const cached = zodMiniPrinterCache.get(resolver)
44
+ if (cached && cached.guidType === params.guidType) return cached.printer
45
+ const p = printerZodMini({ ...params, resolver } as Parameters<typeof printerZodMini>[0])
46
+ zodMiniPrinterCache.set(resolver, { printer: p, guidType: params.guidType })
47
+ return p
48
+ }
49
+
50
+ /**
51
+ * Built-in generator for `@kubb/plugin-zod`. Emits one Zod schema per
52
+ * schema in the spec plus per-operation request/response/parameter schemas.
53
+ * When `mini: true`, schemas use the Zod Mini functional API instead of
54
+ * chainable methods.
55
+ */
13
56
  export const zodGenerator = defineGenerator<PluginZod>({
14
57
  name: 'zod',
15
58
  renderer: jsxRenderer,
@@ -22,92 +65,182 @@ export const zodGenerator = defineGenerator<PluginZod>({
22
65
  return
23
66
  }
24
67
 
25
- const mode = ctx.getMode(output)
26
68
  const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath as 'zod' | 'zod/mini')
27
-
28
- const imports = adapter.getImports(node, (schemaName) => ({
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) ? (extractRefName(n.ref) ?? undefined) : undefined),
79
+ })
80
+ : [],
81
+ )
82
+ const importEntries = adapter.getImports(node, (schemaName) => ({
29
83
  name: resolver.resolveSchemaName(schemaName),
30
- path: resolver.resolveFile({ name: schemaName, extname: '.ts' }, { root, output, group }).path,
84
+ path: resolver.resolveFile({ name: schemaName, extname: '.ts' }, { root, output, group: group ?? undefined }).path,
31
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
+ })
32
99
 
33
100
  const meta = {
34
101
  name: resolver.resolveSchemaName(node.name),
35
- file: resolver.resolveFile({ name: node.name, extname: '.ts' }, { root, output, group }),
102
+ file: resolver.resolveFile({ name: node.name, extname: '.ts' }, { root, output, group: group ?? undefined }),
36
103
  } as const
37
104
 
38
- const inferTypeName = inferred ? resolver.resolveSchemaTypeName(node.name) : undefined
39
-
40
- const cyclicSchemas = adapter.inputNode ? ast.findCircularSchemas(adapter.inputNode.schemas) : undefined
105
+ const inferTypeName = inferred ? resolver.resolveSchemaTypeName(node.name) : null
41
106
 
42
- const schemaPrinter = mini
43
- ? printerZodMini({ guidType, wrapOutput, resolver, cyclicSchemas, nodes: printer?.nodes })
44
- : printerZod({ coercion, guidType, dateType, wrapOutput, resolver, cyclicSchemas, nodes: printer?.nodes })
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
45
109
 
46
110
  return (
47
111
  <File
48
112
  baseName={meta.file.baseName}
49
113
  path={meta.file.path}
50
114
  meta={meta.file.meta}
51
- banner={resolver.resolveBanner(adapter.inputNode, { output, config })}
52
- footer={resolver.resolveFooter(adapter.inputNode, { output, config })}
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 } })}
53
117
  >
54
118
  <File.Import name={isZodImport ? 'z' : ['z']} path={importPath} isNameSpace={isZodImport} />
55
- {mode === 'split' && imports.map((imp) => <File.Import key={[node.name, imp.path].join('-')} root={meta.file.path} path={imp.path} name={imp.name} />)}
119
+ {imports.map((imp) => (
120
+ <File.Import key={[node.name, imp.path, imp.name].join('-')} root={meta.file.path} path={imp.path} name={imp.name} />
121
+ ))}
56
122
 
57
123
  <Zod name={meta.name} node={node} printer={schemaPrinter} inferTypeName={inferTypeName} />
124
+ {hasCodec && stdPrinters && (
125
+ <Zod
126
+ name={resolver.resolveInputSchemaName(node.name)}
127
+ node={node}
128
+ printer={stdPrinters.input}
129
+ inferTypeName={inferred ? resolver.resolveInputSchemaTypeName(node.name) : null}
130
+ />
131
+ )}
58
132
  </File>
59
133
  )
60
134
  },
61
135
  operation(node, ctx) {
136
+ if (!ast.isHttpOperationNode(node)) return null
62
137
  const { adapter, config, resolver, root } = ctx
63
138
  const { output, coercion, guidType, mini, wrapOutput, inferred, importPath, group, paramsCasing, printer } = ctx.options
64
139
  const dateType = (adapter as Adapter<AdapterOas>).options.dateType
65
140
 
66
- const mode = ctx.getMode(output)
67
141
  const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath as 'zod' | 'zod/mini')
68
142
 
69
143
  const params = ast.caseParams(node.parameters, paramsCasing)
70
144
 
71
145
  const meta = {
72
- file: resolver.resolveFile({ name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path }, { root, output, group }),
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
+ ),
73
150
  } as const
74
151
 
75
- const cyclicSchemas = adapter.inputNode ? ast.findCircularSchemas(adapter.inputNode.schemas) : undefined
76
-
77
- function renderSchemaEntry({ schema, name, keysToOmit }: { schema: ast.SchemaNode | null; name: string; keysToOmit?: Array<string> }) {
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
+ }) {
78
165
  if (!schema) return null
79
166
 
80
- const inferTypeName = inferred ? resolver.resolveTypeName(name) : undefined
167
+ const inferTypeName = inferred ? resolver.resolveTypeName(name) : null
81
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) ? (extractRefName(n.ref) ?? undefined) : undefined),
175
+ }),
176
+ )
177
+ : null
82
178
  const imports = adapter.getImports(schema, (schemaName) => ({
83
- name: resolver.resolveSchemaName(schemaName),
84
- path: resolver.resolveFile({ name: schemaName, extname: '.ts' }, { root, output, group }).path,
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,
85
181
  }))
86
182
 
87
183
  const schemaPrinter = mini
88
- ? printerZodMini({ guidType, wrapOutput, resolver, keysToOmit, cyclicSchemas, nodes: printer?.nodes })
89
- : printerZod({ coercion, guidType, dateType, wrapOutput, resolver, keysToOmit, cyclicSchemas, nodes: printer?.nodes })
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]
90
190
 
91
191
  return (
92
192
  <>
93
- {mode === 'split' &&
94
- imports.map((imp) => <File.Import key={[name, imp.path, imp.name].join('-')} root={meta.file.path} path={imp.path} name={imp.name} />)}
193
+ {imports.map((imp) => (
194
+ <File.Import key={[name, imp.path, imp.name].join('-')} root={meta.file.path} path={imp.path} name={imp.name} />
195
+ ))}
95
196
  <Zod name={name} node={schema} printer={schemaPrinter} inferTypeName={inferTypeName} />
96
197
  </>
97
198
  )
98
199
  }
99
200
 
100
- const paramSchemas = params.map((param) => renderSchemaEntry({ schema: param.schema, name: resolver.resolveParamName(node, param) }))
201
+ // Multiple content types for a single name — emit one schema per content type plus a union alias.
202
+ function buildContentTypeVariants(
203
+ entries: Array<{ contentType: string; schema?: ast.SchemaNode | null; keysToOmit?: Array<string> | null }>,
204
+ baseName: string,
205
+ decorate?: (schema: ast.SchemaNode) => ast.SchemaNode,
206
+ direction?: 'input' | 'output',
207
+ ) {
208
+ const variants = resolveContentTypeVariants(entries, baseName)
209
+ const unionSchema = ast.createSchema({
210
+ type: 'union',
211
+ members: variants.map((variant) => ast.createSchema({ type: 'ref', name: variant.name })),
212
+ })
213
+ return (
214
+ <>
215
+ {variants.map((variant) =>
216
+ renderSchemaEntry({
217
+ schema: decorate ? decorate(variant.schema) : variant.schema,
218
+ name: variant.name,
219
+ keysToOmit: variant.keysToOmit,
220
+ direction,
221
+ }),
222
+ )}
223
+ {renderSchemaEntry({ schema: unionSchema, name: baseName, direction })}
224
+ </>
225
+ )
226
+ }
227
+
228
+ const paramSchemas = params.map((param) => renderSchemaEntry({ schema: param.schema, name: resolver.resolveParamName(node, param), direction: 'input' }))
101
229
 
102
- const responseSchemas = node.responses.map((res) =>
103
- renderSchemaEntry({
104
- schema: res.schema,
230
+ const responseSchemas = node.responses.map((res) => {
231
+ const variants = (res.content ?? []).filter((entry) => entry.schema)
232
+ if (variants.length > 1) {
233
+ return buildContentTypeVariants(res.content!, resolver.resolveResponseStatusName(node, res.statusCode))
234
+ }
235
+ const primary = variants[0] ?? res.content?.[0]
236
+ return renderSchemaEntry({
237
+ schema: primary?.schema ?? null,
105
238
  name: resolver.resolveResponseStatusName(node, res.statusCode),
106
- keysToOmit: res.keysToOmit,
107
- }),
108
- )
239
+ keysToOmit: primary?.keysToOmit,
240
+ })
241
+ })
109
242
 
110
- const responsesWithSchema = node.responses.filter((res) => res.schema)
243
+ const responsesWithSchema = node.responses.filter((res) => res.content?.some((entry) => entry.schema))
111
244
  const responseUnionSchema =
112
245
  responsesWithSchema.length > 0
113
246
  ? (() => {
@@ -118,14 +251,16 @@ export const zodGenerator = defineGenerator<PluginZod>({
118
251
  // the response union name, skip generation to avoid redeclaration errors.
119
252
  const importedNames = new Set(
120
253
  responsesWithSchema.flatMap((res) =>
121
- res.schema
122
- ? adapter
123
- .getImports(res.schema, (schemaName) => ({
124
- name: resolver.resolveSchemaName(schemaName),
125
- path: '',
126
- }))
127
- .flatMap((imp) => (Array.isArray(imp.name) ? imp.name : [imp.name]))
128
- : [],
254
+ (res.content ?? []).flatMap((entry) =>
255
+ entry.schema
256
+ ? adapter
257
+ .getImports(entry.schema, (schemaName) => ({
258
+ name: resolver.resolveSchemaName(schemaName),
259
+ path: '',
260
+ }))
261
+ .flatMap((imp) => (Array.isArray(imp.name) ? imp.name : [imp.name]))
262
+ : [],
263
+ ),
129
264
  ),
130
265
  )
131
266
 
@@ -143,24 +278,37 @@ export const zodGenerator = defineGenerator<PluginZod>({
143
278
  })()
144
279
  : null
145
280
 
146
- const requestSchema = node.requestBody?.content?.[0]?.schema
147
- ? renderSchemaEntry({
148
- schema: {
149
- ...node.requestBody.content![0]!.schema!,
150
- description: node.requestBody.description ?? node.requestBody.content![0]!.schema!.description,
151
- },
281
+ const requestBodyContent = node.requestBody?.content ?? []
282
+ const requestSchema = (() => {
283
+ if (requestBodyContent.length === 0) return null
284
+ if (requestBodyContent.length === 1) {
285
+ const entry = requestBodyContent[0]!
286
+ if (!entry.schema) return null
287
+ return renderSchemaEntry({
288
+ schema: { ...entry.schema, description: node.requestBody!.description ?? entry.schema.description },
152
289
  name: resolver.resolveDataName(node),
153
- keysToOmit: node.requestBody.content![0]!.keysToOmit,
290
+ keysToOmit: entry.keysToOmit,
291
+ direction: 'input',
154
292
  })
155
- : null
293
+ }
294
+ return buildContentTypeVariants(
295
+ requestBodyContent,
296
+ resolver.resolveDataName(node),
297
+ (schema) => ({
298
+ ...schema,
299
+ description: node.requestBody!.description ?? schema.description,
300
+ }),
301
+ 'input',
302
+ )
303
+ })()
156
304
 
157
305
  return (
158
306
  <File
159
307
  baseName={meta.file.baseName}
160
308
  path={meta.file.path}
161
309
  meta={meta.file.meta}
162
- banner={resolver.resolveBanner(adapter.inputNode, { output, config })}
163
- footer={resolver.resolveFooter(adapter.inputNode, { output, config })}
310
+ banner={resolver.resolveBanner(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
311
+ footer={resolver.resolveFooter(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
164
312
  >
165
313
  <File.Import name={isZodImport ? 'z' : ['z']} path={importPath} isNameSpace={isZodImport} />
166
314
  {paramSchemas}
@@ -171,7 +319,7 @@ export const zodGenerator = defineGenerator<PluginZod>({
171
319
  )
172
320
  },
173
321
  operations(nodes, ctx) {
174
- const { adapter, config, resolver, root } = ctx
322
+ const { config, resolver, root } = ctx
175
323
  const { output, importPath, group, operations, paramsCasing } = ctx.options
176
324
 
177
325
  if (!operations) {
@@ -180,10 +328,10 @@ export const zodGenerator = defineGenerator<PluginZod>({
180
328
  const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath as 'zod' | 'zod/mini')
181
329
 
182
330
  const meta = {
183
- file: resolver.resolveFile({ name: 'operations', extname: '.ts' }, { root, output, group }),
331
+ file: resolver.resolveFile({ name: 'operations', extname: '.ts' }, { root, output, group: group ?? undefined }),
184
332
  } as const
185
333
 
186
- const transformedOperations = nodes.map((node) => {
334
+ const transformedOperations = nodes.filter(ast.isHttpOperationNode).map((node) => {
187
335
  const params = ast.caseParams(node.parameters, paramsCasing)
188
336
 
189
337
  return {
@@ -193,8 +341,11 @@ export const zodGenerator = defineGenerator<PluginZod>({
193
341
  })
194
342
 
195
343
  const imports = transformedOperations.flatMap(({ node, data }) => {
196
- const names = [data.request, ...Object.values(data.responses), ...Object.values(data.parameters)].filter(Boolean) as string[]
197
- const opFile = resolver.resolveFile({ name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path }, { root, output, group })
344
+ const names = [data.request, ...Object.values(data.responses), ...Object.values(data.parameters)].filter(Boolean) as Array<string>
345
+ const opFile = resolver.resolveFile(
346
+ { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
347
+ { root, output, group: group ?? undefined },
348
+ )
198
349
 
199
350
  return names.map((name) => <File.Import key={[name, opFile.path].join('-')} name={[name]} root={meta.file.path} path={opFile.path} />)
200
351
  })
@@ -204,8 +355,8 @@ export const zodGenerator = defineGenerator<PluginZod>({
204
355
  baseName={meta.file.baseName}
205
356
  path={meta.file.path}
206
357
  meta={meta.file.meta}
207
- banner={resolver.resolveBanner(adapter.inputNode, { output, config })}
208
- footer={resolver.resolveFooter(adapter.inputNode, { output, config })}
358
+ banner={resolver.resolveBanner(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
359
+ footer={resolver.resolveFooter(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
209
360
  >
210
361
  <File.Import isTypeOnly name={isZodImport ? 'z' : ['z']} path={importPath} isNameSpace={isZodImport} />
211
362
  {imports}
package/src/plugin.ts CHANGED
@@ -1,30 +1,43 @@
1
- import { camelCase } from '@internals/utils'
2
- import { definePlugin, type Group } from '@kubb/core'
1
+ import { createGroupConfig } from '@internals/shared'
2
+ import { definePlugin } from '@kubb/core'
3
3
  import { zodGenerator } from './generators/zodGenerator.tsx'
4
4
  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`, used in driver lookups and warnings.
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 validation schemas from an OpenAPI specification.
14
- * Walks schemas and operations, delegates to generators, and writes barrel files
15
- * based on the configured `barrelType`.
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 Zod schema generator
19
+ * @example
18
20
  * ```ts
19
- * import pluginZod from '@kubb/plugin-zod'
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
- * plugins: [pluginZod({ output: { path: 'zod' } })]
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
  */
25
38
  export const pluginZod = definePlugin<PluginZod>((options) => {
26
39
  const {
27
- output = { path: 'zod', barrelType: 'named' },
40
+ output = { path: 'zod', barrel: { type: 'named' } },
28
41
  group,
29
42
  exclude = [],
30
43
  include,
@@ -44,17 +57,7 @@ export const pluginZod = definePlugin<PluginZod>((options) => {
44
57
  generators: userGenerators = [],
45
58
  } = options
46
59
 
47
- const groupConfig = group
48
- ? ({
49
- ...group,
50
- name: (ctx) => {
51
- if (group.type === 'path') {
52
- return `${ctx.group.split('/')[1]}`
53
- }
54
- return `${camelCase(ctx.group)}Controller`
55
- },
56
- } satisfies Group)
57
- : undefined
60
+ const groupConfig = createGroupConfig(group)
58
61
 
59
62
  return {
60
63
  name: pluginZodName,