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,220 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Nuxt Runtime Helper - This file is copied to the generated output
4
+ * It requires Nuxt 3 to be installed in the target project
5
+ */
6
+ import { watch } from 'vue';
7
+ import {
8
+ getGlobalHeaders,
9
+ applyPick,
10
+ applyRequestModifications,
11
+ mergeCallbacks,
12
+ type RequestContext,
13
+ type ModifiedRequestContext,
14
+ type FinishContext,
15
+ type ApiRequestOptions as BaseApiRequestOptions,
16
+ } from '../../shared/runtime/apiHelpers.js';
17
+
18
+ /**
19
+ * Helper type to infer transformed data type
20
+ * If transform is provided, infer its return type
21
+ * If pick is provided, return partial object (type inference for nested paths is complex)
22
+ * Otherwise, return original type
23
+ */
24
+ type MaybeTransformed<T, Options> = Options extends { transform: (...args: any) => infer R }
25
+ ? R
26
+ : Options extends { pick: ReadonlyArray<any> }
27
+ ? any // With nested paths, type inference is complex, so we use any
28
+ : T;
29
+
30
+ /**
31
+ * Extended options specific to useAsyncData
32
+ */
33
+ export interface ApiAsyncDataOptions<T> extends BaseApiRequestOptions<T> {
34
+ /**
35
+ * Whether to fetch data immediately on mount (default: true)
36
+ */
37
+ immediate?: boolean;
38
+
39
+ /**
40
+ * Lazy mode: don't block navigation (default: false)
41
+ */
42
+ lazy?: boolean;
43
+
44
+ /**
45
+ * Server-side rendering mode (default: true)
46
+ */
47
+ server?: boolean;
48
+
49
+ /**
50
+ * Deduplicate requests with the same key (default: 'cancel')
51
+ */
52
+ dedupe?: 'cancel' | 'defer';
53
+
54
+ /**
55
+ * Disable automatic refresh when reactive params change.
56
+ * Set to false to prevent re-fetching when params/url refs update.
57
+ * @default true
58
+ */
59
+ watch?: boolean;
60
+ }
61
+
62
+ /**
63
+ * Generic wrapper for API calls using Nuxt's useAsyncData
64
+ * Supports:
65
+ * - Lifecycle callbacks (onRequest, onSuccess, onError, onFinish)
66
+ * - Request modification via onRequest return value
67
+ * - Transform and pick operations
68
+ * - Global headers from useApiHeaders or $getApiHeaders
69
+ * - Watch pattern for reactive parameters
70
+ */
71
+ export function useApiAsyncData<T>(
72
+ key: string,
73
+ url: string | (() => string),
74
+ options?: ApiAsyncDataOptions<T>
75
+ ) {
76
+ const {
77
+ method = 'GET',
78
+ body,
79
+ headers = {},
80
+ params,
81
+ transform,
82
+ pick,
83
+ onRequest,
84
+ onSuccess,
85
+ onError,
86
+ onFinish,
87
+ skipGlobalCallbacks,
88
+ immediate = true,
89
+ lazy = false,
90
+ server = true,
91
+ dedupe = 'cancel',
92
+ watch: watchOption = true,
93
+ ...restOptions
94
+ } = options || {};
95
+
96
+ // Create reactive watch sources — use refs/computeds directly so Vue can track them
97
+ // watchOption: false disables auto-refresh entirely
98
+ const watchSources =
99
+ watchOption === false
100
+ ? []
101
+ : [
102
+ ...(typeof url === 'function' ? [url] : []),
103
+ ...(body ? (isRef(body) ? [body] : typeof body === 'object' ? [() => body] : []) : []),
104
+ ...(params
105
+ ? isRef(params)
106
+ ? [params]
107
+ : typeof params === 'object'
108
+ ? [() => params]
109
+ : []
110
+ : []),
111
+ ];
112
+
113
+ // Fetch function for useAsyncData
114
+ const fetchFn = async () => {
115
+ // Get URL value for merging callbacks
116
+ const finalUrl = typeof url === 'function' ? url() : url;
117
+
118
+ // Merge local and global callbacks
119
+ const mergedCallbacks = mergeCallbacks(
120
+ finalUrl,
121
+ { onRequest, onSuccess, onError, onFinish },
122
+ skipGlobalCallbacks
123
+ );
124
+
125
+ try {
126
+ // Get global headers
127
+ const globalHeaders = getGlobalHeaders();
128
+
129
+ // Prepare request context
130
+ const requestContext: RequestContext = {
131
+ url: finalUrl,
132
+ method: method as any,
133
+ headers: { ...globalHeaders, ...headers },
134
+ body,
135
+ params,
136
+ };
137
+
138
+ // Execute merged onRequest callback and potentially modify request
139
+ const modifiedContext = { ...requestContext };
140
+ if (mergedCallbacks.onRequest) {
141
+ const result = await mergedCallbacks.onRequest(requestContext);
142
+ // If onRequest returns modifications, apply them
143
+ if (result && typeof result === 'object') {
144
+ const modifications = result as ModifiedRequestContext;
145
+ if (modifications.body !== undefined) {
146
+ modifiedContext.body = modifications.body;
147
+ }
148
+ if (modifications.headers !== undefined) {
149
+ modifiedContext.headers = {
150
+ ...modifiedContext.headers,
151
+ ...modifications.headers,
152
+ };
153
+ }
154
+ if (modifications.params !== undefined) {
155
+ modifiedContext.params = {
156
+ ...modifiedContext.params,
157
+ ...modifications.params,
158
+ };
159
+ }
160
+ }
161
+ }
162
+
163
+ // Make the request with $fetch — toValue() unrefs any Ref/ComputedRef
164
+ let data = await $fetch<T>(modifiedContext.url, {
165
+ method: modifiedContext.method,
166
+ headers: modifiedContext.headers,
167
+ body: toValue(modifiedContext.body),
168
+ params: toValue(modifiedContext.params),
169
+ ...restOptions,
170
+ });
171
+
172
+ // Apply pick if provided
173
+ if (pick) {
174
+ data = applyPick(data, pick) as T;
175
+ }
176
+
177
+ // Apply transform if provided
178
+ if (transform) {
179
+ data = transform(data);
180
+ }
181
+
182
+ // Call merged onSuccess callback
183
+ if (mergedCallbacks.onSuccess) {
184
+ await mergedCallbacks.onSuccess(data, {
185
+ url: finalUrl,
186
+ method,
187
+ headers: modifiedContext.headers,
188
+ });
189
+ }
190
+
191
+ return data;
192
+ } catch (error: any) {
193
+ // Call merged onError callback
194
+ if (mergedCallbacks.onError) {
195
+ await mergedCallbacks.onError(error, { url: finalUrl, method, headers });
196
+ }
197
+ throw error;
198
+ } finally {
199
+ // Call merged onFinish callback
200
+ if (mergedCallbacks.onFinish) {
201
+ await mergedCallbacks.onFinish({
202
+ url: finalUrl,
203
+ method,
204
+ headers: { ...getGlobalHeaders(), ...headers },
205
+ });
206
+ }
207
+ }
208
+ };
209
+
210
+ // Use Nuxt's useAsyncData
211
+ const result = useAsyncData<MaybeTransformed<T, ApiAsyncDataOptions<T>>>(key, fetchFn, {
212
+ immediate,
213
+ lazy,
214
+ server,
215
+ dedupe,
216
+ watch: watchSources.length > 0 ? watchSources : undefined,
217
+ });
218
+
219
+ return result;
220
+ }
@@ -0,0 +1,236 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Nuxt Runtime Helper - This file is copied to the generated output
4
+ * It requires Nuxt 3 to be installed in the target project
5
+ *
6
+ * RAW VERSION: Returns full response including headers, status, and statusText
7
+ */
8
+ import { watch } from 'vue';
9
+ import {
10
+ getGlobalHeaders,
11
+ applyPick,
12
+ applyRequestModifications,
13
+ mergeCallbacks,
14
+ type RequestContext,
15
+ type ModifiedRequestContext,
16
+ type FinishContext,
17
+ type ApiRequestOptions as BaseApiRequestOptions,
18
+ } from '../../shared/runtime/apiHelpers.js';
19
+
20
+ /**
21
+ * Response structure for Raw version
22
+ * Includes data, headers, status, and statusText
23
+ */
24
+ export interface RawResponse<T> {
25
+ data: T;
26
+ headers: Headers;
27
+ status: number;
28
+ statusText: string;
29
+ }
30
+
31
+ /**
32
+ * Helper type to infer transformed data type for Raw responses
33
+ * Transform only applies to the data property, not the entire response
34
+ */
35
+ type MaybeTransformedRaw<T, Options> = Options extends { transform: (...args: any) => infer R }
36
+ ? RawResponse<R>
37
+ : Options extends { pick: ReadonlyArray<any> }
38
+ ? RawResponse<any> // With nested paths, type inference is complex
39
+ : RawResponse<T>;
40
+
41
+ /**
42
+ * Extended options specific to useAsyncData Raw version
43
+ */
44
+ export interface ApiAsyncDataRawOptions<T> extends Omit<BaseApiRequestOptions<T>, 'onSuccess'> {
45
+ /**
46
+ * Success callback that receives both data and full response
47
+ */
48
+ onSuccess?: (
49
+ data: T,
50
+ response: { headers: Headers; status: number; statusText: string; url: string }
51
+ ) => void | Promise<void>;
52
+
53
+ /**
54
+ * Whether to fetch data immediately on mount (default: true)
55
+ */
56
+ immediate?: boolean;
57
+
58
+ /**
59
+ * Lazy mode: don't block navigation (default: false)
60
+ */
61
+ lazy?: boolean;
62
+
63
+ /**
64
+ * Server-side rendering mode (default: true)
65
+ */
66
+ server?: boolean;
67
+
68
+ /**
69
+ * Deduplicate requests with the same key (default: 'cancel')
70
+ */
71
+ dedupe?: 'cancel' | 'defer';
72
+ }
73
+
74
+ /**
75
+ * Generic wrapper for API calls using Nuxt's useAsyncData - RAW VERSION
76
+ * Returns full response with headers and status information
77
+ *
78
+ * Supports:
79
+ * - Lifecycle callbacks (onRequest, onSuccess with response, onError, onFinish)
80
+ * - Request modification via onRequest return value
81
+ * - Transform (applies only to data, not full response)
82
+ * - Pick operations (applies only to data)
83
+ * - Global headers from useApiHeaders or $getApiHeaders
84
+ * - Watch pattern for reactive parameters
85
+ */
86
+ export function useApiAsyncDataRaw<T>(
87
+ key: string,
88
+ url: string | (() => string),
89
+ options?: ApiAsyncDataRawOptions<T>
90
+ ) {
91
+ const {
92
+ method = 'GET',
93
+ body,
94
+ headers = {},
95
+ params,
96
+ transform,
97
+ pick,
98
+ onRequest,
99
+ onSuccess,
100
+ onError,
101
+ onFinish,
102
+ skipGlobalCallbacks,
103
+ immediate = true,
104
+ lazy = false,
105
+ server = true,
106
+ dedupe = 'cancel',
107
+ ...restOptions
108
+ } = options || {};
109
+
110
+ // Create reactive watch sources for callbacks
111
+ const watchSources = [
112
+ ...(typeof url === 'function' ? [url] : []),
113
+ ...(body && typeof body === 'object' ? [() => body] : []),
114
+ ...(params && typeof params === 'object' ? [() => params] : []),
115
+ ];
116
+
117
+ // Fetch function for useAsyncData
118
+ const fetchFn = async (): Promise<RawResponse<T>> => {
119
+ // Get URL value for merging callbacks
120
+ const finalUrl = typeof url === 'function' ? url() : url;
121
+
122
+ // Merge local and global callbacks
123
+ const mergedCallbacks = mergeCallbacks(
124
+ finalUrl,
125
+ { onRequest, onSuccess, onError, onFinish },
126
+ skipGlobalCallbacks
127
+ );
128
+
129
+ try {
130
+ // Get global headers
131
+ const globalHeaders = getGlobalHeaders();
132
+
133
+ // Prepare request context
134
+ const requestContext: RequestContext = {
135
+ url: finalUrl,
136
+ method: method as any,
137
+ headers: { ...globalHeaders, ...headers },
138
+ body,
139
+ params,
140
+ };
141
+
142
+ // Execute merged onRequest callback and potentially modify request
143
+ const modifiedContext = { ...requestContext };
144
+ if (mergedCallbacks.onRequest) {
145
+ const result = await mergedCallbacks.onRequest(requestContext);
146
+ // If onRequest returns modifications, apply them
147
+ if (result && typeof result === 'object') {
148
+ const modifications = result as ModifiedRequestContext;
149
+ if (modifications.body !== undefined) {
150
+ modifiedContext.body = modifications.body;
151
+ }
152
+ if (modifications.headers !== undefined) {
153
+ modifiedContext.headers = {
154
+ ...modifiedContext.headers,
155
+ ...modifications.headers,
156
+ };
157
+ }
158
+ if (modifications.params !== undefined) {
159
+ modifiedContext.params = {
160
+ ...modifiedContext.params,
161
+ ...modifications.params,
162
+ };
163
+ }
164
+ }
165
+ }
166
+
167
+ // Make the request with $fetch.raw to get full response
168
+ const response = await $fetch.raw<T>(modifiedContext.url, {
169
+ method: modifiedContext.method,
170
+ headers: modifiedContext.headers,
171
+ body: modifiedContext.body,
172
+ params: modifiedContext.params,
173
+ ...restOptions,
174
+ });
175
+
176
+ // Extract data from response
177
+ let data = response._data as T;
178
+
179
+ // Apply pick if provided (only to data)
180
+ if (pick) {
181
+ data = applyPick(data, pick) as T;
182
+ }
183
+
184
+ // Apply transform if provided (only to data)
185
+ if (transform) {
186
+ data = transform(data);
187
+ }
188
+
189
+ // Construct the raw response object
190
+ const rawResponse: RawResponse<T> = {
191
+ data,
192
+ headers: response.headers,
193
+ status: response.status,
194
+ statusText: response.statusText,
195
+ };
196
+
197
+ // Call merged onSuccess callback with data and response context
198
+ if (mergedCallbacks.onSuccess) {
199
+ await mergedCallbacks.onSuccess(data, {
200
+ headers: response.headers,
201
+ status: response.status,
202
+ statusText: response.statusText,
203
+ url: finalUrl,
204
+ });
205
+ }
206
+
207
+ return rawResponse;
208
+ } catch (error: any) {
209
+ // Call merged onError callback
210
+ if (mergedCallbacks.onError) {
211
+ await mergedCallbacks.onError(error, { url: finalUrl, method, headers });
212
+ }
213
+ throw error;
214
+ } finally {
215
+ // Call merged onFinish callback
216
+ if (mergedCallbacks.onFinish) {
217
+ await mergedCallbacks.onFinish({
218
+ url: finalUrl,
219
+ method,
220
+ headers: { ...getGlobalHeaders(), ...headers },
221
+ });
222
+ }
223
+ }
224
+ };
225
+
226
+ // Use Nuxt's useAsyncData
227
+ const result = useAsyncData<MaybeTransformedRaw<T, ApiAsyncDataRawOptions<T>>>(key, fetchFn, {
228
+ immediate,
229
+ lazy,
230
+ server,
231
+ dedupe,
232
+ watch: watchSources.length > 0 ? watchSources : undefined,
233
+ });
234
+
235
+ return result;
236
+ }
@@ -0,0 +1,250 @@
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: MaybeRef<${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 = isRef(params) ? params : shallowRef(params)` : '';
167
+
168
+ return `${description}export const ${composableName} = (${args}) => {${pInit}
169
+ return ${wrapperFunction}${responseTypeGeneric}(${key}, ${url}, ${fetchOptions})
170
+ }`;
171
+ }
172
+
173
+ /**
174
+ * Generate URL (with path params if needed)
175
+ */
176
+ function generateUrl(method: MethodInfo): string {
177
+ if (method.pathParams.length === 0) {
178
+ return `'${method.path}'`;
179
+ }
180
+
181
+ let url = method.path;
182
+ for (const param of method.pathParams) {
183
+ const accessor = method.paramsShape === 'nested' ? `p.value.path.${param}` : `p.value.${param}`;
184
+ url = url.replace(`{${param}}`, `\${${accessor}}`);
185
+ }
186
+
187
+ return `() => \`${url}\``;
188
+ }
189
+
190
+ /**
191
+ * Generate fetch options object
192
+ */
193
+ function generateFetchOptions(method: MethodInfo, generateOptions?: GenerateOptions): string {
194
+ const options: string[] = [];
195
+
196
+ // Method
197
+ options.push(`method: '${method.httpMethod}'`);
198
+
199
+ // Base URL (if provided in config)
200
+ if (generateOptions?.baseUrl) {
201
+ options.push(`baseURL: '${generateOptions.baseUrl}'`);
202
+ }
203
+
204
+ // Body
205
+ if (method.hasBody) {
206
+ if (method.paramsShape === 'nested') {
207
+ options.push(`body: computed(() => p.value.body)`);
208
+ } else if (method.bodyField) {
209
+ options.push(`body: computed(() => p.value.${method.bodyField})`);
210
+ }
211
+ }
212
+
213
+ // Query params (renamed to 'params' for $fetch)
214
+ if (method.hasQueryParams) {
215
+ if (method.paramsShape === 'nested') {
216
+ options.push(`params: computed(() => p.value.query)`);
217
+ } else if (method.queryParams.length > 0) {
218
+ const queryObj = method.queryParams
219
+ .map((param) => `${param}: p.value.${param}`)
220
+ .join(',\n ');
221
+ options.push(`params: computed(() => ({\n ${queryObj}\n }))`);
222
+ }
223
+ }
224
+
225
+ // Headers
226
+ if (Object.keys(method.headers).length > 0) {
227
+ const headersEntries = Object.entries(method.headers)
228
+ .map(([key, value]) => `'${key}': '${value}'`)
229
+ .join(',\n ');
230
+ options.push(`headers: {\n ${headersEntries},\n ...options?.headers\n }`);
231
+ }
232
+
233
+ // Spread options
234
+ options.push('...options');
235
+
236
+ const optionsStr = options.join(',\n ');
237
+ return `{\n ${optionsStr}\n }`;
238
+ }
239
+
240
+ /**
241
+ * Generate index.ts that exports all composables
242
+ */
243
+ export function generateIndexFile(composableNames: string[]): string {
244
+ const header = generateFileHeader();
245
+ const exports = composableNames
246
+ .map((name) => `export { ${name} } from './composables/${name}'`)
247
+ .join('\n');
248
+
249
+ return `${header}${exports}\n`;
250
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Re-export all types from shared types
3
+ */
4
+ export * from '../shared/types.js';