@fullgream/heat-cache
v0.1.3
Published
LRU/LFU Cache
Maintainers
Readme
HeatCache
A lightweight in-memory cache with TTL support, hit-based scoring, priority weighting, and automatic garbage collection.
HeatCache ranks cached items by “heat”. Hotter items are kept longer, while colder or expired items are removed first when the cache exceeds its size limit.
Features
- Generic key/value cache
- Optional per-item TTL
- Sliding TTL refresh on
get() - Hit counter tracking
- Priority-based retention
- Automatic aging over time
- Periodic cleanup of expired entries
- Size-based eviction using heat score
- Node.js-friendly timer cleanup with
unref()
Installation
Install the module from npm:
npm i @fullgream/heat-cacheAnd connect to your project
import { HeatCache } from "@fullgream/heat-cache";Basic Usage
const cache = new HeatCache<string, string>(100);
cache.set("user:1", "Alice");
const user = cache.get("user:1");
console.log(user);
// "Alice"Creating a Cache
const cache = new HeatCache<K, V>(elementLimit, intervalPeriod);Parameters
| Parameter | Type | Default | Description |
| ---------------- | -------: | -------: | --------------------------------------------------------------------------- |
| elementLimit | number | required | Maximum preferred number of items in the cache. |
| intervalPeriod | number | 15000 | Interval in milliseconds used for aging items and removing expired entries. |
Example:
const cache = new HeatCache<string, number>(500, 10_000);This creates a cache that tries to keep up to 500 items and runs its internal aging/cleanup cycle every 10 seconds.
Setting Values
cache.set(key, value, options);Options
{
priority?: number;
ttl?: number;
}| Option | Type | Default | Description |
| ---------- | -------: | ----------: | ------------------------------------------------------------------- |
| priority | number | 0 | Retention priority. Higher priority increases item heat. |
| ttl | number | undefined | Time-to-live in milliseconds. Must be greater than 0 if provided. |
Example:
cache.set("session:abc", { userId: 123 }, {
ttl: 60_000,
priority: 2,
});This stores the item for 60 seconds and gives it higher retention priority.
Getting Values
const value = cache.get(key);Returns the cached value, or undefined if the key does not exist or the item has expired.
const session = cache.get("session:abc");
if (session) {
console.log(session.userId);
}Calling get() on a valid item:
- refreshes its TTL if TTL is enabled
- increments its hit counter
- resets its recency category to
0 - marks the eviction priority list as dirty
Checking Existence
cache.has(key);Returns true if the key exists and is not expired.
if (cache.has("user:1")) {
console.log("User is cached");
}Expired items are deleted automatically when checked.
Deleting Values
cache.delete(key);Returns true if the item existed and was deleted.
cache.delete("user:1");Clearing the Cache
cache.clear();Removes all cached items and resets the internal eviction priority list.
Destroying the Cache
cache.destroy();Stops the internal timer and clears all cached data.
Use this when the cache is no longer needed, especially in tests, scripts, or short-lived processes.
cache.destroy();Iterating Over Items
cache.forEach((value, key, cacheInstance) => {
console.log(key, value);
});Expired items are removed during iteration and are not passed to the callback.
Cache Size
cache.size;Returns the number of non-expired items, based on the current eviction priority list.
console.log(cache.size);TTL Behavior
TTL is optional per item.
cache.set("token", "abc", {
ttl: 5_000,
});The item expires after 5 seconds.
When get() is called before expiration, the TTL is refreshed:
cache.get("token");This makes TTL behave like a sliding expiration window.
Invalid TTL
TTL must be greater than 0.
cache.set("bad", "value", {
ttl: 0,
});This throws:
TypeError: TTL must be more than 0 or null for disabling TTLHeat Calculation
Each item has a computed heat score.
item.heat;The score is based on:
- recency category
- number of hits
- configured priority
- expiration status
Expired items always have a heat score of -1.
Current formula:
heat = recency + frequency + priorityScoreWhere:
recency = 100 - category * 20
frequency = Math.log1p(hits) * 10
priorityScore = priority * 20Lower heat means the item is more likely to be removed first.
Aging
Each cache item has a category from 0 to 5.
0means most recent5means oldest/coldest
On every internal interval tick:
- expired items are deleted
- non-expired items move one category older
- items already in category
5stay there
Calling get() resets the item category back to 0.
Eviction Behavior
When the number of stored items exceeds elementLimit, the cache sorts items by heat in ascending order.
The coldest items are deleted first.
Expired items are preferred for deletion.
Example:
const cache = new HeatCache<string, string>(2);
cache.set("a", "A");
cache.set("b", "B");
cache.set("c", "C");After adding "c", the cache evicts the coldest item to keep the size near the configured limit.
Priority
Priority increases an item’s heat score.
cache.set("important", "value", {
priority: 5,
});Higher-priority items are less likely to be evicted.
Priority does not prevent expiration. If an item expires, it is removed regardless of priority.
Replacing Existing Keys
Calling set() with an existing key creates a new HeatCacheItem.
cache.set("user:1", "Alice");
cache.set("user:1", "Bob");The new item replaces the old one and resets:
- hits
- category
- TTL
- priority
This is intentional behavior in the current implementation.
API Reference
HeatCache<K, V>
Constructor
new HeatCache<K, V>(elementLimit: number, intervalPeriod?: number)Creates a new cache.
set()
set(
key: K,
value: V,
options?: {
priority?: number;
ttl?: number;
}
): thisStores a value in the cache.
get()
get(key: K): V | undefinedReturns the cached value or undefined.
has()
has(key: K): booleanChecks whether a non-expired item exists.
delete()
delete(key: K): booleanDeletes an item from the cache.
clear()
clear(): voidRemoves all items.
destroy()
destroy(): voidStops the internal timer and clears the cache.
forEach()
forEach(
callbackfn: (this: any, value: V, key: K, map: HeatCache<K, V>) => void,
thisArg?: any
): voidIterates over non-expired items.
size
get size(): numberReturns the approximate number of non-expired items.
HeatCacheItem<K, V>
Represents a single cache entry.
Properties
| Property | Type | Description |
| ----------- | ---------------------------: | --------------------------------------------------- |
| key | K | Item key. |
| value | V | Stored value. |
| priority | number | Retention priority. |
| category | 0 \| 1 \| 2 \| 3 \| 4 \| 5 | Recency category. |
| hits | number | Number of successful reads. |
| expiresAt | number \| null | Expiration timestamp, or null if TTL is disabled. |
| heat | number | Computed heat score. |
| isExpired | boolean | Whether the item has expired. |
Example: Session Cache
type Session = {
userId: number;
role: string;
};
const sessions = new HeatCache<string, Session>(10_000);
sessions.set("session:abc", {
userId: 1,
role: "admin",
}, {
ttl: 30 * 60 * 1000,
priority: 3,
});
const session = sessions.get("session:abc");
if (session) {
console.log(session.userId);
}Example: Small Hot Cache
const cache = new HeatCache<string, string>(3);
cache.set("a", "A");
cache.set("b", "B");
cache.set("c", "C");
cache.get("a");
cache.get("a");
cache.get("b");
cache.set("d", "D");In this example, "a" and "b" are more likely to stay because they have more hits and therefore higher heat.
Notes and Caveats
size depends on the GC priority list
The size getter subtracts expired items detected at the beginning of the current GC priority list. It may not always force a full scan of the map.
For an exact count, iterate through the cache or clean expired entries first.
TTL refresh only happens on get()
Calling has() checks expiration but does not refresh TTL.
ttl: undefined disables TTL
TTL is only active when a positive number is provided.
ttl: null is accepted by HeatCacheItem
Internally, HeatCacheItem supports null TTL to disable expiration.
The cache is in-memory only
Data is lost when the process exits.
Not designed for distributed caching
This cache is local to a single JavaScript/TypeScript runtime.
Recommended Cleanup
Always call destroy() when the cache lifecycle ends:
cache.destroy();This clears the interval timer and removes all stored items.
