Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

convict

Package Overview
Dependencies
Maintainers
3
Versions
63
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

convict - npm Package Compare versions

Comparing version 0.0.6 to 0.1.0

.npmignore

13

example/server.js

@@ -5,15 +5,18 @@ const

conf = convict({
var conf = convict({
ip: {
doc: "The IP Address to bind.",
format: 'string = "127.0.0.1"',
format: "ipaddress",
default: "127.0.0.1",
env: "IP_ADDRESS",
},
port: {
format: 'integer = 0',
env: "PORT",
doc: "The port to bind."
doc: "The port to bind.",
format: 'int',
default: 0,
env: "PORT"
}
}).loadFile(__dirname + '/config.json').validate();
var server = http.createServer(function (req, res) {

@@ -20,0 +23,0 @@ res.writeHead(200, {'Content-Type': 'text/plain'});

@@ -1,37 +0,156 @@

// TODO:
// * nesting
// * environment
// * all sorts of error handling
/*
* node-convict
* Configuration management with support for environmental variables, files, and validation.
*
*/
const
orderly = require('orderly'),
JSV = require("JSV").JSV.createEnvironment(),
cjson = require('cjson'),
fs = require('fs');
const cjson = require('cjson');
const fs = require('fs');
const validator = require('validator');
const moment = require('moment');
function buildSchema(name, o, props, fullName, env) {
if (typeof o === 'string' || (o && o.format && typeof o.format === 'string')) {
var fmt = (typeof o === 'string') ? o : o.format;
try {
var schema = orderly.parse(fmt);
if (!schema.optional) schema.required = true;
props[name] = schema;
} catch(e) {
throw "'" + fullName + "' has an invalid format: " + e.toString();
}
if (o.env) {
if (env[o.env]) {
throw "'" + fullName + "' reuses an env variable: " + o.env
var check = validator.check;
// format can be a:
// - predefine type, as seen below
// - an array of enumerated values, e.g. ["production", "development", "testing"]
// - built-in JavaScript type, i.e. Object, Array, String, Number, Boolean
// - or if omitted, the Object.prototype.toString.call of the default value
var types = {
"*": function() { },
int: function(x) {
check(x, 'must be an integer').isInt();
},
nat: function(x) {
check(x, 'must be a positive integer').isInt().min(0);
},
port: function(x) {
check(x, 'Ports must be within range 1 - 65535').isInt().min(1).max(65535);
},
url: function(x) {
check(x, 'must be a URL').isUrl();
},
email: function(x) {
check(x, 'must be an email address').isEmail();
},
ipaddress: function(x) {
check(x, 'must be an IP address').isIP();
},
duration: function(x) {
check(x, 'must be a positive integer').isInt().min(0);
},
timestamp: function(x) {
check(x, 'must be a positive integer').isInt().min(0);
}
};
function validate (instance, schema, errors) {
Object.keys(schema.properties).reduce(function(previousErrors, name) {
var p = schema.properties[name];
if (p.properties) {
var kids = instance[name] || {};
validate(kids, p, previousErrors);
} else {
try {
p._format(instance[name]);
} catch (e) {
previousErrors.push(e);
}
env[o.env] = fullName;
}
} else {
return previousErrors;
}, errors);
return errors;
}
// helper for checking that a value is in the list of valid options
function contains (options, x) {
check(x, 'must be one of the possible values: ' + JSON.stringify(options)).isIn(options);
}
var BUILT_INS = [Object, Array, String, Number, Boolean];
function normalizeSchema (name, o, props, fullName, env) {
if (typeof o === 'object' && !Array.isArray(o) && typeof o.default === 'undefined') {
props[name] = {
properties: {},
additionalProperties: false
};
Object.keys(o).forEach(function(k) {
buildSchema(k, o[k], props[name].properties, fullName + "." + k, env);
normalizeSchema(k, o[k], props[name].properties, fullName + "." + k, env);
});
return;
} else if (typeof o !== 'object' || Array.isArray(o) || o === null) {
o = { default: o };
}
if (typeof o === 'object') {
props[name] = o;
if (o.env) {
if (env[o.env]) {
throw new Error("'" + fullName + "' reuses an env variable: " + o.env);
}
env[o.env] = fullName;
}
// store original format function
var format = o.format;
var newFormat;
if (BUILT_INS.indexOf(format) >= 0) {
// if the format property is a built-in JavaScript constructor,
// check that the value is of that type
newFormat = function(x) {
check(Object.prototype.toString.call(x), 'must be of type ' + format.name)
.equals(Object.prototype.toString.call(new format()));
};
o.format = format.name.toLowerCase();
} else if (typeof format === 'string') {
// store declared type
if (!format in types) {
throw new Error("'" + fullName + "' uses an unknown format type: " + format);
}
// use a predefined type
newFormat = types[format];
} else if (Array.isArray(format)) {
// check that the value is a valid option
newFormat = contains.bind(null, format);
} else if (typeof format === 'function') {
newFormat = format;
} else if (format && typeof format !== 'function') {
throw new Error("'" + fullName + "': `format` must be a function or a known format type.");
}
if (!newFormat && !format) {
// default format is the typeof the default value
var type = Object.prototype.toString.call(o.default);
newFormat = function(x) {
check(Object.prototype.toString.call(x),
' should be of type ' + type.replace(/\[.* |]/g, '')).equals(type);
};
}
o._format = function (x) {
try {
newFormat(x);
} catch (e) {
// attach the value and the property's fullName to the error
e.fullName = fullName;
e.value = x;
throw e;
}
};
} else {
throw new Error("'" + fullName + "' doesn't appear to be a valid schema object: "
+ JSON.stringify(o))
+ '. Note: schema objects must have a default value.';
}
}

@@ -56,3 +175,3 @@

} else {
if (!c[name] && typeof p.default !== 'undefined') c[name] = p.default
if (!c[name] && typeof p.default !== 'undefined') c[name] = coerce(name, p.default, schema);
}

@@ -62,10 +181,10 @@ });

function overlay(from, to) {
function isObj(o) { return (typeof o === 'object' && o !== null); }
function isObj(o) { return (typeof o === 'object' && o !== null); }
function overlay(from, to, schema) {
Object.keys(from).forEach(function(k) {
// leaf
if (Array.isArray(from[k]) || !isObj(from[k])) to[k] = from[k];
// not
else {
if (Array.isArray(from[k]) || !isObj(from[k])) {
to[k] = coerce(k, from[k], schema);
} else {
if (!isObj(to[k])) to[k] = {};

@@ -77,3 +196,3 @@ overlay(from[k], to[k]);

function getType(schema, path) {
function traverseSchema(schema, path) {
var ar = path.split('.');

@@ -90,13 +209,42 @@ var o = schema;

}
return (o && o.type) ? o.type : null;
return o;
}
module.exports = function(def) {
function getFormat(schema, path) {
var o = traverseSchema(schema, path);
return o ? (typeof o.format === 'string' ? o.format : typeof o.default) : null;
}
function coerce(k, v, schema) {
// magic coerceing
var format = getFormat(schema, k);
if (typeof v === 'string') {
switch (format) {
case 'port':
case 'int': v = parseInt(v, 10); break;
case 'number': v = parseFloat(v); break;
case 'boolean': v = ((v === 'false') ? false : true); break;
case 'array': v = v.split(','); break;
case 'timestamp': v = moment(v).valueOf(); break;
case 'duration':
var split = v.split(' ');
if (!split[1].match(/s$/)) split[1] += 's';
v = moment.duration(parseInt(split[0], 10), split[1]).valueOf();
break;
}
}
return v;
}
module.exports = function convict(def) {
var rv = {
template: function() {
},
toString: function() {
return JSON.stringify(this._instance, null, 2)
return JSON.stringify(this._instance, null, 2);
},
toSchemaString: function() {
return JSON.stringify(this._schema, null, 2);
},
get: function(path) {

@@ -112,3 +260,5 @@ var o = JSON.parse(JSON.stringify(this._instance));

}
if (o === undefined) throw "cannot find configuration param '" + path + "'";
if (o === undefined) {
throw new Error("cannot find configuration param '" + path + "'");
}
return o;

@@ -120,3 +270,3 @@ },

return true;
} catch(e) {
} catch (e) {
return false;

@@ -126,10 +276,4 @@ }

set: function(k, v) {
// magic casting
if (typeof v === 'string') {
switch (getType(this._schema, k)) {
case 'integer': v = parseInt(v); break;
case 'number': v = parseFloat(v); break;
case 'boolean': v = ((v === 'false') ? false : true); break;
}
}
v = coerce(k, v, this._schema);
var ar = k.split('.');

@@ -146,3 +290,3 @@ var o = this._instance;

load: function(conf) {
overlay(conf, this._instance);
overlay(conf, this._instance, this._schema);
// environment always overrides config files

@@ -154,3 +298,3 @@ importEnvironment(rv);

var self = this;
if (!Array.isArray(paths)) paths = [ paths ];
if (!Array.isArray(paths)) paths = [paths];
paths.forEach(function(path) {

@@ -164,19 +308,22 @@ overlay(cjson.load(path), self._instance);

validate: function() {
var report = JSV.validate(this._instance, this._schema);
if (report.errors.length) {
var errBuf = "";
for (var i = 0; i < report.errors.length; i++) {
if (errBuf.length) errBuf += "\n";
var e = report.errors[i];
// get the property name in dot notation
if (e.uri) {
errBuf += e.uri.split('/').slice(1).join('.') + ": ";
var errors = validate(this._instance, this._schema, []);
if (errors.length) {
var errBuf = '';
for (var i = 0; i < errors.length; i++) {
if (errBuf.length) errBuf += '\n';
var e = errors[i];
if (e.fullName) {
errBuf += e.fullName + ': ';
}
if (e.message) errBuf += e.message + ": ";
if (e.details) {
errBuf += ((typeof e.details === 'string') ?
e.details : JSON.stringify(e.details));
if (e.message) errBuf += e.message;
if (e.value) {
errBuf += ': value was ' + JSON.stringify(e.value);
}
}
throw errBuf;
throw new Error(errBuf);
}

@@ -186,3 +333,2 @@ return this;

};
// XXX validate definition

@@ -193,16 +339,13 @@ rv._def = def;

rv._schema = {
properties: {},
additionalProperties: false
properties: {}
};
rv._env = { };
rv._env = {};
Object.keys(def).forEach(function(k) {
buildSchema(k, def[k], rv._schema.properties, k, rv._env);
normalizeSchema(k, def[k], rv._schema.properties, k, rv._env);
});
var report = JSV.validate({}, rv._schema);
rv._instance = report.instance.getValue() || {};
addDefaultValues(rv._schema, rv._instance)
rv._instance = {};
addDefaultValues(rv._schema, rv._instance);
importEnvironment(rv);

@@ -212,1 +355,2 @@

};

@@ -5,3 +5,3 @@ {

"description": "Unruly configuration management for nodejs",
"version": "0.0.6",
"version": "0.1.0",
"homepage": "https://github.com/lloyd/node-convict",

@@ -17,10 +17,10 @@ "repository": {

"dependencies": {
"orderly": "1.0.1",
"JSV": "3.5.0",
"cjson": "0.0.6"
"cjson": "0.2.1",
"validator": "0.4.24",
"moment": "1.7.2"
},
"devDependencies": {
"obj_diff": "0.2.0",
"should": "0.5.1",
"mocha": "0.10.2"
"obj_diff": "0.3.0",
"should": "1.2.2",
"mocha": "1.8.1"
},

@@ -27,0 +27,0 @@ "scripts": {

@@ -1,3 +0,169 @@

## What
# node-convict
convict is a node
Convict expands on the standard pattern of configuring node.js applications in a way that is more robust and accessible to collaborators, who may have less interest in digging through imperative code in order to inspect or modify settings. By introducting a configuration schema, convict gives project collaborators more **context** on each setting and enables **validation and early failures** for when configuration goes wrong.
## Features
* **Loading and merging**: configurations are loaded from disk or inline and merged. JSON files are loaded with `cjson` so comments are welcome.
* **Environmental variables**: values can be set from environmental variables
* **Validation**: configurations are validated against your schema, generating an error report with all errors that are found
Example:
var convict = require('convict');
var http = require('http');
// define a schema
var conf = convict({
env: {
doc: "The applicaton environment.",
format: ["production", "development", "test"],
default: "development",
env: "NODE_ENV"
},
ip: {
doc: "The IP address to bind.",
format: "ipaddress",
default: "127.0.0.1",
env: "IP_ADDRESS",
},
port: {
doc: "The port to bind.",
format: "port",
default: 0,
env: "PORT"
}
});
// load environment dependent configuration
var env = conf.get('env');
conf.loadFile('./config/' + env + '.json');
// perform validation
conf.validate();
var server = http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello World\n');
});
// consume
server.listen(conf.get('port'), conf.get('ip'), function(x) {
var addy = server.address();
console.log('running on http://' + addy.address + ":" + addy.port);
});
## Schema
A configuration module could look like this:
config.js:
var config = module.exports = convict({
env: {
doc: "The application environment.",
format: ["production", "development", "test"],
default: "development",
env: "NODE_ENV"
}
});
config.loadFile(['./prod.json', './config.json']);
You'll notice that each setting in the schema has four possible properties, each aiding in convict's goal of being more robust and collaborator friendly.
* **Type information**: the `format` property specifies either a built-in convict format (`ipaddress`, `port`, `int`, etc.), or it can be a function to check a custom format. During validation, if a format check fails it will be added to the error report.
* **Default values**: Every setting *must* have a default value.
* **Environmental variables**: If the variable specified by `env` has a value, it will overwrite the setting's default value.
* **Documentation**: The `doc` property is pretty self-explanatory. The nice part about having it in the schema rather than as a comment is that we can call `conf.toSchemaString()` and have it displayed in the output.
### Validation
In order to help detect misconfigurations, convict allows you to define a format for each setting. By defualt, convict checks if the value of the property has the same `typeof` value as the default value specified in the schema. You can define a custom format checking function in the schema by setting the `format` property.
convict provides serveral predefined formats for validation that you can use ([using node-validator](https://github.com/chriso/node-validator#list-of-validation-methods) and moment.js). Most of them are self-explanatory:
* `*` - any value is valid
* `int`
* `port`
* `url`
* `email`
* `ipaddress` - IPv4 addresses
* `duration` - miliseconds or a human readable string (e.g. "5 days")
* `timestamp` - Unix timestamps or date strings recognized by moment.js
* `nat` - positive integer (natural number)
If `format` is set to one of the built-in JavaScript constructors, `Object`, `Array`, `String`, `Number`, or `Boolean`, validation will use Object.prototype.toString.call to check that the setting is the proper type.
You can also provide your own format checking function. For example:
var check = require('validator').check;
var conf = convict({
key: {
doc: "API key",
format: function (val) {
check(val, 'should be a 64 character hex key').regex(/^[a-fA-F0-9]{64}$/);
},
default: '3cec609c9bc601c047af917a544645c50caf8cd606806b4e0a23312441014deb'
}
});
### Coercion
Convict will automatically coerce environmental variables from strings to their proper types when importing them. For instance, values with the format `int`, `nat`, `port`, or `Number` will become numbers after a straight forward `parseInt` or `parseFloat`. `duration` and `timestamp` are also parse and converted into numbers, though they utilize `moment.js` for date parsing.
## API
### `var config = convict(schema)`
`convict()` takes a schema object and returns a convict configuration object. The configuration object has an API for getting and setting values, described below.
### `config.get(name)`
Returns the value of the `name` property. `name` can use dot notation to reference nested values. E.g.:
config.get('database.host');
// or
config.get('database').host;
### `config.has(name)`
Returns `true` if the property `name` is defined, or `false` otherwise. E.g.:
if (config.has('some.property')) {
// do something
}
### `config.set(name, value)`
Sets the value of `name` to value. `name` can use dot notation to reference nested values, e.g. `"database.port"`. If objects in the chain don't yet exist, they will be initialized to empty objects. E.g.:
config.set('property.that.may.not.exist.yet', 'some value');
config.get('property.that.may.not.exist.yet');
// returns "some value"
### `config.load(object)`
This will load and merge a JavaScript object into `config`. E.g.:
config.load({
"env": "test",
"ip": "127.0.0.1",
"port": 80
});
### `config.loadFile(file or [file1, file2, ...])`
This will load and merge one or multiple JSON configuration files into `config`. JSON files are loaded using `cjson`, so they can contain comments. E.g.:
conf.loadFile('./config/' + conf.get('env') + '.json');
Or, loading multiple files at once:
// CONFIG_FILES=/path/to/production.json,/path/to/secrets.json,/path/to/sitespecific.json
conf.loadFile(process.env.CONFIG_FILES.split(','));
### `config.validate()`
Validates `config` against the schema used to initialize it. All errors are collected and thrown at once.
exports.conf = {
arr: "array { any; }*"
arr: {
default: [],
format: Array
}
};
exports.conf = {
env: {
format: 'string ["production", "local"] = "local"',
format: ["production", "local"],
default: "local",
env: "NODE_ENV",

@@ -8,8 +9,10 @@ doc: "The environment that we're running in."

URL: {
format: 'string = "https://browserid.org"',
format: String,
default: "https://browserid.org",
env: "URL",
doc: "The externally visible url of the server",
doc: "The externally visible url of the server"
},
use_minified_resources: {
format: "boolean = false;",
format: Boolean,
default: false,
doc: "All resources should be combined and minified",

@@ -19,5 +22,6 @@ env: "MINIFIED"

var_path: {
format: 'string = "/home/browserid/var"',
format: String,
default: "/home/browserid/var",
doc: "The path the the 'var' directory, where logs and such will go"
}
};

@@ -1,32 +0,52 @@

module.exports = {
exports.conf = {
env: {
format: 'string ["production", "local"]',
format: ["production", "local"],
default: "production",
env: "NODE_ENV"
},
URL: {
format: 'string = "https://browserid.org"',
format: String,
default: "https://browserid.org",
env: 'URL'
},
use_minified_resources: 'boolean = true',
var_path: 'string = "/home/browserid/var"',
use_minified_resources: true,
var_path: "/home/browserid/var",
database: {
driver: 'string ["json", "mysql"] = "mysql"',
user: 'string',
create_schema: 'boolean',
may_write: 'boolean'
driver: {
default: "mysql",
format: ["json", "mysql"]
},
user: 'browserid',
create_schema: true,
may_write: false
},
statsd: {
enabled: 'boolean'
enabled: true
},
bcrypt_work_factor: "integer[6,20] = 12",
authentication_duration: 'string = "2 weeks"',
certificate_validity: 'string = "1 day"',
min_time_between_emails: 'string = "1 minute"',
max_compute_duration: 'string = "10 seconds"',
disable_primary_support: 'boolean = false',
enable_code_version: 'boolean = false',
default_lang: 'array { string; }',
supported_languages: 'array { string; }',
locale_directory: 'string = "locale"',
express_log_format: 'string = "default"'
bcrypt_work_factor: {
default: 12,
format: 'nat'
},
authentication_duration: {
default: "2 weeks",
format: "duration"
},
certificate_validity: {
default: "1 day",
format: "duration"
},
min_time_between_emails: {
default: "1 minute",
format: "duration"
},
max_compute_duration: {
default: "10 seconds",
format: "duration"
},
disable_primary_support: false,
enable_code_version: false,
default_lang: [ 'en-US' ],
supported_languages: [ 'en-US' ],
locale_directory: "locale",
express_log_format: "default"
};
exports.conf = {
ip: {
format: 'string = "127.0.0.1"',
default: "127.0.0.1",
format: "ipaddress",
env: "IP_ADDRESS"
},
port: {
format: 'integer = 0',
default: 0,
format: "port",
env: "PORT"

@@ -9,0 +11,0 @@ }

exports.conf = {
foo: {
format: 'string = "a"',
default: "a",
format: String,
env: "FOO"

@@ -5,0 +6,0 @@ }

exports.conf = {
foo: {
bar: {
format: 'string ["a", "b"]',
default: "a",
format: ["a", "b"],
env: "BAR"

@@ -6,0 +7,0 @@ }

exports.conf = {
bool: {
format: 'boolean',
default: true,
format: Boolean,
env: "BOOL"
},
int: {
format: 'integer',
default: 42,
format: 'int',
env: "INT"
},
num: {
format: 'number',
default: 10.1,
format: Number,
env: 'NUM'
},
array: {
default: ['a', 'b'],
format: Array,
env: 'ARRAY'
}

@@ -19,3 +27,4 @@ };

INT: 77,
NUM: 789.1011
NUM: 789.1011,
ARRAY: "a,b,c"
};
exports.conf = {
ip: {
doc: "The IP Address to bind.",
format: 'string = "127.0.0.1"',
default: "127.0.0.1",
format: 'ipaddress',
env: "IP_ADDRESS",
},
port: {
format: 'integer = 0',
default: 0,
format: 'port',
env: "PORT",

@@ -10,0 +12,0 @@ doc: "The port to bind."

exports.conf = {
top: {
middle: {
leaf: 'string = "foo"',
leaf: "foo",
leaf2: {
format: 'string = "bar"',
default: "bar",
doc: "a second leaf"
}
},
internal_leaf: 'string = "baz"'
internal_leaf: "baz"
}
};

@@ -10,7 +10,7 @@ const should = require('should');

foo: {
bar: "number = 7",
bar: 7,
baz: {
bing: 'string = "foo"',
bing: "foo",
"name with spaces": {
name_with_underscores: "boolean = true"
name_with_underscores: true
}

@@ -28,11 +28,12 @@ }

it('should find a nested value', function() {
(conf.get('foo.bar')).should.equal(7);
var val = conf.get('foo.bar');
should.equal(val, 7);
});
it('should handle three levels of nesting', function() {
(conf.get('foo.baz.bing')).should.equal('foo');
should.equal(conf.get('foo.baz.bing'), 'foo');
});
it('should handle names with spaces and underscores', function() {
(conf.get('foo.baz.name with spaces.name_with_underscores')).should.equal(true);
should.equal(conf.get('foo.baz.name with spaces.name_with_underscores'), true);
});

@@ -39,0 +40,0 @@

@@ -12,3 +12,4 @@ const

} catch(e) {
process.send({error: e.toString() });
console.error(e);
process.send({error: e.message });
process.exit(1);

@@ -15,0 +16,0 @@ }

@@ -6,3 +6,3 @@ const

cp = require('child_process'),
obj_diff = require('obj_diff').diff,
obj_diff = require('obj_diff'),
mocha = require('mocha');

@@ -53,3 +53,3 @@

var n = cp.fork(path.join(__dirname + '/runner.js'), null, { env: env });
var n = cp.fork(path.join(__dirname + '/runner.js'), [], { env: env });

@@ -56,0 +56,0 @@ n.on('message', function(m) {

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc