@kubb/plugin-client 5.0.0-beta.22 → 5.0.0-beta.25
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/dist/clients/fetch.cjs.map +1 -1
- package/dist/clients/fetch.d.ts +1 -1
- package/dist/clients/fetch.js.map +1 -1
- package/dist/index.cjs +135 -88
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +125 -65
- package/dist/index.js +135 -88
- package/dist/index.js.map +1 -1
- package/dist/templates/clients/fetch.source.cjs +1 -1
- package/dist/templates/clients/fetch.source.js +1 -1
- package/extension.yaml +774 -260
- package/package.json +6 -6
- package/src/clients/fetch.ts +1 -1
- package/src/components/ClassClient.tsx +3 -3
- package/src/components/Client.tsx +17 -17
- package/src/components/StaticClassClient.tsx +3 -3
- package/src/components/Url.tsx +1 -1
- package/src/functionParams.ts +8 -8
- package/src/generators/classClientGenerator.tsx +16 -11
- package/src/generators/clientGenerator.tsx +16 -8
- package/src/generators/groupedClientGenerator.tsx +9 -3
- package/src/generators/operationsGenerator.tsx +7 -1
- package/src/generators/staticClassClientGenerator.tsx +16 -10
- package/src/plugin.ts +24 -11
- package/src/resolvers/resolverClient.ts +10 -4
- package/src/types.ts +66 -53
- package/src/utils.ts +14 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubb/plugin-client",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.25",
|
|
4
4
|
"description": "Generate type-safe HTTP clients from your OpenAPI specification. Supports Axios, Fetch, and custom client adapters with full TypeScript inference and zero boilerplate.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"api-client",
|
|
@@ -87,10 +87,10 @@
|
|
|
87
87
|
"registry": "https://registry.npmjs.org/"
|
|
88
88
|
},
|
|
89
89
|
"dependencies": {
|
|
90
|
-
"@kubb/core": "5.0.0-beta.
|
|
91
|
-
"@kubb/renderer-jsx": "5.0.0-beta.
|
|
92
|
-
"@kubb/plugin-ts": "5.0.0-beta.
|
|
93
|
-
"@kubb/plugin-zod": "5.0.0-beta.
|
|
90
|
+
"@kubb/core": "5.0.0-beta.25",
|
|
91
|
+
"@kubb/renderer-jsx": "5.0.0-beta.25",
|
|
92
|
+
"@kubb/plugin-ts": "5.0.0-beta.25",
|
|
93
|
+
"@kubb/plugin-zod": "5.0.0-beta.25"
|
|
94
94
|
},
|
|
95
95
|
"devDependencies": {
|
|
96
96
|
"axios": "^1.16.1",
|
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
"@internals/utils": "0.0.0"
|
|
99
99
|
},
|
|
100
100
|
"peerDependencies": {
|
|
101
|
-
"@kubb/renderer-jsx": "5.0.0-beta.
|
|
101
|
+
"@kubb/renderer-jsx": "5.0.0-beta.25",
|
|
102
102
|
"axios": "^1.7.2"
|
|
103
103
|
},
|
|
104
104
|
"peerDependenciesMeta": {
|
package/src/clients/fetch.ts
CHANGED
|
@@ -14,7 +14,7 @@ export type RequestConfig<TData = unknown> = {
|
|
|
14
14
|
data?: TData | FormData
|
|
15
15
|
responseType?: 'arraybuffer' | 'blob' | 'document' | 'json' | 'text' | 'stream'
|
|
16
16
|
signal?: AbortSignal
|
|
17
|
-
headers?: [string, string]
|
|
17
|
+
headers?: Array<[string, string]> | Record<string, string>
|
|
18
18
|
credentials?: RequestCredentials
|
|
19
19
|
contentType?: string
|
|
20
20
|
}
|
|
@@ -14,7 +14,7 @@ type OperationData = {
|
|
|
14
14
|
node: ast.OperationNode
|
|
15
15
|
name: string
|
|
16
16
|
tsResolver: ResolverTs
|
|
17
|
-
zodResolver?: ResolverZod
|
|
17
|
+
zodResolver?: ResolverZod | null
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
type Props = {
|
|
@@ -35,7 +35,7 @@ type GenerateMethodProps = {
|
|
|
35
35
|
node: ast.OperationNode
|
|
36
36
|
name: string
|
|
37
37
|
tsResolver: ResolverTs
|
|
38
|
-
zodResolver?: ResolverZod
|
|
38
|
+
zodResolver?: ResolverZod | null
|
|
39
39
|
baseURL: string | null | undefined
|
|
40
40
|
dataReturnType: PluginClient['resolvedOptions']['dataReturnType']
|
|
41
41
|
parser: PluginClient['resolvedOptions']['parser'] | undefined
|
|
@@ -62,7 +62,7 @@ function generateMethod({
|
|
|
62
62
|
const { defaultContentType: contentType, isMultipleContentTypes, hasFormData } = getContentTypeInfo(node)
|
|
63
63
|
const isFormData = !isMultipleContentTypes && contentType === 'multipart/form-data'
|
|
64
64
|
const { header: headerParams } = getOperationParameters(node)
|
|
65
|
-
const headerParamsName = headerParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, headerParams[0]!) :
|
|
65
|
+
const headerParamsName = headerParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, headerParams[0]!) : null
|
|
66
66
|
const headers = isMultipleContentTypes ? (headerParamsName ? ['...headers'] : []) : buildHeaders(contentType, !!headerParamsName)
|
|
67
67
|
const generics = buildGenerics(node, tsResolver)
|
|
68
68
|
const paramsNode = buildClientParamsNode({ paramsType, paramsCasing, pathParamsType, node, tsResolver, isConfigurable: true })
|
|
@@ -26,7 +26,7 @@ type Props = {
|
|
|
26
26
|
parser: PluginClient['resolvedOptions']['parser'] | undefined
|
|
27
27
|
node: ast.OperationNode
|
|
28
28
|
tsResolver: ResolverTs
|
|
29
|
-
zodResolver?: ResolverZod
|
|
29
|
+
zodResolver?: ResolverZod | null
|
|
30
30
|
children?: KubbReactNode
|
|
31
31
|
}
|
|
32
32
|
|
|
@@ -96,17 +96,17 @@ export function Client({
|
|
|
96
96
|
const { path: originalPathParams, query: originalQueryParams, header: originalHeaderParams } = getOperationParameters(node)
|
|
97
97
|
const { path: casedPathParams, query: casedQueryParams, header: casedHeaderParams } = getOperationParameters(node, { paramsCasing })
|
|
98
98
|
|
|
99
|
-
const pathParamsMapping = paramsCasing && !urlName ? buildParamsMapping(originalPathParams, casedPathParams) :
|
|
100
|
-
const queryParamsMapping = paramsCasing ? buildParamsMapping(originalQueryParams, casedQueryParams) :
|
|
101
|
-
const headerParamsMapping = paramsCasing ? buildParamsMapping(originalHeaderParams, casedHeaderParams) :
|
|
99
|
+
const pathParamsMapping = paramsCasing && !urlName ? buildParamsMapping(originalPathParams, casedPathParams) : null
|
|
100
|
+
const queryParamsMapping = paramsCasing ? buildParamsMapping(originalQueryParams, casedQueryParams) : null
|
|
101
|
+
const headerParamsMapping = paramsCasing ? buildParamsMapping(originalHeaderParams, casedHeaderParams) : null
|
|
102
102
|
|
|
103
|
-
const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) :
|
|
103
|
+
const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : null
|
|
104
104
|
const responseName = tsResolver.resolveResponseName(node)
|
|
105
|
-
const queryParamsName = originalQueryParams.length > 0 ? tsResolver.resolveQueryParamsName(node, originalQueryParams[0]!) :
|
|
106
|
-
const headerParamsName = originalHeaderParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, originalHeaderParams[0]!) :
|
|
105
|
+
const queryParamsName = originalQueryParams.length > 0 ? tsResolver.resolveQueryParamsName(node, originalQueryParams[0]!) : null
|
|
106
|
+
const headerParamsName = originalHeaderParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, originalHeaderParams[0]!) : null
|
|
107
107
|
|
|
108
|
-
const zodResponseName = zodResolver && parser === 'zod' ? zodResolver.resolveResponseName?.(node) :
|
|
109
|
-
const zodRequestName = zodResolver && parser === 'zod' && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) :
|
|
108
|
+
const zodResponseName = zodResolver && parser === 'zod' ? zodResolver.resolveResponseName?.(node) : null
|
|
109
|
+
const zodRequestName = zodResolver && parser === 'zod' && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null
|
|
110
110
|
|
|
111
111
|
const errorNames = node.responses
|
|
112
112
|
.filter((r) => {
|
|
@@ -116,8 +116,8 @@ export function Client({
|
|
|
116
116
|
.map((r) => tsResolver.resolveResponseStatusName(node, r.statusCode))
|
|
117
117
|
|
|
118
118
|
const headers = [
|
|
119
|
-
!isMultipleContentTypes && contentType !== 'application/json' && contentType !== 'multipart/form-data' ? `'Content-Type': '${contentType}'` :
|
|
120
|
-
headerParamsName ? (headerParamsMapping ? '...mappedHeaders' : '...headers') :
|
|
119
|
+
!isMultipleContentTypes && contentType !== 'application/json' && contentType !== 'multipart/form-data' ? `'Content-Type': '${contentType}'` : null,
|
|
120
|
+
headerParamsName ? (headerParamsMapping ? '...mappedHeaders' : '...headers') : null,
|
|
121
121
|
].filter(Boolean)
|
|
122
122
|
|
|
123
123
|
const TError = `ResponseErrorConfig<${errorNames.length > 0 ? errorNames.join(' | ') : 'Error'}>`
|
|
@@ -158,8 +158,8 @@ export function Client({
|
|
|
158
158
|
? {
|
|
159
159
|
value: `\`${baseURL}\``,
|
|
160
160
|
}
|
|
161
|
-
:
|
|
162
|
-
params: queryParamsName ? (queryParamsMapping ? { value: 'mappedParams' } : {}) :
|
|
161
|
+
: null,
|
|
162
|
+
params: queryParamsName ? (queryParamsMapping ? { value: 'mappedParams' } : {}) : null,
|
|
163
163
|
data: requestName
|
|
164
164
|
? {
|
|
165
165
|
value:
|
|
@@ -169,18 +169,18 @@ export function Client({
|
|
|
169
169
|
? 'formData as FormData'
|
|
170
170
|
: 'requestData',
|
|
171
171
|
}
|
|
172
|
-
:
|
|
173
|
-
contentType: isConfigurable && isMultipleContentTypes ? {} :
|
|
172
|
+
: null,
|
|
173
|
+
contentType: isConfigurable && isMultipleContentTypes ? {} : null,
|
|
174
174
|
requestConfig: isConfigurable
|
|
175
175
|
? {
|
|
176
176
|
mode: 'inlineSpread',
|
|
177
177
|
}
|
|
178
|
-
:
|
|
178
|
+
: null,
|
|
179
179
|
headers: headers.length
|
|
180
180
|
? {
|
|
181
181
|
value: isConfigurable ? `{ ${headers.join(', ')}, ...requestConfig.headers }` : `{ ${headers.join(', ')} }`,
|
|
182
182
|
}
|
|
183
|
-
:
|
|
183
|
+
: null,
|
|
184
184
|
},
|
|
185
185
|
},
|
|
186
186
|
})
|
|
@@ -14,7 +14,7 @@ type OperationData = {
|
|
|
14
14
|
node: ast.OperationNode
|
|
15
15
|
name: string
|
|
16
16
|
tsResolver: ResolverTs
|
|
17
|
-
zodResolver?: ResolverZod
|
|
17
|
+
zodResolver?: ResolverZod | null
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
type Props = {
|
|
@@ -35,7 +35,7 @@ type GenerateMethodProps = {
|
|
|
35
35
|
node: ast.OperationNode
|
|
36
36
|
name: string
|
|
37
37
|
tsResolver: ResolverTs
|
|
38
|
-
zodResolver?: ResolverZod
|
|
38
|
+
zodResolver?: ResolverZod | null
|
|
39
39
|
baseURL: string | null | undefined
|
|
40
40
|
dataReturnType: PluginClient['resolvedOptions']['dataReturnType']
|
|
41
41
|
parser: PluginClient['resolvedOptions']['parser'] | undefined
|
|
@@ -62,7 +62,7 @@ function generateMethod({
|
|
|
62
62
|
const { defaultContentType: contentType, isMultipleContentTypes, hasFormData } = getContentTypeInfo(node)
|
|
63
63
|
const isFormData = !isMultipleContentTypes && contentType === 'multipart/form-data'
|
|
64
64
|
const { header: headerParams } = getOperationParameters(node)
|
|
65
|
-
const headerParamsName = headerParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, headerParams[0]!) :
|
|
65
|
+
const headerParamsName = headerParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, headerParams[0]!) : null
|
|
66
66
|
const headers = isMultipleContentTypes ? (headerParamsName ? ['...headers'] : []) : buildHeaders(contentType, !!headerParamsName)
|
|
67
67
|
const generics = buildGenerics(node, tsResolver)
|
|
68
68
|
const paramsNode = buildClientParamsNode({ paramsType, paramsCasing, pathParamsType, node, tsResolver, isConfigurable: true })
|
package/src/components/Url.tsx
CHANGED
|
@@ -70,7 +70,7 @@ export function Url({
|
|
|
70
70
|
|
|
71
71
|
const { path: originalPathParams } = getOperationParameters(node)
|
|
72
72
|
const { path: casedPathParams } = getOperationParameters(node, { paramsCasing })
|
|
73
|
-
const pathParamsMapping = paramsCasing ? buildParamsMapping(originalPathParams, casedPathParams) :
|
|
73
|
+
const pathParamsMapping = paramsCasing ? buildParamsMapping(originalPathParams, casedPathParams) : null
|
|
74
74
|
|
|
75
75
|
return (
|
|
76
76
|
<File.Source name={name} isExportable={isExportable} isIndexable={isIndexable}>
|
package/src/functionParams.ts
CHANGED
|
@@ -11,7 +11,7 @@ type ParamLeaf = {
|
|
|
11
11
|
|
|
12
12
|
type ParamGroup = {
|
|
13
13
|
mode: 'object' | 'inlineSpread'
|
|
14
|
-
children: Record<string, ParamLeaf | undefined>
|
|
14
|
+
children: Record<string, ParamLeaf | null | undefined>
|
|
15
15
|
default?: string
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -25,14 +25,14 @@ function isGroup(spec: ParamSpec): spec is ParamGroup {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
function createType(type?: string) {
|
|
28
|
-
return type ? ast.createParamsType({ variant: 'reference', name: type }) :
|
|
28
|
+
return type ? ast.createParamsType({ variant: 'reference', name: type }) : null
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
function createDeclarationLeaf(name: string, spec: ParamLeaf): ast.FunctionParameterNode {
|
|
32
32
|
if (spec.default !== undefined) {
|
|
33
33
|
return ast.createFunctionParameter({
|
|
34
34
|
name,
|
|
35
|
-
type: createType(spec.type),
|
|
35
|
+
type: createType(spec.type) ?? undefined,
|
|
36
36
|
default: spec.default,
|
|
37
37
|
rest: spec.mode === 'inlineSpread',
|
|
38
38
|
})
|
|
@@ -40,7 +40,7 @@ function createDeclarationLeaf(name: string, spec: ParamLeaf): ast.FunctionParam
|
|
|
40
40
|
|
|
41
41
|
return ast.createFunctionParameter({
|
|
42
42
|
name,
|
|
43
|
-
type: createType(spec.type),
|
|
43
|
+
type: createType(spec.type) ?? undefined,
|
|
44
44
|
optional: !!spec.optional,
|
|
45
45
|
rest: spec.mode === 'inlineSpread',
|
|
46
46
|
})
|
|
@@ -52,7 +52,7 @@ function createDeclarationParam(name: string, spec: ParamSpec): ast.FunctionPara
|
|
|
52
52
|
inline: spec.mode === 'inlineSpread',
|
|
53
53
|
default: spec.default,
|
|
54
54
|
properties: Object.entries(spec.children)
|
|
55
|
-
.filter(([, child]) => child
|
|
55
|
+
.filter(([, child]) => child != null)
|
|
56
56
|
.map(([childName, child]) => createDeclarationLeaf(childName, child!)),
|
|
57
57
|
})
|
|
58
58
|
}
|
|
@@ -65,7 +65,7 @@ function createCallParam(name: string, spec: ParamSpec): ast.FunctionParameterNo
|
|
|
65
65
|
return ast.createParameterGroup({
|
|
66
66
|
inline: spec.mode === 'inlineSpread',
|
|
67
67
|
properties: Object.entries(spec.children)
|
|
68
|
-
.filter(([, child]) => child
|
|
68
|
+
.filter(([, child]) => child != null)
|
|
69
69
|
.map(([childName, child]) =>
|
|
70
70
|
ast.createFunctionParameter({
|
|
71
71
|
name:
|
|
@@ -92,8 +92,8 @@ function createCallParam(name: string, spec: ParamSpec): ast.FunctionParameterNo
|
|
|
92
92
|
* Creates function parameter builders for generating function signatures and calls.
|
|
93
93
|
* Returns utilities to output constructor signatures (`toConstructor()`) or call expressions (`toCall()`).
|
|
94
94
|
*/
|
|
95
|
-
export function createFunctionParams(params: Record<string, ParamSpec | undefined>) {
|
|
96
|
-
const entries = Object.entries(params).filter(([, spec]) => spec
|
|
95
|
+
export function createFunctionParams(params: Record<string, ParamSpec | null | undefined>) {
|
|
96
|
+
const entries = Object.entries(params).filter(([, spec]) => spec != null) as Array<[string, ParamSpec]>
|
|
97
97
|
|
|
98
98
|
return {
|
|
99
99
|
toConstructor() {
|
|
@@ -16,9 +16,9 @@ type OperationData = {
|
|
|
16
16
|
node: ast.OperationNode
|
|
17
17
|
name: string
|
|
18
18
|
tsResolver: ResolverTs
|
|
19
|
-
zodResolver: ResolverZod |
|
|
19
|
+
zodResolver: ResolverZod | null
|
|
20
20
|
typeFile: ast.FileNode
|
|
21
|
-
zodFile: ast.FileNode |
|
|
21
|
+
zodFile: ast.FileNode | null
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
type Controller = {
|
|
@@ -33,13 +33,18 @@ function resolveTypeImportNames(node: ast.OperationNode, tsResolver: ResolverTs)
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
function resolveZodImportNames(node: ast.OperationNode, zodResolver: ResolverZod): Array<string> {
|
|
36
|
-
const names: Array<string | undefined> = [
|
|
36
|
+
const names: Array<string | null | undefined> = [
|
|
37
37
|
zodResolver.resolveResponseName?.(node),
|
|
38
|
-
node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) :
|
|
38
|
+
node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null,
|
|
39
39
|
]
|
|
40
40
|
return names.filter((n): n is string => Boolean(n))
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Built-in `operations` generator for `@kubb/plugin-client` when
|
|
45
|
+
* `clientType: 'class'`. Emits one class per tag, with one instance method
|
|
46
|
+
* per operation and a shared constructor for request configuration.
|
|
47
|
+
*/
|
|
43
48
|
export const classClientGenerator = defineGenerator<PluginClient>({
|
|
44
49
|
name: 'classClient',
|
|
45
50
|
renderer: jsxRendererSync,
|
|
@@ -53,8 +58,8 @@ export const classClientGenerator = defineGenerator<PluginClient>({
|
|
|
53
58
|
|
|
54
59
|
const tsResolver = driver.getResolver(pluginTsName)
|
|
55
60
|
const tsPluginOptions = pluginTs.options
|
|
56
|
-
const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) :
|
|
57
|
-
const zodResolver = pluginZod ? driver.getResolver(pluginZodName) :
|
|
61
|
+
const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) : null
|
|
62
|
+
const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null
|
|
58
63
|
|
|
59
64
|
function buildOperationData(node: ast.OperationNode): OperationData {
|
|
60
65
|
const typeFile = tsResolver.resolveFile(
|
|
@@ -65,9 +70,9 @@ export const classClientGenerator = defineGenerator<PluginClient>({
|
|
|
65
70
|
zodResolver && pluginZod?.options
|
|
66
71
|
? zodResolver.resolveFile(
|
|
67
72
|
{ name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
|
|
68
|
-
{ root, output: pluginZod.options?.output ?? output, group: pluginZod.options?.group },
|
|
73
|
+
{ root, output: pluginZod.options?.output ?? output, group: pluginZod.options?.group ?? undefined },
|
|
69
74
|
)
|
|
70
|
-
:
|
|
75
|
+
: null
|
|
71
76
|
|
|
72
77
|
return {
|
|
73
78
|
node: node,
|
|
@@ -85,7 +90,7 @@ export const classClientGenerator = defineGenerator<PluginClient>({
|
|
|
85
90
|
|
|
86
91
|
if (!tag && !group) {
|
|
87
92
|
const name = resolver.resolveClassName('ApiClient')
|
|
88
|
-
const file = resolver.resolveFile({ name, extname: '.ts' }, { root, output, group })
|
|
93
|
+
const file = resolver.resolveFile({ name, extname: '.ts' }, { root, output, group: group ?? undefined })
|
|
89
94
|
const operationData = buildOperationData(operationNode)
|
|
90
95
|
const previous = acc.find((item) => item.file.path === file.path)
|
|
91
96
|
|
|
@@ -99,7 +104,7 @@ export const classClientGenerator = defineGenerator<PluginClient>({
|
|
|
99
104
|
|
|
100
105
|
if (tag) {
|
|
101
106
|
const name = groupName
|
|
102
|
-
const file = resolver.resolveFile({ name, extname: '.ts', tag }, { root, output, group })
|
|
107
|
+
const file = resolver.resolveFile({ name, extname: '.ts', tag }, { root, output, group: group ?? undefined })
|
|
103
108
|
const operationData = buildOperationData(operationNode)
|
|
104
109
|
const previous = acc.find((item) => item.file.path === file.path)
|
|
105
110
|
|
|
@@ -215,7 +220,7 @@ export const classClientGenerator = defineGenerator<PluginClient>({
|
|
|
215
220
|
})
|
|
216
221
|
|
|
217
222
|
if (sdk) {
|
|
218
|
-
const sdkFile = resolver.resolveFile({ name: sdk.className, extname: '.ts' }, { root, output, group })
|
|
223
|
+
const sdkFile = resolver.resolveFile({ name: sdk.className, extname: '.ts' }, { root, output, group: group ?? undefined })
|
|
219
224
|
|
|
220
225
|
files.push(
|
|
221
226
|
<File
|
|
@@ -8,6 +8,11 @@ import { Client } from '../components/Client'
|
|
|
8
8
|
import { Url } from '../components/Url.tsx'
|
|
9
9
|
import type { PluginClient } from '../types'
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Built-in operation generator for `@kubb/plugin-client`. Emits one async
|
|
13
|
+
* function per OpenAPI operation, plus the matching URL helper. Used when
|
|
14
|
+
* `clientType: 'function'` (the default).
|
|
15
|
+
*/
|
|
11
16
|
export const clientGenerator = defineGenerator<PluginClient>({
|
|
12
17
|
name: 'client',
|
|
13
18
|
renderer: jsxRendererSync,
|
|
@@ -24,14 +29,14 @@ export const clientGenerator = defineGenerator<PluginClient>({
|
|
|
24
29
|
|
|
25
30
|
const tsResolver = driver.getResolver(pluginTsName)
|
|
26
31
|
|
|
27
|
-
const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) :
|
|
28
|
-
const zodResolver = pluginZod ? driver.getResolver(pluginZodName) :
|
|
32
|
+
const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) : null
|
|
33
|
+
const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null
|
|
29
34
|
|
|
30
35
|
const importedTypeNames = resolveOperationTypeNames(node, tsResolver, { paramsCasing })
|
|
31
36
|
|
|
32
37
|
const importedZodNames =
|
|
33
38
|
zodResolver && parser === 'zod'
|
|
34
|
-
? [zodResolver.resolveResponseName?.(node), node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) :
|
|
39
|
+
? [zodResolver.resolveResponseName?.(node), node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null].filter(
|
|
35
40
|
(name): name is string => Boolean(name),
|
|
36
41
|
)
|
|
37
42
|
: []
|
|
@@ -39,13 +44,16 @@ export const clientGenerator = defineGenerator<PluginClient>({
|
|
|
39
44
|
const meta = {
|
|
40
45
|
name: resolver.resolveName(node.operationId),
|
|
41
46
|
urlName: resolver.resolveUrlName(node),
|
|
42
|
-
file: resolver.resolveFile(
|
|
47
|
+
file: resolver.resolveFile(
|
|
48
|
+
{ name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
|
|
49
|
+
{ root, output, group: group ?? undefined },
|
|
50
|
+
),
|
|
43
51
|
fileTs: tsResolver.resolveFile(
|
|
44
52
|
{ name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
|
|
45
53
|
{
|
|
46
54
|
root,
|
|
47
55
|
output: pluginTs.options?.output ?? output,
|
|
48
|
-
group: pluginTs.options?.group,
|
|
56
|
+
group: pluginTs.options?.group ?? undefined,
|
|
49
57
|
},
|
|
50
58
|
),
|
|
51
59
|
fileZod:
|
|
@@ -55,10 +63,10 @@ export const clientGenerator = defineGenerator<PluginClient>({
|
|
|
55
63
|
{
|
|
56
64
|
root,
|
|
57
65
|
output: pluginZod.options.output ?? output,
|
|
58
|
-
group: pluginZod.options?.group,
|
|
66
|
+
group: pluginZod.options?.group ?? undefined,
|
|
59
67
|
},
|
|
60
68
|
)
|
|
61
|
-
:
|
|
69
|
+
: null,
|
|
62
70
|
} as const
|
|
63
71
|
|
|
64
72
|
const hasFormData = node.requestBody?.content?.some((e) => e.contentType === 'multipart/form-data') ?? false
|
|
@@ -90,7 +98,7 @@ export const clientGenerator = defineGenerator<PluginClient>({
|
|
|
90
98
|
|
|
91
99
|
{hasFormData && <File.Import name={['buildFormData']} root={meta.file.path} path={path.resolve(root, '.kubb/config.ts')} />}
|
|
92
100
|
|
|
93
|
-
{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} />}
|
|
94
102
|
|
|
95
103
|
{meta.fileTs && importedTypeNames.length > 0 && (
|
|
96
104
|
<File.Import name={Array.from(new Set(importedTypeNames))} root={meta.file.path} path={meta.fileTs.path} isTypeOnly />
|
|
@@ -4,6 +4,12 @@ import { defineGenerator } from '@kubb/core'
|
|
|
4
4
|
import { File, Function, jsxRendererSync } 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: jsxRendererSync,
|
|
@@ -15,16 +21,16 @@ export const groupedClientGenerator = defineGenerator<PluginClient>({
|
|
|
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 = {
|
|
@@ -3,6 +3,12 @@ import { File, jsxRendererSync } from '@kubb/renderer-jsx'
|
|
|
3
3
|
import { Operations } from '../components/Operations'
|
|
4
4
|
import type { PluginClient } from '../types'
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Generates an `operations.ts` file that re-exports every operation grouped
|
|
8
|
+
* by HTTP method. Enabled when `pluginClient({ operations: true })`. Useful
|
|
9
|
+
* for building meta-tooling on top of the generated client (route
|
|
10
|
+
* registries, API explorers).
|
|
11
|
+
*/
|
|
6
12
|
export const operationsGenerator = defineGenerator<PluginClient>({
|
|
7
13
|
name: 'client',
|
|
8
14
|
renderer: jsxRendererSync,
|
|
@@ -11,7 +17,7 @@ export const operationsGenerator = defineGenerator<PluginClient>({
|
|
|
11
17
|
const { output, group } = ctx.options
|
|
12
18
|
|
|
13
19
|
const name = 'operations'
|
|
14
|
-
const file = resolver.resolveFile({ name, extname: '.ts' }, { root, output, group })
|
|
20
|
+
const file = resolver.resolveFile({ name, extname: '.ts' }, { root, output, group: group ?? undefined })
|
|
15
21
|
|
|
16
22
|
return (
|
|
17
23
|
<File
|
|
@@ -15,9 +15,9 @@ type OperationData = {
|
|
|
15
15
|
node: ast.OperationNode
|
|
16
16
|
name: string
|
|
17
17
|
tsResolver: ResolverTs
|
|
18
|
-
zodResolver: ResolverZod |
|
|
18
|
+
zodResolver: ResolverZod | null
|
|
19
19
|
typeFile: ast.FileNode
|
|
20
|
-
zodFile: ast.FileNode |
|
|
20
|
+
zodFile: ast.FileNode | null
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
type Controller = {
|
|
@@ -31,13 +31,19 @@ function resolveTypeImportNames(node: ast.OperationNode, tsResolver: ResolverTs)
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
function resolveZodImportNames(node: ast.OperationNode, zodResolver: ResolverZod): Array<string> {
|
|
34
|
-
const names: Array<string | undefined> = [
|
|
34
|
+
const names: Array<string | null | undefined> = [
|
|
35
35
|
zodResolver.resolveResponseName?.(node),
|
|
36
|
-
node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) :
|
|
36
|
+
node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null,
|
|
37
37
|
]
|
|
38
38
|
return names.filter((n): n is string => Boolean(n))
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Built-in `operations` generator for `@kubb/plugin-client` when
|
|
43
|
+
* `clientType: 'staticClass'`. Emits one class per tag, with a static method
|
|
44
|
+
* per operation so callers can use `Pet.getPetById(...)` without
|
|
45
|
+
* instantiating the class.
|
|
46
|
+
*/
|
|
41
47
|
export const staticClassClientGenerator = defineGenerator<PluginClient>({
|
|
42
48
|
name: 'staticClassClient',
|
|
43
49
|
renderer: jsxRendererSync,
|
|
@@ -51,8 +57,8 @@ export const staticClassClientGenerator = defineGenerator<PluginClient>({
|
|
|
51
57
|
|
|
52
58
|
const tsResolver = driver.getResolver(pluginTsName)
|
|
53
59
|
const tsPluginOptions = pluginTs.options
|
|
54
|
-
const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) :
|
|
55
|
-
const zodResolver = pluginZod ? driver.getResolver(pluginZodName) :
|
|
60
|
+
const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) : null
|
|
61
|
+
const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null
|
|
56
62
|
|
|
57
63
|
function buildOperationData(node: ast.OperationNode): OperationData {
|
|
58
64
|
const typeFile = tsResolver.resolveFile(
|
|
@@ -63,9 +69,9 @@ export const staticClassClientGenerator = defineGenerator<PluginClient>({
|
|
|
63
69
|
zodResolver && pluginZod?.options
|
|
64
70
|
? zodResolver.resolveFile(
|
|
65
71
|
{ name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
|
|
66
|
-
{ root, output: pluginZod.options?.output ?? output, group: pluginZod.options?.group },
|
|
72
|
+
{ root, output: pluginZod.options?.output ?? output, group: pluginZod.options?.group ?? undefined },
|
|
67
73
|
)
|
|
68
|
-
:
|
|
74
|
+
: null
|
|
69
75
|
|
|
70
76
|
return {
|
|
71
77
|
node: node,
|
|
@@ -83,7 +89,7 @@ export const staticClassClientGenerator = defineGenerator<PluginClient>({
|
|
|
83
89
|
|
|
84
90
|
if (!tag && !group) {
|
|
85
91
|
const name = resolver.resolveClassName('ApiClient')
|
|
86
|
-
const file = resolver.resolveFile({ name, extname: '.ts' }, { root, output, group })
|
|
92
|
+
const file = resolver.resolveFile({ name, extname: '.ts' }, { root, output, group: group ?? undefined })
|
|
87
93
|
const operationData = buildOperationData(operationNode)
|
|
88
94
|
const previous = acc.find((item) => item.file.path === file.path)
|
|
89
95
|
|
|
@@ -97,7 +103,7 @@ export const staticClassClientGenerator = defineGenerator<PluginClient>({
|
|
|
97
103
|
|
|
98
104
|
if (tag) {
|
|
99
105
|
const name = groupName
|
|
100
|
-
const file = resolver.resolveFile({ name, extname: '.ts', tag }, { root, output, group })
|
|
106
|
+
const file = resolver.resolveFile({ name, extname: '.ts', tag }, { root, output, group: group ?? undefined })
|
|
101
107
|
const operationData = buildOperationData(operationNode)
|
|
102
108
|
const previous = acc.find((item) => item.file.path === file.path)
|
|
103
109
|
|
package/src/plugin.ts
CHANGED
|
@@ -16,20 +16,33 @@ import { source as configSource } from './templates/config.source.ts'
|
|
|
16
16
|
import type { PluginClient } from './types.ts'
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
* Canonical plugin name for `@kubb/plugin-client
|
|
19
|
+
* Canonical plugin name for `@kubb/plugin-client`. Used for driver lookups and
|
|
20
|
+
* cross-plugin dependency references.
|
|
20
21
|
*/
|
|
21
22
|
export const pluginClientName = 'plugin-client' satisfies PluginClient['name']
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
|
-
* Generates
|
|
25
|
-
*
|
|
26
|
-
*
|
|
25
|
+
* Generates one HTTP client function per OpenAPI operation. Each function has
|
|
26
|
+
* typed path params, query params, body, and response, so callers use the API
|
|
27
|
+
* like any other typed function. Ships with `axios` and `fetch` runtimes; bring
|
|
28
|
+
* your own by setting `importPath`.
|
|
27
29
|
*
|
|
28
|
-
* @example
|
|
30
|
+
* @example
|
|
29
31
|
* ```ts
|
|
30
|
-
* import
|
|
32
|
+
* import { defineConfig } from 'kubb'
|
|
33
|
+
* import { pluginTs } from '@kubb/plugin-ts'
|
|
34
|
+
* import { pluginClient } from '@kubb/plugin-client'
|
|
35
|
+
*
|
|
31
36
|
* export default defineConfig({
|
|
32
|
-
*
|
|
37
|
+
* input: { path: './petStore.yaml' },
|
|
38
|
+
* output: { path: './src/gen' },
|
|
39
|
+
* plugins: [
|
|
40
|
+
* pluginTs(),
|
|
41
|
+
* pluginClient({
|
|
42
|
+
* output: { path: './clients' },
|
|
43
|
+
* client: 'fetch',
|
|
44
|
+
* }),
|
|
45
|
+
* ],
|
|
33
46
|
* })
|
|
34
47
|
* ```
|
|
35
48
|
*/
|
|
@@ -63,8 +76,8 @@ export const pluginClient = definePlugin<PluginClient>((options) => {
|
|
|
63
76
|
options.generators ??
|
|
64
77
|
[
|
|
65
78
|
clientType === 'staticClass' ? staticClassClientGenerator : clientType === 'class' ? classClientGenerator : clientGenerator,
|
|
66
|
-
group && clientType === 'function' ? groupedClientGenerator :
|
|
67
|
-
operations ? operationsGenerator :
|
|
79
|
+
group && clientType === 'function' ? groupedClientGenerator : null,
|
|
80
|
+
operations ? operationsGenerator : null,
|
|
68
81
|
].filter((x): x is NonNullable<typeof x> => Boolean(x))
|
|
69
82
|
|
|
70
83
|
const groupConfig = group
|
|
@@ -79,12 +92,12 @@ export const pluginClient = definePlugin<PluginClient>((options) => {
|
|
|
79
92
|
return `${camelCase(ctx.group)}Controller`
|
|
80
93
|
},
|
|
81
94
|
} satisfies Group)
|
|
82
|
-
:
|
|
95
|
+
: null
|
|
83
96
|
|
|
84
97
|
return {
|
|
85
98
|
name: pluginClientName,
|
|
86
99
|
options,
|
|
87
|
-
dependencies: [pluginTsName, parser === 'zod' ? pluginZodName :
|
|
100
|
+
dependencies: [pluginTsName, parser === 'zod' ? pluginZodName : null].filter((dependency): dependency is string => Boolean(dependency)),
|
|
88
101
|
hooks: {
|
|
89
102
|
'kubb:plugin:setup'(ctx) {
|
|
90
103
|
const resolver = userResolver ? { ...resolverClient, ...userResolver } : resolverClient
|
|
@@ -3,12 +3,18 @@ import { defineResolver } from '@kubb/core'
|
|
|
3
3
|
import type { PluginClient } from '../types.ts'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Default resolver used by `@kubb/plugin-client`. Decides the names and file
|
|
7
|
+
* paths for every generated client function or class. Functions and files use
|
|
8
|
+
* camelCase; classes and tag groups use PascalCase.
|
|
7
9
|
*
|
|
8
|
-
*
|
|
10
|
+
* @example Resolve client function and class names
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { resolverClient } from '@kubb/plugin-client'
|
|
9
13
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
14
|
+
* resolverClient.default('list pets', 'function') // 'listPets'
|
|
15
|
+
* resolverClient.resolveClassName('pet') // 'Pet'
|
|
16
|
+
* resolverClient.resolveUrlName(operationNode) // 'getShowPetByIdUrl'
|
|
17
|
+
* ```
|
|
12
18
|
*/
|
|
13
19
|
export const resolverClient = defineResolver<PluginClient>(() => ({
|
|
14
20
|
name: 'default',
|