nuxt-openapi-hyperfetch 0.2.7-alpha.1 → 0.3.0-beta

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 (68) hide show
  1. package/.editorconfig +26 -26
  2. package/.prettierignore +17 -17
  3. package/CONTRIBUTING.md +291 -291
  4. package/INSTRUCTIONS.md +327 -327
  5. package/LICENSE +202 -202
  6. package/README.md +309 -231
  7. package/dist/cli/config.d.ts +9 -2
  8. package/dist/cli/config.js +1 -1
  9. package/dist/cli/logo.js +5 -5
  10. package/dist/cli/messages.d.ts +1 -0
  11. package/dist/cli/messages.js +2 -0
  12. package/dist/cli/prompts.d.ts +5 -0
  13. package/dist/cli/prompts.js +12 -0
  14. package/dist/cli/types.d.ts +1 -1
  15. package/dist/generators/components/connector-generator/templates.js +68 -19
  16. package/dist/generators/shared/runtime/useFormConnector.js +8 -1
  17. package/dist/generators/shared/runtime/useListConnector.js +13 -6
  18. package/dist/generators/use-async-data/generator.js +4 -0
  19. package/dist/generators/use-async-data/runtime/useApiAsyncData.js +4 -4
  20. package/dist/generators/use-async-data/runtime/useApiAsyncDataRaw.js +4 -4
  21. package/dist/generators/use-async-data/templates.js +17 -17
  22. package/dist/generators/use-fetch/generator.js +4 -0
  23. package/dist/generators/use-fetch/templates.js +14 -14
  24. package/dist/index.js +40 -27
  25. package/dist/module/index.js +19 -0
  26. package/dist/module/types.d.ts +7 -0
  27. package/docs/API-REFERENCE.md +886 -886
  28. package/docs/generated-components.md +615 -615
  29. package/docs/headless-composables-ui.md +569 -569
  30. package/eslint.config.js +85 -85
  31. package/package.json +1 -1
  32. package/src/cli/config.ts +147 -140
  33. package/src/cli/logger.ts +124 -124
  34. package/src/cli/logo.ts +25 -25
  35. package/src/cli/messages.ts +4 -0
  36. package/src/cli/prompts.ts +14 -1
  37. package/src/cli/types.ts +50 -50
  38. package/src/generators/components/connector-generator/generator.ts +138 -138
  39. package/src/generators/components/connector-generator/templates.ts +307 -254
  40. package/src/generators/components/connector-generator/types.ts +34 -34
  41. package/src/generators/components/schema-analyzer/index.ts +44 -44
  42. package/src/generators/components/schema-analyzer/intent-detector.ts +187 -187
  43. package/src/generators/components/schema-analyzer/openapi-reader.ts +96 -96
  44. package/src/generators/components/schema-analyzer/resource-grouper.ts +166 -166
  45. package/src/generators/components/schema-analyzer/schema-field-mapper.ts +268 -268
  46. package/src/generators/components/schema-analyzer/types.ts +177 -177
  47. package/src/generators/nuxt-server/generator.ts +272 -272
  48. package/src/generators/shared/runtime/apiHelpers.ts +535 -535
  49. package/src/generators/shared/runtime/pagination.ts +323 -323
  50. package/src/generators/shared/runtime/useDeleteConnector.ts +109 -109
  51. package/src/generators/shared/runtime/useDetailConnector.ts +64 -64
  52. package/src/generators/shared/runtime/useFormConnector.ts +147 -139
  53. package/src/generators/shared/runtime/useListConnector.ts +158 -148
  54. package/src/generators/shared/runtime/zod-error-merger.ts +119 -119
  55. package/src/generators/shared/templates/api-callbacks-plugin.ts +399 -399
  56. package/src/generators/shared/templates/api-pagination-plugin.ts +158 -158
  57. package/src/generators/use-async-data/generator.ts +213 -205
  58. package/src/generators/use-async-data/runtime/useApiAsyncData.ts +329 -329
  59. package/src/generators/use-async-data/runtime/useApiAsyncDataRaw.ts +324 -324
  60. package/src/generators/use-async-data/templates.ts +257 -257
  61. package/src/generators/use-fetch/generator.ts +178 -170
  62. package/src/generators/use-fetch/runtime/useApiRequest.ts +354 -354
  63. package/src/generators/use-fetch/templates.ts +214 -214
  64. package/src/index.ts +306 -303
  65. package/src/module/index.ts +158 -133
  66. package/src/module/types.ts +39 -31
  67. package/dist/generators/tanstack-query/generator.d.ts +0 -5
  68. package/dist/generators/tanstack-query/generator.js +0 -11
