Finished completely rewriting both library and unit tests
This commit is contained in:
parent
1104c0d3ad
commit
1014cdec86
57 changed files with 2785 additions and 2929 deletions
|
@ -23,26 +23,10 @@ commands:
|
|||
key: v{{ .Environment.CIRCLE_CACHE_VERSION }}-{{ arch }}-npm-cache-{{ .Branch }}-{{ .Environment.CIRCLE_JOB }}-{{ checksum "package-lock.json" }}
|
||||
paths:
|
||||
- ~/.npm/_cacache
|
||||
coverage:
|
||||
steps:
|
||||
- run:
|
||||
command: npm run cover
|
||||
- run:
|
||||
command: npm run coveralls
|
||||
jobs:
|
||||
node-v8:
|
||||
node-v14:
|
||||
docker:
|
||||
- image: node:8
|
||||
steps:
|
||||
- test-nodejs
|
||||
node-v10:
|
||||
docker:
|
||||
- image: node:10
|
||||
steps:
|
||||
- test-nodejs
|
||||
node-v12:
|
||||
docker:
|
||||
- image: node:12
|
||||
- image: node:14
|
||||
steps:
|
||||
- test-nodejs
|
||||
|
||||
|
@ -50,6 +34,4 @@ workflows:
|
|||
version: 2
|
||||
node-multi-build:
|
||||
jobs:
|
||||
- node-v8
|
||||
- node-v10
|
||||
- node-v12
|
||||
- node-v14
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
node_modules
|
||||
usage.js
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6
|
||||
},
|
||||
"rules": {
|
||||
"no-unused-vars": "error"
|
||||
}
|
||||
}
|
1
.gitattributes
vendored
1
.gitattributes
vendored
|
@ -1 +0,0 @@
|
|||
package-lock.json binary
|
14
.gitignore
vendored
14
.gitignore
vendored
|
@ -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
|
|
@ -1,3 +1,9 @@
|
|||
v2.0.0 / Tue, 2 Jun 2020
|
||||
========================
|
||||
|
||||
Completely redesigned and re-written with zero dependencies among other things.
|
||||
|
||||
|
||||
v1.0.0 / Tue, 2 Jun 2020
|
||||
========================
|
||||
|
||||
|
|
121
README.md
121
README.md
|
@ -1,14 +1,17 @@
|
|||
# nconf-lite
|
||||
|
||||
Hierarchical node.js configuration with files, environment variables, and atomic object merging.
|
||||
Nconf-lite is a complete re-written design of original nconf with zero dependancy, tiny and fast while maintaining most if not all of the documented features of the old nconf.
|
||||
|
||||
This is a fork of nconf without the bloated yargs dependancy.
|
||||
It is a hierarchical node.js configuration with files, environment variables, and atomic object merging.
|
||||
|
||||
Compared to nconf running at 952KB with over 220 files *installed*, nconf-lite is clocking at measly 42KB with only 11 files of easily reviewable code and a ton more unit test, testing every micro functionality.
|
||||
|
||||
## Example
|
||||
Using nconf is easy; it is designed to be a simple key-value store with support for both local and remote storage. Keys are namespaced and delimited by `:`. Let's dive right into sample usage:
|
||||
|
||||
``` js
|
||||
var nconf = require('nconf');
|
||||
import Nconf from 'nconf-lite'
|
||||
const nconf = new Nconf()
|
||||
|
||||
//
|
||||
// Setup nconf to use (in-order):
|
||||
|
@ -35,11 +38,7 @@ Using nconf is easy; it is designed to be a simple key-value store with support
|
|||
//
|
||||
// Save the configuration object to disk
|
||||
//
|
||||
nconf.save(function (err) {
|
||||
require('fs').readFile('path/to/your/config.json', function (err, data) {
|
||||
console.dir(JSON.parse(data.toString()))
|
||||
});
|
||||
});
|
||||
nconf.save()
|
||||
```
|
||||
|
||||
If you run the above script:
|
||||
|
@ -67,7 +66,8 @@ Configuration management can get complicated very quickly for even trivial appli
|
|||
A sane default for this could be:
|
||||
|
||||
``` js
|
||||
var nconf = require('nconf');
|
||||
import Nconf from 'nconf-lite'
|
||||
const nconf = new Nconf()
|
||||
|
||||
//
|
||||
// 1. any overrides
|
||||
|
@ -92,16 +92,6 @@ A sane default for this could be:
|
|||
//
|
||||
nconf.file('custom', '/path/to/config.json');
|
||||
|
||||
//
|
||||
// Or searching from a base directory.
|
||||
// Note: `name` is optional.
|
||||
//
|
||||
nconf.file(name, {
|
||||
file: 'config.json',
|
||||
dir: 'search/from/here',
|
||||
search: true
|
||||
});
|
||||
|
||||
//
|
||||
// 5. Any default values
|
||||
//
|
||||
|
@ -112,17 +102,6 @@ A sane default for this could be:
|
|||
|
||||
## API Documentation
|
||||
|
||||
The top-level of `nconf` is an instance of the `nconf.Provider` abstracts this all for you into a simple API.
|
||||
|
||||
### nconf.add(name, options)
|
||||
Adds a new store with the specified `name` and `options`. If `options.type` is not set, then `name` will be used instead:
|
||||
|
||||
``` js
|
||||
nconf.add('supplied', { type: 'literal', store: { 'some': 'config' });
|
||||
nconf.add('user', { type: 'file', file: '/path/to/userconf.json' });
|
||||
nconf.add('global', { type: 'file', file: '/path/to/globalconf.json' });
|
||||
```
|
||||
|
||||
### nconf.any(names, callback)
|
||||
Given a set of key names, gets the value of the first key found to be truthy. The key names can be given as separate arguments
|
||||
or as an array. If the last argument is a function, it will be called with the result; otherwise, the value is returned.
|
||||
|
@ -131,36 +110,21 @@ or as an array. If the last argument is a function, it will be called with the r
|
|||
//
|
||||
// Get one of 'NODEJS_PORT' and 'PORT' as a return value
|
||||
//
|
||||
var port = nconf.any('NODEJS_PORT', 'PORT');
|
||||
|
||||
//
|
||||
// Get one of 'NODEJS_IP' and 'IPADDRESS' using a callback
|
||||
//
|
||||
nconf.any(['NODEJS_IP', 'IPADDRESS'], function(err, value) {
|
||||
console.log('Connect to IP address ' + value);
|
||||
});
|
||||
let port = nconf.any('NODEJS_PORT', 'PORT');
|
||||
```
|
||||
|
||||
### nconf.use(name, options)
|
||||
Similar to `nconf.add`, except that it can replace an existing store if new options are provided
|
||||
### nconf.use(name)
|
||||
Fetch a specific store with the specified name.
|
||||
|
||||
``` js
|
||||
//
|
||||
// Load a file store onto nconf with the specified settings
|
||||
//
|
||||
nconf.use('file', { file: '/path/to/some/config-file.json' });
|
||||
|
||||
nconf.file('custom', '/path/to/config.json');
|
||||
//
|
||||
// Replace the file store with new settings
|
||||
// Grab the instance and set it to be readonly
|
||||
//
|
||||
nconf.use('file', { file: 'path/to/a-new/config-file.json' });
|
||||
```
|
||||
|
||||
### nconf.remove(name)
|
||||
Removes the store with the specified `name.` The configuration stored at that level will no longer be used for lookup(s).
|
||||
|
||||
``` js
|
||||
nconf.remove('file');
|
||||
nconf.use('custom').readOnly = true
|
||||
```
|
||||
|
||||
### nconf.required(keys)
|
||||
|
@ -185,10 +149,7 @@ config
|
|||
.file( 'oauth', path.resolve( 'configs', 'oauth', config.get( 'OAUTH:MODE' ) + '.json' ) )
|
||||
.file( 'app', path.resolve( 'configs', 'app.json' ) )
|
||||
.required([ 'LOGS_MODE']) // here you should haveLOGS_MODE, otherwise throw an error
|
||||
.add( 'logs', {
|
||||
type: 'literal',
|
||||
store: require( path.resolve( 'configs', 'logs', config.get( 'LOGS_MODE' ) + '.js') )
|
||||
} )
|
||||
.literal( 'logs', require( path.resolve( 'configs', 'logs', config.get( 'LOGS_MODE' ) + '.js') ))
|
||||
.defaults( defaults );
|
||||
```
|
||||
|
||||
|
@ -216,9 +177,6 @@ If this option is enabled, all calls to `nconf.get()` must pass in a lowercase s
|
|||
Attempt to parse well-known values (e.g. 'false', 'true', 'null', 'undefined', '3', '5.1' and JSON values)
|
||||
into their proper types. If a value cannot be parsed, it will remain a string.
|
||||
|
||||
#### `readOnly: {true|false}` (defaultL `true`)
|
||||
Allow values in the env store to be updated in the future. The default is to not allow items in the env store to be updated.
|
||||
|
||||
##### `transform: function(obj)`
|
||||
Pass each key/value pair to the specified function for transformation.
|
||||
|
||||
|
@ -248,9 +206,9 @@ If the return value is falsey, the entry will be dropped from the store, otherwi
|
|||
//
|
||||
// Can also specify a separator for nested keys (instead of the default ':')
|
||||
//
|
||||
nconf.env('__');
|
||||
nconf.env({ separator: '__' });
|
||||
// Get the value of the env variable 'database__host'
|
||||
var dbHost = nconf.get('database:host');
|
||||
let dbHost = nconf.get('database:host');
|
||||
|
||||
//
|
||||
// Can also lowerCase keys.
|
||||
|
@ -260,10 +218,10 @@ If the return value is falsey, the entry will be dropped from the store, otherwi
|
|||
|
||||
// Given an environment variable PORT=3001
|
||||
nconf.env();
|
||||
var port = nconf.get('port') // undefined
|
||||
let port = nconf.get('port') // undefined
|
||||
|
||||
nconf.env({ lowerCase: true });
|
||||
var port = nconf.get('port') // 3001
|
||||
let port = nconf.get('port') // 3001
|
||||
|
||||
//
|
||||
// Or use all options
|
||||
|
@ -281,7 +239,7 @@ If the return value is falsey, the entry will be dropped from the store, otherwi
|
|||
return obj;
|
||||
}
|
||||
});
|
||||
var dbHost = nconf.get('database:host');
|
||||
let dbHost = nconf.get('database:host');
|
||||
```
|
||||
|
||||
### Literal
|
||||
|
@ -294,7 +252,7 @@ Loads a given object literal into the configuration hierarchy. Both `nconf.defau
|
|||
```
|
||||
|
||||
### File
|
||||
Based on the Memory store, but provides additional methods `.save()` and `.load()` which allow you to read your configuration to and from file. As with the Memory store, all method calls are synchronous with the exception of `.save()` and `.load()` which take callback functions.
|
||||
Based on the Memory store, but provides additional methods `.save()` and `.load()` which allow you to read your configuration to and from file. As with the Memory store, all method calls are synchronous includ `.save()` and `.load()`.
|
||||
|
||||
It is important to note that setting keys in the File engine will not be persisted to disk until a call to `.save()` is made. Note a custom key must be supplied as the first parameter for hierarchy to work if multiple files are used.
|
||||
|
||||
|
@ -303,6 +261,10 @@ It is important to note that setting keys in the File engine will not be persist
|
|||
// add multiple files, hierarchically. notice the unique key for each file
|
||||
nconf.file('user', 'path/to/your/user.json');
|
||||
nconf.file('global', 'path/to/your/global.json');
|
||||
|
||||
// Set a variable in the user store and save it
|
||||
nconf.user('user').set('some:variable', true)
|
||||
nconf.user('user').save()
|
||||
```
|
||||
|
||||
The file store is also extensible for multiple file formats, defaulting to `JSON`. To use a custom format, simply pass a format object to the `.use()` method. This object must have `.parse()` and `.stringify()` methods just like the native `JSON` object.
|
||||
|
@ -311,7 +273,7 @@ If the file does not exist at the provided path, the store will simply be empty.
|
|||
|
||||
#### Encrypting file contents
|
||||
|
||||
As of `nconf@0.8.0` it is now possible to encrypt and decrypt file contents using the `secure` option:
|
||||
Encryption and decrypting file contents using the `secure` option:
|
||||
|
||||
``` js
|
||||
nconf.file('secure-file', {
|
||||
|
@ -340,31 +302,9 @@ This will encrypt each key using [`crypto.createCipheriv`](https://nodejs.org/ap
|
|||
}
|
||||
```
|
||||
|
||||
### Redis
|
||||
There is a separate Redis-based store available through [nconf-redis][0]. To install and use this store simply:
|
||||
|
||||
``` bash
|
||||
$ npm install nconf
|
||||
$ npm install nconf-redis
|
||||
```
|
||||
|
||||
Once installing both `nconf` and `nconf-redis`, you must require both modules to use the Redis store:
|
||||
|
||||
``` js
|
||||
var nconf = require('nconf');
|
||||
|
||||
//
|
||||
// Requiring `nconf-redis` will extend the `nconf`
|
||||
// module.
|
||||
//
|
||||
require('nconf-redis');
|
||||
|
||||
nconf.use('redis', { host: 'localhost', port: 6379, ttl: 60 * 60 * 1000 });
|
||||
```
|
||||
|
||||
## Installation
|
||||
``` bash
|
||||
npm install nconf --save
|
||||
npm install nconf-lite --save
|
||||
```
|
||||
|
||||
## Run Tests
|
||||
|
@ -374,7 +314,8 @@ Tests are written in vows and give complete coverage of all APIs and storage eng
|
|||
$ npm test
|
||||
```
|
||||
|
||||
#### Author: [Charlie Robbins](http://nodejitsu.com)
|
||||
#### Original author: [Charlie Robbins](http://nodejitsu.com)
|
||||
#### Rewriter of all that garbage: TheThing
|
||||
#### License: MIT
|
||||
|
||||
[0]: http://github.com/indexzero/nconf-redis
|
||||
[0]: http://github.com/nfp-projects/nconf-lite
|
||||
|
|
177
lib/common.mjs
Normal file
177
lib/common.mjs
Normal file
|
@ -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
|
||||
}, {})
|
||||
}
|
40
lib/nconf.js
40
lib/nconf.js
|
@ -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;
|
181
lib/nconf.mjs
Normal file
181
lib/nconf.mjs
Normal file
|
@ -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);
|
||||
}
|
||||
|
||||