figue
Version:
Platform agnostic configuration management library, with environmental variables and validation, like convict but cross-env and zod schemas
154 lines (151 loc) • 4.72 kB
JavaScript
//#region src/figue.errors.ts
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: message$1, definition }) => `${path?.join(".")}${definition?.env ? ` (${definition.env})` : ""}: ${message$1}`).join("\n");
return createFigueError({
message,
code: "CONFIG_VALIDATION_ERROR"
});
}
//#endregion
//#region src/utils.ts
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];
}
//#endregion
//#region src/figue.ts
function validateConfig({ configDefinition, configValues, path = [] }) {
const config = {};
const issues = [];
for (const key in configDefinition) {
const currentPath = [...path, key];
const value = configValues[key];
const definition = configDefinition[key];
const nestedResult = validateConfigElement({
definition,
value,
currentPath
});
config[key] = nestedResult.config;
issues.push(...nestedResult.issues);
}
return {
config,
issues
};
}
function validateConfigDefinitionElement({ definition, value, currentPath }) {
const validation = definition.schema["~standard"].validate(value);
if (validation instanceof Promise) throw new TypeError("Schema validation must be synchronous");
if (validation.issues) return { issues: validation.issues.map((issue) => ({
...issue,
path: currentPath,
definition
})) };
return {
config: validation.value,
issues: []
};
}
function validateConfigElement({ definition, value, currentPath }) {
if (isConfigDefinitionElement(definition)) return validateConfigDefinitionElement({
definition,
value,
currentPath
});
if (typeof value === "object" && value !== null) return validateConfig({
configDefinition: definition,
configValues: value,
path: currentPath
});
return { issues: [{
path: currentPath,
message: `Expected object with schema at ${currentPath.join(".")}, got ${typeof value}`
}] };
}
function isConfigDefinitionElement(config) {
return typeof config === "object" && config !== null && "schema" in config && typeof config.schema === "object" && config.schema !== null && "~standard" in config.schema;
}
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, env$1) => ({
...acc,
...env$1
}), {});
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 { issues, config } = validateConfig({
configDefinition,
configValues: mergedConfig
});
if (issues.length > 0) throw createConfigValidationError({ issues });
return {
config,
env,
envConfig
};
}
//#endregion
export { defineConfig };
//# sourceMappingURL=index.js.map