6.0.0 • Published 3 years ago

@jrh/adapt v6.0.0

Weekly downloads
-
License
-
Repository
-
Last release
3 years ago

@jrh/adapt

Keep your Node.js application's configuration in one place. Access it simply and declaratively. Easily change it based on your application's environment.

Installation

npm install @jrh/adapt

Basic Usage

Setting Up

To get started, all you need is a Configuration File in context/context.json (relative to the root of your project).

You can then use the Selecting Function to access your configuration data.

For example:

context/context.json

{
  "app": {
    "name": "hello-world"
  }
}

app.js

import adapt from '@jrh/adapt'

const appName = adapt('app.name')
console.log(appName)

CLI

node app.js
> "hello-world"

Adding Adaptive Objects

Maybe your app's configuration values change based on its environment. To handle this, add Adaptive Objects and the mode environment variable.

context/context.json

{
  "app": {
    "names": {
      "dev": "hello-world-dev",
      "production": "hello-world-production"
    }
  }
}

app.js

import adapt from '@jrh/adapt'

const appName = adapt('app.name')
console.log(appName)

CLI

mode=dev node app.js
> "hello-world-dev"

mode=production node app.js
> "hello-world-production"

Adding Adaptive Values

If there's configuration data you'd rather keep secret, bake in some Adaptive Values.

adapt will look in the Environment File for these values. If it can't find them there, it will look in process.env.

This is useful in production environments where the context/environment.json file may not exist.

Note: Values in the Environment File will take precedence over (but not replace) values in process.env.

context/context.json

{
  "api": {
    "token": "[API_TOKEN]"
  }
}

context/environment.json

{
  "API_TOKEN": "super-secret-token"
}

app.js

import adapt from '@jrh/adapt'

const apiToken = adapt('api.token')
console.log(apiToken)

CLI

node app.js
> "super-secret-token"

Adaptive Values + Adaptive Objects

You can use Adaptive Objects in the Environment File too.

context/context.json

{
  "app": {
    "names": {
      "dev": "hello-world-dev",
      "production": "hello-world-production"
    }
  },

  "api": {
    "token": "[API_TOKEN]"
  }
}

context/environment.json

{
  "API_TOKEN": {
    "dev": "super-secret-token-dev",
    "production": "super-secret-token-production"
  }
}

app.js

import adapt from '@jrh/adapt'

const apiToken = adapt('api.token')
console.log(apiToken)

CLI

mode=dev node app.js
> "super-secret-token-dev"

mode=production node app.js
> "super-secret-token-production"

Putting It All Together

With our powers combined, make your application adaptable!

context/context.json

{
  "app": {
    "name": "hello-world-[APP_NAME]"
  },

  "api": {
    "tokens": {
      "dev": "not-secret-token",
      "staging": "[API_TOKEN]",
      "production": "[API_TOKEN]"
    }
  },

  "services": {
    "logging": {
      "keys": {
        "_default": "default-logging-key",
        "staging": "[LOGGING_KEY]",
        "production": "[LOGGING_KEY]"
      }
    }
  }
}

context/environment.json

{
  "APP_NAME": {
    "dev": "from-environment-dev",
    "production": "from-environment-production"
  },

  "API_TOKEN": "super-secret-token",

  "LOGGING_KEY": {
    "staging": "super-secret-logging-key-staging",
    "production": "super-secret-logging-key-production"
  }
}

app.js

import adapt from '@jrh/adapt'

const appName = adapt('app.name')
const apiToken = adapt('api.token')
const loggingKey = adapt('services.logging.key')

console.log('App Name:', appName)
console.log('API Token:', apiToken)
console.log('Logging Key:', loggingKey)

CLI

mode=dev node app.js
> "App Name: hello-world-from-environment-dev"
> "API Token: not-secret-token"
> "Logging Key: default-logging-key"

mode=staging node app.js
> "App Name: hello-world-from-environment-staging"
> "API Token: super-secret-token"
> "Logging Key: super-secret-logging-key-staging"

mode=production node app.js
> "App Name: hello-world-from-environment-production"
> "API Token: super-secret-token"
> "Logging Key: super-secret-logging-key-production"

Configuration

The Configuration File

The Configuration File lives at context/context.json (relative to the root of your project). To change the default location, see Customizing File Locations.

This file can contain Adaptive Objects and Adaptive Values which will be parsed by the Selecting Function.

Example Configuration File

{
  "api": {
    "keys": {
      "_default": "bcd-234",
      "staging": "abc-123"
      "production": "[API_KEY]"
    }
  },

  "general": {
    "number": 5,
    "text": "Hello world"
  }
}

The Environment File

The Environment File lives at context/environment.json (relative to the root of your project). To change the default location, see Customizing File Locations.

This file is optional and contains environment variables for your application. It typically should not be committed to source control.

