app-etc-config

Creates a configuration API.

Usage no npm install needed!

<script type="module">
  import appEtcConfig from 'https://cdn.skypack.dev/app-etc-config';
</script>

README

Config

NPM version Build Status Coverage Status Dependencies

Creates a configuration API.

Installation

$ npm install app-etc-config

Usage

var etc = require( 'app-etc-config' );

etc( [options] )

Creates a configuration API.

var config = etc();

The constructor accepts the following options:

To specify options,

var config = etc({
    'sep': '|',
    'create': false,
    'schema': require( '/path/to/schema.json' ),
    'formats': {
        'only-a': /^a+$/
    },
    'extSchemas': {
        'ref_schema1': require( '/path/to/ref_schema1.json' ),
        'ref_schema2': require( '/path/to/ref_schema2.json' )
    }
});

===

config.set( keypath, value[, options] )

Sets a configuration value at a specified keypath.

var bool = config.set( 'foo.bar', 'beep' );
// returns a <boolean> indicating whether a value was set

/*
    {
        'foo': {
            'bar': 'beep'
        }
    }
*/

The method accepts the following options:

  • sep: keypath separator used when setting configuration values. See utils-deep-set.
  • create: boolean indicating whether to create a keypath if it does not exist. See utils-deep-set.

Specifying method options will override the default options provided during config creation.

===

config.merge( [keypath,] config[, options] )

Merges a configuration object or another config instance.

var bool = config.merge({
    'beep': 'boop'
});
// returns a <boolean> indicating if merge was successful

/*
    {
        'foo': {
            'bar': 'beep'
        },
        'beep': 'boop'
    }
*/

If provided a keypath, the method merges a configuration object with a sub-configuration.

var config2 = etc();
config2.set( 'hello', 'world' );

var bool = config.merge( 'foo', config2 );
/*
    {
        'foo': {
            'bar': 'beep',
            'hello': 'world'
        },
        'beep': 'boop'
    }
*/

If a keypath does not correspond to an object, the method returns false and set() should be used instead.

var bool = config.merge( 'abcdefg', config2 );
// returns false

/*
    {
        'foo': {
            'bar': 'beep',
            'hello': 'world'
        },
        'beep': 'boop'
    }
*/

bool = config.set( 'abcdefg', config2 );
// returns true

The method accepts the following options:

  • sep: keypath separator used when merging nested configuration values. See utils-deep-set.

Specifying method options will override the default options provided during config creation.

===

config.get( [ keypath[, options] ] )

Returns a copy of the raw configuration store.

var obj = config.get();
/*
    {
        'foo': {
            'bar': 'beep',
            'hello': 'world'
        },
        'beep': 'boop'
    }
*/

If provided a keypath, the method returns a copy of the corresponding configuration value.

var val = config.get( 'foo.hello' );
// returns 'world'

If a keypath does not exist, the method returns undefined.

var val = config.get( 'non.existent.path' );
// returns undefined

The method accepts the following options:

  • sep: keypath separator used when getting configuration values. See utils-deep-get.

Specifying method options will override the default options provided during config creation.

===

config.clone( [keypath[, options] ] )

Clones a config instance.

var config2 = config.clone();
console.log( config2.get() );
/*
    {
        'foo': {
            'bar': 'beep',
            'hello': 'world'
        },
        'beep': 'boop'
    }
*/

console.log( config === config2 );
// returns false

If provided a keypath, the method clones a sub-configuration value as a new config instance.

var config2;

// Configuration value is an object:
config2 = config.clone( 'foo' );
/*
    {
        'bar': 'beep',
        'hello': 'world'
    }
*/

// Configuration value is not an object:
config2 = config.clone( 'beep' );
/*
    {
        'beep': 'boop'
    }
*/

If a keypath does not exist, the method returns undefined.

var config3 = config.clone( 'non.existent.path' );
// returns undefined

The method accepts the following options:

  • sep: keypath separator used when getting configuration values. See utils-deep-get.

Specifying method options will override the default options provided during config creation.

===

config.load( filename )

Convenience method which loads and merges a configuration file.

config.load( '/path/to/config/file.<ext>' );

Note: this method does not directly support loading a configuration file into a sub-configuration. To achieve this, use app-etc-load and then merge at a specified keypath.

var load = require( 'app-etc-load' );

