vue-componentes/.agent/skills/criar-schema-zod.global/SKILL.md

4.6 KiB

name scope description
criar-schema-zod global Guia operacional para criar, organizar e manter schemas de validação Zod em projetos TypeScript.

Skill: Criar Schema Zod

Use esta skill ao adicionar validação de dados em qualquer ponto de entrada do sistema — API, formulário, webhook, importação, fila ou variáveis de ambiente.


PASSO 1 — Identificar a Entrada Externa

Determinar qual dado externo precisa de validação:

Tipo de entrada Onde validar
Corpo de requisição (POST/PUT) Handler da rota, antes de qualquer lógica
Query/path params Handler da rota
Formulário frontend Antes de chamar a API
Dados de fila (BullMQ) No processador da fila
Arquivo importado (CSV/JSON) No parser, linha a linha
Variáveis de ambiente No boot da aplicação
Resposta de API externa Após receber a resposta

PASSO 2 — Localizar ou Criar o Schema no Pacote Comuns

Schemas compartilhados vivem no pacote *-comuns:

projeto-comuns/src/tabelas/
├── usuarios/
│   ├── zUsuarioCriar.ts
│   ├── zUsuarioEditar.ts
│   └── index.ts
└── index.ts

2.1. Verificar se já existe

rg "z{NomeDaEntidade}" --include='*.ts' -l

Se já existir → reutilizar ou compor a partir dele (.extend(), .pick(), .partial()).

2.2. Criar o schema

import { z } from "zod"

export const zUsuarioCriar = z.object({
  nome: z.string().trim().min(1, "Nome é obrigatório"),
  email: z.string().email("E-mail inválido"),
  ativo: z.boolean().default(true),
})

2.3. Nomenclatura obrigatória

Padrão Uso
z{Entidade}Criar Criação
z{Entidade}Editar Edição (geralmente .partial())
z{Entidade}Filtro Filtros de listagem
z{Entidade}Resposta Resposta de API
z{Entidade}Params Parâmetros de rota
z{Modulo}Config Configuração
z{Modulo}Ambiente Variáveis de ambiente

PASSO 3 — Derivar o Tipo com z.infer<>

export type tipoUsuarioCriar = z.infer<typeof zUsuarioCriar>

Nunca criar um interface ou type manual separado do schema. O tipo sempre deriva do schema.


PASSO 4 — Implementar safeParse no Ponto de Entrada

Backend (Express)

import { zUsuarioCriar } from "projeto-comuns/tabelas/usuarios"

app.post("/usuarios", async (req, res) => {
  const resultado = zUsuarioCriar.safeParse(req.body)
  if (!resultado.success) {
    return res.status(400).json({ erro: resultado.error.issues[0].message })
  }
  const usuario = await criarUsuario(resultado.data)
  return res.json(usuario)
})

Frontend (Vue)

import { zUsuarioCriar } from "projeto-comuns/tabelas/usuarios"

const salvar = async () => {
  const resultado = zUsuarioCriar.safeParse(formulario.value)
  if (!resultado.success) {
    erros.value = resultado.error.flatten().fieldErrors
    return
  }
  await api.post("/usuarios", resultado.data)
}

Variáveis de ambiente

const zAmbiente = z.object({
  DATABASE_URL: z.string().url(),
  REDIS_HOST: z.string().default("localhost"),
  NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
})

// Aqui o throw é desejável — a app não deve subir com env inválido
export const ambiente = () => zAmbiente.parse(process.env)

PASSO 5 — Compor Schemas Derivados

Quando precisar de variações do mesmo schema, compor em vez de duplicar:

// Schema base
const zProduto = z.object({
  nome: z.string().trim().min(1),
  preco: z.coerce.number().positive(),
  categoria: z.string(),
})

// Edição: todos os campos opcionais
const zProdutoEditar = zProduto.partial()

// Resposta: base + campos gerados
const zProdutoResposta = zProduto.extend({
  codigo: z.string().uuid(),
  criadoEm: z.string().datetime(),
})

// Filtro: subset de campos opcionais
const zProdutoFiltro = zProduto.pick({ categoria: true }).partial()

PASSO 6 — Validar Tipagem

npx tsc --noEmit

Zero erros = concluído.


Checklist Final

  • O schema está no pacote *-comuns (quando compartilhado entre front/back)?
  • O tipo é derivado do schema com z.infer<>?
  • A validação usa safeParse (exceto em boot/middleware)?
  • A nomenclatura segue o padrão z{Entidade}{Acao}?
  • Transformações (trim, coerce, default) estão no schema, não no handler?
  • Schemas derivados usam composição (.partial, .extend, .pick), não duplicação?
  • tsc --noEmit passa sem erros?

Referências

  • Rule: .agent/rules/zod-validacao-padrao.global.md
  • Rule: .agent/rules/typescript-sem-any.global.md