@kubb/plugin-zod 5.0.0-alpha.9 → 5.0.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/LICENSE +17 -10
  2. package/README.md +1 -3
  3. package/dist/index.cjs +1061 -105
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.ts +369 -4
  6. package/dist/index.js +1053 -104
  7. package/dist/index.js.map +1 -1
  8. package/extension.yaml +502 -0
  9. package/package.json +44 -70
  10. package/src/components/Operations.tsx +25 -18
  11. package/src/components/Zod.tsx +21 -121
  12. package/src/constants.ts +5 -0
  13. package/src/generators/zodGenerator.tsx +174 -160
  14. package/src/index.ts +11 -2
  15. package/src/plugin.ts +67 -156
  16. package/src/printers/printerZod.ts +339 -0
  17. package/src/printers/printerZodMini.ts +295 -0
  18. package/src/resolvers/resolverZod.ts +57 -0
  19. package/src/types.ts +130 -115
  20. package/src/utils.ts +222 -0
  21. package/dist/components-B7zUFnAm.cjs +0 -890
  22. package/dist/components-B7zUFnAm.cjs.map +0 -1
  23. package/dist/components-eECfXVou.js +0 -842
  24. package/dist/components-eECfXVou.js.map +0 -1
  25. package/dist/components.cjs +0 -4
  26. package/dist/components.d.ts +0 -56
  27. package/dist/components.js +0 -2
  28. package/dist/generators-BjPDdJUz.cjs +0 -301
  29. package/dist/generators-BjPDdJUz.cjs.map +0 -1
  30. package/dist/generators-lTWPS6oN.js +0 -290
  31. package/dist/generators-lTWPS6oN.js.map +0 -1
  32. package/dist/generators.cjs +0 -4
  33. package/dist/generators.d.ts +0 -508
  34. package/dist/generators.js +0 -2
  35. package/dist/templates/ToZod.source.cjs +0 -7
  36. package/dist/templates/ToZod.source.cjs.map +0 -1
  37. package/dist/templates/ToZod.source.d.ts +0 -7
  38. package/dist/templates/ToZod.source.js +0 -6
  39. package/dist/templates/ToZod.source.js.map +0 -1
  40. package/dist/types-CoCoOc2u.d.ts +0 -172
  41. package/src/components/index.ts +0 -2
  42. package/src/generators/index.ts +0 -2
  43. package/src/generators/operationsGenerator.tsx +0 -50
  44. package/src/parser.ts +0 -909
  45. package/src/templates/ToZod.source.ts +0 -4
  46. package/templates/ToZod.ts +0 -61
@@ -1,35 +1,42 @@
1
1
  import { stringifyObject } from '@internals/utils'
2
- import type { HttpMethod, Operation } from '@kubb/oas'
3
- import type { SchemaNames } from '@kubb/plugin-oas/hooks'
4
- import { Const, File, Type } from '@kubb/react-fabric'
5
- import type { FabricReactNode } from '@kubb/react-fabric/types'
2
+ import type { 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 | undefined
8
+ parameters: {
9
+ path: string | undefined
10
+ query: string | undefined
11
+ header: string | undefined
12
+ }
13
+ responses: { default?: string } & Record<number | string, string>
14
+ errors: Record<number | string, string>
15
+ }
6
16
 
7
17
  type Props = {
8
18
  name: string
9
- operations: Array<{ operation: Operation; data: SchemaNames }>
19
+ operations: Array<{ node: ast.OperationNode; data: SchemaNames }>
10
20
  }
11
21
 
