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,122 @@
1
+ import { getGlobalHeaders, applyPick, mergeCallbacks, } from '../../shared/runtime/apiHelpers.js';
2
+ /**
3
+ * Generic wrapper for API calls using Nuxt's useAsyncData
4
+ * Supports:
5
+ * - Lifecycle callbacks (onRequest, onSuccess, onError, onFinish)
6
+ * - Request modification via onRequest return value
7
+ * - Transform and pick operations
8
+ * - Global headers from useApiHeaders or $getApiHeaders
9
+ * - Watch pattern for reactive parameters
10
+ */
11
+ export function useApiAsyncData(key, url, options) {
12
+ const { method = 'GET', body, headers = {}, params, transform, pick, onRequest, onSuccess, onError, onFinish, skipGlobalCallbacks, immediate = true, lazy = false, server = true, dedupe = 'cancel', watch: watchOption = true, ...restOptions } = options || {};
13
+ // Create reactive watch sources — use refs/computeds directly so Vue can track them
14
+ // watchOption: false disables auto-refresh entirely
15
+ const watchSources = watchOption === false
16
+ ? []
17
+ : [
18
+ ...(typeof url === 'function' ? [url] : []),
19
+ ...(body ? (isRef(body) ? [body] : typeof body === 'object' ? [() => body] : []) : []),
20
+ ...(params
21
+ ? isRef(params)
22
+ ? [params]
23
+ : typeof params === 'object'
24
+ ? [() => params]
25
+ : []
26
+ : []),
27
+ ];
28
+ // Fetch function for useAsyncData
29
+ const fetchFn = async () => {
30
+ // Get URL value for merging callbacks
31
+ const finalUrl = typeof url === 'function' ? url() : url;
32
+ // Merge local and global callbacks
33
+ const mergedCallbacks = mergeCallbacks(finalUrl, { onRequest, onSuccess, onError, onFinish }, skipGlobalCallbacks);
34
+ try {
35
+ // Get global headers
36
+ const globalHeaders = getGlobalHeaders();
37
+ // Prepare request context
38
+ const requestContext = {
39
+ url: finalUrl,
40
+ method: method,
41
+ headers: { ...globalHeaders, ...headers },
42
+ body,
43
+ params,
44
+ };
45
+ // Execute merged onRequest callback and potentially modify request
46
+ const modifiedContext = { ...requestContext };
47
+ if (mergedCallbacks.onRequest) {
48
+ const result = await mergedCallbacks.onRequest(requestContext);
49
+ // If onRequest returns modifications, apply them
50
+ if (result && typeof result === 'object') {
51
+ const modifications = result;
52
+ if (modifications.body !== undefined) {
53
+ modifiedContext.body = modifications.body;
54
+ }
55
+ if (modifications.headers !== undefined) {
56
+ modifiedContext.headers = {
57
+ ...modifiedContext.headers,
58
+ ...modifications.headers,
59
+ };
60
+ }
61
+ if (modifications.params !== undefined) {
62
+ modifiedContext.params = {
63
+ ...modifiedContext.params,
64
+ ...modifications.params,
65
+ };
66
+ }
67
+ }
68
+ }
69
+ // Make the request with $fetch — toValue() unrefs any Ref/ComputedRef
70
+ let data = await $fetch(modifiedContext.url, {
71
+ method: modifiedContext.method,
72
+ headers: modifiedContext.headers,
73
+ body: toValue(modifiedContext.body),
74
+ params: toValue(modifiedContext.params),
75
+ ...restOptions,
76
+ });
77
+ // Apply pick if provided
78
+ if (pick) {
79
+ data = applyPick(data, pick);
80
+ }
81
+ // Apply transform if provided
82
+ if (transform) {
83
+ data = transform(data);
84
+ }
85
+ // Call merged onSuccess callback
86
+ if (mergedCallbacks.onSuccess) {
87
+ await mergedCallbacks.onSuccess(data, {
88
+ url: finalUrl,
89
+ method,
90
+ headers: modifiedContext.headers,
91
+ });
92
+ }
93
+ return data;
94
+ }
95
+ catch (error) {
96
+ // Call merged onError callback
97
+ if (mergedCallbacks.onError) {
98
+ await mergedCallbacks.onError(error, { url: finalUrl, method, headers });
99
+ }
100
+ throw error;
101
+ }
102
+ finally {
103
+ // Call merged onFinish callback
104
+ if (mergedCallbacks.onFinish) {
105
+ await mergedCallbacks.onFinish({
106
+ url: finalUrl,
107
+ method,
108
+ headers: { ...getGlobalHeaders(), ...headers },
109
+ });
110
+ }
111
+ }
112
+ };
113
+ // Use Nuxt's useAsyncData
114
+ const result = useAsyncData(key, fetchFn, {
115
+ immediate,
116
+ lazy,
117
+ server,
118
+ dedupe,
119
+ watch: watchSources.length > 0 ? watchSources : undefined,
120
+ });
121
+ return result;
122
+ }
@@ -0,0 +1,54 @@
1
+ import { type ApiRequestOptions as BaseApiRequestOptions } from '../../shared/runtime/apiHelpers.js';
2
+ /**
3
+ * Response structure for Raw version
4
+ * Includes data, headers, status, and statusText
5
+ */
6
+ export interface RawResponse<T> {
7
+ data: T;
8
+ headers: Headers;
9
+ status: number;
10
+ statusText: string;
11
+ }
12
+ /**
13
+ * Extended options specific to useAsyncData Raw version
14
+ */
15
+ export interface ApiAsyncDataRawOptions<T> extends Omit<BaseApiRequestOptions<T>, 'onSuccess'> {
16
+ /**
17
+ * Success callback that receives both data and full response
18
+ */
19
+ onSuccess?: (data: T, response: {
20
+ headers: Headers;
21
+ status: number;
22
+ statusText: string;
23
+ url: string;
24
+ }) => void | Promise<void>;
25
+ /**
26
+ * Whether to fetch data immediately on mount (default: true)
27
+ */
28
+ immediate?: boolean;
29
+ /**
30
+ * Lazy mode: don't block navigation (default: false)
31
+ */
32
+ lazy?: boolean;
33
+ /**
34
+ * Server-side rendering mode (default: true)
35
+ */
36
+ server?: boolean;
37
+ /**
38
+ * Deduplicate requests with the same key (default: 'cancel')
39
+ */
40
+ dedupe?: 'cancel' | 'defer';
41
+ }
42
+ /**
43
+ * Generic wrapper for API calls using Nuxt's useAsyncData - RAW VERSION
44
+ * Returns full response with headers and status information
45
+ *
46
+ * Supports:
47
+ * - Lifecycle callbacks (onRequest, onSuccess with response, onError, onFinish)
48
+ * - Request modification via onRequest return value
49
+ * - Transform (applies only to data, not full response)
50
+ * - Pick operations (applies only to data)
51
+ * - Global headers from useApiHeaders or $getApiHeaders
52
+ * - Watch pattern for reactive parameters
53
+ */
54
+ export declare function useApiAsyncDataRaw<T>(key: string, url: string | (() => string), options?: ApiAsyncDataRawOptions<T>): any;
@@ -0,0 +1,126 @@
1
+ import { getGlobalHeaders, applyPick, mergeCallbacks, } from '../../shared/runtime/apiHelpers.js';
2
+ /**
3
+ * Generic wrapper for API calls using Nuxt's useAsyncData - RAW VERSION
4
+ * Returns full response with headers and status information
5
+ *
6
+ * Supports:
7
+ * - Lifecycle callbacks (onRequest, onSuccess with response, onError, onFinish)
8
+ * - Request modification via onRequest return value
9
+ * - Transform (applies only to data, not full response)
10
+ * - Pick operations (applies only to data)
11
+ * - Global headers from useApiHeaders or $getApiHeaders
12
+ * - Watch pattern for reactive parameters
13
+ */
14
+ export function useApiAsyncDataRaw(key, url, options) {
15
+ const { method = 'GET', body, headers = {}, params, transform, pick, onRequest, onSuccess, onError, onFinish, skipGlobalCallbacks, immediate = true, lazy = false, server = true, dedupe = 'cancel', ...restOptions } = options || {};
16
+ // Create reactive watch sources for callbacks
17
+ const watchSources = [
18
+ ...(typeof url === 'function' ? [url] : []),
19
+ ...(body && typeof body === 'object' ? [() => body] : []),
20
+ ...(params && typeof params === 'object' ? [() => params] : []),
21
+ ];
22
+ // Fetch function for useAsyncData
23
+ const fetchFn = async () => {
24
+ // Get URL value for merging callbacks
25
+ const finalUrl = typeof url === 'function' ? url() : url;
26
+ // Merge local and global callbacks
27
+ const mergedCallbacks = mergeCallbacks(finalUrl, { onRequest, onSuccess, onError, onFinish }, skipGlobalCallbacks);
28
+ try {
29
+ // Get global headers
30
+ const globalHeaders = getGlobalHeaders();
31
+ // Prepare request context
32
+ const requestContext = {
33
+ url: finalUrl,
34
+ method: method,
35
+ headers: { ...globalHeaders, ...headers },
36
+ body,
37
+ params,
38
+ };
39
+ // Execute merged onRequest callback and potentially modify request
40
+ const modifiedContext = { ...requestContext };
41
+ if (mergedCallbacks.onRequest) {
42
+ const result = await mergedCallbacks.onRequest(requestContext);
43
+ // If onRequest returns modifications, apply them
44
+ if (result && typeof result === 'object') {
45
+ const modifications = result;
46
+ if (modifications.body !== undefined) {
47
+ modifiedContext.body = modifications.body;
48
+ }
49
+ if (modifications.headers !== undefined) {
50
+ modifiedContext.headers = {
51
+ ...modifiedContext.headers,
52
+ ...modifications.headers,
53
+ };
54
+ }
55
+ if (modifications.params !== undefined) {
56
+ modifiedContext.params = {
57
+ ...modifiedContext.params,
58
+ ...modifications.params,
59
+ };
60
+ }
61
+ }
62
+ }
63
+ // Make the request with $fetch.raw to get full response
64
+ const response = await $fetch.raw(modifiedContext.url, {
65
+ method: modifiedContext.method,
66
+ headers: modifiedContext.headers,
67
+ body: modifiedContext.body,
68
+ params: modifiedContext.params,
69
+ ...restOptions,
70
+ });
71
+ // Extract data from response
72
+ let data = response._data;
73
+ // Apply pick if provided (only to data)
74
+ if (pick) {
75
+ data = applyPick(data, pick);
76
+ }
77
+ // Apply transform if provided (only to data)
78
+ if (transform) {
79
+ data = transform(data);
80
+ }
81
+ // Construct the raw response object
82
+ const rawResponse = {
83
+ data,
84
+ headers: response.headers,
85
+ status: response.status,
86
+ statusText: response.statusText,
87
+ };
88
+ // Call merged onSuccess callback with data and response context
89
+ if (mergedCallbacks.onSuccess) {
90
+ await mergedCallbacks.onSuccess(data, {
91
+ headers: response.headers,
92
+ status: response.status,
93
+ statusText: response.statusText,
94
+ url: finalUrl,
95
+ });
96
+ }
97
+ return rawResponse;
98
+ }
99
+ catch (error) {
100
+ // Call merged onError callback
101
+ if (mergedCallbacks.onError) {
102
+ await mergedCallbacks.onError(error, { url: finalUrl, method, headers });
103
+ }
104
+ throw error;
105
+ }
106
+ finally {
107
+ // Call merged onFinish callback
108
+ if (mergedCallbacks.onFinish) {
109
+ await mergedCallbacks.onFinish({
110
+ url: finalUrl,
111
+ method,
112
+ headers: { ...getGlobalHeaders(), ...headers },
113
+ });
114
+ }
115
+ }
116
+ };
117
+ // Use Nuxt's useAsyncData
118
+ const result = useAsyncData(key, fetchFn, {
119
+ immediate,
120
+ lazy,
121
+ server,
122
+ dedupe,
123
+ watch: watchSources.length > 0 ? watchSources : undefined,
124
+ });
125
+ return result;
126
+ }
@@ -0,0 +1,20 @@
1
+ import type { MethodInfo } from './types.js';
2
+ /**
3
+ * Options for code generation
4
+ */
5
+ export interface GenerateOptions {
6
+ baseUrl?: string;
7
+ backend?: string;
8
+ }
9
+ /**
10
+ * Generate a useAsyncData composable function
11
+ */
12
+ export declare function generateComposableFile(method: MethodInfo, apiImportPath: string, options?: GenerateOptions): string;
13
+ /**
14
+ * Generate a useAsyncData composable function (Raw version with headers)
15
+ */
16
+ export declare function generateRawComposableFile(method: MethodInfo, apiImportPath: string, options?: GenerateOptions): string;
17
+ /**
18
+ * Generate index.ts that exports all composables
19
+ */
20
+ export declare function generateIndexFile(composableNames: string[]): string;
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Generate file header with auto-generation warning
3
+ */
4
+ function generateFileHeader() {
5
+ return `/**
6
+ * ⚠️ AUTO-GENERATED FILE - DO NOT EDIT MANUALLY
7
+ *
8
+ * This file was automatically generated by nuxt-openapi-generator.
9
+ * Any manual changes will be overwritten on the next generation.
10
+ *
11
+ * @generated by nuxt-openapi-generator
12
+ * @see https://github.com/dmartindiaz/nuxt-openapi-hyperfetch
13
+ */
14
+
15
+ /* eslint-disable */
16
+ // @ts-nocheck
17
+ `;
18
+ }
19
+ /**
20
+ * Generate a useAsyncData composable function
21
+ */
22
+ export function generateComposableFile(method, apiImportPath, options) {
23
+ const header = generateFileHeader();
24
+ const imports = generateImports(method, apiImportPath, false);
25
+ const functionBody = generateFunctionBody(method, false, options);
26
+ return `${header}${imports}\n\n${functionBody}\n`;
27
+ }
28
+ /**
29
+ * Generate a useAsyncData composable function (Raw version with headers)
30
+ */
31
+ export function generateRawComposableFile(method, apiImportPath, options) {
32
+ const header = generateFileHeader();
33
+ const imports = generateImports(method, apiImportPath, true);
34
+ const functionBody = generateFunctionBody(method, true, options);
35
+ return `${header}${imports}\n\n${functionBody}\n`;
36
+ }
37
+ /**
38
+ * Extract base type names from a type string
39
+ * Examples:
40
+ * Pet[] -> Pet
41
+ * Array<Pet> -> Pet
42
+ * Pet -> Pet
43
+ * { [key: string]: Pet } -> (empty, it's anonymous)
44
+ */
45
+ function extractBaseTypes(type) {
46
+ if (!type) {
47
+ return [];
48
+ }
49
+ // Handle array syntax: Pet[]
50
+ const arrayMatch = type.match(/^(\w+)\[\]$/);
51
+ if (arrayMatch) {
52
+ return [arrayMatch[1]];
53
+ }
54
+ // Handle Array generic: Array<Pet>
55
+ const arrayGenericMatch = type.match(/^Array<(\w+)>$/);
56
+ if (arrayGenericMatch) {
57
+ return [arrayGenericMatch[1]];
58
+ }
59
+ // If it's a simple named type (single word, PascalCase), include it
60
+ if (/^[A-Z][a-zA-Z0-9]*$/.test(type)) {
61
+ return [type];
62
+ }
63
+ // For complex types, don't extract anything
64
+ return [];
65
+ }
66
+ /**
67
+ * Generate import statements
68
+ */
69
+ function generateImports(method, apiImportPath, isRaw) {
70
+ const typeNames = new Set();
71
+ // Extract base types from request type
72
+ if (method.requestType) {
73
+ const extracted = extractBaseTypes(method.requestType);
74
+ extracted.forEach((t) => typeNames.add(t));
75
+ }
76
+ // Extract base types from response type
77
+ if (method.responseType && method.responseType !== 'void') {
78
+ const extracted = extractBaseTypes(method.responseType);
79
+ extracted.forEach((t) => typeNames.add(t));
80
+ }
81
+ let imports = '';
82
+ // Import types from API (only if we have named types to import)
83
+ if (typeNames.size > 0) {
84
+ imports += `import type { ${Array.from(typeNames).join(', ')} } from '${apiImportPath}';\n`;
85
+ }
86
+ // Import runtime helper (normal or raw)
87
+ if (isRaw) {
88
+ imports += `import { useApiAsyncDataRaw, type ApiAsyncDataRawOptions, type RawResponse } from '../runtime/useApiAsyncDataRaw';`;
89
+ }
90
+ else {
91
+ imports += `import { useApiAsyncData, type ApiAsyncDataOptions } from '../runtime/useApiAsyncData';`;
92
+ }
93
+ return imports;
94
+ }
95
+ /**
96
+ * Generate the composable function body
97
+ */
98
+ function generateFunctionBody(method, isRaw, generateOptions) {
99
+ const hasParams = !!method.requestType;
100
+ const paramsArg = hasParams ? `params: MaybeRef<${method.requestType}>` : '';
101
+ // Determine the options type based on isRaw
102
+ const optionsType = isRaw
103
+ ? `ApiAsyncDataRawOptions<${method.responseType}>`
104
+ : `ApiAsyncDataOptions<${method.responseType}>`;
105
+ const optionsArg = `options?: ${optionsType}`;
106
+ const args = hasParams ? `${paramsArg}, ${optionsArg}` : optionsArg;
107
+ // Determine the response type generic
108
+ const responseTypeGeneric = method.responseType !== 'void' ? `<${method.responseType}>` : '';
109
+ // Generate unique key for useAsyncData
110
+ const composableName = isRaw && method.rawMethodName
111
+ ? `useAsyncData${method.rawMethodName.replace(/Raw$/, '')}Raw`
112
+ : method.composableName.replace(/^useFetch/, 'useAsyncData');
113
+ const key = `'${composableName}'`;
114
+ const url = generateUrl(method);
115
+ const fetchOptions = generateFetchOptions(method, generateOptions);
116
+ const description = method.description ? `/**\n * ${method.description}\n */\n` : '';
117
+ // Choose the correct wrapper function
118
+ const wrapperFunction = isRaw ? 'useApiAsyncDataRaw' : 'useApiAsyncData';
119
+ const pInit = hasParams ? `\n const p = isRef(params) ? params : shallowRef(params)` : '';
120
+ return `${description}export const ${composableName} = (${args}) => {${pInit}
121
+ return ${wrapperFunction}${responseTypeGeneric}(${key}, ${url}, ${fetchOptions})
122
+ }`;
123
+ }
124
+ /**
125
+ * Generate URL (with path params if needed)
126
+ */
127
+ function generateUrl(method) {
128
+ if (method.pathParams.length === 0) {
129
+ return `'${method.path}'`;
130
+ }
131
+ let url = method.path;
132
+ for (const param of method.pathParams) {
133
+ const accessor = method.paramsShape === 'nested' ? `p.value.path.${param}` : `p.value.${param}`;
134
+ url = url.replace(`{${param}}`, `\${${accessor}}`);
135
+ }
136
+ return `() => \`${url}\``;
137
+ }
138
+ /**
139
+ * Generate fetch options object
140
+ */
141
+ function generateFetchOptions(method, generateOptions) {
142
+ const options = [];
143
+ // Method
144
+ options.push(`method: '${method.httpMethod}'`);
145
+ // Base URL (if provided in config)
146
+ if (generateOptions?.baseUrl) {
147
+ options.push(`baseURL: '${generateOptions.baseUrl}'`);
148
+ }
149
+ // Body
150
+ if (method.hasBody) {
151
+ if (method.paramsShape === 'nested') {
152
+ options.push(`body: computed(() => p.value.body)`);
153
+ }
154
+ else if (method.bodyField) {
155
+ options.push(`body: computed(() => p.value.${method.bodyField})`);
156
+ }
157
+ }
158
+ // Query params (renamed to 'params' for $fetch)
159
+ if (method.hasQueryParams) {
160
+ if (method.paramsShape === 'nested') {
161
+ options.push(`params: computed(() => p.value.query)`);
162
+ }
163
+ else if (method.queryParams.length > 0) {
164
+ const queryObj = method.queryParams
165
+ .map((param) => `${param}: p.value.${param}`)
166
+ .join(',\n ');
167
+ options.push(`params: computed(() => ({\n ${queryObj}\n }))`);
168
+ }
169
+ }
170
+ // Headers
171
+ if (Object.keys(method.headers).length > 0) {
172
+ const headersEntries = Object.entries(method.headers)
173
+ .map(([key, value]) => `'${key}': '${value}'`)
174
+ .join(',\n ');
175
+ options.push(`headers: {\n ${headersEntries},\n ...options?.headers\n }`);
176
+ }
177
+ // Spread options
178
+ options.push('...options');
179
+ const optionsStr = options.join(',\n ');
180
+ return `{\n ${optionsStr}\n }`;
181
+ }
182
+ /**
183
+ * Generate index.ts that exports all composables
184
+ */
185
+ export function generateIndexFile(composableNames) {
186
+ const header = generateFileHeader();
187
+ const exports = composableNames
188
+ .map((name) => `export { ${name} } from './composables/${name}'`)
189
+ .join('\n');
190
+ return `${header}${exports}\n`;
191
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Re-export all types from shared types
3
+ */
4
+ export * from '../shared/types.js';
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Re-export all types from shared types
3
+ */
4
+ export * from '../shared/types.js';
@@ -0,0 +1,5 @@
1
+ import { type GenerateOptions } from './templates.js';
2
+ /**
3
+ * Main function to generate useFetch composables
4
+ */
5
+ export declare function generateUseFetchComposables(inputDir: string, outputDir: string, options?: GenerateOptions): Promise<void>;
@@ -0,0 +1,131 @@
1
+ import * as path from 'path';
2
+ import fs from 'fs-extra';
3
+ import { fileURLToPath } from 'url';
4
+ import { format } from 'prettier';
5
+ import { getApiFiles as getApiFilesOfficial, parseApiFile as parseApiFileOfficial, } from './parser.js';
6
+ import { getApiFiles as getApiFilesHeyApi, parseApiFile as parseApiFileHeyApi, } from '../shared/parsers/heyapi-parser.js';
7
+ import { generateComposableFile, generateIndexFile } from './templates.js';
8
+ import { p, logSuccess, logError } from '../../cli/logger.js';
9
+ /**
10
+ * Main function to generate useFetch composables
11
+ */
12
+ export async function generateUseFetchComposables(inputDir, outputDir, 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
+ // 1. Get all API files
18
+ mainSpinner.start('Scanning API files');
19
+ const apiFiles = getApiFiles(inputDir);
20
+ mainSpinner.stop(`Found ${apiFiles.length} API file(s)`);
21
+ if (apiFiles.length === 0) {
22
+ throw new Error('No API files found in the input directory');
23
+ }
24
+ // 2. Parse each API file
25
+ mainSpinner.start('Parsing API files');
26
+ const allMethods = [];
27
+ for (const file of apiFiles) {
28
+ const fileName = path.basename(file);
29
+ try {
30
+ const apiInfo = parseApiFile(file);
31
+ allMethods.push(...apiInfo.methods);
32
+ }
33
+ catch (error) {
34
+ logError(`Error parsing ${fileName}: ${String(error)}`);
35
+ }
36
+ }
37
+ mainSpinner.stop(`Found ${allMethods.length} methods to generate`);
38
+ if (allMethods.length === 0) {
39
+ p.log.warn('No methods found to generate');
40
+ return;
41
+ }
42
+ // 3. Clean and create output directories
43
+ mainSpinner.start('Preparing output directories');
44
+ const composablesDir = path.join(outputDir, 'composables');
45
+ const runtimeDir = path.join(outputDir, 'runtime');
46
+ const sharedRuntimeDir = path.join(outputDir, 'shared', 'runtime');
47
+ await fs.emptyDir(composablesDir);
48
+ await fs.ensureDir(runtimeDir);
49
+ await fs.ensureDir(sharedRuntimeDir);
50
+ mainSpinner.stop('Output directories ready');
51
+ // 4. Copy runtime helpers
52
+ mainSpinner.start('Copying runtime files');
53
+ // Derive __dirname equivalent for ESM
54
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
55
+ // When compiled, __dirname points to dist/generators/use-fetch/
56
+ // We need to go back to src/ to get the original .ts files
57
+ // Copy useApiRequest.ts
58
+ const runtimeSource = path.resolve(__dirname, '../../../src/generators/use-fetch/runtime/useApiRequest.ts');
59
+ const runtimeDest = path.join(runtimeDir, 'useApiRequest.ts');
60
+ await fs.copyFile(runtimeSource, runtimeDest);
61
+ // Copy shared apiHelpers.ts
62
+ const sharedHelpersSource = path.resolve(__dirname, '../../../src/generators/shared/runtime/apiHelpers.ts');
63
+ const sharedHelpersDest = path.join(sharedRuntimeDir, 'apiHelpers.ts');
64
+ await fs.copyFile(sharedHelpersSource, sharedHelpersDest);
65
+ mainSpinner.stop('Runtime files copied');
66
+ // 5. Calculate relative import path from composables to APIs
67
+ const relativePath = calculateRelativeImportPath(composablesDir, inputDir);
68
+ // 6. Generate each composable
69
+ mainSpinner.start('Generating composables');
70
+ let successCount = 0;
71
+ let errorCount = 0;
72
+ for (const method of allMethods) {
73
+ try {
74
+ const code = generateComposableFile(method, relativePath, options);
75
+ const formattedCode = await formatCode(code);
76
+ const fileName = `${method.composableName}.ts`;
77
+ const filePath = path.join(composablesDir, fileName);
78
+ await fs.writeFile(filePath, formattedCode, 'utf-8');
79
+ successCount++;
80
+ }
81
+ catch (error) {
82
+ logError(`Error generating ${method.composableName}: ${String(error)}`);
83
+ errorCount++;
84
+ }
85
+ }
86
+ // 7. Generate index.ts
87
+ const indexCode = generateIndexFile(allMethods.map((m) => m.composableName));
88
+ const formattedIndex = await formatCode(indexCode);
89
+ await fs.writeFile(path.join(outputDir, 'index.ts'), formattedIndex, 'utf-8');
90
+ mainSpinner.stop(`Generated ${successCount} composables`);
91
+ // 8. Summary
92
+ if (errorCount > 0) {
93
+ p.log.warn(`Completed with ${errorCount} error(s)`);
94
+ }
95
+ logSuccess(`Generated ${successCount} useFetch composable(s) in ${outputDir}`);
96
+ }
97
+ /**
98
+ * Calculate relative import path from composables to APIs
99
+ */
100
+ function calculateRelativeImportPath(composablesDir, inputDir) {
101
+ // Import from the root index.ts which exports apis, models, and runtime
102
+ let relativePath = path.relative(composablesDir, inputDir);
103
+ // Convert Windows paths to Unix-style
104
+ relativePath = relativePath.replace(/\\/g, '/');
105
+ // Ensure it starts with './' or '../'
106
+ if (!relativePath.startsWith('.')) {
107
+ relativePath = './' + relativePath;
108
+ }
109
+ // Remove .ts extension and trailing /
110
+ relativePath = relativePath.replace(/\.ts$/, '').replace(/\/$/, '');
111
+ return relativePath;
112
+ }
113
+ /**
114
+ * Format code with Prettier
115
+ */
116
+ async function formatCode(code) {
117
+ try {
118
+ return await format(code, {
119
+ parser: 'typescript',
120
+ semi: true,
121
+ singleQuote: true,
122
+ trailingComma: 'es5',
123
+ printWidth: 80,
124
+ tabWidth: 2,
125
+ });
126
+ }
127
+ catch {
128
+ p.log.warn('Could not format code with Prettier');
129
+ return code;
130
+ }
131
+ }
@@ -0,0 +1,9 @@
1
+ import type { ApiClassInfo } from './types.js';
2
+ /**
3
+ * Get all API files from the input directory
4
+ */
5
+ export declare function getApiFiles(inputDir: string): string[];
6
+ /**
7
+ * Parse an API file and extract all methods
8
+ */
9
+ export declare function parseApiFile(filePath: string): ApiClassInfo;