A simple configuration system allowing multiple configuration layers (e.g. defaults, user-defined config)
Each layer is treated independently. If the configuration is queried, all layers are scanned from the layer with the highest priority to the one with the lowest. The first layer that can resolve the queried path will be the one returning the configured value.
By default, an instance of LayeredConfiguration
is created when the module is required.
// Load a ready-to-use LayeredConfiguration instance
let config = require('layered-config');
// If you want to create your own instance, use this instead:
let LayeredConfig = require('layered-config').LayeredConfiguration;
let myConfig = new LayeredConfig();
Before any configuration values can be read or written, we need to add one or more layers to the configuration.
If added by using addlayer()
, every new layer will have a higher priority than the ones that were added before.
// Add a first layer including some data
config.addLayer(
'layerOne',
{
one: 1,
two: 2,
three: {
foo: 'bar'
}
}
);
// Add another layer
// This one has a higher priority than "layerOne"
config.addLayer(
'layerTwo',
{
three: {
foo: 'overwritten', // This value overwrites the one in layerOne
bar: 'baz'
}
}
);
console.dir(config.getLayerNames()); // ["layerTwo", "layerOne"]
getLayerNames()
returns the names of the layers inside the configuration ordered from highest to lowest priority.
Each value inside the configuration hierarchy can be addressed by using a configuration path that describes the position you want to access. By default, configuration paths use "." as separator. To get the value of "bar" in our example above, the corresponding configuration path would be three.bar
.
Querying the configuration is done by using get()
:
let valueOfBar = config.get('three.bar');
console.log(valueOfBar); // Output: 'baz'
// This will output "overwritten", since layerTwo has the highest priority
console.log(config.get('three.foo'));
If you want to query data from a specific layer, just add the layer name as second parameter:
console.log(config.get('three.foo', 'layerOne')); // Output: 'bar'
You can even change the priority by passing the layer names in the order they should be queried:
console.log(config.get('three.foo', ['layerOne', 'layerTwo'])); // Output: 'bar'
Data is written to the config using the set()
method. It takes up to three parameters: The path to write to, the value to write and the layer the value shall be stored in. If the layer name is omitted, the new value will be written into the layer with the highest priority:
config.set('my.precious.data', 'This is my value'); // Writes to "layerTwo"
config.set('some.other.data', 'Hello', 'layerOne'); // Writes to "layerOne"
You can write complete object hierarchies, too:
config.set('my.data', {hello: {world: '!'});
let value = config.get('my.data.hello.world');
console.log(value); // Output: '!'
layered-config
reads and writes its configuration data using Hjson. This way, the configuration files can be written in a little bit more relaxed way and contain - for example - comments.
For more information about Hjson, see the Hjson website.
You can choose to either load each layer one by one using loadFromFile()
or to fill the whole configuration by reading all .json
and .hjson
files from a directory using loadFromDirectory()
. Both function return Promises that resolve when the load operations have completed.
// Load the contents of userData.hjson into a new layer named "userData"
config.loadFromFile('./data/userData.hjson')
.then(/* ... */);
// Load the same file into a layer named "foo"
config.loadFromFile('./data/userData.hjson', 'foo')
.then(/* ... */);
// Cleanup
config.removeAllLayers();
// Load all (h)json files from ./data into the configuration
// Files will be loaded in alphabetical order
config.loadFromDirectory('./data')
.then(/* ... */);
// Assuming that the directory contains the files "foo.hjson", "bar.json" and "baz.hjson",
let names = config.getLayerNames(); // ["foo", "baz", "bar"]
Attention:
If both, a .hjson
and a .json
file exist having the same filename, the resulting configuration will only contain the data from the .json
file, because it will be loaded after the .hjson
one, thus overwriting its data.
You can also load configuration data from environment variables using loadFromEnv()
. This function takes an option object which configures the way, the environment variables are processed.
The available options are:
lowerCase
(Boolean): convert environment variable names to lowercase? (default:true
)separator
(String): if set, the variable name will be split into a path using the separator (default:'_'
)whitelist
(String[]): if set, only the variables inside this array will be loaded into the layer (defaultundefined
)match
(regex): if set, only variables that match the regular expression will be loaded into the layer (default:undefined
)
If neither whitelist
nor match
are set, all environment variables will be imported into the configuration layer.
// Add a layer containing process.env variables
// If no layer name is given, a new layer named 'process_env' will be created
config.loadFromEnv();
console.log(config.get('home'))
When saving the configuration data you can either write the data of one layer into a single file using saveToFile()
or write the data of all layers into Hjson files inside a directory, each having the layers' name by using saveToDirectory()
.
Just like the load methods, each save method returns a Promise.
When using saveToDirectory()
, only those layers will be written to disk that have their writeToDisk
property set to true
(default: false
).
// Add some layers with data
config.addLayer('one', {a: 1}).writeToDisk = true;
config.addLayer('two', {b: 2});
config.addLayer('three', {c: 3}).writeToDisk = true;
// Write layer "two" to disk
config.saveToFile('two', 'myConfigFile.hjson')
.then(/* ... */);
// This will write layers "one" and "three" to the target directory
config.saveToDirectory('./data')
.then(/* ... */);
// The "data" directory now contains the files "one.hjson" and "three.hjson"