nuxt-openapi-hyperfetch 0.1.7-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.
Files changed (97) hide show
  1. package/CONTRIBUTING.md +291 -292
  2. package/INSTRUCTIONS.md +327 -327
  3. package/LICENSE +202 -202
  4. package/README.md +231 -227
  5. package/dist/cli/logger.d.ts +26 -0
  6. package/dist/cli/logger.js +36 -0
  7. package/dist/cli/logo.js +5 -5
  8. package/dist/generators/components/connector-generator/generator.d.ts +12 -0
  9. package/dist/generators/components/connector-generator/generator.js +116 -0
  10. package/dist/generators/components/connector-generator/templates.d.ts +18 -0
  11. package/dist/generators/components/connector-generator/templates.js +222 -0
  12. package/dist/generators/components/connector-generator/types.d.ts +32 -0
  13. package/dist/generators/components/connector-generator/types.js +7 -0
  14. package/dist/generators/components/schema-analyzer/index.d.ts +17 -0
  15. package/dist/generators/components/schema-analyzer/index.js +20 -0
  16. package/dist/generators/components/schema-analyzer/intent-detector.d.ts +17 -0
  17. package/dist/generators/components/schema-analyzer/intent-detector.js +143 -0
  18. package/dist/generators/components/schema-analyzer/openapi-reader.d.ts +11 -0
  19. package/dist/generators/components/schema-analyzer/openapi-reader.js +76 -0
  20. package/dist/generators/components/schema-analyzer/resource-grouper.d.ts +6 -0
  21. package/dist/generators/components/schema-analyzer/resource-grouper.js +132 -0
  22. package/dist/generators/components/schema-analyzer/schema-field-mapper.d.ts +35 -0
  23. package/dist/generators/components/schema-analyzer/schema-field-mapper.js +220 -0
  24. package/dist/generators/components/schema-analyzer/types.d.ts +156 -0
  25. package/dist/generators/components/schema-analyzer/types.js +7 -0
  26. package/dist/generators/nuxt-server/generator.d.ts +2 -1
  27. package/dist/generators/nuxt-server/generator.js +21 -21
  28. package/dist/generators/shared/runtime/apiHelpers.d.ts +81 -41
  29. package/dist/generators/shared/runtime/apiHelpers.js +97 -104
  30. package/dist/generators/shared/runtime/pagination.d.ts +168 -0
  31. package/dist/generators/shared/runtime/pagination.js +179 -0
  32. package/dist/generators/shared/runtime/useDeleteConnector.d.ts +16 -0
  33. package/dist/generators/shared/runtime/useDeleteConnector.js +93 -0
  34. package/dist/generators/shared/runtime/useDetailConnector.d.ts +14 -0
  35. package/dist/generators/shared/runtime/useDetailConnector.js +50 -0
  36. package/dist/generators/shared/runtime/useFormConnector.d.ts +19 -0
  37. package/dist/generators/shared/runtime/useFormConnector.js +113 -0
  38. package/dist/generators/shared/runtime/useListConnector.d.ts +25 -0
  39. package/dist/generators/shared/runtime/useListConnector.js +125 -0
  40. package/dist/generators/shared/runtime/zod-error-merger.d.ts +23 -0
  41. package/dist/generators/shared/runtime/zod-error-merger.js +106 -0
  42. package/dist/generators/shared/templates/api-callbacks-plugin.js +54 -11
  43. package/dist/generators/shared/templates/api-pagination-plugin.d.ts +51 -0
  44. package/dist/generators/shared/templates/api-pagination-plugin.js +152 -0
  45. package/dist/generators/use-async-data/generator.d.ts +2 -1
  46. package/dist/generators/use-async-data/generator.js +14 -14
  47. package/dist/generators/use-async-data/runtime/useApiAsyncData.js +114 -13
  48. package/dist/generators/use-async-data/runtime/useApiAsyncDataRaw.js +88 -10
  49. package/dist/generators/use-async-data/templates.js +17 -17
  50. package/dist/generators/use-fetch/generator.d.ts +2 -1
  51. package/dist/generators/use-fetch/generator.js +12 -12
  52. package/dist/generators/use-fetch/runtime/useApiRequest.js +149 -40
  53. package/dist/generators/use-fetch/templates.js +14 -14
  54. package/dist/index.js +25 -0
  55. package/dist/module/index.d.ts +4 -0
  56. package/dist/module/index.js +93 -0
  57. package/dist/module/types.d.ts +27 -0
  58. package/dist/module/types.js +1 -0
  59. package/docs/API-REFERENCE.md +886 -887
  60. package/docs/generated-components.md +615 -0
  61. package/docs/headless-composables-ui.md +569 -0
  62. package/eslint.config.js +13 -0
  63. package/package.json +29 -2
  64. package/src/cli/config.ts +140 -140
  65. package/src/cli/logger.ts +124 -66
  66. package/src/cli/logo.ts +25 -25
  67. package/src/cli/types.ts +50 -50
  68. package/src/generators/components/connector-generator/generator.ts +138 -0
  69. package/src/generators/components/connector-generator/templates.ts +254 -0
  70. package/src/generators/components/connector-generator/types.ts +34 -0
  71. package/src/generators/components/schema-analyzer/index.ts +44 -0
  72. package/src/generators/components/schema-analyzer/intent-detector.ts +187 -0
  73. package/src/generators/components/schema-analyzer/openapi-reader.ts +96 -0
  74. package/src/generators/components/schema-analyzer/resource-grouper.ts +166 -0
  75. package/src/generators/components/schema-analyzer/schema-field-mapper.ts +268 -0
  76. package/src/generators/components/schema-analyzer/types.ts +177 -0
  77. package/src/generators/nuxt-server/generator.ts +272 -270
  78. package/src/generators/shared/runtime/apiHelpers.ts +535 -507
  79. package/src/generators/shared/runtime/pagination.ts +323 -0
  80. package/src/generators/shared/runtime/useDeleteConnector.ts +109 -0
  81. package/src/generators/shared/runtime/useDetailConnector.ts +64 -0
  82. package/src/generators/shared/runtime/useFormConnector.ts +139 -0
  83. package/src/generators/shared/runtime/useListConnector.ts +148 -0
  84. package/src/generators/shared/runtime/zod-error-merger.ts +119 -0
  85. package/src/generators/shared/templates/api-callbacks-plugin.ts +399 -352
  86. package/src/generators/shared/templates/api-pagination-plugin.ts +158 -0
  87. package/src/generators/use-async-data/generator.ts +205 -204
  88. package/src/generators/use-async-data/runtime/useApiAsyncData.ts +329 -229
  89. package/src/generators/use-async-data/runtime/useApiAsyncDataRaw.ts +324 -245
  90. package/src/generators/use-async-data/templates.ts +257 -257
  91. package/src/generators/use-fetch/generator.ts +170 -169
  92. package/src/generators/use-fetch/runtime/useApiRequest.ts +354 -234
  93. package/src/generators/use-fetch/templates.ts +214 -214
  94. package/src/index.ts +303 -265
  95. package/src/module/index.ts +133 -0
  96. package/src/module/types.ts +31 -0
  97. package/src/generators/tanstack-query/generator.ts +0 -11
