@kubb/plugin-client 5.0.0-beta.4 → 5.0.0-beta.42
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 +625 -354
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +160 -94
- package/dist/index.js +626 -355
- package/dist/index.js.map +1 -1
- package/dist/templates/clients/axios.source.cjs +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/fetch.source.cjs +1 -1
- package/dist/templates/clients/fetch.source.d.ts +1 -1
- package/dist/templates/clients/fetch.source.js +1 -1
- package/dist/templates/config.source.d.ts +1 -1
- package/extension.yaml +792 -301
- package/package.json +11 -16
- package/src/clients/axios.ts +41 -7
- package/src/clients/fetch.ts +106 -6
- package/src/components/ClassClient.tsx +19 -20
- package/src/components/Client.tsx +74 -53
- package/src/components/Operations.tsx +2 -1
- package/src/components/StaticClassClient.tsx +19 -20
- package/src/components/Url.tsx +8 -9
- package/src/components/WrapperClient.tsx +9 -5
- package/src/functionParams.ts +8 -8
- package/src/generators/classClientGenerator.tsx +51 -47
- package/src/generators/clientGenerator.tsx +37 -48
- package/src/generators/groupedClientGenerator.tsx +14 -8
- package/src/generators/operationsGenerator.tsx +14 -8
- package/src/generators/staticClassClientGenerator.tsx +45 -41
- package/src/plugin.ts +27 -26
- package/src/resolvers/resolverClient.ts +31 -8
- package/src/types.ts +93 -55
- package/src/utils.ts +35 -56
- 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,19 +1,20 @@
|
|
|
1
|
+
import { buildOperationComments, getContentTypeInfo, getOperationParameters } from '@internals/shared'
|
|
1
2
|
import { buildJSDoc, URLPath } from '@internals/utils'
|
|
2
|
-
import
|
|
3
|
+
import { ast } from '@kubb/core'
|
|
3
4
|
import type { ResolverTs } from '@kubb/plugin-ts'
|
|
4
5
|
import { functionPrinter } from '@kubb/plugin-ts'
|
|
5
6
|
import type { ResolverZod } from '@kubb/plugin-zod'
|
|
6
7
|
import { File } from '@kubb/renderer-jsx'
|
|
7
8
|
import type { KubbReactNode } from '@kubb/renderer-jsx/types'
|
|
8
9
|
import type { PluginClient } from '../types.ts'
|
|
9
|
-
import { buildClassClientParams, buildFormDataLine, buildGenerics, buildHeaders, buildRequestDataLine, buildReturnStatement
|
|
10
|
-
import {
|
|
10
|
+
import { buildClassClientParams, buildFormDataLine, buildGenerics, buildHeaders, buildRequestDataLine, buildReturnStatement } from '../utils.ts'
|
|
11
|
+
import { buildClientParamsNode } from './Client.tsx'
|
|
11
12
|
|
|
12
13
|
type OperationData = {
|
|
13
14
|
node: ast.OperationNode
|
|
14
15
|
name: string
|
|
15
16
|
tsResolver: ResolverTs
|
|
16
|
-
zodResolver?: ResolverZod
|
|
17
|
+
zodResolver?: ResolverZod | null
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
type Props = {
|
|
@@ -21,7 +22,7 @@ type Props = {
|
|
|
21
22
|
isExportable?: boolean
|
|
22
23
|
isIndexable?: boolean
|
|
23
24
|
operations: Array<OperationData>
|
|
24
|
-
baseURL: string | undefined
|
|
25
|
+
baseURL: string | null | undefined
|
|
25
26
|
dataReturnType: PluginClient['resolvedOptions']['dataReturnType']
|
|
26
27
|
paramsCasing: PluginClient['resolvedOptions']['paramsCasing']
|
|
27
28
|
paramsType: PluginClient['resolvedOptions']['pathParamsType']
|
|
@@ -34,8 +35,8 @@ type GenerateMethodProps = {
|
|
|
34
35
|
node: ast.OperationNode
|
|
35
36
|
name: string
|
|
36
37
|
tsResolver: ResolverTs
|
|
37
|
-
zodResolver?: ResolverZod
|
|
38
|
-
baseURL: string | undefined
|
|
38
|
+
zodResolver?: ResolverZod | null
|
|
39
|
+
baseURL: string | null | undefined
|
|
39
40
|
dataReturnType: PluginClient['resolvedOptions']['dataReturnType']
|
|
40
41
|
parser: PluginClient['resolvedOptions']['parser'] | undefined
|
|
41
42
|
paramsType: PluginClient['resolvedOptions']['paramsType']
|
|
@@ -57,26 +58,25 @@ function generateMethod({
|
|
|
57
58
|
paramsCasing,
|
|
58
59
|
pathParamsType,
|
|
59
60
|
}: GenerateMethodProps): string {
|
|
61
|
+
if (!ast.isHttpOperationNode(node)) return ''
|
|
60
62
|
const path = new URLPath(node.path, { casing: paramsCasing })
|
|
61
|
-
const contentType = node
|
|
62
|
-
const isFormData = contentType === 'multipart/form-data'
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
: undefined
|
|
67
|
-
const headers = buildHeaders(contentType, !!headerParamsName)
|
|
63
|
+
const { defaultContentType: contentType, isMultipleContentTypes, hasFormData } = getContentTypeInfo(node)
|
|
64
|
+
const isFormData = !isMultipleContentTypes && contentType === 'multipart/form-data'
|
|
65
|
+
const { header: headerParams } = getOperationParameters(node)
|
|
66
|
+
const headerParamsName = headerParams.length > 0 ? tsResolver.resolveHeaderParamsName(node, headerParams[0]!) : null
|
|
67
|
+
const headers = isMultipleContentTypes ? (headerParamsName ? ['...headers'] : []) : buildHeaders(contentType, !!headerParamsName)
|
|
68
68
|
const generics = buildGenerics(node, tsResolver)
|
|
69
|
-
const paramsNode =
|
|
69
|
+
const paramsNode = buildClientParamsNode({ paramsType, paramsCasing, pathParamsType, node, tsResolver, isConfigurable: true })
|
|
70
70
|
const paramsSignature = declarationPrinter.print(paramsNode) ?? ''
|
|
71
|
-
const clientParams = buildClassClientParams({ node, path, baseURL, tsResolver, isFormData, headers })
|
|
72
|
-
const jsdoc = buildJSDoc(
|
|
71
|
+
const clientParams = buildClassClientParams({ node, path, baseURL, tsResolver, isFormData, isMultipleContentTypes, hasFormData, headers })
|
|
72
|
+
const jsdoc = buildJSDoc(buildOperationComments(node, { link: 'urlPath', linkPosition: 'beforeDeprecated', splitLines: true }))
|
|
73
73
|
|
|
74
74
|
const requestDataLine = buildRequestDataLine({ parser, node, zodResolver })
|
|
75
|
-
const formDataLine = buildFormDataLine(isFormData, !!node.requestBody?.content?.[0]?.schema)
|
|
75
|
+
const formDataLine = buildFormDataLine(isFormData || (isMultipleContentTypes && hasFormData), !!node.requestBody?.content?.[0]?.schema)
|
|
76
76
|
const returnStatement = buildReturnStatement({ dataReturnType, parser, node, zodResolver })
|
|
77
77
|
|
|
78
78
|
const methodBody = [
|
|
79
|
-
|
|
79
|
+
`const { client: request = client, ${isMultipleContentTypes ? `contentType = ${JSON.stringify(contentType)}, ` : ''}...requestConfig } = mergeConfig(this.#config, config)`,
|
|
80
80
|
'',
|
|
81
81
|
requestDataLine,
|
|
82
82
|
formDataLine,
|
|
@@ -127,4 +127,3 @@ export function StaticClassClient({
|
|
|
127
127
|
</File.Source>
|
|
128
128
|
)
|
|
129
129
|
}
|
|
130
|
-
StaticClassClient.getParams = Client.getParams
|
package/src/components/Url.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { buildParamsMapping, getOperationParameters } from '@internals/shared'
|
|
1
2
|
import { isValidVarName, URLPath } from '@internals/utils'
|
|
2
3
|
import { ast } from '@kubb/core'
|
|
3
4
|
import type { ResolverTs } from '@kubb/plugin-ts'
|
|
@@ -5,14 +6,13 @@ import { functionPrinter } from '@kubb/plugin-ts'
|
|
|
5
6
|
import { Const, File, Function } from '@kubb/renderer-jsx'
|
|
6
7
|
import type { KubbReactNode } from '@kubb/renderer-jsx/types'
|
|
7
8
|
import type { PluginClient } from '../types.ts'
|
|
8
|
-
import { buildParamsMapping } from '../utils.ts'
|
|
9
9
|
|
|
10
10
|
type Props = {
|
|
11
11
|
name: string
|
|
12
12
|
isExportable?: boolean
|
|
13
13
|
isIndexable?: boolean
|
|
14
14
|
|
|
15
|
-
baseURL: string | undefined
|
|
15
|
+
baseURL: string | null | undefined
|
|
16
16
|
paramsCasing: PluginClient['resolvedOptions']['paramsCasing']
|
|
17
17
|
paramsType: PluginClient['resolvedOptions']['pathParamsType']
|
|
18
18
|
pathParamsType: PluginClient['resolvedOptions']['pathParamsType']
|
|
@@ -30,7 +30,7 @@ type GetParamsProps = {
|
|
|
30
30
|
|
|
31
31
|
const declarationPrinter = functionPrinter({ mode: 'declaration' })
|
|
32
32
|
|
|
33
|
-
function
|
|
33
|
+
export function buildUrlParamsNode({ paramsType, paramsCasing, pathParamsType, node, tsResolver }: GetParamsProps): ast.FunctionParametersNode {
|
|
34
34
|
// Build a URL-only node with only path params (no body, query, header)
|
|
35
35
|
const urlNode: ast.OperationNode = {
|
|
36
36
|
...node,
|
|
@@ -57,9 +57,10 @@ export function Url({
|
|
|
57
57
|
node,
|
|
58
58
|
tsResolver,
|
|
59
59
|
}: Props): KubbReactNode {
|
|
60
|
+
if (!ast.isHttpOperationNode(node)) return null
|
|
60
61
|
const path = new URLPath(node.path)
|
|
61
62
|
|
|
62
|
-
const paramsNode =
|
|
63
|
+
const paramsNode = buildUrlParamsNode({
|
|
63
64
|
paramsType,
|
|
64
65
|
paramsCasing,
|
|
65
66
|
pathParamsType,
|
|
@@ -68,9 +69,9 @@ export function Url({
|
|
|
68
69
|
})
|
|
69
70
|
const paramsSignature = declarationPrinter.print(paramsNode) ?? ''
|
|
70
71
|
|
|
71
|
-
const originalPathParams = node
|
|
72
|
-
const casedPathParams =
|
|
73
|
-
const pathParamsMapping = paramsCasing ? buildParamsMapping(originalPathParams, casedPathParams) :
|
|
72
|
+
const { path: originalPathParams } = getOperationParameters(node)
|
|
73
|
+
const { path: casedPathParams } = getOperationParameters(node, { paramsCasing })
|
|
74
|
+
const pathParamsMapping = paramsCasing ? buildParamsMapping(originalPathParams, casedPathParams) : null
|
|
74
75
|
|
|
75
76
|
return (
|
|
76
77
|
<File.Source name={name} isExportable={isExportable} isIndexable={isIndexable}>
|
|
@@ -88,5 +89,3 @@ export function Url({
|
|
|
88
89
|
</File.Source>
|
|
89
90
|
)
|
|
90
91
|
}
|
|
91
|
-
|
|
92
|
-
Url.getParams = getParams
|
|
@@ -1,17 +1,21 @@
|
|
|
1
|
-
import { camelCase } from '@internals/utils'
|
|
2
1
|
import { File } from '@kubb/renderer-jsx'
|
|
3
2
|
import type { KubbReactNode } from '@kubb/renderer-jsx/types'
|
|
4
3
|
|
|
4
|
+
type ClientController = {
|
|
5
|
+
className: string
|
|
6
|
+
propertyName: string
|
|
7
|
+
}
|
|
8
|
+
|
|
5
9
|
type Props = {
|
|
6
10
|
name: string
|
|
7
|
-
|
|
11
|
+
controllers: Array<ClientController>
|
|
8
12
|
isExportable?: boolean
|
|
9
13
|
isIndexable?: boolean
|
|
10
14
|
}
|
|
11
15
|
|
|
12
|
-
export function WrapperClient({ name,
|
|
13
|
-
const properties =
|
|
14
|
-
const assignments =
|
|
16
|
+
export function WrapperClient({ name, controllers, isExportable = true, isIndexable = true }: Props): KubbReactNode {
|
|
17
|
+
const properties = controllers.map(({ className, propertyName }) => ` readonly ${propertyName}: ${className}`).join('\n')
|
|
18
|
+
const assignments = controllers.map(({ className, propertyName }) => ` this.${propertyName} = new ${className}(config)`).join('\n')
|
|
15
19
|
|
|
16
20
|
const classCode = `export class ${name} {
|
|
17
21
|
${properties}
|
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() {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import path from 'node:path'
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import { defineGenerator } from '@kubb/core'
|
|
2
|
+
import { 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'
|
|
8
8
|
import { pluginZodName } from '@kubb/plugin-zod'
|
|
9
|
-
import { File,
|
|
9
|
+
import { File, jsxRendererSync } 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'
|
|
@@ -15,65 +15,65 @@ 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 = {
|
|
24
24
|
name: string
|
|
25
|
+
propertyName: string
|
|
25
26
|
file: ast.FileNode
|
|
26
27
|
operations: Array<OperationData>
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
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))
|
|
31
|
+
return resolveOperationTypeNames(node, tsResolver, { order: 'body-response-first' })
|
|
39
32
|
}
|
|
40
33
|
|
|
41
34
|
function resolveZodImportNames(node: ast.OperationNode, zodResolver: ResolverZod): Array<string> {
|
|
42
|
-
const names: Array<string | undefined> = [
|
|
35
|
+
const names: Array<string | null | undefined> = [
|
|
43
36
|
zodResolver.resolveResponseName?.(node),
|
|
44
|
-
node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) :
|
|
37
|
+
node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null,
|
|
45
38
|
]
|
|
46
39
|
return names.filter((n): n is string => Boolean(n))
|
|
47
40
|
}
|
|
48
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Built-in `operations` generator for `@kubb/plugin-client` when
|
|
44
|
+
* `clientType: 'class'`. Emits one class per tag, with one instance method
|
|
45
|
+
* per operation and a shared constructor for request configuration.
|
|
46
|
+
*/
|
|
49
47
|
export const classClientGenerator = defineGenerator<PluginClient>({
|
|
50
48
|
name: 'classClient',
|
|
51
|
-
renderer:
|
|
49
|
+
renderer: jsxRendererSync,
|
|
52
50
|
operations(nodes, ctx) {
|
|
53
|
-
const {
|
|
51
|
+
const { config, driver, resolver, root } = ctx
|
|
54
52
|
const { output, group, dataReturnType, paramsCasing, paramsType, pathParamsType, parser, importPath, sdk } = ctx.options
|
|
55
|
-
const baseURL = ctx.options.baseURL ??
|
|
53
|
+
const baseURL = ctx.options.baseURL ?? ctx.meta.baseURL
|
|
56
54
|
|
|
57
55
|
const pluginTs = driver.getPlugin(pluginTsName)
|
|
58
56
|
if (!pluginTs) return null
|
|
59
57
|
|
|
60
58
|
const tsResolver = driver.getResolver(pluginTsName)
|
|
61
59
|
const tsPluginOptions = pluginTs.options
|
|
62
|
-
const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) :
|
|
63
|
-
const zodResolver = pluginZod ? driver.getResolver(pluginZodName) :
|
|
60
|
+
const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) : null
|
|
61
|
+
const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null
|
|
64
62
|
|
|
65
63
|
function buildOperationData(node: ast.OperationNode): OperationData {
|
|
66
|
-
const typeFile = tsResolver.resolveFile(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
64
|
+
const typeFile = tsResolver.resolveFile(operationFileEntry(node, node.operationId), {
|
|
65
|
+
root,
|
|
66
|
+
output: tsPluginOptions?.output ?? output,
|
|
67
|
+
group: tsPluginOptions?.group,
|
|
68
|
+
})
|
|
70
69
|
const zodFile =
|
|
71
70
|
zodResolver && pluginZod?.options
|
|
72
|
-
? zodResolver.resolveFile(
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
71
|
+
? zodResolver.resolveFile(operationFileEntry(node, node.operationId), {
|
|
72
|
+
root,
|
|
73
|
+
output: pluginZod.options?.output ?? output,
|
|
74
|
+
group: pluginZod.options?.group ?? undefined,
|
|
75
|
+
})
|
|
76
|
+
: null
|
|
77
77
|
|
|
78
78
|
return {
|
|
79
79
|
node: node,
|
|
@@ -86,30 +86,34 @@ export const classClientGenerator = defineGenerator<PluginClient>({
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
const controllers = nodes.reduce((acc, operationNode) => {
|
|
89
|
+
if (!ast.isHttpOperationNode(operationNode)) return acc
|
|
89
90
|
const tag = operationNode.tags[0]
|
|
90
|
-
const groupName = tag ? (group?.name?.({ group: camelCase(tag) }) ??
|
|
91
|
+
const groupName = tag ? (group?.name?.({ group: camelCase(tag) }) ?? resolver.resolveGroupName(tag)) : resolver.resolveGroupName('Client')
|
|
91
92
|
|
|
92
93
|
if (!tag && !group) {
|
|
93
|
-
const name = 'ApiClient'
|
|
94
|
-
const file = resolver.resolveFile({ name, extname: '.ts' }, { root, output, group })
|
|
94
|
+
const name = resolver.resolveClassName('ApiClient')
|
|
95
|
+
const file = resolver.resolveFile({ name, extname: '.ts' }, { root, output, group: group ?? undefined })
|
|
95
96
|
const operationData = buildOperationData(operationNode)
|
|
96
97
|
const previous = acc.find((item) => item.file.path === file.path)
|
|
97
98
|
|
|
98
99
|
if (previous) {
|
|
99
100
|
previous.operations.push(operationData)
|
|
100
101
|
} else {
|
|
101
|
-
acc.push({ name, file, operations: [operationData] })
|
|
102
|
+
acc.push({ name, propertyName: resolver.resolveClientPropertyName(name), file, operations: [operationData] })
|
|
102
103
|
}
|
|
103
|
-
|
|
104
|
+
return acc
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (tag) {
|
|
104
108
|
const name = groupName
|
|
105
|
-
const file = resolver.resolveFile({ name, extname: '.ts', tag }, { root, output, group })
|
|
109
|
+
const file = resolver.resolveFile({ name, extname: '.ts', tag }, { root, output, group: group ?? undefined })
|
|
106
110
|
const operationData = buildOperationData(operationNode)
|
|
107
111
|
const previous = acc.find((item) => item.file.path === file.path)
|
|
108
112
|
|
|
109
113
|
if (previous) {
|
|
110
114
|
previous.operations.push(operationData)
|
|
111
115
|
} else {
|
|
112
|
-
acc.push({ name, file, operations: [operationData] })
|
|
116
|
+
acc.push({ name, propertyName: resolver.resolveClientPropertyName(name), file, operations: [operationData] })
|
|
113
117
|
}
|
|
114
118
|
}
|
|
115
119
|
|
|
@@ -159,7 +163,7 @@ export const classClientGenerator = defineGenerator<PluginClient>({
|
|
|
159
163
|
const { typeImportsByFile, typeFilesByPath } = collectTypeImports(ops)
|
|
160
164
|
const { zodImportsByFile, zodFilesByPath } =
|
|
161
165
|
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?.
|
|
166
|
+
const hasFormData = ops.some((op) => op.node.requestBody?.content?.some((e) => e.contentType === 'multipart/form-data') ?? false)
|
|
163
167
|
|
|
164
168
|
return (
|
|
165
169
|
<File
|
|
@@ -167,18 +171,18 @@ export const classClientGenerator = defineGenerator<PluginClient>({
|
|
|
167
171
|
baseName={file.baseName}
|
|
168
172
|
path={file.path}
|
|
169
173
|
meta={file.meta}
|
|
170
|
-
banner={resolver.resolveBanner(
|
|
171
|
-
footer={resolver.resolveFooter(
|
|
174
|
+
banner={resolver.resolveBanner(ctx.meta, { output, config, file: { path: file.path, baseName: file.baseName } })}
|
|
175
|
+
footer={resolver.resolveFooter(ctx.meta, { output, config, file: { path: file.path, baseName: file.baseName } })}
|
|
172
176
|
>
|
|
173
177
|
{importPath ? (
|
|
174
178
|
<>
|
|
175
|
-
<File.Import name={'
|
|
179
|
+
<File.Import name={'client'} path={importPath} />
|
|
176
180
|
<File.Import name={['mergeConfig']} path={importPath} />
|
|
177
181
|
<File.Import name={['Client', 'RequestConfig', 'ResponseErrorConfig']} path={importPath} isTypeOnly />
|
|
178
182
|
</>
|
|
179
183
|
) : (
|
|
180
184
|
<>
|
|
181
|
-
<File.Import name={['
|
|
185
|
+
<File.Import name={['client']} root={file.path} path={path.resolve(root, '.kubb/client.ts')} />
|
|
182
186
|
<File.Import name={['mergeConfig']} root={file.path} path={path.resolve(root, '.kubb/client.ts')} />
|
|
183
187
|
<File.Import name={['Client', 'RequestConfig', 'ResponseErrorConfig']} root={file.path} path={path.resolve(root, '.kubb/client.ts')} isTypeOnly />
|
|
184
188
|
</>
|
|
@@ -218,7 +222,7 @@ export const classClientGenerator = defineGenerator<PluginClient>({
|
|
|
218
222
|
})
|
|
219
223
|
|
|
220
224
|
if (sdk) {
|
|
221
|
-
const sdkFile = resolver.resolveFile({ name: sdk.className, extname: '.ts' }, { root, output, group })
|
|
225
|
+
const sdkFile = resolver.resolveFile({ name: sdk.className, extname: '.ts' }, { root, output, group: group ?? undefined })
|
|
222
226
|
|
|
223
227
|
files.push(
|
|
224
228
|
<File
|
|
@@ -226,8 +230,8 @@ export const classClientGenerator = defineGenerator<PluginClient>({
|
|
|
226
230
|
baseName={sdkFile.baseName}
|
|
227
231
|
path={sdkFile.path}
|
|
228
232
|
meta={sdkFile.meta}
|
|
229
|
-
banner={resolver.resolveBanner(
|
|
230
|
-
footer={resolver.resolveFooter(
|
|
233
|
+
banner={resolver.resolveBanner(ctx.meta, { output, config, file: { path: sdkFile.path, baseName: sdkFile.baseName } })}
|
|
234
|
+
footer={resolver.resolveFooter(ctx.meta, { output, config, file: { path: sdkFile.path, baseName: sdkFile.baseName } })}
|
|
231
235
|
>
|
|
232
236
|
{importPath ? (
|
|
233
237
|
<File.Import name={['Client', 'RequestConfig']} path={importPath} isTypeOnly />
|
|
@@ -239,7 +243,7 @@ export const classClientGenerator = defineGenerator<PluginClient>({
|
|
|
239
243
|
<File.Import key={name} name={[name]} root={sdkFile.path} path={file.path} />
|
|
240
244
|
))}
|
|
241
245
|
|
|
242
|
-
<WrapperClient name={sdk.className}
|
|
246
|
+
<WrapperClient name={sdk.className} controllers={controllers.map(({ name, propertyName }) => ({ className: name, propertyName }))} />
|
|
243
247
|
</File>,
|
|
244
248
|
)
|
|
245
249
|
}
|
|
@@ -1,19 +1,26 @@
|
|
|
1
1
|
import path from 'node:path'
|
|
2
|
+
import { 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'
|
|
5
|
-
import { File,
|
|
6
|
+
import { File, jsxRendererSync } 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'
|
|
9
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
|
+
*/
|
|
10
16
|
export const clientGenerator = defineGenerator<PluginClient>({
|
|
11
17
|
name: 'client',
|
|
12
|
-
renderer:
|
|
18
|
+
renderer: jsxRendererSync,
|
|
13
19
|
operation(node, ctx) {
|
|
14
|
-
|
|
20
|
+
if (!ast.isHttpOperationNode(node)) return null
|
|
21
|
+
const { config, driver, resolver, root } = ctx
|
|
15
22
|
const { output, urlType, dataReturnType, paramsCasing, paramsType, pathParamsType, parser, importPath, group } = ctx.options
|
|
16
|
-
const baseURL = ctx.options.baseURL ??
|
|
23
|
+
const baseURL = ctx.options.baseURL ?? ctx.meta.baseURL
|
|
17
24
|
|
|
18
25
|
const pluginTs = driver.getPlugin(pluginTsName)
|
|
19
26
|
|
|
@@ -23,71 +30,55 @@ export const clientGenerator = defineGenerator<PluginClient>({
|
|
|
23
30
|
|
|
24
31
|
const tsResolver = driver.getResolver(pluginTsName)
|
|
25
32
|
|
|
26
|
-
const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) :
|
|
27
|
-
const zodResolver = pluginZod ? driver.getResolver(pluginZodName) :
|
|
33
|
+
const pluginZod = parser === 'zod' ? driver.getPlugin(pluginZodName) : null
|
|
34
|
+
const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null
|
|
28
35
|
|
|
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')
|
|
33
|
-
|
|
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)
|
|
36
|
+
const importedTypeNames = resolveOperationTypeNames(node, tsResolver, { paramsCasing })
|
|
42
37
|
|
|
43
38
|
const importedZodNames =
|
|
44
39
|
zodResolver && parser === 'zod'
|
|
45
|
-
? [zodResolver.resolveResponseName?.(node), node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) :
|
|
40
|
+
? [zodResolver.resolveResponseName?.(node), node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null].filter(
|
|
41
|
+
(name): name is string => Boolean(name),
|
|
42
|
+
)
|
|
46
43
|
: []
|
|
47
44
|
|
|
48
45
|
const meta = {
|
|
49
46
|
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
|
-
),
|
|
47
|
+
urlName: resolver.resolveUrlName(node),
|
|
48
|
+
file: resolver.resolveFile(operationFileEntry(node, node.operationId), { root, output, group: group ?? undefined }),
|
|
49
|
+
fileTs: tsResolver.resolveFile(operationFileEntry(node, node.operationId), {
|
|
50
|
+
root,
|
|
51
|
+
output: pluginTs.options?.output ?? output,
|
|
52
|
+
group: pluginTs.options?.group ?? undefined,
|
|
53
|
+
}),
|
|
60
54
|
fileZod:
|
|
61
55
|
zodResolver && pluginZod?.options
|
|
62
|
-
? zodResolver.resolveFile(
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
},
|
|
69
|
-
)
|
|
70
|
-
: undefined,
|
|
56
|
+
? zodResolver.resolveFile(operationFileEntry(node, node.operationId), {
|
|
57
|
+
root,
|
|
58
|
+
output: pluginZod.options.output ?? output,
|
|
59
|
+
group: pluginZod.options?.group ?? undefined,
|
|
60
|
+
})
|
|
61
|
+
: null,
|
|
71
62
|
} as const
|
|
72
63
|
|
|
73
|
-
const
|
|
64
|
+
const hasFormData = node.requestBody?.content?.some((e) => e.contentType === 'multipart/form-data') ?? false
|
|
74
65
|
|
|
75
66
|
return (
|
|
76
67
|
<File
|
|
77
68
|
baseName={meta.file.baseName}
|
|
78
69
|
path={meta.file.path}
|
|
79
70
|
meta={meta.file.meta}
|
|
80
|
-
banner={resolver.resolveBanner(
|
|
81
|
-
footer={resolver.resolveFooter(
|
|
71
|
+
banner={resolver.resolveBanner(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
|
|
72
|
+
footer={resolver.resolveFooter(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
|
|
82
73
|
>
|
|
83
74
|
{importPath ? (
|
|
84
75
|
<>
|
|
85
|
-
<File.Import name={'
|
|
76
|
+
<File.Import name={'client'} path={importPath} />
|
|
86
77
|
<File.Import name={['Client', 'RequestConfig', 'ResponseErrorConfig']} path={importPath} isTypeOnly />
|
|
87
78
|
</>
|
|
88
79
|
) : (
|
|
89
80
|
<>
|
|
90
|
-
<File.Import name={['
|
|
81
|
+
<File.Import name={['client']} root={meta.file.path} path={path.resolve(root, '.kubb/client.ts')} />
|
|
91
82
|
<File.Import
|
|
92
83
|
name={['Client', 'RequestConfig', 'ResponseErrorConfig']}
|
|
93
84
|
root={meta.file.path}
|
|
@@ -97,11 +88,9 @@ export const clientGenerator = defineGenerator<PluginClient>({
|
|
|
97
88
|
</>
|
|
98
89
|
)}
|
|
99
90
|
|
|
100
|
-
{
|
|
101
|
-
<File.Import name={['buildFormData']} root={meta.file.path} path={path.resolve(root, '.kubb/config.ts')} />
|
|
102
|
-
)}
|
|
91
|
+
{hasFormData && <File.Import name={['buildFormData']} root={meta.file.path} path={path.resolve(root, '.kubb/config.ts')} />}
|
|
103
92
|
|
|
104
|
-
{meta.fileZod && importedZodNames.length > 0 && <File.Import name={importedZodNames as string
|
|
93
|
+
{meta.fileZod && importedZodNames.length > 0 && <File.Import name={importedZodNames as Array<string>} root={meta.file.path} path={meta.fileZod.path} />}
|
|
105
94
|
|
|
106
95
|
{meta.fileTs && importedTypeNames.length > 0 && (
|
|
107
96
|
<File.Import name={Array.from(new Set(importedTypeNames))} root={meta.file.path} path={meta.fileTs.path} isTypeOnly />
|
|
@@ -1,30 +1,36 @@
|
|
|
1
1
|
import { camelCase } from '@internals/utils'
|
|
2
2
|
import type { ast } from '@kubb/core'
|
|
3
3
|
import { defineGenerator } from '@kubb/core'
|
|
4
|
-
import { File, Function,
|
|
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
|
-
renderer:
|
|
15
|
+
renderer: jsxRendererSync,
|
|
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} />
|