jszy-swagger-doc-generator 1.4.1 → 1.5.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.
package/dist/index.js CHANGED
@@ -40,6 +40,8 @@ exports.SwaggerDocGenerator = void 0;
40
40
  const axios_1 = __importDefault(require("axios"));
41
41
  const fs = __importStar(require("fs"));
42
42
  const path = __importStar(require("path"));
43
+ const template_helpers_1 = require("./helpers/template.helpers");
44
+ const type_helpers_1 = require("./helpers/type.helpers");
43
45
  /**
44
46
  * Transforms a string to PascalCase
45
47
  */
@@ -60,76 +62,27 @@ function toCamelCase(str) {
60
62
  })
61
63
  .replace(/\s+/g, '');
62
64
  }
63
- /**
64
- * Converts OpenAPI types to TypeScript types
65
- */
66
- function convertTypeToTs(typeDef, schemaComponents) {
67
- if (!typeDef)
68
- return 'any';
69
- if (typeDef.$ref) {
70
- // Extract the type name from the reference
71
- const refTypeName = typeDef.$ref.split('/').pop();
72
- return refTypeName || 'any';
73
- }
74
- // Handle allOf (used for composition/references)
75
- if (typeDef.allOf && Array.isArray(typeDef.allOf)) {
76
- return typeDef.allOf.map((item) => {
77
- if (item.$ref) {
78
- return item.$ref.split('/').pop();
79
- }
80
- else if (item.type) {
81
- return convertTypeToTs(item, schemaComponents);
82
- }
83
- return 'any';
84
- }).filter(Boolean).join(' & ') || 'any';
85
- }
86
- if (Array.isArray(typeDef.type)) {
87
- // Handle union types like ["string", "null"]
88
- if (typeDef.type.includes('null')) {
89
- const nonNullType = typeDef.type.find((t) => t !== 'null');
90
- return `${convertTypeToTs({ ...typeDef, type: nonNullType }, schemaComponents)} | null`;
91
- }
92
- return 'any';
65
+ class SwaggerDocGenerator {
66
+ /**
67
+ * Transforms a string to PascalCase
68
+ */
69
+ toPascalCase(str) {
70
+ return str
71
+ .replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) => {
72
+ return index === 0 ? word.toUpperCase() : word.toUpperCase();
73
+ })
74
+ .replace(/\s+/g, '');
93
75
  }
94
- switch (typeDef.type) {
95
- case 'string':
96
- if (typeDef.enum) {
97
- return `"${typeDef.enum.join('" | "')}"`;
98
- }
99
- if (typeDef.format === 'date' || typeDef.format === 'date-time') {
100
- return 'string';
101
- }
102
- return 'string';
103
- case 'integer':
104
- case 'number':
105
- return 'number';
106
- case 'boolean':
107
- return 'boolean';
108
- case 'array':
109
- if (typeDef.items) {
110
- return `${convertTypeToTs(typeDef.items, schemaComponents)}[]`;
111
- }
112
- return 'any[]';
113
- case 'object':
114
- if (typeDef.properties) {
115
- // Inline object definition
116
- const fields = Object.entries(typeDef.properties)
117
- .map(([propName, propSchema]) => {
118
- const required = typeDef.required && typeDef.required.includes(propName);
119
- const optional = !required ? '?' : '';
120
- return ` ${propName}${optional}: ${convertTypeToTs(propSchema, schemaComponents)};`;
121
- })
122
- .join('\n');
123
- return `{\n${fields}\n}`;
124
- }
125
- return 'Record<string, any>';
126
- case 'null':
127
- return 'null';
128
- default:
129
- return 'any';
76
+ /**
77
+ * Transforms a string to camelCase
78
+ */
79
+ toCamelCase(str) {
80
+ return str
81
+ .replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) => {
82
+ return index === 0 ? word.toLowerCase() : word.toUpperCase();
83
+ })
84
+ .replace(/\s+/g, '');
130
85
  }
