@felipe-lib/schema-local
v2.3.3
Published
Biblioteca de validação simples, leve e agnóstica com NotificationPattern e ResultPattern para respostas padronizadas
Maintainers
Readme
schemaValidation v2.3.3
Biblioteca de validação simples, leve e agnóstica com NotificationPattern e ResultPattern para respostas padronizadas.
Sumário
- Por que usar?
- Modelo Mental
- Quick Start
- Conceitos Fundamentais
- Guias Práticos
- Validar objetos aninhados
- Validar com chamadas assíncronas
- Validar arrays
- Criar regras reutilizáveis
- Transformar dados antes de validar
- Validar condicionalmente
- Parar no primeiro erro
- Combinar transform + condition + abortEarly
- Retry com backoff em validações
- Criar padrões customizados
- Integrar com APIs REST
- Passar contexto para as regras
- Filtrar regras por groups, pick e omit
- API Reference
- Funcionalidades
- Licença
Por que usar?
Validar dados é uma das tarefas mais comuns em qualquer aplicação. A maioria das bibliotecas de validação ou é acoplada a um framework, ou força um schema declarativo com DSL própria, ou é pesada em dependências.
O schemaValidation adota uma abordagem diferente:
| Característica | schemaValidation | Outras libs | | ---------------- | ------------------------------- | ------------------------------------------ | | Dependências | Zero | Zod (~10), Joi (~15), class-validator (~5) | | Estilo | Regras como funções puras | DSL declarativa / decorators | | Acoplamento | Agnóstico (qualquer objeto) | Muitas vezes acoplado a framework | | Padrões | Notification + Result + Command | Apenas validação | | Tamanho | ~174 linhas de código | Milhares de linhas |
Ideal para:
- APIs REST que precisam de respostas estruturadas com lista de erros
- Validação de formulários, payloads, DTOs
- Projetos que valorizam zero dependências
- Quem prefere código imperativo/explícito a DSLs mágicas
Talvez não seja ideal se:
- Você precisa de type narrowing por schema (type inference da forma dos dados) — use Zod
- Você precisa de parsing + validação em cadeia — use Zod
Modelo Mental
A biblioteca implementa três padrões de design que trabalham juntos:
┌─────────────────────────────────────────────────────────────┐
│ SchemaValidator │
│ │
│ execute(data, context, options?) ───────────────────────► │
│ │
│ Schema (Rule[]) │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Rule 1 │──►│ Rule 2 │──►│ Rule 3 │──► ... │
│ │ │ │ │ │ │ │
│ │ 1. transform (opcional) │
│ │ 2. condition (opcional) │
│ │ 3. runValidate │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌────────────────────────────────────┐ │
│ │ NotificationPattern[] │ │
│ │ { key, error, description, ... } │ │
│ └───────────────┬────────────────────┘ │
│ ▼ │
│ ┌────────────────────────────────────┐ │
│ │ ResultPattern<T> │ │
│ │ { success, notification[], data } │ │
│ └────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘Os três padrões
Command Pattern — SchemaValidator implementa Command<T, R, C>. Você instancia com um schema e chama execute(data, context, options?). O terceiro parâmetro aceita { groups?, pick?, omit? } para filtrar quais regras serão executadas. O validator encapsula toda a lógica de execução.
Notification Pattern — Cada falha de validação gera uma Notification com key (campo), error (mensagem) e description (detalhe opcional). Todas as notificações são coletadas em um array.
Result Pattern — O resultado final é um objeto com success (booleano), notification (array de notificações) e data (dados originais/transformados).
Ordem de execução dentro de cada Rule
para cada rule:
1. transform(data, context) → modifica os dados (encadeado entre regras)
2. condition(data, context) → se false, pula a regra
3. runValidate(data, context) → executa a validação
4. se inválido → notificationMappers → adiciona ao array
── se algum passo acima lançar erro ──
5. retry (opcional) → loop com backoff (ver seção "Retry")Quick Start
Instalação
npm install @felipe-lib/schema-localSua primeira validação
import { SchemaValidator } from "@felipe-lib/schema-local";
interface User {
name: string;
email: string;
}
const validator = new SchemaValidator<User>({
schema: [
{
key: "name",
error: () => "Nome é obrigatório",
runValidate: (data) => data.name.trim().length > 0,
},
{
key: "email",
error: () => "Email inválido",
runValidate: (data) =>
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email),
},
],
});
const result = await validator.execute({
name: "João",
email: "[email protected]",
});
console.log(result.success); // true
console.log(result.notification); // []
console.log(result.data); // { name: "João", email: "[email protected]" }O que aconteceu aqui?
- Criamos uma interface
Usercom os campos a validar. - Instanciamos
SchemaValidator<User>passando umschema— array de regras. - Cada regra tem
key(nome do campo),error(mensagem de erro) erunValidate(função que retornatruese válido). - Chamamos
execute(data)e recebemos umResultPatterncomsuccess,notificationedata. - Como o nome e o email eram válidos,
successétrueenotificationestá vazio.
Nota: Os mappers são opcionais — a biblioteca já fornece defaults que geram o
NotificationPatterneResultPatternpadrão. Você só precisa customizá-los se quiser formatos diferentes (ex: resposta de API HTTP).
Adicionando mais regras
interface User {
name: string;
email: string;
age: number;
}
const validator = new SchemaValidator<User>({
schema: [
{
key: "name",
error: () => "Nome é obrigatório",
runValidate: (data) => data.name.trim().length > 0,
},
{
key: "email",
error: () => "Email inválido",
runValidate: (data) =>
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email),
},
{
key: "age",
error: () => "Idade deve ser maior que 0",
description: () => "Verifica se a idade é positiva",
runValidate: (data) => data.age > 0,
},
],
});Lendo erros
const result = await validator.execute({
name: "",
email: "invalido",
age: -5,
});
if (!result.success) {
for (const err of result.notification) {
console.log(`${String(err.key)}: ${err.error}`);
}
}
// name: Nome é obrigatório
// email: Email inválido
// age: Idade deve ser maior que 0Por padrão, todas as regras são executadas e todos os erros são coletados — você recebe uma lista completa do que está errado, não apenas o primeiro erro.
Conceitos Fundamentais
Anatomia de uma Rule
interface Rule<T, C> {
key: keyof T; // campo que está sendo validado
error: (data: T, context?: C) => string; // mensagem de erro (pode ser dinâmica)
groups?: string[]; // grupos para filtrar execução (ver seção "Filtrar regras")
retry?: RetryType; // config de retry com backoff (ver seção "Retry")
transform?: (data: T, context?: C) => T | Promise<T>; // transforma dados antes da validação
condition?: (data: T, context?: C) => boolean | Promise<boolean>; // se false, regra é ignorada
runValidate(data: T, context?: C): boolean | Promise<boolean>; // lógica da validação
description?: (data: T, context?: C) => string; // descrição opcional (pode ser dinâmica)
}Todos os callbacks (transform, condition, runValidate, error, description) suportam tanto execução síncrona quanto assíncrona (retornando Promise). Todos recebem (data, context) — permitindo passar dependências externas como conexão de banco, request object, etc.
RetryType
Configura o loop de retry com backoff quando transform, condition ou runValidate lançarem um erro:
interface RetryType {
active: boolean; // liga/desliga o retry
maxAttemps: number; // delay inicial em ms (ex: 2000)
multiply?: number; // multiplicador de backoff (ex: 2 → 2s, 4s, 8s)
maxRetries: number; // número máximo de tentativas
}Comportamento: se active for true e qualquer etapa da pipeline lançar erro, o validador:
- Aguarda
maxAttempsms - Restaura os dados ao estado original
- Reexecuta
transform→condition→runValidate - Se
conditionretornarfalse→ break (regra ignorada com sucesso) - Se
runValidateretornartrue→ break (sucesso) - Senão → multiplica o timeout (
timeout *= multiply) e tenta novamente - Após
maxRetriestentativas sem sucesso, o loop termina e uma única notificação de erro é gerada
NotificationPattern
Estrutura padronizada de notificação de erro:
interface NotificationPattern {
success: boolean; // sempre false para notificações de erro
key: string | number | symbol; // campo que falhou
error: string; // mensagem de erro
description?: string; // detalhamento opcional
}ResultPattern<T>
Estrutura padronizada do resultado:
interface ResultPattern<T> {
success: boolean; // true se nenhum erro
notification: NotificationPattern[]; // array de erros encontrados
data: T; // dados originais ou transformados
}Mappers Customizados
Os mappers traduzem a regra e os dados para os formatos de notificação e resultado. Use-os quando precisar de estruturas diferentes da padrão:
// Mapper de notificação — transforma Rule + data + context em NotificationPattern
notificationMappers: (rule, data, context?) => ({
success: false,
key: rule.key,
error: rule.error(data, context),
description: rule.description?.(data, context),
});
// Mapper de resultado — transforma data + notificações em ResultPattern
resultMappers: (data, notif) => ({
data,
notification: notif,
success: notif.length === 0,
});Se você não fornecer mappers, a biblioteca usa os defaults — que produzem exatamente as estruturas acima. Forneça mappers customizados apenas quando seu NotificationPattern ou ResultPattern tiverem campos extras.
Command<T, R, C>
Interface que SchemaValidator implementa:
interface Command<
T extends object,
R extends object,
C extends object = {},
> {
execute(data: T, context?: C, options?: OptionsCommand): Promise<R>;
}Isso permite tratar qualquer validador como um comando executável, facilitando integração com padrões como Command Bus, Mediator ou Use Case.
Guias Práticos
Validar objetos aninhados
Componha validators: crie um validador para o objeto interno e chame-o dentro da regra do objeto externo.
interface Address {
street: string;
zipCode: string;
}
interface User {
name: string;
address: Address;
}
const addressValidator = new SchemaValidator<Address>({
schema: [
{
key: "street",
error: () => "Rua é obrigatória",
runValidate: (data) => data.street.length > 0,
},
{
key: "zipCode",
error: () => "CEP inválido",
runValidate: (data) => /^\d{5}-\d{3}$/.test(data.zipCode),
},
],
});
const userValidator = new SchemaValidator<User>({
schema: [
{
key: "name",
error: () => "Nome é obrigatório",
runValidate: (data) => data.name.trim().length > 0,
},
{
key: "address",
error: (data) => "Endereço inválido",
runValidate: async (data) => {
const result = await addressValidator.execute(data.address);
return result.success;
},
},
],
});Validar com chamadas assíncronas
runValidate aceita funções assíncronas — ideal para verificar existência no banco, chamadas de API, etc.
interface LoginData {
email: string;
password: string;
}
const loginValidator = new SchemaValidator<LoginData>({
schema: [
{
key: "email",
error: () => "Email é obrigatório",
runValidate: (data) => !!data.email,
},
{
key: "password",
error: () => "Senha deve ter pelo menos 8 caracteres",
runValidate: (data) => data.password.length >= 8,
},
{
key: "email",
error: () => "Usuário não encontrado",
description: () => "Verifica se o email existe na base",
runValidate: async (data) => {
const response = await fetch("/api/verify-user", {
method: "POST",
body: JSON.stringify({ email: data.email }),
});
return response.ok;
},
},
],
});Validar arrays
Use métodos como every(), some() ou filter() para validar cada item do array.
interface Product {
name: string;
price: number;
}
interface Order {
items: Product[];
}
const orderValidator = new SchemaValidator<Order>({
schema: [
{
key: "items",
error: () => "Pedido vazio",
runValidate: (data) => data.items.length > 0,
},
{
key: "items",
error: () => "Produto sem nome",
runValidate: (data) =>
data.items.every((item) => item.name.trim().length > 0),
},
{
key: "items",
error: () => "Preço inválido",
runValidate: (data) => data.items.every((item) => item.price > 0),
},
],
});Criar regras reutilizáveis
Encapsule regras comuns em factory functions para evitar repetição.
interface User {
username: string;
email: string;
password: string;
confirmPassword: string;
}
const isRequired = (field: keyof User, error: string) => ({
key: field,
error: () => error,
description: () => `Verifica se ${String(field)} foi fornecido`,
runValidate: (data: User) => {
const value = data[field];
return typeof value === "string" ? value.trim().length > 0 : !!value;
},
});
const isEmail = (error: string) => ({
key: "email" as const,
error: () => error,
description: () => "Valida formato do email",
runValidate: (data: User) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email),
});
const minLength = (field: keyof User, min: number, error: string) => ({
key: field,
error: () => error,
description: () => `Mínimo de ${min} caracteres`,
runValidate: (data: User) => {
const value = data[field];
return typeof value === "string" ? value.length >= min : false;
},
});
const passwordsMatch = {
key: "confirmPassword" as const,
error: () => "Senhas não conferem",
description: () => "Confirmação deve ser igual à senha",
runValidate: (data: User) => data.password === data.confirmPassword,
};
const validator = new SchemaValidator<User>({
schema: [
isRequired("username", "Nome de usuário é obrigatório"),
isEmail("Email inválido"),
minLength("password", 8, "Senha deve ter pelo menos 8 caracteres"),
passwordsMatch,
],
});Transformar dados antes de validar
O campo transform modifica os dados antes da validação. O resultado é encadeado entre regras — cada regra recebe os dados já transformados pelas anteriores.
interface User {
name: string;
email: string;
}
const validator = new SchemaValidator<User>({
schema: [
{
key: "name",
error: () => "Nome é obrigatório",
transform: (data) => ({ ...data, name: data.name.trim() }),
runValidate: (data) => data.name.length > 0,
},
{
key: "email",
error: () => "Email inválido",
transform: (data) => ({
...data,
email: data.email.toLowerCase().trim(),
}),
runValidate: (data) =>
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email),
},
],
});
const result = await validator.execute({
name: " João ",
email: " [email protected] ",
});
console.log(result.data.name); // "João"
console.log(result.data.email); // "[email protected]"Transform assíncrono:
const validator = new SchemaValidator<User>({
schema: [
{
key: "email",
error: () => "Email já cadastrado",
transform: async (data) => {
const normalized = data.email.toLowerCase().trim();
return { ...data, email: normalized };
},
runValidate: async (data) => {
const response = await fetch(
`/api/check-email?email=${data.email}`,
);
const { exists } = await response.json();
return !exists;
},
},
],
});Validar condicionalmente
O campo condition determina se a regra deve ser executada. Se a condição retornar false, a regra é ignorada.
interface FormData {
type: "individual" | "company";
cpf: string;
cnpj: string;
companyName: string;
}
const formValidator = new SchemaValidator<FormData>({
schema: [
{
key: "type",
error: () => "Tipo inválido",
runValidate: (data) =>
["individual", "company"].includes(data.type),
},
{
key: "cpf",
error: () => "CPF inválido",
condition: (data) => data.type === "individual",
runValidate: (data) => /^\d{11}$/.test(data.cpf),
},
{
key: "cnpj",
error: () => "CNPJ inválido",
condition: (data) => data.type === "company",
runValidate: (data) => /^\d{14}$/.test(data.cnpj),
},
{
key: "companyName",
error: () => "Razão social obrigatória",
condition: (data) => data.type === "company",
runValidate: (data) => data.companyName.length > 0,
},
],
});Condition assíncrona:
interface ProductForm {
categoryId: string;
discount: number;
}
const validator = new SchemaValidator<ProductForm>({
schema: [
{
key: "discount",
error: () => "Desconto inválido",
condition: async (data) => {
const response = await fetch(
`/api/categories/${data.categoryId}`,
);
const category = await response.json();
return category.allowDiscount;
},
runValidate: (data) => data.discount >= 0 && data.discount <= 100,
},
],
});Parar no primeiro erro
Com abortEarly: true, a validação é interrompida assim que o primeiro erro é encontrado.
interface LoginData {
email: string;
password: string;
}
const loginValidator = new SchemaValidator<LoginData>({
schema: [
{
key: "email",
error: () => "Email é obrigatório",
runValidate: (data) => data.email.trim().length > 0,
},
{
key: "email",
error: () => "Email inválido",
runValidate: (data) =>
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email),
},
{
key: "password",
error: () => "Senha é obrigatória",
runValidate: (data) => data.password.length > 0,
},
{
key: "password",
error: () => "Mínimo de 8 caracteres",
runValidate: (data) => data.password.length >= 8,
},
],
abortEarly: true,
});
const result = await loginValidator.execute({
email: "",
password: "123",
});
console.log(result.notification.length); // 1 — apenas o primeiro erroCombinar transform + condition + abortEarly
As funcionalidades podem ser combinadas livremente. A ordem de execução dentro de cada regra segue o pipeline: transform → condition → runValidate.
interface OrderData {
total: number;
couponCode: string;
finalTotal: number;
}
const orderValidator = new SchemaValidator<OrderData>({
schema: [
{
key: "total",
error: () => "Total deve ser positivo",
runValidate: (data) => data.total > 0,
},
{
key: "couponCode",
error: () => "Cupom inválido",
condition: (data) => data.couponCode.length > 0,
transform: async (data) => {
const response = await fetch(`/api/coupons/${data.couponCode}`);
const coupon = await response.json();
return {
...data,
finalTotal: data.total * (1 - coupon.discount / 100),
};
},
runValidate: async (data) => {
const response = await fetch(`/api/coupons/${data.couponCode}`);
return response.ok;
},
},
],
abortEarly: true,
});
const result = await orderValidator.execute({
total: 200,
couponCode: "BLACK20",
finalTotal: 0,
});
console.log(result.data.finalTotal); // 160 (200 - 20%)Retry com backoff em validações
Se transform, condition ou runValidate lançarem um erro (ex: falha de rede, timeout), você pode configurar um loop de retry com backoff exponencial usando o campo retry.
interface PaymentData {
transactionId: string;
amount: number;
}
const validator = new SchemaValidator<PaymentData>({
schema: [
{
key: "transactionId",
error: () => "Falha na confirmação do pagamento",
runValidate: async (data) => {
const response = await fetch(
`/api/transactions/${data.transactionId}`,
);
return response.ok;
},
retry: {
active: true,
maxRetries: 3,
maxAttemps: 1000,
multiply: 2,
},
},
],
});Como funciona:
- Se
transform,conditionourunValidatelançarem erro, o validador entra no loop de retry - Cada tentativa espera
maxAttempsms (com backoff multiplicativo semultiplyestiver definido) - A cada retry os dados são restaurados ao estado original e toda pipeline é reexecutada
- Se
conditionretornarfalse→ a regra é ignorada (break silencioso) - Se
runValidateretornartrue→ sucesso, sai do loop - Se todas as tentativas falharem → uma única notificação de erro é gerada
Exemplo com backoff: maxAttemps: 1000, multiply: 2, maxRetries: 3
1ª tentativa: falhou → espera 1s
2ª tentativa: falhou → espera 2s
3ª tentativa: falhou → espera 4s → notificação finalCriar padrões customizados
Quando os mappers padrão não bastam, você pode estender NotificationPattern e ResultPattern com campos específicos do seu domínio.
Notification customizado com tipo de erro:
interface CustomNotification {
success: boolean;
key: string | number | symbol;
error: string;
field: string;
message: string;
type: "error" | "warning";
}
interface CustomResult<T> {
success: boolean;
notification: CustomNotification[];
isValid: boolean;
errors: CustomNotification[];
data: T;
}
interface User {
name: string;
email: string;
}
const validator = new SchemaValidator<
User,
CustomNotification,
CustomResult<User>
>({
schema: [
{
key: "email",
error: () => "Email inválido",
runValidate: (data) =>
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email),
},
],
notificationMappers: (rule, data) => ({
success: false,
key: rule.key,
error: rule.error(data),
field: String(rule.key),
message: rule.error(data),
type: "error" as const,
}),
resultMappers: (data, notif) => ({
success: notif.length === 0,
notification: notif,
isValid: notif.length === 0,
errors: notif,
data,
}),
});
const result = await validator.execute({
name: "João",
email: "invalido",
});
console.log(result.isValid); // false
console.log(result.errors[0].field); // "email"
console.log(result.errors[0].type); // "error"Integrar com APIs REST
Use mappers customizados para gerar respostas no formato que sua API espera.
interface ApiResponse<T> {
status: number;
message: string;
payload?: T;
errors: { field: string; reason: string }[];
}
interface ApiNotification {
success: boolean;
key: string | number | symbol;
error: string;
field: string;
message: string;
}
interface User {
name: string;
email: string;
}
const apiValidator = new SchemaValidator<
User,
ApiNotification,
ApiResponse<User>
>({
schema: [
{
key: "name",
error: () => "Nome é obrigatório",
runValidate: (data) => data.name.trim().length > 0,
},
{
key: "email",
error: () => "Email inválido",
runValidate: (data) =>
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email),
},
],
notificationMappers: (rule, data) => ({
success: false,
key: rule.key,
error: rule.error(data),
field: String(rule.key),
message: rule.error(data),
}),
resultMappers: (data, notif) => ({
success: notif.length === 0,
notification: notif as NotificationPattern[],
data,
status: notif.length === 0 ? 200 : 400,
message: notif.length === 0 ? "OK" : "Erro de validação",
payload: notif.length === 0 ? data : undefined,
errors: notif.map((n) => ({
field: n.field,
reason: n.message,
})),
}),
});
const result = await apiValidator.execute({
name: "João",
email: "[email protected]",
});
// result.status === 200
// result.errors === []
// Uso em um controller Express/Fastify:
// res.status(result.status).json(result);Passar contexto para as regras
O parâmetro genérico C permite passar um objeto de contexto (context) que fica disponível em todos os callbacks das regras. Isso é útil para injetar dependências como conexão de banco, objetos de request, ou qualquer dado externo.
interface DatabaseCtx {
db: DatabaseConnection;
userId: string;
}
interface User {
email: string;
}
const validator = new SchemaValidator<
User,
NotificationPattern,
ResultPattern<User>,
DatabaseCtx
>({
schema: [
{
key: "email",
error: (data, ctx) =>
`Email já cadastrado para o usuário ${ctx.userId}`,
description: (data, ctx) =>
`Verifica unicidade do email no tenant ${ctx.userId}`,
runValidate: async (data, ctx) => {
const exists = await ctx.db.findByEmail(data.email);
return !exists;
},
},
],
});
const db = await connectToDatabase();
const result = await validator.execute(
{ email: "[email protected]" },
{ db, userId: "usr_123" },
);Por padrão, C = {} — se você não passa contexto, o TypeScript não exige o segundo argumento no execute() e os callbacks recebem {} como context.
Filtrar regras por groups, pick e omit
O terceiro parâmetro do execute permite filtrar quais regras serão executadas sem modificar o schema original.
groups: execute apenas regras que pertençam a determinados grupos.
interface User {
name: string;
email: string;
password: string;
}
const validator = new SchemaValidator<User>({
schema: [
{
key: "name",
error: () => "Nome é obrigatório",
groups: ["create", "update"],
runValidate: (data) => data.name.trim().length > 0,
},
{
key: "email",
error: () => "Email inválido",
groups: ["create", "update"],
runValidate: (data) =>
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email),
},
{
key: "password",
error: () => "Mínimo de 8 caracteres",
groups: ["create"],
runValidate: (data) => data.password.length >= 8,
},
],
});
// Apenas regras do grupo "create"
const result = await validator.execute(data, {}, { groups: ["create"] });pick: execute apenas regras cujo key ou groups corresponda à lista.
// Por campo
const result = await validator.execute(data, {}, { pick: ["name", "email"] });
// Por grupo
const result = await validator.execute(data, {}, { pick: ["admin", "audit"] });omit: execute todas as regras exceto as cujo key ou groups corresponda à lista.
// Por campo
const result = await validator.execute(data, {}, { omit: ["password"] });
// Por grupo
const result = await validator.execute(data, {}, { omit: ["admin"] });Regras que não possuem groups são executadas independentemente do filtro de grupos. Os filtros podem ser combinados: { groups: ["create"], pick: ["name", "email"] }. Tanto pick quanto omit verificam simultaneamente rule.key e rule.groups — se qualquer um dos dois corresponder, a regra é incluída (pick) ou excluída (omit).
API Reference
SchemaValidator<T, N, R, C>
Classe principal. Implementa Command<T, R, C>.
| Parâmetro genérico | Default | Descrição |
| ------------------------------- | --------------------- | --------------------------------------------------- |
| T extends object | — | Tipo do objeto a ser validado |
| N extends NotificationPattern | NotificationPattern | Tipo da notificação de erro |
| R extends ResultPattern<T> | ResultPattern<T> | Tipo do resultado |
| C extends object | {} | Contexto passado para todos os callbacks das regras |
Construtor:
new SchemaValidator<T, N, R, C>({
schema: Rule<T, C>[];
notificationMappers?: (rule: Rule<T, C>, data: T, context?: C) => N;
resultMappers?: (data: T, notif: N[]) => R;
abortEarly?: boolean;
})| Opção | Obrigatório | Padrão | Descrição |
| --------------------- | ----------- | ------------- | --------------------------------------- |
| schema | Sim | — | Array de regras de validação |
| notificationMappers | Não | mapper padrão | Customiza como notificações são geradas |
| resultMappers | Não | mapper padrão | Customiza como o resultado é montado |
| abortEarly | Não | false | Se true, para no primeiro erro |
Métodos:
| Método | Retorno | Descrição |
| ----------------------------------------------------------- | ------------ | ------------------------------------------------------------- |
| execute(data: T, context?: C, options?: OptionsCommand) | Promise<R> | Executa as regras (com filtro opcional) e retorna o resultado |
| validation(data: T, context?: C, options?: OptionsCommand) | Promise<R> | Alias interno — mesmo comportamento de execute |
Tipos exportados
import type {
NotificationPattern,
ResultPattern,
Rule,
Command,
OptionsCommand,
RetryType,
} from "@felipe-lib/schema-local/types";Rule<T, C>
interface Rule<T, C> {
key: keyof T;
error: (data: T, context?: C) => string;
groups?: string[];
retry?: RetryType;
transform?: (data: T, context?: C) => T | Promise<T>;
condition?: (data: T, context?: C) => boolean | Promise<boolean>;
runValidate(data: T, context?: C): boolean | Promise<boolean>;
description?: (data: T, context?: C) => string;
}RetryType
interface RetryType {
active: boolean;
maxAttemps: number;
multiply?: number;
maxRetries: number;
}NotificationPattern
interface NotificationPattern {
success: boolean;
key: string | number | symbol;
error: string;
description?: string;
}ResultPattern<T>
interface ResultPattern<T> {
success: boolean;
notification: NotificationPattern[];
data: T;
}Command<T, R, C>
interface Command<
T extends object,
R extends object,
C extends object = {},
> {
execute(data: T, context?: C, options?: OptionsCommand): Promise<R>;
}
interface OptionsCommand {
groups?: string[];
pick?: string[];
omit?: string[];
}Funcionalidades
- Zero dependências — não requer bibliotecas externas
- NotificationPattern — notificações de erro padronizadas
- ResultPattern — resultado padronizado com
success,notification[]edata - Mappers customizáveis — personalize os formatos de notificação e resultado
- Suporte sync/async — todos os callbacks aceitam funções síncronas e assíncronas
transform— modifique os dados antes da validação (encadeado entre regras)condition— execute regras apenas quando uma condição for atendidaabortEarly— interrompa no primeiro erroretry— backoff automático com repetição em caso de falha- Validação contínua — execute todas as regras e colete todos os erros (padrão)
- Totalmente tipado — TypeScript nativo com genéricos
- Command Pattern — interface
Command<T, R>para integração com Command Bus / Mediator - Filtro por
groups,pickeomit— execute apenas regras específicas sem modificar o schema - Agnóstico — funciona com qualquer formato de objeto
Licença
MIT © Felca