var obj = load( '/path/to/config/file.<ext>' );
config.merge( 'foo', obj );

===

config.validate( [validator] )

Validates a configuration.

// Valid configuration:
var out = config.validate();
// returns true

If a configuration is invalid, the method returns an array containing validation errors.

// Invalid configuration:
out = config.validate();
// returns [{...},{...},...]

The method accepts a validator function, which can be useful for validating against multiple schemas or when a schema was not provided during initialization. The validator should accept as its first argument the configuration object to be validated. The method returns validation results without modification.

// Example JSON schema validator:
var validator = require( 'jsen' );

var schema = require( '/path/to/schema.json' );
var validate = validator( schema, {
    'greedy': true
});

var out = config.validate( validate );
console.log( out );
console.log( validate.errors );

If a schema option was not provided during initialization and a validator is not provided at runtime, the method always returns true.

var config = etc();
config.set( 'port', 80 );

var out = config.validate();
// returns true

===

etc.exts()

Returns a list of supported filename extensions.

var exts = etc.exts();
// returns ['.json','.toml',...]

For more details, see app-etc-load.

etc.parser( extname[, parser] )

Returns a parser for the specified extension.

var parser = etc.parser( '.json' );

Including the . when specifying an extension is optional.

var parser = etc.parser( 'json' );

To support additional file formats or to override a parser, provide a parser function for an associated extension.

var parser = require( 'my-special-fmt-parser' );

etc.parser( '<my-ext>', parser );

Once a parser is set, all config instances will parse provided files accordingly.

config.load( './file.<my-ext>' );

For more details, see app-etc-load.


Notes

  • This module uses jsen for validating an internal configuration object against a JSON schema. One compelling feature of jsen is the ability to extend a schema definition by specifying custom error messages. For example, given the following schema,

    {
        "type": "object",
        "definitions": {
            "port": {
                "description": "schema for a port",
                "type": "integer",
                "minimum": 1024,
                "maximum": 65536,
                "requiredMessage": "port is required",
                "messages": {
                    "type": "invalid type. Must be an integer.",
                    "minimum": "invalid value. Must be an integer greater than or equal to 1024.",
                    "maximum": "invalid value. Must be an integer less than or equal to 65536."
                }
            }
        },
        "properties": {
            "port": {
                "$ref": "#/definitions/port"
            }
        },
        "required": [
            "port"
        ],
        "messages": {
            "type": "invalid data type where an object is expected"
        }
    }
    

    validation will return more informative error messages based on keywords.

    var validator = require( 'jsen' );
    
    var validate = validator( schema );
    
    var bool = validate({
        'port': 80
    })
    
    console.dir( validate.errors );
    /*
        [
            {
                'path': 'port',
                'keyword': 'minimum',
                'message': 'invalid value. Must be an integer greater than or equal to 1024.'
            }
        ]
    */
    

Examples

var etc = require( 'app-etc-config' );

// Create a new configuration API:
var config = etc();

// Load local files:
config.load( './.travis.yml' );
console.dir( config.get() );

config.load( './package.json' );
console.dir( config.get() );

// Merge in a custom object:
config.merge( 'author', {
    'beep': 'boop'
});
console.dir( config.get( 'author' ) );

// Access a shallow value:
console.log( config.get( 'license' ) );

// Access nested values:
console.log( config.get( 'author.name' ) );

// Create and set shallow values:
config.set( 'hello', false );
console.log( config.get( 'hello' ) );

// Create and set deeply nested values:
config.set( 'foo.bar.bip', 'bap', {
    'create': true
});
console.log( config.get( 'foo.bar.bip' ) );

// Clone the current configuration:
var clone = config.clone();
console.log( config === clone );
// returns false

To run the example code from the top-level application directory,

$ node ./examples/index.js

Tests

Unit

Unit tests use the Mocha test framework with Chai assertions. To run the tests, execute the following command in the top-level application directory:

$ make test

All new feature development should have corresponding unit tests to validate correct functionality.

Test Coverage

This repository uses Istanbul as its code coverage tool. To generate a test coverage report, execute the following command in the top-level application directory:

$ make test-cov

Istanbul creates a ./reports/coverage directory. To access an HTML version of the report,

$ make view-cov

License

MIT license.

Copyright

Copyright © 2015. Athan Reines.