131
- }
132
- class SwaggerDocGenerator {
133
86
  /**
134
87
  * Fetches the Swagger/OpenAPI JSON from a given URL
135
88
  */
@@ -242,6 +195,149 @@ class SwaggerDocGenerator {
242
195
  });
243
196
  return typeDefs;
244
197
  }
198
+ /**
199
+ * Generates React hooks from the paths in Swagger doc organized by tag
200
+ */
201
+ /**
202
+ * Converts OpenAPI types to TypeScript types
203
+ */
204
+ convertTypeToTs(typeDef, schemaComponents) {
205
+ if (!typeDef)
206
+ return 'any';
207
+ if (typeDef.$ref) {
208
+ // Extract the type name from the reference
209
+ const refTypeName = typeDef.$ref.split('/').pop();
210
+ return refTypeName || 'any';
211
+ }
212
+ // Handle allOf (used for composition/references) - combine all properties
213
+ if (typeDef.allOf && Array.isArray(typeDef.allOf)) {
214
+ const combinedProperties = {};
215
+ const refTypes = [];
216
+ for (const item of typeDef.allOf) {
217
+ if (item.$ref) {
218
+ // Extract the type name from the reference
219
+ const refTypeName = item.$ref.split('/').pop();
220
+ if (refTypeName) {
221
+ refTypes.push(refTypeName);
222
+ }
223
+ }
224
+ else if (item.type === 'object' && item.properties) {
225
+ // Combine properties from inline object definitions
226
+ Object.assign(combinedProperties, item.properties);
227
+ }
228
+ }
229
+ if (refTypes.length > 0 && Object.keys(combinedProperties).length > 0) {
230
+ // We have both references and inline properties
231
+ const inlineDef = {
232
+ type: 'object',
233
+ properties: combinedProperties,
234
+ required: typeDef.required
235
+ };
236
+ const inlineType = this.convertTypeToTs(inlineDef, schemaComponents);
237
+ return `${refTypes.join(' & ')} & ${inlineType}`;
238
+ }
239
+ else if (refTypes.length > 0) {
240
+ // Only references
241
+ return refTypes.join(' & ');
242
+ }
243
+ else if (Object.keys(combinedProperties).length > 0) {
244
+ // Only inline properties
245
+ return this.convertTypeToTs({
246
+ type: 'object',
247
+ properties: combinedProperties,
248
+ required: typeDef.required
249
+ }, schemaComponents);
250
+ }
251
+ else {
252
+ return 'any';
253
+ }
254
+ }
255
+ // Handle oneOf (union types)
256
+ if (typeDef.oneOf && Array.isArray(typeDef.oneOf)) {
257
+ return typeDef.oneOf.map((item) => {
258
+ if (item.$ref) {
259
+ return item.$ref.split('/').pop();
260
+ }
261
+ else if (item.type) {
262
+ return this.convertTypeToTs(item, schemaComponents);
263
+ }
264
+ return 'any';
265
+ }).filter(Boolean).join(' | ') || 'any';
266
+ }
267
+ // Handle anyOf (union types)
268
+ if (typeDef.anyOf && Array.isArray(typeDef.anyOf)) {
269
+ return typeDef.anyOf.map((item) => {
270
+ if (item.$ref) {
271
+ return item.$ref.split('/').pop();
272
+ }
273
+ else if (item.type) {
274
+ return this.convertTypeToTs(item, schemaComponents);
275
+ }
276
+ return 'any';
277
+ }).filter(Boolean).join(' | ') || 'any';
278
+ }
279
+ if (Array.isArray(typeDef.type)) {
280
+ // Handle union types like ["string", "null"]
281
+ if (typeDef.type.includes('null')) {
282
+ const nonNullTypes = typeDef.type.filter((t) => t !== 'null');
283
+ if (nonNullTypes.length === 1) {
284
+ return `${this.convertTypeToTs({ ...typeDef, type: nonNullTypes[0] }, schemaComponents)} | null`;
285
+ }
286
+ else {
287
+ // Handle complex union types with null
288
+ const nonNullTypeStr = nonNullTypes
289
+ .map((t) => this.convertTypeToTs({ ...typeDef, type: t }, schemaComponents))
290
+ .join(' | ');
291
+ return `${nonNullTypeStr} | null`;
292
+ }
293
+ }
294
+ // Handle other array type unions
295
+ return typeDef.type
296
+ .map((t) => this.convertTypeToTs({ ...typeDef, type: t }, schemaComponents))
297
+ .join(' | ') || 'any';
298
+ }
299
+ switch (typeDef.type) {
300
+ case 'string':
301
+ if (typeDef.enum) {
302
+ return `"${typeDef.enum.join('" | "')}"`;
303
+ }
304
+ if (typeDef.format === 'date' || typeDef.format === 'date-time') {
305
+ return 'string';
306
+ }
307
+ return 'string';
308
+ case 'integer':
309
+ case 'number':
310
+ return 'number';
311
+ case 'boolean':
312
+ return 'boolean';
313
+ case 'array':
314
+ if (typeDef.items) {
315
+ return `${this.convertTypeToTs(typeDef.items, schemaComponents)}[]`;
316
+ }
317
+ return 'any[]';
318
+ case 'object':
319
+ if (typeDef.properties) {
320
+ // Inline object definition
321
+ const fields = Object.entries(typeDef.properties)
322
+ .map(([propName, propSchema]) => {
323
+ const required = typeDef.required && typeDef.required.includes(propName);
324
+ const optional = !required ? '?' : '';
325
+ const type = this.convertTypeToTs(propSchema, schemaComponents);
326
+ // Get the description for JSDoc if available
327
+ const propDescription = propSchema.description || propSchema.title;
328
+ const jsDoc = propDescription ? ` /** ${propDescription} */\n` : '';
329
+ return `${jsDoc} ${propName}${optional}: ${type};`;
330
+ })
331
+ .join('\n');
332
+ return `{\n${fields}\n }`;
333
+ }
334
+ return 'Record<string, any>';
335
+ case 'null':
336
+ return 'null';
337
+ default:
338
+ return 'any';
339
+ }
340
+ }
245
341
  /**
246
342
  * Generates a single TypeScript type definition
247
343
  */
