Cache
The Cache class is a core subsystem in Hyper Fetch, responsible for storing and managing response data from requests.
It provides a flexible, event-driven system for data retention, validation, and propagation throughout your application.
By default, it uses an in-memory Map for storage, but you can easily swap in persistent storage solutions like
localStorage or IndexedDB.
- Store and manage request results for fast, reliable data access.
- Emit events to keep your app in sync with cache changes.
- Support flexible storage—from in-memory to persistent solutions.
- Integrate seamlessly with the Client and Request systems for a unified data flow.
Quick Start
Here's how to get started with the Cache in Hyper Fetch:
import { createClient, Cache } from "@hyper-fetch/core";
// Initialize the client with default cache
const client = createClient({ url: "http://localhost:3000" });
// Read from cache
const data = client.cache.get("key");
// Write to cache
client.cache.set("key", { data: "value" });
You can also customize the cache storage:
const client = createClient({
url: "http://localhost:3000",
cache: () => new Cache({ storage: localStorage }),
});
Available methods
| Name | Description |
|---|---|
| Update the cache data with partial response data |
| Set the cache data to the storage |
| Schedule garbage collection for given key |
| Get sync storage keys, lazyStorage keys will not be included |
| Invalidate cache by cacheKey or partial matching with RegExp It emits invalidation event for each matching cacheKey and sets staleTime to 0 to indicate out of time cache |
| Initialize the cache with a client reference, set up logging, and start garbage collection. |
| Used to receive data from lazy storage |
| Used to receive keys from lazy storage only |
| Used to receive deduplicated keys from both sync storage and lazy storage |
| Get particular record from storage by cacheKey. It will trigger lazyStorage to emit lazy load event for reading it's data. |
| Delete record from storages and trigger invalidation event |
| Clear cache storages |
Features
- Key-based storage: Data is stored and retrieved using unique cache keys, auto-generated from request details.
- Flexible storage: Use in-memory, localStorage, IndexedDB, or any compatible storage interface.
- Event-driven: Emits events on cache changes for real-time updates.
- Persistence: Easily enable persistent cache across sessions.
- Fine-grained control: Read, write, update, delete, and invalidate cache entries with simple methods.
- Integration: Works seamlessly with the Client and Request systems.
How it works
The cache stores data on a key-value basis. The key (usually cacheKey) is auto-generated from the request's
method, endpoint, and query params, but you can override it for advanced scenarios.
const getUsers = client.createRequest()({
method: "GET",
endpoint: "/users",
});
// get data by cache key
const data = client.cache.get(getUsers.cacheKey);
// or directly by request instance
const data = getUsers.read();
You can also set a custom cache key:
const getUsers = client.createRequest()({
method: "GET",
endpoint: "/users",
cacheKey: "CUSTOM_CACHE_KEY",
});
// get data by custom cache key
const data = client.cache.get("CUSTOM_CACHE_KEY");
// or directly by request instance
const data = getUsers.read();
CacheKey
The cacheKey is a unique identifier for the cache entry. By default it is auto-generated from the request's endpoint,
url params and query params but you can still add the key manually when setting the Request or generic generator.
const getUser = client.createRequest()({
method: "GET",
endpoint: "/users/:userId",
});
const cacheKey = getUser.cacheKey; // "/users/:userId"
const cacheKeyWithParams = getUser.setParams({ userId: 1 }).cacheKey; // "/users/1"
const cacheKeyWithQueryParams = getUser.setQueryParams({ page: 1 }).cacheKey; // "/users/:userId?page=1"
Custom cacheKey
You can also set a custom cache key:
import { client } from "./api";
const getUser = client.createRequest()({
method: "GET",
endpoint: "/users/:userId",
cacheKey: "CUSTOM_CACHE_KEY",
});
console.log(getUser.cacheKey); // "CUSTOM_CACHE_KEY"
Generic cacheKey
You can also set a generic cache key:
import { client } from "./api";
client.setCacheKeyMapper((request) => {
if (request.requestOptions.endpoint === "/users/:userId") {
return `CUSTOM_CACHE_KEY_${request.params?.userId || "unknown"}`;
}
});
const getUser = client.createRequest()({
method: "GET",
endpoint: "/users/:userId",
});
console.log(getUser.setParams({ userId: 1 }).cacheKey); // "CUSTOM_CACHE_KEY_1"
Quick Start
-
Reading from Cache
Use the get method to read data stored under a given key:
const data = client.cache.get("key");
Or with a request instance:
const getNote = client.createRequest<{ response: Note }>()({
method: "GET",
endpoint: "/notes/:noteId",
});
const data = getNote.setParams({ noteId: 1 }).read();
console.log(data); // Note or undefined
-
Writing to Cache
Use the set method to store data under a given key. Pass the request instance to ensure correct configuration:
const getNote = client.createRequest()({
method: "GET",
endpoint: "/notes/:noteId",
});
client.cache.set(getNote.setParams({ noteId: 1 }), {
data: { text: "Hello World" },
error: null,
status: 200,
success: true,
extra: xhrExtra,
});
-
Updating Cache
Update existing cache entries with the update method. The cache will not update if the initial data is missing.
Cache will NOT update if the initial data is not present in the cache.
client.cache.update(getNote.setParams({ noteId: 1 }), {
data: { text: "Hello World" },
error: null,
status: 200,
success: true,
extra: xhrExtra,
});
// Or with a callback
client.cache.update(getNote.setParams({ noteId: 1 }), (prevData) => ({
...prevData,
data: { text: "Hello World" },
}));
-
Deleting from Cache
Remove data from the cache using the delete method:
client.cache.delete(getNote.setParams({ noteId: 1 }).cacheKey);
// OR
client.cache.delete("CUSTOM_CACHE_KEY");
-
Invalidating Cache
Invalidate cache entries with the invalidate method. This sets the staleTime to 0, so the next request will refetch
the data. It also emits an event, triggering refetching in dedicated frameworks integrations like for example in React.
client.cache.invalidate(getNote.setParams({ noteId: 1 }).cacheKey);
// OR
client.cache.invalidate("CUSTOM_CACHE_KEY");
// OR via RegExp
client.cache.invalidate(new RegExp("CUSTOM_CACHE_KEY"));
Cache Modes
The client mode option (default "auto") decides how Hyper Fetch resolves client.mode to "client" or
"server":
mode: "auto"(or omit) — browser →client.mode === "client", Node.js →"server".mode: "client"/mode: "server"— force that effective mode everywhere (overrides detection).
Caching then follows the resolved mode:
client: Cache works for cacheable requests by default.setScopeis optional and acts as a key prefix for organizing data (e.g. per feature, per workspace).server: Cache is disabled by default. A request is only cached if.setScope()is explicitly set. This prevents cross-user data leaks in server environments.
import { createClient } from "@hyper-fetch/core";
const client = createClient({ url: "https://api.example.com" });
// Equivalent: createClient({ url: "https://api.example.com", mode: "auto" })
const getUser = client.createRequest<{ response: User }>()({
endpoint: "/users/:userId",
method: "GET",
});
// NOT cached on server (no scope = safe default)
getUser.setParams({ userId: 1 }).send();
// CACHED — scoped to this user's session, isolated from other users
getUser
.setParams({ userId: 1 })
.setScope(currentSessionId)
.send();
Key Scoping
When setScope is set, all keys (cache, queue, abort) are automatically prefixed with the scope:
${scope}__${originalCacheKey}
This ensures complete isolation between scopes — different users, sessions, or tenants will never share cache entries.
Shared Cache Pattern
For data that is safe to share across all users (e.g. public configuration), use a well-known scope:
getPublicConfig.setScope("public").send();
Persistence
Enable persistent cache by providing a compatible storage interface (e.g., localStorage, IndexedDB):
const client = createClient({
url: "http://localhost:3000",
cache: () => new Cache({ storage: localStorage }),
});
Currently, there is no cross-tab synchronization. This is planned for a future release.
Events & Lifecycle
The cache emits events on changes, allowing you to react to updates in real time. See all available events:
| Name | Type | Description |
|---|---|---|
| emitCacheData | | Set cache data |
| emitDelete | | Delete of cache values |
| emitInvalidation | | Invalidate cache values event |
| onData | | Cache data listener for all keys |
| onDataByKey | | Cache data listener filtered by a specific cache key |
| onDelete | | Cache deletion listener for all keys |
| onDeleteByKey | | Cache deletion listener filtered by a specific cache key |
| onInvalidate | | Cache invalidation listener |
| onInvalidateByKey | | Cache invalidation listener |
Configuration Options
You can configure the cache with various options:
| Name | Type | Description |
|---|---|---|
| lazyStorage | | Lazy loading from remote resources - possibly persistent |
| storage | | Assign your custom sync storage |
| version | | Key to clear lazy storage data, often used for versioning If the new key is different from the old one, the cache will be cleared |
TypeScript
The Cache system is fully typed and integrates with your Client and Request types. When using TypeScript, you get full type safety for cache operations, including data, error, and extra fields.
const getUser = client.createRequest<{ response: { name: string } }>()({ endpoint: "/users/:id" });
const data = getUser.setParams({ id: 1 }).read();
console.log(data); // { name: string }
