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

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 (44) hide show
  1. package/LICENSE +17 -10
  2. package/README.md +26 -7
  3. package/dist/index.cjs +1526 -5
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.ts +558 -27
  6. package/dist/index.js +1488 -2
  7. package/dist/index.js.map +1 -0
  8. package/extension.yaml +632 -0
  9. package/package.json +43 -65
  10. package/src/components/{v2/Enum.tsx → Enum.tsx} +33 -17
  11. package/src/components/Type.tsx +31 -161
  12. package/src/constants.ts +10 -0
  13. package/src/factory.ts +295 -39
  14. package/src/generators/typeGenerator.tsx +248 -420
  15. package/src/index.ts +9 -3
  16. package/src/plugin.ts +66 -205
  17. package/src/printers/functionPrinter.ts +197 -0
  18. package/src/printers/printerTs.ts +329 -0
  19. package/src/resolvers/resolverTs.ts +66 -0
  20. package/src/types.ts +233 -221
  21. package/src/utils.ts +129 -0
  22. package/dist/components-CRu8IKY3.js +0 -729
  23. package/dist/components-CRu8IKY3.js.map +0 -1
  24. package/dist/components-DeNDKlzf.cjs +0 -982
  25. package/dist/components-DeNDKlzf.cjs.map +0 -1
  26. package/dist/components.cjs +0 -3
  27. package/dist/components.d.ts +0 -36
  28. package/dist/components.js +0 -2
  29. package/dist/generators.cjs +0 -4
  30. package/dist/generators.d.ts +0 -509
  31. package/dist/generators.js +0 -2
  32. package/dist/plugin-BZkBwnEA.js +0 -1269
  33. package/dist/plugin-BZkBwnEA.js.map +0 -1
  34. package/dist/plugin-Bunz1oGa.cjs +0 -1322
  35. package/dist/plugin-Bunz1oGa.cjs.map +0 -1
  36. package/dist/types-mSXmB8WU.d.ts +0 -298
  37. package/src/components/index.ts +0 -1
  38. package/src/components/v2/Type.tsx +0 -59
  39. package/src/generators/index.ts +0 -2
  40. package/src/generators/v2/typeGenerator.tsx +0 -167
  41. package/src/generators/v2/utils.ts +0 -140
  42. package/src/parser.ts +0 -389
  43. package/src/printer.ts +0 -368
  44. package/src/resolverTs.ts +0 -77
