Research
Security News
Malicious npm Packages Inject SSH Backdoors via Typosquatted Libraries
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
caching-map
Advanced tools
This is an in-memory cache for JavaScript objects. The API is similar to the
ES6 Map
class, which is also an in-memory cache for JavaScript objects, but
you get a few additional features:
const Cache = require('caching-map');
// Cache 10 most recently used documents
const documents = new Cache(10);
// If key is missing, load document from file system
documents.materialize = function(filename) {
return promisify(fs.readFile)(filename);
}
// filename -> promise(Buffer)
//
// Returns promise that resolves to the content of this file
// File becomes the most recently used
function loadDocument(filename) {
return documents.get(filename);
}
// -> [ filename ]
//
// Returns up to ten filenames of the most recently loaded documents
function listDocuments() {
const filenames = [ ...documents.keys() ];
return filenames;
}
When creating a new cache, the first constructor argument is the cache limit.
The second argument can be another cache, a Map
, or any iterator that returns
name/value pairs. You can easily create a new cache from a map (new Cache(limit, map)
), or turn a cache into a map (new Map(cache)
).
If you set the cache limit to zero (or negative number), it will hold zero keys. This could be useful when you want to use the same object in different configurations, e.g. cache in production but always live load in development.
If you set the cache limit to infinity, it will hold as many keys as you've got. This is useful if you want cache features, but don't care about memory usage. For example, flipping between caching all or nothing, using TTL to expire old values, or tracking most recently used keys:
// Cache keys in production, always live load in development
// For example, for caching templates
const limit = (NODE.ENV === 'production') ? Infinity : 0;
const cache = new Cache(limit);
You can use the limit
property to change the cache limit at any time.
Changing the limit doesn't evict any keys until the cache needs to make room for
new keys.
When you set a key, that key becomes the most recently used.
When you set a key, if the cache runs into its storage limit, it will start evicting (deleting) keys until it has room to store the new key. It will first evict any expired keys, and then evict the least recently used keys.
When setting a key, you can associate a cost for that key. The default is one, so the default behavior is to limit the number of keys stored in the cache:
cache.limit = 2;
cache
.set('x', 'XXX')
.set('y', 'YYY')
.set('z', 'ZZZ');
cache.size
=> 2
[ ...cache.keys() ]
=> [ 'z', 'y' ]
However, if you are able to calculate a more accurate cost for each of the keys (e.g. the size of a string), you can use that for better memory usage:
const x = 'X';
const y = 'YYYY';
cache.set('x', x, { cost: Buffer.byteLength(x) });
[ cache.size, cache.cost ]
=> [ 1, 1 ]
cache.set('y', y, { cost: Buffer.byteLength(y) });
[ cache.size, cache.cost ]
=> [ 2, 5 ]
When setting a key, you can associate the time to live (in milliseconds). Once that time has passed, the key is expired. Expired keys are removed first to make room for new keys. There is no way to retrieve the value of an expired key:
const ttl = ms('1h');
cache.set('key', 'good for an hour', { ttl });
cache.get('key');
=> 'good for an hour'
setTimeout(function() {
cache.get('key');
}, ttl);
=> undefined
If a key expires immediately (TTL is zero or negative), or if the key cost is larger than the limit, then that key is not stored, and no other key is evicted.
When you retrieve a key (get(key)
), that key becomes the most recently used
key. It will be the last key removed to make room for new keys, and the first
key returned when iterating through the keys.
In contrast, checking whether a key exists (has(key)
), or iterating over keys,
does not change their order. Only getting or setting a key changes it to most
recent.
The default iterator, entries()
, keys()
and values()
are all available, as
well as forEach
. Since this is an LRU cache, they all iterate on entries
based on their caching order: from most to least recently used.
You can use iteration for operations like deleting keys based on a pattern, listing all keys, and so forth:
function deleteKeysInNamespace(cache, namespace) {
const prefix = `${namespace}:`;
for (let key of cache)
if (key.startsWith(prefix))
cache.delete(key);
}
function listAllKeys(cache) {
return [ ...cache.keys() ];
}
Just watch out, iteration is O(N), and will be expensive for caches with many keys.
Expired keys are lazily evicted from the cache, either to make room for new keys, or when attempting to retrieve, check existence or iterate over the expired key.
If you want to force evict all expired keys, you need to do so yourself, by iterating over all keys:
function evictExpiredKeys() {
// Iterating over expired key removes it from the cache
for (let entry of cache) ;
}
setTimeout(evictExpiredKeys, ms('5m'));
Don't forget that whenever you read the size
or cost
of the cache, that
value may include expired keys that are still in the cache but no longer
accessible.
A common pattern for caching code is to retrieve a key, and when the key doesn't exist, resolve and store the value. This easily leads to the Thundering herd problem.
For example, if you have 100 concurrent requests that all need to render the same data, but the cache is empty, you may end up with 100 database queries attempting to set a single cache key.
The simplest solution is to cache a promise that resolves to that value. That way, everyone is waiting for that one promise to resolve once. However, if an error occurs and the promise is rejected, you want to remove it from the cache, so a new promise can take its place.
The materialize function is a convenient way to implement this pattern. When a key has no value, this function is called with the key, and should return the expected value, or a promise that resolves to that value. For example:
cache.materialize = function(url) {
return promisify(request)(url);
};
const URL = 'http://example.com/';
cache.get(URL).then(
function(result) {
// We cached a promise that always resolves to this response
console.log(result.body);
assert( cache.has(URL) );
},
function(error) {
// The promise is no longer in the cache, we can try again
assert( !cache.has(URL) );
});
If you want to set the cost and/or expiration for that key, returns a promise, but also set the key when that promise resolves:
cache.materialize = function(url) {
const promise = promisify(request)(url);
promise.then(function(response) {
const cost = response.body.length;
cache.set(url, promise, { cost });
});
return promise;
};
MIT License Copyright (c) 2015 Broadly Inc
FAQs
LRU cache for people who like ES6 and promises
The npm package caching-map receives a total of 556 weekly downloads. As such, caching-map popularity was classified as not popular.
We found that caching-map demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
Security News
MITRE's 2024 CWE Top 25 highlights critical software vulnerabilities like XSS, SQL Injection, and CSRF, reflecting shifts due to a refined ranking methodology.
Security News
In this segment of the Risky Business podcast, Feross Aboukhadijeh and Patrick Gray discuss the challenges of tracking malware discovered in open source softare.