ts-app-env
Version:
A config library for TypeScript.
147 lines (146 loc) • 4.89 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.option = exports.boolean = exports.string = exports.number = exports.ConfigError = exports.newConfig = void 0;
function newConfig(spec, env, options = {}) {
const errors = [];
// go through our spec version of S that the type system thinks has primitives
// but are really ConfigOptions that we've casted to the primitive
const config = {};
Object.keys(spec).forEach((k) => {
try {
const v = spec[k];
if (v instanceof ConfigOption) {
config[k] = v.getValue(k, env, options);
}
else if (["string", "number", "boolean", "symbol", "bigint"].includes(typeof v)) {
// Add primitive types as-is
config[k] = v;
}
else if (typeof v === "object") {
// assume this is a nested config spec
config[k] = newConfig(v, env, options);
}
}
catch (e) {
if (e instanceof Error) {
errors.push(e);
}
else {
throw e;
}
}
});
logOrFailIfErrors(options, errors, options.ignoreErrors || false);
return Object.freeze(config);
}
exports.newConfig = newConfig;
function logOrFailIfErrors(options, errors, ignoreErrors) {
if (errors.length === 0) {
return;
}
const message = errors.map((e) => e.message).join(", ");
if (!ignoreErrors) {
maybeLog("error", message, options);
throw new ConfigError(message);
}
else {
maybeLog("info", `Ignoring errors while instantiating config: ${message}`, options);
}
}
function maybeLog(level, message, options) {
const { doNotLogErrors, logger = console } = options;
if (doNotLogErrors === true) {
return;
}
switch (level) {
case "error":
logger.error(message);
break;
case "warn":
logger.warn(message);
break;
case "info":
logger.info(message);
break;
case "debug":
logger.debug ? logger.debug(message) : undefined;
break;
}
}
class ConfigError extends Error {
}
exports.ConfigError = ConfigError;
/** An individual config option, e.g. a string/int. */
class ConfigOption {
}
function number(options = {}) {
return option(options, (s) => {
const v = parseFloat(s);
if (isNaN(v)) {
throw new Error("is not a number");
}
return v;
});
}
exports.number = number;
function string(options = {}) {
return option(options, (s) => s);
}
exports.string = string;
function boolean(options = {}) {
return option(options, (s) => s === "true");
}
exports.boolean = boolean;
/** Construct a generic config option. */
function option(options, parser) {
const opt = new (class extends ConfigOption {
getValue(propertyName, env, appOptions) {
// use propertyName if they didn't specify an env
const envName = options.env || snakeAndUpIfNeeded(propertyName);
const envValue = env[envName];
if (envValue !== undefined) {
try {
return parser(envValue);
}
catch (e) {
if (e instanceof Error) {
throw new ConfigError(`${envName} ${e.message}`);
}
else {
throw e;
}
}
}
if (options.default !== undefined) {
return options.default;
}
if (options.optional) {
return undefined;
}
if (options.notNeededIn !== undefined && appOptions !== undefined && appOptions.nodeEnv !== undefined) {
if (toList(options.notNeededIn).indexOf(appOptions.nodeEnv) > -1) {
return undefined;
}
}
throw new ConfigError(`${envName} is not set`);
}
})();
// This is the cute "lie through our teeth to the type system" hack where
// we pretend our ConfigOption<V> objects are Vs so that we can instaniate
// the initial "spec" version of the app's config class, which we'll then
// use to iterate over the ConfigOption<V>'s and resolve them to V's on
// the real config instance.
return opt;
}
exports.option = option;
function snakeAndUpIfNeeded(propertyName) {
if (propertyName.includes("_") || propertyName.match(/^[A-Z]+$/)) {
return propertyName; // assume it's already FOO_BAR
}
else {
return propertyName.replace(/([A-Z])/g, (l) => "_" + l).toUpperCase();
}
}
function toList(data) {
return typeof data === "string" ? [data] : data;
}