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.
- package/.editorconfig +26 -0
- package/.prettierignore +17 -0
- package/.prettierrc.json +12 -0
- package/CONTRIBUTING.md +292 -0
- package/INSTRUCTIONS.md +327 -0
- package/LICENSE +202 -0
- package/README.md +202 -0
- package/dist/cli/config.d.ts +57 -0
- package/dist/cli/config.js +85 -0
- package/dist/cli/logger.d.ts +44 -0
- package/dist/cli/logger.js +58 -0
- package/dist/cli/logo.d.ts +6 -0
- package/dist/cli/logo.js +21 -0
- package/dist/cli/messages.d.ts +65 -0
- package/dist/cli/messages.js +86 -0
- package/dist/cli/prompts.d.ts +30 -0
- package/dist/cli/prompts.js +118 -0
- package/dist/cli/types.d.ts +43 -0
- package/dist/cli/types.js +4 -0
- package/dist/cli/utils.d.ts +26 -0
- package/dist/cli/utils.js +45 -0
- package/dist/generate.d.ts +6 -0
- package/dist/generate.js +48 -0
- package/dist/generators/nuxt-server/bff-templates.d.ts +25 -0
- package/dist/generators/nuxt-server/bff-templates.js +737 -0
- package/dist/generators/nuxt-server/generator.d.ts +7 -0
- package/dist/generators/nuxt-server/generator.js +206 -0
- package/dist/generators/nuxt-server/parser.d.ts +5 -0
- package/dist/generators/nuxt-server/parser.js +5 -0
- package/dist/generators/nuxt-server/templates.d.ts +35 -0
- package/dist/generators/nuxt-server/templates.js +412 -0
- package/dist/generators/nuxt-server/types.d.ts +5 -0
- package/dist/generators/nuxt-server/types.js +5 -0
- package/dist/generators/shared/parsers/heyapi-parser.d.ts +11 -0
- package/dist/generators/shared/parsers/heyapi-parser.js +248 -0
- package/dist/generators/shared/parsers/official-parser.d.ts +5 -0
- package/dist/generators/shared/parsers/official-parser.js +5 -0
- package/dist/generators/shared/runtime/apiHelpers.d.ts +183 -0
- package/dist/generators/shared/runtime/apiHelpers.js +268 -0
- package/dist/generators/shared/templates/api-callbacks-plugin.d.ts +178 -0
- package/dist/generators/shared/templates/api-callbacks-plugin.js +338 -0
- package/dist/generators/shared/types.d.ts +25 -0
- package/dist/generators/shared/types.js +4 -0
- package/dist/generators/tanstack-query/generator.d.ts +5 -0
- package/dist/generators/tanstack-query/generator.js +11 -0
- package/dist/generators/use-async-data/generator.d.ts +5 -0
- package/dist/generators/use-async-data/generator.js +156 -0
- package/dist/generators/use-async-data/parser.d.ts +5 -0
- package/dist/generators/use-async-data/parser.js +5 -0
- package/dist/generators/use-async-data/runtime/useApiAsyncData.d.ts +38 -0
- package/dist/generators/use-async-data/runtime/useApiAsyncData.js +122 -0
- package/dist/generators/use-async-data/runtime/useApiAsyncDataRaw.d.ts +54 -0
- package/dist/generators/use-async-data/runtime/useApiAsyncDataRaw.js +126 -0
- package/dist/generators/use-async-data/templates.d.ts +20 -0
- package/dist/generators/use-async-data/templates.js +191 -0
- package/dist/generators/use-async-data/types.d.ts +4 -0
- package/dist/generators/use-async-data/types.js +4 -0
- package/dist/generators/use-fetch/generator.d.ts +5 -0
- package/dist/generators/use-fetch/generator.js +131 -0
- package/dist/generators/use-fetch/parser.d.ts +9 -0
- package/dist/generators/use-fetch/parser.js +282 -0
- package/dist/generators/use-fetch/runtime/useApiRequest.d.ts +46 -0
- package/dist/generators/use-fetch/runtime/useApiRequest.js +158 -0
- package/dist/generators/use-fetch/templates.d.ts +16 -0
- package/dist/generators/use-fetch/templates.js +169 -0
- package/dist/generators/use-fetch/types.d.ts +5 -0
- package/dist/generators/use-fetch/types.js +5 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +213 -0
- package/docs/API-REFERENCE.md +887 -0
- package/docs/ARCHITECTURE.md +649 -0
- package/docs/DEVELOPMENT.md +918 -0
- package/docs/QUICK-START.md +323 -0
- package/docs/README.md +155 -0
- package/docs/TROUBLESHOOTING.md +881 -0
- package/eslint.config.js +72 -0
- package/package.json +65 -0
- package/src/cli/config.ts +140 -0
- package/src/cli/logger.ts +66 -0
- package/src/cli/logo.ts +25 -0
- package/src/cli/messages.ts +97 -0
- package/src/cli/prompts.ts +143 -0
- package/src/cli/types.ts +50 -0
- package/src/cli/utils.ts +49 -0
- package/src/generate.ts +57 -0
- package/src/generators/nuxt-server/bff-templates.ts +754 -0
- package/src/generators/nuxt-server/generator.ts +270 -0
- package/src/generators/nuxt-server/parser.ts +5 -0
- package/src/generators/nuxt-server/templates.ts +483 -0
- package/src/generators/nuxt-server/types.ts +5 -0
- package/src/generators/shared/parsers/heyapi-parser.ts +307 -0
- package/src/generators/shared/parsers/official-parser.ts +5 -0
- package/src/generators/shared/runtime/apiHelpers.ts +466 -0
- package/src/generators/shared/templates/api-callbacks-plugin.ts +352 -0
- package/src/generators/shared/types.ts +27 -0
- package/src/generators/tanstack-query/generator.ts +11 -0
- package/src/generators/use-async-data/generator.ts +204 -0
- package/src/generators/use-async-data/parser.ts +5 -0
- package/src/generators/use-async-data/runtime/useApiAsyncData.ts +220 -0
- package/src/generators/use-async-data/runtime/useApiAsyncDataRaw.ts +236 -0
- package/src/generators/use-async-data/templates.ts +250 -0
- package/src/generators/use-async-data/types.ts +4 -0
- package/src/generators/use-fetch/generator.ts +169 -0
- package/src/generators/use-fetch/parser.ts +341 -0
- package/src/generators/use-fetch/runtime/useApiRequest.ts +223 -0
- package/src/generators/use-fetch/templates.ts +214 -0
- package/src/generators/use-fetch/types.ts +5 -0
- package/src/index.ts +265 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,918 @@
|
|
|
1
|
+
# 🔧 Development Guide - Contributing & Extending
|
|
2
|
+
|
|
3
|
+
> **Purpose**: Practical guide for developers who want to contribute, extend, or customize the generator.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Getting Started](#getting-started)
|
|
8
|
+
- [Development Workflow](#development-workflow)
|
|
9
|
+
- [Adding Features](#adding-features)
|
|
10
|
+
- [Testing Changes](#testing-changes)
|
|
11
|
+
- [Code Style](#code-style)
|
|
12
|
+
- [Common Tasks](#common-tasks)
|
|
13
|
+
- [Extension Points](#extension-points)
|
|
14
|
+
- [Debugging Guide](#debugging-guide)
|
|
15
|
+
|
|
16
|
+
## Getting Started
|
|
17
|
+
|
|
18
|
+
### Prerequisites
|
|
19
|
+
|
|
20
|
+
- **Node.js**: 18.x or higher
|
|
21
|
+
- **npm**: 9.x or higher
|
|
22
|
+
- **TypeScript**: Basic knowledge
|
|
23
|
+
- **Git**: For version control
|
|
24
|
+
|
|
25
|
+
### Initial Setup
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# Clone repository
|
|
29
|
+
git clone https://github.com/dmartindiaz/nuxt-openapi-hyperfetch.git
|
|
30
|
+
cd nuxt-openapi-hyperfetch
|
|
31
|
+
|
|
32
|
+
# Install dependencies
|
|
33
|
+
npm install
|
|
34
|
+
|
|
35
|
+
# Build project
|
|
36
|
+
npm run build
|
|
37
|
+
|
|
38
|
+
# Test generation
|
|
39
|
+
npm run generator
|
|
40
|
+
|
|
41
|
+
# Or with parameters
|
|
42
|
+
echo "useFetch" | npm run generator
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Project Scripts
|
|
46
|
+
|
|
47
|
+
| Command | Purpose |
|
|
48
|
+
| ---------------------- | -------------------------------------- |
|
|
49
|
+
| `npm run build` | Compile TypeScript to JavaScript |
|
|
50
|
+
| `npm run generator` | Test generation with example swagger |
|
|
51
|
+
| `npm run lint` | Run ESLint checks |
|
|
52
|
+
| `npm run lint:fix` | Auto-fix ESLint issues |
|
|
53
|
+
| `npm run format` | Format with Prettier |
|
|
54
|
+
| `npm run format:check` | Check formatting |
|
|
55
|
+
| `npm run type-check` | TypeScript type checking |
|
|
56
|
+
| `npm run validate` | Run all checks (lint + format + types) |
|
|
57
|
+
|
|
58
|
+
### Development Tools
|
|
59
|
+
|
|
60
|
+
- **VS Code**: Recommended IDE with extensions:
|
|
61
|
+
- ESLint
|
|
62
|
+
- Prettier
|
|
63
|
+
- TypeScript
|
|
64
|
+
- EditorConfig
|
|
65
|
+
|
|
66
|
+
- **Terminal**: PowerShell (Windows) or Bash (Unix)
|
|
67
|
+
|
|
68
|
+
## Development Workflow
|
|
69
|
+
|
|
70
|
+
### 1. Make Changes
|
|
71
|
+
|
|
72
|
+
Edit files in `src/` directory:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
src/
|
|
76
|
+
index.ts # CLI entry point
|
|
77
|
+
generate.ts # OpenAPI wrapper
|
|
78
|
+
generators/
|
|
79
|
+
shared/ # Common code
|
|
80
|
+
use-fetch/ # useFetch generator
|
|
81
|
+
use-async-data/ # useAsyncData generator
|
|
82
|
+
nuxt-server/ # Server routes generator
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 2. Build
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
npm run build
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Output**: `dist/` directory with compiled JavaScript
|
|
92
|
+
|
|
93
|
+
### 3. Test Locally
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# Interactive mode
|
|
97
|
+
node dist/index.js generate
|
|
98
|
+
|
|
99
|
+
# With arguments
|
|
100
|
+
node dist/index.js generate -i swagger.yaml -o ./test-output
|
|
101
|
+
|
|
102
|
+
# Piped input for composable type
|
|
103
|
+
echo "useFetch" | node dist/index.js generate -i swagger.yaml -o ./test-output
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 4. Validate Code Quality
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# Run all checks
|
|
110
|
+
npm run validate
|
|
111
|
+
|
|
112
|
+
# Or individually
|
|
113
|
+
npm run lint
|
|
114
|
+
npm run format:check
|
|
115
|
+
npm run type-check
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 5. Commit
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
git add .
|
|
122
|
+
git commit -m "feat: add support for X"
|
|
123
|
+
git push
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Commit Convention**: Use [Conventional Commits](https://www.conventionalcommits.org/)
|
|
127
|
+
|
|
128
|
+
- `feat:` - New feature
|
|
129
|
+
- `fix:` - Bug fix
|
|
130
|
+
- `docs:` - Documentation only
|
|
131
|
+
- `style:` - Code style changes
|
|
132
|
+
- `refactor:` - Code refactoring
|
|
133
|
+
- `test:` - Adding tests
|
|
134
|
+
- `chore:` - Build/tooling changes
|
|
135
|
+
|
|
136
|
+
## Adding Features
|
|
137
|
+
|
|
138
|
+
### Add a New Callback Type
|
|
139
|
+
|
|
140
|
+
**Example**: Add `onRetry` callback
|
|
141
|
+
|
|
142
|
+
**1. Update Interfaces** (`src/generators/shared/runtime/apiHelpers.ts`)
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
export interface ApiRequestOptions<T> {
|
|
146
|
+
// ... existing
|
|
147
|
+
onRetry?: (attempt: number, error: any) => void | false;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export interface GlobalCallbacksConfig {
|
|
151
|
+
// ... existing
|
|
152
|
+
onRetry?: (attempt: number, error: any) => void | false;
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**2. Update mergeCallbacks()** (`src/generators/shared/runtime/apiHelpers.ts`)
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
export function mergeCallbacks(/* ... */) {
|
|
160
|
+
// ... existing
|
|
161
|
+
|
|
162
|
+
const merged: any = {
|
|
163
|
+
// ... existing callbacks
|
|
164
|
+
|
|
165
|
+
onRetry: (attempt: number, error: any) => {
|
|
166
|
+
if (global.onRetry && shouldApplyGlobalCallback('onRetry', skipConfig, url, patterns)) {
|
|
167
|
+
const result = global.onRetry(attempt, error);
|
|
168
|
+
if (result === false) return; // Cancel local
|
|
169
|
+
}
|
|
170
|
+
if (local.onRetry) {
|
|
171
|
+
local.onRetry(attempt, error);
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
return merged;
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**3. Update Runtime Wrappers**
|
|
181
|
+
|
|
182
|
+
`src/generators/use-fetch/runtime/useApiRequest.ts`:
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
export function useApiRequest<T>(url: string, options?: ApiRequestOptions<T>) {
|
|
186
|
+
// ... existing code
|
|
187
|
+
|
|
188
|
+
// Add retry logic
|
|
189
|
+
const executeWithRetry = async (fn: () => Promise<T>, maxRetries = 3) => {
|
|
190
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
191
|
+
try {
|
|
192
|
+
return await fn();
|
|
193
|
+
} catch (error) {
|
|
194
|
+
if (mergedCallbacks.onRetry) {
|
|
195
|
+
mergedCallbacks.onRetry(attempt, error);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (attempt === maxRetries) throw error;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
// ... use executeWithRetry
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**4. Update Plugin Template** (`src/generators/shared/templates/api-callbacks-plugin.ts`)
|
|
208
|
+
|
|
209
|
+
Add documentation and examples:
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
/**
|
|
213
|
+
* onRetry: Called when a request fails and will be retried
|
|
214
|
+
* Parameters: (attempt: number, error: any)
|
|
215
|
+
* Return: void | false (return false to cancel local onRetry)
|
|
216
|
+
*
|
|
217
|
+
* @example
|
|
218
|
+
* onRetry: (attempt, error) => {
|
|
219
|
+
* console.log(`Retry attempt ${attempt}`);
|
|
220
|
+
* if (error.status === 429) {
|
|
221
|
+
* // Wait before retry
|
|
222
|
+
* return new Promise(resolve => setTimeout(resolve, 1000));
|
|
223
|
+
* }
|
|
224
|
+
* }
|
|
225
|
+
*/
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**5. Update Documentation** (`docs/API-REFERENCE.md`)
|
|
229
|
+
|
|
230
|
+
Add to callback interfaces section and usage examples.
|
|
231
|
+
|
|
232
|
+
**6. Test**
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
npm run build
|
|
236
|
+
node dist/index.js generate -i swagger.yaml -o ./test-output
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Verify callback is called correctly.
|
|
240
|
+
|
|
241
|
+
### Add a New Generator Type
|
|
242
|
+
|
|
243
|
+
**Example**: Add TanStack Query generator
|
|
244
|
+
|
|
245
|
+
**1. Create Generator Directory**
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
mkdir src/generators/tanstack-query
|
|
249
|
+
cd src/generators/tanstack-query
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
**2. Create Files**
|
|
253
|
+
|
|
254
|
+
```
|
|
255
|
+
tanstack-query/
|
|
256
|
+
types.ts # Re-export from shared/types.ts
|
|
257
|
+
parser.ts # Re-export from use-fetch/parser.ts
|
|
258
|
+
templates.ts # Generate TanStack Query code
|
|
259
|
+
generator.ts # Main orchestration
|
|
260
|
+
runtime/
|
|
261
|
+
useApiQuery.ts # TanStack Query wrapper
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
**3. Implement Parser** (`parser.ts`)
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
// Re-export shared parser
|
|
268
|
+
export * from '../shared/types';
|
|
269
|
+
export { parseApiFile, getApiFiles } from '../use-fetch/parser';
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
**4. Implement Templates** (`templates.ts`)
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
import type { MethodInfo } from './types';
|
|
276
|
+
|
|
277
|
+
export function generateFileHeader(): string {
|
|
278
|
+
return `// @ts-nocheck - TanStack Query composable\n`;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export function generateImports(method: MethodInfo, apiImportPath: string): string {
|
|
282
|
+
return `import { useQuery } from '@tanstack/vue-query';
|
|
283
|
+
import type { ${method.requestType}, ${method.responseType} } from '${apiImportPath}';
|
|
284
|
+
`;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export function generateFunctionBody(method: MethodInfo): string {
|
|
288
|
+
const url =
|
|
289
|
+
method.pathParams.length === 0
|
|
290
|
+
? `'${method.path}'`
|
|
291
|
+
: `\`${method.path.replace(/{(\w+)}/g, '${params.$1}')}\``;
|
|
292
|
+
|
|
293
|
+
return `export const useQuery${method.name} = (params: ${method.requestType}) => {
|
|
294
|
+
return useQuery({
|
|
295
|
+
queryKey: ['${method.name}', params],
|
|
296
|
+
queryFn: async () => {
|
|
297
|
+
const response = await $fetch<${method.responseType}>(${url}, {
|
|
298
|
+
method: '${method.method}',
|
|
299
|
+
${method.bodyField ? `body: params.${method.bodyField},` : ''}
|
|
300
|
+
});
|
|
301
|
+
return response;
|
|
302
|
+
},
|
|
303
|
+
});
|
|
304
|
+
};`;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export function generateComposableFile(method: MethodInfo, apiImportPath: string): string {
|
|
308
|
+
const header = generateFileHeader();
|
|
309
|
+
const imports = generateImports(method, apiImportPath);
|
|
310
|
+
const body = generateFunctionBody(method);
|
|
311
|
+
|
|
312
|
+
return `${header}${imports}\n${body}\n`;
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
**5. Implement Generator** (`generator.ts`)
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
import * as fs from 'fs-extra';
|
|
320
|
+
import * as path from 'path';
|
|
321
|
+
import * as p from '@clack/prompts';
|
|
322
|
+
import prettier from 'prettier';
|
|
323
|
+
import { parseApiFile, scanApiFiles, type MethodInfo } from './parser';
|
|
324
|
+
import { generateComposableFile } from './templates';
|
|
325
|
+
|
|
326
|
+
export async function generateTanstackQueryComposables(
|
|
327
|
+
inputDir: string,
|
|
328
|
+
outputDir: string
|
|
329
|
+
): Promise<void> {
|
|
330
|
+
p.log.step('📦 Starting TanStack Query composables generation...');
|
|
331
|
+
|
|
332
|
+
// 1. Scan and parse
|
|
333
|
+
const apiFiles = getApiFiles(inputDir);
|
|
334
|
+
|
|
335
|
+
const allMethods: MethodInfo[] = [];
|
|
336
|
+
for (const apiFile of apiFiles) {
|
|
337
|
+
const apiClass = parseApiFile(apiFile);
|
|
338
|
+
allMethods.push(...apiClass.methods);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// 2. Create directories
|
|
342
|
+
const composablesDir = path.join(outputDir, 'composables', 'tanstack-query');
|
|
343
|
+
await fs.ensureDir(composablesDir);
|
|
344
|
+
|
|
345
|
+
// 3. Generate files
|
|
346
|
+
for (const method of allMethods) {
|
|
347
|
+
const content = generateComposableFile(method, '../apis');
|
|
348
|
+
const formatted = await prettier.format(content, { parser: 'typescript' });
|
|
349
|
+
await fs.writeFile(path.join(composablesDir, method.fileName), formatted);
|
|
350
|
+
p.log.success(` ✓ useQuery${method.name}`);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
p.log.success(`✅ Generated ${allMethods.length} TanStack Query composables`);
|
|
354
|
+
}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
**6. Register in CLI** (`src/index.ts`)
|
|
358
|
+
|
|
359
|
+
```typescript
|
|
360
|
+
const composableChoice = await p.multiselect({
|
|
361
|
+
message: 'Select composables to generate:',
|
|
362
|
+
options: [
|
|
363
|
+
{ value: 'useFetch', label: 'useFetch (Nuxt 3 useFetch)' },
|
|
364
|
+
{ value: 'useAsyncData', label: 'useAsyncData (Nuxt 3 useAsyncData)' },
|
|
365
|
+
{ value: 'tanstack-query', label: '@tanstack/vue-query (TanStack Query)' }, // NEW
|
|
366
|
+
{ value: 'nuxt-server', label: 'nuxt-server (Nuxt Server Routes)' },
|
|
367
|
+
],
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
// ... later
|
|
371
|
+
if (selectedComposables.includes('tanstack-query')) {
|
|
372
|
+
const { generateTanstackQueryComposables } =
|
|
373
|
+
await import('./generators/tanstack-query/generator');
|
|
374
|
+
await generateTanstackQueryComposables(input, output);
|
|
375
|
+
}
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
**7. Test**
|
|
379
|
+
|
|
380
|
+
```bash
|
|
381
|
+
npm run build
|
|
382
|
+
node dist/index.js generate -i swagger.yaml -o ./test-output
|
|
383
|
+
# Select TanStack Query option
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
### Add Parser Feature
|
|
387
|
+
|
|
388
|
+
**Example**: Extract response headers from OpenAPI
|
|
389
|
+
|
|
390
|
+
**1. Update MethodInfo** (`src/generators/shared/types.ts`)
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
export interface MethodInfo {
|
|
394
|
+
// ... existing
|
|
395
|
+
responseHeaders?: string[]; // NEW: Expected response headers
|
|
396
|
+
}
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
**2. Update Parser** (`src/generators/use-fetch/parser.ts`)
|
|
400
|
+
|
|
401
|
+
```typescript
|
|
402
|
+
export function extractMethodInfo(
|
|
403
|
+
method: MethodDeclaration,
|
|
404
|
+
sourceFile: SourceFile
|
|
405
|
+
): MethodInfo | null {
|
|
406
|
+
// ... existing extraction logic
|
|
407
|
+
|
|
408
|
+
// Extract response headers from JSDoc or OpenAPI comments
|
|
409
|
+
const jsdocTags = method.getJsDocs();
|
|
410
|
+
const responseHeaders: string[] = [];
|
|
411
|
+
|
|
412
|
+
for (const jsdoc of jsdocTags) {
|
|
413
|
+
const tags = jsdoc.getTags();
|
|
414
|
+
for (const tag of tags) {
|
|
415
|
+
if (tag.getTagName() === 'responseHeader') {
|
|
416
|
+
const headerName = tag.getCommentText();
|
|
417
|
+
if (headerName) responseHeaders.push(headerName);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return {
|
|
423
|
+
// ... existing fields
|
|
424
|
+
responseHeaders: responseHeaders.length > 0 ? responseHeaders : undefined,
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
**3. Use in Templates** (`src/generators/use-fetch/templates.ts`)
|
|
430
|
+
|
|
431
|
+
```typescript
|
|
432
|
+
export function generateFunctionBody(method: MethodInfo): string {
|
|
433
|
+
const headerChecks = method.responseHeaders
|
|
434
|
+
? method.responseHeaders.map((h) => ` console.log('${h}:', headers.get('${h}'));`).join('\n')
|
|
435
|
+
: '';
|
|
436
|
+
|
|
437
|
+
return `export const ${method.composableName} = (...) => {
|
|
438
|
+
const result = useApiRequest(...);
|
|
439
|
+
|
|
440
|
+
${
|
|
441
|
+
headerChecks
|
|
442
|
+
? `watch(result.data, (data) => {
|
|
443
|
+
if (data) {
|
|
444
|
+
${headerChecks}
|
|
445
|
+
}
|
|
446
|
+
});`
|
|
447
|
+
: ''
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
return result;
|
|
451
|
+
};`;
|
|
452
|
+
}
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
**4. Test**
|
|
456
|
+
|
|
457
|
+
```bash
|
|
458
|
+
npm run build
|
|
459
|
+
node dist/index.js generate -i swagger.yaml -o ./test-output
|
|
460
|
+
# Check generated composables for header logging
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
## Testing Changes
|
|
464
|
+
|
|
465
|
+
### Manual Testing
|
|
466
|
+
|
|
467
|
+
**1. Build**
|
|
468
|
+
|
|
469
|
+
```bash
|
|
470
|
+
npm run build
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
**2. Generate with Test Swagger**
|
|
474
|
+
|
|
475
|
+
```bash
|
|
476
|
+
# Use included swagger.yaml
|
|
477
|
+
node dist/index.js generate -i swagger.yaml -o ./test-output
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
**3. Check Output**
|
|
481
|
+
|
|
482
|
+
```bash
|
|
483
|
+
# List generated files
|
|
484
|
+
ls test-output/composables/use-fetch/
|
|
485
|
+
|
|
486
|
+
# Read a generated file
|
|
487
|
+
cat test-output/composables/use-fetch/use-fetch-add-pet.ts
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
**4. Test in Nuxt Project**
|
|
491
|
+
|
|
492
|
+
```bash
|
|
493
|
+
# Copy to a Nuxt project
|
|
494
|
+
cp -r test-output/composables ~/my-nuxt-app/composables/
|
|
495
|
+
|
|
496
|
+
# In Nuxt project
|
|
497
|
+
cd ~/my-nuxt-app
|
|
498
|
+
npm run dev
|
|
499
|
+
|
|
500
|
+
# Try using composable in a page
|
|
501
|
+
# pages/test.vue
|
|
502
|
+
<script setup>
|
|
503
|
+
const { data } = useFetchAddPet({ pet: { name: 'Fluffy' } });
|
|
504
|
+
</script>
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
### Testing Checklist
|
|
508
|
+
|
|
509
|
+
- [ ] `npm run build` succeeds
|
|
510
|
+
- [ ] `npm run validate` passes
|
|
511
|
+
- [ ] Generation completes without errors
|
|
512
|
+
- [ ] All expected files are created
|
|
513
|
+
- [ ] Generated code has correct imports
|
|
514
|
+
- [ ] Generated code compiles in TypeScript
|
|
515
|
+
- [ ] Composables work in Nuxt project
|
|
516
|
+
- [ ] Callbacks fire correctly
|
|
517
|
+
- [ ] Types are correct
|
|
518
|
+
- [ ] Edge cases handled (no params, path params, etc.)
|
|
519
|
+
|
|
520
|
+
### Debugging Tips
|
|
521
|
+
|
|
522
|
+
See [Debugging Guide](#debugging-guide) section below.
|
|
523
|
+
|
|
524
|
+
## Code Style
|
|
525
|
+
|
|
526
|
+
### TypeScript Style
|
|
527
|
+
|
|
528
|
+
```typescript
|
|
529
|
+
// ✅ Good: Explicit types
|
|
530
|
+
export interface MethodInfo {
|
|
531
|
+
name: string;
|
|
532
|
+
path: string;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// ❌ Bad: Implicit any
|
|
536
|
+
export interface MethodInfo {
|
|
537
|
+
name;
|
|
538
|
+
path;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// ✅ Good: Readonly where possible
|
|
542
|
+
export interface MethodInfo {
|
|
543
|
+
readonly name: string;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// ✅ Good: Defensive null checks
|
|
547
|
+
if (params.length > 0) {
|
|
548
|
+
const type = params[0].getType();
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// ❌ Bad: Assuming existence
|
|
552
|
+
const type = params[0].getType(); // Might crash if empty
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
### Naming Conventions
|
|
556
|
+
|
|
557
|
+
```typescript
|
|
558
|
+
// Interfaces: PascalCase
|
|
559
|
+
interface ApiRequestOptions {}
|
|
560
|
+
|
|
561
|
+
// Functions: camelCase
|
|
562
|
+
function generateComposableFile() {}
|
|
563
|
+
|
|
564
|
+
// Constants: UPPER_SNAKE_CASE
|
|
565
|
+
const BASE_PATH = '/api';
|
|
566
|
+
|
|
567
|
+
// Files: kebab-case
|
|
568
|
+
// use-api-request.ts (not useApiRequest.ts)
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
### Error Handling
|
|
572
|
+
|
|
573
|
+
```typescript
|
|
574
|
+
// ✅ Good: Try-catch with specific error
|
|
575
|
+
try {
|
|
576
|
+
const method = extractMethodInfo(node);
|
|
577
|
+
if (method) methods.push(method);
|
|
578
|
+
} catch (error) {
|
|
579
|
+
console.warn(`Warning: Could not parse ${node.getName()}: ${error}`);
|
|
580
|
+
// Continue processing
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// ❌ Bad: Silent failure
|
|
584
|
+
try {
|
|
585
|
+
const method = extractMethodInfo(node);
|
|
586
|
+
methods.push(method);
|
|
587
|
+
} catch {}
|
|
588
|
+
|
|
589
|
+
// ✅ Good: Helpful error messages
|
|
590
|
+
throw new Error(`APIs directory not found: ${apisDir}
|
|
591
|
+
|
|
592
|
+
Make sure you've generated OpenAPI files first:
|
|
593
|
+
nxh generate -i swagger.yaml -o ./output
|
|
594
|
+
`);
|
|
595
|
+
|
|
596
|
+
// ❌ Bad: Vague error
|
|
597
|
+
throw new Error('Directory not found');
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
### Comments
|
|
601
|
+
|
|
602
|
+
```typescript
|
|
603
|
+
// ✅ Good: JSDoc for public APIs
|
|
604
|
+
/**
|
|
605
|
+
* Generate Nuxt composables from OpenAPI specification
|
|
606
|
+
* @param inputDir Directory containing generated TypeScript files
|
|
607
|
+
* @param outputDir Output directory for composables
|
|
608
|
+
* @returns Promise that resolves when generation completes
|
|
609
|
+
*/
|
|
610
|
+
export async function generate(inputDir: string, outputDir: string): Promise<void> {}
|
|
611
|
+
|
|
612
|
+
// ✅ Good: Inline for complex logic
|
|
613
|
+
// Extract path from return statement: return { path: '/pet' }
|
|
614
|
+
const pathProperty = returnObj.getProperty('path');
|
|
615
|
+
|
|
616
|
+
// ❌ Bad: Stating the obvious
|
|
617
|
+
// Increment counter
|
|
618
|
+
counter++;
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
## Common Tasks
|
|
622
|
+
|
|
623
|
+
### Task: Add Support for a New HTTP Method
|
|
624
|
+
|
|
625
|
+
**Example**: Add PATCH method support
|
|
626
|
+
|
|
627
|
+
**1. Verify Parser Handles It**
|
|
628
|
+
|
|
629
|
+
Check `src/generators/use-fetch/parser.ts`:
|
|
630
|
+
|
|
631
|
+
```typescript
|
|
632
|
+
// HTTP method is extracted from returnObj.method property
|
|
633
|
+
// Should automatically work if OpenAPI Generator includes it
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
**2. Update Type** (if restricted)
|
|
637
|
+
|
|
638
|
+
```typescript
|
|
639
|
+
// In types.ts, the http method field is httpMethod: string
|
|
640
|
+
// PATCH is already supported — no type change needed
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
**3. Test**
|
|
644
|
+
|
|
645
|
+
```bash
|
|
646
|
+
# Create OpenAPI with PATCH endpoint
|
|
647
|
+
# Generate and verify
|
|
648
|
+
npm run build
|
|
649
|
+
node dist/index.js generate -i swagger-with-patch.yaml -o ./test
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
### Task: Change Generated File Names
|
|
653
|
+
|
|
654
|
+
**Example**: Use PascalCase instead of kebab-case
|
|
655
|
+
|
|
656
|
+
**1. Update Converter** (`src/generators/use-fetch/parser.ts`)
|
|
657
|
+
|
|
658
|
+
```typescript
|
|
659
|
+
import { pascalCase } from 'change-case';
|
|
660
|
+
|
|
661
|
+
export function extractMethodInfo(/* ... */): MethodInfo {
|
|
662
|
+
// ... existing
|
|
663
|
+
|
|
664
|
+
return {
|
|
665
|
+
// ... existing
|
|
666
|
+
fileName: `${pascalCase(methodName)}.ts`, // NEW: PascalCase
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
**2. Test**
|
|
672
|
+
|
|
673
|
+
```bash
|
|
674
|
+
npm run build
|
|
675
|
+
node dist/index.js generate -i swagger.yaml -o ./test
|
|
676
|
+
ls test/composables/use-fetch/
|
|
677
|
+
# Should see: UseFetchAddPet.ts instead of use-fetch-add-pet.ts
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
### Task: Add CLI Flag
|
|
681
|
+
|
|
682
|
+
**Example**: Add `--skip-formatting` flag
|
|
683
|
+
|
|
684
|
+
**1. Update CLI** (`src/index.ts`)
|
|
685
|
+
|
|
686
|
+
```typescript
|
|
687
|
+
program
|
|
688
|
+
.command('generate')
|
|
689
|
+
.option('-i, --input <file>', 'OpenAPI file')
|
|
690
|
+
.option('-o, --output <dir>', 'Output directory')
|
|
691
|
+
.option('--skip-formatting', 'Skip Prettier formatting') // NEW
|
|
692
|
+
.action(async (options) => {
|
|
693
|
+
// Pass to generators
|
|
694
|
+
await generateUseFetchComposables(input, output, {
|
|
695
|
+
skipFormatting: options.skipFormatting,
|
|
696
|
+
});
|
|
697
|
+
});
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
**2. Update Generator** (`src/generators/use-fetch/generator.ts`)
|
|
701
|
+
|
|
702
|
+
```typescript
|
|
703
|
+
export async function generateUseFetchComposables(
|
|
704
|
+
inputDir: string,
|
|
705
|
+
outputDir: string,
|
|
706
|
+
options?: { skipFormatting?: boolean }
|
|
707
|
+
): Promise<void> {
|
|
708
|
+
// ... generate content
|
|
709
|
+
|
|
710
|
+
const finalContent = options?.skipFormatting
|
|
711
|
+
? content
|
|
712
|
+
: await prettier.format(content, { parser: 'typescript' });
|
|
713
|
+
|
|
714
|
+
await fs.writeFile(filePath, finalContent);
|
|
715
|
+
}
|
|
716
|
+
```
|
|
717
|
+
|
|
718
|
+
**3. Test**
|
|
719
|
+
|
|
720
|
+
```bash
|
|
721
|
+
npm run build
|
|
722
|
+
node dist/index.js generate -i swagger.yaml -o ./test --skip-formatting
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
## Extension Points
|
|
726
|
+
|
|
727
|
+
### 1. Custom Runtime Wrappers
|
|
728
|
+
|
|
729
|
+
**Location**: `src/generators/*/runtime/`
|
|
730
|
+
|
|
731
|
+
**Purpose**: Modify behavior of generated composables
|
|
732
|
+
|
|
733
|
+
**Example**: Add automatic retry
|
|
734
|
+
|
|
735
|
+
```typescript
|
|
736
|
+
// src/generators/use-fetch/runtime/useApiRequest.ts
|
|
737
|
+
export function useApiRequest<T>(url: string, options?: ApiRequestOptions<T>) {
|
|
738
|
+
const maxRetries = options?.maxRetries ?? 3;
|
|
739
|
+
let attempt = 0;
|
|
740
|
+
|
|
741
|
+
const fetchWithRetry = async () => {
|
|
742
|
+
try {
|
|
743
|
+
return await useFetch<T>(url, options);
|
|
744
|
+
} catch (error) {
|
|
745
|
+
if (attempt < maxRetries) {
|
|
746
|
+
attempt++;
|
|
747
|
+
return fetchWithRetry();
|
|
748
|
+
}
|
|
749
|
+
throw error;
|
|
750
|
+
}
|
|
751
|
+
};
|
|
752
|
+
|
|
753
|
+
return fetchWithRetry();
|
|
754
|
+
}
|
|
755
|
+
```
|
|
756
|
+
|
|
757
|
+
### 2. Custom Template Functions
|
|
758
|
+
|
|
759
|
+
**Location**: `src/generators/*/templates.ts`
|
|
760
|
+
|
|
761
|
+
**Purpose**: Change generated code format
|
|
762
|
+
|
|
763
|
+
**Example**: Add banner comment
|
|
764
|
+
|
|
765
|
+
```typescript
|
|
766
|
+
export function generateFileHeader(): string {
|
|
767
|
+
return `/**
|
|
768
|
+
* AUTO-GENERATED - DO NOT EDIT
|
|
769
|
+
* Generated by Nuxt Generator
|
|
770
|
+
* Timestamp: ${new Date().toISOString()}
|
|
771
|
+
*/
|
|
772
|
+
// @ts-nocheck
|
|
773
|
+
`;
|
|
774
|
+
}
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
### 3. Custom Parser Logic
|
|
778
|
+
|
|
779
|
+
**Location**: `src/generators/*/parser.ts`
|
|
780
|
+
|
|
781
|
+
**Purpose**: Extract additional metadata
|
|
782
|
+
|
|
783
|
+
**Example**: Extract JSDoc descriptions
|
|
784
|
+
|
|
785
|
+
```typescript
|
|
786
|
+
export function extractMethodInfo(method: MethodDeclaration): MethodInfo {
|
|
787
|
+
// ...existing
|
|
788
|
+
|
|
789
|
+
const jsDocs = method.getJsDocs();
|
|
790
|
+
const description = jsDocs[0]?.getDescription() ?? '';
|
|
791
|
+
|
|
792
|
+
return {
|
|
793
|
+
// ...existing
|
|
794
|
+
description,
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
## Debugging Guide
|
|
800
|
+
|
|
801
|
+
### Common Issues
|
|
802
|
+
|
|
803
|
+
#### Build Fails
|
|
804
|
+
|
|
805
|
+
**Symptom**: `npm run build` errors
|
|
806
|
+
|
|
807
|
+
**Solution**:
|
|
808
|
+
|
|
809
|
+
```bash
|
|
810
|
+
# Clean and rebuild
|
|
811
|
+
rm -rf dist/
|
|
812
|
+
npm run build
|
|
813
|
+
|
|
814
|
+
# Check TypeScript errors
|
|
815
|
+
npm run type-check
|
|
816
|
+
```
|
|
817
|
+
|
|
818
|
+
#### Parser Not Finding Methods
|
|
819
|
+
|
|
820
|
+
**Symptom**: "Found 0 methods"
|
|
821
|
+
|
|
822
|
+
**Debug**:
|
|
823
|
+
|
|
824
|
+
```typescript
|
|
825
|
+
// Add logging in parser.ts
|
|
826
|
+
export function parseApiFile(filePath: string): ApiClassInfo {
|
|
827
|
+
console.log('Parsing file:', filePath);
|
|
828
|
+
|
|
829
|
+
const classes = sourceFile.getClasses();
|
|
830
|
+
console.log(
|
|
831
|
+
'Found classes:',
|
|
832
|
+
classes.map((c) => c.getName())
|
|
833
|
+
);
|
|
834
|
+
|
|
835
|
+
for (const cls of classes) {
|
|
836
|
+
const methods = cls.getMethods();
|
|
837
|
+
console.log(
|
|
838
|
+
`Methods in ${cls.getName()}:`,
|
|
839
|
+
methods.map((m) => m.getName())
|
|
840
|
+
);
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// Continue...
|
|
844
|
+
}
|
|
845
|
+
```
|
|
846
|
+
|
|
847
|
+
#### Generated Code Has Type Errors
|
|
848
|
+
|
|
849
|
+
**Symptom**: TypeScript errors in generated files
|
|
850
|
+
|
|
851
|
+
**Debug**:
|
|
852
|
+
|
|
853
|
+
1. Check import paths (relative vs absolute)
|
|
854
|
+
2. Verify type exports from OpenAPI Generator
|
|
855
|
+
3. Check `responseType` and `requestType` extraction
|
|
856
|
+
|
|
857
|
+
```typescript
|
|
858
|
+
// Add logging in parser
|
|
859
|
+
console.log('Response type:', responseType);
|
|
860
|
+
console.log('Request type:', requestType);
|
|
861
|
+
```
|
|
862
|
+
|
|
863
|
+
#### Callbacks Not Firing
|
|
864
|
+
|
|
865
|
+
**Symptom**: `onSuccess` never called
|
|
866
|
+
|
|
867
|
+
**Debug**:
|
|
868
|
+
|
|
869
|
+
```typescript
|
|
870
|
+
// In useApiRequest.ts
|
|
871
|
+
watch(
|
|
872
|
+
() => [result.data.value, result.error.value, result.pending.value] as const,
|
|
873
|
+
([data, error, pending], [prevData, prevError, prevPending]) => {
|
|
874
|
+
console.log('Watch triggered:', { data, error, pending });
|
|
875
|
+
|
|
876
|
+
if (data && data !== prevData) {
|
|
877
|
+
console.log('Calling onSuccess');
|
|
878
|
+
mergedCallbacks.onSuccess?.(data);
|
|
879
|
+
}
|
|
880
|
+
},
|
|
881
|
+
{ immediate: true }
|
|
882
|
+
);
|
|
883
|
+
```
|
|
884
|
+
|
|
885
|
+
### DevTools
|
|
886
|
+
|
|
887
|
+
**ts-morph Explorer**:
|
|
888
|
+
|
|
889
|
+
```typescript
|
|
890
|
+
// Explore AST structure
|
|
891
|
+
import { Project } from 'ts-morph';
|
|
892
|
+
|
|
893
|
+
const project = new Project();
|
|
894
|
+
const sourceFile = project.addSourceFileAtPath('PetApi.ts');
|
|
895
|
+
|
|
896
|
+
// Print entire AST
|
|
897
|
+
console.log(sourceFile.getFullText());
|
|
898
|
+
|
|
899
|
+
// Print structure
|
|
900
|
+
sourceFile.getDescendants().forEach((node) => {
|
|
901
|
+
console.log(node.getKindName(), node.getText().substring(0, 50));
|
|
902
|
+
});
|
|
903
|
+
```
|
|
904
|
+
|
|
905
|
+
**Prettier Test**:
|
|
906
|
+
|
|
907
|
+
```typescript
|
|
908
|
+
// Test formatting
|
|
909
|
+
import prettier from 'prettier';
|
|
910
|
+
|
|
911
|
+
const code = `export const test=()=>{return"hello"}`;
|
|
912
|
+
const formatted = await prettier.format(code, { parser: 'typescript' });
|
|
913
|
+
console.log(formatted);
|
|
914
|
+
```
|
|
915
|
+
|
|
916
|
+
---
|
|
917
|
+
|
|
918
|
+
**Next**: [Troubleshooting Common Issues](./TROUBLESHOOTING.md)
|