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,483 @@
1
+ import type { MethodInfo } from './types.js';
2
+ import { camelCase, pascalCase } from 'change-case';
3
+
4
+ /**
5
+ * Generate file header with auto-generation warning
6
+ */
7
+ function generateFileHeader(): string {
8
+ return `/**
9
+ * ⚠️ AUTO-GENERATED FILE - DO NOT EDIT MANUALLY
10
+ *
11
+ * This file was automatically generated by nuxt-openapi-generator.
12
+ * Any manual changes will be overwritten on the next generation.
13
+ *
14
+ * @generated by nuxt-openapi-generator
15
+ * @see https://github.com/dmartindiaz/nuxt-openapi-hyperfetch
16
+ */
17
+
18
+ /* eslint-disable */
19
+ // @ts-nocheck
20
+ `;
21
+ }
22
+
23
+ /**
24
+ * Generate a complete server route file
25
+ */
26
+ export function generateServerRouteFile(
27
+ method: MethodInfo,
28
+ apiImportPath: string,
29
+ options?: {
30
+ enableBff?: boolean;
31
+ resource?: string;
32
+ }
33
+ ): string {
34
+ const header = generateFileHeader();
35
+ const imports = generateImports(method, apiImportPath);
36
+ const handlerBody = generateHandlerBody(method, options);
37
+
38
+ return `${header}${imports}\n\n${handlerBody}\n`;
39
+ }
40
+
41
+ /**
42
+ * Calculate the file path for a server route
43
+ * Examples:
44
+ * /pet + GET -> pet/index.get.ts
45
+ * /pet + POST -> pet/index.post.ts
46
+ * /pet/{petId} + GET -> pet/[id].get.ts
47
+ * /pet/{petId}/uploadImage + POST -> pet/[id]/uploadImage.post.ts
48
+ * /store/inventory + GET -> store/inventory.get.ts
49
+ * /user/{username} + GET -> user/[username].get.ts
50
+ */
51
+ export function generateRouteFilePath(method: MethodInfo): string {
52
+ let routePath = method.path;
53
+
54
+ // Remove leading slash
55
+ if (routePath.startsWith('/')) {
56
+ routePath = routePath.substring(1);
57
+ }
58
+
59
+ // Replace path params: {petId} -> [id], {username} -> [username]
60
+ routePath = routePath.replace(/\{(\w+)\}/g, (match, paramName) => {
61
+ // Simplify common params
62
+ const simplified = paramName.toLowerCase().replace(/id$/, '');
63
+ return `[${simplified || 'id'}]`;
64
+ });
65
+
66
+ // Split path into segments
67
+ const segments = routePath.split('/').filter(Boolean);
68
+
69
+ // If empty or root, use 'index'
70
+ if (segments.length === 0) {
71
+ return `index.${method.httpMethod.toLowerCase()}.ts`;
72
+ }
73
+
74
+ // Check if last segment is a dynamic param [xxx]
75
+ const lastSegment = segments[segments.length - 1];
76
+ const isDynamicParam = lastSegment.startsWith('[') && lastSegment.endsWith(']');
77
+
78
+ // If last segment is dynamic, add index
79
+ if (isDynamicParam) {
80
+ return `${segments.join('/')}/index.${method.httpMethod.toLowerCase()}.ts`;
81
+ }
82
+
83
+ // Otherwise, use the last segment as filename
84
+ const fileName = segments.pop();
85
+ const dir = segments.length > 0 ? segments.join('/') + '/' : '';
86
+
87
+ return `${dir}${fileName}.${method.httpMethod.toLowerCase()}.ts`;
88
+ }
89
+
90
+ /**
91
+ * Extract base type names from a type string (same as use-fetch)
92
+ */
93
+ function extractBaseTypes(type: string): string[] {
94
+ if (!type) {
95
+ return [];
96
+ }
97
+
98
+ // Handle array syntax: Pet[]
99
+ const arrayMatch = type.match(/^(\w+)\[\]$/);
100
+ if (arrayMatch) {
101
+ return [arrayMatch[1]];
102
+ }
103
+
104
+ // Handle Array generic: Array<Pet>
105
+ const arrayGenericMatch = type.match(/^Array<(\w+)>$/);
106
+ if (arrayGenericMatch) {
107
+ return [arrayGenericMatch[1]];
108
+ }
109
+
110
+ // If it's a simple named type (single word, PascalCase), include it
111
+ if (/^[A-Z][a-zA-Z0-9]*$/.test(type)) {
112
+ return [type];
113
+ }
114
+
115
+ return [];
116
+ }
117
+
118
+ /**
119
+ * Generate import statements
120
+ */
121
+ function generateImports(method: MethodInfo, apiImportPath: string): string {
122
+ const h3Imports: string[] = ['defineEventHandler', 'createError'];
123
+
124
+ // Add h3 imports based on method needs
125
+ if (method.pathParams.length > 0) {
126
+ h3Imports.push('getRouterParam');
127
+ }
128
+ if (method.hasQueryParams) {
129
+ h3Imports.push('getQuery');
130
+ }
131
+ if (method.hasBody) {
132
+ h3Imports.push('readBody');
133
+ }
134
+
135
+ let imports = `import { ${h3Imports.join(', ')} } from 'h3'\n`;
136
+
137
+ // Import types
138
+ const typeNames = new Set<string>();
139
+
140
+ // Extract base types from request type
141
+ if (method.requestType) {
142
+ const extracted = extractBaseTypes(method.requestType);
143
+ extracted.forEach((t) => typeNames.add(t));
144
+ }
145
+
146
+ // Extract base types from response type
147
+ if (method.responseType && method.responseType !== 'void') {
148
+ const extracted = extractBaseTypes(method.responseType);
149
+ extracted.forEach((t) => typeNames.add(t));
150
+ }
151
+
152
+ // Import types from API (only if we have named types to import)
153
+ if (typeNames.size > 0) {
154
+ imports += `import type { ${Array.from(typeNames).join(', ')} } from '${apiImportPath}'\n`;
155
+ }
156
+
157
+ return imports;
158
+ }
159
+
160
+ /**
161
+ * Generate the handler body
162
+ */
163
+ function generateHandlerBody(
164
+ method: MethodInfo,
165
+ options?: {
166
+ enableBff?: boolean;
167
+ resource?: string;
168
+ }
169
+ ): string {
170
+ const description = method.description ? `/**\n * ${method.description}\n */\n` : '';
171
+
172
+ const pathParamCapture = generatePathParamCapture(method);
173
+ const queryCapture = generateQueryCapture(method);
174
+ const bodyCapture = generateBodyCapture(method);
175
+ const backendUrl = generateBackendUrl(method);
176
+ const fetchOptions = generateFetchOptions(method);
177
+
178
+ // BFF: Auth context loading
179
+ const authContextCode = options?.enableBff
180
+ ? ` // Try to load auth context (optional)
181
+ let auth = null
182
+ try {
183
+ const { getAuthContext } = await import('~/server/auth/context')
184
+ auth = await getAuthContext(event)
185
+ } catch {
186
+ // Auth not configured - continue without it
187
+ }
188
+
189
+ `
190
+ : '';
191
+
192
+ // BFF: Transformer call
193
+ const transformerCode =
194
+ options?.enableBff && options?.resource
195
+ ? `
196
+ // Try to transform data (optional)
197
+ try {
198
+ const { transform${pascalCase(options.resource)} } = await import('~/server/bff/transformers/${options.resource}')
199
+ return await transform${pascalCase(options.resource)}(data, event, auth)
200
+ } catch {
201
+ // Transformer not found - return raw data
202
+ return data
203
+ }`
204
+ : `
205
+ return data`;
206
+
207
+ return `${description}export default defineEventHandler(async (event): Promise<${method.responseType}> => {
208
+ ${pathParamCapture}${queryCapture}${bodyCapture}${authContextCode}const config = useRuntimeConfig()
209
+ const baseUrl = config.apiBaseUrl
210
+
211
+ try {
212
+ const data = await $fetch<${method.responseType}>(${backendUrl}, {
213
+ ${fetchOptions}
214
+ })${transformerCode}
215
+ } catch (error: any) {
216
+ throw createError({
217
+ statusCode: error.statusCode || 500,
218
+ statusMessage: error.message || 'Request failed'
219
+ })
220
+ }
221
+ })`;
222
+ }
223
+
224
+ /**
225
+ * Generate path param capture code
226
+ */
227
+ function generatePathParamCapture(method: MethodInfo): string {
228
+ if (method.pathParams.length === 0) {
229
+ return '';
230
+ }
231
+
232
+ const captures = method.pathParams.map((param) => {
233
+ const paramName = camelCase(param);
234
+ const paramKey = param.toLowerCase().replace(/id$/, '') || 'id';
235
+
236
+ return `const ${paramName} = getRouterParam(event, '${paramKey}')
237
+ if (!${paramName}) {
238
+ throw createError({
239
+ statusCode: 400,
240
+ statusMessage: '${param} is required'
241
+ })
242
+ }
243
+ `;
244
+ });
245
+
246
+ return captures.join('') + '\n ';
247
+ }
248
+
249
+ /**
250
+ * Generate query param capture code
251
+ */
252
+ function generateQueryCapture(method: MethodInfo): string {
253
+ if (!method.hasQueryParams) {
254
+ return '';
255
+ }
256
+ return 'const query = getQuery(event)\n ';
257
+ }
258
+
259
+ /**
260
+ * Generate body capture code
261
+ */
262
+ function generateBodyCapture(method: MethodInfo): string {
263
+ if (!method.hasBody) {
264
+ return '';
265
+ }
266
+
267
+ const typeAnnotation = method.requestType ? `<${method.requestType}>` : '';
268
+ return `const body = await readBody${typeAnnotation}(event)\n `;
269
+ }
270
+
271
+ /**
272
+ * Generate backend URL with path params replaced
273
+ */
274
+ function generateBackendUrl(method: MethodInfo): string {
275
+ let url = method.path;
276
+
277
+ // Replace {param} with ${paramName}
278
+ if (method.pathParams.length > 0) {
279
+ for (const param of method.pathParams) {
280
+ const paramName = camelCase(param);
281
+ url = url.replace(`{${param}}`, `\${${paramName}}`);
282
+ }
283
+ return `\`\${baseUrl}${url}\``;
284
+ }
285
+
286
+ // Static URL
287
+ return `\`\${baseUrl}${url}\``;
288
+ }
289
+
290
+ /**
291
+ * Generate $fetch options object
292
+ */
293
+ function generateFetchOptions(method: MethodInfo): string {
294
+ const options: string[] = [];
295
+
296
+ // Method (if not GET)
297
+ if (method.httpMethod !== 'GET') {
298
+ options.push(`method: '${method.httpMethod}'`);
299
+ }
300
+
301
+ // Query params
302
+ if (method.hasQueryParams) {
303
+ options.push('query: query');
304
+ }
305
+
306
+ // Body
307
+ if (method.hasBody) {
308
+ options.push('body: body');
309
+ }
310
+
311
+ // Headers
312
+ const headerLines: string[] = [];
313
+ if (method.hasBody) {
314
+ headerLines.push(`'Content-Type': 'application/json'`);
315
+ }
316
+ headerLines.push(
317
+ `...(config.apiSecret ? { 'Authorization': \`Bearer \${config.apiSecret}\` } : {})`
318
+ );
319
+
320
+ options.push(`headers: {\n ${headerLines.join(',\n ')}\n }`);
321
+
322
+ return options.join(',\n ');
323
+ }
324
+
325
+ /**
326
+ * Generate nuxt.config.example.ts
327
+ */
328
+ export function generateConfigFile(): string {
329
+ return `/**
330
+ * ⚠️ AUTO-GENERATED EXAMPLE FILE
331
+ *
332
+ * Copy this configuration to your nuxt.config.ts
333
+ * @generated by nuxt-openapi-generator
334
+ */
335
+
336
+ // nuxt.config.ts
337
+ export default defineNuxtConfig({
338
+ runtimeConfig: {
339
+ // Private keys (server-only, never exposed to client)
340
+ apiSecret: process.env.API_SECRET || '',
341
+ apiBaseUrl: process.env.API_BASE_URL || 'https://petstore3.swagger.io/api/v3'
342
+ }
343
+ })
344
+ `;
345
+ }
346
+
347
+ /**
348
+ * Generate .env.example
349
+ */
350
+ export function generateEnvFile(): string {
351
+ return `# ⚠️ AUTO-GENERATED EXAMPLE FILE
352
+ # Copy this file to .env and configure your values
353
+ # @generated by nuxt-openapi-generator
354
+
355
+ # Backend API Configuration
356
+ API_BASE_URL=https://petstore3.swagger.io/api/v3
357
+ API_SECRET=your-api-secret-token-here
358
+ `;
359
+ }
360
+
361
+ /**
362
+ * Generate README.md
363
+ */
364
+ export function generateReadme(serverPath: string): string {
365
+ return `<!--
366
+ ⚠️ AUTO-GENERATED DOCUMENTATION
367
+ This file was automatically generated by nuxt-openapi-generator.
368
+ @generated by nuxt-openapi-generator
369
+ -->
370
+
371
+ # Nuxt Server Routes
372
+
373
+ Auto-generated server routes that proxy requests to your backend API.
374
+
375
+ ## 🔧 Configuration
376
+
377
+ 1. **Copy \`.env.example\` to \`.env\`**:
378
+ \`\`\`bash
379
+ cp .env.example .env
380
+ \`\`\`
381
+
382
+ 2. **Update \`.env\` with your backend URL**:
383
+ \`\`\`env
384
+ API_BASE_URL=https://your-backend-api.com/api/v3
385
+ API_SECRET=your-secret-token
386
+ \`\`\`
387
+
388
+ 3. **Update \`nuxt.config.ts\`** (see \`nuxt.config.example.ts\`):
389
+ \`\`\`typescript
390
+ export default defineNuxtConfig({
391
+ runtimeConfig: {
392
+ // All these variables are server-only
393
+ apiSecret: process.env.API_SECRET || '',
394
+ apiBaseUrl: process.env.API_BASE_URL || ''
395
+ }
396
+ })
397
+ \`\`\`
398
+
399
+ ## 🚀 Usage
400
+
401
+ All routes are available at \`/api/*\`:
402
+
403
+ \`\`\`typescript
404
+ // In your Vue components
405
+ const { data: pet } = await useFetch('/api/pet/123')
406
+
407
+ // With query params
408
+ const { data: pets } = await useFetch('/api/pet', {
409
+ query: { status: 'available' }
410
+ })
411
+
412
+ // POST request
413
+ const { data: newPet } = await useFetch('/api/pet', {
414
+ method: 'POST',
415
+ body: { name: 'Fluffy', status: 'available' }
416
+ })
417
+ \`\`\`
418
+
419
+ ## 📁 Generated Routes
420
+
421
+ - **${serverPath}/** - All server routes
422
+ - Each file corresponds to an OpenAPI endpoint
423
+ - Path params use dynamic routes: \`[id]\`, \`[username]\`
424
+ - HTTP methods: \`.get.ts\`, \`.post.ts\`, \`.put.ts\`, \`.delete.ts\`
425
+
426
+ ## 🔒 Security
427
+
428
+ - \`API_SECRET\` is only available server-side (never exposed to client)
429
+ - All requests go through your Nuxt server (CORS handled automatically)
430
+ - You can add custom authentication/validation logic in each route
431
+
432
+ ## 🛠️ Customization
433
+
434
+ Each generated route can be customized:
435
+
436
+ \`\`\`typescript
437
+ // server/api/pet/[id].get.ts
438
+ export default defineEventHandler(async (event) => {
439
+ const petId = getRouterParam(event, 'id')
440
+
441
+ // Add custom logic here
442
+ // - Rate limiting
443
+ // - Caching
444
+ // - Request validation
445
+ // - Response transformation
446
+
447
+ const config = useRuntimeConfig()
448
+ const data = await \$fetch(\`\${config.apiBaseUrl}/pet/\${petId}\`)
449
+
450
+ return data
451
+ })
452
+ \`\`\`
453
+ `;
454
+ }
455
+
456
+ /**
457
+ * Generate index file that exports all route paths (optional, for documentation)
458
+ */
459
+ export function generateRoutesIndexFile(methods: MethodInfo[]): string {
460
+ const header = generateFileHeader();
461
+ const routes = methods.map((method) => {
462
+ const filePath = generateRouteFilePath(method);
463
+ const routePath =
464
+ '/' +
465
+ filePath
466
+ .replace(/\\/g, '/')
467
+ .replace(/index\.(get|post|put|delete|patch)\.ts$/, '')
468
+ .replace(/\.(get|post|put|delete|patch)\.ts$/, '')
469
+ .replace(/\[(\w+)\]/g, ':$1');
470
+
471
+ return ` // ${method.httpMethod} ${routePath} -> ${filePath}`;
472
+ });
473
+
474
+ return `${header}/**
475
+ * Generated Server Routes
476
+ *
477
+ * Available routes:
478
+ ${routes.join('\n')}
479
+ */
480
+
481
+ export default {}
482
+ `;
483
+ }
@@ -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';