@@ -1,257 +1,257 @@
1
- import type { MethodInfo } from './types.js';
2
-
3
- /**
4
- * Generate file header with auto-generation warning
5
- */
6
- function generateFileHeader(): string {
7
- return `/**
8
- * ⚠️ AUTO-GENERATED FILE - DO NOT EDIT MANUALLY
9
- *
10
- * This file was automatically generated by nuxt-openapi-generator.
11
- * Any manual changes will be overwritten on the next generation.
12
- *
13
- * @generated by nuxt-openapi-generator
14
- * @see https://github.com/dmartindiaz/nuxt-openapi-hyperfetch
15
- */
16
-
17
- /* eslint-disable */
18
- // @ts-nocheck
19
- `;
20
- }
21
-
22
- /**
23
- * Options for code generation
24
- */
25
- export interface GenerateOptions {
26
- baseUrl?: string;
27
- backend?: string;
28
- }
29
-
30
- /**
31
- * Generate a useAsyncData composable function
32
- */
33
- export function generateComposableFile(
34
- method: MethodInfo,
35
- apiImportPath: string,
36
- options?: GenerateOptions
37
- ): string {
38
- const header = generateFileHeader();
39
- const imports = generateImports(method, apiImportPath, false);
40
- const functionBody = generateFunctionBody(method, false, options);
41
-
42
- return `${header}${imports}\n\n${functionBody}\n`;
43
- }
44
-
45
- /**
46
- * Generate a useAsyncData composable function (Raw version with headers)
47
- */
48
- export function generateRawComposableFile(
49
- method: MethodInfo,
50
- apiImportPath: string,
51
- options?: GenerateOptions
52
- ): string {
53
- const header = generateFileHeader();
54
- const imports = generateImports(method, apiImportPath, true);
55
- const functionBody = generateFunctionBody(method, true, options);
56
-
57
- return `${header}${imports}\n\n${functionBody}\n`;
58
- }
59
-
60
- /**
61
- * Extract base type names from a type string
62
- * Examples:
63
- * Pet[] -> Pet
64
- * Array<Pet> -> Pet
65
- * Pet -> Pet
66
- * { [key: string]: Pet } -> (empty, it's anonymous)
67
- */
68
- function extractBaseTypes(type: string): string[] {
69
- if (!type) {
70
- return [];
71
- }
72
-
73
- // Handle array syntax: Pet[]
74
- const arrayMatch = type.match(/^(\w+)\[\]$/);
75
- if (arrayMatch) {
76
- return [arrayMatch[1]];
77
- }
78
-
79
- // Handle Array generic: Array<Pet>
80
- const arrayGenericMatch = type.match(/^Array<(\w+)>$/);
81
- if (arrayGenericMatch) {
82
- return [arrayGenericMatch[1]];
83
- }
84
-
85
- // If it's a simple named type (single word, PascalCase), include it
86
- if (/^[A-Z][a-zA-Z0-9]*$/.test(type)) {
87
- return [type];
88
- }
89
-
90
- // For complex types, don't extract anything
91
- return [];
92
- }
93
-
94
- /**
95
- * Generate import statements
96
- */
97
- function generateImports(method: MethodInfo, apiImportPath: string, isRaw: boolean): string {
98
- const typeNames = new Set<string>();
99
-
100
- // Extract base types from request type
101
- if (method.requestType) {
102
- const extracted = extractBaseTypes(method.requestType);
103
- extracted.forEach((t) => typeNames.add(t));
104
- }
105
-
106
- // Extract base types from response type
107
- if (method.responseType && method.responseType !== 'void') {
108
- const extracted = extractBaseTypes(method.responseType);
109
- extracted.forEach((t) => typeNames.add(t));
110
- }
111
-
112
- let imports = '';
113
-
114
- // Import types from API (only if we have named types to import)
115
- if (typeNames.size > 0) {
116
- imports += `import type { ${Array.from(typeNames).join(', ')} } from '${apiImportPath}';\n`;
117
- }
118
-
119
- // Import runtime helper (normal or raw)
120
- if (isRaw) {
121
- imports += `import { useApiAsyncDataRaw, type ApiAsyncDataRawOptions, type RawResponse } from '../runtime/useApiAsyncDataRaw';`;
122
- } else {
123
- imports += `import { useApiAsyncData, type ApiAsyncDataOptions } from '../runtime/useApiAsyncData';`;
124
- }
125
-
126
- return imports;
127
- }
128
-
129
- /**
130
- * Generate the composable function body
131
- */
132
- function generateFunctionBody(
133
- method: MethodInfo,
134
- isRaw: boolean,
135
- generateOptions?: GenerateOptions
136
- ): string {
137
- const hasParams = !!method.requestType;
138
- const paramsArg = hasParams ? `params: ${method.requestType}` : '';
139
-
140
- // Determine the options type based on isRaw
141
- const optionsType = isRaw
142
- ? `ApiAsyncDataRawOptions<${method.responseType}>`
143
- : `ApiAsyncDataOptions<${method.responseType}>`;
144
- const optionsArg = `options?: ${optionsType}`;
145
- const args = hasParams ? `${paramsArg}, ${optionsArg}` : optionsArg;
146
-
147
- // Determine the response type generic
148
- const responseTypeGeneric = method.responseType !== 'void' ? `<${method.responseType}>` : '';
149
-
150
- // Generate unique key for useAsyncData
151
- const composableName =
152
- isRaw && method.rawMethodName
153
- ? `useAsyncData${method.rawMethodName.replace(/Raw$/, '')}Raw`
154
- : method.composableName.replace(/^useFetch/, 'useAsyncData');
155
-
156
- const key = `'${composableName}'`;
157
-
158
- const url = generateUrl(method);
159
- const fetchOptions = generateFetchOptions(method, generateOptions);
160
-
161
- const description = method.description ? `/**\n * ${method.description}\n */\n` : '';
162
-
163
- // Choose the correct wrapper function
164
- const wrapperFunction = isRaw ? 'useApiAsyncDataRaw' : 'useApiAsyncData';
165
-
166
- const pInit = hasParams ? `\n const p = shallowRef(params)` : '';
167
-
168
- const argsExtraction = hasParams
169
- ? ` const _hasKey = typeof args[0] === 'string'\n const params = _hasKey ? args[1] : args[0]\n const options = _hasKey ? { cacheKey: args[0], ...args[2] } : args[1]`
170
- : ` const _hasKey = typeof args[0] === 'string'\n const options = _hasKey ? { cacheKey: args[0], ...args[1] } : args[0]`;
171
-
172
- return `${description}export function ${composableName}(key: string, ${args})
173
- export function ${composableName}(${args})
174
- export function ${composableName}(...args: any[]) {
175
- ${argsExtraction}${pInit}
176
- return ${wrapperFunction}${responseTypeGeneric}(${key}, ${url}, ${fetchOptions})
177
- }`;
178
- }
179
-
180
- /**
181
- * Generate URL (with path params if needed)
182
- */
183
- function generateUrl(method: MethodInfo): string {
184
- if (method.pathParams.length === 0) {
185
- return `'${method.path}'`;
186
- }
187
-
188
- let url = method.path;
189
- for (const param of method.pathParams) {
190
- const accessor = method.paramsShape === 'nested' ? `p.value.path.${param}` : `p.value.${param}`;
191
- url = url.replace(`{${param}}`, `\${${accessor}}`);
192
- }
193
-
194
- return `() => \`${url}\``;
195
- }
196
-
197
- /**
198
- * Generate fetch options object
199
- */
200
- function generateFetchOptions(method: MethodInfo, generateOptions?: GenerateOptions): string {
201
- const options: string[] = [];
202
-
203
- // Method
204
- options.push(`method: '${method.httpMethod}'`);
205
-
206
- // Base URL (if provided in config)
207
- if (generateOptions?.baseUrl) {
208
- options.push(`baseURL: '${generateOptions.baseUrl}'`);
209
- }
210
-
211
- // Body
212
- if (method.hasBody) {
213
- if (method.paramsShape === 'nested') {
214
- options.push(`body: computed(() => p.value.body)`);
215
- } else if (method.bodyField) {
216
- options.push(`body: computed(() => p.value.${method.bodyField})`);
217
- }
218
- }
219
-
220
- // Query params (renamed to 'params' for $fetch)
221
- if (method.hasQueryParams) {
222
- if (method.paramsShape === 'nested') {
223
- options.push(`params: computed(() => p.value.query)`);
224
- } else if (method.queryParams.length > 0) {
225
- const queryObj = method.queryParams
226
- .map((param) => `${param}: p.value.${param}`)
227
- .join(',\n ');
228
- options.push(`params: computed(() => ({\n ${queryObj}\n }))`);
229
- }
230
- }
231
-
232
- // Headers
233
- if (Object.keys(method.headers).length > 0) {
234
- const headersEntries = Object.entries(method.headers)
235
- .map(([key, value]) => `'${key}': '${value}'`)
236
- .join(',\n ');
237
- options.push(`headers: {\n ${headersEntries},\n ...options?.headers\n }`);
238
- }
239
-
240
- // Spread options
241
- options.push('...options');
242
-
243
- const optionsStr = options.join(',\n ');
244
- return `{\n ${optionsStr}\n }`;
245
- }
246
-
247
- /**
248
- * Generate index.ts that exports all composables
249
- */
250
- export function generateIndexFile(composableNames: string[]): string {
251
- const header = generateFileHeader();
252
- const exports = composableNames
253
- .map((name) => `export { ${name} } from './composables/${name}'`)
254
- .join('\n');
255
-
256
- return `${header}${exports}\n`;
257
- }
1
+ import type { MethodInfo } from './types.js';
2
+
3
+ /**
4
+ * Generate file header with auto-generation warning
5
+ */
6
+ function generateFileHeader(): string {
7
+ return `/**
8
+ * ⚠️ AUTO-GENERATED FILE - DO NOT EDIT MANUALLY
9
+ *
10
+ * This file was automatically generated by nuxt-openapi-generator.
11
+ * Any manual changes will be overwritten on the next generation.
12
+ *
13
+ * @generated by nuxt-openapi-generator
14
+ * @see https://github.com/dmartindiaz/nuxt-openapi-hyperfetch
15
+ */
16
+
17
+ /* eslint-disable */
18
+ // @ts-nocheck
19
+ `;
20
+ }
21
+
22
+ /**
23
+ * Options for code generation
24
+ */
25
+ export interface GenerateOptions {
26
+ baseUrl?: string;
27
+ backend?: string;
28
+ }
29
+
30
+ /**
31
+ * Generate a useAsyncData composable function
32
+ */
33
+ export function generateComposableFile(
34
+ method: MethodInfo,
35
+ apiImportPath: string,
36
+ options?: GenerateOptions
37
+ ): string {
38
+ const header = generateFileHeader();
39
+ const imports = generateImports(method, apiImportPath, false);
40
+ const functionBody = generateFunctionBody(method, false, options);
41
+
42
+ return `${header}${imports}\n\n${functionBody}\n`;
43
+ }
44
+
45
+ /**
46
+ * Generate a useAsyncData composable function (Raw version with headers)
47
+ */
48
+ export function generateRawComposableFile(
49
+ method: MethodInfo,
50
+ apiImportPath: string,
51
+ options?: GenerateOptions
52
+ ): string {
53
+ const header = generateFileHeader();
54
+ const imports = generateImports(method, apiImportPath, true);
55
+ const functionBody = generateFunctionBody(method, true, options);
56
+
57
+ return `${header}${imports}\n\n${functionBody}\n`;
58
+ }
59
+
60
+ /**
61
+ * Extract base type names from a type string
62
+ * Examples:
63
+ * Pet[] -> Pet
64
+ * Array<Pet> -> Pet
65
+ * Pet -> Pet
66
+ * { [key: string]: Pet } -> (empty, it's anonymous)
67
+ */
68
+ function extractBaseTypes(type: string): string[] {
69
+ if (!type) {
70
+ return [];
71
+ }
72
+
73
+ // Handle array syntax: Pet[]
74
+ const arrayMatch = type.match(/^(\w+)\[\]$/);
75
+ if (arrayMatch) {
76
+ return [arrayMatch[1]];
77
+ }
78
+
79
+ // Handle Array generic: Array<Pet>
80
+ const arrayGenericMatch = type.match(/^Array<(\w+)>$/);
81
+ if (arrayGenericMatch) {
82
+ return [arrayGenericMatch[1]];
83
+ }
84
+
85
+ // If it's a simple named type (single word, PascalCase), include it
86
+ if (/^[A-Z][a-zA-Z0-9]*$/.test(type)) {
87
+ return [type];
88
+ }
89
+
90
+ // For complex types, don't extract anything
91
+ return [];
92
+ }
93
+
94
+ /**
95
+ * Generate import statements
96
+ */
97
+ function generateImports(method: MethodInfo, apiImportPath: string, isRaw: boolean): string {
98
+ const typeNames = new Set<string>();
99
+
100
+ // Extract base types from request type
101
+ if (method.requestType) {
102
+ const extracted = extractBaseTypes(method.requestType);
103
+ extracted.forEach((t) => typeNames.add(t));
104
+ }
105
+
106
+ // Extract base types from response type
107
+ if (method.responseType && method.responseType !== 'void') {
108
+ const extracted = extractBaseTypes(method.responseType);
109
+ extracted.forEach((t) => typeNames.add(t));
110
+ }
111
+
112
+ let imports = '';
113
+
114
+ // Import types from API (only if we have named types to import)
115
+ if (typeNames.size > 0) {
116
+ imports += `import type { ${Array.from(typeNames).join(', ')} } from '${apiImportPath}';\n`;
117
+ }
118
+
119
+ // Import runtime helper (normal or raw)
120
+ if (isRaw) {
121
+ imports += `import { useApiAsyncDataRaw, type ApiAsyncDataRawOptions, type RawResponse } from '../runtime/useApiAsyncDataRaw';`;
122
+ } else {
123
+ imports += `import { useApiAsyncData, type ApiAsyncDataOptions } from '../runtime/useApiAsyncData';`;
124
+ }
125
+
126
+ return imports;
127
+ }
128
+
129
+ /**
130
+ * Generate the composable function body
131
+ */
132
+ function generateFunctionBody(
133
+ method: MethodInfo,
134
+ isRaw: boolean,
135
+ generateOptions?: GenerateOptions
136
+ ): string {
137
+ const hasParams = !!method.requestType;
138
+ const paramsArg = hasParams ? `params: ${method.requestType}` : '';
139
+
140
+ // Determine the options type based on isRaw
141
+ const optionsType = isRaw
142
+ ? `ApiAsyncDataRawOptions<${method.responseType}>`
143
+ : `ApiAsyncDataOptions<${method.responseType}>`;
144
+ const optionsArg = `options?: ${optionsType}`;
145
+ const args = hasParams ? `${paramsArg}, ${optionsArg}` : optionsArg;
146
+
147
+ // Determine the response type generic
148
+ const responseTypeGeneric = method.responseType !== 'void' ? `<${method.responseType}>` : '';
149
+
150
+ // Generate unique key for useAsyncData
151
+ const composableName =
152
+ isRaw && method.rawMethodName
153
+ ? `useAsyncData${method.rawMethodName.replace(/Raw$/, '')}Raw`
154
+ : method.composableName.replace(/^useFetch/, 'useAsyncData');
155
+
156
+ const key = `'${composableName}'`;
157
+
158
+ const url = generateUrl(method);
159
+ const fetchOptions = generateFetchOptions(method, generateOptions);
160
+
161
+ const description = method.description ? `/**\n * ${method.description}\n */\n` : '';
162
+
163
+ // Choose the correct wrapper function
164
+ const wrapperFunction = isRaw ? 'useApiAsyncDataRaw' : 'useApiAsyncData';
165
+
166
+ const pInit = hasParams ? `\n const p = shallowRef(params)` : '';
167
+
168
+ const argsExtraction = hasParams
169
+ ? ` const _hasKey = typeof args[0] === 'string'\n const params = _hasKey ? args[1] : args[0]\n const options = _hasKey ? { cacheKey: args[0], ...args[2] } : args[1]`
170
+ : ` const _hasKey = typeof args[0] === 'string'\n const options = _hasKey ? { cacheKey: args[0], ...args[1] } : args[0]`;
171
+
172
+ return `${description}export function ${composableName}(key: string, ${args})
173
+ export function ${composableName}(${args})
174
+ export function ${composableName}(...args: any[]) {
175
+ ${argsExtraction}${pInit}
176
+ return ${wrapperFunction}${responseTypeGeneric}(${key}, ${url}, ${fetchOptions})
177
+ }`;
178
+ }
179
+
180
+ /**
181
+ * Generate URL (with path params if needed)
182
+ */
183
+ function generateUrl(method: MethodInfo): string {
184
+ if (method.pathParams.length === 0) {
185
+ return `'${method.path}'`;
186
+ }
187
+
188
+ let url = method.path;
189
+ for (const param of method.pathParams) {
190
+ const accessor = method.paramsShape === 'nested' ? `p.value.path.${param}` : `p.value.${param}`;
191
+ url = url.replace(`{${param}}`, `\${${accessor}}`);
192
+ }
193
+
194
+ return `() => \`${url}\``;
195
+ }
196
+
197
+ /**
198
+ * Generate fetch options object
199
+ */
200
+ function generateFetchOptions(method: MethodInfo, generateOptions?: GenerateOptions): string {
201
+ const options: string[] = [];
202
+
203
+ // Method
204
+ options.push(`method: '${method.httpMethod}'`);
205
+
206
+ // Base URL (if provided in config)
207
+ if (generateOptions?.baseUrl) {
208
+ options.push(`baseURL: '${generateOptions.baseUrl}'`);
209
+ }
210
+
211
+ // Body
212
+ if (method.hasBody) {
213
+ if (method.paramsShape === 'nested') {
214
+ options.push(`body: computed(() => p.value.body)`);
215
+ } else if (method.bodyField) {
216
+ options.push(`body: computed(() => p.value.${method.bodyField})`);
217
+ }
218
+ }
219
+
220
+ // Query params (renamed to 'params' for $fetch)
221
+ if (method.hasQueryParams) {
222
+ if (method.paramsShape === 'nested') {
223
+ options.push(`params: computed(() => p.value.query)`);
224
+ } else if (method.queryParams.length > 0) {
225
+ const queryObj = method.queryParams
226
+ .map((param) => `${param}: p.value.${param}`)
227
+ .join(',\n ');
228
+ options.push(`params: computed(() => ({\n ${queryObj}\n }))`);
229
+ }
230
+ }
231
+
232
+ // Headers
233
+ if (Object.keys(method.headers).length > 0) {
234
+ const headersEntries = Object.entries(method.headers)
235
+ .map(([key, value]) => `'${key}': '${value}'`)
236
+ .join(',\n ');
237
+ options.push(`headers: {\n ${headersEntries},\n ...options?.headers\n }`);
238
+ }
239
+
240
+ // Spread options
241
+ options.push('...options');
242
+
243
+ const optionsStr = options.join(',\n ');
244
+ return `{\n ${optionsStr}\n }`;
245
+ }
246
+
247
+ /**
248
+ * Generate index.ts that exports all composables
249
+ */
250
+ export function generateIndexFile(composableNames: string[]): string {
251
+ const header = generateFileHeader();
252
+ const exports = composableNames
253
+ .map((name) => `export { ${name} } from './composables/${name}'`)
254
+ .join('\n');
255
+
256
+ return `${header}${exports}\n`;
257
+ }