@kubb/plugin-ts 5.0.0-alpha.2 → 5.0.0-alpha.20
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/Type-B70QnSzH.cjs +688 -0
- package/dist/Type-B70QnSzH.cjs.map +1 -0
- package/dist/Type-CMC7L-38.js +671 -0
- package/dist/Type-CMC7L-38.js.map +1 -0
- package/dist/casing-Cp-jbC_k.js +84 -0
- package/dist/casing-Cp-jbC_k.js.map +1 -0
- package/dist/casing-D2uQKLWS.cjs +144 -0
- package/dist/casing-D2uQKLWS.cjs.map +1 -0
- package/dist/components.cjs +3 -2
- package/dist/components.d.ts +41 -11
- package/dist/components.js +2 -2
- package/dist/generators-BFkr7ecU.js +556 -0
- package/dist/generators-BFkr7ecU.js.map +1 -0
- package/dist/generators-xHWQCNd9.cjs +560 -0
- package/dist/generators-xHWQCNd9.cjs.map +1 -0
- package/dist/generators.cjs +2 -2
- package/dist/generators.d.ts +3 -491
- package/dist/generators.js +1 -1
- package/dist/index.cjs +146 -3
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +145 -1
- package/dist/index.js.map +1 -0
- package/dist/resolvers-DsKabI0F.js +184 -0
- package/dist/resolvers-DsKabI0F.js.map +1 -0
- package/dist/resolvers-YIpeP5YD.cjs +194 -0
- package/dist/resolvers-YIpeP5YD.cjs.map +1 -0
- package/dist/resolvers.cjs +4 -0
- package/dist/resolvers.d.ts +52 -0
- package/dist/resolvers.js +2 -0
- package/dist/types-zqLMbIqZ.d.ts +340 -0
- package/package.json +15 -8
- package/src/components/Enum.tsx +83 -0
- package/src/components/Type.tsx +25 -144
- package/src/components/index.ts +1 -0
- package/src/constants.ts +29 -0
- package/src/factory.ts +14 -16
- package/src/generators/typeGenerator.tsx +221 -414
- package/src/generators/utils.ts +308 -0
- package/src/index.ts +1 -1
- package/src/plugin.ts +74 -87
- package/src/presets.ts +23 -0
- package/src/printer.ts +256 -92
- package/src/resolvers/index.ts +2 -0
- package/src/resolvers/resolverTs.ts +104 -0
- package/src/resolvers/resolverTsLegacy.ts +87 -0
- package/src/types.ts +234 -63
- package/dist/components-9wydyqUx.cjs +0 -848
- package/dist/components-9wydyqUx.cjs.map +0 -1
- package/dist/components-LmqJfxMv.js +0 -721
- package/dist/components-LmqJfxMv.js.map +0 -1
- package/dist/plugin-CNkzbtpl.cjs +0 -508
- package/dist/plugin-CNkzbtpl.cjs.map +0 -1
- package/dist/plugin-DoLrDl9P.js +0 -476
- package/dist/plugin-DoLrDl9P.js.map +0 -1
- package/dist/types-BpeKGgCn.d.ts +0 -170
- package/src/parser.ts +0 -396
package/src/printer.ts
CHANGED
|
@@ -1,27 +1,61 @@
|
|
|
1
1
|
import { jsStringEscape, stringify } from '@internals/utils'
|
|
2
|
+
import { isStringType, narrowSchema, schemaTypes } from '@kubb/ast'
|
|
2
3
|
import type { ArraySchemaNode, SchemaNode } from '@kubb/ast/types'
|
|
3
4
|
import type { PrinterFactoryOptions } from '@kubb/core'
|
|
4
5
|
import { definePrinter } from '@kubb/core'
|
|
5
6
|
import type ts from 'typescript'
|
|
7
|
+
import { ENUM_TYPES_WITH_KEY_SUFFIX, OPTIONAL_ADDS_QUESTION_TOKEN, OPTIONAL_ADDS_UNDEFINED } from './constants.ts'
|
|
6
8
|
import * as factory from './factory.ts'
|
|
9
|
+
import type { PluginTs, ResolverTs } from './types.ts'
|
|
7
10
|
|
|
8
11
|
type TsOptions = {
|
|
9
12
|
/**
|
|
10
13
|
* @default `'questionToken'`
|
|
11
14
|
*/
|
|
12
|
-
optionalType: '
|
|
15
|
+
optionalType: PluginTs['resolvedOptions']['optionalType']
|
|
13
16
|
/**
|
|
14
17
|
* @default `'array'`
|
|
15
18
|
*/
|
|
16
|
-
arrayType: '
|
|
19
|
+
arrayType: PluginTs['resolvedOptions']['arrayType']
|
|
17
20
|
/**
|
|
18
21
|
* @default `'inlineLiteral'`
|
|
19
22
|
*/
|
|
20
|
-
enumType: '
|
|
23
|
+
enumType: PluginTs['resolvedOptions']['enumType']
|
|
24
|
+
/**
|
|
25
|
+
* Controls whether a `type` alias or `interface` declaration is emitted.
|
|
26
|
+
* @default `'type'`
|
|
27
|
+
*/
|
|
28
|
+
syntaxType?: PluginTs['resolvedOptions']['syntaxType']
|
|
29
|
+
/**
|
|
30
|
+
* When set, `printer.print(node)` produces a full `type Name = …` declaration.
|
|
31
|
+
* When omitted, `printer.print(node)` returns the raw type node.
|
|
32
|
+
*/
|
|
33
|
+
typeName?: string
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* JSDoc `@description` comment added to the generated type or interface declaration.
|
|
37
|
+
*/
|
|
38
|
+
description?: string
|
|
39
|
+
/**
|
|
40
|
+
* Property keys to exclude from the generated type via `Omit<Type, Keys>`.
|
|
41
|
+
* Forces type-alias syntax even when `syntaxType` is `'interface'`.
|
|
42
|
+
*/
|
|
43
|
+
keysToOmit?: Array<string>
|
|
44
|
+
/**
|
|
45
|
+
* Resolver used to transform raw schema names into valid TypeScript identifiers.
|
|
46
|
+
*/
|
|
47
|
+
resolver: ResolverTs
|
|
21
48
|
}
|
|
22
49
|
|
|
23
|
-
|
|
50
|
+
/**
|
|
51
|
+
* TypeScript printer factory options: maps `SchemaNode` → `ts.TypeNode` (raw) or `ts.Node` (full declaration).
|
|
52
|
+
*/
|
|
53
|
+
type TsPrinter = PrinterFactoryOptions<'typescript', TsOptions, ts.TypeNode, ts.Node>
|
|
24
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Converts a primitive const value to a TypeScript literal type node.
|
|
57
|
+
* Handles negative numbers via a prefix unary expression.
|
|
58
|
+
*/
|
|
25
59
|
function constToTypeNode(value: string | number | boolean, format: 'string' | 'number' | 'boolean'): ts.TypeNode | undefined {
|
|
26
60
|
if (format === 'boolean') {
|
|
27
61
|
return factory.createLiteralTypeNode(value === true ? factory.createTrue() : factory.createFalse())
|
|
@@ -35,16 +69,26 @@ function constToTypeNode(value: string | number | boolean, format: 'string' | 'n
|
|
|
35
69
|
return factory.createLiteralTypeNode(factory.createStringLiteral(String(value)))
|
|
36
70
|
}
|
|
37
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Returns a `Date` reference type node when `representation` is `'date'`, otherwise falls back to `string`.
|
|
74
|
+
*/
|
|
38
75
|
function dateOrStringNode(node: { representation?: string }): ts.TypeNode {
|
|
39
76
|
return node.representation === 'date' ? factory.createTypeReferenceNode(factory.createIdentifier('Date')) : factory.keywordTypeNodes.string
|
|
40
77
|
}
|
|
41
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Maps an array of `SchemaNode`s through the printer, filtering out `null` and `undefined` results.
|
|
81
|
+
*/
|
|
42
82
|
function buildMemberNodes(members: Array<SchemaNode> | undefined, print: (node: SchemaNode) => ts.TypeNode | null | undefined): Array<ts.TypeNode> {
|
|
43
|
-
return (members ?? []).map(print).filter(Boolean)
|
|
83
|
+
return (members ?? []).map(print).filter(Boolean)
|
|
44
84
|
}
|
|
45
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Builds a TypeScript tuple type node from an array schema's `items`,
|
|
88
|
+
* applying min/max slice and optional/rest element rules.
|
|
89
|
+
*/
|
|
46
90
|
function buildTupleNode(node: ArraySchemaNode, print: (node: SchemaNode) => ts.TypeNode | null | undefined): ts.TypeNode | undefined {
|
|
47
|
-
let items = (node.items ?? []).map(print).filter(Boolean)
|
|
91
|
+
let items = (node.items ?? []).map(print).filter(Boolean)
|
|
48
92
|
|
|
49
93
|
const restNode = node.rest ? (print(node.rest) ?? undefined) : undefined
|
|
50
94
|
const { min, max } = node
|
|
@@ -67,28 +111,37 @@ function buildTupleNode(node: ArraySchemaNode, print: (node: SchemaNode) => ts.T
|
|
|
67
111
|
return factory.createTupleTypeNode(items)
|
|
68
112
|
}
|
|
69
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Applies `nullable` and optional/nullish `| undefined` union modifiers to a property's resolved base type.
|
|
116
|
+
*/
|
|
70
117
|
function buildPropertyType(schema: SchemaNode, baseType: ts.TypeNode, optionalType: TsOptions['optionalType']): ts.TypeNode {
|
|
71
|
-
const addsUndefined =
|
|
118
|
+
const addsUndefined = OPTIONAL_ADDS_UNDEFINED.has(optionalType)
|
|
72
119
|
|
|
73
120
|
let type = baseType
|
|
74
121
|
|
|
75
122
|
if (schema.nullable) {
|
|
76
|
-
type = factory.createUnionDeclaration({ nodes: [type, factory.keywordTypeNodes.null] })
|
|
123
|
+
type = factory.createUnionDeclaration({ nodes: [type, factory.keywordTypeNodes.null] })
|
|
77
124
|
}
|
|
78
125
|
|
|
79
126
|
if ((schema.nullish || schema.optional) && addsUndefined) {
|
|
80
|
-
type = factory.createUnionDeclaration({ nodes: [type, factory.keywordTypeNodes.undefined] })
|
|
127
|
+
type = factory.createUnionDeclaration({ nodes: [type, factory.keywordTypeNodes.undefined] })
|
|
81
128
|
}
|
|
82
129
|
|
|
83
130
|
return type
|
|
84
131
|
}
|
|
85
132
|
|
|
133
|
+
/**
|
|
134
|
+
* Collects JSDoc annotation strings (description, deprecated, min/max, pattern, default, example, type) for a schema node.
|
|
135
|
+
*/
|
|
86
136
|
function buildPropertyJSDocComments(schema: SchemaNode): Array<string | undefined> {
|
|
137
|
+
const isArray = schema.type === 'array'
|
|
138
|
+
|
|
87
139
|
return [
|
|
88
|
-
'description' in schema && schema.description ? `@description ${jsStringEscape(schema.description
|
|
140
|
+
'description' in schema && schema.description ? `@description ${jsStringEscape(schema.description)}` : undefined,
|
|
89
141
|
'deprecated' in schema && schema.deprecated ? '@deprecated' : undefined,
|
|
90
|
-
|
|
91
|
-
'
|
|
142
|
+
// minItems/maxItems on arrays should not be emitted as @minLength/@maxLength
|
|
143
|
+
!isArray && 'min' in schema && schema.min !== undefined ? `@minLength ${schema.min}` : undefined,
|
|
144
|
+
!isArray && 'max' in schema && schema.max !== undefined ? `@maxLength ${schema.max}` : undefined,
|
|
92
145
|
'pattern' in schema && schema.pattern ? `@pattern ${schema.pattern}` : undefined,
|
|
93
146
|
'default' in schema && schema.default !== undefined
|
|
94
147
|
? `@default ${'primitive' in schema && schema.primitive === 'string' ? stringify(schema.default as string) : schema.default}`
|
|
@@ -100,6 +153,9 @@ function buildPropertyJSDocComments(schema: SchemaNode): Array<string | undefine
|
|
|
100
153
|
]
|
|
101
154
|
}
|
|
102
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Creates TypeScript index signatures for `additionalProperties` and `patternProperties` on an object schema node.
|
|
158
|
+
*/
|
|
103
159
|
function buildIndexSignatures(
|
|
104
160
|
node: { additionalProperties?: SchemaNode | boolean; patternProperties?: Record<string, SchemaNode> },
|
|
105
161
|
propertyCount: number,
|
|
@@ -108,7 +164,8 @@ function buildIndexSignatures(
|
|
|
108
164
|
const elements: Array<ts.TypeElement> = []
|
|
109
165
|
|
|
110
166
|
if (node.additionalProperties && node.additionalProperties !== true) {
|
|
111
|
-
const additionalType =
|
|
167
|
+
const additionalType = print(node.additionalProperties) ?? factory.keywordTypeNodes.unknown
|
|
168
|
+
|
|
112
169
|
elements.push(factory.createIndexSignature(propertyCount > 0 ? factory.keywordTypeNodes.unknown : additionalType))
|
|
113
170
|
} else if (node.additionalProperties === true) {
|
|
114
171
|
elements.push(factory.createIndexSignature(factory.keywordTypeNodes.unknown))
|
|
@@ -117,9 +174,10 @@ function buildIndexSignatures(
|
|
|
117
174
|
if (node.patternProperties) {
|
|
118
175
|
const first = Object.values(node.patternProperties)[0]
|
|
119
176
|
if (first) {
|
|
120
|
-
let patternType =
|
|
177
|
+
let patternType = print(first) ?? factory.keywordTypeNodes.unknown
|
|
178
|
+
|
|
121
179
|
if (first.nullable) {
|
|
122
|
-
patternType = factory.createUnionDeclaration({ nodes: [patternType, factory.keywordTypeNodes.null] })
|
|
180
|
+
patternType = factory.createUnionDeclaration({ nodes: [patternType, factory.keywordTypeNodes.null] })
|
|
123
181
|
}
|
|
124
182
|
elements.push(factory.createIndexSignature(patternType))
|
|
125
183
|
}
|
|
@@ -129,93 +187,199 @@ function buildIndexSignatures(
|
|
|
129
187
|
}
|
|
130
188
|
|
|
131
189
|
/**
|
|
132
|
-
*
|
|
190
|
+
* TypeScript type printer built with `definePrinter`.
|
|
191
|
+
*
|
|
192
|
+
* Converts a `SchemaNode` AST node into a TypeScript AST node:
|
|
193
|
+
* - **`printer.print(node)`** — when `options.typeName` is set, returns a full
|
|
194
|
+
* `type Name = …` or `interface Name { … }` declaration (`ts.Node`).
|
|
195
|
+
* Without `typeName`, returns the raw `ts.TypeNode` for the schema.
|
|
196
|
+
*
|
|
197
|
+
* Dispatches on `node.type` to the appropriate handler in `nodes`. Options are closed
|
|
198
|
+
* over per printer instance, so each call to `printerTs(options)` produces an independent printer.
|
|
199
|
+
*
|
|
200
|
+
* @example Raw type node (no `typeName`)
|
|
201
|
+
* ```ts
|
|
202
|
+
* const printer = printerTs({ optionalType: 'questionToken', arrayType: 'array', enumType: 'inlineLiteral' })
|
|
203
|
+
* const typeNode = printer.print(schemaNode) // ts.TypeNode
|
|
204
|
+
* ```
|
|
133
205
|
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
206
|
+
* @example Full declaration (with `typeName`)
|
|
207
|
+
* ```ts
|
|
208
|
+
* const printer = printerTs({ optionalType: 'questionToken', arrayType: 'array', enumType: 'inlineLiteral', typeName: 'MyType' })
|
|
209
|
+
* const declaration = printer.print(schemaNode) // ts.TypeAliasDeclaration | ts.InterfaceDeclaration
|
|
210
|
+
* ```
|
|
137
211
|
*/
|
|
138
|
-
export const printerTs = definePrinter<TsPrinter>((options) =>
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
212
|
+
export const printerTs = definePrinter<TsPrinter>((options) => {
|
|
213
|
+
const addsUndefined = OPTIONAL_ADDS_UNDEFINED.has(options.optionalType)
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
name: 'typescript',
|
|
217
|
+
options,
|
|
218
|
+
nodes: {
|
|
219
|
+
any: () => factory.keywordTypeNodes.any,
|
|
220
|
+
unknown: () => factory.keywordTypeNodes.unknown,
|
|
221
|
+
void: () => factory.keywordTypeNodes.void,
|
|
222
|
+
never: () => factory.keywordTypeNodes.never,
|
|
223
|
+
boolean: () => factory.keywordTypeNodes.boolean,
|
|
224
|
+
null: () => factory.keywordTypeNodes.null,
|
|
225
|
+
blob: () => factory.createTypeReferenceNode('Blob', []),
|
|
226
|
+
string: () => factory.keywordTypeNodes.string,
|
|
227
|
+
uuid: () => factory.keywordTypeNodes.string,
|
|
228
|
+
email: () => factory.keywordTypeNodes.string,
|
|
229
|
+
url: (node) => {
|
|
230
|
+
if (node.path) {
|
|
231
|
+
return factory.createUrlTemplateType(node.path)
|
|
232
|
+
}
|
|
233
|
+
return factory.keywordTypeNodes.string
|
|
234
|
+
},
|
|
235
|
+
datetime: () => factory.keywordTypeNodes.string,
|
|
236
|
+
number: () => factory.keywordTypeNodes.number,
|
|
237
|
+
integer: () => factory.keywordTypeNodes.number,
|
|
238
|
+
bigint: () => factory.keywordTypeNodes.bigint,
|
|
239
|
+
date: dateOrStringNode,
|
|
240
|
+
time: dateOrStringNode,
|
|
241
|
+
ref(node) {
|
|
242
|
+
if (!node.name) {
|
|
243
|
+
return undefined
|
|
244
|
+
}
|
|
245
|
+
// Parser-generated refs (with $ref) carry raw schema names that need resolving.
|
|
246
|
+
// Use the canonical name from the $ref path — node.name may have been overridden
|
|
247
|
+
// (e.g. by single-member allOf flatten using the property-derived child name).
|
|
248
|
+
// Inline refs (without $ref) from utils already carry resolved type names.
|
|
249
|
+
const refName = node.ref ? (node.ref.split('/').at(-1) ?? node.name) : node.name
|
|
250
|
+
const name = node.ref ? this.options.resolver.default(refName, 'type') : refName
|
|
251
|
+
|
|
252
|
+
return factory.createTypeReferenceNode(name, undefined)
|
|
253
|
+
},
|
|
254
|
+
enum(node) {
|
|
255
|
+
const values = node.namedEnumValues?.map((v) => v.value) ?? node.enumValues ?? []
|
|
256
|
+
|
|
257
|
+
if (this.options.enumType === 'inlineLiteral' || !node.name) {
|
|
258
|
+
const literalNodes = values
|
|
259
|
+
.filter((v): v is string | number | boolean => v !== null)
|
|
260
|
+
.map((value) => constToTypeNode(value, typeof value as 'string' | 'number' | 'boolean'))
|
|
261
|
+
.filter(Boolean)
|
|
262
|
+
|
|
263
|
+
return factory.createUnionDeclaration({ withParentheses: true, nodes: literalNodes }) ?? undefined
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const resolvedName = this.options.resolver.default(node.name, 'type')
|
|
267
|
+
const typeName = ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.enumType) ? `${resolvedName}Key` : resolvedName
|
|
268
|
+
|
|
269
|
+
return factory.createTypeReferenceNode(typeName, undefined)
|
|
270
|
+
},
|
|
271
|
+
union(node) {
|
|
272
|
+
const members = node.members ?? []
|
|
273
|
+
|
|
274
|
+
const hasStringLiteral = members.some((m) => {
|
|
275
|
+
const enumNode = narrowSchema(m, schemaTypes.enum)
|
|
276
|
+
return enumNode?.primitive === 'string'
|
|
277
|
+
})
|
|
278
|
+
const hasPlainString = members.some((m) => isStringType(m))
|
|
279
|
+
|
|
280
|
+
if (hasStringLiteral && hasPlainString) {
|
|
281
|
+
const memberNodes = members
|
|
282
|
+
.map((m) => {
|
|
283
|
+
if (isStringType(m)) {
|
|
284
|
+
return factory.createIntersectionDeclaration({
|
|
285
|
+
nodes: [factory.keywordTypeNodes.string, factory.createTypeLiteralNode([])],
|
|
286
|
+
withParentheses: true,
|
|
287
|
+
})
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return this.print(m)
|
|
291
|
+
})
|
|
292
|
+
.filter(Boolean)
|
|
293
|
+
|
|
294
|
+
return factory.createUnionDeclaration({ withParentheses: true, nodes: memberNodes }) ?? undefined
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return factory.createUnionDeclaration({ withParentheses: true, nodes: buildMemberNodes(members, this.print) }) ?? undefined
|
|
298
|
+
},
|
|
299
|
+
intersection(node) {
|
|
300
|
+
return factory.createIntersectionDeclaration({ withParentheses: true, nodes: buildMemberNodes(node.members, this.print) }) ?? undefined
|
|
301
|
+
},
|
|
302
|
+
array(node) {
|
|
303
|
+
const itemNodes = (node.items ?? []).map((item) => this.print(item)).filter(Boolean)
|
|
304
|
+
|
|
305
|
+
return factory.createArrayDeclaration({ nodes: itemNodes, arrayType: this.options.arrayType }) ?? undefined
|
|
306
|
+
},
|
|
307
|
+
tuple(node) {
|
|
308
|
+
return buildTupleNode(node, this.print)
|
|
309
|
+
},
|
|
310
|
+
object(node) {
|
|
311
|
+
const { print, options } = this
|
|
312
|
+
const addsQuestionToken = OPTIONAL_ADDS_QUESTION_TOKEN.has(options.optionalType)
|
|
313
|
+
|
|
314
|
+
const propertyNodes: Array<ts.TypeElement> = node.properties.map((prop) => {
|
|
315
|
+
const baseType = print(prop.schema) ?? factory.keywordTypeNodes.unknown
|
|
316
|
+
const type = buildPropertyType(prop.schema, baseType, options.optionalType)
|
|
317
|
+
|
|
318
|
+
const propertyNode = factory.createPropertySignature({
|
|
319
|
+
questionToken: prop.schema.optional || prop.schema.nullish ? addsQuestionToken : false,
|
|
320
|
+
name: prop.name,
|
|
321
|
+
type,
|
|
322
|
+
readOnly: prop.schema.readOnly,
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
return factory.appendJSDocToNode({ node: propertyNode, comments: buildPropertyJSDocComments(prop.schema) })
|
|
326
|
+
})
|
|
172
327
|
|
|
173
|
-
|
|
174
|
-
}
|
|
328
|
+
const allElements = [...propertyNodes, ...buildIndexSignatures(node, propertyNodes.length, print)]
|
|
175
329
|
|
|
176
|
-
|
|
330
|
+
if (!allElements.length) {
|
|
331
|
+
return factory.keywordTypeNodes.object
|
|
332
|
+
}
|
|
177
333
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
union(node) {
|
|
181
|
-
return factory.createUnionDeclaration({ withParentheses: true, nodes: buildMemberNodes(node.members, this.print) }) ?? undefined
|
|
334
|
+
return factory.createTypeLiteralNode(allElements)
|
|
335
|
+
},
|
|
182
336
|
},
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
},
|
|
186
|
-
array(node) {
|
|
187
|
-
const itemNodes = (node.items ?? []).map((item) => this.print(item)).filter(Boolean) as Array<ts.TypeNode>
|
|
337
|
+
print(node) {
|
|
338
|
+
let type = this.print(node)
|
|
188
339
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
return buildTupleNode(node, this.print)
|
|
193
|
-
},
|
|
194
|
-
object(node) {
|
|
195
|
-
const addsQuestionToken = ['questionToken', 'questionTokenAndUndefined'].includes(this.options.optionalType)
|
|
196
|
-
const { print } = this
|
|
197
|
-
|
|
198
|
-
const propertyNodes: Array<ts.TypeElement> = node.properties.map((prop) => {
|
|
199
|
-
const baseType = (print(prop.schema) ?? factory.keywordTypeNodes.unknown) as ts.TypeNode
|
|
200
|
-
const type = buildPropertyType(prop.schema, baseType, this.options.optionalType)
|
|
201
|
-
|
|
202
|
-
const propertyNode = factory.createPropertySignature({
|
|
203
|
-
questionToken: prop.schema.optional || prop.schema.nullish ? addsQuestionToken : false,
|
|
204
|
-
name: prop.name,
|
|
205
|
-
type,
|
|
206
|
-
readOnly: prop.schema.readOnly,
|
|
207
|
-
})
|
|
340
|
+
if (!type) {
|
|
341
|
+
return undefined
|
|
342
|
+
}
|
|
208
343
|
|
|
209
|
-
|
|
210
|
-
|
|
344
|
+
// Apply top-level nullable / optional union modifiers.
|
|
345
|
+
if (node.nullable) {
|
|
346
|
+
type = factory.createUnionDeclaration({ nodes: [type, factory.keywordTypeNodes.null] })
|
|
347
|
+
}
|
|
211
348
|
|
|
212
|
-
|
|
349
|
+
if ((node.nullish || node.optional) && addsUndefined) {
|
|
350
|
+
type = factory.createUnionDeclaration({ nodes: [type, factory.keywordTypeNodes.undefined] })
|
|
351
|
+
}
|
|
213
352
|
|
|
214
|
-
|
|
215
|
-
|
|
353
|
+
// Without typeName, return the type node as-is (no declaration wrapping).
|
|
354
|
+
const { typeName, syntaxType = 'type', description, keysToOmit } = this.options
|
|
355
|
+
if (!typeName) {
|
|
356
|
+
return type
|
|
216
357
|
}
|
|
217
358
|
|
|
218
|
-
|
|
359
|
+
const useTypeGeneration = syntaxType === 'type' || type.kind === factory.syntaxKind.union || !!keysToOmit?.length
|
|
360
|
+
|
|
361
|
+
return factory.createTypeDeclaration({
|
|
362
|
+
name: typeName,
|
|
363
|
+
isExportable: true,
|
|
364
|
+
type: keysToOmit?.length
|
|
365
|
+
? factory.createOmitDeclaration({
|
|
366
|
+
keys: keysToOmit,
|
|
367
|
+
type,
|
|
368
|
+
nonNullable: true,
|
|
369
|
+
})
|
|
370
|
+
: type,
|
|
371
|
+
syntax: useTypeGeneration ? 'type' : 'interface',
|
|
372
|
+
comments: [
|
|
373
|
+
node?.title ? jsStringEscape(node.title) : undefined,
|
|
374
|
+
description ? `@description ${jsStringEscape(description)}` : undefined,
|
|
375
|
+
node?.deprecated ? '@deprecated' : undefined,
|
|
376
|
+
node && 'min' in node && node.min !== undefined ? `@minLength ${node.min}` : undefined,
|
|
377
|
+
node && 'max' in node && node.max !== undefined ? `@maxLength ${node.max}` : undefined,
|
|
378
|
+
node && 'pattern' in node && node.pattern ? `@pattern ${node.pattern}` : undefined,
|
|
379
|
+
node?.default ? `@default ${node.default}` : undefined,
|
|
380
|
+
node?.example ? `@example ${node.example}` : undefined,
|
|
381
|
+
],
|
|
382
|
+
})
|
|
219
383
|
},
|
|
220
|
-
}
|
|
221
|
-
})
|
|
384
|
+
}
|
|
385
|
+
})
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { pascalCase } from '@internals/utils'
|
|
2
|
+
import { defineResolver } from '@kubb/core'
|
|
3
|
+
import type { PluginTs } from '../types.ts'
|
|
4
|
+
|
|
5
|
+
function resolveName(name: string, type?: 'file' | 'function' | 'type' | 'const'): string {
|
|
6
|
+
return pascalCase(name, { isFile: type === 'file' })
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Resolver for `@kubb/plugin-ts` that provides the default naming and path-resolution
|
|
11
|
+
* helpers used by the plugin. Import this in other plugins to resolve the exact names and
|
|
12
|
+
* paths that `plugin-ts` generates without hardcoding the conventions.
|
|
13
|
+
*
|
|
14
|
+
* The `default` method is automatically injected by `defineResolver` — it uses `camelCase`
|
|
15
|
+
* for identifiers/files and `pascalCase` for type names.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* import { resolver } from '@kubb/plugin-ts'
|
|
20
|
+
*
|
|
21
|
+
* resolver.default('list pets', 'type') // → 'ListPets'
|
|
22
|
+
* resolver.resolveName('list pets status 200') // → 'listPetsStatus200'
|
|
23
|
+
* resolver.resolveTypedName('list pets status 200') // → 'ListPetsStatus200'
|
|
24
|
+
* resolver.resolvePathName('list pets', 'file') // → 'listPets'
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export const resolverTs = defineResolver<PluginTs>(() => {
|
|
28
|
+
return {
|
|
29
|
+
name: 'default',
|
|
30
|
+
default(name, type) {
|
|
31
|
+
return resolveName(name, type)
|
|
32
|
+
},
|
|
33
|
+
resolveName(name) {
|
|
34
|
+
return this.default(name, 'function')
|
|
35
|
+
},
|
|
36
|
+
resolveTypedName(name) {
|
|
37
|
+
return this.default(name, 'type')
|
|
38
|
+
},
|
|
39
|
+
resolvePathName(name, type) {
|
|
40
|
+
return this.default(name, type)
|
|
41
|
+
},
|
|
42
|
+
resolveParamName(node, param) {
|
|
43
|
+
return this.resolveName(`${node.operationId} ${this.default(param.in)} ${param.name}`)
|
|
44
|
+
},
|
|
45
|
+
resolveParamTypedName(node, param) {
|
|
46
|
+
return this.resolveTypedName(`${node.operationId} ${this.default(param.in)} ${param.name}`)
|
|
47
|
+
},
|
|
48
|
+
resolveResponseStatusName(node, statusCode) {
|
|
49
|
+
return this.resolveName(`${node.operationId} Status ${statusCode}`)
|
|
50
|
+
},
|
|
51
|
+
resolveResponseStatusTypedName(node, statusCode) {
|
|
52
|
+
return this.resolveTypedName(`${node.operationId} Status ${statusCode}`)
|
|
53
|
+
},
|
|
54
|
+
resolveDataName(node) {
|
|
55
|
+
return this.resolveName(`${node.operationId} Data`)
|
|
56
|
+
},
|
|
57
|
+
resolveDataTypedName(node) {
|
|
58
|
+
return this.resolveTypedName(`${node.operationId} Data`)
|
|
59
|
+
},
|
|
60
|
+
resolveRequestConfigName(node) {
|
|
61
|
+
return this.resolveName(`${node.operationId} RequestConfig`)
|
|
62
|
+
},
|
|
63
|
+
resolveRequestConfigTypedName(node) {
|
|
64
|
+
return this.resolveTypedName(`${node.operationId} RequestConfig`)
|
|
65
|
+
},
|
|
66
|
+
resolveResponsesName(node) {
|
|
67
|
+
return this.resolveName(`${node.operationId} Responses`)
|
|
68
|
+
},
|
|
69
|
+
resolveResponsesTypedName(node) {
|
|
70
|
+
return this.resolveTypedName(`${node.operationId} Responses`)
|
|
71
|
+
},
|
|
72
|
+
resolveResponseName(node) {
|
|
73
|
+
return this.resolveName(`${node.operationId} Response`)
|
|
74
|
+
},
|
|
75
|
+
resolveResponseTypedName(node) {
|
|
76
|
+
return this.resolveTypedName(`${node.operationId} Response`)
|
|
77
|
+
},
|
|
78
|
+
resolveEnumKeyTypedName(node) {
|
|
79
|
+
return `${this.resolveTypedName(node.name ?? '')}Key`
|
|
80
|
+
},
|
|
81
|
+
resolvePathParamsName(_node) {
|
|
82
|
+
throw new Error("resolvePathParamsName is only available with compatibilityPreset: 'kubbV4'. Use resolveParamName per individual parameter instead.")
|
|
83
|
+
},
|
|
84
|
+
resolvePathParamsTypedName(_node) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
"resolvePathParamsTypedName is only available with compatibilityPreset: 'kubbV4'. Use resolveParamTypedName per individual parameter instead.",
|
|
87
|
+
)
|
|
88
|
+
},
|
|
89
|
+
resolveQueryParamsName(node) {
|
|
90
|
+
return this.resolveName(`${node.operationId} QueryParams`)
|
|
91
|
+
},
|
|
92
|
+
resolveQueryParamsTypedName(node) {
|
|
93
|
+
return this.resolveTypedName(`${node.operationId} QueryParams`)
|
|
94
|
+
},
|
|
95
|
+
resolveHeaderParamsName(_node) {
|
|
96
|
+
throw new Error("resolveHeaderParamsName is only available with compatibilityPreset: 'kubbV4'. Use resolveParamName per individual parameter instead.")
|
|
97
|
+
},
|
|
98
|
+
resolveHeaderParamsTypedName(_node) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
"resolveHeaderParamsTypedName is only available with compatibilityPreset: 'kubbV4'. Use resolveParamTypedName per individual parameter instead.",
|
|
101
|
+
)
|
|
102
|
+
},
|
|
103
|
+
}
|
|
104
|
+
})
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { defineResolver } from '@kubb/core'
|
|
2
|
+
import type { PluginTs } from '../types.ts'
|
|
3
|
+
import { resolverTs } from './resolverTs.ts'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Legacy resolver for `@kubb/plugin-ts` that reproduces the naming conventions
|
|
7
|
+
* used before the v2 resolver refactor. Enable via `compatibilityPreset: 'kubbV4'`
|
|
8
|
+
* (or by composing this resolver manually).
|
|
9
|
+
*
|
|
10
|
+
* Key differences from the default resolver:
|
|
11
|
+
* - Response status types: `<OperationId><StatusCode>` (e.g. `CreatePets201`) instead of `<OperationId>Status201`
|
|
12
|
+
* - Default/error responses: `<OperationId>Error` instead of `<OperationId>StatusDefault`
|
|
13
|
+
* - Request body: `<OperationId>MutationRequest` (non-GET) / `<OperationId>QueryRequest` (GET)
|
|
14
|
+
* - Combined responses type: `<OperationId>Mutation` / `<OperationId>Query`
|
|
15
|
+
* - Response union: `<OperationId>MutationResponse` / `<OperationId>QueryResponse`
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* import { resolverTsLegacy } from '@kubb/plugin-ts'
|
|
20
|
+
*
|
|
21
|
+
* resolverTsLegacy.resolveResponseStatusTypedName(node, 201) // → 'CreatePets201'
|
|
22
|
+
* resolverTsLegacy.resolveResponseStatusTypedName(node, 'default') // → 'CreatePetsError'
|
|
23
|
+
* resolverTsLegacy.resolveDataTypedName(node) // → 'CreatePetsMutationRequest' (POST)
|
|
24
|
+
* resolverTsLegacy.resolveResponsesTypedName(node) // → 'CreatePetsMutation' (POST)
|
|
25
|
+
* resolverTsLegacy.resolveResponseTypedName(node) // → 'CreatePetsMutationResponse' (POST)
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export const resolverTsLegacy = defineResolver<PluginTs>(() => {
|
|
29
|
+
return {
|
|
30
|
+
...resolverTs,
|
|
31
|
+
name: 'legacy',
|
|
32
|
+
resolveResponseStatusName(node, statusCode) {
|
|
33
|
+
if (statusCode === 'default') {
|
|
34
|
+
return this.resolveName(`${node.operationId} Error`)
|
|
35
|
+
}
|
|
36
|
+
return this.resolveName(`${node.operationId} ${statusCode}`)
|
|
37
|
+
},
|
|
38
|
+
resolveResponseStatusTypedName(node, statusCode) {
|
|
39
|
+
if (statusCode === 'default') {
|
|
40
|
+
return this.resolveTypedName(`${node.operationId} Error`)
|
|
41
|
+
}
|
|
42
|
+
return this.resolveTypedName(`${node.operationId} ${statusCode}`)
|
|
43
|
+
},
|
|
44
|
+
resolveDataName(node) {
|
|
45
|
+
const suffix = node.method === 'GET' ? 'QueryRequest' : 'MutationRequest'
|
|
46
|
+
return this.resolveName(`${node.operationId} ${suffix}`)
|
|
47
|
+
},
|
|
48
|
+
resolveDataTypedName(node) {
|
|
49
|
+
const suffix = node.method === 'GET' ? 'QueryRequest' : 'MutationRequest'
|
|
50
|
+
return this.resolveTypedName(`${node.operationId} ${suffix}`)
|
|
51
|
+
},
|
|
52
|
+
resolveResponsesName(node) {
|
|
53
|
+
const suffix = node.method === 'GET' ? 'Query' : 'Mutation'
|
|
54
|
+
return `${this.default(node.operationId, 'function')}${suffix}`
|
|
55
|
+
},
|
|
56
|
+
resolveResponsesTypedName(node) {
|
|
57
|
+
const suffix = node.method === 'GET' ? 'Query' : 'Mutation'
|
|
58
|
+
return `${this.default(node.operationId, 'type')}${suffix}`
|
|
59
|
+
},
|
|
60
|
+
resolveResponseName(node) {
|
|
61
|
+
const suffix = node.method === 'GET' ? 'QueryResponse' : 'MutationResponse'
|
|
62
|
+
return this.resolveName(`${node.operationId} ${suffix}`)
|
|
63
|
+
},
|
|
64
|
+
resolveResponseTypedName(node) {
|
|
65
|
+
const suffix = node.method === 'GET' ? 'QueryResponse' : 'MutationResponse'
|
|
66
|
+
return this.resolveTypedName(`${node.operationId} ${suffix}`)
|
|
67
|
+
},
|
|
68
|
+
resolvePathParamsName(node) {
|
|
69
|
+
return this.resolveName(`${node.operationId} PathParams`)
|
|
70
|
+
},
|
|
71
|
+
resolvePathParamsTypedName(node) {
|
|
72
|
+
return this.resolveTypedName(`${node.operationId} PathParams`)
|
|
73
|
+
},
|
|
74
|
+
resolveQueryParamsName(node) {
|
|
75
|
+
return this.resolveName(`${node.operationId} QueryParams`)
|
|
76
|
+
},
|
|
77
|
+
resolveQueryParamsTypedName(node) {
|
|
78
|
+
return this.resolveTypedName(`${node.operationId} QueryParams`)
|
|
79
|
+
},
|
|
80
|
+
resolveHeaderParamsName(node) {
|
|
81
|
+
return this.resolveName(`${node.operationId} HeaderParams`)
|
|
82
|
+
},
|
|
83
|
+
resolveHeaderParamsTypedName(node) {
|
|
84
|
+
return this.resolveTypedName(`${node.operationId} HeaderParams`)
|
|
85
|
+
},
|
|
86
|
+
}
|
|
87
|
+
})
|