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,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,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;
|