nuxt-openapi-hyperfetch 0.2.7-alpha.1 → 0.2.8-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/.editorconfig +26 -26
  2. package/.prettierignore +17 -17
  3. package/CONTRIBUTING.md +291 -291
  4. package/INSTRUCTIONS.md +327 -327
  5. package/LICENSE +202 -202
  6. package/README.md +231 -231
  7. package/dist/cli/config.d.ts +9 -2
  8. package/dist/cli/config.js +1 -1
  9. package/dist/cli/logo.js +5 -5
  10. package/dist/cli/messages.d.ts +1 -0
  11. package/dist/cli/messages.js +2 -0
  12. package/dist/cli/prompts.d.ts +5 -0
  13. package/dist/cli/prompts.js +12 -0
  14. package/dist/cli/types.d.ts +1 -1
  15. package/dist/generators/components/connector-generator/templates.js +12 -12
  16. package/dist/generators/use-async-data/templates.js +17 -17
  17. package/dist/generators/use-fetch/templates.js +14 -14
  18. package/dist/index.js +39 -27
  19. package/dist/module/index.js +19 -0
  20. package/dist/module/types.d.ts +7 -0
  21. package/docs/API-REFERENCE.md +886 -886
  22. package/docs/generated-components.md +615 -615
  23. package/docs/headless-composables-ui.md +569 -569
  24. package/eslint.config.js +85 -85
  25. package/package.json +1 -1
  26. package/src/cli/config.ts +147 -140
  27. package/src/cli/logger.ts +124 -124
  28. package/src/cli/logo.ts +25 -25
  29. package/src/cli/messages.ts +4 -0
  30. package/src/cli/prompts.ts +14 -1
  31. package/src/cli/types.ts +50 -50
  32. package/src/generators/components/connector-generator/generator.ts +138 -138
  33. package/src/generators/components/connector-generator/templates.ts +254 -254
  34. package/src/generators/components/connector-generator/types.ts +34 -34
  35. package/src/generators/components/schema-analyzer/index.ts +44 -44
  36. package/src/generators/components/schema-analyzer/intent-detector.ts +187 -187
  37. package/src/generators/components/schema-analyzer/openapi-reader.ts +96 -96
  38. package/src/generators/components/schema-analyzer/resource-grouper.ts +166 -166
  39. package/src/generators/components/schema-analyzer/schema-field-mapper.ts +268 -268
  40. package/src/generators/components/schema-analyzer/types.ts +177 -177
  41. package/src/generators/nuxt-server/generator.ts +272 -272
  42. package/src/generators/shared/runtime/apiHelpers.ts +535 -535
  43. package/src/generators/shared/runtime/pagination.ts +323 -323
  44. package/src/generators/shared/runtime/useDeleteConnector.ts +109 -109
  45. package/src/generators/shared/runtime/useDetailConnector.ts +64 -64
  46. package/src/generators/shared/runtime/useFormConnector.ts +139 -139
  47. package/src/generators/shared/runtime/useListConnector.ts +148 -148
  48. package/src/generators/shared/runtime/zod-error-merger.ts +119 -119
  49. package/src/generators/shared/templates/api-callbacks-plugin.ts +399 -399
  50. package/src/generators/shared/templates/api-pagination-plugin.ts +158 -158
  51. package/src/generators/use-async-data/generator.ts +205 -205
  52. package/src/generators/use-async-data/runtime/useApiAsyncData.ts +329 -329
  53. package/src/generators/use-async-data/runtime/useApiAsyncDataRaw.ts +324 -324
  54. package/src/generators/use-async-data/templates.ts +257 -257
  55. package/src/generators/use-fetch/generator.ts +170 -170
  56. package/src/generators/use-fetch/runtime/useApiRequest.ts +354 -354
  57. package/src/generators/use-fetch/templates.ts +214 -214
  58. package/src/index.ts +305 -303
  59. package/src/module/index.ts +158 -133
  60. package/src/module/types.ts +39 -31
@@ -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
- goToPage,
320
- nextPage,
321
- prevPage,
322
- setPerPage,
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
+ })),
319
+ goToPage,
320
+ nextPage,
321
+ prevPage,
322
+ setPerPage,
323
+ };
324
+ }