57 changed files with 2785 additions and 2929 deletions
@ -1,2 +0,0 @@ |
|||
node_modules |
|||
usage.js |
@ -1,11 +0,0 @@ |
|||
{ |
|||
"env": { |
|||
"node": true |
|||
}, |
|||
"parserOptions": { |
|||
"ecmaVersion": 6 |
|||
}, |
|||
"rules": { |
|||
"no-unused-vars": "error" |
|||
} |
|||
} |
@ -1 +0,0 @@ |
|||
package-lock.json binary |
@ -1,14 +1,4 @@ |
|||
.DS_Store |
|||
config.json |
|||
test/fixtures/*.json |
|||
!test/fixtures/complete.json |
|||
!test/fixtures/malformed.json |
|||
!test/fixtures/bom.json |
|||
!test/fixtures/no-bom.json |
|||
!test/fixtures/secure.json |
|||
!test/fixtures/secure-iv.json |
|||
node_modules/ |
|||
node_modules/* |
|||
npm-debug.log |
|||
package-lock.json |
|||
coverage |
|||
node_modules |
|||
npm-debug.log |
@ -0,0 +1,177 @@ |
|||
import fs from 'fs' |
|||
import Memory from './stores/memory.mjs' |
|||
|
|||
//
|
|||
// ### function validkeyvalue(key)
|
|||
// #### @key {any} key to check
|
|||
// Return string of key if valid string type key,
|
|||
// otherwise transform into new key containing
|
|||
// the error message
|
|||
export function validkeyvalue(key) { |
|||
let type = typeof(key) |
|||
if (key && type !== 'string' && type !== 'number') { |
|||
return '__invalid_valuetype_of_' + type + '__' |
|||
} |
|||
return null |
|||
} |
|||
|
|||
//
|
|||
// ### function path (key)
|
|||
// #### @key {string} The ':' delimited key to split
|
|||
// Returns a fully-qualified path to a nested nconf key.
|
|||
// If given null or undefined it should return an empty path.
|
|||
// '' should still be respected as a path.
|
|||
//
|
|||
export function path(key, separator) { |
|||
let invalidType = validkeyvalue(key) |
|||
if (invalidType) { |
|||
return [invalidType] |
|||
} |
|||
separator = separator || ':' |
|||
return key == null |
|||
|| key === '' |
|||
? [] |
|||
: key.toString().split(separator) |
|||
} |
|||
|
|||
//
|
|||
// ### function key (arguments)
|
|||
// Returns a `:` joined string from the `arguments`.
|
|||
//
|
|||
export function key(...path) { |
|||
return path.map(function(item) { |
|||
return validkeyvalue(item) || ('' + item) |
|||
}).join(':') |
|||
} |
|||
|
|||
//
|
|||
// ### function key (arguments)
|
|||
// Returns a joined string from the `arguments`,
|
|||
// first argument is the join delimiter.
|
|||
//
|
|||
export function keyed(separator, ...path) { |
|||
return path.map(function(item) { |
|||
return validkeyvalue(item) || ('' + item) |
|||
}).join(separator) |
|||
} |
|||
|
|||
// taken from isobject npm library
|
|||
export function isObject(val) { |
|||
return val != null && typeof val === 'object' && Array.isArray(val) === false |
|||
} |
|||
|
|||
// Return a new recursive deep instance of array of objects
|
|||
// or values to make sure no original object ever get touched
|
|||
export function mergeRecursiveArray(arr) { |
|||
return arr.map(function(item) { |
|||
if (isObject(item)) return mergeRecursive({}, item) |
|||
if (Array.isArray(item)) return mergeRecursiveArray(item) |
|||
return item |
|||
}) |
|||
} |
|||
|
|||
// Recursively merges the child into the parent.
|
|||
export function mergeRecursive(parent, child) { |
|||
Object.keys(child).forEach(key => { |
|||
// Arrays will always overwrite for now
|
|||
if (Array.isArray(child[key])) { |
|||
parent[key] = mergeRecursiveArray(child[key]) |
|||
} else if (child[key] && typeof child[key] === 'object') { |
|||
// We don't wanna support cross merging between array and objects
|
|||
// so we overwrite the old value (at least for now).
|
|||
if (parent[key] && Array.isArray(parent[key])) { |
|||
parent[key] = mergeRecursive({}, child[key]) |
|||
} else { |
|||
parent[key] = mergeRecursive(parent[key] || {}, child[key]) |
|||
} |
|||
} else { |
|||
parent[key] = child[key] |
|||
} |
|||
}) |
|||
|
|||
return parent |
|||
} |
|||
|
|||
|
|||
//
|
|||
// ### function merge (objs)
|
|||
// #### @objs {Array} Array of object literals to merge
|
|||
// Merges the specified `objs` together into a new object.
|
|||
// This differs from the old logic as it does not affect or chagne
|
|||
// any of the objects being merged.
|
|||
//
|
|||
export function merge(orgOut, orgObjs) { |
|||
let out = orgOut |
|||
let objs = orgObjs |
|||
if (objs === undefined) { |
|||
out = {} |
|||
objs = orgOut |
|||
} |
|||
if (!Array.isArray(objs)) { |
|||
throw new Error('merge called with non-array of objects') |
|||
} |
|||
for (let x = 0; x < objs.length; x++) { |
|||
out = mergeRecursive(out, objs[x]) |
|||
} |
|||
return out |
|||
} |
|||
|
|||
//
|
|||
// ### function capitalize (str)
|
|||
// #### @str {string} String to capitalize
|
|||
// Capitalizes the specified `str` if string, otherwise
|
|||
// returns the original object
|
|||
//
|
|||
export function capitalize(str) { |
|||
if (typeof(str) !== 'string' && typeof(str) !== 'number') { |
|||
return str |
|||
} |
|||
let out = str.toString() |
|||
return out && (out[0].toString()).toUpperCase() + out.slice(1) |
|||
} |
|||
|
|||
//
|
|||
// ### function parseValues (any)
|
|||
// #### @any {string} String to parse as json or return as is
|
|||
// try to parse `any` as a json stringified
|
|||
//
|
|||
export function parseValues(value) { |
|||
if (value === 'undefined') { |
|||
return undefined |
|||
} |
|||
|
|||
try { |
|||
return JSON.parse(value) |
|||
} catch (ignore) { |
|||
return value |
|||
} |
|||
} |
|||
|
|||
//
|
|||
// ### function transform(map, fn)
|
|||
// #### @map {object} Object of key/value pairs to apply `fn` to
|
|||
// #### @fn {function} Transformation function that will be applied to every key/value pair
|
|||
// transform a set of key/value pairs and return the transformed result
|
|||
export function transform(map, fn) { |
|||
var pairs = Object.keys(map).map(function(key) { |
|||
var result = fn(key, map[key]) |
|||
|
|||
if (!result) { |
|||
return null |
|||
} else if (result.key) { |
|||
return result |
|||
} |
|||
|
|||
throw new Error('Transform function passed to store returned an invalid format: ' + JSON.stringify(result)) |
|||
}) |
|||
|
|||
|
|||
return pairs |
|||
.filter(function(pair) { |
|||
return pair !== null |
|||
}) |
|||
.reduce(function(accumulator, pair) { |
|||
accumulator[pair.key] = pair.value |
|||
return accumulator |
|||
}, {}) |
|||
} |
@ -1,40 +0,0 @@ |
|||
/* |
|||
* nconf.js: Top-level include for the nconf module |
|||
* |
|||
* (C) 2011, Charlie Robbins and the Contributors. |
|||
* |
|||
*/ |
|||
|
|||
var common = require('./nconf/common'), |
|||
Provider = require('./nconf/provider').Provider; |
|||
|
|||
//
|
|||
// `nconf` is by default an instance of `nconf.Provider`.
|
|||
//
|
|||
var nconf = module.exports = new Provider(); |
|||
|
|||
//
|
|||
// Expose the version from the package.json
|
|||
//
|
|||
nconf.version = require('../package.json').version; |
|||
|
|||
//
|
|||
// Setup all stores as lazy-loaded getters.
|
|||
//
|
|||
['env', 'file', 'literal', 'memory'].forEach(function (store) { |
|||
var name = common.capitalize(store); |
|||
|
|||
nconf.__defineGetter__(name, function () { |
|||
return require('./nconf/stores/' + store)[name]; |
|||
}); |
|||
}); |
|||
|
|||
//
|
|||
// Expose the various components included with nconf
|
|||
//
|
|||
nconf.key = common.key; |
|||
nconf.path = common.path; |
|||
nconf.loadFiles = common.loadFiles; |
|||
nconf.loadFilesSync = common.loadFilesSync; |
|||
nconf.formats = require('./nconf/formats'); |
|||
nconf.Provider = Provider; |
@ -0,0 +1,181 @@ |
|||
import fs from 'fs' |
|||
import { fileURLToPath } from 'url' |
|||
import path from 'path' |
|||
import * as common from './common.mjs' |
|||
import Literal from './stores/literal.mjs' |
|||
import Memory from './stores/memory.mjs' |
|||
import File from './stores/file.mjs' |
|||
import Env from './stores/env.mjs' |
|||
import Argv from './stores/argv.mjs' |
|||
|
|||
const __dirname = path.dirname(fileURLToPath(import.meta.url)) |
|||
const pckg = JSON.parse(fs.readFileSync(path.resolve(path.join(__dirname, '../package.json')))) |
|||
|
|||
const AvailableStores = [ |
|||
['memory', Memory], |
|||
['file', File], |
|||
['defaults', Literal], |
|||
['overrides', Literal], |
|||
['literal', Literal], |
|||
['env', Env], |
|||
['argv', Argv], |
|||
] |
|||
|
|||
function Nconf(options) { |
|||
let opts = options || {} |
|||
this.sources = [] |
|||
this.using = new Map() |
|||
this.version = pckg.version |
|||
this.init() |
|||
} |
|||
|
|||
Nconf.prototype.key = common.key |
|||
Nconf.prototype.path = common.path |
|||
|
|||
Nconf.prototype.init = function() { |
|||
AvailableStores.forEach((storeType) => { |
|||
let nameCapital = common.capitalize(storeType[0]) |
|||
let nameLower = storeType[0].toLowerCase() |
|||
|
|||
Object.defineProperty(this, nameCapital, { |
|||
value: storeType[1], |
|||
writable: false, |
|||
enumerable: true, |
|||
}) |
|||
Object.defineProperty(this, nameLower, { |
|||
value: function(leName, leOpts) { |
|||
let name = leName |
|||
let options = leOpts || {} |
|||
if (typeof(name) !== 'string') { |
|||
name = nameLower |
|||
options = leName || {} |
|||
} |
|||
this.add(name, new this[nameCapital](options)) |
|||
return this |
|||
}, |
|||
writable: false, |
|||
enumerable: true, |
|||
}) |
|||
}) |
|||
} |
|||
|
|||
Nconf.prototype.any = function(...items) { |
|||
let check = items |
|||
if (items.length === 1 && Array.isArray(items[0])) { |
|||
check = items[0] |
|||
} |
|||
for (let i = 0; i < check.length; i++) { |
|||
let found = this.get(check[i]) |
|||
if (found) return found |
|||
} |
|||
return undefined |
|||
} |
|||
Nconf.prototype.get = function(key) { |
|||
let out = [] |
|||
for (let i = 0; i < this.sources.length; i++) { |
|||
let found = this.sources[i].get(key) |
|||
if (found && !out.length && (Array.isArray(found) || typeof(found) !== 'object')) { |
|||
return found |
|||
} |
|||
if (found) { |
|||
out.push(found) |
|||
} |
|||
} |
|||
if (!out.length) return undefined |
|||
return common.merge(out.reverse()) |
|||
} |
|||
Nconf.prototype.set = function(key, value) { |
|||
for (let i = 0; i < this.sources.length; i++) { |
|||
if (!this.sources[i].readOnly) { |
|||
if (this.sources[i].set(key, value)) |
|||
return this |
|||
} |
|||
} |
|||
return false |
|||
} |
|||
|
|||
Nconf.prototype.clear = function(key) { |
|||
for (let i = 0; i < this.sources.length; i++) { |
|||
this.sources[i].clear(key) |
|||
} |
|||
if (this.get(key)) { |
|||
return false |
|||
} |
|||
return this |
|||
} |
|||
Nconf.prototype.load = function() { |
|||
for (let i = 0; i < this.sources.length; i++) { |
|||
if (typeof(this.sources[i].load) === 'function') { |
|||
this.sources[i].load() |
|||
} |
|||
} |
|||
} |
|||
Nconf.prototype.save = function() { |
|||
for (let i = 0; i < this.sources.length; i++) { |
|||
if (typeof(this.sources[i].save) === 'function') { |
|||
this.sources[i].save() |
|||
} |
|||
} |
|||
} |
|||
Nconf.prototype.reset = function() { |
|||
throw new Error('Deprecated, create new instance instead') |
|||
} |
|||
|
|||
Nconf.prototype.required = function(...items) { |
|||
let check = items |
|||
if (items.length === 1 && Array.isArray(items[0])) { |
|||
check = items[0] |
|||
} |
|||
let missing = [] |
|||
for (let i = 0; i < check.length; i++) { |
|||
if (!this.get(check[i])) { |
|||
missing.push(check[i]) |
|||
} |
|||
} |
|||
|
|||
if (missing.length) { |
|||
throw new Error('Missing required keys: ' + missing.join(', ')); |
|||
} |
|||
|
|||
return this |
|||
} |
|||
|
|||
Nconf.prototype.add = function(name, store) { |
|||
let oldStore = this.using.get(name) |
|||
|
|||
if (typeof(store.load) === 'function') { |
|||
store.load() |
|||
} |
|||
|
|||
if (oldStore) { |
|||
this.sources.splice(this.sources.indexOf(oldStore), 1) |
|||
this.using.delete(name) |
|||
} |
|||
this.using.set(name, store) |
|||
this.sources.push(store) |
|||
} |
|||
|
|||
Nconf.prototype.use = function(name) { |
|||
return this.using.get(name) |
|||
} |
|||
|
|||
Nconf.register = function(name, val) { |
|||
AvailableStores.push([name, val]) |
|||
let nameCapital = common.capitalize(name) |
|||
Object.defineProperty(Nconf, nameCapital, { |
|||
value: val, |
|||
writable: false, |
|||
enumerable: true, |
|||
}) |
|||
} |
|||
|
|||
AvailableStores.forEach((storeType) => { |
|||
let nameCapital = common.capitalize(storeType[0]) |
|||
Object.defineProperty(Nconf, nameCapital, { |
|||
value: storeType[1], |
|||
writable: false, |
|||
enumerable: true, |
|||
}) |
|||
}) |
|||
|
|||
export default Nconf |
@ -1,175 +0,0 @@ |
|||
/* |
|||
* utils.js: Utility functions for the nconf module. |
|||
* |
|||
* (C) 2011, Charlie Robbins and the Contributors. |
|||
* |
|||
*/ |
|||
|
|||
var fs = require('fs'), |
|||
async = require('async'), |
|||
formats = require('./formats'), |
|||
Memory = require('./stores/memory').Memory; |
|||
|
|||
var common = exports; |
|||
|
|||
//
|
|||
// ### function path (key)
|
|||
// #### @key {string} The ':' delimited key to split
|
|||
// Returns a fully-qualified path to a nested nconf key.
|
|||
// If given null or undefined it should return an empty path.
|
|||
// '' should still be respected as a path.
|
|||
//
|
|||
common.path = function (key, separator) { |
|||
separator = separator || ':'; |
|||
return key == null ? [] : key.split(separator); |
|||
}; |
|||
|
|||
//
|
|||
// ### function key (arguments)
|
|||
// Returns a `:` joined string from the `arguments`.
|
|||
//
|
|||
common.key = function () { |
|||
return Array.prototype.slice.call(arguments).join(':'); |
|||
}; |
|||
|
|||
//
|
|||
// ### function key (arguments)
|
|||
// Returns a joined string from the `arguments`,
|
|||
// first argument is the join delimiter.
|
|||
//
|
|||
common.keyed = function () { |
|||
return Array.prototype.slice.call(arguments, 1).join(arguments[0]); |
|||
}; |
|||
|
|||
//
|
|||
// ### function loadFiles (files, callback)
|
|||
// #### @files {Object|Array} List of files (or settings object) to load.
|
|||
// #### @callback {function} Continuation to respond to when complete.
|
|||
// Loads all the data in the specified `files`.
|
|||
//
|
|||
common.loadFiles = function (files, callback) { |
|||
if (!files) { |
|||
return callback(null, {}); |
|||
} |
|||
|
|||
var options = Array.isArray(files) ? { files: files } : files; |
|||
|
|||
//
|
|||
// Set the default JSON format if not already
|
|||
// specified
|
|||
//
|
|||
options.format = options.format || formats.json; |
|||
|
|||
function parseFile (file, next) { |
|||
fs.readFile(file, function (err, data) { |
|||
return !err |
|||
? next(null, options.format.parse(data.toString())) |
|||
: next(err); |
|||
}); |
|||
} |
|||
|
|||
async.map(options.files, parseFile, function (err, objs) { |
|||
return err ? callback(err) : callback(null, common.merge(objs)); |
|||
}); |
|||
}; |
|||
|
|||
//
|
|||
// ### function loadFilesSync (files)
|
|||
// #### @files {Object|Array} List of files (or settings object) to load.
|
|||
// Loads all the data in the specified `files` synchronously.
|
|||
//
|
|||
common.loadFilesSync = function (files) { |
|||
if (!files) { |
|||
return; |
|||
} |
|||
|
|||
//
|
|||
// Set the default JSON format if not already
|
|||
// specified
|
|||
//
|
|||
var options = Array.isArray(files) ? { files: files } : files; |
|||
options.format = options.format || formats.json; |
|||
|
|||
return common.merge(options.files.map(function (file) { |
|||
return options.format.parse(fs.readFileSync(file, 'utf8')); |
|||
})); |
|||
}; |
|||
|
|||
//
|
|||
// ### function merge (objs)
|
|||
// #### @objs {Array} Array of object literals to merge
|
|||
// Merges the specified `objs` using a temporary instance
|
|||
// of `stores.Memory`.
|
|||
//
|
|||
common.merge = function (objs) { |
|||
var store = new Memory(); |
|||
|
|||
objs.forEach(function (obj) { |
|||
Object.keys(obj).forEach(function (key) { |
|||
store.merge(key, obj[key]); |
|||
}); |
|||
}); |
|||
|
|||
return store.store; |
|||
}; |
|||
|
|||
//
|
|||
// ### function capitalize (str)
|
|||
// #### @str {string} String to capitalize
|
|||
// Capitalizes the specified `str`.
|
|||
//
|
|||
common.capitalize = function (str) { |
|||
return str && str[0].toUpperCase() + str.slice(1); |
|||
}; |
|||
|
|||
//
|
|||
// ### function parseValues (any)
|
|||
// #### @any {string} String to parse as native data-type or return as is
|
|||
// try to parse `any` as a native data-type
|
|||
//
|
|||
common.parseValues = function (value) { |
|||
var val = value; |
|||
|
|||
try { |
|||
val = JSON.parse(value); |
|||
} catch (ignore) { |
|||
// Check for any other well-known strings that should be "parsed"
|
|||
if (value === 'undefined'){ |
|||
val = void 0; |
|||
} |
|||
} |
|||
|
|||
return val; |
|||
}; |
|||
|
|||
//
|
|||
// ### function transform(map, fn)
|
|||
// #### @map {object} Object of key/value pairs to apply `fn` to
|
|||
// #### @fn {function} Transformation function that will be applied to every key/value pair
|
|||
// transform a set of key/value pairs and return the transformed result
|
|||
common.transform = function(map, fn) { |
|||
var pairs = Object.keys(map).map(function(key) { |
|||
var obj = { key: key, value: map[key]}; |
|||
var result = fn.call(null, obj); |
|||
|
|||
if (!result) { |
|||
return null; |
|||
} else if (result.key) { |
|||
return result; |
|||
} |
|||
|
|||
var error = new Error('Transform function passed to store returned an invalid format: ' + JSON.stringify(result)); |
|||
error.name = 'RuntimeError'; |
|||
throw error; |
|||
}); |
|||
|
|||
|
|||
return pairs |
|||
.filter(function(pair) { |
|||
return pair !== null; |
|||
}) |
|||
.reduce(function(accumulator, pair) { |
|||
accumulator[pair.key] = pair.value; |
|||
return accumulator; |
|||
}, {}); |
|||
} |
@ -1,28 +0,0 @@ |
|||
/* |
|||
* formats.js: Default formats supported by nconf |
|||
* |
|||
* (C) 2011, Charlie Robbins and the Contributors. |
|||
* |
|||
*/ |
|||
|
|||
var ini = require('ini'); |
|||
|
|||
var formats = exports; |
|||
|
|||
//
|
|||
// ### @json
|
|||
// Standard JSON format which pretty prints `.stringify()`.
|
|||
//
|
|||
formats.json = { |
|||
stringify: function (obj, replacer, spacing) { |
|||
return JSON.stringify(obj, replacer || null, spacing || 2) |
|||
}, |
|||
parse: JSON.parse |
|||
}; |
|||
|
|||
//
|
|||
// ### @ini
|
|||
// Standard INI format supplied from the `ini` module
|
|||
// http://en.wikipedia.org/wiki/INI_file
|
|||
//
|
|||
formats.ini = ini; |
@ -1,655 +0,0 @@ |
|||
/* |
|||
* provider.js: Abstraction providing an interface into pluggable configuration storage. |
|||
* |
|||
* (C) 2011, Charlie Robbins and the Contributors. |
|||
* |
|||
*/ |
|||
|
|||
var async = require('async'), |
|||
common = require('./common'); |
|||
|
|||
//
|
|||
// ### 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) { |
|||
//
|
|||
// Setup default options for working with `stores`,
|
|||
// `overrides`, `process.env`.
|
|||
//
|
|||
options = options || {}; |
|||
this.stores = {}; |
|||
this.sources = []; |
|||
this.init(options); |
|||
}; |
|||
|
|||
//
|
|||
// Define wrapper functions for using basic stores
|
|||
// in this instance
|
|||
//
|
|||
|
|||
['env'].forEach(function (type) { |
|||
Provider.prototype[type] = function () { |
|||
var args = [type].concat(Array.prototype.slice.call(arguments)); |
|||
return this.add.apply(this, args); |
|||
}; |
|||
}); |
|||
|
|||
//
|
|||
// ### function file (key, options)
|
|||
// #### @key {string|Object} Fully qualified options, name of file store, or path.
|
|||
// #### @path {string|Object} **Optional** Full qualified options, or path.
|
|||
// Adds a new `File` store to this instance. Accepts the following options
|
|||
//
|
|||
// nconf.file({ file: '.jitsuconf', dir: process.env.HOME, search: true });
|
|||
// nconf.file('path/to/config/file');
|
|||
// nconf.file('userconfig', 'path/to/config/file');
|
|||
// nconf.file('userconfig', { file: '.jitsuconf', search: true });
|
|||
//
|
|||
Provider.prototype.file = function (key, options) { |
|||
if (arguments.length == 1) { |
|||
options = typeof key === 'string' ? { file: key } : key; |
|||
key = 'file'; |
|||
} |
|||
else { |
|||
options = typeof options === 'string' |
|||
? { file: options } |
|||
: options; |
|||
} |
|||
|
|||
options.type = 'file'; |
|||
return this.add(key, options); |
|||
}; |
|||
|
|||
//
|
|||
// Define wrapper functions for using
|
|||
// overrides and defaults
|
|||
//
|
|||
['defaults', 'overrides'].forEach(function (type) { |
|||
Provider.prototype[type] = function (options) { |
|||
options = options || {}; |
|||
if (!options.type) { |
|||
options.type = 'literal'; |
|||
} |
|||
|
|||
return this.add(type, options); |
|||
}; |
|||
}); |
|||
|
|||
//
|
|||
// ### 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) { |
|||
options = options || {}; |
|||
|
|||
function sameOptions (store) { |
|||
return Object.keys(options).every(function (key) { |
|||
return options[key] === store[key]; |
|||
}); |
|||
} |
|||
|
|||
var store = this.stores[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, usage) { |
|||
options = options || {}; |
|||
var type = options.type || name; |
|||
|
|||
if (!require('../nconf')[common.capitalize(type)]) { |
|||
throw new Error('Cannot add store with unknown type: ' + type); |
|||
} |
|||
|
|||
this.stores[name] = this.create(type, options, usage); |
|||
|
|||
if (this.stores[name].loadSync) { |
|||
this.stores[name].loadSync(); |
|||
} |
|||
|
|||
return this; |
|||
}; |
|||
|
|||
//
|
|||
// ### 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) { |
|||
delete this.stores[name]; |
|||
return this; |
|||
}; |
|||
|
|||
//
|
|||
// ### 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, usage) { |
|||
return new (require('../nconf')[common.capitalize(type.toLowerCase())])(options, usage); |
|||
}; |
|||
|
|||
//
|
|||
// ### function init (options)
|
|||
// #### @options {Object} Options to initialize this instance with.
|
|||
// Initializes this instance with additional `stores` or `sources` in the
|
|||
// `options` supplied.
|
|||
//
|
|||
Provider.prototype.init = function (options) { |
|||
var self = this; |
|||
|
|||
//
|
|||
// Add any stores passed in through the options
|
|||
// to this instance.
|
|||
//
|
|||
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 (name) { |
|||
var store = options.stores[name]; |
|||
self.add(store.name || name || store.type, store); |
|||
}); |
|||
} |
|||
|
|||
//
|
|||
// Add any read-only sources to this instance
|
|||
//
|
|||
if (options.source) { |
|||
this.sources.push(this.create(options.source.type || options.source.name, options.source)); |
|||
} |
|||
else if (options.sources) { |
|||
Object.keys(options.sources).forEach(function (name) { |
|||
var source = options.sources[name]; |
|||
self.sources.push(self.create(source.type || source.name || name, source)); |
|||
}); |
|||
} |
|||
}; |
|||
|
|||
//
|
|||
// ### 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 (typeof key === 'function') { |
|||
// Allow a * key call to be made
|
|||
callback = key; |
|||
key = null; |
|||
} |
|||
|
|||
//
|
|||
// 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, |
|||
names = Object.keys(this.stores).filter(x => x !== 'mock'), |
|||
self = this, |
|||
response, |
|||
mergeObjs = []; |
|||
|
|||
async.whilst(function () { |
|||
return typeof response === 'undefined' && current < names.length; |
|||
}, function (next) { |
|||
var store = self.stores[names[current]]; |
|||
current++; |
|||
|
|||
if (store.get.length >= 2) { |
|||
return store.get(key, function (err, value) { |
|||
if (err) { |
|||
return next(err); |
|||
} |
|||
|
|||
response = value; |
|||
|
|||
// Merge objects if necessary
|
|||
if (response && typeof response === 'object' && !Array.isArray(response)) { |
|||
mergeObjs.push(response); |
|||
response = undefined; |
|||
} |
|||
|
|||
next(); |
|||
}); |
|||
} |
|||
|
|||
response = store.get(key); |
|||
|
|||
// Merge objects if necessary
|
|||
if (response && typeof response === 'object' && !Array.isArray(response)) { |
|||
mergeObjs.push(response); |
|||
response = undefined; |
|||
} |
|||
|
|||
next(); |
|||
}, function (err) { |
|||
if (!err && mergeObjs.length) { |
|||
response = common.merge(mergeObjs.reverse()); |
|||
} |
|||
return err ? callback(err) : callback(null, response); |
|||
}); |
|||
}; |
|||
|
|||
|
|||
//
|
|||
// ### function any (keys, callback)
|
|||
// #### @keys {array|string...} Array of keys to query, or a variable list of strings
|
|||
// #### @callback {function} **Optional** Continuation to respond to when complete.
|
|||
// Retrieves the first truthy value (if any) for the specified list of keys.
|
|||
//
|
|||
Provider.prototype.any = function (keys, callback) { |
|||
|
|||
if (!Array.isArray(keys)) { |
|||
keys = Array.prototype.slice.call(arguments); |
|||
if (keys.length > 0 && typeof keys[keys.length - 1] === 'function') { |
|||
callback = keys.pop(); |
|||
} else { |
|||
callback = null; |
|||
} |
|||
} |
|||
|
|||
//
|
|||
// If there is no callback, use the short-circuited "get"
|
|||
// on each key in turn.
|
|||
//
|
|||
if (!callback) { |
|||
var val; |
|||
for (var i = 0; i < keys.length; ++i) { |
|||
val = this._execute('get', 1, keys[i], callback); |
|||
if (val) { |
|||
return val; |
|||
} |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
var keyIndex = 0, |
|||
result, |
|||
self = this; |
|||
|
|||
async.whilst(function() { |
|||
return !result && keyIndex < keys.length; |
|||
}, function(next) { |
|||
var key = keys[keyIndex]; |
|||
keyIndex++; |
|||
|
|||
self.get(key, function(err, v) { |
|||
if (err) { |
|||
next(err); |
|||
} else { |
|||
result = v; |
|||
next(); |
|||
} |
|||
}); |
|||
}, function(err) { |
|||
return err ? callback(err) : 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} **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 required (keys)
|
|||
// #### @keys {array} List of keys
|
|||
// Throws an error if any of `keys` has no value, otherwise returns `true`
|
|||
Provider.prototype.required = function (keys) { |
|||
if (!Array.isArray(keys)) { |
|||
throw new Error('Incorrect parameter, array expected'); |
|||
} |
|||
|
|||
var missing = []; |
|||
keys.forEach(function(key) { |
|||
if (typeof this.get(key) === 'undefined') { |
|||
missing.push(key); |
|||
} |
|||
}, this); |
|||
|
|||
if (missing.length) { |
|||
throw new Error('Missing required keys: ' + missing.join(', ')); |
|||
} else { |
|||
return this; |
|||
} |
|||
|
|||
}; |
|||
|
|||
//
|
|||
// ### 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`.
|
|||
//
|
|||
// 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 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 getStores () { |
|||
var stores = Object.keys(self.stores); |
|||
stores.reverse(); |
|||
return stores.map(function (name) { |
|||
return self.stores[name]; |
|||
}); |
|||
} |
|||
|
|||
function loadStoreSync(store) { |
|||
if (!store.loadSync) { |
|||
throw new Error('nconf store "' + store.type + '" has no loadSync() method'); |
|||
} |
|||
|
|||
return store.loadSync(); |
|||
} |
|||
|
|||
function loadStore(store, next) { |
|||
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); |
|||
} |
|||
|
|||
function loadBatch (targets, done) { |
|||
if (!done) { |
|||
return common.merge(targets.map(loadStoreSync)); |
|||
} |
|||
|
|||
async.map(targets, loadStore, function (err, objs) { |
|||
return err ? done(err) : done(null, common.merge(objs)); |
|||
}); |
|||
} |
|||
|
|||
function mergeSources (data) { |
|||
//
|
|||
// If `data` was returned then merge it into
|
|||
// the system store.
|
|||
//
|
|||
if (data && typeof data === 'object') { |
|||
self.use('sources', { |
|||
type: 'literal', |
|||
store: data |
|||
}); |
|||
} |
|||
} |
|||
|
|||
function loadSources () { |
|||
var sourceHierarchy = self.sources.splice(0); |
|||
sourceHierarchy.reverse(); |
|||
|
|||
//
|
|||
// If we don't have a callback and the current
|
|||
// store is capable of loading synchronously
|
|||
// then do so.
|
|||
//
|
|||
if (!callback) { |
|||
mergeSources(loadBatch(sourceHierarchy)); |
|||
return loadBatch(getStores()); |
|||
} |
|||
|
|||
loadBatch(sourceHierarchy, function (err, data) { |
|||
if (err) { |
|||
return callback(err); |
|||
} |
|||
|
|||
mergeSources(data); |
|||
return loadBatch(getStores(), callback); |
|||
}); |
|||
} |
|||
|
|||
return self.sources.length |
|||
? loadSources() |
|||
: loadBatch(getStores(), callback); |
|||
}; |
|||
|
|||
//
|
|||
// ### function save (callback)
|
|||
// #### @callback {function} **optional** Continuation to respond to when
|
|||
// complete.
|
|||
// Instructs each provider to save. If a callback is provided, we will attempt
|
|||
// asynchronous saves on the providers, falling back to synchronous saves if
|
|||
// this isn't possible. If a provider does not know how to save, it will be
|
|||
// ignored. Returns an object consisting of all of the data which was
|
|||
// actually saved.
|
|||
//
|
|||
Provider.prototype.save = function (value, callback) { |
|||
if (!callback && typeof value === 'function') { |
|||
callback = value; |
|||
value = null; |
|||
} |
|||
|
|||
var self = this, |
|||
names = Object.keys(this.stores); |
|||
|
|||
function saveStoreSync(memo, name) { |
|||
var store = self.stores[name]; |
|||
|
|||
//
|
|||
// If the `store` doesn't have a `saveSync` method,
|
|||
// just ignore it and continue.
|
|||
//
|
|||
if (store.saveSync) { |
|||
var ret = store.saveSync(); |
|||
if (typeof ret == 'object' && ret !== null) { |
|||
memo.push(ret); |
|||
} |
|||
} |
|||
return memo; |
|||
} |
|||
|
|||
function saveStore(memo, name, next) { |
|||
var store = self.stores[name]; |
|||
|
|||
//
|
|||
// If the `store` doesn't have a `save` or saveSync`
|
|||
// method(s), just ignore it and continue.
|
|||
//
|
|||
|
|||
if (store.save) { |
|||
return store.save(value, function (err, data) { |
|||
if (err) { |
|||
return next(err); |
|||
} |
|||
|
|||
if (typeof data == 'object' && data !== null) { |
|||
memo.push(data); |
|||
} |
|||
|
|||
next(null, memo); |
|||
}); |
|||
} |
|||
else if (store.saveSync) { |
|||
memo.push(store.saveSync()); |
|||
} |
|||
|
|||
next(null, memo); |
|||
} |
|||
|
|||
//
|
|||
// If we don't have a callback and the current
|
|||
// store is capable of saving synchronously
|
|||
// then do so.
|
|||
//
|
|||
if (!callback) { |
|||
return common.merge(names.reduce(saveStoreSync, [])); |
|||
} |
|||
|
|||
async.reduce(names, [], saveStore, function (err, objs) { |
|||
return err ? callback(err) : callback(null, common.merge(objs)); |
|||
}); |
|||
}; |
|||
|
|||
//
|
|||
// ### @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(), |
|||
destructive = ['set', 'clear', 'merge', 'reset'].indexOf(action) !== -1, |
|||
self = this, |
|||
response, |
|||
mergeObjs = [], |
|||
keys = Object.keys(this.stores).filter(x => x !== 'mock'); |
|||
|
|||
|
|||
function runAction (name, next) { |
|||
var store = self.stores[name]; |
|||
|
|||
if (destructive && store.readOnly) { |
|||
return next(); |
|||
} |
|||
|
|||
return store[action].length > syncLength |
|||
? store[action].apply(store, args.concat(next)) |
|||
: next(null, store[action].apply(store, args)); |
|||
} |
|||
|
|||
if (callback) { |
|||
return async.forEach(keys, runAction, function (err) { |
|||
return err ? callback(err) : callback(); |
|||
}); |
|||
} |
|||
|
|||
keys.forEach(function (name) { |
|||
if (typeof response === 'undefined') { |
|||
var store = self.stores[name]; |
|||
|
|||
if (destructive && store.readOnly) { |
|||
return; |
|||
} |
|||
|
|||
response = store[action].apply(store, args); |
|||
|
|||
// Merge objects if necessary
|
|||
if (response && action === 'get' && typeof response === 'object' && !Array.isArray(response)) { |
|||
mergeObjs.push(response); |
|||
response = undefined; |
|||
} |
|||
} |
|||
}); |
|||