Spraxium logoSpraxium

Padrões Avançados

Padrões avançados para variáveis de ambiente, incluindo validadores personalizados, herança de schema e tratamento de erros. Aprenda a compor validadores, montar pipelines de validação e integrar o schema de env ao seu bot.

Compondo múltiplos validadores

Uma única propriedade pode carregar quantos validadores forem necessários. O pipeline os executa na ordem em que foram declarados, aplicando primeiro as transformações e depois validando o resultado. Isso permite empilhar restrições para que cada decorator cuide de uma preocupação específica. Uma URL de banco de dados, por exemplo, pode usar IsString para garantir presença, um Validate personalizado para checar o prefixo e Secret para mascaramento. Cada decorator continua focado, e a combinação expressa o requisito completo.

Ao empilhar validadores, coloque os decorators de transformação antes dos decorators de validação. O decorator Transform converte a string bruta no próprio fluxo, e todo Validate ou validador de tipo declarado depois dele enxerga o valor já transformado. Se você inverter a ordem, um validador de tipo pode rejeitar a string bruta antes que sua transformação tenha a chance de convertê-la.

Você também pode combinar validadores de tipo embutidos com verificações personalizadas. Por exemplo, IsPort já valida que o valor é um inteiro entre 1 e 65535. Adicionar um Validate por cima permite restringir ainda mais o intervalo, como rejeitar portas abaixo de 1024 para exigir binding não privilegiado.

src/app.env.ts
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;
}

Herança de schema

Schemas de ambiente oferecem suporte a herança entre classes. Uma classe filha estende uma classe pai e adiciona seus próprios campos. O pipeline de validação percorre toda a cadeia de protótipos, coletando campos de cada ancestral. Isso torna possível montar schemas em camadas, em que uma classe base define campos compartilhados e classes específicas de cada recurso adicionam seus próprios dados.

O padrão mais comum é estender SpraxiumBaseEnv, que fornece DISCORD_TOKEN, DISCORD_CLIENT_ID e NODE_ENV. Você aplica @EnvSchema apenas na classe filha. A classe pai já possui seu próprio @EnvSchema, e ambas as entradas de metadata são reconhecidas durante a validação.

Quando duas classes da cadeia declaram um campo com a mesma chave de propriedade, a classe filha prevalece. Isso permite sobrescrever o valor padrão da classe pai, por exemplo mudando o default de NODE_ENV de "development" para "production" em um schema específico de deploy. Campos com chaves de propriedade diferentes são mesclados normalmente, independentemente de qual classe os declara.

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;
}

Fluxo de erros de validação

Quando um ou mais campos falham na validação, o framework monta uma lista de objetos EnvFieldError. Cada erro traz a chave da variável, um código de motivo, um valor recebido opcional e, se existir, a mensagem do validador que a rejeitou. Os quatro códigos de motivo são missing, quando a variável está ausente; invalid_value, quando um validador retorna uma mensagem de erro; enum_mismatch, quando IsEnum falha; e validation_failed, para rejeições de Validate personalizados.

No modo de produção, quando NODE_ENV não é "development", o framework imprime todos os erros em stderr e encerra o processo imediatamente. Isso impede que um bot mal configurado se conecte ao Discord com configurações parciais ou incorretas. A saída de erro lista cada variável com falha, o motivo, uma máscara ou o valor recebido, dependendo da flag secret, e a mensagem do validador.

No modo de desenvolvimento, o framework entra em um ciclo de hot reload em vez de encerrar. Ele observa o arquivo .env e revalida a cada 500 milissegundos. Quando você corrige o arquivo e salva, o pipeline roda de novo. Se ainda houver erros, a lista é atualizada. Quando todos os erros desaparecem, o framework imprime uma mensagem de sucesso com um diff mostrando o que mudou e continua a sequência de boot. Esse loop tem um timeout de cinco minutos, após o qual o processo é encerrado para evitar processos zumbis.

Validação silenciosa

Quando você precisa validar um schema sem imprimir a tabela de inicialização, passe a opção silent. Isso é útil em ambientes de teste, onde a saída no console polui os resultados, ou em processos worker que precisam validar sem registrar logs. O schema continua executando todos os validadores e continua lançando erro quando algo falha, apenas pula a tabela de sucesso.

src/test/setup.ts
import { EnvValidator } from '@spraxium/env';
import { AppEnv } from '../app.env';

const env = EnvValidator.validate(AppEnv, { silent: true });

O método validate retorna uma instância completamente preenchida da sua classe de schema. Cada propriedade contém seu valor validado e transformado. Você pode usar essa instância diretamente em testes ou repassá-la a serviços que precisam de configuração, sem passar pelo container de injeção de dependência.

Carregamento de arquivos DotEnv

O framework carrega arquivos .env automaticamente durante a validação. Ele procura primeiro .env.local e depois .env, usando o primeiro que encontrar. Variáveis que já existem em process.env nunca são sobrescritas, então variáveis definidas no sistema e no pipeline de CI sempre têm precedência.

O parser lida com comentários, linhas que começam com #, valores entre aspas simples ou duplas, que são removidas, e comentários inline após valores sem aspas. Ele não oferece suporte a valores multilinha nem interpolação de variáveis. Para valores complexos, como objetos JSON, coloque-os entre aspas e use IsJson para interpretá-los.

Exemplo completo

Este exemplo mostra um schema completo que combina herança, múltiplos tipos de validadores, transformações personalizadas e mascaramento de secrets. O schema estende SpraxiumBaseEnv para herdar o token e o ambiente do Node, adiciona campos de banco de dados e API, e inclui uma transformação personalizada que converte uma lista separada por vírgulas em um array.

src/app.env.ts
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[];
}