@bagelink/sdk 1.5.32 → 1.6.4

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 CHANGED
@@ -317,7 +317,7 @@ function generateResponseType(responses) {
317
317
  for (const [statusCode, response] of Object.entries(responses)) {
318
318
  if (statusCode.startsWith("2")) {
319
319
  const responseType = getResponseType(response);
320
- if (responseType && "any" !== responseType) {
320
+ if (responseType && responseType !== "any") {
321
321
  types.push(responseType);
322
322
  }
323
323
  }
@@ -337,7 +337,7 @@ function formatPathWithParams(path) {
337
337
  }
338
338
  function generateRequestBody(requestBody) {
339
339
  const content = dereference(requestBody)?.content;
340
- if (!content || 0 === Object.keys(content).length) {
340
+ if (!content || Object.keys(content).length === 0) {
341
341
  return { requestBodyParam: "", requestBodyPayload: "" };
342
342
  }
343
343
  if (content["multipart/form-data"]) {
@@ -390,7 +390,7 @@ function generateFunctionParameters(params, isFileUpload = false) {
390
390
  collectTypeForImportStatement(paramType);
391
391
  const paramName = param.name;
392
392
  const varName = toCamelCaseSafe(param.name);
393
- if ("path" === param.in || "query" === param.in || "header" === param.in) {
393
+ if (param.in === "path" || param.in === "query" || param.in === "header") {
394
394
  functionParams.push(
395
395
  formatVarType({
396
396
  varName,
@@ -400,7 +400,7 @@ function generateFunctionParameters(params, isFileUpload = false) {
400
400
  })
401
401
  );
402
402
  }
403
- if ("query" === param.in || "header" === param.in) {
403
+ if (param.in === "query" || param.in === "header") {
404
404
  queryParams.push(
405
405
  paramName === varName ? paramName : `'${paramName}': ${varName}`
406
406
  );
@@ -430,7 +430,7 @@ function combineAllParams(parameters, requestBodyParam) {
430
430
  if (requestBodyParam) {
431
431
  allParamsArray.push(requestBodyParam.trim());
432
432
  }
433
- if (0 === allParamsArray.length) {
433
+ if (allParamsArray.length === 0) {
434
434
  return "";
435
435
  }
436
436
  const parsedParams = allParamsArray.filter(Boolean).map(parseParameter);
@@ -441,12 +441,12 @@ function combineAllParams(parameters, requestBodyParam) {
441
441
  return `{ ${destructuredNames} }: { ${typeDefinition} } = {}`;
442
442
  }
443
443
  function generateAxiosFunction(method, formattedPath, allParams, responseTypeStr, parameters, requestBodyPayload) {
444
- if ("undefined" === allParams) {
444
+ if (allParams === "undefined") {
445
445
  allParams = "";
446
446
  }
447
447
  let axiosFunction = `async (${allParams})${responseTypeStr} => {`;
448
448
  const paramStr = parameters.config?.params ? `params: {${parameters.config.params}}` : "";
449
- if ("formData" === requestBodyPayload) {
449
+ if (requestBodyPayload === "formData") {
450
450
  if (allParams.includes("file: File")) {
451
451
  axiosFunction += `
452
452
  const formData = new FormData()
@@ -496,6 +496,21 @@ function generateFunctionForOperation(method, path, operation) {
496
496
  if (!operation) {
497
497
  return "";
498
498
  }
499
+ const methodLower = method.toLowerCase();
500
+ if (["get", "delete"].includes(methodLower) && operation.requestBody) {
501
+ const requestBodyDeref = dereference(operation.requestBody);
502
+ const content = requestBodyDeref?.content;
503
+ if (content && Object.keys(content).length > 0) {
504
+ const hasSchema = Object.values(content).some(
505
+ (mediaType) => mediaType && "schema" in mediaType && mediaType.schema !== void 0 && mediaType.schema !== null
506
+ );
507
+ if (hasSchema) {
508
+ throw new Error(
509
+ `Invalid OpenAPI spec: ${method.toUpperCase()} request at path "${path}" has a request body. GET and DELETE requests should not have request bodies according to HTTP standards. Please move the body parameters to query parameters or use a different HTTP method.`
510
+ );
511
+ }
512
+ }
513
+ }
499
514
  const multiPartSchema = dereference(operation.requestBody)?.content?.["multipart/form-data"]?.schema;
500
515
  const isFileUpload = isReferenceObject(multiPartSchema) && multiPartSchema?.$ref?.includes("Body_upload_files");
501
516
  const parameters = generateFunctionParameters(
@@ -524,7 +539,7 @@ function hasConflict(path, method) {
524
539
  (p) => p.path === cleanPathName && p.method === method
525
540
  );
526
541
  pathOperations.push({ path: cleanPathName, method });
527
- return 0 < matchingPaths.length;
542
+ return matchingPaths.length > 0;
528
543
  }
529
544
  function generateRandomString() {
530
545
  return Math.random().toString(36).slice(7);
@@ -570,13 +585,21 @@ function generateFunctions(paths, baseUrl) {
570
585
  return acc;
571
586
  }
572
587
  const methods = Object.keys(operation);
573
- if (index === array.length - 1 && 1 === methods.length && 1 === allPathsClean.filter((p) => p === cleanPath(path)).length) {
588
+ const currentPathClean = cleanPath(path);
589
+ const currentPathPrefix = splitPath.slice(0, index + 1).join("/");
590
+ const prefixWithSlash = `${currentPathPrefix}/`;
591
+ const hasChildPaths = allPathsClean.some(
592
+ (otherPath) => otherPath !== currentPathClean && otherPath.startsWith(prefixWithSlash)
593
+ );
594
+ if (index === array.length - 1 && methods.length === 1 && allPathsClean.filter((p) => p === currentPathClean).length === 1 && !hasChildPaths) {
574
595
  const method = methods[0];
575
596
  const opp = operation[method];
576
597
  acc[objFuncKey] = createFunctionPlaceholder(path, method, opp);
577
598
  } else if (index === array.length - 1) {
578
- acc[objFuncKey] = handlePathSegment(path, operation, acc[objFuncKey]);
579
- } else if (!acc[objFuncKey] || "object" !== typeof acc[objFuncKey]) {
599
+ const existingValue = acc[objFuncKey];
600
+ const existingObj = existingValue && typeof existingValue === "object" && !Array.isArray(existingValue) ? existingValue : {};
601
+ acc[objFuncKey] = handlePathSegment(path, operation, existingObj);
602
+ } else if (!acc[objFuncKey] || typeof acc[objFuncKey] !== "object") {
580
603
  acc[objFuncKey] = {};
581
604
  }
582
605
  return acc[objFuncKey];
package/dist/index.mjs CHANGED
@@ -311,7 +311,7 @@ function generateResponseType(responses) {
311
311
  for (const [statusCode, response] of Object.entries(responses)) {
312
312
  if (statusCode.startsWith("2")) {
313
313
  const responseType = getResponseType(response);
314
- if (responseType && "any" !== responseType) {
314
+ if (responseType && responseType !== "any") {
315
315
  types.push(responseType);
316
316
  }
317
317
  }
@@ -331,7 +331,7 @@ function formatPathWithParams(path) {
331
331
  }
332
332
  function generateRequestBody(requestBody) {
333
333
  const content = dereference(requestBody)?.content;
334
- if (!content || 0 === Object.keys(content).length) {
334
+ if (!content || Object.keys(content).length === 0) {
335
335
  return { requestBodyParam: "", requestBodyPayload: "" };
336
336
  }
337
337
  if (content["multipart/form-data"]) {
@@ -384,7 +384,7 @@ function generateFunctionParameters(params, isFileUpload = false) {
384
384
  collectTypeForImportStatement(paramType);
385
385
  const paramName = param.name;
386
386
  const varName = toCamelCaseSafe(param.name);
387
- if ("path" === param.in || "query" === param.in || "header" === param.in) {
387
+ if (param.in === "path" || param.in === "query" || param.in === "header") {
388
388
  functionParams.push(
389
389
  formatVarType({
390
390
  varName,
@@ -394,7 +394,7 @@ function generateFunctionParameters(params, isFileUpload = false) {
394
394
  })
395
395
  );
396
396
  }
397
- if ("query" === param.in || "header" === param.in) {
397
+ if (param.in === "query" || param.in === "header") {
398
398
  queryParams.push(
399
399
  paramName === varName ? paramName : `'${paramName}': ${varName}`
400
400
  );
@@ -424,7 +424,7 @@ function combineAllParams(parameters, requestBodyParam) {
424
424
  if (requestBodyParam) {
425
425
  allParamsArray.push(requestBodyParam.trim());
426
426
  }
427
- if (0 === allParamsArray.length) {
427
+ if (allParamsArray.length === 0) {
428
428
  return "";
429
429
  }
430
430
  const parsedParams = allParamsArray.filter(Boolean).map(parseParameter);
@@ -435,12 +435,12 @@ function combineAllParams(parameters, requestBodyParam) {
435
435
  return `{ ${destructuredNames} }: { ${typeDefinition} } = {}`;
436
436
  }
437
437
  function generateAxiosFunction(method, formattedPath, allParams, responseTypeStr, parameters, requestBodyPayload) {
438
- if ("undefined" === allParams) {
438
+ if (allParams === "undefined") {
439
439
  allParams = "";
440
440
  }
441
441
  let axiosFunction = `async (${allParams})${responseTypeStr} => {`;
442
442
  const paramStr = parameters.config?.params ? `params: {${parameters.config.params}}` : "";
443
- if ("formData" === requestBodyPayload) {
443
+ if (requestBodyPayload === "formData") {
444
444
  if (allParams.includes("file: File")) {
445
445
  axiosFunction += `
446
446
  const formData = new FormData()
@@ -490,6 +490,21 @@ function generateFunctionForOperation(method, path, operation) {
490
490
  if (!operation) {
491
491
  return "";
492
492
  }
493
+ const methodLower = method.toLowerCase();
494
+ if (["get", "delete"].includes(methodLower) && operation.requestBody) {
495
+ const requestBodyDeref = dereference(operation.requestBody);
496
+ const content = requestBodyDeref?.content;
497
+ if (content && Object.keys(content).length > 0) {
498
+ const hasSchema = Object.values(content).some(
499
+ (mediaType) => mediaType && "schema" in mediaType && mediaType.schema !== void 0 && mediaType.schema !== null
500
+ );
501
+ if (hasSchema) {
502
+ throw new Error(
503
+ `Invalid OpenAPI spec: ${method.toUpperCase()} request at path "${path}" has a request body. GET and DELETE requests should not have request bodies according to HTTP standards. Please move the body parameters to query parameters or use a different HTTP method.`
504
+ );
505
+ }
506
+ }
507
+ }
493
508
  const multiPartSchema = dereference(operation.requestBody)?.content?.["multipart/form-data"]?.schema;
494
509
  const isFileUpload = isReferenceObject(multiPartSchema) && multiPartSchema?.$ref?.includes("Body_upload_files");
495
510
  const parameters = generateFunctionParameters(
@@ -518,7 +533,7 @@ function hasConflict(path, method) {
518
533
  (p) => p.path === cleanPathName && p.method === method
519
534
  );
520
535
  pathOperations.push({ path: cleanPathName, method });
521
- return 0 < matchingPaths.length;
536
+ return matchingPaths.length > 0;
522
537
  }
523
538
  function generateRandomString() {
524
539
  return Math.random().toString(36).slice(7);
@@ -564,13 +579,21 @@ function generateFunctions(paths, baseUrl) {
564
579
  return acc;
565
580
  }
566
581
  const methods = Object.keys(operation);
567
- if (index === array.length - 1 && 1 === methods.length && 1 === allPathsClean.filter((p) => p === cleanPath(path)).length) {
582
+ const currentPathClean = cleanPath(path);
583
+ const currentPathPrefix = splitPath.slice(0, index + 1).join("/");
584
+ const prefixWithSlash = `${currentPathPrefix}/`;
585
+ const hasChildPaths = allPathsClean.some(
586
+ (otherPath) => otherPath !== currentPathClean && otherPath.startsWith(prefixWithSlash)
587
+ );
588
+ if (index === array.length - 1 && methods.length === 1 && allPathsClean.filter((p) => p === currentPathClean).length === 1 && !hasChildPaths) {
568
589
  const method = methods[0];
569
590
  const opp = operation[method];
570
591
  acc[objFuncKey] = createFunctionPlaceholder(path, method, opp);
571
592
  } else if (index === array.length - 1) {
572
- acc[objFuncKey] = handlePathSegment(path, operation, acc[objFuncKey]);
573
- } else if (!acc[objFuncKey] || "object" !== typeof acc[objFuncKey]) {
593
+ const existingValue = acc[objFuncKey];
594
+ const existingObj = existingValue && typeof existingValue === "object" && !Array.isArray(existingValue) ? existingValue : {};
595
+ acc[objFuncKey] = handlePathSegment(path, operation, existingObj);
596
+ } else if (!acc[objFuncKey] || typeof acc[objFuncKey] !== "object") {
574
597
  acc[objFuncKey] = {};
575
598
  }
576
599
  return acc[objFuncKey];
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bagelink/sdk",
3
3
  "type": "module",
4
- "version": "1.5.32",
4
+ "version": "1.6.4",
5
5
  "description": "Bagel core sdk packages",
6
6
  "author": {
7
7
  "name": "Neveh Allon",
@@ -75,8 +75,8 @@ function collectTypeForImportStatement(typeName: string): void {
75
75
  const isPrimitive = primitiveTypes.includes(typeName)
76
76
  typeName = formatType(typeName)
77
77
 
78
- if (!typeName || isPrimitive) {return}
79
- if (!allTypes.includes(typeName)) {allTypes.push(typeName)}
78
+ if (!typeName || isPrimitive) { return }
79
+ if (!allTypes.includes(typeName)) { allTypes.push(typeName) }
80
80
  }
81
81
 
82
82
  /**
@@ -100,7 +100,7 @@ function getResponseType(response: OpenAPIResponse | ReferenceObject): string |
100
100
  return undefined // TODO: handle references properly
101
101
  }
102
102
  const mediaTypeObject = response.content?.['application/json']
103
- if (!mediaTypeObject?.schema) {return undefined}
103
+ if (!mediaTypeObject?.schema) { return undefined }
104
104
  return schemaToTypeWithCollection(mediaTypeObject.schema)
105
105
  }
106
106
 
@@ -110,13 +110,13 @@ function getResponseType(response: OpenAPIResponse | ReferenceObject): string |
110
110
  * @returns The TypeScript response type string
111
111
  */
112
112
  function generateResponseType(responses?: OpenAPIResponses): string {
113
- if (!responses) {return ''}
113
+ if (!responses) { return '' }
114
114
 
115
115
  const types: string[] = []
116
116
  for (const [statusCode, response] of Object.entries(responses)) {
117
117
  if (statusCode.startsWith('2')) {
118
118
  const responseType = getResponseType(response)
119
- if (responseType && 'any' !== responseType) {
119
+ if (responseType && responseType !== 'any') {
120
120
  types.push(responseType)
121
121
  }
122
122
  }
@@ -142,7 +142,7 @@ function getParamsFromPath(path: string): string[] | undefined {
142
142
  */
143
143
  function formatPathWithParams(path: string): string {
144
144
  const params = getParamsFromPath(path)
145
- if (!params) {return `'${path}'`}
145
+ if (!params) { return `'${path}'` }
146
146
 
147
147
  return `\`${path.replace(/\{([^}]+)\}/g, (_, paramName) => `\${${toCamelCase(paramName)}}`)}\``
148
148
  }
@@ -160,7 +160,7 @@ function generateRequestBody(requestBody?: OpenAPIOperation['requestBody']): {
160
160
  // return { requestBodyParam: '', requestBodyPayload: '' }
161
161
 
162
162
  const content = dereference(requestBody)?.content
163
- if (!content || 0 === Object.keys(content).length) {
163
+ if (!content || Object.keys(content).length === 0) {
164
164
  return { requestBodyParam: '', requestBodyPayload: '' }
165
165
  }
166
166
 
@@ -217,7 +217,7 @@ function generateFunctionParameters(
217
217
  params?: OpenAPIOperation['parameters'],
218
218
  isFileUpload = false
219
219
  ): { params?: string, config?: { params?: string } } {
220
- if (!params?.length) {return {}}
220
+ if (!params?.length) { return {} }
221
221
 
222
222
  // Special handling for file uploads
223
223
  if (isFileUpload) {
@@ -241,7 +241,7 @@ function generateFunctionParameters(
241
241
  const paramName = param.name
242
242
  const varName = toCamelCaseSafe(param.name)
243
243
 
244
- if ('path' === param.in || 'query' === param.in || 'header' === param.in) {
244
+ if (param.in === 'path' || param.in === 'query' || param.in === 'header') {
245
245
  functionParams.push(
246
246
  formatVarType({
247
247
  varName,
@@ -252,7 +252,7 @@ function generateFunctionParameters(
252
252
  )
253
253
  }
254
254
 
255
- if ('query' === param.in || 'header' === param.in) {
255
+ if (param.in === 'query' || param.in === 'header') {
256
256
  queryParams.push(
257
257
  paramName === varName ? paramName : `'${paramName}': ${varName}`
258
258
  )
@@ -316,7 +316,7 @@ function combineAllParams(
316
316
  allParamsArray.push(requestBodyParam.trim())
317
317
  }
318
318
 
319
- if (0 === allParamsArray.length) {
319
+ if (allParamsArray.length === 0) {
320
320
  return ''
321
321
  }
322
322
 
@@ -357,14 +357,14 @@ function generateAxiosFunction(
357
357
  parameters: { config?: { params?: string } },
358
358
  requestBodyPayload: string
359
359
  ): string {
360
- if ('undefined' === allParams) {allParams = ''}
360
+ if (allParams === 'undefined') { allParams = '' }
361
361
 
362
362
  let axiosFunction = `async (${allParams})${responseTypeStr} => {`
363
363
  const paramStr = parameters.config?.params
364
364
  ? `params: {${parameters.config.params}}`
365
365
  : ''
366
366
 
367
- if ('formData' === requestBodyPayload) {
367
+ if (requestBodyPayload === 'formData') {
368
368
  // Check if this is a file upload with specific file parameter
369
369
  if (allParams.includes('file: File')) {
370
370
  axiosFunction += `
@@ -384,8 +384,6 @@ function generateAxiosFunction(
384
384
  } else {
385
385
  const configParams = paramStr ? `, { ${paramStr} }` : ''
386
386
  const bodyVar = requestBodyPayload || '{}'
387
-
388
- // Handle different HTTP methods appropriately
389
387
  axiosFunction += `return axios.${method}(${formattedPath}${
390
388
  ['get', 'delete'].includes(method)
391
389
  ? configParams
@@ -415,7 +413,7 @@ function buildJSDocComment(operation: OpenAPIOperation, method: string, path: st
415
413
  // Add description if available
416
414
  if (operation.description) {
417
415
  // If there's already a summary, add a line break
418
- if (operation.summary) {functionComment += ` *\n`}
416
+ if (operation.summary) { functionComment += ` *\n` }
419
417
 
420
418
  // Split description into lines and add each line with proper JSDoc formatting
421
419
  const descriptionLines = operation.description.split('\n')
@@ -443,7 +441,28 @@ function generateFunctionForOperation(
443
441
  path: string,
444
442
  operation: OpenAPIOperation
445
443
  ): string {
446
- if (!operation) {return ''}
444
+ if (!operation) { return '' }
445
+
446
+ // Validate: GET and DELETE requests should not have request bodies
447
+ const methodLower = method.toLowerCase()
448
+ if (['get', 'delete'].includes(methodLower) && operation.requestBody) {
449
+ const requestBodyDeref = dereference(operation.requestBody)
450
+ const content = requestBodyDeref?.content
451
+ // Check if request body has actual content with schemas
452
+ if (content && Object.keys(content).length > 0) {
453
+ // Check if any content type has a schema (indicating actual body data)
454
+ const hasSchema = Object.values(content).some(
455
+ mediaType => mediaType && 'schema' in mediaType && mediaType.schema !== undefined && mediaType.schema !== null
456
+ )
457
+ if (hasSchema) {
458
+ throw new Error(
459
+ `Invalid OpenAPI spec: ${method.toUpperCase()} request at path "${path}" has a request body. `
460
+ + `GET and DELETE requests should not have request bodies according to HTTP standards. `
461
+ + `Please move the body parameters to query parameters or use a different HTTP method.`
462
+ )
463
+ }
464
+ }
465
+ }
447
466
 
448
467
  // Check if this is a file upload
449
468
  const multiPartSchema = dereference(operation.requestBody)?.content?.[
@@ -500,7 +519,7 @@ function hasConflict(path: string, method: string): boolean {
500
519
  )
501
520
 
502
521
  pathOperations.push({ path: cleanPathName, method })
503
- return 0 < matchingPaths.length
522
+ return matchingPaths.length > 0
504
523
  }
505
524
 
506
525
  /**
@@ -585,24 +604,40 @@ export function generateFunctions(paths: OpenAPIPaths, baseUrl: string): string
585
604
 
586
605
  splitPath.reduce((acc, key: string, index: number, array: string[]) => {
587
606
  const objFuncKey = toCamelCaseSafe(key)
588
- if (!objFuncKey) {return acc}
607
+ if (!objFuncKey) { return acc }
589
608
 
590
609
  const methods = Object.keys(operation) as Array<keyof OpenAPIPath>
610
+ const currentPathClean = cleanPath(path)
611
+ const currentPathPrefix = splitPath.slice(0, index + 1).join('/')
612
+
613
+ // Check if any other path has this path as a prefix (indicating child paths exist)
614
+ const prefixWithSlash = `${currentPathPrefix}/`
615
+ const hasChildPaths = allPathsClean.some(
616
+ otherPath => otherPath !== currentPathClean && otherPath.startsWith(prefixWithSlash)
617
+ )
591
618
 
592
619
  if (
593
620
  index === array.length - 1
594
- && 1 === methods.length
595
- && 1 === allPathsClean.filter(p => p === cleanPath(path)).length
621
+ && methods.length === 1
622
+ && allPathsClean.filter(p => p === currentPathClean).length === 1
623
+ && !hasChildPaths
596
624
  ) {
597
- // Single method endpoint with unique path
625
+ // Single method endpoint with unique path and no child paths
598
626
  const method = methods[0]
599
627
  const opp = operation[method!] as OpenAPIOperation
600
628
  acc[objFuncKey] = createFunctionPlaceholder(path, method!, opp)
601
629
  } else if (index === array.length - 1) {
602
- // Multiple methods endpoint or path with conflicts
603
- acc[objFuncKey] = handlePathSegment(path, operation, acc[objFuncKey])
604
- } else if (!acc[objFuncKey] || 'object' !== typeof acc[objFuncKey]) {
605
- // Intermediate path segment
630
+ // Multiple methods endpoint or path with conflicts or has child paths
631
+ // Ensure we have an object to merge with (handlePathSegment expects an object)
632
+ const existingValue = acc[objFuncKey]
633
+ const existingObj = (existingValue && typeof existingValue === 'object' && !Array.isArray(existingValue))
634
+ ? existingValue
635
+ : {}
636
+ acc[objFuncKey] = handlePathSegment(path, operation, existingObj)
637
+ } else if (!acc[objFuncKey] || typeof acc[objFuncKey] !== 'object') {
638
+ // Intermediate path segment - ensure it's an object
639
+ // If it's already a function, we need to preserve it but this shouldn't happen
640
+ // if hasChildPaths check works correctly
606
641
  acc[objFuncKey] = {}
607
642
  }
608
643