Componer múltiples validadores
Una sola propiedad puede tener tantos validadores como necesite. El pipeline los ejecuta en orden de declaración, aplicando primero transformaciones y luego validando el resultado. Esto te permite apilar restricciones para que cada decorador maneje una sola responsabilidad. Una URL de base de datos puede usar IsString para presencia, un Validate personalizado para revisar prefijo y Secret para enmascarado. Cada decorador se mantiene enfocado y la combinación expresa el requisito completo.
Al apilar validadores, coloca decoradores de transformación antes que decoradores de validación. El decorador Transform convierte el string crudo in place, y cada Validate posterior o validador de tipo verá el valor transformado. Si inviertes el orden, un validador de tipo podría rechazar el string crudo antes de que tu transformación tenga oportunidad de convertirlo.
También puedes combinar validadores de tipo incorporados con comprobaciones personalizadas. Por ejemplo, IsPort ya valida que el valor sea un entero entre 1 y 65535. Agregar un Validate encima te permite restringir aún más el rango, como rechazar puertos por debajo de 1024 para forzar un binding no privilegiado.
import { EnvSchema, Env, IsPort, Validate } from '@spraxium/env';
@EnvSchema()
export class AppEnv {
@Env('HTTP_PORT', { secret: false })
@IsPort()
@Validate((value) =>
(value as number) >= 1024 ? true : 'Port must be 1024 or higher',
)
HTTP_PORT!: number;
}Herencia de esquemas
Los esquemas de entorno soportan herencia de clases. Una clase hija extiende una clase padre y agrega sus propios campos. El pipeline de validación recorre toda la cadena de prototipos, recopilando campos de cada ancestro. Esto permite construir esquemas por capas, donde una clase base define campos compartidos y clases específicas de una feature agregan los suyos.
El patrón más común es extender SpraxiumBaseEnv, que proporciona DISCORD_TOKEN, DISCORD_CLIENT_ID y NODE_ENV. Aplicas @EnvSchema solo a la clase hija. La clase padre ya tiene su propio @EnvSchema, y ambas entradas de metadatos se reconocen durante la validación.
Cuando dos clases en la cadena declaran un campo con la misma property key, gana la clase hija. Esto te permite sobrescribir un default del padre, por ejemplo cambiando el default de NODE_ENV de "development" a "production" en un esquema específico de despliegue. Los campos con property keys diferentes se fusionan normalmente sin importar qué clase los declare.
import { EnvSchema, Env, IsString, IsOptional } from '@spraxium/env';
import { SpraxiumBaseEnv } from '@spraxium/env';
@EnvSchema()
export class BaseEnv extends SpraxiumBaseEnv {
@Env('LOG_LEVEL', { default: 'info', secret: false })
@IsString()
LOG_LEVEL!: string;
@Env('SENTRY_DSN')
@IsString()
@IsOptional()
SENTRY_DSN?: string;
}Flujo de errores de validación
Cuando uno o más campos fallan validación, el framework construye una lista de objetos EnvFieldError. Cada error incluye la clave de variable, un reason code, un valor recibido opcional y un mensaje opcional del validador que lo rechazó. Los cuatro reason codes son missing cuando la variable no existe, invalid_value cuando un validador devuelve un mensaje de error, enum_mismatch cuando falla IsEnum y validation_failed para rechazos de Validate personalizados.
En modo producción (cuando NODE_ENV no es "development"), el framework imprime todos los errores en stderr y sale del proceso inmediatamente. Esto evita que un bot mal configurado se conecte a Discord con ajustes parciales o incorrectos. La salida de error enumera cada variable fallida con su reason, una máscara o el valor recibido (según el flag secret) y el mensaje del validador.
En modo desarrollo, el framework entra en un ciclo de hot-reload en lugar de salir. Observa el archivo .env y vuelve a validar cada 500 milisegundos. Cuando corriges el archivo y guardas, el pipeline se ejecuta otra vez. Si aún quedan errores, actualiza la lista de errores. Cuando todos se corrigen, imprime un mensaje de éxito con un diff de lo que cambió y continúa la secuencia de arranque. Este ciclo tiene un timeout de cinco minutos tras el cual sale para evitar procesos zombie.
Validación silenciosa
Cuando necesitas validar un esquema sin imprimir la tabla de inicio, pasa la opción silent. Esto es útil en entornos de prueba donde la salida de consola ensucia los resultados, o en procesos worker que deben validar sin loguear. El esquema sigue ejecutando todos los validadores y sigue lanzando errores, simplemente omite la tabla de éxito.
import { EnvValidator } from '@spraxium/env';
import { AppEnv } from '../app.env';
const env = EnvValidator.validate(AppEnv, { silent: true });El método validate devuelve una instancia totalmente poblada de tu clase de esquema. Cada propiedad contiene su valor validado y transformado. Puedes usar esta instancia directamente en tests o pasarla a servicios que necesiten configuración sin pasar por el contenedor de inyección de dependencias.
Carga de archivos DotEnv
El framework carga archivos .env automáticamente durante la validación. Primero comprueba .env.local y luego .env, usando el primero que encuentre. Las variables que ya existen en process.env nunca se sobrescriben, por lo que las variables de sistema y de pipelines CI siempre tienen prioridad.
El parser maneja comentarios (líneas que comienzan con #), valores entre comillas (simples o dobles, que se eliminan) y comentarios inline después de valores sin comillas. No soporta valores multilínea ni interpolación de variables. Para valores complejos como objetos JSON, envuélvelos entre comillas y usa IsJson para parsearlos.
Ejemplo completo
Este ejemplo muestra un esquema completo que combina herencia, múltiples tipos de validadores, transformaciones personalizadas y enmascarado de secretos. El esquema extiende SpraxiumBaseEnv para el token y el entorno de Node, agrega campos de base de datos y API, e incluye una transformación personalizada que divide una lista separada por comas en un array.
import {
EnvSchema,
Env,
SpraxiumBaseEnv,
IsMongoUri,
IsRedisUri,
IsPort,
IsBoolean,
IsEnum,
IsOptional,
Secret,
Default,
Transform,
Validate,
} from '@spraxium/env';
@EnvSchema()
export class AppEnv extends SpraxiumBaseEnv {
@Env('MONGO_URI')
@IsMongoUri()
MONGO_URI!: string;
@Env('REDIS_URI')
@IsRedisUri()
@IsOptional()
REDIS_URI?: string;
@Env('API_PORT', { default: '3000', secret: false })
@IsPort()
API_PORT!: number;
@Env('ENABLE_METRICS', { default: 'false', secret: false })
@IsBoolean()
ENABLE_METRICS!: boolean;
@Env('LOG_LEVEL', { default: 'info', secret: false })
@IsEnum(['debug', 'info', 'warn', 'error'])
LOG_LEVEL!: string;
@Env('WEBHOOK_SECRET')
@IsOptional()
@Secret()
WEBHOOK_SECRET?: string;
@Env('CORS_ORIGINS', { default: 'http://localhost:3000', secret: false })
@Transform((raw) => raw.split(',').map((s) => s.trim()))
@Validate((value) =>
Array.isArray(value) && value.length > 0 ? true : 'Need at least one origin',
)
CORS_ORIGINS!: string[];
}