the Environment File file can contain Adaptive Keys which will be parsed by the selecting function.

If the selecting function can't find a value in the Environment File, it will look in process.env. This is useful in production environments where the context/environment.json file may not exist.

Note: Values in the Environment File will take precedence over (but not replace) values in process.env.

Adaptive Values cannot be used in the Environment File.

Example Environment File

{
  "API_LOCATION": {
    "_default": "dev-api.com",
    "staging": "staging-api.com"
    "production": "production-api.com"
  },
  
  "DATABASE_URL": "mysql://mydb"
}

Customizing File Locations

When importing adapt, it will look in the context directory (relative to the root of your project) for the Configuration and Environment files.

To customize where adapt looks for these files, use the loadAdapt function instead of the default export:

import { loadAdapt } from '@jrh/adapt'

const adapt = loadAdapt({
  configurationDirectory: '/my/custom/directory'
})

Programmatic Configuration

If you want to get even closer to the metal, you can skip file loading altogether and give adapt the data it needs programatically using the createSelectingFunction function:

import { createSelectingFunction } from '@jrh/adapt'

const adapt = createSelectingFunction({
  configuration: {
    // Provide configuration data like in the Configuration File.
  },

  environment: {
    // Provide environment data like in the Environment File.
  },

  // Optionally define a mode (or use `process.env.mode`).
  mode: 'dev'
})

Detailed Usage

The Selecting Function

Usage

import adapt from '@jrh/adapt'

Syntax

adapt(selector)

Arguments

NameTypeDescriptionExample
selectorStringA string in dot notation which targets a key in the configuration file.api.key

Exceptions

Throws a standard Error if:

Normal Keys

Normal keys can be accessed by name, using dot notation.

Using the Example Configuration File:

adapt('api.keys.staging') // 'abc-123'
adapt('general.number') // 5
adapt('general.text') // 'Hello world'

Adaptive Objects

In the Configuration File

In the Configuration File, Adaptive Objects have two special qualities:

  • The key which contains them is plural (i.e. tokens),
  • They are an object with keys representing application modes, such as:
"keys": {
  "dev": "key-dev",
  "staging": "key-staging"
}

Adaptive Objects in the Configuration File will be accessed when the singular form of a given selector (api.key) cannot be found.

A key from the object will be chosen based on the mode environment variable.

See Adding Adaptive Objects for an example.

In the Environment File

In the Environment File, Adaptive Objects have one special quality:

  • They are an object with keys representing application modes, such as:
"API_TOKEN": {
  "dev": "token-dev",
  "staging": "token-staging"
}

Adaptive Objects in the Environment File will be accessed when the Configuration File contains an Adaptive Value.

A key from the object will be chosen based on the mode environment variable.

See Adaptive Values + Adaptive Objects for an example.

The Default Key

All Adaptive Objects can contain a _default key.

This key will be accessed if the mode environment variable is not set, or if there are no keys within the Adaptive Object which match the current mode.

See Putting it All Together for an example.

The mode Environment Variable

Adaptive Objects can only function if the mode environment variable is set.

It can be set however you like, as long as it is present when the Selecting Function or the adapt command are used.

Adaptive Values

Adaptive Values are used in the Configuration File to reference data in the environment.

These values are surrounded in brackets, such as [API_KEY]. This tells the Selecting Function to replace this section with a value from the Environment File or process.env.

See Adding Adaptive Values for an example.

Interpolated Adaptive Values

Adaptive Values can contain additional text and/or multiple bracketed sections. For example:

{
  "database": "mysql://[DATABASE_USER]:[DATABASE_PASSWORD]"
}

See Putting it All Together for an example.

Preloading Environment Data

By default, adapt will not load data from the Environment File into process.env.

If you'd like to do this you can use the adapt command.

Example Environment Files

Basic
{
  "API_KEY": "api-key",
  "DATABASE_URL": "db-url"
}
With Adaptive Objects
{
  "APP_ID": "abc123",

  "API_KEY": {
    "_default": "base-key",
    "production": "production-key"
  },

  "DATABASE_URL": {
    "dev": "dev-db",
    "staging": "staging-db",
    "production": "production-db"
  }
}

The adapt Command

The adapt command will run shell commands with the environment variables defined in your Environment File loaded into process.env.

This can be useful when running a command line utility which relies on environment variables.

Usage

adapt '[command]'

Example

context/environment.json

{
  "DATABASE_URL": "my-database"
}

CLI

mode=testing adapt "echo $DATABASE_URL"
> "my-database"
6.0.0

3 years ago

5.5.0

3 years ago

5.4.0

3 years ago

5.3.0

3 years ago

5.2.0

3 years ago

5.1.0

3 years ago

5.0.0

3 years ago

4.1.0

3 years ago

4.0.0

3 years ago

3.1.0

3 years ago

3.0.1

3 years ago

3.0.0

3 years ago