@kubb/swagger-ts 2.10.0 → 2.11.1

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.
@@ -0,0 +1,324 @@
1
+ import transformers from '@kubb/core/transformers'
2
+ import { print } from '@kubb/parser'
3
+ import * as factory from '@kubb/parser/factory'
4
+ import { isKeyword, SchemaGenerator, schemaKeywords } from '@kubb/swagger'
5
+
6
+ import type { ts } from '@kubb/parser'
7
+ import type { Schema, SchemaKeywordMapper, SchemaMapper } from '@kubb/swagger'
8
+
9
+ export const typeKeywordMapper = {
10
+ any: () => factory.keywordTypeNodes.any,
11
+ unknown: () => factory.keywordTypeNodes.unknown,
12
+ number: () => factory.keywordTypeNodes.number,
13
+ integer: () => factory.keywordTypeNodes.number,
14
+ object: (nodes?: ts.TypeElement[]) => {
15
+ if (!nodes) {
16
+ return factory.keywordTypeNodes.object
17
+ }
18
+
19
+ return factory.createTypeLiteralNode(nodes)
20
+ },
21
+ lazy: undefined,
22
+ string: () => factory.keywordTypeNodes.string,
23
+ boolean: () => factory.keywordTypeNodes.boolean,
24
+ undefined: () => factory.keywordTypeNodes.undefined,
25
+ nullable: undefined,
26
+ null: () => factory.keywordTypeNodes.null,
27
+ nullish: undefined,
28
+ array: (nodes?: ts.TypeNode[]) => {
29
+ if (!nodes) {
30
+ return undefined
31
+ }
32
+
33
+ return factory.createArrayDeclaration({ nodes })
34
+ },
35
+ tuple: (nodes?: ts.TypeNode[]) => {
36
+ if (!nodes) {
37
+ return undefined
38
+ }
39
+
40
+ return factory.createTupleTypeNode(nodes)
41
+ },
42
+ enum: (name?: string) => {
43
+ if (!name) {
44
+ return undefined
45
+ }
46
+
47
+ return factory.createTypeReferenceNode(name, undefined)
48
+ },
49
+ union: (nodes?: ts.TypeNode[]) => {
50
+ if (!nodes) {
51
+ return undefined
52
+ }
53
+
54
+ return factory.createUnionDeclaration({
55
+ withParentheses: true,
56
+ nodes,
57
+ })
58
+ },
59
+ const: (name?: string | number, format?: 'string' | 'number') => {
60
+ if (!name) {
61
+ return undefined
62
+ }
63
+
64
+ if (format === 'number') {
65
+ return factory.createLiteralTypeNode(factory.createNumericLiteral(name))
66
+ }
67
+
68
+ return factory.createLiteralTypeNode(factory.createStringLiteral(name.toString()))
69
+ },
70
+ datetime: () => factory.keywordTypeNodes.string,
71
+ date: () => factory.createTypeReferenceNode(factory.createIdentifier('Date')),
72
+ uuid: undefined,
73
+ url: undefined,
74
+ strict: undefined,
75
+ default: undefined,
76
+ and: (nodes?: ts.TypeNode[]) => {
77
+ if (!nodes) {
78
+ return undefined
79
+ }
80
+
81
+ return factory.createIntersectionDeclaration({
82
+ withParentheses: true,
83
+ nodes,
84
+ })
85
+ },
86
+ describe: undefined,
87
+ min: undefined,
88
+ max: undefined,
89
+ optional: undefined,
90
+ matches: undefined,
91
+ email: undefined,
92
+ firstName: undefined,
93
+ lastName: undefined,
94
+ password: undefined,
95
+ phone: undefined,
96
+ readOnly: undefined,
97
+ ref: (propertyName?: string) => {
98
+ if (!propertyName) {
99
+ return undefined
100
+ }
101
+
102
+ return factory.createTypeReferenceNode(propertyName, undefined)
103
+ },
104
+ blob: () => factory.createTypeReferenceNode('Blob', []),
105
+ deprecated: undefined,
106
+ example: undefined,
107
+ type: undefined,
108
+ format: undefined,
109
+ catchall: undefined,
110
+ } satisfies SchemaMapper<(ctx?: any) => ts.Node | null | undefined>
111
+
112
+ type ParserOptions = {
113
+ name: string
114
+ typeName?: string
115
+ description?: string
116
+ /**
117
+ * @default `'questionToken'`
118
+ */
119
+ optionalType: 'questionToken' | 'undefined' | 'questionTokenAndUndefined'
120
+ /**
121
+ * @default `'asConst'`
122
+ */
123
+ enumType: 'enum' | 'asConst' | 'asPascalConst' | 'constEnum' | 'literal'
124
+ keysToOmit?: string[]
125
+ mapper?: typeof typeKeywordMapper
126
+ }
127
+
128
+ export function parseTypeMeta(item: Schema, options: ParserOptions): ts.Node | null | undefined {
129
+ const mapper = options.mapper || typeKeywordMapper
130
+ const value = mapper[item.keyword as keyof typeof mapper]
131
+
132
+ if (!value) {
133
+ return undefined
134
+ }
135
+
136
+ if (isKeyword(item, schemaKeywords.union)) {
137
+ const value = mapper[item.keyword as keyof typeof mapper] as typeof typeKeywordMapper['union']
138
+ return value(item.args.map(orItem => parseTypeMeta(orItem, options)).filter(Boolean) as ts.TypeNode[])
139
+ }
140
+
141
+ if (isKeyword(item, schemaKeywords.and)) {
142
+ const value = mapper[item.keyword as keyof typeof mapper] as typeof typeKeywordMapper['and']
143
+ return value(item.args.map(orItem => parseTypeMeta(orItem, options)).filter(Boolean) as ts.TypeNode[])
144
+ }
145
+
146
+ if (isKeyword(item, schemaKeywords.array)) {
147
+ const value = mapper[item.keyword as keyof typeof mapper] as typeof typeKeywordMapper['array']
148
+ return value(item.args.items.map(orItem => parseTypeMeta(orItem, options)).filter(Boolean) as ts.TypeNode[])
149
+ }
150
+
151
+ if (isKeyword(item, schemaKeywords.enum)) {
152
+ const value = mapper[item.keyword as keyof typeof mapper] as typeof typeKeywordMapper['enum']
153
+ return value(item.args.typeName)
154
+ }
155
+
156
+ if (isKeyword(item, schemaKeywords.ref)) {
157
+ const value = mapper[item.keyword as keyof typeof mapper] as typeof typeKeywordMapper['ref']
158
+ return value(item.args.name)
159
+ }
160
+
161
+ if (isKeyword(item, schemaKeywords.blob)) {
162
+ return value()
163
+ }
164
+
165
+ if (isKeyword(item, schemaKeywords.tuple)) {
166
+ const value = mapper[item.keyword as keyof typeof mapper] as typeof typeKeywordMapper['tuple']
167
+ return value(
168
+ item.args.map(tupleItem => parseTypeMeta(tupleItem, options)).filter(Boolean) as ts.TypeNode[],
169
+ )
170
+ }
171
+
172
+ if (isKeyword(item, schemaKeywords.const)) {
173
+ const value = mapper[item.keyword as keyof typeof mapper] as typeof typeKeywordMapper['const']
174
+
175
+ return value(item.args.name, item.args.format)
176
+ }
177
+
178
+ if (isKeyword(item, schemaKeywords.object)) {
179
+ const value = mapper[item.keyword as keyof typeof mapper] as typeof typeKeywordMapper['object']
180
+
181
+ const properties = Object.entries(item.args?.properties || {})
182
+ .filter((item) => {
183
+ const schemas = item[1]
184
+ return schemas && typeof schemas.map === 'function'
185
+ })
186
+ .map((item) => {
187
+ const name = item[0]
188
+ const schemas = item[1]
189
+
190
+ const isNullish = schemas.some(item => item.keyword === schemaKeywords.nullish)
191
+ const isNullable = schemas.some(item => item.keyword === schemaKeywords.nullable)
192
+ const isOptional = schemas.some(item => item.keyword === schemaKeywords.optional)
193
+ const isReadonly = schemas.some(item => item.keyword === schemaKeywords.readOnly)
194
+ const describeSchema = schemas.find(item => item.keyword === schemaKeywords.describe) as SchemaKeywordMapper['describe'] | undefined
195
+ const deprecatedSchema = schemas.find(item => item.keyword === schemaKeywords.deprecated) as SchemaKeywordMapper['deprecated'] | undefined
196
+ const defaultSchema = schemas.find(item => item.keyword === schemaKeywords.default) as SchemaKeywordMapper['default'] | undefined
197
+ const exampleSchema = schemas.find(item => item.keyword === schemaKeywords.example) as SchemaKeywordMapper['example'] | undefined
198
+ const typeSchema = schemas.find(item => item.keyword === schemaKeywords.type) as SchemaKeywordMapper['type'] | undefined
199
+ const formatSchema = schemas.find(item => item.keyword === schemaKeywords.format) as SchemaKeywordMapper['format'] | undefined
200
+
201
+ let type = schemas
202
+ .map((item) => parseTypeMeta(item, options))
203
+ .filter(Boolean)[0] as ts.TypeNode
204
+
205
+ if (isNullable) {
206
+ type = factory.createUnionDeclaration({ nodes: [type, factory.keywordTypeNodes.null] }) as ts.TypeNode
207
+ }
208
+
209
+ if (isNullish && ['undefined', 'questionTokenAndUndefined'].includes(options.optionalType as string)) {
210
+ type = factory.createUnionDeclaration({ nodes: [type, factory.keywordTypeNodes.undefined] }) as ts.TypeNode
211
+ }
212
+
213
+ if (isOptional && ['undefined', 'questionTokenAndUndefined'].includes(options.optionalType as string)) {
214
+ type = factory.createUnionDeclaration({ nodes: [type, factory.keywordTypeNodes.undefined] }) as ts.TypeNode
215
+ }
216
+
217
+ const propertySignature = factory.createPropertySignature({
218
+ questionToken: isOptional && ['questionToken', 'questionTokenAndUndefined'].includes(options.optionalType as string),
219
+ name,
220
+ type,
221
+ readOnly: isReadonly,
222
+ })
223
+
224
+ return factory.appendJSDocToNode({
225
+ node: propertySignature,
226
+ comments: [
227
+ describeSchema ? `@description ${transformers.jsStringEscape(describeSchema.args)}` : undefined,
228
+ deprecatedSchema ? `@deprecated` : undefined,
229
+ defaultSchema
230
+ ? `@default ${defaultSchema.args}`
231
+ : undefined,
232
+ exampleSchema ? `@example ${exampleSchema.args}` : undefined,
233
+ typeSchema
234
+ ? `@type ${typeSchema.args}${!isOptional ? '' : ' | undefined'} ${formatSchema?.args || ''}`
235
+ : undefined,
236
+ ].filter(Boolean),
237
+ })
238
+ })
239
+
240
+ const additionalProperties = item.args?.additionalProperties?.length
241
+ ? factory.createIndexSignature(item.args.additionalProperties.map(schema => parseTypeMeta(schema, options)).filter(Boolean).at(0) as ts.TypeNode)
242
+ : undefined
243
+
244
+ return value([...properties, additionalProperties].filter(Boolean))
245
+ }
246
+
247
+ if (item.keyword in mapper) {
248
+ return value()
249
+ }
250
+
251
+ return undefined
252
+ }
253
+
254
+ export function typeParser(
255
+ schemas: Schema[],
256
+ options: ParserOptions,
257
+ ): string {
258
+ const nodes: ts.Node[] = []
259
+ const extraNodes: ts.Node[] = []
260
+
261
+ if (!schemas.length) {
262
+ return ''
263
+ }
264
+
265
+ const isNullish = schemas.some(item => item.keyword === schemaKeywords.nullish)
266
+ const isNullable = schemas.some(item => item.keyword === schemaKeywords.nullable)
267
+ const isOptional = schemas.some(item => item.keyword === schemaKeywords.optional)
268
+
269
+ let type = schemas.map((schema) => parseTypeMeta(schema, options)).filter(Boolean).at(0) as ts.TypeNode
270
+ || typeKeywordMapper.undefined()
271
+
272
+ if (isNullable) {
273
+ type = factory.createUnionDeclaration({ nodes: [type, factory.keywordTypeNodes.null] }) as ts.TypeNode
274
+ }
275
+
276
+ if (isNullish && ['undefined', 'questionTokenAndUndefined'].includes(options.optionalType as string)) {
277
+ type = factory.createUnionDeclaration({ nodes: [type, factory.keywordTypeNodes.undefined] }) as ts.TypeNode
278
+ }
279
+
280
+ if (isOptional && ['undefined', 'questionTokenAndUndefined'].includes(options.optionalType as string)) {
281
+ type = factory.createUnionDeclaration({ nodes: [type, factory.keywordTypeNodes.undefined] }) as ts.TypeNode
282
+ }
283
+
284
+ const node = factory.createTypeAliasDeclaration({
285
+ modifiers: [factory.modifiers.export],
286
+ name: options.name,
287
+ type: options.keysToOmit?.length ? factory.createOmitDeclaration({ keys: options.keysToOmit, type, nonNullable: true }) : type,
288
+ })
289
+
290
+ const enumSchemas = SchemaGenerator.deepSearch(schemas, schemaKeywords.enum)
291
+ if (enumSchemas) {
292
+ enumSchemas.forEach(enumSchema => {
293
+ extraNodes.push(...factory.createEnumDeclaration({
294
+ name: transformers.camelCase(enumSchema.args.name),
295
+ typeName: enumSchema.args.typeName,
296
+ enums: enumSchema.args.items.map(item => item.value === undefined ? undefined : [transformers.trimQuotes(item.name?.toString()), item.value]).filter(
297
+ Boolean,
298
+ ) as unknown as [
299
+ string,
300
+ string,
301
+ ][],
302
+ type: options.enumType,
303
+ }))
304
+ })
305
+ }
306
+
307
+ nodes.push(
308
+ factory.appendJSDocToNode({
309
+ node,
310
+ comments: [
311
+ options.description ? `@description ${transformers.jsStringEscape(options.description)}` : undefined,
312
+ ].filter(Boolean),
313
+ }),
314
+ )
315
+
316
+ const filterdNodes = nodes.filter(
317
+ (node: ts.Node) =>
318
+ !extraNodes.some(
319
+ (extraNode: ts.Node) => (extraNode as ts.TypeAliasDeclaration)?.name?.escapedText === (node as ts.TypeAliasDeclaration)?.name?.escapedText,
320
+ ),
321
+ )
322
+
323
+ return print([...extraNodes, ...filterdNodes])
324
+ }