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.
Files changed (68) 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 +309 -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 +68 -19
  16. package/dist/generators/shared/runtime/useFormConnector.js +8 -1
  17. package/dist/generators/shared/runtime/useListConnector.js +13 -6
  18. package/dist/generators/use-async-data/generator.js +4 -0
  19. package/dist/generators/use-async-data/runtime/useApiAsyncData.js +4 -4
  20. package/dist/generators/use-async-data/runtime/useApiAsyncDataRaw.js +4 -4
  21. package/dist/generators/use-async-data/templates.js +17 -17
  22. package/dist/generators/use-fetch/generator.js +4 -0
  23. package/dist/generators/use-fetch/templates.js +14 -14
  24. package/dist/index.js +40 -27
  25. package/dist/module/index.js +19 -0
  26. package/dist/module/types.d.ts +7 -0
  27. package/docs/API-REFERENCE.md +886 -886
  28. package/docs/generated-components.md +615 -615
  29. package/docs/headless-composables-ui.md +569 -569
  30. package/eslint.config.js +85 -85
  31. package/package.json +1 -1
  32. package/src/cli/config.ts +147 -140
  33. package/src/cli/logger.ts +124 -124
  34. package/src/cli/logo.ts +25 -25
  35. package/src/cli/messages.ts +4 -0
  36. package/src/cli/prompts.ts +14 -1
  37. package/src/cli/types.ts +50 -50
  38. package/src/generators/components/connector-generator/generator.ts +138 -138
  39. package/src/generators/components/connector-generator/templates.ts +307 -254
  40. package/src/generators/components/connector-generator/types.ts +34 -34
  41. package/src/generators/components/schema-analyzer/index.ts +44 -44
  42. package/src/generators/components/schema-analyzer/intent-detector.ts +187 -187
  43. package/src/generators/components/schema-analyzer/openapi-reader.ts +96 -96
  44. package/src/generators/components/schema-analyzer/resource-grouper.ts +166 -166
  45. package/src/generators/components/schema-analyzer/schema-field-mapper.ts +268 -268
  46. package/src/generators/components/schema-analyzer/types.ts +177 -177
  47. package/src/generators/nuxt-server/generator.ts +272 -272
  48. package/src/generators/shared/runtime/apiHelpers.ts +535 -535
  49. package/src/generators/shared/runtime/pagination.ts +323 -323
  50. package/src/generators/shared/runtime/useDeleteConnector.ts +109 -109
  51. package/src/generators/shared/runtime/useDetailConnector.ts +64 -64
  52. package/src/generators/shared/runtime/useFormConnector.ts +147 -139
  53. package/src/generators/shared/runtime/useListConnector.ts +158 -148
  54. package/src/generators/shared/runtime/zod-error-merger.ts +119 -119
  55. package/src/generators/shared/templates/api-callbacks-plugin.ts +399 -399
  56. package/src/generators/shared/templates/api-pagination-plugin.ts +158 -158
  57. package/src/generators/use-async-data/generator.ts +213 -205
  58. package/src/generators/use-async-data/runtime/useApiAsyncData.ts +329 -329
  59. package/src/generators/use-async-data/runtime/useApiAsyncDataRaw.ts +324 -324
  60. package/src/generators/use-async-data/templates.ts +257 -257
  61. package/src/generators/use-fetch/generator.ts +178 -170
  62. package/src/generators/use-fetch/runtime/useApiRequest.ts +354 -354
  63. package/src/generators/use-fetch/templates.ts +214 -214
  64. package/src/index.ts +306 -303
  65. package/src/module/index.ts +158 -133
  66. package/src/module/types.ts +39 -31
  67. package/dist/generators/tanstack-query/generator.d.ts +0 -5
  68. 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
- 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
+ goToPage,
319
+ nextPage,
320
+ prevPage,
321
+ setPerPage,
322
+ })),
323
+ };
324
+ }