@kubb/plugin-ts 3.0.0-alpha.0

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/package.json ADDED
@@ -0,0 +1,91 @@
1
+ {
2
+ "name": "@kubb/plugin-ts",
3
+ "version": "3.0.0-alpha.0",
4
+ "description": "Generator plugin-ts",
5
+ "keywords": [
6
+ "zod",
7
+ "plugins",
8
+ "kubb",
9
+ "codegen",
10
+ "swagger",
11
+ "openapi"
12
+ ],
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git://github.com/kubb-labs/kubb.git",
16
+ "directory": "packages/plugin-ts"
17
+ },
18
+ "license": "MIT",
19
+ "author": "Stijn Van Hulle <stijn@stijnvanhulle.be",
20
+ "sideEffects": false,
21
+ "type": "module",
22
+ "exports": {
23
+ ".": {
24
+ "import": "./dist/index.js",
25
+ "require": "./dist/index.cjs",
26
+ "default": "./dist/index.cjs"
27
+ },
28
+ "./components": {
29
+ "import": "./dist/components.js",
30
+ "require": "./dist/components.cjs",
31
+ "default": "./dist/components.cjs"
32
+ },
33
+ "./package.json": "./package.json",
34
+ "./*": "./*"
35
+ },
36
+ "main": "dist/index.cjs",
37
+ "module": "dist/index.js",
38
+ "types": "./dist/index.d.ts",
39
+ "typesVersions": {
40
+ "*": {
41
+ "components": [
42
+ "./dist/components.d.ts"
43
+ ]
44
+ }
45
+ },
46
+ "files": [
47
+ "src",
48
+ "dist",
49
+ "!/**/**.test.**",
50
+ "!/**/__tests__/**"
51
+ ],
52
+ "dependencies": {
53
+ "@kubb/core": "3.0.0-alpha.0",
54
+ "@kubb/fs": "3.0.0-alpha.0",
55
+ "@kubb/oas": "3.0.0-alpha.0",
56
+ "@kubb/parser-ts": "3.0.0-alpha.0",
57
+ "@kubb/plugin-oas": "3.0.0-alpha.0",
58
+ "@kubb/react": "3.0.0-alpha.0"
59
+ },
60
+ "devDependencies": {
61
+ "@types/react": "^18.3.3",
62
+ "prettier": "^3.3.3",
63
+ "react": "^18.3.1",
64
+ "tsup": "^8.2.4",
65
+ "typescript": "^5.5.4",
66
+ "@kubb/config-biome": "3.0.0-alpha.0",
67
+ "@kubb/config-ts": "3.0.0-alpha.0",
68
+ "@kubb/config-tsup": "3.0.0-alpha.0"
69
+ },
70
+ "peerDependencies": {
71
+ "@kubb/react": "3.0.0-alpha.0"
72
+ },
73
+ "engines": {
74
+ "node": ">=20"
75
+ },
76
+ "publishConfig": {
77
+ "access": "public",
78
+ "registry": "https://registry.npmjs.org/"
79
+ },
80
+ "scripts": {
81
+ "build": "tsup",
82
+ "clean": "npx rimraf ./dist",
83
+ "lint": "bun biome lint .",
84
+ "lint:fix": "bun biome lint --apply-unsafe .",
85
+ "release": "pnpm publish --no-git-check",
86
+ "release:canary": "bash ../../.github/canary.sh && node ../../scripts/build.js canary && pnpm publish --no-git-check",
87
+ "start": "tsup --watch",
88
+ "test": "vitest --passWithNoTests",
89
+ "typecheck": "tsc -p ./tsconfig.json --noEmit --emitDeclarationOnly false"
90
+ }
91
+ }
@@ -0,0 +1,49 @@
1
+ import { OperationGenerator as Generator } from '@kubb/plugin-oas'
2
+ import { Oas } from '@kubb/plugin-oas/components'
3
+ import { App, createRoot } from '@kubb/react'
4
+
5
+ import { OasType } from './components/OasType.tsx'
6
+ import { OperationSchema } from './components/OperationSchema.tsx'
7
+
8
+ import type { Operation } from '@kubb/oas'
9
+ import type { OperationMethodResult } from '@kubb/plugin-oas'
10
+ import type { FileMeta, PluginTs } from './types.ts'
11
+
12
+ export class OperationGenerator extends Generator<PluginTs['resolvedOptions'], PluginTs> {
13
+ async all(operations: Operation[]): OperationMethodResult<FileMeta> {
14
+ const { oas, pluginManager, plugin, mode } = this.context
15
+
16
+ const root = createRoot({
17
+ logger: pluginManager.logger,
18
+ })
19
+
20
+ root.render(
21
+ <App pluginManager={pluginManager} plugin={plugin} mode={mode}>
22
+ <Oas oas={oas} operations={operations} generator={this}>
23
+ {plugin.options.oasType && <OasType.File name="oas" typeName="Oas" />}
24
+ </Oas>
25
+ </App>,
26
+ )
27
+
28
+ return root.files
29
+ }
30
+
31
+ async operation(operation: Operation, options: PluginTs['resolvedOptions']): OperationMethodResult<FileMeta> {
32
+ const { oas, pluginManager, plugin, mode } = this.context
33
+
34
+ const root = createRoot({
35
+ logger: pluginManager.logger,
36
+ })
37
+ root.render(
38
+ <App pluginManager={pluginManager} plugin={{ ...plugin, options }} mode={mode}>
39
+ <Oas oas={oas} operations={[operation]} generator={this}>
40
+ <Oas.Operation operation={operation}>
41
+ <OperationSchema.File />
42
+ </Oas.Operation>
43
+ </Oas>
44
+ </App>,
45
+ )
46
+
47
+ return root.files
48
+ }
49
+ }
@@ -0,0 +1,31 @@
1
+ import type { SchemaObject } from '@kubb/oas'
2
+ import { SchemaGenerator as Generator } from '@kubb/plugin-oas'
3
+ import type { SchemaMethodResult } from '@kubb/plugin-oas'
4
+ import { Oas } from '@kubb/plugin-oas/components'
5
+ import { App, createRoot } from '@kubb/react'
6
+ import { Schema } from './components/Schema.tsx'
7
+ import type { FileMeta, PluginTs } from './types.ts'
8
+
9
+ export class SchemaGenerator extends Generator<PluginTs['resolvedOptions'], PluginTs> {
10
+ async schema(name: string, schema: SchemaObject, options: PluginTs['resolvedOptions']): SchemaMethodResult<FileMeta> {
11
+ const { oas, pluginManager, plugin, mode, output } = this.context
12
+
13
+ const root = createRoot({
14
+ logger: pluginManager.logger,
15
+ })
16
+
17
+ const tree = this.parse({ schema, name })
18
+
19
+ root.render(
20
+ <App pluginManager={pluginManager} plugin={{ ...plugin, options }} mode={mode}>
21
+ <Oas oas={oas}>
22
+ <Oas.Schema name={name} value={schema} tree={tree}>
23
+ <Schema.File />
24
+ </Oas.Schema>
25
+ </Oas>
26
+ </App>,
27
+ )
28
+
29
+ return root.files
30
+ }
31
+ }
@@ -0,0 +1,76 @@
1
+ import { Parser, File, Type, useApp } from '@kubb/react'
2
+ import { useOas } from '@kubb/plugin-oas/hooks'
3
+
4
+ import type { OasTypes } from '@kubb/oas'
5
+ import type { ReactNode } from 'react'
6
+ import type { FileMeta, PluginTs } from '../types.ts'
7
+
8
+ type TemplateProps = {
9
+ /**
10
+ * Name of the function
11
+ */
12
+ name: string
13
+ typeName: string
14
+ api: OasTypes.OASDocument
15
+ }
16
+
17
+ function Template({ name, typeName, api }: TemplateProps): ReactNode {
18
+ return (
19
+ <>
20
+ {`export const ${name} = ${JSON.stringify(api, undefined, 2)} as const`}
21
+ <br />
22
+ <Type name={typeName} export>
23
+ {`Infer<typeof ${name}>`}
24
+ </Type>
25
+ </>
26
+ )
27
+ }
28
+
29
+ const defaultTemplates = { default: Template } as const
30
+
31
+ type Props = {
32
+ name: string
33
+ typeName: string
34
+ /**
35
+ * This will make it possible to override the default behaviour.
36
+ */
37
+ Template?: React.ComponentType<React.ComponentProps<typeof Template>>
38
+ }
39
+
40
+ export function OasType({ name, typeName, Template = defaultTemplates.default }: Props): ReactNode {
41
+ const oas = useOas()
42
+
43
+ return <Template name={name} typeName={typeName} api={oas.api} />
44
+ }
45
+
46
+ type FileProps = {
47
+ name: string
48
+ typeName: string
49
+ /**
50
+ * This will make it possible to override the default behaviour.
51
+ */
52
+ templates?: typeof defaultTemplates
53
+ }
54
+
55
+ OasType.File = function ({ name, typeName, templates = defaultTemplates }: FileProps): ReactNode {
56
+ const {
57
+ pluginManager,
58
+ plugin: { key: pluginKey },
59
+ } = useApp<PluginTs>()
60
+ const file = pluginManager.getFile({ name, extName: '.ts', pluginKey })
61
+
62
+ const Template = templates.default
63
+
64
+ return (
65
+ <Parser language="typescript">
66
+ <File<FileMeta> baseName={file.baseName} path={file.path} meta={file.meta}>
67
+ <File.Import name={['Infer']} path="@kubb/oas" isTypeOnly />
68
+ <File.Source>
69
+ <OasType Template={Template} name={name} typeName={typeName} />
70
+ </File.Source>
71
+ </File>
72
+ </Parser>
73
+ )
74
+ }
75
+
76
+ OasType.templates = defaultTemplates
@@ -0,0 +1,161 @@
1
+ import transformers from '@kubb/core/transformers'
2
+ import { print } from '@kubb/parser-ts'
3
+ import * as factory from '@kubb/parser-ts/factory'
4
+ import { Oas } from '@kubb/plugin-oas/components'
5
+ import { useOas, useOperation, useOperationManager } from '@kubb/plugin-oas/hooks'
6
+ import { File, Parser, useApp } from '@kubb/react'
7
+
8
+ import { SchemaGenerator } from '../SchemaGenerator.tsx'
9
+
10
+ import type { PluginManager } from '@kubb/core'
11
+ import type { Operation } from '@kubb/oas'
12
+ import type { ts } from '@kubb/parser-ts'
13
+ import type { OperationSchema as OperationSchemaType } from '@kubb/plugin-oas'
14
+ import type { OperationSchemas } from '@kubb/plugin-oas'
15
+ import { pluginTsName } from '@kubb/plugin-ts'
16
+ import type { ReactNode } from 'react'
17
+ import type { FileMeta, PluginTs } from '../types.ts'
18
+ import { Schema } from './Schema.tsx'
19
+
20
+ function printCombinedSchema({
21
+ name,
22
+ operation,
23
+ schemas,
24
+ pluginManager,
25
+ }: { name: string; operation: Operation; schemas: OperationSchemas; pluginManager: PluginManager }): string {
26
+ const properties: Record<string, ts.TypeNode> = {}
27
+
28
+ if (schemas.response) {
29
+ const identifier = pluginManager.resolveName({
30
+ name: schemas.response.name,
31
+ pluginKey: [pluginTsName],
32
+ type: 'function',
33
+ })
34
+ properties['response'] = factory.createTypeReferenceNode(factory.createIdentifier(identifier), undefined)
35
+ }
36
+
37
+ if (schemas.request) {
38
+ const identifier = pluginManager.resolveName({
39
+ name: schemas.request.name,
40
+ pluginKey: [pluginTsName],
41
+ type: 'function',
42
+ })
43
+ properties['request'] = factory.createTypeReferenceNode(factory.createIdentifier(identifier), undefined)
44
+ }
45
+
46
+ if (schemas.pathParams) {
47
+ const identifier = pluginManager.resolveName({
48
+ name: schemas.pathParams.name,
49
+ pluginKey: [pluginTsName],
50
+ type: 'function',
51
+ })
52
+ properties['pathParams'] = factory.createTypeReferenceNode(factory.createIdentifier(identifier), undefined)
53
+ }
54
+
55
+ if (schemas.queryParams) {
56
+ const identifier = pluginManager.resolveName({
57
+ name: schemas.queryParams.name,
58
+ pluginKey: [pluginTsName],
59
+ type: 'function',
60
+ })
61
+ properties['queryParams'] = factory.createTypeReferenceNode(factory.createIdentifier(identifier), undefined)
62
+ }
63
+
64
+ if (schemas.headerParams) {
65
+ const identifier = pluginManager.resolveName({
66
+ name: schemas.headerParams.name,
67
+ pluginKey: [pluginTsName],
68
+ type: 'function',
69
+ })
70
+ properties['headerParams'] = factory.createTypeReferenceNode(factory.createIdentifier(identifier), undefined)
71
+ }
72
+
73
+ if (schemas.errors) {
74
+ properties['errors'] = factory.createUnionDeclaration({
75
+ nodes: schemas.errors.map((error) => {
76
+ const identifier = pluginManager.resolveName({
77
+ name: error.name,
78
+ pluginKey: [pluginTsName],
79
+ type: 'function',
80
+ })
81
+
82
+ return factory.createTypeReferenceNode(factory.createIdentifier(identifier), undefined)
83
+ }),
84
+ })!
85
+ }
86
+
87
+ const namespaceNode = factory.createTypeAliasDeclaration({
88
+ name: operation.method === 'get' ? `${name}Query` : `${name}Mutation`,
89
+ type: factory.createTypeLiteralNode(
90
+ Object.keys(properties)
91
+ .map((key) => {
92
+ const type = properties[key]
93
+ if (!type) {
94
+ return undefined
95
+ }
96
+
97
+ return factory.createPropertySignature({
98
+ name: transformers.pascalCase(key),
99
+ type,
100
+ })
101
+ })
102
+ .filter(Boolean),
103
+ ),
104
+ modifiers: [factory.modifiers.export],
105
+ })
106
+
107
+ return print(namespaceNode)
108
+ }
109
+
110
+ type Props = {
111
+ description?: string
112
+ keysToOmit?: string[]
113
+ }
114
+
115
+ export function OperationSchema({ keysToOmit, description }: Props): ReactNode {
116
+ return <Schema keysToOmit={keysToOmit} description={description} />
117
+ }
118
+
119
+ type FileProps = {}
120
+
121
+ OperationSchema.File = function ({}: FileProps): ReactNode {
122
+ const { pluginManager, plugin, mode, fileManager } = useApp<PluginTs>()
123
+ const oas = useOas()
124
+ const { getSchemas, getFile, getName } = useOperationManager()
125
+ const operation = useOperation()
126
+
127
+ const file = getFile(operation)
128
+ const schemas = getSchemas(operation)
129
+ const factoryName = getName(operation, { type: 'type' })
130
+ const generator = new SchemaGenerator(plugin.options, {
131
+ oas,
132
+ plugin,
133
+ pluginManager,
134
+ mode,
135
+ override: plugin.options.override,
136
+ })
137
+ const items = [schemas.pathParams, schemas.queryParams, schemas.headerParams, schemas.statusCodes, schemas.request, schemas.response].flat().filter(Boolean)
138
+
139
+ const mapItem = ({ name, schema, description, keysToOmit, ...options }: OperationSchemaType, i: number) => {
140
+ const tree = generator.parse({ schema, name })
141
+
142
+ return (
143
+ <Oas.Schema key={i} name={name} value={schema} tree={tree}>
144
+ {mode === 'split' && <Oas.Schema.Imports extName={plugin.options.extName} isTypeOnly />}
145
+ <File.Source>
146
+ <OperationSchema description={description} keysToOmit={keysToOmit} />
147
+ </File.Source>
148
+ </Oas.Schema>
149
+ )
150
+ }
151
+
152
+ return (
153
+ <Parser language="typescript">
154
+ <File<FileMeta> baseName={file.baseName} path={file.path} meta={file.meta}>
155
+ {items.map(mapItem)}
156
+
157
+ <File.Source>{printCombinedSchema({ name: factoryName, operation, schemas, pluginManager })}</File.Source>
158
+ </File>
159
+ </Parser>
160
+ )
161
+ }
@@ -0,0 +1,135 @@
1
+ import { Oas } from '@kubb/plugin-oas/components'
2
+ import { File, useApp } from '@kubb/react'
3
+
4
+ import transformers from '@kubb/core/transformers'
5
+ import { print, type ts } from '@kubb/parser-ts'
6
+ import * as factory from '@kubb/parser-ts/factory'
7
+ import { SchemaGenerator, schemaKeywords } from '@kubb/plugin-oas'
8
+ import { useSchema } from '@kubb/plugin-oas/hooks'
9
+ import type { ReactNode } from 'react'
10
+ import { parse, typeKeywordMapper } from '../parser/index.ts'
11
+ import { pluginTsName } from '../plugin.ts'
12
+ import type { PluginTs } from '../types.ts'
13
+
14
+ type Props = {
15
+ description?: string
16
+ keysToOmit?: string[]
17
+ }
18
+
19
+ export function Schema(props: Props): ReactNode {
20
+ const { keysToOmit, description } = props
21
+ const { tree, name } = useSchema()
22
+ const {
23
+ pluginManager,
24
+ plugin: {
25
+ options: { mapper, enumType, optionalType },
26
+ },
27
+ } = useApp<PluginTs>()
28
+
29
+ // all checks are also inside this.schema(React)
30
+ const resolvedName = pluginManager.resolveName({
31
+ name,
32
+ pluginKey: [pluginTsName],
33
+ type: 'function',
34
+ })
35
+
36
+ const typeName = pluginManager.resolveName({
37
+ name,
38
+ pluginKey: [pluginTsName],
39
+ type: 'type',
40
+ })
41
+
42
+ const nodes: ts.Node[] = []
43
+ const extraNodes: ts.Node[] = []
44
+
45
+ if (!tree.length) {
46
+ return ''
47
+ }
48
+
49
+ const isNullish = tree.some((item) => item.keyword === schemaKeywords.nullish)
50
+ const isNullable = tree.some((item) => item.keyword === schemaKeywords.nullable)
51
+ const isOptional = tree.some((item) => item.keyword === schemaKeywords.optional)
52
+
53
+ let type =
54
+ (tree
55
+ .map((schema) => parse(undefined, schema, { name: resolvedName, typeName, description, keysToOmit, optionalType, enumType, mapper }))
56
+ .filter(Boolean)
57
+ .at(0) as ts.TypeNode) || typeKeywordMapper.undefined()
58
+
59
+ if (isNullable) {
60
+ type = factory.createUnionDeclaration({
61
+ nodes: [type, factory.keywordTypeNodes.null],
62
+ }) as ts.TypeNode
63
+ }
64
+
65
+ if (isNullish && ['undefined', 'questionTokenAndUndefined'].includes(optionalType as string)) {
66
+ type = factory.createUnionDeclaration({
67
+ nodes: [type, factory.keywordTypeNodes.undefined],
68
+ }) as ts.TypeNode
69
+ }
70
+
71
+ if (isOptional && ['undefined', 'questionTokenAndUndefined'].includes(optionalType as string)) {
72
+ type = factory.createUnionDeclaration({
73
+ nodes: [type, factory.keywordTypeNodes.undefined],
74
+ }) as ts.TypeNode
75
+ }
76
+
77
+ const node = factory.createTypeAliasDeclaration({
78
+ modifiers: [factory.modifiers.export],
79
+ name: resolvedName,
80
+ type: keysToOmit?.length
81
+ ? factory.createOmitDeclaration({
82
+ keys: keysToOmit,
83
+ type,
84
+ nonNullable: true,
85
+ })
86
+ : type,
87
+ })
88
+
89
+ const enumSchemas = SchemaGenerator.deepSearch(tree, schemaKeywords.enum)
90
+ if (enumSchemas) {
91
+ enumSchemas.forEach((enumSchema) => {
92
+ extraNodes.push(
93
+ ...factory.createEnumDeclaration({
94
+ name: transformers.camelCase(enumSchema.args.name),
95
+ typeName: enumSchema.args.typeName,
96
+ enums: enumSchema.args.items
97
+ .map((item) => (item.value === undefined ? undefined : [transformers.trimQuotes(item.name?.toString()), item.value]))
98
+ .filter(Boolean) as unknown as [string, string][],
99
+ type: enumType,
100
+ }),
101
+ )
102
+ })
103
+ }
104
+
105
+ nodes.push(
106
+ factory.appendJSDocToNode({
107
+ node,
108
+ comments: [description ? `@description ${transformers.jsStringEscape(description)}` : undefined].filter(Boolean),
109
+ }),
110
+ )
111
+
112
+ const filterdNodes = nodes.filter(
113
+ (node: ts.Node) =>
114
+ !extraNodes.some(
115
+ (extraNode: ts.Node) => (extraNode as ts.TypeAliasDeclaration)?.name?.escapedText === (node as ts.TypeAliasDeclaration)?.name?.escapedText,
116
+ ),
117
+ )
118
+
119
+ return print([...extraNodes, ...filterdNodes])
120
+ }
121
+
122
+ type FileProps = {}
123
+
124
+ Schema.File = function ({}: FileProps): ReactNode {
125
+ const { pluginManager } = useApp<PluginTs>()
126
+ const { schema } = useSchema()
127
+
128
+ return (
129
+ <Oas.Schema.File output={pluginManager.config.output.path}>
130
+ <File.Source>
131
+ <Schema description={schema?.description} />
132
+ </File.Source>
133
+ </Oas.Schema.File>
134
+ )
135
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @description Null response
3
+ */
4
+ export type CreatePets201 = any
5
+ /**
6
+ * @description unexpected error
7
+ */
8
+ export type CreatePetsError = Error
9
+ export type CreatePetsMutationRequest = {
10
+ /**
11
+ * @type string
12
+ */
13
+ name: string
14
+ /**
15
+ * @type string
16
+ */
17
+ tag: string
18
+ }
19
+ export type CreatePetsMutationResponse = any
20
+ export type CreatePetsMutation = {
21
+ Response: CreatePetsMutationResponse
22
+ Request: CreatePetsMutationRequest
23
+ }
@@ -0,0 +1,28 @@
1
+ export type ShowPetByIdPathParams = {
2
+ /**
3
+ * @description The id of the pet to retrieve
4
+ * @type string
5
+ */
6
+ petId: string
7
+ /**
8
+ * @description The id of the pet to retrieve
9
+ * @type string
10
+ */
11
+ testId: string
12
+ }
13
+ /**
14
+ * @description Expected response to a valid request
15
+ */
16
+ export type ShowPetById200 = Pet
17
+ /**
18
+ * @description unexpected error
19
+ */
20
+ export type ShowPetByIdError = Error
21
+ /**
22
+ * @description Expected response to a valid request
23
+ */
24
+ export type ShowPetByIdQueryResponse = Pet
25
+ export type ShowPetByIdQuery = {
26
+ Response: ShowPetByIdQueryResponse
27
+ PathParams: ShowPetByIdPathParams
28
+ }
@@ -0,0 +1,3 @@
1
+ export { OasType } from './OasType.tsx'
2
+ export { OperationSchema } from './OperationSchema.tsx'
3
+ export { Schema } from './Schema.tsx'
package/src/index.ts ADDED
@@ -0,0 +1,15 @@
1
+ import { pluginTs } from './plugin.ts'
2
+
3
+ export { pluginTs, pluginTsName } from './plugin.ts'
4
+ export type { PluginTs } from './types.ts'
5
+
6
+ /**
7
+ * @deprecated Use `import { pluginTs } from '@kubb/plugin-ts'` instead
8
+ */
9
+ const definePluginDefault = pluginTs
10
+ /**
11
+ * @deprecated Use `import { pluginTs } from '@kubb/plugin-ts'` instead
12
+ */
13
+ export const definePlugin = pluginTs
14
+
15
+ export default definePluginDefault
@@ -0,0 +1 @@
1
+ export type { Infer, Response, RequestParams, Model } from '@kubb/oas'