@kubb/plugin-ts 5.0.0-alpha.11 → 5.0.0-alpha.13

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 (57) hide show
  1. package/dist/Type-C8EHVKjc.js +663 -0
  2. package/dist/Type-C8EHVKjc.js.map +1 -0
  3. package/dist/Type-DrOq6-nh.cjs +680 -0
  4. package/dist/Type-DrOq6-nh.cjs.map +1 -0
  5. package/dist/casing-Cp-jbC_k.js +84 -0
  6. package/dist/casing-Cp-jbC_k.js.map +1 -0
  7. package/dist/casing-D2uQKLWS.cjs +144 -0
  8. package/dist/casing-D2uQKLWS.cjs.map +1 -0
  9. package/dist/components.cjs +3 -2
  10. package/dist/components.d.ts +41 -9
  11. package/dist/components.js +2 -2
  12. package/dist/generators-CX3cSSdF.cjs +551 -0
  13. package/dist/generators-CX3cSSdF.cjs.map +1 -0
  14. package/dist/generators-dCqW0ECC.js +547 -0
  15. package/dist/generators-dCqW0ECC.js.map +1 -0
  16. package/dist/generators.cjs +2 -3
  17. package/dist/generators.d.ts +3 -503
  18. package/dist/generators.js +2 -2
  19. package/dist/index.cjs +135 -4
  20. package/dist/index.cjs.map +1 -0
  21. package/dist/index.d.ts +2 -41
  22. package/dist/index.js +134 -2
  23. package/dist/index.js.map +1 -0
  24. package/dist/resolvers-CH7hINyz.js +181 -0
  25. package/dist/resolvers-CH7hINyz.js.map +1 -0
  26. package/dist/resolvers-ebHaaCyw.cjs +191 -0
  27. package/dist/resolvers-ebHaaCyw.cjs.map +1 -0
  28. package/dist/resolvers.cjs +4 -0
  29. package/dist/resolvers.d.ts +51 -0
  30. package/dist/resolvers.js +2 -0
  31. package/dist/{types-mSXmB8WU.d.ts → types-BSRhtbGl.d.ts} +80 -57
  32. package/package.json +12 -5
  33. package/src/components/{v2/Enum.tsx → Enum.tsx} +27 -11
  34. package/src/components/Type.tsx +24 -141
  35. package/src/components/index.ts +1 -0
  36. package/src/generators/index.ts +0 -1
  37. package/src/generators/typeGenerator.tsx +204 -413
  38. package/src/generators/utils.ts +300 -0
  39. package/src/index.ts +0 -1
  40. package/src/plugin.ts +81 -126
  41. package/src/printer.ts +20 -6
  42. package/src/resolvers/index.ts +2 -0
  43. package/src/{resolverTs.ts → resolvers/resolverTs.ts} +26 -2
  44. package/src/resolvers/resolverTsLegacy.ts +85 -0
  45. package/src/types.ts +75 -52
  46. package/dist/components-CRu8IKY3.js +0 -729
  47. package/dist/components-CRu8IKY3.js.map +0 -1
  48. package/dist/components-DeNDKlzf.cjs +0 -982
  49. package/dist/components-DeNDKlzf.cjs.map +0 -1
  50. package/dist/plugin-CJ29AwE2.cjs +0 -1320
  51. package/dist/plugin-CJ29AwE2.cjs.map +0 -1
  52. package/dist/plugin-D60XNJSD.js +0 -1267
  53. package/dist/plugin-D60XNJSD.js.map +0 -1
  54. package/src/components/v2/Type.tsx +0 -59
  55. package/src/generators/v2/typeGenerator.tsx +0 -167
  56. package/src/generators/v2/utils.ts +0 -140
  57. package/src/parser.ts +0 -389
