@kubb/plugin-ts 5.0.0-alpha.3 → 5.0.0-alpha.31

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.
Files changed (42) hide show
  1. package/dist/index.cjs +1806 -3
  2. package/dist/index.cjs.map +1 -0
  3. package/dist/index.d.ts +590 -4
  4. package/dist/index.js +1776 -2
  5. package/dist/index.js.map +1 -0
  6. package/package.json +7 -27
  7. package/src/components/Enum.tsx +82 -0
  8. package/src/components/Type.tsx +29 -162
  9. package/src/constants.ts +39 -0
  10. package/src/factory.ts +134 -49
  11. package/src/generators/typeGenerator.tsx +165 -428
  12. package/src/generators/typeGeneratorLegacy.tsx +349 -0
  13. package/src/index.ts +9 -1
  14. package/src/plugin.ts +98 -176
  15. package/src/presets.ts +28 -0
  16. package/src/printers/functionPrinter.ts +196 -0
  17. package/src/printers/printerTs.ts +310 -0
  18. package/src/resolvers/resolverTs.ts +66 -0
  19. package/src/resolvers/resolverTsLegacy.ts +60 -0
  20. package/src/types.ts +258 -98
  21. package/src/utils.ts +131 -0
  22. package/dist/components-CRjwjdyE.js +0 -725
  23. package/dist/components-CRjwjdyE.js.map +0 -1
  24. package/dist/components-DI0aTIBg.cjs +0 -978
  25. package/dist/components-DI0aTIBg.cjs.map +0 -1
  26. package/dist/components.cjs +0 -3
  27. package/dist/components.d.ts +0 -38
  28. package/dist/components.js +0 -2
  29. package/dist/generators.cjs +0 -4
  30. package/dist/generators.d.ts +0 -503
  31. package/dist/generators.js +0 -2
  32. package/dist/plugin-D5rCK1zO.cjs +0 -992
  33. package/dist/plugin-D5rCK1zO.cjs.map +0 -1
  34. package/dist/plugin-DmwgRHK8.js +0 -944
  35. package/dist/plugin-DmwgRHK8.js.map +0 -1
  36. package/dist/types-BpeKGgCn.d.ts +0 -170
  37. package/src/components/index.ts +0 -1
  38. package/src/components/v2/Type.tsx +0 -165
  39. package/src/generators/index.ts +0 -2
  40. package/src/generators/v2/typeGenerator.tsx +0 -196
  41. package/src/parser.ts +0 -396
  42. package/src/printer.ts +0 -244
