diff --git a/lib/nconf/stores/redis.js b/lib/nconf/stores/redis.js deleted file mode 100644 index 3935996..0000000 --- a/lib/nconf/stores/redis.js +++ /dev/null @@ -1,435 +0,0 @@ -/* - * 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 instance -// Constructor 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 `key` supplied. If the value - // to be returned is an Object, this list will not be empty. - // - 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 `this.get` - // to support nested Object keys. - // - 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 `value` for the specified `key` in this instance. -// -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 `Object` (and not an `Array`) then - // nest into the value and set the child keys appropriately. - // This is done for efficient lookup when setting Object keys. - // (i.e. If you set and Object then wish to later retrieve only a - // member of that Object, the entire Object need not be retrieved). - // - self.cache.set(key, value); - self._setObject(fullKey, value, callback); - } - else { - // - // If the value is a simple literal (or an `Array`) then JSON - // stringify it and put it into Redis. - // - 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 key -// #### 2callback {function} Continuation to respond to when complete. -// Merges the properties in `value` into the existing object value -// at `key`. If the existing value `key` is not an Object, it will be -// completely overwritten. -// -Redis.prototype.merge = function (key, value, callback) { - // - // If the key is not an `Object` or is an `Array`, - // then simply set it. Merging is for Objects. - // - 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 `key` supplied. If the value - // to be returned is an Object, this list will not be empty. - // - 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 `set` to override the current - // literal or Array value with our new Object value - // - 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 `key` from this instance. -// -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 `key` from the parent set of keys. - // - 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` here to ensure we remove any - // nested Objects completely from this instance. - // - 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 `value`. -// -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 `key` path to Redis via `sadd`. -// -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 `_setObject` in the event of nested Object(s). - // - 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(); - }); -}; \ No newline at end of file diff --git a/test/file-store-test.js b/test/file-store-test.js index 918adbb..6f7d1b5 100644 --- a/test/file-store-test.js +++ b/test/file-store-test.js @@ -42,7 +42,7 @@ vows.describe('nconf/stores/file').addBatch({ store.load(this.callback.bind(null, null)); }, "should respond with an error": function (ign, err) { - console.dir(err); + assert.isTrue(!!err); } } } diff --git a/test/redis-store-test.js b/test/redis-store-test.js deleted file mode 100644 index 514bbfe..0000000 --- a/test/redis-store-test.js +++ /dev/null @@ -1,235 +0,0 @@ -/* - * redis-store-test.js: Tests for the redis nconf storage engine. - * - * (C) 2011, Charlie Robbins - * - */ - -var vows = require('vows'), - assert = require('assert'), - nconf = require('../lib/nconf'), - data = require('./fixtures/data').data, - merge = require('./fixtures/data').merge; - -vows.describe('nconf/stores/redis').addBatch({ - "When using the nconf redis store": { - topic: new nconf.stores.Redis(), - "the set() method": { - "with a literal": { - topic: function (store) { - store.set('foo:literal', 'bazz', this.callback) - }, - "should respond without an error": function (err, ok) { - assert.isNull(err); - } - }, - "with an Array": { - topic: function (store) { - store.set('foo:array', data.arr, this.callback) - }, - "should respond without an": function (err, ok) { - assert.isNull(err); - } - }, - "with an Object": { - topic: function (store) { - store.set('foo:object', data.obj, this.callback) - }, - "should respond without an error": function (err, ok) { - assert.isNull(err); - } - }, - "with null": { - topic: function (store) { - store.set('falsy:object', null, this.callback); - }, - "should respond without an error": function(err, ok) { - assert.isNull(err); - } - } - } - } -}).addBatch({ - "When using the nconf redis store": { - topic: new nconf.stores.Redis(), - "the get() method": { - "with a literal value": { - topic: function (store) { - store.get('foo:literal', this.callback); - }, - "should respond with the correct value": function (err, value) { - assert.equal(value, data.literal); - } - }, - "with an Array value": { - topic: function (store) { - store.get('foo:array', this.callback); - }, - "should respond with the correct value": function (err, value) { - assert.deepEqual(value, data.arr); - } - }, - "with an Object value": { - topic: function (store) { - store.get('foo:object', this.callback); - }, - "should respond with the correct value": function (err, value) { - assert.deepEqual(value, data.obj); - } - }, - "with a nested Object value": { - topic: function (store) { - store.get('foo:object:auth', this.callback); - }, - "should respond with the correct value": function (err, value) { - assert.deepEqual(value, data.obj.auth); - } - }, - "with null": { - topic: function(store) { - store.get('falsy:object', this.callback); - }, - "should respond with the correct value": function(err, value) { - assert.equal(value, null); - } - } - } - } -}).addBatch({ - "When using the nconf redis store": { - topic: new nconf.stores.Redis(), - "the clear() method": { - topic: function (store) { - var that = this; - store.clear('foo', function (err) { - if (err) { - return that.callback(err); - } - - store.get('foo', that.callback); - }); - }, - "should actually remove the value from Redis": function (err, value) { - assert.isNull(err); - assert.isNull(value); - } - } - } -}).addBatch({ - "When using the nconf redis store": { - topic: new nconf.stores.Redis(), - "the save() method": { - topic: function (store) { - var that = this; - store.save(data, function (err) { - if (err) { - return that.callback(err); - } - - store.get('obj', that.callback); - }); - }, - "should set all values correctly": function (err, value) { - assert.isNull(err); - assert.deepEqual(value, data.obj); - } - } - } -}).addBatch({ - "When using the nconf redis store": { - topic: new nconf.stores.Redis(), - "the load() method": { - topic: function (store) { - store.load(this.callback); - }, - "should respond with the correct object": function (err, value) { - assert.isNull(err); - assert.deepEqual(value, data); - } - } - } -}).addBatch({ - "when using the nconf redis store": { - topic: new nconf.stores.Redis(), - "the merge() method": { - "when overriding an existing literal value": { - topic: function (store) { - var that = this; - store.set('merge:literal', 'string-value', function () { - store.merge('merge:literal', merge, function () { - store.get('merge:literal', that.callback); - }); - }); - }, - "should merge correctly": function (err, data) { - assert.deepEqual(data, merge); - } - }, - "when overriding an existing Array value": { - topic: function (store) { - var that = this; - store.set('merge:array', [1, 2, 3, 4], function () { - store.merge('merge:array', merge, function () { - store.get('merge:array', that.callback); - }); - }); - }, - "should merge correctly": function (err, data) { - assert.deepEqual(data, merge); - } - }, - "when merging into an existing Object value": { - topic: function (store) { - var that = this, current; - current = { - prop1: 2, - prop2: 'prop2', - prop3: { - bazz: 'bazz' - }, - prop4: ['foo', 'bar'] - }; - - store.set('merge:object', current, function () { - store.merge('merge:object', merge, function () { - store.get('merge:object', that.callback); - }); - }); - }, - "should merge correctly": function (err, data) { - assert.equal(data['prop1'], 1); - assert.equal(data['prop2'].length, 3); - assert.deepEqual(data['prop3'], { - foo: 'bar', - bar: 'foo', - bazz: 'bazz' - }); - assert.equal(data['prop4'].length, 2); - } - } - } - } -}).addBatch({ - "When using the nconf redis store": { - topic: new nconf.stores.Redis(), - "the reset() method": { - topic: function (store) { - var that = this; - this.store = store; - - store.reset(function (err) { - if (err) { - return that.callback(err); - } - - store.get('obj', that.callback); - }); - }, - "should remove all keys from redis": function (err, value) { - assert.isNull(err); - assert.isNull(value); - assert.length(Object.keys(this.store.cache.store), 0); - } - } - } -}).export(module); \ No newline at end of file