@kubb/plugin-ts 5.0.0-alpha.4 → 5.0.0-alpha.5
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/dist/{components-CRjwjdyE.js → components-Cwn1rflQ.js} +9 -3
- package/dist/components-Cwn1rflQ.js.map +1 -0
- package/dist/{components-DI0aTIBg.cjs → components-CxTvawXI.cjs} +9 -3
- package/dist/components-CxTvawXI.cjs.map +1 -0
- package/dist/components.cjs +1 -1
- package/dist/components.d.ts +1 -1
- package/dist/components.js +1 -1
- package/dist/generators.cjs +1 -1
- package/dist/generators.d.ts +2 -3
- package/dist/generators.js +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/{plugin-Bgm8TNUt.js → plugin-kdQ5D2cW.js} +255 -70
- package/dist/plugin-kdQ5D2cW.js.map +1 -0
- package/dist/{plugin-DvK-Uhvv.cjs → plugin-meWNDVe7.cjs} +252 -67
- package/dist/plugin-meWNDVe7.cjs.map +1 -0
- package/dist/{types-aotMcdUB.d.ts → types-eWhVEVgF.d.ts} +11 -1
- package/package.json +8 -8
- package/src/components/v2/Type.tsx +15 -2
- package/src/factory.ts +12 -16
- package/src/generators/v2/typeGenerator.tsx +74 -107
- package/src/generators/v2/utils.ts +145 -0
- package/src/plugin.ts +11 -0
- package/src/printer.ts +15 -1
- package/src/types.ts +10 -0
- package/dist/components-CRjwjdyE.js.map +0 -1
- package/dist/components-DI0aTIBg.cjs.map +0 -1
- package/dist/plugin-Bgm8TNUt.js.map +0 -1
- package/dist/plugin-DvK-Uhvv.cjs.map +0 -1
|
@@ -65,6 +65,8 @@ type Options = {
|
|
|
65
65
|
/**
|
|
66
66
|
* Set a suffix for the generated enums.
|
|
67
67
|
* @default 'enum'
|
|
68
|
+
* @deprecated Set `enumSuffix` on the adapter (`adapterOas({ enumSuffix })`) instead.
|
|
69
|
+
* In v5, the adapter owns this decision at parse time; the plugin option is ignored.
|
|
68
70
|
*/
|
|
69
71
|
enumSuffix?: string;
|
|
70
72
|
/**
|
|
@@ -72,6 +74,8 @@ type Options = {
|
|
|
72
74
|
* - 'string' represents dates as string values.
|
|
73
75
|
* - 'date' represents dates as JavaScript Date objects.
|
|
74
76
|
* @default 'string'
|
|
77
|
+
* @deprecated Set `dateType` on the adapter (`adapterOas({ dateType })`) instead.
|
|
78
|
+
* In v5, the adapter owns this decision at parse time; the plugin option is ignored.
|
|
75
79
|
*/
|
|
76
80
|
dateType?: 'string' | 'date';
|
|
77
81
|
/**
|
|
@@ -80,6 +84,8 @@ type Options = {
|
|
|
80
84
|
* - 'bigint' uses the TypeScript `bigint` type (accurate for values exceeding Number.MAX_SAFE_INTEGER).
|
|
81
85
|
* @note in v5 of Kubb 'bigint' will become the default to better align with OpenAPI's int64 specification.
|
|
82
86
|
* @default 'number'
|
|
87
|
+
* @deprecated Set `integerType` on the adapter (`adapterOas({ integerType })`) instead.
|
|
88
|
+
* In v5, the adapter owns this decision at parse time; the plugin option is ignored.
|
|
83
89
|
*/
|
|
84
90
|
integerType?: 'number' | 'bigint';
|
|
85
91
|
/**
|
|
@@ -88,6 +94,8 @@ type Options = {
|
|
|
88
94
|
* - 'unknown' requires type narrowing before use.
|
|
89
95
|
* - 'void' represents no value.
|
|
90
96
|
* @default 'any'
|
|
97
|
+
* @deprecated Set `unknownType` on the adapter (`adapterOas({ unknownType })`) instead.
|
|
98
|
+
* In v5, the adapter owns this decision at parse time; the plugin option is ignored.
|
|
91
99
|
*/
|
|
92
100
|
unknownType?: 'any' | 'unknown' | 'void';
|
|
93
101
|
/**
|
|
@@ -96,6 +104,8 @@ type Options = {
|
|
|
96
104
|
* - 'unknown' requires type narrowing before use.
|
|
97
105
|
* - 'void' represents no value.
|
|
98
106
|
* @default `unknownType`
|
|
107
|
+
* @deprecated Set `emptySchemaType` on the adapter (`adapterOas({ emptySchemaType })`) instead.
|
|
108
|
+
* In v5, the adapter owns this decision at parse time; the plugin option is ignored.
|
|
99
109
|
*/
|
|
100
110
|
emptySchemaType?: 'any' | 'unknown' | 'void';
|
|
101
111
|
/**
|
|
@@ -167,4 +177,4 @@ type ResolvedOptions = {
|
|
|
167
177
|
type PluginTs = PluginFactoryOptions<'plugin-ts', Options, ResolvedOptions, never, ResolvePathOptions>;
|
|
168
178
|
//#endregion
|
|
169
179
|
export { PluginTs as n, Options as t };
|
|
170
|
-
//# sourceMappingURL=types-
|
|
180
|
+
//# sourceMappingURL=types-eWhVEVgF.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubb/plugin-ts",
|
|
3
|
-
"version": "5.0.0-alpha.
|
|
3
|
+
"version": "5.0.0-alpha.5",
|
|
4
4
|
"description": "TypeScript code generation plugin for Kubb, transforming OpenAPI schemas into TypeScript interfaces, types, and utility functions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
@@ -67,17 +67,17 @@
|
|
|
67
67
|
}
|
|
68
68
|
],
|
|
69
69
|
"dependencies": {
|
|
70
|
-
"@kubb/fabric-core": "0.
|
|
71
|
-
"@kubb/react-fabric": "0.
|
|
70
|
+
"@kubb/fabric-core": "0.14.0",
|
|
71
|
+
"@kubb/react-fabric": "0.14.0",
|
|
72
72
|
"remeda": "^2.33.6",
|
|
73
73
|
"typescript": "5.9.3",
|
|
74
|
-
"@kubb/ast": "5.0.0-alpha.
|
|
75
|
-
"@kubb/core": "5.0.0-alpha.
|
|
76
|
-
"@kubb/oas": "5.0.0-alpha.
|
|
77
|
-
"@kubb/
|
|
74
|
+
"@kubb/ast": "5.0.0-alpha.5",
|
|
75
|
+
"@kubb/core": "5.0.0-alpha.5",
|
|
76
|
+
"@kubb/plugin-oas": "5.0.0-alpha.5",
|
|
77
|
+
"@kubb/oas": "5.0.0-alpha.5"
|
|
78
78
|
},
|
|
79
79
|
"peerDependencies": {
|
|
80
|
-
"@kubb/react-fabric": "0.
|
|
80
|
+
"@kubb/react-fabric": "0.14.0"
|
|
81
81
|
},
|
|
82
82
|
"engines": {
|
|
83
83
|
"node": ">=22"
|
|
@@ -18,11 +18,24 @@ type Props = {
|
|
|
18
18
|
enumType: PluginTs['resolvedOptions']['enumType']
|
|
19
19
|
enumKeyCasing: PluginTs['resolvedOptions']['enumKeyCasing']
|
|
20
20
|
syntaxType: PluginTs['resolvedOptions']['syntaxType']
|
|
21
|
+
mapper?: PluginTs['resolvedOptions']['mapper']
|
|
21
22
|
description?: string
|
|
22
23
|
keysToOmit?: string[]
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
export function Type({
|
|
26
|
+
export function Type({
|
|
27
|
+
name,
|
|
28
|
+
typedName,
|
|
29
|
+
node,
|
|
30
|
+
keysToOmit,
|
|
31
|
+
optionalType,
|
|
32
|
+
arrayType,
|
|
33
|
+
syntaxType,
|
|
34
|
+
enumType,
|
|
35
|
+
enumKeyCasing,
|
|
36
|
+
mapper,
|
|
37
|
+
...rest
|
|
38
|
+
}: Props): FabricReactNode {
|
|
26
39
|
const typeNodes: ts.Node[] = []
|
|
27
40
|
|
|
28
41
|
const description = rest.description || node?.description
|
|
@@ -32,7 +45,7 @@ export function Type({ name, typedName, node, keysToOmit, optionalType, arrayTyp
|
|
|
32
45
|
},
|
|
33
46
|
})
|
|
34
47
|
|
|
35
|
-
const printer = printerTs({ optionalType, arrayType, enumType })
|
|
48
|
+
const printer = printerTs({ optionalType, arrayType, enumType, mapper })
|
|
36
49
|
let type = printer.print(node)
|
|
37
50
|
|
|
38
51
|
if (!type) {
|
package/src/factory.ts
CHANGED
|
@@ -662,43 +662,39 @@ export const keywordTypeNodes = {
|
|
|
662
662
|
* Converts a path like '/pet/{petId}/uploadImage' to a template literal type
|
|
663
663
|
* like `/pet/${string}/uploadImage`
|
|
664
664
|
*/
|
|
665
|
+
/**
|
|
666
|
+
* Converts an OAS-style path (e.g. `/pets/{petId}`) or an Express-style path
|
|
667
|
+
* (e.g. `/pets/:petId`) to a TypeScript template literal type
|
|
668
|
+
* like `` `/pets/${string}` ``.
|
|
669
|
+
*/
|
|
665
670
|
export function createUrlTemplateType(path: string): ts.TypeNode {
|
|
666
|
-
//
|
|
667
|
-
|
|
668
|
-
return factory.createLiteralTypeNode(factory.createStringLiteral(path))
|
|
669
|
-
}
|
|
671
|
+
// normalized Express `:param` → OAS `{param}` so a single regex handles both.
|
|
672
|
+
const normalized = path.replace(/:([^/]+)/g, '{$1}')
|
|
670
673
|
|
|
671
|
-
|
|
672
|
-
|
|
674
|
+
if (!normalized.includes('{')) {
|
|
675
|
+
return factory.createLiteralTypeNode(factory.createStringLiteral(normalized))
|
|
676
|
+
}
|
|
673
677
|
|
|
674
|
-
|
|
678
|
+
const segments = normalized.split(/(\{[^}]+\})/)
|
|
675
679
|
const parts: string[] = []
|
|
676
680
|
const parameterIndices: number[] = []
|
|
677
681
|
|
|
678
682
|
segments.forEach((segment) => {
|
|
679
683
|
if (segment.startsWith('{') && segment.endsWith('}')) {
|
|
680
|
-
// This is a parameter placeholder
|
|
681
684
|
parameterIndices.push(parts.length)
|
|
682
|
-
parts.push(segment)
|
|
685
|
+
parts.push(segment)
|
|
683
686
|
} else if (segment) {
|
|
684
|
-
// This is a static part
|
|
685
687
|
parts.push(segment)
|
|
686
688
|
}
|
|
687
689
|
})
|
|
688
690
|
|
|
689
|
-
// Build template literal type
|
|
690
|
-
// Template literal structure: head + templateSpans[]
|
|
691
|
-
// For '/pet/{petId}/upload': head = '/pet/', spans = [{ type: string, literal: '/upload' }]
|
|
692
|
-
|
|
693
691
|
const head = ts.factory.createTemplateHead(parts[0] || '')
|
|
694
692
|
const templateSpans: ts.TemplateLiteralTypeSpan[] = []
|
|
695
693
|
|
|
696
694
|
parameterIndices.forEach((paramIndex, i) => {
|
|
697
695
|
const isLast = i === parameterIndices.length - 1
|
|
698
696
|
const nextPart = parts[paramIndex + 1] || ''
|
|
699
|
-
|
|
700
697
|
const literal = isLast ? ts.factory.createTemplateTail(nextPart) : ts.factory.createTemplateMiddle(nextPart)
|
|
701
|
-
|
|
702
698
|
templateSpans.push(ts.factory.createTemplateLiteralTypeSpan(keywordTypeNodes.string, literal))
|
|
703
699
|
})
|
|
704
700
|
|
|
@@ -1,45 +1,46 @@
|
|
|
1
|
+
import { applyParamsCasing } from '@kubb/ast'
|
|
1
2
|
import type { SchemaNode } from '@kubb/ast/types'
|
|
2
3
|
import { defineGenerator } from '@kubb/core'
|
|
3
4
|
import { useKubb } from '@kubb/core/hooks'
|
|
4
5
|
import { File } from '@kubb/react-fabric'
|
|
5
6
|
import { Type } from '../../components/v2/Type.tsx'
|
|
6
7
|
import type { PluginTs } from '../../types'
|
|
8
|
+
import { buildDataSchemaNode, buildResponsesSchemaNode, buildResponseUnionSchemaNode } from './utils.ts'
|
|
7
9
|
|
|
8
10
|
export const typeGenerator = defineGenerator<PluginTs>({
|
|
9
11
|
name: 'typescript',
|
|
10
12
|
type: 'react',
|
|
11
13
|
Operation({ node, adapter, options }) {
|
|
12
|
-
const { enumType, enumKeyCasing, optionalType, arrayType, syntaxType } = options
|
|
14
|
+
const { enumType, enumKeyCasing, optionalType, arrayType, syntaxType, paramsCasing, mapper } = options
|
|
15
|
+
const { mode, getFile, resolveName } = useKubb<PluginTs>()
|
|
13
16
|
|
|
14
|
-
const {
|
|
17
|
+
const file = getFile({ name: node.operationId, extname: '.ts', mode })
|
|
18
|
+
const params = applyParamsCasing(node.parameters, paramsCasing)
|
|
15
19
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
20
|
+
function renderSchemaType({
|
|
21
|
+
node: schemaNode,
|
|
22
|
+
name,
|
|
23
|
+
typedName,
|
|
24
|
+
description,
|
|
25
|
+
}: {
|
|
26
|
+
node: SchemaNode | null
|
|
27
|
+
name: string
|
|
28
|
+
typedName: string
|
|
29
|
+
description?: string
|
|
30
|
+
}) {
|
|
31
|
+
if (!schemaNode) {
|
|
32
|
+
return null
|
|
33
|
+
}
|
|
22
34
|
|
|
23
|
-
function renderSchemaType({ node: schemaNode, name, typedName, description }: { node: SchemaNode; name: string; typedName: string; description?: string }) {
|
|
24
35
|
const imports = adapter.getImports(schemaNode, (schemaName) => ({
|
|
25
|
-
name: resolveName({
|
|
26
|
-
|
|
27
|
-
pluginName: plugin.name,
|
|
28
|
-
type: 'type',
|
|
29
|
-
}),
|
|
30
|
-
path: getFile({
|
|
31
|
-
name: schemaName,
|
|
32
|
-
pluginName: plugin.name,
|
|
33
|
-
extname: '.ts',
|
|
34
|
-
mode,
|
|
35
|
-
}).path,
|
|
36
|
+
name: resolveName({ name: schemaName, type: 'type' }),
|
|
37
|
+
path: getFile({ name: schemaName, extname: '.ts', mode }).path,
|
|
36
38
|
}))
|
|
37
39
|
|
|
38
40
|
return (
|
|
39
41
|
<>
|
|
40
42
|
{mode === 'split' &&
|
|
41
43
|
imports.map((imp) => <File.Import key={[name, imp.path, imp.isTypeOnly].join('-')} root={file.path} path={imp.path} name={imp.name} isTypeOnly />)}
|
|
42
|
-
|
|
43
44
|
<Type
|
|
44
45
|
name={name}
|
|
45
46
|
typedName={typedName}
|
|
@@ -50,127 +51,93 @@ export const typeGenerator = defineGenerator<PluginTs>({
|
|
|
50
51
|
optionalType={optionalType}
|
|
51
52
|
arrayType={arrayType}
|
|
52
53
|
syntaxType={syntaxType}
|
|
54
|
+
mapper={mapper}
|
|
53
55
|
/>
|
|
54
56
|
</>
|
|
55
57
|
)
|
|
56
58
|
}
|
|
57
59
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
name: `${node.operationId} ${param.name}`,
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const typedName = resolveName({
|
|
66
|
-
name: `${node.operationId} ${param.name}`,
|
|
67
|
-
pluginName: plugin.name,
|
|
68
|
-
type: 'type',
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
return renderSchemaType({ node: param.schema, name, typedName })
|
|
72
|
-
})
|
|
60
|
+
const paramTypes = params.map((param) =>
|
|
61
|
+
renderSchemaType({
|
|
62
|
+
node: param.schema,
|
|
63
|
+
name: resolveName({ name: `${node.operationId} ${param.name}`, type: 'function' }),
|
|
64
|
+
typedName: resolveName({ name: `${node.operationId} ${param.name}`, type: 'type' }),
|
|
65
|
+
}),
|
|
66
|
+
)
|
|
73
67
|
|
|
74
|
-
// Response types
|
|
75
68
|
const responseTypes = node.responses
|
|
76
69
|
.filter((res) => res.schema)
|
|
77
|
-
.map((res) =>
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
name:
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const typedName = resolveName({
|
|
86
|
-
name: responseName,
|
|
87
|
-
pluginName: plugin.name,
|
|
88
|
-
type: 'type',
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
return renderSchemaType({ node: schemaNode, name: resolvedName, typedName, description: res.description })
|
|
92
|
-
})
|
|
70
|
+
.map((res) =>
|
|
71
|
+
renderSchemaType({
|
|
72
|
+
node: res.schema!,
|
|
73
|
+
name: resolveName({ name: `${node.operationId} ${res.statusCode}`, type: 'function' }),
|
|
74
|
+
typedName: resolveName({ name: `${node.operationId} ${res.statusCode}`, type: 'type' }),
|
|
75
|
+
description: res.description,
|
|
76
|
+
}),
|
|
77
|
+
)
|
|
93
78
|
|
|
94
|
-
// Request body type
|
|
95
79
|
const requestType = node.requestBody
|
|
96
|
-
? (
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
})
|
|
103
|
-
const typedName = resolveName({
|
|
104
|
-
name: requestName,
|
|
105
|
-
pluginName: plugin.name,
|
|
106
|
-
type: 'type',
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
return renderSchemaType({ node: node.requestBody, name: resolvedName, typedName, description: node.requestBody.description })
|
|
110
|
-
})()
|
|
80
|
+
? renderSchemaType({
|
|
81
|
+
node: node.requestBody,
|
|
82
|
+
name: resolveName({ name: `${node.operationId} MutationRequest`, type: 'function' }),
|
|
83
|
+
typedName: resolveName({ name: `${node.operationId} MutationRequest`, type: 'type' }),
|
|
84
|
+
description: node.requestBody.description,
|
|
85
|
+
})
|
|
111
86
|
: null
|
|
112
87
|
|
|
88
|
+
const dataType = renderSchemaType({
|
|
89
|
+
node: buildDataSchemaNode({ node: { ...node, parameters: params }, resolveName }),
|
|
90
|
+
name: resolveName({ name: `${node.operationId} Data`, type: 'function' }),
|
|
91
|
+
typedName: resolveName({ name: `${node.operationId} Data`, type: 'type' }),
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
const responsesType = renderSchemaType({
|
|
95
|
+
node: buildResponsesSchemaNode({ node, resolveName }),
|
|
96
|
+
name: resolveName({ name: `${node.operationId} Responses`, type: 'function' }),
|
|
97
|
+
typedName: resolveName({ name: `${node.operationId} Responses`, type: 'type' }),
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
const responseType = renderSchemaType({
|
|
101
|
+
node: buildResponseUnionSchemaNode({ node, resolveName }),
|
|
102
|
+
name: resolveName({ name: `${node.operationId} Response`, type: 'function' }),
|
|
103
|
+
typedName: resolveName({ name: `${node.operationId} Response`, type: 'type' }),
|
|
104
|
+
})
|
|
105
|
+
|
|
113
106
|
return (
|
|
114
107
|
<File baseName={file.baseName} path={file.path} meta={file.meta}>
|
|
115
108
|
{paramTypes}
|
|
116
109
|
{responseTypes}
|
|
117
110
|
{requestType}
|
|
111
|
+
{dataType}
|
|
112
|
+
{responsesType}
|
|
113
|
+
{responseType}
|
|
118
114
|
</File>
|
|
119
115
|
)
|
|
120
116
|
},
|
|
121
117
|
Schema({ node, adapter, options }) {
|
|
122
|
-
const { enumType, enumKeyCasing, syntaxType, optionalType, arrayType } = options
|
|
123
|
-
const {
|
|
118
|
+
const { enumType, enumKeyCasing, syntaxType, optionalType, arrayType, mapper } = options
|
|
119
|
+
const { mode, resolveName, getFile } = useKubb<PluginTs>()
|
|
124
120
|
|
|
125
121
|
if (!node.name) {
|
|
126
122
|
return
|
|
127
123
|
}
|
|
128
124
|
|
|
129
125
|
const imports = adapter.getImports(node, (schemaName) => ({
|
|
130
|
-
name: resolveName({
|
|
131
|
-
|
|
132
|
-
pluginName: plugin.name,
|
|
133
|
-
type: 'type',
|
|
134
|
-
}),
|
|
135
|
-
path: getFile({
|
|
136
|
-
name: schemaName,
|
|
137
|
-
pluginName: plugin.name,
|
|
138
|
-
extname: '.ts',
|
|
139
|
-
mode,
|
|
140
|
-
// options: {
|
|
141
|
-
// group
|
|
142
|
-
// },
|
|
143
|
-
}).path,
|
|
126
|
+
name: resolveName({ name: schemaName, type: 'type' }),
|
|
127
|
+
path: getFile({ name: schemaName, extname: '.ts', mode }).path,
|
|
144
128
|
}))
|
|
145
129
|
|
|
146
130
|
const isEnumSchema = node.type === 'enum'
|
|
147
131
|
|
|
148
|
-
let typedName = resolveName({
|
|
149
|
-
name: node.name,
|
|
150
|
-
pluginName: plugin.name,
|
|
151
|
-
type: 'type',
|
|
152
|
-
})
|
|
153
|
-
|
|
132
|
+
let typedName = resolveName({ name: node.name, type: 'type' })
|
|
154
133
|
if (['asConst', 'asPascalConst'].includes(enumType) && isEnumSchema) {
|
|
155
|
-
typedName
|
|
134
|
+
typedName += 'Key'
|
|
156
135
|
}
|
|
157
136
|
|
|
158
137
|
const type = {
|
|
159
|
-
name: resolveName({
|
|
160
|
-
name: node.name,
|
|
161
|
-
pluginName: plugin.name,
|
|
162
|
-
type: 'function',
|
|
163
|
-
}),
|
|
138
|
+
name: resolveName({ name: node.name, type: 'function' }),
|
|
164
139
|
typedName,
|
|
165
|
-
file: getFile({
|
|
166
|
-
name: node.name,
|
|
167
|
-
pluginName: plugin.name,
|
|
168
|
-
extname: '.ts',
|
|
169
|
-
mode,
|
|
170
|
-
// options: {
|
|
171
|
-
// group
|
|
172
|
-
// },
|
|
173
|
-
}),
|
|
140
|
+
file: getFile({ name: node.name, extname: '.ts', mode }),
|
|
174
141
|
} as const
|
|
175
142
|
|
|
176
143
|
return (
|
|
@@ -179,7 +146,6 @@ export const typeGenerator = defineGenerator<PluginTs>({
|
|
|
179
146
|
imports.map((imp) => (
|
|
180
147
|
<File.Import key={[node.name, imp.path, imp.isTypeOnly].join('-')} root={type.file.path} path={imp.path} name={imp.name} isTypeOnly />
|
|
181
148
|
))}
|
|
182
|
-
|
|
183
149
|
<Type
|
|
184
150
|
name={type.name}
|
|
185
151
|
typedName={type.typedName}
|
|
@@ -189,6 +155,7 @@ export const typeGenerator = defineGenerator<PluginTs>({
|
|
|
189
155
|
optionalType={optionalType}
|
|
190
156
|
arrayType={arrayType}
|
|
191
157
|
syntaxType={syntaxType}
|
|
158
|
+
mapper={mapper}
|
|
192
159
|
/>
|
|
193
160
|
</File>
|
|
194
161
|
)
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { createProperty, createSchema } from '@kubb/ast'
|
|
2
|
+
import type { OperationNode, ParameterNode, SchemaNode } from '@kubb/ast/types'
|
|
3
|
+
|
|
4
|
+
type ResolveName = (opts: { name: string; type: 'type' | 'function' }) => string
|
|
5
|
+
|
|
6
|
+
type BuildParamsSchemaOptions = {
|
|
7
|
+
params: Array<ParameterNode>
|
|
8
|
+
operationId: string
|
|
9
|
+
resolveName: ResolveName
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Builds an `ObjectSchemaNode` for a group of parameters (path/query/header).
|
|
14
|
+
* Each property is a `ref` schema pointing to the individually-resolved parameter type.
|
|
15
|
+
*/
|
|
16
|
+
export function buildParamsSchema({ params, operationId, resolveName }: BuildParamsSchemaOptions): SchemaNode {
|
|
17
|
+
return createSchema({
|
|
18
|
+
type: 'object',
|
|
19
|
+
properties: params.map((param) =>
|
|
20
|
+
createProperty({
|
|
21
|
+
name: param.name,
|
|
22
|
+
schema: createSchema({
|
|
23
|
+
type: 'ref',
|
|
24
|
+
name: resolveName({ name: `${operationId} ${param.name}`, type: 'function' }),
|
|
25
|
+
optional: !param.required,
|
|
26
|
+
}),
|
|
27
|
+
}),
|
|
28
|
+
),
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type BuildOperationSchemaOptions = {
|
|
33
|
+
node: OperationNode
|
|
34
|
+
resolveName: ResolveName
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Builds an `ObjectSchemaNode` representing the `<OperationId>Data` type:
|
|
39
|
+
* - `data` → request body ref (optional) or `never`
|
|
40
|
+
* - `pathParams` → inline object of path param refs, or `never`
|
|
41
|
+
* - `queryParams` → inline object of query param refs (optional), or `never`
|
|
42
|
+
* - `headerParams` → inline object of header param refs (optional), or `never`
|
|
43
|
+
* - `url` → Express-style template literal (plugin-ts extension, handled by printer)
|
|
44
|
+
*/
|
|
45
|
+
export function buildDataSchemaNode({ node, resolveName }: BuildOperationSchemaOptions): SchemaNode {
|
|
46
|
+
const pathParams = node.parameters.filter((p) => p.in === 'path')
|
|
47
|
+
const queryParams = node.parameters.filter((p) => p.in === 'query')
|
|
48
|
+
const headerParams = node.parameters.filter((p) => p.in === 'header')
|
|
49
|
+
|
|
50
|
+
return createSchema({
|
|
51
|
+
type: 'object',
|
|
52
|
+
properties: [
|
|
53
|
+
createProperty({
|
|
54
|
+
name: 'data',
|
|
55
|
+
schema: node.requestBody
|
|
56
|
+
? createSchema({
|
|
57
|
+
type: 'ref',
|
|
58
|
+
name: resolveName({ name: `${node.operationId} MutationRequest`, type: 'function' }),
|
|
59
|
+
optional: true,
|
|
60
|
+
})
|
|
61
|
+
: createSchema({ type: 'never', optional: true }),
|
|
62
|
+
}),
|
|
63
|
+
createProperty({
|
|
64
|
+
name: 'pathParams',
|
|
65
|
+
schema:
|
|
66
|
+
pathParams.length > 0
|
|
67
|
+
? buildParamsSchema({ params: pathParams, operationId: node.operationId, resolveName })
|
|
68
|
+
: createSchema({ type: 'never', optional: true }),
|
|
69
|
+
}),
|
|
70
|
+
createProperty({
|
|
71
|
+
name: 'queryParams',
|
|
72
|
+
schema:
|
|
73
|
+
queryParams.length > 0
|
|
74
|
+
? createSchema({ ...buildParamsSchema({ params: queryParams, operationId: node.operationId, resolveName }), optional: true })
|
|
75
|
+
: createSchema({ type: 'never', optional: true }),
|
|
76
|
+
}),
|
|
77
|
+
createProperty({
|
|
78
|
+
name: 'headerParams',
|
|
79
|
+
schema:
|
|
80
|
+
headerParams.length > 0
|
|
81
|
+
? createSchema({ ...buildParamsSchema({ params: headerParams, operationId: node.operationId, resolveName }), optional: true })
|
|
82
|
+
: createSchema({ type: 'never', optional: true }),
|
|
83
|
+
}),
|
|
84
|
+
createProperty({
|
|
85
|
+
name: 'url',
|
|
86
|
+
schema: createSchema({ type: 'url', path: node.path }),
|
|
87
|
+
}),
|
|
88
|
+
],
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Builds an `ObjectSchemaNode` representing `<OperationId>Responses` — keyed by HTTP status code.
|
|
94
|
+
*
|
|
95
|
+
* Example output:
|
|
96
|
+
* ```ts
|
|
97
|
+
* export type PlaceOrderPatchResponses = { 200: PlaceOrderPatch200; 405: PlaceOrderPatch405 }
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
export function buildResponsesSchemaNode({ node, resolveName }: BuildOperationSchemaOptions): SchemaNode | null {
|
|
101
|
+
const responsesWithSchema = node.responses.filter((res) => res.schema)
|
|
102
|
+
|
|
103
|
+
if (responsesWithSchema.length === 0) {
|
|
104
|
+
return null
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return createSchema({
|
|
108
|
+
type: 'object',
|
|
109
|
+
properties: responsesWithSchema.map((res) =>
|
|
110
|
+
createProperty({
|
|
111
|
+
name: String(res.statusCode),
|
|
112
|
+
schema: createSchema({
|
|
113
|
+
type: 'ref',
|
|
114
|
+
name: resolveName({ name: `${node.operationId} ${res.statusCode}`, type: 'function' }),
|
|
115
|
+
}),
|
|
116
|
+
}),
|
|
117
|
+
),
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Builds a `UnionSchemaNode` representing `<OperationId>Response` — all response types in union format.
|
|
123
|
+
*
|
|
124
|
+
* Example output:
|
|
125
|
+
* ```ts
|
|
126
|
+
* export type PlaceOrderPatchResponse = PlaceOrderPatch200 | PlaceOrderPatch405
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
export function buildResponseUnionSchemaNode({ node, resolveName }: BuildOperationSchemaOptions): SchemaNode | null {
|
|
130
|
+
const responsesWithSchema = node.responses.filter((res) => res.schema)
|
|
131
|
+
|
|
132
|
+
if (responsesWithSchema.length === 0) {
|
|
133
|
+
return null
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return createSchema({
|
|
137
|
+
type: 'union',
|
|
138
|
+
members: responsesWithSchema.map((res) =>
|
|
139
|
+
createSchema({
|
|
140
|
+
type: 'ref',
|
|
141
|
+
name: resolveName({ name: `${node.operationId} ${res.statusCode}`, type: 'function' }),
|
|
142
|
+
}),
|
|
143
|
+
),
|
|
144
|
+
})
|
|
145
|
+
}
|
package/src/plugin.ts
CHANGED
|
@@ -167,6 +167,17 @@ export const pluginTs = definePlugin<PluginTs>((options) => {
|
|
|
167
167
|
{ depth: 'shallow' },
|
|
168
168
|
)
|
|
169
169
|
|
|
170
|
+
const barrelFiles = await getBarrelFiles(this.fabric.files, {
|
|
171
|
+
type: output.barrelType ?? 'named',
|
|
172
|
+
root,
|
|
173
|
+
output,
|
|
174
|
+
meta: {
|
|
175
|
+
pluginName: this.plugin.name,
|
|
176
|
+
},
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
await this.upsertFile(...barrelFiles)
|
|
180
|
+
|
|
170
181
|
return
|
|
171
182
|
}
|
|
172
183
|
|
package/src/printer.ts
CHANGED
|
@@ -19,6 +19,10 @@ type TsOptions = {
|
|
|
19
19
|
* @default `'inlineLiteral'`
|
|
20
20
|
*/
|
|
21
21
|
enumType: 'enum' | 'asConst' | 'asPascalConst' | 'constEnum' | 'literal' | 'inlineLiteral'
|
|
22
|
+
/**
|
|
23
|
+
* Custom property signatures that override specific object properties by name.
|
|
24
|
+
*/
|
|
25
|
+
mapper?: Record<string, ts.PropertySignature>
|
|
22
26
|
}
|
|
23
27
|
|
|
24
28
|
type TsPrinter = PrinterFactoryOptions<'typescript', TsOptions, ts.TypeNode>
|
|
@@ -143,13 +147,19 @@ export const printerTs = definePrinter<TsPrinter>((options) => ({
|
|
|
143
147
|
any: () => factory.keywordTypeNodes.any,
|
|
144
148
|
unknown: () => factory.keywordTypeNodes.unknown,
|
|
145
149
|
void: () => factory.keywordTypeNodes.void,
|
|
150
|
+
never: () => factory.keywordTypeNodes.never,
|
|
146
151
|
boolean: () => factory.keywordTypeNodes.boolean,
|
|
147
152
|
null: () => factory.keywordTypeNodes.null,
|
|
148
153
|
blob: () => factory.createTypeReferenceNode('Blob', []),
|
|
149
154
|
string: () => factory.keywordTypeNodes.string,
|
|
150
155
|
uuid: () => factory.keywordTypeNodes.string,
|
|
151
156
|
email: () => factory.keywordTypeNodes.string,
|
|
152
|
-
url: () =>
|
|
157
|
+
url: (node) => {
|
|
158
|
+
if (node.path) {
|
|
159
|
+
return factory.createUrlTemplateType(node.path)
|
|
160
|
+
}
|
|
161
|
+
return factory.keywordTypeNodes.string
|
|
162
|
+
},
|
|
153
163
|
datetime: () => factory.keywordTypeNodes.string,
|
|
154
164
|
number: () => factory.keywordTypeNodes.number,
|
|
155
165
|
integer: () => factory.keywordTypeNodes.number,
|
|
@@ -219,6 +229,10 @@ export const printerTs = definePrinter<TsPrinter>((options) => ({
|
|
|
219
229
|
const { print } = this
|
|
220
230
|
|
|
221
231
|
const propertyNodes: Array<ts.TypeElement> = node.properties.map((prop) => {
|
|
232
|
+
if (this.options.mapper && Object.hasOwn(this.options.mapper, prop.name)) {
|
|
233
|
+
return this.options.mapper[prop.name]!
|
|
234
|
+
}
|
|
235
|
+
|
|
222
236
|
const baseType = (print(prop.schema) ?? factory.keywordTypeNodes.unknown) as ts.TypeNode
|
|
223
237
|
const type = buildPropertyType(prop.schema, baseType, this.options.optionalType)
|
|
224
238
|
|