redis.js | |
---|---|
/*
* redis.js: Redis storage engine for nconf configuration(s)
*
* (C) 2011, Charlie Robbins
*
*/
var async = require('async'),
redis = require('redis'),
nconf = require('../../nconf'),
Memory = require('./memory').Memory; | |
function Redis (options)@options {Object} Options for this instanceConstructor function for the Redis nconf store which maintains
a nested Redis key structure based on key delimiters e.g. my:nested:key, 'value' namespace:keys ==> ['my'] namespace:nested:keys ==> ['key'] namespace:nested:key ==> 'value' | var Redis = exports.Redis = function (options) {
options = options || {};
this.type = 'redis';
this.namespace = options.namespace || 'nconf';
this.host = options.host || 'localhost';
this.port = options.port || 6379;
this.ttl = options.ttl || 60 * 60 * 1000;
this.cache = new Memory();
this.redis = redis.createClient(options.port, options.host);
if (options.auth) {
this.redis.auth(options.auth);
}
|
Suppress errors from the Redis client | this.redis.on('error', function (err) {
console.dir(err);
});
}; |
function get (key, callback)@key {string} Key to retrieve for this instance.@callback {function} Continuation to respond to when complete.Retrieves the value for the specified key (if any). | Redis.prototype.get = function (key, callback) {
var self = this,
result = {},
now = Date.now(),
mtime = this.cache.mtimes[key],
fullKey = nconf.key(this.namespace, key);
|
Set the callback if not provided for "fire and forget" | callback = callback || function () { };
|
If the key exists in the cache and the ttl is less than the value set for this instance, return from the cache. | if (mtime && now - mtime < this.ttl) {
return callback(null, this.cache.get(key));
}
|
Get the set of all children keys for the | this.redis.smembers(nconf.key(fullKey, 'keys'), function (err, keys) {
function addValue (source, next) {
self.get(nconf.key(key, source), function (err, value) {
if (err) {
return next(err);
}
result[source] = value;
next();
});
}
if (keys && keys.length > 0) { |
If the value to be retrieved is an Object, recursively attempt
to get the value from redis. Here we use a recursive call to | async.forEach(keys, addValue, function (err) {
if (err) {
return callback(err);
}
self.cache.set(key, result);
callback(null, result);
})
}
else { |
If there are no keys, then the value to be retrieved is a literal and we can simply return the value from redis directly. | self.redis.get(fullKey, function (err, value) {
if (err) {
return callback(err);
}
result = JSON.parse(value);
if (result) {
self.cache.set(key, result);
}
callback(null, result);
});
}
});
}; |
function set (key, value, callback)@key {string} Key to set in this instance@value {literal|Object} Value for the specified key@callback {function} Continuation to respond to when complete.Sets the | Redis.prototype.set = function (key, value, callback) {
var self = this,
path = nconf.path(key);
|
Set the callback if not provided for "fire and forget" | callback = callback || function () { };
this._addKeys(key, function (err) {
if (err) {
return callback(err);
}
var fullKey = nconf.key(self.namespace, key);
if (!Array.isArray(value) && value !== null && typeof value === 'object') { |
If the value is an | self.cache.set(key, value);
self._setObject(fullKey, value, callback);
}
else { |
If the value is a simple literal (or an | self.cache.set(key, value);
value = JSON.stringify(value);
self.redis.set(fullKey, value, callback);
}
});
}; |
function merge (key, value, callback)@key {string} Key to merge the value into@value {literal|Object} Value to merge into the key2callback {function} Continuation to respond to when complete.Merges the properties in | Redis.prototype.merge = function (key, value, callback) { |
If the key is not an | if (typeof value !== 'object' || Array.isArray(value)) {
return this.set(key, value, callback);
}
var self = this,
path = nconf.path(key),
fullKey = nconf.key(this.namespace, key);
|
Set the callback if not provided for "fire and forget" | callback = callback || function () { };
|
Get the set of all children keys for the | this._addKeys(key, function (err) {
self.redis.smembers(nconf.key(fullKey, 'keys'), function (err, keys) {
function nextMerge (nested, next) {
var keyPath = nconf.key.apply(null, path.concat([nested]));
self.merge(keyPath, value[nested], next);
}
if (keys && keys.length > 0) { |
If there are existing keys then we must do a recursive merge of the two Objects. | return async.forEach(Object.keys(value), nextMerge, callback);
} |
Otherwise, we can simply invoke | self.set(key, value, callback);
});
});
}; |
function clear (key, callback)@key {string} Key to remove from this instance@callback {function} Continuation to respond to when complete.Removes the value for the specified | Redis.prototype.clear = function (key, callback) {
var self = this,
result = {},
path = [this.namespace].concat(nconf.path(key)),
last = path.pop(),
fullKey = nconf.key(this.namespace, key); |
Set the callback if not provided for "fire and forget" | callback = callback || function () { }; |
Clear the key from the cache for this instance | this.cache.clear(key);
|
Remove the | this.redis.srem(nconf.key.apply(null, path.concat(['keys'])), last, function (err) { |
Remove the value from redis by iterating over the set of keys (if any) and deleting each value. If no keys, then just delete the simple literal. | self.redis.smembers(nconf.key(fullKey, 'keys'), function (err, keys) {
function removeValue (child, next) { |
Recursively call | self.clear(nconf.key(key, child), next);
}
if (keys && keys.length > 0) { |
If there are child keys then iterate over them, removing each child along the way. | async.forEach(keys, removeValue, callback);
}
else { |
Otherwise if this is just a simple literal, then simply remove it from Redis directly. | self.redis.del(fullKey, callback);
}
});
});
}; |
function save (value, callback)@value {Object} Config object to set for this instance@callback {function} Continuation to respond to when complete.Removes any existing configuration settings that may exist in this
instance and then adds all key-value pairs in | Redis.prototype.save = function (value, callback) {
if (Array.isArray(value) || typeof value !== 'object') {
return callback(new Error('`value` to be saved must be an object.'));
}
var self = this,
keys = Object.keys(value);
|
Set the callback if not provided for "fire and forget" | callback = callback || function () { }; |
Clear all existing keys associated with this instance. | this.reset(function (err) {
if (err) {
return callback(err);
}
|
Iterate over the keys in the new value, setting each of them. | async.forEach(keys, function (key, next) {
self.set(key, value[key], next);
}, callback);
});
}; |
function load (callback)@callback {function} Continuation to respond to when complete.Responds with an Object representing all keys associated in this instance. | Redis.prototype.load = function (callback) {
var self = this,
result = {};
|
Set the callback if not provided for "fire and forget" | callback = callback || function () { };
this.redis.smembers(nconf.key(this.namespace, 'keys'), function (err, keys) {
if (err) {
return callback(err);
}
function addValue (key, next) {
self.get(key, function (err, value) {
if (err) {
return next(err);
}
result[key] = value;
next();
});
}
keys = keys || [];
async.forEach(keys, addValue, function (err) {
return err ? callback(err) : callback(null, result);
});
});
}; |
function reset (callback)@callback {function} Continuation to respond to when complete.Clears all keys associated with this instance. | Redis.prototype.reset = function (callback) {
var self = this;
|
Set the callback if not provided for "fire and forget" | callback = callback || function () { };
|
Get the list of of top-level keys, then clear each of them | this.redis.smembers(nconf.key(this.namespace, 'keys'), function (err, existing) {
if (err) {
return callback(err);
}
existing = existing || [];
async.forEach(existing, function (key, next) {
self.clear(key, next);
}, callback);
});
}; |
@private function _addKeys (key, callback)@key {string} Key to add parent keys for@callback {function} Continuation to respond to when complete.Adds the full | Redis.prototype._addKeys = function (key, callback) {
var self = this,
path = nconf.path(key);
function addKey (partial, next) {
var index = path.indexOf(partial),
base = [self.namespace].concat(path.slice(0, index)),
parent = nconf.key.apply(null, base.concat(['keys']));
self.redis.sadd(parent, partial, next);
}; |
Iterate over the entire key path and add each key to the parent key-set if it doesn't exist already. | async.forEach(path, addKey, callback);
}; |
@private function _setObject (key, value, callback)@key {string} Key to set in this instance@value {Object} Value for the specified key@callback {function} Continuation to respond to when complete.Internal helper function for setting all keys of a nested object. | Redis.prototype._setObject = function (key, value, callback) {
var self = this,
keys = Object.keys(value);
function addValue (child, next) { |
Add the child key to the parent key-set, then set the value.
Recursively call | self.redis.sadd(nconf.key(key, 'keys'), child, function (err) {
if (err) {
return next(err);
}
var fullKey = nconf.key(key, child),
childValue = value[child];
if (!Array.isArray(childValue) && typeof childValue === 'object') {
self._setObject(fullKey, childValue, next);
}
else {
childValue = JSON.stringify(childValue);
self.redis.set(fullKey, childValue, next);
}
});
}
|
Iterate over the keys of the Object and set the appropriate values. | async.forEach(keys, addValue, function (err) {
return err ? callback(err) : callback();
});
};
|