@bagelink/sdk 1.2.20 → 1.2.25
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/dist/index.cjs +153 -80
- package/dist/index.mjs +153 -80
- package/package.json +1 -1
- package/src/openAPITools/functionGenerator.ts +390 -162
- package/src/openAPITools/openApiTypes.ts +2 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
OperationObject,
|
|
3
3
|
ParameterObject,
|
|
4
|
+
PathItemObject,
|
|
4
5
|
PathsObject,
|
|
5
6
|
RequestBodyObject,
|
|
6
7
|
ResponseObject,
|
|
@@ -17,6 +18,9 @@ import {
|
|
|
17
18
|
toPascalCase,
|
|
18
19
|
} from './utils'
|
|
19
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Collection of types referenced across generated functions
|
|
23
|
+
*/
|
|
20
24
|
const allTypes: string[] = []
|
|
21
25
|
|
|
22
26
|
const primitiveTypes = [
|
|
@@ -28,40 +32,82 @@ const primitiveTypes = [
|
|
|
28
32
|
'any',
|
|
29
33
|
'{ [key: string]: any }',
|
|
30
34
|
'undefined',
|
|
31
|
-
'{ [key: string]: any }',
|
|
32
35
|
]
|
|
33
36
|
|
|
34
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Upload options interface for file upload operations
|
|
39
|
+
*/
|
|
40
|
+
interface UploadOptions {
|
|
41
|
+
onUploadProgress?: (progressEvent: any) => void
|
|
42
|
+
dirPath?: string
|
|
43
|
+
tags?: string[]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Represents a unique path and method combination
|
|
48
|
+
*/
|
|
49
|
+
interface PathOperation {
|
|
50
|
+
path: string
|
|
51
|
+
method: string
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Tracking for function generation
|
|
55
|
+
const functionsInventory: Record<string, string> = {}
|
|
56
|
+
const pathOperations: PathOperation[] = []
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Collects non-primitive types for import statements
|
|
60
|
+
* @param typeName - The type name to analyze
|
|
61
|
+
*/
|
|
62
|
+
function collectTypeForImportStatement(typeName: string): void {
|
|
35
63
|
typeName = typeName.trim().replace('[]', '')
|
|
64
|
+
|
|
65
|
+
// Handle union types
|
|
36
66
|
if (typeName.includes('|')) {
|
|
37
67
|
typeName.split('|').forEach((singleType) => {
|
|
38
|
-
collectTypeForImportStatement(singleType)
|
|
68
|
+
collectTypeForImportStatement(singleType.trim())
|
|
39
69
|
})
|
|
40
70
|
return
|
|
41
71
|
}
|
|
72
|
+
|
|
42
73
|
const isPrimitive = primitiveTypes.includes(typeName)
|
|
43
74
|
typeName = formatType(typeName)
|
|
75
|
+
|
|
44
76
|
if (!typeName || isPrimitive) return
|
|
45
77
|
if (!allTypes.includes(typeName)) allTypes.push(typeName)
|
|
46
78
|
}
|
|
47
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Wrapper for schemaToType that also collects types for import statements
|
|
82
|
+
* @param schema - The schema to convert to a type
|
|
83
|
+
* @returns The TypeScript type string
|
|
84
|
+
*/
|
|
48
85
|
function schemaToTypeWithCollection(schema?: SchemaObject): string {
|
|
49
86
|
const type = schemaToType(schema)
|
|
50
87
|
collectTypeForImportStatement(type)
|
|
51
88
|
return type
|
|
52
89
|
}
|
|
53
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Extracts the response type from an OpenAPI response object
|
|
93
|
+
* @param response - The OpenAPI response object
|
|
94
|
+
* @returns The TypeScript type string or undefined
|
|
95
|
+
*/
|
|
54
96
|
function getResponseType(response: ResponseObject): string | undefined {
|
|
55
97
|
const mediaTypeObject = response.content?.['application/json']
|
|
56
|
-
if (!mediaTypeObject
|
|
57
|
-
|
|
58
|
-
return responseType
|
|
98
|
+
if (!mediaTypeObject?.schema) return undefined
|
|
99
|
+
return schemaToTypeWithCollection(mediaTypeObject.schema)
|
|
59
100
|
}
|
|
60
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Generates the response type string from OpenAPI responses
|
|
104
|
+
* @param responses - The OpenAPI responses object
|
|
105
|
+
* @returns The TypeScript response type string
|
|
106
|
+
*/
|
|
61
107
|
function generateResponseType(responses?: ResponsesObject): string {
|
|
62
108
|
if (!responses) return ''
|
|
63
|
-
const types: string[] = []
|
|
64
109
|
|
|
110
|
+
const types: string[] = []
|
|
65
111
|
for (const [statusCode, response] of Object.entries(responses)) {
|
|
66
112
|
if (statusCode.startsWith('2')) {
|
|
67
113
|
const responseType = getResponseType(response)
|
|
@@ -70,88 +116,47 @@ function generateResponseType(responses?: ResponsesObject): string {
|
|
|
70
116
|
}
|
|
71
117
|
}
|
|
72
118
|
}
|
|
119
|
+
|
|
73
120
|
return types.join(' | ')
|
|
74
121
|
}
|
|
75
122
|
|
|
76
123
|
/**
|
|
77
|
-
*
|
|
78
|
-
* @param
|
|
79
|
-
* @
|
|
80
|
-
* @param allParams - All parameters required for the function, formatted as a string.
|
|
81
|
-
* @param responseTypeStr - The expected response type for the function.
|
|
82
|
-
* @param parameters - Additional configuration parameters, if any.
|
|
83
|
-
* @returns A string representing the Axios function.
|
|
124
|
+
* Extracts path parameters from an API path
|
|
125
|
+
* @param path - The API path
|
|
126
|
+
* @returns Array of parameter names or undefined
|
|
84
127
|
*/
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
formattedPath: string,
|
|
89
|
-
allParams: string,
|
|
90
|
-
responseTypeStr: string,
|
|
91
|
-
parameters: any,
|
|
92
|
-
requestBodyPayload: string
|
|
93
|
-
): string {
|
|
94
|
-
if (allParams === 'undefined') allParams = ''
|
|
95
|
-
|
|
96
|
-
let axiosFunction = `async (${allParams})${responseTypeStr} => {`
|
|
97
|
-
|
|
98
|
-
if (requestBodyPayload === 'formData') {
|
|
99
|
-
const paramStr = parameters?.config?.params
|
|
100
|
-
? `params: {${parameters.config.params}}`
|
|
101
|
-
: ''
|
|
102
|
-
|
|
103
|
-
// Check if this is a file upload (with specific file parameter) or general form data
|
|
104
|
-
if (allParams.includes('file: File')) {
|
|
105
|
-
axiosFunction += `
|
|
106
|
-
const formData = new FormData()
|
|
107
|
-
formData.append('upload', file)
|
|
108
|
-
return axios.${method}(${formattedPath}, formData, {
|
|
109
|
-
headers: { 'Content-Type': 'multipart/form-data' },
|
|
110
|
-
onUploadProgress: options?.onUploadProgress${paramStr ? `, ${paramStr}` : ''}
|
|
111
|
-
})`
|
|
112
|
-
} else {
|
|
113
|
-
// Generic FormData handling
|
|
114
|
-
axiosFunction += `
|
|
115
|
-
return axios.${method}(${formattedPath}, formData, {
|
|
116
|
-
headers: { 'Content-Type': 'multipart/form-data' }${paramStr ? `, ${paramStr}` : ''}
|
|
117
|
-
})`
|
|
118
|
-
}
|
|
119
|
-
} else {
|
|
120
|
-
const paramStr = parameters?.config?.params
|
|
121
|
-
? `, { params: {${parameters.config.params}} }`
|
|
122
|
-
: ''
|
|
123
|
-
const bodyVar = requestBodyPayload ? `${requestBodyPayload}` : '{}'
|
|
124
|
-
axiosFunction += `return axios.${method}(${formattedPath}${['get', 'delete'].includes(method) ? paramStr : `, ${bodyVar}${paramStr}`})`
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
axiosFunction += '}'
|
|
128
|
-
return axiosFunction
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// eslint-disable-next-line regexp/no-unused-capturing-group
|
|
132
|
-
const pathParamRegex = /\{([^}]+)\}/g
|
|
133
|
-
function getParamsFromPath(path: string) {
|
|
134
|
-
const params = path.match(pathParamRegex)?.map(p => p.slice(1, -1))
|
|
135
|
-
return params
|
|
128
|
+
function getParamsFromPath(path: string): string[] | undefined {
|
|
129
|
+
const pathParamRegex = /\{[^}]+\}/g
|
|
130
|
+
return path.match(pathParamRegex)?.map(p => p.slice(1, -1))
|
|
136
131
|
}
|
|
137
132
|
|
|
138
|
-
|
|
133
|
+
/**
|
|
134
|
+
* Formats a path with template literals for parameters
|
|
135
|
+
* @param path - The API path
|
|
136
|
+
* @returns The formatted path string
|
|
137
|
+
*/
|
|
138
|
+
function formatPathWithParams(path: string): string {
|
|
139
139
|
const params = getParamsFromPath(path)
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
return formattedPath
|
|
140
|
+
if (!params) return `'${path}'`
|
|
141
|
+
|
|
142
|
+
return `\`${path.replace(/\{([^}]+)\}/g, (_, paramName) => `\${${toCamelCase(paramName)}}`)}\``
|
|
144
143
|
}
|
|
145
144
|
|
|
145
|
+
/**
|
|
146
|
+
* Processes request body information from an OpenAPI operation
|
|
147
|
+
* @param requestBody - The OpenAPI request body object
|
|
148
|
+
* @returns Object containing request body parameter and payload information
|
|
149
|
+
*/
|
|
146
150
|
function generateRequestBody(requestBody?: RequestBodyObject): {
|
|
147
|
-
|
|
151
|
+
requestBodyParam: string
|
|
152
|
+
requestBodyPayload: string
|
|
148
153
|
} {
|
|
149
154
|
if (!requestBody?.content)
|
|
150
155
|
return { requestBodyParam: '', requestBodyPayload: '' }
|
|
151
156
|
|
|
152
157
|
const { content } = requestBody
|
|
153
158
|
|
|
154
|
-
//
|
|
159
|
+
// Handle multipart/form-data requests (file uploads)
|
|
155
160
|
if (content['multipart/form-data']) {
|
|
156
161
|
const multipartFormData = content['multipart/form-data']
|
|
157
162
|
|
|
@@ -180,6 +185,7 @@ function generateRequestBody(requestBody?: RequestBodyObject): {
|
|
|
180
185
|
const bodySchema = jsonContent.schema
|
|
181
186
|
const requestBodyType = schemaToType(bodySchema)
|
|
182
187
|
collectTypeForImportStatement(requestBodyType)
|
|
188
|
+
|
|
183
189
|
const requestBodyPayload = (
|
|
184
190
|
toCamelCase(bodySchema.title) || toCamelCase(requestBodyType) || 'requestBody'
|
|
185
191
|
)
|
|
@@ -189,40 +195,24 @@ function generateRequestBody(requestBody?: RequestBodyObject): {
|
|
|
189
195
|
schema: bodySchema,
|
|
190
196
|
defaultValue: bodySchema.default,
|
|
191
197
|
})
|
|
198
|
+
|
|
192
199
|
return { requestBodyParam, requestBodyPayload }
|
|
193
200
|
}
|
|
194
201
|
|
|
195
202
|
/**
|
|
196
|
-
*
|
|
197
|
-
* @param
|
|
198
|
-
* @param
|
|
199
|
-
* @returns
|
|
203
|
+
* Generates function parameters from OpenAPI parameters
|
|
204
|
+
* @param params - The OpenAPI parameters
|
|
205
|
+
* @param isFileUpload - Whether this is a file upload operation
|
|
206
|
+
* @returns Object containing parameter information
|
|
200
207
|
*/
|
|
201
|
-
function combineAllParams(
|
|
202
|
-
parameters: { params?: string },
|
|
203
|
-
requestBodyParam: string
|
|
204
|
-
): string {
|
|
205
|
-
let allParamsArray: string[] = []
|
|
206
|
-
if (parameters && parameters.params)
|
|
207
|
-
allParamsArray = parameters.params.split(',').map(p => p.trim())
|
|
208
|
-
if (requestBodyParam) allParamsArray.push(requestBodyParam.trim())
|
|
209
|
-
|
|
210
|
-
allParamsArray = allParamsArray
|
|
211
|
-
.filter(p => p)
|
|
212
|
-
.sort((a, b) => (a.includes('?') ? 1 : -1) - (b.includes('?') ? 1 : -1))
|
|
213
|
-
|
|
214
|
-
return allParamsArray.join(', ')
|
|
215
|
-
}
|
|
216
|
-
|
|
217
208
|
function generateFunctionParameters(
|
|
218
209
|
params?: ParameterObject[],
|
|
219
210
|
isFileUpload = false
|
|
220
|
-
) {
|
|
211
|
+
): { params?: string, config?: { params?: string } } {
|
|
221
212
|
if (!params?.length) return {}
|
|
222
213
|
|
|
223
|
-
//
|
|
214
|
+
// Special handling for file uploads
|
|
224
215
|
if (isFileUpload) {
|
|
225
|
-
// Don't include these as function parameters, they'll be part of options
|
|
226
216
|
return {
|
|
227
217
|
config: {
|
|
228
218
|
params: 'dir_path: options?.dirPath, tags: options?.tags',
|
|
@@ -232,7 +222,7 @@ function generateFunctionParameters(
|
|
|
232
222
|
}
|
|
233
223
|
|
|
234
224
|
const functionParams: string[] = []
|
|
235
|
-
const
|
|
225
|
+
const queryParams: string[] = []
|
|
236
226
|
|
|
237
227
|
for (const param of params) {
|
|
238
228
|
const paramType = schemaToType(param.schema)
|
|
@@ -252,7 +242,7 @@ function generateFunctionParameters(
|
|
|
252
242
|
}
|
|
253
243
|
|
|
254
244
|
if (param.in === 'query' || param.in === 'header') {
|
|
255
|
-
|
|
245
|
+
queryParams.push(
|
|
256
246
|
paramName === varName ? paramName : `'${paramName}': ${varName}`
|
|
257
247
|
)
|
|
258
248
|
}
|
|
@@ -260,10 +250,102 @@ function generateFunctionParameters(
|
|
|
260
250
|
|
|
261
251
|
return {
|
|
262
252
|
params: functionParams.join(', '),
|
|
263
|
-
config:
|
|
253
|
+
config: queryParams.length ? { params: queryParams.join(', ') } : undefined,
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Combines and formats all parameters into a single parameter string
|
|
259
|
+
* @param parameters - The parameters object
|
|
260
|
+
* @param requestBodyParam - The request body parameter
|
|
261
|
+
* @returns A formatted parameter string
|
|
262
|
+
*/
|
|
263
|
+
function combineAllParams(
|
|
264
|
+
parameters: { params?: string },
|
|
265
|
+
requestBodyParam: string
|
|
266
|
+
): string {
|
|
267
|
+
let allParamsArray: string[] = []
|
|
268
|
+
|
|
269
|
+
if (parameters.params) {
|
|
270
|
+
allParamsArray = parameters.params.split(',').map(p => p.trim())
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (requestBodyParam) {
|
|
274
|
+
allParamsArray.push(requestBodyParam.trim())
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Sort required parameters before optional ones
|
|
278
|
+
return allParamsArray
|
|
279
|
+
.filter(Boolean)
|
|
280
|
+
.sort((a, b) => (a.includes('?') ? 1 : -1) - (b.includes('?') ? 1 : -1))
|
|
281
|
+
.join(', ')
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Generates an Axios function call as a string
|
|
286
|
+
* @param method - The HTTP method
|
|
287
|
+
* @param formattedPath - The formatted API path
|
|
288
|
+
* @param allParams - The combined parameter string
|
|
289
|
+
* @param responseTypeStr - The response type string
|
|
290
|
+
* @param parameters - Additional parameters
|
|
291
|
+
* @param requestBodyPayload - The request body payload
|
|
292
|
+
* @returns A string with the Axios function
|
|
293
|
+
*/
|
|
294
|
+
function generateAxiosFunction(
|
|
295
|
+
method: string,
|
|
296
|
+
formattedPath: string,
|
|
297
|
+
allParams: string,
|
|
298
|
+
responseTypeStr: string,
|
|
299
|
+
parameters: { config?: { params?: string } },
|
|
300
|
+
requestBodyPayload: string
|
|
301
|
+
): string {
|
|
302
|
+
if (allParams === 'undefined') allParams = ''
|
|
303
|
+
|
|
304
|
+
let axiosFunction = `async (${allParams})${responseTypeStr} => {`
|
|
305
|
+
const paramStr = parameters.config?.params
|
|
306
|
+
? `params: {${parameters.config.params}}`
|
|
307
|
+
: ''
|
|
308
|
+
|
|
309
|
+
if (requestBodyPayload === 'formData') {
|
|
310
|
+
// Check if this is a file upload with specific file parameter
|
|
311
|
+
if (allParams.includes('file: File')) {
|
|
312
|
+
axiosFunction += `
|
|
313
|
+
const formData = new FormData()
|
|
314
|
+
formData.append('upload', file)
|
|
315
|
+
return axios.${method}(${formattedPath}, formData, {
|
|
316
|
+
headers: { 'Content-Type': 'multipart/form-data' },
|
|
317
|
+
onUploadProgress: options?.onUploadProgress${paramStr ? `, ${paramStr}` : ''}
|
|
318
|
+
})`
|
|
319
|
+
} else {
|
|
320
|
+
// Generic FormData handling
|
|
321
|
+
axiosFunction += `
|
|
322
|
+
return axios.${method}(${formattedPath}, formData, {
|
|
323
|
+
headers: { 'Content-Type': 'multipart/form-data' }${paramStr ? `, ${paramStr}` : ''}
|
|
324
|
+
})`
|
|
325
|
+
}
|
|
326
|
+
} else {
|
|
327
|
+
const configParams = paramStr ? `, { ${paramStr} }` : ''
|
|
328
|
+
const bodyVar = requestBodyPayload || '{}'
|
|
329
|
+
|
|
330
|
+
// Handle different HTTP methods appropriately
|
|
331
|
+
axiosFunction += `return axios.${method}(${formattedPath}${
|
|
332
|
+
['get', 'delete'].includes(method)
|
|
333
|
+
? configParams
|
|
334
|
+
: `, ${bodyVar}${configParams}`
|
|
335
|
+
})`
|
|
264
336
|
}
|
|
337
|
+
|
|
338
|
+
axiosFunction += '}'
|
|
339
|
+
return axiosFunction
|
|
265
340
|
}
|
|
266
341
|
|
|
342
|
+
/**
|
|
343
|
+
* Generates a function for an OpenAPI operation
|
|
344
|
+
* @param method - The HTTP method
|
|
345
|
+
* @param path - The API path
|
|
346
|
+
* @param operation - The OpenAPI operation object
|
|
347
|
+
* @returns The function as a string
|
|
348
|
+
*/
|
|
267
349
|
function generateFunctionForOperation(
|
|
268
350
|
method: string,
|
|
269
351
|
path: string,
|
|
@@ -271,11 +353,11 @@ function generateFunctionForOperation(
|
|
|
271
353
|
): string {
|
|
272
354
|
if (!operation) return ''
|
|
273
355
|
|
|
274
|
-
// Check if this is a file upload
|
|
275
|
-
const isFileUpload
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
356
|
+
// Check if this is a file upload
|
|
357
|
+
const isFileUpload = operation.requestBody?.content[
|
|
358
|
+
'multipart/form-data'
|
|
359
|
+
]?.schema?.$ref?.includes('Body_upload_files')
|
|
360
|
+
|
|
279
361
|
const parameters = generateFunctionParameters(
|
|
280
362
|
operation.parameters,
|
|
281
363
|
isFileUpload
|
|
@@ -284,7 +366,7 @@ function generateFunctionForOperation(
|
|
|
284
366
|
operation.requestBody
|
|
285
367
|
)
|
|
286
368
|
|
|
287
|
-
//
|
|
369
|
+
// Handle parameters appropriately based on operation type
|
|
288
370
|
const allParams = isFileUpload
|
|
289
371
|
? 'file: File, options?: UploadOptions & { dirPath?: string, tags?: string[] }'
|
|
290
372
|
: combineAllParams(parameters, requestBodyParam)
|
|
@@ -294,129 +376,275 @@ function generateFunctionForOperation(
|
|
|
294
376
|
? `: Promise<AxiosResponse<${responseType}>>`
|
|
295
377
|
: ''
|
|
296
378
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
formatPathWithParams(path),
|
|
300
|
-
allParams,
|
|
301
|
-
responseTypeStr,
|
|
302
|
-
parameters,
|
|
303
|
-
requestBodyPayload
|
|
304
|
-
)
|
|
305
|
-
}
|
|
379
|
+
// Create JSDoc comment with OpenAPI documentation
|
|
380
|
+
let functionComment = '/**\n'
|
|
306
381
|
|
|
307
|
-
|
|
382
|
+
// Add summary
|
|
383
|
+
if (operation.summary) {
|
|
384
|
+
functionComment += ` * ${operation.summary}\n`
|
|
385
|
+
}
|
|
308
386
|
|
|
309
|
-
|
|
387
|
+
// Add description if available
|
|
388
|
+
if (operation.description) {
|
|
389
|
+
// If there's already a summary, add a line break
|
|
390
|
+
if (operation.summary) functionComment += ` *\n`
|
|
310
391
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
)
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
392
|
+
// Split description into lines and add each line with proper JSDoc formatting
|
|
393
|
+
const descriptionLines = operation.description.split('\n')
|
|
394
|
+
descriptionLines.forEach((line: string) => {
|
|
395
|
+
functionComment += ` * ${line}\n`
|
|
396
|
+
})
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Add endpoint path and method information
|
|
400
|
+
functionComment += ` *\n * @endpoint ${method.toUpperCase()} ${path}\n`
|
|
401
|
+
|
|
402
|
+
// Add parameter documentation if available
|
|
403
|
+
if (operation.parameters?.length) {
|
|
404
|
+
functionComment += ' *\n'
|
|
405
|
+
operation.parameters.forEach((param) => {
|
|
406
|
+
const paramType = schemaToType(param.schema)
|
|
407
|
+
const paramName = toCamelCase(param.name)
|
|
408
|
+
const required = param.required ? '' : '?'
|
|
409
|
+
|
|
410
|
+
functionComment += ` * @param {${paramType}} ${paramName}${required}`
|
|
411
|
+
|
|
412
|
+
// Add parameter description if available
|
|
413
|
+
if (param.description) {
|
|
414
|
+
functionComment += ` - ${param.description}`
|
|
415
|
+
} else {
|
|
416
|
+
functionComment += ` - ${param.name} parameter`
|
|
417
|
+
}
|
|
319
418
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
dirPath?: string
|
|
323
|
-
tags?: string[]
|
|
419
|
+
functionComment += '\n'
|
|
420
|
+
})
|
|
324
421
|
}
|
|
325
422
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
423
|
+
// Add requestBody documentation if available
|
|
424
|
+
if (operation.requestBody && requestBodyParam) {
|
|
425
|
+
const bodyParamName = requestBodyParam.split(':')[0].trim()
|
|
426
|
+
const bodyParamType = requestBodyParam.split(':')[1]?.split('=')[0]?.trim() || 'any'
|
|
330
427
|
|
|
331
|
-
|
|
332
|
-
path: string
|
|
333
|
-
method: string
|
|
334
|
-
}
|
|
428
|
+
functionComment += ` * @param {${bodyParamType}} ${bodyParamName}`
|
|
335
429
|
|
|
336
|
-
|
|
337
|
-
|
|
430
|
+
if (operation.requestBody.description) {
|
|
431
|
+
functionComment += ` - ${operation.requestBody.description}`
|
|
432
|
+
} else {
|
|
433
|
+
functionComment += ` - Request body`
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
functionComment += '\n'
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Add response documentation
|
|
440
|
+
if (responseType) {
|
|
441
|
+
functionComment += ` * @returns {Promise<AxiosResponse<${responseType}>>} The API response\n`
|
|
442
|
+
|
|
443
|
+
// Include any response descriptions for 2xx status codes
|
|
444
|
+
for (const [statusCode, response] of Object.entries(operation.responses)) {
|
|
445
|
+
if (statusCode.startsWith('2') && response.description) {
|
|
446
|
+
functionComment += ` * @response ${statusCode} - ${response.description}\n`
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
functionComment += ' */\n'
|
|
452
|
+
|
|
453
|
+
return functionComment + generateAxiosFunction(
|
|
454
|
+
method,
|
|
455
|
+
formatPathWithParams(path),
|
|
456
|
+
allParams,
|
|
457
|
+
responseTypeStr,
|
|
458
|
+
parameters,
|
|
459
|
+
requestBodyPayload
|
|
460
|
+
)
|
|
461
|
+
}
|
|
338
462
|
|
|
339
|
-
|
|
463
|
+
/**
|
|
464
|
+
* Checks if there's a conflict with an existing path operation
|
|
465
|
+
* @param path - The API path
|
|
466
|
+
* @param method - The HTTP method
|
|
467
|
+
* @returns Whether there's a conflict
|
|
468
|
+
*/
|
|
469
|
+
function hasConflict(path: string, method: string): boolean {
|
|
340
470
|
const cleanPathName = path
|
|
341
471
|
.split('/')
|
|
342
472
|
.filter(p => p && !/\{|\}/.test(p))
|
|
343
473
|
.join('/')
|
|
474
|
+
|
|
344
475
|
const matchingPaths = pathOperations.filter(
|
|
345
476
|
p => p.path === cleanPathName && p.method === method
|
|
346
477
|
)
|
|
478
|
+
|
|
347
479
|
pathOperations.push({ path: cleanPathName, method })
|
|
348
480
|
return matchingPaths.length > 0
|
|
349
481
|
}
|
|
350
482
|
|
|
351
|
-
|
|
483
|
+
/**
|
|
484
|
+
* Generates a random string for function IDs
|
|
485
|
+
* @returns A random string
|
|
486
|
+
*/
|
|
487
|
+
function generateRandomString(): string {
|
|
488
|
+
return Math.random().toString(36).slice(7)
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Creates a function placeholder and stores it in the inventory
|
|
493
|
+
* @param path - The API path
|
|
494
|
+
* @param method - The HTTP method
|
|
495
|
+
* @param operation - The OpenAPI operation
|
|
496
|
+
* @returns A function ID
|
|
497
|
+
*/
|
|
352
498
|
function createFunctionPlaceholder(
|
|
353
499
|
path: string,
|
|
354
500
|
method: string,
|
|
355
|
-
operation:
|
|
356
|
-
) {
|
|
501
|
+
operation: OperationObject | undefined
|
|
502
|
+
): string {
|
|
357
503
|
const funcID = generateRandomString()
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
504
|
+
if (operation) {
|
|
505
|
+
functionsInventory[funcID] = generateFunctionForOperation(
|
|
506
|
+
method,
|
|
507
|
+
path,
|
|
508
|
+
operation
|
|
509
|
+
)
|
|
510
|
+
} else {
|
|
511
|
+
functionsInventory[funcID] = ''
|
|
512
|
+
}
|
|
363
513
|
return funcID
|
|
364
514
|
}
|
|
365
515
|
|
|
516
|
+
/**
|
|
517
|
+
* Handles a path segment when generating functions
|
|
518
|
+
* @param path - The API path
|
|
519
|
+
* @param operation - The OpenAPI operation
|
|
520
|
+
* @param existingObj - Existing object to merge with
|
|
521
|
+
* @returns The updated object
|
|
522
|
+
*/
|
|
366
523
|
function handlePathSegment(
|
|
367
524
|
path: string,
|
|
368
|
-
operation:
|
|
369
|
-
existingObj:
|
|
370
|
-
) {
|
|
371
|
-
const methods = Object.keys(operation)
|
|
372
|
-
const obj:
|
|
525
|
+
operation: PathItemObject,
|
|
526
|
+
existingObj: Record<string, any> = {}
|
|
527
|
+
): Record<string, any> {
|
|
528
|
+
const methods = Object.keys(operation) as Array<keyof PathItemObject>
|
|
529
|
+
const obj: Record<string, any> = {}
|
|
530
|
+
|
|
373
531
|
for (const method of methods) {
|
|
374
532
|
let functionName = method.toLowerCase()
|
|
533
|
+
|
|
375
534
|
if (hasConflict(path, method)) {
|
|
376
|
-
const params
|
|
535
|
+
const params = getParamsFromPath(path)
|
|
377
536
|
functionName += params ? `By${toPascalCase(params.pop() || '')}` : 'All'
|
|
378
537
|
}
|
|
538
|
+
|
|
379
539
|
obj[functionName] = createFunctionPlaceholder(
|
|
380
540
|
path,
|
|
381
541
|
method,
|
|
382
542
|
operation[method]
|
|
383
543
|
)
|
|
384
544
|
}
|
|
545
|
+
|
|
385
546
|
return { ...obj, ...existingObj }
|
|
386
547
|
}
|
|
387
548
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
549
|
+
/**
|
|
550
|
+
* Generates TypeScript API client functions from OpenAPI paths
|
|
551
|
+
* @param paths - The OpenAPI paths object
|
|
552
|
+
* @param baseUrl - The base URL for the API
|
|
553
|
+
* @returns The generated TypeScript code
|
|
554
|
+
*/
|
|
555
|
+
export function generateFunctions(paths: PathsObject, baseUrl: string): string {
|
|
556
|
+
const body: Record<string, any> = {}
|
|
391
557
|
const allPathsClean = Object.keys(paths).map(cleanPath)
|
|
558
|
+
|
|
559
|
+
// Process all paths to generate functions
|
|
392
560
|
for (const [path, operation] of Object.entries(paths)) {
|
|
393
561
|
const splitPath = path.split('/').filter(p => p && !/\{|\}/.test(p))
|
|
562
|
+
|
|
394
563
|
splitPath.reduce((acc, key: string, index: number, array: string[]) => {
|
|
395
564
|
const objFuncKey = toCamelCase(key)
|
|
396
565
|
if (!objFuncKey) return acc
|
|
397
|
-
|
|
566
|
+
|
|
567
|
+
const methods = Object.keys(operation) as Array<keyof PathItemObject>
|
|
568
|
+
|
|
398
569
|
if (
|
|
399
570
|
index === array.length - 1
|
|
400
571
|
&& methods.length === 1
|
|
401
572
|
&& allPathsClean.filter(p => p === cleanPath(path)).length === 1
|
|
402
573
|
) {
|
|
403
|
-
|
|
404
|
-
const
|
|
405
|
-
|
|
574
|
+
// Single method endpoint with unique path
|
|
575
|
+
const method = methods[0]
|
|
576
|
+
const opp = operation[method]
|
|
577
|
+
acc[objFuncKey] = createFunctionPlaceholder(path, method, opp)
|
|
406
578
|
} else if (index === array.length - 1) {
|
|
579
|
+
// Multiple methods endpoint or path with conflicts
|
|
407
580
|
acc[objFuncKey] = handlePathSegment(path, operation, acc[objFuncKey])
|
|
408
581
|
} else if (!acc[objFuncKey] || typeof acc[objFuncKey] !== 'object') {
|
|
582
|
+
// Intermediate path segment
|
|
409
583
|
acc[objFuncKey] = {}
|
|
410
584
|
}
|
|
585
|
+
|
|
411
586
|
return acc[objFuncKey]
|
|
412
587
|
}, body)
|
|
413
588
|
}
|
|
589
|
+
|
|
590
|
+
// Generate TypeScript code from processed paths
|
|
591
|
+
let tsString = ''
|
|
592
|
+
|
|
593
|
+
// Convert object structure to TypeScript
|
|
414
594
|
for (const [parent, object] of Object.entries(body)) {
|
|
415
|
-
tsString += `
|
|
595
|
+
tsString += `
|
|
596
|
+
/**
|
|
597
|
+
* API functions for ${parent} endpoints
|
|
598
|
+
*/
|
|
599
|
+
export const ${parent} = ${JSON.stringify(object, undefined, 2)};\n`
|
|
416
600
|
}
|
|
601
|
+
|
|
602
|
+
// Replace placeholders with actual function bodies
|
|
417
603
|
Object.entries(functionsInventory).forEach(([key, value]) => {
|
|
418
604
|
tsString = tsString.replace(`"${key}"`, value)
|
|
419
605
|
})
|
|
420
|
-
|
|
421
|
-
|
|
606
|
+
|
|
607
|
+
// Generate the final TypeScript file
|
|
608
|
+
return fileTemplate(tsString, allTypes, baseUrl)
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* Generates the TypeScript file template
|
|
613
|
+
* @param tsString - The generated TypeScript code
|
|
614
|
+
* @param typeForImport - Types to import
|
|
615
|
+
* @param baseURL - The base URL for the API
|
|
616
|
+
* @returns The final TypeScript file content
|
|
617
|
+
*/
|
|
618
|
+
function fileTemplate(
|
|
619
|
+
tsString: string,
|
|
620
|
+
typeForImport: string[],
|
|
621
|
+
baseURL: string
|
|
622
|
+
): string {
|
|
623
|
+
const templateCode = `import ax from 'axios';
|
|
624
|
+
import type { AxiosResponse } from 'axios';
|
|
625
|
+
import type { ${typeForImport.join(', ')} } from './types.d';
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* Options for file upload operations
|
|
629
|
+
*/
|
|
630
|
+
export interface UploadOptions {
|
|
631
|
+
/** Progress callback for upload operations */
|
|
632
|
+
onUploadProgress?: (progressEvent: any) => void
|
|
633
|
+
/** Directory path for the uploaded file */
|
|
634
|
+
dirPath?: string
|
|
635
|
+
/** Tags to associate with the uploaded file */
|
|
636
|
+
tags?: string[]
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Configured axios instance for API requests
|
|
641
|
+
* @example
|
|
642
|
+
* // Making a direct request with the axios instance
|
|
643
|
+
* const response = await axios.get('/some-endpoint');
|
|
644
|
+
*/
|
|
645
|
+
export const axios = ax.create({baseURL: ${baseURL}, withCredentials: true});
|
|
646
|
+
${tsString}`
|
|
647
|
+
|
|
648
|
+
// Remove potential double quotes around property names
|
|
649
|
+
return templateCode.replace(/"([^"]+)":/g, '$1:')
|
|
422
650
|
}
|