figue
Version:
Platform agnostic configuration management library, with environmental variables and validation, like convict but cross-env and zod schemas
115 lines (109 loc) • 3.74 kB
JavaScript
;
const zod = require('zod');
function createFigueError({ message, code }) {
const error = Object.assign(new Error(message), { code, isFigueError: true });
return error;
}
function createConfigValidationError({ issues }) {
const message = issues.map(({ path, message: message2 }) => `${path.join(".")}: ${message2}`).join("\n");
return createFigueError({ message, code: "CONFIG_VALIDATION_ERROR" });
}
function mapValues(obj, fn) {
return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, fn(value, key)]));
}
function mergeDeep(...sources) {
return sources.reduce((acc, source) => {
for (const key in source) {
const value = source[key];
if (value && value instanceof Object && !Array.isArray(value)) {
acc[key] = mergeDeep(acc[key], value);
} else if (value !== void 0) {
Object.assign(acc, { [key]: value });
}
}
return acc;
}, {});
}
function castArray(value) {
return Array.isArray(value) ? value : [value];
}
function buildConfigSchema({ configDefinition }) {
const schema = mapValues(configDefinition, (config) => {
if (isConfigDefinitionElement(config)) {
return config.schema;
} else {
return buildConfigSchema({
configDefinition: config
});
}
});
return zod.z.object(schema);
}
function isConfigDefinitionElement(config) {
try {
return config instanceof Object && "schema" in config && config.schema instanceof zod.z.ZodType;
} catch (_ignored) {
return false;
}
}
function buildEnvConfig({ configDefinition, env }) {
return mapValues(configDefinition, (config) => {
if (isConfigDefinitionElement(config)) {
const { env: envKey } = config;
if (envKey === void 0) {
return void 0;
}
const value = env[envKey];
return value;
} else {
return buildEnvConfig({ configDefinition: config, env });
}
});
}
function getConfigDefaults({ configDefinition }) {
return mapValues(configDefinition, (config) => {
if (isConfigDefinitionElement(config)) {
const { default: defaultValue } = config;
return defaultValue;
} else {
return getConfigDefaults({
configDefinition: config
});
}
});
}
const isNotFalsy = (value) => Boolean(value);
function buildDefaultsConfig({
rawDefaults,
getDefaults,
envConfig,
configDefaults
}) {
const config = mergeDeep(configDefaults, envConfig);
const defaults = castArray(rawDefaults).filter(isNotFalsy);
const gotDefaultsRaw = getDefaults?.({ configDefaults, envConfig, config });
const gotDefaults = castArray(gotDefaultsRaw).filter(isNotFalsy);
return mergeDeep(...defaults, ...gotDefaults);
}
function defineConfig(configDefinition, {
envSources = [],
envSource = {},
defaults: rawDefaults = [],
priority = "env",
getDefaults
} = {}) {
const env = [...envSources, envSource].reduce((acc, env2) => ({ ...acc, ...env2 }), {});
const schema = buildConfigSchema({ configDefinition });
const configDefaults = getConfigDefaults({ configDefinition });
const envConfig = buildEnvConfig({ configDefinition, env });
const defaultsConfig = buildDefaultsConfig({ rawDefaults, envConfig, configDefaults, getDefaults });
const mergedConfig = priority === "env" ? mergeDeep(configDefaults, envConfig, defaultsConfig) : mergeDeep(configDefaults, defaultsConfig, envConfig);
const parsingResult = schema.safeParse(mergedConfig);
if (!parsingResult.success) {
throw createConfigValidationError({ issues: parsingResult.error.issues });
}
const { data: config } = parsingResult;
return { config, env, envConfig, schema };
}
exports.buildConfigSchema = buildConfigSchema;
exports.defineConfig = defineConfig;