atikin-env
Version:
Ultra-fast, zero-dependency .env loader with type safety for Node/TS. Created by: Atikin Verse.
57 lines (49 loc) • 1.74 kB
text/typescript
import fs from 'fs';
import path from 'path';
type EnvSchema = Record<string, 'string' | 'number' | 'boolean'>;
interface LoadOptions {
path?: string;
allowPartial?: boolean;
defaults?: Record<string, any>;
}
function parseValue(value: string, type: string) {
switch (type) {
case 'number':
const n = Number(value);
if (isNaN(n)) throw new Error(`Invalid number: ${value}`);
return n;
case 'boolean':
if (value === 'true') return true;
if (value === 'false') return false;
throw new Error(`Invalid boolean: ${value}`);
case 'string':
default:
return value;
}
}
export function loadEnv(schema: EnvSchema, options: LoadOptions = {}) {
const envPath = options.path || path.resolve(process.cwd(), '.env');
const allowPartial = options.allowPartial ?? false;
const defaults = options.defaults || {};
if (!fs.existsSync(envPath)) throw new Error(`.env file not found at ${envPath}`);
const raw = fs.readFileSync(envPath, 'utf8');
const lines = raw.split('\n');
const envRaw: Record<string, string> = {};
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith('#')) continue;
const [key, ...rest] = trimmed.split('=');
envRaw[key] = rest.join('=').trim();
}
const finalEnv: Record<string, any> = {};
for (const key in schema) {
const expectedType = schema[key];
const rawValue = envRaw[key] ?? defaults[key];
if (rawValue === undefined) {
if (!allowPartial) throw new Error(`Missing required env variable: ${key}`);
continue;
}
finalEnv[key] = parseValue(rawValue, expectedType);
}
return finalEnv;
}