--- name: criar-schema-zod scope: global description: > 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 ```bash rg "z{NomeDaEntidade}" --include='*.ts' -l ``` Se já existir → reutilizar ou compor a partir dele (`.extend()`, `.pick()`, `.partial()`). ### 2.2. Criar o schema ```typescript 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<>` ```typescript export type tipoUsuarioCriar = z.infer ``` **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) ```typescript 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) ```typescript 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 ```typescript 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: ```typescript // 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 ```bash 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: `global/rules/zod-validacao-padrao.md` - Rule: `global/rules/typescript-sem-any.md`