UNPKG

ts-app-env

Version:
147 lines (146 loc) 4.89 kB
"use strict"; 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; }