@kubb/plugin-client 5.0.0-beta.56 → 5.0.0-beta.64

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.
@@ -1,261 +0,0 @@
1
- import path from 'node:path'
2
- import { getOperationParameters, operationFileEntry, resolveOperationTypeNames } from '@internals/shared'
3
- import { camelCase } from '@internals/utils'
4
- import { ast, defineGenerator } from '@kubb/core'
5
- import type { ResolverTs } from '@kubb/plugin-ts'
6
- import { pluginTsName } from '@kubb/plugin-ts'
7
- import type { ResolverZod } from '@kubb/plugin-zod'
8
- import { pluginZodName } from '@kubb/plugin-zod'
9
- import { File, jsxRenderer } from '@kubb/renderer-jsx'
10
- import { ClassClient } from '../components/ClassClient'
11
- import { WrapperClient } from '../components/WrapperClient'
12
- import type { PluginClient } from '../types'
13
- import { isParserEnabled, resolveQueryParamsParser, resolveRequestParser, resolveResponseParser } from '../utils.ts'
14
-
15
- type OperationData = {
16
- node: ast.OperationNode
17
- name: string
18
- tsResolver: ResolverTs
19
- zodResolver: ResolverZod | null
20
- typeFile: ast.FileNode
21
- zodFile: ast.FileNode | null
22
- }
23
-
24
- type Controller = {
25
- name: string
26
- propertyName: string
27
- file: ast.FileNode
28
- operations: Array<OperationData>
29
- }
30
-
31
- function resolveTypeImportNames(node: ast.OperationNode, tsResolver: ResolverTs): Array<string> {
32
- return resolveOperationTypeNames(node, tsResolver, { order: 'body-response-first' })
33
- }
34
-
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,
41
- ]
42
- return names.filter((n): n is string => Boolean(n))
43
- }
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
- */
50
- export const classClientGenerator = defineGenerator<PluginClient>({
51
- name: 'classClient',
52
- renderer: jsxRenderer,
53
- operations(nodes, ctx) {
54
- const { config, driver, resolver, root } = ctx
55
- const { output, group, dataReturnType, paramsCasing, paramsType, pathParamsType, parser, importPath, sdk } = ctx.options
56
- const baseURL = ctx.options.baseURL ?? ctx.meta.baseURL
57
-
58
- const pluginTs = driver.getPlugin(pluginTsName)
59
- if (!pluginTs) return null
60
-
61
- const tsResolver = driver.getResolver(pluginTsName)
62
- const tsPluginOptions = pluginTs.options
63
- const pluginZod = isParserEnabled(parser) ? driver.getPlugin(pluginZodName) : null
64
- const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null
65
-
66
- function buildOperationData(node: ast.OperationNode): OperationData {
67
- const typeFile = tsResolver.resolveFile(operationFileEntry(node, node.operationId), {
68
- root,
69
- output: tsPluginOptions?.output ?? output,
70
- group: tsPluginOptions?.group,
71
- })
72
- const zodFile =
73
- zodResolver && pluginZod?.options
74
- ? zodResolver.resolveFile(operationFileEntry(node, node.operationId), {
75
- root,
76
- output: pluginZod.options?.output ?? output,
77
- group: pluginZod.options?.group ?? undefined,
78
- })
79
- : null
80
-
81
- return {
82
- node: node,
83
- name: resolver.resolveName(node.operationId),
84
- tsResolver,
85
- zodResolver,
86
- typeFile,
87
- zodFile,
88
- }
89
- }
90
-
91
- const controllers = nodes.reduce((acc, operationNode) => {
92
- if (!ast.isHttpOperationNode(operationNode)) return acc
93
- const tag = operationNode.tags[0]
94
- const groupName = tag ? (group?.name?.({ group: camelCase(tag) }) ?? resolver.resolveGroupName(tag)) : resolver.resolveClassName('Client')
95
-
96
- if (!tag && !group) {
97
- const name = resolver.resolveClassName('ApiClient')
98
- const file = resolver.resolveFile({ name, extname: '.ts' }, { root, output, group: group ?? undefined })
99
- const operationData = buildOperationData(operationNode)
100
- const previous = acc.find((item) => item.file.path === file.path)
101
-
102
- if (previous) {
103
- previous.operations.push(operationData)
104
- } else {
105
- acc.push({ name, propertyName: resolver.resolveClientPropertyName(name), file, operations: [operationData] })
106
- }
107
- return acc
108
- }
109
-
110
- if (tag) {
111
- const name = groupName
112
- const file = resolver.resolveFile({ name, extname: '.ts', tag }, { root, output, group: group ?? undefined })
113
- const operationData = buildOperationData(operationNode)
114
- const previous = acc.find((item) => item.file.path === file.path)
115
-
116
- if (previous) {
117
- previous.operations.push(operationData)
118
- } else {
119
- acc.push({ name, propertyName: resolver.resolveClientPropertyName(name), file, operations: [operationData] })
120
- }
121
- }
122
-
123
- return acc
124
- }, [] as Array<Controller>)
125
-
126
- function collectTypeImports(ops: Array<OperationData>) {
127
- const typeImportsByFile = new Map<string, Set<string>>()
128
- const typeFilesByPath = new Map<string, ast.FileNode>()
129
-
130
- ops.forEach((op) => {
131
- const names = resolveTypeImportNames(op.node, tsResolver)
132
- if (!typeImportsByFile.has(op.typeFile.path)) {
133
- typeImportsByFile.set(op.typeFile.path, new Set())
134
- }
135
- const imports = typeImportsByFile.get(op.typeFile.path)!
136
- names.forEach((n) => {
137
- imports.add(n)
138
- })
139
- typeFilesByPath.set(op.typeFile.path, op.typeFile)
140
- })
141
-
142
- return { typeImportsByFile, typeFilesByPath }
143
- }
144
-
145
- function collectZodImports(ops: Array<OperationData>) {
146
- const zodImportsByFile = new Map<string, Set<string>>()
147
- const zodFilesByPath = new Map<string, ast.FileNode>()
148
-
149
- ops.forEach((op) => {
150
- if (!op.zodFile || !zodResolver) return
151
- const names = resolveZodImportNames(op.node, zodResolver, parser)
152
- if (!zodImportsByFile.has(op.zodFile.path)) {
153
- zodImportsByFile.set(op.zodFile.path, new Set())
154
- }
155
- const imports = zodImportsByFile.get(op.zodFile.path)!
156
- names.forEach((n) => {
157
- imports.add(n)
158
- })
159
- zodFilesByPath.set(op.zodFile.path, op.zodFile)
160
- })
161
-
162
- return { zodImportsByFile, zodFilesByPath }
163
- }
164
-
165
- const files = controllers.map(({ name, file, operations: ops }) => {
166
- const { typeImportsByFile, typeFilesByPath } = collectTypeImports(ops)
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)
171
-
172
- return (
173
- <File
174
- key={file.path}
175
- baseName={file.baseName}
176
- path={file.path}
177
- meta={file.meta}
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 } })}
180
- >
181
- {importPath ? (
182
- <>
183
- <File.Import name={'client'} path={importPath} />
184
- <File.Import name={['mergeConfig']} path={importPath} />
185
- <File.Import name={['Client', 'RequestConfig', 'ResponseErrorConfig']} path={importPath} isTypeOnly />
186
- </>
187
- ) : (
188
- <>
189
- <File.Import name={['client']} root={file.path} path={path.resolve(root, '.kubb/client.ts')} />
190
- <File.Import name={['mergeConfig']} root={file.path} path={path.resolve(root, '.kubb/client.ts')} />
191
- <File.Import name={['Client', 'RequestConfig', 'ResponseErrorConfig']} root={file.path} path={path.resolve(root, '.kubb/client.ts')} isTypeOnly />
192
- </>
193
- )}
194
-
195
- {hasFormData && <File.Import name={['buildFormData']} root={file.path} path={path.resolve(root, '.kubb/config.ts')} />}
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
-
201
- {Array.from(typeImportsByFile.entries()).map(([filePath, importSet]) => {
202
- const typeFile = typeFilesByPath.get(filePath)
203
- if (!typeFile) return null
204
- const importNames = Array.from(importSet).filter(Boolean)
205
- if (importNames.length === 0) return null
206
- return <File.Import key={filePath} name={importNames} root={file.path} path={typeFile.path} isTypeOnly />
207
- })}
208
-
209
- {isParserEnabled(parser) &&
210
- Array.from(zodImportsByFile.entries()).map(([filePath, importSet]) => {
211
- const zodFile = zodFilesByPath.get(filePath)
212
- if (!zodFile) return null
213
- const importNames = Array.from(importSet).filter(Boolean)
214
- if (importNames.length === 0) return null
215
- return <File.Import key={filePath} name={importNames} root={file.path} path={zodFile.path} />
216
- })}
217
-
218
- <ClassClient
219
- name={name}
220
- operations={ops}
221
- baseURL={baseURL}
222
- dataReturnType={dataReturnType}
223
- pathParamsType={pathParamsType}
224
- paramsCasing={paramsCasing}
225
- paramsType={paramsType}
226
- parser={parser}
227
- />
228
- </File>
229
- )
230
- })
231
-
232
- if (sdk) {
233
- const sdkFile = resolver.resolveFile({ name: sdk.className, extname: '.ts' }, { root, output, group: group ?? undefined })
234
-
235
- files.push(
236
- <File
237
- key={sdkFile.path}
238
- baseName={sdkFile.baseName}
239
- path={sdkFile.path}
240
- meta={sdkFile.meta}
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 } })}
243
- >
244
- {importPath ? (
245
- <File.Import name={['Client', 'RequestConfig']} path={importPath} isTypeOnly />
246
- ) : (
247
- <File.Import name={['Client', 'RequestConfig']} root={sdkFile.path} path={path.resolve(root, '.kubb/client.ts')} isTypeOnly />
248
- )}
249
-
250
- {controllers.map(({ name, file }) => (
251
- <File.Import key={name} name={[name]} root={sdkFile.path} path={file.path} />
252
- ))}
253
-
254
- <WrapperClient name={sdk.className} controllers={controllers.map(({ name, propertyName }) => ({ className: name, propertyName }))} />
255
- </File>,
256
- )
257
- }
258
-
259
- return <>{files}</>
260
- },
261
- })
@@ -1,135 +0,0 @@
1
- import path from 'node:path'
2
- import { getOperationParameters, operationFileEntry, resolveOperationTypeNames } from '@internals/shared'
3
- import { ast, defineGenerator } from '@kubb/core'
4
- import { pluginTsName } from '@kubb/plugin-ts'
5
- import { pluginZodName } from '@kubb/plugin-zod'
6
- import { File, jsxRenderer } from '@kubb/renderer-jsx'
7
- import { Client } from '../components/Client'
8
- import { Url } from '../components/Url.tsx'
9
- import type { PluginClient } from '../types'
10
- import { isParserEnabled, resolveQueryParamsParser, resolveRequestParser, resolveResponseParser } from '../utils.ts'
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
- */
17
- export const clientGenerator = defineGenerator<PluginClient>({
18
- name: 'client',
19
- renderer: jsxRenderer,
20
- operation(node, ctx) {
21
- if (!ast.isHttpOperationNode(node)) return null
22
- const { config, driver, resolver, root } = ctx
23
- const { output, urlType, dataReturnType, paramsCasing, paramsType, pathParamsType, parser, importPath, group } = ctx.options
24
- const baseURL = ctx.options.baseURL ?? ctx.meta.baseURL
25
-
26
- const pluginTs = driver.getPlugin(pluginTsName)
27
-
28
- if (!pluginTs) {
29
- return null
30
- }
31
-
32
- const tsResolver = driver.getResolver(pluginTsName)
33
-
34
- const pluginZod = isParserEnabled(parser) ? driver.getPlugin(pluginZodName) : null
35
- const zodResolver = pluginZod ? driver.getResolver(pluginZodName) : null
36
-
37
- const importedTypeNames = resolveOperationTypeNames(node, tsResolver, { paramsCasing })
38
-
39
- const { query: queryParams } = getOperationParameters(node)
40
-
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
50
-
51
- const meta = {
52
- name: resolver.resolveName(node.operationId),
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
- fileZod:
61
- zodResolver && pluginZod?.options
62
- ? zodResolver.resolveFile(operationFileEntry(node, node.operationId), {
63
- root,
64
- output: pluginZod.options.output ?? output,
65
- group: pluginZod.options?.group ?? undefined,
66
- })
67
- : null,
68
- } as const
69
-
70
- const hasFormData = node.requestBody?.content?.some((e) => e.contentType === 'multipart/form-data') ?? false
71
-
72
- return (
73
- <File
74
- baseName={meta.file.baseName}
75
- path={meta.file.path}
76
- meta={meta.file.meta}
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 } })}
79
- >
80
- {importPath ? (
81
- <>
82
- <File.Import name={'client'} path={importPath} />
83
- <File.Import name={['Client', 'RequestConfig', 'ResponseErrorConfig']} path={importPath} isTypeOnly />
84
- </>
85
- ) : (
86
- <>
87
- <File.Import name={['client']} root={meta.file.path} path={path.resolve(root, '.kubb/client.ts')} />
88
- <File.Import
89
- name={['Client', 'RequestConfig', 'ResponseErrorConfig']}
90
- root={meta.file.path}
91
- path={path.resolve(root, '.kubb/client.ts')}
92
- isTypeOnly
93
- />
94
- </>
95
- )}
96
-
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 />}
100
-
101
- {meta.fileZod && importedZodNames.length > 0 && <File.Import name={importedZodNames as Array<string>} root={meta.file.path} path={meta.fileZod.path} />}
102
-
103
- {meta.fileTs && importedTypeNames.length > 0 && (
104
- <File.Import name={Array.from(new Set(importedTypeNames))} root={meta.file.path} path={meta.fileTs.path} isTypeOnly />
105
- )}
106
-
107
- <Url
108
- name={meta.urlName}
109
- baseURL={baseURL}
110
- pathParamsType={pathParamsType}
111
- paramsCasing={paramsCasing}
112
- paramsType={paramsType}
113
- node={node}
114
- tsResolver={tsResolver}
115
- isIndexable={urlType === 'export'}
116
- isExportable={urlType === 'export'}
117
- />
118
-
119
- <Client
120
- name={meta.name}
121
- urlName={meta.urlName}
122
- baseURL={baseURL}
123
- dataReturnType={dataReturnType}
124
- pathParamsType={pathParamsType}
125
- paramsCasing={paramsCasing}
126
- paramsType={paramsType}
127
- node={node}
128
- tsResolver={tsResolver}
129
- zodResolver={zodResolver}
130
- parser={parser}
131
- />
132
- </File>
133
- )
134
- },
135
- })
@@ -1,82 +0,0 @@
1
- import { camelCase } from '@internals/utils'
2
- import type { ast } from '@kubb/core'
3
- import { defineGenerator } from '@kubb/core'
4
- import { File, Function, jsxRenderer } from '@kubb/renderer-jsx'
5
- import type { PluginClient } from '../types'
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
- */
13
- export const groupedClientGenerator = defineGenerator<PluginClient>({
14
- name: 'groupedClient',
15
- renderer: jsxRenderer,
16
- operations(nodes, ctx) {
17
- const { config, resolver, root } = ctx
18
- const { output, group } = ctx.options
19
-
20
- const controllers = nodes.reduce(
21
- (acc, operationNode) => {
22
- if (group?.type === 'tag') {
23
- const tag = operationNode.tags[0]
24
- const name = tag ? group?.name?.({ group: camelCase(tag) }) : null
25
-
26
- if (!tag || !name) {
27
- return acc
28
- }
29
-
30
- const file = resolver.resolveFile({ name, extname: '.ts', tag }, { root, output, group: group ?? undefined })
31
- const clientFile = resolver.resolveFile(
32
- { name: operationNode.operationId, extname: '.ts', tag: operationNode.tags[0] ?? 'default', path: operationNode.path },
33
- { root, output, group: group ?? undefined },
34
- )
35
-
36
- const client = {
37
- name: resolver.resolveName(operationNode.operationId),
38
- file: clientFile,
39
- }
40
-
41
- const previous = acc.find((item) => item.file.path === file.path)
42
-
43
- if (previous) {
44
- previous.clients.push(client)
45
- } else {
46
- acc.push({ name, file, clients: [client] })
47
- }
48
- }
49
-
50
- return acc
51
- },
52
- [] as Array<{ name: string; file: ast.FileNode; clients: Array<{ name: string; file: ast.FileNode }> }>,
53
- )
54
-
55
- return (
56
- <>
57
- {controllers.map(({ name, file, clients }) => {
58
- return (
59
- <File
60
- key={file.path}
61
- baseName={file.baseName}
62
- path={file.path}
63
- meta={file.meta}
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 } })}
66
- >
67
- {clients.map((client) => (
68
- <File.Import key={client.name} name={[client.name]} root={file.path} path={client.file.path} />
69
- ))}
70
-
71
- <File.Source name={name} isExportable isIndexable>
72
- <Function export name={name}>
73
- {`return { ${clients.map((client) => client.name).join(', ')} }`}
74
- </Function>
75
- </File.Source>
76
- </File>
77
- )
78
- })}
79
- </>
80
- )
81
- },
82
- })
@@ -1,47 +0,0 @@
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
- })