appcfg

Application Configuration Library for Node.js

Usage no npm install needed!

<script type="module">
  import appcfg from 'https://cdn.skypack.dev/appcfg';
</script>

README

Application Configuration Library for Node.js

Build Status NPM version

About

Load and Combine Configuration from many sources:

  • Files
    • JavaScript Module
    • JSON
    • Yaml
  • Directory (combine files)
  • MongoDB

Additional features:

When combining objects from many sources, deep copy is used:

// a
{ sub: { foo: 'foo' }, arr: [ 'foo' ] }
// b
{ sub: { bar: 'bar' }, arr: [ 'bar' ] }

// combined (a + b)
{
    sub: { foo: 'foo', bar: 'bar'},
    arr: ['foo', 'bar']
}

// E.g you want completely to overwrite `arr` property, then prefix the key's name with `!`

// a
{ sub: { foo: 'foo' }, arr: [ 'foo' ] }
// b
{ sub: { bar: 'bar' }, '!arr': [ 'bar' ] }

// combined (a + b)
{
    sub: { foo: 'foo', bar: 'bar'},
    arr: ['bar']
}

Example:

    var config = Config
        .fetch([
            // from file. (with Special Folder format syntax support)
            {
                path: '%APPDATA%/.appName/config.yml',
                
                // set this source as writable for configuration persistance
                writable: true
            },
            // directory
            {
                path: 'defaults/**.yml'
            },
            // mongodb
            {
                mongo: 'barSettings'
            },
            // from file, but use only nested property
            {
                path: 'package.json',
                getterProperty: 'atma'
            }
        ])
        .done(function(cfg){
            // config === cfg === this;
            // ...
        })
Command Line overrides

Command line arguments are parsed and also set to the configuration object.

> node app --foo.bar barValue --debug
Config
    .fetch(someSources)
    .done(function(config){
        assert.has(config, {
            foo: {
                bar: 'barValue'
            },
            debug: true
        })
    })
Conditions

Yaml conditions example. (same is also for json format)

# conditional root configuration example
'#if debug':
    name: Foo
    host: dev.example.com
'#if test':
    name: Baz
    host: localhost

# conditional property example
port: 
    '#if debug': 5000
    '#if test': 5030
    'default': 8080

# conditional array item example
scipts:
    - lib.js
    - '#if debug || test'
        - lib.debug-extension.js
    

Arguments lookup:

  • in configuration object
  • in cli overrides
  • in process.env
  • compare value as string from process.env.ENV
# from cli example
> node app --debug

# from environment
> set ENV=DEBUG
> node app
{
    name: 'Foo',
    host: 'localhost',
    port: 5000,
    scripts: [
        "lib.js",
        "lib.debug-extension.js"
    ]
}
Special Folder

Use special folders for loading/writing configurations, like %APPDATA% or %HOME%. Is system agnostic and is parsed from the environment variables.

Config
    .fetch({
        path: '%APPDATA%/.myApplication/global.yml',
        writable: true
    })

Interpolations

Sometimes to not repeat the configuration it is convinient to use interpolations. It will embed configuration from itself.

# someConfig.yml
name: 'Foo'
A:
    lorem: '#[name]'
B:
    ipsum: '#[A.lorem]'
Config
    .fetch({
        path: 'someConfig.yml'
    })
    .done(function(config){
        assert.has(config, {
            name: 'Foo',
            A: { lorem: 'Foo' },
            B: { ipsum: 'Foo' }
        });
    })

API

Config

static

.fetch(Array<Source>) => Config Instance

Start loading the configuration from specified sources, returns new deferrable configuration instance

methods

<Constructor> (Array<Source>)
  • .$read(?Mix)

    • Mix:
      • String: File/Directory/Glob path
      • Source: Source object
      • Array<Source>
      • @default - Array taken from constructor
    • @return: self. Is deferrable, so you can attach done callbacks to it.

    Load configuration from sources

  • .$write(config:Object [, ?deepExtend:Boolean, ?setterPath:String):Deferred

    Update and save the configuration. Use first matched writable source.

    • deepExtend: complex objects and arrays are merged
    • setterPath: define nested object in current configuration
  • .$get(property:String)

    • property: Dot delimited accessor config.$get('foo.bar.quux')

      // is the same as you if you would write null-check expressions yourself:
      var quuxVal = config.foo && config.foo.bar && config.foo.bar.quux;
      
    • @return: null or value

  • .$is(name:String):Boolean

    Check conditional environment variable, e.g.: config.$is('debug')

    Ways to define the variables. (Example defines DEBUG and TEST flags)

    • directly in the configuration
      // foo.yml
      debug: true
      test: true
      
    • from the command line:
      > node index --debug --test
      
    • using environment configuration(comma delimited)
      > set ENV=DEBUG,TEST
      # also
      > set NODE_ENV=DEBUG,TEST
      > node index
      
  • .toJSON():Object

    Returns clean json configuration object.

  • .done(callback)

    Fire the callback when the configuration is loaded

Source

Common properties for all source types
{
    // Define specific property to extract SUB-JSON from the loaded configuration
    // @default: null
    getterProperty: String
    
    // Define specific property in the root configuration,
    // where the loaded configuration should be inserted into
    // @default: null
    setterProperty: String
    
    // Specify if this source can be used for persistence
    // @default: false
    writable: Boolean
    
    // Fires before source $read function is called
    // (e.g. change this.path property or any other things)
    beforeRead: Function<Source, RootConfig>
    
    // Fires after source completes reading
    // (e.g. access config object in `Source.config`)
    afterRead: Function<Source, RootConfig>
    
    // If true, do not log any warning if the source returns 404
    // @default: false
    optional: true
    
    // If true, then waits until all previous sources are loaded
    // @default: false
    sync: true
}
FileSource
{
    // File path
    path: String
}
DirectorySource

It will be mapped to multiple FileSources

{
    // Directory path with GLOB look-up, e.g. 'configs/**.json'
    path: String
}
MongoDBSource

Depends on ClassJS

{
    // Collection name
    mongo: String,
    
    // if source is writable
    // @default: true
    writable: Boolean
    
    // MongoDB Connection Settings
    // It can be also specified in previous configuration source, under `mongodb` property
    // @default: null - 
    settings: {
        // connection string
        connection: String
        // or
        
        // Port, default 27017
        port: Number,
        // IP, default '127.0.0.1'
        ip: String,
        
        // Database name, no default
        db: String
    }
}
CustomSource

This source type can suit any needs.


// Constructor with the Deferrable Interface and the method `read`
Function 

// e.g. using ClassJS

Class({
    Base: Class.Deferred,
    read: function(){
        // do any reads and calcs, after that resolve the source
        this.config = fooConfig();
        this.resolve();
    }
})

Embedded

Include config direct into the source

{
    config: Object
}

Test

> npm install
> npm test

(c) 2014 MIT License