@kubb/plugin-client 5.12.0-canary.20261109T134631 → 6.0.0
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 +1 -1
- package/dist/client-Dq0z61hu.d.ts +554 -0
- package/dist/index.cjs +1396 -802
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +126 -74
- package/dist/index.d.ts +126 -74
- package/dist/index.js +1394 -804
- package/dist/index.js.map +1 -1
- package/dist/serializers-BRgfPGr1.d.ts +119 -0
- package/dist/{standardSchema-BShJWz2i.d.ts → standardSchema-CGuiSK7F.d.ts} +15 -37
- package/dist/templates/client.d.ts +3 -2
- package/dist/templates/core.d.ts +3 -3
- package/dist/templates/mediaTypes/formData.d.ts +5 -0
- package/dist/templates/mediaTypes/urlencoded.d.ts +5 -0
- package/dist/templates/mediaTypes.d.ts +2 -0
- package/dist/templates/serializers/cookies.d.ts +5 -0
- package/dist/templates/serializers/headers.d.ts +5 -0
- package/dist/templates/serializers/path.d.ts +5 -0
- package/dist/templates/serializers/pathStyles.d.ts +5 -0
- package/dist/templates/serializers/query.d.ts +5 -0
- package/dist/templates/serializers/queryStyles.d.ts +14 -0
- package/dist/templates/serializers.d.ts +2 -2
- package/dist/templates/sse.d.ts +20 -113
- package/dist/templates/standardSchema.d.ts +1 -1
- package/docs/guides/authentication.md +9 -1
- package/docs/guides/migrate-to-v6.md +139 -0
- package/docs/guides/validate-requests-and-responses.md +8 -3
- package/docs/guides/write-your-client.md +117 -11
- package/docs/options.md +24 -0
- package/docs/overview.md +1 -1
- package/kubb-extension.json +1 -1
- package/package.json +8 -7
- package/src/builders/callConfig.ts +80 -0
- package/src/builders/contentType.ts +35 -0
- package/src/builders/generics.ts +29 -0
- package/src/builders/operationBody.ts +43 -0
- package/src/builders/operationData.ts +64 -0
- package/src/builders/returnStatement.ts +103 -0
- package/src/builders/security.ts +125 -0
- package/src/builders/signature.ts +71 -0
- package/src/builders/styles.ts +94 -0
- package/src/builders/validator.ts +84 -0
- package/src/builders/validatorOptions.ts +53 -0
- package/src/clientTypes.ts +195 -0
- package/src/components/ClientRuntime.tsx +97 -0
- package/src/components/Operation.tsx +72 -0
- package/src/components/SdkClient.tsx +100 -0
- package/src/components/SdkFacade.tsx +35 -0
- package/src/generators/clientGenerator.tsx +91 -6
- package/src/generators/sdkGenerator.tsx +209 -0
- package/src/index.ts +3 -1
- package/src/macros.ts +9 -0
- package/src/plugin.ts +9 -9
- package/src/resolver.ts +28 -0
- package/src/setup.ts +90 -0
- package/src/types.ts +16 -2
- package/templates/client.ts +2 -1
- package/templates/core.ts +735 -286
- package/templates/mediaTypes/formData.ts +59 -0
- package/templates/mediaTypes/urlencoded.ts +33 -0
- package/templates/mediaTypes.ts +147 -0
- package/templates/serializers/cookies.ts +58 -0
- package/templates/serializers/headers.ts +35 -0
- package/templates/serializers/path.ts +19 -0
- package/templates/serializers/pathStyles.ts +27 -0
- package/templates/serializers/query.ts +21 -0
- package/templates/serializers/queryStyles.ts +107 -0
- package/templates/serializers.ts +48 -364
- package/templates/sse.ts +87 -178
- package/templates/standardSchema.ts +16 -37
- package/dist/client-BxO6Dygu.d.ts +0 -355
- package/dist/serializers-Ds8zZwOO.d.ts +0 -190
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
import { type OperationTypeSource, resolveOperationTypes, resultTypeName } from '@internals/client'
|
|
3
|
+
import { isEventStream } from '@internals/shared'
|
|
4
|
+
import { ensureValidVarName } from '@internals/utils'
|
|
5
|
+
import { ast, defineGenerator, Diagnostics } from '@kubb/kit'
|
|
6
|
+
import type { Generator } from '@kubb/kit'
|
|
7
|
+
import { File, jsxRenderer } from '@kubb/renderer-jsx'
|
|
8
|
+
import { type GeneratorContext, type OperationData, resolveOperationData } from '../builders/operationData.ts'
|
|
9
|
+
import { buildEventStreamImports } from '../builders/returnStatement.ts'
|
|
10
|
+
import { isValidatorEnabled } from '../builders/validatorOptions.ts'
|
|
11
|
+
import { SdkClient } from '../components/SdkClient.tsx'
|
|
12
|
+
import { ClientRuntime } from '../components/ClientRuntime.tsx'
|
|
13
|
+
import { SdkFacade } from '../components/SdkFacade.tsx'
|
|
14
|
+
import type { ContractClientFactory } from '../types.ts'
|
|
15
|
+
|
|
16
|
+
type Controller = {
|
|
17
|
+
name: string
|
|
18
|
+
tag: string | undefined
|
|
19
|
+
file: ast.FileNode
|
|
20
|
+
operations: Array<OperationData>
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Groups operations into one controller per tag. Operations without a tag fall back to a single
|
|
25
|
+
* `Client`/`ApiClient` controller, matching the resolver's default naming.
|
|
26
|
+
*/
|
|
27
|
+
function buildControllers(nodes: ReadonlyArray<ast.OperationNode>, ctx: GeneratorContext, types: OperationTypeSource): Array<Controller> {
|
|
28
|
+
const { resolver, root } = ctx
|
|
29
|
+
const { output, group } = ctx.options
|
|
30
|
+
|
|
31
|
+
return nodes.reduce((acc, operationNode) => {
|
|
32
|
+
if (!ast.http.isOperation(operationNode)) return acc
|
|
33
|
+
const tag = operationNode.tags[0]
|
|
34
|
+
const name = tag ? ensureValidVarName(resolver.groupName(tag)) : resolver.className('ApiClient')
|
|
35
|
+
// `group.name` names the folder and file, never the class.
|
|
36
|
+
const fileName = tag && group ? ensureValidVarName(path.basename(resolver.groupDir(group, tag))) : name
|
|
37
|
+
const file = resolver.file({ name: fileName, extname: '.ts', tag, root, output, group: group ?? undefined })
|
|
38
|
+
const operationData = resolveOperationData({ node: operationNode, ctx, types })
|
|
39
|
+
const previous = acc.find((item) => item.file.path === file.path)
|
|
40
|
+
|
|
41
|
+
if (previous) {
|
|
42
|
+
previous.operations.push(operationData)
|
|
43
|
+
} else {
|
|
44
|
+
acc.push({ name, tag, file, operations: [operationData] })
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return acc
|
|
48
|
+
}, [] as Array<Controller>)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function collectImportsByFile(ops: Array<OperationData>, pick: (op: OperationData) => { file: ast.FileNode | null; names: Array<string> }) {
|
|
52
|
+
const namesByPath = new Map<string, Set<string>>()
|
|
53
|
+
const filesByPath = new Map<string, ast.FileNode>()
|
|
54
|
+
|
|
55
|
+
ops.forEach((op) => {
|
|
56
|
+
const { file, names } = pick(op)
|
|
57
|
+
if (!file || names.length === 0) return
|
|
58
|
+
if (!namesByPath.has(file.path)) namesByPath.set(file.path, new Set())
|
|
59
|
+
const set = namesByPath.get(file.path)!
|
|
60
|
+
names.forEach((n) => set.add(n))
|
|
61
|
+
filesByPath.set(file.path, file)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
return { namesByPath, filesByPath }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Builds the class-based SDK generator for a client plugin (`@kubb/plugin-fetch`,
|
|
69
|
+
* `@kubb/plugin-axios`). Only registered when `sdk` is set; otherwise the plugin keeps its
|
|
70
|
+
* standalone per-operation functions.
|
|
71
|
+
*
|
|
72
|
+
* Every tag client is an instance class whose constructor takes a client config and builds its own
|
|
73
|
+
* client, or takes an existing client, so each environment is a separate instance. With `sdk.mode:
|
|
74
|
+
* 'tag'` (the default) it emits one class per tag, named by `resolver.groupName`, and, when `sdk.name`
|
|
75
|
+
* is set, a composed root that creates one client and shares it with every tag client. With
|
|
76
|
+
* `sdk.mode: 'flat'` it emits one class named by `sdk.name`, with every operation as a direct method.
|
|
77
|
+
*
|
|
78
|
+
* `templates` is the folder the optional runtime is copied from: `sse.ts` only for a document that streams, and each
|
|
79
|
+
* form media type only for a document with a body of that type.
|
|
80
|
+
*/
|
|
81
|
+
export function createSdkGenerator<TFactory extends ContractClientFactory>({ templates }: { templates?: string } = {}): Generator<TFactory> {
|
|
82
|
+
return defineGenerator<TFactory>({
|
|
83
|
+
name: 'sdk',
|
|
84
|
+
renderer: jsxRenderer,
|
|
85
|
+
operations(nodes, ctx) {
|
|
86
|
+
const { config, resolver, root } = ctx
|
|
87
|
+
const { output, group, validator, returnType, throwOnErrorDefault, sdk } = ctx.options
|
|
88
|
+
|
|
89
|
+
if (!sdk) return null
|
|
90
|
+
|
|
91
|
+
// `resolver.file(...)` resolves every tag to the same single `output.path` under `mode:
|
|
92
|
+
// 'file'`, so a tag-per-class split has nothing to split into: every tag would collapse
|
|
93
|
+
// into whichever controller `buildControllers` reduces first, silently dropping the rest.
|
|
94
|
+
if (sdk.mode === 'tag' && output.mode === 'file') {
|
|
95
|
+
throw new Diagnostics.Error({
|
|
96
|
+
code: Diagnostics.code.invalidPluginOptions,
|
|
97
|
+
severity: 'error',
|
|
98
|
+
message: `Plugin "${ctx.plugin.name}" resolves \`output.mode\` to 'file' but also configures \`sdk.mode: 'tag'\`.`,
|
|
99
|
+
help: "A single-file output has nothing to split tag classes into. Set `sdk.mode: 'flat'` to emit one class into that file, or give `output.path` an extensionless directory name (or set `output.mode: 'directory'` explicitly) so each tag gets its own file.",
|
|
100
|
+
location: { kind: 'config' },
|
|
101
|
+
plugin: ctx.plugin.name,
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const types = resolveOperationTypes(ctx.driver, ctx.plugin.name)
|
|
106
|
+
|
|
107
|
+
const validatorEnabled = isValidatorEnabled(validator)
|
|
108
|
+
const controllers = buildControllers(nodes, ctx, types)
|
|
109
|
+
const clientPath = path.resolve(root, '.kubb/client.ts')
|
|
110
|
+
const standardSchemaPath = path.resolve(root, '.kubb/standardSchema.ts')
|
|
111
|
+
|
|
112
|
+
const banner = (file: ast.FileNode) => resolver.default.banner(ctx.meta, { output, config, file: { path: file.path, baseName: file.baseName } })
|
|
113
|
+
const footer = (file: ast.FileNode) => resolver.default.footer(ctx.meta, { output, config, file: { path: file.path, baseName: file.baseName } })
|
|
114
|
+
|
|
115
|
+
const renderClassFile = (className: string, file: ast.FileNode, ops: Array<OperationData>) => {
|
|
116
|
+
const { namesByPath: typeNamesByPath, filesByPath: typeFilesByPath } = collectImportsByFile(ops, (op) => ({
|
|
117
|
+
file: op.typeFile,
|
|
118
|
+
names: [op.types.response.options(op.node), op.types.response.responses(op.node), ...op.headerTypeNames],
|
|
119
|
+
}))
|
|
120
|
+
const { namesByPath: zodNamesByPath, filesByPath: zodFilesByPath } = validatorEnabled
|
|
121
|
+
? collectImportsByFile(ops, (op) => ({ file: op.zodFile, names: op.validatorHooks.importedZodNames }))
|
|
122
|
+
: { namesByPath: new Map<string, Set<string>>(), filesByPath: new Map<string, ast.FileNode>() }
|
|
123
|
+
const resultOps = ops.filter((op) => !isEventStream(op.node))
|
|
124
|
+
const typedHeaders = (op: OperationData) => op.headerTypeNames.length > 0
|
|
125
|
+
const resultName = returnType === 'data' ? 'UnwrappedResult' : 'RequestResult'
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<File key={file.path} baseName={file.baseName} path={file.path} meta={file.meta} banner={banner(file)} footer={footer(file)}>
|
|
129
|
+
<File.Import
|
|
130
|
+
name={[
|
|
131
|
+
'createClient',
|
|
132
|
+
returnType === 'data' ? 'unwrapResult' : 'withUnwrap',
|
|
133
|
+
'resolveThrowOnError',
|
|
134
|
+
...(ctx.options.importPath === undefined && ops.some((op) => op.security) ? ['securitySchemes'] : []),
|
|
135
|
+
]}
|
|
136
|
+
root={file.path}
|
|
137
|
+
path={clientPath}
|
|
138
|
+
/>
|
|
139
|
+
<File.Import
|
|
140
|
+
name={Array.from(
|
|
141
|
+
new Set([
|
|
142
|
+
'ClientConfig',
|
|
143
|
+
'ClientInstance',
|
|
144
|
+
'Options',
|
|
145
|
+
...(returnType === 'data' ? [] : ['Unwrappable']),
|
|
146
|
+
...(resultOps.some((op) => !typedHeaders(op)) ? [resultName] : []),
|
|
147
|
+
...(resultOps.some(typedHeaders) ? [resultTypeName(resultName, true)] : []),
|
|
148
|
+
...ops.filter((op) => isEventStream(op.node)).flatMap((op) => buildEventStreamImports({ node: op.node, types: op.types, returnType })),
|
|
149
|
+
]),
|
|
150
|
+
)}
|
|
151
|
+
root={file.path}
|
|
152
|
+
path={clientPath}
|
|
153
|
+
isTypeOnly
|
|
154
|
+
/>
|
|
155
|
+
|
|
156
|
+
{validator === 'zod' && ops.some((op) => ast.http.requestBody.getSchema(op.node) != null) && <File.Import name={['z']} path="zod" isTypeOnly />}
|
|
157
|
+
|
|
158
|
+
{Array.from(typeNamesByPath.entries()).map(([filePath, set]) => (
|
|
159
|
+
<File.Import key={filePath} name={Array.from(set)} root={file.path} path={typeFilesByPath.get(filePath)!.path} isTypeOnly />
|
|
160
|
+
))}
|
|
161
|
+
|
|
162
|
+
{validatorEnabled &&
|
|
163
|
+
Array.from(zodNamesByPath.entries()).map(([filePath, set]) => (
|
|
164
|
+
<File.Import key={filePath} name={Array.from(set)} root={file.path} path={zodFilesByPath.get(filePath)!.path} />
|
|
165
|
+
))}
|
|
166
|
+
|
|
167
|
+
{ops.some((op) => op.validatorHooks.headers) && <File.Import name={['toHeadersStandardSchema']} root={file.path} path={standardSchemaPath} />}
|
|
168
|
+
|
|
169
|
+
<SdkClient name={className} operations={ops} returnType={returnType} throwOnErrorDefault={throwOnErrorDefault} />
|
|
170
|
+
</File>
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// `flat` collapses every operation into one class named by `sdk.name`, so callers reach
|
|
175
|
+
// an operation as `new PetStore(config).getPetById(...)` without a per-tag sub-client.
|
|
176
|
+
const renderFacadeFile = (name: string) => {
|
|
177
|
+
const sdkFile = resolver.file({ name, extname: '.ts', root, output, group: group ?? undefined })
|
|
178
|
+
const members = controllers.map(({ name, tag }) => ({ className: name, propName: resolver.propertyName(tag ?? name) }))
|
|
179
|
+
|
|
180
|
+
return (
|
|
181
|
+
<File key={sdkFile.path} baseName={sdkFile.baseName} path={sdkFile.path} meta={sdkFile.meta} banner={banner(sdkFile)} footer={footer(sdkFile)}>
|
|
182
|
+
<File.Import name={['createClient']} root={sdkFile.path} path={clientPath} />
|
|
183
|
+
<File.Import name={['ClientConfig', 'ClientInstance']} root={sdkFile.path} path={clientPath} isTypeOnly />
|
|
184
|
+
{controllers.map(({ name, file }) => (
|
|
185
|
+
<File.Import key={name} name={[name]} root={sdkFile.path} path={file.path} />
|
|
186
|
+
))}
|
|
187
|
+
<SdkFacade name={resolver.className(name)} members={members} />
|
|
188
|
+
</File>
|
|
189
|
+
)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const files =
|
|
193
|
+
sdk.mode === 'flat'
|
|
194
|
+
? renderClassFile(
|
|
195
|
+
resolver.className(sdk.name ?? 'sdk'),
|
|
196
|
+
resolver.file({ name: sdk.name ?? 'sdk', extname: '.ts', root, output, group: group ?? undefined }),
|
|
197
|
+
controllers.flatMap((controller) => controller.operations),
|
|
198
|
+
)
|
|
199
|
+
: [...controllers.map(({ name, file, operations: ops }) => renderClassFile(name, file, ops)), sdk.name && renderFacadeFile(sdk.name)]
|
|
200
|
+
|
|
201
|
+
return (
|
|
202
|
+
<>
|
|
203
|
+
<ClientRuntime nodes={nodes} root={root} templates={templates} baseURL={ctx.options.baseURL} servers={ctx.meta.servers} auth={ctx.meta.auth} />
|
|
204
|
+
{files}
|
|
205
|
+
</>
|
|
206
|
+
)
|
|
207
|
+
},
|
|
208
|
+
})
|
|
209
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
export { default, pluginClient, pluginClientName } from './plugin.ts'
|
|
2
|
-
export
|
|
2
|
+
export { resolverClient } from './resolver.ts'
|
|
3
|
+
export { clientDependencies, resolveClientOptions, setupClient } from './setup.ts'
|
|
4
|
+
export type { ClientOptions, ClientResolvedOptions, Options, PluginClient, ResolvedOptions, ResolverClient } from './types.ts'
|
package/src/macros.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { macroDedupeUnion } from '@kubb/kit'
|
|
2
|
+
import type { ast } from '@kubb/kit'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Macros the client plugins apply by default, ahead of any user macros. `macroDedupeUnion`
|
|
6
|
+
* drops repeated union members, keeping the generated response and error
|
|
7
|
+
* unions tidy. A plugin wires them with `ctx.setMacros([...defaultMacros, ...userMacros])`.
|
|
8
|
+
*/
|
|
9
|
+
export const defaultMacros: ReadonlyArray<ast.Macro> = [macroDedupeUnion]
|
package/src/plugin.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { createGroupConfig } from '@internals/shared'
|
|
2
1
|
import { addGenerator, defineExtension, Resolver, setMacros, setResolver } from '@kubb/kit'
|
|
3
2
|
import { compatibility } from '../kubb-extension.json'
|
|
4
3
|
import { version } from '../package.json'
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
4
|
+
import { createClientGenerator } from './generators/clientGenerator.tsx'
|
|
5
|
+
import { defaultMacros } from './macros.ts'
|
|
6
|
+
import { resolverClient } from './resolver.ts'
|
|
7
|
+
import { clientDependencies } from './setup.ts'
|
|
9
8
|
import type { PluginClient, ResolverClient } from './types.ts'
|
|
10
9
|
|
|
11
10
|
/**
|
|
@@ -26,7 +25,7 @@ export const pluginClientName = 'plugin-client' satisfies PluginClient['name']
|
|
|
26
25
|
export const pluginClient = defineExtension<PluginClient>({
|
|
27
26
|
kind: 'plugin',
|
|
28
27
|
meta: { name: pluginClientName, version, compatibility },
|
|
29
|
-
dependencies:
|
|
28
|
+
dependencies: clientDependencies,
|
|
30
29
|
setup(options) {
|
|
31
30
|
const {
|
|
32
31
|
output = { path: 'clients', barrel: { type: 'named' } },
|
|
@@ -36,6 +35,7 @@ export const pluginClient = defineExtension<PluginClient>({
|
|
|
36
35
|
importPath,
|
|
37
36
|
throwOnErrorDefault = true,
|
|
38
37
|
validator = false,
|
|
38
|
+
returnType = 'plain',
|
|
39
39
|
group,
|
|
40
40
|
resolver: userResolver,
|
|
41
41
|
} = options
|
|
@@ -44,19 +44,19 @@ export const pluginClient = defineExtension<PluginClient>({
|
|
|
44
44
|
|
|
45
45
|
setResolver(resolver)
|
|
46
46
|
setMacros([...defaultMacros, ...(options.macros ?? [])])
|
|
47
|
-
addGenerator(
|
|
47
|
+
addGenerator(createClientGenerator<PluginClient>('client'))
|
|
48
48
|
|
|
49
49
|
return {
|
|
50
50
|
output,
|
|
51
51
|
exclude,
|
|
52
52
|
include,
|
|
53
53
|
override,
|
|
54
|
-
group:
|
|
54
|
+
group: group ?? null,
|
|
55
55
|
importPath,
|
|
56
56
|
baseURL: undefined,
|
|
57
57
|
throwOnErrorDefault,
|
|
58
58
|
validator,
|
|
59
|
-
returnType
|
|
59
|
+
returnType,
|
|
60
60
|
sdk: undefined,
|
|
61
61
|
resolver,
|
|
62
62
|
}
|
package/src/resolver.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { resolveOperationName } from '@internals/shared'
|
|
2
|
+
import { ensureValidVarName } from '@internals/utils'
|
|
3
|
+
import { casing, createResolver } from '@kubb/kit'
|
|
4
|
+
import type { PluginContractClient, ResolverClient } from './types.ts'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Default resolver shared by the client plugins. Functions use safe camelCase identifiers and
|
|
8
|
+
* files inherit the built-in casing; classes and tag groups use PascalCase.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* resolverClient.name('show pet by id') // 'showPetById'
|
|
13
|
+
* resolverClient.groupName('pet') // 'PetClient'
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export const resolverClient: ResolverClient = createResolver<PluginContractClient>({
|
|
17
|
+
pluginName: 'plugin-contract-client',
|
|
18
|
+
name: resolveOperationName,
|
|
19
|
+
className(name) {
|
|
20
|
+
return ensureValidVarName(casing.pascal(name))
|
|
21
|
+
},
|
|
22
|
+
groupName(name) {
|
|
23
|
+
return ensureValidVarName(casing.pascal(`${name} Client`))
|
|
24
|
+
},
|
|
25
|
+
propertyName(name) {
|
|
26
|
+
return ensureValidVarName(casing.camel(name))
|
|
27
|
+
},
|
|
28
|
+
})
|
package/src/setup.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
import { fileURLToPath } from 'node:url'
|
|
3
|
+
import { addGenerator, injectFile, Resolver, setMacros, setResolver, useConfig } from '@kubb/kit'
|
|
4
|
+
import { pluginTsName } from '@kubb/plugin-ts'
|
|
5
|
+
import { pluginZodName } from '@kubb/plugin-zod'
|
|
6
|
+
import { isValidatorEnabled } from './builders/validatorOptions.ts'
|
|
7
|
+
import { createClientGenerator } from './generators/clientGenerator.tsx'
|
|
8
|
+
import { createSdkGenerator } from './generators/sdkGenerator.tsx'
|
|
9
|
+
import { defaultMacros } from './macros.ts'
|
|
10
|
+
import { resolverClient } from './resolver.ts'
|
|
11
|
+
import type { ClientOptions, ClientResolvedOptions, ContractClientFactory, ResolverClient } from './types.ts'
|
|
12
|
+
|
|
13
|
+
// Resolved from a file directly in `src/`, so the same `../templates/` path works from `dist/`.
|
|
14
|
+
const templatePath = (name: string) => fileURLToPath(new URL(`../templates/${name}`, import.meta.url))
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Applies the client option defaults and merges a user `resolver` over the built-in one.
|
|
18
|
+
*/
|
|
19
|
+
export function resolveClientOptions(options: ClientOptions): ClientResolvedOptions {
|
|
20
|
+
const {
|
|
21
|
+
output = { path: 'clients', barrel: { type: 'named' } },
|
|
22
|
+
exclude = [],
|
|
23
|
+
include,
|
|
24
|
+
override = [],
|
|
25
|
+
baseURL,
|
|
26
|
+
throwOnErrorDefault = true,
|
|
27
|
+
validator = false,
|
|
28
|
+
returnType = 'full',
|
|
29
|
+
group,
|
|
30
|
+
sdk,
|
|
31
|
+
resolver: userResolver,
|
|
32
|
+
} = options
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
output,
|
|
36
|
+
exclude,
|
|
37
|
+
include,
|
|
38
|
+
override,
|
|
39
|
+
group: group ?? null,
|
|
40
|
+
baseURL,
|
|
41
|
+
throwOnErrorDefault,
|
|
42
|
+
validator,
|
|
43
|
+
returnType,
|
|
44
|
+
sdk: sdk ? { mode: sdk.mode ?? 'tag', name: sdk.name } : undefined,
|
|
45
|
+
resolver: userResolver ? Resolver.merge<ResolverClient>(resolverClient, userResolver) : resolverClient,
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Wires a client plugin in its `setup`: resolver, macros, client or SDK generator, and the `.kubb/` runtime with the plugin's own `client.ts`.
|
|
51
|
+
*/
|
|
52
|
+
export function setupClient({
|
|
53
|
+
name,
|
|
54
|
+
resolved,
|
|
55
|
+
macros = [],
|
|
56
|
+
client,
|
|
57
|
+
}: {
|
|
58
|
+
name: string
|
|
59
|
+
resolved: ClientResolvedOptions
|
|
60
|
+
macros?: ClientOptions['macros']
|
|
61
|
+
client: string
|
|
62
|
+
}): void {
|
|
63
|
+
const config = useConfig()
|
|
64
|
+
|
|
65
|
+
setResolver(resolved.resolver)
|
|
66
|
+
setMacros([...defaultMacros, ...macros])
|
|
67
|
+
|
|
68
|
+
const templates = templatePath('')
|
|
69
|
+
addGenerator<ContractClientFactory>(resolved.sdk ? createSdkGenerator({ templates }) : createClientGenerator(name, { templates }))
|
|
70
|
+
|
|
71
|
+
const root = path.resolve(config.root, config.output.path)
|
|
72
|
+
const files = [
|
|
73
|
+
['serializers.ts', templatePath('serializers.ts')],
|
|
74
|
+
['mediaTypes.ts', templatePath('mediaTypes.ts')],
|
|
75
|
+
['core.ts', templatePath('core.ts')],
|
|
76
|
+
['client.ts', client],
|
|
77
|
+
['standardSchema.ts', templatePath('standardSchema.ts')],
|
|
78
|
+
] as const
|
|
79
|
+
|
|
80
|
+
for (const [baseName, copy] of files) {
|
|
81
|
+
injectFile({ baseName, path: path.resolve(root, '.kubb', baseName), copy })
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Declares `plugin-ts` as optional and `plugin-zod` as required once `validator` enables a direction.
|
|
87
|
+
*/
|
|
88
|
+
export function clientDependencies(options: Pick<ClientOptions, 'validator'>) {
|
|
89
|
+
return { [pluginTsName]: { optional: true }, [pluginZodName]: { optional: !isValidatorEnabled(options.validator) } }
|
|
90
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { Override, PluginType } from '@kubb/kit'
|
|
2
|
-
import type {
|
|
2
|
+
import type { ReturnTypeOption } from '@internals/client'
|
|
3
|
+
import type { ClientOptions, ClientResolvedOptions, ResolverClient } from './clientTypes.ts'
|
|
3
4
|
|
|
4
|
-
export type
|
|
5
|
+
export type * from './clientTypes.ts'
|
|
6
|
+
export type { ReturnTypeOption } from '@internals/client'
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Omits keys from every member of a union, where the built-in `Omit` would collapse the union into one shape.
|
|
@@ -18,6 +20,18 @@ type DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K>
|
|
|
18
20
|
* ```
|
|
19
21
|
*/
|
|
20
22
|
export type Options = DistributiveOmit<ClientOptions, 'sdk' | 'returnType' | 'baseURL' | 'override'> & {
|
|
23
|
+
/**
|
|
24
|
+
* Shape of the value a generated operation resolves to. `'full'` and `'data'` import `withUnwrap` or `unwrapResult`
|
|
25
|
+
* and their types from your `importPath` module, so that module must export them.
|
|
26
|
+
*
|
|
27
|
+
* @default 'plain'
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* pluginClient({ importPath: '../../client', returnType: 'full' })
|
|
31
|
+
* // await getPetById({ path: { petId: 1 } }).unwrap()
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
returnType?: ReturnTypeOption
|
|
21
35
|
/**
|
|
22
36
|
* Applies different options to operations that match a pattern.
|
|
23
37
|
* `sdk`, `returnType`, and `baseURL` are not available, because this plugin generates standalone operations and your client module owns the base URL.
|
package/templates/client.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Stands in for the transport's `client.ts` next to `sse.ts`, so the shared templates typecheck on their own. In the
|
|
2
2
|
// generated `.kubb/` folder, `sse.ts` reads these from the real client; this file is never copied.
|
|
3
|
-
export type
|
|
3
|
+
export type * from './core.ts'
|
|
4
|
+
export type NativeOptions = Record<string, unknown>
|
|
4
5
|
export type NativeRequest = unknown
|
|
5
6
|
export type NativeResponse = unknown
|