@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 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,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 โ€” 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)
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
- Watch refs and automatically refetch when they change. Perfect for filters, search, and dynamic content.
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
- interface User {
366
- id: number
367
- name: string
368
- }
369
-
370
- const userId = ref(1)
394
+ const search = ref('')
395
+ const page = ref(1)
371
396
 
372
- const { data } = useApi<User>(
373
- () => `/users/${userId.value}`,
374
- { watch: userId, immediate: true }
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
- userId.value = 2 // โ†’ automatic refetch
403
+ // Change any dep โ†’ request re-fires automatically
404
+ search.value = 'shoes'
405
+ page.value = 2
378
406
  ```
379
407
 
380
- #### Multiple Dependencies
408
+ **Reactive URL:**
381
409
  ```typescript
382
- import { ref } from 'vue'
383
- import { useApi } from '@ametie/vue-muza-use'
410
+ const userId = ref(1)
384
411
 
385
- const searchQuery = ref('')
386
- const category = ref('all')
412
+ const { data } = useApi(() => `/users/${userId.value}`, {
413
+ immediate: true,
414
+ })
387
415
 
388
- const { data } = useApi(
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
- #### Auto-Save Form
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
- import { ref } from 'vue'
400
- import { useApi } from '@ametie/vue-muza-use'
424
+ const form = ref({ name: '', email: '' })
401
425
 
402
- const settings = ref({
403
- theme: 'dark',
404
- notifications: true
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
- useApi('/user/settings', {
408
- method: 'PUT',
409
- data: settings,
410
- watch: settings,
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
- 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]
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
- // 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
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
- #### โœ… With ignoreUpdates โ€” 1 request fires
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
- import { ref } from 'vue'
447
- import { useApi } from '@ametie/vue-muza-use'
458
+ // Only one request fires (Vue batches sync changes)
459
+ status.value = 'active'
460
+ page.value = 1
461
+ ```
448
462
 
449
- const page = ref(1)
450
- const search = ref('')
463
+ ---
451
464
 
452
- const { execute, ignoreUpdates } = useApi('/users', {
453
- params: { page, search },
454
- watch: [page, search]
455
- })
465
+ ### ignoreUpdates โ€” Update Without Re-fetching
456
466
 
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
- ```
467
+ **TL;DR: Update a reactive dep without triggering auto-tracking.**
464
468
 
465
- #### Reset filters without auto-fetching
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
- Use `ignoreUpdates` to reset all filters to their defaults, then manually trigger a single request.
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 { execute, ignoreUpdates } = useApi('/users', {
478
- params: { page, search, status },
479
- watch: [page, search, status]
479
+ const { data, ignoreUpdates } = useApi('/products', {
480
+ params: () => ({ q: search.value }),
481
+ debounce: 300,
480
482
  })
481
483
 
482
- function resetFilters() {
484
+ function clearSearch() {
483
485
  ignoreUpdates(() => {
484
- page.value = 1
485
486
  search.value = ''
486
- status.value = 'all'
487
487
  })
488
- execute() // single request with reset values
488
+ // auto-tracking is paused โ€” no request fires
489
489
  }
490
490
  ```
491
491
 
492
- #### Safe to call without a watch option
492
+ The user types โ†’ auto-tracking fires โ†’ debounced request. Clicking "Clear" resets the input without triggering a fetch.
493
493
 
494
- If no `watch` option is configured, `ignoreUpdates` still runs the updater โ€” it just has nothing to suppress.
494
+ #### Safe to call when `lazy: true`
495
495
 
496
- ```typescript
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
- const counter = ref(0)
498
+ ```typescript
501
499
  const { ignoreUpdates } = useApi('/data')
502
500
 
503
- // Safe โ€” no error thrown, updater still runs
504
501
  ignoreUpdates(() => {
505
- counter.value = 42
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. If you need to update async values, update them outside
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 + watch
622
+ #### cache + auto-tracking
627
623
 
628
- When `watch` is configured, each watch-triggered `execute()` still checks the cache first:
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
- watch: categoryId,
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 in `onSuccess`
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
- | `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 |
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
- | `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) |
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 `staleWhileRevalidate` is not set |
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 watch-based re-execution |
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
- Full component: debounced search that resets cleanly without triggering an intermediate request.
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, 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
- )
2222
+ const { data, loading, ignoreUpdates } = useApi<User[]>('/users', {
2223
+ params: () => ({ q: search.value }),
2224
+ debounce: 400,
2225
+ immediate: true,
2226
+ })
2223
2227
 
2224
- function resetSearch() {
2228
+ function clearSearch() {
2225
2229
  ignoreUpdates(() => {
2226
2230
  search.value = ''
2227
- page.value = 1
2228
2231
  })
2229
- execute() // single request with reset values
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="resetSearch">Clear</button>
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 using `ignoreUpdates` so only one request fires.
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, execute, ignoreUpdates } = useApi<Post[]>(
2269
- () => `/posts?status=${status.value}&page=${page.value}`,
2270
- { watch: [status, page], immediate: true }
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
- // Reset page to 1 when filter changes โ€” one request, not two
2275
- ignoreUpdates(() => {
2276
- status.value = newStatus
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
- id: string
2488
- status: 'pending' | 'processing' | 'complete' | 'failed'
2489
- progress: number
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
- 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
- }
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` AND `watch` on a ref both trigger on setup | Use only `immediate` OR `watch` for the first load โ€” not both |
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 |