@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.
@@ -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
- function collectTypeForImportStatement(typeName: string) {
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 || !mediaTypeObject.schema) return
57
- const responseType = schemaToTypeWithCollection(mediaTypeObject.schema)
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
- * Generates the string representation of an Axios function call.
78
- * @param method - The HTTP method (get, post, etc.).
79
- * @param formattedPath - The API endpoint path, formatted with template literals for parameters.
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
- function generateAxiosFunction(
87
- method: string,
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
- function formatPathWithParams(path: string) {
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
- const formattedPath = params
141
- ? `\`${path.replace(pathParamRegex, v => `$${toCamelCase(v)}`)}\``
142
- : `'${path}'`
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
- [key: string]: string
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
- // Check if this is a multipart/form-data request
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
- * Combines and formats function parameters and request body parameters into a single parameter string.
197
- * @param parameters - The parameters derived from the OpenAPI specification.
198
- * @param requestBodyParam - The parameter representing the request body.
199
- * @returns A string representing all combined parameters.
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
- // For file uploads, we want to handle query parameters differently
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 paramList: string[] = []
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
- paramList.push(
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: paramList.length ? { params: paramList.join(', ') } : {},
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 operation by looking at the schema reference
275
- const isFileUpload
276
- = operation.requestBody?.content[
277
- 'multipart/form-data'
278
- ]?.schema?.$ref?.includes('Body_upload_files')
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
- // For file uploads, ignore the regular parameter generation
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
- return generateAxiosFunction(
298
- method,
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
- const generateRandomString = () => Math.random().toString(36).slice(7)
382
+ // Add summary
383
+ if (operation.summary) {
384
+ functionComment += ` * ${operation.summary}\n`
385
+ }
308
386
 
309
- const DOUBLE_QUOTE_REGEX = /"([^"]+)":/g // ! TODO: @nevehallon believes this may not be necessary
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
- function fileTemplate(
312
- tsString: string,
313
- typeForImport: string[],
314
- baseURL: string
315
- ) {
316
- const templateCode = `import ax from 'axios';
317
- import type { AxiosResponse } from 'axios';
318
- import type {${typeForImport.join(', ')}} from './types.d';
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
- export interface UploadOptions {
321
- onUploadProgress?: (progressEvent: any) => void
322
- dirPath?: string
323
- tags?: string[]
419
+ functionComment += '\n'
420
+ })
324
421
  }
325
422
 
326
- export const axios = ax.create({baseURL:${baseURL}, withCredentials: true});
327
- ${tsString}`
328
- return templateCode.replace(DOUBLE_QUOTE_REGEX, '$1:')
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
- interface PathOperation {
332
- path: string
333
- method: string
334
- }
428
+ functionComment += ` * @param {${bodyParamType}} ${bodyParamName}`
335
429
 
336
- const functionsInventory: Record<string, string> = {}
337
- const pathOperations: PathOperation[] = []
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
- function hasConflict(path: string, method: string) {
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
- // Creates a placeholder for a function and stores its body in the inventory
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: any
356
- ) {
501
+ operation: OperationObject | undefined
502
+ ): string {
357
503
  const funcID = generateRandomString()
358
- functionsInventory[funcID] = generateFunctionForOperation(
359
- method,
360
- path,
361
- operation
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: any,
369
- existingObj: { [key: string]: any } = {}
370
- ) {
371
- const methods = Object.keys(operation)
372
- const obj: { [key: string]: any } = {}
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: string[] | undefined = getParamsFromPath(path)
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
- export function generateFunctions(paths: PathsObject, baseUrl: string) {
389
- let tsString = ''
390
- const body: { [key: string]: any } = {}
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
- const methods = Object.keys(operation)
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
- const method: string = methods[0]
404
- const opp: any = { ...operation }[method]
405
- acc[objFuncKey] = createFunctionPlaceholder(path, methods[0], opp)
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 += `export const ${parent} = ${JSON.stringify(object, undefined, 2)};\n`
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
- tsString = fileTemplate(tsString, allTypes, baseUrl)
421
- return tsString
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
  }