@kubb/plugin-zod 5.0.0-alpha.24 → 5.0.0-alpha.26

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 (47) hide show
  1. package/dist/index.cjs +1673 -88
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.ts +317 -2
  4. package/dist/index.js +1646 -88
  5. package/dist/index.js.map +1 -1
  6. package/package.json +5 -33
  7. package/src/components/Operations.tsx +22 -15
  8. package/src/components/Zod.tsx +18 -118
  9. package/src/components/ZodMini.tsx +41 -0
  10. package/src/constants.ts +5 -0
  11. package/src/generators/zodGenerator.tsx +165 -158
  12. package/src/generators/zodGeneratorLegacy.tsx +401 -0
  13. package/src/index.ts +11 -1
  14. package/src/plugin.ts +105 -129
  15. package/src/presets.ts +25 -0
  16. package/src/printers/printerZod.ts +271 -0
  17. package/src/printers/printerZodMini.ts +246 -0
  18. package/src/resolvers/resolverZod.ts +71 -0
  19. package/src/resolvers/resolverZodLegacy.ts +60 -0
  20. package/src/types.ts +121 -92
  21. package/src/utils.ts +248 -0
  22. package/dist/components-DW4Q2yVq.js +0 -868
  23. package/dist/components-DW4Q2yVq.js.map +0 -1
  24. package/dist/components-qFUGs0vU.cjs +0 -916
  25. package/dist/components-qFUGs0vU.cjs.map +0 -1
  26. package/dist/components.cjs +0 -4
  27. package/dist/components.d.ts +0 -56
  28. package/dist/components.js +0 -2
  29. package/dist/generators-C9BCTLXg.cjs +0 -301
  30. package/dist/generators-C9BCTLXg.cjs.map +0 -1
  31. package/dist/generators-maqx12yN.js +0 -290
  32. package/dist/generators-maqx12yN.js.map +0 -1
  33. package/dist/generators.cjs +0 -4
  34. package/dist/generators.d.ts +0 -12
  35. package/dist/generators.js +0 -2
  36. package/dist/templates/ToZod.source.cjs +0 -7
  37. package/dist/templates/ToZod.source.cjs.map +0 -1
  38. package/dist/templates/ToZod.source.d.ts +0 -7
  39. package/dist/templates/ToZod.source.js +0 -6
  40. package/dist/templates/ToZod.source.js.map +0 -1
  41. package/dist/types-CClg-ikj.d.ts +0 -172
  42. package/src/components/index.ts +0 -2
  43. package/src/generators/index.ts +0 -2
  44. package/src/generators/operationsGenerator.tsx +0 -50
  45. package/src/parser.ts +0 -952
  46. package/src/templates/ToZod.source.ts +0 -4
  47. package/templates/ToZod.ts +0 -61
@@ -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,208 @@
1
1
  import path from 'node:path'
2
- import { useDriver, useMode } 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'
2
+ import { caseParams, composeTransformers, transform } from '@kubb/ast'
3
+ import type { SchemaNode } from '@kubb/ast/types'
4
+ import { defineGenerator, getMode } from '@kubb/core'
8
5
  import { File } from '@kubb/react-fabric'