@@ -0,0 +1,300 @@
1
+ import { pascalCase } from '@internals/utils'
2
+ import { createProperty, createSchema, narrowSchema, transform } from '@kubb/ast'
3
+ import type { OperationNode, ParameterNode, SchemaNode } from '@kubb/ast/types'
4
+ import type { ResolverTs } from '../types.ts'
5
+
6
+ type BuildParamsSchemaOptions = {
7
+ params: Array<ParameterNode>
8
+ node: OperationNode
9
+ resolver: ResolverTs
10
+ }
11
+
12
+ /**
13
+ * Builds an `ObjectSchemaNode` for a group of parameters (path/query/header).
14
+ * Each property is a `ref` schema pointing to the individually-resolved parameter type.
15
+ * The ref name includes the parameter location so generated type names follow
16
+ * the `<OperationId><Location><ParamName>` convention.
17
+ */
18
+ export function buildParamsSchema({ params, node, resolver }: BuildParamsSchemaOptions): SchemaNode {
19
+ return createSchema({
20
+ type: 'object',
21
+ properties: params.map((param) =>
22
+ createProperty({
23
+ name: param.name,
24
+ schema: createSchema({
25
+ type: 'ref',
26
+ name: resolver.resolveParamName(node, param),
27
+ optional: !param.required,
28
+ }),
29
+ }),
30
+ ),
31
+ })
32
+ }
33
+
34
+ type BuildOperationSchemaOptions = {
35
+ node: OperationNode
36
+ resolver: ResolverTs
37
+ }
38
+
39
+ /**
40
+ * Builds an `ObjectSchemaNode` representing the `<OperationId>RequestConfig` type:
41
+ * - `data` → request body ref (optional) or `never`
42
+ * - `pathParams` → inline object of path param refs, or `never`
43
+ * - `queryParams` → inline object of query param refs (optional), or `never`
44
+ * - `headerParams` → inline object of header param refs (optional), or `never`
45
+ * - `url` → Express-style template literal (plugin-ts extension, handled by printer)
46
+ */
47
+ export function buildDataSchemaNode({ node, resolver }: BuildOperationSchemaOptions): SchemaNode {
48
+ const pathParams = node.parameters.filter((p) => p.in === 'path')
49
+ const queryParams = node.parameters.filter((p) => p.in === 'query')
50
+ const headerParams = node.parameters.filter((p) => p.in === 'header')
51
+
52
+ return createSchema({
53
+ type: 'object',
54
+ deprecated: node.deprecated,
55
+ properties: [
56
+ createProperty({
57
+ name: 'data',
58
+ schema: node.requestBody?.schema
59
+ ? createSchema({
60
+ type: 'ref',
61
+ name: resolver.resolveDataTypedName(node),
62
+ optional: true,
63
+ })
64
+ : createSchema({ type: 'never', optional: true }),
65
+ }),
66
+ createProperty({
67
+ name: 'pathParams',
68
+ schema: pathParams.length > 0 ? buildParamsSchema({ params: pathParams, node, resolver }) : createSchema({ type: 'never', optional: true }),
69
+ }),
70
+ createProperty({
71
+ name: 'queryParams',
72
+ schema:
73
+ queryParams.length > 0
74
+ ? createSchema({ ...buildParamsSchema({ params: queryParams, node, resolver }), optional: true })
75
+ : createSchema({ type: 'never', optional: true }),
76
+ }),
77
+ createProperty({
78
+ name: 'headerParams',
79
+ schema:
80
+ headerParams.length > 0
81
+ ? createSchema({ ...buildParamsSchema({ params: headerParams, node, resolver }), optional: true })
82
+ : createSchema({ type: 'never', optional: true }),
83
+ }),
84
+ createProperty({
85
+ name: 'url',
86
+ schema: createSchema({ type: 'url', path: node.path }),
87
+ }),
88
+ ],
89
+ })
90
+ }
91
+
92
+ /**
93
+ * Builds an `ObjectSchemaNode` representing `<OperationId>Responses` — keyed by HTTP status code.
94
+ * Numeric status codes produce unquoted numeric keys (e.g. `200:`).
95
+ * All responses are included; those without a schema are represented as a ref to a `never` type.
96
+ */
97
+ export function buildResponsesSchemaNode({ node, resolver }: BuildOperationSchemaOptions): SchemaNode | null {
98
+ if (node.responses.length === 0) {
99
+ return null
100
+ }
101
+
102
+ return createSchema({
103
+ type: 'object',
104
+ properties: node.responses.map((res) =>
105
+ createProperty({
106
+ name: String(res.statusCode),
107
+ schema: createSchema({
108
+ type: 'ref',
109
+ name: resolver.resolveResponseStatusTypedName(node, res.statusCode),
110
+ }),
111
+ }),
112
+ ),
113
+ })
114
+ }
115
+
116
+ /**
117
+ * Builds a `UnionSchemaNode` representing `<OperationId>Response` — all response types in union format.
118
+ * Returns `null` when the operation has no responses with schemas.
119
+ */
120
+ export function buildResponseUnionSchemaNode({ node, resolver }: BuildOperationSchemaOptions): SchemaNode | null {
121
+ const responsesWithSchema = node.responses.filter((res) => res.schema)
122
+
123
+ if (responsesWithSchema.length === 0) {
124
+ return null
125
+ }
126
+
127
+ return createSchema({
128
+ type: 'union',
129
+ members: responsesWithSchema.map((res) =>
130
+ createSchema({
131
+ type: 'ref',
132
+ name: resolver.resolveResponseStatusTypedName(node, res.statusCode),
133
+ }),
134
+ ),
135
+ })
136
+ }
137
+
138
+ type BuildGroupedParamsSchemaOptions = {
139
+ params: Array<ParameterNode>
140
+ /**
141
+ * Parent type name (e.g. `FindPetsByStatusQueryParams`) used to derive enum names
142
+ * for inline enum properties (e.g. `FindPetsByStatusQueryParamsStatusEnum`).
143
+ */
144
+ parentName?: string
145
+ }
146
+
147
+ /**
148
+ * Builds an `ObjectSchemaNode` for a grouped parameters type (path/query/header) in legacy mode.
149
+ * Each property directly embeds the parameter's schema inline (not a ref).
150
+ * Used to generate `<OperationId>PathParams`, `<OperationId>QueryParams`, `<OperationId>HeaderParams`.
151
+ * @deprecated Legacy only — will be removed in v6.
152
+ */
153
+ export function buildGroupedParamsSchema({ params, parentName }: BuildGroupedParamsSchemaOptions): SchemaNode {
154
+ return createSchema({
155
+ type: 'object',
156
+ properties: params.map((param) => {
157
+ let schema = { ...param.schema, optional: !param.required } as SchemaNode
158
+ // Name unnamed enum properties so they are emitted as enum declarations
159
+ if (narrowSchema(schema, 'enum') && !schema.name && parentName) {
160
+ schema = { ...schema, name: pascalCase([parentName, param.name, 'enum'].join(' ')) }
161
+ }
162
+ return createProperty({
163
+ name: param.name,
164
+ schema,
165
+ })
166
+ }),
167
+ })
168
+ }
169
+
170
+ /**
171
+ * Builds the legacy wrapper `ObjectSchemaNode` for `<OperationId>Mutation` / `<OperationId>Query`.
172
+ * Structure: `{ Response, Request?, QueryParams?, PathParams?, HeaderParams?, Errors }`.
173
+ * Mirrors the v4 naming convention where this type acts as a namespace for the operation's shapes.
174
+ *
175
+ * @deprecated Legacy only — will be removed in v6.
176
+ */
177
+ export function buildLegacyResponsesSchemaNode({ node, resolver }: BuildOperationSchemaOptions): SchemaNode | null {
178
+ const isGet = node.method.toLowerCase() === 'get'
179
+ const successResponses = node.responses.filter((res) => {
180
+ const code = Number(res.statusCode)
181
+ return !Number.isNaN(code) && code >= 200 && code < 300
182
+ })
183
+ const errorResponses = node.responses.filter((res) => res.statusCode === 'default' || Number(res.statusCode) >= 400)
184
+
185
+ const responseSchema =
186
+ successResponses.length > 0
187
+ ? successResponses.length === 1
188
+ ? createSchema({ type: 'ref', name: resolver.resolveResponseStatusTypedName(node, successResponses[0]!.statusCode) })
189
+ : createSchema({
190
+ type: 'union',
191
+ members: successResponses.map((res) => createSchema({ type: 'ref', name: resolver.resolveResponseStatusTypedName(node, res.statusCode) })),
192
+ })
193
+ : createSchema({ type: 'any' })
194
+
195
+ const errorsSchema =
196
+ errorResponses.length > 0
197
+ ? errorResponses.length === 1
198
+ ? createSchema({ type: 'ref', name: resolver.resolveResponseStatusTypedName(node, errorResponses[0]!.statusCode) })
199
+ : createSchema({
200
+ type: 'union',
201
+ members: errorResponses.map((res) => createSchema({ type: 'ref', name: resolver.resolveResponseStatusTypedName(node, res.statusCode) })),
202
+ })
203
+ : createSchema({ type: 'any' })
204
+
205
+ const properties = [createProperty({ name: 'Response', schema: responseSchema })]
206
+
207
+ if (!isGet && node.requestBody?.schema) {
208
+ properties.push(
209
+ createProperty({
210
+ name: 'Request',
211
+ schema: createSchema({ type: 'ref', name: resolver.resolveDataTypedName(node) }),
212
+ }),
213
+ )
214
+ }
215
+
216
+ if (node.parameters.some((p) => p.in === 'query') && resolver.resolveQueryParamsTypedName) {
217
+ properties.push(
218
+ createProperty({
219
+ name: 'QueryParams',
220
+ schema: createSchema({ type: 'ref', name: resolver.resolveQueryParamsTypedName(node) }),
221
+ }),
222
+ )
223
+ }
224
+
225
+ if (node.parameters.some((p) => p.in === 'path') && resolver.resolvePathParamsTypedName) {
226
+ properties.push(
227
+ createProperty({
228
+ name: 'PathParams',
229
+ schema: createSchema({ type: 'ref', name: resolver.resolvePathParamsTypedName(node) }),
230
+ }),
231
+ )
232
+ }
233
+
234
+ if (node.parameters.some((p) => p.in === 'header') && resolver.resolveHeaderParamsTypedName) {
235
+ properties.push(
236
+ createProperty({
237
+ name: 'HeaderParams',
238
+ schema: createSchema({ type: 'ref', name: resolver.resolveHeaderParamsTypedName(node) }),
239
+ }),
240
+ )
241
+ }
242
+
243
+ properties.push(createProperty({ name: 'Errors', schema: errorsSchema }))
244
+
245
+ return createSchema({ type: 'object', properties })
246
+ }
247
+
248
+ /**
249
+ * Builds the legacy response union for `<OperationId>MutationResponse` / `<OperationId>QueryResponse`.
250
+ * In legacy mode this is the **success** response only (not the full union including errors).
251
+ * Returns an `any` schema when there is no success response, matching v4 behavior.
252
+ * @deprecated Legacy only — will be removed in v6.
253
+ */
254
+ export function buildLegacyResponseUnionSchemaNode({ node, resolver }: BuildOperationSchemaOptions): SchemaNode {
255
+ const successResponses = node.responses.filter((res) => {
256
+ const code = Number(res.statusCode)
257
+ return !Number.isNaN(code) && code >= 200 && code < 300
258
+ })
259
+
260
+ if (successResponses.length === 0) {
261
+ return createSchema({ type: 'any' })
262
+ }
263
+
264
+ if (successResponses.length === 1) {
265
+ return createSchema({ type: 'ref', name: resolver.resolveResponseStatusTypedName(node, successResponses[0]!.statusCode) })
266
+ }
267
+
268
+ return createSchema({
269
+ type: 'union',
270
+ members: successResponses.map((res) => createSchema({ type: 'ref', name: resolver.resolveResponseStatusTypedName(node, res.statusCode) })),
271
+ })
272
+ }
273
+
274
+ /**
275
+ * Names unnamed enum nodes within a schema tree based on the parent type name.
276
+ * Used in legacy mode to ensure inline enums in response/request schemas get
277
+ * extracted as named enum declarations (e.g. `DeletePet200Enum`).
278
+ *
279
+ * @deprecated Legacy only — will be removed in v6.
280
+ */
281
+ export function nameUnnamedEnums(node: SchemaNode, parentName: string): SchemaNode {
282
+ return transform(node, {
283
+ schema(n) {
284
+ if (n.type === 'enum' && !n.name) {
285
+ return { ...n, name: pascalCase([parentName, 'enum'].join(' ')) }
286
+ }
287
+ return undefined
288
+ },
289
+ property(p) {
290
+ const enumNode = narrowSchema(p.schema, 'enum')
291
+ if (enumNode && !enumNode.name) {
292
+ return {
293
+ ...p,
294
+ schema: { ...enumNode, name: pascalCase([parentName, p.name, 'enum'].join(' ')) },
295
+ }
296
+ }
297
+ return undefined
298
+ },
299
+ })
300
+ }
package/src/index.ts CHANGED
@@ -1,3 +1,2 @@
1
1
  export { pluginTs, pluginTsName } from './plugin.ts'