12
- export function Operations({ name, operations }: Props): FabricReactNode {
13
- const operationsJSON = operations.reduce(
22
+ export function Operations({ name, operations }: Props): KubbReactNode {
23
+ const operationsJSON = operations.reduce<Record<string, unknown>>(
14
24
  (prev, acc) => {
15
- prev[`"${acc.operation.getOperationId()}"`] = acc.data
25
+ prev[`"${acc.node.operationId}"`] = acc.data
16
26
 
17
27
  return prev
18
28
  },
19
29
  {} as Record<string, unknown>,
20
30
  )
21
31
 
22
- const pathsJSON = operations.reduce(
23
- (prev, acc) => {
24
- prev[`"${acc.operation.path}"`] = {
25
- ...(prev[`"${acc.operation.path}"`] || ({} as Record<HttpMethod, string>)),
26
- [acc.operation.method]: `operations["${acc.operation.getOperationId()}"]`,
27
- }
32
+ const pathsJSON = operations.reduce<Record<string, Record<string, string>>>((prev, acc) => {
33
+ prev[`"${acc.node.path}"`] = {
34
+ ...(prev[`"${acc.node.path}"`] ?? {}),
35
+ [acc.node.method]: `operations["${acc.node.operationId}"]`,
36
+ }
28
37
 
29
- return prev
30
- },
31
- {} as Record<string, Record<HttpMethod, string>>,
32
- )
38
+ return prev
39
+ }, {})
33
40
 
34
41
  return (
35
42
  <>
@@ -1,140 +1,40 @@
1
- import { jsStringEscape } from '@internals/utils'
2
- import type { SchemaObject } from '@kubb/oas'
3
- import { isKeyword, type Schema, SchemaGenerator, schemaKeywords } from '@kubb/plugin-oas'
4
- import { Const, File, Type } from '@kubb/react-fabric'
5
- import type { FabricReactNode } from '@kubb/react-fabric/types'
6
- import * as parserZod from '../parser.ts'
7
- import type { PluginZod } from '../types.ts'
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'
8
6
 
9
7
  type Props = {
10
8
  name: string
11
- typeName?: 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>
12
16
  inferTypeName?: string
13
- tree: Array<Schema>
14
- schema: SchemaObject
15
- description?: string
16
- coercion: PluginZod['resolvedOptions']['coercion']
17
- mapper: PluginZod['resolvedOptions']['mapper']
18
- keysToOmit?: string[]
19
- wrapOutput?: PluginZod['resolvedOptions']['wrapOutput']
20
- version: '3' | '4'
21
- guidType: PluginZod['resolvedOptions']['guidType']
22
- emptySchemaType: PluginZod['resolvedOptions']['emptySchemaType']
23
- mini?: boolean
24
17
  }
25
18
 
26
- export function Zod({
27
- name,
28
- typeName,
29
- tree,
30
- schema,
31
- inferTypeName,
32
- mapper,
33
- coercion,
34
- keysToOmit,
35
- description,
36
- wrapOutput,
37
- version,
38
- guidType,
39
- emptySchemaType,
40
- mini = false,
41
- }: Props): FabricReactNode {
42
- const hasTuple = !!SchemaGenerator.find(tree, schemaKeywords.tuple)
19
+ export function Zod({ name, node, printer, inferTypeName }: Props): KubbReactNode {
20
+ const output = printer.print(node)
43
21
 
44
- const schemas = parserZod.sort(tree).filter((item) => {
45
- if (hasTuple && (isKeyword(item, schemaKeywords.min) || isKeyword(item, schemaKeywords.max))) {
46
- return false
47
- }
48
-
49
- return true
50
- })
51
-
52
- // In mini mode, filter out modifiers from the main schema parsing
53
- const baseSchemas = mini ? parserZod.filterMiniModifiers(schemas) : schemas
54
-
55
- const output = baseSchemas
56
- .map((schemaKeyword, index) => {
57
- const siblings = baseSchemas.filter((_, i) => i !== index)
58
-
59
- return parserZod.parse({ schema, parent: undefined, current: schemaKeyword, siblings, name }, { mapper, coercion, wrapOutput, version, guidType, mini })
60
- })
61
- .filter(Boolean)
62
- .join('')
63
-
64
- let suffix = ''
65
- const firstSchema = schemas.at(0)
66
- const lastSchema = schemas.at(-1)
67
-
68
- if (!mini && lastSchema && isKeyword(lastSchema, schemaKeywords.nullable)) {
69
- if (firstSchema && isKeyword(firstSchema, schemaKeywords.ref)) {
70
- if (version === '3') {
71
- suffix = '.unwrap().schema.unwrap()'
72
- } else {
73
- suffix = '.unwrap().unwrap()'
74
- }
75
- } else {
76
- suffix = '.unwrap()'
77
- }
78
- } else if (!mini) {
79
- if (firstSchema && isKeyword(firstSchema, schemaKeywords.ref)) {
80
- if (version === '3') {
81
- suffix = '.schema'
82
- } else {
83
- suffix = '.unwrap()'
84
- }
85
- }
22
+ if (!output) {
23
+ return
86
24
  }
87
25
 
88
- const emptyValue = parserZod.parse(
89
- {
90
- schema,
91
- parent: undefined,
92
- current: {
93
- keyword: schemaKeywords[emptySchemaType],
94
- },
95
- siblings: [],
96
- },
97
- { mapper, coercion, wrapOutput, version, guidType, mini },
98
- )
99
-
100
- let baseSchemaOutput =
101
- [output, keysToOmit?.length ? `${suffix}.omit({ ${keysToOmit.map((key) => `'${key}': true`).join(',')} })` : undefined].filter(Boolean).join('') ||
102
- emptyValue ||
103
- ''
104
-
105
- // For mini mode, wrap the output with modifiers using the parser function
106
- if (mini) {
107
- baseSchemaOutput = parserZod.wrapWithMiniModifiers(baseSchemaOutput, parserZod.extractMiniModifiers(schemas))
108
- }
109
-
110
- const wrappedSchemaOutput = wrapOutput ? wrapOutput({ output: baseSchemaOutput, schema }) || baseSchemaOutput : baseSchemaOutput
111
- const finalOutput = typeName ? `${wrappedSchemaOutput} as unknown as ${version === '4' ? 'z.ZodType' : 'ToZod'}<${typeName}>` : wrappedSchemaOutput
112
-
113
26
  return (
114
27
  <>
115
28
  <File.Source name={name} isExportable isIndexable>
116
- <Const
117
- export
118
- name={name}
119
- JSDoc={{
120
- comments: [description ? `@description ${jsStringEscape(description)}` : undefined].filter(Boolean),
121
- }}
122
- >
123
- {finalOutput}
29
+ <Const export name={name}>
30
+ {output}
124
31
  </Const>
125
32
  </File.Source>
126
33
  {inferTypeName && (
127
34
  <File.Source name={inferTypeName} isExportable isIndexable isTypeOnly>
128
- {typeName && (
129
- <Type export name={inferTypeName}>
130
- {typeName}
131
- </Type>
132
- )}
133
- {!typeName && (
134
- <Type export name={inferTypeName}>
135
- {`z.infer<typeof ${name}>`}
136
- </Type>
137
- )}
35
+ <Type export name={inferTypeName}>
36
+ {`z.infer<typeof ${name}>`}
37
+ </Type>
138
38
  </File.Source>
139
39
  )}
140
40
  </>
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Import paths that use a namespace import (`import * as z from '...'`).
3
+ * All other import paths use a named import (`import { z } from '...'`).
4
+ */
5
+ export const ZOD_NAMESPACE_IMPORTS = new Set(['zod', 'zod/mini'] as const)
@@ -1,201 +1,215 @@
1
- import path from 'node:path'
2
- import { useMode, usePluginDriver } from '@kubb/core/hooks'
3
- import { type OperationSchema as OperationSchemaType, SchemaGenerator, schemaKeywords } from '@kubb/plugin-oas'
4
- import { createReactGenerator } from '@kubb/plugin-oas/generators'
5
- import { useOas, useOperationManager, useSchemaManager } from '@kubb/plugin-oas/hooks'
6
- import { getBanner, getFooter, getImports } from '@kubb/plugin-oas/utils'
7
- import { pluginTsName } from '@kubb/plugin-ts'
8
- import { File } from '@kubb/react-fabric'
9
- import { Zod } from '../components'
1
+ import type { Adapter } from '@kubb/core'
2
+ import { ast, defineGenerator } from '@kubb/core'
3
+ import type { AdapterOas } from '@kubb/adapter-oas'
4
+ import { File, jsxRenderer } from '@kubb/renderer-jsx'
5
+ import { Operations } from '../components/Operations.tsx'
6
+ import { Zod } from '../components/Zod.tsx'
7
+ import { ZOD_NAMESPACE_IMPORTS } from '../constants.ts'
8
+ import { printerZod } from '../printers/printerZod.ts'
9
+ import { printerZodMini } from '../printers/printerZodMini.ts'
10
10
  import type { PluginZod } from '../types'
11
+ import { buildSchemaNames } from '../utils.ts'
11
12
 
12
- export const zodGenerator = createReactGenerator<PluginZod>({
13
+ export const zodGenerator = defineGenerator<PluginZod>({
13
14
  name: 'zod',
14
- Operation({ config, operation, generator, plugin }) {
15
- const {
16
- options,
17
- options: { coercion: globalCoercion, inferred, typed, mapper, wrapOutput, version, guidType, mini },
18
- } = plugin
19
-
20
- const mode = useMode()
21
- const driver = usePluginDriver()
22
-
23
- const oas = useOas()
24
- const { getSchemas, getFile, getGroup } = useOperationManager(generator)
25
- const schemaManager = useSchemaManager()
26
-
27
- const file = getFile(operation)
28
- const schemas = getSchemas(operation)
29
- const schemaGenerator = new SchemaGenerator(options, {
30
- fabric: generator.context.fabric,
31
- oas,
32
- plugin,
33
- driver,
34
- events: generator.context.events,
35
- mode,
36
- override: options.override,
37
- })
15
+ renderer: jsxRenderer,
16
+ schema(node, ctx) {
17
+ const { adapter, config, resolver, root } = ctx
18
+ const { output, coercion, guidType, mini, wrapOutput, inferred, importPath, group, printer } = ctx.options
19
+ const dateType = (adapter as Adapter<AdapterOas>).options.dateType
20
+
21
+ if (!node.name) {
22
+ return
23
+ }
38
24
 
39
- const operationSchemas = [schemas.pathParams, schemas.queryParams, schemas.headerParams, schemas.statusCodes, schemas.request, schemas.response]
40
- .flat()
41
- .filter(Boolean)
42
- const toZodPath = path.resolve(config.root, config.output.path, '.kubb/ToZod.ts')
25
+ const mode = ctx.getMode(output)
26
+ const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath as 'zod' | 'zod/mini')
43
27
 
44
- const mapOperationSchema = ({ name, schema: schemaOriginal, description, keysToOmit: keysToOmitOriginal, ...options }: OperationSchemaType) => {
45
- let schemaObject = schemaOriginal
46
- let keysToOmit = keysToOmitOriginal
28
+ const imports = adapter.getImports(node, (schemaName) => ({
29
+ name: resolver.resolveSchemaName(schemaName),
30
+ path: resolver.resolveFile({ name: schemaName, extname: '.ts' }, { root, output, group }).path,
31
+ }))
47
32
 
48
- if ((schemaOriginal.anyOf || schemaOriginal.oneOf) && keysToOmitOriginal && keysToOmitOriginal.length > 0) {
49
- schemaObject = structuredClone(schemaOriginal)
33
+ const meta = {
34
+ name: resolver.resolveSchemaName(node.name),
35
+ file: resolver.resolveFile({ name: node.name, extname: '.ts' }, { root, output, group }),
36
+ } as const
50
37
 
51
- // Remove $ref so the schema parser generates inline schema instead of a reference
52
- delete schemaObject.$ref
38
+ const inferTypeName = inferred ? resolver.resolveSchemaTypeName(node.name) : undefined
53
39
 
54
- for (const key of keysToOmitOriginal) {
55
- delete schemaObject.properties?.[key]
56
- }
40
+ const cyclicSchemas = adapter.inputNode ? ast.findCircularSchemas(adapter.inputNode.schemas) : undefined
57
41
 
58
- if (Array.isArray(schemaObject.required)) {
59
- schemaObject.required = schemaObject.required.filter((key) => !keysToOmitOriginal.includes(key))
60
- }
42
+ const schemaPrinter = mini
43
+ ? printerZodMini({ guidType, wrapOutput, resolver, cyclicSchemas, nodes: printer?.nodes })
44
+ : printerZod({ coercion, guidType, dateType, wrapOutput, resolver, cyclicSchemas, nodes: printer?.nodes })
61
45
 
62
- keysToOmit = undefined
63
- }
46
+ return (
47
+ <File
48
+ baseName={meta.file.baseName}
49
+ path={meta.file.path}
50
+ meta={meta.file.meta}
51
+ banner={resolver.resolveBanner(adapter.inputNode, { output, config })}
52
+ footer={resolver.resolveFooter(adapter.inputNode, { output, config })}
53
+ >
54
+ <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} />)}
64
56
 
65
- const hasProperties = Object.keys(schemaObject || {}).length > 0
66
- const hasDefaults = Object.values(schemaObject.properties || {}).some((prop) => prop && Object.hasOwn(prop, 'default'))
57
+ <Zod name={meta.name} node={node} printer={schemaPrinter} inferTypeName={inferTypeName} />
58
+ </File>
59
+ )
60
+ },
61
+ operation(node, ctx) {
62
+ const { adapter, config, resolver, root } = ctx
63
+ const { output, coercion, guidType, mini, wrapOutput, inferred, importPath, group, paramsCasing, printer } = ctx.options
64
+ const dateType = (adapter as Adapter<AdapterOas>).options.dateType
67
65
 
68
- const required = Array.isArray(schemaObject?.required) ? schemaObject.required.length > 0 : !!schemaObject?.required
69
- const optional = !required && !hasDefaults && hasProperties && name.includes('Params')
66
+ const mode = ctx.getMode(output)
67
+ const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath as 'zod' | 'zod/mini')
70
68
 
71
- if (!optional && Array.isArray(schemaObject.required) && !schemaObject.required.length) {
72
- schemaObject.required = Object.entries(schemaObject.properties || {})
73
- .filter(([_key, value]) => value && Object.hasOwn(value, 'default'))
74
- .map(([key]) => key)
75
- }
69
+ const params = ast.caseParams(node.parameters, paramsCasing)
76
70
 
77
- const tree = [
78
- ...schemaGenerator.parse({ schema: schemaObject, name, parentName: null }),
79
- optional ? { keyword: schemaKeywords.optional } : undefined,
80
- ].filter(Boolean)
81
- const imports = getImports(tree)
82
- const group = options.operation ? getGroup(options.operation) : undefined
71
+ const meta = {
72
+ file: resolver.resolveFile({ name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path }, { root, output, group }),
73
+ } as const
83
74
 
84
- const coercion = name.includes('Params') ? { numbers: true, strings: false, dates: true } : globalCoercion
75
+ const cyclicSchemas = adapter.inputNode ? ast.findCircularSchemas(adapter.inputNode.schemas) : undefined
85
76
 
86
- const zod = {
87
- name: schemaManager.getName(name, { type: 'function' }),
88
- inferTypeName: schemaManager.getName(name, { type: 'type' }),
89
- file: schemaManager.getFile(name),
90
- }
77
+ function renderSchemaEntry({ schema, name, keysToOmit }: { schema: ast.SchemaNode | null; name: string; keysToOmit?: Array<string> }) {
78
+ if (!schema) return null
91
79
 
92
- const type = {
93
- name: schemaManager.getName(name, {
94
- type: 'type',
95
- pluginName: pluginTsName,
96
- }),
97
- file: schemaManager.getFile(options.operationName || name, {
98
- pluginName: pluginTsName,
99
- group,
100
- }),
101
- }
80
+ const inferTypeName = inferred ? resolver.resolveTypeName(name) : undefined
81
+
82
+ const imports = adapter.getImports(schema, (schemaName) => ({
83
+ name: resolver.resolveSchemaName(schemaName),
84
+ path: resolver.resolveFile({ name: schemaName, extname: '.ts' }, { root, output, group }).path,
85
+ }))
86
+
87
+ 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 })
102
90
 
103
91
  return (
104
92
  <>
105
- {typed && <File.Import isTypeOnly root={file.path} path={type.file.path} name={[type.name]} />}
106
- {imports.map((imp) => (
107
- <File.Import key={[imp.path, imp.name, imp.isTypeOnly].join('-')} root={file.path} path={imp.path} name={imp.name} />
108
- ))}
109
- <Zod
110
- name={zod.name}
111
- typeName={typed ? type.name : undefined}
112
- inferTypeName={inferred ? zod.inferTypeName : undefined}
113
- description={description}
114
- tree={tree}
115
- schema={schemaObject}
116
- mapper={mapper}
117
- coercion={coercion}
118
- keysToOmit={keysToOmit}
119
- wrapOutput={wrapOutput}
120
- version={plugin.options.version}
121
- guidType={guidType}
122
- emptySchemaType={plugin.options.emptySchemaType}
123
- mini={mini}
124
- />
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} />)}
95
+ <Zod name={name} node={schema} printer={schemaPrinter} inferTypeName={inferTypeName} />
125
96
  </>
126
97
  )
127
98
  }
128
99
 
129
- const isZodImport = plugin.options.importPath === 'zod' || plugin.options.importPath === 'zod/mini'
100
+ const paramSchemas = params.map((param) => renderSchemaEntry({ schema: param.schema, name: resolver.resolveParamName(node, param) }))
101
+
102
+ const responseSchemas = node.responses.map((res) =>
103
+ renderSchemaEntry({
104
+ schema: res.schema,
105
+ name: resolver.resolveResponseStatusName(node, res.statusCode),
106
+ keysToOmit: res.keysToOmit,
107
+ }),
108
+ )
109
+
110
+ const responsesWithSchema = node.responses.filter((res) => res.schema)
111
+ const responseUnionSchema =
112
+ responsesWithSchema.length > 0
113
+ ? (() => {
114
+ const responseUnionName = resolver.resolveResponseName(node)
115
+
116
+ // Collect all import names from response schemas to detect naming collisions.
117
+ // When a response is a $ref to a component schema whose resolved name matches
118
+ // the response union name, skip generation to avoid redeclaration errors.
119
+ const importedNames = new Set(
120
+ 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
+ : [],
129
+ ),
130
+ )
131
+
132
+ if (importedNames.has(responseUnionName)) {
133
+ return null
134
+ }
135
+
136
+ const members = responsesWithSchema.map((res) => ast.createSchema({ type: 'ref', name: resolver.resolveResponseStatusName(node, res.statusCode) }))
137
+ const unionNode = members.length === 1 ? members[0]! : ast.createSchema({ type: 'union', members })
138
+
139
+ return renderSchemaEntry({
140
+ schema: unionNode,
141
+ name: responseUnionName,
142
+ })
143
+ })()
144
+ : null
145
+
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
+ },
152
+ name: resolver.resolveDataName(node),
153
+ keysToOmit: node.requestBody.content![0]!.keysToOmit,
154
+ })
155
+ : null
130
156
 
131
157
  return (
132
158
  <File
133
- baseName={file.baseName}
134
- path={file.path}
135
- meta={file.meta}
136
- banner={getBanner({ oas, output: plugin.options.output, config: driver.config })}
137
- footer={getFooter({ oas, output: plugin.options.output })}
159
+ baseName={meta.file.baseName}
160
+ path={meta.file.path}
161
+ meta={meta.file.meta}
162
+ banner={resolver.resolveBanner(adapter.inputNode, { output, config })}
163
+ footer={resolver.resolveFooter(adapter.inputNode, { output, config })}
138
164
  >
139
- <File.Import name={isZodImport ? 'z' : ['z']} path={plugin.options.importPath} isNameSpace={isZodImport} />
140
- {typed && version === '3' && <File.Import name={['ToZod']} isTypeOnly root={file.path} path={toZodPath} />}
141
- {operationSchemas.map(mapOperationSchema)}
165
+ <File.Import name={isZodImport ? 'z' : ['z']} path={importPath} isNameSpace={isZodImport} />
166
+ {paramSchemas}
167
+ {responseSchemas}
168
+ {responseUnionSchema}
169
+ {requestSchema}
142
170
  </File>
143
171
  )
144
172
  },
