happn-3
Version:
pub/sub api as a service using primus and mongo & redis or nedb, can work as cluster, single process or embedded using nedb
264 lines (206 loc) • 6.65 kB
JavaScript
module.exports = LRUCache;
var EventEmitter = require('events').EventEmitter;
var LRU = require('lru-cache');
var Promise = require('bluebird');
var sift = require('sift').default;
LRUCache.prototype.setSync = setSync;
LRUCache.prototype.getSync = getSync;
LRUCache.prototype.removeSync = removeSync;
LRUCache.prototype.values = values;
LRUCache.prototype.set = Promise.promisify(set);
LRUCache.prototype.has = has;
LRUCache.prototype.get = Promise.promisify(get);
LRUCache.prototype.increment = Promise.promisify(increment);
LRUCache.prototype.update = Promise.promisify(update);
LRUCache.prototype.remove = Promise.promisify(remove);
LRUCache.prototype.clear = Promise.promisify(clear);
LRUCache.prototype.all = Promise.promisify(all);
LRUCache.prototype.stop = stop;
LRUCache.prototype.on = on;
LRUCache.prototype.off = off;
LRUCache.prototype.__emit = __emit;
LRUCache.prototype.__tryCallback = __tryCallback;
LRUCache.prototype.__all = _all;
function LRUCache(opts) {
if (opts == null) opts = {};
if (!opts.max) opts.max = 1000;
this.__cache = new LRU(opts);
this.__eventEmitter = new EventEmitter();
}
function setSync(key, data, opts) {
if (!opts) opts = {};
var maxAge;
if (opts.ttl) maxAge = opts.ttl;
var cacheItem = {
data: opts.clone === false ? data : this.utilities.clone(data),
key: key,
ttl: opts.ttl,
noclone: opts.clone === false
};
this.__cache.set(key, cacheItem, maxAge);
return cacheItem;
}
function getSync(key, opts) {
if (this.__cache.has(key)) {
if (!opts) opts = {};
var explicitClone = opts.clone === true; //explicitly clone result, even if it was set with clone:false
var cached = this.__cache.get(key);
if (cached.noclone && !explicitClone) return cached.data;
return this.utilities.clone(cached.data);
}
return null;
}
function set(key, data, opts, callback) {
if (key == null || key === undefined) return callback(new Error('invalid key'));
if (typeof opts === 'function') {
callback = opts;
opts = null;
}
if (!opts) opts = {};
var maxAge;
if (opts.ttl) maxAge = opts.ttl;
var cacheItem = {
data: opts.clone === false ? data : this.utilities.clone(data),
key: key,
ttl: opts.ttl,
noclone: opts.clone === false
};
this.__cache.set(key, cacheItem, maxAge);
callback(null, cacheItem);
}
function get(key, opts, callback) {
if (key == null || key === undefined) return this.__tryCallback(callback, null, null);
if (typeof opts === 'function') {
callback = opts;
opts = null;
}
if (!opts) opts = {};
var explicitClone = opts.clone === true; //explicitly clone result, even if it was set with clone:false
if (opts.clone == null) opts.clone = true; //clone result by default
var cached = this.__cache.get(key);
if (cached != null)
return this.__tryCallback(callback, cached.data, null, explicitClone || !cached.noclone);
var _this = this;
if (opts.retrieveMethod)
return opts.retrieveMethod.call(opts.retrieveMethod, function(e, result) {
if (e) return callback(e);
// -1 and 0 are perfectly viable things to cache
if (result == null) return _this.__tryCallback(callback, null, null);
_this.set(key, result, opts, function(e) {
return _this.__tryCallback(callback, result, e, opts.clone);
});
});
if (opts.default) {
var value = opts.default.value;
delete opts.default.value;
return _this.set(key, value, opts.default, function(e) {
return _this.__tryCallback(callback, value, e, opts.clone);
});
}
return _this.__tryCallback(callback, null, null);
}
function values() {
return this.__cache.values();
}
function increment(key, by, callback) {
try {
var result = this.__cache.get(key);
if (typeof result.data === 'number') {
result.data += by;
this.__cache.set(key, result);
return this.__tryCallback(callback, result.data, null);
}
return this.__tryCallback(callback, null, null);
} catch (e) {
return this.__tryCallback(callback, null, e);
}
}
function update(key, data, callback) {
try {
var result = this.__cache.get(key);
if (result != null && result !== undefined) {
result.data = data;
this.__cache.set(key, result, result.ttl);
this.__tryCallback(callback, this.__cache.get(key), null);
} else this.__tryCallback(callback, null, null);
} catch (e) {
return this.__tryCallback(callback, null, e);
}
}
function has(key) {
return this.__cache.has(key);
}
function removeSync(key) {
if (key == null || key === undefined) throw new Error('invalid key');
var existing = this.__cache.get(key);
this.__cache.del(key);
return existing;
}
function remove(key, opts, callback) {
if (key == null || key === undefined) return callback(new Error('invalid key'));
if (typeof opts === 'function') {
callback = opts;
opts = null;
}
var existed = this.__cache.get(key);
var removed = existed != null && existed !== undefined;
this.__cache.del(key);
callback(null, removed);
}
function stop() {
//do nothing
}
function clear(callback) {
if (this.__cache) this.__cache.reset();
if (callback) callback();
}
function all(filter, callback) {
try {
if (typeof filter === 'function') {
callback = filter;
filter = null;
}
try {
if (filter)
return callback(
null,
sift(
{
$and: [filter]
},
this.__all()
)
);
else return callback(null, this.__all());
} catch (e) {
return callback(e);
}
} catch (e) {
callback(e);
}
}
function on(key, handler) {
return this.__eventEmitter.on(key, handler);
}
function off(key, handler) {
return this.__eventEmitter.removeListener(key, handler);
}
function __emit(key, data) {
return this.__eventEmitter.emit(key, data);
}
function __tryCallback(callback, data, e, clone) {
var callbackData = data;
if (data && clone) callbackData = this.utilities.clone(data);
if (e) {
if (callback) return callback(e);
else throw e;
}
if (callback) callback(null, callbackData);
else return callbackData;
}
function _all() {
return this.__cache.values().map(function(value) {
//dont clone as these may not be POJOS, and may hold volatile state
return value.data;
});
}