@kubb/plugin-ts 5.0.0-alpha.9 → 5.0.0-beta.3

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 (43) hide show
  1. package/LICENSE +17 -10
  2. package/README.md +1 -3
  3. package/dist/index.cjs +1452 -5
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.ts +558 -27
  6. package/dist/index.js +1418 -2
  7. package/dist/index.js.map +1 -0
  8. package/package.json +44 -64
  9. package/src/components/{v2/Enum.tsx → Enum.tsx} +33 -17
  10. package/src/components/Type.tsx +31 -161
  11. package/src/constants.ts +10 -0
  12. package/src/factory.ts +283 -35
  13. package/src/generators/typeGenerator.tsx +189 -424
  14. package/src/index.ts +9 -3
  15. package/src/plugin.ts +66 -205
  16. package/src/printers/functionPrinter.ts +197 -0
  17. package/src/printers/printerTs.ts +325 -0
  18. package/src/resolvers/resolverTs.ts +66 -0
  19. package/src/types.ts +233 -221
  20. package/src/utils.ts +130 -0
  21. package/dist/components-CRu8IKY3.js +0 -729
  22. package/dist/components-CRu8IKY3.js.map +0 -1
  23. package/dist/components-DeNDKlzf.cjs +0 -982
  24. package/dist/components-DeNDKlzf.cjs.map +0 -1
  25. package/dist/components.cjs +0 -3
  26. package/dist/components.d.ts +0 -36
  27. package/dist/components.js +0 -2
  28. package/dist/generators.cjs +0 -4
  29. package/dist/generators.d.ts +0 -509
  30. package/dist/generators.js +0 -2
  31. package/dist/plugin-BZkBwnEA.js +0 -1269
  32. package/dist/plugin-BZkBwnEA.js.map +0 -1
  33. package/dist/plugin-Bunz1oGa.cjs +0 -1322
  34. package/dist/plugin-Bunz1oGa.cjs.map +0 -1
  35. package/dist/types-mSXmB8WU.d.ts +0 -298
  36. package/src/components/index.ts +0 -1
  37. package/src/components/v2/Type.tsx +0 -59
  38. package/src/generators/index.ts +0 -2
  39. package/src/generators/v2/typeGenerator.tsx +0 -167
  40. package/src/generators/v2/utils.ts +0 -140
  41. package/src/parser.ts +0 -389
  42. package/src/printer.ts +0 -368
  43. package/src/resolverTs.ts +0 -77