145
- Schema({ config, schema, plugin }) {
146
- const { getName, getFile } = useSchemaManager()
147
- const {
148
- options: { output, emptySchemaType, coercion, inferred, typed, mapper, importPath, wrapOutput, version, guidType, mini },
149
- } = plugin
150
- const driver = usePluginDriver()
151
- const oas = useOas()
152
-
153
- const imports = getImports(schema.tree)
154
-
155
- const zod = {
156
- name: getName(schema.name, { type: 'function' }),
157
- inferTypeName: getName(schema.name, { type: 'type' }),
158
- file: getFile(schema.name),
159
- }
173
+ operations(nodes, ctx) {
174
+ const { adapter, config, resolver, root } = ctx
175
+ const { output, importPath, group, operations, paramsCasing } = ctx.options
160
176
 
161
- const type = {
162
- name: getName(schema.name, { type: 'type', pluginName: pluginTsName }),
163
- file: getFile(schema.name, { pluginName: pluginTsName }),
177
+ if (!operations) {
178
+ return
164
179
  }
180
+ const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath as 'zod' | 'zod/mini')
181
+
182
+ const meta = {
183
+ file: resolver.resolveFile({ name: 'operations', extname: '.ts' }, { root, output, group }),
184
+ } as const
165
185
 
166
- const isZodImport = importPath === 'zod' || importPath === 'zod/mini'
167
- const toZodPath = path.resolve(config.root, config.output.path, '.kubb/ToZod.ts')
186
+ const transformedOperations = nodes.map((node) => {
187
+ const params = ast.caseParams(node.parameters, paramsCasing)
188
+
189
+ return {
190
+ node,
191
+ data: buildSchemaNames(node, { params, resolver }),
192
+ }
193
+ })
194
+
195
+ 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 })
198
+
199
+ return names.map((name) => <File.Import key={[name, opFile.path].join('-')} name={[name]} root={meta.file.path} path={opFile.path} />)
200
+ })
168
201
 