9
- import { Zod } from '../components'
6
+ import { Operations } from '../components/Operations.tsx'
7
+ import { Zod } from '../components/Zod.tsx'
8
+ import { ZodMini } from '../components/ZodMini.tsx'
9
+ import { ZOD_NAMESPACE_IMPORTS } from '../constants.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 = useDriver()
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
+ type: 'react',
16
+ Schema({ node, adapter, options, config, resolver }) {
17
+ const { output, coercion, guidType, mini, wrapOutput, inferred, importPath, group, transformers = [] } = options
38
18
 
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')
19
+ const transformedNode = transform(node, composeTransformers(...transformers))
43
20
 
44
- const mapOperationSchema = ({ name, schema: schemaOriginal, description, keysToOmit: keysToOmitOriginal, ...options }: OperationSchemaType) => {
45
- let schemaObject = schemaOriginal
46
- let keysToOmit = keysToOmitOriginal
21
+ if (!transformedNode.name) {
22
+ return
23
+ }
47
24
 
48
- if ((schemaOriginal.anyOf || schemaOriginal.oneOf) && keysToOmitOriginal && keysToOmitOriginal.length > 0) {
49
- schemaObject = structuredClone(schemaOriginal)
25
+ const root = path.resolve(config.root, config.output.path)
26
+ const mode = getMode(path.resolve(root, output.path))
27
+ const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath as 'zod' | 'zod/mini')
50
28
 
51
- // Remove $ref so the schema parser generates inline schema instead of a reference
52
- delete schemaObject.$ref
29
+ const imports = adapter.getImports(transformedNode, (schemaName) => ({
30
+ name: resolver.default(schemaName, 'function'),
31
+ path: resolver.resolveFile({ name: schemaName, extname: '.ts' }, { root, output, group }).path,
32
+ }))
53
33
 
54
- for (const key of keysToOmitOriginal) {
55
- delete schemaObject.properties?.[key]
56
- }
34
+ const inferTypeName = inferred ? resolver.resolveInferName(resolver.resolveName(transformedNode.name)) : undefined
57
35
 
58
- if (Array.isArray(schemaObject.required)) {
59
- schemaObject.required = schemaObject.required.filter((key) => !keysToOmitOriginal.includes(key))
60
- }
36
+ const meta = {
37
+ name: resolver.default(transformedNode.name, 'function'),
38
+ file: resolver.resolveFile({ name: transformedNode.name, extname: '.ts' }, { root, output, group }),
39
+ } as const
61
40
 
62
- keysToOmit = undefined
63
- }
41
+ return (
42
+ <File
43
+ baseName={meta.file.baseName}
44
+ path={meta.file.path}
45
+ meta={meta.file.meta}
46
+ banner={resolver.resolveBanner(adapter.rootNode, { output, config })}
47
+ footer={resolver.resolveFooter(adapter.rootNode, { output, config })}
48
+ >
49
+ <File.Import name={isZodImport ? 'z' : ['z']} path={importPath} isNameSpace={isZodImport} />
50
+ {mode === 'split' &&
51
+ imports.map((imp) => <File.Import key={[transformedNode.name, imp.path].join('-')} root={meta.file.path} path={imp.path} name={imp.name} />)}
52
+
53
+ {mini ? (
54
+ <ZodMini name={meta.name} node={transformedNode} guidType={guidType} wrapOutput={wrapOutput} inferTypeName={inferTypeName} resolver={resolver} />
55
+ ) : (
56
+ <Zod
57
+ name={meta.name}
58
+ node={transformedNode}
59
+ coercion={coercion}
60
+ guidType={guidType}
61
+ wrapOutput={wrapOutput}
62
+ inferTypeName={inferTypeName}
63
+ resolver={resolver}
64
+ />
65
+ )}
66
+ </File>
67
+ )
68
+ },
69
+ Operation({ node, adapter, options, config, resolver }) {
70
+ const { output, coercion, guidType, mini, wrapOutput, inferred, importPath, group, paramsCasing, transformers } = options
64
71
 
65
- const hasProperties = Object.keys(schemaObject || {}).length > 0
66
- const hasDefaults = Object.values(schemaObject.properties || {}).some((prop) => prop && Object.hasOwn(prop, 'default'))
72
+ const transformedNode = transform(node, composeTransformers(...transformers))
67
73
 
68
- const required = Array.isArray(schemaObject?.required) ? schemaObject.required.length > 0 : !!schemaObject?.required
69
- const optional = !required && !hasDefaults && hasProperties && name.includes('Params')
74
+ const root = path.resolve(config.root, config.output.path)
75
+ const mode = getMode(path.resolve(root, output.path))
76
+ const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath as 'zod' | 'zod/mini')
70
77
 
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
- }
78
+ const params = caseParams(transformedNode.parameters, paramsCasing)
76
79
 
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
80
+ const meta = {
81
+ file: resolver.resolveFile(
82
+ { name: transformedNode.operationId, extname: '.ts', tag: transformedNode.tags[0] ?? 'default', path: transformedNode.path },
83
+ { root, output, group },
84
+ ),
85
+ } as const
83
86
 
84
- const coercion = name.includes('Params') ? { numbers: true, strings: false, dates: true } : globalCoercion
87
+ function renderSchemaEntry({ schema, name, keysToOmit }: { schema: SchemaNode | null; name: string; keysToOmit?: Array<string> }) {
88
+ if (!schema) return null
85
89
 
86
- const zod = {
87
- name: schemaManager.getName(name, { type: 'function' }),
88
- inferTypeName: schemaManager.getName(name, { type: 'type' }),
89
- file: schemaManager.getFile(name),
90
- }
90
+ const inferTypeName = inferred ? resolver.resolveInferName(name) : undefined
91
91
 
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
- }
92
+ const imports = adapter.getImports(schema, (schemaName) => ({
93
+ name: resolver.default(schemaName, 'function'),
94
+ path: resolver.resolveFile({ name: schemaName, extname: '.ts' }, { root, output, group }).path,
95
+ }))
102
96
 
