README
lambdaconf
A small, yet powerful typed and structured config library with lambda support for things like AWS Secrets Manager. Written in Typescript.
Out-of-the-box Typescript support 🔒
- Turn your runtime errors into safer compile-time errors! Automatically generated Typescript type definition for configuration object
Lambda Support 🤖
- Works with AWS Secrets Manager, AWS Parameter Store, or custom dynamic lambda functions
- Any custom logic can go here, keeping your config files logic-free
- Provides an easy sharable and reusable plugin interface for sharing or re-use
Simple & Easy to Use 😃
- All settings are in simple
.json
files. No logic (those can go into loaders) - Highly structured. Any override must satisfy
Partial<DefaultConfig>
- Enforces a simple and sensible folder structure
- Limited yet powerful feature set with clean documentation
- Small, simple, and modular codebase written in Typescript with no dependencies.
Flexible & Powerful 💪
- Provides for overrides via CLI without polluting the CLI argument namespace
- Differentiates between concepts such as
environment
,deployment
, anduser
and provides an out-of-the-box solution with sensible merge strategy
Installation
npm i lambdaconf -D
Setup
Install from npm
Create a directory called
/conf
in the root of your project and create adefault.json
file. Below is a typical structure:root/ └── conf/ └── 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. Think of them simply as overrides to the default. That means for a configuration to exist in any
.json
file, it must also exist indefault.json
. In Typescript terms, conf files must be of typePartial<Conf>
. In fact, theConf
type is simply created from thedefault.json
file.A property's type should not change simply because of a different environment, user, or deployment. This is basically saying the same as above.
loaders
that are used on the same property in different files should all return the same type (again, same as above).Arrays should be homogenous (not of mixed types).
The default configuration directory is
/conf
, but you can specify a custom directory withprocess.env.CONF_DIR
Call
lambdaconf
in yourpackage.json
file to build theConf
type declaration file whenever the configuration changes. Example:{ "scripts": { "prepare": "lambdaconf" } }
This must be run any time the configuration has changed. Feel free to set this up however you want. In the above example, the file will be generated after
npm i
. Some IDEs might have some issues with caching the generatedConf.d.ts file
(which is stored innode_modules
). If you run into this problem, try restarting Typescript.
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 {loadConf, getConf} from "lambdaconf";
loadConf().then(() => {
//start server, etc.
console.log(getConf()); // outputs config object
}).catch(console.log.bind(console));
Getting the Config Object
Once loaded, use getConf
to access:
import {getConf} from "lambdaconf";
const conf = getConf(); // type of Conf is inferred
console.log(conf); // logs config object
const isFooBarEnabled: boolean = conf.foo.bar; // Typescript error if does not exist or type mismatch
If you need the type interface, you can import it:
import {Conf} from "lambdaconf";
Configuration, Overrides, and Merge Strategy
Configurations are merged in this order, with the later ones overriding the earlier ones:
- default.json
- environment file
- deployment file
- user file
- CLI overrides
Which of these sources to choose depends on the presence of certain process.env
configuration variables:
process.env | conf file |
---|---|
NODE_ENV |
/conf/environments/[NODE_ENV].json |
DEPLOYMENT |
/conf/deployments/[DEPLOYMENT].json |
USER |
/conf/users/[USER].json |
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 are called during startup (run-time). A great example of this is fetching API keys from AWS Secrets Manager.
Loaders are run once during the type declaration build step (compile-time), and once while the configuration is loading (run-time). They can be normal functions or use async/await/Promise.
Loader "foo" Example
loaders/foo.ts
export function foo(params: {a: string}) {
return Promise.resolve(`foo_${a}`);
}
conf/default.json
{
"foo": {
"[foo]": {
"a": "demo"
}
}
}
index.ts
import {loadConfig, getConf} from "lambdaconf";
import {foo} from './loaders/foo';
const loaders = [{foo}];
loadConfig(loaders)
.then(() => {
console.log(getConf().foo); // "foo_demo"
//start server, etc.
})
.catch(console.log.bind(console));
To register a loader, simply pass it in an array to loadConfig()
.
Formal definition
Loader functions must implement the Loader
interface:
interface Loader {
[key: string]: (params: any) => any
}
It's not necessary, but you can import the Loader interface like so:
import {Loader} from 'lambdaconf';
In a conf file, any object with a single property matching the pattern /^\[.*\]$/
(or [...]
) is assumed to refer to a loader. If a matching loader is not found, it will throw a LoaderNotFound
error.
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