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,535 +1,535 @@
|
|
|
1
|
-
// @ts-nocheck - This file runs in user's Nuxt project with different TypeScript config
|
|
2
|
-
/**
|
|
3
|
-
* Shared API Helpers - Used by both useFetch and useAsyncData wrappers
|
|
4
|
-
* This file contains common logic for callbacks, transforms, and global configuration
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Context provided to onRequest interceptor
|
|
9
|
-
*/
|
|
10
|
-
export interface RequestContext {
|
|
11
|
-
/** Request URL */
|
|
12
|
-
url: string;
|
|
13
|
-
/** HTTP method */
|
|
14
|
-
method: string;
|
|
15
|
-
/** Request body (if any) */
|
|
16
|
-
body?: any;
|
|
17
|
-
/** Request headers */
|
|
18
|
-
headers?: Record<string, string>;
|
|
19
|
-
/** Query parameters */
|
|
20
|
-
query?: Record<string, any>;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Modified context that can be returned from onRequest
|
|
25
|
-
*/
|
|
26
|
-
export interface ModifiedRequestContext {
|
|
27
|
-
/** Modified request body */
|
|
28
|
-
body?: any;
|
|
29
|
-
/** Modified request headers */
|
|
30
|
-
headers?: Record<string, string>;
|
|
31
|
-
/** Modified query parameters */
|
|
32
|
-
query?: Record<string, any>;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Result context provided to onFinish callback
|
|
37
|
-
*/
|
|
38
|
-
export interface FinishContext<T> {
|
|
39
|
-
/** Response data (if successful) */
|
|
40
|
-
data?: T;
|
|
41
|
-
/** Error (if failed) */
|
|
42
|
-
error?: any;
|
|
43
|
-
/** Whether the request was successful */
|
|
44
|
-
success: boolean;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* A single rule in the global callbacks configuration.
|
|
49
|
-
* Each rule independently targets specific URL patterns and/or HTTP methods.
|
|
50
|
-
* Rules are executed in order; any rule may return false to suppress the local callback.
|
|
51
|
-
*/
|
|
52
|
-
export interface GlobalCallbacksRule {
|
|
53
|
-
/**
|
|
54
|
-
* URL glob patterns — only apply this rule to matching URLs.
|
|
55
|
-
* Supports wildcards: '/api/**', '/api/public/*', etc.
|
|
56
|
-
* If omitted, the rule applies to all URLs.
|
|
57
|
-
*/
|
|
58
|
-
patterns?: string[];
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* HTTP methods — only apply this rule to matching methods (case-insensitive).
|
|
62
|
-
* Example: ['DELETE', 'POST']
|
|
63
|
-
* If omitted, the rule applies to all methods.
|
|
64
|
-
*/
|
|
65
|
-
methods?: string[];
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Called before the request is sent.
|
|
69
|
-
* Return modified context (headers/body/query) to alter the request.
|
|
70
|
-
* Return false to prevent local onRequest execution (Opción 2).
|
|
71
|
-
*/
|
|
72
|
-
onRequest?: (
|
|
73
|
-
context: RequestContext
|
|
74
|
-
) =>
|
|
75
|
-
| void
|
|
76
|
-
| Promise<void>
|
|
77
|
-
| ModifiedRequestContext
|
|
78
|
-
| Promise<ModifiedRequestContext>
|
|
79
|
-
| boolean
|
|
80
|
-
| Promise<boolean>;
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Called when the request succeeds.
|
|
84
|
-
* Return false to prevent local onSuccess execution (Opción 2).
|
|
85
|
-
*/
|
|
86
|
-
onSuccess?: (data: any, context?: any) => void | Promise<void> | boolean | Promise<boolean>;
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Called when the request fails.
|
|
90
|
-
* Return false to prevent local onError execution (Opción 2).
|
|
91
|
-
*/
|
|
92
|
-
onError?: (error: any, context?: any) => void | Promise<void> | boolean | Promise<boolean>;
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Called when the request finishes (success or error).
|
|
96
|
-
* Return false to prevent local onFinish execution (Opción 2).
|
|
97
|
-
*/
|
|
98
|
-
onFinish?: (context: FinishContext<any>) => void | Promise<void> | boolean | Promise<boolean>;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Global callbacks configuration.
|
|
103
|
-
* Accepts a single rule (backward-compatible) or an array of rules.
|
|
104
|
-
* Each rule can independently target URLs and HTTP methods.
|
|
105
|
-
* Provided via Nuxt plugin: $getGlobalApiCallbacks
|
|
106
|
-
*
|
|
107
|
-
* @example Single rule (backward-compatible)
|
|
108
|
-
* getGlobalApiCallbacks: () => ({ onError: (e) => console.error(e) })
|
|
109
|
-
*
|
|
110
|
-
* @example Multiple rules with method/pattern targeting
|
|
111
|
-
* getGlobalApiCallbacks: () => [
|
|
112
|
-
* { onRequest: (ctx) => console.log(ctx.url) },
|
|
113
|
-
* { methods: ['DELETE'], onSuccess: () => toast.success('Deleted!') },
|
|
114
|
-
* { patterns: ['/api/private/**'], onRequest: () => ({ headers: { Authorization: '...' } }) },
|
|
115
|
-
* ]
|
|
116
|
-
*/
|
|
117
|
-
export type GlobalCallbacksConfig = GlobalCallbacksRule | GlobalCallbacksRule[];
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Type for skipGlobalCallbacks option (Opción 1)
|
|
121
|
-
* - true: skip all global callbacks
|
|
122
|
-
* - array: skip specific callbacks by name
|
|
123
|
-
*/
|
|
124
|
-
export type SkipGlobalCallbacks =
|
|
125
|
-
| boolean
|
|
126
|
-
| Array<'onRequest' | 'onSuccess' | 'onError' | 'onFinish'>;
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Base options for API requests with lifecycle callbacks
|
|
130
|
-
* This is extended by specific wrapper options (useFetch, useAsyncData)
|
|
131
|
-
*/
|
|
132
|
-
export interface ApiRequestOptions<T = any> {
|
|
133
|
-
/**
|
|
134
|
-
* Called before the request is sent - can be used as an interceptor
|
|
135
|
-
* Return modified body/headers to transform the request
|
|
136
|
-
*/
|
|
137
|
-
onRequest?: (
|
|
138
|
-
context: RequestContext
|
|
139
|
-
) => void | Promise<void> | ModifiedRequestContext | Promise<ModifiedRequestContext>;
|
|
140
|
-
|
|
141
|
-
/** Called when the request succeeds with data (after transform/pick if provided) */
|
|
142
|
-
onSuccess?: (data: any) => void | Promise<void>;
|
|
143
|
-
|
|
144
|
-
/** Called when the request fails with an error */
|
|
145
|
-
onError?: (error: any) => void | Promise<void>;
|
|
146
|
-
|
|
147
|
-
/** Called when the request finishes (success or error) with result context */
|
|
148
|
-
onFinish?: (context: FinishContext<any>) => void | Promise<void>;
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Skip global callbacks for this specific request (Opción 1)
|
|
152
|
-
* - true: skip all global callbacks
|
|
153
|
-
* - ['onSuccess', 'onError']: skip specific callbacks
|
|
154
|
-
* - false/undefined: use global callbacks (default)
|
|
155
|
-
* @example
|
|
156
|
-
* skipGlobalCallbacks: true // Skip all global callbacks
|
|
157
|
-
* skipGlobalCallbacks: ['onSuccess'] // Skip only global onSuccess
|
|
158
|
-
*/
|
|
159
|
-
skipGlobalCallbacks?: SkipGlobalCallbacks;
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Transform the response data
|
|
163
|
-
* @example
|
|
164
|
-
* transform: (pet) => ({ displayName: pet.name, isAvailable: pet.status === 'available' })
|
|
165
|
-
*/
|
|
166
|
-
transform?: (data: T) => any;
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Pick specific keys from the response (applied before transform)
|
|
170
|
-
* Supports dot notation for nested paths
|
|
171
|
-
* @example
|
|
172
|
-
* pick: ['id', 'name'] as const
|
|
173
|
-
* pick: ['person.name', 'person.email', 'status']
|
|
174
|
-
*/
|
|
175
|
-
pick?: ReadonlyArray<string>;
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Custom cache key for useAsyncData. If provided, used as-is instead of the auto-generated key.
|
|
179
|
-
* Useful for manual cache control or sharing cache between components.
|
|
180
|
-
*/
|
|
181
|
-
cacheKey?: string;
|
|
182
|
-
|
|
183
|
-
// --- Pagination options ---
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Enable pagination for this request.
|
|
187
|
-
* When true, the composable injects page/perPage params and exposes `pagination` state + helpers.
|
|
188
|
-
* Uses global pagination config by default (set via plugins/api-pagination.ts).
|
|
189
|
-
* @example
|
|
190
|
-
* const { data, pagination, goToPage, nextPage, prevPage, setPerPage } = useGetPets(params, { paginated: true })
|
|
191
|
-
*/
|
|
192
|
-
paginated?: boolean;
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Initial page number. Defaults to global config default (usually 1).
|
|
196
|
-
*/
|
|
197
|
-
initialPage?: number;
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Initial page size. Defaults to global config default (usually 20).
|
|
201
|
-
*/
|
|
202
|
-
initialPerPage?: number;
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Per-request pagination config override.
|
|
206
|
-
* Takes priority over the global pagination config set in plugins/api-pagination.ts.
|
|
207
|
-
* Useful when one specific endpoint has a different pagination convention.
|
|
208
|
-
*/
|
|
209
|
-
paginationConfig?: import('./pagination.js').PaginationConfig;
|
|
210
|
-
|
|
211
|
-
// --- Common fetch options (available in all composables) ---
|
|
212
|
-
|
|
213
|
-
/** Base URL prepended to every request URL. Overrides runtimeConfig.public.apiBaseUrl. */
|
|
214
|
-
baseURL?: string;
|
|
215
|
-
|
|
216
|
-
/** HTTP method (GET, POST, PUT, PATCH, DELETE, etc.) */
|
|
217
|
-
method?: string;
|
|
218
|
-
|
|
219
|
-
/** Request body */
|
|
220
|
-
body?: any;
|
|
221
|
-
|
|
222
|
-
/** Request headers */
|
|
223
|
-
headers?: Record<string, string> | HeadersInit;
|
|
224
|
-
|
|
225
|
-
/** URL query parameters */
|
|
226
|
-
query?: Record<string, any>;
|
|
227
|
-
|
|
228
|
-
/** Alias for query */
|
|
229
|
-
params?: Record<string, any>;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Helper function to apply request modifications from onRequest interceptor
|
|
234
|
-
*/
|
|
235
|
-
export function applyRequestModifications(
|
|
236
|
-
options: Record<string, any>,
|
|
237
|
-
modifications: ModifiedRequestContext
|
|
238
|
-
): void {
|
|
239
|
-
if (modifications.body !== undefined) {
|
|
240
|
-
options.body = modifications.body;
|
|
241
|
-
}
|
|
242
|
-
if (modifications.headers !== undefined) {
|
|
243
|
-
options.headers = {
|
|
244
|
-
...options.headers,
|
|
245
|
-
...modifications.headers,
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
if (modifications.query !== undefined) {
|
|
249
|
-
options.query = {
|
|
250
|
-
...options.query,
|
|
251
|
-
...modifications.query,
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* Helper function to pick specific keys from an object
|
|
258
|
-
* Supports dot notation for nested paths (e.g., 'person.name')
|
|
259
|
-
*/
|
|
260
|
-
export function applyPick<T>(data: T, paths: ReadonlyArray<string>): any {
|
|
261
|
-
const result: any = {};
|
|
262
|
-
|
|
263
|
-
for (const path of paths) {
|
|
264
|
-
const keys = path.split('.');
|
|
265
|
-
|
|
266
|
-
// Navigate to the nested value
|
|
267
|
-
let value: any = data;
|
|
268
|
-
let exists = true;
|
|
269
|
-
|
|
270
|
-
for (const key of keys) {
|
|
271
|
-
if (value && typeof value === 'object' && key in value) {
|
|
272
|
-
value = value[key];
|
|
273
|
-
} else {
|
|
274
|
-
exists = false;
|
|
275
|
-
break;
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// Set the value in the result, maintaining nested structure
|
|
280
|
-
if (exists) {
|
|
281
|
-
let current = result;
|
|
282
|
-
for (let i = 0; i < keys.length - 1; i++) {
|
|
283
|
-
const key = keys[i];
|
|
284
|
-
if (!(key in current)) {
|
|
285
|
-
current[key] = {};
|
|
286
|
-
}
|
|
287
|
-
current = current[key];
|
|
288
|
-
}
|
|
289
|
-
current[keys[keys.length - 1]] = value;
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
return result;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* Helper function to get global headers from user configuration
|
|
298
|
-
* Supports two methods:
|
|
299
|
-
* 1. Auto-imported composable: composables/useApiHeaders.ts
|
|
300
|
-
* 2. Nuxt plugin provide: plugins/api-config.ts with $getApiHeaders
|
|
301
|
-
*/
|
|
302
|
-
export function getGlobalHeaders(): Record<string, string> {
|
|
303
|
-
let headers: Record<string, string> = {};
|
|
304
|
-
|
|
305
|
-
// Method 1: Try to use auto-imported composable (useApiHeaders)
|
|
306
|
-
try {
|
|
307
|
-
// @ts-ignore - useApiHeaders may or may not exist (user-defined)
|
|
308
|
-
if (typeof useApiHeaders !== 'undefined') {
|
|
309
|
-
// @ts-ignore
|
|
310
|
-
const getHeaders = useApiHeaders();
|
|
311
|
-
if (getHeaders) {
|
|
312
|
-
const h = typeof getHeaders === 'function' ? getHeaders() : getHeaders;
|
|
313
|
-
if (h && typeof h === 'object') {
|
|
314
|
-
headers = { ...headers, ...h };
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
} catch (e) {
|
|
319
|
-
// useApiHeaders doesn't exist or failed, that's OK
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// Method 2: Try to use Nuxt App plugin ($getApiHeaders)
|
|
323
|
-
try {
|
|
324
|
-
const nuxtApp = useNuxtApp();
|
|
325
|
-
// @ts-ignore - $getApiHeaders may or may not exist (user-defined)
|
|
326
|
-
if (nuxtApp.$getApiHeaders) {
|
|
327
|
-
// @ts-ignore
|
|
328
|
-
const h = nuxtApp.$getApiHeaders();
|
|
329
|
-
if (h && typeof h === 'object') {
|
|
330
|
-
headers = { ...headers, ...h };
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
} catch (e) {
|
|
334
|
-
// useNuxtApp not available or plugin not configured, that's OK
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
return headers;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
/**
|
|
341
|
-
* Helper function to get global callback rules from user configuration.
|
|
342
|
-
* Always returns a normalized array — wraps legacy single-object config automatically for
|
|
343
|
-
* full backward compatibility.
|
|
344
|
-
* Uses Nuxt plugin provide: plugins/api-callbacks.ts with $getGlobalApiCallbacks
|
|
345
|
-
*/
|
|
346
|
-
export function getGlobalCallbacks(): GlobalCallbacksRule[] {
|
|
347
|
-
try {
|
|
348
|
-
const nuxtApp = useNuxtApp();
|
|
349
|
-
// @ts-ignore - $getGlobalApiCallbacks may or may not exist (user-defined)
|
|
350
|
-
if (nuxtApp.$getGlobalApiCallbacks) {
|
|
351
|
-
// @ts-ignore
|
|
352
|
-
const config: GlobalCallbacksConfig = nuxtApp.$getGlobalApiCallbacks();
|
|
353
|
-
if (config && typeof config === 'object') {
|
|
354
|
-
// Normalize: wrap single-rule object in array for backward compatibility
|
|
355
|
-
return Array.isArray(config) ? config : [config];
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
} catch (e) {
|
|
359
|
-
// useNuxtApp not available or plugin not configured, that's OK
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
return [];
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* Helper function to get the global base URL from runtimeConfig.public.apiBaseUrl
|
|
367
|
-
* Returns the configured URL or undefined if not set or not in a Nuxt context.
|
|
368
|
-
*/
|
|
369
|
-
export function getGlobalBaseUrl(): string | undefined {
|
|
370
|
-
try {
|
|
371
|
-
const runtimeConfig = useRuntimeConfig();
|
|
372
|
-
const url = runtimeConfig?.public?.apiBaseUrl as string | undefined;
|
|
373
|
-
return url || undefined;
|
|
374
|
-
} catch {
|
|
375
|
-
// useRuntimeConfig not available outside Nuxt context, that's OK
|
|
376
|
-
return undefined;
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
/**
|
|
381
|
-
* Check if a global rule should be applied to a specific request.
|
|
382
|
-
* Implements Opción 1 (skipGlobalCallbacks), URL pattern matching, and HTTP method matching.
|
|
383
|
-
*/
|
|
384
|
-
export function shouldApplyGlobalCallback(
|
|
385
|
-
url: string,
|
|
386
|
-
method: string,
|
|
387
|
-
callbackName: 'onRequest' | 'onSuccess' | 'onError' | 'onFinish',
|
|
388
|
-
rule: GlobalCallbacksRule,
|
|
389
|
-
skipConfig?: SkipGlobalCallbacks
|
|
390
|
-
): boolean {
|
|
391
|
-
// Opción 1: Check if callback is skipped via skipGlobalCallbacks
|
|
392
|
-
if (skipConfig === true) return false;
|
|
393
|
-
if (Array.isArray(skipConfig) && skipConfig.includes(callbackName)) return false;
|
|
394
|
-
|
|
395
|
-
// URL pattern matching — if patterns defined, URL must match at least one
|
|
396
|
-
if (rule.patterns && rule.patterns.length > 0) {
|
|
397
|
-
const matchesUrl = rule.patterns.some((pattern) => {
|
|
398
|
-
// Convert glob pattern to regex: ** = any path, * = single segment
|
|
399
|
-
const regexPattern = pattern
|
|
400
|
-
.replace(/\*\*/g, '@@DOUBLE_STAR@@')
|
|
401
|
-
.replace(/\*/g, '[^/]*')
|
|
402
|
-
.replace(/@@DOUBLE_STAR@@/g, '.*');
|
|
403
|
-
return new RegExp('^' + regexPattern + '$').test(url);
|
|
404
|
-
});
|
|
405
|
-
if (!matchesUrl) return false;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
// Method matching — if methods defined, request method must match at least one
|
|
409
|
-
if (rule.methods && rule.methods.length > 0) {
|
|
410
|
-
if (!rule.methods.map((m) => m.toUpperCase()).includes(method.toUpperCase())) return false;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
return true;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
/**
|
|
417
|
-
* Merge local and global callback rules with proper execution order.
|
|
418
|
-
* Global rules are iterated in definition order. Any rule returning false suppresses the local callback.
|
|
419
|
-
* Implements all 3 options:
|
|
420
|
-
* - Opción 1: skipGlobalCallbacks to disable all global rules per request
|
|
421
|
-
* - Opción 2: a rule callback can return false to prevent local callback execution
|
|
422
|
-
* - Opción 3: per-rule URL pattern matching and HTTP method filtering
|
|
423
|
-
*/
|
|
424
|
-
export function mergeCallbacks(
|
|
425
|
-
url: string,
|
|
426
|
-
method: string,
|
|
427
|
-
localCallbacks: {
|
|
428
|
-
onRequest?: Function;
|
|
429
|
-
onSuccess?: Function;
|
|
430
|
-
onError?: Function;
|
|
431
|
-
onFinish?: Function;
|
|
432
|
-
},
|
|
433
|
-
skipConfig?: SkipGlobalCallbacks
|
|
434
|
-
) {
|
|
435
|
-
const rules = getGlobalCallbacks();
|
|
436
|
-
|
|
437
|
-
/**
|
|
438
|
-
* Iterate all applicable global rules for onSuccess, onError, or onFinish.
|
|
439
|
-
* Returns true if the local callback should still execute.
|
|
440
|
-
*/
|
|
441
|
-
async function runGlobalRules(
|
|
442
|
-
callbackName: 'onSuccess' | 'onError' | 'onFinish',
|
|
443
|
-
...args: any[]
|
|
444
|
-
): Promise<boolean> {
|
|
445
|
-
let continueLocal = true;
|
|
446
|
-
for (const rule of rules) {
|
|
447
|
-
const cb = rule[callbackName];
|
|
448
|
-
if (!cb || !shouldApplyGlobalCallback(url, method, callbackName, rule, skipConfig)) continue;
|
|
449
|
-
try {
|
|
450
|
-
const result = await (cb as Function)(...args);
|
|
451
|
-
// Opción 2: returning false from any rule suppresses the local callback
|
|
452
|
-
if (result === false) continueLocal = false;
|
|
453
|
-
} catch (error) {
|
|
454
|
-
console.error(`Error in global ${callbackName} callback:`, error);
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
return continueLocal;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
return {
|
|
461
|
-
/**
|
|
462
|
-
* Merged onRequest: runs all applicable global rules collecting and deep-merging
|
|
463
|
-
* modifications (headers and query are merged; body is last-write-wins).
|
|
464
|
-
* Local onRequest runs after all rules unless any returns false, and its
|
|
465
|
-
* modifications take highest priority.
|
|
466
|
-
*/
|
|
467
|
-
onRequest: async (ctx: RequestContext) => {
|
|
468
|
-
let mergedMods: ModifiedRequestContext | undefined;
|
|
469
|
-
let continueLocal = true;
|
|
470
|
-
|
|
471
|
-
for (const rule of rules) {
|
|
472
|
-
if (!rule.onRequest || !shouldApplyGlobalCallback(url, method, 'onRequest', rule, skipConfig)) continue;
|
|
473
|
-
try {
|
|
474
|
-
const result = await rule.onRequest(ctx);
|
|
475
|
-
if (result === false) {
|
|
476
|
-
continueLocal = false;
|
|
477
|
-
} else if (result && typeof result === 'object') {
|
|
478
|
-
const mod = result as ModifiedRequestContext;
|
|
479
|
-
// Deep-merge headers and query; body is last-write-wins
|
|
480
|
-
mergedMods = {
|
|
481
|
-
...(mergedMods ?? {}),
|
|
482
|
-
...mod,
|
|
483
|
-
headers: { ...(mergedMods?.headers ?? {}), ...(mod.headers ?? {}) },
|
|
484
|
-
query: { ...(mergedMods?.query ?? {}), ...(mod.query ?? {}) },
|
|
485
|
-
};
|
|
486
|
-
}
|
|
487
|
-
} catch (error) {
|
|
488
|
-
console.error('Error in global onRequest callback:', error);
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
// Execute local onRequest — its modifications take highest priority
|
|
493
|
-
if (continueLocal && localCallbacks.onRequest) {
|
|
494
|
-
const localResult = await localCallbacks.onRequest(ctx);
|
|
495
|
-
if (localResult && typeof localResult === 'object') {
|
|
496
|
-
const localMod = localResult as ModifiedRequestContext;
|
|
497
|
-
return mergedMods
|
|
498
|
-
? {
|
|
499
|
-
...mergedMods,
|
|
500
|
-
...localMod,
|
|
501
|
-
headers: { ...(mergedMods.headers ?? {}), ...(localMod.headers ?? {}) },
|
|
502
|
-
query: { ...(mergedMods.query ?? {}), ...(localMod.query ?? {}) },
|
|
503
|
-
}
|
|
504
|
-
: localMod;
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
return mergedMods;
|
|
509
|
-
},
|
|
510
|
-
|
|
511
|
-
/** Merged onSuccess: global rules first in order, then local (unless suppressed). */
|
|
512
|
-
onSuccess: async (data: any, context?: any) => {
|
|
513
|
-
const continueLocal = await runGlobalRules('onSuccess', data, context);
|
|
514
|
-
if (continueLocal && localCallbacks.onSuccess) {
|
|
515
|
-
await localCallbacks.onSuccess(data, context);
|
|
516
|
-
}
|
|
517
|
-
},
|
|
518
|
-
|
|
519
|
-
/** Merged onError: global rules first in order, then local (unless suppressed). */
|
|
520
|
-
onError: async (error: any, context?: any) => {
|
|
521
|
-
const continueLocal = await runGlobalRules('onError', error, context);
|
|
522
|
-
if (continueLocal && localCallbacks.onError) {
|
|
523
|
-
await localCallbacks.onError(error, context);
|
|
524
|
-
}
|
|
525
|
-
},
|
|
526
|
-
|
|
527
|
-
/** Merged onFinish: global rules first in order, then local (unless suppressed). */
|
|
528
|
-
onFinish: async (context: any) => {
|
|
529
|
-
const continueLocal = await runGlobalRules('onFinish', context);
|
|
530
|
-
if (continueLocal && localCallbacks.onFinish) {
|
|
531
|
-
await localCallbacks.onFinish(context);
|
|
532
|
-
}
|
|
533
|
-
},
|
|
534
|
-
};
|
|
535
|
-
}
|
|
1
|
+
// @ts-nocheck - This file runs in user's Nuxt project with different TypeScript config
|
|
2
|
+
/**
|
|
3
|
+
* Shared API Helpers - Used by both useFetch and useAsyncData wrappers
|
|
4
|
+
* This file contains common logic for callbacks, transforms, and global configuration
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Context provided to onRequest interceptor
|
|
9
|
+
*/
|
|
10
|
+
export interface RequestContext {
|
|
11
|
+
/** Request URL */
|
|
12
|
+
url: string;
|
|
13
|
+
/** HTTP method */
|
|
14
|
+
method: string;
|
|
15
|
+
/** Request body (if any) */
|
|
16
|
+
body?: any;
|
|
17
|
+
/** Request headers */
|
|
18
|
+
headers?: Record<string, string>;
|
|
19
|
+
/** Query parameters */
|
|
20
|
+
query?: Record<string, any>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Modified context that can be returned from onRequest
|
|
25
|
+
*/
|
|
26
|
+
export interface ModifiedRequestContext {
|
|
27
|
+
/** Modified request body */
|
|
28
|
+
body?: any;
|
|
29
|
+
/** Modified request headers */
|
|
30
|
+
headers?: Record<string, string>;
|
|
31
|
+
/** Modified query parameters */
|
|
32
|
+
query?: Record<string, any>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Result context provided to onFinish callback
|
|
37
|
+
*/
|
|
38
|
+
export interface FinishContext<T> {
|
|
39
|
+
/** Response data (if successful) */
|
|
40
|
+
data?: T;
|
|
41
|
+
/** Error (if failed) */
|
|
42
|
+
error?: any;
|
|
43
|
+
/** Whether the request was successful */
|
|
44
|
+
success: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* A single rule in the global callbacks configuration.
|
|
49
|
+
* Each rule independently targets specific URL patterns and/or HTTP methods.
|
|
50
|
+
* Rules are executed in order; any rule may return false to suppress the local callback.
|
|
51
|
+
*/
|
|
52
|
+
export interface GlobalCallbacksRule {
|
|
53
|
+
/**
|
|
54
|
+
* URL glob patterns — only apply this rule to matching URLs.
|
|
55
|
+
* Supports wildcards: '/api/**', '/api/public/*', etc.
|
|
56
|
+
* If omitted, the rule applies to all URLs.
|
|
57
|
+
*/
|
|
58
|
+
patterns?: string[];
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* HTTP methods — only apply this rule to matching methods (case-insensitive).
|
|
62
|
+
* Example: ['DELETE', 'POST']
|
|
63
|
+
* If omitted, the rule applies to all methods.
|
|
64
|
+
*/
|
|
65
|
+
methods?: string[];
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Called before the request is sent.
|
|
69
|
+
* Return modified context (headers/body/query) to alter the request.
|
|
70
|
+
* Return false to prevent local onRequest execution (Opción 2).
|
|
71
|
+
*/
|
|
72
|
+
onRequest?: (
|
|
73
|
+
context: RequestContext
|
|
74
|
+
) =>
|
|
75
|
+
| void
|
|
76
|
+
| Promise<void>
|
|
77
|
+
| ModifiedRequestContext
|
|
78
|
+
| Promise<ModifiedRequestContext>
|
|
79
|
+
| boolean
|
|
80
|
+
| Promise<boolean>;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Called when the request succeeds.
|
|
84
|
+
* Return false to prevent local onSuccess execution (Opción 2).
|
|
85
|
+
*/
|
|
86
|
+
onSuccess?: (data: any, context?: any) => void | Promise<void> | boolean | Promise<boolean>;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Called when the request fails.
|
|
90
|
+
* Return false to prevent local onError execution (Opción 2).
|
|
91
|
+
*/
|
|
92
|
+
onError?: (error: any, context?: any) => void | Promise<void> | boolean | Promise<boolean>;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Called when the request finishes (success or error).
|
|
96
|
+
* Return false to prevent local onFinish execution (Opción 2).
|
|
97
|
+
*/
|
|
98
|
+
onFinish?: (context: FinishContext<any>) => void | Promise<void> | boolean | Promise<boolean>;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Global callbacks configuration.
|
|
103
|
+
* Accepts a single rule (backward-compatible) or an array of rules.
|
|
104
|
+
* Each rule can independently target URLs and HTTP methods.
|
|
105
|
+
* Provided via Nuxt plugin: $getGlobalApiCallbacks
|
|
106
|
+
*
|
|
107
|
+
* @example Single rule (backward-compatible)
|
|
108
|
+
* getGlobalApiCallbacks: () => ({ onError: (e) => console.error(e) })
|
|
109
|
+
*
|
|
110
|
+
* @example Multiple rules with method/pattern targeting
|
|
111
|
+
* getGlobalApiCallbacks: () => [
|
|
112
|
+
* { onRequest: (ctx) => console.log(ctx.url) },
|
|
113
|
+
* { methods: ['DELETE'], onSuccess: () => toast.success('Deleted!') },
|
|
114
|
+
* { patterns: ['/api/private/**'], onRequest: () => ({ headers: { Authorization: '...' } }) },
|
|
115
|
+
* ]
|
|
116
|
+
*/
|
|
117
|
+
export type GlobalCallbacksConfig = GlobalCallbacksRule | GlobalCallbacksRule[];
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Type for skipGlobalCallbacks option (Opción 1)
|
|
121
|
+
* - true: skip all global callbacks
|
|
122
|
+
* - array: skip specific callbacks by name
|
|
123
|
+
*/
|
|
124
|
+
export type SkipGlobalCallbacks =
|
|
125
|
+
| boolean
|
|
126
|
+
| Array<'onRequest' | 'onSuccess' | 'onError' | 'onFinish'>;
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Base options for API requests with lifecycle callbacks
|
|
130
|
+
* This is extended by specific wrapper options (useFetch, useAsyncData)
|
|
131
|
+
*/
|
|
132
|
+
export interface ApiRequestOptions<T = any> {
|
|
133
|
+
/**
|
|
134
|
+
* Called before the request is sent - can be used as an interceptor
|
|
135
|
+
* Return modified body/headers to transform the request
|
|
136
|
+
*/
|
|
137
|
+
onRequest?: (
|
|
138
|
+
context: RequestContext
|
|
139
|
+
) => void | Promise<void> | ModifiedRequestContext | Promise<ModifiedRequestContext>;
|
|
140
|
+
|
|
141
|
+
/** Called when the request succeeds with data (after transform/pick if provided) */
|
|
142
|
+
onSuccess?: (data: any) => void | Promise<void>;
|
|
143
|
+
|
|
144
|
+
/** Called when the request fails with an error */
|
|
145
|
+
onError?: (error: any) => void | Promise<void>;
|
|
146
|
+
|
|
147
|
+
/** Called when the request finishes (success or error) with result context */
|
|
148
|
+
onFinish?: (context: FinishContext<any>) => void | Promise<void>;
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Skip global callbacks for this specific request (Opción 1)
|
|
152
|
+
* - true: skip all global callbacks
|
|
153
|
+
* - ['onSuccess', 'onError']: skip specific callbacks
|
|
154
|
+
* - false/undefined: use global callbacks (default)
|
|
155
|
+
* @example
|
|
156
|
+
* skipGlobalCallbacks: true // Skip all global callbacks
|
|
157
|
+
* skipGlobalCallbacks: ['onSuccess'] // Skip only global onSuccess
|
|
158
|
+
*/
|
|
159
|
+
skipGlobalCallbacks?: SkipGlobalCallbacks;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Transform the response data
|
|
163
|
+
* @example
|
|
164
|
+
* transform: (pet) => ({ displayName: pet.name, isAvailable: pet.status === 'available' })
|
|
165
|
+
*/
|
|
166
|
+
transform?: (data: T) => any;
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Pick specific keys from the response (applied before transform)
|
|
170
|
+
* Supports dot notation for nested paths
|
|
171
|
+
* @example
|
|
172
|
+
* pick: ['id', 'name'] as const
|
|
173
|
+
* pick: ['person.name', 'person.email', 'status']
|
|
174
|
+
*/
|
|
175
|
+
pick?: ReadonlyArray<string>;
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Custom cache key for useAsyncData. If provided, used as-is instead of the auto-generated key.
|
|
179
|
+
* Useful for manual cache control or sharing cache between components.
|
|
180
|
+
*/
|
|
181
|
+
cacheKey?: string;
|
|
182
|
+
|
|
183
|
+
// --- Pagination options ---
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Enable pagination for this request.
|
|
187
|
+
* When true, the composable injects page/perPage params and exposes `pagination` state + helpers.
|
|
188
|
+
* Uses global pagination config by default (set via plugins/api-pagination.ts).
|
|
189
|
+
* @example
|
|
190
|
+
* const { data, pagination, goToPage, nextPage, prevPage, setPerPage } = useGetPets(params, { paginated: true })
|
|
191
|
+
*/
|
|
192
|
+
paginated?: boolean;
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Initial page number. Defaults to global config default (usually 1).
|
|
196
|
+
*/
|
|
197
|
+
initialPage?: number;
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Initial page size. Defaults to global config default (usually 20).
|
|
201
|
+
*/
|
|
202
|
+
initialPerPage?: number;
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Per-request pagination config override.
|
|
206
|
+
* Takes priority over the global pagination config set in plugins/api-pagination.ts.
|
|
207
|
+
* Useful when one specific endpoint has a different pagination convention.
|
|
208
|
+
*/
|
|
209
|
+
paginationConfig?: import('./pagination.js').PaginationConfig;
|
|
210
|
+
|
|
211
|
+
// --- Common fetch options (available in all composables) ---
|
|
212
|
+
|
|
213
|
+
/** Base URL prepended to every request URL. Overrides runtimeConfig.public.apiBaseUrl. */
|
|
214
|
+
baseURL?: string;
|
|
215
|
+
|
|
216
|
+
/** HTTP method (GET, POST, PUT, PATCH, DELETE, etc.) */
|
|
217
|
+
method?: string;
|
|
218
|
+
|
|
219
|
+
/** Request body */
|
|
220
|
+
body?: any;
|
|
221
|
+
|
|
222
|
+
/** Request headers */
|
|
223
|
+
headers?: Record<string, string> | HeadersInit;
|
|
224
|
+
|
|
225
|
+
/** URL query parameters */
|
|
226
|
+
query?: Record<string, any>;
|
|
227
|
+
|
|
228
|
+
/** Alias for query */
|
|
229
|
+
params?: Record<string, any>;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Helper function to apply request modifications from onRequest interceptor
|
|
234
|
+
*/
|
|
235
|
+
export function applyRequestModifications(
|
|
236
|
+
options: Record<string, any>,
|
|
237
|
+
modifications: ModifiedRequestContext
|
|
238
|
+
): void {
|
|
239
|
+
if (modifications.body !== undefined) {
|
|
240
|
+
options.body = modifications.body;
|
|
241
|
+
}
|
|
242
|
+
if (modifications.headers !== undefined) {
|
|
243
|
+
options.headers = {
|
|
244
|
+
...options.headers,
|
|
245
|
+
...modifications.headers,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
if (modifications.query !== undefined) {
|
|
249
|
+
options.query = {
|
|
250
|
+
...options.query,
|
|
251
|
+
...modifications.query,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Helper function to pick specific keys from an object
|
|
258
|
+
* Supports dot notation for nested paths (e.g., 'person.name')
|
|
259
|
+
*/
|
|
260
|
+
export function applyPick<T>(data: T, paths: ReadonlyArray<string>): any {
|
|
261
|
+
const result: any = {};
|
|
262
|
+
|
|
263
|
+
for (const path of paths) {
|
|
264
|
+
const keys = path.split('.');
|
|
265
|
+
|
|
266
|
+
// Navigate to the nested value
|
|
267
|
+
let value: any = data;
|
|
268
|
+
let exists = true;
|
|
269
|
+
|
|
270
|
+
for (const key of keys) {
|
|
271
|
+
if (value && typeof value === 'object' && key in value) {
|
|
272
|
+
value = value[key];
|
|
273
|
+
} else {
|
|
274
|
+
exists = false;
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Set the value in the result, maintaining nested structure
|
|
280
|
+
if (exists) {
|
|
281
|
+
let current = result;
|
|
282
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
283
|
+
const key = keys[i];
|
|
284
|
+
if (!(key in current)) {
|
|
285
|
+
current[key] = {};
|
|
286
|
+
}
|
|
287
|
+
current = current[key];
|
|
288
|
+
}
|
|
289
|
+
current[keys[keys.length - 1]] = value;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return result;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Helper function to get global headers from user configuration
|
|
298
|
+
* Supports two methods:
|
|
299
|
+
* 1. Auto-imported composable: composables/useApiHeaders.ts
|
|
300
|
+
* 2. Nuxt plugin provide: plugins/api-config.ts with $getApiHeaders
|
|
301
|
+
*/
|
|
302
|
+
export function getGlobalHeaders(): Record<string, string> {
|
|
303
|
+
let headers: Record<string, string> = {};
|
|
304
|
+
|
|
305
|
+
// Method 1: Try to use auto-imported composable (useApiHeaders)
|
|
306
|
+
try {
|
|
307
|
+
// @ts-ignore - useApiHeaders may or may not exist (user-defined)
|
|
308
|
+
if (typeof useApiHeaders !== 'undefined') {
|
|
309
|
+
// @ts-ignore
|
|
310
|
+
const getHeaders = useApiHeaders();
|
|
311
|
+
if (getHeaders) {
|
|
312
|
+
const h = typeof getHeaders === 'function' ? getHeaders() : getHeaders;
|
|
313
|
+
if (h && typeof h === 'object') {
|
|
314
|
+
headers = { ...headers, ...h };
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
} catch (e) {
|
|
319
|
+
// useApiHeaders doesn't exist or failed, that's OK
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Method 2: Try to use Nuxt App plugin ($getApiHeaders)
|
|
323
|
+
try {
|
|
324
|
+
const nuxtApp = useNuxtApp();
|
|
325
|
+
// @ts-ignore - $getApiHeaders may or may not exist (user-defined)
|
|
326
|
+
if (nuxtApp.$getApiHeaders) {
|
|
327
|
+
// @ts-ignore
|
|
328
|
+
const h = nuxtApp.$getApiHeaders();
|
|
329
|
+
if (h && typeof h === 'object') {
|
|
330
|
+
headers = { ...headers, ...h };
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
} catch (e) {
|
|
334
|
+
// useNuxtApp not available or plugin not configured, that's OK
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return headers;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Helper function to get global callback rules from user configuration.
|
|
342
|
+
* Always returns a normalized array — wraps legacy single-object config automatically for
|
|
343
|
+
* full backward compatibility.
|
|
344
|
+
* Uses Nuxt plugin provide: plugins/api-callbacks.ts with $getGlobalApiCallbacks
|
|
345
|
+
*/
|
|
346
|
+
export function getGlobalCallbacks(): GlobalCallbacksRule[] {
|
|
347
|
+
try {
|
|
348
|
+
const nuxtApp = useNuxtApp();
|
|
349
|
+
// @ts-ignore - $getGlobalApiCallbacks may or may not exist (user-defined)
|
|
350
|
+
if (nuxtApp.$getGlobalApiCallbacks) {
|
|
351
|
+
// @ts-ignore
|
|
352
|
+
const config: GlobalCallbacksConfig = nuxtApp.$getGlobalApiCallbacks();
|
|
353
|
+
if (config && typeof config === 'object') {
|
|
354
|
+
// Normalize: wrap single-rule object in array for backward compatibility
|
|
355
|
+
return Array.isArray(config) ? config : [config];
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
} catch (e) {
|
|
359
|
+
// useNuxtApp not available or plugin not configured, that's OK
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return [];
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Helper function to get the global base URL from runtimeConfig.public.apiBaseUrl
|
|
367
|
+
* Returns the configured URL or undefined if not set or not in a Nuxt context.
|
|
368
|
+
*/
|
|
369
|
+
export function getGlobalBaseUrl(): string | undefined {
|
|
370
|
+
try {
|
|
371
|
+
const runtimeConfig = useRuntimeConfig();
|
|
372
|
+
const url = runtimeConfig?.public?.apiBaseUrl as string | undefined;
|
|
373
|
+
return url || undefined;
|
|
374
|
+
} catch {
|
|
375
|
+
// useRuntimeConfig not available outside Nuxt context, that's OK
|
|
376
|
+
return undefined;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Check if a global rule should be applied to a specific request.
|
|
382
|
+
* Implements Opción 1 (skipGlobalCallbacks), URL pattern matching, and HTTP method matching.
|
|
383
|
+
*/
|
|
384
|
+
export function shouldApplyGlobalCallback(
|
|
385
|
+
url: string,
|
|
386
|
+
method: string,
|
|
387
|
+
callbackName: 'onRequest' | 'onSuccess' | 'onError' | 'onFinish',
|
|
388
|
+
rule: GlobalCallbacksRule,
|
|
389
|
+
skipConfig?: SkipGlobalCallbacks
|
|
390
|
+
): boolean {
|
|
391
|
+
// Opción 1: Check if callback is skipped via skipGlobalCallbacks
|
|
392
|
+
if (skipConfig === true) return false;
|
|
393
|
+
if (Array.isArray(skipConfig) && skipConfig.includes(callbackName)) return false;
|
|
394
|
+
|
|
395
|
+
// URL pattern matching — if patterns defined, URL must match at least one
|
|
396
|
+
if (rule.patterns && rule.patterns.length > 0) {
|
|
397
|
+
const matchesUrl = rule.patterns.some((pattern) => {
|
|
398
|
+
// Convert glob pattern to regex: ** = any path, * = single segment
|
|
399
|
+
const regexPattern = pattern
|
|
400
|
+
.replace(/\*\*/g, '@@DOUBLE_STAR@@')
|
|
401
|
+
.replace(/\*/g, '[^/]*')
|
|
402
|
+
.replace(/@@DOUBLE_STAR@@/g, '.*');
|
|
403
|
+
return new RegExp('^' + regexPattern + '$').test(url);
|
|
404
|
+
});
|
|
405
|
+
if (!matchesUrl) return false;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Method matching — if methods defined, request method must match at least one
|
|
409
|
+
if (rule.methods && rule.methods.length > 0) {
|
|
410
|
+
if (!rule.methods.map((m) => m.toUpperCase()).includes(method.toUpperCase())) return false;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return true;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Merge local and global callback rules with proper execution order.
|
|
418
|
+
* Global rules are iterated in definition order. Any rule returning false suppresses the local callback.
|
|
419
|
+
* Implements all 3 options:
|
|
420
|
+
* - Opción 1: skipGlobalCallbacks to disable all global rules per request
|
|
421
|
+
* - Opción 2: a rule callback can return false to prevent local callback execution
|
|
422
|
+
* - Opción 3: per-rule URL pattern matching and HTTP method filtering
|
|
423
|
+
*/
|
|
424
|
+
export function mergeCallbacks(
|
|
425
|
+
url: string,
|
|
426
|
+
method: string,
|
|
427
|
+
localCallbacks: {
|
|
428
|
+
onRequest?: Function;
|
|
429
|
+
onSuccess?: Function;
|
|
430
|
+
onError?: Function;
|
|
431
|
+
onFinish?: Function;
|
|
432
|
+
},
|
|
433
|
+
skipConfig?: SkipGlobalCallbacks
|
|
434
|
+
) {
|
|
435
|
+
const rules = getGlobalCallbacks();
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Iterate all applicable global rules for onSuccess, onError, or onFinish.
|
|
439
|
+
* Returns true if the local callback should still execute.
|
|
440
|
+
*/
|
|
441
|
+
async function runGlobalRules(
|
|
442
|
+
callbackName: 'onSuccess' | 'onError' | 'onFinish',
|
|
443
|
+
...args: any[]
|
|
444
|
+
): Promise<boolean> {
|
|
445
|
+
let continueLocal = true;
|
|
446
|
+
for (const rule of rules) {
|
|
447
|
+
const cb = rule[callbackName];
|
|
448
|
+
if (!cb || !shouldApplyGlobalCallback(url, method, callbackName, rule, skipConfig)) continue;
|
|
449
|
+
try {
|
|
450
|
+
const result = await (cb as Function)(...args);
|
|
451
|
+
// Opción 2: returning false from any rule suppresses the local callback
|
|
452
|
+
if (result === false) continueLocal = false;
|
|
453
|
+
} catch (error) {
|
|
454
|
+
console.error(`Error in global ${callbackName} callback:`, error);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
return continueLocal;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
return {
|
|
461
|
+
/**
|
|
462
|
+
* Merged onRequest: runs all applicable global rules collecting and deep-merging
|
|
463
|
+
* modifications (headers and query are merged; body is last-write-wins).
|
|
464
|
+
* Local onRequest runs after all rules unless any returns false, and its
|
|
465
|
+
* modifications take highest priority.
|
|
466
|
+
*/
|
|
467
|
+
onRequest: async (ctx: RequestContext) => {
|
|
468
|
+
let mergedMods: ModifiedRequestContext | undefined;
|
|
469
|
+
let continueLocal = true;
|
|
470
|
+
|
|
471
|
+
for (const rule of rules) {
|
|
472
|
+
if (!rule.onRequest || !shouldApplyGlobalCallback(url, method, 'onRequest', rule, skipConfig)) continue;
|
|
473
|
+
try {
|
|
474
|
+
const result = await rule.onRequest(ctx);
|
|
475
|
+
if (result === false) {
|
|
476
|
+
continueLocal = false;
|
|
477
|
+
} else if (result && typeof result === 'object') {
|
|
478
|
+
const mod = result as ModifiedRequestContext;
|
|
479
|
+
// Deep-merge headers and query; body is last-write-wins
|
|
480
|
+
mergedMods = {
|
|
481
|
+
...(mergedMods ?? {}),
|
|
482
|
+
...mod,
|
|
483
|
+
headers: { ...(mergedMods?.headers ?? {}), ...(mod.headers ?? {}) },
|
|
484
|
+
query: { ...(mergedMods?.query ?? {}), ...(mod.query ?? {}) },
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
} catch (error) {
|
|
488
|
+
console.error('Error in global onRequest callback:', error);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Execute local onRequest — its modifications take highest priority
|
|
493
|
+
if (continueLocal && localCallbacks.onRequest) {
|
|
494
|
+
const localResult = await localCallbacks.onRequest(ctx);
|
|
495
|
+
if (localResult && typeof localResult === 'object') {
|
|
496
|
+
const localMod = localResult as ModifiedRequestContext;
|
|
497
|
+
return mergedMods
|
|
498
|
+
? {
|
|
499
|
+
...mergedMods,
|
|
500
|
+
...localMod,
|
|
501
|
+
headers: { ...(mergedMods.headers ?? {}), ...(localMod.headers ?? {}) },
|
|
502
|
+
query: { ...(mergedMods.query ?? {}), ...(localMod.query ?? {}) },
|
|
503
|
+
}
|
|
504
|
+
: localMod;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
return mergedMods;
|
|
509
|
+
},
|
|
510
|
+
|
|
511
|
+
/** Merged onSuccess: global rules first in order, then local (unless suppressed). */
|
|
512
|
+
onSuccess: async (data: any, context?: any) => {
|
|
513
|
+
const continueLocal = await runGlobalRules('onSuccess', data, context);
|
|
514
|
+
if (continueLocal && localCallbacks.onSuccess) {
|
|
515
|
+
await localCallbacks.onSuccess(data, context);
|
|
516
|
+
}
|
|
517
|
+
},
|
|
518
|
+
|
|
519
|
+
/** Merged onError: global rules first in order, then local (unless suppressed). */
|
|
520
|
+
onError: async (error: any, context?: any) => {
|
|
521
|
+
const continueLocal = await runGlobalRules('onError', error, context);
|
|
522
|
+
if (continueLocal && localCallbacks.onError) {
|
|
523
|
+
await localCallbacks.onError(error, context);
|
|
524
|
+
}
|
|
525
|
+
},
|
|
526
|
+
|
|
527
|
+
/** Merged onFinish: global rules first in order, then local (unless suppressed). */
|
|
528
|
+
onFinish: async (context: any) => {
|
|
529
|
+
const continueLocal = await runGlobalRules('onFinish', context);
|
|
530
|
+
if (continueLocal && localCallbacks.onFinish) {
|
|
531
|
+
await localCallbacks.onFinish(context);
|
|
532
|
+
}
|
|
533
|
+
},
|
|
534
|
+
};
|
|
535
|
+
}
|