@@ -0,0 +1,196 @@
1
+ import type { PrinterFactoryOptions } from '@kubb/ast'
2
+ import { createPrinterFactory } from '@kubb/ast'
3
+ import type { FunctionNode, FunctionNodeType, FunctionParameterNode, FunctionParametersNode, ParameterGroupNode, TypeNode } from '@kubb/ast/types'
4
+ import { PARAM_RANK } from '../constants.ts'
5
+
6
+ /**
7
+ * Maps each function-printer handler key to its concrete node type.
8
+ */
9
+ export type FunctionNodeByType = {
10
+ functionParameter: FunctionParameterNode
11
+ parameterGroup: ParameterGroupNode
12
+ functionParameters: FunctionParametersNode
13
+ type: TypeNode
14
+ }
15
+
16
+ const kindToHandlerKey = {
17
+ FunctionParameter: 'functionParameter',
18
+ ParameterGroup: 'parameterGroup',
19
+ FunctionParameters: 'functionParameters',
20
+ Type: 'type',
21
+ } satisfies Record<string, FunctionNodeType>
22
+
23
+ /**
24
+ * Creates a function-parameter printer factory.
25
+ *
26
+ * Uses `createPrinterFactory` and dispatches handlers by `node.kind`
27
+ * (for function nodes) rather than by `node.type` (for schema nodes).
28
+ */
29
+ export const defineFunctionPrinter = createPrinterFactory<FunctionNode, FunctionNodeType, FunctionNodeByType>((node) => kindToHandlerKey[node.kind])
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 = PrinterFactoryOptions<'functionParameters', FunctionPrinterOptions, string>
54
+
55
+ function rank(param: FunctionParameterNode | 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<FunctionParameterNode | ParameterGroupNode>): Array<FunctionParameterNode | ParameterGroupNode> {
67
+ return [...params].sort((a, b) => rank(a) - rank(b))
68
+ }
69
+
70
+ function sortChildParams(params: Array<FunctionParameterNode>): Array<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
+ type(node) {
97
+ if (node.variant === 'member') {
98
+ return `${node.base}['${node.key}']`
99
+ }
100
+ if (node.variant === 'struct') {
101
+ const parts = node.properties.map((p) => {
102
+ const typeStr = this.transform(p.type)
103
+ const key = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(p.name) ? p.name : JSON.stringify(p.name)
104
+ return p.optional ? `${key}?: ${typeStr}` : `${key}: ${typeStr}`
105
+ })
106
+ return `{ ${parts.join('; ')} }`
107
+ }
108
+ if (node.variant === 'reference') {
109
+ return node.name
110
+ }
111
+ return null
112
+ },
113
+ functionParameter(node) {
114
+ const { mode, transformName, transformType } = this.options
115
+ const name = transformName ? transformName(node.name) : node.name
116
+
117
+ const rawType = node.type ? this.transform(node.type) : undefined
118
+ const type = rawType != null && transformType ? transformType(rawType) : rawType
119
+
120
+ if (mode === 'keys' || mode === 'values') {
121
+ return node.rest ? `...${name}` : name
122
+ }
123
+
124
+ if (mode === 'call') {
125
+ return node.rest ? `...${name}` : name
126
+ }
127
+
128
+ if (node.rest) {
129
+ return type ? `...${name}: ${type}` : `...${name}`
130
+ }
131
+ if (type) {
132
+ if (node.optional) return `${name}?: ${type}`
133
+ return node.default ? `${name}: ${type} = ${node.default}` : `${name}: ${type}`
134
+ }
135
+ return node.default ? `${name} = ${node.default}` : name
136
+ },
137
+ parameterGroup(node) {
138
+ const { mode, transformName, transformType } = this.options
139
+ const sorted = sortChildParams(node.properties)
140
+ const isOptional = node.optional ?? sorted.every((p) => p.optional || p.default !== undefined)
141
+
142
+ if (node.inline) {
143
+ return sorted
144
+ .map((p) => this.transform(p))
145
+ .filter(Boolean)
146
+ .join(', ')
147
+ }
148
+
149
+ if (mode === 'keys' || mode === 'values') {
150
+ const keys = sorted.map((p) => p.name).join(', ')
151
+ return `{ ${keys} }`
152
+ }
153
+
154
+ if (mode === 'call') {
155
+ const keys = sorted.map((p) => p.name).join(', ')
156
+ return `{ ${keys} }`
157
+ }
158
+
159
+ const names = sorted.map((p) => {
160
+ const n = transformName ? transformName(p.name) : p.name
161
+
162
+ return n
163
+ })
164
+
165
+ const nameStr = names.length ? `{ ${names.join(', ')} }` : undefined
166
+ if (!nameStr) return null
167
+
168
+ let typeAnnotation: string | undefined = node.type ? (this.transform(node.type) ?? undefined) : undefined
169
+ if (!typeAnnotation) {
170
+ const typeParts = sorted
171
+ .filter((p) => p.type)
172
+ .map((p) => {
173
+ const rawT = p.type ? this.transform(p.type) : undefined
174
+ const t = rawT != null && transformType ? transformType(rawT) : rawT
175
+ return p.optional || p.default !== undefined ? `${p.name}?: ${t}` : `${p.name}: ${t}`
176
+ })
177
+ typeAnnotation = typeParts.length ? `{ ${typeParts.join('; ')} }` : undefined
178
+ }
179
+
180
+ if (typeAnnotation) {
181
+ if (isOptional) return `${nameStr}: ${typeAnnotation} = ${node.default ?? '{}'}`
182
+ return node.default ? `${nameStr}: ${typeAnnotation} = ${node.default}` : `${nameStr}: ${typeAnnotation}`
183
+ }
184
+
185
+ return node.default ? `${nameStr} = ${node.default}` : nameStr
186
+ },
187
+ functionParameters(node) {
188
+ const sorted = sortParams(node.params)
189
+
190
+ return sorted
191
+ .map((p) => this.transform(p))
192
+ .filter(Boolean)
193
+ .join(', ')
194
+ },
195
+ },
196
+ }))
@@ -0,0 +1,310 @@
1
+ import { extractRefName, isStringType, narrowSchema, schemaTypes, syncSchemaRef } from '@kubb/ast'
2
+ import type { PrinterFactoryOptions, PrinterPartial } from '@kubb/core'
3
+ import { definePrinter } from '@kubb/core'
4
+ import { safePrint } from '@kubb/fabric-core/parsers/typescript'
5
+ import type ts from 'typescript'
6
+ import { ENUM_TYPES_WITH_KEY_SUFFIX, OPTIONAL_ADDS_QUESTION_TOKEN, OPTIONAL_ADDS_UNDEFINED } from '../constants.ts'
7
+ import * as factory from '../factory.ts'
8
+ import type { PluginTs, ResolverTs } from '../types.ts'
9
+ import { buildPropertyJSDocComments } from '../utils.ts'
10
+
11
+ /**
12
+ * Partial map of node-type overrides for the TypeScript printer.
13
+ *
14
+ * Each key is a `SchemaType` string (e.g. `'date'`, `'string'`). The function
15
+ * replaces the built-in handler for that node type. Use `this.transform` to
16
+ * recurse into nested schema nodes, and `this.options` to read printer options.
17
+ *
18
+ * @example Override the `date` handler
19
+ * ```ts
20
+ * pluginTs({
21
+ * printer: {
22
+ * nodes: {
23
+ * date(node) {
24
+ * return ts.factory.createTypeReferenceNode('Date', [])
25
+ * },
26
+ * },
27
+ * },
28
+ * })
29
+ * ```
30
+ */
31
+ export type PrinterTsNodes = PrinterPartial<ts.TypeNode, PrinterTsOptions>
32
+
33
+ export type PrinterTsOptions = {
34
+ /**
35
+ * @default `'questionToken'`
36
+ */
37
+ optionalType: PluginTs['resolvedOptions']['optionalType']
38
+ /**
39
+ * @default `'array'`
40
+ */
41
+ arrayType: PluginTs['resolvedOptions']['arrayType']
42
+ /**
43
+ * @default `'inlineLiteral'`
44
+ */
45
+ enumType: PluginTs['resolvedOptions']['enumType']
46
+ /**
47
+ * Suffix appended to the generated type alias name when `enumType` is `asConst` or `asPascalConst`.
48
+ *
49
+ * @default `'Key'`
50
+ */
51
+ enumTypeSuffix?: PluginTs['resolvedOptions']['enumTypeSuffix']
52
+ /**
53
+ * Controls whether a `type` alias or `interface` declaration is emitted.
54
+ * @default `'type'`
55
+ */
56
+ syntaxType?: PluginTs['resolvedOptions']['syntaxType']
57
+ /**
58
+ * When set, `printer.print(node)` produces a full `type Name = …` declaration.
59
+ * When omitted, `printer.print(node)` returns the raw type node.
60
+ */
61
+ name?: string
62
+
63
+ /**
64
+ * JSDoc `@description` comment added to the generated type or interface declaration.
65
+ */
66
+ description?: string
67
+ /**
68
+ * Property keys to exclude from the generated type via `Omit<Type, Keys>`.
69
+ * Forces type-alias syntax even when `syntaxType` is `'interface'`.
70
+ */
71
+ keysToOmit?: Array<string>
72
+ /**
73
+ * Resolver used to transform raw schema names into valid TypeScript identifiers.
74
+ */
75
+ resolver: ResolverTs
76
+ /**
77
+ * Names of top-level schemas that are enums.
78
+ * When set, the `ref` handler uses the suffixed type name (e.g. `StatusKey`) for enum refs
79
+ * instead of the plain PascalCase name, so imports align with what the enum file actually exports.
80
+ */
81
+ enumSchemaNames?: Set<string>
82
+ /**
83
+ * Partial map of node-type overrides. Each entry replaces the built-in handler for that node type.
84
+ */
85
+ nodes?: PrinterTsNodes
86
+ }
87
+
88
+ /**
89
+ * TypeScript printer factory options: maps `SchemaNode` → `ts.TypeNode` (raw) or `ts.Node` (full declaration).
90
+ */
91
+ export type PrinterTsFactory = PrinterFactoryOptions<'typescript', PrinterTsOptions, ts.TypeNode, string>
92
+
93
+ type PrinterTs = PrinterTsFactory
94
+
95
+ /**
96
+ * TypeScript type printer built with `definePrinter`.
97
+ *
98
+ * Converts a `SchemaNode` AST node into a TypeScript AST node:
99
+ * - **`printer.print(node)`** — when `options.typeName` is set, returns a full
100
+ * `type Name = …` or `interface Name { … }` declaration (`ts.Node`).
101
+ * Without `typeName`, returns the raw `ts.TypeNode` for the schema.
102
+ *
103
+ * Dispatches on `node.type` to the appropriate handler in `nodes`. Options are closed
104
+ * over per printer instance, so each call to `printerTs(options)` produces an independent printer.
105
+ *
106
+ * @example Raw type node (no `typeName`)
107
+ * ```ts
108
+ * const printer = printerTs({ optionalType: 'questionToken', arrayType: 'array', enumType: 'inlineLiteral' })
109
+ * const typeNode = printer.print(schemaNode) // ts.TypeNode
110
+ * ```
111
+ *
112
+ * @example Full declaration (with `typeName`)
113
+ * ```ts
114
+ * const printer = printerTs({ optionalType: 'questionToken', arrayType: 'array', enumType: 'inlineLiteral', typeName: 'MyType' })
115
+ * const declaration = printer.print(schemaNode) // ts.TypeAliasDeclaration | ts.InterfaceDeclaration
116
+ * ```
117
+ */
118
+ export const printerTs = definePrinter<PrinterTs>((options) => {
119
+ const addsUndefined = OPTIONAL_ADDS_UNDEFINED.has(options.optionalType)
120
+
121
+ return {
122
+ name: 'typescript',
123
+ options,
124
+ nodes: {
125
+ any: () => factory.keywordTypeNodes.any,
126
+ unknown: () => factory.keywordTypeNodes.unknown,
127
+ void: () => factory.keywordTypeNodes.void,
128
+ never: () => factory.keywordTypeNodes.never,
129
+ boolean: () => factory.keywordTypeNodes.boolean,
130
+ null: () => factory.keywordTypeNodes.null,
131
+ blob: () => factory.createTypeReferenceNode('Blob', []),
132
+ string: () => factory.keywordTypeNodes.string,
133
+ uuid: () => factory.keywordTypeNodes.string,
134
+ email: () => factory.keywordTypeNodes.string,
135
+ url: (node) => {
136
+ if (node.path) {
137
+ return factory.createUrlTemplateType(node.path)
138
+ }
139
+ return factory.keywordTypeNodes.string
140
+ },
141
+ ipv4: () => factory.keywordTypeNodes.string,
142
+ ipv6: () => factory.keywordTypeNodes.string,
143
+ datetime: () => factory.keywordTypeNodes.string,
144
+ number: () => factory.keywordTypeNodes.number,
145
+ integer: () => factory.keywordTypeNodes.number,
146
+ bigint: () => factory.keywordTypeNodes.bigint,
147
+ date: factory.dateOrStringNode,
148
+ time: factory.dateOrStringNode,
149
+ ref(node) {
150
+ if (!node.name) {
151
+ return undefined
152
+ }
153
+ // Parser-generated refs (with $ref) carry raw schema names that need resolving.
154
+ // Use the canonical name from the $ref path — node.name may have been overridden
155
+ // (e.g. by single-member allOf flatten using the property-derived child name).
156
+ // Inline refs (without $ref) from utils already carry resolved type names.
157
+ const refName = node.ref ? (extractRefName(node.ref) ?? node.name) : node.name
158
+
159
+ // When a Key suffix is configured, enum refs must use the suffixed name (e.g. `StatusKey`)
160
+ // so the reference matches what the enum file actually exports.
161
+ const isEnumRef =
162
+ node.ref && ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.enumType) && this.options.enumTypeSuffix && this.options.enumSchemaNames?.has(refName)
163
+
164
+ const name = isEnumRef
165
+ ? this.options.resolver.resolveEnumKeyName({ name: refName }, this.options.enumTypeSuffix!)
166
+ : node.ref
167
+ ? this.options.resolver.default(refName, 'type')
168
+ : refName
169
+
170
+ return factory.createTypeReferenceNode(name, undefined)
171
+ },
172
+ enum(node) {
173
+ const values = node.namedEnumValues?.map((v) => v.value) ?? node.enumValues ?? []
174
+
175
+ if (this.options.enumType === 'inlineLiteral' || !node.name) {
176
+ const literalNodes = values
177
+ .filter((v): v is string | number | boolean => v !== null && v !== undefined)
178
+ .map((value) => factory.constToTypeNode(value, typeof value as 'string' | 'number' | 'boolean'))
179
+ .filter(Boolean)
180
+
181
+ return factory.createUnionDeclaration({ withParentheses: true, nodes: literalNodes }) ?? undefined
182
+ }
183
+
184
+ const resolvedName =
185
+ ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.enumType) && this.options.enumTypeSuffix
186
+ ? this.options.resolver.resolveEnumKeyName(node, this.options.enumTypeSuffix)
187
+ : this.options.resolver.default(node.name, 'type')
188
+
189
+ return factory.createTypeReferenceNode(resolvedName, undefined)
190
+ },
191
+ union(node) {
192
+ const members = node.members ?? []
193
+
194
+ const hasStringLiteral = members.some((m) => {
195
+ const enumNode = narrowSchema(m, schemaTypes.enum)
196
+ return enumNode?.primitive === 'string'
197
+ })
198
+ const hasPlainString = members.some((m) => isStringType(m))
199
+
200
+ if (hasStringLiteral && hasPlainString) {
201
+ const memberNodes = members
202
+ .map((m) => {
203
+ if (isStringType(m)) {
204
+ return factory.createIntersectionDeclaration({
205
+ nodes: [factory.keywordTypeNodes.string, factory.createTypeLiteralNode([])],
206
+ withParentheses: true,
207
+ })
208
+ }
209
+
210
+ return this.transform(m)
211
+ })
212
+ .filter(Boolean)
213
+
214
+ return factory.createUnionDeclaration({ withParentheses: true, nodes: memberNodes }) ?? undefined
215
+ }
216
+
217
+ return factory.createUnionDeclaration({ withParentheses: true, nodes: factory.buildMemberNodes(members, this.transform) }) ?? undefined
218
+ },
219
+ intersection(node) {
220
+ return factory.createIntersectionDeclaration({ withParentheses: true, nodes: factory.buildMemberNodes(node.members, this.transform) }) ?? undefined
221
+ },
222
+ array(node) {
223
+ const itemNodes = (node.items ?? []).map((item) => this.transform(item)).filter(Boolean)
224
+
225
+ return factory.createArrayDeclaration({ nodes: itemNodes, arrayType: this.options.arrayType }) ?? undefined
226
+ },
227
+ tuple(node) {
228
+ return factory.buildTupleNode(node, this.transform)
229
+ },
230
+ object(node) {
231
+ const { transform, options } = this
232
+
233
+ const addsQuestionToken = OPTIONAL_ADDS_QUESTION_TOKEN.has(options.optionalType)
234
+
235
+ const propertyNodes: Array<ts.TypeElement> = node.properties.map((prop) => {
236
+ const baseType = transform(prop.schema) ?? factory.keywordTypeNodes.unknown
237
+ const type = factory.buildPropertyType(prop.schema, baseType, options.optionalType)
238
+ const propMeta = syncSchemaRef(prop.schema)
239
+
240
+ const propertyNode = factory.createPropertySignature({
241
+ questionToken: prop.schema.optional || prop.schema.nullish ? addsQuestionToken : false,
242
+ name: prop.name,
243
+ type,
244
+ readOnly: propMeta?.readOnly,
245
+ })
246
+
247
+ return factory.appendJSDocToNode({ node: propertyNode, comments: buildPropertyJSDocComments(prop.schema) })
248
+ })
249
+
250
+ const allElements = [...propertyNodes, ...factory.buildIndexSignatures(node, propertyNodes.length, transform)]
251
+
252
+ if (!allElements.length) {
253
+ return factory.keywordTypeNodes.object
254
+ }
255
+
256
+ return factory.createTypeLiteralNode(allElements)
257
+ },
258
+ ...options.nodes,
259
+ },
260
+ print(node) {
261
+ const { name, syntaxType = 'type', description, keysToOmit } = this.options
262
+
263
+ let base = this.transform(node)
264
+ if (!base) return null
265
+
266
+ // For ref nodes, structural metadata lives on node.schema rather than the ref node itself.
267
+ const meta = syncSchemaRef(node)
268
+
269
+ // Without name, apply modifiers inline and return.
270
+ if (!name) {
271
+ if (meta.nullable) {
272
+ base = factory.createUnionDeclaration({ nodes: [base, factory.keywordTypeNodes.null] })
273
+ }
274
+ if ((meta.nullish || meta.optional) && addsUndefined) {
275
+ base = factory.createUnionDeclaration({ nodes: [base, factory.keywordTypeNodes.undefined] })
276
+ }
277
+ return safePrint(base)
278
+ }
279
+
280
+ // When keysToOmit is present, wrap with Omit first, then apply nullable/optional
281
+ // modifiers so they are not swallowed by NonNullable inside createOmitDeclaration.
282
+ let inner: ts.TypeNode = keysToOmit?.length ? factory.createOmitDeclaration({ keys: keysToOmit, type: base, nonNullable: true }) : base
283
+
284
+ if (meta.nullable) {
285
+ inner = factory.createUnionDeclaration({ nodes: [inner, factory.keywordTypeNodes.null] })
286
+ }
287
+
288
+ // For named type declarations (type aliases), optional/nullish always produces | undefined
289
+ // regardless of optionalType — the questionToken ? modifier only applies to object properties.
290
+ if (meta.nullish || meta.optional) {
291
+ inner = factory.createUnionDeclaration({ nodes: [inner, factory.keywordTypeNodes.undefined] })
292
+ }
293
+
294
+ const useTypeGeneration = syntaxType === 'type' || inner.kind === factory.syntaxKind.union || !!keysToOmit?.length
295
+
296
+ const typeNode = factory.createTypeDeclaration({
297
+ name,
298
+ isExportable: true,
299
+ type: inner,
300
+ syntax: useTypeGeneration ? 'type' : 'interface',
301
+ comments: buildPropertyJSDocComments({
302
+ ...meta,
303
+ description,
304
+ }),
305
+ })
306
+
307
+ return safePrint(typeNode)
308
+ },
309
+ }
310
+ })
@@ -0,0 +1,66 @@
1
+ import { pascalCase } from '@internals/utils'
2
+ import { defineResolver } from '@kubb/core'
3
+ import type { PluginTs } from '../types.ts'
4
+
5
+ /**
6
+ * Resolver for `@kubb/plugin-ts` that provides the default naming and path-resolution
7
+ * helpers used by the plugin. Import this in other plugins to resolve the exact names and
8
+ * paths that `plugin-ts` generates without hardcoding the conventions.
9
+ *
10
+ * The `default` method is automatically injected by `defineResolver` — it uses `camelCase`
11
+ * for identifiers/files and `pascalCase` for type names.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * import { resolver } from '@kubb/plugin-ts'
16
+ *
17
+ * resolver.default('list pets', 'type') // → 'ListPets'
18
+ * resolver.resolveName('list pets status 200') // → 'ListPetsStatus200'
19
+ * resolver.resolvePathName('list pets', 'file') // → 'listPets'
20
+ * ```
21
+ */
22
+ export const resolverTs = defineResolver<PluginTs>(() => {
23
+ return {
24
+ name: 'default',
25
+ pluginName: 'plugin-ts',
26
+ default(name, type) {
27
+ return pascalCase(name, { isFile: type === 'file' })
28
+ },
29
+ resolveTypeName(name) {
30
+ return pascalCase(name)
31
+ },
32
+ resolvePathName(name, type) {
33
+ return pascalCase(name, { isFile: type === 'file' })
34
+ },
35
+ resolveParamName(node, param) {
36
+ return this.resolveTypeName(`${node.operationId} ${param.in} ${param.name}`)
37
+ },
38
+ resolveResponseStatusName(node, statusCode) {
39
+ return this.resolveTypeName(`${node.operationId} Status ${statusCode}`)
40
+ },
41
+ resolveDataName(node) {
42
+ return this.resolveTypeName(`${node.operationId} Data`)
43
+ },
44
+ resolveRequestConfigName(node) {
45
+ return this.resolveTypeName(`${node.operationId} RequestConfig`)
46
+ },
47
+ resolveResponsesName(node) {
48
+ return this.resolveTypeName(`${node.operationId} Responses`)
49
+ },
50
+ resolveResponseName(node) {
51
+ return this.resolveTypeName(`${node.operationId} Response`)
52
+ },
53
+ resolveEnumKeyName(node, enumTypeSuffix = 'key') {
54
+ return `${this.resolveTypeName(node.name ?? '')}${enumTypeSuffix}`
55
+ },
56
+ resolvePathParamsName(node, param) {
57
+ return this.resolveParamName(node, param)
58
+ },
59
+ resolveQueryParamsName(node, param) {
60
+ return this.resolveParamName(node, param)
61
+ },
62
+ resolveHeaderParamsName(node, param) {
63
+ return this.resolveParamName(node, param)
64
+ },
65
+ }
66
+ })
@@ -0,0 +1,60 @@
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
+ pluginName: 'plugin-ts',
32
+ resolveResponseStatusName(node, statusCode) {
33
+ if (statusCode === 'default') {
34
+ return this.resolveTypeName(`${node.operationId} Error`)
35
+ }
36
+ return this.resolveTypeName(`${node.operationId} ${statusCode}`)
37
+ },
38
+ resolveDataName(node) {
39
+ const suffix = node.method === 'GET' ? 'QueryRequest' : 'MutationRequest'
40
+ return this.resolveTypeName(`${node.operationId} ${suffix}`)
41
+ },
42
+ resolveResponsesName(node) {
43
+ const suffix = node.method === 'GET' ? 'Query' : 'Mutation'
44
+ return this.resolveTypeName(`${node.operationId} ${suffix}`)
45
+ },
46
+ resolveResponseName(node) {
47
+ const suffix = node.method === 'GET' ? 'QueryResponse' : 'MutationResponse'
48
+ return this.resolveTypeName(`${node.operationId} ${suffix}`)
49
+ },
50
+ resolvePathParamsName(node, _param) {
51
+ return this.resolveTypeName(`${node.operationId} PathParams`)
52
+ },
53
+ resolveQueryParamsName(node, _param) {
54
+ return this.resolveTypeName(`${node.operationId} QueryParams`)
55
+ },
56
+ resolveHeaderParamsName(node, _param) {
57
+ return this.resolveTypeName(`${node.operationId} HeaderParams`)
58
+ },
59
+ }
60
+ })