Comparing version 0.2.5 to 0.2.7
@@ -19,2 +19,4 @@ var events = require('events') | ||
} | ||
this._accessCount = 0 | ||
this._hitCount = 0 | ||
} | ||
@@ -108,2 +110,30 @@ util.inherits(CacheInstance, events.EventEmitter) | ||
/** | ||
* Get the number of cache accesses. | ||
* | ||
* @return {number} | ||
*/ | ||
CacheInstance.prototype.getAccessCount = function () { | ||
return this._accessCount | ||
} | ||
/** | ||
* Get the number of cache hits. | ||
* | ||
* @return {number} | ||
*/ | ||
CacheInstance.prototype.getHitCount = function () { | ||
return this._hitCount | ||
} | ||
/** | ||
* Reset the access count and hit count to get counts during time intervals. | ||
* | ||
* @return {number} | ||
*/ | ||
CacheInstance.prototype.resetCount = function () { | ||
this._hitCount = 0 | ||
this._accessCount = 0 | ||
} | ||
/** | ||
* Get service information about this cache instance if it is a standlone service. | ||
@@ -120,2 +150,25 @@ * | ||
/** | ||
* Update the access count and hit count according to the result of get/mget. | ||
* | ||
* @param {Array.<Object>|Object} data The data fetched by get or mget. | ||
* @return {Function} A function that updates the counts and returns the parameter | ||
* that is passed into it. It is handy to chain to a promise. | ||
*/ | ||
CacheInstance.prototype.updateCount = function() { | ||
var self = this | ||
return function (data) { | ||
if (Array.isArray(data)) { | ||
self._accessCount += data.length | ||
for (var i = 0; i < data.length; i++) { | ||
if (typeof data[i] !== 'undefined' && data[i] !== null) self._hitCount += 1 | ||
} | ||
} else { | ||
self._accessCount += 1 | ||
if (typeof data !== 'undefined' && data !== null) self._hitCount += 1 | ||
} | ||
return data | ||
} | ||
} | ||
/** | ||
* Update the stats of a certain operation on this cache. | ||
@@ -122,0 +175,0 @@ * |
@@ -0,1 +1,3 @@ | ||
// Copyright 2013 The Obvious Corporation. | ||
var Q = require('kew') | ||
@@ -5,2 +7,14 @@ var util = require('util') | ||
/** | ||
* A complete in-memory in-process cache. It stores all the key/values in memory | ||
* and usually keeps them for a short period of time. The purpose is to accommodate | ||
* the access patterns where the same piece of data is required for multiple times | ||
* in a short period of time. | ||
* | ||
* You can set the TTL of each invidicual key, and you can also set a global TTL | ||
* for all the keys. A reaper is invoked periodically to remove expired keys. | ||
* | ||
* @constructor | ||
* @implements {CacheInstance} | ||
*/ | ||
function InMemoryCache() { | ||
@@ -28,26 +42,7 @@ CacheInstance.call(this) | ||
// create a reaper which scans through all of the items in _data every | ||
// this._reaperIntervalMs millisseconds, if the item has expired then | ||
// it's deleted. | ||
InMemoryCache.prototype._createReaper = function () { | ||
return setInterval(function () { | ||
Object.keys(this._expireAt).map(function (key) { | ||
if (this._expireAt[key] < Date.now()) this.del(key) | ||
}.bind(this)) | ||
}.bind(this), this._reaperIntervalMs) | ||
} | ||
// Destroy the reaper interval if it exists | ||
InMemoryCache.prototype._destroyReaper = function () { | ||
if (!!this._reaperInterval) clearInterval(this._reaperInterval) | ||
} | ||
// Destroy the reaper interval and create a new reaper | ||
// this is done when you reset the reaper interval | ||
InMemoryCache.prototype._resetReaper = function () { | ||
this._destroyReaper() | ||
this._reaperInterval = this._createReaper() | ||
} | ||
// set the reaper to run every everyMs ms | ||
/** | ||
* Set how frequently the reaper runs. | ||
* | ||
* @param {number} everyMs The interval of two consecutive runs of the reaper, in milliseconds. | ||
*/ | ||
InMemoryCache.prototype.setReaperInterval = function (everyMs) { | ||
@@ -58,3 +53,7 @@ this._reaperIntervalMs = everyMs | ||
// set a custom ttl for every object added to the cache from here on out | ||
/** | ||
* Set a custom ttl for every object added to the cache from here on out. | ||
* | ||
* @param {number} maxAgeMs The TTL of the objects in this cache, in milliseconds. | ||
*/ | ||
InMemoryCache.prototype.overrideMaxAgeMs = function (maxAgeMs) { | ||
@@ -64,2 +63,3 @@ this._maxAgeOverride = maxAgeMs | ||
/** @inheritDoc */ | ||
InMemoryCache.prototype.isAvailable = function () { | ||
@@ -69,2 +69,3 @@ return this._isAvailable | ||
/** @inheritDoc */ | ||
InMemoryCache.prototype.connect = function () { | ||
@@ -77,2 +78,3 @@ this._isAvailable = true | ||
/** @inheritDoc */ | ||
InMemoryCache.prototype.disconnect = function () { | ||
@@ -84,2 +86,3 @@ this._destroyReaper() | ||
/** @inheritDoc */ | ||
InMemoryCache.prototype.destroy = function () { | ||
@@ -93,10 +96,18 @@ this._destroyReaper() | ||
/** @inheritDoc */ | ||
InMemoryCache.prototype.get = function (key) { | ||
return (this._expireAt[key] > Date.now() ? this._data[key] : undefined) | ||
return this.mget([key]).then(function (data) { | ||
return data[0] | ||
}) | ||
} | ||
/** @inheritDoc */ | ||
InMemoryCache.prototype.mget = function (keys) { | ||
var ret = [] | ||
this._accessCount += keys.length | ||
for (var i = 0; i < keys.length; i++) { | ||
ret.push(this.get(keys[i])) | ||
if (this._expireAt[keys[i]] > Date.now()) { | ||
ret[i] = this._data[keys[i]] | ||
this._hitCount += 1 | ||
} | ||
} | ||
@@ -106,2 +117,3 @@ return Q.resolve(ret) | ||
/** @inheritDoc */ | ||
InMemoryCache.prototype.set = function (key, val, maxAgeMs) { | ||
@@ -112,2 +124,3 @@ this._expireAt[key] = Date.now() + (this._maxAgeOverride || maxAgeMs ) | ||
/** @inheritDoc */ | ||
InMemoryCache.prototype.mset = function (items, maxAgeMs) { | ||
@@ -119,2 +132,3 @@ for (var i = 0; i < items.length; i++) { | ||
/** @inheritDoc */ | ||
InMemoryCache.prototype.del = function (key) { | ||
@@ -126,2 +140,25 @@ ;delete this._data[key] | ||
// Create a reaper which scans through all of the items in _data every | ||
// this._reaperIntervalMs millisseconds, if the item has expired then | ||
// it's deleted. | ||
InMemoryCache.prototype._createReaper = function () { | ||
return setInterval(function () { | ||
Object.keys(this._expireAt).map(function (key) { | ||
if (this._expireAt[key] < Date.now()) this.del(key) | ||
}.bind(this)) | ||
}.bind(this), this._reaperIntervalMs) | ||
} | ||
// Destroy the reaper interval if it exists | ||
InMemoryCache.prototype._destroyReaper = function () { | ||
if (!!this._reaperInterval) clearInterval(this._reaperInterval) | ||
} | ||
// Destroy the reaper interval and create a new reaper | ||
// this is done when you reset the reaper interval | ||
InMemoryCache.prototype._resetReaper = function () { | ||
this._destroyReaper() | ||
this._reaperInterval = this._createReaper() | ||
} | ||
module.exports = InMemoryCache |
@@ -85,2 +85,3 @@ var redis = require('redis') | ||
return deferred.promise | ||
.then(this.updateCount()) | ||
.then(this.updateStats('mget')) | ||
@@ -87,0 +88,0 @@ } |
{ | ||
"name": "zcache", | ||
"description": "AWS zone-aware multi-layer cache", | ||
"version": "0.2.5", | ||
"version": "0.2.7", | ||
"homepage": "https://github.com/Obvious/zcache", | ||
@@ -6,0 +6,0 @@ "authors": [ |
@@ -7,2 +7,3 @@ var zcache = require('../index') | ||
this.cI.connect() | ||
this.cI.resetCount() | ||
callback() | ||
@@ -13,2 +14,3 @@ } | ||
this.cI.disconnect() | ||
this.cI.destroy() | ||
callback() | ||
@@ -33,5 +35,8 @@ } | ||
setTimeout(function () { | ||
test.equal(this.cI.get('foo'), undefined, 'foo should have expired by now') | ||
test.done() | ||
}.bind(this), 2501) | ||
this.cI.get('foo') | ||
.then(function (data) { | ||
test.equal(data, undefined, 'foo should have expired by now') | ||
test.done() | ||
}) | ||
}.bind(this), 2) | ||
} | ||
@@ -64,8 +69,14 @@ | ||
this.cI.set('foo', 'bar', 500) | ||
var self = this | ||
// undefined should be returned since the item has expired, but before the reaper could clean it | ||
setTimeout(function () { | ||
test.equal(this.cI.get('foo'), undefined, 'foo should still be in the cache') | ||
test.done() | ||
}.bind(this), 750) | ||
self.cI.get('foo') | ||
.then(function (data) { | ||
test.equal(data, undefined, 'foo should still be in the cache') | ||
test.equal(1, self.cI.getAccessCount(), 'The number of accesses is 1') | ||
test.equal(0, self.cI.getHitCount(), 'The number of hits is 0 - the cache entry has expired') | ||
test.done() | ||
}) | ||
}, 750) | ||
} | ||
@@ -76,4 +87,10 @@ | ||
this.cI._expireAt['foo'] = Date.now() + 1000 | ||
test.equal(this.cI.get('foo'), 1, '1 should be returned') | ||
test.done() | ||
var self = this | ||
this.cI.get('foo') | ||
.then(function (data) { | ||
test.equal(data, 1, '1 should be returned') | ||
test.equal(1, self.cI.getAccessCount(), 'The number of accesses is 1') | ||
test.equal(1, self.cI.getHitCount(), 'The number of hits is 1') | ||
test.done() | ||
}) | ||
} | ||
@@ -104,3 +121,3 @@ | ||
this.cI.mset([{key: 'a', value: 1}, {key: 'b', value: 2}, {key: 'c', value: 3}], 1000) | ||
var self = this | ||
this.cI.mget(['a', 'b', 'c']) | ||
@@ -112,4 +129,4 @@ .then(function (keys) { | ||
test.equal(keys[2], 3, 'c should be 3') | ||
}) | ||
.fin(function () { | ||
test.equal(3, self.cI.getAccessCount(), 'The number of accesses is 3') | ||
test.equal(3, self.cI.getHitCount(), 'The number of hits is 3') | ||
test.done() | ||
@@ -124,15 +141,14 @@ }) | ||
this.cI.mset([{key: 'a', value: 1}, {key: 'b', value: 2}, {key: 'c', value: 3}], 100) | ||
var self = this | ||
setTimeout(function () { | ||
this.cI.mget(['a', 'b', 'c']) | ||
self.cI.mget(['a', 'b', 'c']) | ||
.then(function (keys) { | ||
if (keys.length != 3) test.fail('there should be 3 items returned') | ||
test.equal(keys[0], undefined, 'a should be undefined') | ||
test.equal(keys[1], undefined, 'b should be undefined') | ||
test.equal(keys[2], undefined, 'c should be undefined') | ||
}) | ||
.fin(function () { | ||
test.equal(3, self.cI.getAccessCount(), 'The number of accesses is 3') | ||
test.equal(0, self.cI.getHitCount(), 'The number of hits is 0') | ||
test.done() | ||
}) | ||
}.bind(this), 1101) | ||
}, 1101) | ||
} |
@@ -47,3 +47,3 @@ var zcache = require('../index') | ||
.then(function (vals) { | ||
test.equal(vals[0], undefined) | ||
test.equal(vals[0], null) | ||
}) | ||
@@ -60,8 +60,9 @@ .then(function () { | ||
.then(function () { | ||
return cacheInstance.mget(['a', 'b']) | ||
return cacheInstance.mget(['a', 'b', 'c']) | ||
}) | ||
.then(function (vals) { | ||
test.equal(vals.length, 2, 'Should have precisely 2 results') | ||
test.equal(vals.length, 3, 'Should have precisely 3 results') | ||
test.equal(vals[0], '456') | ||
test.equal(vals[1], '789') | ||
test.equal(vals[2], null) | ||
test.equal(1, cacheInstance.getStats('set').count(), 'set() is called for once') | ||
@@ -72,2 +73,4 @@ test.equal(1, cacheInstance.getStats('mset').count(), 'mset() is called for once') | ||
test.equal(1, cacheInstance.getStats('del').count(), 'del() is call for once') | ||
test.equal(5, cacheInstance.getAccessCount(), 'The number of cache access is 5') | ||
test.equal(3, cacheInstance.getHitCount(), 'The number of cache hit is 3') | ||
return cacheInstance.getServerInfo() | ||
@@ -74,0 +77,0 @@ }) |
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
87413
24
2068