@ametie/vue-muza-use 1.1.1 → 1.2.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/README.md +120 -5
- package/dist/index.cjs +127 -1
- package/dist/index.d.cts +89 -1
- package/dist/index.d.ts +89 -1
- package/dist/index.mjs +128 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -41,6 +41,7 @@ A production-ready composable that eliminates boilerplate and solves the hard pr
|
|
|
41
41
|
- 🔐 **JWT Token Management** — Automatic token refresh with request queueing on 401 responses
|
|
42
42
|
- 🎛️ **Flexible Architecture** — Bring your own Axios instance with full configuration control
|
|
43
43
|
- 🍪 **withCredentials** — Per-request cookie and cross-origin credential control
|
|
44
|
+
- 🔭 **DevTools Panel** — Inspect live requests, payloads, and instance state via `@ametie/vue-muza-devtools`
|
|
44
45
|
|
|
45
46
|
---
|
|
46
47
|
|
|
@@ -63,11 +64,11 @@ A production-ready composable that eliminates boilerplate and solves the hard pr
|
|
|
63
64
|
| **Response caching** | ✅ | ❌ | ✅ | ✅ |
|
|
64
65
|
| **TypeScript** | ✅ | ✅ | ✅ | ✅ |
|
|
65
66
|
| **SSR / Nuxt** | ❌ | ✅ | ✅ | ✅ |
|
|
66
|
-
| **DevTools** |
|
|
67
|
+
| **DevTools** | ✅ | ❌ | ✅ | ❌ |
|
|
67
68
|
|
|
68
69
|
**Choose vue-muza-use if:** you build Vue 3 SPAs with Axios, need JWT token refresh out of the box, and want reactive request management without a heavyweight server-state solution.
|
|
69
70
|
|
|
70
|
-
**Choose TanStack Query if:** you need SSR
|
|
71
|
+
**Choose TanStack Query if:** you need SSR or advanced server-state normalization.
|
|
71
72
|
|
|
72
73
|
**Choose @vueuse/useFetch if:** you want a minimal fetch wrapper with no opinions.
|
|
73
74
|
|
|
@@ -97,9 +98,12 @@ A production-ready composable that eliminates boilerplate and solves the hard pr
|
|
|
97
98
|
- [Data Table with Pagination](#data-table-with-pagination--sorting)
|
|
98
99
|
- [Request Cancellation](#request-cancellation)
|
|
99
100
|
- [Batch Requests](#batch-requests)
|
|
101
|
+
- [Reactive Requests (Auto-tracking)](#reactive-requests--auto-tracking)
|
|
102
|
+
- [Batch Polling](#batch-polling)
|
|
100
103
|
|
|
101
104
|
**Advanced:**
|
|
102
105
|
- [Advanced Configuration](#️-advanced-configuration)
|
|
106
|
+
- [DevTools Panel](#-devtools-panel)
|
|
103
107
|
- [Authentication & Token Management](#-authentication--token-management)
|
|
104
108
|
- [Error Handling Reference](#-error-handling-reference)
|
|
105
109
|
- [Utilities & Standalone Composables](#-utilities--standalone-composables)
|
|
@@ -1405,6 +1409,77 @@ const { loading, progress, execute } = useApiBatch(urls, {
|
|
|
1405
1409
|
</template>
|
|
1406
1410
|
```
|
|
1407
1411
|
|
|
1412
|
+
#### Reactive Requests — Auto-tracking
|
|
1413
|
+
|
|
1414
|
+
**TL;DR: Pass a getter function as `requests` — the batch re-executes automatically when the getter's reactive dependencies change.**
|
|
1415
|
+
|
|
1416
|
+
This mirrors `useApi`'s auto-tracking behavior. No explicit `watch` option needed.
|
|
1417
|
+
|
|
1418
|
+
```typescript
|
|
1419
|
+
import { ref } from 'vue'
|
|
1420
|
+
import { useApiBatch } from '@ametie/vue-muza-use'
|
|
1421
|
+
|
|
1422
|
+
interface User { id: number; name: string }
|
|
1423
|
+
|
|
1424
|
+
const pages = ref([1, 2, 3])
|
|
1425
|
+
|
|
1426
|
+
// Getter is auto-tracked — re-executes when pages.value changes
|
|
1427
|
+
const { successfulData, loading } = useApiBatch<User>(
|
|
1428
|
+
() => pages.value.map(page => ({ url: '/users', params: { page } }))
|
|
1429
|
+
)
|
|
1430
|
+
|
|
1431
|
+
pages.value = [4, 5, 6] // → new batch fires automatically
|
|
1432
|
+
```
|
|
1433
|
+
|
|
1434
|
+
Set `lazy: true` to disable auto-tracking and keep full manual control:
|
|
1435
|
+
|
|
1436
|
+
```typescript
|
|
1437
|
+
const { execute } = useApiBatch(
|
|
1438
|
+
() => ids.value.map(id => `/items/${id}`),
|
|
1439
|
+
{ lazy: true } // reactive changes to ids do NOT trigger re-execution
|
|
1440
|
+
)
|
|
1441
|
+
|
|
1442
|
+
// You control when it runs
|
|
1443
|
+
await execute()
|
|
1444
|
+
```
|
|
1445
|
+
|
|
1446
|
+
#### Batch Polling
|
|
1447
|
+
|
|
1448
|
+
**TL;DR: Re-execute the batch on a fixed interval — useful for dashboards and status monitoring.**
|
|
1449
|
+
|
|
1450
|
+
Same semantics as `useApi`'s `poll` option.
|
|
1451
|
+
|
|
1452
|
+
```typescript
|
|
1453
|
+
import { useApiBatch } from '@ametie/vue-muza-use'
|
|
1454
|
+
|
|
1455
|
+
const { data, loading } = useApiBatch(
|
|
1456
|
+
['/stats/cpu', '/stats/memory', '/stats/disk'],
|
|
1457
|
+
{
|
|
1458
|
+
immediate: true,
|
|
1459
|
+
poll: 5000 // re-execute every 5 seconds
|
|
1460
|
+
}
|
|
1461
|
+
)
|
|
1462
|
+
```
|
|
1463
|
+
|
|
1464
|
+
With `whenHidden` control:
|
|
1465
|
+
|
|
1466
|
+
```typescript
|
|
1467
|
+
import { ref } from 'vue'
|
|
1468
|
+
import { useApiBatch } from '@ametie/vue-muza-use'
|
|
1469
|
+
|
|
1470
|
+
const interval = ref(10000)
|
|
1471
|
+
|
|
1472
|
+
const { data, abort } = useApiBatch(
|
|
1473
|
+
['/queue/jobs', '/queue/workers'],
|
|
1474
|
+
{
|
|
1475
|
+
immediate: true,
|
|
1476
|
+
poll: { interval, whenHidden: false } // pauses when tab is hidden
|
|
1477
|
+
}
|
|
1478
|
+
)
|
|
1479
|
+
|
|
1480
|
+
interval.value = 0 // stop polling
|
|
1481
|
+
```
|
|
1482
|
+
|
|
1408
1483
|
---
|
|
1409
1484
|
|
|
1410
1485
|
## ⚙️ Advanced Configuration
|
|
@@ -1496,6 +1571,44 @@ createApp(App).use(createApi({
|
|
|
1496
1571
|
|
|
1497
1572
|
---
|
|
1498
1573
|
|
|
1574
|
+
## 🔭 DevTools Panel
|
|
1575
|
+
|
|
1576
|
+
**TL;DR: Enable it in the plugin and get a live network inspector in your browser. No extra packages to install.**
|
|
1577
|
+
|
|
1578
|
+
The devtools panel is included with `@ametie/vue-muza-use`. It loads on demand and has zero impact on production bundles when disabled.
|
|
1579
|
+
|
|
1580
|
+
### Setup
|
|
1581
|
+
|
|
1582
|
+
Pass `devtools` to `createApi`. Gate it on `NODE_ENV` to keep production builds clean:
|
|
1583
|
+
|
|
1584
|
+
```typescript
|
|
1585
|
+
import { createApp } from 'vue'
|
|
1586
|
+
import { createApi, createApiClient } from '@ametie/vue-muza-use'
|
|
1587
|
+
import App from './App.vue'
|
|
1588
|
+
|
|
1589
|
+
const api = createApiClient({ baseURL: 'https://api.example.com' })
|
|
1590
|
+
|
|
1591
|
+
createApp(App).use(createApi({
|
|
1592
|
+
axios: api,
|
|
1593
|
+
devtools: {
|
|
1594
|
+
enabled: process.env.NODE_ENV !== 'production'
|
|
1595
|
+
}
|
|
1596
|
+
}))
|
|
1597
|
+
```
|
|
1598
|
+
|
|
1599
|
+
The panel loads asynchronously — it has zero impact on startup time.
|
|
1600
|
+
|
|
1601
|
+
### DevTools Options
|
|
1602
|
+
|
|
1603
|
+
| Option | Type | Default | Description |
|
|
1604
|
+
|--------|------|---------|-------------|
|
|
1605
|
+
| `enabled` | `boolean` | — | Required. Set `true` to mount the panel |
|
|
1606
|
+
| `maxHistory` | `number` | `300` | Maximum number of requests kept in the Network tab history |
|
|
1607
|
+
| `maxPayloadSize` | `number` | `200_000` | Maximum bytes per payload/response before truncation in the viewer |
|
|
1608
|
+
| `tabs` | `DevtoolsTab[]` | `[]` | Additional custom tabs to register in the panel |
|
|
1609
|
+
|
|
1610
|
+
---
|
|
1611
|
+
|
|
1499
1612
|
## 🔐 Authentication & Token Management
|
|
1500
1613
|
|
|
1501
1614
|
> **Note:** Authentication setup is optional. Only add this if your API requires JWT tokens.
|
|
@@ -2141,15 +2254,17 @@ type BatchInput = string | BatchRequestConfig
|
|
|
2141
2254
|
|
|
2142
2255
|
| Option | Type | Default | Description |
|
|
2143
2256
|
|--------|------|---------|-------------|
|
|
2144
|
-
| `settled` | `boolean` | `true` | When `true`, all requests run even if some fail. When `false`, the first error
|
|
2257
|
+
| `settled` | `boolean` | `true` | When `true`, all requests run even if some fail. When `false`, the first error aborts remaining requests |
|
|
2145
2258
|
| `concurrency` | `number` | unlimited | Maximum number of requests that run in parallel at once |
|
|
2146
2259
|
| `immediate` | `boolean` | `false` | Execute the batch automatically when the composable is created |
|
|
2260
|
+
| `lazy` | `boolean` | `false` | Disable auto-tracking. When `false`, a getter passed as `requests` re-executes the batch automatically when its reactive deps change. Set `true` for full manual control via `execute()` |
|
|
2261
|
+
| `poll` | `number \| { interval: MaybeRefOrGetter<number>, whenHidden?: MaybeRefOrGetter<boolean> }` | `0` | Polling interval in ms. After each execution, schedules the next one after `interval` ms. `0` disables polling. `whenHidden: false` (default) pauses when the tab is hidden |
|
|
2147
2262
|
| `skipErrorNotification` | `boolean` | `true` | Suppress global error handler for individual item failures |
|
|
2148
|
-
| `watch` | `WatchSource \| WatchSource[]` | `undefined` |
|
|
2263
|
+
| `watch` | `WatchSource \| WatchSource[]` | `undefined` | **Deprecated** — use a reactive getter with `lazy: false` instead. Will be removed in v2.0 |
|
|
2149
2264
|
| `onItemSuccess` | `(item: BatchResultItem<T>, index: number) => void` | `undefined` | Called each time a single request in the batch succeeds |
|
|
2150
2265
|
| `onItemError` | `(item: BatchResultItem<T>, index: number) => void` | `undefined` | Called each time a single request in the batch fails |
|
|
2151
2266
|
| `onProgress` | `(progress: BatchProgress) => void` | `undefined` | Called after each request completes with updated progress |
|
|
2152
|
-
| `onFinish` | `(results: BatchResultItem<T>[]) => void` | `undefined` | Called once when all requests have completed |
|
|
2267
|
+
| `onFinish` | `(results: BatchResultItem<T>[]) => void` | `undefined` | Called once when all requests have completed (even on `settled: false` rejection) |
|
|
2153
2268
|
|
|
2154
2269
|
**UseApiBatchReturn:**
|
|
2155
2270
|
|
package/dist/index.cjs
CHANGED
|
@@ -53,6 +53,48 @@ module.exports = __toCommonJS(index_exports);
|
|
|
53
53
|
|
|
54
54
|
// src/plugin.ts
|
|
55
55
|
var import_vue = require("vue");
|
|
56
|
+
|
|
57
|
+
// src/devtools.ts
|
|
58
|
+
var bridge = null;
|
|
59
|
+
var requestCounter = 0;
|
|
60
|
+
var pendingCalls = [];
|
|
61
|
+
function nextRequestId() {
|
|
62
|
+
return `req_${++requestCounter}`;
|
|
63
|
+
}
|
|
64
|
+
async function initDevtools(options, app) {
|
|
65
|
+
if (!options.enabled) return;
|
|
66
|
+
try {
|
|
67
|
+
const { createBridge } = await import("@ametie/vue-muza-devtools");
|
|
68
|
+
bridge = createBridge(options, app);
|
|
69
|
+
for (const fn of pendingCalls) fn();
|
|
70
|
+
pendingCalls.length = 0;
|
|
71
|
+
} catch {
|
|
72
|
+
console.warn("[vue-muza-use] devtools enabled but @ametie/vue-muza-devtools is not installed");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
var devtoolsBridge = {
|
|
76
|
+
onInstanceCreated(id, url, options) {
|
|
77
|
+
if (bridge) {
|
|
78
|
+
bridge.onInstanceCreated(id, url, options);
|
|
79
|
+
} else {
|
|
80
|
+
pendingCalls.push(() => bridge?.onInstanceCreated(id, url, options));
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
onInstanceDestroyed(id) {
|
|
84
|
+
bridge?.onInstanceDestroyed(id);
|
|
85
|
+
},
|
|
86
|
+
onStateUpdate(id, state) {
|
|
87
|
+
bridge?.onStateUpdate(id, state);
|
|
88
|
+
},
|
|
89
|
+
onRequestStart(record) {
|
|
90
|
+
bridge?.onRequestStart(record);
|
|
91
|
+
},
|
|
92
|
+
onRequestEnd(id, result) {
|
|
93
|
+
bridge?.onRequestEnd(id, result);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// src/plugin.ts
|
|
56
98
|
var API_INJECTION_KEY = /* @__PURE__ */ Symbol("use-api-config");
|
|
57
99
|
var globalConfig = null;
|
|
58
100
|
function createApi(options) {
|
|
@@ -60,6 +102,9 @@ function createApi(options) {
|
|
|
60
102
|
return {
|
|
61
103
|
install(app) {
|
|
62
104
|
app.provide(API_INJECTION_KEY, options);
|
|
105
|
+
if (options.devtools) {
|
|
106
|
+
void initDevtools(options.devtools, app);
|
|
107
|
+
}
|
|
63
108
|
}
|
|
64
109
|
};
|
|
65
110
|
}
|
|
@@ -110,6 +155,18 @@ function debounceFn(fn, delay) {
|
|
|
110
155
|
};
|
|
111
156
|
}
|
|
112
157
|
|
|
158
|
+
// src/utils/urlUtils.ts
|
|
159
|
+
function parseUrlQueryParams(url) {
|
|
160
|
+
if (!url) return null;
|
|
161
|
+
const qIndex = url.indexOf("?");
|
|
162
|
+
if (qIndex === -1) return null;
|
|
163
|
+
const params = {};
|
|
164
|
+
for (const [k, v] of new URLSearchParams(url.slice(qIndex + 1))) {
|
|
165
|
+
params[k] = v;
|
|
166
|
+
}
|
|
167
|
+
return Object.keys(params).length > 0 ? params : null;
|
|
168
|
+
}
|
|
169
|
+
|
|
113
170
|
// src/useApi.ts
|
|
114
171
|
var import_axios2 = require("axios");
|
|
115
172
|
var import_vue5 = require("vue");
|
|
@@ -339,6 +396,30 @@ function useApi(url, options = {}) {
|
|
|
339
396
|
const startLoading = initialLoading ?? immediate;
|
|
340
397
|
const state = useApiState(initialData, { initialLoading: startLoading });
|
|
341
398
|
const revalidating = (0, import_vue5.ref)(false);
|
|
399
|
+
const instanceId = (0, import_vue5.getCurrentInstance)() != null ? (0, import_vue5.useId)() : nextRequestId();
|
|
400
|
+
devtoolsBridge.onInstanceCreated(instanceId, (0, import_vue5.toValue)(url), {
|
|
401
|
+
authMode: options.authMode ?? "default",
|
|
402
|
+
cache: options.cache,
|
|
403
|
+
retry: options.retry ?? false,
|
|
404
|
+
poll: (() => {
|
|
405
|
+
const v = (0, import_vue5.toValue)(options.poll);
|
|
406
|
+
return typeof v === "number" ? v : 0;
|
|
407
|
+
})(),
|
|
408
|
+
immediate: options.immediate ?? false,
|
|
409
|
+
lazy: options.lazy ?? false
|
|
410
|
+
});
|
|
411
|
+
if ((0, import_vue5.getCurrentScope)()) {
|
|
412
|
+
(0, import_vue5.watch)(
|
|
413
|
+
() => ({
|
|
414
|
+
loading: state.loading.value,
|
|
415
|
+
error: state.error.value,
|
|
416
|
+
statusCode: state.statusCode.value,
|
|
417
|
+
data: state.data.value
|
|
418
|
+
}),
|
|
419
|
+
(s) => devtoolsBridge.onStateUpdate(instanceId, s),
|
|
420
|
+
{ deep: true }
|
|
421
|
+
);
|
|
422
|
+
}
|
|
342
423
|
const abortController2 = (0, import_vue5.ref)(null);
|
|
343
424
|
const globalAbort = useGlobalAbort ? useAbortController() : null;
|
|
344
425
|
let pollTimer = null;
|
|
@@ -403,6 +484,9 @@ function useApi(url, options = {}) {
|
|
|
403
484
|
state.setError(null);
|
|
404
485
|
let wasCancelled = false;
|
|
405
486
|
let retryCount = 0;
|
|
487
|
+
let devtoolsRequestId = null;
|
|
488
|
+
let devtoolsRequestStartedAt = 0;
|
|
489
|
+
let devtoolsRequestEndResult = null;
|
|
406
490
|
try {
|
|
407
491
|
if (!requestUrl) {
|
|
408
492
|
throw new Error("Request URL is missing");
|
|
@@ -411,6 +495,21 @@ function useApi(url, options = {}) {
|
|
|
411
495
|
const resolvedData = (0, import_vue5.toValue)(rawData);
|
|
412
496
|
const rawParams = config?.params !== void 0 ? config.params : axiosConfig.params;
|
|
413
497
|
const resolvedParams = (0, import_vue5.toValue)(rawParams);
|
|
498
|
+
const devtoolsQueryParams = resolvedParams ?? parseUrlQueryParams(requestUrl);
|
|
499
|
+
devtoolsRequestId = nextRequestId();
|
|
500
|
+
devtoolsRequestStartedAt = Date.now();
|
|
501
|
+
devtoolsBridge.onRequestStart({
|
|
502
|
+
id: devtoolsRequestId,
|
|
503
|
+
instanceId,
|
|
504
|
+
url: requestUrl,
|
|
505
|
+
method,
|
|
506
|
+
startedAt: devtoolsRequestStartedAt,
|
|
507
|
+
status: "pending",
|
|
508
|
+
statusCode: null,
|
|
509
|
+
requestHeaders: {},
|
|
510
|
+
payload: resolvedData ?? null,
|
|
511
|
+
queryParams: devtoolsQueryParams
|
|
512
|
+
});
|
|
414
513
|
while (true) {
|
|
415
514
|
try {
|
|
416
515
|
const response = await axios2.request({
|
|
@@ -434,6 +533,12 @@ function useApi(url, options = {}) {
|
|
|
434
533
|
}
|
|
435
534
|
onSuccess?.(response);
|
|
436
535
|
notifyFetched();
|
|
536
|
+
devtoolsRequestEndResult = {
|
|
537
|
+
status: "success",
|
|
538
|
+
statusCode: response.status,
|
|
539
|
+
response: response.data,
|
|
540
|
+
duration: Date.now() - devtoolsRequestStartedAt
|
|
541
|
+
};
|
|
437
542
|
return selected;
|
|
438
543
|
} catch (err) {
|
|
439
544
|
if (controller.signal.aborted || (0, import_axios2.isAxiosError)(err) && err.code === "ERR_CANCELED") {
|
|
@@ -452,6 +557,12 @@ function useApi(url, options = {}) {
|
|
|
452
557
|
}
|
|
453
558
|
continue;
|
|
454
559
|
}
|
|
560
|
+
devtoolsRequestEndResult = {
|
|
561
|
+
status: "error",
|
|
562
|
+
error: apiError,
|
|
563
|
+
statusCode: apiError.status ?? null,
|
|
564
|
+
duration: Date.now() - devtoolsRequestStartedAt
|
|
565
|
+
};
|
|
455
566
|
if (!skipErrorNotification && globalErrorHandler) {
|
|
456
567
|
globalErrorHandler(apiError, err);
|
|
457
568
|
}
|
|
@@ -467,6 +578,12 @@ function useApi(url, options = {}) {
|
|
|
467
578
|
return null;
|
|
468
579
|
}
|
|
469
580
|
const apiError = errorParser ? errorParser(err) : parseApiError(err);
|
|
581
|
+
devtoolsRequestEndResult = {
|
|
582
|
+
status: "error",
|
|
583
|
+
error: apiError,
|
|
584
|
+
statusCode: null,
|
|
585
|
+
duration: Date.now() - devtoolsRequestStartedAt
|
|
586
|
+
};
|
|
470
587
|
if (!skipErrorNotification && globalErrorHandler) {
|
|
471
588
|
globalErrorHandler(apiError, err);
|
|
472
589
|
}
|
|
@@ -475,6 +592,12 @@ function useApi(url, options = {}) {
|
|
|
475
592
|
onError?.(apiError);
|
|
476
593
|
return null;
|
|
477
594
|
} finally {
|
|
595
|
+
if (devtoolsRequestId !== null) {
|
|
596
|
+
devtoolsBridge.onRequestEnd(
|
|
597
|
+
devtoolsRequestId,
|
|
598
|
+
devtoolsRequestEndResult ?? { status: "aborted", duration: Date.now() - devtoolsRequestStartedAt }
|
|
599
|
+
);
|
|
600
|
+
}
|
|
478
601
|
if (globalAbortHandler && subscribedSignal) subscribedSignal.removeEventListener("abort", globalAbortHandler);
|
|
479
602
|
revalidating.value = false;
|
|
480
603
|
if (!wasCancelled) {
|
|
@@ -551,7 +674,10 @@ function useApi(url, options = {}) {
|
|
|
551
674
|
}
|
|
552
675
|
};
|
|
553
676
|
if ((0, import_vue5.getCurrentScope)()) {
|
|
554
|
-
(0, import_vue5.onScopeDispose)(() =>
|
|
677
|
+
(0, import_vue5.onScopeDispose)(() => {
|
|
678
|
+
abort("Scope disposed");
|
|
679
|
+
devtoolsBridge.onInstanceDestroyed(instanceId);
|
|
680
|
+
});
|
|
555
681
|
}
|
|
556
682
|
if (immediate) execute();
|
|
557
683
|
if (typeof document !== "undefined") {
|
package/dist/index.d.cts
CHANGED
|
@@ -244,6 +244,8 @@ interface ApiPluginOptions {
|
|
|
244
244
|
* Useful if your backend has a different error structure.
|
|
245
245
|
*/
|
|
246
246
|
errorParser?: (error: unknown) => ApiError;
|
|
247
|
+
/** Devtools panel configuration. Panel is disabled by default. */
|
|
248
|
+
devtools?: DevtoolsOptions;
|
|
247
249
|
globalOptions?: {
|
|
248
250
|
retry?: number | boolean;
|
|
249
251
|
retryDelay?: number;
|
|
@@ -397,6 +399,92 @@ interface UseApiBatchReturn<T = unknown> {
|
|
|
397
399
|
/** Reset state to initial */
|
|
398
400
|
reset: () => void;
|
|
399
401
|
}
|
|
402
|
+
/** Lifecycle status of an HTTP request tracked by devtools. */
|
|
403
|
+
type RequestStatus = "pending" | "success" | "error" | "aborted";
|
|
404
|
+
/** Current reactive state of a useApi instance as seen by devtools. */
|
|
405
|
+
interface DevtoolsInstanceState {
|
|
406
|
+
loading: boolean;
|
|
407
|
+
error: ApiError | null;
|
|
408
|
+
statusCode: number | null;
|
|
409
|
+
data: unknown;
|
|
410
|
+
}
|
|
411
|
+
/** Configuration options of a useApi instance as seen by devtools. */
|
|
412
|
+
interface DevtoolsInstanceOptions {
|
|
413
|
+
authMode: AuthMode;
|
|
414
|
+
cache: CacheOptions | string | undefined;
|
|
415
|
+
retry: boolean | number;
|
|
416
|
+
poll: number;
|
|
417
|
+
immediate: boolean;
|
|
418
|
+
lazy: boolean;
|
|
419
|
+
}
|
|
420
|
+
/** An outgoing HTTP request record sent to devtools on request start. */
|
|
421
|
+
interface DevtoolsRequestRecord {
|
|
422
|
+
id: string;
|
|
423
|
+
instanceId: string | null;
|
|
424
|
+
url: string;
|
|
425
|
+
method: string;
|
|
426
|
+
startedAt: number;
|
|
427
|
+
status: RequestStatus;
|
|
428
|
+
statusCode: null;
|
|
429
|
+
requestHeaders: Record<string, string>;
|
|
430
|
+
payload: unknown;
|
|
431
|
+
queryParams: unknown;
|
|
432
|
+
}
|
|
433
|
+
/** Result of a completed HTTP request, sent to devtools on request end. */
|
|
434
|
+
type RequestEndResult = {
|
|
435
|
+
status: "success";
|
|
436
|
+
statusCode: number;
|
|
437
|
+
response: unknown;
|
|
438
|
+
duration: number;
|
|
439
|
+
} | {
|
|
440
|
+
status: "error";
|
|
441
|
+
error: ApiError;
|
|
442
|
+
statusCode: number | null;
|
|
443
|
+
duration: number;
|
|
444
|
+
} | {
|
|
445
|
+
status: "aborted";
|
|
446
|
+
duration: number;
|
|
447
|
+
};
|
|
448
|
+
/** Event callbacks implemented by the devtools panel, called by useApi instrumentation. */
|
|
449
|
+
interface DevtoolsBridge {
|
|
450
|
+
/** Fired when a useApi instance is created. */
|
|
451
|
+
onInstanceCreated: (id: string, url: string | undefined, options: DevtoolsInstanceOptions) => void;
|
|
452
|
+
/** Fired when a useApi instance is destroyed (scope disposed). */
|
|
453
|
+
onInstanceDestroyed: (id: string) => void;
|
|
454
|
+
/** Fired when instance state (loading, error, statusCode, data) changes. */
|
|
455
|
+
onStateUpdate: (id: string, state: Partial<DevtoolsInstanceState>) => void;
|
|
456
|
+
/** Fired when an HTTP request starts. */
|
|
457
|
+
onRequestStart: (record: DevtoolsRequestRecord) => void;
|
|
458
|
+
/** Fired when an HTTP request completes (success, error, or abort). */
|
|
459
|
+
onRequestEnd: (id: string, result: RequestEndResult) => void;
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Options for the `@ametie/vue-muza-devtools` panel.
|
|
463
|
+
*
|
|
464
|
+
* @example
|
|
465
|
+
* ```ts
|
|
466
|
+
* app.use(createApi({
|
|
467
|
+
* axios: apiClient,
|
|
468
|
+
* devtools: { enabled: process.env.NODE_ENV !== 'production' },
|
|
469
|
+
* }))
|
|
470
|
+
* ```
|
|
471
|
+
*/
|
|
472
|
+
interface DevtoolsOptions {
|
|
473
|
+
/** Enable the devtools panel. Default: false. */
|
|
474
|
+
enabled: boolean;
|
|
475
|
+
/** Maximum number of network requests kept in history. Default: 300. */
|
|
476
|
+
maxHistory?: number;
|
|
477
|
+
/** Maximum payload/response size in bytes before truncation. Default: 200_000. */
|
|
478
|
+
maxPayloadSize?: number;
|
|
479
|
+
/** Custom tabs appended after built-in tabs. */
|
|
480
|
+
tabs?: Array<{
|
|
481
|
+
id: string;
|
|
482
|
+
label: string;
|
|
483
|
+
component: unknown;
|
|
484
|
+
icon?: unknown;
|
|
485
|
+
order?: number;
|
|
486
|
+
}>;
|
|
487
|
+
}
|
|
400
488
|
|
|
401
489
|
declare function createApi(options: ApiPluginOptions): {
|
|
402
490
|
install(app: App): void;
|
|
@@ -782,4 +870,4 @@ declare function invalidateCache(id: string | string[]): void;
|
|
|
782
870
|
*/
|
|
783
871
|
declare function clearAllCache(): void;
|
|
784
872
|
|
|
785
|
-
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 CacheOptions, type SetDataInput, type UseApiBatchOptions, type UseApiBatchReturn, type UseApiOptions, type UseApiReturn, clearAllCache, createApi, createApiClient, invalidateCache, setAuthMonitor, setupInterceptors, tokenManager, useAbortController, useApi, useApiBatch, useApiConfig, useApiDelete, useApiGet, useApiPatch, useApiPost, useApiPut, useApiState };
|
|
873
|
+
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 CacheOptions, type DevtoolsBridge, type DevtoolsInstanceOptions, type DevtoolsInstanceState, type DevtoolsOptions, type DevtoolsRequestRecord, type RequestEndResult, type RequestStatus, type SetDataInput, type UseApiBatchOptions, type UseApiBatchReturn, type UseApiOptions, type UseApiReturn, clearAllCache, createApi, createApiClient, invalidateCache, setAuthMonitor, setupInterceptors, tokenManager, useAbortController, useApi, useApiBatch, useApiConfig, useApiDelete, useApiGet, useApiPatch, useApiPost, useApiPut, useApiState };
|
package/dist/index.d.ts
CHANGED
|
@@ -244,6 +244,8 @@ interface ApiPluginOptions {
|
|
|
244
244
|
* Useful if your backend has a different error structure.
|
|
245
245
|
*/
|
|
246
246
|
errorParser?: (error: unknown) => ApiError;
|
|
247
|
+
/** Devtools panel configuration. Panel is disabled by default. */
|
|
248
|
+
devtools?: DevtoolsOptions;
|
|
247
249
|
globalOptions?: {
|
|
248
250
|
retry?: number | boolean;
|
|
249
251
|
retryDelay?: number;
|
|
@@ -397,6 +399,92 @@ interface UseApiBatchReturn<T = unknown> {
|
|
|
397
399
|
/** Reset state to initial */
|
|
398
400
|
reset: () => void;
|
|
399
401
|
}
|
|
402
|
+
/** Lifecycle status of an HTTP request tracked by devtools. */
|
|
403
|
+
type RequestStatus = "pending" | "success" | "error" | "aborted";
|
|
404
|
+
/** Current reactive state of a useApi instance as seen by devtools. */
|
|
405
|
+
interface DevtoolsInstanceState {
|
|
406
|
+
loading: boolean;
|
|
407
|
+
error: ApiError | null;
|
|
408
|
+
statusCode: number | null;
|
|
409
|
+
data: unknown;
|
|
410
|
+
}
|
|
411
|
+
/** Configuration options of a useApi instance as seen by devtools. */
|
|
412
|
+
interface DevtoolsInstanceOptions {
|
|
413
|
+
authMode: AuthMode;
|
|
414
|
+
cache: CacheOptions | string | undefined;
|
|
415
|
+
retry: boolean | number;
|
|
416
|
+
poll: number;
|
|
417
|
+
immediate: boolean;
|
|
418
|
+
lazy: boolean;
|
|
419
|
+
}
|
|
420
|
+
/** An outgoing HTTP request record sent to devtools on request start. */
|
|
421
|
+
interface DevtoolsRequestRecord {
|
|
422
|
+
id: string;
|
|
423
|
+
instanceId: string | null;
|
|
424
|
+
url: string;
|
|
425
|
+
method: string;
|
|
426
|
+
startedAt: number;
|
|
427
|
+
status: RequestStatus;
|
|
428
|
+
statusCode: null;
|
|
429
|
+
requestHeaders: Record<string, string>;
|
|
430
|
+
payload: unknown;
|
|
431
|
+
queryParams: unknown;
|
|
432
|
+
}
|
|
433
|
+
/** Result of a completed HTTP request, sent to devtools on request end. */
|
|
434
|
+
type RequestEndResult = {
|
|
435
|
+
status: "success";
|
|
436
|
+
statusCode: number;
|
|
437
|
+
response: unknown;
|
|
438
|
+
duration: number;
|
|
439
|
+
} | {
|
|
440
|
+
status: "error";
|
|
441
|
+
error: ApiError;
|
|
442
|
+
statusCode: number | null;
|
|
443
|
+
duration: number;
|
|
444
|
+
} | {
|
|
445
|
+
status: "aborted";
|
|
446
|
+
duration: number;
|
|
447
|
+
};
|
|
448
|
+
/** Event callbacks implemented by the devtools panel, called by useApi instrumentation. */
|
|
449
|
+
interface DevtoolsBridge {
|
|
450
|
+
/** Fired when a useApi instance is created. */
|
|
451
|
+
onInstanceCreated: (id: string, url: string | undefined, options: DevtoolsInstanceOptions) => void;
|
|
452
|
+
/** Fired when a useApi instance is destroyed (scope disposed). */
|
|
453
|
+
onInstanceDestroyed: (id: string) => void;
|
|
454
|
+
/** Fired when instance state (loading, error, statusCode, data) changes. */
|
|
455
|
+
onStateUpdate: (id: string, state: Partial<DevtoolsInstanceState>) => void;
|
|
456
|
+
/** Fired when an HTTP request starts. */
|
|
457
|
+
onRequestStart: (record: DevtoolsRequestRecord) => void;
|
|
458
|
+
/** Fired when an HTTP request completes (success, error, or abort). */
|
|
459
|
+
onRequestEnd: (id: string, result: RequestEndResult) => void;
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Options for the `@ametie/vue-muza-devtools` panel.
|
|
463
|
+
*
|
|
464
|
+
* @example
|
|
465
|
+
* ```ts
|
|
466
|
+
* app.use(createApi({
|
|
467
|
+
* axios: apiClient,
|
|
468
|
+
* devtools: { enabled: process.env.NODE_ENV !== 'production' },
|
|
469
|
+
* }))
|
|
470
|
+
* ```
|
|
471
|
+
*/
|
|
472
|
+
interface DevtoolsOptions {
|
|
473
|
+
/** Enable the devtools panel. Default: false. */
|
|
474
|
+
enabled: boolean;
|
|
475
|
+
/** Maximum number of network requests kept in history. Default: 300. */
|
|
476
|
+
maxHistory?: number;
|
|
477
|
+
/** Maximum payload/response size in bytes before truncation. Default: 200_000. */
|
|
478
|
+
maxPayloadSize?: number;
|
|
479
|
+
/** Custom tabs appended after built-in tabs. */
|
|
480
|
+
tabs?: Array<{
|
|
481
|
+
id: string;
|
|
482
|
+
label: string;
|
|
483
|
+
component: unknown;
|
|
484
|
+
icon?: unknown;
|
|
485
|
+
order?: number;
|
|
486
|
+
}>;
|
|
487
|
+
}
|
|
400
488
|
|
|
401
489
|
declare function createApi(options: ApiPluginOptions): {
|
|
402
490
|
install(app: App): void;
|
|
@@ -782,4 +870,4 @@ declare function invalidateCache(id: string | string[]): void;
|
|
|
782
870
|
*/
|
|
783
871
|
declare function clearAllCache(): void;
|
|
784
872
|
|
|
785
|
-
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 CacheOptions, type SetDataInput, type UseApiBatchOptions, type UseApiBatchReturn, type UseApiOptions, type UseApiReturn, clearAllCache, createApi, createApiClient, invalidateCache, setAuthMonitor, setupInterceptors, tokenManager, useAbortController, useApi, useApiBatch, useApiConfig, useApiDelete, useApiGet, useApiPatch, useApiPost, useApiPut, useApiState };
|
|
873
|
+
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 CacheOptions, type DevtoolsBridge, type DevtoolsInstanceOptions, type DevtoolsInstanceState, type DevtoolsOptions, type DevtoolsRequestRecord, type RequestEndResult, type RequestStatus, type SetDataInput, type UseApiBatchOptions, type UseApiBatchReturn, type UseApiOptions, type UseApiReturn, clearAllCache, createApi, createApiClient, invalidateCache, setAuthMonitor, setupInterceptors, tokenManager, useAbortController, useApi, useApiBatch, useApiConfig, useApiDelete, useApiGet, useApiPatch, useApiPost, useApiPut, useApiState };
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
// src/plugin.ts
|
|
2
2
|
import { inject } from "vue";
|
|
3
|
+
|
|
4
|
+
// src/devtools.ts
|
|
5
|
+
var bridge = null;
|
|
6
|
+
var requestCounter = 0;
|
|
7
|
+
var pendingCalls = [];
|
|
8
|
+
function nextRequestId() {
|
|
9
|
+
return `req_${++requestCounter}`;
|
|
10
|
+
}
|
|
11
|
+
async function initDevtools(options, app) {
|
|
12
|
+
if (!options.enabled) return;
|
|
13
|
+
try {
|
|
14
|
+
const { createBridge } = await import("@ametie/vue-muza-devtools");
|
|
15
|
+
bridge = createBridge(options, app);
|
|
16
|
+
for (const fn of pendingCalls) fn();
|
|
17
|
+
pendingCalls.length = 0;
|
|
18
|
+
} catch {
|
|
19
|
+
console.warn("[vue-muza-use] devtools enabled but @ametie/vue-muza-devtools is not installed");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
var devtoolsBridge = {
|
|
23
|
+
onInstanceCreated(id, url, options) {
|
|
24
|
+
if (bridge) {
|
|
25
|
+
bridge.onInstanceCreated(id, url, options);
|
|
26
|
+
} else {
|
|
27
|
+
pendingCalls.push(() => bridge?.onInstanceCreated(id, url, options));
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
onInstanceDestroyed(id) {
|
|
31
|
+
bridge?.onInstanceDestroyed(id);
|
|
32
|
+
},
|
|
33
|
+
onStateUpdate(id, state) {
|
|
34
|
+
bridge?.onStateUpdate(id, state);
|
|
35
|
+
},
|
|
36
|
+
onRequestStart(record) {
|
|
37
|
+
bridge?.onRequestStart(record);
|
|
38
|
+
},
|
|
39
|
+
onRequestEnd(id, result) {
|
|
40
|
+
bridge?.onRequestEnd(id, result);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// src/plugin.ts
|
|
3
45
|
var API_INJECTION_KEY = /* @__PURE__ */ Symbol("use-api-config");
|
|
4
46
|
var globalConfig = null;
|
|
5
47
|
function createApi(options) {
|
|
@@ -7,6 +49,9 @@ function createApi(options) {
|
|
|
7
49
|
return {
|
|
8
50
|
install(app) {
|
|
9
51
|
app.provide(API_INJECTION_KEY, options);
|
|
52
|
+
if (options.devtools) {
|
|
53
|
+
void initDevtools(options.devtools, app);
|
|
54
|
+
}
|
|
10
55
|
}
|
|
11
56
|
};
|
|
12
57
|
}
|
|
@@ -57,9 +102,21 @@ function debounceFn(fn, delay) {
|
|
|
57
102
|
};
|
|
58
103
|
}
|
|
59
104
|
|
|
105
|
+
// src/utils/urlUtils.ts
|
|
106
|
+
function parseUrlQueryParams(url) {
|
|
107
|
+
if (!url) return null;
|
|
108
|
+
const qIndex = url.indexOf("?");
|
|
109
|
+
if (qIndex === -1) return null;
|
|
110
|
+
const params = {};
|
|
111
|
+
for (const [k, v] of new URLSearchParams(url.slice(qIndex + 1))) {
|
|
112
|
+
params[k] = v;
|
|
113
|
+
}
|
|
114
|
+
return Object.keys(params).length > 0 ? params : null;
|
|
115
|
+
}
|
|
116
|
+
|
|
60
117
|
// src/useApi.ts
|
|
61
118
|
import { isAxiosError as isAxiosError2 } from "axios";
|
|
62
|
-
import { ref as ref3, computed, effectScope, getCurrentScope as getCurrentScope2, onScopeDispose as onScopeDispose2, toValue, watch } from "vue";
|
|
119
|
+
import { ref as ref3, computed, effectScope, getCurrentInstance, getCurrentScope as getCurrentScope2, onScopeDispose as onScopeDispose2, toValue, watch, useId } from "vue";
|
|
63
120
|
|
|
64
121
|
// src/utils/errorParser.ts
|
|
65
122
|
import { isAxiosError } from "axios";
|
|
@@ -286,6 +343,30 @@ function useApi(url, options = {}) {
|
|
|
286
343
|
const startLoading = initialLoading ?? immediate;
|
|
287
344
|
const state = useApiState(initialData, { initialLoading: startLoading });
|
|
288
345
|
const revalidating = ref3(false);
|
|
346
|
+
const instanceId = getCurrentInstance() != null ? useId() : nextRequestId();
|
|
347
|
+
devtoolsBridge.onInstanceCreated(instanceId, toValue(url), {
|
|
348
|
+
authMode: options.authMode ?? "default",
|
|
349
|
+
cache: options.cache,
|
|
350
|
+
retry: options.retry ?? false,
|
|
351
|
+
poll: (() => {
|
|
352
|
+
const v = toValue(options.poll);
|
|
353
|
+
return typeof v === "number" ? v : 0;
|
|
354
|
+
})(),
|
|
355
|
+
immediate: options.immediate ?? false,
|
|
356
|
+
lazy: options.lazy ?? false
|
|
357
|
+
});
|
|
358
|
+
if (getCurrentScope2()) {
|
|
359
|
+
watch(
|
|
360
|
+
() => ({
|
|
361
|
+
loading: state.loading.value,
|
|
362
|
+
error: state.error.value,
|
|
363
|
+
statusCode: state.statusCode.value,
|
|
364
|
+
data: state.data.value
|
|
365
|
+
}),
|
|
366
|
+
(s) => devtoolsBridge.onStateUpdate(instanceId, s),
|
|
367
|
+
{ deep: true }
|
|
368
|
+
);
|
|
369
|
+
}
|
|
289
370
|
const abortController2 = ref3(null);
|
|
290
371
|
const globalAbort = useGlobalAbort ? useAbortController() : null;
|
|
291
372
|
let pollTimer = null;
|
|
@@ -350,6 +431,9 @@ function useApi(url, options = {}) {
|
|
|
350
431
|
state.setError(null);
|
|
351
432
|
let wasCancelled = false;
|
|
352
433
|
let retryCount = 0;
|
|
434
|
+
let devtoolsRequestId = null;
|
|
435
|
+
let devtoolsRequestStartedAt = 0;
|
|
436
|
+
let devtoolsRequestEndResult = null;
|
|
353
437
|
try {
|
|
354
438
|
if (!requestUrl) {
|
|
355
439
|
throw new Error("Request URL is missing");
|
|
@@ -358,6 +442,21 @@ function useApi(url, options = {}) {
|
|
|
358
442
|
const resolvedData = toValue(rawData);
|
|
359
443
|
const rawParams = config?.params !== void 0 ? config.params : axiosConfig.params;
|
|
360
444
|
const resolvedParams = toValue(rawParams);
|
|
445
|
+
const devtoolsQueryParams = resolvedParams ?? parseUrlQueryParams(requestUrl);
|
|
446
|
+
devtoolsRequestId = nextRequestId();
|
|
447
|
+
devtoolsRequestStartedAt = Date.now();
|
|
448
|
+
devtoolsBridge.onRequestStart({
|
|
449
|
+
id: devtoolsRequestId,
|
|
450
|
+
instanceId,
|
|
451
|
+
url: requestUrl,
|
|
452
|
+
method,
|
|
453
|
+
startedAt: devtoolsRequestStartedAt,
|
|
454
|
+
status: "pending",
|
|
455
|
+
statusCode: null,
|
|
456
|
+
requestHeaders: {},
|
|
457
|
+
payload: resolvedData ?? null,
|
|
458
|
+
queryParams: devtoolsQueryParams
|
|
459
|
+
});
|
|
361
460
|
while (true) {
|
|
362
461
|
try {
|
|
363
462
|
const response = await axios2.request({
|
|
@@ -381,6 +480,12 @@ function useApi(url, options = {}) {
|
|
|
381
480
|
}
|
|
382
481
|
onSuccess?.(response);
|
|
383
482
|
notifyFetched();
|
|
483
|
+
devtoolsRequestEndResult = {
|
|
484
|
+
status: "success",
|
|
485
|
+
statusCode: response.status,
|
|
486
|
+
response: response.data,
|
|
487
|
+
duration: Date.now() - devtoolsRequestStartedAt
|
|
488
|
+
};
|
|
384
489
|
return selected;
|
|
385
490
|
} catch (err) {
|
|
386
491
|
if (controller.signal.aborted || isAxiosError2(err) && err.code === "ERR_CANCELED") {
|
|
@@ -399,6 +504,12 @@ function useApi(url, options = {}) {
|
|
|
399
504
|
}
|
|
400
505
|
continue;
|
|
401
506
|
}
|
|
507
|
+
devtoolsRequestEndResult = {
|
|
508
|
+
status: "error",
|
|
509
|
+
error: apiError,
|
|
510
|
+
statusCode: apiError.status ?? null,
|
|
511
|
+
duration: Date.now() - devtoolsRequestStartedAt
|
|
512
|
+
};
|
|
402
513
|
if (!skipErrorNotification && globalErrorHandler) {
|
|
403
514
|
globalErrorHandler(apiError, err);
|
|
404
515
|
}
|
|
@@ -414,6 +525,12 @@ function useApi(url, options = {}) {
|
|
|
414
525
|
return null;
|
|
415
526
|
}
|
|
416
527
|
const apiError = errorParser ? errorParser(err) : parseApiError(err);
|
|
528
|
+
devtoolsRequestEndResult = {
|
|
529
|
+
status: "error",
|
|
530
|
+
error: apiError,
|
|
531
|
+
statusCode: null,
|
|
532
|
+
duration: Date.now() - devtoolsRequestStartedAt
|
|
533
|
+
};
|
|
417
534
|
if (!skipErrorNotification && globalErrorHandler) {
|
|
418
535
|
globalErrorHandler(apiError, err);
|
|
419
536
|
}
|
|
@@ -422,6 +539,12 @@ function useApi(url, options = {}) {
|
|
|
422
539
|
onError?.(apiError);
|
|
423
540
|
return null;
|
|
424
541
|
} finally {
|
|
542
|
+
if (devtoolsRequestId !== null) {
|
|
543
|
+
devtoolsBridge.onRequestEnd(
|
|
544
|
+
devtoolsRequestId,
|
|
545
|
+
devtoolsRequestEndResult ?? { status: "aborted", duration: Date.now() - devtoolsRequestStartedAt }
|
|
546
|
+
);
|
|
547
|
+
}
|
|
425
548
|
if (globalAbortHandler && subscribedSignal) subscribedSignal.removeEventListener("abort", globalAbortHandler);
|
|
426
549
|
revalidating.value = false;
|
|
427
550
|
if (!wasCancelled) {
|
|
@@ -498,7 +621,10 @@ function useApi(url, options = {}) {
|
|
|
498
621
|
}
|
|
499
622
|
};
|
|
500
623
|
if (getCurrentScope2()) {
|
|
501
|
-
onScopeDispose2(() =>
|
|
624
|
+
onScopeDispose2(() => {
|
|
625
|
+
abort("Scope disposed");
|
|
626
|
+
devtoolsBridge.onInstanceDestroyed(instanceId);
|
|
627
|
+
});
|
|
502
628
|
}
|
|
503
629
|
if (immediate) execute();
|
|
504
630
|
if (typeof document !== "undefined") {
|