@ametie/vue-muza-use 0.8.0 โ 0.9.1
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 +226 -0
- package/dist/index.cjs +49 -0
- package/dist/index.d.cts +34 -1
- package/dist/index.d.ts +34 -1
- package/dist/index.mjs +47 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,6 +22,7 @@ A production-ready composable that eliminates boilerplate and solves the hard pr
|
|
|
22
22
|
- ๐ **Batch Requests** โ Execute multiple requests in parallel with progress tracking
|
|
23
23
|
- ๐งน **Zero Memory Leaks** โ Automatic cleanup of pending requests on component unmount
|
|
24
24
|
- ๐ **ignoreUpdates** โ Atomic updates without triggering intermediate requests
|
|
25
|
+
- ๐๏ธ **Response Caching** โ In-memory cache with configurable TTL and manual invalidation
|
|
25
26
|
|
|
26
27
|
**Advanced Features** (When you need them):
|
|
27
28
|
- โป๏ธ **Intelligent Retries** โ Lifecycle-aware retry logic with configurable status codes
|
|
@@ -40,6 +41,7 @@ A production-ready composable that eliminates boilerplate and solves the hard pr
|
|
|
40
41
|
**Core Features:**
|
|
41
42
|
- [Watch & Auto-Refetch](#watch--auto-refetch)
|
|
42
43
|
- [ignoreUpdates โ Atomic Updates Without Refetch](#ignoreupdates--atomic-updates-without-refetch)
|
|
44
|
+
- [Response Caching](#response-caching)
|
|
43
45
|
- [Polling (Background Updates)](#polling-background-updates)
|
|
44
46
|
- [Error Handling](#error-handling)
|
|
45
47
|
- [retry โ Automatic Request Retry](#retry--automatic-request-retry)
|
|
@@ -499,6 +501,184 @@ ignoreUpdates(() => {
|
|
|
499
501
|
|
|
500
502
|
---
|
|
501
503
|
|
|
504
|
+
### Response Caching
|
|
505
|
+
|
|
506
|
+
**TL;DR: Pass `cache: 'key'` to serve repeated requests from memory instead of the network. Entries expire after 5 minutes by default.**
|
|
507
|
+
|
|
508
|
+
The cache is an in-memory `Map` shared across all `useApi` instances in the app.
|
|
509
|
+
It is intentionally simple: no reactive subscriptions, no persistence, no background timers.
|
|
510
|
+
Entries expire **lazily** โ stale entries are removed the next time they are read.
|
|
511
|
+
|
|
512
|
+
#### Basic Usage โ String Shorthand
|
|
513
|
+
|
|
514
|
+
```vue
|
|
515
|
+
<script setup lang="ts">
|
|
516
|
+
import { useApi } from '@ametie/vue-muza-use'
|
|
517
|
+
|
|
518
|
+
const { data, loading } = useApi<Category[]>('/categories', {
|
|
519
|
+
cache: 'categories', // uses DEFAULT_STALE_TIME (5 minutes)
|
|
520
|
+
immediate: true,
|
|
521
|
+
})
|
|
522
|
+
</script>
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
The first call hits the network and caches the result under the key `'categories'`.
|
|
526
|
+
Every subsequent `execute()` within 5 minutes is served from cache instantly โ `loading` never becomes `true` and no axios request is made.
|
|
527
|
+
|
|
528
|
+
#### Custom TTL โ CacheOptions Object
|
|
529
|
+
|
|
530
|
+
```vue
|
|
531
|
+
<script setup lang="ts">
|
|
532
|
+
import { useApi } from '@ametie/vue-muza-use'
|
|
533
|
+
|
|
534
|
+
const { data, execute } = useApi<Product[]>('/products', {
|
|
535
|
+
cache: {
|
|
536
|
+
id: 'products',
|
|
537
|
+
staleTime: 60_000, // 1 minute
|
|
538
|
+
},
|
|
539
|
+
immediate: true,
|
|
540
|
+
})
|
|
541
|
+
</script>
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
#### Cache Hit Behavior
|
|
545
|
+
|
|
546
|
+
When a valid cache entry is found:
|
|
547
|
+
|
|
548
|
+
| Property / Hook | Cache Hit |
|
|
549
|
+
|---|---|
|
|
550
|
+
| `loading` | stays `false` โ never set to `true` |
|
|
551
|
+
| `data` | updated immediately via `mutate()` |
|
|
552
|
+
| `onBefore` | **not called** |
|
|
553
|
+
| `onSuccess` | **not called** |
|
|
554
|
+
| `onFinish` | **not called** |
|
|
555
|
+
| axios request | **not made** |
|
|
556
|
+
|
|
557
|
+
This is intentional โ a cache hit is silent. If you need to know when data comes from cache vs the network, track it with `onSuccess` (only fires on network hits).
|
|
558
|
+
|
|
559
|
+
#### invalidateCache โ Bust Related Caches on Mutation
|
|
560
|
+
|
|
561
|
+
Use `invalidateCache` on a POST/PUT/DELETE to automatically clear caches when the mutation succeeds.
|
|
562
|
+
|
|
563
|
+
```vue
|
|
564
|
+
<script setup lang="ts">
|
|
565
|
+
import { useApi } from '@ametie/vue-muza-use'
|
|
566
|
+
|
|
567
|
+
// GET โ caches the list
|
|
568
|
+
const { data: products, execute: reload } = useApi<Product[]>('/products', {
|
|
569
|
+
cache: 'products',
|
|
570
|
+
immediate: true,
|
|
571
|
+
})
|
|
572
|
+
|
|
573
|
+
// POST โ busts the list cache on success so the next GET hits the network
|
|
574
|
+
const { execute: createProduct, loading } = useApi('/products', {
|
|
575
|
+
method: 'POST',
|
|
576
|
+
invalidateCache: 'products',
|
|
577
|
+
})
|
|
578
|
+
|
|
579
|
+
async function submit(form: NewProduct) {
|
|
580
|
+
await createProduct({ data: form })
|
|
581
|
+
await reload() // cache is gone โ fetches fresh data
|
|
582
|
+
}
|
|
583
|
+
</script>
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
`invalidateCache` fires **only on HTTP 2xx success**. It never runs in `catch` or `finally`.
|
|
587
|
+
Pass an array to bust multiple keys at once:
|
|
588
|
+
|
|
589
|
+
```typescript
|
|
590
|
+
const { execute } = useApi('/orders', {
|
|
591
|
+
method: 'POST',
|
|
592
|
+
invalidateCache: ['orders', 'products', 'inventory'],
|
|
593
|
+
})
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
#### Imperative Cache Control
|
|
597
|
+
|
|
598
|
+
Import `invalidateCache` or `clearAllCache` anywhere in your app โ outside components, in Pinia stores, in route guards:
|
|
599
|
+
|
|
600
|
+
```typescript
|
|
601
|
+
import { invalidateCache, clearAllCache } from '@ametie/vue-muza-use'
|
|
602
|
+
|
|
603
|
+
// Bust a single key (e.g. after a WebSocket push)
|
|
604
|
+
invalidateCache('products')
|
|
605
|
+
|
|
606
|
+
// Bust multiple keys at once
|
|
607
|
+
invalidateCache(['products', 'categories'])
|
|
608
|
+
|
|
609
|
+
// Wipe everything โ call on logout to prevent data leaks between users
|
|
610
|
+
clearAllCache()
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
#### cache + watch
|
|
614
|
+
|
|
615
|
+
When `watch` is configured, each watch-triggered `execute()` still checks the cache first:
|
|
616
|
+
|
|
617
|
+
```vue
|
|
618
|
+
<script setup lang="ts">
|
|
619
|
+
import { useApi } from '@ametie/vue-muza-use'
|
|
620
|
+
import { ref } from 'vue'
|
|
621
|
+
|
|
622
|
+
const categoryId = ref<number>(1)
|
|
623
|
+
|
|
624
|
+
const { data } = useApi<Product[]>(() => `/categories/${categoryId.value}/products`, {
|
|
625
|
+
cache: { id: `products-cat-${categoryId.value}`, staleTime: 30_000 },
|
|
626
|
+
watch: categoryId,
|
|
627
|
+
immediate: true,
|
|
628
|
+
})
|
|
629
|
+
</script>
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
> [!NOTE]
|
|
633
|
+
> The cache `id` is evaluated once when `useApi` is called. To cache per category,
|
|
634
|
+
> use a computed or a dynamic key string derived from your reactive state.
|
|
635
|
+
|
|
636
|
+
#### cache + retry
|
|
637
|
+
|
|
638
|
+
Cache is written **after the final successful attempt**, not after the first.
|
|
639
|
+
If the first attempt fails and a retry succeeds, the retry's response is cached:
|
|
640
|
+
|
|
641
|
+
```typescript
|
|
642
|
+
const { data } = useApi('/reports', {
|
|
643
|
+
cache: 'reports',
|
|
644
|
+
retry: 2,
|
|
645
|
+
retryStatusCodes: [500, 503],
|
|
646
|
+
immediate: true,
|
|
647
|
+
})
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
#### Complete Options Reference
|
|
651
|
+
|
|
652
|
+
| Option | Type | Default | Description |
|
|
653
|
+
|--------|------|---------|-------------|
|
|
654
|
+
| `cache` | `string \| CacheOptions` | `undefined` | Enable caching. String = `{ id, staleTime: 300_000 }` shorthand |
|
|
655
|
+
| `invalidateCache` | `string \| string[]` | `undefined` | Cache key(s) to delete on 2xx success |
|
|
656
|
+
|
|
657
|
+
**`CacheOptions`**
|
|
658
|
+
|
|
659
|
+
| Field | Type | Default | Description |
|
|
660
|
+
|-------|------|---------|-------------|
|
|
661
|
+
| `id` | `string` | โ | Unique cache key |
|
|
662
|
+
| `staleTime` | `number` | `300_000` | TTL in milliseconds. Entry is deleted on next read after this time |
|
|
663
|
+
|
|
664
|
+
#### Out of Scope (by design)
|
|
665
|
+
|
|
666
|
+
The following are intentionally **not** supported in v1:
|
|
667
|
+
|
|
668
|
+
- ๐ซ No reactive cache entries โ the cache is a plain `Map`, not Vue refs
|
|
669
|
+
- ๐ซ No `localStorage` / `sessionStorage` persistence
|
|
670
|
+
- ๐ซ No background TTL timers โ expiry is checked lazily on read
|
|
671
|
+
- ๐ซ No cache for `useApiBatch` โ batch requests manage their own state
|
|
672
|
+
- ๐ซ No automatic refetch on cache invalidation โ call `execute()` manually after invalidating
|
|
673
|
+
- ๐ซ No request deduplication โ concurrent calls for the same key each fire their own request
|
|
674
|
+
|
|
675
|
+
> [!WARNING]
|
|
676
|
+
> The cache store is **module-level** (a singleton). In SSR / Node.js environments it is
|
|
677
|
+
> shared between all incoming requests. Call `clearAllCache()` between requests or avoid
|
|
678
|
+
> using caching in SSR contexts.
|
|
679
|
+
|
|
680
|
+
---
|
|
681
|
+
|
|
502
682
|
### Polling (Background Updates)
|
|
503
683
|
|
|
504
684
|
**TL;DR: Keep data fresh with smart polling that automatically pauses when the browser tab is hidden.**
|
|
@@ -1511,6 +1691,13 @@ function useMyCustomComposable<T>(fetchFn: () => Promise<T>) {
|
|
|
1511
1691
|
| `watch` | `WatchSource \| WatchSource[]` | `undefined` | One or more refs to watch โ request re-fires when any of them change |
|
|
1512
1692
|
| `debounce` | `number` | `0` | Milliseconds to wait after the last watch change before firing the request |
|
|
1513
1693
|
|
|
1694
|
+
**Caching:**
|
|
1695
|
+
|
|
1696
|
+
| Option | Type | Default | Description |
|
|
1697
|
+
|--------|------|---------|-------------|
|
|
1698
|
+
| `cache` | `string \| CacheOptions` | `undefined` | Cache the response in memory. String shorthand uses default 5-min TTL. See [Response Caching](#response-caching) |
|
|
1699
|
+
| `invalidateCache` | `string \| string[]` | `undefined` | Cache key(s) to delete on 2xx success. Never fires on error |
|
|
1700
|
+
|
|
1514
1701
|
**Polling:**
|
|
1515
1702
|
|
|
1516
1703
|
| Option | Type | Default | Description |
|
|
@@ -1768,6 +1955,45 @@ const { data, loading, error, mutate, setLoading, setError, reset } =
|
|
|
1768
1955
|
|
|
1769
1956
|
---
|
|
1770
1957
|
|
|
1958
|
+
### `invalidateCache(id)` / `clearAllCache()`
|
|
1959
|
+
|
|
1960
|
+
**TL;DR: Imperatively delete one, many, or all cache entries from anywhere in your app.**
|
|
1961
|
+
|
|
1962
|
+
```typescript
|
|
1963
|
+
import { invalidateCache, clearAllCache } from '@ametie/vue-muza-use'
|
|
1964
|
+
```
|
|
1965
|
+
|
|
1966
|
+
| Function | Signature | Description |
|
|
1967
|
+
|----------|-----------|-------------|
|
|
1968
|
+
| `invalidateCache` | `(id: string \| string[]) => void` | Delete one or more cache entries by key |
|
|
1969
|
+
| `clearAllCache` | `() => void` | Wipe the entire cache โ use on logout |
|
|
1970
|
+
|
|
1971
|
+
**Example โ bust cache after a WebSocket push:**
|
|
1972
|
+
|
|
1973
|
+
```typescript
|
|
1974
|
+
// pinia store or composable outside a component
|
|
1975
|
+
import { invalidateCache } from '@ametie/vue-muza-use'
|
|
1976
|
+
|
|
1977
|
+
socket.on('products:updated', () => {
|
|
1978
|
+
invalidateCache('products')
|
|
1979
|
+
})
|
|
1980
|
+
```
|
|
1981
|
+
|
|
1982
|
+
**Example โ clear all on logout:**
|
|
1983
|
+
|
|
1984
|
+
```typescript
|
|
1985
|
+
import { clearAllCache } from '@ametie/vue-muza-use'
|
|
1986
|
+
import { tokenManager } from '@ametie/vue-muza-use'
|
|
1987
|
+
|
|
1988
|
+
function logout() {
|
|
1989
|
+
tokenManager.clearTokens()
|
|
1990
|
+
clearAllCache() // prevent stale data from leaking to the next user session
|
|
1991
|
+
router.push('/login')
|
|
1992
|
+
}
|
|
1993
|
+
```
|
|
1994
|
+
|
|
1995
|
+
---
|
|
1996
|
+
|
|
1771
1997
|
## ๐งฉ Common Patterns
|
|
1772
1998
|
|
|
1773
1999
|
### 1. Search with Debounce and Reset
|
package/dist/index.cjs
CHANGED
|
@@ -31,8 +31,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
AuthEventType: () => AuthEventType,
|
|
34
|
+
clearAllCache: () => clearAllCache,
|
|
34
35
|
createApi: () => createApi,
|
|
35
36
|
createApiClient: () => createApiClient,
|
|
37
|
+
invalidateCache: () => invalidateCache,
|
|
36
38
|
setAuthMonitor: () => setAuthMonitor,
|
|
37
39
|
setupInterceptors: () => setupInterceptors,
|
|
38
40
|
tokenManager: () => tokenManager,
|
|
@@ -212,8 +214,39 @@ function useAbortController() {
|
|
|
212
214
|
};
|
|
213
215
|
}
|
|
214
216
|
|
|
217
|
+
// src/features/cacheManager.ts
|
|
218
|
+
var DEFAULT_STALE_TIME = 3e5;
|
|
219
|
+
var cacheStore = /* @__PURE__ */ new Map();
|
|
220
|
+
function readCache(id) {
|
|
221
|
+
const entry = cacheStore.get(id);
|
|
222
|
+
if (!entry) return null;
|
|
223
|
+
const isValid = Date.now() - entry.cachedAt < entry.staleTime;
|
|
224
|
+
if (!isValid) {
|
|
225
|
+
cacheStore.delete(id);
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
return entry.data;
|
|
229
|
+
}
|
|
230
|
+
function writeCache(id, data, staleTime) {
|
|
231
|
+
cacheStore.set(id, { data, cachedAt: Date.now(), staleTime });
|
|
232
|
+
}
|
|
233
|
+
function invalidateCache(id) {
|
|
234
|
+
const ids = Array.isArray(id) ? id : [id];
|
|
235
|
+
ids.forEach((key) => cacheStore.delete(key));
|
|
236
|
+
}
|
|
237
|
+
function clearAllCache() {
|
|
238
|
+
cacheStore.clear();
|
|
239
|
+
}
|
|
240
|
+
|
|
215
241
|
// src/useApi.ts
|
|
216
242
|
var DEFAULT_RETRY_STATUS_CODES = [408, 429, 500, 502, 503, 504];
|
|
243
|
+
function normalizeCacheOptions(cache) {
|
|
244
|
+
if (!cache) return null;
|
|
245
|
+
if (typeof cache === "string") {
|
|
246
|
+
return { id: cache, staleTime: DEFAULT_STALE_TIME };
|
|
247
|
+
}
|
|
248
|
+
return { id: cache.id, staleTime: cache.staleTime ?? DEFAULT_STALE_TIME };
|
|
249
|
+
}
|
|
217
250
|
function cancellableSleep(ms, signal) {
|
|
218
251
|
return new Promise((resolve) => {
|
|
219
252
|
if (signal.aborted) {
|
|
@@ -272,6 +305,14 @@ function useApi(url, options = {}) {
|
|
|
272
305
|
return { interval: 0, whenHidden: false };
|
|
273
306
|
};
|
|
274
307
|
const executeRequest = async (config) => {
|
|
308
|
+
const cacheOpts = normalizeCacheOptions(options.cache);
|
|
309
|
+
if (cacheOpts) {
|
|
310
|
+
const cached = readCache(cacheOpts.id);
|
|
311
|
+
if (cached !== null) {
|
|
312
|
+
state.mutate(cached);
|
|
313
|
+
return cached;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
275
316
|
if (pollTimer) clearTimeout(pollTimer);
|
|
276
317
|
const requestUrl = (0, import_vue4.toValue)(url);
|
|
277
318
|
if (abortController2.value) abortController2.value.abort("Cancelled by new request");
|
|
@@ -317,6 +358,12 @@ function useApi(url, options = {}) {
|
|
|
317
358
|
});
|
|
318
359
|
state.mutate(response.data, response);
|
|
319
360
|
state.setStatusCode(response.status);
|
|
361
|
+
if (cacheOpts) {
|
|
362
|
+
writeCache(cacheOpts.id, response.data, cacheOpts.staleTime);
|
|
363
|
+
}
|
|
364
|
+
if (options.invalidateCache) {
|
|
365
|
+
invalidateCache(options.invalidateCache);
|
|
366
|
+
}
|
|
320
367
|
onSuccess?.(response);
|
|
321
368
|
return response.data;
|
|
322
369
|
} catch (err) {
|
|
@@ -1015,8 +1062,10 @@ function createApiClient(options = {}) {
|
|
|
1015
1062
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1016
1063
|
0 && (module.exports = {
|
|
1017
1064
|
AuthEventType,
|
|
1065
|
+
clearAllCache,
|
|
1018
1066
|
createApi,
|
|
1019
1067
|
createApiClient,
|
|
1068
|
+
invalidateCache,
|
|
1020
1069
|
setAuthMonitor,
|
|
1021
1070
|
setupInterceptors,
|
|
1022
1071
|
tokenManager,
|
package/dist/index.d.cts
CHANGED
|
@@ -10,6 +10,14 @@ interface ApiError {
|
|
|
10
10
|
details?: unknown;
|
|
11
11
|
}
|
|
12
12
|
type AuthMode = "default" | "public" | "optional";
|
|
13
|
+
interface CacheOptions {
|
|
14
|
+
id: string;
|
|
15
|
+
/**
|
|
16
|
+
* How long the cached entry is valid in milliseconds.
|
|
17
|
+
* Default: 300_000 (5 minutes)
|
|
18
|
+
*/
|
|
19
|
+
staleTime?: number;
|
|
20
|
+
}
|
|
13
21
|
interface ApiState<T = unknown> {
|
|
14
22
|
data: T | null;
|
|
15
23
|
loading: boolean;
|
|
@@ -47,6 +55,22 @@ interface UseApiOptions<T = unknown, D = unknown> extends ApiRequestConfig<D> {
|
|
|
47
55
|
* - Pass an **object** `{ interval: number, whenHidden?: boolean }` for advanced control.
|
|
48
56
|
* Properties inside the object can also be Refs.
|
|
49
57
|
*/
|
|
58
|
+
/**
|
|
59
|
+
* Cache the response data by a string id.
|
|
60
|
+
* - String shorthand: `cache: 'key'` uses DEFAULT_STALE_TIME (5 min)
|
|
61
|
+
* - Object form: `cache: { id: 'key', staleTime: 10_000 }` for custom TTL
|
|
62
|
+
*
|
|
63
|
+
* On cache hit: mutate() is called with cached data, loading stays false,
|
|
64
|
+
* onBefore/onSuccess/onFinish are NOT called, axios request is NOT made.
|
|
65
|
+
* Cache is written only on HTTP 2xx success.
|
|
66
|
+
*/
|
|
67
|
+
cache?: string | CacheOptions;
|
|
68
|
+
/**
|
|
69
|
+
* Invalidate one or more cache entries on HTTP 2xx success.
|
|
70
|
+
* Fires only after a confirmed successful response โ never in catch/finally.
|
|
71
|
+
* Useful for POST/PUT/DELETE that should bust related GET caches.
|
|
72
|
+
*/
|
|
73
|
+
invalidateCache?: string | string[];
|
|
50
74
|
poll?: MaybeRefOrGetter<number | {
|
|
51
75
|
interval: MaybeRefOrGetter<number>;
|
|
52
76
|
whenHidden?: MaybeRefOrGetter<boolean>;
|
|
@@ -595,4 +619,13 @@ interface AuthEventPayload {
|
|
|
595
619
|
type AuthMonitorFn = (type: AuthEventType, payload: AuthEventPayload) => void;
|
|
596
620
|
declare function setAuthMonitor(fn: AuthMonitorFn): void;
|
|
597
621
|
|
|
598
|
-
|
|
622
|
+
/**
|
|
623
|
+
* Invalidate one or multiple cache entries by id.
|
|
624
|
+
*/
|
|
625
|
+
declare function invalidateCache(id: string | string[]): void;
|
|
626
|
+
/**
|
|
627
|
+
* Clear all cache entries. Call on logout to prevent data leaks between users.
|
|
628
|
+
*/
|
|
629
|
+
declare function clearAllCache(): void;
|
|
630
|
+
|
|
631
|
+
export { type ApiError, type ApiPluginOptions, type ApiRequestConfig, type ApiState, type AuthEventPayload, AuthEventType, type AuthMode, type AuthMonitorFn, type AuthTokens$1 as AuthTokens, type BatchProgress, type BatchRequestConfig, type BatchResultItem, type CacheOptions, type SetDataInput, type UseApiBatchOptions, type UseApiBatchReturn, type UseApiOptions, type UseApiReturn, clearAllCache, createApi, createApiClient, invalidateCache, setAuthMonitor, setupInterceptors, tokenManager, useAbortController, useApi, useApiBatch, useApiConfig, useApiDelete, useApiGet, useApiPatch, useApiPost, useApiPut, useApiState };
|
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,14 @@ interface ApiError {
|
|
|
10
10
|
details?: unknown;
|
|
11
11
|
}
|
|
12
12
|
type AuthMode = "default" | "public" | "optional";
|
|
13
|
+
interface CacheOptions {
|
|
14
|
+
id: string;
|
|
15
|
+
/**
|
|
16
|
+
* How long the cached entry is valid in milliseconds.
|
|
17
|
+
* Default: 300_000 (5 minutes)
|
|
18
|
+
*/
|
|
19
|
+
staleTime?: number;
|
|
20
|
+
}
|
|
13
21
|
interface ApiState<T = unknown> {
|
|
14
22
|
data: T | null;
|
|
15
23
|
loading: boolean;
|
|
@@ -47,6 +55,22 @@ interface UseApiOptions<T = unknown, D = unknown> extends ApiRequestConfig<D> {
|
|
|
47
55
|
* - Pass an **object** `{ interval: number, whenHidden?: boolean }` for advanced control.
|
|
48
56
|
* Properties inside the object can also be Refs.
|
|
49
57
|
*/
|
|
58
|
+
/**
|
|
59
|
+
* Cache the response data by a string id.
|
|
60
|
+
* - String shorthand: `cache: 'key'` uses DEFAULT_STALE_TIME (5 min)
|
|
61
|
+
* - Object form: `cache: { id: 'key', staleTime: 10_000 }` for custom TTL
|
|
62
|
+
*
|
|
63
|
+
* On cache hit: mutate() is called with cached data, loading stays false,
|
|
64
|
+
* onBefore/onSuccess/onFinish are NOT called, axios request is NOT made.
|
|
65
|
+
* Cache is written only on HTTP 2xx success.
|
|
66
|
+
*/
|
|
67
|
+
cache?: string | CacheOptions;
|
|
68
|
+
/**
|
|
69
|
+
* Invalidate one or more cache entries on HTTP 2xx success.
|
|
70
|
+
* Fires only after a confirmed successful response โ never in catch/finally.
|
|
71
|
+
* Useful for POST/PUT/DELETE that should bust related GET caches.
|
|
72
|
+
*/
|
|
73
|
+
invalidateCache?: string | string[];
|
|
50
74
|
poll?: MaybeRefOrGetter<number | {
|
|
51
75
|
interval: MaybeRefOrGetter<number>;
|
|
52
76
|
whenHidden?: MaybeRefOrGetter<boolean>;
|
|
@@ -595,4 +619,13 @@ interface AuthEventPayload {
|
|
|
595
619
|
type AuthMonitorFn = (type: AuthEventType, payload: AuthEventPayload) => void;
|
|
596
620
|
declare function setAuthMonitor(fn: AuthMonitorFn): void;
|
|
597
621
|
|
|
598
|
-
|
|
622
|
+
/**
|
|
623
|
+
* Invalidate one or multiple cache entries by id.
|
|
624
|
+
*/
|
|
625
|
+
declare function invalidateCache(id: string | string[]): void;
|
|
626
|
+
/**
|
|
627
|
+
* Clear all cache entries. Call on logout to prevent data leaks between users.
|
|
628
|
+
*/
|
|
629
|
+
declare function clearAllCache(): void;
|
|
630
|
+
|
|
631
|
+
export { type ApiError, type ApiPluginOptions, type ApiRequestConfig, type ApiState, type AuthEventPayload, AuthEventType, type AuthMode, type AuthMonitorFn, type AuthTokens$1 as AuthTokens, type BatchProgress, type BatchRequestConfig, type BatchResultItem, type CacheOptions, type SetDataInput, type UseApiBatchOptions, type UseApiBatchReturn, type UseApiOptions, type UseApiReturn, clearAllCache, createApi, createApiClient, invalidateCache, setAuthMonitor, setupInterceptors, tokenManager, useAbortController, useApi, useApiBatch, useApiConfig, useApiDelete, useApiGet, useApiPatch, useApiPost, useApiPut, useApiState };
|
package/dist/index.mjs
CHANGED
|
@@ -161,8 +161,39 @@ function useAbortController() {
|
|
|
161
161
|
};
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
+
// src/features/cacheManager.ts
|
|
165
|
+
var DEFAULT_STALE_TIME = 3e5;
|
|
166
|
+
var cacheStore = /* @__PURE__ */ new Map();
|
|
167
|
+
function readCache(id) {
|
|
168
|
+
const entry = cacheStore.get(id);
|
|
169
|
+
if (!entry) return null;
|
|
170
|
+
const isValid = Date.now() - entry.cachedAt < entry.staleTime;
|
|
171
|
+
if (!isValid) {
|
|
172
|
+
cacheStore.delete(id);
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
return entry.data;
|
|
176
|
+
}
|
|
177
|
+
function writeCache(id, data, staleTime) {
|
|
178
|
+
cacheStore.set(id, { data, cachedAt: Date.now(), staleTime });
|
|
179
|
+
}
|
|
180
|
+
function invalidateCache(id) {
|
|
181
|
+
const ids = Array.isArray(id) ? id : [id];
|
|
182
|
+
ids.forEach((key) => cacheStore.delete(key));
|
|
183
|
+
}
|
|
184
|
+
function clearAllCache() {
|
|
185
|
+
cacheStore.clear();
|
|
186
|
+
}
|
|
187
|
+
|
|
164
188
|
// src/useApi.ts
|
|
165
189
|
var DEFAULT_RETRY_STATUS_CODES = [408, 429, 500, 502, 503, 504];
|
|
190
|
+
function normalizeCacheOptions(cache) {
|
|
191
|
+
if (!cache) return null;
|
|
192
|
+
if (typeof cache === "string") {
|
|
193
|
+
return { id: cache, staleTime: DEFAULT_STALE_TIME };
|
|
194
|
+
}
|
|
195
|
+
return { id: cache.id, staleTime: cache.staleTime ?? DEFAULT_STALE_TIME };
|
|
196
|
+
}
|
|
166
197
|
function cancellableSleep(ms, signal) {
|
|
167
198
|
return new Promise((resolve) => {
|
|
168
199
|
if (signal.aborted) {
|
|
@@ -221,6 +252,14 @@ function useApi(url, options = {}) {
|
|
|
221
252
|
return { interval: 0, whenHidden: false };
|
|
222
253
|
};
|
|
223
254
|
const executeRequest = async (config) => {
|
|
255
|
+
const cacheOpts = normalizeCacheOptions(options.cache);
|
|
256
|
+
if (cacheOpts) {
|
|
257
|
+
const cached = readCache(cacheOpts.id);
|
|
258
|
+
if (cached !== null) {
|
|
259
|
+
state.mutate(cached);
|
|
260
|
+
return cached;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
224
263
|
if (pollTimer) clearTimeout(pollTimer);
|
|
225
264
|
const requestUrl = toValue(url);
|
|
226
265
|
if (abortController2.value) abortController2.value.abort("Cancelled by new request");
|
|
@@ -266,6 +305,12 @@ function useApi(url, options = {}) {
|
|
|
266
305
|
});
|
|
267
306
|
state.mutate(response.data, response);
|
|
268
307
|
state.setStatusCode(response.status);
|
|
308
|
+
if (cacheOpts) {
|
|
309
|
+
writeCache(cacheOpts.id, response.data, cacheOpts.staleTime);
|
|
310
|
+
}
|
|
311
|
+
if (options.invalidateCache) {
|
|
312
|
+
invalidateCache(options.invalidateCache);
|
|
313
|
+
}
|
|
269
314
|
onSuccess?.(response);
|
|
270
315
|
return response.data;
|
|
271
316
|
} catch (err) {
|
|
@@ -963,8 +1008,10 @@ function createApiClient(options = {}) {
|
|
|
963
1008
|
}
|
|
964
1009
|
export {
|
|
965
1010
|
AuthEventType,
|
|
1011
|
+
clearAllCache,
|
|
966
1012
|
createApi,
|
|
967
1013
|
createApiClient,
|
|
1014
|
+
invalidateCache,
|
|
968
1015
|
setAuthMonitor,
|
|
969
1016
|
setupInterceptors,
|
|
970
1017
|
tokenManager,
|