npm package: 12factorial
Description: A library for consuming config from environment variables and Consul
Author: Bob Gregory
Latest version: 0.6.1

# 12 Factorial

12 Factorial is a simple lib for building dynamic configuration from environment variables and Consul. 
## What does it do?

```
// myconfig.js
import cfg from '12factorial'

// Configs are described as plain javascript objects.
const spec = {

  // they can contain any data you like
  constantValue: 'abc-123',
  someOtherValue: myFunction(),

  // 12factorial.service will synchronise a field
  // with a consul service, or with env vars.
  database: cfg.service('my-db'),

  credentials: {
    // 12factorial.value will synchronise a field
    // with consul's KV store, or with an env var.
    username: cfg.value(),
    password: cfg.value()
  },
}

// 12factorial.build is the factory function that turns your
// object into a synchronised config object
cfg.build(spec).then((x) => doSomethingWithConfig(x))
```

## Using values

The `value` function synchronises an object field with a scalar value. Env vars take precedence over values stored in Consul, and defaults can be provided. Keys and environment variable names are generated by convention. Values can be declared with a default.

```
{
  // This can be set with the env var `VALUE` or the consul key `consul-prefix/value`
  value: cfg.value(),

  // this can be set with the env var `NESTED_OBJECT_VALUE` or the 
  // consul key `consul-prefix/nested/object/value`
  nested: {
     object: {
        value: cfg.value()
     }
  },

  // defaults to 'cheese' if no env var or consul key is available.
  defaulted: cfg.value({ default: 'cheese' })
}
```


### Namespacing environment variables

Environment variables can be namespaced. In the following example, we use an `envPrefix` of `myapp`. This prefix will be added to the variable names.

```
spec = {
  nested: {
    value: cfg.value()
  }
}

process.env.MYAPP_NESTED_VALUE = 'beep'

cfg.build(spec, {envPrefix: 'myapp'}).then(config => { 
    console.log(config.nested.value) // prints beep
});
```

### Syncing with consul

Values can be synchronised with Consul by passing consul configuration to `12factor.build`. If no consul config is provided, we will skip consul synchronisation. Only the 'prefix' key is required, the other values default to development values. Values are kept up to date with a Consul Watch.

```
const consulConfig = {

  prefix: 'myapp',     // required.

  host: '127.0.0.1',   // defaults
  port: 8500,
  scheme: 'http'
}

const spec = {
    nested: {
        value: cfg.value()
    }
}

cfg.build(spec, { consul: consulConfig }).then( config => {
        console.log(config.nested.value) // prints the value of myapp/nested/value from consul kv.
});
```

## Using Services

The `12factor.service` function synchronises an object key with the address and port of a service. As with values, environment variables take precedence over Consul values. Environment variable names are generated by convention, and support namespacing. 

```
const spec = {
   web: cfg.service('my-web-service'),
   db: cfg.service('my-database')
}

process.env.MYAPP_WEB_ADDRESS = '127.0.0.1'
process.env.MYAPP_WEB_PORT = '3002'

const config = await cfg.build(spec, { envPrefix: 'myapp' })

console.log(config.web.getAddress())             // prints 127.0.0.1:3002
console.log(config.web.buildUri('/hello/world')) // prints 127.0.0.1:3002/hello/world

console.log(config.db.getAddress())              // prints the address + port of the 'my-database' 
                                                 // service registered in Consul.
```

### Using Services from Consul

Services are automatically synchronised from Consul. By default, we use 'http://127.0.0.1:8500' as the address of our consul server. Services from Consul are kept up to date with a Consul watch.

If there are multiple addresses registered for a service, 12factorial will select an address at random and return that address consistently until the service is updated in Consul.


### Mixing Values into Services

Occasionally, for ease of consumption, you might want to add extra values into a service object. This is typically useful for storing credentials with a service's address. This use case is covered by the `extend` method of a `service`.


```

const spec = {
    database: cfg.service('myapp-db').extend({
        username: cfg.value(),
        password: cfg.value({ sensitive: true })
    })
}   

const config = await cfg.build(spec)

console.log(config.database.getAddress())
console.log(config.database.username)
console.log(config.database.password)
```

### Type Coercion

Values can be automagically coerced from strings. If you set a default, we will coerce to the same type as the default value. You can override the parsing of your values by passing a reader function.

```
const spec = {
    number: cfg.value({ default: 123 }).
    bool: cfg.value({ default: true }),
    custom: cfg.value({ reader: function (x) { return {msg: x } } })
}   

process.env.NUMBER = "0xFF"
process.env.BOOL = FALSE // or false
process.env.CUSTOM = 'Hello World'

const config = await cfg.build(spec)

console.log(config.number) // 255
console.log(config.bool) // false
console.log(config.custom.msg) // hello world

```

## Current Status

This is alpha-quality. There are some missing features, and little error handling. This is intended to meet my own requirements for a production system, but may not meet yours. Feel free to play around and report bugs. 

## Roadmap

- [X] Add logging
- [ ] Make sure we handle errors properly
- [ ] Add type coercion for values
- [ ] Allow consul values to query using data center, tags etc.
- [ ] Extend env var opts to support an arbitrary variable name
- [ ] Support Hashicorp Vault for secrets.
- [ ] Consider supporting other back-ends
- [ ] Basic validity checks, eg. required fields.
- [ ] Reactivity, eg. raise an event when the database service updates so we can close connections etc