Extend easyConfig API
Current EasyConfig
Limits: Everything is a string. No built-in Zod validation like auto-config. No nesting, no arrays, no booleans, no numbers.
export const config = easyConfig({
"url": ['--url', '-u', 'APP_URL'],
"port": ['--port', '-p', 'PORT'],
"debug": ['--debug', '-D'],
});
Well, it lives up to the name at least!
Proposed API Changes
Validator function interface:
type ValidatorFunction<TReturnType> = (
value: string, // The value to validate: '08080'
sourceKey: string, // `--port`
fieldName: string, // `port`
allMatchedKeys: Record<string, string> // { 'PORT': '8080', '--port': '08080' };
) => TReturnType;
Example Validators
To support custom logic while getting types for free we'll support a trailing function pattern.
After the list of input args & env keys, include a ValidatorFunction as the last array element.
export const config = easyConfig({
"debug": ['--debug', '-D', Boolean],
"url": ['--url', '-u', 'APP_URL', url => new URL(url)],
});
config.debug; // boolean
config.url; // URL instance
Tip 1: Auto-type keys using JS' built-in methods Boolean(), Number(), String(), etc
A quick way to add type hints: use the language's primitive constructors.
export const config = easyConfig({
"port": ['--port', '-p', Number],
"debug": ['--debug', '-D', Boolean],
});
config.port; // number
config.debug; // boolean
ProTip 2: Convert inputs into advanced types
This snippet takes a url string and returns a new URL() instance.
If an error is encountered, the app will prevent invalid runtime states by exiting & re-throwing the original error message!
// Example used anonymous arrow function, but you can use a named function too.
const parseUrl = (url: string) => new URL(url);
ProTip 3: Parse JSON input strings
Note: Advanced example. Feel free to skip & revisit later.
The following example is a fair bit more complex.
The goal is showing a 'real-world' JSON parsing pattern evolves with this library.
Since arguments and environment variables can support long strings, it can sometimes be a convenient strategy to use stringified JSON. For example adding/removing features is easier if you know that theres only 1 place it's configured. A related issue: most enterprise apps eventually have to manage dozens to 100+'s of environment & configuration variables. The sprawl can quickly be a drag to manage and difficult to trace when things go wrong.
Let's visualize combining 2 keys into PRODUCT_PAGE={json}.
Given
PRODUCT_PAGE_SCROLL=infinite
PRODUCT_PAGE_LIMIT=10
We want to combine them into a single JSON key:
PRODUCT_PAGE={"scroll": "infinite", "limit": 10}
Future product additions can be added to the JSON with relative ease:
Let's say, your legal team has requested a new disclaimer be added to the product page.
We might have named this DISCLAIMER_PRODUCT, which could easily be overlooked when sorting and looking for PRODUCT_* in a sea of keys.
PRODUCT_PAGE={"scroll": "infinite", "limit": 10, "disclaimer": "..."}
SUGGESTIONS_PAGE={"scroll": "none", "limit": 10, "disclaimer": "(items other shoppers purchased)"}
When adding legal text to a website, it may be more important to see the disclaimers co-located in one key.
DISCLAIMERS={"product": "...", "home": "all rights reversed", "partner_content": "rights owned by their respective owners"}
Design to make features more serviceable & manageable. ✨
Let's look at more examples of JSON embedded keys.
export DATADOG_CONFIGS='{"apiKey":"my-key","sampling":"0.25","tags":["env:prod"]}'
export ACTIVE_FEATURES='["checkout_v4","cta_ab_test_v55","rewards_v3"]'
The following example shows how to tie this all together:
export const config = easyConfig({
"datadog": ['DATADOG_CONFIGS', safeJsonParser],
"features": ['--active-features', 'ACTIVE_FEATURES', safeJsonParser],
});
Example Strict & Safe JSON Parsers
// Throws on invalid JSON.
const strictJsonParser = (json: string) => JSON.parse(json);
// Invalid JSON will get logged, and the app will return false.
const safeJsonParser = (json: string, sourceKey: string) => {
try {
if (!json || json.length < 2) return false;
return JSON.parse(json);
} catch (error) {
console.error('Invalid JSON supplied', error, {json, sourceKey});
// NOTE: Change your default fall-back json value: instead of `false`, maybe `null` or an empty object or array might make more sense.
return false;
}
};
4. ProTip: Configurable Port number validator
// Using the curried `portRangeChecker()` helper (see below)
const isHttpIsh = portRangeChecker({min: 80, max: 443});
// The func `isHttpIsh(port)` accepts a number, and if outside the range provided it'll fail, and if inside the.
isHttpIsh(79) //-> false
isHttpIsh(80) //-> true
isHttpIsh(444) //-> false
isHttpIsh(79) //-> false
const portRangeChecker = ({min = 1_000, max = 60_000}: PortRange) =>
(port: number | string) => {
port = parseInt(`${port}`);
if (port > 65_535) throw Error(`Port number ${port} can't exceed 65535`);
if (port < 1) throw Error(`Port number ${port} must be at least 1`);
if (port >= min && port <= max) return port;
throw new Error(`Unexpected Port ${port} value detected.`);
}
type PortRange = {min: number, max: number};
5. ProTip: Zod-based type validation
// Use a Zod schema to validate, type check any config values.
const parseAcctId = input => zod.any(input).refine(Number).safeParse(input)?.data;
export const config = easyConfig({
"accountId": ['--accountId', 'ACCOUNT_ID', parseAcctId],
});
Extend easyConfig API
@elite-libs/config, and seamlessly combine the 2 use case/interfaces.Array<string | Function>w/ optional last-position validation function.)autoConfigrename tocliConfig,configMap,appConfig, (should this be default export anyway?)easyConfig, keep name? Might change to:appConfig?easyConfigdoesn't support the optional formatter function in tail position.Current EasyConfig
Limits: Everything is a string. No built-in Zod validation like auto-config. No nesting, no arrays, no booleans, no numbers.
Proposed API Changes
Validator function interface:
Example Validators
To support custom logic while getting types for free we'll support a trailing function pattern.
After the list of input args & env keys, include a
ValidatorFunctionas the last array element.Tip 1: Auto-type keys using JS' built-in methods
Boolean(),Number(),String(), etcA quick way to add type hints: use the language's primitive constructors.
ProTip 2: Convert inputs into advanced types
This snippet takes a
urlstring and returns anew URL()instance.If an error is encountered, the app will prevent invalid runtime states by exiting & re-throwing the original error message!
ProTip 3: Parse JSON input strings
The following example is a fair bit more complex.
The goal is showing a 'real-world' JSON parsing pattern evolves with this library.
Since
argumentsandenvironment variablescan support long strings, it can sometimes be a convenient strategy to use stringified JSON. For example adding/removing features is easier if you know that theres only 1 place it's configured. A related issue: most enterprise apps eventually have to manage dozens to 100+'s of environment & configuration variables. The sprawl can quickly be a drag to manage and difficult to trace when things go wrong.Let's visualize combining 2 keys into
PRODUCT_PAGE={json}.Given
PRODUCT_PAGE_SCROLL=infinitePRODUCT_PAGE_LIMIT=10We want to combine them into a single JSON key:
PRODUCT_PAGE={"scroll": "infinite", "limit": 10}Future
productadditions can be added to the JSON with relative ease:Let's say, your legal team has requested a new disclaimer be added to the product page.
We might have named this
DISCLAIMER_PRODUCT, which could easily be overlooked when sorting and looking forPRODUCT_*in a sea of keys.PRODUCT_PAGE={"scroll": "infinite", "limit": 10, "disclaimer": "..."}SUGGESTIONS_PAGE={"scroll": "none", "limit": 10, "disclaimer": "(items other shoppers purchased)"}When adding legal text to a website, it may be more important to see the disclaimers co-located in one key.
DISCLAIMERS={"product": "...", "home": "all rights reversed", "partner_content": "rights owned by their respective owners"}Let's look at more examples of JSON embedded keys.
The following example shows how to tie this all together:
Example Strict & Safe JSON Parsers
4. ProTip: Configurable Port number validator
5. ProTip: Zod-based type validation