redis wrapper
'use strict';
/**
* Wrapper for general/all purpose shared caching (clustered)
* @module ./RedisCache
*
* Options:
* {
* connectUrl: 'redis://127.0.0.1:6379', // (required) url connection to redis
* enabled: true, // (default: true) indicator if redis should be used or bypassed
* expiration: 1209600, // (required) default ttl in seconds for each value. can be overridden for each pair
* errorHandler: function(err) (optional) error handler
* }
*/
const _ = require('lodash');
const redis = require('redis');
const Q = require('q');
const errorHelper = require('./error-helper');
let RedisCache = function (options) {
this.options = options ? _.cloneDeep(options) : {};
this.options.enabled = _.get(this.options, "enabled", true);
this.client = null;
};
function handleResult(result) {
if(!this.options.errorHandler) {
return result;
}
if(!result || !result.catch) {
return this.options.errorHandler(result);
}
return result.catch(err => {
return this.options.errorHandler(err);
});
}
function exec(func) {
return _.flow(func, handleResult);
}
/**
* Set in cache a key/value combination
* requires you provide key, value and expiration.
* expiration can either be in date or seconds
* does not return a result if all is successful
*/
RedisCache.prototype.set = exec(function(key, value, options) {
value = JSON.stringify(value);
return this.setRaw(key, value, options);
});
/**
* Set a value in the redis cache without stringifying the value
*/
RedisCache.prototype.setRaw = exec(function(key, value, options) {
options = options || {};
if(!this.options.enabled) {
return Q();
}
if(!key || !value){
return Q.reject(new Error('Invalid key value set'));
}
var timeToLive = options.expiration || this.options.expiration;
if(!timeToLive){
return Q.reject(new Error('Expiration not provided for key: ' + key));
}
var isExpirationDate = Object.prototype.toString.call(timeToLive) === '[object Date]';
if(isExpirationDate){
var time = timeToLive.getTime() - new Date().getTime();
timeToLive = Math.ceil(Math.abs(time / 1000));
}
return this.getClient()
.then(function(client) {
return Q.ninvoke(client, 'setex', key, timeToLive, value);
})
.then(function(result) {
if(!result || result.toLowerCase() !== 'ok') {
return Q.reject(new Error('Failed to cache: ' + key));
}
});
});
/**
* Get from cache (key)
* requires you provide key you want the value for
* parses a string value with JSON.parse
*/
RedisCache.prototype.get = exec(function(key) {
return this.getRaw(key)
.then(value => {
if(_.isString(value)) {
value = JSON.parse(value);
}
return value;
});
});
/**
* Get from cache (key)
* requires you provide key you want the value for
*/
RedisCache.prototype.getRaw = exec(function(key) {
if (!this.options.enabled) {
return Q();
}
if (!key) {
return Q.reject(new Error('Invalid key'));
}
return this.getClient()
.then(client => {
return Q.ninvoke(client, 'get', key);
});
});
/**
* Delete from cache (key)
* requires you provide key you want to delete
*/
RedisCache.prototype.delete = exec(function(key) {
if(!this.options.enabled) {
return;
}
if(!key){
return Q.reject(new Error('Invalid key'));
}
return this.getClient()
.then(function(client){
return Q.ninvoke(client, 'del', key);
});
});
/**
* Checks if the (key) exists in cache
* requires you provide key you want to check for
*/
RedisCache.prototype.exists = exec(function(key) {
if(!this.options.enabled) {
return false;
}
if(!key){
return Q.reject(new Error('Invalid key'));
}
return this.getClient()
.then(function(client){
return Q.ninvoke(client, 'exists', key);
});
});
/**
* Returns the redis client
* creates the client and connects to it
*/
RedisCache.prototype.getClient = function() {
if(!this.options.enabled) {
return Q.reject(new Error('disabled'));
}
if(this.client) {
return Q(this.client);
}
if(!this.options.connectUrl) {
return Q.reject(new Error('missing connectUrl'));
}
var client = redis.createClient(this.options.connectUrl);
return Q.Promise(function(resolve, reject){
client.on('connect', function(){
this.client = client;
resolve(this.client);
}.bind(this));
client.on('error', function(err){
err = errorHelper.ensureError(err, 'Unknown getClient redis error');
this.client = null;
handleResult.call(this, err);
reject(err);
}.bind(this));
client.on('end', function(){
this.client = null;
resolve(null);
}.bind(this));
}.bind(this));
};
module.exports = RedisCache;