169
202
  return (
170
203
  <File
171
- baseName={zod.file.baseName}
172
- path={zod.file.path}
173
- meta={zod.file.meta}
174
- banner={getBanner({ oas, output, config: driver.config })}
175
- footer={getFooter({ oas, output })}
204
+ baseName={meta.file.baseName}
205
+ path={meta.file.path}
206
+ meta={meta.file.meta}
207
+ banner={resolver.resolveBanner(adapter.inputNode, { output, config })}
208
+ footer={resolver.resolveFooter(adapter.inputNode, { output, config })}
176
209
  >
177
- <File.Import name={isZodImport ? 'z' : ['z']} path={importPath} isNameSpace={isZodImport} />
178
- {typed && <File.Import isTypeOnly root={zod.file.path} path={type.file.path} name={[type.name]} />}
179
- {typed && version === '3' && <File.Import name={['ToZod']} isTypeOnly root={zod.file.path} path={toZodPath} />}
180
- {imports.map((imp) => (
181
- <File.Import key={[imp.path, imp.name, imp.isTypeOnly].join('-')} root={zod.file.path} path={imp.path} name={imp.name} />
182
- ))}
183
-
184
- <Zod
185
- name={zod.name}
186
- typeName={typed ? type.name : undefined}
187
- inferTypeName={inferred ? zod.inferTypeName : undefined}
188
- description={schema.value.description}
189
- tree={schema.tree}
190
- schema={schema.value}
191
- mapper={mapper}
192
- coercion={coercion}
193
- wrapOutput={wrapOutput}
194
- version={version}
195
- guidType={guidType}
196
- emptySchemaType={emptySchemaType}
197
- mini={mini}
198
- />
210
+ <File.Import isTypeOnly name={isZodImport ? 'z' : ['z']} path={importPath} isNameSpace={isZodImport} />
211
+ {imports}
212
+ <Operations name="operations" operations={transformedOperations} />
199
213
  </File>
200
214
  )
201
215
  },
package/src/index.ts CHANGED
@@ -1,2 +1,11 @@
1
- export { pluginZod, pluginZodName } from './plugin.ts'
2
- export type { PluginZod } from './types.ts'
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'