# config

Creates a configuration object of an arbitrary shape, merging multiple layers, including layers based on NODE_ENV setting.  Settings can be optionally over-ridden by environment variable settings.  

Also integrates with aws SSM Parameter Store, allowing SSM values to be used as source of environment variable overrides.

## Basic Usage

#### Create your configuration

We'll use yaml here, but these files could be js (ES5 or 6), typescript or json.  Or, you could pass the builder raw settings objects, or objects imported directly in code.

***src/settings/base.yaml***

````yaml
setting1: mySetting
setting2: mySecondSetting
````

***src/settings/development.yaml***

````yaml
setting1: myDevSetting
````

***src/settings/env-vars.yaml***

````yaml
setting2: MYAPP_SETTING2
````

***\#shell***

````bash
$ export NODE_ENV="development"
$ export MYAPP_SETTING2="my env setting"
````

#### Load configuration at run time

***src/loadConfig.ts***

````typescript
import { createPackageConfigBuilder } from '@cirrusct/config';

// some run time base/default settings
const myDefaults = {
    setting0: 'internal default setting'
}

// create a builder with path info to our config settings
const builder = createPackageConfigBuilder(
    myDefaults, // pass defaults first, will be over-ridden if defined by imports
    {
        rootPath: 'src',
        baseName: 'settings'
    },
    // ... any number of objects to over-ride previous arguments
);

// build config
const settings = await builder.build();

// build() returns a single merged object based on source settings and environment

// outputs
console.log(settings.setting1);
  // -> $ myDevSetting
console.log(settings.setting2);
  // -> $ my env setting 
console.log(settings.setting0);
  // -> $ internal default setting
````

### PackageConfigBuilder

**PackageConfigBuilder** handles merging (and optionally loading) of configuration settings.  It takes various representations of the source settings and produces a single merged output object.

The constructor looks like this:

````typescript
createPackageConfigBuilder = <T = {}>(...settings: PackageConfigBuilderArgs<T>[]): PackageConfigBuilder<T> => {
    return new PackageConfigBuilder<T>(...settings);
};
````

where `T` is the type of your final merged config object.  

The constructor accepts any number of parameters representing source settings to be merged (with later arguments overriding prior ones).  

Each argument can be of one of the following types:

### A PackageConfigSettings Object

A **PackageConfigSettings** object with the following shape:

````typescript
export interface PackageConfigSettings<TConfig = {}> {
    base: TConfig;
    nodeEnv?: { [node_env: string]: TConfig };
    envVars?: any; // all values are string names, but shape should match TConfig
}
````

***base***: default version of the final output object

***nodeEnv***: a map of the final output object keyed on **NODE_ENV** environment variable values, where values defined will be merged over base values

***envVars***: an object/JSON in the shape of the output object, with values containing environment variable names where the environment variable values for those names will be merged over **base** and **nodeEnv** values.

### A Literal Object of the output Type

An argument can be an instance of the output object itself.  Useful if there are no NODE_ENV specific settings, or as a set of values to provide defaults or over-rides for other PackageConfigSettings passed to the constructor.

### A PackageConfigPathOptions Object

This argument refers to a file or directory containing configuration settings to be loaded by the builder.  ***PackageConfigPathOptions*** looks like this:

````typescript
export interface PackageConfigPathOptions {
    rootPath: string;
    baseName?: string;
    isRequired?: boolean;
}
````

#### PackagePathOptions Arguments
***rootPath***: Name of a supported configuration file

***baseName*** (optional):  If ***rootPath*** is a directory, ***baseName*** is required to indicate the name of a sub directory or a supported file name under the rootPath from which to read config.  The builder will attempt to find files with the baseName and a supported extension directly under rootPath

**isRequired**: Flag indicating whether an error should be raised if configurations are not found, as there are use cases where configuration files are optional.

#### PackagePathOptions Supported Configuration Files

Configuration files can be defined in several formats to be directly imported by the builder (via an argument in the form of the PackageConfigPathOptions):

***typescript***: entry files with .ts extensions, transpiled by Typescript

***ES6***: entry files with .js extensions, transpiled by Babel

**ES5**: entry files with .js extensions, using traditional Node module.exports

**json**: standard json

**yaml**: standard yaml

Regardless of how configurations are defined, its is expected that each source will return an object with either the shape of your final configuration object, or in the shape ***PackageConfigSettings*** (which allows settings defined in above files to be merged.

#### PackagePathOptions File Resolution

When importing file-based configuration (via a PackageConfigPathOptions argument), the following rules are used to resolve files:

1. If **rootPath** is a valid file, it is imported directly
2. if **rootPath** is a directory, **baseName** is used to search first to search for a file with that name and a supported extension.  If found, that file will be imported.  If no files exist with baseName + supported extension, the importer will look for a directory with baseName.
3. For directory imports, the importer will first look for **index.(js|ts)**, and try to load that.  If no index.(js|ts), importer will look for separate files named based on the structure of PackageConfigSettings (ie base.(yaml|json), files named based on NODE_ENV setting, and a file named env-vars.(yaml|json), etc.).  These files will be loaded and assembled into a PackageConfigSettings object, and the values will be merged as described above.


#### PackagePathOptions References to other config files

For settings defined in files and passed to the builder through **PackageConfigPathOptions** argument, external config settings can be referenced inline using the syntax **$ref=<path>** where <path> is relative to the current file.  <path> can reference a directory containing standard files (base, nodEnv, envOverride), or can be a path to a specific configuration file in a supported format.

***src/build.yaml***

````yaml
buildSetting1: buildSetting
appSettings: $ref=./app/settings
````

***src/app/settings/base.yaml***

````yaml
setting1: myAppSetting
setting2: myAppSetting2
````
***src/loadConfig.ts***

````typescript
import { createPackageConfigBuilder } from '@cirrusct/config';

// create a builder with path info to our config settings
const builder = createPackageConfigBuilder(
    {
        rootPath: 'src/build.yaml',
    }
);

// build config
const settings = await builder.build();

// outputs
console.dir(settings.appSettings);
  // -> $ { setting1: myAppSetting, setting2: myAppSetting2 }

````

### AWS Parameter Store Integration

AWS Parameter store can be used to hold values and is essentially used as a source for environment variable overrides.  

Current implementation is very limited.  It looks for an environment variable value for ***PACKAGE_CONFIG_SSM_PARAMETER***.  The value of this environment variable defines the name of the SSM parameter to lookup.  The value of this parameter in SSM is expected to be a JSON object containing keys with environment variable names defined in **envVars** of the configuration and values that will be used to override the setting its mapped to.  The SSM values for each **envOverride** will be merged on top of the configuration settings.

For example:

````bash
## Define the name of the SSM parameter to pull env var vals from
export PACKAGE_CONFIG_SSM_PARAMETER="mySsmParam"
````

***src/settings/env-vars.yaml***

````yaml
## Define the env var names that map to a setting
setting1: MYAPP_SETTING1
````

***AWS SSM mySsmParam value***
````
{
    "MYAPP_SETTING1": "SSM setting1 value"
}
````

*setting1* gets its value from SSM

