@kubb/plugin-ts 5.0.0-beta.10 → 5.0.0-beta.100

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,296 +0,0 @@
1
- import { ast, defineGenerator } from '@kubb/core'
2
- import { File, jsxRenderer } from '@kubb/renderer-jsx'
3
- import { Type } from '../components/Type.tsx'
4
- import { ENUM_TYPES_WITH_KEY_SUFFIX } from '../constants.ts'
5
- import { printerTs } from '../printers/printerTs.ts'
6
- import type { PluginTs } from '../types'
7
- import { buildData, buildResponses, buildResponseUnion } from '../utils.ts'
8
-
9
- function getContentTypeSuffix(contentType: string): string {
10
- const baseType = contentType.split(';')[0]!.trim()
11
- if (baseType === 'application/json') return 'Json'
12
- if (baseType === 'multipart/form-data') return 'FormData'
13
- if (baseType === 'application/x-www-form-urlencoded') return 'FormUrlEncoded'
14
- const subtype = baseType.split('/').pop() ?? baseType
15
- const parts = subtype.split(/[^a-zA-Z0-9]+/).filter(Boolean)
16
- if (parts.length === 0) return 'Unknown'
17
- return parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join('')
18
- }
19
-
20
- function getPerContentTypeName(dataName: string, suffix: string): string {
21
- if (dataName.endsWith('Data')) {
22
- return suffix.endsWith('Data') ? dataName.slice(0, -4) + suffix : `${dataName.slice(0, -4)}${suffix}Data`
23
- }
24
- return dataName + suffix
25
- }
26
-
27
- export const typeGenerator = defineGenerator<PluginTs>({
28
- name: 'typescript',
29
- renderer: jsxRenderer,
30
- schema(node, ctx) {
31
- const { enumType, enumTypeSuffix, enumKeyCasing, syntaxType, optionalType, arrayType, output, group, printer } = ctx.options
32
- const { adapter, config, resolver, root } = ctx
33
-
34
- if (!node.name) {
35
- return
36
- }
37
- const mode = ctx.getMode(output)
38
- // Build a set of schema names that are enums so the ref handler and getImports
39
- // callback can use the suffixed type name (e.g. `StatusKey`) for those refs.
40
- const enumSchemaNames = new Set((adapter.inputNode?.schemas ?? []).filter((s) => ast.narrowSchema(s, ast.schemaTypes.enum) && s.name).map((s) => s.name!))
41
-
42
- function resolveImportName(schemaName: string): string {
43
- if (ENUM_TYPES_WITH_KEY_SUFFIX.has(enumType) && enumTypeSuffix && enumSchemaNames.has(schemaName)) {
44
- return resolver.resolveEnumKeyName({ name: schemaName }, enumTypeSuffix)
45
- }
46
- return resolver.resolveTypeName(schemaName)
47
- }
48
-
49
- const imports = adapter.getImports(node, (schemaName) => ({
50
- name: resolveImportName(schemaName),
51
- path: resolver.resolveFile({ name: schemaName, extname: '.ts' }, { root, output, group }).path,
52
- }))
53
-
54
- const isEnumSchema = !!ast.narrowSchema(node, ast.schemaTypes.enum)
55
-
56
- const meta = {
57
- name: ENUM_TYPES_WITH_KEY_SUFFIX.has(enumType) && isEnumSchema ? resolver.resolveEnumKeyName(node, enumTypeSuffix) : resolver.resolveTypeName(node.name),
58
- file: resolver.resolveFile({ name: node.name, extname: '.ts' }, { root, output, group }),
59
- } as const
60
-
61
- const schemaPrinter = printerTs({
62
- optionalType,
63
- arrayType,
64
- enumType,
65
- enumTypeSuffix,
66
- name: meta.name,
67
- syntaxType,
68
- description: node.description,
69
- resolver,
70
- enumSchemaNames,
71
- nodes: printer?.nodes,
72
- })
73
-
74
- return (
75
- <File
76
- baseName={meta.file.baseName}
77
- path={meta.file.path}
78
- meta={meta.file.meta}
79
- banner={resolver.resolveBanner(adapter.inputNode, { output, config })}
80
- footer={resolver.resolveFooter(adapter.inputNode, { output, config })}
81
- >
82
- {mode === 'split' &&
83
- imports.map((imp) => (
84
- <File.Import key={[node.name, imp.path, imp.isTypeOnly].join('-')} root={meta.file.path} path={imp.path} name={imp.name} isTypeOnly />
85
- ))}
86
- <Type
87
- name={meta.name}
88
- node={node}
89
- enumType={enumType}
90
- enumTypeSuffix={enumTypeSuffix}
91
- enumKeyCasing={enumKeyCasing}
92
- resolver={resolver}
93
- printer={schemaPrinter}
94
- />
95
- </File>
96
- )
97
- },
98
- operation(node, ctx) {
99
- const { enumType, enumTypeSuffix, enumKeyCasing, optionalType, arrayType, syntaxType, paramsCasing, group, output, printer } = ctx.options
100
- const { adapter, config, resolver, root } = ctx
101
-
102
- const mode = ctx.getMode(output)
103
-
104
- const params = ast.caseParams(node.parameters, paramsCasing)
105
-
106
- const meta = {
107
- file: resolver.resolveFile({ name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path }, { root, output, group }),
108
- } as const
109
-
110
- // Build a set of schema names that are enums so the ref handler and getImports
111
- // callback can use the suffixed type name (e.g. `StatusKey`) for those refs.
112
- const enumSchemaNames = new Set((adapter.inputNode?.schemas ?? []).filter((s) => ast.narrowSchema(s, ast.schemaTypes.enum) && s.name).map((s) => s.name!))
113
-
114
- function resolveImportName(schemaName: string): string {
115
- if (ENUM_TYPES_WITH_KEY_SUFFIX.has(enumType) && enumTypeSuffix && enumSchemaNames.has(schemaName)) {
116
- return resolver.resolveEnumKeyName({ name: schemaName }, enumTypeSuffix)
117
- }
118
- return resolver.resolveTypeName(schemaName)
119
- }
120
-
121
- function renderSchemaType({ schema, name, keysToOmit }: { schema: ast.SchemaNode | null; name: string; keysToOmit?: Array<string> }) {
122
- if (!schema) return null
123
-
124
- const imports = adapter.getImports(schema, (schemaName) => ({
125
- name: resolveImportName(schemaName),
126
- path: resolver.resolveFile({ name: schemaName, extname: '.ts' }, { root, output, group }).path,
127
- }))
128
-
129
- const schemaPrinter = printerTs({
130
- optionalType,
131
- arrayType,
132
- enumType,
133
- enumTypeSuffix,
134
- name,
135
- syntaxType,
136
- description: schema.description,
137
- keysToOmit,
138
- resolver,
139
- enumSchemaNames,
140
- nodes: printer?.nodes,
141
- })
142
-
143
- return (
144
- <>
145
- {mode === 'split' &&
146
- imports.map((imp) => (
147
- <File.Import key={[name, imp.path, imp.isTypeOnly].join('-')} root={meta.file.path} path={imp.path} name={imp.name} isTypeOnly />
148
- ))}
149
- <Type
150
- name={name}
151
- node={schema}
152
- enumType={enumType}
153
- enumTypeSuffix={enumTypeSuffix}
154
- enumKeyCasing={enumKeyCasing}
155
- resolver={resolver}
156
- printer={schemaPrinter}
157
- />
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
- const dataName = resolver.resolveDataName(node)
187
- const usedNames = new Set<string>()
188
- const individualItems = requestBodyContent
189
- .filter((entry) => entry.schema)
190
- .map((entry) => {
191
- const baseSuffix = getContentTypeSuffix(entry.contentType)
192
- let individualName = getPerContentTypeName(dataName, baseSuffix)
193
- let counter = 2
194
- while (usedNames.has(individualName)) {
195
- individualName = getPerContentTypeName(dataName, `${baseSuffix}${counter++}`)
196
- }
197
- usedNames.add(individualName)
198
- return {
199
- name: individualName,
200
- rendered: renderSchemaType({
201
- schema: {
202
- ...entry.schema!,
203
- description: node.requestBody!.description ?? entry.schema!.description,
204
- },
205
- name: individualName,
206
- keysToOmit: entry.keysToOmit,
207
- }),
208
- }
209
- })
210
- const unionSchema = ast.createSchema({
211
- type: 'union',
212
- members: individualItems.map((item) => ast.createSchema({ type: 'ref', name: item.name })),
213
- })
214
- const unionType = renderSchemaType({ schema: unionSchema, name: dataName })
215
- return (
216
- <>
217
- {individualItems.map((item) => item.rendered)}
218
- {unionType}
219
- </>
220
- )
221
- }
222
-
223
- const requestType = buildRequestType()
224
-
225
- const responseTypes = node.responses.map((res) =>
226
- renderSchemaType({
227
- schema: res.schema,
228
- name: resolver.resolveResponseStatusName(node, res.statusCode),
229
- keysToOmit: res.keysToOmit,
230
- }),
231
- )
232
-
233
- const dataType = renderSchemaType({
234
- schema: buildData({ ...node, parameters: params }, { resolver }),
235
- name: resolver.resolveRequestConfigName(node),
236
- })
237
-
238
- const responsesType = renderSchemaType({
239
- schema: buildResponses(node, { resolver }),
240
- name: resolver.resolveResponsesName(node),
241
- })
242
-
243
- function buildResponseType() {
244
- if (!node.responses.some((res) => res.schema)) {
245
- return null
246
- }
247
-
248
- const responseName = resolver.resolveResponseName(node)
249
-
250
- const responsesWithSchema = node.responses.filter((res) => res.schema)
251
- const importedNames = new Set(
252
- responsesWithSchema.flatMap((res) =>
253
- res.schema
254
- ? adapter
255
- .getImports(res.schema, (schemaName) => ({
256
- name: resolveImportName(schemaName),
257
- path: '',
258
- }))
259
- .flatMap((imp) => (Array.isArray(imp.name) ? imp.name : [imp.name]))
260
- : [],
261
- ),
262
- )
263
-
264
- if (importedNames.has(responseName)) {
265
- return null
266
- }
267
-
268
- return renderSchemaType({
269
- schema: {
270
- ...buildResponseUnion(node, { resolver })!,
271
- description: 'Union of all possible responses',
272
- },
273
- name: responseName,
274
- })
275
- }
276
-
277
- const responseType = buildResponseType()
278
-
279
- return (
280
- <File
281
- baseName={meta.file.baseName}
282
- path={meta.file.path}
283
- meta={meta.file.meta}
284
- banner={resolver.resolveBanner(adapter.inputNode, { output, config })}
285
- footer={resolver.resolveFooter(adapter.inputNode, { output, config })}
286
- >
287
- {paramTypes}
288
- {responseTypes}
289
- {requestType}
290
- {dataType}
291
- {responsesType}
292
- {responseType}
293
- </File>
294
- )
295
- },
296
- })
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,93 +0,0 @@
1
- import { camelCase } from '@internals/utils'
2
- import { definePlugin, type Group } 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 to identify the plugin in driver lookups and warnings.
9
- */
10
- export const pluginTsName = 'plugin-ts' satisfies PluginTs['name']
11
-
12
- /**
13
- * The `@kubb/plugin-ts` plugin factory.
14
- *
15
- * Generates TypeScript type declarations from an OpenAPI/AST `RootNode`.
16
- * Walks schemas and operations, delegates rendering to the active generators,
17
- * and writes barrel files based on `output.barrelType`.
18
- *
19
- * @example
20
- * ```ts
21
- * import pluginTs from '@kubb/plugin-ts'
22
- *
23
- * export default defineConfig({
24
- * plugins: [pluginTs({ output: { path: 'types' }, enumType: 'asConst' })],
25
- * })
26
- * ```
27
- */
28
- export const pluginTs = definePlugin<PluginTs>((options) => {
29
- const {
30
- output = { path: 'types', barrelType: 'named' },
31
- group,
32
- exclude = [],
33
- include,
34
- override = [],
35
- enumType = 'asConst',
36
- enumTypeSuffix = 'Key',
37
- enumKeyCasing = 'none',
38
- optionalType = 'questionToken',
39
- arrayType = 'array',
40
- syntaxType = 'type',
41
- paramsCasing,
42
- printer,
43
- resolver: userResolver,
44
- transformer: userTransformer,
45
- generators: userGenerators = [],
46
- } = options
47
-
48
- const groupConfig = group
49
- ? ({
50
- ...group,
51
- name: (ctx) => {
52
- if (group.type === 'path') {
53
- return `${ctx.group.split('/')[1]}`
54
- }
55
- return `${camelCase(ctx.group)}Controller`
56
- },
57
- } satisfies Group)
58
- : undefined
59
-
60
- return {
61
- name: pluginTsName,
62
- options,
63
- hooks: {
64
- 'kubb:plugin:setup'(ctx) {
65
- ctx.setOptions({
66
- output,
67
- exclude,
68
- include,
69
- override,
70
- optionalType,
71
- group: groupConfig,
72
- arrayType,
73
- enumType,
74
- enumTypeSuffix,
75
- enumKeyCasing,
76
- syntaxType,
77
- paramsCasing,
78
- printer,
79
- })
80
- ctx.setResolver(userResolver ? { ...resolverTs, ...userResolver } : resolverTs)
81
- if (userTransformer) {
82
- ctx.setTransformer(userTransformer)
83
- }
84
- ctx.addGenerator(typeGenerator)
85
- for (const gen of userGenerators) {
86
- ctx.addGenerator(gen)
87
- }
88
- },
89
- },
90
- }
91
- })
92
-
93
- 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].sort((a, b) => rank(a) - rank(b))
68
- }
69
-
70
- function sortChildParams(params: Array<ast.FunctionParameterNode>): Array<ast.FunctionParameterNode> {
71
- return [...params].sort((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
- }))