API Reference
Complete API documentation for layercache.
Table of Contents
- CacheStack
- Cache Layers
- Options Reference
- Invalidation Strategies
- Freshness Strategies
- Resilience
- Compression & Serialization
- Distributed Features
- Event Hooks
- Framework Integrations
- Admin CLI
CacheStack
The main class that orchestrates reads, writes, and invalidation across multiple cache layers.
Constructor
Parameters:
layers-CacheLayer[]- Array of cache layers, ordered from fastest (L1) to slowest (Ln)options-CacheStackOptions- Optional configuration (see CacheStackOptions)
Read Operations
cache.get<T>(key, fetcher?, options?): Promise<T | undefined>
Reads through all layers in order. On a partial hit (found in L2 but not L1), backfills the upper layers automatically. On a full miss, runs the fetcher if provided.
cache.getOrThrow<T>(key, fetcher?, options?): Promise<T>
Like get(), but throws CacheMissError instead of returning undefined. A stored null is a cache hit and is returned normally.
cache.mget<T>(entries): Promise<Array<T | undefined>>
Concurrent multi-key fetch. Uses layer-level getMany() fast paths when all entries are simple reads.
cache.has(key): Promise<boolean>
Check if a key exists in any layer.
cache.ttl(key): Promise<number | null>
Get the remaining TTL in milliseconds for a key in the first layer that has it. Returns null if the key doesn't exist.
cache.inspect(key): Promise<CacheInspectResult | null>
Returns detailed metadata about a cache key for debugging.
cache.getEntry<T>(key): Promise<CacheEntryResult<T> | null>
Reads a key and returns entry metadata instead of only the value. Use this when
null is a valid cached value and you need to distinguish stored nulls,
negative-cache entries, stale entries, and misses.
cache.set() stores null values directly. In v4, read-through fetchers also
store null as a regular value by default, so misses and negative-cache entries
remain distinguishable as undefined. Set cacheNullValues: false only when a
fetcher uses null to mean absence.
Write Operations
cache.set<T>(key, value, options?): Promise<void>
Writes to all layers simultaneously.
cache.mset<T>(entries): Promise<void>
Concurrent multi-key write.
cache.delete(key): Promise<void>
Delete one exact key from all layers.
cache.mdelete(keys): Promise<void>
Bulk delete exact keys.
cache.clear(): Promise<void>
Delete all keys from all layers.
Invalidation
cache.invalidateByKey(key): Promise<void>
Alias for cache.delete(key). Use it when you want the invalidateBy* naming style for one exact key.
cache.invalidateByKeys(keys): Promise<void>
Alias for cache.mdelete(keys). Deletes only the exact keys provided.
cache.invalidateByTag(tag): Promise<void>
Deletes every key stored with this tag across all layers.
cache.invalidateByTags(tags, mode?): Promise<void>
Delete keys matching any or all of a set of tags.
cache.invalidateByPattern(pattern): Promise<void>
Glob-style deletion. Patterns must be non-empty, at most 1024 characters, and free of control characters.
cache.invalidateByPrefix(prefix): Promise<void>
Hierarchical prefix-based invalidation. Prefer this over glob when keys are hierarchical.
cache.expireByKey(key): Promise<void>
Marks one exact key as no longer fresh while keeping the cached value available for stale-while-revalidate / stale-if-error windows.
cache.expireByKeys(keys): Promise<void>
Expires only the exact keys provided without deleting their stored values.
cache.expireByTag(tag): Promise<void>
Marks every key stored with this tag as no longer fresh while keeping the cached value available for stale-while-revalidate / stale-if-error windows.
cache.expireByTags(tags, mode?): Promise<void>
Expire keys matching any or all of a set of tags without deleting the stored values.
cache.expireByPattern(pattern): Promise<void>
Glob-style expiration. Matching envelope-backed entries keep their stale windows; plain layer values that do not carry layercache freshness metadata are left unchanged.
cache.expireByPrefix(prefix): Promise<void>
Hierarchical prefix-based expiration. Prefer this over glob when keys are hierarchical.
Wrapping & Namespaces
cache.wrap(prefix, fetcher, options?)
Wraps an async function so every call is transparently cached. The key is derived from function arguments unless you supply a keyResolver.
Structured arguments use the versioned j2: key schema. Plain objects cannot use reserved native $type tags (Date, URL, RegExp, Map, or Set); provide a keyResolver if your domain objects intentionally contain one of those tags.
cache.namespace(prefix): CacheNamespace
Returns a scoped view with the same full API. clear() only touches prefix:* keys.
Namespace prefixes must be non-empty, at most 256 characters, and free of control characters.
Warming & Persistence
cache.warm(entries, options?)
Pre-populate layers at startup. Higher priority values run first.
cache.exportState() / cache.importState(snapshot)
In-memory snapshot transfer.
cache.persistToFile(path) / cache.restoreFromFile(path)
Disk-based snapshot persistence. Restricted to process.cwd() by default (configurable via snapshotBaseDir).
Keep snapshotBaseDir process-owned and avoid group/world-writable parent directories. Snapshot writes validate paths and reject symlinked target parents before commit, but shared writable directories are still a poor snapshot boundary.
Observability
cache.getMetrics(): CacheMetricsSnapshot
cache.getStats(): CacheStatsSnapshot
Returns metrics, per-layer degradation state, and background refresh count.
cache.captureMetrics(operation)
Runs an async operation and returns only the metrics emitted while that operation was active. Namespaces use this internally so overlapping namespace operations do not serialize on a global metrics lock.
If the operation rejects, the thrown error is annotated with a metrics
property containing the captured CacheMetricsSnapshot.
cache.getHitRate()
Computed hit rate overall and per-layer.
cache.healthCheck(): Promise<CacheHealthCheckResult[]>
cache.resetMetrics(): void
Resets all counters to zero.
Generation Management
Add a generation prefix to every key and rotate it for bulk invalidation without scanning.
Cleanup retains a de-duplication set while scanning layers, so maxMatches defaults to 10,000 unique keys per run. Exceeding the limit stops the cleanup and emits the existing generation-cleanup-error warning. Set maxMatches: false only when the deployment bounds the keyspace elsewhere.
Persist the active generation outside the process when you deploy multiple instances or restart workers:
cache.bumpGeneration()
Rotate cache namespace by incrementing generation.
cache.getGeneration()
Get current generation number.
Lifecycle
cache.disconnect(): Promise<void>
Graceful shutdown (unsubscribes from invalidation bus, etc.).
Cache Layers
All layers implement the CacheLayer interface:
MemoryLayer
In-process LRU/LFU/FIFO eviction with configurable max size.
RedisLayer
Distributed caching via ioredis with compression, serializers, and optional prefix.
commandTimeoutMs applies a per-command timeout to Redis round-trips. When a Redis command exceeds this threshold, the layer surfaces an error so CacheStack can trigger graceful degradation instead of waiting on a slow dependency indefinitely.
DiskLayer
Persistent file-based caching with atomic writes and optional at-rest protection.
maxWriteQueueDepth caps pending serialized set() / delete() work so a
slow disk cannot accumulate unbounded writes. Defaults to 10,000. Set it to
false to disable the guard for trusted low-volume environments.
At-Rest Protection
DiskLayer supports AES-256-GCM encryption or HMAC-SHA256 signing to protect cached data on disk:
Encryption also provides authenticated integrity — a separate signingKey is unnecessary when encryptionKey is provided.
When encryptionKey or signingKey is configured, plaintext legacy entries are rejected by default. Use allowLegacyPlaintext: true only during a controlled migration window:
MemcachedLayer
Memcached support with pluggable serializers and bulk operations.
Custom Layers
Implement CacheLayer to plug in any backend:
Options Reference
CacheStackOptions
All single-key and bulk writes use the same per-key ordering boundary so stale cleanup cannot overtake newer data. When a writeCoordination limit is reached, the write rejects with CacheWriteSaturationError; treat it as backpressure or increase a limit only after sizing the expected burst.
CircuitBreakerOptions
If breakerKey is provided, it selects the explicit bucket id and takes
precedence over scope. Otherwise scope: 'key' creates one bucket per cache
key, while scope: 'shared' uses one shared bucket for all keys using those
options.
RateLimitOptions
Per-Operation Options
Invalidation Strategies
Tag Invalidation
Exact-Key Invalidation
Batch Tag Invalidation
Wildcard Invalidation
Prefix Invalidation
Expiration Without Deletion
Use the expireBy* counterparts when stale serving is preferable to removing values immediately.
Generation-Based Invalidation
Freshness Strategies
Stale-While-Revalidate
Sliding TTL
Adaptive TTL
Adaptive TTL counters are process-local. In multi-instance deployments, each Node.js process ramps TTLs from its own observed hits, so use explicit TTLs or a shared Redis counter when every instance must make the same TTL decision.
Refresh-Ahead
TTL Policies
Context-Aware Entry Options
contextOptions() runs immediately before a cache write and overrides static
entry settings on the same call. Use it for value-dependent ttl,
negativeTtl, staleWhileRevalidate, staleIfError, ttlJitter,
adaptiveTtl, or tags.
Per-Layer TTL Overrides
Conditional Caching
Resilience
Graceful Degradation
Circuit Breaker
Write Policies
Scoped Fetcher Rate Limiting
Compression & Serialization
Compression
For large payloads such as HTML fragments, denormalized API responses, or MB-scale documents, prefer compression: 'brotli' with a threshold around 1_024 * 1_024 so small values avoid compression overhead while large values pay less network cost.
MessagePack Serializer
Distributed Features
Distributed Single-Flight
Cross-Server L1 Invalidation
Distributed Tag Index
Event Hooks
CacheStack extends EventEmitter:
Framework Integrations
Express
Fastify
Hono
tRPC
GraphQL
NestJS
Use CacheStack directly in your NestJS modules:
Note: The separate
@cachestack/nestjspackage was removed in v1.3.2. Import directly fromlayercacheinstead.
OpenTelemetry
By default the plugin exports layercache.key_hash instead of raw cache keys. Raw key attributes are available only when explicitly requested:
Stats HTTP Handler
Admin CLI
Inspect and manage Redis-backed caches from the terminal.
Full-cache invalidation requires --force, both for the default pattern and for every wildcard-only combination of * and ? such as *, **, and ?*:
Debug Logging
Or pass a logger instance: