provider.js

Jump To …
nconf/common.html nconf/formats.html nconf/provider.html nconf/stores/file.html nconf/stores/memory.html nconf/stores/system.html nconf/stores.html nconf.html

# provider.js |
--- | ---
¶ |
    /*
     * 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) {
      var self = this;
¶ Setup default options for working with `stores`, `overrides`, `process.env` and `process.argv`. |
      options         = options           || {};
      this._overrides = options.overrides || null;
      this._argv      = options.argv      || false;
      this._env       = options.env       || false;
      this._reserved  = Object.keys(Provider.prototype);
      this._stores    = [];
¶ Add the default `system` store for working with `overrides`, `process.env`, `process.argv` and a simple in-memory objects. |
      this.add('system', options);

      if (options.type) {
        this.add(options.type, options);
      }
      else if (options.store) {
        this.add(options.store.name || options.store.type, options.store);
      }
      else if (options.stores) {
        Object.keys(options.stores).forEach(function (store) {
          self.add(store.name || store.type, store);
        });
      }
    };
¶ ### function use (name, options) #### @type {string} Type of the nconf store to use. #### @options {Object} Options for the store instance. Adds (or replaces) a new store with the specified `name` and `options`. If `options.type` is not set, then `name` will be used instead: provider.use('file'); provider.use('file', { type: 'file', filename: '/path/to/userconf' }) |
    Provider.prototype.use = function (name, options) {
      if (name === 'system') {
        return;
      }
      else if (this._reserved.indexOf(name) !== -1) {
        throw new Error('Cannot use reserved name: ' + name);
      }

      options  = options      || {};
      var type = options.type || name;

      function sameOptions (store) {
        return Object.keys(options).every(function (key) {
          return options[key] === store[key];
        });
      }

      var store = this[name],
          update = store && !sameOptions(store);

      if (!store || update) {
        if (update) {
          this.remove(name);
        }

        this.add(name, options);
      }

      return this;
    };
¶ ### function add (name, options) #### @name {string} Name of the store to add to this instance #### @options {Object} Options for the store to create Adds a new store with the specified `name` and `options`. If `options.type` is not set, then `name` will be used instead: provider.add('memory'); provider.add('userconf', { type: 'file', filename: '/path/to/userconf' }) |
    Provider.prototype.add = function (name, options) {
      if (this._reserved.indexOf(name) !== -1) {
        throw new Error('Cannot use reserved name: ' + name);
      }

      options  = options      || {};
      var type = options.type || name;

      if (Object.keys(stores).indexOf(common.capitalize(type)) === -1) {
        throw new Error('Cannot add store with unknown type: ' + type);
      }

      this[name] = this.create(type, options);
      this._stores.push(name);

      if (this[name].loadSync) {
        this[name].loadSync();
      }
    };
¶ ### function remove (name) #### @name {string} Name of the store to remove from this instance Removes a store with the specified `name` from this instance. Users are allowed to pass in a type argument (e.g. `memory`) as name if this was used in the call to `.add()`. |
    Provider.prototype.remove = function (name) {
      if (this._reserved.indexOf(name) !== -1) {
        throw new Error('Cannot use reserved name: ' + name);
      }
      else if (!this[name]) {
        throw new Error('Cannot remove store that does not exist: ' + name);
      }

      delete this[name];
      this._stores.splice(this._stores.indexOf(name), 1);
    };
¶ ### function create (type, options) #### @type {string} Type of the nconf store to use. #### @options {Object} Options for the store instance. Creates a store of the specified `type` using the specified `options`. |
    Provider.prototype.create = function (type, options) {
      return new stores[common.capitalize(type.toLowerCase())](options);
    };
¶ ### 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 there is no callback we can short-circuit into the default logic for traversing stores. |
      if (!callback) {
        return this._execute('get', 1, key, callback);
      }
¶ Otherwise the asynchronous, hierarchical `get` is slightly more complicated because we do not need to traverse the entire set of stores, but up until there is a defined value. |
      var current = 0,
          self = this,
          response;

      async.whilst(function () {
        return typeof response === 'undefined' && current < self._stores.length;
      }, function (next) {
        var store = self[self._stores[current]];
        current++;

        if (store.get.length >= 2) {
          return store.get(key, function (err, value) {
            if (err) {
              return next(err);
            }

            response = value;
            next();
          });
        }

        response = store.get(key);
        next();
      }, function (err) {
        return err ? callback(err) : callback(null, response);
      });
    };
¶ ### 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 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);
    };
¶ ### 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 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`. If the existing value `key` is not an Object, it will be completely overwritten. 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 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) {
      var self = this;

      function loadStoreSync(name) {
        var store = self[name];

        if (!store.loadSync) {
          throw new Error('nconf store ' + store.type + ' has no loadSync() method');
        }

        return store.loadSync();
      }

      function loadStore(name, next) {
        var store = self[name];

        if (!store.load && !store.loadSync) {
          return next(new Error('nconf store ' + store.type + ' has no load() method'));
        }

        return store.loadSync
          ? next(null, store.loadSync())
          : store.load(next);
      }
¶ If we don't have a callback and the current store is capable of loading synchronously then do so. |
      if (!callback) {
        return common.merge(this._stores.map(loadStoreSync));
      }

      async.map(this._stores, loadStore, function (err, objs) {
        return err ? callback(err) : callback(null, common.merge(objs));
      });
    };
¶ ### 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 && typeof value === 'function') {
        callback = value;
        value = null;
      }

      var self = this;

      function saveStoreSync(name) {
        var store = self[name];

        if (!store.saveSync) {
          throw new Error('nconf store ' + store.type + ' has no saveSync() method');
        }

        return store.saveSync();
      }

      function saveStore(name, next) {
        var store = self[name];

        if (!store.save && !store.saveSync) {
          return next(new Error('nconf store ' + store.type + ' has no save() method'));
        }

        return store.saveSync
          ? next(null, store.saveSync())
          : store.save(next);
      }
¶ If we don't have a callback and the current store is capable of saving synchronously then do so. |
      if (!callback) {
        return common.merge(this._stores.map(saveStoreSync));
      }

      async.map(this._stores, saveStore, function (err, objs) {
        return err ? callback(err) : 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 all stores for this instance, 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 = typeof args[args.length - 1] === 'function' && args.pop(),
          self = this,
          response;

      function runAction (name, next) {
        var store = self[name]

        return store[action].length > syncLength
          ? store[action].apply(store, args.concat(next))
          : next(null, store[action].apply(store, args));
      }

      if (callback) {
        return async.forEach(self._stores, runAction, function (err) {
          return err ? callback(err) : callback();
        });
      }

      this._stores.forEach(function (name) {
        var store = self[name];
        response = store[action].apply(store, args);
      });

      return response;
    }
¶ ### @argv {boolean} Gets or sets a property representing overrides which supercede all other values for this instance. |
    Provider.prototype.__defineSetter__('overrides', function (val) { updateSystem.call(this, 'overrides', val) });
    Provider.prototype.__defineGetter__('overrides', function () { return this._argv });
¶ ### @argv {boolean} Gets or sets a property indicating if we should wrap calls to `.get` by checking `yargs.argv`. Can be a boolean or the pass-thru options for `yargs`. |
    Provider.prototype.__defineSetter__('argv', function (val) { updateSystem.call(this, 'argv', val) });
    Provider.prototype.__defineGetter__('argv', function () { return this._argv });
¶ ### @env {boolean} Gets or sets a property indicating if we should wrap calls to `.get` by checking `process.env`. Can be a boolean or an Array of environment variables to extract. |
    Provider.prototype.__defineSetter__('env', function (val) { updateSystem.call(this, 'env', val) });
    Provider.prototype.__defineGetter__('env', function () { return this._env });
¶ Throw the `err` if a callback is not supplied |
    function onError(err, callback) {
      if (callback) {
        return callback(err);
      }

      throw err;
    }
¶ Helper function for working with the default `system` store for providers. |
    function updateSystem(prop, value) {
      var system = this['system'];

      if (system[prop] === value) {
        return;
      }

      value = value || false;
      this['_' + prop] = value;
      system[prop] = value;
      system.loadSync();
    }