nuxt-openapi-hyperfetch 0.1.0-alpha.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 (109) hide show
  1. package/.editorconfig +26 -0
  2. package/.prettierignore +17 -0
  3. package/.prettierrc.json +12 -0
  4. package/CONTRIBUTING.md +292 -0
  5. package/INSTRUCTIONS.md +327 -0
  6. package/LICENSE +202 -0
  7. package/README.md +202 -0
  8. package/dist/cli/config.d.ts +57 -0
  9. package/dist/cli/config.js +85 -0
  10. package/dist/cli/logger.d.ts +44 -0
  11. package/dist/cli/logger.js +58 -0
  12. package/dist/cli/logo.d.ts +6 -0
  13. package/dist/cli/logo.js +21 -0
  14. package/dist/cli/messages.d.ts +65 -0
  15. package/dist/cli/messages.js +86 -0
  16. package/dist/cli/prompts.d.ts +30 -0
  17. package/dist/cli/prompts.js +118 -0
  18. package/dist/cli/types.d.ts +43 -0
  19. package/dist/cli/types.js +4 -0
  20. package/dist/cli/utils.d.ts +26 -0
  21. package/dist/cli/utils.js +45 -0
  22. package/dist/generate.d.ts +6 -0
  23. package/dist/generate.js +48 -0
  24. package/dist/generators/nuxt-server/bff-templates.d.ts +25 -0
  25. package/dist/generators/nuxt-server/bff-templates.js +737 -0
  26. package/dist/generators/nuxt-server/generator.d.ts +7 -0
  27. package/dist/generators/nuxt-server/generator.js +206 -0
  28. package/dist/generators/nuxt-server/parser.d.ts +5 -0
  29. package/dist/generators/nuxt-server/parser.js +5 -0
  30. package/dist/generators/nuxt-server/templates.d.ts +35 -0
  31. package/dist/generators/nuxt-server/templates.js +412 -0
  32. package/dist/generators/nuxt-server/types.d.ts +5 -0
  33. package/dist/generators/nuxt-server/types.js +5 -0
  34. package/dist/generators/shared/parsers/heyapi-parser.d.ts +11 -0
  35. package/dist/generators/shared/parsers/heyapi-parser.js +248 -0
  36. package/dist/generators/shared/parsers/official-parser.d.ts +5 -0
  37. package/dist/generators/shared/parsers/official-parser.js +5 -0
  38. package/dist/generators/shared/runtime/apiHelpers.d.ts +183 -0
  39. package/dist/generators/shared/runtime/apiHelpers.js +268 -0
  40. package/dist/generators/shared/templates/api-callbacks-plugin.d.ts +178 -0
  41. package/dist/generators/shared/templates/api-callbacks-plugin.js +338 -0
  42. package/dist/generators/shared/types.d.ts +25 -0
  43. package/dist/generators/shared/types.js +4 -0
  44. package/dist/generators/tanstack-query/generator.d.ts +5 -0
  45. package/dist/generators/tanstack-query/generator.js +11 -0
  46. package/dist/generators/use-async-data/generator.d.ts +5 -0
  47. package/dist/generators/use-async-data/generator.js +156 -0
  48. package/dist/generators/use-async-data/parser.d.ts +5 -0
  49. package/dist/generators/use-async-data/parser.js +5 -0
  50. package/dist/generators/use-async-data/runtime/useApiAsyncData.d.ts +38 -0
  51. package/dist/generators/use-async-data/runtime/useApiAsyncData.js +122 -0
  52. package/dist/generators/use-async-data/runtime/useApiAsyncDataRaw.d.ts +54 -0
  53. package/dist/generators/use-async-data/runtime/useApiAsyncDataRaw.js +126 -0
  54. package/dist/generators/use-async-data/templates.d.ts +20 -0
  55. package/dist/generators/use-async-data/templates.js +191 -0
  56. package/dist/generators/use-async-data/types.d.ts +4 -0
  57. package/dist/generators/use-async-data/types.js +4 -0
  58. package/dist/generators/use-fetch/generator.d.ts +5 -0
  59. package/dist/generators/use-fetch/generator.js +131 -0
  60. package/dist/generators/use-fetch/parser.d.ts +9 -0
  61. package/dist/generators/use-fetch/parser.js +282 -0
  62. package/dist/generators/use-fetch/runtime/useApiRequest.d.ts +46 -0
  63. package/dist/generators/use-fetch/runtime/useApiRequest.js +158 -0
  64. package/dist/generators/use-fetch/templates.d.ts +16 -0
  65. package/dist/generators/use-fetch/templates.js +169 -0
  66. package/dist/generators/use-fetch/types.d.ts +5 -0
  67. package/dist/generators/use-fetch/types.js +5 -0
  68. package/dist/index.d.ts +2 -0
  69. package/dist/index.js +213 -0
  70. package/docs/API-REFERENCE.md +887 -0
  71. package/docs/ARCHITECTURE.md +649 -0
  72. package/docs/DEVELOPMENT.md +918 -0
  73. package/docs/QUICK-START.md +323 -0
  74. package/docs/README.md +155 -0
  75. package/docs/TROUBLESHOOTING.md +881 -0
  76. package/eslint.config.js +72 -0
  77. package/package.json +65 -0
  78. package/src/cli/config.ts +140 -0
  79. package/src/cli/logger.ts +66 -0
  80. package/src/cli/logo.ts +25 -0
  81. package/src/cli/messages.ts +97 -0
  82. package/src/cli/prompts.ts +143 -0
  83. package/src/cli/types.ts +50 -0
  84. package/src/cli/utils.ts +49 -0
  85. package/src/generate.ts +57 -0
  86. package/src/generators/nuxt-server/bff-templates.ts +754 -0
  87. package/src/generators/nuxt-server/generator.ts +270 -0
  88. package/src/generators/nuxt-server/parser.ts +5 -0
  89. package/src/generators/nuxt-server/templates.ts +483 -0
  90. package/src/generators/nuxt-server/types.ts +5 -0
  91. package/src/generators/shared/parsers/heyapi-parser.ts +307 -0
  92. package/src/generators/shared/parsers/official-parser.ts +5 -0
  93. package/src/generators/shared/runtime/apiHelpers.ts +466 -0
  94. package/src/generators/shared/templates/api-callbacks-plugin.ts +352 -0
  95. package/src/generators/shared/types.ts +27 -0
  96. package/src/generators/tanstack-query/generator.ts +11 -0
  97. package/src/generators/use-async-data/generator.ts +204 -0
  98. package/src/generators/use-async-data/parser.ts +5 -0
  99. package/src/generators/use-async-data/runtime/useApiAsyncData.ts +220 -0
  100. package/src/generators/use-async-data/runtime/useApiAsyncDataRaw.ts +236 -0
  101. package/src/generators/use-async-data/templates.ts +250 -0
  102. package/src/generators/use-async-data/types.ts +4 -0
  103. package/src/generators/use-fetch/generator.ts +169 -0
  104. package/src/generators/use-fetch/parser.ts +341 -0
  105. package/src/generators/use-fetch/runtime/useApiRequest.ts +223 -0
  106. package/src/generators/use-fetch/templates.ts +214 -0
  107. package/src/generators/use-fetch/types.ts +5 -0
  108. package/src/index.ts +265 -0
  109. package/tsconfig.json +15 -0
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Main function to generate Nuxt Server Routes
3
+ */
4
+ export declare function generateNuxtServerRoutes(inputDir: string, serverRoutePath: string, options?: {
5
+ enableBff?: boolean;
6
+ backend?: string;
7
+ }): Promise<void>;
@@ -0,0 +1,206 @@
1
+ import * as path from 'path';
2
+ import fs from 'fs-extra';
3
+ import { format } from 'prettier';
4
+ import { getApiFiles as getApiFilesOfficial, parseApiFile as parseApiFileOfficial, } from './parser.js';
5
+ import { getApiFiles as getApiFilesHeyApi, parseApiFile as parseApiFileHeyApi, } from '../shared/parsers/heyapi-parser.js';
6
+ import { generateServerRouteFile, generateRouteFilePath, generateRoutesIndexFile, } from './templates.js';
7
+ import { generateAuthContextStub, generateAuthTypesStub, generateTransformerStub, generateTransformerExamples, generateBffReadme, } from './bff-templates.js';
8
+ import { p, logSuccess, logError, logNote } from '../../cli/logger.js';
9
+ /**
10
+ * Main function to generate Nuxt Server Routes
11
+ */
12
+ export async function generateNuxtServerRoutes(inputDir, serverRoutePath, options) {
13
+ const mainSpinner = p.spinner();
14
+ // Select parser based on chosen backend
15
+ const getApiFiles = options?.backend === 'heyapi' ? getApiFilesHeyApi : getApiFilesOfficial;
16
+ const parseApiFile = options?.backend === 'heyapi' ? parseApiFileHeyApi : parseApiFileOfficial;
17
+ const enableBff = options?.enableBff ?? false;
18
+ if (enableBff) {
19
+ p.log.info('BFF Mode: Enabled (transformers + auth)');
20
+ }
21
+ // 1. Get all API files
22
+ mainSpinner.start('Scanning API files');
23
+ const apiFiles = getApiFiles(inputDir);
24
+ mainSpinner.stop(`Found ${apiFiles.length} API file(s)`);
25
+ if (apiFiles.length === 0) {
26
+ throw new Error('No API files found in the input directory');
27
+ }
28
+ // 2. Parse each API file
29
+ mainSpinner.start('Parsing API files');
30
+ const allMethods = [];
31
+ for (const file of apiFiles) {
32
+ const fileName = path.basename(file);
33
+ try {
34
+ const apiInfo = parseApiFile(file);
35
+ allMethods.push(...apiInfo.methods);
36
+ }
37
+ catch (error) {
38
+ logError(`Error parsing ${fileName}: ${String(error)}`);
39
+ }
40
+ }
41
+ mainSpinner.stop(`Found ${allMethods.length} routes to generate`);
42
+ if (allMethods.length === 0) {
43
+ p.log.warn('No methods found to generate');
44
+ return;
45
+ }
46
+ // 3. Clean and create output directory
47
+ mainSpinner.start('Preparing output directory');
48
+ await fs.emptyDir(serverRoutePath);
49
+ mainSpinner.stop('Output directory ready');
50
+ // 4. Generate BFF structure if enabled
51
+ if (enableBff) {
52
+ await generateBffStructure(allMethods, serverRoutePath, inputDir);
53
+ }
54
+ // 5. Calculate relative import path from server routes to APIs
55
+ const relativePath = calculateRelativeImportPath(serverRoutePath, inputDir);
56
+ // 6. Generate each server route
57
+ mainSpinner.start('Generating server routes');
58
+ let successCount = 0;
59
+ let errorCount = 0;
60
+ for (const method of allMethods) {
61
+ try {
62
+ // Extract resource name from path
63
+ const resource = extractResourceFromPath(method.path);
64
+ const code = generateServerRouteFile(method, relativePath, {
65
+ enableBff: enableBff,
66
+ resource: resource,
67
+ });
68
+ const formattedCode = await formatCode(code);
69
+ const routeFilePath = generateRouteFilePath(method);
70
+ const fullPath = path.join(serverRoutePath, routeFilePath);
71
+ // Ensure directory exists
72
+ await fs.ensureDir(path.dirname(fullPath));
73
+ await fs.writeFile(fullPath, formattedCode, 'utf-8');
74
+ successCount++;
75
+ }
76
+ catch (error) {
77
+ logError(`Error generating ${method.path} [${method.httpMethod}]: ${String(error)}`);
78
+ errorCount++;
79
+ }
80
+ }
81
+ mainSpinner.stop(`Generated ${successCount} server routes`);
82
+ // 7. Generate configuration files
83
+ mainSpinner.start('Generating configuration files');
84
+ // Generate routes index (documentation)
85
+ const routesIndexCode = generateRoutesIndexFile(allMethods);
86
+ const formattedRoutesIndex = await formatCode(routesIndexCode);
87
+ await fs.writeFile(path.join(serverRoutePath, '_routes.ts'), formattedRoutesIndex, 'utf-8');
88
+ mainSpinner.stop('Configuration files generated');
89
+ // 8. Summary and Next Steps
90
+ if (errorCount > 0) {
91
+ p.log.warn(`Completed with ${errorCount} error(s)`);
92
+ }
93
+ logSuccess(`Generated ${successCount} server route(s) in ${serverRoutePath}`);
94
+ // Build next steps message
95
+ let nextSteps = '1. Configure API_BASE_URL and API_SECRET in your .env\n';
96
+ nextSteps += '2. Update nuxt.config.ts with runtimeConfig (add apiBaseUrl and apiSecret)';
97
+ if (enableBff) {
98
+ nextSteps += '\n3. Implement authentication in server/auth/context.ts';
99
+ nextSteps += '\n4. Add business logic to transformers in server/bff/transformers/';
100
+ nextSteps += '\n5. See server/bff/README.md for BFF documentation';
101
+ nextSteps += '\n6. Start your Nuxt dev server and test the routes';
102
+ }
103
+ else {
104
+ nextSteps += '\n3. Start your Nuxt dev server and test the routes';
105
+ }
106
+ logNote(nextSteps, 'Next steps');
107
+ }
108
+ /**
109
+ * Calculate relative import path from server routes to APIs
110
+ */
111
+ function calculateRelativeImportPath(serverRoutePath, inputDir) {
112
+ // Use Nuxt's ~ alias (project root) so the path is stable regardless of serverRoutePath depth
113
+ const projectRoot = process.cwd();
114
+ const relativeInputDir = path.relative(projectRoot, path.resolve(inputDir));
115
+ // Convert Windows paths to Unix-style
116
+ return '~/' + relativeInputDir.replace(/\\/g, '/');
117
+ }
118
+ /**
119
+ * Format code with Prettier
120
+ */
121
+ async function formatCode(code) {
122
+ try {
123
+ return await format(code, {
124
+ parser: 'typescript',
125
+ semi: false,
126
+ singleQuote: true,
127
+ trailingComma: 'es5',
128
+ printWidth: 100,
129
+ });
130
+ }
131
+ catch {
132
+ p.log.warn('Prettier formatting failed, using unformatted code');
133
+ return code;
134
+ }
135
+ }
136
+ /**
137
+ * Generate BFF structure (auth + transformers)
138
+ */
139
+ async function generateBffStructure(allMethods, serverRoutePath, inputDir) {
140
+ const bffSpinner = p.spinner();
141
+ bffSpinner.start('Generating BFF structure (auth + transformers)');
142
+ const serverRoot = path.dirname(serverRoutePath);
143
+ // 1. Generate auth files (only if they don't exist)
144
+ const authDir = path.join(serverRoot, 'auth');
145
+ await fs.ensureDir(authDir);
146
+ const authContextPath = path.join(authDir, 'context.ts');
147
+ if (!fs.existsSync(authContextPath)) {
148
+ const authContextCode = generateAuthContextStub();
149
+ const formattedAuthContext = await formatCode(authContextCode);
150
+ await fs.writeFile(authContextPath, formattedAuthContext, 'utf-8');
151
+ }
152
+ const authTypesPath = path.join(authDir, 'types.ts');
153
+ if (!fs.existsSync(authTypesPath)) {
154
+ const authTypesCode = generateAuthTypesStub();
155
+ const formattedAuthTypes = await formatCode(authTypesCode);
156
+ await fs.writeFile(authTypesPath, formattedAuthTypes, 'utf-8');
157
+ }
158
+ // 2. Generate transformer stubs (only if they don't exist)
159
+ const bffDir = path.join(serverRoot, 'bff');
160
+ const transformersDir = path.join(bffDir, 'transformers');
161
+ await fs.ensureDir(transformersDir);
162
+ // Group methods by resource
163
+ const methodsByResource = new Map();
164
+ for (const method of allMethods) {
165
+ const resource = extractResourceFromPath(method.path);
166
+ if (!methodsByResource.has(resource)) {
167
+ methodsByResource.set(resource, []);
168
+ }
169
+ methodsByResource.get(resource).push(method);
170
+ }
171
+ // Generate transformer for each resource
172
+ for (const [resource, methods] of methodsByResource.entries()) {
173
+ const transformerPath = path.join(transformersDir, `${resource}.ts`);
174
+ if (!fs.existsSync(transformerPath)) {
175
+ const transformerCode = generateTransformerStub(resource, methods, inputDir);
176
+ const formattedTransformer = await formatCode(transformerCode);
177
+ await fs.writeFile(transformerPath, formattedTransformer, 'utf-8');
178
+ }
179
+ }
180
+ // 3. Generate examples file (always regenerated)
181
+ const examplesPath = path.join(bffDir, '_transformers.example.ts');
182
+ const examplesCode = generateTransformerExamples();
183
+ const formattedExamples = await formatCode(examplesCode);
184
+ await fs.writeFile(examplesPath, formattedExamples, 'utf-8');
185
+ // 4. Generate BFF README (always regenerated)
186
+ const bffReadmePath = path.join(bffDir, 'README.md');
187
+ const bffReadmeCode = generateBffReadme();
188
+ await fs.writeFile(bffReadmePath, bffReadmeCode, 'utf-8');
189
+ bffSpinner.stop('BFF structure generated');
190
+ }
191
+ /**
192
+ * Extract resource name from API path
193
+ * Examples:
194
+ * /pet -> pet
195
+ * /pet/{id} -> pet
196
+ * /store/inventory -> store
197
+ * /user/login -> user
198
+ */
199
+ function extractResourceFromPath(path) {
200
+ // Remove leading slash
201
+ const cleanPath = path.startsWith('/') ? path.substring(1) : path;
202
+ // Get first segment
203
+ const firstSegment = cleanPath.split('/')[0];
204
+ // Remove any path params
205
+ return firstSegment.replace(/\{[^}]+\}/g, '').toLowerCase();
206
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Re-export all parser functionality from use-fetch
3
+ * The parsing logic is identical for all generators
4
+ */
5
+ export * from '../use-fetch/parser.js';
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Re-export all parser functionality from use-fetch
3
+ * The parsing logic is identical for all generators
4
+ */
5
+ export * from '../use-fetch/parser.js';
@@ -0,0 +1,35 @@
1
+ import type { MethodInfo } from './types.js';
2
+ /**
3
+ * Generate a complete server route file
4
+ */
5
+ export declare function generateServerRouteFile(method: MethodInfo, apiImportPath: string, options?: {
6
+ enableBff?: boolean;
7
+ resource?: string;
8
+ }): string;
9
+ /**
10
+ * Calculate the file path for a server route
11
+ * Examples:
12
+ * /pet + GET -> pet/index.get.ts
13
+ * /pet + POST -> pet/index.post.ts
14
+ * /pet/{petId} + GET -> pet/[id].get.ts
15
+ * /pet/{petId}/uploadImage + POST -> pet/[id]/uploadImage.post.ts
16
+ * /store/inventory + GET -> store/inventory.get.ts
17
+ * /user/{username} + GET -> user/[username].get.ts
18
+ */
19
+ export declare function generateRouteFilePath(method: MethodInfo): string;
20
+ /**
21
+ * Generate nuxt.config.example.ts
22
+ */
23
+ export declare function generateConfigFile(): string;
24
+ /**
25
+ * Generate .env.example
26
+ */
27
+ export declare function generateEnvFile(): string;
28
+ /**
29
+ * Generate README.md
30
+ */
31
+ export declare function generateReadme(serverPath: string): string;
32
+ /**
33
+ * Generate index file that exports all route paths (optional, for documentation)
34
+ */
35
+ export declare function generateRoutesIndexFile(methods: MethodInfo[]): string;
@@ -0,0 +1,412 @@
1
+ import { camelCase, pascalCase } from 'change-case';
2
+ /**
3
+ * Generate file header with auto-generation warning
4
+ */
5
+ function generateFileHeader() {
6
+ return `/**
7
+ * ⚠️ AUTO-GENERATED FILE - DO NOT EDIT MANUALLY
8
+ *
9
+ * This file was automatically generated by nuxt-openapi-generator.
10
+ * Any manual changes will be overwritten on the next generation.
11
+ *
12
+ * @generated by nuxt-openapi-generator
13
+ * @see https://github.com/dmartindiaz/nuxt-openapi-hyperfetch
14
+ */
15
+
16
+ /* eslint-disable */
17
+ // @ts-nocheck
18
+ `;
19
+ }
20
+ /**
21
+ * Generate a complete server route file
22
+ */
23
+ export function generateServerRouteFile(method, apiImportPath, options) {
24
+ const header = generateFileHeader();
25
+ const imports = generateImports(method, apiImportPath);
26
+ const handlerBody = generateHandlerBody(method, options);
27
+ return `${header}${imports}\n\n${handlerBody}\n`;
28
+ }
29
+ /**
30
+ * Calculate the file path for a server route
31
+ * Examples:
32
+ * /pet + GET -> pet/index.get.ts
33
+ * /pet + POST -> pet/index.post.ts
34
+ * /pet/{petId} + GET -> pet/[id].get.ts
35
+ * /pet/{petId}/uploadImage + POST -> pet/[id]/uploadImage.post.ts
36
+ * /store/inventory + GET -> store/inventory.get.ts
37
+ * /user/{username} + GET -> user/[username].get.ts
38
+ */
39
+ export function generateRouteFilePath(method) {
40
+ let routePath = method.path;
41
+ // Remove leading slash
42
+ if (routePath.startsWith('/')) {
43
+ routePath = routePath.substring(1);
44
+ }
45
+ // Replace path params: {petId} -> [id], {username} -> [username]
46
+ routePath = routePath.replace(/\{(\w+)\}/g, (match, paramName) => {
47
+ // Simplify common params
48
+ const simplified = paramName.toLowerCase().replace(/id$/, '');
49
+ return `[${simplified || 'id'}]`;
50
+ });
51
+ // Split path into segments
52
+ const segments = routePath.split('/').filter(Boolean);
53
+ // If empty or root, use 'index'
54
+ if (segments.length === 0) {
55
+ return `index.${method.httpMethod.toLowerCase()}.ts`;
56
+ }
57
+ // Check if last segment is a dynamic param [xxx]
58
+ const lastSegment = segments[segments.length - 1];
59
+ const isDynamicParam = lastSegment.startsWith('[') && lastSegment.endsWith(']');
60
+ // If last segment is dynamic, add index
61
+ if (isDynamicParam) {
62
+ return `${segments.join('/')}/index.${method.httpMethod.toLowerCase()}.ts`;
63
+ }
64
+ // Otherwise, use the last segment as filename
65
+ const fileName = segments.pop();
66
+ const dir = segments.length > 0 ? segments.join('/') + '/' : '';
67
+ return `${dir}${fileName}.${method.httpMethod.toLowerCase()}.ts`;
68
+ }
69
+ /**
70
+ * Extract base type names from a type string (same as use-fetch)
71
+ */
72
+ function extractBaseTypes(type) {
73
+ if (!type) {
74
+ return [];
75
+ }
76
+ // Handle array syntax: Pet[]
77
+ const arrayMatch = type.match(/^(\w+)\[\]$/);
78
+ if (arrayMatch) {
79
+ return [arrayMatch[1]];
80
+ }
81
+ // Handle Array generic: Array<Pet>
82
+ const arrayGenericMatch = type.match(/^Array<(\w+)>$/);
83
+ if (arrayGenericMatch) {
84
+ return [arrayGenericMatch[1]];
85
+ }
86
+ // If it's a simple named type (single word, PascalCase), include it
87
+ if (/^[A-Z][a-zA-Z0-9]*$/.test(type)) {
88
+ return [type];
89
+ }
90
+ return [];
91
+ }
92
+ /**
93
+ * Generate import statements
94
+ */
95
+ function generateImports(method, apiImportPath) {
96
+ const h3Imports = ['defineEventHandler', 'createError'];
97
+ // Add h3 imports based on method needs
98
+ if (method.pathParams.length > 0) {
99
+ h3Imports.push('getRouterParam');
100
+ }
101
+ if (method.hasQueryParams) {
102
+ h3Imports.push('getQuery');
103
+ }
104
+ if (method.hasBody) {
105
+ h3Imports.push('readBody');
106
+ }
107
+ let imports = `import { ${h3Imports.join(', ')} } from 'h3'\n`;
108
+ // Import types
109
+ const typeNames = new Set();
110
+ // Extract base types from request type
111
+ if (method.requestType) {
112
+ const extracted = extractBaseTypes(method.requestType);
113
+ extracted.forEach((t) => typeNames.add(t));
114
+ }
115
+ // Extract base types from response type
116
+ if (method.responseType && method.responseType !== 'void') {
117
+ const extracted = extractBaseTypes(method.responseType);
118
+ extracted.forEach((t) => typeNames.add(t));
119
+ }
120
+ // Import types from API (only if we have named types to import)
121
+ if (typeNames.size > 0) {
122
+ imports += `import type { ${Array.from(typeNames).join(', ')} } from '${apiImportPath}'\n`;
123
+ }
124
+ return imports;
125
+ }
126
+ /**
127
+ * Generate the handler body
128
+ */
129
+ function generateHandlerBody(method, options) {
130
+ const description = method.description ? `/**\n * ${method.description}\n */\n` : '';
131
+ const pathParamCapture = generatePathParamCapture(method);
132
+ const queryCapture = generateQueryCapture(method);
133
+ const bodyCapture = generateBodyCapture(method);
134
+ const backendUrl = generateBackendUrl(method);
135
+ const fetchOptions = generateFetchOptions(method);
136
+ // BFF: Auth context loading
137
+ const authContextCode = options?.enableBff
138
+ ? ` // Try to load auth context (optional)
139
+ let auth = null
140
+ try {
141
+ const { getAuthContext } = await import('~/server/auth/context')
142
+ auth = await getAuthContext(event)
143
+ } catch {
144
+ // Auth not configured - continue without it
145
+ }
146
+
147
+ `
148
+ : '';
149
+ // BFF: Transformer call
150
+ const transformerCode = options?.enableBff && options?.resource
151
+ ? `
152
+ // Try to transform data (optional)
153
+ try {
154
+ const { transform${pascalCase(options.resource)} } = await import('~/server/bff/transformers/${options.resource}')
155
+ return await transform${pascalCase(options.resource)}(data, event, auth)
156
+ } catch {
157
+ // Transformer not found - return raw data
158
+ return data
159
+ }`
160
+ : `
161
+ return data`;
162
+ return `${description}export default defineEventHandler(async (event): Promise<${method.responseType}> => {
163
+ ${pathParamCapture}${queryCapture}${bodyCapture}${authContextCode}const config = useRuntimeConfig()
164
+ const baseUrl = config.apiBaseUrl
165
+
166
+ try {
167
+ const data = await $fetch<${method.responseType}>(${backendUrl}, {
168
+ ${fetchOptions}
169
+ })${transformerCode}
170
+ } catch (error: any) {
171
+ throw createError({
172
+ statusCode: error.statusCode || 500,
173
+ statusMessage: error.message || 'Request failed'
174
+ })
175
+ }
176
+ })`;
177
+ }
178
+ /**
179
+ * Generate path param capture code
180
+ */
181
+ function generatePathParamCapture(method) {
182
+ if (method.pathParams.length === 0) {
183
+ return '';
184
+ }
185
+ const captures = method.pathParams.map((param) => {
186
+ const paramName = camelCase(param);
187
+ const paramKey = param.toLowerCase().replace(/id$/, '') || 'id';
188
+ return `const ${paramName} = getRouterParam(event, '${paramKey}')
189
+ if (!${paramName}) {
190
+ throw createError({
191
+ statusCode: 400,
192
+ statusMessage: '${param} is required'
193
+ })
194
+ }
195
+ `;
196
+ });
197
+ return captures.join('') + '\n ';
198
+ }
199
+ /**
200
+ * Generate query param capture code
201
+ */
202
+ function generateQueryCapture(method) {
203
+ if (!method.hasQueryParams) {
204
+ return '';
205
+ }
206
+ return 'const query = getQuery(event)\n ';
207
+ }
208
+ /**
209
+ * Generate body capture code
210
+ */
211
+ function generateBodyCapture(method) {
212
+ if (!method.hasBody) {
213
+ return '';
214
+ }
215
+ const typeAnnotation = method.requestType ? `<${method.requestType}>` : '';
216
+ return `const body = await readBody${typeAnnotation}(event)\n `;
217
+ }
218
+ /**
219
+ * Generate backend URL with path params replaced
220
+ */
221
+ function generateBackendUrl(method) {
222
+ let url = method.path;
223
+ // Replace {param} with ${paramName}
224
+ if (method.pathParams.length > 0) {
225
+ for (const param of method.pathParams) {
226
+ const paramName = camelCase(param);
227
+ url = url.replace(`{${param}}`, `\${${paramName}}`);
228
+ }
229
+ return `\`\${baseUrl}${url}\``;
230
+ }
231
+ // Static URL
232
+ return `\`\${baseUrl}${url}\``;
233
+ }
234
+ /**
235
+ * Generate $fetch options object
236
+ */
237
+ function generateFetchOptions(method) {
238
+ const options = [];
239
+ // Method (if not GET)
240
+ if (method.httpMethod !== 'GET') {
241
+ options.push(`method: '${method.httpMethod}'`);
242
+ }
243
+ // Query params
244
+ if (method.hasQueryParams) {
245
+ options.push('query: query');
246
+ }
247
+ // Body
248
+ if (method.hasBody) {
249
+ options.push('body: body');
250
+ }
251
+ // Headers
252
+ const headerLines = [];
253
+ if (method.hasBody) {
254
+ headerLines.push(`'Content-Type': 'application/json'`);
255
+ }
256
+ headerLines.push(`...(config.apiSecret ? { 'Authorization': \`Bearer \${config.apiSecret}\` } : {})`);
257
+ options.push(`headers: {\n ${headerLines.join(',\n ')}\n }`);
258
+ return options.join(',\n ');
259
+ }
260
+ /**
261
+ * Generate nuxt.config.example.ts
262
+ */
263
+ export function generateConfigFile() {
264
+ return `/**
265
+ * ⚠️ AUTO-GENERATED EXAMPLE FILE
266
+ *
267
+ * Copy this configuration to your nuxt.config.ts
268
+ * @generated by nuxt-openapi-generator
269
+ */
270
+
271
+ // nuxt.config.ts
272
+ export default defineNuxtConfig({
273
+ runtimeConfig: {
274
+ // Private keys (server-only, never exposed to client)
275
+ apiSecret: process.env.API_SECRET || '',
276
+ apiBaseUrl: process.env.API_BASE_URL || 'https://petstore3.swagger.io/api/v3'
277
+ }
278
+ })
279
+ `;
280
+ }
281
+ /**
282
+ * Generate .env.example
283
+ */
284
+ export function generateEnvFile() {
285
+ return `# ⚠️ AUTO-GENERATED EXAMPLE FILE
286
+ # Copy this file to .env and configure your values
287
+ # @generated by nuxt-openapi-generator
288
+
289
+ # Backend API Configuration
290
+ API_BASE_URL=https://petstore3.swagger.io/api/v3
291
+ API_SECRET=your-api-secret-token-here
292
+ `;
293
+ }
294
+ /**
295
+ * Generate README.md
296
+ */
297
+ export function generateReadme(serverPath) {
298
+ return `<!--
299
+ ⚠️ AUTO-GENERATED DOCUMENTATION
300
+ This file was automatically generated by nuxt-openapi-generator.
301
+ @generated by nuxt-openapi-generator
302
+ -->
303
+
304
+ # Nuxt Server Routes
305
+
306
+ Auto-generated server routes that proxy requests to your backend API.
307
+
308
+ ## 🔧 Configuration
309
+
310
+ 1. **Copy \`.env.example\` to \`.env\`**:
311
+ \`\`\`bash
312
+ cp .env.example .env
313
+ \`\`\`
314
+
315
+ 2. **Update \`.env\` with your backend URL**:
316
+ \`\`\`env
317
+ API_BASE_URL=https://your-backend-api.com/api/v3
318
+ API_SECRET=your-secret-token
319
+ \`\`\`
320
+
321
+ 3. **Update \`nuxt.config.ts\`** (see \`nuxt.config.example.ts\`):
322
+ \`\`\`typescript
323
+ export default defineNuxtConfig({
324
+ runtimeConfig: {
325
+ // All these variables are server-only
326
+ apiSecret: process.env.API_SECRET || '',
327
+ apiBaseUrl: process.env.API_BASE_URL || ''
328
+ }
329
+ })
330
+ \`\`\`
331
+
332
+ ## 🚀 Usage
333
+
334
+ All routes are available at \`/api/*\`:
335
+
336
+ \`\`\`typescript
337
+ // In your Vue components
338
+ const { data: pet } = await useFetch('/api/pet/123')
339
+
340
+ // With query params
341
+ const { data: pets } = await useFetch('/api/pet', {
342
+ query: { status: 'available' }
343
+ })
344
+
345
+ // POST request
346
+ const { data: newPet } = await useFetch('/api/pet', {
347
+ method: 'POST',
348
+ body: { name: 'Fluffy', status: 'available' }
349
+ })
350
+ \`\`\`
351
+
352
+ ## 📁 Generated Routes
353
+
354
+ - **${serverPath}/** - All server routes
355
+ - Each file corresponds to an OpenAPI endpoint
356
+ - Path params use dynamic routes: \`[id]\`, \`[username]\`
357
+ - HTTP methods: \`.get.ts\`, \`.post.ts\`, \`.put.ts\`, \`.delete.ts\`
358
+
359
+ ## 🔒 Security
360
+
361
+ - \`API_SECRET\` is only available server-side (never exposed to client)
362
+ - All requests go through your Nuxt server (CORS handled automatically)
363
+ - You can add custom authentication/validation logic in each route
364
+
365
+ ## 🛠️ Customization
366
+
367
+ Each generated route can be customized:
368
+
369
+ \`\`\`typescript
370
+ // server/api/pet/[id].get.ts
371
+ export default defineEventHandler(async (event) => {
372
+ const petId = getRouterParam(event, 'id')
373
+
374
+ // Add custom logic here
375
+ // - Rate limiting
376
+ // - Caching
377
+ // - Request validation
378
+ // - Response transformation
379
+
380
+ const config = useRuntimeConfig()
381
+ const data = await \$fetch(\`\${config.apiBaseUrl}/pet/\${petId}\`)
382
+
383
+ return data
384
+ })
385
+ \`\`\`
386
+ `;
387
+ }
388
+ /**
389
+ * Generate index file that exports all route paths (optional, for documentation)
390
+ */
391
+ export function generateRoutesIndexFile(methods) {
392
+ const header = generateFileHeader();
393
+ const routes = methods.map((method) => {
394
+ const filePath = generateRouteFilePath(method);
395
+ const routePath = '/' +
396
+ filePath
397
+ .replace(/\\/g, '/')
398
+ .replace(/index\.(get|post|put|delete|patch)\.ts$/, '')
399
+ .replace(/\.(get|post|put|delete|patch)\.ts$/, '')
400
+ .replace(/\[(\w+)\]/g, ':$1');
401
+ return ` // ${method.httpMethod} ${routePath} -> ${filePath}`;
402
+ });
403
+ return `${header}/**
404
+ * Generated Server Routes
405
+ *
406
+ * Available routes:
407
+ ${routes.join('\n')}
408
+ */
409
+
410
+ export default {}
411
+ `;
412
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Re-export all types from shared types
3
+ * Nuxt Server uses the same MethodInfo structure
4
+ */
5
+ export * from '../shared/types.js';
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Re-export all types from shared types
3
+ * Nuxt Server uses the same MethodInfo structure
4
+ */
5
+ export * from '../shared/types.js';