@kubb/plugin-client 5.0.0-beta.3 → 5.0.0-beta.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +24 -4
  2. package/dist/clients/axios.cjs +25 -3
  3. package/dist/clients/axios.cjs.map +1 -1
  4. package/dist/clients/axios.d.ts +9 -2
  5. package/dist/clients/axios.js +25 -3
  6. package/dist/clients/axios.js.map +1 -1
  7. package/dist/clients/fetch.cjs +76 -8
  8. package/dist/clients/fetch.cjs.map +1 -1
  9. package/dist/clients/fetch.d.ts +9 -2
  10. package/dist/clients/fetch.js +76 -8
  11. package/dist/clients/fetch.js.map +1 -1
  12. package/dist/index.cjs +627 -353
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.ts +153 -86
  15. package/dist/index.js +628 -354
  16. package/dist/index.js.map +1 -1
  17. package/dist/templates/clients/axios.source.cjs +1 -1
  18. package/dist/templates/clients/axios.source.js +1 -1
  19. package/dist/templates/clients/fetch.source.cjs +1 -1
  20. package/dist/templates/clients/fetch.source.js +1 -1
  21. package/extension.yaml +1293 -0
  22. package/package.json +11 -17
  23. package/src/clients/axios.ts +41 -7
  24. package/src/clients/fetch.ts +106 -6
  25. package/src/components/ClassClient.tsx +19 -20
  26. package/src/components/Client.tsx +74 -53
  27. package/src/components/Operations.tsx +2 -1
  28. package/src/components/StaticClassClient.tsx +19 -20
  29. package/src/components/Url.tsx +8 -9
  30. package/src/components/WrapperClient.tsx +9 -5
  31. package/src/functionParams.ts +8 -8
  32. package/src/generators/classClientGenerator.tsx +51 -47
  33. package/src/generators/clientGenerator.tsx +37 -48
  34. package/src/generators/groupedClientGenerator.tsx +14 -8
  35. package/src/generators/operationsGenerator.tsx +14 -8
  36. package/src/generators/staticClassClientGenerator.tsx +45 -41
  37. package/src/plugin.ts +27 -26
  38. package/src/resolvers/resolverClient.ts +31 -8
  39. package/src/types.ts +93 -55
  40. package/src/utils.ts +35 -56
  41. package/templates/clients/axios.ts +0 -73
  42. package/templates/clients/fetch.ts +0 -96
  43. package/templates/config.ts +0 -43
package/src/utils.ts CHANGED
@@ -1,51 +1,19 @@
1
- import { URLPath } from '@internals/utils'
2
- import type { ast } from '@kubb/core'
1
+ import { getOperationParameters, getResponseType, resolveSuccessNames } from '@internals/shared'
2
+ import type { URLPath } from '@internals/utils'
3
+ import { ast } from '@kubb/core'
3
4
  import type { ResolverTs } from '@kubb/plugin-ts'
4
5
  import type { ResolverZod } from '@kubb/plugin-zod'
5
6
  import { createFunctionParams } from './functionParams.ts'
6
7
  import type { PluginClient } from './types.ts'
7
8
 
8
- /**
9
- * Extracts documentation comments from an operation node.
10
- * Includes description, summary, link, and deprecation information.
11
- */
12
- export function getComments(node: ast.OperationNode): Array<string> {
13
- return [
14
- node.description && `@description ${node.description}`,
15
- node.summary && `@summary ${node.summary}`,
16
- node.path && `{@link ${new URLPath(node.path).URL}}`,
17
- node.deprecated && '@deprecated',
18
- ]
19
- .filter((x): x is string => Boolean(x))
20
- .flatMap((text) => text.split(/\r?\n/).map((line) => line.trim()))
21
- .filter((x): x is string => Boolean(x))
22
- }
23
-
24
- /**
25
- * Builds a mapping of original parameter names to their transformed (cased) names.
26
- * Returns undefined if no names have changed.
27
- */
28
- export function buildParamsMapping(originalParams: Array<ast.ParameterNode>, casedParams: Array<ast.ParameterNode>): Record<string, string> | undefined {
29
- const mapping: Record<string, string> = {}
30
- let hasChanged = false
31
- originalParams.forEach((param, i) => {
32
- const casedName = casedParams[i]?.name ?? param.name
33
- mapping[param.name] = casedName
34
- if (param.name !== casedName) {
35
- hasChanged = true
36
- }
37
- })
38
- return hasChanged ? mapping : undefined
39
- }
40
-
41
9
  /**
42
10
  * Builds HTTP headers array for a client request.
43
11
  * Includes Content-Type (if not default) and spreads header parameters if present.
44
12
  */
