@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 CHANGED
@@ -5,7 +5,7 @@
5
5
  [![Vue 3](https://img.shields.io/badge/Vue-3.x-green.svg?style=flat-square)](https://vuejs.org/)
6
6
  [![TypeScript](https://img.shields.io/badge/TypeScript-Included-blue.svg?style=flat-square)](https://www.typescriptlang.org/)
7
7
 
8
- **Type-safe, feature-rich Axios wrapper for Vue 3 Composition API. Built for real-world business logic.**
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-useApi/blob/main/.claude/skills/use-api/SKILL.md)**
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
- - ๐ŸŽฏ **Fully Type-Safe** โ€” End-to-end TypeScript support with strict typing for requests and responses
26
- - ๐Ÿ”„ **Smart Reactivity** โ€” Watch refs and automatically refetch when dependencies change
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** โ€” Atomic updates without triggering intermediate requests
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 โ€” Atomic Updates Without Refetch](#ignoreupdates--atomic-updates-without-refetch)
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
- Watch refs and automatically refetch when they change. Perfect for filters, search, and dynamic content.
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
- interface User {
366
- id: number
367
- name: string
368
- }
369
-
370
- const userId = ref(1)
395
+ const search = ref('')
396
+ const page = ref(1)
371
397
 
372
- const { data } = useApi<User>(
373
- () => `/users/${userId.value}`,
374
- { watch: userId, immediate: true }
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
- userId.value = 2 // โ†’ automatic refetch
404
+ // Change any dep โ†’ request re-fires automatically
405
+ search.value = 'shoes'
406
+ page.value = 2
378
407
  ```
379
408
 
380
- #### Multiple Dependencies
409
+ **Reactive URL:**
381
410
  ```typescript
382
- import { ref } from 'vue'
383
- import { useApi } from '@ametie/vue-muza-use'
411
+ const userId = ref(1)
384
412
 
385
- const searchQuery = ref('')
386
- const category = ref('all')
413
+ const { data } = useApi(() => `/users/${userId.value}`, {
414
+ immediate: true,
415
+ })
387
416
 
388
- const { data } = useApi(
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
- #### Auto-Save Form
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
- import { ref } from 'vue'
400
- import { useApi } from '@ametie/vue-muza-use'
425
+ const form = ref({ name: '', email: '' })
401
426
 
402
- const settings = ref({
403
- theme: 'dark',
404
- notifications: true
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
- useApi('/user/settings', {
408
- method: 'PUT',
409
- data: settings,
410
- watch: settings,
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
- import { ref } from 'vue'
428
- import { useApi } from '@ametie/vue-muza-use'
429
-
430
- const page = ref(1)
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
- // BAD: each assignment triggers a separate request
439
- page.value = 1 // โ†’ request 1: page=1, search=''
440
- search.value = 'john' // โ†’ request 2: page=1, search=john
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
- #### โœ… With ignoreUpdates โ€” 1 request fires
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
- import { ref } from 'vue'
447
- import { useApi } from '@ametie/vue-muza-use'
459
+ // Only one request fires (Vue batches sync changes)
460
+ status.value = 'active'
461
+ page.value = 1
462
+ ```
448
463
 
449
- const page = ref(1)
450
- const search = ref('')
464
+ ---
451
465
 
452
- const { execute, ignoreUpdates } = useApi('/users', {
453
- params: { page, search },
454
- watch: [page, search]
455
- })
466
+ ### ignoreUpdates โ€” Update Without Re-fetching
456
467
 
457
- // GOOD: all changes are batched, only one request fires
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
- #### Reset filters without auto-fetching
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
- Use `ignoreUpdates` to reset all filters to their defaults, then manually trigger a single request.
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 { execute, ignoreUpdates } = useApi('/users', {
478
- params: { page, search, status },
479
- watch: [page, search, status]
480
+ const { data, ignoreUpdates } = useApi('/products', {
481
+ params: () => ({ q: search.value }),
482
+ debounce: 300,
480
483
  })
481
484
 
482
- function resetFilters() {
485
+ function clearSearch() {
483
486
  ignoreUpdates(() => {
484
- page.value = 1
485
487
  search.value = ''
486
- status.value = 'all'
487
488
  })
488
- execute() // single request with reset values
489
+ // auto-tracking is paused โ€” no request fires
489
490
  }
490
491
  ```
491
492
 
492
- #### Safe to call without a watch option
493
+ The user types โ†’ auto-tracking fires โ†’ debounced request. Clicking "Clear" resets the input without triggering a fetch.
493
494
 
494
- If no `watch` option is configured, `ignoreUpdates` still runs the updater โ€” it just has nothing to suppress.
495
+ #### Safe to call when `lazy: true`
495
496
 
496
- ```typescript
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
- const counter = ref(0)
499
+ ```typescript
501
500
  const { ignoreUpdates } = useApi('/data')
502
501
 
503
- // Safe โ€” no error thrown, updater still runs
504
502
  ignoreUpdates(() => {
505
- counter.value = 42
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. If you need to update async values, update them outside
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 + watch
623
+ #### cache + auto-tracking
627
624
 
628
- When `watch` is configured, each watch-triggered `execute()` still checks the cache first:
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
- watch: categoryId,
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 in `onSuccess`
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
- | `watch` | `WatchSource \| WatchSource[]` | `undefined` | One or more refs to watch โ€” request re-fires when any of them change |
1876
- | `debounce` | `number` | `0` | Milliseconds to wait after the last watch change before firing the request |
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
- | `staleWhileRevalidate` | `boolean` | `false` | When `true` and a cache hit occurs, return stale data immediately and revalidate in the background. `revalidating` is `true` during the background fetch. See [SWR](#stale-while-revalidate-swr) |
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 `staleWhileRevalidate` is not set |
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 watch-based re-execution |
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
- Full component: debounced search that resets cleanly without triggering an intermediate request.
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, execute, ignoreUpdates } = useApi<User[]>(
2216
- () => `/users?search=${search.value}&page=${page.value}`,
2217
- {
2218
- watch: [search, page],
2219
- debounce: 400,
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 resetSearch() {
2296
+ function clearSearch() {
2225
2297
  ignoreUpdates(() => {
2226
2298
  search.value = ''
2227
- page.value = 1
2228
2299
  })
2229
- execute() // single request with reset values
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="resetSearch">Clear</button>
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 using `ignoreUpdates` so only one request fires.
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, execute, ignoreUpdates } = useApi<Post[]>(
2269
- () => `/posts?status=${status.value}&page=${page.value}`,
2270
- { watch: [status, page], immediate: true }
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
- // Reset page to 1 when filter changes โ€” one request, not two
2275
- ignoreUpdates(() => {
2276
- status.value = newStatus
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
- id: string
2488
- status: 'pending' | 'processing' | 'complete' | 'failed'
2489
- progress: number
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
- const { data: job, error } = useApi<JobStatus>(
2496
- () => jobId.value ? `/jobs/${jobId.value}` : undefined,
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` AND `watch` on a ref both trigger on setup | Use only `immediate` OR `watch` for the first load โ€” not both |
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 |