@ametie/vue-muza-use 0.6.1 → 0.7.0
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/dist/index.cjs +149 -58
- package/dist/index.d.cts +49 -33
- package/dist/index.d.ts +49 -33
- package/dist/index.mjs +150 -59
- package/package.json +3 -2
package/dist/index.cjs
CHANGED
|
@@ -75,17 +75,34 @@ function useApiConfig() {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
// src/utils/debounce.ts
|
|
78
|
+
var DebounceCancelledError = class extends Error {
|
|
79
|
+
isDebounceCancelled = true;
|
|
80
|
+
constructor() {
|
|
81
|
+
super("Debounced call was superseded by a newer call");
|
|
82
|
+
this.name = "DebounceCancelledError";
|
|
83
|
+
}
|
|
84
|
+
};
|
|
78
85
|
function debounceFn(fn, delay) {
|
|
79
86
|
let timeoutId = null;
|
|
87
|
+
let pendingReject = null;
|
|
80
88
|
return function(...args) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
89
|
+
if (pendingReject) {
|
|
90
|
+
pendingReject(new DebounceCancelledError());
|
|
91
|
+
pendingReject = null;
|
|
92
|
+
}
|
|
93
|
+
if (timeoutId) {
|
|
94
|
+
clearTimeout(timeoutId);
|
|
95
|
+
}
|
|
96
|
+
return new Promise((resolve, reject) => {
|
|
97
|
+
pendingReject = reject;
|
|
85
98
|
timeoutId = setTimeout(async () => {
|
|
86
|
-
|
|
87
|
-
resolve(result);
|
|
99
|
+
pendingReject = null;
|
|
88
100
|
timeoutId = null;
|
|
101
|
+
try {
|
|
102
|
+
resolve(await fn(...args));
|
|
103
|
+
} catch (err) {
|
|
104
|
+
reject(err);
|
|
105
|
+
}
|
|
89
106
|
}, delay);
|
|
90
107
|
});
|
|
91
108
|
};
|
|
@@ -196,6 +213,26 @@ function useAbortController() {
|
|
|
196
213
|
}
|
|
197
214
|
|
|
198
215
|
// src/useApi.ts
|
|
216
|
+
var DEFAULT_RETRY_STATUS_CODES = [408, 429, 500, 502, 503, 504];
|
|
217
|
+
function cancellableSleep(ms, signal) {
|
|
218
|
+
return new Promise((resolve) => {
|
|
219
|
+
if (signal.aborted) {
|
|
220
|
+
resolve(true);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
const timer = setTimeout(() => {
|
|
224
|
+
cleanup();
|
|
225
|
+
resolve(false);
|
|
226
|
+
}, ms);
|
|
227
|
+
const onAbort = () => {
|
|
228
|
+
clearTimeout(timer);
|
|
229
|
+
cleanup();
|
|
230
|
+
resolve(true);
|
|
231
|
+
};
|
|
232
|
+
const cleanup = () => signal.removeEventListener("abort", onAbort);
|
|
233
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
234
|
+
});
|
|
235
|
+
}
|
|
199
236
|
function useApi(url, options = {}) {
|
|
200
237
|
const { axios: axios2, onError: globalErrorHandler, globalOptions, errorParser } = useApiConfig();
|
|
201
238
|
const {
|
|
@@ -210,12 +247,14 @@ function useApi(url, options = {}) {
|
|
|
210
247
|
skipErrorNotification = false,
|
|
211
248
|
retry = globalOptions?.retry ?? false,
|
|
212
249
|
retryDelay = globalOptions?.retryDelay ?? 1e3,
|
|
250
|
+
retryStatusCodes = globalOptions?.retryStatusCodes ?? DEFAULT_RETRY_STATUS_CODES,
|
|
213
251
|
authMode = "default",
|
|
214
252
|
useGlobalAbort = globalOptions?.useGlobalAbort ?? true,
|
|
215
253
|
initialLoading = false,
|
|
216
254
|
poll = 0,
|
|
217
255
|
...axiosConfig
|
|
218
256
|
} = options;
|
|
257
|
+
const maxRetries = retry === false ? 0 : retry === true ? 3 : retry;
|
|
219
258
|
const startLoading = initialLoading ?? immediate;
|
|
220
259
|
const state = useApiState(initialData, { initialLoading: startLoading });
|
|
221
260
|
const abortController2 = (0, import_vue4.ref)(null);
|
|
@@ -255,28 +294,57 @@ function useApi(url, options = {}) {
|
|
|
255
294
|
state.setLoading(true);
|
|
256
295
|
state.setError(null);
|
|
257
296
|
let wasCancelled = false;
|
|
297
|
+
let retryCount = 0;
|
|
258
298
|
try {
|
|
299
|
+
if (!requestUrl) {
|
|
300
|
+
throw new Error("Request URL is missing");
|
|
301
|
+
}
|
|
259
302
|
const rawData = config?.data !== void 0 ? config.data : axiosConfig.data;
|
|
260
303
|
const resolvedData = (0, import_vue4.toValue)(rawData);
|
|
261
304
|
const rawParams = config?.params !== void 0 ? config.params : axiosConfig.params;
|
|
262
305
|
const resolvedParams = (0, import_vue4.toValue)(rawParams);
|
|
263
|
-
|
|
264
|
-
|
|
306
|
+
while (true) {
|
|
307
|
+
try {
|
|
308
|
+
const response = await axios2.request({
|
|
309
|
+
url: requestUrl,
|
|
310
|
+
method,
|
|
311
|
+
...axiosConfig,
|
|
312
|
+
...config,
|
|
313
|
+
data: resolvedData,
|
|
314
|
+
params: resolvedParams,
|
|
315
|
+
signal: controller.signal,
|
|
316
|
+
...{ authMode: config?.authMode || authMode }
|
|
317
|
+
});
|
|
318
|
+
state.mutate(response.data, response);
|
|
319
|
+
state.setStatusCode(response.status);
|
|
320
|
+
onSuccess?.(response);
|
|
321
|
+
return response.data;
|
|
322
|
+
} catch (err) {
|
|
323
|
+
if (controller.signal.aborted || (0, import_axios2.isAxiosError)(err) && err.code === "ERR_CANCELED") {
|
|
324
|
+
wasCancelled = true;
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
const apiError = errorParser ? errorParser(err) : parseApiError(err);
|
|
328
|
+
const canRetry = retryCount < maxRetries && (retryStatusCodes.length === 0 || retryStatusCodes.includes(apiError.status));
|
|
329
|
+
if (canRetry) {
|
|
330
|
+
retryCount++;
|
|
331
|
+
const aborted = await cancellableSleep(retryDelay, controller.signal);
|
|
332
|
+
if (aborted) {
|
|
333
|
+
wasCancelled = true;
|
|
334
|
+
state.setLoading(false);
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
if (!skipErrorNotification && globalErrorHandler) {
|
|
340
|
+
globalErrorHandler(apiError, err);
|
|
341
|
+
}
|
|
342
|
+
state.setError(apiError);
|
|
343
|
+
state.setStatusCode(apiError.status);
|
|
344
|
+
onError?.(apiError);
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
265
347
|
}
|
|
266
|
-
const response = await axios2.request({
|
|
267
|
-
url: requestUrl,
|
|
268
|
-
method,
|
|
269
|
-
...axiosConfig,
|
|
270
|
-
...config,
|
|
271
|
-
data: resolvedData,
|
|
272
|
-
params: resolvedParams,
|
|
273
|
-
signal: controller.signal,
|
|
274
|
-
...{ authMode: config?.authMode || authMode }
|
|
275
|
-
});
|
|
276
|
-
state.mutate(response.data, response);
|
|
277
|
-
state.setStatusCode(response.status);
|
|
278
|
-
onSuccess?.(response);
|
|
279
|
-
return response.data;
|
|
280
348
|
} catch (err) {
|
|
281
349
|
if (controller.signal.aborted || (0, import_axios2.isAxiosError)(err) && err.code === "ERR_CANCELED") {
|
|
282
350
|
wasCancelled = true;
|
|
@@ -311,7 +379,11 @@ function useApi(url, options = {}) {
|
|
|
311
379
|
}
|
|
312
380
|
}
|
|
313
381
|
};
|
|
314
|
-
const
|
|
382
|
+
const _debounced = debounce > 0 ? debounceFn(executeRequest, debounce) : null;
|
|
383
|
+
const execute = _debounced ? (config) => _debounced(config).catch((err) => {
|
|
384
|
+
if (err instanceof DebounceCancelledError) return null;
|
|
385
|
+
throw err;
|
|
386
|
+
}) : executeRequest;
|
|
315
387
|
const abort = (msg) => {
|
|
316
388
|
if (pollTimer) clearTimeout(pollTimer);
|
|
317
389
|
abortController2.value?.abort(msg);
|
|
@@ -383,7 +455,11 @@ function useApiDelete(url, options) {
|
|
|
383
455
|
|
|
384
456
|
// src/useApiBatch.ts
|
|
385
457
|
var import_vue5 = require("vue");
|
|
386
|
-
function
|
|
458
|
+
function normalizeRequest(item) {
|
|
459
|
+
if (typeof item === "string") return { url: item, method: "GET" };
|
|
460
|
+
return { method: "GET", ...item };
|
|
461
|
+
}
|
|
462
|
+
function useApiBatch(requests, options = {}) {
|
|
387
463
|
const {
|
|
388
464
|
settled = true,
|
|
389
465
|
concurrency,
|
|
@@ -396,7 +472,7 @@ function useApiBatch(urls, options = {}) {
|
|
|
396
472
|
onProgress,
|
|
397
473
|
...apiOptions
|
|
398
474
|
} = options;
|
|
399
|
-
const
|
|
475
|
+
const getRequests = () => (0, import_vue5.toValue)(requests).map(normalizeRequest);
|
|
400
476
|
const data = (0, import_vue5.ref)([]);
|
|
401
477
|
const loading = (0, import_vue5.ref)(false);
|
|
402
478
|
const error = (0, import_vue5.ref)(null);
|
|
@@ -414,43 +490,54 @@ function useApiBatch(urls, options = {}) {
|
|
|
414
490
|
const abortControllers = (0, import_vue5.ref)([]);
|
|
415
491
|
let isAborted = false;
|
|
416
492
|
const updateProgress = (succeeded, failed) => {
|
|
417
|
-
const
|
|
493
|
+
const currentRequests = getRequests();
|
|
418
494
|
const completed = succeeded + failed;
|
|
419
495
|
const newProgress = {
|
|
420
496
|
completed,
|
|
421
|
-
total:
|
|
422
|
-
percentage:
|
|
497
|
+
total: currentRequests.length,
|
|
498
|
+
percentage: currentRequests.length > 0 ? Math.round(completed / currentRequests.length * 100) : 0,
|
|
423
499
|
succeeded,
|
|
424
500
|
failed
|
|
425
501
|
};
|
|
426
502
|
progress.value = newProgress;
|
|
427
503
|
onProgress?.(newProgress);
|
|
428
504
|
};
|
|
429
|
-
const executeRequest = async (
|
|
430
|
-
const
|
|
505
|
+
const executeRequest = async (config, index, signal) => {
|
|
506
|
+
const scope = (0, import_vue5.effectScope)();
|
|
507
|
+
const api = scope.run(() => useApi(config.url, {
|
|
431
508
|
...apiOptions,
|
|
509
|
+
method: config.method,
|
|
510
|
+
data: config.data,
|
|
511
|
+
params: config.params,
|
|
512
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
513
|
+
...config.headers && { headers: config.headers },
|
|
432
514
|
useGlobalAbort: false,
|
|
433
515
|
skipErrorNotification
|
|
434
|
-
});
|
|
516
|
+
}));
|
|
517
|
+
const { execute: execute2, error: reqError, statusCode, response } = api;
|
|
435
518
|
try {
|
|
436
519
|
const result = await execute2({ signal });
|
|
437
520
|
if (signal.aborted) {
|
|
438
521
|
return {
|
|
439
|
-
url,
|
|
522
|
+
url: config.url,
|
|
440
523
|
index,
|
|
441
524
|
success: false,
|
|
442
525
|
data: null,
|
|
443
526
|
error: { message: "Request aborted", status: 0, code: "ABORTED" },
|
|
444
|
-
statusCode: null
|
|
527
|
+
statusCode: null,
|
|
528
|
+
response: null,
|
|
529
|
+
request: config
|
|
445
530
|
};
|
|
446
531
|
}
|
|
447
532
|
const item = {
|
|
448
|
-
url,
|
|
533
|
+
url: config.url,
|
|
449
534
|
index,
|
|
450
535
|
success: result !== null && result !== void 0,
|
|
451
536
|
data: result ?? null,
|
|
452
537
|
error: reqError.value,
|
|
453
|
-
statusCode: statusCode.value
|
|
538
|
+
statusCode: statusCode.value,
|
|
539
|
+
response: response.value,
|
|
540
|
+
request: config
|
|
454
541
|
};
|
|
455
542
|
if (item.success) {
|
|
456
543
|
onItemSuccess?.(item, index);
|
|
@@ -465,26 +552,30 @@ function useApiBatch(urls, options = {}) {
|
|
|
465
552
|
code: "BATCH_ERROR"
|
|
466
553
|
};
|
|
467
554
|
const item = {
|
|
468
|
-
url,
|
|
555
|
+
url: config.url,
|
|
469
556
|
index,
|
|
470
557
|
success: false,
|
|
471
558
|
data: null,
|
|
472
559
|
error: apiError,
|
|
473
|
-
statusCode: null
|
|
560
|
+
statusCode: null,
|
|
561
|
+
response: null,
|
|
562
|
+
request: config
|
|
474
563
|
};
|
|
475
564
|
onItemError?.(item, index);
|
|
476
565
|
return item;
|
|
566
|
+
} finally {
|
|
567
|
+
scope.stop();
|
|
477
568
|
}
|
|
478
569
|
};
|
|
479
|
-
const executeWithConcurrency = async (
|
|
480
|
-
const results = new Array(
|
|
570
|
+
const executeWithConcurrency = async (requests2, limit) => {
|
|
571
|
+
const results = new Array(requests2.length);
|
|
481
572
|
let succeededCount = 0;
|
|
482
573
|
let failedCount = 0;
|
|
483
|
-
if (!limit || limit >=
|
|
484
|
-
const promises =
|
|
574
|
+
if (!limit || limit >= requests2.length) {
|
|
575
|
+
const promises = requests2.map((config, index) => {
|
|
485
576
|
const controller = new AbortController();
|
|
486
577
|
abortControllers.value.push(controller);
|
|
487
|
-
return executeRequest(
|
|
578
|
+
return executeRequest(config, index, controller.signal).then((result) => {
|
|
488
579
|
results[index] = result;
|
|
489
580
|
if (result.success) {
|
|
490
581
|
succeededCount++;
|
|
@@ -509,12 +600,12 @@ function useApiBatch(urls, options = {}) {
|
|
|
509
600
|
} else {
|
|
510
601
|
let currentIndex = 0;
|
|
511
602
|
const executeNext = async () => {
|
|
512
|
-
while (currentIndex <
|
|
603
|
+
while (currentIndex < requests2.length && !isAborted) {
|
|
513
604
|
const index = currentIndex++;
|
|
514
|
-
const
|
|
605
|
+
const config = requests2[index];
|
|
515
606
|
const controller = new AbortController();
|
|
516
607
|
abortControllers.value.push(controller);
|
|
517
|
-
const result = await executeRequest(
|
|
608
|
+
const result = await executeRequest(config, index, controller.signal);
|
|
518
609
|
results[index] = result;
|
|
519
610
|
if (result.success) {
|
|
520
611
|
succeededCount++;
|
|
@@ -531,7 +622,7 @@ function useApiBatch(urls, options = {}) {
|
|
|
531
622
|
}
|
|
532
623
|
}
|
|
533
624
|
};
|
|
534
|
-
const workers = Array.from({ length: Math.min(limit,
|
|
625
|
+
const workers = Array.from({ length: Math.min(limit, requests2.length) }, () => executeNext());
|
|
535
626
|
if (settled) {
|
|
536
627
|
await Promise.allSettled(workers);
|
|
537
628
|
} else {
|
|
@@ -541,7 +632,7 @@ function useApiBatch(urls, options = {}) {
|
|
|
541
632
|
return results;
|
|
542
633
|
};
|
|
543
634
|
const execute = async () => {
|
|
544
|
-
const
|
|
635
|
+
const currentRequests = getRequests();
|
|
545
636
|
isAborted = false;
|
|
546
637
|
loading.value = true;
|
|
547
638
|
error.value = null;
|
|
@@ -550,7 +641,7 @@ function useApiBatch(urls, options = {}) {
|
|
|
550
641
|
abortControllers.value = [];
|
|
551
642
|
updateProgress(0, 0);
|
|
552
643
|
try {
|
|
553
|
-
const results = await executeWithConcurrency(
|
|
644
|
+
const results = await executeWithConcurrency(currentRequests, concurrency);
|
|
554
645
|
data.value = results;
|
|
555
646
|
const allFailed = results.every((r) => !r.success);
|
|
556
647
|
if (allFailed && results.length > 0) {
|
|
@@ -587,7 +678,7 @@ function useApiBatch(urls, options = {}) {
|
|
|
587
678
|
data.value = [];
|
|
588
679
|
progress.value = {
|
|
589
680
|
completed: 0,
|
|
590
|
-
total:
|
|
681
|
+
total: getRequests().length,
|
|
591
682
|
percentage: 0,
|
|
592
683
|
succeeded: 0,
|
|
593
684
|
failed: 0
|
|
@@ -779,16 +870,16 @@ var tokenManager = new TokenManager();
|
|
|
779
870
|
// src/features/interceptors.ts
|
|
780
871
|
var AUTH_HEADER = "Authorization";
|
|
781
872
|
var TOKEN_TYPE2 = "Bearer";
|
|
782
|
-
var failedQueue = [];
|
|
783
|
-
var isRefreshing = false;
|
|
784
|
-
function processQueue(error, token = null) {
|
|
785
|
-
failedQueue.forEach((promise) => {
|
|
786
|
-
if (error) promise.reject(error);
|
|
787
|
-
else if (token) promise.resolve(token);
|
|
788
|
-
});
|
|
789
|
-
failedQueue = [];
|
|
790
|
-
}
|
|
791
873
|
function setupInterceptors(axiosInstance, options = {}) {
|
|
874
|
+
let failedQueue = [];
|
|
875
|
+
let isRefreshing = false;
|
|
876
|
+
function processQueue(error, token = null) {
|
|
877
|
+
failedQueue.forEach((promise) => {
|
|
878
|
+
if (error) promise.reject(error);
|
|
879
|
+
else if (token) promise.resolve(token);
|
|
880
|
+
});
|
|
881
|
+
failedQueue = [];
|
|
882
|
+
}
|
|
792
883
|
const {
|
|
793
884
|
refreshUrl = "/auth/refresh",
|
|
794
885
|
refreshWithCredentials = false,
|
package/dist/index.d.cts
CHANGED
|
@@ -23,6 +23,12 @@ interface ApiRequestConfig<D = unknown> extends Omit<AxiosRequestConfig<D>, "dat
|
|
|
23
23
|
authMode?: AuthMode;
|
|
24
24
|
retry?: boolean | number;
|
|
25
25
|
retryDelay?: number;
|
|
26
|
+
/**
|
|
27
|
+
* Retry only when the response status code is in this list.
|
|
28
|
+
* Default: [408, 429, 500, 502, 503, 504]
|
|
29
|
+
* Empty array = retry on any error (network errors included).
|
|
30
|
+
*/
|
|
31
|
+
retryStatusCodes?: number[];
|
|
26
32
|
}
|
|
27
33
|
interface UseApiOptions<T = unknown, D = unknown> extends ApiRequestConfig<D> {
|
|
28
34
|
immediate?: boolean;
|
|
@@ -86,6 +92,7 @@ interface ApiPluginOptions {
|
|
|
86
92
|
globalOptions?: {
|
|
87
93
|
retry?: number | boolean;
|
|
88
94
|
retryDelay?: number;
|
|
95
|
+
retryStatusCodes?: number[];
|
|
89
96
|
useGlobalAbort?: boolean;
|
|
90
97
|
};
|
|
91
98
|
}
|
|
@@ -94,6 +101,23 @@ interface AuthTokens$1 {
|
|
|
94
101
|
refreshToken?: string;
|
|
95
102
|
expiresIn?: number;
|
|
96
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Per-request configuration for a single item in a batch operation.
|
|
106
|
+
* String items in the batch array are automatically normalized to this shape
|
|
107
|
+
* with method: 'GET' and no data/params/headers.
|
|
108
|
+
*/
|
|
109
|
+
interface BatchRequestConfig<D = unknown> {
|
|
110
|
+
/** The URL to request */
|
|
111
|
+
url: string;
|
|
112
|
+
/** HTTP method. Default: 'GET' */
|
|
113
|
+
method?: string;
|
|
114
|
+
/** Request body (for POST, PUT, PATCH) */
|
|
115
|
+
data?: D;
|
|
116
|
+
/** Query parameters */
|
|
117
|
+
params?: D;
|
|
118
|
+
/** Per-request headers that override global defaults for this request only */
|
|
119
|
+
headers?: Record<string, string>;
|
|
120
|
+
}
|
|
97
121
|
/**
|
|
98
122
|
* Result of a single request in a batch operation
|
|
99
123
|
*/
|
|
@@ -110,6 +134,10 @@ interface BatchResultItem<T = unknown> {
|
|
|
110
134
|
error: ApiError | null;
|
|
111
135
|
/** HTTP status code */
|
|
112
136
|
statusCode: number | null;
|
|
137
|
+
/** Full AxiosResponse (null if failed — headers, status, etc. accessible here) */
|
|
138
|
+
response: AxiosResponse<T> | null;
|
|
139
|
+
/** The original normalized request config that produced this result */
|
|
140
|
+
request: BatchRequestConfig;
|
|
113
141
|
}
|
|
114
142
|
/**
|
|
115
143
|
* Progress information for batch operations
|
|
@@ -239,7 +267,9 @@ declare function useApiDelete<T = unknown>(url: MaybeRefOrGetter<string | undefi
|
|
|
239
267
|
*
|
|
240
268
|
* Features:
|
|
241
269
|
* - Reactive loading, data, error, progress states
|
|
242
|
-
* - Reactive
|
|
270
|
+
* - Reactive request list support (MaybeRefOrGetter)
|
|
271
|
+
* - Per-request method, data, params, headers configuration
|
|
272
|
+
* - Full backward compatibility — plain string arrays still work
|
|
243
273
|
* - Error tolerance with `settled: true` (default)
|
|
244
274
|
* - Concurrency limiting
|
|
245
275
|
* - Abort support for all pending requests
|
|
@@ -249,43 +279,29 @@ declare function useApiDelete<T = unknown>(url: MaybeRefOrGetter<string | undefi
|
|
|
249
279
|
*
|
|
250
280
|
* @example
|
|
251
281
|
* ```ts
|
|
252
|
-
* // Basic usage
|
|
253
|
-
* const { data,
|
|
254
|
-
* '/users/1',
|
|
255
|
-
* '/users/2',
|
|
256
|
-
* '/users/3'
|
|
257
|
-
* ])
|
|
282
|
+
* // Basic usage — plain strings (backward compatible)
|
|
283
|
+
* const { data, execute } = useApiBatch(['/users/1', '/users/2'])
|
|
258
284
|
*
|
|
259
|
-
*
|
|
260
|
-
*
|
|
285
|
+
* // Per-request config — method, data, params, headers
|
|
286
|
+
* const { data } = useApiBatch([
|
|
287
|
+
* { url: '/users', params: { page: 1 } },
|
|
288
|
+
* { url: '/posts', method: 'POST', data: { title: 'New' } },
|
|
289
|
+
* '/health', // string and object can be mixed
|
|
290
|
+
* ])
|
|
261
291
|
*
|
|
262
|
-
* //
|
|
263
|
-
* const
|
|
264
|
-
*
|
|
265
|
-
* const { successfulData } = useApiBatch<User>(urls, { immediate: true })
|
|
292
|
+
* // Batch DELETE by IDs
|
|
293
|
+
* const ids = [1, 2, 3]
|
|
294
|
+
* useApiBatch(ids.map(id => ({ url: `/users/${id}`, method: 'DELETE' })))
|
|
266
295
|
*
|
|
267
|
-
* //
|
|
268
|
-
* const
|
|
269
|
-
*
|
|
270
|
-
* {
|
|
271
|
-
*
|
|
272
|
-
* immediate: true, // Execute on mount
|
|
273
|
-
* onProgress: (p) => console.log(`${p.percentage}%`)
|
|
274
|
-
* }
|
|
296
|
+
* // Reactive getter with object configs
|
|
297
|
+
* const pages = ref([1, 2, 3])
|
|
298
|
+
* const { successfulData } = useApiBatch(
|
|
299
|
+
* () => pages.value.map(page => ({ url: '/users', params: { page } })),
|
|
300
|
+
* { watch: pages, immediate: true }
|
|
275
301
|
* )
|
|
276
|
-
*
|
|
277
|
-
* // Strict mode - fail on first error
|
|
278
|
-
* const { execute } = useApiBatch<User>(urls, { settled: false })
|
|
279
|
-
*
|
|
280
|
-
* // Auto re-execute when dependency changes
|
|
281
|
-
* const filters = ref({ status: 'active' })
|
|
282
|
-
* const { data } = useApiBatch<User>(urls, {
|
|
283
|
-
* watch: filters,
|
|
284
|
-
* immediate: true
|
|
285
|
-
* })
|
|
286
302
|
* ```
|
|
287
303
|
*/
|
|
288
|
-
declare function useApiBatch<T = unknown>(
|
|
304
|
+
declare function useApiBatch<T = unknown>(requests: MaybeRefOrGetter<Array<string | BatchRequestConfig>>, options?: UseApiBatchOptions<T>): UseApiBatchReturn<T>;
|
|
289
305
|
|
|
290
306
|
/**
|
|
291
307
|
* API State Composable
|
|
@@ -558,4 +574,4 @@ interface AuthEventPayload {
|
|
|
558
574
|
type AuthMonitorFn = (type: AuthEventType, payload: AuthEventPayload) => void;
|
|
559
575
|
declare function setAuthMonitor(fn: AuthMonitorFn): void;
|
|
560
576
|
|
|
561
|
-
export { type ApiError, type ApiPluginOptions, type ApiRequestConfig, type ApiState, type AuthEventPayload, AuthEventType, type AuthMode, type AuthMonitorFn, type AuthTokens$1 as AuthTokens, type BatchProgress, type BatchResultItem, type SetDataInput, type UseApiBatchOptions, type UseApiBatchReturn, type UseApiOptions, type UseApiReturn, createApi, createApiClient, setAuthMonitor, setupInterceptors, tokenManager, useAbortController, useApi, useApiBatch, useApiConfig, useApiDelete, useApiGet, useApiPatch, useApiPost, useApiPut, useApiState };
|
|
577
|
+
export { type ApiError, type ApiPluginOptions, type ApiRequestConfig, type ApiState, type AuthEventPayload, AuthEventType, type AuthMode, type AuthMonitorFn, type AuthTokens$1 as AuthTokens, type BatchProgress, type BatchRequestConfig, type BatchResultItem, type SetDataInput, type UseApiBatchOptions, type UseApiBatchReturn, type UseApiOptions, type UseApiReturn, createApi, createApiClient, setAuthMonitor, setupInterceptors, tokenManager, useAbortController, useApi, useApiBatch, useApiConfig, useApiDelete, useApiGet, useApiPatch, useApiPost, useApiPut, useApiState };
|
package/dist/index.d.ts
CHANGED
|
@@ -23,6 +23,12 @@ interface ApiRequestConfig<D = unknown> extends Omit<AxiosRequestConfig<D>, "dat
|
|
|
23
23
|
authMode?: AuthMode;
|
|
24
24
|
retry?: boolean | number;
|
|
25
25
|
retryDelay?: number;
|
|
26
|
+
/**
|
|
27
|
+
* Retry only when the response status code is in this list.
|
|
28
|
+
* Default: [408, 429, 500, 502, 503, 504]
|
|
29
|
+
* Empty array = retry on any error (network errors included).
|
|
30
|
+
*/
|
|
31
|
+
retryStatusCodes?: number[];
|
|
26
32
|
}
|
|
27
33
|
interface UseApiOptions<T = unknown, D = unknown> extends ApiRequestConfig<D> {
|
|
28
34
|
immediate?: boolean;
|
|
@@ -86,6 +92,7 @@ interface ApiPluginOptions {
|
|
|
86
92
|
globalOptions?: {
|
|
87
93
|
retry?: number | boolean;
|
|
88
94
|
retryDelay?: number;
|
|
95
|
+
retryStatusCodes?: number[];
|
|
89
96
|
useGlobalAbort?: boolean;
|
|
90
97
|
};
|
|
91
98
|
}
|
|
@@ -94,6 +101,23 @@ interface AuthTokens$1 {
|
|
|
94
101
|
refreshToken?: string;
|
|
95
102
|
expiresIn?: number;
|
|
96
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Per-request configuration for a single item in a batch operation.
|
|
106
|
+
* String items in the batch array are automatically normalized to this shape
|
|
107
|
+
* with method: 'GET' and no data/params/headers.
|
|
108
|
+
*/
|
|
109
|
+
interface BatchRequestConfig<D = unknown> {
|
|
110
|
+
/** The URL to request */
|
|
111
|
+
url: string;
|
|
112
|
+
/** HTTP method. Default: 'GET' */
|
|
113
|
+
method?: string;
|
|
114
|
+
/** Request body (for POST, PUT, PATCH) */
|
|
115
|
+
data?: D;
|
|
116
|
+
/** Query parameters */
|
|
117
|
+
params?: D;
|
|
118
|
+
/** Per-request headers that override global defaults for this request only */
|
|
119
|
+
headers?: Record<string, string>;
|
|
120
|
+
}
|
|
97
121
|
/**
|
|
98
122
|
* Result of a single request in a batch operation
|
|
99
123
|
*/
|
|
@@ -110,6 +134,10 @@ interface BatchResultItem<T = unknown> {
|
|
|
110
134
|
error: ApiError | null;
|
|
111
135
|
/** HTTP status code */
|
|
112
136
|
statusCode: number | null;
|
|
137
|
+
/** Full AxiosResponse (null if failed — headers, status, etc. accessible here) */
|
|
138
|
+
response: AxiosResponse<T> | null;
|
|
139
|
+
/** The original normalized request config that produced this result */
|
|
140
|
+
request: BatchRequestConfig;
|
|
113
141
|
}
|
|
114
142
|
/**
|
|
115
143
|
* Progress information for batch operations
|
|
@@ -239,7 +267,9 @@ declare function useApiDelete<T = unknown>(url: MaybeRefOrGetter<string | undefi
|
|
|
239
267
|
*
|
|
240
268
|
* Features:
|
|
241
269
|
* - Reactive loading, data, error, progress states
|
|
242
|
-
* - Reactive
|
|
270
|
+
* - Reactive request list support (MaybeRefOrGetter)
|
|
271
|
+
* - Per-request method, data, params, headers configuration
|
|
272
|
+
* - Full backward compatibility — plain string arrays still work
|
|
243
273
|
* - Error tolerance with `settled: true` (default)
|
|
244
274
|
* - Concurrency limiting
|
|
245
275
|
* - Abort support for all pending requests
|
|
@@ -249,43 +279,29 @@ declare function useApiDelete<T = unknown>(url: MaybeRefOrGetter<string | undefi
|
|
|
249
279
|
*
|
|
250
280
|
* @example
|
|
251
281
|
* ```ts
|
|
252
|
-
* // Basic usage
|
|
253
|
-
* const { data,
|
|
254
|
-
* '/users/1',
|
|
255
|
-
* '/users/2',
|
|
256
|
-
* '/users/3'
|
|
257
|
-
* ])
|
|
282
|
+
* // Basic usage — plain strings (backward compatible)
|
|
283
|
+
* const { data, execute } = useApiBatch(['/users/1', '/users/2'])
|
|
258
284
|
*
|
|
259
|
-
*
|
|
260
|
-
*
|
|
285
|
+
* // Per-request config — method, data, params, headers
|
|
286
|
+
* const { data } = useApiBatch([
|
|
287
|
+
* { url: '/users', params: { page: 1 } },
|
|
288
|
+
* { url: '/posts', method: 'POST', data: { title: 'New' } },
|
|
289
|
+
* '/health', // string and object can be mixed
|
|
290
|
+
* ])
|
|
261
291
|
*
|
|
262
|
-
* //
|
|
263
|
-
* const
|
|
264
|
-
*
|
|
265
|
-
* const { successfulData } = useApiBatch<User>(urls, { immediate: true })
|
|
292
|
+
* // Batch DELETE by IDs
|
|
293
|
+
* const ids = [1, 2, 3]
|
|
294
|
+
* useApiBatch(ids.map(id => ({ url: `/users/${id}`, method: 'DELETE' })))
|
|
266
295
|
*
|
|
267
|
-
* //
|
|
268
|
-
* const
|
|
269
|
-
*
|
|
270
|
-
* {
|
|
271
|
-
*
|
|
272
|
-
* immediate: true, // Execute on mount
|
|
273
|
-
* onProgress: (p) => console.log(`${p.percentage}%`)
|
|
274
|
-
* }
|
|
296
|
+
* // Reactive getter with object configs
|
|
297
|
+
* const pages = ref([1, 2, 3])
|
|
298
|
+
* const { successfulData } = useApiBatch(
|
|
299
|
+
* () => pages.value.map(page => ({ url: '/users', params: { page } })),
|
|
300
|
+
* { watch: pages, immediate: true }
|
|
275
301
|
* )
|
|
276
|
-
*
|
|
277
|
-
* // Strict mode - fail on first error
|
|
278
|
-
* const { execute } = useApiBatch<User>(urls, { settled: false })
|
|
279
|
-
*
|
|
280
|
-
* // Auto re-execute when dependency changes
|
|
281
|
-
* const filters = ref({ status: 'active' })
|
|
282
|
-
* const { data } = useApiBatch<User>(urls, {
|
|
283
|
-
* watch: filters,
|
|
284
|
-
* immediate: true
|
|
285
|
-
* })
|
|
286
302
|
* ```
|
|
287
303
|
*/
|
|
288
|
-
declare function useApiBatch<T = unknown>(
|
|
304
|
+
declare function useApiBatch<T = unknown>(requests: MaybeRefOrGetter<Array<string | BatchRequestConfig>>, options?: UseApiBatchOptions<T>): UseApiBatchReturn<T>;
|
|
289
305
|
|
|
290
306
|
/**
|
|
291
307
|
* API State Composable
|
|
@@ -558,4 +574,4 @@ interface AuthEventPayload {
|
|
|
558
574
|
type AuthMonitorFn = (type: AuthEventType, payload: AuthEventPayload) => void;
|
|
559
575
|
declare function setAuthMonitor(fn: AuthMonitorFn): void;
|
|
560
576
|
|
|
561
|
-
export { type ApiError, type ApiPluginOptions, type ApiRequestConfig, type ApiState, type AuthEventPayload, AuthEventType, type AuthMode, type AuthMonitorFn, type AuthTokens$1 as AuthTokens, type BatchProgress, type BatchResultItem, type SetDataInput, type UseApiBatchOptions, type UseApiBatchReturn, type UseApiOptions, type UseApiReturn, createApi, createApiClient, setAuthMonitor, setupInterceptors, tokenManager, useAbortController, useApi, useApiBatch, useApiConfig, useApiDelete, useApiGet, useApiPatch, useApiPost, useApiPut, useApiState };
|
|
577
|
+
export { type ApiError, type ApiPluginOptions, type ApiRequestConfig, type ApiState, type AuthEventPayload, AuthEventType, type AuthMode, type AuthMonitorFn, type AuthTokens$1 as AuthTokens, type BatchProgress, type BatchRequestConfig, type BatchResultItem, type SetDataInput, type UseApiBatchOptions, type UseApiBatchReturn, type UseApiOptions, type UseApiReturn, createApi, createApiClient, setAuthMonitor, setupInterceptors, tokenManager, useAbortController, useApi, useApiBatch, useApiConfig, useApiDelete, useApiGet, useApiPatch, useApiPost, useApiPut, useApiState };
|
package/dist/index.mjs
CHANGED
|
@@ -24,17 +24,34 @@ function useApiConfig() {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
// src/utils/debounce.ts
|
|
27
|
+
var DebounceCancelledError = class extends Error {
|
|
28
|
+
isDebounceCancelled = true;
|
|
29
|
+
constructor() {
|
|
30
|
+
super("Debounced call was superseded by a newer call");
|
|
31
|
+
this.name = "DebounceCancelledError";
|
|
32
|
+
}
|
|
33
|
+
};
|
|
27
34
|
function debounceFn(fn, delay) {
|
|
28
35
|
let timeoutId = null;
|
|
36
|
+
let pendingReject = null;
|
|
29
37
|
return function(...args) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
38
|
+
if (pendingReject) {
|
|
39
|
+
pendingReject(new DebounceCancelledError());
|
|
40
|
+
pendingReject = null;
|
|
41
|
+
}
|
|
42
|
+
if (timeoutId) {
|
|
43
|
+
clearTimeout(timeoutId);
|
|
44
|
+
}
|
|
45
|
+
return new Promise((resolve, reject) => {
|
|
46
|
+
pendingReject = reject;
|
|
34
47
|
timeoutId = setTimeout(async () => {
|
|
35
|
-
|
|
36
|
-
resolve(result);
|
|
48
|
+
pendingReject = null;
|
|
37
49
|
timeoutId = null;
|
|
50
|
+
try {
|
|
51
|
+
resolve(await fn(...args));
|
|
52
|
+
} catch (err) {
|
|
53
|
+
reject(err);
|
|
54
|
+
}
|
|
38
55
|
}, delay);
|
|
39
56
|
});
|
|
40
57
|
};
|
|
@@ -145,6 +162,26 @@ function useAbortController() {
|
|
|
145
162
|
}
|
|
146
163
|
|
|
147
164
|
// src/useApi.ts
|
|
165
|
+
var DEFAULT_RETRY_STATUS_CODES = [408, 429, 500, 502, 503, 504];
|
|
166
|
+
function cancellableSleep(ms, signal) {
|
|
167
|
+
return new Promise((resolve) => {
|
|
168
|
+
if (signal.aborted) {
|
|
169
|
+
resolve(true);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const timer = setTimeout(() => {
|
|
173
|
+
cleanup();
|
|
174
|
+
resolve(false);
|
|
175
|
+
}, ms);
|
|
176
|
+
const onAbort = () => {
|
|
177
|
+
clearTimeout(timer);
|
|
178
|
+
cleanup();
|
|
179
|
+
resolve(true);
|
|
180
|
+
};
|
|
181
|
+
const cleanup = () => signal.removeEventListener("abort", onAbort);
|
|
182
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
183
|
+
});
|
|
184
|
+
}
|
|
148
185
|
function useApi(url, options = {}) {
|
|
149
186
|
const { axios: axios2, onError: globalErrorHandler, globalOptions, errorParser } = useApiConfig();
|
|
150
187
|
const {
|
|
@@ -159,12 +196,14 @@ function useApi(url, options = {}) {
|
|
|
159
196
|
skipErrorNotification = false,
|
|
160
197
|
retry = globalOptions?.retry ?? false,
|
|
161
198
|
retryDelay = globalOptions?.retryDelay ?? 1e3,
|
|
199
|
+
retryStatusCodes = globalOptions?.retryStatusCodes ?? DEFAULT_RETRY_STATUS_CODES,
|
|
162
200
|
authMode = "default",
|
|
163
201
|
useGlobalAbort = globalOptions?.useGlobalAbort ?? true,
|
|
164
202
|
initialLoading = false,
|
|
165
203
|
poll = 0,
|
|
166
204
|
...axiosConfig
|
|
167
205
|
} = options;
|
|
206
|
+
const maxRetries = retry === false ? 0 : retry === true ? 3 : retry;
|
|
168
207
|
const startLoading = initialLoading ?? immediate;
|
|
169
208
|
const state = useApiState(initialData, { initialLoading: startLoading });
|
|
170
209
|
const abortController2 = ref3(null);
|
|
@@ -204,28 +243,57 @@ function useApi(url, options = {}) {
|
|
|
204
243
|
state.setLoading(true);
|
|
205
244
|
state.setError(null);
|
|
206
245
|
let wasCancelled = false;
|
|
246
|
+
let retryCount = 0;
|
|
207
247
|
try {
|
|
248
|
+
if (!requestUrl) {
|
|
249
|
+
throw new Error("Request URL is missing");
|
|
250
|
+
}
|
|
208
251
|
const rawData = config?.data !== void 0 ? config.data : axiosConfig.data;
|
|
209
252
|
const resolvedData = toValue(rawData);
|
|
210
253
|
const rawParams = config?.params !== void 0 ? config.params : axiosConfig.params;
|
|
211
254
|
const resolvedParams = toValue(rawParams);
|
|
212
|
-
|
|
213
|
-
|
|
255
|
+
while (true) {
|
|
256
|
+
try {
|
|
257
|
+
const response = await axios2.request({
|
|
258
|
+
url: requestUrl,
|
|
259
|
+
method,
|
|
260
|
+
...axiosConfig,
|
|
261
|
+
...config,
|
|
262
|
+
data: resolvedData,
|
|
263
|
+
params: resolvedParams,
|
|
264
|
+
signal: controller.signal,
|
|
265
|
+
...{ authMode: config?.authMode || authMode }
|
|
266
|
+
});
|
|
267
|
+
state.mutate(response.data, response);
|
|
268
|
+
state.setStatusCode(response.status);
|
|
269
|
+
onSuccess?.(response);
|
|
270
|
+
return response.data;
|
|
271
|
+
} catch (err) {
|
|
272
|
+
if (controller.signal.aborted || isAxiosError2(err) && err.code === "ERR_CANCELED") {
|
|
273
|
+
wasCancelled = true;
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
const apiError = errorParser ? errorParser(err) : parseApiError(err);
|
|
277
|
+
const canRetry = retryCount < maxRetries && (retryStatusCodes.length === 0 || retryStatusCodes.includes(apiError.status));
|
|
278
|
+
if (canRetry) {
|
|
279
|
+
retryCount++;
|
|
280
|
+
const aborted = await cancellableSleep(retryDelay, controller.signal);
|
|
281
|
+
if (aborted) {
|
|
282
|
+
wasCancelled = true;
|
|
283
|
+
state.setLoading(false);
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
if (!skipErrorNotification && globalErrorHandler) {
|
|
289
|
+
globalErrorHandler(apiError, err);
|
|
290
|
+
}
|
|
291
|
+
state.setError(apiError);
|
|
292
|
+
state.setStatusCode(apiError.status);
|
|
293
|
+
onError?.(apiError);
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
214
296
|
}
|
|
215
|
-
const response = await axios2.request({
|
|
216
|
-
url: requestUrl,
|
|
217
|
-
method,
|
|
218
|
-
...axiosConfig,
|
|
219
|
-
...config,
|
|
220
|
-
data: resolvedData,
|
|
221
|
-
params: resolvedParams,
|
|
222
|
-
signal: controller.signal,
|
|
223
|
-
...{ authMode: config?.authMode || authMode }
|
|
224
|
-
});
|
|
225
|
-
state.mutate(response.data, response);
|
|
226
|
-
state.setStatusCode(response.status);
|
|
227
|
-
onSuccess?.(response);
|
|
228
|
-
return response.data;
|
|
229
297
|
} catch (err) {
|
|
230
298
|
if (controller.signal.aborted || isAxiosError2(err) && err.code === "ERR_CANCELED") {
|
|
231
299
|
wasCancelled = true;
|
|
@@ -260,7 +328,11 @@ function useApi(url, options = {}) {
|
|
|
260
328
|
}
|
|
261
329
|
}
|
|
262
330
|
};
|
|
263
|
-
const
|
|
331
|
+
const _debounced = debounce > 0 ? debounceFn(executeRequest, debounce) : null;
|
|
332
|
+
const execute = _debounced ? (config) => _debounced(config).catch((err) => {
|
|
333
|
+
if (err instanceof DebounceCancelledError) return null;
|
|
334
|
+
throw err;
|
|
335
|
+
}) : executeRequest;
|
|
264
336
|
const abort = (msg) => {
|
|
265
337
|
if (pollTimer) clearTimeout(pollTimer);
|
|
266
338
|
abortController2.value?.abort(msg);
|
|
@@ -331,8 +403,12 @@ function useApiDelete(url, options) {
|
|
|
331
403
|
}
|
|
332
404
|
|
|
333
405
|
// src/useApiBatch.ts
|
|
334
|
-
import { ref as ref4, computed, getCurrentScope as getCurrentScope2, onScopeDispose as onScopeDispose2, toValue as toValue2, watch as watch2 } from "vue";
|
|
335
|
-
function
|
|
406
|
+
import { ref as ref4, computed, effectScope, getCurrentScope as getCurrentScope2, onScopeDispose as onScopeDispose2, toValue as toValue2, watch as watch2 } from "vue";
|
|
407
|
+
function normalizeRequest(item) {
|
|
408
|
+
if (typeof item === "string") return { url: item, method: "GET" };
|
|
409
|
+
return { method: "GET", ...item };
|
|
410
|
+
}
|
|
411
|
+
function useApiBatch(requests, options = {}) {
|
|
336
412
|
const {
|
|
337
413
|
settled = true,
|
|
338
414
|
concurrency,
|
|
@@ -345,7 +421,7 @@ function useApiBatch(urls, options = {}) {
|
|
|
345
421
|
onProgress,
|
|
346
422
|
...apiOptions
|
|
347
423
|
} = options;
|
|
348
|
-
const
|
|
424
|
+
const getRequests = () => toValue2(requests).map(normalizeRequest);
|
|
349
425
|
const data = ref4([]);
|
|
350
426
|
const loading = ref4(false);
|
|
351
427
|
const error = ref4(null);
|
|
@@ -363,43 +439,54 @@ function useApiBatch(urls, options = {}) {
|
|
|
363
439
|
const abortControllers = ref4([]);
|
|
364
440
|
let isAborted = false;
|
|
365
441
|
const updateProgress = (succeeded, failed) => {
|
|
366
|
-
const
|
|
442
|
+
const currentRequests = getRequests();
|
|
367
443
|
const completed = succeeded + failed;
|
|
368
444
|
const newProgress = {
|
|
369
445
|
completed,
|
|
370
|
-
total:
|
|
371
|
-
percentage:
|
|
446
|
+
total: currentRequests.length,
|
|
447
|
+
percentage: currentRequests.length > 0 ? Math.round(completed / currentRequests.length * 100) : 0,
|
|
372
448
|
succeeded,
|
|
373
449
|
failed
|
|
374
450
|
};
|
|
375
451
|
progress.value = newProgress;
|
|
376
452
|
onProgress?.(newProgress);
|
|
377
453
|
};
|
|
378
|
-
const executeRequest = async (
|
|
379
|
-
const
|
|
454
|
+
const executeRequest = async (config, index, signal) => {
|
|
455
|
+
const scope = effectScope();
|
|
456
|
+
const api = scope.run(() => useApi(config.url, {
|
|
380
457
|
...apiOptions,
|
|
458
|
+
method: config.method,
|
|
459
|
+
data: config.data,
|
|
460
|
+
params: config.params,
|
|
461
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
462
|
+
...config.headers && { headers: config.headers },
|
|
381
463
|
useGlobalAbort: false,
|
|
382
464
|
skipErrorNotification
|
|
383
|
-
});
|
|
465
|
+
}));
|
|
466
|
+
const { execute: execute2, error: reqError, statusCode, response } = api;
|
|
384
467
|
try {
|
|
385
468
|
const result = await execute2({ signal });
|
|
386
469
|
if (signal.aborted) {
|
|
387
470
|
return {
|
|
388
|
-
url,
|
|
471
|
+
url: config.url,
|
|
389
472
|
index,
|
|
390
473
|
success: false,
|
|
391
474
|
data: null,
|
|
392
475
|
error: { message: "Request aborted", status: 0, code: "ABORTED" },
|
|
393
|
-
statusCode: null
|
|
476
|
+
statusCode: null,
|
|
477
|
+
response: null,
|
|
478
|
+
request: config
|
|
394
479
|
};
|
|
395
480
|
}
|
|
396
481
|
const item = {
|
|
397
|
-
url,
|
|
482
|
+
url: config.url,
|
|
398
483
|
index,
|
|
399
484
|
success: result !== null && result !== void 0,
|
|
400
485
|
data: result ?? null,
|
|
401
486
|
error: reqError.value,
|
|
402
|
-
statusCode: statusCode.value
|
|
487
|
+
statusCode: statusCode.value,
|
|
488
|
+
response: response.value,
|
|
489
|
+
request: config
|
|
403
490
|
};
|
|
404
491
|
if (item.success) {
|
|
405
492
|
onItemSuccess?.(item, index);
|
|
@@ -414,26 +501,30 @@ function useApiBatch(urls, options = {}) {
|
|
|
414
501
|
code: "BATCH_ERROR"
|
|
415
502
|
};
|
|
416
503
|
const item = {
|
|
417
|
-
url,
|
|
504
|
+
url: config.url,
|
|
418
505
|
index,
|
|
419
506
|
success: false,
|
|
420
507
|
data: null,
|
|
421
508
|
error: apiError,
|
|
422
|
-
statusCode: null
|
|
509
|
+
statusCode: null,
|
|
510
|
+
response: null,
|
|
511
|
+
request: config
|
|
423
512
|
};
|
|
424
513
|
onItemError?.(item, index);
|
|
425
514
|
return item;
|
|
515
|
+
} finally {
|
|
516
|
+
scope.stop();
|
|
426
517
|
}
|
|
427
518
|
};
|
|
428
|
-
const executeWithConcurrency = async (
|
|
429
|
-
const results = new Array(
|
|
519
|
+
const executeWithConcurrency = async (requests2, limit) => {
|
|
520
|
+
const results = new Array(requests2.length);
|
|
430
521
|
let succeededCount = 0;
|
|
431
522
|
let failedCount = 0;
|
|
432
|
-
if (!limit || limit >=
|
|
433
|
-
const promises =
|
|
523
|
+
if (!limit || limit >= requests2.length) {
|
|
524
|
+
const promises = requests2.map((config, index) => {
|
|
434
525
|
const controller = new AbortController();
|
|
435
526
|
abortControllers.value.push(controller);
|
|
436
|
-
return executeRequest(
|
|
527
|
+
return executeRequest(config, index, controller.signal).then((result) => {
|
|
437
528
|
results[index] = result;
|
|
438
529
|
if (result.success) {
|
|
439
530
|
succeededCount++;
|
|
@@ -458,12 +549,12 @@ function useApiBatch(urls, options = {}) {
|
|
|
458
549
|
} else {
|
|
459
550
|
let currentIndex = 0;
|
|
460
551
|
const executeNext = async () => {
|
|
461
|
-
while (currentIndex <
|
|
552
|
+
while (currentIndex < requests2.length && !isAborted) {
|
|
462
553
|
const index = currentIndex++;
|
|
463
|
-
const
|
|
554
|
+
const config = requests2[index];
|
|
464
555
|
const controller = new AbortController();
|
|
465
556
|
abortControllers.value.push(controller);
|
|
466
|
-
const result = await executeRequest(
|
|
557
|
+
const result = await executeRequest(config, index, controller.signal);
|
|
467
558
|
results[index] = result;
|
|
468
559
|
if (result.success) {
|
|
469
560
|
succeededCount++;
|
|
@@ -480,7 +571,7 @@ function useApiBatch(urls, options = {}) {
|
|
|
480
571
|
}
|
|
481
572
|
}
|
|
482
573
|
};
|
|
483
|
-
const workers = Array.from({ length: Math.min(limit,
|
|
574
|
+
const workers = Array.from({ length: Math.min(limit, requests2.length) }, () => executeNext());
|
|
484
575
|
if (settled) {
|
|
485
576
|
await Promise.allSettled(workers);
|
|
486
577
|
} else {
|
|
@@ -490,7 +581,7 @@ function useApiBatch(urls, options = {}) {
|
|
|
490
581
|
return results;
|
|
491
582
|
};
|
|
492
583
|
const execute = async () => {
|
|
493
|
-
const
|
|
584
|
+
const currentRequests = getRequests();
|
|
494
585
|
isAborted = false;
|
|
495
586
|
loading.value = true;
|
|
496
587
|
error.value = null;
|
|
@@ -499,7 +590,7 @@ function useApiBatch(urls, options = {}) {
|
|
|
499
590
|
abortControllers.value = [];
|
|
500
591
|
updateProgress(0, 0);
|
|
501
592
|
try {
|
|
502
|
-
const results = await executeWithConcurrency(
|
|
593
|
+
const results = await executeWithConcurrency(currentRequests, concurrency);
|
|
503
594
|
data.value = results;
|
|
504
595
|
const allFailed = results.every((r) => !r.success);
|
|
505
596
|
if (allFailed && results.length > 0) {
|
|
@@ -536,7 +627,7 @@ function useApiBatch(urls, options = {}) {
|
|
|
536
627
|
data.value = [];
|
|
537
628
|
progress.value = {
|
|
538
629
|
completed: 0,
|
|
539
|
-
total:
|
|
630
|
+
total: getRequests().length,
|
|
540
631
|
percentage: 0,
|
|
541
632
|
succeeded: 0,
|
|
542
633
|
failed: 0
|
|
@@ -728,16 +819,16 @@ var tokenManager = new TokenManager();
|
|
|
728
819
|
// src/features/interceptors.ts
|
|
729
820
|
var AUTH_HEADER = "Authorization";
|
|
730
821
|
var TOKEN_TYPE2 = "Bearer";
|
|
731
|
-
var failedQueue = [];
|
|
732
|
-
var isRefreshing = false;
|
|
733
|
-
function processQueue(error, token = null) {
|
|
734
|
-
failedQueue.forEach((promise) => {
|
|
735
|
-
if (error) promise.reject(error);
|
|
736
|
-
else if (token) promise.resolve(token);
|
|
737
|
-
});
|
|
738
|
-
failedQueue = [];
|
|
739
|
-
}
|
|
740
822
|
function setupInterceptors(axiosInstance, options = {}) {
|
|
823
|
+
let failedQueue = [];
|
|
824
|
+
let isRefreshing = false;
|
|
825
|
+
function processQueue(error, token = null) {
|
|
826
|
+
failedQueue.forEach((promise) => {
|
|
827
|
+
if (error) promise.reject(error);
|
|
828
|
+
else if (token) promise.resolve(token);
|
|
829
|
+
});
|
|
830
|
+
failedQueue = [];
|
|
831
|
+
}
|
|
741
832
|
const {
|
|
742
833
|
refreshUrl = "/auth/refresh",
|
|
743
834
|
refreshWithCredentials = false,
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ametie/vue-muza-use",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Powerful Vue 3 API composable (Muza Kit) with Axios, Auto-Refresh & TypeScript",
|
|
5
5
|
"author": "MortyQ",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "
|
|
9
|
+
"url": "https://github.com/MortyQ/vue-useApi",
|
|
10
|
+
"directory": "packages/use-api"
|
|
10
11
|
},
|
|
11
12
|
"files": [
|
|
12
13
|
"dist",
|