Adequate Cache
Entirely adequate node.js in-memory cache with lru and ttl support. Typescript typings are included.
npm i --save adequate-cache
or
yarn add adequate-cache
Features
- In-memory cache:
get
,set
,del
,has
,keys
iterator - Optional TTL expiration
- Optional LRU (least-recently used) pruning
- "Provider", helper for async workflows
- Full typescript support
- No dependencies
Documentation
Full documentation is available at https://panta82.github.io/adequate-cache/
Basic usage
The cache is a class with a rather standard API surface.
const AdequateCache = require('adequate-cache');
const cache = new AdequateCache();
// Set a value for key
cache.set('key', 'value');
// Check whether key is in cache. Returns undefined if not found.
cache.has('key'); // true
// Retrieve value for key.
cache.get('key'); // 'value'
// Add another value to cache
cache.set('key2', 'value2');
// Get iterator for all keys in the cache
Array.from(cache.keys()); // ['key', 'key2']
// Delete value from cache
cache.del('key'); // true
cache.has('key'); // false
// Delete everything from cache
cache.emptyOut();
cache.get('key2'); // undefined
All operations are synchronous. Keys are always cast to string
. Values can be any javascript value, except undefined
(setting undefined
is equivalent to deleting an entry).
TTL (time-to-live, in ms) can be provided as a setting "ttl
" when creating the cache, or for each key. Max keys are configured through max
option.
const AdequateCache = require('adequate-cache');
const cache = new AdequateCache({
max: 2, // Hold only 2 keys, get rid of the least used ones
ttl: 1000 // Live for 1 second
});
cache.set('a', {value: 'A'}); // TTL: 1 second (default)
cache.set('b', {value: 'B'}, 2000); // TTL: 2 seconds
cache.set('c', {value: 'C'}, null); // TTL: none (live forever)
cache.get('a'); // undefined, it was removed due to 'max' setting.
Provider
Provide allows you to reduce boilerplate in a very common usage pattern, where you try to get a value from cache and fall back to an asynchronous fetch method.
Code like this:
const userCache = new AdequateCache();
function getUser(id) {
if (userCache.has(id)) {
return Promise.resolve(userCache.get(id));
}
return fetchUser(id).then(user => {
userCache.set(id, user);
return user;
});
}
//...
getUser(id).then(user => {
console.log(user.name);
});
Can be converted to something like this:
const userCache = new AdequateCache({
provider: fetchUser
});
//...
userCache.provide(id).then(user => {
console.log(user.name);
});
You can also have multiple input values. By default, they will be stringified and joined into a string key, but you can also provide your own key generator.
const conversionRateCache = new AdequateCache({
provider: getConversionRate,
providerArgsToKey: (a, b) => `${a} to ${b}`
});
//...
conversionRateCache.provide('USD', 'EUR').then(rate => {
console.log('USD is worth ' + rate + ' EUR');
});
Version history
2018/09/10 - 0.1.0
Initial release
2018/09/10 - 0.1.0
Initial release
2018/11/01 - 0.2.0
Added cache.provide(key)
and provider
option.
2018/11/01 - 0.2.1
Better docs
2018/12/14 - 0.3.0
cache.provide
now accepts multiple arguments.
Also added providerArgsToKey
.
2019/05/17 - 0.3.1
Added cache.emptyOut()
.
2019/07/10 - 0.3.2
Added cache.keys()
.
2020/03/10 - 0.4.0
Provider promises are now reused, so provider()
won't be called multiple times needlessly
Also removed package.lock
from repo.
2020/03/10 - 0.4.1
Minor JSDoc fix
2021/10/14 - 1.0.0
The entire library was rewritten in typescript. It now includes full types.
We now also ship full API documentation, at https://panta82.github.io/adequate-cache/.
The API has remained fully backwards compatible. However, there are a few minor changes, necessitating a major version bump:
-
We no longer export
AdequateCache.Options
andAdequateCache.Entry
classes.Entry
is now fully internal, whileOptions
are now a typescript interface and are no longer available as a class. If you did something likenew AdequateCache.Options({...})
, you can just remove that constructor and just use theIAdequateCacheOptions
interface instead (or nothing at all). -
In lieu of
Options
, we now exportDEFAULT_OPTIONS
. You can mutate this if you want to change the defaults. -
Options now take
now
parameter, for customizing time generation officially. -
All previously "informally" private methods are now typescript private. Your IDE might mark them with red lines. They are still available at their own places, though.
Implementation details
All values are stored as internal entries, in a native js Map
. If cache is configured to have max capacity, entries are connected in a doubly-linked list and rearranged any time cache is touched.
There are no background intervals or timers. Cleaning ("vacuuming") is triggered occasionally, when user "touches" the cache and some of the conditions for vacuuming are met (enough time has passed, overflow is above certain factor, etc.). These factors are configurable through options. Vacuuming is (by default) done in a separate run loop instance, so the duration of cache calls remains constant.
Vacuuming complexity is at most O(N)
. All other operations are O(1)
. The trade-off is in potential memory fragmentation. The magnitude of this is to be determined.
Project status
Library is feature-complete, and covered with unit tests. It's been in production for several years without issues.