@kubb/plugin-client 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.
- package/README.md +39 -24
- package/dist/clients/axios.cjs +26 -4
- package/dist/clients/axios.cjs.map +1 -1
- package/dist/clients/axios.d.ts +11 -5
- package/dist/clients/axios.js +26 -4
- package/dist/clients/axios.js.map +1 -1
- package/dist/clients/fetch.cjs +77 -9
- package/dist/clients/fetch.cjs.map +1 -1
- package/dist/clients/fetch.d.ts +10 -3
- package/dist/clients/fetch.js +77 -9
- package/dist/clients/fetch.js.map +1 -1
- package/dist/index.cjs +836 -514
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +206 -105
- package/dist/index.js +833 -515
- package/dist/index.js.map +1 -1
- package/dist/templates/clients/axios.source.cjs +1 -1
- package/dist/templates/clients/axios.source.cjs.map +1 -1
- package/dist/templates/clients/axios.source.d.ts +1 -1
- package/dist/templates/clients/axios.source.js +1 -1
- package/dist/templates/clients/axios.source.js.map +1 -1
- package/dist/templates/clients/fetch.source.cjs +1 -1
- package/dist/templates/clients/fetch.source.cjs.map +1 -1
- package/dist/templates/clients/fetch.source.d.ts +1 -1
- package/dist/templates/clients/fetch.source.js +1 -1
- package/dist/templates/clients/fetch.source.js.map +1 -1
- package/dist/templates/config.source.cjs.map +1 -1
- package/dist/templates/config.source.d.ts +1 -1
- package/dist/templates/config.source.js.map +1 -1
- package/package.json +14 -26
- package/src/clients/axios.ts +41 -7
- package/src/clients/fetch.ts +106 -6
- package/src/components/ClassClient.tsx +47 -24
- package/src/components/Client.tsx +100 -71
- package/src/components/StaticClassClient.tsx +47 -24
- package/src/components/Url.tsx +10 -12
- package/src/components/WrapperClient.tsx +9 -5
- package/src/functionParams.ts +8 -8
- package/src/generators/classClientGenerator.tsx +63 -51
- package/src/generators/clientGenerator.tsx +45 -48
- package/src/generators/groupedClientGenerator.tsx +12 -6
- package/src/generators/operationsGenerator.ts +47 -0
- package/src/generators/staticClassClientGenerator.tsx +57 -45
- package/src/index.ts +2 -1
- package/src/plugin.ts +30 -28
- package/src/resolvers/resolverClient.ts +32 -8
- package/src/types.ts +111 -65
- package/src/utils.ts +142 -63
- package/extension.yaml +0 -776
- package/src/components/Operations.tsx +0 -28
- package/src/generators/operationsGenerator.tsx +0 -28
- package/templates/clients/axios.ts +0 -73
- package/templates/clients/fetch.ts +0 -96
- package/templates/config.ts +0 -43
- /package/dist/{chunk-ByKO4r7w.cjs → chunk-Bx3C2hgW.cjs} +0 -0
- /package/dist/{chunk--u3MIqq1.js → chunk-C0LytTxp.js} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from 'node:path'
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import { defineGenerator } from '@kubb/core'
|
|
2
|
+
import { getOperationParameters, operationFileEntry, resolveOperationTypeNames } from '@internals/shared'
|
|
3
|
+
import { camelCase } from '@internals/utils'
|
|
4
|
+
import { ast, defineGenerator } from '@kubb/core'
|
|
5
5
|
import type { ResolverTs } from '@kubb/plugin-ts'
|
|
6
6
|
import { pluginTsName } from '@kubb/plugin-ts'
|
|
7
7
|
import type { ResolverZod } from '@kubb/plugin-zod'
|
|
@@ -10,70 +10,73 @@ import { File, jsxRenderer } from '@kubb/renderer-jsx'
|
|
|
10
10
|
import { ClassClient } from '../components/ClassClient'
|
|
11
11
|
import { WrapperClient } from '../components/WrapperClient'
|
|
12
12
|
import type { PluginClient } from '../types'
|
|
13
|
+
import { isParserEnabled, resolveQueryParamsParser, resolveRequestParser, resolveResponseParser } from '../utils.ts'
|
|
13
14
|
|
|
14
15
|
type OperationData = {
|
|
15
16
|
node: ast.OperationNode
|
|
16
17
|
name: string
|
|
17
18
|
tsResolver: ResolverTs
|
|
18
|
-
zodResolver: ResolverZod |
|
|
19
|
+
zodResolver: ResolverZod | null
|
|
19
20
|
typeFile: ast.FileNode
|
|
20
|
-
zodFile: ast.FileNode |
|
|
21
|
+
zodFile: ast.FileNode | null
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
type Controller = {
|
|
24
25
|
name: string
|
|
26
|
+
propertyName: string
|
|
25
27
|
file: ast.FileNode
|
|
26
28
|
operations: Array<OperationData>
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
function resolveTypeImportNames(node: ast.OperationNode, tsResolver: ResolverTs): Array<string> {
|
|
30
|
-
|
|
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))
|
|
32
|
+
return resolveOperationTypeNames(node, tsResolver, { order: 'body-response-first' })
|
|
39
33
|
}
|
|
40
34
|
|
|
41
|
-
function resolveZodImportNames(node: ast.OperationNode, zodResolver: ResolverZod): Array<string> {
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
35
|
+
function resolveZodImportNames(node: ast.OperationNode, zodResolver: ResolverZod, parser: PluginClient['resolvedOptions']['parser']): Array<string> {
|
|
36
|
+
const { query: queryParams } = getOperationParameters(node)
|
|
37
|
+
const names: Array<string | null | undefined> = [
|
|
38
|
+
resolveResponseParser(parser) === 'zod' ? zodResolver.resolveResponseName?.(node) : null,
|
|
39
|
+
resolveRequestParser(parser) === 'zod' && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null,
|
|
40
|
+
resolveQueryParamsParser(parser) === 'zod' && queryParams.length > 0 ? zodResolver.resolveQueryParamsName?.(node, queryParams[0]!) : null,
|
|
45
41
|
]
|
|
46
42
|
return names.filter((n): n is string => Boolean(n))
|
|
47
43
|
}
|
|
48
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Built-in `operations` generator for `@kubb/plugin-client` when
|
|
47
|
+
* `clientType: 'class'`. Emits one class per tag, with one instance method
|
|
48
|
+
* per operation and a shared constructor for request configuration.
|
|
49
|
+
*/
|
|
49
50
|
export const classClientGenerator = defineGenerator<PluginClient>({
|
|
50
51
|
name: 'classClient',
|
|
51
52
|
renderer: jsxRenderer,
|
|
52
53
|
operations(nodes, ctx) {
|
|
53
|
-
const {
|
|
54
|
+
const { config, driver, resolver, root } = ctx
|
|
54
55
|
const { output, group, dataReturnType, paramsCasing, paramsType, pathParamsType, parser, importPath, sdk } = ctx.options
|
|
55
|
-
const baseURL = ctx.options.baseURL ??
|
|
56
|
+
const baseURL = ctx.options.baseURL ?? ctx.meta.baseURL
|
|
56
57
|
|
|
57
58
|
const pluginTs = driver.getPlugin(pluginTsName)
|
|
58
59
|
if (!pluginTs) return null
|
|
59
60
|
|
|
60
61
|
const tsResolver = driver.getResolver(pluginTsName)
|
|
61
62
|
const tsPluginOptions = pluginTs.options
|
|
62
|
-
const pluginZod = parser
|
|
63
|
-
const zodResolver = pluginZod ? driver.getResolver(pluginZodName) :
|
|
63
|
+
const pluginZod = isParserEnabled(parser) ? driver.getPlugin(pluginZodName) : null
|
|
64
|
+
const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null
|
|
64
65
|
|
|
65
66
|
function buildOperationData(node: ast.OperationNode): OperationData {
|
|
66
|
-
const typeFile = tsResolver.resolveFile(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
const typeFile = tsResolver.resolveFile(operationFileEntry(node, node.operationId), {
|
|
68
|
+
root,
|
|
69
|
+
output: tsPluginOptions?.output ?? output,
|
|
70
|
+
group: tsPluginOptions?.group,
|
|
71
|
+
})
|
|
70
72
|
const zodFile =
|
|
71
73
|
zodResolver && pluginZod?.options
|
|
72
|
-
? zodResolver.resolveFile(
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
? zodResolver.resolveFile(operationFileEntry(node, node.operationId), {
|
|
75
|
+
root,
|
|
76
|
+
output: pluginZod.options?.output ?? output,
|
|
77
|
+
group: pluginZod.options?.group ?? undefined,
|
|
78
|
+
})
|
|
79
|
+
: null
|
|
77
80
|
|
|
78
81
|
return {
|
|
79
82
|
node: node,
|
|
@@ -86,30 +89,34 @@ export const classClientGenerator = defineGenerator<PluginClient>({
|
|
|
86
89
|
}
|
|
87
90
|
|
|
88
91
|
const controllers = nodes.reduce((acc, operationNode) => {
|
|
92
|
+
if (!ast.isHttpOperationNode(operationNode)) return acc
|
|
89
93
|
const tag = operationNode.tags[0]
|
|
90
|
-
const groupName = tag ? (group?.name?.({ group: camelCase(tag) }) ??
|
|
94
|
+
const groupName = tag ? (group?.name?.({ group: camelCase(tag) }) ?? resolver.resolveGroupName(tag)) : resolver.resolveClassName('Client')
|
|
91
95
|
|
|
92
96
|
if (!tag && !group) {
|
|
93
|
-
const name = 'ApiClient'
|
|
94
|
-
const file = resolver.resolveFile({ name, extname: '.ts' }, { root, output, group })
|
|
97
|
+
const name = resolver.resolveClassName('ApiClient')
|
|
98
|
+
const file = resolver.resolveFile({ name, extname: '.ts' }, { root, output, group: group ?? undefined })
|
|
95
99
|
const operationData = buildOperationData(operationNode)
|
|
96
100
|
const previous = acc.find((item) => item.file.path === file.path)
|
|
97
101
|
|
|
98
102
|
if (previous) {
|
|
99
103
|
previous.operations.push(operationData)
|
|
100
104
|
} else {
|
|
101
|
-
acc.push({ name, file, operations: [operationData] })
|
|
105
|
+
acc.push({ name, propertyName: resolver.resolveClientPropertyName(name), file, operations: [operationData] })
|
|
102
106
|
}
|
|
103
|
-
|
|
107
|
+
return acc
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (tag) {
|
|
104
111
|
const name = groupName
|
|
105
|
-
const file = resolver.resolveFile({ name, extname: '.ts', tag }, { root, output, group })
|
|
112
|
+
const file = resolver.resolveFile({ name, extname: '.ts', tag }, { root, output, group: group ?? undefined })
|
|
106
113
|
const operationData = buildOperationData(operationNode)
|
|
107
114
|
const previous = acc.find((item) => item.file.path === file.path)
|
|
108
115
|
|
|
109
116
|
if (previous) {
|
|
110
117
|
previous.operations.push(operationData)
|
|
111
118
|
} else {
|
|
112
|
-
acc.push({ name, file, operations: [operationData] })
|
|
119
|
+
acc.push({ name, propertyName: resolver.resolveClientPropertyName(name), file, operations: [operationData] })
|
|
113
120
|
}
|
|
114
121
|
}
|
|
115
122
|
|
|
@@ -141,7 +148,7 @@ export const classClientGenerator = defineGenerator<PluginClient>({
|
|
|
141
148
|
|
|
142
149
|
ops.forEach((op) => {
|
|
143
150
|
if (!op.zodFile || !zodResolver) return
|
|
144
|
-
const names = resolveZodImportNames(op.node, zodResolver)
|
|
151
|
+
const names = resolveZodImportNames(op.node, zodResolver, parser)
|
|
145
152
|
if (!zodImportsByFile.has(op.zodFile.path)) {
|
|
146
153
|
zodImportsByFile.set(op.zodFile.path, new Set())
|
|
147
154
|
}
|
|
@@ -157,9 +164,10 @@ export const classClientGenerator = defineGenerator<PluginClient>({
|
|
|
157
164
|
|
|
158
165
|
const files = controllers.map(({ name, file, operations: ops }) => {
|
|
159
166
|
const { typeImportsByFile, typeFilesByPath } = collectTypeImports(ops)
|
|
160
|
-
const { zodImportsByFile, zodFilesByPath } =
|
|
161
|
-
|
|
162
|
-
|
|
167
|
+
const { zodImportsByFile, zodFilesByPath } = isParserEnabled(parser)
|
|
168
|
+
? collectZodImports(ops)
|
|
169
|
+
: { zodImportsByFile: new Map<string, Set<string>>(), zodFilesByPath: new Map<string, ast.FileNode>() }
|
|
170
|
+
const hasFormData = ops.some((op) => op.node.requestBody?.content?.some((e) => e.contentType === 'multipart/form-data') ?? false)
|
|
163
171
|
|
|
164
172
|
return (
|
|
165
173
|
<File
|
|
@@ -167,18 +175,18 @@ export const classClientGenerator = defineGenerator<PluginClient>({
|
|
|
167
175
|
baseName={file.baseName}
|
|
168
176
|
path={file.path}
|
|
169
177
|
meta={file.meta}
|
|
170
|
-
banner={resolver.resolveBanner(
|
|
171
|
-
footer={resolver.resolveFooter(
|
|
178
|
+
banner={resolver.resolveBanner(ctx.meta, { output, config, file: { path: file.path, baseName: file.baseName } })}
|
|
179
|
+
footer={resolver.resolveFooter(ctx.meta, { output, config, file: { path: file.path, baseName: file.baseName } })}
|
|
172
180
|
>
|
|
173
181
|
{importPath ? (
|
|
174
182
|
<>
|
|
175
|
-
<File.Import name={'
|
|
183
|
+
<File.Import name={'client'} path={importPath} />
|
|
176
184
|
<File.Import name={['mergeConfig']} path={importPath} />
|
|
177
185
|
<File.Import name={['Client', 'RequestConfig', 'ResponseErrorConfig']} path={importPath} isTypeOnly />
|
|
178
186
|
</>
|
|
179
187
|
) : (
|
|
180
188
|
<>
|
|
181
|
-
<File.Import name={['
|
|
189
|
+
<File.Import name={['client']} root={file.path} path={path.resolve(root, '.kubb/client.ts')} />
|
|
182
190
|
<File.Import name={['mergeConfig']} root={file.path} path={path.resolve(root, '.kubb/client.ts')} />
|
|
183
191
|
<File.Import name={['Client', 'RequestConfig', 'ResponseErrorConfig']} root={file.path} path={path.resolve(root, '.kubb/client.ts')} isTypeOnly />
|
|
184
192
|
</>
|
|
@@ -186,6 +194,10 @@ export const classClientGenerator = defineGenerator<PluginClient>({
|
|
|
186
194
|
|
|
187
195
|
{hasFormData && <File.Import name={['buildFormData']} root={file.path} path={path.resolve(root, '.kubb/config.ts')} />}
|
|
188
196
|
|
|
197
|
+
{parser === 'zod' && zodResolver && ops.some((op) => op.node.requestBody?.content?.[0]?.schema != null) && (
|
|
198
|
+
<File.Import name={['z']} path="zod" isTypeOnly />
|
|
199
|
+
)}
|
|
200
|
+
|
|
189
201
|
{Array.from(typeImportsByFile.entries()).map(([filePath, importSet]) => {
|
|
190
202
|
const typeFile = typeFilesByPath.get(filePath)
|
|
191
203
|
if (!typeFile) return null
|
|
@@ -194,7 +206,7 @@ export const classClientGenerator = defineGenerator<PluginClient>({
|
|
|
194
206
|
return <File.Import key={filePath} name={importNames} root={file.path} path={typeFile.path} isTypeOnly />
|
|
195
207
|
})}
|
|
196
208
|
|
|
197
|
-
{parser
|
|
209
|
+
{isParserEnabled(parser) &&
|
|
198
210
|
Array.from(zodImportsByFile.entries()).map(([filePath, importSet]) => {
|
|
199
211
|
const zodFile = zodFilesByPath.get(filePath)
|
|
200
212
|
if (!zodFile) return null
|
|
@@ -218,7 +230,7 @@ export const classClientGenerator = defineGenerator<PluginClient>({
|
|
|
218
230
|
})
|
|
219
231
|
|
|
220
232
|
if (sdk) {
|
|
221
|
-
const sdkFile = resolver.resolveFile({ name: sdk.className, extname: '.ts' }, { root, output, group })
|
|
233
|
+
const sdkFile = resolver.resolveFile({ name: sdk.className, extname: '.ts' }, { root, output, group: group ?? undefined })
|
|
222
234
|
|
|
223
235
|
files.push(
|
|
224
236
|
<File
|
|
@@ -226,8 +238,8 @@ export const classClientGenerator = defineGenerator<PluginClient>({
|
|
|
226
238
|
baseName={sdkFile.baseName}
|
|
227
239
|
path={sdkFile.path}
|
|
228
240
|
meta={sdkFile.meta}
|
|
229
|
-
banner={resolver.resolveBanner(
|
|
230
|
-
footer={resolver.resolveFooter(
|
|
241
|
+
banner={resolver.resolveBanner(ctx.meta, { output, config, file: { path: sdkFile.path, baseName: sdkFile.baseName } })}
|
|
242
|
+
footer={resolver.resolveFooter(ctx.meta, { output, config, file: { path: sdkFile.path, baseName: sdkFile.baseName } })}
|
|
231
243
|
>
|
|
232
244
|
{importPath ? (
|
|
233
245
|
<File.Import name={['Client', 'RequestConfig']} path={importPath} isTypeOnly />
|
|
@@ -239,7 +251,7 @@ export const classClientGenerator = defineGenerator<PluginClient>({
|
|
|
239
251
|
<File.Import key={name} name={[name]} root={sdkFile.path} path={file.path} />
|
|
240
252
|
))}
|
|
241
253
|
|
|
242
|
-
<WrapperClient name={sdk.className}
|
|
254
|
+
<WrapperClient name={sdk.className} controllers={controllers.map(({ name, propertyName }) => ({ className: name, propertyName }))} />
|
|
243
255
|
</File>,
|
|
244
256
|
)
|
|
245
257
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from 'node:path'
|
|
2
|
+
import { getOperationParameters, operationFileEntry, resolveOperationTypeNames } from '@internals/shared'
|
|
2
3
|
import { ast, defineGenerator } from '@kubb/core'
|
|
3
4
|
import { pluginTsName } from '@kubb/plugin-ts'
|
|
4
5
|
import { pluginZodName } from '@kubb/plugin-zod'
|
|
@@ -6,14 +7,21 @@ import { File, jsxRenderer } from '@kubb/renderer-jsx'
|
|
|
6
7
|
import { Client } from '../components/Client'
|
|
7
8
|
import { Url } from '../components/Url.tsx'
|
|
8
9
|
import type { PluginClient } from '../types'
|
|
10
|
+
import { isParserEnabled, resolveQueryParamsParser, resolveRequestParser, resolveResponseParser } from '../utils.ts'
|
|
9
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Built-in operation generator for `@kubb/plugin-client`. Emits one async
|
|
14
|
+
* function per OpenAPI operation, plus the matching URL helper. Used when
|
|
15
|
+
* `clientType: 'function'` (the default).
|
|
16
|
+
*/
|
|
10
17
|
export const clientGenerator = defineGenerator<PluginClient>({
|
|
11
18
|
name: 'client',
|
|
12
19
|
renderer: jsxRenderer,
|
|
13
20
|
operation(node, ctx) {
|
|
14
|
-
|
|
21
|
+
if (!ast.isHttpOperationNode(node)) return null
|
|
22
|
+
const { config, driver, resolver, root } = ctx
|
|
15
23
|
const { output, urlType, dataReturnType, paramsCasing, paramsType, pathParamsType, parser, importPath, group } = ctx.options
|
|
16
|
-
const baseURL = ctx.options.baseURL ??
|
|
24
|
+
const baseURL = ctx.options.baseURL ?? ctx.meta.baseURL
|
|
17
25
|
|
|
18
26
|
const pluginTs = driver.getPlugin(pluginTsName)
|
|
19
27
|
|
|
@@ -23,71 +31,60 @@ export const clientGenerator = defineGenerator<PluginClient>({
|
|
|
23
31
|
|
|
24
32
|
const tsResolver = driver.getResolver(pluginTsName)
|
|
25
33
|
|
|
26
|
-
const pluginZod = parser
|
|
27
|
-
const zodResolver = pluginZod ? driver.getResolver(pluginZodName) :
|
|
34
|
+
const pluginZod = isParserEnabled(parser) ? driver.getPlugin(pluginZodName) : null
|
|
35
|
+
const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null
|
|
28
36
|
|
|
29
|
-
const
|
|
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')
|
|
37
|
+
const importedTypeNames = resolveOperationTypeNames(node, tsResolver, { paramsCasing })
|
|
33
38
|
|
|
34
|
-
const
|
|
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)
|
|
39
|
+
const { query: queryParams } = getOperationParameters(node)
|
|
42
40
|
|
|
43
|
-
const importedZodNames =
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
const importedZodNames = zodResolver
|
|
42
|
+
? [
|
|
43
|
+
resolveResponseParser(parser) === 'zod' ? zodResolver.resolveResponseName?.(node) : null,
|
|
44
|
+
resolveRequestParser(parser) === 'zod' && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null,
|
|
45
|
+
resolveQueryParamsParser(parser) === 'zod' && queryParams.length > 0 ? zodResolver.resolveQueryParamsName?.(node, queryParams[0]!) : null,
|
|
46
|
+
].filter((name): name is string => Boolean(name))
|
|
47
|
+
: []
|
|
48
|
+
|
|
49
|
+
const zodRequestName = zodResolver && parser === 'zod' && node.requestBody?.content?.[0]?.schema ? (zodResolver.resolveDataName?.(node) ?? null) : null
|
|
47
50
|
|
|
48
51
|
const meta = {
|
|
49
52
|
name: resolver.resolveName(node.operationId),
|
|
50
|
-
urlName:
|
|
51
|
-
file: resolver.resolveFile(
|
|
52
|
-
fileTs: tsResolver.resolveFile(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
group: pluginTs.options?.group,
|
|
58
|
-
},
|
|
59
|
-
),
|
|
53
|
+
urlName: resolver.resolveUrlName(node),
|
|
54
|
+
file: resolver.resolveFile(operationFileEntry(node, node.operationId), { root, output, group: group ?? undefined }),
|
|
55
|
+
fileTs: tsResolver.resolveFile(operationFileEntry(node, node.operationId), {
|
|
56
|
+
root,
|
|
57
|
+
output: pluginTs.options?.output ?? output,
|
|
58
|
+
group: pluginTs.options?.group ?? undefined,
|
|
59
|
+
}),
|
|
60
60
|
fileZod:
|
|
61
61
|
zodResolver && pluginZod?.options
|
|
62
|
-
? zodResolver.resolveFile(
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
},
|
|
69
|
-
)
|
|
70
|
-
: undefined,
|
|
62
|
+
? zodResolver.resolveFile(operationFileEntry(node, node.operationId), {
|
|
63
|
+
root,
|
|
64
|
+
output: pluginZod.options.output ?? output,
|
|
65
|
+
group: pluginZod.options?.group ?? undefined,
|
|
66
|
+
})
|
|
67
|
+
: null,
|
|
71
68
|
} as const
|
|
72
69
|
|
|
73
|
-
const
|
|
70
|
+
const hasFormData = node.requestBody?.content?.some((e) => e.contentType === 'multipart/form-data') ?? false
|
|
74
71
|
|
|
75
72
|
return (
|
|
76
73
|
<File
|
|
77
74
|
baseName={meta.file.baseName}
|
|
78
75
|
path={meta.file.path}
|
|
79
76
|
meta={meta.file.meta}
|
|
80
|
-
banner={resolver.resolveBanner(
|
|
81
|
-
footer={resolver.resolveFooter(
|
|
77
|
+
banner={resolver.resolveBanner(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
|
|
78
|
+
footer={resolver.resolveFooter(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
|
|
82
79
|
>
|
|
83
80
|
{importPath ? (
|
|
84
81
|
<>
|
|
85
|
-
<File.Import name={'
|
|
82
|
+
<File.Import name={'client'} path={importPath} />
|
|
86
83
|
<File.Import name={['Client', 'RequestConfig', 'ResponseErrorConfig']} path={importPath} isTypeOnly />
|
|
87
84
|
</>
|
|
88
85
|
) : (
|
|
89
86
|
<>
|
|
90
|
-
<File.Import name={['
|
|
87
|
+
<File.Import name={['client']} root={meta.file.path} path={path.resolve(root, '.kubb/client.ts')} />
|
|
91
88
|
<File.Import
|
|
92
89
|
name={['Client', 'RequestConfig', 'ResponseErrorConfig']}
|
|
93
90
|
root={meta.file.path}
|
|
@@ -97,11 +94,11 @@ export const clientGenerator = defineGenerator<PluginClient>({
|
|
|
97
94
|
</>
|
|
98
95
|
)}
|
|
99
96
|
|
|
100
|
-
{
|
|
101
|
-
|
|
102
|
-
|
|
97
|
+
{hasFormData && <File.Import name={['buildFormData']} root={meta.file.path} path={path.resolve(root, '.kubb/config.ts')} />}
|
|
98
|
+
|
|
99
|
+
{zodRequestName && <File.Import name={['z']} path="zod" isTypeOnly />}
|
|
103
100
|
|
|
104
|
-
{meta.fileZod && importedZodNames.length > 0 && <File.Import name={importedZodNames as string
|
|
101
|
+
{meta.fileZod && importedZodNames.length > 0 && <File.Import name={importedZodNames as Array<string>} root={meta.file.path} path={meta.fileZod.path} />}
|
|
105
102
|
|
|
106
103
|
{meta.fileTs && importedTypeNames.length > 0 && (
|
|
107
104
|
<File.Import name={Array.from(new Set(importedTypeNames))} root={meta.file.path} path={meta.fileTs.path} isTypeOnly />
|
|
@@ -4,27 +4,33 @@ import { defineGenerator } from '@kubb/core'
|
|
|
4
4
|
import { File, Function, jsxRenderer } from '@kubb/renderer-jsx'
|
|
5
5
|
import type { PluginClient } from '../types'
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Emits one aggregate file per tag/group when `group` is configured. Each
|
|
9
|
+
* file re-exports every client function for that group, so callers can
|
|
10
|
+
* `import { petController } from './gen/clients'` instead of importing
|
|
11
|
+
* each operation individually.
|
|
12
|
+
*/
|
|
7
13
|
export const groupedClientGenerator = defineGenerator<PluginClient>({
|
|
8
14
|
name: 'groupedClient',
|
|
9
15
|
renderer: jsxRenderer,
|
|
10
16
|
operations(nodes, ctx) {
|
|
11
|
-
const { config, resolver,
|
|
17
|
+
const { config, resolver, root } = ctx
|
|
12
18
|
const { output, group } = ctx.options
|
|
13
19
|
|
|
14
20
|
const controllers = nodes.reduce(
|
|
15
21
|
(acc, operationNode) => {
|
|
16
22
|
if (group?.type === 'tag') {
|
|
17
23
|
const tag = operationNode.tags[0]
|
|
18
|
-
const name = tag ? group?.name?.({ group: camelCase(tag) }) :
|
|
24
|
+
const name = tag ? group?.name?.({ group: camelCase(tag) }) : null
|
|
19
25
|
|
|
20
26
|
if (!tag || !name) {
|
|
21
27
|
return acc
|
|
22
28
|
}
|
|
23
29
|
|
|
24
|
-
const file = resolver.resolveFile({ name, extname: '.ts', tag }, { root, output, group })
|
|
30
|
+
const file = resolver.resolveFile({ name, extname: '.ts', tag }, { root, output, group: group ?? undefined })
|
|
25
31
|
const clientFile = resolver.resolveFile(
|
|
26
32
|
{ name: operationNode.operationId, extname: '.ts', tag: operationNode.tags[0] ?? 'default', path: operationNode.path },
|
|
27
|
-
{ root, output, group },
|
|
33
|
+
{ root, output, group: group ?? undefined },
|
|
28
34
|
)
|
|
29
35
|
|
|
30
36
|
const client = {
|
|
@@ -55,8 +61,8 @@ export const groupedClientGenerator = defineGenerator<PluginClient>({
|
|
|
55
61
|
baseName={file.baseName}
|
|
56
62
|
path={file.path}
|
|
57
63
|
meta={file.meta}
|
|
58
|
-
banner={resolver.resolveBanner(
|
|
59
|
-
footer={resolver.resolveFooter(
|
|
64
|
+
banner={resolver.resolveBanner(ctx.meta, { output, config, file: { path: file.path, baseName: file.baseName, isAggregation: true } })}
|
|
65
|
+
footer={resolver.resolveFooter(ctx.meta, { output, config, file: { path: file.path, baseName: file.baseName, isAggregation: true } })}
|
|
60
66
|
>
|
|
61
67
|
{clients.map((client) => (
|
|
62
68
|
<File.Import key={client.name} name={[client.name]} root={file.path} path={client.file.path} />
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Url } from '@internals/utils'
|
|
2
|
+
import { ast, defineGenerator } from '@kubb/core'
|
|
3
|
+
import type { PluginClient } from '../types'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Generates an `operations.ts` file that re-exports every operation grouped
|
|
7
|
+
* by HTTP method. Enabled when `pluginClient({ operations: true })`. Useful
|
|
8
|
+
* for building meta-tooling on top of the generated client (route
|
|
9
|
+
* registries, API explorers).
|
|
10
|
+
*/
|
|
11
|
+
export const operationsGenerator = defineGenerator<PluginClient>({
|
|
12
|
+
name: 'client',
|
|
13
|
+
operations(nodes, ctx) {
|
|
14
|
+
const { config, resolver, root } = ctx
|
|
15
|
+
const { output, group } = ctx.options
|
|
16
|
+
|
|
17
|
+
const name = 'operations'
|
|
18
|
+
const file = resolver.resolveFile({ name, extname: '.ts' }, { root, output, group: group ?? undefined })
|
|
19
|
+
|
|
20
|
+
const operationsObject: Record<string, { path: string; method: string }> = {}
|
|
21
|
+
for (const node of nodes) {
|
|
22
|
+
if (!ast.isHttpOperationNode(node)) continue
|
|
23
|
+
operationsObject[node.operationId] = {
|
|
24
|
+
path: Url.toPath(node.path),
|
|
25
|
+
method: node.method.toLowerCase(),
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return [
|
|
30
|
+
ast.createFile({
|
|
31
|
+
baseName: file.baseName,
|
|
32
|
+
path: file.path,
|
|
33
|
+
meta: file.meta,
|
|
34
|
+
banner: resolver.resolveBanner(ctx.meta, { output, config, file: { path: file.path, baseName: file.baseName } }),
|
|
35
|
+
footer: resolver.resolveFooter(ctx.meta, { output, config, file: { path: file.path, baseName: file.baseName } }),
|
|
36
|
+
sources: [
|
|
37
|
+
ast.createSource({
|
|
38
|
+
name,
|
|
39
|
+
isExportable: true,
|
|
40
|
+
isIndexable: true,
|
|
41
|
+
nodes: [ast.createConst({ name, export: true, nodes: [ast.createText(JSON.stringify(operationsObject, undefined, 2))] })],
|
|
42
|
+
}),
|
|
43
|
+
],
|
|
44
|
+
}),
|
|
45
|
+
]
|
|
46
|
+
},
|
|
47
|
+
})
|