@kubb/plugin-ts 5.0.0-beta.56 → 5.0.0-beta.73

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,274 +0,0 @@
1
- import { resolveContentTypeVariants } from '@internals/shared'
2
- import { ast, defineGenerator } from '@kubb/core'
3
- import { File, jsxRenderer } from '@kubb/renderer-jsx'
4
- import { Type } from '../components/Type.tsx'
5
- import { ENUM_TYPES_WITH_KEY_SUFFIX } from '../constants.ts'
6
- import { printerTs } from '../printers/printerTs.ts'
7
- import type { PluginTs } from '../types'
8
- import { buildData, buildResponses, buildResponseUnion } from '../utils.ts'
9
-
10
- /**
11
- * Built-in generator for `@kubb/plugin-ts`. Emits one TypeScript file per
12
- * schema in the spec plus per-operation request, response, and parameter
13
- * types. Drop-replace with a custom `Generator<PluginTs>` to change how
14
- * TypeScript output is produced.
15
- */
16
- export const typeGenerator = defineGenerator<PluginTs>({
17
- name: 'typescript',
18
- renderer: jsxRenderer,
19
- schema(node, ctx) {
20
- const { enum: enumOptions, syntaxType, optionalType, arrayType, output, group, printer } = ctx.options
21
- const { adapter, config, resolver, root } = ctx
22
-
23
- if (!node.name) {
24
- return
25
- }
26
- // Build a set of schema names that are enums so the ref handler and getImports
27
- // callback can use the suffixed type name (e.g. `StatusKey`) for those refs.
28
- const enumSchemaNames = new Set<string>(ctx.meta.enumNames)
29
-
30
- function resolveImportName(schemaName: string): string {
31
- if (ENUM_TYPES_WITH_KEY_SUFFIX.has(enumOptions.type) && enumOptions.typeSuffix && enumSchemaNames.has(schemaName)) {
32
- return resolver.resolveEnumKeyName({ name: schemaName }, enumOptions.typeSuffix)
33
- }
34
- return resolver.resolveTypeName(schemaName)
35
- }
36
-
37
- const imports = adapter.getImports(node, (schemaName) => ({
38
- name: resolveImportName(schemaName),
39
- path: resolver.resolveFile({ name: schemaName, extname: '.ts' }, { root, output, group: group ?? undefined }).path,
40
- }))
41
-
42
- const isEnumSchema = !!ast.narrowSchema(node, ast.schemaTypes.enum)
43
-
44
- const meta = {
45
- name:
46
- ENUM_TYPES_WITH_KEY_SUFFIX.has(enumOptions.type) && isEnumSchema
47
- ? resolver.resolveEnumKeyName(node, enumOptions.typeSuffix)
48
- : resolver.resolveTypeName(node.name),
49
- file: resolver.resolveFile({ name: node.name, extname: '.ts' }, { root, output, group: group ?? undefined }),
50
- } as const
51
-
52
- const schemaPrinter = printerTs({
53
- optionalType,
54
- arrayType,
55
- enum: enumOptions,
56
- name: meta.name,
57
- syntaxType,
58
- description: node.description,
59
- resolver,
60
- enumSchemaNames,
61
- nodes: printer?.nodes,
62
- })
63
-
64
- return (
65
- <File
66
- baseName={meta.file.baseName}
67
- path={meta.file.path}
68
- meta={meta.file.meta}
69
- banner={resolver.resolveBanner(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
70
- footer={resolver.resolveFooter(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
71
- >
72
- {imports.map((imp) => (
73
- <File.Import key={[node.name, imp.path, imp.isTypeOnly].join('-')} root={meta.file.path} path={imp.path} name={imp.name} isTypeOnly />
74
- ))}
75
- <Type name={meta.name} node={node} enum={enumOptions} resolver={resolver} printer={schemaPrinter} />
76
- </File>
77
- )
78
- },
79
- operation(node, ctx) {
80
- const { enum: enumOptions, optionalType, arrayType, syntaxType, paramsCasing, group, output, printer } = ctx.options
81
- const { adapter, config, resolver, root } = ctx
82
-
83
- const params = ast.caseParams(node.parameters, paramsCasing)
84
-
85
- const meta = {
86
- file: resolver.resolveFile(
87
- { name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path },
88
- { root, output, group: group ?? undefined },
89
- ),
90
- } as const
91
-
92
- // Build a set of schema names that are enums so the ref handler and getImports
93
- // callback can use the suffixed type name (e.g. `StatusKey`) for those refs.
94
- const enumSchemaNames = new Set<string>(ctx.meta.enumNames)
95
-
96
- function resolveImportName(schemaName: string): string {
97
- if (ENUM_TYPES_WITH_KEY_SUFFIX.has(enumOptions.type) && enumOptions.typeSuffix && enumSchemaNames.has(schemaName)) {
98
- return resolver.resolveEnumKeyName({ name: schemaName }, enumOptions.typeSuffix)
99
- }
100
- return resolver.resolveTypeName(schemaName)
101
- }
102
-
103
- function renderSchemaType({ schema, name, keysToOmit }: { schema: ast.SchemaNode | null; name: string; keysToOmit?: Array<string> | null }) {
104
- if (!schema) return null
105
-
106
- const imports = adapter.getImports(schema, (schemaName) => ({
107
- name: resolveImportName(schemaName),
108
- path: resolver.resolveFile({ name: schemaName, extname: '.ts' }, { root, output, group: group ?? undefined }).path,
109
- }))
110
-
111
- const schemaPrinter = printerTs({
112
- optionalType,
113
- arrayType,
114
- enum: enumOptions,
115
- name,
116
- syntaxType,
117
- description: schema.description,
118
- keysToOmit,
119
- resolver,
120
- enumSchemaNames,
121
- nodes: printer?.nodes,
122
- })
123
-
124
- return (
125
- <>
126
- {imports.map((imp) => (
127
- <File.Import key={[name, imp.path, imp.isTypeOnly].join('-')} root={meta.file.path} path={imp.path} name={imp.name} isTypeOnly />
128
- ))}
129
- <Type name={name} node={schema} enum={enumOptions} resolver={resolver} printer={schemaPrinter} />
130
- </>
131
- )
132
- }
133
-
134
- /**
135
- * Emits an individual type per content type plus a union alias under `baseName`.
136
- * Shared by the request body and multi-content-type responses.
137
- */
138
- function buildContentTypeVariants(
139
- entries: Array<{ contentType: string; schema?: ast.SchemaNode | null; keysToOmit?: Array<string> | null }>,
140
- baseName: string,
141
- decorate?: (schema: ast.SchemaNode) => ast.SchemaNode,
142
- ) {
143
- const variants = resolveContentTypeVariants(entries, baseName)
144
- const unionSchema = ast.createSchema({
145
- type: 'union',
146
- members: variants.map((variant) => ast.createSchema({ type: 'ref', name: variant.name })),
147
- })
148
- return (
149
- <>
150
- {variants.map((variant) =>
151
- renderSchemaType({
152
- schema: decorate ? decorate(variant.schema) : variant.schema,
153
- name: variant.name,
154
- keysToOmit: variant.keysToOmit,
155
- }),
156
- )}
157
- {renderSchemaType({ schema: unionSchema, name: baseName })}
158
- </>
159
- )
160
- }
161
-
162
- const paramTypes = params.map((param) =>
163
- renderSchemaType({
164
- schema: param.schema,
165
- name: resolver.resolveParamName(node, param),
166
- }),
167
- )
168
-
169
- const requestBodyContent = node.requestBody?.content ?? []
170
-
171
- function buildRequestType() {
172
- if (requestBodyContent.length === 0) return null
173
- if (requestBodyContent.length === 1) {
174
- const entry = requestBodyContent[0]!
175
- if (!entry.schema) return null
176
- return renderSchemaType({
177
- schema: {
178
- ...entry.schema,
179
- description: node.requestBody!.description ?? entry.schema.description,
180
- },
181
- name: resolver.resolveDataName(node),
182
- keysToOmit: entry.keysToOmit,
183
- })
184
- }
185
- // Multiple content types — generate individual types + union alias
186
- return buildContentTypeVariants(requestBodyContent, resolver.resolveDataName(node), (schema) => ({
187
- ...schema,
188
- description: node.requestBody!.description ?? schema.description,
189
- }))
190
- }
191
-
192
- const requestType = buildRequestType()
193
-
194
- const responseTypes = node.responses.map((res) => {
195
- const variants = (res.content ?? []).filter((entry) => entry.schema)
196
- // Multiple content types for a single status code — generate a union of the variants.
197
- if (variants.length > 1) {
198
- return buildContentTypeVariants(variants, resolver.resolveResponseStatusName(node, res.statusCode))
199
- }
200
- const primary = variants[0] ?? res.content?.[0]
201
- return renderSchemaType({
202
- schema: primary?.schema ?? null,
203
- name: resolver.resolveResponseStatusName(node, res.statusCode),
204
- keysToOmit: primary?.keysToOmit,
205
- })
206
- })
207
-
208
- const dataType = renderSchemaType({
209
- schema: buildData({ ...node, parameters: params }, { resolver }),
210
- name: resolver.resolveRequestConfigName(node),
211
- })
212
-
213
- const responsesType = renderSchemaType({
214
- schema: buildResponses(node, { resolver }),
215
- name: resolver.resolveResponsesName(node),
216
- })
217
-
218
- function buildResponseType() {
219
- const hasSchema = (res: ast.ResponseNode) => (res.content ?? []).some((entry) => entry.schema)
220
- if (!node.responses.some(hasSchema)) {
221
- return null
222
- }
223
-
224
- const responseName = resolver.resolveResponseName(node)
225
-
226
- const responsesWithSchema = node.responses.filter(hasSchema)
227
- const importedNames = new Set(
228
- responsesWithSchema.flatMap((res) =>
229
- (res.content ?? []).flatMap((entry) =>
230
- entry.schema
231
- ? adapter
232
- .getImports(entry.schema, (schemaName) => ({
233
- name: resolveImportName(schemaName),
234
- path: '',
235
- }))
236
- .flatMap((imp) => (Array.isArray(imp.name) ? imp.name : [imp.name]))
237
- : [],
238
- ),
239
- ),
240
- )
241
-
242
- if (importedNames.has(responseName)) {
243
- return null
244
- }
245
-
246
- return renderSchemaType({
247
- schema: {
248
- ...buildResponseUnion(node, { resolver })!,
249
- description: 'Union of all possible responses',
250
- },
251
- name: responseName,
252
- })
253
- }
254
-
255
- const responseType = buildResponseType()
256
-
257
- return (
258
- <File
259
- baseName={meta.file.baseName}
260
- path={meta.file.path}
261
- meta={meta.file.meta}
262
- banner={resolver.resolveBanner(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
263
- footer={resolver.resolveFooter(ctx.meta, { output, config, file: { path: meta.file.path, baseName: meta.file.baseName } })}
264
- >
265
- {paramTypes}
266
- {responseTypes}
267
- {requestType}
268
- {dataType}
269
- {responsesType}
270
- {responseType}
271
- </File>
272
- )
273
- },
274
- })
package/src/index.ts DELETED
@@ -1,9 +0,0 @@
1
- export { Enum } from './components/Enum.tsx'
2
- export { Type } from './components/Type.tsx'
3
- export { typeGenerator } from './generators/typeGenerator.tsx'
4
- export { default, pluginTs, pluginTsName } from './plugin.ts'
5
- export { functionPrinter } from './printers/functionPrinter.ts'
6
- export type { PrinterTsFactory, PrinterTsNodes, PrinterTsOptions } from './printers/printerTs.ts'
7
- export { printerTs } from './printers/printerTs.ts'
8
- export { resolverTs } from './resolvers/resolverTs.ts'
9
- export type { PluginTs, ResolverTs } from './types.ts'
package/src/plugin.ts DELETED
@@ -1,95 +0,0 @@
1
- import { createGroupConfig } from '@internals/shared'
2
- import { definePlugin } from '@kubb/core'
3
- import { typeGenerator } from './generators/typeGenerator.tsx'
4
- import { resolverTs } from './resolvers/resolverTs.ts'
5
- import type { PluginTs } from './types.ts'
6
-
7
- /**
8
- * Canonical plugin name for `@kubb/plugin-ts`. Used for driver lookups and
9
- * cross-plugin dependency references.
10
- */
11
- export const pluginTsName = 'plugin-ts' satisfies PluginTs['name']
12
-
13
- /**
14
- * Generates TypeScript `type` aliases and `interface` declarations from an
15
- * OpenAPI spec. The foundation that every other Kubb plugin builds on:
16
- * clients, query hooks, mocks, and validators all reference the names this
17
- * plugin produces.
18
- *
19
- * @example
20
- * ```ts
21
- * import { defineConfig } from 'kubb'
22
- * import { pluginTs } from '@kubb/plugin-ts'
23
- *
24
- * export default defineConfig({
25
- * input: { path: './petStore.yaml' },
26
- * output: { path: './src/gen' },
27
- * plugins: [
28
- * pluginTs({
29
- * output: { path: './types' },
30
- * enum: { type: 'asConst' },
31
- * optionalType: 'questionTokenAndUndefined',
32
- * }),
33
- * ],
34
- * })
35
- * ```
36
- */
37
- export const pluginTs = definePlugin<PluginTs>((options) => {
38
- const {
39
- output = { path: 'types', barrel: { type: 'named' } },
40
- group,
41
- exclude = [],
42
- include,
43
- override = [],
44
- enum: enumOptions = {},
45
- optionalType = 'questionToken',
46
- arrayType = 'array',
47
- syntaxType = 'type',
48
- paramsCasing,
49
- printer,
50
- resolver: userResolver,
51
- transformer: userTransformer,
52
- generators: userGenerators = [],
53
- } = options
54
-
55
- const groupConfig = createGroupConfig(group)
56
-
57
- const resolvedEnum = {
58
- type: enumOptions.type ?? 'asConst',
59
- constCasing: enumOptions.constCasing ?? 'camelCase',
60
- typeSuffix: enumOptions.typeSuffix ?? 'Key',
61
- keyCasing: enumOptions.keyCasing ?? 'none',
62
- }
63
-
64
- return {
65
- name: pluginTsName,
66
- options,
67
- hooks: {
68
- 'kubb:plugin:setup'(ctx) {
69
- ctx.setOptions({
70
- output,
71
- exclude,
72
- include,
73
- override,
74
- optionalType,
75
- group: groupConfig,
76
- arrayType,
77
- enum: resolvedEnum,
78
- syntaxType,
79
- paramsCasing,
80
- printer,
81
- })
82
- ctx.setResolver(userResolver ? { ...resolverTs, ...userResolver } : resolverTs)
83
- if (userTransformer) {
84
- ctx.setTransformer(userTransformer)
85
- }
86
- ctx.addGenerator(typeGenerator)
87
- for (const gen of userGenerators) {
88
- ctx.addGenerator(gen)
89
- }
90
- },
91
- },
92
- }
93
- })
94
-
95
- export default pluginTs
@@ -1,197 +0,0 @@
1
- import { ast } from '@kubb/core'
2
- import { PARAM_RANK } from '../constants.ts'
3
-
4
- /**
5
- * Maps each function-printer handler key to its concrete node type.
6
- */
7
- export type FunctionNodeByType = {
8
- functionParameter: ast.FunctionParameterNode
9
- parameterGroup: ast.ParameterGroupNode
10
- functionParameters: ast.FunctionParametersNode
11
- paramsType: ast.ParamsTypeNode
12
- }
13
-
14
- const kindToHandlerKey = {
15
- FunctionParameter: 'functionParameter',
16
- ParameterGroup: 'parameterGroup',
17
- FunctionParameters: 'functionParameters',
18
- ParamsType: 'paramsType',
19
- } satisfies Record<string, ast.FunctionNodeType>
20
-
21
- /**
22
- * Creates a function-parameter printer factory.
23
- *
24
- * Uses `createPrinterFactory` and dispatches handlers by `node.kind`
25
- * (for function nodes) rather than by `node.type` (for schema nodes).
26
- */
27
- export const defineFunctionPrinter = ast.createPrinterFactory<ast.FunctionParamNode, ast.FunctionNodeType, FunctionNodeByType>(
28
- (node) => kindToHandlerKey[node.kind],
29
- )
30
-
31
- export type FunctionPrinterOptions = {
32
- /**
33
- * Rendering modes supported by `functionPrinter`.
34
- *
35
- * | Mode | Output example | Use case |
36
- * |---------------|---------------------------------------------|---------------------------------|
37
- * | `declaration` | `id: string, config: Config = {}` | Function parameter declaration |
38
- * | `call` | `id, { method, url }` | Function call arguments |
39
- * | `keys` | `{ id, config }` | Key names only (destructuring) |
40
- * | `values` | `{ id: id, config: config }` | Key/value object entries |
41
- */
42
- mode: 'declaration' | 'call' | 'keys' | 'values'
43
- /**
44
- * Optional transformation applied to every parameter name before printing.
45
- */
46
- transformName?: (name: string) => string
47
- /**
48
- * Optional transformation applied to every type string before printing.
49
- */
50
- transformType?: (type: string) => string
51
- }
52
-
53
- type DefaultPrinter = ast.PrinterFactoryOptions<'functionParameters', FunctionPrinterOptions, string>
54
-
55
- function rank(param: ast.FunctionParameterNode | ast.ParameterGroupNode): number {
56
- if (param.kind === 'ParameterGroup') {
57
- if (param.default) return PARAM_RANK.withDefault
58
- const isOptional = param.optional ?? param.properties.every((p) => p.optional || p.default !== undefined)
59
- return isOptional ? PARAM_RANK.optional : PARAM_RANK.required
60
- }
61
- if (param.rest) return PARAM_RANK.rest
62
- if (param.default) return PARAM_RANK.withDefault
63
- return param.optional ? PARAM_RANK.optional : PARAM_RANK.required
64
- }
65
-
66
- function sortParams(params: ReadonlyArray<ast.FunctionParameterNode | ast.ParameterGroupNode>): Array<ast.FunctionParameterNode | ast.ParameterGroupNode> {
67
- return params.toSorted((a, b) => rank(a) - rank(b))
68
- }
69
-
70
- function sortChildParams(params: Array<ast.FunctionParameterNode>): Array<ast.FunctionParameterNode> {
71
- return params.toSorted((a, b) => rank(a) - rank(b))
72
- }
73
-
74
- /**
75
- * Default function-signature printer.
76
- * Covers the four standard output modes used across Kubb plugins.
77
- *
78
- * @example
79
- * ```ts
80
- * const printer = functionPrinter({ mode: 'declaration' })
81
- *
82
- * const sig = createFunctionParameters({
83
- * params: [
84
- * createFunctionParameter({ name: 'petId', type: 'string', optional: false }),
85
- * createFunctionParameter({ name: 'config', type: 'Config', optional: false, default: '{}' }),
86
- * ],
87
- * })
88
- *
89
- * printer.print(sig) // → "petId: string, config: Config = {}"
90
- * ```
91
- */
92
- export const functionPrinter = defineFunctionPrinter<DefaultPrinter>((options) => ({
93
- name: 'functionParameters',
94
- options,
95
- nodes: {
96
- paramsType(node) {
97
- if (node.kind !== 'ParamsType') return null
98
- if (node.variant === 'member') {
99
- return `${node.base}['${node.key}']`
100
- }
101
- if (node.variant === 'struct') {
102
- const parts = node.properties.map((p) => {
103
- const typeStr = this.transform(p.type)
104
- const key = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(p.name) ? p.name : JSON.stringify(p.name)
105
- return p.optional ? `${key}?: ${typeStr}` : `${key}: ${typeStr}`
106
- })
107
- return `{ ${parts.join('; ')} }`
108
- }
109
- if (node.variant === 'reference') {
110
- return node.name
111
- }
112
- return null
113
- },
114
- functionParameter(node) {
115
- const { mode, transformName, transformType } = this.options
116
- const name = transformName ? transformName(node.name) : node.name
117
-
118
- const rawType = node.type ? this.transform(node.type) : undefined
119
- const type = rawType != null && transformType ? transformType(rawType) : rawType
120
-
121
- if (mode === 'keys' || mode === 'values') {
122
- return node.rest ? `...${name}` : name
123
- }
124
-
125
- if (mode === 'call') {
126
- return node.rest ? `...${name}` : name
127
- }
128
-
129
- if (node.rest) {
130
- return type ? `...${name}: ${type}` : `...${name}`
131
- }
132
- if (type) {
133
- if (node.optional) return `${name}?: ${type}`
134
- return node.default ? `${name}: ${type} = ${node.default}` : `${name}: ${type}`
135
- }
136
- return node.default ? `${name} = ${node.default}` : name
137
- },
138
- parameterGroup(node) {
139
- const { mode, transformName, transformType } = this.options
140
- const sorted = sortChildParams(node.properties)
141
- const isOptional = node.optional ?? sorted.every((p) => p.optional || p.default !== undefined)
142
-
143
- if (node.inline) {
144
- return sorted
145
- .map((p) => this.transform(p))
146
- .filter(Boolean)
147
- .join(', ')
148
- }
149
-
150
- if (mode === 'keys' || mode === 'values') {
151
- const keys = sorted.map((p) => p.name).join(', ')
152
- return `{ ${keys} }`
153
- }
154
-
155
- if (mode === 'call') {
156
- const keys = sorted.map((p) => p.name).join(', ')
157
- return `{ ${keys} }`
158
- }
159
-
160
- const names = sorted.map((p) => {
161
- const n = transformName ? transformName(p.name) : p.name
162
-
163
- return n
164
- })
165
-
166
- const nameStr = names.length ? `{ ${names.join(', ')} }` : undefined
167
- if (!nameStr) return null
168
-
169
- let typeAnnotation: string | undefined = node.type ? (this.transform(node.type) ?? undefined) : undefined
170
- if (!typeAnnotation) {
171
- const typeParts = sorted
172
- .filter((p) => p.type)
173
- .map((p) => {
174
- const rawT = p.type ? this.transform(p.type) : undefined
175
- const t = rawT != null && transformType ? transformType(rawT) : rawT
176
- return p.optional || p.default !== undefined ? `${p.name}?: ${t}` : `${p.name}: ${t}`
177
- })
178
- typeAnnotation = typeParts.length ? `{ ${typeParts.join('; ')} }` : undefined
179
- }
180
-
181
- if (typeAnnotation) {
182
- if (isOptional) return `${nameStr}: ${typeAnnotation} = ${node.default ?? '{}'}`
183
- return node.default ? `${nameStr}: ${typeAnnotation} = ${node.default}` : `${nameStr}: ${typeAnnotation}`
184
- }
185
-
186
- return node.default ? `${nameStr} = ${node.default}` : nameStr
187
- },
188
- functionParameters(node) {
189
- const sorted = sortParams(node.params)
190
-
191
- return sorted
192
- .map((p) => this.transform(p))
193
- .filter(Boolean)
194
- .join(', ')
195
- },
196
- },
197
- }))