Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

[feature] Added removal events

[minor] The data is now stored in a Data instance.
  • Loading branch information...
commit 751e4b094ea46b5c98f1a1c8ab46ef4fd602017f 1 parent 0cfa23d
@3rd-Eden authored
Showing with 100 additions and 25 deletions.
  1. +7 −0 README.md
  2. +66 −24 index.js
  3. +1 −1  package.json
  4. +26 −0 tests/expirable.test.js
View
7 README.md
@@ -34,6 +34,13 @@ cache.has('key') ? 'yes' : 'no';
// remove a key from the cache.
cache.remove('key');
+// chen a key is removed from the cache it will emit an event for it. This is
+// useful when you want to re-cache an item again when it expires.
+cache.on('key::removed', function (expired) {
+ // The expired boolean tells you if the key was removed because it was expired
+ // or if it was a manual removal
+});
+
// update the expiree of a key
cache.expire('key', '10 seconds');
cache.expire('kex'); // alias for cache.remove, as it expired directly
View
90 index.js
@@ -1,5 +1,23 @@
'use strict';
+var EventEmitter = require('events').EventEmitter;
+
+/**
+ * Simple data interface.
+ *
+ * @constructor
+ * @param {Mixed} data
+ * @param {Number} expires
+ * @param {Boolean} streaming
+ * @api private
+ */
+function Data(value, expires, streaming) {
+ this.value = value;
+ this.expires = expires;
+ this.streaming = streaming || false;
+ this.last = Date.now();
+}
+
/**
* Simple automatic expiring cache.
*
@@ -21,16 +39,18 @@ function Expire(options) {
options = options || {};
- this.cache = {};
- this.length = 0;
+ this.cache = Object.create(null);
this.expiree = Expire.parse(options.expire || '5 minutes');
this.interval = Expire.parse(options.interval || '2 minutes');
- this.lru = options.lru || 0;
+
+ this.length = 0;
// Start watching for expired items.
if (!options.manually) this.start();
}
+Expire.prototype.__proto__ = EventEmitter.prototype;
+
/**
* Get an item from the cache based on the given key.
*
@@ -50,8 +70,7 @@ Expire.prototype.get = function get(key, dontUpdate) {
// We found a match, make sure that it's not expired.
if (now - result.last >= result.expires) {
- this.length--;
- delete this.cache[key];
+ this.remove(key, true);
return undefined;
}
@@ -74,11 +93,8 @@ Expire.prototype.get = function get(key, dontUpdate) {
Expire.prototype.set = function set(key, value, expires) {
if (!(key in this.cache)) this.length++;
- this.cache[key] = {
- value: value
- , expires: expires ? Expire.parse(expires) : this.expiree
- , last: Date.now()
- };
+ expires = expires ? Expire.parse(expires) : this.expiree;
+ this.cache[key] = new Data(value, expires);
return value;
};
@@ -92,23 +108,34 @@ Expire.prototype.set = function set(key, value, expires) {
* @returns {Stream} the stream you passed it
*/
Expire.prototype.stream = function streamer(key, stream, expires) {
- var chunks = []
+ var error = false
+ , chunks = []
, self = this;
- this.cache[key] = { streaming: true };
+ this.cache[key] = new Data(undefined, undefined, true);
stream.on('data', function data(buffer) {
chunks.push(buffer);
});
- stream.on('error', function error() {
+ stream.on('error', function errors() {
chunks.length = 0;
+ error = true;
});
stream.on('end', function end(buffer) {
- if (buffer) chunks.push(buffer);
+ if (!error) {
+ if (buffer) chunks.push(buffer);
+
+ if (chunks.length) {
+ self.set(key, Buffer.concat(chunks), expires);
+ } else {
+ self.remove(key);
+ }
+ } else {
+ self.remove(key);
+ }
- if (chunks.length) self.set(key, Buffer.concat(chunks), expires);
chunks.length = 0;
});
@@ -123,9 +150,10 @@ Expire.prototype.stream = function streamer(key, stream, expires) {
* @api public
*/
Expire.prototype.has = function has(key) {
- var now = Date.now();
+ var now = Date.now()
+ , item = this.cache[key];
- return key in this.cache && (now - this.cache[key].last) <= this.cache[key].expires;
+ return !!item && !item.streaming && (now - item.last) <= item.expires;
};
/**
@@ -142,18 +170,26 @@ Expire.prototype.expire = function expires(key, expire) {
this.cache[key].expires = Expire.parse(expire);
this.cache[key].last = Date.now();
}
+
+ return this;
};
/**
* Remove an item from the cache.
*
* @param {String} key
+ * @param {Boolean} expired was the reason of removal because it was expired
* @api public
*/
-Expire.prototype.remove = function remove(key) {
- if (key in this.cache) this.length--;
+Expire.prototype.remove = function remove(key, expired) {
+ if (key in this.cache) {
+ this.length--;
- delete this.cache[key];
+ this.emit(key + ':removed', !!expired);
+ delete this.cache[key];
+ }
+
+ return this;
};
/**
@@ -171,10 +207,11 @@ Expire.prototype.scan = function scan() {
if (result.streaming) continue;
if (now - result.last >= result.expires) {
- this.length--;
- delete this.cache[key];
+ this.remove(key, true);
}
}
+
+ return this;
};
/**
@@ -184,6 +221,8 @@ Expire.prototype.scan = function scan() {
*/
Expire.prototype.stop = function stop() {
if (this.timer) clearInterval(this.timer);
+
+ return this;
};
/**
@@ -192,10 +231,11 @@ Expire.prototype.stop = function stop() {
* @api public
*/
Expire.prototype.start = function start() {
- // Top old timers before starting a new one
+ // Stop old timers before starting a new one
this.stop();
this.timer = setInterval(this.scan.bind(this), this.expire);
+ return this;
};
/**
@@ -205,8 +245,10 @@ Expire.prototype.start = function start() {
*/
Expire.prototype.destroy = function destroy() {
this.stop();
- this.cache = {};
+ this.cache = Object.create(null);
this.length = 0;
+
+ return this;
};
/**
View
2  package.json
@@ -1,6 +1,6 @@
{
"name": "expirable",
- "version": "0.0.3",
+ "version": "0.0.4",
"description": "Expirable cache",
"main": "index.js",
"scripts": {
View
26 tests/expirable.test.js
@@ -73,6 +73,32 @@ describe('Expirable', function () {
cache.destroy();
});
+ it('should emit <key>:removed when a key is removed', function (done) {
+ var cache = new Expirable('10000 ms');
+
+ cache.set('foo', 'bar');
+ cache.on('foo:removed', function (expired) {
+ expect(expired).to.equal(false);
+
+ cache.destroy();
+ done();
+ });
+
+ cache.remove('foo');
+ });
+
+ it('should emit <key>:removed when a key is expired', function (done) {
+ var cache = new Expirable('5 ms');
+
+ cache.set('foo', 'bar');
+ cache.on('foo:removed', function (expired) {
+ expect(expired).to.equal(true);
+
+ cache.destroy();
+ done();
+ });
+ });
+
it('should override default expire time during set', function (done) {
var cache = new Expirable('5 ms');
Please sign in to comment.
Something went wrong with that request. Please try again.