45
13
  export function buildHeaders(contentType: string, hasHeaderParams: boolean): Array<string> {
46
14
  return [
47
- contentType !== 'application/json' && contentType !== 'multipart/form-data' ? `'Content-Type': '${contentType}'` : undefined,
48
- hasHeaderParams ? '...headers' : undefined,
15
+ contentType !== 'application/json' && contentType !== 'multipart/form-data' ? `'Content-Type': '${contentType}'` : null,
16
+ hasHeaderParams ? '...headers' : null,
49
17
  ].filter(Boolean) as Array<string>
50
18
  }
51
19
 
@@ -54,8 +22,9 @@ export function buildHeaders(contentType: string, hasHeaderParams: boolean): Arr
54
22
  * Includes response type, error type, and optional request type.
55
23
  */
56
24
  export function buildGenerics(node: ast.OperationNode, tsResolver: ResolverTs): Array<string> {
57
- const responseName = tsResolver.resolveResponseName(node)
58
- const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : undefined
25
+ const successNames = resolveSuccessNames(node, tsResolver)
26
+ const responseName = successNames.length > 0 ? successNames.join(' | ') : tsResolver.resolveResponseName(node)
27
+ const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : null
59
28
  const errorNames = node.responses.filter((r) => Number.parseInt(r.statusCode, 10) >= 400).map((r) => tsResolver.resolveResponseStatusName(node, r.statusCode))
60
29
  const TError = `ResponseErrorConfig<${errorNames.length > 0 ? errorNames.join(' | ') : 'Error'}>`
61
30
  return [responseName, TError, requestName || 'unknown'].filter(Boolean)
@@ -71,20 +40,23 @@ export function buildClassClientParams({
71
40
  baseURL,
72
41
  tsResolver,
73
42
  isFormData,
43
+ isMultipleContentTypes,
44
+ hasFormData,
74
45
  headers,
75
46
  }: {
76
47
  node: ast.OperationNode
77
48
  path: URLPath
78
- baseURL: string | undefined
49
+ baseURL: string | null | undefined
79
50
  tsResolver: ResolverTs
80
51
  isFormData: boolean
52
+ isMultipleContentTypes: boolean
53
+ hasFormData: boolean
81
54
  headers: Array<string>
82
55
  }) {
83
- const queryParamsName =
84
- node.parameters.filter((p) => p.in === 'query').length > 0
85
- ? tsResolver.resolveQueryParamsName(node, node.parameters.filter((p) => p.in === 'query')[0]!)
86
- : undefined
87
- const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : undefined
56
+ const { query: queryParams } = getOperationParameters(node)
57
+ const queryParamsName = queryParams.length > 0 ? tsResolver.resolveQueryParamsName(node, queryParams[0]!) : null
58
+ const requestName = node.requestBody?.content?.[0]?.schema ? tsResolver.resolveDataName(node) : null
59
+ const responseType = getResponseType(node)
88
60
 
89
61
  return createFunctionParams({
90
62
  config: {
@@ -94,7 +66,7 @@ export function buildClassClientParams({
94
66
  mode: 'inlineSpread',
95
67
  },
96
68
  method: {
97
- value: JSON.stringify(node.method.toUpperCase()),
69
+ value: JSON.stringify(ast.isHttpOperationNode(node) ? node.method.toUpperCase() : ''),
98
70
  },
99
71
  url: {
100
72
  value: path.template,
@@ -103,18 +75,25 @@ export function buildClassClientParams({
103
75
  ? {
104
76
  value: JSON.stringify(baseURL),
105
77
  }
106
- : undefined,
107
- params: queryParamsName ? {} : undefined,
78
+ : null,
79
+ params: queryParamsName ? {} : null,
108
80
  data: requestName
109
81
  ? {
110
- value: isFormData ? 'formData as FormData' : 'requestData',
82
+ value:
83
+ isMultipleContentTypes && hasFormData
84
+ ? "contentType === 'multipart/form-data' ? formData as FormData : requestData"
85
+ : isFormData
86
+ ? 'formData as FormData'
87
+ : 'requestData',
111
88
  }
112
- : undefined,
89
+ : null,
90
+ contentType: isMultipleContentTypes ? {} : null,
91
+ responseType: responseType ? { value: JSON.stringify(responseType) } : null,
113
92
  headers: headers.length
114
93
  ? {
115
94
  value: `{ ${headers.join(', ')}, ...requestConfig.headers }`,
116
95
  }
117
- : undefined,
96
+ : null,
118
97
  },
119
98
  },
120
99
  })
@@ -131,9 +110,9 @@ export function buildRequestDataLine({
131
110
  }: {
132
111
  parser: PluginClient['resolvedOptions']['parser'] | undefined
133
112
  node: ast.OperationNode
134
- zodResolver?: ResolverZod
113
+ zodResolver?: ResolverZod | null
135
114
  }): string {
136
- const zodRequestName = zodResolver && parser === 'zod' && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : undefined
115
+ const zodRequestName = zodResolver && parser === 'zod' && node.requestBody?.content?.[0]?.schema ? zodResolver.resolveDataName?.(node) : null
137
116
  if (parser === 'zod' && zodRequestName) {
138
117
  return `const requestData = ${zodRequestName}.parse(data)`
139
118
  }
@@ -164,16 +143,16 @@ export function buildReturnStatement({
164
143
  dataReturnType: PluginClient['resolvedOptions']['dataReturnType']
165
144
  parser: PluginClient['resolvedOptions']['parser'] | undefined
166
145
  node: ast.OperationNode
167
- zodResolver?: ResolverZod
146
+ zodResolver?: ResolverZod | null
168
147
  }): string {
169
- const zodResponseName = zodResolver && parser === 'zod' ? zodResolver.resolveResponseName?.(node) : undefined
148
+ const zodResponseName = zodResolver && parser === 'zod' ? zodResolver.resolveResponseName?.(node) : null
170
149
  if (dataReturnType === 'full' && parser === 'zod' && zodResponseName) {
171
150
  return `return {...res, data: ${zodResponseName}.parse(res.data)}`
172
151
  }
173
152
  if (dataReturnType === 'data' && parser === 'zod' && zodResponseName) {
174
153
  return `return ${zodResponseName}.parse(res.data)`
175
154
  }
176
- if (dataReturnType === 'full' && parser === 'client') {
155
+ if (dataReturnType === 'full' && parser !== 'zod') {
177
156
  return 'return res'
178
157
  }
179
158
  return 'return res.data'
@@ -1,73 +0,0 @@
1
- import type { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios'
2
- import axios from 'axios'
3
-
4
- declare const AXIOS_BASE: string
5
- declare const AXIOS_HEADERS: string
6
-
7
- /**
8
- * Subset of AxiosRequestConfig
9
- */
10
- export type RequestConfig<TData = unknown> = {
11
- baseURL?: string
12
- url?: string
13
- method?: 'GET' | 'PUT' | 'PATCH' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD'
14
- params?: unknown
15
- data?: TData | FormData
16
- responseType?: 'arraybuffer' | 'blob' | 'document' | 'json' | 'text' | 'stream'
17
- signal?: AbortSignal
18
- validateStatus?: (status: number) => boolean
19
- headers?: AxiosRequestConfig['headers']
20
- }
21
-
22
- /**
23
- * Subset of AxiosResponse
24
- */
25
- export type ResponseConfig<TData = unknown> = {
26
- data: TData
27
- status: number
28
- statusText: string
29
- headers: AxiosResponse['headers']
30
- }
31
-
32
- export type ResponseErrorConfig<TError = unknown> = AxiosError<TError>
33
-
34
- export type Client = <TData, _TError = unknown, TVariables = unknown>(config: RequestConfig<TVariables>, request?: unknown) => Promise<ResponseConfig<TData>>
35
-
36
- let _config: Partial<RequestConfig> = {
37
- baseURL: typeof AXIOS_BASE !== 'undefined' ? AXIOS_BASE : undefined,
38
- headers: typeof AXIOS_HEADERS !== 'undefined' ? JSON.parse(AXIOS_HEADERS) : undefined,
39
- }
40
-
41
- export const getConfig = () => _config
42
-
43
- export const setConfig = (config: RequestConfig) => {
44
- _config = config
45
- return getConfig()
46
- }
47
-
48
- export const mergeConfig = <T extends RequestConfig>(...configs: Array<Partial<T>>): Partial<T> => {
49
- return configs.reduce<Partial<T>>((merged, config) => {
50
- return {
51
- ...merged,
52
- ...config,
53
- headers: {
54
- ...merged.headers,
55
- ...config.headers,
56
- },
57
- }
58
- }, {})
59
- }
60
-
61
- export const axiosInstance = axios.create(getConfig())
62
-
63
- export const fetch = async <TData, TError = unknown, TVariables = unknown>(
64
- config: RequestConfig<TVariables>,
65
- _request?: unknown,
66
- ): Promise<ResponseConfig<TData>> => {
67
- return axiosInstance.request<TData, ResponseConfig<TData>>(mergeConfig(getConfig(), config)).catch((e: AxiosError<TError>) => {
68
- throw e
69
- })
70
- }
71
-
72
- fetch.getConfig = getConfig
73
- fetch.setConfig = setConfig
@@ -1,96 +0,0 @@
1
- /**
2
- * RequestCredentials
3
- */
4
- export type RequestCredentials = 'omit' | 'same-origin' | 'include'
5
-
6
- /**
7
- * Subset of FetchRequestConfig
8
- */
9
- export type RequestConfig<TData = unknown> = {
10
- baseURL?: string
11
- url?: string
12
- method?: 'GET' | 'PUT' | 'PATCH' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD'
13
- params?: unknown
14
- data?: TData | FormData
15
- responseType?: 'arraybuffer' | 'blob' | 'document' | 'json' | 'text' | 'stream'
16
- signal?: AbortSignal
17
- headers?: [string, string][] | Record<string, string>
18
- credentials?: RequestCredentials
19
- }
20
-
21
- /**
22
- * Subset of FetchResponse
23
- */
24
- export type ResponseConfig<TData = unknown> = {
25
- data: TData
26
- status: number
27
- statusText: string
28
- headers: Headers
29
- }
30
-
31
- let _config: Partial<RequestConfig> = {}
32
-
33
- export const getConfig = () => _config
34
-
35
- export const setConfig = (config: Partial<RequestConfig>) => {
36
- _config = config
37
- return getConfig()
38
- }
39
-
40
- export const mergeConfig = <T extends RequestConfig>(...configs: Array<Partial<T>>): Partial<T> => {
41
- return configs.reduce<Partial<T>>((merged, config) => {
42
- return {
43
- ...merged,
44
- ...config,
45
- headers: {
46
- ...(Array.isArray(merged.headers) ? Object.fromEntries(merged.headers) : merged.headers),
47
- ...(Array.isArray(config.headers) ? Object.fromEntries(config.headers) : config.headers),
48
- },
49
- }
50
- }, {})
51
- }
52
-
53
- export type ResponseErrorConfig<TError = unknown> = TError
54
-
55
- export type Client = <TData, _TError = unknown, TVariables = unknown>(config: RequestConfig<TVariables>, request?: unknown) => Promise<ResponseConfig<TData>>
56
-
57
- export const fetch = async <TData, _TError = unknown, TVariables = unknown>(
58
- paramsConfig: RequestConfig<TVariables>,
59
- _request?: unknown,
60
- ): Promise<ResponseConfig<TData>> => {
61
- const normalizedParams = new URLSearchParams()
62
-
63
- const config = mergeConfig(getConfig(), paramsConfig)
64
-
65
- Object.entries(config.params || {}).forEach(([key, value]) => {
66
- if (value !== undefined) {
67
- normalizedParams.append(key, value === null ? 'null' : value.toString())
68
- }
69
- })
70
-
71
- let targetUrl = [config.baseURL, config.url].filter(Boolean).join('')
72
-
73
- if (config.params) {
74
- targetUrl += `?${normalizedParams}`
75
- }
76
-
77
- const response = await globalThis.fetch(targetUrl, {
78
- credentials: config.credentials || 'same-origin',
79
- method: config.method?.toUpperCase(),
80
- body: config.data instanceof FormData ? config.data : JSON.stringify(config.data),
81
- signal: config.signal,
82
- headers: config.headers,
83
- })
84
-
85
- const data = [204, 205, 304].includes(response.status) || !response.body ? {} : await response.json()
86
-
87
- return {
88
- data: data as TData,
89
- status: response.status,
90
- statusText: response.statusText,
91
- headers: response.headers as Headers,
92
- }
93
- }
94
-
95
- fetch.getConfig = getConfig
96
- fetch.setConfig = setConfig
@@ -1,43 +0,0 @@
1
- export function buildFormData<T = unknown>(data: T): FormData {
2
- const formData = new FormData()
3
-
4
- function appendData(key: string, value: any) {
5
- if (value instanceof Blob) {
6
- formData.append(key, value)
7
- return
8
- }
9
- if (value instanceof Date) {
10
- formData.append(key, value.toISOString())
11
- return
12
- }
13
- if (typeof value === 'number' || typeof value === 'boolean') {
14
- formData.append(key, String(value))
15
- return
16
- }
17
- if (typeof value === 'string') {
18
- formData.append(key, value)
19
- return
20
- }
21
- if (typeof value === 'object') {
22
- formData.append(key, new Blob([JSON.stringify(value)], { type: 'application/json' }))
23
- return
24
- }
25
- }
26
-
27
- if (data) {
28
- Object.entries(data).forEach(([key, value]) => {
29
- if (value === undefined || value === null) return
30
-
31
- if (Array.isArray(value)) {
32
- for (const valueItem of value) {
33
- if (valueItem === undefined || valueItem === null) continue
34
- appendData(key, valueItem)
35
- }
36
- } else {
37
- appendData(key, value)
38
- }
39
- })
40
- }
41
-
42
- return formData
43
- }