memoize-cache
Advanced tools
Comparing version 0.1.0 to 0.1.1
{ | ||
"name": "memoize-cache", | ||
"version": "0.1.0", | ||
"version": "0.1.1", | ||
"description": "A cache support for memoized functions", | ||
@@ -31,4 +31,5 @@ "main": "index.js", | ||
"dependencies": { | ||
"md5-o-matic": "^0.1.1", | ||
"sizeof": "^1.0.0" | ||
} | ||
} |
var sizeof = require('sizeof'); | ||
var Heap = require('./heap'); | ||
var md5omatic = require('md5-o-matic'); | ||
/* | ||
@@ -9,7 +10,20 @@ | ||
function sortByLessPopular(a, b) { | ||
return a.times < b.times; | ||
} | ||
function sortByOldest(a, b) { | ||
return a.ts < b.ts; | ||
} | ||
function removeByKey(key) { | ||
return function (item) { | ||
return item.key === key; | ||
}; | ||
} | ||
function Cache(opts) { | ||
opts = opts || {}; | ||
this._cache = {}; // key, value | ||
this._cacheKeys = []; // sorted by time {ts: xxx, key: xxx} new ones first | ||
this.reset(); | ||
@@ -21,14 +35,29 @@ this._getCacheKey = opts.key || function () { return '_default'; }; | ||
Cache.prototype.push = function cache_push(args, output) { | ||
Cache.prototype.getCacheKey = function cache_getCacheKey(args) { | ||
var k = this._getCacheKey.apply(undefined, args); | ||
if (typeof k !== 'string') { | ||
k = JSON.stringify(k); | ||
k = md5omatic(JSON.stringify(k)); | ||
} | ||
return k; | ||
}; | ||
Cache.prototype.push = function cache_push(args, output) { | ||
var lru; | ||
var k = this.getCacheKey(args); | ||
if (k in this._cache) return; | ||
if(this._LRU.size() === this._maxLen) { | ||
lru = this._LRU.pop(); | ||
delete this._cache[lru.key]; | ||
this._oldest.remove(removeByKey(lru.key)); | ||
} | ||
this._cache[k] = output; | ||
this._cacheKeys.unshift({ | ||
this._LRU.push({key: k, times: 0}); | ||
this._oldest.push({ | ||
key: k, | ||
ts: Date.now() | ||
}); | ||
this._purgeByLen(); | ||
}; | ||
@@ -38,32 +67,22 @@ | ||
// remove old entries | ||
var maxAge = this._maxAge; | ||
var cache = this._cache; | ||
var oldest; | ||
var now = Date.now(); | ||
var now = Date.now(); | ||
this._cacheKeys = this._cacheKeys.filter(function (item) { | ||
if (item.ts + maxAge < now ) { | ||
delete cache[item.key]; | ||
return false; | ||
while (this._oldest.size()) { | ||
oldest = this._oldest.pop(); | ||
if (oldest.ts + this._maxAge < now) { | ||
delete this._cache[oldest.key]; | ||
this._LRU.remove(removeByKey(oldest.key)); | ||
} | ||
return true; | ||
}); | ||
else { | ||
this._oldest.push(oldest); | ||
break; | ||
} | ||
} | ||
}; | ||
Cache.prototype._purgeByLen = function cache__purgeByLen() { | ||
// remove old entries | ||
var maxLen = this._maxLen; | ||
var cache = this._cache; | ||
// trim cache | ||
var keysToRemove = this._cacheKeys.slice(maxLen, Infinity); | ||
keysToRemove.forEach(function (item) { | ||
var k = item.key; | ||
delete cache[k]; | ||
}); | ||
this._cacheKeys = this._cacheKeys.slice(0, maxLen); | ||
}; | ||
Cache.prototype.reset = function cache_reset() { | ||
this._cache = {}; // key, value | ||
this._cacheKeys = []; // sorted by time {ts: xxx, key: xxx} | ||
this._LRU = new Heap(sortByLessPopular); | ||
this._oldest = new Heap(sortByOldest); | ||
}; | ||
@@ -74,13 +93,15 @@ | ||
cached = false, | ||
lru, | ||
key; | ||
try { | ||
this._purgeByAge(); // purge stale cache entries | ||
key = this._getCacheKey.apply(undefined, args); | ||
if (typeof key !== 'string') { | ||
key = JSON.stringify(key); | ||
} | ||
key = this.getCacheKey(args); | ||
if (key in this._cache) { | ||
cached = true; | ||
hit = this._cache[key]; // cache hit! | ||
lru = this._LRU.remove(removeByKey(key)); | ||
lru.times++; | ||
this._LRU.push(lru); | ||
} | ||
@@ -104,5 +125,5 @@ } | ||
Cache.prototype.len = function cache_len() { | ||
return this._cacheKeys.length; | ||
return this._LRU.size(); | ||
}; | ||
module.exports = Cache; |
memoize-cache | ||
============= | ||
A configurable cache support for memoized functions. | ||
A configurable cache support for memoized functions. It is lightweight so it can run in the browser without any problem. | ||
@@ -12,8 +12,10 @@ A note about the API | ||
The constructor takes an option object with 3 optional attributes: | ||
* key: a function used to extract the cache key (used in the push and query method for storing, retrieving the cached value). The key returned should be a string or it will be converted to JSON. Default: a function returning a fixed key. | ||
* key: a function used to extract the cache key (used in the push and query method for storing, retrieving the cached value). The key returned should be a string or it will be converted to JSON and then md5. Default: a function returning a fixed key. | ||
* maxAge: the maximum age of the item stored in the cache (in ms). Default: Infinity | ||
* maxLen: the maximum number of items stored in the cache. Default: Infinity | ||
* maxLen: the maximum number of items stored in the cache. Default: Infinity. Cache items will be purged using an LRU algorithm | ||
Example: | ||
```js | ||
var Cache = require('memoize-cache/ram-cache'); // or require('memoize-cache').ramCache; | ||
// no values, uses always the same key for store any value | ||
@@ -20,0 +22,0 @@ var cache = new Cache(); |
@@ -6,2 +6,9 @@ var assert = require('chai').assert; | ||
it('must translate args to key', function () { | ||
var cache = new Cache({key: function (n) {return n;}}); | ||
assert.equal(cache.getCacheKey(['1']), '1'); | ||
assert.equal(cache.getCacheKey([1]), 'c4ca4238a0b923820dcc509a6f75849b'); | ||
assert.equal(cache.getCacheKey([{d:1}]), 'dc6f789c90af7a7f8156af120f33e3be'); | ||
}); | ||
it('must configure cache: default key', function (done) { | ||
@@ -72,3 +79,3 @@ var cache = new Cache(); | ||
assert.equal(res1.cached, true); | ||
assert.equal(res1.key, '[1,2]'); | ||
assert.equal(res1.key, 'f79408e5ca998cd53faf44af31e6eb45'); | ||
assert.equal(res1.hit, 'result1'); | ||
@@ -87,3 +94,3 @@ done(); | ||
assert.equal(res1.cached, true); | ||
assert.equal(res1.key, '1'); | ||
assert.equal(res1.key, 'c4ca4238a0b923820dcc509a6f75849b'); | ||
assert.equal(res1.hit, 'result1'); | ||
@@ -102,3 +109,3 @@ done(); | ||
assert.equal(res1.cached, true); | ||
assert.equal(res1.key, '[1,2]'); | ||
assert.equal(res1.key, 'f79408e5ca998cd53faf44af31e6eb45'); | ||
assert.equal(res1.hit, 'result1'); | ||
@@ -117,3 +124,3 @@ done(); | ||
assert.equal(res1.cached, true); | ||
assert.equal(res1.key, '8'); | ||
assert.equal(res1.key, 'c9f0f895fb98ab9159f51fd0297e236d'); | ||
assert.equal(res1.hit, 'result1'); | ||
@@ -131,14 +138,10 @@ done(); | ||
}, maxLen: 2}); | ||
cache.push([{test: 1}], 'result1'); | ||
cache.push([{test: 2}], 'result2'); | ||
cache.push([{test: 3}], 'result3'); | ||
cache.push([{test: '1'}], 'result1'); | ||
cache.push([{test: '2'}], 'result2'); | ||
}); | ||
it('must be right size', function () { | ||
assert.equal(cache.len(), 2); | ||
}); | ||
it('must not be cached (purged)', function (done) { | ||
cache.query([{test: 1}], function (err, res1) { | ||
assert.equal(res1.cached, false); | ||
it('must be on the top of the heap', function (done) { | ||
assert.deepEqual(cache._LRU.peek(), {key: '1', times: 0}); | ||
cache.query([{test: '1'}], function () { | ||
assert.deepEqual(cache._LRU.peek(), {key: '2', times: 0}); | ||
done(); | ||
@@ -148,18 +151,38 @@ }); | ||
it('must not be cached 1', function (done) { | ||
cache.query([{test: 2}], function (err, res2) { | ||
assert.equal(res2.cached, true); | ||
assert.equal(res2.key, '2'); | ||
assert.equal(res2.hit, 'result2'); | ||
done(); | ||
describe('remove one', function () { | ||
beforeEach(function (done) { | ||
cache.query([{test: '2'}], function () { | ||
cache.push([{test: '3'}], 'result3'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
it('must be right size', function () { | ||
assert.equal(cache.len(), 2); | ||
}); | ||
it('must not be cached 2', function (done) { | ||
cache.query([{test: 3}], function (err, res3) { | ||
assert.equal(res3.cached, true); | ||
assert.equal(res3.key, '3'); | ||
assert.equal(res3.hit, 'result3'); | ||
done(); | ||
it('must not be cached (purged)', function (done) { | ||
cache.query([{test: '1'}], function (err, res1) { | ||
assert.equal(res1.cached, false); | ||
done(); | ||
}); | ||
}); | ||
it('must not be cached 1', function (done) { | ||
cache.query([{test: '2'}], function (err, res2) { | ||
assert.equal(res2.cached, true); | ||
assert.equal(res2.key, '2'); | ||
assert.equal(res2.hit, 'result2'); | ||
done(); | ||
}); | ||
}); | ||
it('must not be cached 2', function (done) { | ||
cache.query([{test: '3'}], function (err, res3) { | ||
assert.equal(res3.cached, true); | ||
assert.equal(res3.key, '3'); | ||
assert.equal(res3.hit, 'result3'); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
@@ -175,7 +198,7 @@ }); | ||
}, maxAge: 30}); | ||
cache.push([{test: 1}], 'result1'); | ||
cache.push([{test: '1'}], 'result1'); | ||
}); | ||
it('must be cached', function (done) { | ||
cache.query([{test: 1}], function (err, res1) { | ||
cache.query([{test: '1'}], function (err, res1) { | ||
assert.equal(res1.cached, true); | ||
@@ -190,3 +213,3 @@ assert.equal(res1.key, '1'); | ||
setTimeout(function () { | ||
cache.query([{test: 1}], function (err, res1) { | ||
cache.query([{test: '1'}], function (err, res1) { | ||
assert.equal(res1.cached, true); | ||
@@ -202,7 +225,13 @@ assert.equal(res1.key, '1'); | ||
setTimeout(function () { | ||
cache.query([{test: 1}], function (err, res1) { | ||
cache.push([{test: '2'}], 'result2'); | ||
cache.query([{test: '1'}], function (err, res1) { | ||
assert.equal(res1.cached, false); | ||
assert.equal(res1.key, '1'); | ||
assert.isUndefined(res1.hit); | ||
done(); | ||
cache.query([{test: '2'}], function (err, res1) { | ||
assert.equal(res1.cached, true); | ||
assert.equal(res1.key, '2'); | ||
assert.equal(res1.hit, 'result2'); | ||
done(); | ||
}); | ||
}); | ||
@@ -217,3 +246,3 @@ }, 40); | ||
}}); | ||
cache.push([{test: 1}], 'result1'); | ||
cache.push([{test: '1'}], 'result1'); | ||
@@ -220,0 +249,0 @@ cache.reset(); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
22036
11
559
69
0
2
+ Addedmd5-o-matic@^0.1.1
+ Addedmd5-o-matic@0.1.1(transitive)