nuxt-openapi-hyperfetch 0.1.6-alpha.1 → 0.2.7-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/CONTRIBUTING.md +291 -292
- package/INSTRUCTIONS.md +327 -327
- package/LICENSE +202 -202
- package/README.md +231 -227
- package/dist/cli/logger.d.ts +26 -0
- package/dist/cli/logger.js +36 -0
- package/dist/cli/logo.js +6 -6
- package/dist/generators/components/connector-generator/generator.d.ts +12 -0
- package/dist/generators/components/connector-generator/generator.js +116 -0
- package/dist/generators/components/connector-generator/templates.d.ts +18 -0
- package/dist/generators/components/connector-generator/templates.js +222 -0
- package/dist/generators/components/connector-generator/types.d.ts +32 -0
- package/dist/generators/components/connector-generator/types.js +7 -0
- package/dist/generators/components/schema-analyzer/index.d.ts +17 -0
- package/dist/generators/components/schema-analyzer/index.js +20 -0
- package/dist/generators/components/schema-analyzer/intent-detector.d.ts +17 -0
- package/dist/generators/components/schema-analyzer/intent-detector.js +143 -0
- package/dist/generators/components/schema-analyzer/openapi-reader.d.ts +11 -0
- package/dist/generators/components/schema-analyzer/openapi-reader.js +76 -0
- package/dist/generators/components/schema-analyzer/resource-grouper.d.ts +6 -0
- package/dist/generators/components/schema-analyzer/resource-grouper.js +132 -0
- package/dist/generators/components/schema-analyzer/schema-field-mapper.d.ts +35 -0
- package/dist/generators/components/schema-analyzer/schema-field-mapper.js +220 -0
- package/dist/generators/components/schema-analyzer/types.d.ts +156 -0
- package/dist/generators/components/schema-analyzer/types.js +7 -0
- package/dist/generators/nuxt-server/generator.d.ts +2 -1
- package/dist/generators/nuxt-server/generator.js +21 -21
- package/dist/generators/shared/runtime/apiHelpers.d.ts +98 -41
- package/dist/generators/shared/runtime/apiHelpers.js +97 -104
- package/dist/generators/shared/runtime/pagination.d.ts +168 -0
- package/dist/generators/shared/runtime/pagination.js +179 -0
- package/dist/generators/shared/runtime/useDeleteConnector.d.ts +16 -0
- package/dist/generators/shared/runtime/useDeleteConnector.js +93 -0
- package/dist/generators/shared/runtime/useDetailConnector.d.ts +14 -0
- package/dist/generators/shared/runtime/useDetailConnector.js +50 -0
- package/dist/generators/shared/runtime/useFormConnector.d.ts +19 -0
- package/dist/generators/shared/runtime/useFormConnector.js +113 -0
- package/dist/generators/shared/runtime/useListConnector.d.ts +25 -0
- package/dist/generators/shared/runtime/useListConnector.js +125 -0
- package/dist/generators/shared/runtime/zod-error-merger.d.ts +23 -0
- package/dist/generators/shared/runtime/zod-error-merger.js +106 -0
- package/dist/generators/shared/templates/api-callbacks-plugin.js +54 -11
- package/dist/generators/shared/templates/api-pagination-plugin.d.ts +51 -0
- package/dist/generators/shared/templates/api-pagination-plugin.js +152 -0
- package/dist/generators/use-async-data/generator.d.ts +2 -1
- package/dist/generators/use-async-data/generator.js +14 -14
- package/dist/generators/use-async-data/runtime/useApiAsyncData.js +130 -17
- package/dist/generators/use-async-data/runtime/useApiAsyncDataRaw.js +103 -13
- package/dist/generators/use-async-data/templates.js +20 -14
- package/dist/generators/use-fetch/generator.d.ts +2 -1
- package/dist/generators/use-fetch/generator.js +12 -12
- package/dist/generators/use-fetch/runtime/useApiRequest.js +149 -40
- package/dist/generators/use-fetch/templates.js +14 -14
- package/dist/index.js +25 -0
- package/dist/module/index.d.ts +4 -0
- package/dist/module/index.js +93 -0
- package/dist/module/types.d.ts +27 -0
- package/dist/module/types.js +1 -0
- package/docs/API-REFERENCE.md +886 -887
- package/docs/generated-components.md +615 -0
- package/docs/headless-composables-ui.md +569 -0
- package/eslint.config.js +13 -0
- package/package.json +29 -2
- package/src/cli/config.ts +140 -140
- package/src/cli/logger.ts +124 -66
- package/src/cli/logo.ts +25 -25
- package/src/cli/types.ts +50 -50
- package/src/generators/components/connector-generator/generator.ts +138 -0
- package/src/generators/components/connector-generator/templates.ts +254 -0
- package/src/generators/components/connector-generator/types.ts +34 -0
- package/src/generators/components/schema-analyzer/index.ts +44 -0
- package/src/generators/components/schema-analyzer/intent-detector.ts +187 -0
- package/src/generators/components/schema-analyzer/openapi-reader.ts +96 -0
- package/src/generators/components/schema-analyzer/resource-grouper.ts +166 -0
- package/src/generators/components/schema-analyzer/schema-field-mapper.ts +268 -0
- package/src/generators/components/schema-analyzer/types.ts +177 -0
- package/src/generators/nuxt-server/generator.ts +272 -270
- package/src/generators/shared/runtime/apiHelpers.ts +535 -481
- package/src/generators/shared/runtime/pagination.ts +323 -0
- package/src/generators/shared/runtime/useDeleteConnector.ts +109 -0
- package/src/generators/shared/runtime/useDetailConnector.ts +64 -0
- package/src/generators/shared/runtime/useFormConnector.ts +139 -0
- package/src/generators/shared/runtime/useListConnector.ts +148 -0
- package/src/generators/shared/runtime/zod-error-merger.ts +119 -0
- package/src/generators/shared/templates/api-callbacks-plugin.ts +399 -352
- package/src/generators/shared/templates/api-pagination-plugin.ts +158 -0
- package/src/generators/use-async-data/generator.ts +205 -204
- package/src/generators/use-async-data/runtime/useApiAsyncData.ts +329 -214
- package/src/generators/use-async-data/runtime/useApiAsyncDataRaw.ts +324 -230
- package/src/generators/use-async-data/templates.ts +257 -250
- package/src/generators/use-fetch/generator.ts +170 -169
- package/src/generators/use-fetch/runtime/useApiRequest.ts +354 -234
- package/src/generators/use-fetch/templates.ts +214 -214
- package/src/index.ts +303 -265
- package/src/module/index.ts +133 -0
- package/src/module/types.ts +31 -0
- package/src/generators/tanstack-query/generator.ts +0 -11
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
* Nuxt Runtime Helper - This file is copied to the generated output
|
|
4
4
|
* It requires Nuxt 3 to be installed in the target project
|
|
5
5
|
*/
|
|
6
|
-
import { watch } from 'vue';
|
|
6
|
+
import { watch, ref, computed } from 'vue';
|
|
7
7
|
import { getGlobalHeaders, getGlobalBaseUrl, applyPick, applyRequestModifications, mergeCallbacks, } from '../../shared/runtime/apiHelpers.js';
|
|
8
|
+
import { getGlobalApiPagination, buildPaginationRequest, extractPaginationMetaFromBody, extractPaginationMetaFromHeaders, unwrapDataKey, } from '../../shared/runtime/pagination.js';
|
|
8
9
|
/**
|
|
9
10
|
* Enhanced useFetch wrapper with lifecycle callbacks and request interception
|
|
10
11
|
*
|
|
@@ -42,18 +43,26 @@ import { getGlobalHeaders, getGlobalBaseUrl, applyPick, applyRequestModification
|
|
|
42
43
|
* ```
|
|
43
44
|
*/
|
|
44
45
|
export function useApiRequest(url, options) {
|
|
45
|
-
const { onRequest, onSuccess, onError, onFinish, skipGlobalCallbacks, transform, pick, ...fetchOptions } = options || {};
|
|
46
|
-
//
|
|
46
|
+
const { onRequest, onSuccess, onError, onFinish, skipGlobalCallbacks, transform, pick, paginated, initialPage, initialPerPage, paginationConfig, ...fetchOptions } = options || {};
|
|
47
|
+
// Resolve URL value for callbacks and pattern matching
|
|
47
48
|
const urlValue = typeof url === 'function' ? url() : url;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// Pagination setup
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
const activePaginationConfig = paginationConfig ?? (paginated ? getGlobalApiPagination() : null);
|
|
53
|
+
const page = ref(initialPage ?? activePaginationConfig?.request.defaults.page ?? 1);
|
|
54
|
+
const perPage = ref(initialPerPage ?? activePaginationConfig?.request.defaults.perPage ?? 20);
|
|
55
|
+
// Reactive pagination state (populated after response)
|
|
56
|
+
const paginationState = ref({
|
|
57
|
+
currentPage: page.value,
|
|
58
|
+
totalPages: 0,
|
|
59
|
+
total: 0,
|
|
60
|
+
perPage: perPage.value,
|
|
61
|
+
});
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
55
63
|
// Merge local and global callbacks
|
|
56
|
-
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
const mergedCallbacks = mergeCallbacks(urlValue, String(fetchOptions.method || 'GET'), { onRequest, onSuccess, onError, onFinish }, skipGlobalCallbacks);
|
|
57
66
|
// Apply global headers configuration (from composable or plugin)
|
|
58
67
|
const modifiedOptions = { ...fetchOptions };
|
|
59
68
|
const globalHeaders = getGlobalHeaders();
|
|
@@ -70,30 +79,82 @@ export function useApiRequest(url, options) {
|
|
|
70
79
|
if (!modifiedOptions.baseURL) {
|
|
71
80
|
console.warn('[nuxt-openapi-hyperfetch] No baseURL configured. Set runtimeConfig.public.apiBaseUrl in nuxt.config.ts or pass baseURL in options.');
|
|
72
81
|
}
|
|
73
|
-
//
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
// Inject pagination request params before every request
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
if (paginated && activePaginationConfig) {
|
|
86
|
+
const existingOnRequestForPagination = modifiedOptions.onRequest;
|
|
87
|
+
modifiedOptions.onRequest = async (ctx) => {
|
|
88
|
+
// Inject page/perPage according to sendAs strategy
|
|
89
|
+
const paginationPayload = buildPaginationRequest(page.value, perPage.value, activePaginationConfig);
|
|
90
|
+
if (paginationPayload.query) {
|
|
91
|
+
ctx.options.query = { ...ctx.options.query, ...paginationPayload.query };
|
|
92
|
+
}
|
|
93
|
+
if (paginationPayload.body) {
|
|
94
|
+
ctx.options.body = {
|
|
95
|
+
...(ctx.options.body && typeof ctx.options.body === 'object' ? ctx.options.body : {}),
|
|
96
|
+
...paginationPayload.body,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
if (paginationPayload.headers) {
|
|
100
|
+
ctx.options.headers = { ...ctx.options.headers, ...paginationPayload.headers };
|
|
101
|
+
}
|
|
102
|
+
if (existingOnRequestForPagination) {
|
|
103
|
+
await existingOnRequestForPagination(ctx);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
// Extract pagination metadata from headers after response (metaSource: 'headers')
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
if (paginated && activePaginationConfig && activePaginationConfig.meta.metaSource === 'headers') {
|
|
111
|
+
const existingOnResponse = modifiedOptions.onResponse;
|
|
112
|
+
modifiedOptions.onResponse = async ({ response }) => {
|
|
113
|
+
const meta = extractPaginationMetaFromHeaders(response.headers, activePaginationConfig);
|
|
114
|
+
if (meta.total !== undefined)
|
|
115
|
+
paginationState.value.total = meta.total;
|
|
116
|
+
if (meta.totalPages !== undefined)
|
|
117
|
+
paginationState.value.totalPages = meta.totalPages;
|
|
118
|
+
if (meta.currentPage !== undefined)
|
|
119
|
+
paginationState.value.currentPage = meta.currentPage;
|
|
120
|
+
if (meta.perPage !== undefined)
|
|
121
|
+
paginationState.value.perPage = meta.perPage;
|
|
122
|
+
if (existingOnResponse) {
|
|
123
|
+
await existingOnResponse({ response });
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
// If page/perPage refs change, re-trigger useFetch (add them to watch array)
|
|
128
|
+
if (paginated) {
|
|
129
|
+
modifiedOptions.watch = [...(modifiedOptions.watch ?? []), page, perPage];
|
|
130
|
+
}
|
|
131
|
+
// Pass onRequest to useFetch's native interceptor so async callbacks are
|
|
132
|
+
// properly awaited by ofetch before the request is sent.
|
|
74
133
|
if (mergedCallbacks.onRequest) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
//
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
else if (result && typeof result === 'object') {
|
|
91
|
-
applyRequestModifications(modifiedOptions, result);
|
|
134
|
+
const existingOnRequest = modifiedOptions.onRequest;
|
|
135
|
+
modifiedOptions.onRequest = async ({ options: fetchOpts }) => {
|
|
136
|
+
// Build context from the live options at request time
|
|
137
|
+
const requestContext = {
|
|
138
|
+
url: urlValue,
|
|
139
|
+
method: String(fetchOpts.method || modifiedOptions.method || 'GET'),
|
|
140
|
+
body: fetchOpts.body,
|
|
141
|
+
headers: fetchOpts.headers,
|
|
142
|
+
query: fetchOpts.query,
|
|
143
|
+
};
|
|
144
|
+
try {
|
|
145
|
+
const modifications = await mergedCallbacks.onRequest(requestContext);
|
|
146
|
+
if (modifications && typeof modifications === 'object') {
|
|
147
|
+
applyRequestModifications(fetchOpts, modifications);
|
|
148
|
+
}
|
|
92
149
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
150
|
+
catch (error) {
|
|
151
|
+
console.error('Error in merged onRequest callback:', error);
|
|
152
|
+
}
|
|
153
|
+
// Chain any pre-existing onRequest interceptor
|
|
154
|
+
if (existingOnRequest) {
|
|
155
|
+
await existingOnRequest({ options: fetchOpts });
|
|
156
|
+
}
|
|
157
|
+
};
|
|
97
158
|
}
|
|
98
159
|
// Make the actual request using Nuxt's useFetch
|
|
99
160
|
const result = useFetch(url, modifiedOptions);
|
|
@@ -106,7 +167,22 @@ export function useApiRequest(url, options) {
|
|
|
106
167
|
const [prevData, prevError, prevPending] = prev ?? [undefined, undefined, undefined];
|
|
107
168
|
// Apply transformations when data arrives
|
|
108
169
|
if (data && data !== prevData) {
|
|
109
|
-
|
|
170
|
+
// Unwrap dataKey for paginated body-based responses BEFORE pick/transform
|
|
171
|
+
let processedData = paginated && activePaginationConfig && activePaginationConfig.meta.metaSource === 'body'
|
|
172
|
+
? unwrapDataKey(data, activePaginationConfig)
|
|
173
|
+
: data;
|
|
174
|
+
// Extract body-based pagination meta from raw response
|
|
175
|
+
if (paginated && activePaginationConfig && activePaginationConfig.meta.metaSource === 'body') {
|
|
176
|
+
const meta = extractPaginationMetaFromBody(data, activePaginationConfig);
|
|
177
|
+
if (meta.total !== undefined)
|
|
178
|
+
paginationState.value.total = meta.total;
|
|
179
|
+
if (meta.totalPages !== undefined)
|
|
180
|
+
paginationState.value.totalPages = meta.totalPages;
|
|
181
|
+
if (meta.currentPage !== undefined)
|
|
182
|
+
paginationState.value.currentPage = meta.currentPage;
|
|
183
|
+
if (meta.perPage !== undefined)
|
|
184
|
+
paginationState.value.perPage = meta.perPage;
|
|
185
|
+
}
|
|
110
186
|
// Step 1: Apply pick if specified
|
|
111
187
|
if (pick) {
|
|
112
188
|
processedData = applyPick(processedData, pick);
|
|
@@ -123,7 +199,8 @@ export function useApiRequest(url, options) {
|
|
|
123
199
|
// Update transformed data ref
|
|
124
200
|
transformedData.value = processedData;
|
|
125
201
|
// onSuccess - when data arrives and no error (using merged callback)
|
|
126
|
-
|
|
202
|
+
// NOTE: data !== null/undefined check instead of truthy to handle 0, false, ''
|
|
203
|
+
if (data != null && !error && !successExecuted && mergedCallbacks.onSuccess) {
|
|
127
204
|
successExecuted = true;
|
|
128
205
|
try {
|
|
129
206
|
await mergedCallbacks.onSuccess(processedData);
|
|
@@ -146,9 +223,9 @@ export function useApiRequest(url, options) {
|
|
|
146
223
|
// onFinish - when request completes (was pending, now not) (using merged callback)
|
|
147
224
|
if (prevPending && !pending && mergedCallbacks.onFinish) {
|
|
148
225
|
const finishContext = {
|
|
149
|
-
data: transformedData.value
|
|
150
|
-
error: error
|
|
151
|
-
success:
|
|
226
|
+
data: transformedData.value ?? undefined,
|
|
227
|
+
error: error ?? undefined,
|
|
228
|
+
success: data != null && !error,
|
|
152
229
|
};
|
|
153
230
|
try {
|
|
154
231
|
await mergedCallbacks.onFinish(finishContext);
|
|
@@ -158,9 +235,41 @@ export function useApiRequest(url, options) {
|
|
|
158
235
|
}
|
|
159
236
|
}
|
|
160
237
|
}, { immediate: true });
|
|
161
|
-
// Return result with transformed data
|
|
162
|
-
|
|
238
|
+
// Return result with transformed data and optional pagination
|
|
239
|
+
const baseResult = {
|
|
163
240
|
...result,
|
|
164
241
|
data: transformedData,
|
|
165
242
|
};
|
|
243
|
+
if (!paginated)
|
|
244
|
+
return baseResult;
|
|
245
|
+
// Pagination computed helpers
|
|
246
|
+
const hasNextPage = computed(() => paginationState.value.currentPage < paginationState.value.totalPages);
|
|
247
|
+
const hasPrevPage = computed(() => paginationState.value.currentPage > 1);
|
|
248
|
+
const goToPage = (n) => {
|
|
249
|
+
page.value = n;
|
|
250
|
+
successExecuted = false;
|
|
251
|
+
errorExecuted = false;
|
|
252
|
+
};
|
|
253
|
+
const nextPage = () => { if (hasNextPage.value)
|
|
254
|
+
goToPage(page.value + 1); };
|
|
255
|
+
const prevPage = () => { if (hasPrevPage.value)
|
|
256
|
+
goToPage(page.value - 1); };
|
|
257
|
+
const setPerPage = (n) => {
|
|
258
|
+
perPage.value = n;
|
|
259
|
+
page.value = 1;
|
|
260
|
+
successExecuted = false;
|
|
261
|
+
errorExecuted = false;
|
|
262
|
+
};
|
|
263
|
+
return {
|
|
264
|
+
...baseResult,
|
|
265
|
+
pagination: computed(() => ({
|
|
266
|
+
...paginationState.value,
|
|
267
|
+
hasNextPage: hasNextPage.value,
|
|
268
|
+
hasPrevPage: hasPrevPage.value,
|
|
269
|
+
})),
|
|
270
|
+
goToPage,
|
|
271
|
+
nextPage,
|
|
272
|
+
prevPage,
|
|
273
|
+
setPerPage,
|
|
274
|
+
};
|
|
166
275
|
}
|
|
@@ -2,18 +2,18 @@
|
|
|
2
2
|
* Generate file header with auto-generation warning
|
|
3
3
|
*/
|
|
4
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
|
|
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
17
|
`;
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
@@ -95,8 +95,8 @@ function generateFunctionBody(method, options) {
|
|
|
95
95
|
const fetchOptions = generateFetchOptions(method, options);
|
|
96
96
|
const description = method.description ? `/**\n * ${method.description}\n */\n` : '';
|
|
97
97
|
const pInit = hasParams ? `\n const p = shallowRef(params)` : '';
|
|
98
|
-
return `${description}export const ${method.composableName} = (${args}) => {${pInit}
|
|
99
|
-
return useApiRequest${responseTypeGeneric}(${url}, ${fetchOptions})
|
|
98
|
+
return `${description}export const ${method.composableName} = (${args}) => {${pInit}
|
|
99
|
+
return useApiRequest${responseTypeGeneric}(${url}, ${fetchOptions})
|
|
100
100
|
}`;
|
|
101
101
|
}
|
|
102
102
|
/**
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { generateOpenApiFiles, generateHeyApiFiles, checkJavaInstalled } from '.
|
|
|
5
5
|
import { generateUseFetchComposables } from './generators/use-fetch/generator.js';
|
|
6
6
|
import { generateUseAsyncDataComposables } from './generators/use-async-data/generator.js';
|
|
7
7
|
import { generateNuxtServerRoutes } from './generators/nuxt-server/generator.js';
|
|
8
|
+
import { generateConnectors } from './generators/components/connector-generator/generator.js';
|
|
8
9
|
import { promptInitialInputs, promptInputPath, promptComposablesSelection, promptServerRoutePath, promptBffConfig, promptGeneratorBackend, } from './cli/prompts.js';
|
|
9
10
|
import { MESSAGES } from './cli/messages.js';
|
|
10
11
|
import { displayLogo } from './cli/logo.js';
|
|
@@ -210,4 +211,28 @@ program
|
|
|
210
211
|
process.exit(1);
|
|
211
212
|
}
|
|
212
213
|
});
|
|
214
|
+
program
|
|
215
|
+
.command('connectors')
|
|
216
|
+
.description('Generate headless connector composables from an OpenAPI spec')
|
|
217
|
+
.requiredOption('-i, --input <path>', 'Path to OpenAPI YAML or JSON spec')
|
|
218
|
+
.requiredOption('-o, --output <path>', 'Output directory for connector composables')
|
|
219
|
+
.option('--composables-dir <relPath>', 'Relative path from output dir to useAsyncData composables (default: ../use-async-data)')
|
|
220
|
+
.option('--runtime-dir <relPath>', 'Relative path from output dir where runtime helpers are copied (default: ../runtime)')
|
|
221
|
+
.action(async (options) => {
|
|
222
|
+
try {
|
|
223
|
+
displayLogo();
|
|
224
|
+
p.intro('Generating connector composables…');
|
|
225
|
+
await generateConnectors({
|
|
226
|
+
inputSpec: options.input,
|
|
227
|
+
outputDir: options.output,
|
|
228
|
+
composablesRelDir: options.composablesDir,
|
|
229
|
+
runtimeRelDir: options.runtimeDir,
|
|
230
|
+
});
|
|
231
|
+
p.outro('Done!');
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
p.log.error(`Error: ${String(error)}`);
|
|
235
|
+
process.exit(1);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
213
238
|
program.parse();
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { defineNuxtModule, addImportsDir } from '@nuxt/kit';
|
|
2
|
+
import { execSync } from 'child_process';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { checkJavaInstalled } from '../generate.js';
|
|
5
|
+
import { generateUseFetchComposables } from '../generators/use-fetch/generator.js';
|
|
6
|
+
import { generateUseAsyncDataComposables } from '../generators/use-async-data/generator.js';
|
|
7
|
+
import { generateNuxtServerRoutes } from '../generators/nuxt-server/generator.js';
|
|
8
|
+
import { createConsoleLogger } from '../cli/logger.js';
|
|
9
|
+
export default defineNuxtModule({
|
|
10
|
+
meta: {
|
|
11
|
+
name: 'nuxt-openapi-hyperfetch',
|
|
12
|
+
configKey: 'openApiHyperFetch',
|
|
13
|
+
},
|
|
14
|
+
defaults: {
|
|
15
|
+
output: './composables/api',
|
|
16
|
+
generators: ['useFetch', 'useAsyncData'],
|
|
17
|
+
backend: 'heyapi',
|
|
18
|
+
enableDevBuild: true,
|
|
19
|
+
enableProductionBuild: true,
|
|
20
|
+
enableAutoGeneration: false,
|
|
21
|
+
enableAutoImport: true,
|
|
22
|
+
},
|
|
23
|
+
setup(options, nuxt) {
|
|
24
|
+
// --- Guard: input is required ---
|
|
25
|
+
if (!options.input) {
|
|
26
|
+
console.warn('[nuxt-openapi-hyperfetch] No input configured — skipping generation.\n' +
|
|
27
|
+
"Add `openApiHyperFetch: { input: './swagger.yaml' }` to your nuxt.config.ts");
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const resolvedInput = path.resolve(nuxt.options.rootDir, options.input);
|
|
31
|
+
const resolvedOutput = path.resolve(nuxt.options.rootDir, options.output);
|
|
32
|
+
const composablesOutputDir = path.join(resolvedOutput, 'composables');
|
|
33
|
+
const selectedGenerators = options.generators ?? ['useFetch', 'useAsyncData'];
|
|
34
|
+
const backend = options.backend ?? 'heyapi';
|
|
35
|
+
const logger = createConsoleLogger();
|
|
36
|
+
// --- Core generation function ---
|
|
37
|
+
const runGeneration = async () => {
|
|
38
|
+
logger.log.info('Generating OpenAPI composables...');
|
|
39
|
+
// 1. Generate OpenAPI SDK files
|
|
40
|
+
if (backend === 'official') {
|
|
41
|
+
if (!checkJavaInstalled()) {
|
|
42
|
+
throw new Error('[nuxt-openapi-hyperfetch] Java not found. The official backend requires Java 11+.\n' +
|
|
43
|
+
'Install from: https://adoptium.net or set backend: "heyapi" in nuxt.config.ts');
|
|
44
|
+
}
|
|
45
|
+
execSync(`npx @openapitools/openapi-generator-cli generate -i "${resolvedInput}" -g typescript-fetch -o "${resolvedOutput}"`, { stdio: 'inherit' });
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
const { createClient } = await import('@hey-api/openapi-ts');
|
|
49
|
+
await createClient({
|
|
50
|
+
input: resolvedInput,
|
|
51
|
+
output: resolvedOutput,
|
|
52
|
+
plugins: ['@hey-api/typescript', '@hey-api/sdk'],
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
// 2. Run selected composable generators
|
|
56
|
+
const genOptions = { backend };
|
|
57
|
+
if (selectedGenerators.includes('useFetch')) {
|
|
58
|
+
await generateUseFetchComposables(resolvedOutput, path.join(composablesOutputDir, 'use-fetch'), genOptions, logger);
|
|
59
|
+
}
|
|
60
|
+
if (selectedGenerators.includes('useAsyncData')) {
|
|
61
|
+
await generateUseAsyncDataComposables(resolvedOutput, path.join(composablesOutputDir, 'use-async-data'), genOptions, logger);
|
|
62
|
+
}
|
|
63
|
+
if (selectedGenerators.includes('nuxtServer')) {
|
|
64
|
+
const serverRoutePath = path.resolve(nuxt.options.rootDir, options.serverRoutePath ?? 'server/routes/api');
|
|
65
|
+
await generateNuxtServerRoutes(resolvedOutput, serverRoutePath, { enableBff: options.enableBff, backend }, logger);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
// --- Hooks: dev build / production build ---
|
|
69
|
+
const isDev = nuxt.options.dev;
|
|
70
|
+
if ((isDev && options.enableDevBuild) || (!isDev && options.enableProductionBuild)) {
|
|
71
|
+
nuxt.hook('build:before', runGeneration);
|
|
72
|
+
}
|
|
73
|
+
// --- Hook: auto-regeneration on input file change (dev only) ---
|
|
74
|
+
if (options.enableAutoGeneration) {
|
|
75
|
+
nuxt.hook('builder:watch', async (event, watchedPath) => {
|
|
76
|
+
const absWatchedPath = path.resolve(nuxt.options.rootDir, watchedPath);
|
|
77
|
+
if (absWatchedPath === resolvedInput && (event === 'change' || event === 'add')) {
|
|
78
|
+
logger.log.info(`Detected change in ${watchedPath}, regenerating composables...`);
|
|
79
|
+
await runGeneration();
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
// --- Auto-import: register composable directories ---
|
|
84
|
+
if (options.enableAutoImport !== false) {
|
|
85
|
+
if (selectedGenerators.includes('useFetch')) {
|
|
86
|
+
addImportsDir(path.join(composablesOutputDir, 'use-fetch', 'composables'));
|
|
87
|
+
}
|
|
88
|
+
if (selectedGenerators.includes('useAsyncData')) {
|
|
89
|
+
addImportsDir(path.join(composablesOutputDir, 'use-async-data', 'composables'));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { GeneratorConfig } from '../cli/config.js';
|
|
2
|
+
/**
|
|
3
|
+
* Configuration options for the nuxt-openapi-hyperfetch Nuxt module.
|
|
4
|
+
* Extends the CLI GeneratorConfig so the same fields work in both nxh.config.js and nuxt.config.ts.
|
|
5
|
+
*/
|
|
6
|
+
export interface ModuleOptions extends GeneratorConfig {
|
|
7
|
+
/**
|
|
8
|
+
* Generate composables before the dev server starts.
|
|
9
|
+
* @default true
|
|
10
|
+
*/
|
|
11
|
+
enableDevBuild?: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Generate composables before the production build.
|
|
14
|
+
* @default true
|
|
15
|
+
*/
|
|
16
|
+
enableProductionBuild?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Watch the input file and regenerate composables on change (dev mode only).
|
|
19
|
+
* @default false
|
|
20
|
+
*/
|
|
21
|
+
enableAutoGeneration?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Automatically import generated useFetch/useAsyncData composables project-wide.
|
|
24
|
+
* @default true
|
|
25
|
+
*/
|
|
26
|
+
enableAutoImport?: boolean;
|
|
27
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|