103
97
  return (
104
98
  <>
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
- />
99
+ {mode === 'split' &&
100
+ imports.map((imp) => <File.Import key={[name, imp.path, imp.name].join('-')} root={meta.file.path} path={imp.path} name={imp.name} />)}
101
+ {mini ? (
102
+ <ZodMini
103
+ name={name}
104
+ node={schema}
105
+ guidType={guidType}
106
+ wrapOutput={wrapOutput}
107
+ inferTypeName={inferTypeName}
108
+ resolver={resolver}
109
+ keysToOmit={keysToOmit}
110
+ />
111
+ ) : (
112
+ <Zod
113
+ name={name}
114
+ node={schema}
115
+ coercion={coercion}
116
+ guidType={guidType}
117
+ wrapOutput={wrapOutput}
118
+ inferTypeName={inferTypeName}
119
+ resolver={resolver}
120
+ keysToOmit={keysToOmit}
121
+ />
122
+ )}
125
123
  </>
126
124
  )
127
125
  }
128
126
 
129
- const isZodImport = plugin.options.importPath === 'zod' || plugin.options.importPath === 'zod/mini'
127
+ const paramSchemas = params.map((param) => renderSchemaEntry({ schema: param.schema, name: resolver.resolveParamName(node, param) }))
128
+
129
+ const responseSchemas = transformedNode.responses.map((res) =>
130
+ renderSchemaEntry({
131
+ schema: res.schema,
132
+ name: resolver.resolveResponseStatusName(transformedNode, res.statusCode),
133
+ keysToOmit: res.keysToOmit,
134
+ }),
135
+ )
136
+
137
+ const requestSchema = transformedNode.requestBody?.schema
138
+ ? renderSchemaEntry({
139
+ schema: {
140
+ ...transformedNode.requestBody.schema,
141
+ description: transformedNode.requestBody.description ?? transformedNode.requestBody.schema.description,
142
+ },
143
+ name: resolver.resolveDataName(transformedNode),
144
+ keysToOmit: transformedNode.requestBody.keysToOmit,
145
+ })
146
+ : null
130
147
 
131
148
  return (
132
149
  <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 })}
150
+ baseName={meta.file.baseName}
151
+ path={meta.file.path}
152
+ meta={meta.file.meta}
153
+ banner={resolver.resolveBanner(adapter.rootNode, { output, config })}
154
+ footer={resolver.resolveFooter(adapter.rootNode, { output, config })}
138
155
  >
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)}
156
+ <File.Import name={isZodImport ? 'z' : ['z']} path={importPath} isNameSpace={isZodImport} />
157
+ {paramSchemas}
158
+ {responseSchemas}
159
+ {requestSchema}
142
160
  </File>
143
161
  )
144
162
  },
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 = useDriver()
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
- }
163
+ Operations({ nodes, adapter, options, config, resolver }) {
164
+ const { output, importPath, group, operations, paramsCasing, transformers } = options
160
165
 
161
- const type = {
162
- name: getName(schema.name, { type: 'type', pluginName: pluginTsName }),
163
- file: getFile(schema.name, { pluginName: pluginTsName }),
166
+ if (!operations) {
167
+ return
164
168
  }
165
169
 
166
- const isZodImport = importPath === 'zod' || importPath === 'zod/mini'
167
- const toZodPath = path.resolve(config.root, config.output.path, '.kubb/ToZod.ts')
170
+ const root = path.resolve(config.root, config.output.path)
171
+ const isZodImport = ZOD_NAMESPACE_IMPORTS.has(importPath as 'zod' | 'zod/mini')
172
+
173
+ const meta = {
174
+ file: resolver.resolveFile({ name: 'operations', extname: '.ts' }, { root, output, group }),
175
+ } as const
176
+
177
+ const transformedOperations = nodes.map((node) => {
178
+ const transformedNode = transform(node, composeTransformers(...transformers))
179
+
180
+ const params = caseParams(transformedNode.parameters, paramsCasing)
181
+
182
+ return {
183
+ node: transformedNode,
184
+ data: buildSchemaNames(transformedNode, { params, resolver }),
185
+ }
186
+ })
187
+
188
+ const imports = transformedOperations.flatMap(({ node, data }) => {
189
+ const names = [data.request, ...Object.values(data.responses), ...Object.values(data.parameters)].filter(Boolean) as string[]
190
+ const opFile = resolver.resolveFile({ name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path }, { root, output, group })
191
+
192
+ return names.map((name) => <File.Import key={[name, opFile.path].join('-')} name={[name]} root={meta.file.path} path={opFile.path} />)
193
+ })
168
194
 
169
195
  return (
170
196
  <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 })}
197
+ baseName={meta.file.baseName}
198
+ path={meta.file.path}
199
+ meta={meta.file.meta}
200
+ banner={resolver.resolveBanner(adapter.rootNode, { output, config })}
201
+ footer={resolver.resolveFooter(adapter.rootNode, { output, config })}
176
202
  >
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
- />
203
+ <File.Import isTypeOnly name={isZodImport ? 'z' : ['z']} path={importPath} isNameSpace={isZodImport} />
204
+ {imports}
205
+ <Operations name="operations" operations={transformedOperations} />
199
206
  </File>
200
207
  )
201
208
  },