@kubb/plugin-ts 5.0.0-alpha.24 → 5.0.0-alpha.26

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.
@@ -32,11 +32,10 @@ function buildGroupedParamsSchema({ params, parentName }: BuildGroupedParamsSche
32
32
  }
33
33
 
34
34
  type BuildOperationSchemaOptions = {
35
- node: OperationNode
36
35
  resolver: ResolverTs
37
36
  }
38
37
 
39
- function buildLegacyResponsesSchemaNode({ node, resolver }: BuildOperationSchemaOptions): SchemaNode | null {
38
+ function buildLegacyResponsesSchemaNode(node: OperationNode, { resolver }: BuildOperationSchemaOptions): SchemaNode | null {
40
39
  const isGet = node.method.toLowerCase() === 'get'
41
40
  const successResponses = node.responses.filter((res) => {
42
41
  const code = Number(res.statusCode)
@@ -52,7 +51,7 @@ function buildLegacyResponsesSchemaNode({ node, resolver }: BuildOperationSchema
52
51
  type: 'union',
53
52
  members: successResponses.map((res) => createSchema({ type: 'ref', name: resolver.resolveResponseStatusName(node, res.statusCode) })),
54
53
  })
55
- : createSchema({ type: 'any' })
54
+ : createSchema({ type: 'any', primitive: undefined })
56
55
 
57
56
  const errorsSchema =
58
57
  errorResponses.length > 0
@@ -62,7 +61,7 @@ function buildLegacyResponsesSchemaNode({ node, resolver }: BuildOperationSchema
62
61
  type: 'union',
63
62
  members: errorResponses.map((res) => createSchema({ type: 'ref', name: resolver.resolveResponseStatusName(node, res.statusCode) })),
64
63
  })
65
- : createSchema({ type: 'any' })
64
+ : createSchema({ type: 'any', primitive: undefined })
66
65
 
67
66
  const properties = [createProperty({ name: 'Response', required: true, schema: responseSchema })]
68
67
 
@@ -114,14 +113,14 @@ function buildLegacyResponsesSchemaNode({ node, resolver }: BuildOperationSchema
114
113
  return createSchema({ type: 'object', properties })
115
114
  }
116
115
 
117
- function buildLegacyResponseUnionSchemaNode({ node, resolver }: BuildOperationSchemaOptions): SchemaNode {
116
+ function buildLegacyResponseUnionSchemaNode(node: OperationNode, { resolver }: BuildOperationSchemaOptions): SchemaNode {
118
117
  const successResponses = node.responses.filter((res) => {
119
118
  const code = Number(res.statusCode)
120
119
  return !Number.isNaN(code) && code >= 200 && code < 300
121
120
  })
122
121
 
123
122
  if (successResponses.length === 0) {
124
- return createSchema({ type: 'any' })
123
+ return createSchema({ type: 'any', primitive: undefined })
125
124
  }
126
125
 
127
126
  if (successResponses.length === 1) {
@@ -159,33 +158,89 @@ function nameUnnamedEnums(node: SchemaNode, parentName: string): SchemaNode {
159
158
  export const typeGeneratorLegacy = defineGenerator<PluginTs>({
160
159
  name: 'typescript-legacy',
161
160
  type: 'react',
161
+ Schema({ node, adapter, options, config, resolver }) {
162
+ const { enumType, enumTypeSuffix, enumKeyCasing, syntaxType, optionalType, arrayType, output, group, transformers = [] } = options
163
+
164
+ const transformedNode = transform(node, composeTransformers(...transformers))
165
+
166
+ if (!transformedNode.name) {
167
+ return
168
+ }
169
+
170
+ const root = path.resolve(config.root, config.output.path)
171
+ const mode = getMode(path.resolve(root, output.path))
172
+
173
+ const imports = adapter.getImports(transformedNode, (schemaName) => ({
174
+ name: resolver.default(schemaName, 'type'),
175
+ path: resolver.resolveFile({ name: schemaName, extname: '.ts' }, { root, output, group }).path,
176
+ }))
177
+
178
+ const isEnumSchema = !!narrowSchema(transformedNode, schemaTypes.enum)
179
+
180
+ const meta = {
181
+ name:
182
+ ENUM_TYPES_WITH_KEY_SUFFIX.has(enumType) && isEnumSchema
183
+ ? resolver.resolveEnumKeyName(transformedNode, enumTypeSuffix)
184
+ : resolver.resolveName(transformedNode.name),
185
+ file: resolver.resolveFile({ name: transformedNode.name, extname: '.ts' }, { root, output, group }),
186
+ } as const
187
+
188
+ return (
189
+ <File
190
+ baseName={meta.file.baseName}
191
+ path={meta.file.path}
192
+ meta={meta.file.meta}
193
+ banner={resolver.resolveBanner(adapter.rootNode, { output, config })}
194
+ footer={resolver.resolveFooter(adapter.rootNode, { output, config })}
195
+ >
196
+ {mode === 'split' &&
197
+ imports.map((imp) => (
198
+ <File.Import key={[transformedNode.name, imp.path, imp.isTypeOnly].join('-')} root={meta.file.path} path={imp.path} name={imp.name} isTypeOnly />
199
+ ))}
200
+ <Type
201
+ name={meta.name}
202
+ node={transformedNode}
203
+ enumType={enumType}
204
+ enumTypeSuffix={enumTypeSuffix}
205
+ enumKeyCasing={enumKeyCasing}
206
+ optionalType={optionalType}
207
+ arrayType={arrayType}
208
+ syntaxType={syntaxType}
209
+ resolver={resolver}
210
+ />
211
+ </File>
212
+ )
213
+ },
162
214
  Operation({ node, adapter, options, config, resolver }) {
163
215
  const { enumType, enumTypeSuffix, enumKeyCasing, optionalType, arrayType, syntaxType, paramsCasing, group, output, transformers = [] } = options
164
216
 
217
+ const transformedNode = transform(node, composeTransformers(...transformers))
218
+
165
219
  const root = path.resolve(config.root, config.output.path)
166
220
  const mode = getMode(path.resolve(root, output.path))
167
-
168
- const file = resolver.resolveFile({ name: node.operationId, extname: '.ts', tag: node.tags[0] ?? 'default', path: node.path }, { root, output, group })
169
221
  const params = caseParams(node.parameters, paramsCasing)
170
222
 
223
+ const meta = {
224
+ file: resolver.resolveFile(
225
+ { name: transformedNode.operationId, extname: '.ts', tag: transformedNode.tags[0] ?? 'default', path: transformedNode.path },
226
+ { root, output, group },
227
+ ),
228
+ } as const
229
+
171
230
  function renderSchemaType({
172
- node: schemaNode,
231
+ schema,
173
232
  name,
174
233
  description,
175
234
  keysToOmit,
176
235
  }: {
177
- node: SchemaNode | null
236
+ schema: SchemaNode | null
178
237
  name: string
179
238
  description?: string
180
239
  keysToOmit?: Array<string>
181
240
  }) {
182
- if (!schemaNode) {
183
- return null
184
- }
185
-
186
- const transformedNode = transform(schemaNode, composeTransformers(...transformers))
241
+ if (!schema) return null
187
242
 
188
- const imports = adapter.getImports(transformedNode, (schemaName) => ({
243
+ const imports = adapter.getImports(schema, (schemaName) => ({
189
244
  name: resolver.default(schemaName, 'type'),
190
245
  path: resolver.resolveFile({ name: schemaName, extname: '.ts' }, { root, output, group }).path,
191
246
  }))
@@ -193,10 +248,12 @@ export const typeGeneratorLegacy = defineGenerator<PluginTs>({
193
248
  return (
194
249
  <>
195
250
  {mode === 'split' &&
196
- imports.map((imp) => <File.Import key={[name, imp.path, imp.isTypeOnly].join('-')} root={file.path} path={imp.path} name={imp.name} isTypeOnly />)}
251
+ imports.map((imp) => (
252
+ <File.Import key={[name, imp.path, imp.isTypeOnly].join('-')} root={meta.file.path} path={imp.path} name={imp.name} isTypeOnly />
253
+ ))}
197
254
  <Type
198
255
  name={name}
199
- node={transformedNode}
256
+ node={schema}
200
257
  description={description}
201
258
  enumType={enumType}
202
259
  enumTypeSuffix={enumTypeSuffix}
@@ -220,7 +277,7 @@ export const typeGeneratorLegacy = defineGenerator<PluginTs>({
220
277
  const baseResponseName = resolverTsLegacy.resolveResponseStatusName(node, res.statusCode)
221
278
 
222
279
  return renderSchemaType({
223
- node: res.schema ? nameUnnamedEnums(res.schema, baseResponseName) : res.schema,
280
+ schema: res.schema ? nameUnnamedEnums(res.schema, baseResponseName) : res.schema,
224
281
  name: responseName,
225
282
  description: res.description,
226
283
  keysToOmit: res.keysToOmit,
@@ -229,7 +286,7 @@ export const typeGeneratorLegacy = defineGenerator<PluginTs>({
229
286
 
230
287
  const requestType = node.requestBody?.schema
231
288
  ? renderSchemaType({
232
- node: nameUnnamedEnums(node.requestBody.schema, resolverTsLegacy.resolveDataName(node)),
289
+ schema: nameUnnamedEnums(node.requestBody.schema, resolverTsLegacy.resolveDataName(node)),
233
290
  name: resolver.resolveDataName(node),
234
291
  description: node.requestBody.description ?? node.requestBody.schema.description,
235
292
  keysToOmit: node.requestBody.keysToOmit,
@@ -239,39 +296,39 @@ export const typeGeneratorLegacy = defineGenerator<PluginTs>({
239
296
  const legacyParamTypes = [
240
297
  pathParams.length > 0
241
298
  ? renderSchemaType({
242
- node: buildGroupedParamsSchema({ params: pathParams, parentName: resolverTsLegacy.resolvePathParamsName(node, pathParams[0]!) }),
299
+ schema: buildGroupedParamsSchema({ params: pathParams, parentName: resolverTsLegacy.resolvePathParamsName(node, pathParams[0]!) }),
243
300
  name: resolver.resolvePathParamsName(node, pathParams[0]!),
244
301
  })
245
302
  : null,
246
303
  queryParams.length > 0
247
304
  ? renderSchemaType({
248
- node: buildGroupedParamsSchema({ params: queryParams, parentName: resolverTsLegacy.resolveQueryParamsName(node, queryParams[0]!) }),
305
+ schema: buildGroupedParamsSchema({ params: queryParams, parentName: resolverTsLegacy.resolveQueryParamsName(node, queryParams[0]!) }),
249
306
  name: resolver.resolveQueryParamsName(node, queryParams[0]!),
250
307
  })
251
308
  : null,
252
309
  headerParams.length > 0
253
310
  ? renderSchemaType({
254
- node: buildGroupedParamsSchema({ params: headerParams, parentName: resolverTsLegacy.resolveHeaderParamsName(node, headerParams[0]!) }),
311
+ schema: buildGroupedParamsSchema({ params: headerParams, parentName: resolverTsLegacy.resolveHeaderParamsName(node, headerParams[0]!) }),
255
312
  name: resolver.resolveHeaderParamsName(node, headerParams[0]!),
256
313
  })
257
314
  : null,
258
315
  ]
259
316
 
260
317
  const legacyResponsesType = renderSchemaType({
261
- node: buildLegacyResponsesSchemaNode({ node, resolver }),
318
+ schema: buildLegacyResponsesSchemaNode(node, { resolver }),
262
319
  name: resolver.resolveResponsesName(node),
263
320
  })
264
321
 
265
322
  const legacyResponseType = renderSchemaType({
266
- node: buildLegacyResponseUnionSchemaNode({ node, resolver }),
323
+ schema: buildLegacyResponseUnionSchemaNode(node, { resolver }),
267
324
  name: resolver.resolveResponseName(node),
268
325
  })
269
326
 
270
327
  return (
271
328
  <File
272
- baseName={file.baseName}
273
- path={file.path}
274
- meta={file.meta}
329
+ baseName={meta.file.baseName}
330
+ path={meta.file.path}
331
+ meta={meta.file.meta}
275
332
  banner={resolver.resolveBanner(adapter.rootNode, { output, config })}
276
333
  footer={resolver.resolveFooter(adapter.rootNode, { output, config })}
277
334
  >
@@ -283,56 +340,4 @@ export const typeGeneratorLegacy = defineGenerator<PluginTs>({
283
340
  </File>
284
341
  )
285
342
  },
286
- Schema({ node, adapter, options, config, resolver }) {
287
- const { enumType, enumTypeSuffix, enumKeyCasing, syntaxType, optionalType, arrayType, output, group, transformers = [] } = options
288
-
289
- const root = path.resolve(config.root, config.output.path)
290
- const mode = getMode(path.resolve(root, output.path))
291
-
292
- if (!node.name) {
293
- return
294
- }
295
-
296
- const transformedNode = transform(node, composeTransformers(...transformers))
297
-
298
- const imports = adapter.getImports(transformedNode, (schemaName) => ({
299
- name: resolver.default(schemaName, 'type'),
300
- path: resolver.resolveFile({ name: schemaName, extname: '.ts' }, { root, output, group }).path,
301
- }))
302
-
303
- const isEnumSchema = !!narrowSchema(node, schemaTypes.enum)
304
-
305
- const name = ENUM_TYPES_WITH_KEY_SUFFIX.has(enumType) && isEnumSchema ? resolver.resolveEnumKeyName(node, enumTypeSuffix) : resolver.resolveName(node.name)
306
-
307
- const type = {
308
- name,
309
- file: resolver.resolveFile({ name: node.name, extname: '.ts' }, { root, output, group }),
310
- } as const
311
-
312
- return (
313
- <File
314
- baseName={type.file.baseName}
315
- path={type.file.path}
316
- meta={type.file.meta}
317
- banner={resolver.resolveBanner(adapter.rootNode, { output, config })}
318
- footer={resolver.resolveFooter(adapter.rootNode, { output, config })}
319
- >
320
- {mode === 'split' &&
321
- imports.map((imp) => (
322
- <File.Import key={[node.name, imp.path, imp.isTypeOnly].join('-')} root={type.file.path} path={imp.path} name={imp.name} isTypeOnly />
323
- ))}
324
- <Type
325
- name={type.name}
326
- node={transformedNode}
327
- enumType={enumType}
328
- enumTypeSuffix={enumTypeSuffix}
329
- enumKeyCasing={enumKeyCasing}
330
- optionalType={optionalType}
331
- arrayType={arrayType}
332
- syntaxType={syntaxType}
333
- resolver={resolver}
334
- />
335
- </File>
336
- )
337
- },
338
343
  })
package/src/index.ts CHANGED
@@ -1,14 +1,9 @@
1
1
  export { Enum } from './components/Enum.tsx'
2
2
  export { Type } from './components/Type.tsx'
3
-
4
3
  export { typeGenerator } from './generators/typeGenerator.tsx'
5
-
6
4
  export { pluginTs, pluginTsName } from './plugin.ts'
7
-
8
5
  export { functionPrinter } from './printers/functionPrinter.ts'
9
6
  export { printerTs } from './printers/printerTs.ts'
10
-
11
7
  export { resolverTs } from './resolvers/resolverTs.ts'
12
8
  export { resolverTsLegacy } from './resolvers/resolverTsLegacy.ts'
13
-
14
9
  export type { PluginTs, ResolverTs } from './types.ts'
package/src/plugin.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import path from 'node:path'
2
2
  import { camelCase } from '@internals/utils'
3
3
  import { walk } from '@kubb/ast'
4
- import { createPlugin, type Group, getBarrelFiles, getPreset, renderOperation, renderSchema } from '@kubb/core'
4
+ import type { OperationNode } from '@kubb/ast/types'
5
+ import { createPlugin, type Group, getBarrelFiles, getPreset, runGeneratorOperation, runGeneratorOperations, runGeneratorSchema } from '@kubb/core'
5
6
  import { presets } from './presets.ts'
6
7
  import type { PluginTs } from './types.ts'
7
8
 
@@ -68,14 +69,14 @@ export const pluginTs = createPlugin<PluginTs>((options) => {
68
69
  optionalType,
69
70
  group: group
70
71
  ? ({
71
- ...options.group,
72
+ ...group,
72
73
  name: (ctx) => {
73
- if (options.group?.type === 'path') {
74
+ if (group.type === 'path') {
74
75
  return `${ctx.group.split('/')[1]}`
75
76
  }
76
77
  return `${camelCase(ctx.group)}Controller`
77
78
  },
78
- } as Group)
79
+ } satisfies Group)
79
80
  : undefined,
80
81
  arrayType,
81
82
  enumType,
@@ -111,63 +112,32 @@ export const pluginTs = createPlugin<PluginTs>((options) => {
111
112
  const root = path.resolve(config.root, config.output.path)
112
113
 
113
114
  if (!adapter) {
114
- throw new Error('Plugin cannot work without adapter being set')
115
+ throw new Error(`[${pluginTsName}] No adapter found. Add an OAS adapter (e.g. pluginOas()) before this plugin in your Kubb config.`)
115
116
  }
116
117
 
117
118
  await openInStudio({ ast: true })
118
119
 
120
+ const collectedOperations: Array<OperationNode> = []
121
+ const generatorContext = { generators: preset.generators, plugin, resolver, exclude, include, override, fabric, adapter, config, driver }
122
+
119
123
  await walk(rootNode, {
120
124
  depth: 'shallow',
121
125
  async schema(schemaNode) {
122
- const writeTasks = preset.generators.map(async (generator) => {
123
- if (generator.type === 'react' && generator.version === '2') {
124
- const options = resolver.resolveOptions(schemaNode, { options: plugin.options, exclude, include, override })
125
-
126
- if (options === null) {
127
- return
128
- }
129
-
130
- await renderSchema(schemaNode, {
131
- options,
132
- resolver,
133
- adapter,
134
- config,
135
- fabric,
136
- Component: generator.Schema,
137
- plugin,
138
- driver,
139
- })
140
- }
141
- })
142
-
143
- await Promise.all(writeTasks)
126
+ await runGeneratorSchema(schemaNode, generatorContext)
144
127
  },
145
128
  async operation(operationNode) {
146
- const writeTasks = preset.generators.map(async (generator) => {
147
- if (generator.type === 'react' && generator.version === '2') {
148
- const options = resolver.resolveOptions(operationNode, { options: plugin.options, exclude, include, override })
149
-
150
- if (options === null) {
151
- return
152
- }
153
-
154
- await renderOperation(operationNode, {
155
- options,
156
- resolver,
157
- adapter,
158
- config,
159
- fabric,
160
- Component: generator.Operation,
161
- plugin,
162
- driver,
163
- })
164
- }
165
- })
166
-
167
- await Promise.all(writeTasks)
129
+ const baseOptions = resolver.resolveOptions(operationNode, { options: plugin.options, exclude, include, override })
130
+
131
+ if (baseOptions !== null) {
132
+ collectedOperations.push(operationNode)
133
+ }
134
+
135
+ await runGeneratorOperation(operationNode, generatorContext)
168
136
  },
169
137
  })
170
138
 
139
+ await runGeneratorOperations(collectedOperations, generatorContext)
140
+
171
141
  const barrelFiles = await getBarrelFiles(this.fabric.files, {
172
142
  type: output.barrelType ?? 'named',
173
143
  root,
@@ -1,6 +1,7 @@
1
1
  import type { PrinterFactoryOptions } from '@kubb/ast'
2
2
  import { createPrinterFactory } from '@kubb/ast'
3
3
  import type { FunctionNode, FunctionNodeType, FunctionParameterNode, FunctionParametersNode, ParameterGroupNode, TypeNode } from '@kubb/ast/types'
4
+ import { PARAM_RANK } from '../constants.ts'
4
5
 
5
6
  /**
6
7
  * Maps each function-printer handler key to its concrete node type.
@@ -53,13 +54,13 @@ type DefaultPrinter = PrinterFactoryOptions<'functionParameters', FunctionPrinte
53
54
 
54
55
  function rank(param: FunctionParameterNode | ParameterGroupNode): number {
55
56
  if (param.kind === 'ParameterGroup') {
56
- if (param.default) return 2
57
+ if (param.default) return PARAM_RANK.withDefault
57
58
  const isOptional = param.optional ?? param.properties.every((p) => p.optional || p.default !== undefined)
58
- return isOptional ? 1 : 0
59
+ return isOptional ? PARAM_RANK.optional : PARAM_RANK.required
59
60
  }
60
- if (param.rest) return 3
61
- if (param.default) return 2
62
- return param.optional ? 1 : 0
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
63
64
  }
64
65
 
65
66
  function sortParams(params: ReadonlyArray<FunctionParameterNode | ParameterGroupNode>): Array<FunctionParameterNode | ParameterGroupNode> {