/* * provider.js: Abstraction providing an interface into pluggable configuration storage. * * (C) 2011, Charlie Robbins * */ var async = require('async'), common = require('./common'), stores = require('./stores'); // // ### function Provider (options) // #### @options {Object} Options for this instance. // Constructor function for the Provider object responsible // for exposing the pluggable storage features of `nconf`. // var Provider = exports.Provider = function (options) { options = options || {}; this.overrides = options.overrides || null this.useArgv = options.useArgv || false; this.store = stores.create(options.type || 'memory', options); }; // // ### function use (type, options) // #### @type {string} Type of the nconf store to use. // #### @options {Object} Options for the store instance. // Sets the active `this.store` to a new instance of the // specified `type`. // Provider.prototype.use = function (type, options) { var self = this; options = options || {}; function sameOptions () { return Object.keys(options).every(function (key) { return options[key] === self.store[key]; }); } if (!this.store || type.toLowerCase() !== this.store.type || !sameOptions()) { this.store = stores.create(type, options); } return this; }; // // ### function get (key, callback) // #### @key {string} Key to retrieve for this instance. // #### @callback {function} **Optional** Continuation to respond to when complete. // Retrieves the value for the specified key (if any). // Provider.prototype.get = function (key, callback) { if (this.overrides && Object.prototype.hasOwnProperty.call(this.overrides, key)) { if (callback) { callback(null, this.overrides[key]); } return this.overrides[key]; } return this._execute('get', 1, key, callback); }; // // ### function set (key, value, callback) // #### @key {string} Key to set in this instance // #### @value {literal|Object} Value for the specified key // #### @callback {function} **Optional** Continuation to respond to when complete. // Sets the `value` for the specified `key` in this instance. // Provider.prototype.set = function (key, value, callback) { return this._execute('set', 2, key, value, callback); }; // // ### function merge ([key,] value [, callback]) // #### @key {string} Key to merge the value into // #### @value {literal|Object} Value to merge into the key // #### @callback {function} **Optional** Continuation to respond to when complete. // Merges the properties in `value` into the existing object value at `key`. // // 1. If the existing value `key` is not an Object, it will be completely overwritten. // 2. If `key` is not supplied, then the `value` will be merged into the root. // Provider.prototype.merge = function () { var self = this, args = Array.prototype.slice.call(arguments), callback = typeof args[args.length - 1] === 'function' && args.pop(), value = args.pop(), key = args.pop(); function mergeProperty (prop, next) { return self._execute('merge', 2, prop, value[prop], next); } if (!key) { if (Array.isArray(value) || typeof value !== 'object') { return onError(new Error('Cannot merge non-Object into top-level.'), callback); } return async.forEach(Object.keys(value), mergeProperty, callback || function () { }) } return this._execute('merge', 2, key, value, callback); }; // // ### function mergeFiles (files, callback) // #### @files {Object|Array} List of files (or settings object) to load. // #### @callback {function} Continuation to respond to when complete. // Merges all `key:value` pairs in the `files` supplied into the // store that is managed by this provider instance. // Provider.prototype.mergeFiles = function (files, callback) { var self = this; common.loadFiles(files, function (err, merged) { return !err ? self.merge(merged, callback) : onError(err); }); }; // // ### function clear (key, callback) // #### @key {string} Key to remove from this instance // #### @callback {function} **Optional** Continuation to respond to when complete. // Removes the value for the specified `key` from this instance. // Provider.prototype.clear = function (key, callback) { return this._execute('clear', 1, key, callback); }; // // ### function load (callback) // #### @callback {function} Continuation to respond to when complete. // Responds with an Object representing all keys associated in this instance. // Provider.prototype.load = function (callback) { // // If we don't have a callback and the current // store is capable of loading synchronously // then do so. // if (!callback && this.store.loadSync) { return this.store.loadSync(); } return !this.store.load ? onError(new Error('nconf store ' + this.store.type + ' has no load() method'), callback) : this.store.load(callback); }; // // ### function save (value, callback) // #### @value {Object} **Optional** 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`. // Provider.prototype.save = function (value, callback) { if (!callback) { callback = value; value = null; // // If we still don't have a callback and the // current store is capable of saving synchronously // then do so. // if (!callback && this.store.saveSync) { return this.store.saveSync(); } } return !this.store.save ? onError(new Error('nconf store ' + this.store.type + ' has no save() method'), callback) : this.store.save(value, callback); }; // // ### function reset (callback) // #### @callback {function} **Optional** Continuation to respond to when complete. // Clears all keys associated with this instance. // Provider.prototype.reset = function (callback) { return this._execute('reset', 0, callback); }; // // ### @private function _execute (action, syncLength, [arguments]) // #### @action {string} Action to execute on `this.store`. // #### @syncLength {number} Function length of the sync version. // #### @arguments {Array} Arguments array to apply to the action // Executes the specified `action` on `this.store`, ensuring a callback supplied // to a synchronous store function is still invoked. // Provider.prototype._execute = function (action, syncLength /* [arguments] */) { var args = Array.prototype.slice.call(arguments, 2), callback, response; if (this.store[action].length > syncLength) { return this.store[action].apply(this.store, args); } callback = typeof args[args.length - 1] === 'function' && args.pop(); response = this.store[action].apply(this.store, args); if (callback) { callback(null, response); } return response; } // // ### getter @useArgv {boolean} // Gets a property indicating if we should wrap calls to `.get` // by checking `optimist.argv`. // Provider.prototype.__defineGetter__('useArgv', function () { return this._useArgv; }); // // ### setter @useArgv {boolean} // Sets a property indicating if we should wrap calls to `.get` // by checking `optimist.argv`. // Provider.prototype.__defineSetter__('useArgv', function (val) { this._useArgv = val || false; if (this._useArgv) { this._argv = this._argv || require('optimist').argv; this.overrides = this.overrides || this._argv; } }); // // Throw the `err` if a callback is not supplied // function onError(err, callback) { if (callback) { return callback(err); } throw err; }