@ametie/vue-muza-use 0.10.0 โ 0.11.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 +174 -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,7 +82,7 @@ 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)
|
|
57
88
|
- [Polling (Background Updates)](#polling-background-updates)
|
|
@@ -167,7 +198,6 @@ const searchQuery = ref('')
|
|
|
167
198
|
const { data, loading } = useApi<Product[]>(
|
|
168
199
|
() => `/products/search?q=${searchQuery.value}`,
|
|
169
200
|
{
|
|
170
|
-
watch: searchQuery,
|
|
171
201
|
debounce: 500
|
|
172
202
|
}
|
|
173
203
|
)
|
|
@@ -270,7 +300,6 @@ const filters = ref({
|
|
|
270
300
|
|
|
271
301
|
const { data } = useApi('/users', {
|
|
272
302
|
params: filters,
|
|
273
|
-
watch: filters,
|
|
274
303
|
immediate: true
|
|
275
304
|
})
|
|
276
305
|
```
|
|
@@ -292,7 +321,6 @@ const id = ref<number | null>(null)
|
|
|
292
321
|
|
|
293
322
|
const { data } = useApi<User>(
|
|
294
323
|
() => id.value ? `/users/${id.value}` : undefined,
|
|
295
|
-
{ watch: id }
|
|
296
324
|
)
|
|
297
325
|
|
|
298
326
|
// No request fires until id.value is set
|
|
@@ -355,162 +383,130 @@ const { execute, loading, error } = useApi<LoginResponse>(
|
|
|
355
383
|
|
|
356
384
|
### Watch & Auto-Refetch
|
|
357
385
|
|
|
358
|
-
|
|
386
|
+
**TL;DR: Pass reactive refs or getters to `url`, `params`, or `data` โ the request re-fires automatically when they change.**
|
|
387
|
+
|
|
388
|
+
Any reactive dependency accessed inside a getter is tracked automatically. No explicit `watch` option needed.
|
|
359
389
|
|
|
360
|
-
#### Single Dependency
|
|
361
390
|
```typescript
|
|
362
391
|
import { ref } from 'vue'
|
|
363
392
|
import { useApi } from '@ametie/vue-muza-use'
|
|
364
393
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
name: string
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
const userId = ref(1)
|
|
394
|
+
const search = ref('')
|
|
395
|
+
const page = ref(1)
|
|
371
396
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
{
|
|
375
|
-
|
|
397
|
+
// Reactive params getter โ both search and page are tracked automatically
|
|
398
|
+
const { data, loading } = useApi('/products', {
|
|
399
|
+
params: () => ({ q: search.value, page: page.value }),
|
|
400
|
+
immediate: true,
|
|
401
|
+
})
|
|
376
402
|
|
|
377
|
-
|
|
403
|
+
// Change any dep โ request re-fires automatically
|
|
404
|
+
search.value = 'shoes'
|
|
405
|
+
page.value = 2
|
|
378
406
|
```
|
|
379
407
|
|
|
380
|
-
|
|
408
|
+
**Reactive URL:**
|
|
381
409
|
```typescript
|
|
382
|
-
|
|
383
|
-
import { useApi } from '@ametie/vue-muza-use'
|
|
410
|
+
const userId = ref(1)
|
|
384
411
|
|
|
385
|
-
const
|
|
386
|
-
|
|
412
|
+
const { data } = useApi(() => `/users/${userId.value}`, {
|
|
413
|
+
immediate: true,
|
|
414
|
+
})
|
|
387
415
|
|
|
388
|
-
|
|
389
|
-
() => `/products?q=${searchQuery.value}&category=${category.value}`,
|
|
390
|
-
{
|
|
391
|
-
watch: [searchQuery, category],
|
|
392
|
-
debounce: 500
|
|
393
|
-
}
|
|
394
|
-
)
|
|
416
|
+
userId.value = 2 // โ re-fetches /users/2 automatically
|
|
395
417
|
```
|
|
396
418
|
|
|
397
|
-
|
|
419
|
+
**Opt-out with `lazy: true`:**
|
|
420
|
+
|
|
421
|
+
For forms and manual mutations where you control when `execute()` is called:
|
|
422
|
+
|
|
398
423
|
```typescript
|
|
399
|
-
|
|
400
|
-
import { useApi } from '@ametie/vue-muza-use'
|
|
424
|
+
const form = ref({ name: '', email: '' })
|
|
401
425
|
|
|
402
|
-
const
|
|
403
|
-
|
|
404
|
-
|
|
426
|
+
const { execute, loading } = useApi('/users', {
|
|
427
|
+
method: 'POST',
|
|
428
|
+
data: form,
|
|
429
|
+
lazy: true, // form changes do NOT trigger re-fetch
|
|
430
|
+
onSuccess: () => router.push('/users'),
|
|
405
431
|
})
|
|
406
432
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
debounce: 1000,
|
|
412
|
-
onSuccess: () => console.log('Saved!')
|
|
413
|
-
})
|
|
433
|
+
// Only fires when you call it
|
|
434
|
+
async function submit() {
|
|
435
|
+
await execute()
|
|
436
|
+
}
|
|
414
437
|
```
|
|
415
438
|
|
|
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
|
|
439
|
+
**`immediate` works independently of auto-tracking:**
|
|
425
440
|
|
|
426
441
|
```typescript
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
const search = ref('')
|
|
432
|
-
|
|
433
|
-
const { execute } = useApi('/users', {
|
|
434
|
-
params: { page, search },
|
|
435
|
-
watch: [page, search]
|
|
442
|
+
// Fetch on mount + re-fetch on dep change
|
|
443
|
+
useApi('/products', {
|
|
444
|
+
params: () => ({ q: search.value }),
|
|
445
|
+
immediate: true,
|
|
436
446
|
})
|
|
437
447
|
|
|
438
|
-
//
|
|
439
|
-
|
|
440
|
-
|
|
448
|
+
// No fetch on mount, but re-fetch on dep change
|
|
449
|
+
useApi('/products', {
|
|
450
|
+
params: () => ({ q: search.value }),
|
|
451
|
+
// immediate: false is the default
|
|
452
|
+
})
|
|
441
453
|
```
|
|
442
454
|
|
|
443
|
-
|
|
455
|
+
**Batching:** Vue batches synchronous reactive changes before triggering auto-tracking โ two synchronous ref changes fire only one request.
|
|
444
456
|
|
|
445
457
|
```typescript
|
|
446
|
-
|
|
447
|
-
|
|
458
|
+
// Only one request fires (Vue batches sync changes)
|
|
459
|
+
status.value = 'active'
|
|
460
|
+
page.value = 1
|
|
461
|
+
```
|
|
448
462
|
|
|
449
|
-
|
|
450
|
-
const search = ref('')
|
|
463
|
+
---
|
|
451
464
|
|
|
452
|
-
|
|
453
|
-
params: { page, search },
|
|
454
|
-
watch: [page, search]
|
|
455
|
-
})
|
|
465
|
+
### ignoreUpdates โ Update Without Re-fetching
|
|
456
466
|
|
|
457
|
-
|
|
458
|
-
ignoreUpdates(() => {
|
|
459
|
-
page.value = 1
|
|
460
|
-
search.value = 'john'
|
|
461
|
-
})
|
|
462
|
-
await execute() // โ single request: page=1, search=john
|
|
463
|
-
```
|
|
467
|
+
**TL;DR: Update a reactive dep without triggering auto-tracking.**
|
|
464
468
|
|
|
465
|
-
|
|
469
|
+
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
470
|
|
|
467
|
-
|
|
471
|
+
#### Example โ clear search input without fetching
|
|
468
472
|
|
|
469
473
|
```typescript
|
|
470
474
|
import { ref } from 'vue'
|
|
471
475
|
import { useApi } from '@ametie/vue-muza-use'
|
|
472
476
|
|
|
473
|
-
const page = ref(1)
|
|
474
477
|
const search = ref('')
|
|
475
|
-
const status = ref('all')
|
|
476
478
|
|
|
477
|
-
const {
|
|
478
|
-
params: {
|
|
479
|
-
|
|
479
|
+
const { data, ignoreUpdates } = useApi('/products', {
|
|
480
|
+
params: () => ({ q: search.value }),
|
|
481
|
+
debounce: 300,
|
|
480
482
|
})
|
|
481
483
|
|
|
482
|
-
function
|
|
484
|
+
function clearSearch() {
|
|
483
485
|
ignoreUpdates(() => {
|
|
484
|
-
page.value = 1
|
|
485
486
|
search.value = ''
|
|
486
|
-
status.value = 'all'
|
|
487
487
|
})
|
|
488
|
-
|
|
488
|
+
// auto-tracking is paused โ no request fires
|
|
489
489
|
}
|
|
490
490
|
```
|
|
491
491
|
|
|
492
|
-
|
|
492
|
+
The user types โ auto-tracking fires โ debounced request. Clicking "Clear" resets the input without triggering a fetch.
|
|
493
493
|
|
|
494
|
-
|
|
494
|
+
#### Safe to call when `lazy: true`
|
|
495
495
|
|
|
496
|
-
|
|
497
|
-
import { ref } from 'vue'
|
|
498
|
-
import { useApi } from '@ametie/vue-muza-use'
|
|
496
|
+
If `lazy: true`, `ignoreUpdates` still runs the updater โ it just has nothing to suppress.
|
|
499
497
|
|
|
500
|
-
|
|
498
|
+
```typescript
|
|
501
499
|
const { ignoreUpdates } = useApi('/data')
|
|
502
500
|
|
|
503
|
-
// Safe โ no error thrown, updater still runs
|
|
504
501
|
ignoreUpdates(() => {
|
|
505
|
-
|
|
502
|
+
someRef.value = 42 // runs normally, nothing to suppress
|
|
506
503
|
})
|
|
507
504
|
```
|
|
508
505
|
|
|
509
506
|
> [!NOTE]
|
|
510
507
|
> `ignoreUpdates` is synchronous only. Changes made after an `await` inside the
|
|
511
508
|
> updater function will NOT be suppressed โ the flag resets after the synchronous
|
|
512
|
-
> portion completes.
|
|
513
|
-
> `ignoreUpdates` and call `execute()` manually.
|
|
509
|
+
> portion completes.
|
|
514
510
|
|
|
515
511
|
---
|
|
516
512
|
|
|
@@ -623,9 +619,9 @@ invalidateCache(['products', 'categories'])
|
|
|
623
619
|
clearAllCache()
|
|
624
620
|
```
|
|
625
621
|
|
|
626
|
-
#### cache +
|
|
622
|
+
#### cache + auto-tracking
|
|
627
623
|
|
|
628
|
-
When
|
|
624
|
+
When auto-tracking is active, each dep-change-triggered `execute()` still checks the cache first:
|
|
629
625
|
|
|
630
626
|
```vue
|
|
631
627
|
<script setup lang="ts">
|
|
@@ -636,7 +632,7 @@ const categoryId = ref<number>(1)
|
|
|
636
632
|
|
|
637
633
|
const { data } = useApi<Product[]>(() => `/categories/${categoryId.value}/products`, {
|
|
638
634
|
cache: { id: `products-cat-${categoryId.value}`, staleTime: 30_000 },
|
|
639
|
-
|
|
635
|
+
params: () => ({ category: categoryId.value }),
|
|
640
636
|
immediate: true,
|
|
641
637
|
})
|
|
642
638
|
</script>
|
|
@@ -673,6 +669,7 @@ const { data } = useApi('/reports', {
|
|
|
673
669
|
|-------|------|---------|-------------|
|
|
674
670
|
| `id` | `string` | โ | Unique cache key |
|
|
675
671
|
| `staleTime` | `number` | `300_000` | TTL in milliseconds. Entry is deleted on next read after this time |
|
|
672
|
+
| `swr` | `boolean` | `false` | Stale-while-revalidate: serve cached data instantly while revalidating in the background. See [SWR](#stale-while-revalidate-swr) |
|
|
676
673
|
|
|
677
674
|
#### Out of Scope (by design)
|
|
678
675
|
|
|
@@ -709,8 +706,7 @@ import { useApi } from '@ametie/vue-muza-use'
|
|
|
709
706
|
interface User { id: number; name: string }
|
|
710
707
|
|
|
711
708
|
const { data, revalidating } = useApi<User[]>('/users', {
|
|
712
|
-
cache: 'users',
|
|
713
|
-
staleWhileRevalidate: true,
|
|
709
|
+
cache: { id: 'users', swr: true },
|
|
714
710
|
immediate: true,
|
|
715
711
|
})
|
|
716
712
|
</script>
|
|
@@ -747,8 +743,7 @@ If the background revalidation request fails:
|
|
|
747
743
|
|
|
748
744
|
```typescript
|
|
749
745
|
const { data, revalidating, error } = useApi('/dashboard', {
|
|
750
|
-
cache: 'dashboard',
|
|
751
|
-
staleWhileRevalidate: true,
|
|
746
|
+
cache: { id: 'dashboard', swr: true },
|
|
752
747
|
immediate: true,
|
|
753
748
|
})
|
|
754
749
|
// data.value stays the cached value even after a failed revalidation
|
|
@@ -999,7 +994,10 @@ const toggleTodo = (id: number) => {
|
|
|
999
994
|
}
|
|
1000
995
|
```
|
|
1001
996
|
|
|
1002
|
-
#### Transform
|
|
997
|
+
#### Transform after fetch
|
|
998
|
+
|
|
999
|
+
Use `mutate` from the **same** `useApi` call to post-process data after it arrives:
|
|
1000
|
+
|
|
1003
1001
|
```typescript
|
|
1004
1002
|
import { useApi } from '@ametie/vue-muza-use'
|
|
1005
1003
|
|
|
@@ -1010,7 +1008,7 @@ interface User {
|
|
|
1010
1008
|
fullName?: string
|
|
1011
1009
|
}
|
|
1012
1010
|
|
|
1013
|
-
const { data } = useApi<User[]>('/users', {
|
|
1011
|
+
const { data, mutate } = useApi<User[]>('/users', {
|
|
1014
1012
|
immediate: true,
|
|
1015
1013
|
onSuccess: ({ data: users }) => {
|
|
1016
1014
|
mutate(users.map(u => ({
|
|
@@ -1019,10 +1017,13 @@ const { data } = useApi<User[]>('/users', {
|
|
|
1019
1017
|
})))
|
|
1020
1018
|
}
|
|
1021
1019
|
})
|
|
1022
|
-
|
|
1023
|
-
const { mutate } = useApi<User[]>('/users', { immediate: true })
|
|
1024
1020
|
```
|
|
1025
1021
|
|
|
1022
|
+
> [!TIP]
|
|
1023
|
+
> If the same transformation runs on every fetch (including polling or watch re-triggers),
|
|
1024
|
+
> use [`select`](#select--declarative-data-transformation) instead โ it's applied automatically
|
|
1025
|
+
> and keeps your options object clean.
|
|
1026
|
+
|
|
1026
1027
|
---
|
|
1027
1028
|
|
|
1028
1029
|
### select โ Declarative Data Transformation
|
|
@@ -1124,7 +1125,6 @@ const params = computed(() => ({
|
|
|
1124
1125
|
|
|
1125
1126
|
const { data, loading } = useApi<OrdersResponse>('/orders', {
|
|
1126
1127
|
params,
|
|
1127
|
-
watch: params,
|
|
1128
1128
|
immediate: true
|
|
1129
1129
|
})
|
|
1130
1130
|
</script>
|
|
@@ -1421,6 +1421,8 @@ createApp(App).use(createApi({
|
|
|
1421
1421
|
| `retryDelay` | `number` | `1000` | How many milliseconds to wait between retry attempts for all requests |
|
|
1422
1422
|
| `retryStatusCodes` | `number[]` | `[408,429,500,502,503,504]` | Default HTTP status codes that trigger a retry across all requests |
|
|
1423
1423
|
| `useGlobalAbort` | `boolean` | `true` | When `true`, all requests subscribe to the global abort controller |
|
|
1424
|
+
| `refetchOnFocus` | `boolean \| { throttle?: number }` | `undefined` | Apply `refetchOnFocus` to all `useApi` instances. Per-request value takes precedence (including `false` to opt-out) |
|
|
1425
|
+
| `refetchOnReconnect` | `boolean` | `undefined` | Apply `refetchOnReconnect` to all `useApi` instances. Per-request value takes precedence (including `false` to opt-out) |
|
|
1424
1426
|
|
|
1425
1427
|
---
|
|
1426
1428
|
|
|
@@ -1872,16 +1874,22 @@ Three type parameters โ all optional with defaults:
|
|
|
1872
1874
|
| Option | Type | Default | Description |
|
|
1873
1875
|
|--------|------|---------|-------------|
|
|
1874
1876
|
| `immediate` | `boolean` | `false` | When `true`, executes the request automatically when the composable is created |
|
|
1875
|
-
| `
|
|
1876
|
-
| `debounce` | `number` | `0` | Milliseconds to
|
|
1877
|
+
| `lazy` | `boolean` | `false` | Disable auto-tracking โ reactive changes to `url`, `params`, and `data` will NOT trigger a re-fetch |
|
|
1878
|
+
| `debounce` | `number` | `0` | Milliseconds to debounce auto-tracked re-fetches |
|
|
1877
1879
|
|
|
1878
1880
|
**Caching:**
|
|
1879
1881
|
|
|
1880
1882
|
| Option | Type | Default | Description |
|
|
1881
1883
|
|--------|------|---------|-------------|
|
|
1882
|
-
| `cache` | `string \| CacheOptions` | `undefined` | Cache the response in memory. String shorthand uses default 5-min TTL. See [Response Caching](#response-caching) |
|
|
1884
|
+
| `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
1885
|
| `invalidateCache` | `string \| string[]` | `undefined` | Cache key(s) to delete on 2xx success. Never fires on error |
|
|
1884
|
-
|
|
1886
|
+
|
|
1887
|
+
**Refetch Triggers:**
|
|
1888
|
+
|
|
1889
|
+
| Option | Type | Default | Description |
|
|
1890
|
+
|--------|------|---------|-------------|
|
|
1891
|
+
| `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` |
|
|
1892
|
+
| `refetchOnReconnect` | `boolean` | `undefined` | Re-fetch when the browser regains network connectivity (`online` event). No throttle. Configurable globally via `globalOptions` |
|
|
1885
1893
|
|
|
1886
1894
|
**Polling:**
|
|
1887
1895
|
|
|
@@ -1948,12 +1956,12 @@ Three type parameters โ all optional with defaults:
|
|
|
1948
1956
|
| `error` | `Ref<ApiError \| null>` | Error from the last failed request; `null` on success |
|
|
1949
1957
|
| `statusCode` | `Ref<number \| null>` | HTTP status code from the last completed request |
|
|
1950
1958
|
| `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 `
|
|
1959
|
+
| `revalidating` | `Ref<boolean>` | `true` while a background SWR revalidation is in flight. Always `false` when `cache: { swr: true }` is not set |
|
|
1952
1960
|
| `execute(config?)` | `(config?: ApiRequestConfig<D>) => Promise<TSelected \| null>` | Manually trigger the request, optionally overriding options |
|
|
1953
1961
|
| `mutate(newData)` | `(newData: TSelected \| null \| ((prev: TSelected \| null) => TSelected \| null)) => void` | Update `data` locally without a network request; clears `error` |
|
|
1954
1962
|
| `abort(msg?)` | `(message?: string) => void` | Cancel the current in-flight request |
|
|
1955
1963
|
| `reset()` | `() => void` | Cancel the request and reset all state to initial values |
|
|
1956
|
-
| `ignoreUpdates(fn)` | `(updater: () => void) => void` | Run `updater` without triggering
|
|
1964
|
+
| `ignoreUpdates(fn)` | `(updater: () => void) => void` | Run `updater` without triggering auto-tracked re-execution |
|
|
1957
1965
|
|
|
1958
1966
|
#### `execute(config?)`
|
|
1959
1967
|
|
|
@@ -2196,7 +2204,7 @@ function logout() {
|
|
|
2196
2204
|
|
|
2197
2205
|
### 1. Search with Debounce and Reset
|
|
2198
2206
|
|
|
2199
|
-
|
|
2207
|
+
Debounced search input. Typing triggers a request; "Clear" resets the input silently without fetching.
|
|
2200
2208
|
|
|
2201
2209
|
```vue
|
|
2202
2210
|
<script setup lang="ts">
|
|
@@ -2210,30 +2218,25 @@ interface User {
|
|
|
2210
2218
|
}
|
|
2211
2219
|
|
|
2212
2220
|
const search = ref('')
|
|
2213
|
-
const page = ref(1)
|
|
2214
2221
|
|
|
2215
|
-
const { data, loading,
|
|
2216
|
-
() =>
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
immediate: true
|
|
2221
|
-
}
|
|
2222
|
-
)
|
|
2222
|
+
const { data, loading, ignoreUpdates } = useApi<User[]>('/users', {
|
|
2223
|
+
params: () => ({ q: search.value }),
|
|
2224
|
+
debounce: 400,
|
|
2225
|
+
immediate: true,
|
|
2226
|
+
})
|
|
2223
2227
|
|
|
2224
|
-
function
|
|
2228
|
+
function clearSearch() {
|
|
2225
2229
|
ignoreUpdates(() => {
|
|
2226
2230
|
search.value = ''
|
|
2227
|
-
page.value = 1
|
|
2228
2231
|
})
|
|
2229
|
-
|
|
2232
|
+
// auto-tracking is paused โ no request fires on clear
|
|
2230
2233
|
}
|
|
2231
2234
|
</script>
|
|
2232
2235
|
|
|
2233
2236
|
<template>
|
|
2234
2237
|
<div>
|
|
2235
2238
|
<input v-model="search" placeholder="Search users..." />
|
|
2236
|
-
<button @click="
|
|
2239
|
+
<button @click="clearSearch">Clear</button>
|
|
2237
2240
|
|
|
2238
2241
|
<div v-if="loading">Searching...</div>
|
|
2239
2242
|
<ul v-else>
|
|
@@ -2249,7 +2252,7 @@ function resetSearch() {
|
|
|
2249
2252
|
|
|
2250
2253
|
### 2. Paginated List with Filter Reset
|
|
2251
2254
|
|
|
2252
|
-
When the user changes a filter, reset the page to 1
|
|
2255
|
+
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
2256
|
|
|
2254
2257
|
```vue
|
|
2255
2258
|
<script setup lang="ts">
|
|
@@ -2265,18 +2268,15 @@ interface Post {
|
|
|
2265
2268
|
const page = ref(1)
|
|
2266
2269
|
const status = ref('all')
|
|
2267
2270
|
|
|
2268
|
-
const { data, loading
|
|
2269
|
-
() =>
|
|
2270
|
-
|
|
2271
|
-
)
|
|
2271
|
+
const { data, loading } = useApi<Post[]>('/posts', {
|
|
2272
|
+
params: () => ({ status: status.value, page: page.value }),
|
|
2273
|
+
immediate: true,
|
|
2274
|
+
})
|
|
2272
2275
|
|
|
2273
2276
|
function changeStatus(newStatus: string) {
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
page.value = 1
|
|
2278
|
-
})
|
|
2279
|
-
execute()
|
|
2277
|
+
status.value = newStatus
|
|
2278
|
+
page.value = 1
|
|
2279
|
+
// Vue batches these sync changes โ auto-tracking fires once, one request
|
|
2280
2280
|
}
|
|
2281
2281
|
</script>
|
|
2282
2282
|
|
|
@@ -2480,36 +2480,35 @@ Start polling every 2 seconds and stop automatically when the job reaches a term
|
|
|
2480
2480
|
|
|
2481
2481
|
```vue
|
|
2482
2482
|
<script setup lang="ts">
|
|
2483
|
-
import { ref } from 'vue'
|
|
2484
|
-
import { useApi } from '@ametie/vue-muza-use'
|
|
2483
|
+
import { ref } from 'vue'
|
|
2484
|
+
import { useApi } from '@ametie/vue-muza-use'
|
|
2485
2485
|
|
|
2486
|
-
interface JobStatus {
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
}
|
|
2486
|
+
interface JobStatus {
|
|
2487
|
+
id: string
|
|
2488
|
+
status: 'pending' | 'processing' | 'complete' | 'failed'
|
|
2489
|
+
progress: number
|
|
2490
|
+
}
|
|
2491
2491
|
|
|
2492
|
-
const jobId = ref<string | null>(null)
|
|
2493
|
-
const pollInterval = ref(0)
|
|
2492
|
+
const jobId = ref<string | null>(null)
|
|
2493
|
+
const pollInterval = ref(0)
|
|
2494
|
+
|
|
2495
|
+
const { data: job, error } = useApi<JobStatus>(
|
|
2496
|
+
() => jobId.value ? `/jobs/${jobId.value}` : undefined,
|
|
2497
|
+
{
|
|
2498
|
+
poll: { interval: pollInterval },
|
|
2499
|
+
onSuccess(response) {
|
|
2500
|
+
const { status } = response.data
|
|
2501
|
+
if (status === 'complete' || status === 'failed') {
|
|
2502
|
+
pollInterval.value = 0 // Stop polling
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
)
|
|
2494
2507
|
|
|
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
|
-
}
|
|
2508
|
+
function startJob(id: string) {
|
|
2509
|
+
jobId.value = id
|
|
2510
|
+
pollInterval.value = 2000 // Start polling every 2s
|
|
2506
2511
|
}
|
|
2507
|
-
)
|
|
2508
|
-
|
|
2509
|
-
function startJob(id: string) {
|
|
2510
|
-
jobId.value = id
|
|
2511
|
-
pollInterval.value = 2000 // Start polling every 2s
|
|
2512
|
-
}
|
|
2513
2512
|
</script>
|
|
2514
2513
|
|
|
2515
2514
|
<template>
|
|
@@ -2530,7 +2529,7 @@ function startJob(id: string) {
|
|
|
2530
2529
|
| Problem | Likely Cause | Fix |
|
|
2531
2530
|
|---------|--------------|-----|
|
|
2532
2531
|
| `"createApi config not found"` | `createApi()` not called | Call `app.use(createApi(...))` in `main.ts` before mounting |
|
|
2533
|
-
| Request fires twice on mount | `immediate: true`
|
|
2532
|
+
| 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
2533
|
| `retry` option does nothing | Default is `retry: false` | Set `retry: true` or `retry: 3` |
|
|
2535
2534
|
| 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
2535
|
| `ignoreUpdates` still triggers a request | Updater function contains an `await` | `ignoreUpdates` is sync-only โ move async logic outside the updater |
|