@kubb/plugin-client 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 (61) hide show
  1. package/LICENSE +17 -10
  2. package/README.md +4 -4
  3. package/dist/clients/axios.cjs +2 -2
  4. package/dist/clients/axios.cjs.map +1 -1
  5. package/dist/clients/axios.d.ts +4 -4
  6. package/dist/clients/axios.js +1 -1
  7. package/dist/clients/axios.js.map +1 -1
  8. package/dist/clients/fetch.cjs +1 -1
  9. package/dist/clients/fetch.cjs.map +1 -1
  10. package/dist/clients/fetch.d.ts +2 -2
  11. package/dist/clients/fetch.js +1 -1
  12. package/dist/clients/fetch.js.map +1 -1
  13. package/dist/index.cjs +1739 -97
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.d.ts +324 -4
  16. package/dist/index.js +1725 -95
  17. package/dist/index.js.map +1 -1
  18. package/dist/templates/clients/axios.source.cjs +1 -1
  19. package/dist/templates/clients/axios.source.js +1 -1
  20. package/dist/templates/clients/fetch.source.cjs +1 -1
  21. package/dist/templates/clients/fetch.source.js +1 -1
  22. package/extension.yaml +776 -0
  23. package/package.json +67 -84
  24. package/src/clients/axios.ts +5 -1
  25. package/src/clients/fetch.ts +5 -1
  26. package/src/components/ClassClient.tsx +45 -142
  27. package/src/components/Client.tsx +90 -129
  28. package/src/components/Operations.tsx +10 -10
  29. package/src/components/StaticClassClient.tsx +44 -138
  30. package/src/components/Url.tsx +38 -48
  31. package/src/components/WrapperClient.tsx +3 -3
  32. package/src/functionParams.ts +118 -0
  33. package/src/generators/classClientGenerator.tsx +148 -171
  34. package/src/generators/clientGenerator.tsx +95 -82
  35. package/src/generators/groupedClientGenerator.tsx +50 -52
  36. package/src/generators/operationsGenerator.tsx +11 -18
  37. package/src/generators/staticClassClientGenerator.tsx +178 -183
  38. package/src/index.ts +9 -2
  39. package/src/plugin.ts +115 -145
  40. package/src/resolvers/resolverClient.ts +22 -0
  41. package/src/types.ts +104 -44
  42. package/src/utils.ts +180 -0
  43. package/templates/clients/axios.ts +5 -2
  44. package/templates/clients/fetch.ts +5 -2
  45. package/dist/StaticClassClient-By-aMAe4.cjs +0 -677
  46. package/dist/StaticClassClient-By-aMAe4.cjs.map +0 -1
  47. package/dist/StaticClassClient-CCn9g9eF.js +0 -636
  48. package/dist/StaticClassClient-CCn9g9eF.js.map +0 -1
  49. package/dist/components.cjs +0 -7
  50. package/dist/components.d.ts +0 -216
  51. package/dist/components.js +0 -2
  52. package/dist/generators-BYUJaeZP.js +0 -723
  53. package/dist/generators-BYUJaeZP.js.map +0 -1
  54. package/dist/generators-DTxD9FDY.cjs +0 -753
  55. package/dist/generators-DTxD9FDY.cjs.map +0 -1
  56. package/dist/generators.cjs +0 -7
  57. package/dist/generators.d.ts +0 -517
  58. package/dist/generators.js +0 -2
  59. package/dist/types-DBQdg-BV.d.ts +0 -169
  60. package/src/components/index.ts +0 -5
  61. package/src/generators/index.ts +0 -5
@@ -1,133 +1,135 @@
1
1
  import path from 'node:path'
2
2
  import { camelCase, pascalCase } from '@internals/utils'
3
- import { usePluginDriver } from '@kubb/core/hooks'
4
- import type { KubbFile } from '@kubb/fabric-core/types'
5
- import type { Operation } from '@kubb/oas'
6
- import type { OperationSchemas } from '@kubb/plugin-oas'
7
- import { createReactGenerator } from '@kubb/plugin-oas/generators'
8
- import { useOas, useOperationManager } from '@kubb/plugin-oas/hooks'
9
- import { getBanner, getFooter } from '@kubb/plugin-oas/utils'
3
+ import type { ast } from '@kubb/core'
4
+ import { defineGenerator } from '@kubb/core'
5
+ import type { ResolverTs } from '@kubb/plugin-ts'
10
6
  import { pluginTsName } from '@kubb/plugin-ts'
