Cache Plugin
The official caching plugin for endpoint-fetcher. It provides intelligent, type-safe caching with support for TTL, LRU (Least Recently Used) eviction, and custom storage adapters.
Check out the code on GitHubÂ
Check out the package on NPMÂ
Installation
npm install @endpoint-fetcher/cacheNote: Requires
endpoint-fetcherv3.0.0 or higher.
Quick Start
Add the cache plugin to your client and wrap your endpoint return types with CachingWrapper<T> to enable metadata and helper methods.
import { createApiClient, get } from 'endpoint-fetcher';
import { cache, CachingWrapper } from '@endpoint-fetcher/cache';
const api = createApiClient({
users: {
endpoints: {
// 1. Wrap the return type with CachingWrapper<T>
getAll: get<void, CachingWrapper<User[]>>('/users'),
}
}
}, {
baseUrl: 'https://api.example.com',
plugins: [
cache({ ttl: 300 }) // Global TTL: 5 minutes
]
});
// Usage
const result = await api.users.getAll();
console.log(result.data); // User[]
console.log(result.isStale); // false
console.log(result.expiresAt); // Date objectCachingWrapper<T>
When an endpoint is cached, the response object includes the following properties and methods to help you manage the data lifecycle.
Properties
| Property | Type | Description |
|---|---|---|
data | T | The actual API response data. |
cachedAt | Date | Timestamp of the original network fetch. |
expiresAt | Date | When the entry will be considered stale. |
isStale | boolean | Helper to check if the current time has passed the TTL. |
Methods
refresh(): Forces a network re-fetch, updates the cache, and returns the new data.invalidate(): Immediately removes this specific entry from the cache.
const result = await api.users.getAll();
// Force a network refresh
const fresh = await result.refresh();
// Remove this specific entry from cache
result.invalidate();Configuration
The cache(config) function accepts the following options:
| Option | Type | Default | Description |
|---|---|---|---|
ttl | number | 300 | Global time-to-live in seconds. |
maxSize | number | Infinity | Max number of entries before LRU eviction. |
methods | string[] | ['GET'] | HTTP methods to cache. |
storage | CacheStorage | Memory | Custom storage implementation (e.g., localStorage). |
keyGenerator | Function | Default | Logic to generate unique cache keys. |
Custom Storage
You can persist cache across sessions by providing a custom storage adapter.
const api = createApiClient({...}, {
plugins: [
cache({
ttl: 3600,
storage: {
get: (key) => JSON.parse(localStorage.getItem(key) || 'null'),
set: (key, val) => localStorage.setItem(key, JSON.stringify(val)),
delete: (key) => localStorage.removeItem(key),
keys: () => Object.keys(localStorage),
clear: () => localStorage.clear()
}
})
]
});Cache Management
Global Cache Methods
The cache plugin exposes methods on client.plugins.cache for managing the entire cache:
import { createApiClient, get } from 'endpoint-fetcher';
import { cache, CachingWrapper } from '@endpoint-fetcher/cache';
const api = createApiClient({
users: {
endpoints: {
getAll: get<void, CachingWrapper<User[]>>('/users'),
getById: get<{ id: string }, CachingWrapper<User>>((input) => `/users/${input.id}`),
}
}
}, {
baseUrl: 'https://api.example.com',
plugins: [cache({ ttl: 300 })]
});
// Clear the entire cache
api.plugins.cache.clear();
// Invalidate a specific endpoint's cache
api.plugins.cache.invalidate('GET', '/users', undefined);
// Invalidate using a specific cache key
api.plugins.cache.invalidateKey('GET:/users/123:{"id":"123"}');Available Methods
| Method | Signature | Description |
|---|---|---|
clear() | () => void | Clears all cache entries. |
invalidate(method, path, input) | (method: string, path: string, input: any) => void | Invalidates cache for a specific endpoint with given parameters. |
invalidateKey(key) | (key: string) => void | Invalidates cache using a specific cache key string. |
Per-Entry Cache Management
Each cached response also includes methods for managing its own cache entry:
const result = await api.users.getAll();
// Force a network refresh for this specific endpoint
const fresh = await result.refresh();
// Remove only this entry from cache
result.invalidate();Stale-While-Revalidate Pattern
Use the isStale flag to show cached data immediately while updating in the background.
const result = await api.users.getAll();
// Render cached data immediately
render(result.data);
// If stale, refresh in the background
if (result.isStale) {
result.refresh().then(fresh => render(fresh.data));
}Common Cache Management Scenarios
Clear cache on logout
function logout() {
// Clear user data
api.plugins.cache.clear();
// Redirect to login
window.location.href = '/login';
}Invalidate specific user data after update
async function updateUser(userId: string, data: Partial<User>) {
// Update the user
await api.users.update({ id: userId, ...data });
// Invalidate the cached user data
api.plugins.cache.invalidate('GET', `/users/${userId}`, { id: userId });
// Or invalidate the entire user list
api.plugins.cache.invalidate('GET', '/users', undefined);
}Selective cache invalidation
// Clear only specific endpoints while keeping others
api.plugins.cache.invalidate('GET', '/users', undefined); // Clear users list
api.plugins.cache.invalidate('GET', '/posts', undefined); // Clear posts list
// But keep other cached data intact
// (e.g., /settings, /profile, etc.)Troubleshooting
Data is never cached
- Check the Type: Ensure you are using
CachingWrapper<T>. If the return type is justT, the plugin will skip it. - Check the Method: By default, only
GETrequests are cached. Check yourmethodsconfig if you need to cachePOSTrequests.
Cache persists after refresh
- The default storage is in-memory. If you are using a custom
localStorageadapter, the cache will persist across page reloads unlessttlis reached orinvalidate()is called.
How to invalidate cache programmatically
- Use
api.plugins.cache.clear()to clear all cache entries - Use
api.plugins.cache.invalidate(method, path, input)to clear specific endpoints - Use
result.invalidate()on individual responses to clear just that entry
Cache not being cleared
- Make sure you’re calling the methods on the correct plugin instance:
api.plugins.cache.clear() - If using custom storage, ensure your storage adapter’s
clear()anddelete()methods are working correctly