@@ -250,15 +346,15 @@ class SwaggerDocGenerator {
250
346
  // Enum type
251
347
  return `export type ${typeName} = ${schema.enum.map((val) => `'${val}'`).join(' | ')};\n`;
252
348
  }
253
- if (schema.oneOf || schema.anyOf || schema.allOf) {
254
- // Union type or complex type
255
- const typeOption = schema.oneOf ? 'oneOf' : schema.anyOf ? 'anyOf' : 'allOf';
349
+ if (schema.oneOf || schema.anyOf) {
350
+ // Union type or complex type (oneOf/anyOf)
351
+ const typeOption = schema.oneOf ? 'oneOf' : 'anyOf';
256
352
  const types = schema[typeOption].map((item) => {
257
353
  if (item.$ref) {
258
354
  return item.$ref.split('/').pop();
259
355
  }
260
356
  else if (item.type) {
261
- return convertTypeToTs(item, allSchemas);
357
+ return this.convertTypeToTs(item, allSchemas);
262
358
  }
263
359
  else {
264
360
  return 'any';
@@ -266,6 +362,24 @@ class SwaggerDocGenerator {
266
362
  }).filter(Boolean);
267
363
  return `export type ${typeName} = ${types.join(' | ')};\n`;
268
364
  }
365
+ if (schema.allOf) {
366
+ // Handle allOf - composition of multiple schemas
367
+ const allParts = [];
368
+ for (const part of schema.allOf) {
369
+ if (part.$ref) {
370
+ const refTypeName = part.$ref.split('/').pop();
371
+ if (refTypeName) {
372
+ allParts.push(refTypeName);
373
+ }
374
+ }
375
+ else if (part.type === 'object' && part.properties) {
376
+ // Create a temporary interface for inline object
377
+ const inlineInterface = this.generateInlineObjectInterface(part, `${typeName}Inline`, allSchemas);
378
+ allParts.push(inlineInterface);
379
+ }
380
+ }
381
+ return `export type ${typeName} = ${allParts.join(' & ')};\n`;
382
+ }
269
383
  if (schema.type === 'object') {
270
384
  // Object type
271
385
  let result = `export interface ${typeName} {\n`;
@@ -273,18 +387,39 @@ class SwaggerDocGenerator {
273
387
  Object.entries(schema.properties).forEach(([propName, propSchema]) => {
274
388
  const required = schema.required && schema.required.includes(propName);
275
389
  const optional = !required ? '?' : '';
276
- // Add JSDoc comment if available
277
- if (propSchema.description) {
278
- result += ` /** ${propSchema.description} */\n`;
390
+ // Add JSDoc comment if available (using title or description)
391
+ const jsDoc = propSchema.title || propSchema.description;
392
+ if (jsDoc) {
393
+ result += ` /** ${jsDoc} */\n`;
279
394
  }
280
- result += ` ${propName}${optional}: ${convertTypeToTs(propSchema, allSchemas)};\n`;
395
+ result += ` ${propName}${optional}: ${this.convertTypeToTs(propSchema, allSchemas)};\n`;
281
396
  });
282
397
  }
283
398
  result += '}\n';
284
399
  return result;
285
400
  }
286
401
  // For other types (string, number, etc.) that might have additional properties
287
- return `export type ${typeName} = ${convertTypeToTs(schema, allSchemas)};\n`;
402
+ return `export type ${typeName} = ${this.convertTypeToTs(schema, allSchemas)};\n`;
403
+ }
404
+ /**
405
+ * Generates an inline object interface for allOf composition
406
+ */
407
+ generateInlineObjectInterface(schema, tempName, allSchemas) {
408
+ if (!schema.properties)
409
+ return 'any';
410
+ let result = '{\n';
411
+ Object.entries(schema.properties).forEach(([propName, propSchema]) => {
412
+ const required = schema.required && schema.required.includes(propName);
413
+ const optional = !required ? '?' : '';
414
+ // Add JSDoc comment if available (using title or description)
415
+ const jsDoc = propSchema.title || propSchema.description;
416
+ if (jsDoc) {
417
+ result += ` /** ${jsDoc} */\n`;
418
+ }
419
+ result += ` ${propName}${optional}: ${this.convertTypeToTs(propSchema, allSchemas)};\n`;
420
+ });
421
+ result += ' }';
422
+ return result;
288
423
  }
289
424
  /**
290
425
  * Generates React hooks from the paths in Swagger doc organized by tag
@@ -577,14 +712,14 @@ class SwaggerDocGenerator {
577
712
  // Add path parameters
578
713
  pathParams.forEach((param) => {
579
714
  const required = param.required ? '' : '?';
580
- const type = convertTypeToTs(param.schema || {}, schemas);
581
- paramsInterface += ` ${toCamelCase(param.name)}${required}: ${type};\n`;
715
+ const type = this.convertTypeToTs(param.schema || {}, schemas);
716
+ paramsInterface += ` ${this.toCamelCase(param.name)}${required}: ${type};\n`;
582
717
  });
583
718
  // Add query parameters
584
719
  queryParams.forEach((param) => {
585
720
  const required = param.required ? '' : '?';
586
- const type = convertTypeToTs(param.schema || {}, schemas);
587
- paramsInterface += ` ${toCamelCase(param.name)}${required}: ${type};\n`;
721
+ const type = this.convertTypeToTs(param.schema || {}, schemas);
722
+ paramsInterface += ` ${this.toCamelCase(param.name)}${required}: ${type};\n`;
588
723
  });
589
724
  paramsInterface += '}\n';
590
725
  return paramsInterface;
@@ -596,29 +731,36 @@ class SwaggerDocGenerator {
596
731
  const operationId = endpointInfo.operationId || this.generateOperationId(path, method);
597
732
  // Extract action name from operationId to create cleaner hook names
598
733
  // e.g. configController_updateConfig -> useUpdateConfig instead of useConfigController_updateConfig
599
- let hookName = `use${toPascalCase(operationId)}`;
734
+ let hookName = `use${this.toPascalCase(operationId)}`;
600
735
  // Check if operationId follows pattern controller_action and simplify to action
601
736
  if (operationId.includes('_')) {
602
737
  const parts = operationId.split('_');
603
738
  if (parts.length >= 2) {
604
739
  // Use just the action part as the hook name
605
- hookName = `use${toPascalCase(parts[parts.length - 1])}`;
740
+ hookName = `use${this.toPascalCase(parts[parts.length - 1])}`;
606
741
  }
607
742
  }
608
743
  else {
609
744
  // For operationIds without underscores, keep the original naming
610
- hookName = `use${toPascalCase(operationId)}`;
745
+ hookName = `use${this.toPascalCase(operationId)}`;
611
746
  }
612
747
  const hookType = method.toLowerCase() === 'get' ? 'useQuery' : 'useMutation';
613
748
  // Use unique parameter interface name
614
749
  const pathParams = endpointInfo.parameters?.filter((p) => p.in === 'path') || [];
615
750
  const queryParams = endpointInfo.parameters?.filter((p) => p.in === 'query') || [];
616
- // Determine response type
751
+ // Determine response type by checking common success response codes
617
752
  let responseType = 'any';
618
- if (endpointInfo.responses && endpointInfo.responses['200']) {
619
- const responseSchema = endpointInfo.responses['200'].content?.['application/json']?.schema;
620
- if (responseSchema) {
621
- responseType = convertTypeToTs(responseSchema, schemas);
753
+ if (endpointInfo.responses) {
754
+ // Check for success responses in order of preference: 200, 201, 204, etc.
755
+ const successCodes = ['200', '201', '204', '202', '203', '205'];
756
+ for (const code of successCodes) {
757
+ if (endpointInfo.responses[code]) {
758
+ const responseSchema = endpointInfo.responses[code].content?.['application/json']?.schema;
759
+ if (responseSchema) {
760
+ responseType = this.convertTypeToTs(responseSchema, schemas);
761
+ break; // Use the first success response found
762
+ }
763
+ }
622
764
  }
623
765
  }
624
766
  // Generate request body parameter if needed
@@ -627,80 +769,39 @@ class SwaggerDocGenerator {
627
769
  if (method.toLowerCase() !== 'get' && method.toLowerCase() !== 'delete' && endpointInfo.requestBody) {
628
770
  const bodySchema = endpointInfo.requestBody.content?.['application/json']?.schema;
629
771
  if (bodySchema) {
630
- requestBodyType = convertTypeToTs(bodySchema, schemas);
772
+ requestBodyType = this.convertTypeToTs(bodySchema, schemas);
631
773
  hasBody = true;
632
774
  }
633
775
  }
634
776
  // Format the path for use in the code (handle path parameters) - without base URL
635
- const formattedPath = path.replace(/{(\w+)}/g, (_, param) => `\${params.${toCamelCase(param)}}`);
636
- const axiosPath = `\`${formattedPath}\``;
637
- // Generate the hook code
638
- let hookCode = '';
639
- if (method.toLowerCase() === 'get') {
640
- // For GET requests, use useQuery
641
- const hasParams = pathParams.length > 0 || queryParams.length > 0;
642
- if (hasParams) {
643
- // Generate simpler parameter interface name based on hook name instead of operationId
644
- const paramInterfaceName = `${hookName.replace('use', '')}Params`;
645
- hookCode += `export const ${hookName} = (params: ${paramInterfaceName}) => {\n`;
646
- hookCode += ` return useQuery({\n`;
647
- hookCode += ` queryKey: ['${operationId}', params],\n`;
648
- hookCode += ` queryFn: async () => {\n`;
649
- hookCode += ` const response = await axios.get<${responseType}>(${axiosPath}, { params });\n`;
650
- hookCode += ` return response.data;\n`;
651
- hookCode += ` },\n`;
652
- hookCode += ` });\n`;
653
- hookCode += `};\n`;
654
- }
655
- else {
656
- hookCode += `export const ${hookName} = () => {\n`;
657
- hookCode += ` return useQuery({\n`;
658
- hookCode += ` queryKey: ['${operationId}'],\n`;
659
- hookCode += ` queryFn: async () => {\n`;
660
- hookCode += ` const response = await axios.get<${responseType}>(${axiosPath});\n`;
661
- hookCode += ` return response.data;\n`;
662
- hookCode += ` },\n`;
663
- hookCode += ` });\n`;
664
- hookCode += `};\n`;
665
- }
777
+ const formattedPath = path.replace(/{(\w+)}/g, (_, param) => `\${params.${this.toCamelCase(param)}}`);
778
+ // Prepare data for the template
779
+ const hookData = {
780
+ hookName: hookName,
781
+ operationId: operationId,
782
+ method: method.toLowerCase(),
783
+ responseType: responseType,
784
+ requestBodyType: requestBodyType,
785
+ hasParams: pathParams.length > 0 || queryParams.length > 0,
786
+ hasPathParams: pathParams.length > 0,
787
+ paramInterfaceName: `${hookName.replace('use', '')}Params`,
788
+ formattedPath: formattedPath,
789
+ isGetRequest: method.toLowerCase() === 'get'
790
+ };
791
+ // Load and compile the individual hook template
792
+ const fs = require('fs');
793
+ const pathModule = require('path');
794
+ const templatePath = pathModule.join(__dirname, '..', 'templates', 'hooks', 'individual-hook.hbs');
795
+ try {
796
+ const templateSource = fs.readFileSync(templatePath, 'utf8');
797
+ const Handlebars = require('handlebars');
798
+ const template = Handlebars.compile(templateSource);
799
+ return template(hookData);
800
+ }
801
+ catch (error) {
802
+ console.error(`Error reading template file: ${error.message}`);
803
+ return `// Error generating hook for ${operationId}: ${error.message}`;
666
804
  }
667
- else {
668
- // For non-GET requests, use useMutation
669
- const hasPathParams = pathParams.length > 0;
670
- if (hasPathParams) {
671
- // Generate simpler parameter interface name based on hook name instead of operationId
672
- const paramInterfaceName = `${hookName.replace('use', '')}Params`;
673
- hookCode += `export const ${hookName} = () => {\n`;
674
- hookCode += ` const queryClient = useQueryClient();\n\n`;
675
- hookCode += ` return useMutation({\n`;
676
- hookCode += ` mutationFn: async ({ params, data }: { params: ${paramInterfaceName}; data: ${requestBodyType} }) => {\n`;
677
- hookCode += ` const response = await axios.${method.toLowerCase()}<${responseType}>(${axiosPath}, data);\n`;
678
- hookCode += ` return response.data;\n`;
679
- hookCode += ` },\n`;
680
- hookCode += ` onSuccess: () => {\n`;
681
- hookCode += ` // Invalidate and refetch related queries\n`;
682
- hookCode += ` queryClient.invalidateQueries({ queryKey: ['${operationId}'] });\n`;
683
- hookCode += ` },\n`;
684
- hookCode += ` });\n`;
685
- hookCode += `};\n`;
686
- }
687
- else {
688
- hookCode += `export const ${hookName} = () => {\n`;
689
- hookCode += ` const queryClient = useQueryClient();\n\n`;
690
- hookCode += ` return useMutation({\n`;
691
- hookCode += ` mutationFn: async (data: ${requestBodyType}) => {\n`;
692
- hookCode += ` const response = await axios.${method.toLowerCase()}<${responseType}>(${axiosPath}, data);\n`;
693
- hookCode += ` return response.data;\n`;
694
- hookCode += ` },\n`;
695
- hookCode += ` onSuccess: () => {\n`;
696
- hookCode += ` // Invalidate and refetch related queries\n`;
697
- hookCode += ` queryClient.invalidateQueries({ queryKey: ['${operationId}'] });\n`;
698
- hookCode += ` },\n`;
699
- hookCode += ` });\n`;
700
- hookCode += `};\n`;
701
- }
702
- }
703
- return hookCode;
704
805
  }
705
806
  /**
706
807
  * Generate operation ID from path and method if not provided
@@ -808,6 +909,150 @@ class SwaggerDocGenerator {
808
909
  }
809
910
  }
810
911
  }
912
+ /**
913
+ * Generates frontend resources using Handlebars templates
914
+ */
915
+ generateHandlebarsResources(swaggerDoc, templatePaths = {}) {
916
+ const resourcesByTag = new Map();
917
+ const schemas = swaggerDoc.components?.schemas || {};
918
+ // Group endpoints by tag
919
+ const endpointsByTag = {};
920
+ Object.entries(swaggerDoc.paths).forEach(([path, methods]) => {
921
+ Object.entries(methods).forEach(([method, endpointInfo]) => {
922
+ // Determine the tag for this endpoint
923
+ const tag = (endpointInfo.tags && endpointInfo.tags[0]) ? endpointInfo.tags[0] : 'General';
924
+ if (!endpointsByTag[tag]) {
925
+ endpointsByTag[tag] = [];
926
+ }
927
+ endpointsByTag[tag].push({ path, method, endpointInfo });
928
+ });
929
+ });
930
+ // Generate resources for each tag
931
+ Object.entries(endpointsByTag).forEach(([tag, endpoints]) => {
932
+ // Prepare context for templates
933
+ const context = {
934
+ title: swaggerDoc.info.title,
935
+ description: swaggerDoc.info.description || swaggerDoc.info.title,
936
+ version: swaggerDoc.info.version,
937
+ tag: tag,
938
+ endpoints: endpoints.map(e => ({
939
+ path: e.path,
940
+ method: e.method.toUpperCase(),
941
+ operationId: e.endpointInfo.operationId || this.generateOperationId(e.path, e.method),
942
+ summary: e.endpointInfo.summary,
943
+ description: e.endpointInfo.description,
944
+ parameters: e.endpointInfo.parameters || [],
945
+ responses: e.endpointInfo.responses,
946
+ requestBody: e.endpointInfo.requestBody
947
+ })),
948
+ schemas: schemas,
949
+ hasImportTypes: false,
950
+ usedTypeNames: [],
951
+ paramInterfaces: [],
952
+ hooks: [],
953
+ typeDefinitions: []
954
+ };
955
+ // Find types used in this tag
956
+ const directlyUsedSchemas = new Set();
957
+ if (schemas) {
958
+ Object.entries(schemas).forEach(([typeName, schema]) => {
959
+ if (this.isSchemaUsedInEndpoints(typeName, endpoints, schemas)) {
960
+ directlyUsedSchemas.add(typeName);
961
+ }
962
+ });
963
+ }
964
+ const allNeededSchemas = this.findAllReferencedSchemas(directlyUsedSchemas, schemas);
965
+ // Generate TypeScript types
966
+ let typesContent = '';
967
+ if (schemas) {
968
+ for (const typeName of allNeededSchemas) {
969
+ const schema = schemas[typeName];
970
+ if (schema) {
971
+ const typeDef = (0, type_helpers_1.generateSingleTypeDefinition)(typeName, schema, schemas);
972
+ typesContent += typeDef + '\n';
973
+ context.typeDefinitions.push(typeDef);
974
+ }
975
+ }
976
+ }
977
+ // Check if there are used types for import
978
+ if (allNeededSchemas.size > 0) {
979
+ context.hasImportTypes = true;
980
+ context.usedTypeNames = Array.from(allNeededSchemas);
981
+ }
982
+ // Generate parameter interfaces
983
+ const allParamInterfaces = [];
984
+ endpoints.forEach(({ path, method, endpointInfo }) => {
985
+ const paramInterface = this.generateParamInterface(path, method, endpointInfo, schemas);
986
+ if (paramInterface && !allParamInterfaces.includes(paramInterface)) {
987
+ allParamInterfaces.push(paramInterface);
988
+ }
989
+ });
990
+ context.paramInterfaces = allParamInterfaces;
991
+ // Generate individual hooks
992
+ const allHooks = [];
993
+ const endpointHookContents = [];
994
+ endpoints.forEach(({ path, method, endpointInfo }) => {
995
+ const hookContent = this.generateReactQueryHook(path, method, endpointInfo, schemas);
996
+ allHooks.push(hookContent);
997
+ endpointHookContents.push(hookContent); // Store for template context
998
+ });
999
+ context.hooks = allHooks;
1000
+ context.endpointHooks = endpointHookContents;
1001
+ // Generate resources using specified templates
1002
+ let hooksContent = '';
1003
+ if (templatePaths.hooks) {
1004
+ try {
1005
+ // Add utility functions to context for use in templates
1006
+ context['camelCase'] = (str) => this.toCamelCase(str);
1007
+ context['pascalCase'] = (str) => this.toPascalCase(str);
1008
+ hooksContent = (0, template_helpers_1.compileTemplate)(templatePaths.hooks, context);
1009
+ }
1010
+ catch (error) {
1011
+ // If template doesn't exist or fails, fall back to default generation
1012
+ console.warn(`Failed to compile hooks template: ${templatePaths.hooks}`, error);
1013
+ // Use the existing method as fallback
1014
+ hooksContent = `// ${this.toPascalCase(tag)} API Hooks\n`;
1015
+ hooksContent += `import { useQuery, useMutation, useQueryClient } from 'react-query';\n`;
1016
+ hooksContent += `import axios from 'axios';\n`;
1017
+ if (context.hasImportTypes) {
1018
+ hooksContent += `import type { ${context.usedTypeNames.join(', ')} } from './${this.toCamelCase(tag)}.types';\n\n`;
1019
+ }
1020
+ else {
1021
+ hooksContent += `\n`;
1022
+ }
1023
+ allParamInterfaces.forEach(interfaceCode => {
1024
+ hooksContent += interfaceCode + '\n';
1025
+ });
1026
+ allHooks.forEach(hookCode => {
1027
+ hooksContent += hookCode + '\n';
1028
+ });
1029
+ }
1030
+ }
1031
+ else {
1032
+ // Default generation if no template is provided
1033
+ hooksContent = `// ${this.toPascalCase(tag)} API Hooks\n`;
1034
+ hooksContent += `import { useQuery, useMutation, useQueryClient } from 'react-query';\n`;
1035
+ hooksContent += `import axios from 'axios';\n`;
1036
+ if (context.hasImportTypes) {
1037
+ hooksContent += `import type { ${context.usedTypeNames.join(', ')} } from './${this.toCamelCase(tag)}.types';\n\n`;
1038
+ }
1039
+ else {
1040
+ hooksContent += `\n`;
1041
+ }
1042
+ allParamInterfaces.forEach(interfaceCode => {
1043
+ hooksContent += interfaceCode + '\n';
1044
+ });
1045
+ allHooks.forEach(hookCode => {
1046
+ hooksContent += hookCode + '\n';
1047
+ });
1048
+ }
1049
+ resourcesByTag.set(tag, {
1050
+ hooks: hooksContent,
1051
+ types: typesContent
1052
+ });
1053
+ });
1054
+ return resourcesByTag;
1055
+ }
811
1056
  }
812
1057
  exports.SwaggerDocGenerator = SwaggerDocGenerator;
813
1058
  //# sourceMappingURL=index.js.map