@kubb/plugin-ts 4.14.0 → 4.15.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.
Files changed (42) hide show
  1. package/dist/{components-HKIi69kI.js → components-BE9rQGox.js} +60 -28
  2. package/dist/components-BE9rQGox.js.map +1 -0
  3. package/dist/{components-pzX1Kb4u.cjs → components-CGcs8968.cjs} +90 -28
  4. package/dist/components-CGcs8968.cjs.map +1 -0
  5. package/dist/components.cjs +1 -1
  6. package/dist/components.d.cts +3 -1
  7. package/dist/components.d.ts +3 -1
  8. package/dist/components.js +1 -1
  9. package/dist/generators.cjs +1 -1
  10. package/dist/generators.d.cts +1 -1
  11. package/dist/generators.d.ts +1 -1
  12. package/dist/generators.js +1 -1
  13. package/dist/index.cjs +1 -1
  14. package/dist/index.d.cts +1 -1
  15. package/dist/index.d.ts +1 -1
  16. package/dist/index.js +1 -1
  17. package/dist/{plugin-DljW3znk.cjs → plugin-B96TNDf4.cjs} +174 -21
  18. package/dist/plugin-B96TNDf4.cjs.map +1 -0
  19. package/dist/{plugin-BEfry-Dm.js → plugin-DXyNbr_u.js} +173 -21
  20. package/dist/plugin-DXyNbr_u.js.map +1 -0
  21. package/dist/{types-DcA3qMIF.d.ts → types-D59kq_S_.d.ts} +27 -1
  22. package/dist/{types-C51mhXW0.d.cts → types-njfY0PGo.d.cts} +27 -1
  23. package/package.json +4 -4
  24. package/src/components/Type.tsx +8 -2
  25. package/src/factory.ts +77 -3
  26. package/src/generators/__snapshots__/createPet.ts +1 -1
  27. package/src/generators/__snapshots__/createPetWithEmptySchemaTypeUnknown.ts +1 -1
  28. package/src/generators/__snapshots__/createPetWithEmptySchemaTypeVoid.ts +1 -1
  29. package/src/generators/__snapshots__/createPetWithUnknownTypeUnknown.ts +1 -1
  30. package/src/generators/__snapshots__/createPetWithUnknownTypeVoid.ts +1 -1
  31. package/src/generators/__snapshots__/deletePet.ts +1 -1
  32. package/src/generators/__snapshots__/getPets.ts +1 -1
  33. package/src/generators/__snapshots__/showPetById.ts +1 -1
  34. package/src/generators/__snapshots__/systemsWithExplodeForm.ts +1 -1
  35. package/src/generators/typeGenerator.tsx +232 -8
  36. package/src/parser.ts +9 -20
  37. package/src/plugin.ts +4 -0
  38. package/src/types.ts +12 -0
  39. package/dist/components-HKIi69kI.js.map +0 -1
  40. package/dist/components-pzX1Kb4u.cjs.map +0 -1
  41. package/dist/plugin-BEfry-Dm.js.map +0 -1
  42. package/dist/plugin-DljW3znk.cjs.map +0 -1
package/src/factory.ts CHANGED
@@ -17,6 +17,16 @@ export const syntaxKind = {
17
17
  union: SyntaxKind.UnionType as 192,
18
18
  } as const
19
19
 
