envase
Version:
Type-safe environment variable validation with Standard Schema compliance
87 lines • 3.37 kB
JavaScript
import { EnvaseError } from "./errors/envase-error.js";
export const detectNodeEnv = (env) => {
const nodeEnv = env.NODE_ENV;
return {
isProduction: nodeEnv === 'production',
isTest: nodeEnv === 'test',
isDevelopment: nodeEnv === 'development',
};
};
export const envvar = (name, schema) => [name, schema];
export const parseEnv = (env, envSchema) => {
const envvarValidationIssues = [];
// biome-ignore lint/suspicious/noExplicitAny: Explicit 'any' is required due to nature of recursive processing
const parseConfigObject = (schema) => {
return Object.fromEntries(Object.entries(schema).map(([key, value]) => {
if (Array.isArray(value)) {
const [envvarName, schema] = value;
const envvarValue = env[envvarName];
const result = schema['~standard'].validate(envvarValue);
if (result instanceof Promise ||
('then' in result && typeof result.then === 'function')) {
throw new Error(`Schema validation for envvar "${envvarName}" must be synchronous`);
}
if (result.issues) {
envvarValidationIssues.push({
name: envvarName,
value: envvarValue,
messages: result.issues.map(({ message }) => message),
});
return [key, null];
}
return [key, result.value];
}
return [key, parseConfigObject(value)];
}));
};
const config = parseConfigObject(envSchema);
if (envvarValidationIssues.length > 0) {
throw new EnvaseError(envvarValidationIssues);
}
return config;
};
// Helper to check if value is a resolver function
const isResolver = (value) => typeof value === 'function';
// Helper to process computed schema recursively
const processComputed = (computed, rawConfig) => {
return Object.fromEntries(Object.entries(computed).map(([key, value]) => [
key,
isResolver(value)
? value(rawConfig)
: processComputed(value, rawConfig),
]));
};
// Helper to deep merge two objects
const deepMerge = (target, source) => {
const result = { ...target };
for (const key of Object.keys(source)) {
const sourceValue = source[key];
const targetValue = result[key];
if (sourceValue &&
typeof sourceValue === 'object' &&
!Array.isArray(sourceValue) &&
targetValue &&
typeof targetValue === 'object' &&
!Array.isArray(targetValue)) {
result[key] = deepMerge(targetValue, sourceValue);
}
else {
result[key] = sourceValue;
}
}
return result;
};
// Implementation
export function createConfig(env, options) {
// Parse raw config using existing parseEnv
const rawConfig = parseEnv(env, options.schema);
// If no computed values, return raw config
if (!options.computed) {
return rawConfig;
}
// Compute derived values (handles nested structures)
const computedValues = processComputed(options.computed, rawConfig);
// Deep merge raw config with computed values
return deepMerge(rawConfig, computedValues);
}
//# sourceMappingURL=core.js.map