package/package.json CHANGED
@@ -1,64 +1,64 @@
1
1
  {
2
2
  "name": "@kubb/plugin-ts",
3
- "version": "5.0.0-alpha.9",
4
- "description": "TypeScript code generation plugin for Kubb, transforming OpenAPI schemas into TypeScript interfaces, types, and utility functions.",
3
+ "version": "5.0.0-beta.10",
4
+ "description": "Generate TypeScript types, interfaces, and enums from your OpenAPI specification. The foundational plugin that powers type safety across the entire Kubb ecosystem.",
5
5
  "keywords": [
6
- "typescript",
7
- "types",
8
- "type-generation",
6
+ "code-generation",
7
+ "codegen",
8
+ "enums",
9
9
  "interfaces",
10
- "type-definitions",
11
- "type-safe",
10
+ "kubb",
12
11
  "openapi",
13
12
  "swagger",
14
- "oas",
15
- "code-generator",
16
- "codegen",
17
- "plugins",
18
- "kubb"
13
+ "type-generation",
14
+ "types",
15
+ "typescript"
19
16
  ],
17
+ "license": "MIT",
18
+ "author": "stijnvanhulle",
20
19
  "repository": {
21
20
  "type": "git",
22
- "url": "git+https://github.com/kubb-labs/kubb.git",
21
+ "url": "git+https://github.com/kubb-labs/plugins.git",
23
22
  "directory": "packages/plugin-ts"
24
23
  },
25
- "license": "MIT",
26
- "author": "stijnvanhulle",
27
- "sideEffects": false,
24
+ "files": [
25
+ "src",
26
+ "dist",
27
+ "extension.yaml",
28
+ "!/**/**.test.**",
29
+ "!/**/__tests__/**",
30
+ "!/**/__snapshots__/**"
31
+ ],
28
32
  "type": "module",
33
+ "sideEffects": false,
34
+ "main": "./dist/index.cjs",
35
+ "module": "./dist/index.js",
36
+ "types": "./dist/index.d.ts",
29
37
  "exports": {
30
38
  ".": {
31
39
  "import": "./dist/index.js",
32
40
  "require": "./dist/index.cjs"
33
41
  },
34
- "./components": {
35
- "import": "./dist/components.js",
36
- "require": "./dist/components.cjs"
37
- },
38
- "./generators": {
39
- "import": "./dist/generators.js",
40
- "require": "./dist/generators.cjs"
41
- },
42
42
  "./package.json": "./package.json"
43
43
  },
44
- "types": "./dist/index.d.ts",
45
- "typesVersions": {
46
- "*": {
47
- "components": [
48
- "./dist/components.d.ts"
49
- ],
50
- "generators": [
51
- "./dist/generators.d.ts"
52
- ]
53
- }
44
+ "publishConfig": {
45
+ "access": "public",
46
+ "registry": "https://registry.npmjs.org/"
47
+ },
48
+ "dependencies": {
49
+ "@kubb/core": "5.0.0-beta.10",
50
+ "@kubb/parser-ts": "5.0.0-beta.10",
51
+ "@kubb/renderer-jsx": "5.0.0-beta.10",
52
+ "remeda": "^2.34.0",
53
+ "typescript": "^6.0.3"
54
+ },
55
+ "devDependencies": {
56
+ "@internals/shared": "0.0.0",
57
+ "@internals/utils": "0.0.0"
58
+ },
59
+ "peerDependencies": {
60
+ "@kubb/renderer-jsx": "5.0.0-beta.10"
54
61
  },
55
- "files": [
56
- "src",
57
- "dist",
58
- "!/**/**.test.**",
59
- "!/**/__tests__/**",
60
- "!/**/__snapshots__/**"
61
- ],
62
62
  "size-limit": [
63
63
  {
64
64
  "path": "./dist/*.js",
@@ -66,36 +66,14 @@
66
66
  "gzip": true
67
67
  }
68
68
  ],
69
- "dependencies": {
70
- "@kubb/fabric-core": "0.14.0",
71
- "@kubb/react-fabric": "0.14.0",
72
- "remeda": "^2.33.6",
73
- "typescript": "5.9.3",
74
- "@kubb/ast": "5.0.0-alpha.9",
75
- "@kubb/core": "5.0.0-alpha.9",
76
- "@kubb/oas": "5.0.0-alpha.9",
77
- "@kubb/plugin-oas": "5.0.0-alpha.9"
78
- },
79
- "peerDependencies": {
80
- "@kubb/react-fabric": "0.14.0"
81
- },
82
69
  "engines": {
83
70
  "node": ">=22"
84
71
  },
85
- "publishConfig": {
86
- "access": "public",
87
- "registry": "https://registry.npmjs.org/"
88
- },
89
- "main": "./dist/index.cjs",
90
- "module": "./dist/index.js",
91
- "devDependencies": {
92
- "@internals/utils": "0.0.0"
93
- },
94
72
  "scripts": {
95
73
  "build": "tsdown && size-limit",
96
74
  "clean": "npx rimraf ./dist",
97
- "lint": "bun biome lint .",
98
- "lint:fix": "bun biome lint --fix --unsafe .",
75
+ "lint": "oxlint .",
76
+ "lint:fix": "oxlint --fix .",
99
77
  "release": "pnpm publish --no-git-check",
100
78
  "release:canary": "bash ../../.github/canary.sh && node ../../scripts/build.js canary && pnpm publish --no-git-check",
101
79
  "start": "tsdown --watch",
@@ -1,29 +1,45 @@
1
- import { camelCase, pascalCase, trimQuotes } from '@internals/utils'
2
- import type { EnumSchemaNode } from '@kubb/ast/types'
3
- import { safePrint } from '@kubb/fabric-core/parsers/typescript'
4
- import { File } from '@kubb/react-fabric'
5
- import type { FabricReactNode } from '@kubb/react-fabric/types'
6
- import { ENUM_TYPES_WITH_KEY_SUFFIX, ENUM_TYPES_WITH_RUNTIME_VALUE, ENUM_TYPES_WITH_TYPE_ONLY } from '../../constants.ts'
7
- import * as factory from '../../factory.ts'
8
- import type { PluginTs } from '../../types.ts'
1
+ import { camelCase, trimQuotes } from '@internals/utils'
2
+ import type { ast } from '@kubb/core'
3
+ import { safePrint } from '@kubb/parser-ts'
4
+ import { File } from '@kubb/renderer-jsx'
5
+ import type { KubbReactNode } from '@kubb/renderer-jsx/types'
6
+ import { ENUM_TYPES_WITH_KEY_SUFFIX, ENUM_TYPES_WITH_RUNTIME_VALUE, ENUM_TYPES_WITH_TYPE_ONLY } from '../constants.ts'
7
+ import * as factory from '../factory.ts'
8
+ import type { PluginTs, ResolverTs } from '../types.ts'
9
9
 
10
10
  type Props = {
11
- node: EnumSchemaNode
11
+ node: ast.EnumSchemaNode
12
12
  enumType: PluginTs['resolvedOptions']['enumType']
13
+ enumTypeSuffix: PluginTs['resolvedOptions']['enumTypeSuffix']
13
14
  enumKeyCasing: PluginTs['resolvedOptions']['enumKeyCasing']
15
+ resolver: ResolverTs
16
+ key?: string | number | null
14
17
  }
15
18
 
16
19
  /**
17
20
  * Resolves the runtime identifier name and the TypeScript type name for an enum schema node.
18
21
  *
19
22
  * The raw `node.name` may be a YAML key such as `"enumNames.Type"` which is not a
20
- * valid TypeScript identifier. `pascalCase` normalizes it unconditionally; for inline enum
21
- * properties the adapter already emits a PascalCase+suffix name so `pascalCase` is a no-op.
23
+ * valid TypeScript identifier. The resolver normalizes it; for inline enum
24
+ * properties the adapter already emits a PascalCase+suffix name so resolution is typically a no-op.
22
25
  */
23
- export function getEnumNames(node: EnumSchemaNode, enumType: PluginTs['resolvedOptions']['enumType']): { enumName: string; typeName: string } {
24
- const resolved = pascalCase(node.name!)
26
+ export function getEnumNames({
27
+ node,
28
+ enumType,
29
+ enumTypeSuffix,
30
+ resolver,
31
+ }: {
32
+ node: ast.EnumSchemaNode
33
+ enumType: PluginTs['resolvedOptions']['enumType']
34
+ enumTypeSuffix: PluginTs['resolvedOptions']['enumTypeSuffix']
35
+ resolver: ResolverTs
36
+ }): {
37
+ enumName: string
38
+ typeName: string
39
+ } {
40
+ const resolved = resolver.default(node.name!, 'type')
25
41
  const enumName = enumType === 'asPascalConst' ? resolved : camelCase(node.name!)
26
- const typeName = ENUM_TYPES_WITH_KEY_SUFFIX.has(enumType) ? `${resolved}Key` : resolved
42
+ const typeName = ENUM_TYPES_WITH_KEY_SUFFIX.has(enumType) ? resolver.resolveEnumKeyName(node, enumTypeSuffix) : resolved
27
43
 
28
44
  return { enumName, typeName }
29
45
  }
@@ -39,15 +55,15 @@ export function getEnumNames(node: EnumSchemaNode, enumType: PluginTs['resolvedO
39
55
  * The emitted `File.Source` nodes carry the resolved names so that the barrel
40
56
  * index picks up the correct export identifiers.
41
57
  */
42
- export function Enum({ node, enumType, enumKeyCasing }: Props): FabricReactNode {
43
- const { enumName, typeName } = getEnumNames(node, enumType)
58
+ export function Enum({ node, enumType, enumTypeSuffix, enumKeyCasing, resolver }: Props): KubbReactNode {
59
+ const { enumName, typeName } = getEnumNames({ node, enumType, enumTypeSuffix, resolver })
44
60
 
45
61
  const [nameNode, typeNode] = factory.createEnumDeclaration({
46
62
  name: enumName,
47
63
  typeName,
48
64
  enums: (node.namedEnumValues?.map((v) => [trimQuotes(v.name.toString()), v.value]) ??
49
65
  node.enumValues?.filter((v): v is NonNullable<typeof v> => v !== null && v !== undefined).map((v) => [trimQuotes(v.toString()), v]) ??
50
- []) as unknown as Array<[string, string]>,
66
+ []) as Array<[string | number, string | number | boolean]>,
51
67
  type: enumType,
52
68
  enumKeyCasing,
53
69
  })
@@ -1,156 +1,42 @@
1
- import { camelCase, jsStringEscape, pascalCase, trimQuotes } from '@internals/utils'
2
- import { safePrint } from '@kubb/fabric-core/parsers/typescript'
3
- import type { SchemaObject } from '@kubb/oas'
4
- import { isKeyword, type Schema, SchemaGenerator, schemaKeywords } from '@kubb/plugin-oas'
5
- import { File } from '@kubb/react-fabric'
6
- import type { FabricReactNode } from '@kubb/react-fabric/types'
7
- import type ts from 'typescript'
8
- import * as factory from '../factory.ts'
9
- import { parse, typeKeywordMapper } from '../parser.ts'
10
- import type { PluginTs } from '../types.ts'
1
+ import { ast } from '@kubb/core'
2
+ import { File } from '@kubb/renderer-jsx'
3
+ import type { KubbReactNode } from '@kubb/renderer-jsx/types'
4
+ import type { PrinterTsFactory } from '../printers/printerTs.ts'
5
+ import type { PluginTs, ResolverTs } from '../types.ts'
6
+ import { Enum, getEnumNames } from './Enum.tsx'
11
7
 
12
8
  type Props = {
13
9
  name: string
14
- typedName: string
15
- schema: SchemaObject
16
- tree: Array<Schema>
17
- optionalType: PluginTs['resolvedOptions']['optionalType']
18
- arrayType: PluginTs['resolvedOptions']['arrayType']
10
+ node: ast.SchemaNode
11
+ /**
12
+ * Pre-configured printer instance created by the generator.
13
+ * Created with `printerTs({ ..., nodes: options.printer?.nodes })`.
14
+ */
15
+ printer: ast.Printer<PrinterTsFactory>
19
16
  enumType: PluginTs['resolvedOptions']['enumType']
17
+ enumTypeSuffix: PluginTs['resolvedOptions']['enumTypeSuffix']
20
18
  enumKeyCasing: PluginTs['resolvedOptions']['enumKeyCasing']
21
- syntaxType: PluginTs['resolvedOptions']['syntaxType']
22
- description?: string
23
- keysToOmit?: string[]
19
+ resolver: ResolverTs
24
20
  }
25
21
 
26
- export function Type({
27
- name,
28
- typedName,
29
- tree,
30
- keysToOmit,
31
- schema,
32
- optionalType,
33
- arrayType,
34
- syntaxType,
35
- enumType,
36
- enumKeyCasing,
37
- description,
38
- }: Props): FabricReactNode {
39
- const typeNodes: ts.Node[] = []
40
-
41
- if (!tree.length) {
42
- return ''
43
- }
44
-
45
- const schemaFromTree = tree.find((item) => item.keyword === schemaKeywords.schema)
46
- const enumSchemas = SchemaGenerator.deepSearch(tree, schemaKeywords.enum)
47
-
48
- let type =
49
- (tree
50
- .map((current, _index, siblings) =>
51
- parse(
52
- { name, schema, parent: undefined, current, siblings },
53
- {
54
- optionalType,
55
- arrayType,
56
- enumType,
57
- },
58
- ),
59
- )
60
- .filter(Boolean)
61
- .at(0) as ts.TypeNode) || typeKeywordMapper.undefined()
62
-
63
- // Add a "Key" suffix to avoid collisions where necessary
64
- if (['asConst', 'asPascalConst'].includes(enumType) && enumSchemas.length > 0) {
65
- const isDirectEnum = schema.type === 'array' && schema.items !== undefined
66
- const isEnumOnly = 'enum' in schema && schema.enum
67
-
68
- if (isDirectEnum || isEnumOnly) {
69
- const enumSchema = enumSchemas[0]!
70
- const typeNameWithKey = `${enumSchema.args.typeName}Key`
71
-
72
- type = factory.createTypeReferenceNode(typeNameWithKey)
73
-
74
- if (schema.type === 'array') {
75
- if (arrayType === 'generic') {
76
- type = factory.createTypeReferenceNode(factory.createIdentifier('Array'), [type])
77
- } else {
78
- type = factory.createArrayTypeNode(type)
79
- }
80
- }
81
- }
82
- }
83
-
84
- if (schemaFromTree && isKeyword(schemaFromTree, schemaKeywords.schema)) {
85
- const isNullish = tree.some((item) => item.keyword === schemaKeywords.nullish)
86
- const isNullable = tree.some((item) => item.keyword === schemaKeywords.nullable)
87
- const isOptional = tree.some((item) => item.keyword === schemaKeywords.optional)
22
+ export function Type({ name, node, printer, enumType, enumTypeSuffix, enumKeyCasing, resolver }: Props): KubbReactNode {
23
+ const enumSchemaNodes = ast.collect<ast.EnumSchemaNode>(node, {
24
+ schema(n): ast.EnumSchemaNode | undefined {
25
+ const enumNode = ast.narrowSchema(n, ast.schemaTypes.enum)
26
+ if (enumNode?.name) return enumNode
27
+ },
28
+ })
88
29
 
89
- if (isNullable) {
90
- type = factory.createUnionDeclaration({
91
- nodes: [type, factory.keywordTypeNodes.null],
92
- }) as ts.TypeNode
93
- }
30
+ const output = printer.print(node)
94
31
 
95
- if (isNullish && ['undefined', 'questionTokenAndUndefined'].includes(optionalType as string)) {
96
- type = factory.createUnionDeclaration({
97
- nodes: [type, factory.keywordTypeNodes.undefined],
98
- }) as ts.TypeNode
99
- }
100
-
101
- if (isOptional && ['undefined', 'questionTokenAndUndefined'].includes(optionalType as string)) {
102
- type = factory.createUnionDeclaration({
103
- nodes: [type, factory.keywordTypeNodes.undefined],
104
- }) as ts.TypeNode
105
- }
32
+ if (!output) {
33
+ return
106
34
  }
107
35
 
108
- const useTypeGeneration = syntaxType === 'type' || [factory.syntaxKind.union].includes(type.kind as typeof factory.syntaxKind.union) || !!keysToOmit?.length
109
-
110
- typeNodes.push(
111
- factory.createTypeDeclaration({
112
- name,
113
- isExportable: true,
114
- type: keysToOmit?.length
115
- ? factory.createOmitDeclaration({
116
- keys: keysToOmit,
117
- type,
118
- nonNullable: true,
119
- })
120
- : type,
121
- syntax: useTypeGeneration ? 'type' : 'interface',
122
- comments: [
123
- schema.title ? `${jsStringEscape(schema.title)}` : undefined,
124
- description ? `@description ${jsStringEscape(description)}` : undefined,
125
- schema.deprecated ? '@deprecated' : undefined,
126
- schema.minLength ? `@minLength ${schema.minLength}` : undefined,
127
- schema.maxLength ? `@maxLength ${schema.maxLength}` : undefined,
128
- schema.pattern ? `@pattern ${schema.pattern}` : undefined,
129
- schema.default ? `@default ${schema.default}` : undefined,
130
- schema.example ? `@example ${schema.example}` : undefined,
131
- ],
132
- }),
133
- )
134
-
135
- const enums = [...new Set(enumSchemas)].map((enumSchema) => {
136
- const name = enumType === 'asPascalConst' ? pascalCase(enumSchema.args.name) : camelCase(enumSchema.args.name)
137
- const typeName = ['asConst', 'asPascalConst'].includes(enumType) ? `${enumSchema.args.typeName}Key` : enumSchema.args.typeName
138
-
139
- const [nameNode, typeNode] = factory.createEnumDeclaration({
140
- name,
141
- typeName,
142
- enums: enumSchema.args.items
143
- .map((item) => (item.value === undefined ? undefined : [trimQuotes(item.name?.toString()), item.value]))
144
- .filter(Boolean) as unknown as Array<[string, string]>,
145
- type: enumType,
146
- enumKeyCasing,
147
- })
148
-
36
+ const enums = [...new Map(enumSchemaNodes.map((n) => [n.name, n])).values()].map((node) => {
149
37
  return {
150
- nameNode,
151
- typeNode,
152
- name,
153
- typeName,
38
+ node,
39
+ ...getEnumNames({ node, enumType, enumTypeSuffix, resolver }),
154
40
  }
155
41
  })
156
42
 
@@ -161,28 +47,12 @@ export function Type({
161
47
  return (
162
48
  <>
163
49
  {shouldExportEnums &&
164
- enums.map(({ name, nameNode, typeName, typeNode }) => (
165
- <>
166
- {nameNode && (
167
- <File.Source name={name} isExportable isIndexable isTypeOnly={false}>
168
- {safePrint(nameNode)}
169
- </File.Source>
170
- )}
171
- {
172
- <File.Source
173
- name={typeName}
174
- isIndexable
175
- isExportable={['enum', 'asConst', 'asPascalConst', 'constEnum', 'literal', undefined].includes(enumType)}
176
- isTypeOnly={['asConst', 'asPascalConst', 'literal', undefined].includes(enumType)}
177
- >
178
- {safePrint(typeNode)}
179
- </File.Source>
180
- }
181
- </>
50
+ enums.map(({ node }) => (
51
+ <Enum key={node.name} node={node} enumType={enumType} enumTypeSuffix={enumTypeSuffix} enumKeyCasing={enumKeyCasing} resolver={resolver} />
182
52
  ))}
183
53
  {shouldExportType && (
184
- <File.Source name={typedName} isTypeOnly isExportable isIndexable>
185
- {safePrint(...typeNodes)}
54
+ <File.Source name={name} isTypeOnly isExportable isIndexable>
55
+ {output}
186
56
  </File.Source>
187
57
  )}
188
58
  </>
package/src/constants.ts CHANGED
@@ -27,3 +27,13 @@ export const ENUM_TYPES_WITH_RUNTIME_VALUE = new Set<EnumType | undefined>(['enu
27
27
  * `enumType` values whose type declaration is type-only (no runtime value emitted for the type alias).
28
28
  */
29
29
  export const ENUM_TYPES_WITH_TYPE_ONLY = new Set<EnumType | undefined>(['asConst', 'asPascalConst', 'literal', undefined] as const)
30
+
31
+ /**
32
+ * Ordering priority for function parameters: lower = sorted earlier.
33
+ */
34
+ export const PARAM_RANK = {
35
+ required: 0,
36
+ optional: 1,
37
+ withDefault: 2,
38
+ rest: 3,
39
+ } as const