20
+ export function getUnknownType(unknownType: 'any' | 'unknown' | 'void' | undefined) {
21
+ if (unknownType === 'any') {
22
+ return keywordTypeNodes.any
23
+ }
24
+ if (unknownType === 'void') {
25
+ return keywordTypeNodes.void
26
+ }
27
+
28
+ return keywordTypeNodes.unknown
29
+ }
20
30
  function isValidIdentifier(str: string): boolean {
21
31
  if (!str.length || str.trim() !== str) {
22
32
  return false
@@ -86,16 +96,29 @@ export function createTupleDeclaration({ nodes, withParentheses }: { nodes: Arra
86
96
  return node
87
97
  }
88
98
 
89
- export function createArrayDeclaration({ nodes }: { nodes: Array<ts.TypeNode> }): ts.TypeNode | null {
99
+ export function createArrayDeclaration({ nodes, arrayType = 'array' }: { nodes: Array<ts.TypeNode>; arrayType?: 'array' | 'generic' }): ts.TypeNode | null {
90
100
  if (!nodes.length) {
91
101
  return factory.createTupleTypeNode([])
92
102
  }
93
103
 
94
104
  if (nodes.length === 1) {
95
- return factory.createArrayTypeNode(nodes.at(0)!)
105
+ const node = nodes[0]
106
+ if (!node) {
107
+ return null
108
+ }
109
+ if (arrayType === 'generic') {
110
+ return factory.createTypeReferenceNode(factory.createIdentifier('Array'), [node])
111
+ }
112
+ return factory.createArrayTypeNode(node)
96
113
  }
97
114
 
98
- return factory.createExpressionWithTypeArguments(factory.createIdentifier('Array'), [factory.createUnionTypeNode(nodes)])
115
+ // For union types (multiple nodes), respect arrayType preference
116
+ const unionType = factory.createUnionTypeNode(nodes)
117
+ if (arrayType === 'generic') {
118
+ return factory.createTypeReferenceNode(factory.createIdentifier('Array'), [unionType])
119
+ }
120
+ // For array syntax with unions, we need parentheses: (string | number)[]
121
+ return factory.createArrayTypeNode(factory.createParenthesizedType(unionType))
99
122
  }
100
123
 
101
124
  /**
@@ -572,8 +595,56 @@ export const keywordTypeNodes = {
572
595
  boolean: factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword),
573
596
  undefined: factory.createKeywordTypeNode(ts.SyntaxKind.UndefinedKeyword),
574
597
  null: factory.createLiteralTypeNode(factory.createToken(ts.SyntaxKind.NullKeyword)),
598
+ never: factory.createKeywordTypeNode(ts.SyntaxKind.NeverKeyword),
575
599
  } as const
576
600
 
601
+ /**
602
+ * Converts a path like '/pet/{petId}/uploadImage' to a template literal type
603
+ * like `/pet/${string}/uploadImage`
604
+ */
605
+ export function createUrlTemplateType(path: string): ts.TypeNode {
606
+ // If no parameters, return literal string type
607
+ if (!path.includes('{')) {
608
+ return factory.createLiteralTypeNode(factory.createStringLiteral(path))
609
+ }
610
+
611
+ // Split path by parameter placeholders, e.g. '/pet/{petId}/upload' -> ['/pet/', 'petId', '/upload']
612
+ const segments = path.split(/(\{[^}]+\})/)
613
+
614
+ // Separate static parts from parameter placeholders
615
+ const parts: string[] = []
616
+ const parameterIndices: number[] = []
617
+
618
+ segments.forEach((segment) => {
619
+ if (segment.startsWith('{') && segment.endsWith('}')) {
620
+ // This is a parameter placeholder
621
+ parameterIndices.push(parts.length)
622
+ parts.push(segment) // Will be replaced with ${string}
623
+ } else if (segment) {
624
+ // This is a static part
625
+ parts.push(segment)
626
+ }
627
+ })
628
+
629
+ // Build template literal type
630
+ // Template literal structure: head + templateSpans[]
631
+ // For '/pet/{petId}/upload': head = '/pet/', spans = [{ type: string, literal: '/upload' }]
632
+
633
+ const head = ts.factory.createTemplateHead(parts[0] || '')
634
+ const templateSpans: ts.TemplateLiteralTypeSpan[] = []
635
+
636
+ parameterIndices.forEach((paramIndex, i) => {
637
+ const isLast = i === parameterIndices.length - 1
638
+ const nextPart = parts[paramIndex + 1] || ''
639
+
640
+ const literal = isLast ? ts.factory.createTemplateTail(nextPart) : ts.factory.createTemplateMiddle(nextPart)
641
+
642
+ templateSpans.push(ts.factory.createTemplateLiteralTypeSpan(keywordTypeNodes.string, literal))
643
+ })
644
+
645
+ return ts.factory.createTemplateLiteralType(head, templateSpans)
646
+ }
647
+
577
648
  export const createTypeLiteralNode = factory.createTypeLiteralNode
578
649
 
579
650
  export const createTypeReferenceNode = factory.createTypeReferenceNode
@@ -581,6 +652,7 @@ export const createNumericLiteral = factory.createNumericLiteral
581
652
  export const createStringLiteral = factory.createStringLiteral
582
653
 
583
654
  export const createArrayTypeNode = factory.createArrayTypeNode
655
+ export const createParenthesizedType = factory.createParenthesizedType
584
656
 
585
657
  export const createLiteralTypeNode = factory.createLiteralTypeNode
586
658
  export const createNull = factory.createNull
@@ -591,3 +663,5 @@ export const createTupleTypeNode = factory.createTupleTypeNode
591
663
  export const createRestTypeNode = factory.createRestTypeNode
592
664
  export const createTrue = factory.createTrue
593
665
  export const createFalse = factory.createFalse
666
+ export const createIndexedAccessTypeNode = factory.createIndexedAccessTypeNode
667
+ export const createTypeOperatorNode = factory.createTypeOperatorNode
@@ -40,7 +40,7 @@ export type CreatePetsMutationRequest = {
40
40
 
41
41
  export type CreatePetsMutationResponse = createPets201 | createPets202
42
42
 
43
- export type createPetsMutation = {
43
+ export type CreatePetsMutation = {
44
44
  Response: createPets201 | createPets202
45
45
  Request: createPetsMutationRequest
46
46
  Errors: any
@@ -40,7 +40,7 @@ export type CreatePetsMutationRequest = {
40
40
 
41
41
  export type CreatePetsMutationResponse = createPets201 | createPets202
42
42
 
43
- export type createPetsMutation = {
43
+ export type CreatePetsMutation = {
44
44
  Response: createPets201 | createPets202
45
45
  Request: createPetsMutationRequest
46
46
  Errors: any
@@ -40,7 +40,7 @@ export type CreatePetsMutationRequest = {
40
40
 
41
41
  export type CreatePetsMutationResponse = createPets201 | createPets202
42
42
 
43
- export type createPetsMutation = {
43
+ export type CreatePetsMutation = {
44
44
  Response: createPets201 | createPets202
45
45
  Request: createPetsMutationRequest
46
46
  Errors: any
@@ -40,7 +40,7 @@ export type CreatePetsMutationRequest = {
40
40
 
41
41
  export type CreatePetsMutationResponse = createPets201 | createPets202
42
42
 
43
- export type createPetsMutation = {
43
+ export type CreatePetsMutation = {
44
44
  Response: createPets201 | createPets202
45
45
  Request: createPetsMutationRequest
46
46
  Errors: any
@@ -40,7 +40,7 @@ export type CreatePetsMutationRequest = {
40
40
 
41
41
  export type CreatePetsMutationResponse = createPets201 | createPets202
42
42
 
43
- export type createPetsMutation = {
43
+ export type CreatePetsMutation = {
44
44
  Response: createPets201 | createPets202
45
45
  Request: createPetsMutationRequest
46
46
  Errors: any
@@ -5,7 +5,7 @@
5
5
 
6
6
  export type DeletePetsPetidMutationResponse = any
7
7
 
8
- export type deletePetsPetidMutation = {
8
+ export type DeletePetsPetidMutation = {
9
9
  Response: any
10
10
  Errors: any
11
11
  }
@@ -23,7 +23,7 @@ export type ListPetsError = error
23
23
 
24
24
  export type ListPetsQueryResponse = listPets200
25
25
 
26
- export type listPetsQuery = {
26
+ export type ListPetsQuery = {
27
27
  Response: listPets200
28
28
  QueryParams: listPetsQueryParams
29
29
  Errors: any
@@ -28,7 +28,7 @@ export type ShowPetByIdError = error
28
28
 
29
29
  export type ShowPetByIdQueryResponse = showPetById200
30
30
 
31
- export type showPetByIdQuery = {
31
+ export type ShowPetByIdQuery = {
32
32
  Response: showPetById200
33
33
  PathParams: showPetByIdPathParams
34
34
  Errors: any
@@ -17,7 +17,7 @@ export type Systems200 = object
17
17
 
18
18
  export type SystemsQueryResponse = systems200
19
19
 
20
- export type systemsQuery = {
20
+ export type SystemsQuery = {
21
21
  Response: systems200
22
22
  QueryParams: systemsQueryParams
23
23
  Errors: any
@@ -2,14 +2,16 @@ import type { PluginManager } from '@kubb/core'
2
2
  import { useMode, usePluginManager } from '@kubb/core/hooks'
3
3
  import transformers from '@kubb/core/transformers'
4
4
  import { safePrint } from '@kubb/fabric-core/parsers/typescript'
5
+ import type { Operation } from '@kubb/oas'
5
6
  import { isKeyword, type OperationSchemas, type OperationSchema as OperationSchemaType, SchemaGenerator, schemaKeywords } from '@kubb/plugin-oas'
6
7
  import { createReactGenerator } from '@kubb/plugin-oas/generators'
7
8
  import { useOas, useOperationManager, useSchemaManager } from '@kubb/plugin-oas/hooks'
8
9
  import { getBanner, getFooter } from '@kubb/plugin-oas/utils'
9
10
  import { File } from '@kubb/react-fabric'
10
- import type ts from 'typescript'
11
+ import ts from 'typescript'
11
12
  import { Type } from '../components'
12
13
  import * as factory from '../factory.ts'
14
+ import { createUrlTemplateType, getUnknownType, keywordTypeNodes } from '../factory.ts'
13
15
  import { pluginTsName } from '../plugin.ts'
14
16
  import type { PluginTs } from '../types'
15
17
 
@@ -103,12 +105,216 @@ function printCombinedSchema({ name, schemas, pluginManager }: { name: string; s
103
105
  return safePrint(namespaceNode)
104
106
  }
105
107
 
108
+ function printRequestSchema({
109
+ baseName,
110
+ operation,
111
+ schemas,
112
+ pluginManager,
113
+ }: {
114
+ baseName: string
115
+ operation: Operation
116
+ schemas: OperationSchemas
117
+ pluginManager: PluginManager
118
+ }): string {
119
+ const name = pluginManager.resolveName({
120
+ name: `${baseName} Request`,
121
+ pluginKey: [pluginTsName],
122
+ type: 'type',
123
+ })
124
+
125
+ const results: string[] = []
126
+
127
+ // Generate DataRequest type
128
+ const dataRequestProperties: ts.PropertySignature[] = []
129
+
130
+ if (schemas.request) {
131
+ const identifier = pluginManager.resolveName({
132
+ name: schemas.request.name,
133
+ pluginKey: [pluginTsName],
134
+ type: 'type',
135
+ })
136
+ dataRequestProperties.push(
137
+ factory.createPropertySignature({
138
+ name: 'data',
139
+ questionToken: true,
140
+ type: factory.createTypeReferenceNode(factory.createIdentifier(identifier), undefined),
141
+ }),
142
+ )
143
+ } else {
144
+ dataRequestProperties.push(
145
+ factory.createPropertySignature({
146
+ name: 'data',
147
+ questionToken: true,
148
+ type: keywordTypeNodes.never,
149
+ }),
150
+ )
151
+ }
152
+
153
+ // Add pathParams property
154
+ if (schemas.pathParams) {
155
+ const identifier = pluginManager.resolveName({
156
+ name: schemas.pathParams.name,
157
+ pluginKey: [pluginTsName],
158
+ type: 'type',
159
+ })
160
+ dataRequestProperties.push(
161
+ factory.createPropertySignature({
162
+ name: 'pathParams',
163
+ type: factory.createTypeReferenceNode(factory.createIdentifier(identifier), undefined),
164
+ }),
165
+ )
166
+ } else {
167
+ dataRequestProperties.push(
168
+ factory.createPropertySignature({
169
+ name: 'pathParams',
170
+ questionToken: true,
171
+ type: keywordTypeNodes.never,
172
+ }),
173
+ )
174
+ }
175
+
176
+ // Add queryParams property
177
+ if (schemas.queryParams) {
178
+ const identifier = pluginManager.resolveName({
179
+ name: schemas.queryParams.name,
180
+ pluginKey: [pluginTsName],
181
+ type: 'type',
182
+ })
183
+ dataRequestProperties.push(
184
+ factory.createPropertySignature({
185
+ name: 'queryParams',
186
+ questionToken: true,
187
+ type: factory.createTypeReferenceNode(factory.createIdentifier(identifier), undefined),
188
+ }),
189
+ )
190
+ } else {
191
+ dataRequestProperties.push(
192
+ factory.createPropertySignature({
193
+ name: 'queryParams',
194
+ questionToken: true,
195
+ type: keywordTypeNodes.never,
196
+ }),
197
+ )
198
+ }
199
+
200
+ // Add headerParams property
201
+ if (schemas.headerParams) {
202
+ const identifier = pluginManager.resolveName({
203
+ name: schemas.headerParams.name,
204
+ pluginKey: [pluginTsName],
205
+ type: 'type',
206
+ })
207
+ dataRequestProperties.push(
208
+ factory.createPropertySignature({
209
+ name: 'headerParams',
210
+ questionToken: true,
211
+ type: factory.createTypeReferenceNode(factory.createIdentifier(identifier), undefined),
212
+ }),
213
+ )
214
+ } else {
215
+ dataRequestProperties.push(
216
+ factory.createPropertySignature({
217
+ name: 'headerParams',
218
+ questionToken: true,
219
+ type: keywordTypeNodes.never,
220
+ }),
221
+ )
222
+ }
223
+
224
+ // Add url property with template literal type
225
+ dataRequestProperties.push(
226
+ factory.createPropertySignature({
227
+ name: 'url',
228
+ type: createUrlTemplateType(operation.path),
229
+ }),
230
+ )
231
+
232
+ const dataRequestNode = factory.createTypeAliasDeclaration({
233
+ name,
234
+ type: factory.createTypeLiteralNode(dataRequestProperties),
235
+ modifiers: [factory.modifiers.export],
236
+ })
237
+
238
+ results.push(safePrint(dataRequestNode))
239
+
240
+ return results.join('\n\n')
241
+ }
242
+
243
+ function printResponseSchema({
244
+ baseName,
245
+ schemas,
246
+ pluginManager,
247
+ unknownType,
248
+ }: {
249
+ baseName: string
250
+ schemas: OperationSchemas
251
+ pluginManager: PluginManager
252
+ unknownType: PluginTs['resolvedOptions']['unknownType']
253
+ }): string {
254
+ const results: string[] = []
255
+
256
+ const name = pluginManager.resolveName({
257
+ name: `${baseName} ResponseData`,
258
+ pluginKey: [pluginTsName],
259
+ type: 'type',
260
+ })
261
+
262
+ // Generate Responses type (mapping status codes to response types)
263
+ if (schemas.responses && schemas.responses.length > 0) {
264
+ const responsesProperties: ts.PropertySignature[] = schemas.responses.map((res) => {
265
+ const identifier = pluginManager.resolveName({
266
+ name: res.name,
267
+ pluginKey: [pluginTsName],
268
+ type: 'type',
269
+ })
270
+
271
+ return factory.createPropertySignature({
272
+ name: res.statusCode?.toString() ?? 'default',
273
+ type: factory.createTypeReferenceNode(factory.createIdentifier(identifier), undefined),
274
+ })
275
+ })
276
+
277
+ const responsesNode = factory.createTypeAliasDeclaration({
278
+ name: `${baseName}Responses`,
279
+ type: factory.createTypeLiteralNode(responsesProperties),
280
+ modifiers: [factory.modifiers.export],
281
+ })
282
+
283
+ results.push(safePrint(responsesNode))
284
+
285
+ // Generate Response type (union via indexed access)
286
+ const responseNode = factory.createTypeAliasDeclaration({
287
+ name,
288
+ type: factory.createIndexedAccessTypeNode(
289
+ factory.createTypeReferenceNode(factory.createIdentifier(`${baseName}Responses`), undefined),
290
+ factory.createTypeOperatorNode(
291
+ ts.SyntaxKind.KeyOfKeyword,
292
+ factory.createTypeReferenceNode(factory.createIdentifier(`${baseName}Responses`), undefined),
293
+ ),
294
+ ),
295
+ modifiers: [factory.modifiers.export],
296
+ })
297
+
298
+ results.push(safePrint(responseNode))
299
+ } else {
300
+ const responseNode = factory.createTypeAliasDeclaration({
301
+ name,
302
+ modifiers: [factory.modifiers.export],
303
+ type: getUnknownType(unknownType),
304
+ })
305
+
306
+ results.push(safePrint(responseNode))
307
+ }
308
+
309
+ return results.join('\n\n')
310
+ }
311
+
106
312
  export const typeGenerator = createReactGenerator<PluginTs>({
107
313
  name: 'typescript',
108
314
  Operation({ operation, generator, plugin }) {
109
315
  const {
110
316
  options,
111
- options: { mapper, enumType, syntaxType, optionalType },
317
+ options: { mapper, enumType, syntaxType, optionalType, arrayType, unknownType },
112
318
  } = plugin
113
319
 
114
320
  const mode = useMode()
@@ -118,10 +324,10 @@ export const typeGenerator = createReactGenerator<PluginTs>({
118
324
  const { getSchemas, getFile, getName, getGroup } = useOperationManager(generator)
119
325
  const schemaManager = useSchemaManager()
120
326
 
327
+ const name = getName(operation, { type: 'type', pluginKey: [pluginTsName] })
328
+
121
329
  const file = getFile(operation)
122
330
  const schemas = getSchemas(operation)
123
- const type = getName(operation, { type: 'function', pluginKey: [pluginTsName] })
124
- const combinedSchemaName = operation.method === 'get' ? `${type}Query` : `${type}Mutation`
125
331
  const schemaGenerator = new SchemaGenerator(options, {
126
332
  fabric: generator.context.fabric,
127
333
  oas,
@@ -162,6 +368,7 @@ export const typeGenerator = createReactGenerator<PluginTs>({
162
368
  mapper={mapper}
163
369
  enumType={enumType}
164
370
  optionalType={optionalType}
371
+ arrayType={arrayType}
165
372
  keysToOmit={keysToOmit}
166
373
  syntaxType={syntaxType}
167
374
  />
@@ -169,6 +376,11 @@ export const typeGenerator = createReactGenerator<PluginTs>({
169
376
  )
170
377
  }
171
378
 
379
+ const responseName = schemaManager.getName(schemas.response.name, {
380
+ type: 'type',
381
+ })
382
+ const combinedSchemaName = operation.method === 'get' ? `${name}Query` : `${name}Mutation`
383
+
172
384
  return (
173
385
  <File
174
386
  baseName={file.baseName}
@@ -179,15 +391,26 @@ export const typeGenerator = createReactGenerator<PluginTs>({
179
391
  >
180
392
  {operationSchemas.map(mapOperationSchema)}
181
393
 
182
- <File.Source name={combinedSchemaName} isExportable isIndexable isTypeOnly>
183
- {printCombinedSchema({ name: combinedSchemaName, schemas, pluginManager })}
184
- </File.Source>
394
+ {generator.context.UNSTABLE_NAMING ? (
395
+ <>
396
+ <File.Source name={`${name}Request`} isExportable isIndexable isTypeOnly>
397
+ {printRequestSchema({ baseName: name, operation, schemas, pluginManager })}
398
+ </File.Source>
399
+ <File.Source name={responseName} isExportable isIndexable isTypeOnly>
400
+ {printResponseSchema({ baseName: name, schemas, pluginManager, unknownType })}
401
+ </File.Source>
402
+ </>
403
+ ) : (
404
+ <File.Source name={combinedSchemaName} isExportable isIndexable isTypeOnly>
405
+ {printCombinedSchema({ name: combinedSchemaName, schemas, pluginManager })}
406
+ </File.Source>
407
+ )}
185
408
  </File>
186
409
  )
187
410
  },
188
411
  Schema({ schema, plugin }) {
189
412
  const {
190
- options: { mapper, enumType, syntaxType, optionalType, output },
413
+ options: { mapper, enumType, syntaxType, optionalType, arrayType, output },
191
414
  } = plugin
192
415
  const mode = useMode()
193
416
 
@@ -231,6 +454,7 @@ export const typeGenerator = createReactGenerator<PluginTs>({
231
454
  mapper={mapper}
232
455
  enumType={enumType}
233
456
  optionalType={optionalType}
457
+ arrayType={arrayType}
234
458
  syntaxType={syntaxType}
235
459
  />
236
460
  </File>
package/src/parser.ts CHANGED
@@ -23,12 +23,12 @@ export const typeKeywordMapper = {
23
23
  nullable: undefined,
24
24
  null: () => factory.keywordTypeNodes.null,
25
25
  nullish: undefined,
26
- array: (nodes?: ts.TypeNode[]) => {
26
+ array: (nodes?: ts.TypeNode[], arrayType?: 'array' | 'generic') => {
27
27
  if (!nodes) {
28
28
  return undefined
29
29
  }
30
30
 
31
- return factory.createArrayDeclaration({ nodes })
31
+ return factory.createArrayDeclaration({ nodes, arrayType })
32
32
  },
33
33
  tuple: (nodes?: ts.TypeNode[], rest?: ts.TypeNode, min?: number, max?: number) => {
34
34
  if (!nodes) {
@@ -142,6 +142,10 @@ type ParserOptions = {
142
142
  * @default `'questionToken'`
143
143
  */
144
144
  optionalType: 'questionToken' | 'undefined' | 'questionTokenAndUndefined'
145
+ /**
146
+ * @default `'array'`
147
+ */
148
+ arrayType: 'array' | 'generic'
145
149
  /**
146
150
  * Choose to use `enum`, `asConst`, `asPascalConst`, `constEnum`, `literal`, or `inlineLiteral` for enums.
147
151
  * - `enum`: TypeScript enum
@@ -173,7 +177,6 @@ export const parse = createParser<ts.Node | null, ParserOptions>({
173
177
  handlers: {
174
178
  union(tree, options) {
175
179
  const { current, schema, name } = tree
176
- if (!isKeyword(current, schemaKeywords.union)) return undefined
177
180
 
178
181
  return typeKeywordMapper.union(
179
182
  current.args.map((it) => this.parse({ schema, parent: current, name, current: it, siblings: [] }, options)).filter(Boolean) as ts.TypeNode[],
@@ -181,7 +184,6 @@ export const parse = createParser<ts.Node | null, ParserOptions>({
181
184
  },
182
185
  and(tree, options) {
183
186
  const { current, schema, name } = tree
184
- if (!isKeyword(current, schemaKeywords.and)) return undefined
185
187
 
186
188
  return typeKeywordMapper.and(
187
189
  current.args.map((it) => this.parse({ schema, parent: current, name, current: it, siblings: [] }, options)).filter(Boolean) as ts.TypeNode[],
@@ -189,15 +191,14 @@ export const parse = createParser<ts.Node | null, ParserOptions>({
189
191
  },
190
192
  array(tree, options) {
191
193
  const { current, schema, name } = tree
192
- if (!isKeyword(current, schemaKeywords.array)) return undefined
193
194
 
194
195
  return typeKeywordMapper.array(
195
196
  current.args.items.map((it) => this.parse({ schema, parent: current, name, current: it, siblings: [] }, options)).filter(Boolean) as ts.TypeNode[],
197
+ options.arrayType,
196
198
  )
197
199
  },
198
200
  enum(tree, options) {
199
201
  const { current } = tree
200
- if (!isKeyword(current, schemaKeywords.enum)) return undefined
201
202
 
202
203
  // If enumType is 'inlineLiteral', generate the literal union inline instead of a type reference
203
204
  if (options.enumType === 'inlineLiteral') {
@@ -218,19 +219,14 @@ export const parse = createParser<ts.Node | null, ParserOptions>({
218
219
  },
219
220
  ref(tree, _options) {
220
221
  const { current } = tree
221
- if (!isKeyword(current, schemaKeywords.ref)) return undefined
222
222
 
223
223
  return typeKeywordMapper.ref(current.args.name)
224
224
  },
225
- blob(tree) {
226
- const { current } = tree
227
- if (!isKeyword(current, schemaKeywords.blob)) return undefined
228
-
225
+ blob() {
229
226
  return typeKeywordMapper.blob()
230
227
  },
231
228
  tuple(tree, options) {
232
229
  const { current, schema, name } = tree
233
- if (!isKeyword(current, schemaKeywords.tuple)) return undefined
234
230
 
235
231
  return typeKeywordMapper.tuple(
236
232
  current.args.items.map((it) => this.parse({ schema, parent: current, name, current: it, siblings: [] }, options)).filter(Boolean) as ts.TypeNode[],
@@ -242,13 +238,11 @@ export const parse = createParser<ts.Node | null, ParserOptions>({
242
238
  },
243
239
  const(tree, _options) {
244
240
  const { current } = tree
245
- if (!isKeyword(current, schemaKeywords.const)) return undefined
246
241
 
247
242
  return typeKeywordMapper.const(current.args.name, current.args.format)
248
243
  },
249
244
  object(tree, options) {
250
245
  const { current, schema, name } = tree
251
- if (!isKeyword(current, schemaKeywords.object)) return undefined
252
246
 
253
247
  const properties = Object.entries(current.args?.properties || {})
254
248
  .filter((item) => {
@@ -377,21 +371,16 @@ export const parse = createParser<ts.Node | null, ParserOptions>({
377
371
 
378
372
  return typeKeywordMapper.object([...properties, additionalProperties, patternProperties].filter(Boolean))
379
373
  },
380
- datetime(tree) {
381
- const { current } = tree
382
- if (!isKeyword(current, schemaKeywords.datetime)) return undefined
383
-
374
+ datetime() {
384
375
  return typeKeywordMapper.datetime()
385
376
  },
386
377
  date(tree) {
387
378
  const { current } = tree
388
- if (!isKeyword(current, schemaKeywords.date)) return undefined
389
379
 
390
380
  return typeKeywordMapper.date(current.args.type)
391
381
  },
392
382
  time(tree) {
393
383
  const { current } = tree
394
- if (!isKeyword(current, schemaKeywords.time)) return undefined
395
384
 
396
385
  return typeKeywordMapper.time(current.args.type)
397
386
  },
package/src/plugin.ts CHANGED
@@ -19,12 +19,14 @@ export const pluginTs = definePlugin<PluginTs>((options) => {
19
19
  dateType = 'string',
20
20
  unknownType = 'any',
21
21
  optionalType = 'questionToken',
22
+ arrayType = 'array',
22
23
  emptySchemaType = unknownType,
23
24
  syntaxType = 'type',
24
25
  transformers = {},
25
26
  mapper = {},
26
27
  generators = [typeGenerator].filter(Boolean),
27
28
  contentType,
29
+ UNSTABLE_NAMING,
28
30
  } = options
29
31
 
30
32
  const usedEnumNames = {}
@@ -36,6 +38,7 @@ export const pluginTs = definePlugin<PluginTs>((options) => {
36
38
  transformers,
37
39
  dateType,
38
40
  optionalType,
41
+ arrayType,
39
42
  enumType,
40
43
  enumSuffix,
41
44
  unknownType,
@@ -122,6 +125,7 @@ export const pluginTs = definePlugin<PluginTs>((options) => {
122
125
  include,
123
126
  override,
124
127
  mode,
128
+ UNSTABLE_NAMING,
125
129
  })
126
130
 
127
131
  const operationFiles = await operationGenerator.build(...generators)