staleness
v0.1.0
Published
Wrap an async function with stale-while-revalidate caching — serve fresh instantly, serve stale while refreshing once in the background, block only when truly expired. Single-flight, per key.
Maintainers
Readme
staleness
Wrap async functions with stale-while-revalidate caching — serve fresh instantly, serve stale while refreshing once in the background, block only when truly expired. Single-flight, per-key cache for servers and CLIs.
The problem
When cached data expires, every concurrent caller waits for a slow recompute, causing cache stampedes and latency spikes. You want to serve slightly-stale data immediately while refreshing in the background, but only trigger one refresh operation. Existing solutions like swr and react-query focus on React, while lru-cache provides storage without SWR semantics.
Install
npm install staleness
# or
pnpm add staleness
# or
yarn add stalenessUse
import staleness from "staleness";
const cached = staleness(fetchData, {
ttlMs: 60_000,
staleMs: 600_000
});API
staleness(fn, options): Cached
Wrap an async function with stale-while-revalidate caching.
import { staleness } from "staleness";
const cached = staleness(fetchData, {
ttlMs: 60_000, // Required: fresh window
staleMs: 600_000, // Optional: stale-while-revalidate window (default: Infinity)
key: (...args) => args[0], // Optional: custom cache key function
clock: () => Date.now(), // Optional: time provider for testing
onError: (error, key) => console.error(error), // Optional: background refresh failure handler
max: 1000, // Optional: max distinct cache keys with LRU eviction
});Behavior: Fresh (≤ ttlMs) returns cached immediately; Stale (ttlMs < age ≤ staleMs) returns stale value and triggers background refresh; Expired (> staleMs) blocks and fetches fresh value. Single-flight prevents stampedes.
Cached interface
const result = await cached(arg1, arg2);
cached.invalidate(arg1, arg2);
cached.clear();
console.log(cached.size);Non-goals
Focuses on core SWR semantics, explicitly avoiding persistence, React bindings, size-based eviction, and distributed caching.
Composition with persistent storage:
import { LRUCache } from "lru-cache";
import staleness from "staleness";
const persistentCache = new LRUCache({ max: 500 });
const cached = staleness(fetchFromAPI, { ttlMs: 60000, staleMs: 600000 });
async function fetch(key: string) {
if (persistentCache.has(key)) return persistentCache.get(key);
const value = await cached(key);
persistentCache.set(key, value);
return value;
}TypeScript
Full TypeScript support with strict mode enabled. Type inference works for function arguments:
import staleness from "staleness";
// Types inferred from function signature
const cachedFetch = staleness(async (userId: number, resource: string): Promise<UserData> => {
return fetch(`/users/${userId}/${resource}`).then(r => r.json());
});
// Fully typed: userId and resource are validated
const data = await cachedFetch(123, "profile");Related Packages
Caching & Concurrency:
- @azghr/filterkit — Framework-agnostic, type-safe filtering for TypeScript
- @azghr/singlet — Deduplicate concurrent async calls
Text Processing:
- @azghr/shorn — Truncate strings by byte budget without breaking graphemes
- seriatim — Sequential processing utilities
HTTP & Network:
- forbear — Read server rate-limit instructions from HTTP responses
- forestall — Delay execution until a condition is met
- obviate — Render operations unnecessary through caching
System & Process:
- quiesce — Ordered, timeboxed graceful shutdown for Node
- sortition — Deterministic percentage rollouts and A/B bucketing
- stanch — Stop flows or operations based on conditions
Utilities:
- expunge — Remove or exclude items from collections
- occlude — Hide or mask data and functionality
- placemark — Geographic location and mapping utilities
- specie — Currency and financial calculations
License
MIT