@@ -0,0 +1,325 @@
1
+ import { ast } from '@kubb/core'
2
+ import { safePrint } from '@kubb/parser-ts'
3
+ import type ts from 'typescript'
4
+ import { ENUM_TYPES_WITH_KEY_SUFFIX, OPTIONAL_ADDS_QUESTION_TOKEN, OPTIONAL_ADDS_UNDEFINED } from '../constants.ts'
5
+ import * as factory from '../factory.ts'
6
+ import type { PluginTs, ResolverTs } from '../types.ts'
7
+ import { buildPropertyJSDocComments } from '../utils.ts'
8
+
9
+ /**
10
+ * Partial map of node-type overrides for the TypeScript printer.
11
+ *
12
+ * Each key is a `SchemaType` string (e.g. `'date'`, `'string'`). The function
13
+ * replaces the built-in handler for that node type. Use `this.transform` to
14
+ * recurse into nested schema nodes, and `this.options` to read printer options.
15
+ *
16
+ * @example Override the `date` handler
17
+ * ```ts
18
+ * pluginTs({
19
+ * printer: {
20
+ * nodes: {
21
+ * date(node) {
22
+ * return ts.factory.createTypeReferenceNode('Date', [])
23
+ * },
24
+ * },
25
+ * },
26
+ * })
27
+ * ```
28
+ */
29
+ export type PrinterTsNodes = ast.PrinterPartial<ts.TypeNode, PrinterTsOptions>
30
+
31
+ export type PrinterTsOptions = {
32
+ /**
33
+ * Mark parameters as optional with `?` or `| undefined`.
34
+ * - `'questionToken'` adds `?` to properties
35
+ * - `'undefined'` adds `| undefined` to types
36
+ *
37
+ * @default `'questionToken'`
38
+ */
39
+ optionalType: PluginTs['resolvedOptions']['optionalType']
40
+ /**
41
+ * Array representation style.
42
+ * - `'array'` uses bracket notation (`T[]`)
43
+ * - `'generic'` uses generic syntax (`Array<T>`)
44
+ *
45
+ * @default `'array'`
46
+ */
47
+ arrayType: PluginTs['resolvedOptions']['arrayType']
48
+ /**
49
+ * Enum output format.
50
+ * - `'inlineLiteral'` embeds literal unions inline
51
+ * - `'asPascalConst'` generates named const unions
52
+ * - `'asConst'` generates as const declarations
53
+ *
54
+ * @default `'inlineLiteral'`
55
+ */
56
+ enumType: PluginTs['resolvedOptions']['enumType']
57
+ /**
58
+ * Suffix appended to enum key reference names.
59
+ *
60
+ * @example Enum key naming
61
+ * `StatusKey` when `enumType` is `'asConst'`
62
+ *
63
+ * @default `'Key'`
64
+ */
65
+ enumTypeSuffix?: PluginTs['resolvedOptions']['enumTypeSuffix']
66
+ /**
67
+ * Syntax for generated declarations.
68
+ * - `'type'` generates type aliases
69
+ * - `'interface'` generates interface declarations
70
+ *
71
+ * @default `'type'`
72
+ */
73
+ syntaxType?: PluginTs['resolvedOptions']['syntaxType']
74
+ /**
75
+ * Exported name for the type declaration.
76
+ * When omitted, returns only the raw type node.
77
+ */
78
+ name?: string
79
+
80
+ /**
81
+ * JSDoc comment to attach to the generated type.
82
+ */
83
+ description?: string
84
+ /**
85
+ * Properties to exclude using `Omit<Type, Keys>`.
86
+ * Forces type alias syntax regardless of `syntaxType` setting.
87
+ */
88
+ keysToOmit?: Array<string>
89
+ /**
90
+ * Transforms raw schema names into valid TypeScript identifiers.
91
+ */
92
+ resolver: ResolverTs
93
+ /**
94
+ * Schema names that represent enums for suffixed key references.
95
+ */
96
+ enumSchemaNames?: Set<string>
97
+ /**
98
+ * Custom handler map for node type overrides.
99
+ */
100
+ nodes?: PrinterTsNodes
101
+ }
102
+
103
+ /**
104
+ * TypeScript printer factory options: maps `SchemaNode` → `ts.TypeNode` (raw) or `ts.Node` (full declaration).
105
+ */
106
+ export type PrinterTsFactory = ast.PrinterFactoryOptions<'typescript', PrinterTsOptions, ts.TypeNode, string>
107
+
108
+ type PrinterTs = PrinterTsFactory
109
+
110
+ /**
111
+ * TypeScript type printer built with `definePrinter`.
112
+ *
113
+ * Converts a `SchemaNode` AST node into a TypeScript AST node:
114
+ * - **`printer.print(node)`** — when `options.typeName` is set, returns a full
115
+ * `type Name = …` or `interface Name { … }` declaration (`ts.Node`).
116
+ * Without `typeName`, returns the raw `ts.TypeNode` for the schema.
117
+ *
118
+ * Dispatches on `node.type` to the appropriate handler in `nodes`. Options are closed
119
+ * over per printer instance, so each call to `printerTs(options)` produces an independent printer.
120
+ *
121
+ * @example Raw type node (no `typeName`)
122
+ * ```ts
123
+ * const printer = printerTs({ optionalType: 'questionToken', arrayType: 'array', enumType: 'inlineLiteral' })
124
+ * const typeNode = printer.print(schemaNode) // ts.TypeNode
125
+ * ```
126
+ *
127
+ * @example Full declaration (with `typeName`)
128
+ * ```ts
129
+ * const printer = printerTs({ optionalType: 'questionToken', arrayType: 'array', enumType: 'inlineLiteral', typeName: 'MyType' })
130
+ * const declaration = printer.print(schemaNode) // ts.TypeAliasDeclaration | ts.InterfaceDeclaration
131
+ * ```
132
+ */
133
+ export const printerTs = ast.definePrinter<PrinterTs>((options) => {
134
+ const addsUndefined = OPTIONAL_ADDS_UNDEFINED.has(options.optionalType)
135
+
136
+ return {
137
+ name: 'typescript',
138
+ options,
139
+ nodes: {
140
+ any: () => factory.keywordTypeNodes.any,
141
+ unknown: () => factory.keywordTypeNodes.unknown,
142
+ void: () => factory.keywordTypeNodes.void,
143
+ never: () => factory.keywordTypeNodes.never,
144
+ boolean: () => factory.keywordTypeNodes.boolean,
145
+ null: () => factory.keywordTypeNodes.null,
146
+ blob: () => factory.createTypeReferenceNode('Blob', []),
147
+ string: () => factory.keywordTypeNodes.string,
148
+ uuid: () => factory.keywordTypeNodes.string,
149
+ email: () => factory.keywordTypeNodes.string,
150
+ url: (node) => {
151
+ if (node.path) {
152
+ return factory.createUrlTemplateType(node.path)
153
+ }
154
+ return factory.keywordTypeNodes.string
155
+ },
156
+ ipv4: () => factory.keywordTypeNodes.string,
157
+ ipv6: () => factory.keywordTypeNodes.string,
158
+ datetime: () => factory.keywordTypeNodes.string,
159
+ number: () => factory.keywordTypeNodes.number,
160
+ integer: () => factory.keywordTypeNodes.number,
161
+ bigint: () => factory.keywordTypeNodes.bigint,
162
+ date: factory.dateOrStringNode,
163
+ time: factory.dateOrStringNode,
164
+ ref(node) {
165
+ if (!node.name) {
166
+ return undefined
167
+ }
168
+ // Parser-generated refs (with $ref) carry raw schema names that need resolving.
169
+ // Use the canonical name from the $ref path — node.name may have been overridden
170
+ // (e.g. by single-member allOf flatten using the property-derived child name).
171
+ // Inline refs (without $ref) from utils already carry resolved type names.
172
+ const refName = node.ref ? (ast.extractRefName(node.ref) ?? node.name) : node.name
173
+
174
+ // When a Key suffix is configured, enum refs must use the suffixed name (e.g. `StatusKey`)
175
+ // so the reference matches what the enum file actually exports.
176
+ const isEnumRef =
177
+ node.ref && ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.enumType) && this.options.enumTypeSuffix && this.options.enumSchemaNames?.has(refName)
178
+
179
+ const name = isEnumRef
180
+ ? this.options.resolver.resolveEnumKeyName({ name: refName }, this.options.enumTypeSuffix!)
181
+ : node.ref
182
+ ? this.options.resolver.default(refName, 'type')
183
+ : refName
184
+
185
+ return factory.createTypeReferenceNode(name, undefined)
186
+ },
187
+ enum(node) {
188
+ const values = node.namedEnumValues?.map((v) => v.value) ?? node.enumValues ?? []
189
+
190
+ if (this.options.enumType === 'inlineLiteral' || !node.name) {
191
+ const literalNodes = values
192
+ .filter((v): v is string | number | boolean => v !== null && v !== undefined)
193
+ .map((value) => factory.constToTypeNode(value, typeof value as 'string' | 'number' | 'boolean'))
194
+ .filter(Boolean)
195
+
196
+ return factory.createUnionDeclaration({ withParentheses: true, nodes: literalNodes }) ?? undefined
197
+ }
198
+
199
+ const resolvedName =
200
+ ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.enumType) && this.options.enumTypeSuffix
201
+ ? this.options.resolver.resolveEnumKeyName(node, this.options.enumTypeSuffix)
202
+ : this.options.resolver.default(node.name, 'type')
203
+
204
+ return factory.createTypeReferenceNode(resolvedName, undefined)
205
+ },
206
+ union(node) {
207
+ const members = node.members ?? []
208
+
209
+ const hasStringLiteral = members.some((m) => {
210
+ const enumNode = ast.narrowSchema(m, ast.schemaTypes.enum)
211
+ return enumNode?.primitive === 'string'
212
+ })
213
+ const hasPlainString = members.some((m) => ast.isStringType(m))
214
+
215
+ if (hasStringLiteral && hasPlainString) {
216
+ const memberNodes = members
217
+ .map((m) => {
218
+ if (ast.isStringType(m)) {
219
+ return factory.createIntersectionDeclaration({
220
+ nodes: [factory.keywordTypeNodes.string, factory.createTypeLiteralNode([])],
221
+ withParentheses: true,
222
+ })
223
+ }
224
+
225
+ return this.transform(m)
226
+ })
227
+ .filter(Boolean)
228
+
229
+ return factory.createUnionDeclaration({ withParentheses: true, nodes: memberNodes }) ?? undefined
230
+ }
231
+
232
+ return factory.createUnionDeclaration({ withParentheses: true, nodes: factory.buildMemberNodes(members, this.transform) }) ?? undefined
233
+ },
234
+ intersection(node) {
235
+ return factory.createIntersectionDeclaration({ withParentheses: true, nodes: factory.buildMemberNodes(node.members, this.transform) }) ?? undefined
236
+ },
237
+ array(node) {
238
+ const itemNodes = (node.items ?? []).map((item) => this.transform(item)).filter(Boolean)
239
+
240
+ return factory.createArrayDeclaration({ nodes: itemNodes, arrayType: this.options.arrayType }) ?? undefined
241
+ },
242
+ tuple(node) {
243
+ return factory.buildTupleNode(node, this.transform)
244
+ },
245
+ object(node) {
246
+ const { transform, options } = this
247
+
248
+ const addsQuestionToken = OPTIONAL_ADDS_QUESTION_TOKEN.has(options.optionalType)
249
+
250
+ const propertyNodes: Array<ts.TypeElement> = node.properties.map((prop) => {
251
+ const baseType = transform(prop.schema) ?? factory.keywordTypeNodes.unknown
252
+ const type = factory.buildPropertyType(prop.schema, baseType, options.optionalType)
253
+ const propMeta = ast.syncSchemaRef(prop.schema)
254
+
255
+ const propertyNode = factory.createPropertySignature({
256
+ questionToken: prop.schema.optional || prop.schema.nullish ? addsQuestionToken : false,
257
+ name: prop.name,
258
+ type,
259
+ readOnly: propMeta?.readOnly,
260
+ })
261
+
262
+ return factory.appendJSDocToNode({ node: propertyNode, comments: buildPropertyJSDocComments(prop.schema) })
263
+ })
264
+
265
+ const allElements = [...propertyNodes, ...factory.buildIndexSignatures(node, propertyNodes.length, transform)]
266
+
267
+ if (!allElements.length) {
268
+ return factory.keywordTypeNodes.object
269
+ }
270
+
271
+ return factory.createTypeLiteralNode(allElements)
272
+ },
273
+ ...options.nodes,
274
+ },
275
+ print(node) {
276
+ const { name, syntaxType = 'type', description, keysToOmit } = this.options
277
+
278
+ let base = this.transform(node)
279
+ if (!base) return null
280
+
281
+ // For ref nodes, structural metadata lives on node.schema rather than the ref node itself.
282
+ const meta = ast.syncSchemaRef(node)
283
+
284
+ // Without name, apply modifiers inline and return.
285
+ if (!name) {
286
+ if (meta.nullable) {
287
+ base = factory.createUnionDeclaration({ nodes: [base, factory.keywordTypeNodes.null] })
288
+ }
289
+ if ((meta.nullish || meta.optional) && addsUndefined) {
290
+ base = factory.createUnionDeclaration({ nodes: [base, factory.keywordTypeNodes.undefined] })
291
+ }
292
+ return safePrint(base)
293
+ }
294
+
295
+ // When keysToOmit is present, wrap with Omit first, then apply nullable/optional
296
+ // modifiers so they are not swallowed by NonNullable inside createOmitDeclaration.
297
+ let inner: ts.TypeNode = keysToOmit?.length ? factory.createOmitDeclaration({ keys: keysToOmit, type: base, nonNullable: true }) : base
298
+
299
+ if (meta.nullable) {
300
+ inner = factory.createUnionDeclaration({ nodes: [inner, factory.keywordTypeNodes.null] })
301
+ }
302
+
303
+ // For named type declarations (type aliases), optional/nullish always produces | undefined
304
+ // regardless of optionalType — the questionToken ? modifier only applies to object properties.
305
+ if (meta.nullish || meta.optional) {
306
+ inner = factory.createUnionDeclaration({ nodes: [inner, factory.keywordTypeNodes.undefined] })
307
+ }
308
+
309
+ const useTypeGeneration = syntaxType === 'type' || inner.kind === factory.syntaxKind.union || !!keysToOmit?.length
310
+
311
+ const typeNode = factory.createTypeDeclaration({
312
+ name,
313
+ isExportable: true,
314
+ type: inner,
315
+ syntax: useTypeGeneration ? 'type' : 'interface',
316
+ comments: buildPropertyJSDocComments({
317
+ ...meta,
318
+ description,
319
+ }),
320
+ })
321
+
322
+ return safePrint(typeNode)
323
+ },
324
+ }
325
+ })
@@ -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>((ctx) => {
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 ctx.resolveTypeName(`${node.operationId} ${param.in} ${param.name}`)
37
+ },
38
+ resolveResponseStatusName(node, statusCode) {
39
+ return ctx.resolveTypeName(`${node.operationId} Status ${statusCode}`)
40
+ },
41
+ resolveDataName(node) {
42
+ return ctx.resolveTypeName(`${node.operationId} Data`)
43
+ },
44
+ resolveRequestConfigName(node) {
45
+ return ctx.resolveTypeName(`${node.operationId} RequestConfig`)
46
+ },
47
+ resolveResponsesName(node) {
48
+ return ctx.resolveTypeName(`${node.operationId} Responses`)
49
+ },
50
+ resolveResponseName(node) {
51
+ return ctx.resolveTypeName(`${node.operationId} Response`)
52
+ },
53
+ resolveEnumKeyName(node, enumTypeSuffix = 'key') {
54
+ return `${ctx.resolveTypeName(node.name ?? '')}${enumTypeSuffix}`
55
+ },
56
+ resolvePathParamsName(node, param) {
57
+ return ctx.resolveParamName(node, param)
58
+ },
59
+ resolveQueryParamsName(node, param) {
60
+ return ctx.resolveParamName(node, param)
61
+ },
62
+ resolveHeaderParamsName(node, param) {
63
+ return ctx.resolveParamName(node, param)
64
+ },
65
+ }
66
+ })