nuxt-openapi-hyperfetch 0.2.7-alpha.1 → 0.3.0-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +26 -26
- package/.prettierignore +17 -17
- package/CONTRIBUTING.md +291 -291
- package/INSTRUCTIONS.md +327 -327
- package/LICENSE +202 -202
- package/README.md +309 -231
- package/dist/cli/config.d.ts +9 -2
- package/dist/cli/config.js +1 -1
- package/dist/cli/logo.js +5 -5
- package/dist/cli/messages.d.ts +1 -0
- package/dist/cli/messages.js +2 -0
- package/dist/cli/prompts.d.ts +5 -0
- package/dist/cli/prompts.js +12 -0
- package/dist/cli/types.d.ts +1 -1
- package/dist/generators/components/connector-generator/templates.js +68 -19
- package/dist/generators/shared/runtime/useFormConnector.js +8 -1
- package/dist/generators/shared/runtime/useListConnector.js +13 -6
- package/dist/generators/use-async-data/generator.js +4 -0
- package/dist/generators/use-async-data/runtime/useApiAsyncData.js +4 -4
- package/dist/generators/use-async-data/runtime/useApiAsyncDataRaw.js +4 -4
- package/dist/generators/use-async-data/templates.js +17 -17
- package/dist/generators/use-fetch/generator.js +4 -0
- package/dist/generators/use-fetch/templates.js +14 -14
- package/dist/index.js +40 -27
- package/dist/module/index.js +19 -0
- package/dist/module/types.d.ts +7 -0
- package/docs/API-REFERENCE.md +886 -886
- package/docs/generated-components.md +615 -615
- package/docs/headless-composables-ui.md +569 -569
- package/eslint.config.js +85 -85
- package/package.json +1 -1
- package/src/cli/config.ts +147 -140
- package/src/cli/logger.ts +124 -124
- package/src/cli/logo.ts +25 -25
- package/src/cli/messages.ts +4 -0
- package/src/cli/prompts.ts +14 -1
- package/src/cli/types.ts +50 -50
- package/src/generators/components/connector-generator/generator.ts +138 -138
- package/src/generators/components/connector-generator/templates.ts +307 -254
- package/src/generators/components/connector-generator/types.ts +34 -34
- package/src/generators/components/schema-analyzer/index.ts +44 -44
- package/src/generators/components/schema-analyzer/intent-detector.ts +187 -187
- package/src/generators/components/schema-analyzer/openapi-reader.ts +96 -96
- package/src/generators/components/schema-analyzer/resource-grouper.ts +166 -166
- package/src/generators/components/schema-analyzer/schema-field-mapper.ts +268 -268
- package/src/generators/components/schema-analyzer/types.ts +177 -177
- package/src/generators/nuxt-server/generator.ts +272 -272
- package/src/generators/shared/runtime/apiHelpers.ts +535 -535
- package/src/generators/shared/runtime/pagination.ts +323 -323
- package/src/generators/shared/runtime/useDeleteConnector.ts +109 -109
- package/src/generators/shared/runtime/useDetailConnector.ts +64 -64
- package/src/generators/shared/runtime/useFormConnector.ts +147 -139
- package/src/generators/shared/runtime/useListConnector.ts +158 -148
- package/src/generators/shared/runtime/zod-error-merger.ts +119 -119
- package/src/generators/shared/templates/api-callbacks-plugin.ts +399 -399
- package/src/generators/shared/templates/api-pagination-plugin.ts +158 -158
- package/src/generators/use-async-data/generator.ts +213 -205
- package/src/generators/use-async-data/runtime/useApiAsyncData.ts +329 -329
- package/src/generators/use-async-data/runtime/useApiAsyncDataRaw.ts +324 -324
- package/src/generators/use-async-data/templates.ts +257 -257
- package/src/generators/use-fetch/generator.ts +178 -170
- package/src/generators/use-fetch/runtime/useApiRequest.ts +354 -354
- package/src/generators/use-fetch/templates.ts +214 -214
- package/src/index.ts +306 -303
- package/src/module/index.ts +158 -133
- package/src/module/types.ts +39 -31
- package/dist/generators/tanstack-query/generator.d.ts +0 -5
- package/dist/generators/tanstack-query/generator.js +0 -11
|
@@ -1,324 +1,324 @@
|
|
|
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 { ref, computed } from 'vue';
|
|
9
|
-
import type { UseFetchOptions } from '#app';
|
|
10
|
-
import {
|
|
11
|
-
getGlobalHeaders,
|
|
12
|
-
getGlobalBaseUrl,
|
|
13
|
-
applyPick,
|
|
14
|
-
applyRequestModifications,
|
|
15
|
-
mergeCallbacks,
|
|
16
|
-
type RequestContext,
|
|
17
|
-
type ModifiedRequestContext,
|
|
18
|
-
type FinishContext,
|
|
19
|
-
type ApiRequestOptions as BaseApiRequestOptions,
|
|
20
|
-
} from '../../shared/runtime/apiHelpers.js';
|
|
21
|
-
import {
|
|
22
|
-
getGlobalApiPagination,
|
|
23
|
-
buildPaginationRequest,
|
|
24
|
-
extractPaginationMetaFromBody,
|
|
25
|
-
extractPaginationMetaFromHeaders,
|
|
26
|
-
unwrapDataKey,
|
|
27
|
-
type PaginationState,
|
|
28
|
-
} from '../../shared/runtime/pagination.js';
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Response structure for Raw version
|
|
32
|
-
* Includes data, headers, status, and statusText
|
|
33
|
-
*/
|
|
34
|
-
export interface RawResponse<T> {
|
|
35
|
-
data: T;
|
|
36
|
-
headers: Headers;
|
|
37
|
-
status: number;
|
|
38
|
-
statusText: string;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Helper type to infer transformed data type for Raw responses
|
|
43
|
-
* Transform only applies to the data property, not the entire response
|
|
44
|
-
*/
|
|
45
|
-
type MaybeTransformedRaw<T, Options> = Options extends { transform: (...args: any) => infer R }
|
|
46
|
-
? RawResponse<R>
|
|
47
|
-
: Options extends { pick: ReadonlyArray<any> }
|
|
48
|
-
? RawResponse<any> // With nested paths, type inference is complex
|
|
49
|
-
: RawResponse<T>;
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Options for useAsyncData Raw API requests.
|
|
53
|
-
* Extends all native Nuxt useFetch options plus our custom callbacks, transform, and pick.
|
|
54
|
-
* onSuccess receives data AND the full response (headers, status, statusText).
|
|
55
|
-
*/
|
|
56
|
-
export type ApiAsyncDataRawOptions<T> = Omit<BaseApiRequestOptions<T>, 'onSuccess'> &
|
|
57
|
-
Omit<UseFetchOptions<T>, 'transform' | 'pick' | 'onSuccess'> & {
|
|
58
|
-
/**
|
|
59
|
-
* Called when the request succeeds — receives both data and the full response object.
|
|
60
|
-
*/
|
|
61
|
-
onSuccess?: (
|
|
62
|
-
data: T,
|
|
63
|
-
response: { headers: Headers; status: number; statusText: string; url: string }
|
|
64
|
-
) => void | Promise<void>;
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Generic wrapper for API calls using Nuxt's useAsyncData - RAW VERSION
|
|
69
|
-
* Returns full response with headers and status information
|
|
70
|
-
*
|
|
71
|
-
* Supports:
|
|
72
|
-
* - Lifecycle callbacks (onRequest, onSuccess with response, onError, onFinish)
|
|
73
|
-
* - Request modification via onRequest return value
|
|
74
|
-
* - Transform (applies only to data, not full response)
|
|
75
|
-
* - Pick operations (applies only to data)
|
|
76
|
-
* - Global headers from useApiHeaders or $getApiHeaders
|
|
77
|
-
* - Watch pattern for reactive parameters
|
|
78
|
-
*/
|
|
79
|
-
export function useApiAsyncDataRaw<T>(
|
|
80
|
-
key: string,
|
|
81
|
-
url: string | (() => string),
|
|
82
|
-
options?: ApiAsyncDataRawOptions<T>
|
|
83
|
-
) {
|
|
84
|
-
const {
|
|
85
|
-
method = 'GET',
|
|
86
|
-
body,
|
|
87
|
-
headers = {},
|
|
88
|
-
params,
|
|
89
|
-
baseURL,
|
|
90
|
-
cacheKey,
|
|
91
|
-
transform,
|
|
92
|
-
pick,
|
|
93
|
-
onRequest,
|
|
94
|
-
onSuccess,
|
|
95
|
-
onError,
|
|
96
|
-
onFinish,
|
|
97
|
-
skipGlobalCallbacks,
|
|
98
|
-
immediate = true,
|
|
99
|
-
lazy = false,
|
|
100
|
-
server = true,
|
|
101
|
-
dedupe = 'cancel',
|
|
102
|
-
paginated,
|
|
103
|
-
initialPage,
|
|
104
|
-
initialPerPage,
|
|
105
|
-
paginationConfig,
|
|
106
|
-
...restOptions
|
|
107
|
-
} = options || {};
|
|
108
|
-
|
|
109
|
-
// ---------------------------------------------------------------------------
|
|
110
|
-
// Pagination setup
|
|
111
|
-
// ---------------------------------------------------------------------------
|
|
112
|
-
const activePaginationConfig = paginationConfig ?? (paginated ? getGlobalApiPagination() : null);
|
|
113
|
-
const page = ref<number>(initialPage ?? activePaginationConfig?.request.defaults.page ?? 1);
|
|
114
|
-
const perPage = ref<number>(initialPerPage ?? activePaginationConfig?.request.defaults.perPage ?? 20);
|
|
115
|
-
|
|
116
|
-
const paginationState = ref<PaginationState>({
|
|
117
|
-
currentPage: page.value,
|
|
118
|
-
totalPages: 0,
|
|
119
|
-
total: 0,
|
|
120
|
-
perPage: perPage.value,
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
// Resolve base URL once at setup time (not inside fetchFn to avoid warning on every request)
|
|
124
|
-
const resolvedBaseURL = baseURL || getGlobalBaseUrl();
|
|
125
|
-
if (!resolvedBaseURL) {
|
|
126
|
-
console.warn(
|
|
127
|
-
'[nuxt-openapi-hyperfetch] No baseURL configured. Set runtimeConfig.public.apiBaseUrl in nuxt.config.ts or pass baseURL in options.'
|
|
128
|
-
);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Create reactive watch sources for callbacks
|
|
132
|
-
const watchSources = [
|
|
133
|
-
...(typeof url === 'function' ? [url] : []),
|
|
134
|
-
...(body && typeof body === 'object' ? [() => body] : []),
|
|
135
|
-
...(params && typeof params === 'object' ? [() => params] : []),
|
|
136
|
-
// Add pagination refs so page/perPage changes trigger re-fetch
|
|
137
|
-
...(paginated ? [page, perPage] : []),
|
|
138
|
-
];
|
|
139
|
-
|
|
140
|
-
// Build a reactive cache key: composableName + resolved URL + serialized query params
|
|
141
|
-
// This ensures distinct params produce distinct keys — preventing cache collisions
|
|
142
|
-
const computedKey = () => {
|
|
143
|
-
if (cacheKey) return cacheKey;
|
|
144
|
-
const resolvedUrl = typeof url === 'function' ? url() : url;
|
|
145
|
-
const resolvedParams = toValue(params);
|
|
146
|
-
const paramsSuffix =
|
|
147
|
-
resolvedParams && typeof resolvedParams === 'object' && Object.keys(resolvedParams).length > 0
|
|
148
|
-
? '-' + JSON.stringify(resolvedParams)
|
|
149
|
-
: '';
|
|
150
|
-
return `${key}-${resolvedUrl}${paramsSuffix}`;
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
// Fetch function for useAsyncData
|
|
154
|
-
const fetchFn = async (): Promise<RawResponse<T>> => {
|
|
155
|
-
// Get URL value for merging callbacks
|
|
156
|
-
const finalUrl = typeof url === 'function' ? url() : url;
|
|
157
|
-
|
|
158
|
-
// Merge local and global callbacks
|
|
159
|
-
const mergedCallbacks = mergeCallbacks(
|
|
160
|
-
finalUrl,
|
|
161
|
-
method,
|
|
162
|
-
{ onRequest, onSuccess, onError, onFinish },
|
|
163
|
-
skipGlobalCallbacks
|
|
164
|
-
);
|
|
165
|
-
|
|
166
|
-
try {
|
|
167
|
-
// Get global headers
|
|
168
|
-
const globalHeaders = getGlobalHeaders();
|
|
169
|
-
|
|
170
|
-
// Prepare request context
|
|
171
|
-
const requestContext: RequestContext = {
|
|
172
|
-
url: finalUrl,
|
|
173
|
-
method: method as any,
|
|
174
|
-
headers: { ...globalHeaders, ...headers },
|
|
175
|
-
body,
|
|
176
|
-
params,
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
// Execute merged onRequest callback and potentially modify request
|
|
180
|
-
const modifiedContext = { ...requestContext };
|
|
181
|
-
if (mergedCallbacks.onRequest) {
|
|
182
|
-
const result = await mergedCallbacks.onRequest(requestContext);
|
|
183
|
-
// If onRequest returns modifications, apply them
|
|
184
|
-
if (result && typeof result === 'object') {
|
|
185
|
-
const modifications = result as ModifiedRequestContext;
|
|
186
|
-
if (modifications.body !== undefined) {
|
|
187
|
-
modifiedContext.body = modifications.body;
|
|
188
|
-
}
|
|
189
|
-
if (modifications.headers !== undefined) {
|
|
190
|
-
modifiedContext.headers = {
|
|
191
|
-
...modifiedContext.headers,
|
|
192
|
-
...modifications.headers,
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
if (modifications.query !== undefined) {
|
|
196
|
-
modifiedContext.params = {
|
|
197
|
-
...modifiedContext.params,
|
|
198
|
-
...modifications.query,
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// Build final request params, injecting pagination if enabled
|
|
205
|
-
let finalQuery = modifiedContext.params;
|
|
206
|
-
let finalBody = modifiedContext.body;
|
|
207
|
-
let finalHeaders = modifiedContext.headers;
|
|
208
|
-
|
|
209
|
-
if (paginated && activePaginationConfig) {
|
|
210
|
-
const paginationPayload = buildPaginationRequest(page.value, perPage.value, activePaginationConfig);
|
|
211
|
-
if (paginationPayload.query) finalQuery = { ...finalQuery, ...paginationPayload.query };
|
|
212
|
-
if (paginationPayload.body) finalBody = { ...(finalBody ?? {}), ...paginationPayload.body };
|
|
213
|
-
if (paginationPayload.headers) finalHeaders = { ...finalHeaders, ...paginationPayload.headers };
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Make the request with $fetch.raw to get full response
|
|
217
|
-
const response = await $fetch.raw<T>(modifiedContext.url, {
|
|
218
|
-
method: modifiedContext.method,
|
|
219
|
-
headers: finalHeaders,
|
|
220
|
-
body: finalBody,
|
|
221
|
-
params: finalQuery,
|
|
222
|
-
...(resolvedBaseURL ? { baseURL: resolvedBaseURL } : {}),
|
|
223
|
-
...restOptions,
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
// Extract pagination meta from headers or body
|
|
227
|
-
if (paginated && activePaginationConfig) {
|
|
228
|
-
let meta;
|
|
229
|
-
if (activePaginationConfig.meta.metaSource === 'headers') {
|
|
230
|
-
meta = extractPaginationMetaFromHeaders(response.headers, activePaginationConfig);
|
|
231
|
-
} else {
|
|
232
|
-
meta = extractPaginationMetaFromBody(response._data, activePaginationConfig);
|
|
233
|
-
}
|
|
234
|
-
if (meta.total !== undefined) paginationState.value.total = meta.total;
|
|
235
|
-
if (meta.totalPages !== undefined) paginationState.value.totalPages = meta.totalPages;
|
|
236
|
-
if (meta.currentPage !== undefined) paginationState.value.currentPage = meta.currentPage;
|
|
237
|
-
if (meta.perPage !== undefined) paginationState.value.perPage = meta.perPage;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// Extract data from response (unwrap dataKey for paginated responses)
|
|
241
|
-
let data = paginated && activePaginationConfig
|
|
242
|
-
? unwrapDataKey<T>(response._data, activePaginationConfig)
|
|
243
|
-
: response._data as T;
|
|
244
|
-
|
|
245
|
-
// Apply pick if provided (only to data)
|
|
246
|
-
if (pick) {
|
|
247
|
-
data = applyPick(data, pick) as T;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// Apply transform if provided (only to data)
|
|
251
|
-
if (transform) {
|
|
252
|
-
data = transform(data);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// Construct the raw response object
|
|
256
|
-
const rawResponse: RawResponse<T> = {
|
|
257
|
-
data,
|
|
258
|
-
headers: response.headers,
|
|
259
|
-
status: response.status,
|
|
260
|
-
statusText: response.statusText,
|
|
261
|
-
};
|
|
262
|
-
|
|
263
|
-
// Call merged onSuccess callback with data and response context
|
|
264
|
-
if (mergedCallbacks.onSuccess) {
|
|
265
|
-
await mergedCallbacks.onSuccess(data, {
|
|
266
|
-
headers: response.headers,
|
|
267
|
-
status: response.status,
|
|
268
|
-
statusText: response.statusText,
|
|
269
|
-
url: finalUrl,
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
return rawResponse;
|
|
274
|
-
} catch (error: any) {
|
|
275
|
-
// Call merged onError callback
|
|
276
|
-
if (mergedCallbacks.onError) {
|
|
277
|
-
await mergedCallbacks.onError(error, { url: finalUrl, method, headers });
|
|
278
|
-
}
|
|
279
|
-
throw error;
|
|
280
|
-
} finally {
|
|
281
|
-
// Call merged onFinish callback
|
|
282
|
-
if (mergedCallbacks.onFinish) {
|
|
283
|
-
await mergedCallbacks.onFinish({
|
|
284
|
-
url: finalUrl,
|
|
285
|
-
method,
|
|
286
|
-
headers: { ...getGlobalHeaders(), ...headers },
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
};
|
|
291
|
-
|
|
292
|
-
// Use Nuxt's useAsyncData with a computed key for proper cache isolation per params
|
|
293
|
-
const result = useAsyncData<MaybeTransformedRaw<T, ApiAsyncDataRawOptions<T>>>(computedKey, fetchFn, {
|
|
294
|
-
immediate,
|
|
295
|
-
lazy,
|
|
296
|
-
server,
|
|
297
|
-
dedupe,
|
|
298
|
-
watch: watchSources.length > 0 ? watchSources : undefined,
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
if (!paginated) return result;
|
|
302
|
-
|
|
303
|
-
// Pagination computed helpers
|
|
304
|
-
const hasNextPage = computed(() => paginationState.value.currentPage < paginationState.value.totalPages);
|
|
305
|
-
const hasPrevPage = computed(() => paginationState.value.currentPage > 1);
|
|
306
|
-
|
|
307
|
-
const goToPage = (n: number) => { page.value = n; };
|
|
308
|
-
const nextPage = () => { if (hasNextPage.value) goToPage(page.value + 1); };
|
|
309
|
-
const prevPage = () => { if (hasPrevPage.value) goToPage(page.value - 1); };
|
|
310
|
-
const setPerPage = (n: number) => { perPage.value = n; page.value = 1; };
|
|
311
|
-
|
|
312
|
-
return {
|
|
313
|
-
...result,
|
|
314
|
-
pagination: computed(() => ({
|
|
315
|
-
...paginationState.value,
|
|
316
|
-
hasNextPage: hasNextPage.value,
|
|
317
|
-
hasPrevPage: hasPrevPage.value,
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
};
|
|
324
|
-
}
|
|
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 { ref, computed } from 'vue';
|
|
9
|
+
import type { UseFetchOptions } from '#app';
|
|
10
|
+
import {
|
|
11
|
+
getGlobalHeaders,
|
|
12
|
+
getGlobalBaseUrl,
|
|
13
|
+
applyPick,
|
|
14
|
+
applyRequestModifications,
|
|
15
|
+
mergeCallbacks,
|
|
16
|
+
type RequestContext,
|
|
17
|
+
type ModifiedRequestContext,
|
|
18
|
+
type FinishContext,
|
|
19
|
+
type ApiRequestOptions as BaseApiRequestOptions,
|
|
20
|
+
} from '../../shared/runtime/apiHelpers.js';
|
|
21
|
+
import {
|
|
22
|
+
getGlobalApiPagination,
|
|
23
|
+
buildPaginationRequest,
|
|
24
|
+
extractPaginationMetaFromBody,
|
|
25
|
+
extractPaginationMetaFromHeaders,
|
|
26
|
+
unwrapDataKey,
|
|
27
|
+
type PaginationState,
|
|
28
|
+
} from '../../shared/runtime/pagination.js';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Response structure for Raw version
|
|
32
|
+
* Includes data, headers, status, and statusText
|
|
33
|
+
*/
|
|
34
|
+
export interface RawResponse<T> {
|
|
35
|
+
data: T;
|
|
36
|
+
headers: Headers;
|
|
37
|
+
status: number;
|
|
38
|
+
statusText: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Helper type to infer transformed data type for Raw responses
|
|
43
|
+
* Transform only applies to the data property, not the entire response
|
|
44
|
+
*/
|
|
45
|
+
type MaybeTransformedRaw<T, Options> = Options extends { transform: (...args: any) => infer R }
|
|
46
|
+
? RawResponse<R>
|
|
47
|
+
: Options extends { pick: ReadonlyArray<any> }
|
|
48
|
+
? RawResponse<any> // With nested paths, type inference is complex
|
|
49
|
+
: RawResponse<T>;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Options for useAsyncData Raw API requests.
|
|
53
|
+
* Extends all native Nuxt useFetch options plus our custom callbacks, transform, and pick.
|
|
54
|
+
* onSuccess receives data AND the full response (headers, status, statusText).
|
|
55
|
+
*/
|
|
56
|
+
export type ApiAsyncDataRawOptions<T> = Omit<BaseApiRequestOptions<T>, 'onSuccess'> &
|
|
57
|
+
Omit<UseFetchOptions<T>, 'transform' | 'pick' | 'onSuccess'> & {
|
|
58
|
+
/**
|
|
59
|
+
* Called when the request succeeds — receives both data and the full response object.
|
|
60
|
+
*/
|
|
61
|
+
onSuccess?: (
|
|
62
|
+
data: T,
|
|
63
|
+
response: { headers: Headers; status: number; statusText: string; url: string }
|
|
64
|
+
) => void | Promise<void>;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Generic wrapper for API calls using Nuxt's useAsyncData - RAW VERSION
|
|
69
|
+
* Returns full response with headers and status information
|
|
70
|
+
*
|
|
71
|
+
* Supports:
|
|
72
|
+
* - Lifecycle callbacks (onRequest, onSuccess with response, onError, onFinish)
|
|
73
|
+
* - Request modification via onRequest return value
|
|
74
|
+
* - Transform (applies only to data, not full response)
|
|
75
|
+
* - Pick operations (applies only to data)
|
|
76
|
+
* - Global headers from useApiHeaders or $getApiHeaders
|
|
77
|
+
* - Watch pattern for reactive parameters
|
|
78
|
+
*/
|
|
79
|
+
export function useApiAsyncDataRaw<T>(
|
|
80
|
+
key: string,
|
|
81
|
+
url: string | (() => string),
|
|
82
|
+
options?: ApiAsyncDataRawOptions<T>
|
|
83
|
+
) {
|
|
84
|
+
const {
|
|
85
|
+
method = 'GET',
|
|
86
|
+
body,
|
|
87
|
+
headers = {},
|
|
88
|
+
params,
|
|
89
|
+
baseURL,
|
|
90
|
+
cacheKey,
|
|
91
|
+
transform,
|
|
92
|
+
pick,
|
|
93
|
+
onRequest,
|
|
94
|
+
onSuccess,
|
|
95
|
+
onError,
|
|
96
|
+
onFinish,
|
|
97
|
+
skipGlobalCallbacks,
|
|
98
|
+
immediate = true,
|
|
99
|
+
lazy = false,
|
|
100
|
+
server = true,
|
|
101
|
+
dedupe = 'cancel',
|
|
102
|
+
paginated,
|
|
103
|
+
initialPage,
|
|
104
|
+
initialPerPage,
|
|
105
|
+
paginationConfig,
|
|
106
|
+
...restOptions
|
|
107
|
+
} = options || {};
|
|
108
|
+
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
// Pagination setup
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
const activePaginationConfig = paginationConfig ?? (paginated ? getGlobalApiPagination() : null);
|
|
113
|
+
const page = ref<number>(initialPage ?? activePaginationConfig?.request.defaults.page ?? 1);
|
|
114
|
+
const perPage = ref<number>(initialPerPage ?? activePaginationConfig?.request.defaults.perPage ?? 20);
|
|
115
|
+
|
|
116
|
+
const paginationState = ref<PaginationState>({
|
|
117
|
+
currentPage: page.value,
|
|
118
|
+
totalPages: 0,
|
|
119
|
+
total: 0,
|
|
120
|
+
perPage: perPage.value,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Resolve base URL once at setup time (not inside fetchFn to avoid warning on every request)
|
|
124
|
+
const resolvedBaseURL = baseURL || getGlobalBaseUrl();
|
|
125
|
+
if (!resolvedBaseURL) {
|
|
126
|
+
console.warn(
|
|
127
|
+
'[nuxt-openapi-hyperfetch] No baseURL configured. Set runtimeConfig.public.apiBaseUrl in nuxt.config.ts or pass baseURL in options.'
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Create reactive watch sources for callbacks
|
|
132
|
+
const watchSources = [
|
|
133
|
+
...(typeof url === 'function' ? [url] : []),
|
|
134
|
+
...(body && typeof body === 'object' ? [() => body] : []),
|
|
135
|
+
...(params && typeof params === 'object' ? [() => params] : []),
|
|
136
|
+
// Add pagination refs so page/perPage changes trigger re-fetch
|
|
137
|
+
...(paginated ? [page, perPage] : []),
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
// Build a reactive cache key: composableName + resolved URL + serialized query params
|
|
141
|
+
// This ensures distinct params produce distinct keys — preventing cache collisions
|
|
142
|
+
const computedKey = () => {
|
|
143
|
+
if (cacheKey) return cacheKey;
|
|
144
|
+
const resolvedUrl = typeof url === 'function' ? url() : url;
|
|
145
|
+
const resolvedParams = toValue(params);
|
|
146
|
+
const paramsSuffix =
|
|
147
|
+
resolvedParams && typeof resolvedParams === 'object' && Object.keys(resolvedParams).length > 0
|
|
148
|
+
? '-' + JSON.stringify(resolvedParams)
|
|
149
|
+
: '';
|
|
150
|
+
return `${key}-${resolvedUrl}${paramsSuffix}`;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// Fetch function for useAsyncData
|
|
154
|
+
const fetchFn = async (): Promise<RawResponse<T>> => {
|
|
155
|
+
// Get URL value for merging callbacks
|
|
156
|
+
const finalUrl = typeof url === 'function' ? url() : url;
|
|
157
|
+
|
|
158
|
+
// Merge local and global callbacks
|
|
159
|
+
const mergedCallbacks = mergeCallbacks(
|
|
160
|
+
finalUrl,
|
|
161
|
+
method,
|
|
162
|
+
{ onRequest, onSuccess, onError, onFinish },
|
|
163
|
+
skipGlobalCallbacks
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
// Get global headers
|
|
168
|
+
const globalHeaders = getGlobalHeaders();
|
|
169
|
+
|
|
170
|
+
// Prepare request context
|
|
171
|
+
const requestContext: RequestContext = {
|
|
172
|
+
url: finalUrl,
|
|
173
|
+
method: method as any,
|
|
174
|
+
headers: { ...globalHeaders, ...headers },
|
|
175
|
+
body,
|
|
176
|
+
params,
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// Execute merged onRequest callback and potentially modify request
|
|
180
|
+
const modifiedContext = { ...requestContext };
|
|
181
|
+
if (mergedCallbacks.onRequest) {
|
|
182
|
+
const result = await mergedCallbacks.onRequest(requestContext);
|
|
183
|
+
// If onRequest returns modifications, apply them
|
|
184
|
+
if (result && typeof result === 'object') {
|
|
185
|
+
const modifications = result as ModifiedRequestContext;
|
|
186
|
+
if (modifications.body !== undefined) {
|
|
187
|
+
modifiedContext.body = modifications.body;
|
|
188
|
+
}
|
|
189
|
+
if (modifications.headers !== undefined) {
|
|
190
|
+
modifiedContext.headers = {
|
|
191
|
+
...modifiedContext.headers,
|
|
192
|
+
...modifications.headers,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
if (modifications.query !== undefined) {
|
|
196
|
+
modifiedContext.params = {
|
|
197
|
+
...modifiedContext.params,
|
|
198
|
+
...modifications.query,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Build final request params, injecting pagination if enabled
|
|
205
|
+
let finalQuery = modifiedContext.params;
|
|
206
|
+
let finalBody = modifiedContext.body;
|
|
207
|
+
let finalHeaders = modifiedContext.headers;
|
|
208
|
+
|
|
209
|
+
if (paginated && activePaginationConfig) {
|
|
210
|
+
const paginationPayload = buildPaginationRequest(page.value, perPage.value, activePaginationConfig);
|
|
211
|
+
if (paginationPayload.query) finalQuery = { ...finalQuery, ...paginationPayload.query };
|
|
212
|
+
if (paginationPayload.body) finalBody = { ...(finalBody ?? {}), ...paginationPayload.body };
|
|
213
|
+
if (paginationPayload.headers) finalHeaders = { ...finalHeaders, ...paginationPayload.headers };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Make the request with $fetch.raw to get full response
|
|
217
|
+
const response = await $fetch.raw<T>(modifiedContext.url, {
|
|
218
|
+
method: modifiedContext.method,
|
|
219
|
+
headers: finalHeaders,
|
|
220
|
+
body: finalBody,
|
|
221
|
+
params: finalQuery,
|
|
222
|
+
...(resolvedBaseURL ? { baseURL: resolvedBaseURL } : {}),
|
|
223
|
+
...restOptions,
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// Extract pagination meta from headers or body
|
|
227
|
+
if (paginated && activePaginationConfig) {
|
|
228
|
+
let meta;
|
|
229
|
+
if (activePaginationConfig.meta.metaSource === 'headers') {
|
|
230
|
+
meta = extractPaginationMetaFromHeaders(response.headers, activePaginationConfig);
|
|
231
|
+
} else {
|
|
232
|
+
meta = extractPaginationMetaFromBody(response._data, activePaginationConfig);
|
|
233
|
+
}
|
|
234
|
+
if (meta.total !== undefined) paginationState.value.total = meta.total;
|
|
235
|
+
if (meta.totalPages !== undefined) paginationState.value.totalPages = meta.totalPages;
|
|
236
|
+
if (meta.currentPage !== undefined) paginationState.value.currentPage = meta.currentPage;
|
|
237
|
+
if (meta.perPage !== undefined) paginationState.value.perPage = meta.perPage;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Extract data from response (unwrap dataKey for paginated responses)
|
|
241
|
+
let data = paginated && activePaginationConfig
|
|
242
|
+
? unwrapDataKey<T>(response._data, activePaginationConfig)
|
|
243
|
+
: response._data as T;
|
|
244
|
+
|
|
245
|
+
// Apply pick if provided (only to data)
|
|
246
|
+
if (pick) {
|
|
247
|
+
data = applyPick(data, pick) as T;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Apply transform if provided (only to data)
|
|
251
|
+
if (transform) {
|
|
252
|
+
data = transform(data);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Construct the raw response object
|
|
256
|
+
const rawResponse: RawResponse<T> = {
|
|
257
|
+
data,
|
|
258
|
+
headers: response.headers,
|
|
259
|
+
status: response.status,
|
|
260
|
+
statusText: response.statusText,
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
// Call merged onSuccess callback with data and response context
|
|
264
|
+
if (mergedCallbacks.onSuccess) {
|
|
265
|
+
await mergedCallbacks.onSuccess(data, {
|
|
266
|
+
headers: response.headers,
|
|
267
|
+
status: response.status,
|
|
268
|
+
statusText: response.statusText,
|
|
269
|
+
url: finalUrl,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return rawResponse;
|
|
274
|
+
} catch (error: any) {
|
|
275
|
+
// Call merged onError callback
|
|
276
|
+
if (mergedCallbacks.onError) {
|
|
277
|
+
await mergedCallbacks.onError(error, { url: finalUrl, method, headers });
|
|
278
|
+
}
|
|
279
|
+
throw error;
|
|
280
|
+
} finally {
|
|
281
|
+
// Call merged onFinish callback
|
|
282
|
+
if (mergedCallbacks.onFinish) {
|
|
283
|
+
await mergedCallbacks.onFinish({
|
|
284
|
+
url: finalUrl,
|
|
285
|
+
method,
|
|
286
|
+
headers: { ...getGlobalHeaders(), ...headers },
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
// Use Nuxt's useAsyncData with a computed key for proper cache isolation per params
|
|
293
|
+
const result = useAsyncData<MaybeTransformedRaw<T, ApiAsyncDataRawOptions<T>>>(computedKey, fetchFn, {
|
|
294
|
+
immediate,
|
|
295
|
+
lazy,
|
|
296
|
+
server,
|
|
297
|
+
dedupe,
|
|
298
|
+
watch: watchSources.length > 0 ? watchSources : undefined,
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
if (!paginated) return result;
|
|
302
|
+
|
|
303
|
+
// Pagination computed helpers
|
|
304
|
+
const hasNextPage = computed(() => paginationState.value.currentPage < paginationState.value.totalPages);
|
|
305
|
+
const hasPrevPage = computed(() => paginationState.value.currentPage > 1);
|
|
306
|
+
|
|
307
|
+
const goToPage = (n: number) => { page.value = n; };
|
|
308
|
+
const nextPage = () => { if (hasNextPage.value) goToPage(page.value + 1); };
|
|
309
|
+
const prevPage = () => { if (hasPrevPage.value) goToPage(page.value - 1); };
|
|
310
|
+
const setPerPage = (n: number) => { perPage.value = n; page.value = 1; };
|
|
311
|
+
|
|
312
|
+
return {
|
|
313
|
+
...result,
|
|
314
|
+
pagination: computed(() => ({
|
|
315
|
+
...paginationState.value,
|
|
316
|
+
hasNextPage: hasNextPage.value,
|
|
317
|
+
hasPrevPage: hasPrevPage.value,
|
|
318
|
+
goToPage,
|
|
319
|
+
nextPage,
|
|
320
|
+
prevPage,
|
|
321
|
+
setPerPage,
|
|
322
|
+
})),
|
|
323
|
+
};
|
|
324
|
+
}
|