beer-swagger 1.1.1 → 4.0.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.
Files changed (2) hide show
  1. package/generate-apis.js +124 -30
  2. package/package.json +1 -1
package/generate-apis.js CHANGED
@@ -116,7 +116,7 @@ function appendParamSuffix(baseName, paramSuffix) {
116
116
  }
117
117
 
118
118
  // Add a verb when the HTTP method should be reflected in the name.
119
- function addVerbForSingleSegment(name, method, hasPathParams = false) {
119
+ function addVerbForSingleSegment(name, method) {
120
120
  const lower = name.toLowerCase();
121
121
  if (KEEP_METHOD_NAMES.has(lower)) {
122
122
  return name;
@@ -125,7 +125,7 @@ function addVerbForSingleSegment(name, method, hasPathParams = false) {
125
125
  const suffix = name[0].toUpperCase() + name.slice(1);
126
126
  return `removeBy${suffix}`;
127
127
  }
128
- if ((method === 'post' || method === 'put') && !hasPathParams) {
128
+ if (method === 'post' || method === 'put') {
129
129
  return name;
130
130
  }
131
131
  const verbMap = {
@@ -185,7 +185,7 @@ function getBodyTypeName(requestBody, schemas) {
185
185
  // Map a Swagger schema to a runtime return type.
186
186
  function mapSchemaToType(schema, schemas, useTypes) {
187
187
  if (!schema) {
188
- return '{}';
188
+ return 'object';
189
189
  }
190
190
  const {
191
191
  $ref,
@@ -201,18 +201,18 @@ function mapSchemaToType(schema, schemas, useTypes) {
201
201
  if (responseSchema && responseSchema.properties && responseSchema.properties.data) {
202
202
  return mapSchemaToType(responseSchema.properties.data, schemas, useTypes);
203
203
  }
204
- return '{}';
204
+ return 'object';
205
205
  }
206
206
  const refName = normalizeDtoName(rawRefName);
207
207
  const refSchema = schemas[rawRefName] || schemas[refName];
208
208
  if (refSchema && refSchema.properties && refSchema.properties.data) {
209
209
  return mapSchemaToType(refSchema.properties.data, schemas, useTypes);
210
210
  }
211
- return useTypes ? `Types.${refName}` : '{}';
211
+ return useTypes ? `Types.${refName}` : 'object';
212
212
  }
213
213
  if (!type && items) {
214
214
  const itemType = mapSchemaToType(items, schemas, useTypes);
215
- if (itemType === '{}' || itemType === '[]') {
215
+ if (itemType === '[]') {
216
216
  return '[]';
217
217
  }
218
218
  return `${itemType}[]`;
@@ -234,7 +234,7 @@ function mapSchemaToType(schema, schemas, useTypes) {
234
234
  }
235
235
  if (type === 'array') {
236
236
  const itemType = mapSchemaToType(items, schemas, useTypes);
237
- if (itemType === '{}' || itemType === '[]') {
237
+ if (itemType === '[]') {
238
238
  return '[]';
239
239
  }
240
240
  return `${itemType}[]`;
@@ -246,9 +246,9 @@ function mapSchemaToType(schema, schemas, useTypes) {
246
246
  }
247
247
 
248
248
  // Map parameter schema to a type for method signatures.
249
- function mapParamSchemaToType(schema, schemas, useTypes) {
249
+ function mapParamSchemaToType(schema, schemas, useTypes, isQuery = false) {
250
250
  if (!schema) {
251
- return '{}';
251
+ return isQuery ? 'Record<string, string | undefined>' : 'object';
252
252
  }
253
253
  const {
254
254
  $ref,
@@ -259,7 +259,7 @@ function mapParamSchemaToType(schema, schemas, useTypes) {
259
259
  if ($ref) {
260
260
  const refName = normalizeDtoName($ref.split('/')
261
261
  .pop());
262
- return useTypes ? `Types.${refName}` : '{}';
262
+ return useTypes ? `Types.${refName}` : 'object';
263
263
  }
264
264
  if (!type && items) {
265
265
  return '[]';
@@ -283,9 +283,29 @@ function mapParamSchemaToType(schema, schemas, useTypes) {
283
283
  return '[]';
284
284
  }
285
285
  if (type === 'object') {
286
- return 'object';
286
+ return isQuery ? 'Record<string, string | undefined>' : 'object';
287
287
  }
288
- return 'object';
288
+ return isQuery ? 'Record<string, string | undefined>' : 'object';
289
+ }
290
+
291
+ // Resolve parameter type for a parameter definition.
292
+ function resolveParamType(param, schemas) {
293
+ const schemaToUse = param.schema || (param.type ? { type: param.type, format: param.format, items: param.items } : null);
294
+ const isQuery = param.in === 'query';
295
+ return mapParamSchemaToType(schemaToUse, schemas, true, isQuery);
296
+ }
297
+
298
+ // Detect event streams in successful or default responses.
299
+ function isEventStreamResponse(op) {
300
+ return Object.entries(op.responses || {}).some(([status, response]) => {
301
+ if (!/^2(?:\d{2}|XX)$/i.test(status) && status !== 'default') {
302
+ return false;
303
+ }
304
+ return Object.keys(response.content || {}).some((mediaType) => {
305
+ const type = mediaType.split(';')[0].trim().toLowerCase();
306
+ return type === 'text/event-stream' || type === 'event-stream';
307
+ });
308
+ });
289
309
  }
290
310
 
291
311
  // Resolve default response type from Swagger responses.
@@ -295,7 +315,7 @@ function getDefaultReturnType(op, schemas, useTypes) {
295
315
  const { content = {} } = res;
296
316
  const firstContent = content['application/json'] || content['application/*+json'] || Object.values(content)[0];
297
317
  if (!firstContent) {
298
- return '{}';
318
+ return 'object';
299
319
  }
300
320
  return mapSchemaToType(firstContent.schema, schemas, useTypes);
301
321
  }
@@ -455,8 +475,8 @@ function generateForSpec(spec, serviceName, fileBase, isDefaultFetch) {
455
475
  const pathSegments = p.split('/')
456
476
  .filter(Boolean)
457
477
  .filter((part) => !part.startsWith('{'));
458
- const shouldAddVerb = pathSegments.length === 1 || ((method === 'post' || method === 'put') && pathParams.length > 0);
459
- name = shouldAddVerb ? addVerbForSingleSegment(inferred, method, pathParams.length > 0) : inferred;
478
+ const shouldAddVerb = pathSegments.length === 1;
479
+ name = shouldAddVerb ? addVerbForSingleSegment(inferred, method) : inferred;
460
480
  }
461
481
  if (!KEEP_METHOD_NAMES.has(name.toLowerCase())) {
462
482
  name = fixVerbCase(name);
@@ -498,6 +518,11 @@ function generateForSpec(spec, serviceName, fileBase, isDefaultFetch) {
498
518
  const className = toClassName(tag);
499
519
  const facadePrefix = toFacadePrefix(className);
500
520
  const sortedEntries = [...entries].sort((a, b) => {
521
+ const aDynamic = a.params.some((p) => p.in === 'path') || a.path.includes('{') ? 1 : 0;
522
+ const bDynamic = b.params.some((p) => p.in === 'path') || b.path.includes('{') ? 1 : 0;
523
+ if (aDynamic !== bDynamic) {
524
+ return aDynamic - bDynamic;
525
+ }
501
526
  const aScore = (a.hasBody ? 1 : 0) + (a.params.length ? 1 : 0);
502
527
  const bScore = (b.hasBody ? 1 : 0) + (b.params.length ? 1 : 0);
503
528
  return aScore - bScore;
@@ -518,21 +543,28 @@ function generateForSpec(spec, serviceName, fileBase, isDefaultFetch) {
518
543
  if (!KEEP_METHOD_NAMES.has(name.toLowerCase()) && (name === 'get' || name === 'delete') && facadePrefix) {
519
544
  name = `${name}${facadePrefix}`;
520
545
  }
546
+ const isDynamicPath = entryParams.some((p) => p.in === 'path') || entryPath.includes('{');
547
+ if (seen.has(name) && isDynamicPath && (entryMethod === 'post' || entryMethod === 'put')) {
548
+ const hasSavePrefix = name.startsWith('save') && (name.length === 4 || /^[A-Z]/.test(name.slice(4)));
549
+ const saveName = hasSavePrefix ? name : `save${toPascal(name)}`;
550
+ name = saveName;
551
+ }
552
+ const baseName = name;
521
553
  if (seen.has(name)) {
522
554
  const bodyTypeName = entryHasBody ? getBodyTypeName(entryOp.requestBody, schemas) : '';
523
555
  if (bodyTypeName) {
524
- name = `${entryName}By${toPascal(bodyTypeName)}`;
556
+ name = `${baseName}By${toPascal(bodyTypeName)}`;
525
557
  }
526
558
  }
527
559
  if (seen.has(name)) {
528
560
  const paramSuffix = toParamSuffix(entryParams, paramNameMap);
529
561
  if (paramSuffix) {
530
- name = appendParamSuffix(entryName, paramSuffix);
562
+ name = appendParamSuffix(baseName, paramSuffix);
531
563
  }
532
564
  }
533
565
  if (seen.has(name)) {
534
566
  const methodSuffix = entryMethod[0].toUpperCase() + entryMethod.slice(1);
535
- name = `${entryName}${methodSuffix}`;
567
+ name = `${baseName}${methodSuffix}`;
536
568
  }
537
569
  seen.add(name);
538
570
  const defaultType = entryDefaultReturnType;
@@ -543,17 +575,16 @@ function generateForSpec(spec, serviceName, fileBase, isDefaultFetch) {
543
575
 
544
576
  const paramArgs = orderedParams.map((p) => {
545
577
  const {
546
- schema: paramSchema,
547
578
  required,
548
579
  name: paramName
549
580
  } = p;
550
581
  const mappedName = paramNameMap.get(paramName) || paramName;
551
- const paramType = mapParamSchemaToType(paramSchema, schemas, true);
582
+ const paramType = resolveParamType(p, schemas);
552
583
  const optional = required ? '' : '?';
553
584
  return `${mappedName}${optional}: ${paramType}`;
554
585
  });
555
586
 
556
- let bodyType = '{}';
587
+ let bodyType = 'object';
557
588
  const { requestBody: opRequestBody } = entryOp;
558
589
  if (entryHasBody && opRequestBody && opRequestBody.content) {
559
590
  const { content } = opRequestBody;
@@ -605,17 +636,43 @@ function generateForSpec(spec, serviceName, fileBase, isDefaultFetch) {
605
636
  methods.push(' */');
606
637
  }
607
638
 
608
- const queryParamNames = entryParams.filter((p) => p.in === 'query')
609
- .map((p) => {
639
+ const queryParams = entryParams.filter((p) => p.in === 'query');
640
+ let paramsObject = 'undefined';
641
+ if (queryParams.length === 1) {
642
+ const p = queryParams[0];
643
+ const paramType = resolveParamType(p, schemas);
644
+ const mappedName = paramNameMap.get(p.name) || p.name;
645
+ if (paramType === 'Record<string, string | undefined>') {
646
+ paramsObject = mappedName;
647
+ } else {
648
+ const key = formatObjectKey(p.name);
649
+ const prop = mappedName === p.name ? key : `${key}: ${mappedName}`;
650
+ paramsObject = `{ ${prop} }`;
651
+ }
652
+ } else if (queryParams.length > 1) {
653
+ const entries = queryParams.map((p) => {
654
+ const paramType = resolveParamType(p, schemas);
610
655
  const mappedName = paramNameMap.get(p.name) || p.name;
656
+ if (paramType === 'Record<string, string | undefined>') {
657
+ return `...${mappedName}`;
658
+ }
611
659
  const key = formatObjectKey(p.name);
612
660
  return mappedName === p.name ? key : `${key}: ${mappedName}`;
613
661
  });
614
- const paramsObject = queryParamNames.length ? `{ ${queryParamNames.join(', ')} }` : 'undefined';
662
+ paramsObject = `{ ${entries.join(', ')} }`;
663
+ }
615
664
 
616
665
  const pathTemplate = withPathParams(entryPath, entryParams, paramNameMap);
617
666
  const pathLiteral = pathTemplate.includes('${') ? `\`${pathTemplate}\`` : `'${pathTemplate}'`;
618
667
 
668
+ if (isEventStreamResponse(entryOp)) {
669
+ methods.push(` public ${name}(${sigParts}): EventSource {`);
670
+ methods.push(` return this.sse(${pathLiteral}, ${paramsObject});`);
671
+ methods.push(' }');
672
+ methods.push('');
673
+ continue;
674
+ }
675
+
619
676
  if (entryMethod === 'get') {
620
677
  methods.push(` public async ${name}<T = ${defaultType}>(${sigParts}): Promise<JsonResponse<T>> {`);
621
678
  methods.push(` return await this.get<T>(${pathLiteral}, ${paramsObject});`);
@@ -733,7 +790,7 @@ function generateForSpec(spec, serviceName, fileBase, isDefaultFetch) {
733
790
  // Map Swagger schema to a TS type for DTO output.
734
791
  function mapSchemaToTsType(schema, schemas) {
735
792
  if (!schema) {
736
- return '{}';
793
+ return 'object';
737
794
  }
738
795
  const {
739
796
  $ref,
@@ -751,7 +808,7 @@ function mapSchemaToTsType(schema, schemas) {
751
808
  if (refSchema && refSchema.properties && refSchema.properties.data) {
752
809
  return mapSchemaToTsType(refSchema.properties.data, schemas);
753
810
  }
754
- return '{}';
811
+ return 'object';
755
812
  }
756
813
  return normalizeDtoName(refName);
757
814
  }
@@ -763,6 +820,9 @@ function mapSchemaToTsType(schema, schemas) {
763
820
  return schemaEnum.map((v) => (typeof v === 'string' ? `'${v}'` : String(v)))
764
821
  .join(' | ');
765
822
  }
823
+ if (type === 'any') {
824
+ return 'any';
825
+ }
766
826
  if (type === 'string') {
767
827
  return 'string';
768
828
  }
@@ -784,11 +844,38 @@ function mapSchemaToTsType(schema, schemas) {
784
844
  }
785
845
  if (type === 'object') {
786
846
  if (additionalProperties) {
787
- return 'Record<string, {}>';
847
+ return 'Record<string, object>';
788
848
  }
789
- return '{}';
849
+ return 'object';
790
850
  }
791
- return '{}';
851
+ return 'object';
852
+ }
853
+
854
+ // Swagger treats schemas without a type constraint as any.
855
+ function isAnySchema(schema) {
856
+ if (schema === true || schema === null || schema === undefined) {
857
+ return true;
858
+ }
859
+ if (typeof schema !== 'object' || Array.isArray(schema)) {
860
+ return false;
861
+ }
862
+ if (schema.type === 'any') {
863
+ return true;
864
+ }
865
+ const typeConstraints = [
866
+ '$ref',
867
+ 'type',
868
+ 'items',
869
+ 'enum',
870
+ 'const',
871
+ 'properties',
872
+ 'additionalProperties',
873
+ 'allOf',
874
+ 'oneOf',
875
+ 'anyOf',
876
+ 'not'
877
+ ];
878
+ return !typeConstraints.some((key) => schema[key] !== undefined);
792
879
  }
793
880
 
794
881
  // Generate DTO/type definitions file content.
@@ -829,7 +916,14 @@ function generateTypes(spec) {
829
916
  const def = typeDefs.get(normalizedName);
830
917
  const requiredSet = new Set(required || []);
831
918
  for (const [prop, propSchema] of Object.entries(properties)) {
832
- def.props[prop] = mapSchemaToTsType(propSchema, schemas);
919
+ if (isAnySchema(propSchema)) {
920
+ continue;
921
+ }
922
+ const propType = mapSchemaToTsType(propSchema, schemas);
923
+ if (propType === 'any') {
924
+ continue;
925
+ }
926
+ def.props[prop] = propType;
833
927
  def.required[prop] = requiredSet.has(prop);
834
928
  const { description } = propSchema || {};
835
929
  const desc = description
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "beer-swagger",
3
- "version": "1.1.1",
3
+ "version": "4.0.1",
4
4
  "bin": {
5
5
  "beer-swagger": "./generate-apis.js"
6
6
  },