@@ -1,245 +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 { watch } 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
-
22
- /**
23
- * Response structure for Raw version
24
- * Includes data, headers, status, and statusText
25
- */
26
- export interface RawResponse<T> {
27
- data: T;
28
- headers: Headers;
29
- status: number;
30
- statusText: string;
31
- }
32
-
33
- /**
34
- * Helper type to infer transformed data type for Raw responses
35
- * Transform only applies to the data property, not the entire response
36
- */
37
- type MaybeTransformedRaw<T, Options> = Options extends { transform: (...args: any) => infer R }
38
- ? RawResponse<R>
39
- : Options extends { pick: ReadonlyArray<any> }
40
- ? RawResponse<any> // With nested paths, type inference is complex
41
- : RawResponse<T>;
42
-
43
- /**
44
- * Options for useAsyncData Raw API requests.
45
- * Extends all native Nuxt useFetch options plus our custom callbacks, transform, and pick.
46
- * onSuccess receives data AND the full response (headers, status, statusText).
47
- */
48
- export type ApiAsyncDataRawOptions<T> = Omit<BaseApiRequestOptions<T>, 'onSuccess'> &
49
- Omit<UseFetchOptions<T>, 'transform' | 'pick' | 'onSuccess'> & {
50
- /**
51
- * Called when the request succeeds — receives both data and the full response object.
52
- */
53
- onSuccess?: (
54
- data: T,
55
- response: { headers: Headers; status: number; statusText: string; url: string }
56
- ) => void | Promise<void>;
57
- };
58
-
59
- /**
60
- * Generic wrapper for API calls using Nuxt's useAsyncData - RAW VERSION
61
- * Returns full response with headers and status information
62
- *
63
- * Supports:
64
- * - Lifecycle callbacks (onRequest, onSuccess with response, onError, onFinish)
65
- * - Request modification via onRequest return value
66
- * - Transform (applies only to data, not full response)
67
- * - Pick operations (applies only to data)
68
- * - Global headers from useApiHeaders or $getApiHeaders
69
- * - Watch pattern for reactive parameters
70
- */
71
- export function useApiAsyncDataRaw<T>(
72
- key: string,
73
- url: string | (() => string),
74
- options?: ApiAsyncDataRawOptions<T>
75
- ) {
76
- const {
77
- method = 'GET',
78
- body,
79
- headers = {},
80
- params,
81
- baseURL,
82
- cacheKey,
83
- transform,
84
- pick,
85
- onRequest,
86
- onSuccess,
87
- onError,
88
- onFinish,
89
- skipGlobalCallbacks,
90
- immediate = true,
91
- lazy = false,
92
- server = true,
93
- dedupe = 'cancel',
94
- ...restOptions
95
- } = options || {};
96
-
97
- // Resolve base URL once at setup time (not inside fetchFn to avoid warning on every request)
98
- const resolvedBaseURL = baseURL || getGlobalBaseUrl();
99
- if (!resolvedBaseURL) {
100
- console.warn(
101
- '[nuxt-openapi-hyperfetch] No baseURL configured. Set runtimeConfig.public.apiBaseUrl in nuxt.config.ts or pass baseURL in options.'
102
- );
103
- }
104
-
105
- // Create reactive watch sources for callbacks
106
- const watchSources = [
107
- ...(typeof url === 'function' ? [url] : []),
108
- ...(body && typeof body === 'object' ? [() => body] : []),
109
- ...(params && typeof params === 'object' ? [() => params] : []),
110
- ];
111
-
112
- // Build a reactive cache key: composableName + resolved URL + serialized query params
113
- // This ensures distinct params produce distinct keys — preventing cache collisions
114
- const computedKey = () => {
115
- if (cacheKey) return cacheKey;
116
- const resolvedUrl = typeof url === 'function' ? url() : url;
117
- const resolvedParams = toValue(params);
118
- const paramsSuffix =
119
- resolvedParams && typeof resolvedParams === 'object' && Object.keys(resolvedParams).length > 0
120
- ? '-' + JSON.stringify(resolvedParams)
121
- : '';
122
- return `${key}-${resolvedUrl}${paramsSuffix}`;
123
- };
124
-
125
- // Fetch function for useAsyncData
126
- const fetchFn = async (): Promise<RawResponse<T>> => {
127
- // Get URL value for merging callbacks
128
- const finalUrl = typeof url === 'function' ? url() : url;
129
-
130
- // Merge local and global callbacks
131
- const mergedCallbacks = mergeCallbacks(
132
- finalUrl,
133
- { onRequest, onSuccess, onError, onFinish },
134
- skipGlobalCallbacks
135
- );
136
-
137
- try {
138
- // Get global headers
139
- const globalHeaders = getGlobalHeaders();
140
-
141
- // Prepare request context
142
- const requestContext: RequestContext = {
143
- url: finalUrl,
144
- method: method as any,
145
- headers: { ...globalHeaders, ...headers },
146
- body,
147
- params,
148
- };
149
-
150
- // Execute merged onRequest callback and potentially modify request
151
- const modifiedContext = { ...requestContext };
152
- if (mergedCallbacks.onRequest) {
153
- const result = await mergedCallbacks.onRequest(requestContext);
154
- // If onRequest returns modifications, apply them
155
- if (result && typeof result === 'object') {
156
- const modifications = result as ModifiedRequestContext;
157
- if (modifications.body !== undefined) {
158
- modifiedContext.body = modifications.body;
159
- }
160
- if (modifications.headers !== undefined) {
161
- modifiedContext.headers = {
162
- ...modifiedContext.headers,
163
- ...modifications.headers,
164
- };
165
- }
166
- if (modifications.params !== undefined) {
167
- modifiedContext.params = {
168
- ...modifiedContext.params,
169
- ...modifications.params,
170
- };
171
- }
172
- }
173
- }
174
-
175
- // Make the request with $fetch.raw to get full response
176
- const response = await $fetch.raw<T>(modifiedContext.url, {
177
- method: modifiedContext.method,
178
- headers: modifiedContext.headers,
179
- body: modifiedContext.body,
180
- params: modifiedContext.params,
181
- ...(resolvedBaseURL ? { baseURL: resolvedBaseURL } : {}),
182
- ...restOptions,
183
- });
184
-
185
- // Extract data from response
186
- let data = response._data as T;
187
-
188
- // Apply pick if provided (only to data)
189
- if (pick) {
190
- data = applyPick(data, pick) as T;
191
- }
192
-
193
- // Apply transform if provided (only to data)
194
- if (transform) {
195
- data = transform(data);
196
- }
197
-
198
- // Construct the raw response object
199
- const rawResponse: RawResponse<T> = {
200
- data,
201
- headers: response.headers,
202
- status: response.status,
203
- statusText: response.statusText,
204
- };
205
-
206
- // Call merged onSuccess callback with data and response context
207
- if (mergedCallbacks.onSuccess) {
208
- await mergedCallbacks.onSuccess(data, {
209
- headers: response.headers,
210
- status: response.status,
211
- statusText: response.statusText,
212
- url: finalUrl,
213
- });
214
- }
215
-
216
- return rawResponse;
217
- } catch (error: any) {
218
- // Call merged onError callback
219
- if (mergedCallbacks.onError) {
220
- await mergedCallbacks.onError(error, { url: finalUrl, method, headers });
221
- }
222
- throw error;
223
- } finally {
224
- // Call merged onFinish callback
225
- if (mergedCallbacks.onFinish) {
226
- await mergedCallbacks.onFinish({
227
- url: finalUrl,
228
- method,
229
- headers: { ...getGlobalHeaders(), ...headers },
230
- });
231
- }
232
- }
233
- };
234
-
235
- // Use Nuxt's useAsyncData with a computed key for proper cache isolation per params
236
- const result = useAsyncData<MaybeTransformedRaw<T, ApiAsyncDataRawOptions<T>>>(computedKey, fetchFn, {
237
- immediate,
238
- lazy,
239
- server,
240
- dedupe,
241
- watch: watchSources.length > 0 ? watchSources : undefined,
242
- });
243
-
244
- return result;
245
- }
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
+ }