@briandlee/eevee
v0.1.2
Published
A simple environment variable extraction and validation library.
Maintainers
Readme
eevee
A simple, configurable environment variable reader and validator with zero dependencies.
The library is intended to be very lightweight and extensible while providing type-safety. All transformers are implemented as simple functions. The library comes with a handful of common transformers but the intention is that you can easily add your own.
Basic Usage
import { asInt, ev, must, pipe } from "eevee";
const port: number = ev(process.env, "PORT", pipe(must, asInt));Using composition
function validEmail(v: V<string | undefined>): V<string | undefined> => {
if (v.value === undefined) {
return v;
}
if (v.value.indexOf('@') < 0 || !/\.[a-z]{2,}$/.test(v.value)) {
throw new Error("Value must be an email address.");
}
return v;
}
function mustBeEmail(v: V<string | undefined>) {
return validEmail(must(v));
}
const maybeEmail: string | undefined = ev(process.env, "MAYBE_EMAIL", validEmail);
const adminEmail: string = ev(process.env, "ADMIN_EMAIL", mustBeEmail);Readers
A reader's job is simple - yield an environment variable value by name. The most common
form of a reader is to read from process.env. The signature of all environment variables
is a string | undefined. It's the job of a reader to be given a name and yield a
V<string | undefined>. Below are two examples: one using process.env and another for
NestJS's ConfigService.
const processEnvReader = (env: Record<string, string | undefined> => (name: string) => {
return {
value: env[name],
name,
secret: false,
};
};
const nestConfigServiceReader = (cs: ConfigService) => (name: string) => {
return {
value: cs.get<string>(name),
name,
secret: false,
};
};Appliers
An applier's job is a bit more complicated. The purpose of an applier is to intercept a transformation and perform some side effect (like logging) during the transformation phase.
Below are two examples creating an applier for logging using the built-in console and
a third-party logger like NestJS's Logger. Both log errors and
post-transformation results from environment variable parsing.
const consoleLoggerApplier: Applier = <T>(transform: VT<T>): VT<T> => {
return (v: V<string | undefined>) => {
let result: V<T>;
try {
result = transform(v);
} catch (e) {
console.error(`${v.name}: ${e}`);
throw e;
}
if (result.secret) {
console.log(`${v.name} = ********`);
} else {
console.log(`${v.name} = ${result.value}`);
}
return result;
};
};
function createNestLoggerApplier(logger: Logger): Applier {
return <T>(transform: VT<T>): VT<T> => {
return (v: V<string | undefined>) => {
let result: V<T>;
try {
result = transform(v);
} catch (e) {
logger.error(`${v.name}: ${e}`);
throw e;
}
if (result.secret) {
logger.log(`${v.name} = ********`);
} else {
logger.log(`${v.name} = ${result.value}`);
}
return result;
};
};
}bind
When you have a Reader and (optionally) an Applier, you can use bind to
create an object ready to read variables. It will automatically pull from
your configured source and pass through your applier as it is used.
import { asDuration, bind, must, pipe } from "eevee";
const ev = bind(processEnvReader(process.env), consoleLoggerApplier(console));
const host: string = ev("SERVICE_HOST", must);
const hostTimeoutMilliseconds: number = ev(
"SERVICE_TIMEOUT",
pipe(must, asDuration),
);