2
- export { resolverTs } from './resolverTs.ts'
3
2
  export type { PluginTs } from './types.ts'
package/src/plugin.ts CHANGED
@@ -2,9 +2,8 @@ import path from 'node:path'
2
2
  import { camelCase } from '@internals/utils'
3
3
  import { walk } from '@kubb/ast'
4
4
  import { createPlugin, type Group, getBarrelFiles, getMode, renderOperation, renderSchema } from '@kubb/core'
5
- import { OperationGenerator, pluginOasName, SchemaGenerator } from '@kubb/plugin-oas'
6
- import { typeGenerator, typeGeneratorV2 } from './generators'
7
- import { resolverTs } from './resolverTs.ts'
5
+ import { typeGenerator } from './generators/index.ts'
6
+ import { resolverTs, resolverTsLegacy } from './resolvers/index.ts'
8
7
  import type { PluginTs } from './types.ts'
9
8
 
10
9
  export const pluginTsName = 'plugin-ts' satisfies PluginTs['name']
@@ -18,45 +17,50 @@ export const pluginTs = createPlugin<PluginTs>((options) => {
18
17
  override = [],
19
18
  enumType = 'asConst',
20
19
  enumKeyCasing = 'none',
21
- enumSuffix = 'enum',
22
- dateType = 'string',
23
- integerType = 'number',
24
- unknownType = 'any',
25
20
  optionalType = 'questionToken',
26
21
  arrayType = 'array',
27
- emptySchemaType = unknownType,
28
22
  syntaxType = 'type',
29
23
  transformers = {},
30
24
  paramsCasing,
31
- generators = [typeGenerator, typeGeneratorV2].filter(Boolean),
32
- contentType,
33
- UNSTABLE_NAMING,
25
+ generators = [typeGenerator].filter(Boolean),
26
+ legacy = false,
34
27
  } = options
35
28
 
36
- // @deprecated Will be removed in v5 when collisionDetection defaults to true
37
- const usedEnumNames = {}
29
+ const baseResolver = legacy ? resolverTsLegacy : resolverTs
30
+
31
+ // When a `transformers.name` callback is provided, wrap the resolver so that
32
+ // every name produced by `default()` (and therefore by every helper that calls
33
+ // `this.default(...)`) flows through the user's transformer.
34
+ const resolver: typeof baseResolver = transformers?.name
35
+ ? {
36
+ ...baseResolver,
37
+ default(name, type) {
38
+ const resolved = baseResolver.default(name, type)
39
+
40
+ return transformers.name!(resolved, type) || resolved
41
+ },
42
+ }
43
+ : baseResolver
44
+
45
+ let resolveNameWarning = false
38
46
 
39
47
  return {
40
48
  name: pluginTsName,
41
49
  options: {
42
50
  output,
43
51
  transformers,
44
- dateType,
45
- integerType,
46
52
  optionalType,
47
53
  arrayType,
48
54
  enumType,
49
55
  enumKeyCasing,
50
- enumSuffix,
51
- unknownType,
52
- emptySchemaType,
53
56
  syntaxType,
54
57
  group,
55
58
  override,
56
59
  paramsCasing,
57
- usedEnumNames,
60
+ legacy,
61
+ resolver,
62
+ baseResolver,
58
63
  },
59
- pre: [pluginOasName],
60
64
  resolvePath(baseName, pathMode, options) {
61
65
  const root = path.resolve(this.config.root, this.config.output.path)
62
66
  const mode = pathMode ?? getMode(path.resolve(root, output.path))
@@ -92,13 +96,12 @@ export const pluginTs = createPlugin<PluginTs>((options) => {
92
96
  return path.resolve(root, output.path, baseName)
93
97
  },
94
98
  resolveName(name, type) {
95
- const resolvedName = resolverTs.default(name, type)
96
-
97
- if (type) {
98
- return transformers?.name?.(resolvedName, type) || resolvedName
99
+ if (!resolveNameWarning) {
100
+ this.driver.events.emit('warn', 'Do not use resolveName for pluginTs, use resolverTs instead')
101
+ resolveNameWarning = true
99
102
  }
100
103
 
101
- return resolvedName
104
+ return resolver.default(name, type)
102
105
  },
103
106
  async install() {
104
107
  const { config, fabric, plugin, adapter, rootNode, driver, openInStudio } = this
@@ -106,114 +109,66 @@ export const pluginTs = createPlugin<PluginTs>((options) => {
106
109
  const root = path.resolve(config.root, config.output.path)
107
110
  const mode = getMode(path.resolve(root, output.path))
108
111
 
109
- if (adapter) {
110
- await openInStudio({ ast: true })
111
-
112
- await walk(
113
- rootNode,
114
- {
115
- async schema(schemaNode) {
116
- const writeTasks = generators.map(async (generator) => {
117
- if (generator.type === 'react' && generator.version === '2') {
118
- const options = resolverTs.resolveOptions(schemaNode, { options: plugin.options, exclude, include, override })
119
-
120
- if (options === null) {
121
- return
122
- }
123
-
124
- await renderSchema(schemaNode, {
125
- options,
126
- adapter,
127
- config,
128
- fabric,
129
- Component: generator.Schema,
130
- plugin,
131
- driver,
132
- mode,
133
- })
134
- }
135
- })
136
-
137
- await Promise.all(writeTasks)
138
- },
139
- async operation(operationNode) {
140
- const writeTasks = generators.map(async (generator) => {
141
- if (generator.type === 'react' && generator.version === '2') {
142
- const options = resolverTs.resolveOptions(operationNode, { options: plugin.options, exclude, include, override })
143
-
144
- if (options === null) {
145
- return
146
- }
147
-
148
- await renderOperation(operationNode, {
149
- options,
150
- adapter,
151
- config,
152
- fabric,
153
- Component: generator.Operation,
154
- plugin,
155
- driver,
156
- mode,
157
- })
158
- }
159
- })
112
+ if (!adapter) {
113
+ throw new Error('Plugin cannot work without adapter being set')
114
+ }
160
115
 
161
- await Promise.all(writeTasks)
162
- },
163
- },
164
- { depth: 'shallow' },
165
- )
116
+ await openInStudio({ ast: true })
166
117
 
167
- const barrelFiles = await getBarrelFiles(this.fabric.files, {
168
- type: output.barrelType ?? 'named',
169
- root,
170
- output,
171
- meta: {
172
- pluginName: this.plugin.name,
173
- },
174
- })
118
+ await walk(
119
+ rootNode,
120
+ {
121
+ async schema(schemaNode) {
122
+ const writeTasks = generators.map(async (generator) => {
123
+ if (generator.type === 'react' && generator.version === '2') {
124
+ const options = resolver.resolveOptions(schemaNode, { options: plugin.options, exclude, include, override })
175
125
 
176
- await this.upsertFile(...barrelFiles)
126
+ if (options === null) {
127
+ return
128
+ }
177
129
 
178
- return
179
- }
130
+ await renderSchema(schemaNode, {
131
+ options,
132
+ adapter,
133
+ config,
134
+ fabric,
135
+ Component: generator.Schema,
136
+ plugin,
137
+ driver,
138
+ mode,
139
+ })
140
+ }
141
+ })
180
142
 
181
- // v1 flow
182
-
183
- const oas = await this.getOas()
184
-
185
- const schemaGenerator = new SchemaGenerator(this.plugin.options, {
186
- fabric: this.fabric,
187
- oas,
188
- driver: this.driver,
189
- events: this.events,
190
- plugin: this.plugin,
191
- contentType,
192
- include: undefined,
193
- override,
194
- mode,
195
- output: output.path,
196
- })
143
+ await Promise.all(writeTasks)
144
+ },
145
+ async operation(operationNode) {
146
+ const writeTasks = generators.map(async (generator) => {
147
+ if (generator.type === 'react' && generator.version === '2') {
148
+ const options = resolver.resolveOptions(operationNode, { options: plugin.options, exclude, include, override })
197
149
 
198
- const schemaFiles = await schemaGenerator.build(...generators)
199
- await this.upsertFile(...schemaFiles)
200
-
201
- const operationGenerator = new OperationGenerator(this.plugin.options, {
202
- fabric: this.fabric,
203
- oas,
204
- driver: this.driver,
205
- events: this.events,
206
- plugin: this.plugin,
207
- contentType,
208
- exclude,
209
- include,
210
- override,
211
- mode,
212
- UNSTABLE_NAMING,
213
- })
150
+ if (options === null) {
151
+ return
152
+ }
153
+
154
+ await renderOperation(operationNode, {
155
+ options,
156
+ adapter,
157
+ config,
158
+ fabric,
159
+ Component: generator.Operation,
160
+ plugin,
161
+ driver,
162
+ mode,
163
+ })
164
+ }
165
+ })
214
166
 
215
- const operationFiles = await operationGenerator.build(...generators)
216
- await this.upsertFile(...operationFiles)
167
+ await Promise.all(writeTasks)
168
+ },
169
+ },
170
+ { depth: 'shallow' },
171
+ )
217
172
 
218
173
  const barrelFiles = await getBarrelFiles(this.fabric.files, {
219
174
  type: output.barrelType ?? 'named',
package/src/printer.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { jsStringEscape, pascalCase, stringify } from '@internals/utils'
1
+ import { jsStringEscape, stringify } from '@internals/utils'
2
2
  import { isPlainStringType } from '@kubb/ast'
3
3
  import type { ArraySchemaNode, SchemaNode } from '@kubb/ast/types'
4
4
  import type { PrinterFactoryOptions } from '@kubb/core'
@@ -6,7 +6,7 @@ import { definePrinter } from '@kubb/core'
6
6
  import type ts from 'typescript'
7
7
  import { ENUM_TYPES_WITH_KEY_SUFFIX, OPTIONAL_ADDS_QUESTION_TOKEN, OPTIONAL_ADDS_UNDEFINED } from './constants.ts'
8
8
  import * as factory from './factory.ts'
9
- import type { PluginTs } from './types.ts'
9
+ import type { PluginTs, ResolverTs } from './types.ts'
10
10
 
11
11
  type TsOptions = {
12
12
  /**
@@ -41,6 +41,10 @@ type TsOptions = {
41
41
  * Forces type-alias syntax even when `syntaxType` is `'interface'`.
42
42
  */
43
43
  keysToOmit?: Array<string>
44
+ /**
45
+ * Resolver used to transform raw schema names into valid TypeScript identifiers.
46
+ */
47
+ resolver: ResolverTs
44
48
  }
45
49
 
46
50
  /**
@@ -130,11 +134,14 @@ function buildPropertyType(schema: SchemaNode, baseType: ts.TypeNode, optionalTy
130
134
  * Collects JSDoc annotation strings (description, deprecated, min/max, pattern, default, example, type) for a schema node.
131
135
  */
132
136
  function buildPropertyJSDocComments(schema: SchemaNode): Array<string | undefined> {
137
+ const isArray = schema.type === 'array'
138
+
133
139
  return [
134
140
  'description' in schema && schema.description ? `@description ${jsStringEscape(schema.description)}` : undefined,
135
141
  'deprecated' in schema && schema.deprecated ? '@deprecated' : undefined,
136
- 'min' in schema && schema.min !== undefined ? `@minLength ${schema.min}` : undefined,
137
- 'max' in schema && schema.max !== undefined ? `@maxLength ${schema.max}` : undefined,
142
+ // minItems/maxItems on arrays should not be emitted as @minLength/@maxLength
143
+ !isArray && 'min' in schema && schema.min !== undefined ? `@minLength ${schema.min}` : undefined,
144
+ !isArray && 'max' in schema && schema.max !== undefined ? `@maxLength ${schema.max}` : undefined,
138
145
  'pattern' in schema && schema.pattern ? `@pattern ${schema.pattern}` : undefined,
139
146
  'default' in schema && schema.default !== undefined
140
147
  ? `@default ${'primitive' in schema && schema.primitive === 'string' ? stringify(schema.default as string) : schema.default}`
@@ -235,7 +242,14 @@ export const printerTs = definePrinter<TsPrinter>((options) => {
235
242
  if (!node.name) {
236
243
  return undefined
237
244
  }
238
- return factory.createTypeReferenceNode(node.name, undefined)
245
+ // Parser-generated refs (with $ref) carry raw schema names that need resolving.
246
+ // Use the canonical name from the $ref path — node.name may have been overridden
247
+ // (e.g. by single-member allOf flatten using the property-derived child name).
248
+ // Inline refs (without $ref) from utils already carry resolved type names.
249
+ const refName = node.ref ? (node.ref.split('/').at(-1) ?? node.name) : node.name
250
+ const name = node.ref ? this.options.resolver.default(refName, 'type') : refName
251
+
252
+ return factory.createTypeReferenceNode(name, undefined)
239
253
  },
240
254
  enum(node) {
241
255
  const values = node.namedEnumValues?.map((v) => v.value) ?? node.enumValues ?? []
@@ -249,7 +263,7 @@ export const printerTs = definePrinter<TsPrinter>((options) => {
249
263
  return factory.createUnionDeclaration({ withParentheses: true, nodes: literalNodes }) ?? undefined
250
264
  }
251
265
 
252
- const resolvedName = pascalCase(node.name)
266
+ const resolvedName = this.options.resolver.default(node.name, 'type')
253
267
  const typeName = ENUM_TYPES_WITH_KEY_SUFFIX.has(this.options.enumType) ? `${resolvedName}Key` : resolvedName
254
268
 
255
269
  return factory.createTypeReferenceNode(typeName, undefined)
@@ -0,0 +1,2 @@
1
+ export { resolverTs } from './resolverTs.ts'
2
+ export { resolverTsLegacy } from './resolverTsLegacy.ts'