Skip to content

Repository files navigation

Toreda

CIGitHub issues

GitHub package.json version (branch)GitHub Release Date

license

@toreda/cache

TypeScript caches for different use cases.

Constructor params

Every cache type takes a single optional init object. The named wrappers accept the same object minus the cfg flags they pin, and TtlCache additionally requires cfg.ttl.

ParamPurposeDefault
logLog instance (LogLike) used for cache activity & diagnostic output.None — logging disabled.
cfgDeep-partial, data-only config merged over defaults (see table below).All defaults — simple FIFO + TTL behavior.
itemValidatorCalled on every add; return false to reject the item (onAddReject('validator')).None — all items accepted.
rngRandom source for the random eviction basis and sketch hashing. Inject for deterministic tests.Math.random
evictionTargetSelectorEscape hatch for eviction target selection rules no flag combination expresses. Returns the id to evict, or null to reject the incoming add.None — selection follows cfg.evict.
eventsObservability callbacks (onItemAdd, onItemEvict, onItemExpire, onAddReject, …), fired synchronously post-commit.{} — no callbacks.

cfg options

OptionPurposeDefault
capacityMaxMaximum number of items. 0 = unbounded.1000
initialSizeInitial size hint for an empty cache.0
ttlDefault TTL (seconds) for items added without an explicit ttl. 0 = never expire.30
get.countsAsAccessA get call counts as an access for eviction accounting.true
get.slidesExpirationA get refreshes the item's expiration window.false
has.countsAsAccessA has call counts as an access for eviction accounting.false
has.slidesExpirationA has refreshes the item's expiration window.false
touch.countsAsAccessA touch call counts as an access for eviction accounting.true
prune.autoInterval timer prunes expired items automatically.false
prune.intervalSeconds between auto-prune runs.10
prune.minDelayMinimum seconds allowed between prune() calls.10
evict.basisMetadata axis that chooses the eviction target: insertion, access, frequency, random, or none.insertion
evict.orderDirection along the basis: oldest or newest.oldest
evict.secondChanceAccessed items get one skip before eviction (CLOCK).false
evict.tieBreakTie-break for frequency ties: access or insertion.access
segments.enabledSplit the cache into probation + protected regions.false
segments.protectedRatioProtected region size as a fraction of capacityMax.0.8
segments.probationRatioProbation budget as a fraction of capacityMax (2Q A1in).0.25
segments.probationBasisBasis used to pick a probation eviction target: access (SLRU) or insertion (2Q).access
segments.promoteOnHitA probation-region hit promotes the item to protected.true
ghosts.enabledKeep a bounded, ids-only registry of recently-evicted items.false
ghosts.sizeRatioGhost registry size as a fraction of capacityMax.0.5
ghosts.perSegmentSplit ghosts into recency/frequency lists (ARC B1/B2).false
adaptiveGhost hits shift the probation/protected balance (ARC's p).false
admission.policyalways admits every newcomer; frequency compares sketch estimates (W-TinyLFU).always
admission.windowRatioW-TinyLFU entry window as a fraction of capacityMax.0.01
admission.sketchResetThresholdSketch counter-halving threshold. 0 = derived from capacity.0

events callbacks

Callbacks provide external visibility into cache state changes.

Every callback is optional and costs nothing when unset. Callbacks fire post-commit (state and stats already updated), synchronously, each wrapped in try/catch — a throwing callback is logged and never corrupts cache state.

EventSignatureFired when
onItemAdd(item, id)An item is successfully added.
onItemExpire(item, id, timeAdded)An item is removed because its TTL elapsed. Precedes onItemRemove.
onItemEvict(item, id)An item is removed to make room at capacity. Precedes onItemRemove.
onItemRemove(item, id, reason)Every single-item removal, after any specific event. Reasons: delete, evict, expire, overwrite.
onAddReject(item, reason)An add call is refused. Reasons: validator, admission, capacity, duplicate, bad-id.
onItemHit(item, id, source)A cache hit via get or touch (source names which).
onItemMiss(id)A get miss.
onCapacityIncrease(oldMax, newMax)Capacity is raised via setCapacity.
onCapacityDecrease(oldMax, newMax)Capacity is lowered via setCapacity.
onClear(count)Once per clear() / reset() with the number of items removed.
onReset()By reset(), after onClear.
onPrune(removed)Whenever prune() actually runs (not delay-gated), including when 0 items were removed.

Single-item removals fire the specific event (onItemExpire / onItemEvict) first, then the general onItemRemove. Bulk clear() / reset() fire only onClear (and onReset) — never per-item events.

constcache=newLruCache<User>({cfg: {capacityMax: 500,ttl: 300},events: {onItemEvict: (item,id)=>console.log(`evicted ${id}`),onItemExpire: (item,id)=>console.log(`expired ${id}`),onAddReject: (item,reason)=>console.warn(`add refused: ${reason}`)}});

Usage

The base Cache is policy-agnostic — every behavior is a config flag with a default that reproduces simple FIFO + TTL behavior:

import{Cache}from'@toreda/cache';interfaceUser{[k: string]: unknown;id: string;name: string;}constcache=newCache<User>({cfg: {capacityMax: 1000,ttl: 300},events: {onItemEvict: (item,id)=>console.log(`evicted ${id}`)}});cache.add({id: 'u-1',name: 'Ada'});constuser=cache.getOrAdd('u-2',(id)=>({id,name: 'Grace'}));console.log(cache.size,cache.get('u-1'));

Cache types

Named wrappers pin the eviction flags for a well-known replacement policy. Every type combines freely with the expiration axis (ttl, sliding, prune) — the eviction policy and TTL are independent.

TypePolicy — who leaves at capacity
FifoCacheOldest inserted item.
LifoCacheNewest inserted item.
LruCacheLeast-recently used item.
MruCacheMost-recently used item.
LfuCacheLeast-frequently used item (ties: least-recent).
RandomCacheA uniformly random item (inject rng for determinism).
TtlCacheNothing — items leave only on TTL expiry (auto-pruned). Adds getRemainingTtl(id).
ClockCacheInsertion order with a second-chance reprieve for accessed items (CLOCK).
SlruCacheSegmented LRU — probation feeds a protected region; scan-resistant.
TwoQueueCache2Q — FIFO probation + ghost list; re-referenced ids promote to the main region.
ArcCacheARC — adapts the recency/frequency balance from per-segment ghost hits.
TinyLfuCacheW-TinyLFU — a frequency sketch gates admission; rare newcomers are refused.

All examples below use the User interface defined above.

FifoCache

First-in, first-out. Insertion order alone decides the eviction target — reads never reorder anything:

import{FifoCache}from'@toreda/cache';constcache=newFifoCache<User>({cfg: {capacityMax: 2}});cache.add({id: 'a',name: 'Ada'});cache.add({id: 'b',name: 'Grace'});cache.get('a');// Reads don't affect FIFO order.cache.add({id: 'c',name: 'Edsger'});// Evicts 'a' — the oldest insert.

LifoCache

Last-in, first-out — the newest insert is sacrificed to protect older entries:

import{LifoCache}from'@toreda/cache';constcache=newLifoCache<User>({cfg: {capacityMax: 2}});cache.add({id: 'a',name: 'Ada'});cache.add({id: 'b',name: 'Grace'});cache.add({id: 'c',name: 'Edsger'});// Evicts 'b' — the newest insert; 'a' survives.

LruCache

Least-recently used. Every get / touch refreshes an item's recency:

import{LruCache}from'@toreda/cache';constcache=newLruCache<User>({cfg: {capacityMax: 2}});cache.add({id: 'a',name: 'Ada'});cache.add({id: 'b',name: 'Grace'});cache.get('a');// 'a' is now most-recent.cache.add({id: 'c',name: 'Edsger'});// Evicts 'b' — least-recently used.

MruCache

Most-recently used — evicts the hottest item, useful for cyclic scans where the item you just read is the one you'll need last:

import{MruCache}from'@toreda/cache';constcache=newMruCache<User>({cfg: {capacityMax: 2}});cache.add({id: 'a',name: 'Ada'});cache.add({id: 'b',name: 'Grace'});cache.get('a');// 'a' is now most-recent.cache.add({id: 'c',name: 'Edsger'});// Evicts 'a' — the most-recently used.

LfuCache

Least-frequently used. Each read increments a hit count; ties fall back to least-recent:

import{LfuCache}from'@toreda/cache';constcache=newLfuCache<User>({cfg: {capacityMax: 2}});cache.add({id: 'a',name: 'Ada'});cache.add({id: 'b',name: 'Grace'});cache.get('a');cache.get('a');// 'a' has 2 hits, 'b' has 0.cache.add({id: 'c',name: 'Edsger'});// Evicts 'b' — the least-frequently used.

RandomCache

Uniform random replacement. Inject rng to make eviction deterministic in tests:

import{RandomCache}from'@toreda/cache';constcache=newRandomCache<User>({cfg: {capacityMax: 1000},rng: ()=>0.42// Optional — defaults to Math.random.});

TtlCache

Pure expiration — no capacity evictions, items leave only when their (required) TTL elapses. An auto-prune timer sweeps expired items; call stopAutoPrune() when done with the cache:

import{TtlCache}from'@toreda/cache';constcache=newTtlCache<User>({cfg: {ttl: 60}// Seconds. Required, and capacity is unbounded by default.});cache.add({id: 'a',name: 'Ada'});cache.getRemainingTtl('a');// Whole seconds until expiry, or null once gone.cache.stopAutoPrune();// Release the sweep timer on shutdown.

ClockCache

CLOCK (second-chance FIFO). A hand sweeps insertion order, but an accessed item gets one reprieve before eviction — LRU-like behavior without reordering on every read:

import{ClockCache}from'@toreda/cache';constcache=newClockCache<User>({cfg: {capacityMax: 2}});cache.add({id: 'a',name: 'Ada'});cache.add({id: 'b',name: 'Grace'});cache.get('a');// Marks 'a' referenced.cache.add({id: 'c',name: 'Edsger'});// 'a' is spared once; 'b' is evicted instead.

SlruCache

Segmented LRU. New items sit in probation; a hit promotes them into the protected region, so a one-time scan of new ids can't displace the working set:

import{SlruCache}from'@toreda/cache';constcache=newSlruCache<User>({cfg: {capacityMax: 1000,segments: {protectedRatio: 0.8}// Optional — protected region share (default 0.8).}});

TwoQueueCache

2Q. A FIFO probation queue (A1in) absorbs newcomers and a ghost list (A1out) remembers recent evictees — a quick re-reference admits an id straight into the main LRU region:

import{TwoQueueCache}from'@toreda/cache';constcache=newTwoQueueCache<User>({cfg: {capacityMax: 1000,segments: {probationRatio: 0.25},// Optional — A1in share (default 0.25).ghosts: {sizeRatio: 0.5}// Optional — A1out budget vs capacity (default 0.5).}});

ArcCache

ARC. Self-tuning — per-segment ghost lists track whether recency or frequency evictions are being regretted, and the balance point adapts toward whichever is winning:

import{ArcCache}from'@toreda/cache';constcache=newArcCache<User>({cfg: {capacityMax: 1000,segments: {protectedRatio: 0.5}// Optional — ARC's starting balance point (default 0.5).}});

TinyLfuCache

W-TinyLFU. A count-min sketch estimates access frequency, and a newcomer is only admitted over the resident eviction target when the sketch says it's at least as popular — add returns false when admission is refused:

import{TinyLfuCache}from'@toreda/cache';constcache=newTinyLfuCache<User>({cfg: {capacityMax: 1000,admission: {windowRatio: 0.01,// Optional — entry window share (default 0.01).sketchResetThreshold: 100_000// Optional — halve sketch counts after this many increments.}}});constadmitted=cache.add({id: 'rare',name: 'One-hit wonder'});// admitted === false when the frequency sketch refuses the newcomer at capacity.

Combining policies with expiration

Mix-and-match — an LRU cache with a 5-minute default TTL and sliding expiration on reads:

import{LruCache}from'@toreda/cache';constcache=newLruCache<User>({cfg: {capacityMax: 500,ttl: 300,get: {slidesExpiration: true}}});

Wrappers remove the flags they pin from the caller's cfg type, so contradictory combinations (e.g. asking an LruCache for FIFO eviction) are a compile-time error while orthogonal features still combine.

Source Code

@toreda/cache is an open source package provided under the MIT License. Download, clone, or check the complete project source here on Github. We welcome bug reports, comments, and pull requests.

Legal

License

MIT © Toreda, Inc.

Copyright

Copyright © 2019 - 2026 Toreda, Inc. All Rights Reserved.

Website

https://www.toreda.com

About

Simple TTL-based object cache in TypeScript.

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages