@ametie/vue-muza-use 0.10.0 โ 1.0.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 +242 -175
- package/dist/index.cjs +108 -41
- package/dist/index.d.cts +66 -29
- package/dist/index.d.ts +66 -29
- package/dist/index.mjs +91 -24
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://vuejs.org/)
|
|
6
6
|
[](https://www.typescriptlang.org/)
|
|
7
7
|
|
|
8
|
-
**
|
|
8
|
+
**TypeScript-first, feature-rich Axios wrapper for Vue 3 Composition API. Built for real-world business logic.**
|
|
9
9
|
|
|
10
10
|
A production-ready composable that eliminates boilerplate and solves the hard problems: race conditions, token refresh queues, automatic retries, and reactive request management. Write less code, ship faster, sleep better.
|
|
11
11
|
|
|
@@ -15,21 +15,23 @@ A production-ready composable that eliminates boilerplate and solves the hard pr
|
|
|
15
15
|
> This library ships with a skill that teaches Claude the feature wrapper pattern, naming conventions, and all `UseApiOptions`.
|
|
16
16
|
> Claude will generate correct, architecture-consistent API layer code out of the box โ no extra prompting needed.
|
|
17
17
|
>
|
|
18
|
-
> ๐ **[View skill file โ](https://github.com/MortyQ/vue-
|
|
18
|
+
> ๐ **[View skill file โ](https://github.com/MortyQ/vue-muza-use/blob/main/.claude/skills/use-api/SKILL.md)**
|
|
19
19
|
|
|
20
20
|
---
|
|
21
21
|
|
|
22
|
+
> **Using v0.x?** The legacy documentation is available at [v0.10.0 README](https://github.com/MortyQ/vue-muza-use/blob/v0.10.0/packages/use-api/README.md).
|
|
23
|
+
|
|
22
24
|
## โจ Features
|
|
23
25
|
|
|
24
26
|
**Core Features** (Get started in minutes):
|
|
25
|
-
- ๐ฏ **
|
|
26
|
-
- ๐ **Smart Reactivity** โ
|
|
27
|
+
- ๐ฏ **TypeScript-first** โ Full TypeScript support with strict typing for requests and responses
|
|
28
|
+
- ๐ **Smart Reactivity** โ Auto-tracks reactive deps in `url`, `params`, and `data` โ refetches automatically when they change
|
|
27
29
|
- โฑ๏ธ **Built-in Debouncing** โ Perfect for search inputs and auto-save forms
|
|
28
30
|
- ๐ก๏ธ **Race Condition Protection** โ Global abort controller cancels stale requests automatically
|
|
29
31
|
- ๐ **Auto-Polling** โ Built-in interval fetching with smart tab visibility detection
|
|
30
32
|
- ๐ **Batch Requests** โ Execute multiple requests in parallel with progress tracking
|
|
31
33
|
- ๐งน **Zero Memory Leaks** โ Automatic cleanup of pending requests on component unmount
|
|
32
|
-
- ๐ **ignoreUpdates** โ
|
|
34
|
+
- ๐ **ignoreUpdates** โ Update reactive deps silently without triggering a re-fetch
|
|
33
35
|
- ๐๏ธ **Response Caching** โ In-memory cache with configurable TTL and manual invalidation
|
|
34
36
|
- โก **Stale-While-Revalidate** โ Serve cached data instantly while refreshing silently in the background
|
|
35
37
|
- ๐ฌ **select** โ Transform or filter response data declaratively; re-applied on every fetch automatically
|
|
@@ -42,6 +44,35 @@ A production-ready composable that eliminates boilerplate and solves the hard pr
|
|
|
42
44
|
|
|
43
45
|
---
|
|
44
46
|
|
|
47
|
+
## ๐ How it compares
|
|
48
|
+
|
|
49
|
+
> Honest comparison. โ
built-in ยท โ ๏ธ partial or plugin needed ยท โ not supported
|
|
50
|
+
|
|
51
|
+
| Feature | vue-muza-use | @vueuse/useFetch | TanStack Query | swrv |
|
|
52
|
+
|---------|:---:|:---:|:---:|:---:|
|
|
53
|
+
| **Axios-first** | โ
| โ fetch | โ ๏ธ adapter | โ fetch |
|
|
54
|
+
| **JWT auto-refresh + queue** | โ
| โ | โ | โ |
|
|
55
|
+
| **Race condition protection** | โ
| โ | โ
| โ |
|
|
56
|
+
| **ignoreUpdates** | โ
| โ | โ | โ |
|
|
57
|
+
| **Built-in debounce** | โ
| โ | โ | โ |
|
|
58
|
+
| **Batch requests** | โ
| โ | โ | โ |
|
|
59
|
+
| **Built-in retry** | โ
| โ | โ
| โ |
|
|
60
|
+
| **Auto-polling** | โ
| โ | โ
| โ
|
|
|
61
|
+
| **SWR (stale-while-revalidate)** | โ
| โ | โ
| โ
|
|
|
62
|
+
| **select / transform** | โ
| โ | โ
| โ |
|
|
63
|
+
| **Response caching** | โ
| โ | โ
| โ
|
|
|
64
|
+
| **TypeScript** | โ
| โ
| โ
| โ
|
|
|
65
|
+
| **SSR / Nuxt** | โ | โ
| โ
| โ
|
|
|
66
|
+
| **DevTools** | โ | โ | โ
| โ |
|
|
67
|
+
|
|
68
|
+
**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
|
+
**Choose TanStack Query if:** you need SSR, DevTools, or advanced server-state normalization.
|
|
71
|
+
|
|
72
|
+
**Choose @vueuse/useFetch if:** you want a minimal fetch wrapper with no opinions.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
45
76
|
## ๐ Table of Contents
|
|
46
77
|
|
|
47
78
|
**Getting Started:**
|
|
@@ -51,9 +82,10 @@ A production-ready composable that eliminates boilerplate and solves the hard pr
|
|
|
51
82
|
|
|
52
83
|
**Core Features:**
|
|
53
84
|
- [Watch & Auto-Refetch](#watch--auto-refetch)
|
|
54
|
-
- [ignoreUpdates โ
|
|
85
|
+
- [ignoreUpdates โ Update Without Re-fetching](#ignoreupdates--update-without-re-fetching)
|
|
55
86
|
- [Response Caching](#response-caching)
|
|
56
87
|
- [Stale-While-Revalidate (SWR)](#stale-while-revalidate-swr)
|
|
88
|
+
- [Refetch Triggers](#refetch-triggers)
|
|
57
89
|
- [Polling (Background Updates)](#polling-background-updates)
|
|
58
90
|
- [Error Handling](#error-handling)
|
|
59
91
|
- [retry โ Automatic Request Retry](#retry--automatic-request-retry)
|
|
@@ -167,7 +199,6 @@ const searchQuery = ref('')
|
|
|
167
199
|
const { data, loading } = useApi<Product[]>(
|
|
168
200
|
() => `/products/search?q=${searchQuery.value}`,
|
|
169
201
|
{
|
|
170
|
-
watch: searchQuery,
|
|
171
202
|
debounce: 500
|
|
172
203
|
}
|
|
173
204
|
)
|
|
@@ -270,7 +301,6 @@ const filters = ref({
|
|
|
270
301
|
|
|
271
302
|
const { data } = useApi('/users', {
|
|
272
303
|
params: filters,
|
|
273
|
-
watch: filters,
|
|
274
304
|
immediate: true
|
|
275
305
|
})
|
|
276
306
|
```
|
|
@@ -292,7 +322,6 @@ const id = ref<number | null>(null)
|
|
|
292
322
|
|
|
293
323
|
const { data } = useApi<User>(
|
|
294
324
|
() => id.value ? `/users/${id.value}` : undefined,
|
|
295
|
-
{ watch: id }
|
|
296
325
|
)
|
|
297
326
|
|
|
298
327
|
// No request fires until id.value is set
|
|
@@ -355,162 +384,130 @@ const { execute, loading, error } = useApi<LoginResponse>(
|
|
|
355
384
|
|
|
356
385
|
### Watch & Auto-Refetch
|
|
357
386
|
|
|
358
|
-
|
|
387
|
+
**TL;DR: Pass reactive refs or getters to `url`, `params`, or `data` โ the request re-fires automatically when they change.**
|
|
388
|
+
|
|
389
|
+
Any reactive dependency accessed inside a getter is tracked automatically. No explicit `watch` option needed.
|
|
359
390
|
|
|
360
|
-
#### Single Dependency
|
|
361
391
|
```typescript
|
|
362
392
|
import { ref } from 'vue'
|
|
363
393
|
import { useApi } from '@ametie/vue-muza-use'
|
|
364
394
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
name: string
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
const userId = ref(1)
|
|
395
|
+
const search = ref('')
|
|
396
|
+
const page = ref(1)
|
|
371
397
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
{
|
|
375
|
-
|
|
398
|
+
// Reactive params getter โ both search and page are tracked automatically
|
|
399
|
+
const { data, loading } = useApi('/products', {
|
|
400
|
+
params: () => ({ q: search.value, page: page.value }),
|
|
401
|
+
immediate: true,
|
|
402
|
+
})
|
|
376
403
|
|
|
377
|
-
|
|
404
|
+
// Change any dep โ request re-fires automatically
|
|
405
|
+
search.value = 'shoes'
|
|
406
|
+
page.value = 2
|
|
378
407
|
```
|
|
379
408
|
|
|
380
|
-
|
|
409
|
+
**Reactive URL:**
|
|
381
410
|
```typescript
|
|
382
|
-
|
|
383
|
-
import { useApi } from '@ametie/vue-muza-use'
|
|
411
|
+
const userId = ref(1)
|
|
384
412
|
|
|
385
|
-
const
|
|
386
|
-
|
|
413
|
+
const { data } = useApi(() => `/users/${userId.value}`, {
|
|
414
|
+
immediate: true,
|
|
415
|
+
})
|
|
387
416
|
|
|
388
|
-
|
|
389
|
-
() => `/products?q=${searchQuery.value}&category=${category.value}`,
|
|
390
|
-
{
|
|
391
|
-
watch: [searchQuery, category],
|
|
392
|
-
debounce: 500
|
|
393
|
-
}
|
|
394
|
-
)
|
|
417
|
+
userId.value = 2 // โ re-fetches /users/2 automatically
|
|
395
418
|
```
|
|
396
419
|
|
|
397
|
-
|
|
420
|
+
**Opt-out with `lazy: true`:**
|
|
421
|
+
|
|
422
|
+
For forms and manual mutations where you control when `execute()` is called:
|
|
423
|
+
|
|
398
424
|
```typescript
|
|
399
|
-
|
|
400
|
-
import { useApi } from '@ametie/vue-muza-use'
|
|
425
|
+
const form = ref({ name: '', email: '' })
|
|
401
426
|
|
|
402
|
-
const
|
|
403
|
-
|
|
404
|
-
|
|
427
|
+
const { execute, loading } = useApi('/users', {
|
|
428
|
+
method: 'POST',
|
|
429
|
+
data: form,
|
|
430
|
+
lazy: true, // form changes do NOT trigger re-fetch
|
|
431
|
+
onSuccess: () => router.push('/users'),
|
|
405
432
|
})
|
|
406
433
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
debounce: 1000,
|
|
412
|
-
onSuccess: () => console.log('Saved!')
|
|
413
|
-
})
|
|
434
|
+
// Only fires when you call it
|
|
435
|
+
async function submit() {
|
|
436
|
+
await execute()
|
|
437
|
+
}
|
|
414
438
|
```
|
|
415
439
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
### ignoreUpdates โ Atomic Updates Without Refetch
|
|
419
|
-
|
|
420
|
-
**TL;DR: Change multiple reactive values at once without triggering a request between each change.**
|
|
421
|
-
|
|
422
|
-
When `watch` is active, every change to a watched ref triggers a new request. If you need to update three filter fields at once, you'd get three requests instead of one. `ignoreUpdates` wraps your changes so the watcher stays silent while you update all fields, then you call `execute()` once.
|
|
423
|
-
|
|
424
|
-
#### โ Without ignoreUpdates โ 2 requests fire
|
|
440
|
+
**`immediate` works independently of auto-tracking:**
|
|
425
441
|
|
|
426
442
|
```typescript
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
const search = ref('')
|
|
432
|
-
|
|
433
|
-
const { execute } = useApi('/users', {
|
|
434
|
-
params: { page, search },
|
|
435
|
-
watch: [page, search]
|
|
443
|
+
// Fetch on mount + re-fetch on dep change
|
|
444
|
+
useApi('/products', {
|
|
445
|
+
params: () => ({ q: search.value }),
|
|
446
|
+
immediate: true,
|
|
436
447
|
})
|
|
437
448
|
|
|
438
|
-
//
|
|
439
|
-
|
|
440
|
-
|
|
449
|
+
// No fetch on mount, but re-fetch on dep change
|
|
450
|
+
useApi('/products', {
|
|
451
|
+
params: () => ({ q: search.value }),
|
|
452
|
+
// immediate: false is the default
|
|
453
|
+
})
|
|
441
454
|
```
|
|
442
455
|
|
|
443
|
-
|
|
456
|
+
**Batching:** Vue batches synchronous reactive changes before triggering auto-tracking โ two synchronous ref changes fire only one request.
|
|
444
457
|
|
|
445
458
|
```typescript
|
|
446
|
-
|
|
447
|
-
|
|
459
|
+
// Only one request fires (Vue batches sync changes)
|
|
460
|
+
status.value = 'active'
|
|
461
|
+
page.value = 1
|
|
462
|
+
```
|
|
448
463
|
|
|
449
|
-
|
|
450
|
-
const search = ref('')
|
|
464
|
+
---
|
|
451
465
|
|
|
452
|
-
|
|
453
|
-
params: { page, search },
|
|
454
|
-
watch: [page, search]
|
|
455
|
-
})
|
|
466
|
+
### ignoreUpdates โ Update Without Re-fetching
|
|
456
467
|
|
|
457
|
-
|
|
458
|
-
ignoreUpdates(() => {
|
|
459
|
-
page.value = 1
|
|
460
|
-
search.value = 'john'
|
|
461
|
-
})
|
|
462
|
-
await execute() // โ single request: page=1, search=john
|
|
463
|
-
```
|
|
468
|
+
**TL;DR: Update a reactive dep without triggering auto-tracking.**
|
|
464
469
|
|
|
465
|
-
|
|
470
|
+
When auto-tracking is active, any reactive dep change fires a new request. `ignoreUpdates` pauses the tracking scope for the duration of the callback โ changes inside do not trigger a re-fetch.
|
|
466
471
|
|
|
467
|
-
|
|
472
|
+
#### Example โ clear search input without fetching
|
|
468
473
|
|
|
469
474
|
```typescript
|
|
470
475
|
import { ref } from 'vue'
|
|
471
476
|
import { useApi } from '@ametie/vue-muza-use'
|
|
472
477
|
|
|
473
|
-
const page = ref(1)
|
|
474
478
|
const search = ref('')
|
|
475
|
-
const status = ref('all')
|
|
476
479
|
|
|
477
|
-
const {
|
|
478
|
-
params: {
|
|
479
|
-
|
|
480
|
+
const { data, ignoreUpdates } = useApi('/products', {
|
|
481
|
+
params: () => ({ q: search.value }),
|
|
482
|
+
debounce: 300,
|
|
480
483
|
})
|
|
481
484
|
|
|
482
|
-
function
|
|
485
|
+
function clearSearch() {
|
|
483
486
|
ignoreUpdates(() => {
|
|
484
|
-
page.value = 1
|
|
485
487
|
search.value = ''
|
|
486
|
-
status.value = 'all'
|
|
487
488
|
})
|
|
488
|
-
|
|
489
|
+
// auto-tracking is paused โ no request fires
|
|
489
490
|
}
|
|
490
491
|
```
|
|
491
492
|
|
|
492
|
-
|
|
493
|
+
The user types โ auto-tracking fires โ debounced request. Clicking "Clear" resets the input without triggering a fetch.
|
|
493
494
|
|
|
494
|
-
|
|
495
|
+
#### Safe to call when `lazy: true`
|
|
495
496
|
|
|
496
|
-
|
|
497
|
-
import { ref } from 'vue'
|
|
498
|
-
import { useApi } from '@ametie/vue-muza-use'
|
|
497
|
+
If `lazy: true`, `ignoreUpdates` still runs the updater โ it just has nothing to suppress.
|
|
499
498
|
|
|
500
|
-
|
|
499
|
+
```typescript
|
|
501
500
|
const { ignoreUpdates } = useApi('/data')
|
|
502
501
|
|
|
503
|
-
// Safe โ no error thrown, updater still runs
|
|
504
502
|
ignoreUpdates(() => {
|
|
505
|
-
|
|
503
|
+
someRef.value = 42 // runs normally, nothing to suppress
|
|
506
504
|
})
|
|
507
505
|
```
|
|
508
506
|
|
|
509
507
|
> [!NOTE]
|
|
510
508
|
> `ignoreUpdates` is synchronous only. Changes made after an `await` inside the
|
|
511
509
|
> updater function will NOT be suppressed โ the flag resets after the synchronous
|
|
512
|
-
> portion completes.
|
|
513
|
-
> `ignoreUpdates` and call `execute()` manually.
|
|
510
|
+
> portion completes.
|
|
514
511
|
|
|
515
512
|
---
|
|
516
513
|
|
|
@@ -623,9 +620,9 @@ invalidateCache(['products', 'categories'])
|
|
|
623
620
|
clearAllCache()
|
|
624
621
|
```
|
|
625
622
|
|
|
626
|
-
#### cache +
|
|
623
|
+
#### cache + auto-tracking
|
|
627
624
|
|
|
628
|
-
When
|
|
625
|
+
When auto-tracking is active, each dep-change-triggered `execute()` still checks the cache first:
|
|
629
626
|
|
|
630
627
|
```vue
|
|
631
628
|
<script setup lang="ts">
|
|
@@ -636,7 +633,7 @@ const categoryId = ref<number>(1)
|
|
|
636
633
|
|
|
637
634
|
const { data } = useApi<Product[]>(() => `/categories/${categoryId.value}/products`, {
|
|
638
635
|
cache: { id: `products-cat-${categoryId.value}`, staleTime: 30_000 },
|
|
639
|
-
|
|
636
|
+
params: () => ({ category: categoryId.value }),
|
|
640
637
|
immediate: true,
|
|
641
638
|
})
|
|
642
639
|
</script>
|
|
@@ -673,6 +670,7 @@ const { data } = useApi('/reports', {
|
|
|
673
670
|
|-------|------|---------|-------------|
|
|
674
671
|
| `id` | `string` | โ | Unique cache key |
|
|
675
672
|
| `staleTime` | `number` | `300_000` | TTL in milliseconds. Entry is deleted on next read after this time |
|
|
673
|
+
| `swr` | `boolean` | `false` | Stale-while-revalidate: serve cached data instantly while revalidating in the background. See [SWR](#stale-while-revalidate-swr) |
|
|
676
674
|
|
|
677
675
|
#### Out of Scope (by design)
|
|
678
676
|
|
|
@@ -709,8 +707,7 @@ import { useApi } from '@ametie/vue-muza-use'
|
|
|
709
707
|
interface User { id: number; name: string }
|
|
710
708
|
|
|
711
709
|
const { data, revalidating } = useApi<User[]>('/users', {
|
|
712
|
-
cache: 'users',
|
|
713
|
-
staleWhileRevalidate: true,
|
|
710
|
+
cache: { id: 'users', swr: true },
|
|
714
711
|
immediate: true,
|
|
715
712
|
})
|
|
716
713
|
</script>
|
|
@@ -747,8 +744,7 @@ If the background revalidation request fails:
|
|
|
747
744
|
|
|
748
745
|
```typescript
|
|
749
746
|
const { data, revalidating, error } = useApi('/dashboard', {
|
|
750
|
-
cache: 'dashboard',
|
|
751
|
-
staleWhileRevalidate: true,
|
|
747
|
+
cache: { id: 'dashboard', swr: true },
|
|
752
748
|
immediate: true,
|
|
753
749
|
})
|
|
754
750
|
// data.value stays the cached value even after a failed revalidation
|
|
@@ -756,6 +752,73 @@ const { data, revalidating, error } = useApi('/dashboard', {
|
|
|
756
752
|
|
|
757
753
|
---
|
|
758
754
|
|
|
755
|
+
### Refetch Triggers
|
|
756
|
+
|
|
757
|
+
**TL;DR: Automatically re-fetch when the user returns to the tab or regains connectivity โ no manual wiring needed.**
|
|
758
|
+
|
|
759
|
+
#### `refetchOnFocus` โ Re-fetch on Tab Return
|
|
760
|
+
|
|
761
|
+
```typescript
|
|
762
|
+
const { data } = useApi('/dashboard', {
|
|
763
|
+
immediate: true,
|
|
764
|
+
refetchOnFocus: true, // default throttle: 60s
|
|
765
|
+
})
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
Pass `{ throttle: 0 }` to always re-fetch regardless of how recently data was loaded:
|
|
769
|
+
|
|
770
|
+
```typescript
|
|
771
|
+
const { data } = useApi('/notifications', {
|
|
772
|
+
immediate: true,
|
|
773
|
+
refetchOnFocus: { throttle: 0 },
|
|
774
|
+
})
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
Works seamlessly with `cache: { swr: true }` โ the user sees stale data instantly, fresh data arrives silently in the background:
|
|
778
|
+
|
|
779
|
+
```typescript
|
|
780
|
+
const { data, revalidating } = useApi('/feed', {
|
|
781
|
+
cache: { id: 'feed', swr: true },
|
|
782
|
+
refetchOnFocus: true,
|
|
783
|
+
immediate: true,
|
|
784
|
+
})
|
|
785
|
+
```
|
|
786
|
+
|
|
787
|
+
#### `refetchOnReconnect` โ Re-fetch on Network Restore
|
|
788
|
+
|
|
789
|
+
```typescript
|
|
790
|
+
const { data } = useApi('/messages', {
|
|
791
|
+
immediate: true,
|
|
792
|
+
refetchOnReconnect: true,
|
|
793
|
+
})
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
#### Global Configuration
|
|
797
|
+
|
|
798
|
+
Apply to all `useApi` instances at once:
|
|
799
|
+
|
|
800
|
+
```typescript
|
|
801
|
+
createApiClient({
|
|
802
|
+
axios,
|
|
803
|
+
globalOptions: {
|
|
804
|
+
refetchOnFocus: true,
|
|
805
|
+
refetchOnReconnect: true,
|
|
806
|
+
},
|
|
807
|
+
})
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
Opt individual requests out with `refetchOnFocus: false`:
|
|
811
|
+
|
|
812
|
+
```typescript
|
|
813
|
+
// Global refetchOnFocus: true, but this request opts out
|
|
814
|
+
const { data } = useApi('/static-config', {
|
|
815
|
+
refetchOnFocus: false,
|
|
816
|
+
immediate: true,
|
|
817
|
+
})
|
|
818
|
+
```
|
|
819
|
+
|
|
820
|
+
---
|
|
821
|
+
|
|
759
822
|
### Polling (Background Updates)
|
|
760
823
|
|
|
761
824
|
**TL;DR: Keep data fresh with smart polling that automatically pauses when the browser tab is hidden.**
|
|
@@ -999,7 +1062,10 @@ const toggleTodo = (id: number) => {
|
|
|
999
1062
|
}
|
|
1000
1063
|
```
|
|
1001
1064
|
|
|
1002
|
-
#### Transform
|
|
1065
|
+
#### Transform after fetch
|
|
1066
|
+
|
|
1067
|
+
Use `mutate` from the **same** `useApi` call to post-process data after it arrives:
|
|
1068
|
+
|
|
1003
1069
|
```typescript
|
|
1004
1070
|
import { useApi } from '@ametie/vue-muza-use'
|
|
1005
1071
|
|
|
@@ -1010,7 +1076,7 @@ interface User {
|
|
|
1010
1076
|
fullName?: string
|
|
1011
1077
|
}
|
|
1012
1078
|
|
|
1013
|
-
const { data } = useApi<User[]>('/users', {
|
|
1079
|
+
const { data, mutate } = useApi<User[]>('/users', {
|
|
1014
1080
|
immediate: true,
|
|
1015
1081
|
onSuccess: ({ data: users }) => {
|
|
1016
1082
|
mutate(users.map(u => ({
|
|
@@ -1019,10 +1085,13 @@ const { data } = useApi<User[]>('/users', {
|
|
|
1019
1085
|
})))
|
|
1020
1086
|
}
|
|
1021
1087
|
})
|
|
1022
|
-
|
|
1023
|
-
const { mutate } = useApi<User[]>('/users', { immediate: true })
|
|
1024
1088
|
```
|
|
1025
1089
|
|
|
1090
|
+
> [!TIP]
|
|
1091
|
+
> If the same transformation runs on every fetch (including polling or watch re-triggers),
|
|
1092
|
+
> use [`select`](#select--declarative-data-transformation) instead โ it's applied automatically
|
|
1093
|
+
> and keeps your options object clean.
|
|
1094
|
+
|
|
1026
1095
|
---
|
|
1027
1096
|
|
|
1028
1097
|
### select โ Declarative Data Transformation
|
|
@@ -1124,7 +1193,6 @@ const params = computed(() => ({
|
|
|
1124
1193
|
|
|
1125
1194
|
const { data, loading } = useApi<OrdersResponse>('/orders', {
|
|
1126
1195
|
params,
|
|
1127
|
-
watch: params,
|
|
1128
1196
|
immediate: true
|
|
1129
1197
|
})
|
|
1130
1198
|
</script>
|
|
@@ -1421,6 +1489,8 @@ createApp(App).use(createApi({
|
|
|
1421
1489
|
| `retryDelay` | `number` | `1000` | How many milliseconds to wait between retry attempts for all requests |
|
|
1422
1490
|
| `retryStatusCodes` | `number[]` | `[408,429,500,502,503,504]` | Default HTTP status codes that trigger a retry across all requests |
|
|
1423
1491
|
| `useGlobalAbort` | `boolean` | `true` | When `true`, all requests subscribe to the global abort controller |
|
|
1492
|
+
| `refetchOnFocus` | `boolean \| { throttle?: number }` | `undefined` | Apply `refetchOnFocus` to all `useApi` instances. Per-request value takes precedence (including `false` to opt-out) |
|
|
1493
|
+
| `refetchOnReconnect` | `boolean` | `undefined` | Apply `refetchOnReconnect` to all `useApi` instances. Per-request value takes precedence (including `false` to opt-out) |
|
|
1424
1494
|
|
|
1425
1495
|
---
|
|
1426
1496
|
|
|
@@ -1872,16 +1942,22 @@ Three type parameters โ all optional with defaults:
|
|
|
1872
1942
|
| Option | Type | Default | Description |
|
|
1873
1943
|
|--------|------|---------|-------------|
|
|
1874
1944
|
| `immediate` | `boolean` | `false` | When `true`, executes the request automatically when the composable is created |
|
|
1875
|
-
| `
|
|
1876
|
-
| `debounce` | `number` | `0` | Milliseconds to
|
|
1945
|
+
| `lazy` | `boolean` | `false` | Disable auto-tracking โ reactive changes to `url`, `params`, and `data` will NOT trigger a re-fetch |
|
|
1946
|
+
| `debounce` | `number` | `0` | Milliseconds to debounce auto-tracked re-fetches |
|
|
1877
1947
|
|
|
1878
1948
|
**Caching:**
|
|
1879
1949
|
|
|
1880
1950
|
| Option | Type | Default | Description |
|
|
1881
1951
|
|--------|------|---------|-------------|
|
|
1882
|
-
| `cache` | `string \| CacheOptions` | `undefined` | Cache the response in memory. String shorthand uses default 5-min TTL. See [Response Caching](#response-caching) |
|
|
1952
|
+
| `cache` | `string \| CacheOptions` | `undefined` | Cache the response in memory. String shorthand uses default 5-min TTL. `CacheOptions.swr: true` enables stale-while-revalidate. See [Response Caching](#response-caching) |
|
|
1883
1953
|
| `invalidateCache` | `string \| string[]` | `undefined` | Cache key(s) to delete on 2xx success. Never fires on error |
|
|
1884
|
-
|
|
1954
|
+
|
|
1955
|
+
**Refetch Triggers:**
|
|
1956
|
+
|
|
1957
|
+
| Option | Type | Default | Description |
|
|
1958
|
+
|--------|------|---------|-------------|
|
|
1959
|
+
| `refetchOnFocus` | `boolean \| { throttle?: number }` | `undefined` | Re-fetch when the browser tab regains focus. `true` uses a 60s throttle. Pass `{ throttle: 0 }` to always re-fetch. Configurable globally via `globalOptions` |
|
|
1960
|
+
| `refetchOnReconnect` | `boolean` | `undefined` | Re-fetch when the browser regains network connectivity (`online` event). No throttle. Configurable globally via `globalOptions` |
|
|
1885
1961
|
|
|
1886
1962
|
**Polling:**
|
|
1887
1963
|
|
|
@@ -1948,12 +2024,12 @@ Three type parameters โ all optional with defaults:
|
|
|
1948
2024
|
| `error` | `Ref<ApiError \| null>` | Error from the last failed request; `null` on success |
|
|
1949
2025
|
| `statusCode` | `Ref<number \| null>` | HTTP status code from the last completed request |
|
|
1950
2026
|
| `response` | `Ref<AxiosResponse<unknown> \| null>` | Full Axios response object including headers (raw, before `select`) |
|
|
1951
|
-
| `revalidating` | `Ref<boolean>` | `true` while a background SWR revalidation is in flight. Always `false` when `
|
|
2027
|
+
| `revalidating` | `Ref<boolean>` | `true` while a background SWR revalidation is in flight. Always `false` when `cache: { swr: true }` is not set |
|
|
1952
2028
|
| `execute(config?)` | `(config?: ApiRequestConfig<D>) => Promise<TSelected \| null>` | Manually trigger the request, optionally overriding options |
|
|
1953
2029
|
| `mutate(newData)` | `(newData: TSelected \| null \| ((prev: TSelected \| null) => TSelected \| null)) => void` | Update `data` locally without a network request; clears `error` |
|
|
1954
2030
|
| `abort(msg?)` | `(message?: string) => void` | Cancel the current in-flight request |
|
|
1955
2031
|
| `reset()` | `() => void` | Cancel the request and reset all state to initial values |
|
|
1956
|
-
| `ignoreUpdates(fn)` | `(updater: () => void) => void` | Run `updater` without triggering
|
|
2032
|
+
| `ignoreUpdates(fn)` | `(updater: () => void) => void` | Run `updater` without triggering auto-tracked re-execution |
|
|
1957
2033
|
|
|
1958
2034
|
#### `execute(config?)`
|
|
1959
2035
|
|
|
@@ -2196,7 +2272,7 @@ function logout() {
|
|
|
2196
2272
|
|
|
2197
2273
|
### 1. Search with Debounce and Reset
|
|
2198
2274
|
|
|
2199
|
-
|
|
2275
|
+
Debounced search input. Typing triggers a request; "Clear" resets the input silently without fetching.
|
|
2200
2276
|
|
|
2201
2277
|
```vue
|
|
2202
2278
|
<script setup lang="ts">
|
|
@@ -2210,30 +2286,25 @@ interface User {
|
|
|
2210
2286
|
}
|
|
2211
2287
|
|
|
2212
2288
|
const search = ref('')
|
|
2213
|
-
const page = ref(1)
|
|
2214
2289
|
|
|
2215
|
-
const { data, loading,
|
|
2216
|
-
() =>
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
immediate: true
|
|
2221
|
-
}
|
|
2222
|
-
)
|
|
2290
|
+
const { data, loading, ignoreUpdates } = useApi<User[]>('/users', {
|
|
2291
|
+
params: () => ({ q: search.value }),
|
|
2292
|
+
debounce: 400,
|
|
2293
|
+
immediate: true,
|
|
2294
|
+
})
|
|
2223
2295
|
|
|
2224
|
-
function
|
|
2296
|
+
function clearSearch() {
|
|
2225
2297
|
ignoreUpdates(() => {
|
|
2226
2298
|
search.value = ''
|
|
2227
|
-
page.value = 1
|
|
2228
2299
|
})
|
|
2229
|
-
|
|
2300
|
+
// auto-tracking is paused โ no request fires on clear
|
|
2230
2301
|
}
|
|
2231
2302
|
</script>
|
|
2232
2303
|
|
|
2233
2304
|
<template>
|
|
2234
2305
|
<div>
|
|
2235
2306
|
<input v-model="search" placeholder="Search users..." />
|
|
2236
|
-
<button @click="
|
|
2307
|
+
<button @click="clearSearch">Clear</button>
|
|
2237
2308
|
|
|
2238
2309
|
<div v-if="loading">Searching...</div>
|
|
2239
2310
|
<ul v-else>
|
|
@@ -2249,7 +2320,7 @@ function resetSearch() {
|
|
|
2249
2320
|
|
|
2250
2321
|
### 2. Paginated List with Filter Reset
|
|
2251
2322
|
|
|
2252
|
-
When the user changes a filter, reset the page to 1
|
|
2323
|
+
When the user changes a filter, also reset the page to 1. Vue batches synchronous ref changes, so auto-tracking fires once โ no `ignoreUpdates` needed here.
|
|
2253
2324
|
|
|
2254
2325
|
```vue
|
|
2255
2326
|
<script setup lang="ts">
|
|
@@ -2265,18 +2336,15 @@ interface Post {
|
|
|
2265
2336
|
const page = ref(1)
|
|
2266
2337
|
const status = ref('all')
|
|
2267
2338
|
|
|
2268
|
-
const { data, loading
|
|
2269
|
-
() =>
|
|
2270
|
-
|
|
2271
|
-
)
|
|
2339
|
+
const { data, loading } = useApi<Post[]>('/posts', {
|
|
2340
|
+
params: () => ({ status: status.value, page: page.value }),
|
|
2341
|
+
immediate: true,
|
|
2342
|
+
})
|
|
2272
2343
|
|
|
2273
2344
|
function changeStatus(newStatus: string) {
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
page.value = 1
|
|
2278
|
-
})
|
|
2279
|
-
execute()
|
|
2345
|
+
status.value = newStatus
|
|
2346
|
+
page.value = 1
|
|
2347
|
+
// Vue batches these sync changes โ auto-tracking fires once, one request
|
|
2280
2348
|
}
|
|
2281
2349
|
</script>
|
|
2282
2350
|
|
|
@@ -2480,36 +2548,35 @@ Start polling every 2 seconds and stop automatically when the job reaches a term
|
|
|
2480
2548
|
|
|
2481
2549
|
```vue
|
|
2482
2550
|
<script setup lang="ts">
|
|
2483
|
-
import { ref } from 'vue'
|
|
2484
|
-
import { useApi } from '@ametie/vue-muza-use'
|
|
2551
|
+
import { ref } from 'vue'
|
|
2552
|
+
import { useApi } from '@ametie/vue-muza-use'
|
|
2485
2553
|
|
|
2486
|
-
interface JobStatus {
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
}
|
|
2554
|
+
interface JobStatus {
|
|
2555
|
+
id: string
|
|
2556
|
+
status: 'pending' | 'processing' | 'complete' | 'failed'
|
|
2557
|
+
progress: number
|
|
2558
|
+
}
|
|
2491
2559
|
|
|
2492
|
-
const jobId = ref<string | null>(null)
|
|
2493
|
-
const pollInterval = ref(0)
|
|
2560
|
+
const jobId = ref<string | null>(null)
|
|
2561
|
+
const pollInterval = ref(0)
|
|
2562
|
+
|
|
2563
|
+
const { data: job, error } = useApi<JobStatus>(
|
|
2564
|
+
() => jobId.value ? `/jobs/${jobId.value}` : undefined,
|
|
2565
|
+
{
|
|
2566
|
+
poll: { interval: pollInterval },
|
|
2567
|
+
onSuccess(response) {
|
|
2568
|
+
const { status } = response.data
|
|
2569
|
+
if (status === 'complete' || status === 'failed') {
|
|
2570
|
+
pollInterval.value = 0 // Stop polling
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2574
|
+
)
|
|
2494
2575
|
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
watch: jobId,
|
|
2499
|
-
poll: { interval: pollInterval },
|
|
2500
|
-
onSuccess(response) {
|
|
2501
|
-
const { status } = response.data
|
|
2502
|
-
if (status === 'complete' || status === 'failed') {
|
|
2503
|
-
pollInterval.value = 0 // Stop polling
|
|
2504
|
-
}
|
|
2505
|
-
}
|
|
2576
|
+
function startJob(id: string) {
|
|
2577
|
+
jobId.value = id
|
|
2578
|
+
pollInterval.value = 2000 // Start polling every 2s
|
|
2506
2579
|
}
|
|
2507
|
-
)
|
|
2508
|
-
|
|
2509
|
-
function startJob(id: string) {
|
|
2510
|
-
jobId.value = id
|
|
2511
|
-
pollInterval.value = 2000 // Start polling every 2s
|
|
2512
|
-
}
|
|
2513
2580
|
</script>
|
|
2514
2581
|
|
|
2515
2582
|
<template>
|
|
@@ -2530,7 +2597,7 @@ function startJob(id: string) {
|
|
|
2530
2597
|
| Problem | Likely Cause | Fix |
|
|
2531
2598
|
|---------|--------------|-----|
|
|
2532
2599
|
| `"createApi config not found"` | `createApi()` not called | Call `app.use(createApi(...))` in `main.ts` before mounting |
|
|
2533
|
-
| Request fires twice on mount | `immediate: true`
|
|
2600
|
+
| Request fires twice on mount | `immediate: true` fires on mount; auto-tracking also fires when a dep changes immediately | Ensure deps don't change synchronously right after mount, or use `lazy: true` with manual `execute()` |
|
|
2534
2601
|
| `retry` option does nothing | Default is `retry: false` | Set `retry: true` or `retry: 3` |
|
|
2535
2602
|
| ALL errors trigger retry, not just some | `retryStatusCodes` not set โ uses library default | Specify exact codes or use `retryStatusCodes: []` to retry on any error |
|
|
2536
2603
|
| `ignoreUpdates` still triggers a request | Updater function contains an `await` | `ignoreUpdates` is sync-only โ move async logic outside the updater |
|