LRU and TTL cache utilities with memoize
In-memory LRU and TTL cache with memoize; small and tree-shakeable.
Features: Type-safe · Node & Edge · Tree-shakeable · Lightweight
Installation • Quick Start • Features • Import Paths • API Reference • Examples
npm install @simpill/cache.utilsimport { LRUMap, memoize, InMemoryCache } from "@simpill/cache.utils";
const lru = new LRUMap<string, number>(100);
lru.set("key", 42);
const value = lru.get("key");
const fn = memoize((x: number) => expensive(x), { cache: new LRUMap(50) });| Feature | Description |
|---|---|
| LRUMap | In-memory LRU map with max size |
| InMemoryCache | TTL-capable in-memory cache (no max size; use defaultTtlMs or bounded keys to avoid unbounded growth) |
| memoize | Memoize with optional custom cache (e.g. LRUMap) |
| memoizeAsync | Memoize async functions with optional custom cache |
| TTLCache | TTL cache with max size (server) |
| RedisCache | Redis-backed cache via adapter interface (server) |
import { ... } from "@simpill/cache.utils"; // Everything
import { ... } from "@simpill/cache.utils/client"; // Client
import { ... } from "@simpill/cache.utils/server"; // Server
import { ... } from "@simpill/cache.utils/shared"; // Shared only- LRUMap<K, V>(maxSize) — get, set, has, size, clear
- InMemoryCache — get, set, has, delete, clear, size. Optional defaultTtlMs and maxSize (LRU eviction); without both the cache is unbounded.
- memoize(fn, options?) — options: key, cache (MemoizeCache). Default cache is unbounded; for long-lived processes pass a bounded cache (e.g. LRUMap or InMemoryCache with maxSize).
- memoizeAsync(fn, options?) — async memoize with custom key/cache. Same unbounded-default note; use a bounded cache to avoid unbounded growth.
- TTLCache — server TTL cache with max size
- RedisCache — server cache wrapper (requires RedisCacheAdapter)
Memoizes async functions: same arguments return the same promise (cached). Options: key (custom key function), cache (custom cache instance), ttlMs (default TTL when using the default InMemoryCache). Unlike sync memoize, concurrent calls with the same key share one in-flight promise when the cache is empty — no built-in “in-flight dedupe” flag; the default behavior reuses the same promise until it settles and is stored. Use a bounded cache (e.g. InMemoryCache({ maxSize: 500, defaultTtlMs: 60000 })) to avoid unbounded growth.
- LRUMap / TTLCache: Eviction is automatic (oldest-first when max size is reached; TTL expiry for TTLCache). No manual invalidation API beyond
clear()ordelete(key)where available. - InMemoryCache: Supports
delete(key),clear(), and optional TTL + maxSize; eviction when full is LRU. - RedisCache: Use adapter
delete(key)or key patterns for invalidation; TTL is typically set per key by the adapter.
InMemoryCache is unbounded unless you set maxSize and/or defaultTtlMs. For long-running processes, prefer e.g. new InMemoryCache({ maxSize: 1000, defaultTtlMs: 60_000 }) so entries expire and size is capped (LRU eviction when maxSize is reached).
Values are stored as JSON (serialized on set, parsed on get). get(key) returns the parsed value; the generic type is not validated at runtime — validate at use site (e.g. Zod) if you need a guaranteed shape. Use a key prefix (e.g. myapp:user:${id}) to namespace keys and avoid collisions. The adapter is responsible for encoding; ensure your Redis client stores strings (UTF-8). See server example below.
This package does not implement SWR. To serve stale and revalidate in the background, wrap your fetch in logic that returns cached value immediately and triggers an async refresh; then update the cache when the refresh completes.
- Hit/miss counters or metrics — Caches expose
size, get, set, delete, clear only. For hit/miss stats wrap the cache or use a backend (e.g. Redis) that provides them. - SWR — No stale-while-revalidate; implement with immediate cache return plus background refresh and cache update.
- getMany / deleteMany — No batch get or delete; call get/delete in a loop or use an adapter that supports batching.
import { TTLCache, RedisCache } from "@simpill/cache.utils/server";
// TTL in-memory cache (5 min TTL; entries pruned on access)
const ttl = new TTLCache<string, User>(300_000);
ttl.set("user:1", user);
const u = ttl.get("user:1");
// Redis: pass an adapter implementing get(key), set(key, value, { px }), del(key)
const redisAdapter = {
async get(key: string) { return client.get(key); },
async set(key: string, value: string, opts?: { px?: number }) {
if (opts?.px) await client.set(key, value, "PX", opts.px);
else await client.set(key, value);
},
async del(key: string) { await client.del(key); },
};
const redis = new RedisCache<User>(redisAdapter, { keyPrefix: "myapp", defaultTtlMs: 60_000 });
await redis.set("user:1", user);
const u2 = await redis.get("user:1");This package does not expose hit/miss counters, eviction counts, or metrics. LRUMap, InMemoryCache, and TTLCache provide size (and get/ set/ delete/ clear); they do not track hits or misses. For metrics, wrap the cache: e.g. a thin wrapper that increments hits on get when a value is found and misses when not, or use your monitoring (e.g. log cache size periodically). RedisCache delegates to the adapter; if your Redis client or adapter exposes stats, use those.
npx ts-node examples/01-basic-usage.ts| Example | Description |
|---|---|
| 01-basic-usage.ts | LRUMap, memoize, InMemoryCache |
npm install
npm test
npm run build
npm run verify- Examples: examples/ — run with
npx ts-node examples/01-basic-usage.ts. - Monorepo: CONTRIBUTING for creating and maintaining packages.
- README standard: Package README standard.
ISC