npm package: @n7e/configuration
Description: A lightweight configuration library.
Author: Martin Pettersson
Latest version: 0.4.0

# Configuration

A lightweight configuration library.

Whether to be used as configuration for an entire application or just for small
objects with convenience methods, this library provides a configuration solution
that is easy to use and extend.

For further insights, read on.

- [Installation](#installation)
- [Configuration Object](#configuration-object)
  - [Key Path](#key-path)
  - [Check Value Presence](#check-value-presence)
  - [Retrieve Value](#retrieve-value)
  - [Remove Value](#remove-value)
  - [Clear Values](#clear-values)
- [Configuration Builder](#configuration-builder)
    - [Configuration Sources](#configuration-sources)
    - [Object Configuration Source](#object-configuration-source)
    - [Default Configuration Builder](#default-configuration-builder)
- [Configuration Section](#configuration-section)
  - [Configuration Section Factory](#configuration-section-factory)
  - [Default Configuration Section Factory](#default-configuration-section-factory)
- [Configuration Manager](#configuration-manager)


## Installation

To install this library use your favorite package manager. No additional
steps are required to start using the library.

```shell
npm install @n7e/configuration
```

*This library is implemented in TypeScript but can be used with JavaScript
without any additional steps.*


## Configuration Object

The protagonist of this library is the configuration object. Configuration
objects lets you retrieve and manipulate configuration values. All configuration
objects implement the `Configuration` interface. To reference it simply import
it and use it as a type.

```typescript
import { Configuration } from "@n7e/configuration";

function doSomethingWith(configuration: Configuration): void {
    // ...
}
```

*`Configuration` is just an interface describing the functionality of a
configuration object. To create configuration objects use a
[configuration builder](#configuration-builder).*


### Key Path

Key paths are used to reference values in configuration objects. To reference
leaf values in nested objects the key in each subsequent object is separated
with a `.`.

The key path for the value `"value"` in the following structure looks like:
`nested.key`.


```javascript
{
    nested: {
        key: "value"
    }
}
```


### Check Value Presence

To check whether a configuration value is present in a configuration object use the `has` method:

```typescript
if (configuration.has("key")) {
    // ...
}
```

*Even if the value at the given key path is null or undefined the value is
considered to be present.*


### Retrieve Value

To retrieve configuration values from a configuration object use the `get` method:

```typescript
const value = configuration.get("key");
```

The value will be of any type found at the given key path. So if the key path
does not reference a leaf the type will be that of an object.

If the key path does not reference a value `undefined` will be returned. You can
supply a desired value to use in case of an absent value:

```typescript
const value = configuration.get("key", "default value");
```

If the key path does not reference a value "default value" will be returned.


### Set Configuration Value

To set a configuration value use the `set` method. Values can be of any type:

```typescript
configuration.set("key", "value");
```

The key path will reference the given value. If the key path is nested the
necessary intermediary objects will be created in the configuration object. To
illustrate this suppose we start with an empty configuration object and add a
value to a nested key path:

```typescript
configuration.set("nested.key", "value");
```

The value referenced by the key path "nested" will be `{key: "value"}`.


### Remove Value

To remove a value from a configuration source use the `remove` method:

```typescript
configuration.remove("key");
```


### Clear Values

To clear all values from a configuration object use the `clear` method.

```typescript
configuration.clear();
```


## Configuration Builder

To create configuration objects you should use a `ConfigurationBuilder`.

To define what configuration values makes up the final configuration object you
can add any number of [configuration sources](#configuration-sources).

```typescript
configurationBuilder.addConfigurationSource(jsonFileConfigurationSource)
configurationBuilder.addConfigurationSource(restConfigurationSource)
configurationBuilder.addConfigurationSource(someOtherConfigurationSource);
```

A configuration builder can be passed around and incrementally augmented to
eventually produce a desired configuration object.

```typescript
const configuration = await configurationBuilder.build();
```

*`ConfigurationBuilder` is just an interface describing the functionality of a
configuration builder. To create a configuration builder instance you need to
reference a specific [implementation](#default-configuration-builder).*

The return value of the `addConfigurationSource()` method is a configuration
source definition instance which can be used to further configure a
configuration source.


### Configuration Sources

Configuration sources provide configuration builders with the values to build
configuration objects from. The asynchronous nature of configuration sources
allows for configuration values to be retrieved from any number of external
resources.

*`ConfigurationSource` is just an interface describing the functionality of a
configuration source. To create a configuration source instances you need to
reference a specific [implementation](#object-configuration-source).*


#### Object Configuration Source

There's a provided implementation of `ConfigurationSource` that provides
configuration values from a plain JavaScript object. The final configuration
object will contain the values of any object provided to the constructor.

```typescript
import { ObjectConfigurationSource } from "@n7e/configuration";

configurationBuilder.addConfigurationSource(new ObjectConfigurationSource({key: "value"}));
```


### Default Configuration Builder

There's a provided implementation of `ConfigurationBuilder` ready to use.

```typescript
import { DefaultConfigurationBuilder } from "@n7e/configuration";

const configurationBuilder = new DefaultConfigurationBuilder();

// Add any desired configuration sources...

const configuration = await configurationBuilder.build();
```


## Configuration Section

Configuration sections are essentially configuration objects operating in
isolation on a specific key path. Any and all operations on the configuration
section will only affect the specified key path.

This can be useful when you want to enable some procedure to modify
configuration but don't want to give the procedure access to the entire
configuration object.

```typescript
setRelevantConfigurationValues(configurationSectionFactory.createConfigurationSection("nested", configuration));

function setRelevantConfigurationValues(configuration: Configuration): void {
    configuration.set("key", "value");
}
```

Calling `all()` on the provided configuration object after the procedure call
will yield the following value:

```javascript
{
    nested: {
        key: "value"
    }
}
```

*To create a configuration section use a
[configuration section factory](#configuration-section-factory).*


### Configuration Section Factory

Configuration section factories has the ability to create configuration sections.

*`ConfigurationSectionFactory` is just an interface describing the functionality
of a configuration section factory. To create a configuration section factory
instance you need to reference a specific
[implementation](#default-configuration-section-factory).*


#### Default Configuration Section Factory

There's a provided default implementation of `ConfigurationSectionFactory`.

```typescript
import { DefaultConfigurationSectionFactory } from "@n7e/configuration";

const configurationSectionFactory = new DefaultConfigurationSectionFactory();

/*
 * The created configuration section will operate on the "section" key path of
 * the provided configuration object.
 */
const configurationSection = configurationSectionFactory.createConfigurationSection("section", configuration);
```


## Configuration Manager

Configuration managers are used to allow configuration in a running application
to be updated at runtime. Here is an example of how to configure a dependency
injection container with the default configuration manager to allow the
configuration to be updated.

```typescript
import { DefaultConfigurationManager } from "@n7e/configuration";
import type { ContainerBuilder } from "@n7e/dependency-injection";

function configure(containerBuilder: ContainerBuilder): void {
    const configurationManager = new DefaultConfigurationManager(configuration);

    containerBuilder.addFactory("configurationManager", () => configurationManager).makeSingleton();
    containerBuilder.addFactory("configuration", () => configurationManager.current);
}
```

After the configuration has been replaced any service resolving the
configuration from the dependency injection container would get the new
configuration instance.

```typescript
import type { ConfigurationManager } from "@n7e/configuration";

function configure(configurationManager: ConfigurationManager): void {
    configurationManager.replaceWith(newConfiguration);
}
```