7
+ import type { ResolverZod } from '@kubb/plugin-zod'
11
8
  import { pluginZodName } from '@kubb/plugin-zod'
12
- import { File } from '@kubb/react-fabric'
9
+ import { File, jsxRenderer } from '@kubb/renderer-jsx'
13
10
  import { ClassClient } from '../components/ClassClient'
14
11
  import { WrapperClient } from '../components/WrapperClient'
15
12
  import type { PluginClient } from '../types'
16
13
 
17
14
  type OperationData = {
18
- operation: Operation
15
+ node: ast.OperationNode
19
16
  name: string
20
- typeSchemas: OperationSchemas
21
- zodSchemas: OperationSchemas | undefined
22
- typeFile: KubbFile.File
23
- zodFile: KubbFile.File
17
+ tsResolver: ResolverTs
18
+ zodResolver: ResolverZod | undefined
19
+ typeFile: ast.FileNode
20
+ zodFile: ast.FileNode | undefined
24
21
  }
25
22
 
26
23
  type Controller = {
27
24
  name: string
28
- file: KubbFile.File
25
+ file: ast.FileNode
29
26
  operations: Array<OperationData>
30
27
  }
31
28
 
32
- export const classClientGenerator = createReactGenerator<PluginClient>({
33
- name: 'classClient',
34
- Operations({ operations, generator, plugin, config }) {
35
- const { options, name: pluginName } = plugin
36
- const driver = usePluginDriver()
37
-
38
- const oas = useOas()
39
- const { getName, getFile, getGroup, getSchemas } = useOperationManager(generator)
29
+ function resolveTypeImportNames(node: ast.OperationNode, tsResolver: ResolverTs): Array<string> {
30
+ const names: Array<string | undefined> = [
31
+ node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : undefined,
32
+ tsResolver.resolveResponseName(node),
33
+ ...node.parameters.filter((p) => p.in === 'path').map((p) => tsResolver.resolvePathParamsName(node, p)),
34
+ ...node.parameters.filter((p) => p.in === 'query').map((p) => tsResolver.resolveQueryParamsName(node, p)),
35
+ ...node.parameters.filter((p) => p.in === 'header').map((p) => tsResolver.resolveHeaderParamsName(node, p)),
36
+ ...node.responses.map((res) => tsResolver.resolveResponseStatusName(node, res.statusCode)),
37
+ ]
38
+ return names.filter((n): n is string => Boolean(n))
39
+ }
40
40
 
41
- function buildOperationData(operation: Operation): OperationData {
42
- const type = {
43
- file: getFile(operation, { pluginName: pluginTsName }),
44
- schemas: getSchemas(operation, { pluginName: pluginTsName, type: 'type' }),
45
- }
41
+ function resolveZodImportNames(node: ast.OperationNode, zodResolver: ResolverZod): Array<string> {
42
+ const names: Array<string | undefined> = [
43
+ zodResolver.resolveResponseName?.(node),
44
+ node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : undefined,
45
+ ]
46
+ return names.filter((n): n is string => Boolean(n))
47
+ }
46
48
 
47
- const zod = {
48
- file: getFile(operation, { pluginName: pluginZodName }),
49
- schemas: getSchemas(operation, { pluginName: pluginZodName, type: 'function' }),
50
- }
49
+ export const classClientGenerator = defineGenerator<PluginClient>({
50
+ name: 'classClient',
51
+ renderer: jsxRenderer,
52
+ operations(nodes, ctx) {
53
+ const { adapter, config, driver, resolver, root } = ctx
54
+ const { output, group, dataReturnType, paramsCasing, paramsType, pathParamsType, parser, importPath, sdk } = ctx.options
55
+ const baseURL = ctx.options.baseURL ?? adapter.inputNode?.meta?.baseURL
56
+
57
+ const pluginTs = driver.getPlugin(pluginTsName)
58
+ if (!pluginTs) return null
59
+
60
+ const tsResolver = driver.getResolver(pluginTsName)
61
+ const tsPluginOptions = pluginTs.options
62
+ const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) : undefined
63
+ const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : undefined
64
+
65
+ function buildOperationData(node: ast.OperationNode): OperationData {
66
+ const typeFile = tsResolver.resolveFile(
67
+ { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
68
+ { root, output: tsPluginOptions?.output ?? output, group: tsPluginOptions?.group },
69
+ )
70
+ const zodFile =
71
+ zodResolver && pluginZod?.options
72
+ ? zodResolver.resolveFile(
73
+ { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
74
+ { root, output: pluginZod.options?.output ?? output, group: pluginZod.options?.group },
75
+ )
76
+ : undefined
51
77
 
52
78
  return {
53
- operation,
54
- name: getName(operation, { type: 'function' }),
55
- typeSchemas: type.schemas,
56
- zodSchemas: zod.schemas,
57
- typeFile: type.file,
58
- zodFile: zod.file,
79
+ node: node,
80
+ name: resolver.resolveName(node.operationId),
81
+ tsResolver,
82
+ zodResolver,
83
+ typeFile,
84
+ zodFile,
59
85
  }
60
86
  }
61
87
 
62
- // Group operations by tag
63
- const controllers = operations.reduce(
64
- (acc, operation) => {
65
- const group = getGroup(operation)
66
- const groupName = group?.tag ? (options.group?.name?.({ group: camelCase(group.tag) }) ?? pascalCase(group.tag)) : 'Client'
67
-
68
- if (!group?.tag && !options.group) {
69
- // If no grouping, put all operations in a single class
70
- const name = 'ApiClient'
71
- const file = driver.getFile({
72
- name,
73
- extname: '.ts',
74
- pluginName,
75
- })
76
-
77
- const operationData = buildOperationData(operation)
78
- const previousFile = acc.find((item) => item.file.path === file.path)
88
+ const controllers = nodes.reduce((acc, operationNode) => {
89
+ const tag = operationNode.tags[0]
90
+ const groupName = tag ? (group?.name?.({ group: camelCase(tag) }) ?? pascalCase(tag)) : 'Client'
79
91
 
80
- if (previousFile) {
81
- previousFile.operations.push(operationData)
82
- } else {
83
- acc.push({ name, file, operations: [operationData] })
84
- }
85
- } else if (group?.tag) {
86
- // Group by tag
87
- const name = groupName
88
- const file = driver.getFile({
89
- name,
90
- extname: '.ts',
91
- pluginName,
92
- options: { group },
93
- })
92
+ if (!tag && !group) {
93
+ const name = 'ApiClient'
94
+ const file = resolver.resolveFile({ name, extname: '.ts' }, { root, output, group })
95
+ const operationData = buildOperationData(operationNode)
96
+ const previous = acc.find((item) => item.file.path === file.path)
94
97
 
95
- const operationData = buildOperationData(operation)
96
- const previousFile = acc.find((item) => item.file.path === file.path)
97
-
98
- if (previousFile) {
99
- previousFile.operations.push(operationData)
100
- } else {
101
- acc.push({ name, file, operations: [operationData] })
102
- }
98
+ if (previous) {
99
+ previous.operations.push(operationData)
100
+ } else {
101
+ acc.push({ name, file, operations: [operationData] })
102
+ }
103
+ } else if (tag) {
104
+ const name = groupName
105
+ const file = resolver.resolveFile({ name, extname: '.ts', tag }, { root, output, group })
106
+ const operationData = buildOperationData(operationNode)
107
+ const previous = acc.find((item) => item.file.path === file.path)
108
+
109
+ if (previous) {
110
+ previous.operations.push(operationData)
111
+ } else {
112
+ acc.push({ name, file, operations: [operationData] })
103
113
  }
114
+ }
104
115
 
105
- return acc
106
- },
107
- [] as Array<Controller>,
108
- )
116
+ return acc
117
+ }, [] as Array<Controller>)
109
118
 
110
119
  function collectTypeImports(ops: Array<OperationData>) {
111
120
  const typeImportsByFile = new Map<string, Set<string>>()
112
- const typeFilesByPath = new Map<string, KubbFile.File>()
121
+ const typeFilesByPath = new Map<string, ast.FileNode>()
113
122
 
114
123
  ops.forEach((op) => {
115
- const { typeSchemas, typeFile } = op
116
-
117
- if (!typeImportsByFile.has(typeFile.path)) {
118
- typeImportsByFile.set(typeFile.path, new Set())
124
+ const names = resolveTypeImportNames(op.node, tsResolver)
125
+ if (!typeImportsByFile.has(op.typeFile.path)) {
126
+ typeImportsByFile.set(op.typeFile.path, new Set())
119
127
  }
120
- const typeImports = typeImportsByFile.get(typeFile.path)!
121
-
122
- if (typeSchemas.request?.name) typeImports.add(typeSchemas.request.name)
123
- if (typeSchemas.response?.name) typeImports.add(typeSchemas.response.name)
124
- if (typeSchemas.pathParams?.name) typeImports.add(typeSchemas.pathParams.name)
125
- if (typeSchemas.queryParams?.name) typeImports.add(typeSchemas.queryParams.name)
126
- if (typeSchemas.headerParams?.name) typeImports.add(typeSchemas.headerParams.name)
127
- typeSchemas.statusCodes?.forEach((item) => {
128
- if (item?.name) typeImports.add(item.name)
128
+ const imports = typeImportsByFile.get(op.typeFile.path)!
129
+ names.forEach((n) => {
130
+ imports.add(n)
129
131
  })
130
- typeFilesByPath.set(typeFile.path, typeFile)
132
+ typeFilesByPath.set(op.typeFile.path, op.typeFile)
131
133
  })
132
134
 
133
135
  return { typeImportsByFile, typeFilesByPath }
@@ -135,19 +137,19 @@ export const classClientGenerator = createReactGenerator<PluginClient>({
135
137
 
136
138
  function collectZodImports(ops: Array<OperationData>) {
137
139
  const zodImportsByFile = new Map<string, Set<string>>()
138
- const zodFilesByPath = new Map<string, KubbFile.File>()
140
+ const zodFilesByPath = new Map<string, ast.FileNode>()
139
141
 
140
142
  ops.forEach((op) => {
141
- const { zodSchemas, zodFile } = op
142
-
143
- if (!zodImportsByFile.has(zodFile.path)) {
144
- zodImportsByFile.set(zodFile.path, new Set())
143
+ if (!op.zodFile || !zodResolver) return
144
+ const names = resolveZodImportNames(op.node, zodResolver)
145
+ if (!zodImportsByFile.has(op.zodFile.path)) {
146
+ zodImportsByFile.set(op.zodFile.path, new Set())
145
147
  }
146
- const zodImports = zodImportsByFile.get(zodFile.path)!
147
-
148
- if (zodSchemas?.response?.name) zodImports.add(zodSchemas.response.name)
149
- if (zodSchemas?.request?.name) zodImports.add(zodSchemas.request.name)
150
- zodFilesByPath.set(zodFile.path, zodFile)
148
+ const imports = zodImportsByFile.get(op.zodFile.path)!
149
+ names.forEach((n) => {
150
+ imports.add(n)
151
+ })
152
+ zodFilesByPath.set(op.zodFile.path, op.zodFile)
151
153
  })
152
154
 
153
155
  return { zodImportsByFile, zodFilesByPath }
@@ -156,10 +158,8 @@ export const classClientGenerator = createReactGenerator<PluginClient>({
156
158
  const files = controllers.map(({ name, file, operations: ops }) => {
157
159
  const { typeImportsByFile, typeFilesByPath } = collectTypeImports(ops)
158
160
  const { zodImportsByFile, zodFilesByPath } =
159
- options.parser === 'zod'
160
- ? collectZodImports(ops)
161
- : { zodImportsByFile: new Map<string, Set<string>>(), zodFilesByPath: new Map<string, KubbFile.File>() }
162
- const hasFormData = ops.some((op) => op.operation.getContentType() === 'multipart/form-data')
161
+ parser === 'zod' ? collectZodImports(ops) : { zodImportsByFile: new Map<string, Set<string>>(), zodFilesByPath: new Map<string, ast.FileNode>() }
162
+ const hasFormData = ops.some((op) => op.node.requestBody?.content?.[0]?.contentType === 'multipart/form-data')
163
163
 
164
164
  return (
165
165
  <File
@@ -167,106 +167,83 @@ export const classClientGenerator = createReactGenerator<PluginClient>({
167
167
  baseName={file.baseName}
168
168
  path={file.path}
169
169
  meta={file.meta}
170
- banner={getBanner({ oas, output: options.output, config: driver.config })}
171
- footer={getFooter({ oas, output: options.output })}
170
+ banner={resolver.resolveBanner(adapter.inputNode, { output, config })}
171
+ footer={resolver.resolveFooter(adapter.inputNode, { output, config })}
172
172
  >
173
- {options.importPath ? (
173
+ {importPath ? (
174
174
  <>
175
- <File.Import name={'fetch'} path={options.importPath} />
176
- <File.Import name={['mergeConfig']} path={options.importPath} />
177
- <File.Import name={['Client', 'RequestConfig', 'ResponseErrorConfig']} path={options.importPath} isTypeOnly />
175
+ <File.Import name={'fetch'} path={importPath} />
176
+ <File.Import name={['mergeConfig']} path={importPath} />
177
+ <File.Import name={['Client', 'RequestConfig', 'ResponseErrorConfig']} path={importPath} isTypeOnly />
178
178
  </>
179
179
  ) : (
180
180
  <>
181
- <File.Import name={['fetch']} root={file.path} path={path.resolve(config.root, config.output.path, '.kubb/fetch.ts')} />
182
- <File.Import name={['mergeConfig']} root={file.path} path={path.resolve(config.root, config.output.path, '.kubb/fetch.ts')} />
183
- <File.Import
184
- name={['Client', 'RequestConfig', 'ResponseErrorConfig']}
185
- root={file.path}
186
- path={path.resolve(config.root, config.output.path, '.kubb/fetch.ts')}
187
- isTypeOnly
188
- />
181
+ <File.Import name={['fetch']} root={file.path} path={path.resolve(root, '.kubb/client.ts')} />
182
+ <File.Import name={['mergeConfig']} root={file.path} path={path.resolve(root, '.kubb/client.ts')} />
183
+ <File.Import name={['Client', 'RequestConfig', 'ResponseErrorConfig']} root={file.path} path={path.resolve(root, '.kubb/client.ts')} isTypeOnly />
189
184
  </>
190
185
  )}
191
186
 
192
- {hasFormData && <File.Import name={['buildFormData']} root={file.path} path={path.resolve(config.root, config.output.path, '.kubb/config.ts')} />}
187
+ {hasFormData && <File.Import name={['buildFormData']} root={file.path} path={path.resolve(root, '.kubb/config.ts')} />}
193
188
 
194
- {Array.from(typeImportsByFile.entries()).map(([filePath, imports]) => {
189
+ {Array.from(typeImportsByFile.entries()).map(([filePath, importSet]) => {
195
190
  const typeFile = typeFilesByPath.get(filePath)
196
- if (!typeFile) {
197
- return null
198
- }
199
- const importNames = Array.from(imports).filter(Boolean)
200
- if (importNames.length === 0) {
201
- return null
202
- }
191
+ if (!typeFile) return null
192
+ const importNames = Array.from(importSet).filter(Boolean)
193
+ if (importNames.length === 0) return null
203
194
  return <File.Import key={filePath} name={importNames} root={file.path} path={typeFile.path} isTypeOnly />
204
195
  })}
205
196
 
206
- {options.parser === 'zod' &&
207
- Array.from(zodImportsByFile.entries()).map(([filePath, imports]) => {
197
+ {parser === 'zod' &&
198
+ Array.from(zodImportsByFile.entries()).map(([filePath, importSet]) => {
208
199
  const zodFile = zodFilesByPath.get(filePath)
209
- if (!zodFile) {
210
- return null
211
- }
212
- const importNames = Array.from(imports).filter(Boolean)
213
- if (importNames.length === 0) {
214
- return null
215
- }
216
-
200
+ if (!zodFile) return null
201
+ const importNames = Array.from(importSet).filter(Boolean)
202
+ if (importNames.length === 0) return null
217
203
  return <File.Import key={filePath} name={importNames} root={file.path} path={zodFile.path} />
218
204
  })}
219
205
 
220
206
  <ClassClient
221
207
  name={name}
222
208
  operations={ops}
223
- baseURL={options.baseURL}
224
- dataReturnType={options.dataReturnType}
225
- pathParamsType={options.pathParamsType}
226
- paramsCasing={options.paramsCasing}
227
- paramsType={options.paramsType}
228
- parser={options.parser}
209
+ baseURL={baseURL}
210
+ dataReturnType={dataReturnType}
211
+ pathParamsType={pathParamsType}
212
+ paramsCasing={paramsCasing}
213
+ paramsType={paramsType}
214
+ parser={parser}
229
215
  />
230
216
  </File>
231
217
  )
232
218
  })
233
219
 
234
- if (options.wrapper) {
235
- const wrapperFile = driver.getFile({
236
- name: options.wrapper.className,
237
- extname: '.ts',
238
- pluginName,
239
- })
220
+ if (sdk) {
221
+ const sdkFile = resolver.resolveFile({ name: sdk.className, extname: '.ts' }, { root, output, group })
240
222
 
241
223
  files.push(
242
224
  <File
243
- key={wrapperFile.path}
244
- baseName={wrapperFile.baseName}
245
- path={wrapperFile.path}
246
- meta={wrapperFile.meta}
247
- banner={getBanner({ oas, output: options.output, config: driver.config })}
248
- footer={getFooter({ oas, output: options.output })}
225
+ key={sdkFile.path}
226
+ baseName={sdkFile.baseName}
227
+ path={sdkFile.path}
228
+ meta={sdkFile.meta}
229
+ banner={resolver.resolveBanner(adapter.inputNode, { output, config })}
230
+ footer={resolver.resolveFooter(adapter.inputNode, { output, config })}
249
231
  >
250
- {options.importPath ? (
251
- <File.Import name={['Client', 'RequestConfig']} path={options.importPath} isTypeOnly />
232
+ {importPath ? (
233
+ <File.Import name={['Client', 'RequestConfig']} path={importPath} isTypeOnly />
252
234
  ) : (
253
- <File.Import
254
- name={['Client', 'RequestConfig']}
255
- root={wrapperFile.path}
256
- path={path.resolve(config.root, config.output.path, '.kubb/fetch.ts')}
257
- isTypeOnly
258
- />
235
+ <File.Import name={['Client', 'RequestConfig']} root={sdkFile.path} path={path.resolve(root, '.kubb/client.ts')} isTypeOnly />
259
236
  )}
260
237
 
261
238
  {controllers.map(({ name, file }) => (
262
- <File.Import key={name} name={[name]} root={wrapperFile.path} path={file.path} />
239
+ <File.Import key={name} name={[name]} root={sdkFile.path} path={file.path} />
263
240
  ))}
264
241
 
265
- <WrapperClient name={options.wrapper.className} classNames={controllers.map(({ name }) => name)} />
242
+ <WrapperClient name={sdk.className} classNames={controllers.map(({ name }) => name)} />
266
243
  </File>,
267
244
  )
268
245
  }
269
246
 
270
- return files
247
+ return <>{files}</>
271
248
  },
272
249
  })
@@ -1,123 +1,136 @@
1
1
  import path from 'node:path'
2
- import { usePluginDriver } from '@kubb/core/hooks'
3
- import { createReactGenerator } from '@kubb/plugin-oas/generators'
4
- import { useOas, useOperationManager } from '@kubb/plugin-oas/hooks'
5
- import { getBanner, getFooter } from '@kubb/plugin-oas/utils'
2
+ import { ast, defineGenerator } from '@kubb/core'
6
3
  import { pluginTsName } from '@kubb/plugin-ts'
7
4
  import { pluginZodName } from '@kubb/plugin-zod'
8
- import { File } from '@kubb/react-fabric'
5
+ import { File, jsxRenderer } from '@kubb/renderer-jsx'
9
6
  import { Client } from '../components/Client'
10
7
  import { Url } from '../components/Url.tsx'
11
8
  import type { PluginClient } from '../types'
12
9
 
13
- export const clientGenerator = createReactGenerator<PluginClient>({
10
+ export const clientGenerator = defineGenerator<PluginClient>({
14
11
  name: 'client',
15
- Operation({ config, plugin, operation, generator }) {
16
- const driver = usePluginDriver()
17
- const {
18
- options,
19
- options: { output, urlType },
20
- } = plugin
21
-
22
- const oas = useOas()
23
- const { getSchemas, getName, getFile } = useOperationManager(generator)
24
-
25
- const client = {
26
- name: getName(operation, { type: 'function' }),
27
- file: getFile(operation),
28
- }
12
+ renderer: jsxRenderer,
13
+ operation(node, ctx) {
14
+ const { adapter, config, driver, resolver, root } = ctx
15
+ const { output, urlType, dataReturnType, paramsCasing, paramsType, pathParamsType, parser, importPath, group } = ctx.options
16
+ const baseURL = ctx.options.baseURL ?? adapter.inputNode?.meta?.baseURL
29
17
 
30
- const url = {
31
- name: getName(operation, { type: 'function', suffix: 'url', prefix: 'get' }),
32
- file: getFile(operation),
33
- }
18
+ const pluginTs = driver.getPlugin(pluginTsName)
34
19
 
35
- const type = {
36
- file: getFile(operation, { pluginName: pluginTsName }),
37
- schemas: getSchemas(operation, { pluginName: pluginTsName, type: 'type' }),
20
+ if (!pluginTs) {
21
+ return null
38
22
  }
39
23
 
40
- const zod = {
41
- file: getFile(operation, { pluginName: pluginZodName }),
42
- schemas: getSchemas(operation, { pluginName: pluginZodName, type: 'function' }),
43
- }
24
+ const tsResolver = driver.getResolver(pluginTsName)
25
+
26
+ const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) : undefined
27
+ const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : undefined
28
+
29
+ const casedParams = ast.caseParams(node.parameters, paramsCasing)
30
+ const pathParams = casedParams.filter((p) => p.in === 'path')
31
+ const queryParams = casedParams.filter((p) => p.in === 'query')
32
+ const headerParams = casedParams.filter((p) => p.in === 'header')
44
33
 
45
- const isFormData = operation.getContentType() === 'multipart/form-data'
34
+ const importedTypeNames = [
35
+ ...pathParams.map((p) => tsResolver.resolvePathParamsName(node, p)),
36
+ ...queryParams.map((p) => tsResolver.resolveQueryParamsName(node, p)),
37
+ ...headerParams.map((p) => tsResolver.resolveHeaderParamsName(node, p)),
38
+ node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : undefined,
39
+ tsResolver.resolveResponseName(node),
40
+ ...node.responses.map((res) => tsResolver.resolveResponseStatusName(node, res.statusCode)),
41
+ ].filter(Boolean)
42
+
43
+ const importedZodNames =
44
+ zodResolver && parser === 'zod'
45
+ ? [zodResolver.resolveResponseName?.(node), node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : undefined].filter(Boolean)
46
+ : []
47
+
48
+ const meta = {
49
+ name: resolver.resolveName(node.operationId),
50
+ urlName: `get${resolver.resolveName(node.operationId).charAt(0).toUpperCase()}${resolver.resolveName(node.operationId).slice(1)}Url`,
51
+ file: resolver.resolveFile({ name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path }, { root, output, group }),
52
+ fileTs: tsResolver.resolveFile(
53
+ { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
54
+ {
55
+ root,
56
+ output: pluginTs.options?.output ?? output,
57
+ group: pluginTs.options?.group,
58
+ },
59
+ ),
60
+ fileZod:
61
+ zodResolver && pluginZod?.options
62
+ ? zodResolver.resolveFile(
63
+ { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
64
+ {
65
+ root,
66
+ output: pluginZod.options.output ?? output,
67
+ group: pluginZod.options?.group,
68
+ },
69
+ )
70
+ : undefined,
71
+ } as const
72
+
73
+ const isFormData = node.requestBody?.content?.[0]?.contentType === 'multipart/form-data'
46
74
 
47
75
  return (
48
76
  <File
49
- baseName={client.file.baseName}
50
- path={client.file.path}
51
- meta={client.file.meta}
52
- banner={getBanner({ oas, output, config: driver.config })}
53
- footer={getFooter({ oas, output })}
77
+ baseName={meta.file.baseName}
78
+ path={meta.file.path}
79
+ meta={meta.file.meta}
80
+ banner={resolver.resolveBanner(adapter.inputNode, { output, config })}
81
+ footer={resolver.resolveFooter(adapter.inputNode, { output, config })}
54
82
  >
55
- {options.importPath ? (
83
+ {importPath ? (
56
84
  <>
57
- <File.Import name={'fetch'} path={options.importPath} />
58
- <File.Import name={['Client', 'RequestConfig', 'ResponseErrorConfig']} path={options.importPath} isTypeOnly />
85
+ <File.Import name={'fetch'} path={importPath} />
86
+ <File.Import name={['Client', 'RequestConfig', 'ResponseErrorConfig']} path={importPath} isTypeOnly />
59
87
  </>
60
88
  ) : (
61
89
  <>
62
- <File.Import name={['fetch']} root={client.file.path} path={path.resolve(config.root, config.output.path, '.kubb/fetch.ts')} />
90
+ <File.Import name={['fetch']} root={meta.file.path} path={path.resolve(root, '.kubb/client.ts')} />
63
91
  <File.Import
64
92
  name={['Client', 'RequestConfig', 'ResponseErrorConfig']}
65
- root={client.file.path}
66
- path={path.resolve(config.root, config.output.path, '.kubb/fetch.ts')}
93
+ root={meta.file.path}
94
+ path={path.resolve(root, '.kubb/client.ts')}
67
95
  isTypeOnly
68
96
  />
69
97
  </>
70
98
  )}
71
99
 
72
- {isFormData && type.schemas.request?.name && (
73
- <File.Import name={['buildFormData']} root={client.file.path} path={path.resolve(config.root, config.output.path, '.kubb/config.ts')} />
100
+ {isFormData && node.requestBody?.content?.[0]?.schema && (
101
+ <File.Import name={['buildFormData']} root={meta.file.path} path={path.resolve(root, '.kubb/config.ts')} />
74
102
  )}
75
103
 
76
- {options.parser === 'zod' && (
77
- <File.Import
78
- name={[zod.schemas.response.name, zod.schemas.request?.name].filter((x): x is string => Boolean(x))}
79
- root={client.file.path}
80
- path={zod.file.path}
81
- />
104
+ {meta.fileZod && importedZodNames.length > 0 && <File.Import name={importedZodNames as string[]} root={meta.file.path} path={meta.fileZod.path} />}
105
+
106
+ {meta.fileTs && importedTypeNames.length > 0 && (
107
+ <File.Import name={Array.from(new Set(importedTypeNames))} root={meta.file.path} path={meta.fileTs.path} isTypeOnly />
82
108
  )}
83
- <File.Import
84
- name={[
85
- type.schemas.request?.name,
86
- type.schemas.response.name,
87
- type.schemas.pathParams?.name,
88
- type.schemas.queryParams?.name,
89
- type.schemas.headerParams?.name,
90
- ...(type.schemas.statusCodes?.map((item) => item.name) || []),
91
- ].filter((x): x is string => Boolean(x))}
92
- root={client.file.path}
93
- path={type.file.path}
94
- isTypeOnly
95
- />
96
109
 
97
110
  <Url
98
- name={url.name}
99
- baseURL={options.baseURL}
100
- pathParamsType={options.pathParamsType}
101
- paramsCasing={options.paramsCasing}
102
- paramsType={options.paramsType}
103
- typeSchemas={type.schemas}
104
- operation={operation}
111
+ name={meta.urlName}
112
+ baseURL={baseURL}
113
+ pathParamsType={pathParamsType}
114
+ paramsCasing={paramsCasing}
115
+ paramsType={paramsType}
116
+ node={node}
117
+ tsResolver={tsResolver}
105
118
  isIndexable={urlType === 'export'}
106
119
  isExportable={urlType === 'export'}
107
120
  />
108
121
 
109
122
  <Client
110
- name={client.name}
111
- urlName={url.name}
112
- baseURL={options.baseURL}
113
- dataReturnType={options.dataReturnType}
114
- pathParamsType={options.pathParamsType}
115
- paramsCasing={options.paramsCasing}
116
- paramsType={options.paramsType}
117
- typeSchemas={type.schemas}
118
- operation={operation}
119
- parser={options.parser}
120
- zodSchemas={zod.schemas}
123
+ name={meta.name}
124
+ urlName={meta.urlName}
125
+ baseURL={baseURL}
126
+ dataReturnType={dataReturnType}
127
+ pathParamsType={pathParamsType}
128
+ paramsCasing={paramsCasing}
129
+ paramsType={paramsType}
130
+ node={node}
131
+ tsResolver={tsResolver}
132
+ zodResolver={zodResolver}
133
+ parser={parser}
121
134
  />
122
135
  </File>
123
136
  )