-
Notifications
You must be signed in to change notification settings - Fork 0
Services Cache
io.cache is an opt-in, application-governed response cache for GET requests. It is not an HTTP cache: it does not read Cache-Control or max-age. TTLs come from your call, the app decides what to store, and the app decides when to sweep.
import io from 'double-meh';
io.cache.attach();
const a = await io.get('https://example.com/users/42', null, {cache: true});
const b = await io.get('https://example.com/users/42', null, {cache: true});
// second call is served from the cache, no network requestCaching only happens when a request opts in with cache, and only for GET. Use cache: true for the default TTL, or cache: {ttl} (milliseconds) to override it. stream: true requests are never cached.
await io.get('https://example.com/c', null, {cache: true});
await io.get('https://example.com/c', null, {cache: {ttl: 30_000}});io.cache.defaultTtl is 5 minutes. Each entry stores an absolute expiresAt; a fresh entry is served directly, an expired one is refetched. Pass ttl: Infinity to keep an entry until you evict it, or ttl: 0 to force a refetch every time.
io.cache.defaultTtl = 60_000;
await io.get('https://example.com/t', null, {cache: {ttl: 0}}); // always refetchedWhen a stale entry carries an ETag or Last-Modified, the next read attaches If-None-Match / If-Modified-Since. A 304 Not Modified reuses the stored body and refreshes the entry's expiresAt — no re-download.
await io.get('https://example.com/r', null, {cache: {ttl: 0}});
// stale entry with ETag "v1" → conditional request → 304 → cached body reused
const body = await io.get('https://example.com/r', null, {cache: {ttl: 0}});Only ok (2xx) responses are ever stored; error responses pass straight through.
io.cache.remove(pattern) evicts by:
- an exact URL string,
- a trailing-
*prefix string, - a
RegExptested against the stored key, - a function
(key) => boolean.
await io.cache.remove('https://example.com/users/42'); // exact
await io.cache.remove('https://example.com/users/*'); // prefix
await io.cache.remove(/\/users\//); // RegExp on the key
await io.cache.remove(key => key.includes('draft')); // predicateio.cache.clear() drops everything. io.cache.sweep() deletes only expired entries — it is app-called, nothing runs it for you, so call it on your own schedule (e.g. an interval or idle callback).
await io.cache.clear();
await io.cache.sweep(); // purge entries past their expiresAtThe backend lives at io.cache.storage and defaults to an in-memory Map. Any object implementing get / set / delete / clear / keys (sync or async) can replace it — for example a persistent or shared store.
io.cache.storage = {
get: key => myStore.read(key),
set: (key, entry) => myStore.write(key, entry),
delete: key => myStore.drop(key),
clear: () => myStore.reset(),
keys: () => myStore.list()
};io.cache.save(target, response, ttl?) stores a response directly. io.adopt uses it under the hood, so an adopted GET is durable past its in-flight window and later cache: true reads hit without a network call.
await io.adopt('https://example.com/cf', json({from: 'prefetch'}));
const data = await io.get('https://example.com/cf', null, {cache: true}); // no networkio.cache.attach() installs the service (and sets io.cache.isActive); io.cache.detach() removes it. Both return io for chaining.
io.cache.attach();
io.cache.detach();Guides
Concepts
Services
Modules
- fetch transport
- keys & URLs
- helpers
- records
- sse
- storage backends
- compression encoders
- Service Worker integration
Cookbook