Getting started
Documentation · Next: How DialCache works
Create one long-lived cache instance and define reusable readers once. A reader's source function remains authoritative: DialCache calls it whenever the active layers cannot supply a value. Caching is disabled outside an enabled request scope, and each operation must also opt into at least one layer.
Choose your language in the site selector. The behavioral explanation is shared; examples and native integration notes follow that choice.
Install
npm install dialcacheThe package provides ESM, CommonJS and TypeScript declarations. Supported Node.js versions are >=22.15.0 <23.0.0 || >=23.8.0. See the TypeScript guide for native API and serializer details.
Wrap a reader
Start with request-local caching so no Redis server or expiration timer is needed. This example proves three observable outcomes:
- Two calls outside an enabled scope each invoke the source.
- Two same-key calls inside one enabled scope invoke the source once.
- A new request scope invokes the source again.
The displayed region comes from a native test that CI executes. Its surrounding file supplies imports and test setup; the source link opens the complete file.
const cache = new DialCache();
let sourceCalls = 0;
const lookup = cache.cached(async (_id: string) => ++sourceCalls, {
keyType: "user",
useCase: "requestScope",
cacheKey: (id) => id,
// Only request-local storage is enabled: shared layers stay off.
defaultConfig: new DialCacheKeyConfig({ requestLocal: true }),
});
// Outside an enabled scope, each call reaches the source.
assert.equal(await lookup("42"), 1);
assert.equal(await lookup("42"), 2);
await cache.enable(async () => {
assert.equal(await lookup("42"), 3);
assert.equal(await lookup("42"), 3);
});
// A new request starts with an empty request-local cache.
await cache.enable(async () => {
assert.equal(await lookup("42"), 4);
});
assert.equal(sourceCalls, 4);The example enables only request-local storage. It has no TTL or capacity limit and disappears when the outer scope closes. Keep requests and their key counts bounded. Add a process-local TTL when reuse across requests is appropriate; the read model explains all three layer lifetimes.
Include every input that can change the result in the key, including tenant, locale or authorization dimensions. Keys and identity explains how result identity also determines shared work and invalidation groups.
Choose the enabled scope
Put one enabled scope around each read request. Nested enabled scopes reuse the outer request memo; a disabled region bypasses caching without deleting entries. A scope's lifetime ends explicitly, so detached work must not assume a retained scope keeps caching enabled forever.
dialcache.enable(async () => ...) propagates scope through Node's AsyncLocalStorage. Use dialcache.disable(() => ...) for nested uncached work. The outer callback settling closes its request-local memo. Registered readers return promises and preserve their source parameters.
Disabling caching does not invalidate stored data. After a source mutation, freshness still depends on TTLs and invalidation policy. Treat reused in-memory values as immutable.
Keep a calculation inline
An inline operation uses the same cache path without registering a reusable reader. All call sites sharing an identity must agree on value meaning and serialization.
Use dialcache.getOrLoad(loader, options) with a direct key instead of the cacheKey selector. See the TypeScript API.
Introduce runtime policy
Keep stable defaults next to the reader and use a runtime provider for sparse overrides. Omitted fields inherit; explicit false and zero values disable their respective features. A provider runs once per enabled invocation before lookup, so keep it inexpensive and bound asynchronous work.
Configuration and rollout follows an executed example that inherits the local TTL through a sparse overlay, then explicitly disables both configured layers. A ramp selects a stable cohort of keys, not a percentage of requests. Policy changes do not cancel admitted work or evict existing entries.
Add shared caching when needed
Connect an application-owned Redis client and pass its native DialCache adapter. Add a remote TTL to participating readers; start remote serving at zero while checking configuration and observability.
Redis and Valkey covers adapters, serialization, Cluster routing and client ownership. Observability covers Prometheus and Datadog. For mutable data, read Targeted invalidation before enabling remote serving. Shadow validation compares Redis with the source during a rollout.