README
configspace
A simple yet powerful typed config library, written in TypeScript, that supports lambdas and differentiates between environment and deployment.
- Simple, easy to use API, and thorough documentation.
- Automatically generated Typescript typings for both "intellisense" IDE support and compile-time errors.
- Works with AWS Secrets Manager, AWS Parameter Store, or custom dynamic lambda functions.
.json
files (no code) help keep configs clean and flexible. Any required code goes in lambda functions ("loaders").- Differentiates between concepts such as
environment
,deployment
, anduser
and provides an out-of-the-box solution with sensible folder structure and merge strategy. - Provides for overrides via CLI without polluting the CLI argument namespace.
- Forces user to put all possible settings into one default file for increased transparency and readability. Any override
must satisfy
Partial<DefaultConfig>
. - Clean, simple, readable code for future updates with minimal dependencies.
Installation
npm i configspace -DE
Setup
Install
Create a directory called
/config
in the root of your project and create adefault.json
file. Below is a typical structure:root/ └── config/ └── deployments └── test.acme.json └── environments └── development.json └── production.json └── users └── john.json └── default.json
default.json
is required, everything else is optional. Recommended practice is that this contains all of your "local development" settings.All configuration files must be a subset of the default configuration. That means for a configuration to exist in any
.json
file, it must also exist indefault.json
. In Typescript terms, all configurations must be of typePartial<DefaultConfig>
. In fact, the interfaceConfig
is created from the default. One of the main reasons for this limitation is due to the fact that otherwise, a type declaration wouldn't be possible at compile time.Arrays should all contain the same type (not mixed).
loaders
that are on the same property should all return the same type.None of the above are enforced (except the required default), but future versions may check for these conditions and throw an error.
Add build step to your build process by using
build-config-type
in your npm scripts as desired. Example:"scripts": { "build": "build-config-type" }
Running this will compile the
Config
interface from yourdefault.json
file. This must be run before you can use load or access the configuration. We recommend this is run often to ensure your type declaration is always up-to-date. Two recommended options are to usescripts.postinstall
orscripts.prepare
in your package.json file.
Usage
First, make sure you have already done everything in Setup above!
Loading the Configuration
You must first load the config, which resolves any loaders
and performs the merge.
import {loadConfig, getConfig} from "configspace";
loadConfig().then(() => {
//start server, etc.
console.log(getConfig()); // outputs config object
}).catch(console.log.bind(console));
Getting the Config Object
Once loaded, use getConfig
to access:
import {getConfig} from "configspace";
const config = getConfig(); // type of Config is inferred
console.log(config); // logs config object
const isFooBarEnabled: boolean = config.foo.bar; // Typescript error if does not exist or type mismatch
If you need the type interface, you can import it:
import {Config} from "configspace";
Configuration, Overrides, and Merge Strategy
Configurations are merged in order of importance, from least to most:
- default
- environment
- deployment
- user
- overrides
These can be provided via CLI or process.env
:
name | process.env | source |
---|---|---|
default | N/A | /config/default.json |
environment | NODE_ENV |
/config/environments/[NODE_ENV].json |
deployment | DEPLOYMENT |
/config/deployments/[DEPLOYMENT].json |
user | USER |
/config/users/[USER].json |
overrides | OVERRIDE |
N/A |
A few notes:
OVERRIDE
must be a valid JSON string with escaped quotes. Example:
NODE_ENV=development OVERRIDE="{\"a\": {\"b\": \"q\"}}" ts-node src/index.ts
USER
is usually provided by default by UNIX environments (tryconsole.log(process.env.USER)
)loaders
parameters are not merged. Aloader
instance is treated as a primitive.- Arrays are not merged
Loaders
Loaders are lambda functions that can return any value. They are run once during the type declaration build step, and once while the configuration is loading. They can be normal functions or use async/await/Promise.
To register a loader, simply pass them to loadConfig()
:
import {loadConfig} from "configspace";
const loaders = [
{foo_loader: async (params: {a: string}) => 'foo_' + params.a}
];
loadConfig(loaders).then(() => {
//start server, etc.
}).catch(console.log.bind(console));
Example config using this loader:
{
"foo": {
"[foo_loader]": {
"a": "demo"
}
}
}
Any object with a single property matching the pattern /^\[.*\]$/
(or [...]
) is assumed to be a loader. If a matching loader is not found, it will throw LoaderNotFound
. Loaders must implement the Loader
interface (you can also import it):
interface Loader {
[key: string]: (params: any) => any
}
Contribution
Please contribute to this project! Issue a PR against master
and request review.
- Please test your work thoroughly.
- Make sure all tests pass with appropriate coverage.
How to build locally
npm i
Running tests
npm test
How to publish (for owners)
Create a release off of master
. Be sure to use proper semver. Use -rc[x]
for pre-releases. Also make sure to check 'pre-release' in GitHub page if you are publishing a pre-release.