Verificando tipos em tempo de compilação com satisfies
1. O problema que satisfies resolve
1.1. Inferência vs. validação de tipos: quando o tipo ampliado perde informação
TypeScript é excelente em inferir tipos, mas essa inferência muitas vezes entra em conflito com a necessidade de validar estruturas. Quando anotamos um tipo explicitamente, perdemos informações literais preciosas. O satisfies operador, introduzido no TypeScript 4.9, resolve exatamente esse dilema: ele verifica se um valor satisfaz um tipo sem alterar o tipo inferido.
1.2. O dilema entre as const e anotações explícitas de tipo
Desenvolvedores frequentemente usam as const para preservar literais, mas isso congela tudo. Anotações explícitas ampliam tipos. O satisfies oferece um meio-termo elegante.
1.3. Exemplo motivacional: um objeto de configuração que precisa de tipos literais
// Problema: queremos validar a estrutura, mas manter tipos literais
type Config = {
api: string;
timeout: number;
retry: boolean;
};
const config = {
api: "https://api.exemplo.com",
timeout: 5000,
retry: true,
} satisfies Config;
// config.timeout é inferido como 5000 (literal), não number
2. Sintaxe e funcionamento básico do satisfies
2.1. Estrutura: expressão satisfies Tipo
const resultado = expressao satisfies Tipo;
O compilador verifica se expressao é atribuível a Tipo, mas o tipo de resultado permanece sendo o tipo inferido de expressao.
2.2. Diferença fundamental entre satisfies e anotação de tipo (: Tipo)
- Anotação (
: Tipo): força o valor a ser do tipoTipo, ampliando ou restringindo. satisfies: apenas verifica compatibilidade, mantendo o tipo inferido original.
2.3. Primeiro exemplo prático
type CorRGB = { r: number; g: number; b: number };
const cor1: CorRGB = { r: 255, g: 0, b: 0 };
// cor1.r é number (perdeu o literal 255)
const cor2 = { r: 255, g: 0, b: 0 } satisfies CorRGB;
// cor2.r é 255 (literal preservado)
3. satisfies vs. anotação de tipo (: Tipo)
3.1. Anotação de tipo amplia o tipo inferido
type Status = "ativo" | "inativo" | "pendente";
const status1: Status = "ativo";
// status1 é Status, perdemos o literal "ativo"
3.2. satisfies preserva o tipo mais específico
const status2 = "ativo" satisfies Status;
// status2 é "ativo" (literal), mas o compilador verifica que é válido para Status
3.3. Comparação visual com objetos, arrays e tuplas
type Ponto = [number, number, string?];
const ponto1: Ponto = [10, 20, "origem"];
// ponto1[0] é number
const ponto2 = [10, 20, "origem"] as const satisfies Ponto;
// ponto2[0] é 10 (literal), ponto2[2] é "origem" (literal)
type ListaNumeros = number[];
const lista1 = [1, 2, 3] satisfies ListaNumeros;
// lista1 é number[], mas elementos são literais 1, 2, 3
4. satisfies vs. as const + anotação
4.1. as const congela tudo — nem sempre desejável
const frozen = { nome: "João", idade: 30 } as const;
// frozen é { readonly nome: "João"; readonly idade: 30 }
// Não podemos modificar, mesmo que quiséssemos
4.2. Combinando as const com satisfies para máxima precisão
type Pessoa = {
nome: string;
idade: number;
ativo?: boolean;
};
const pessoa = {
nome: "Maria",
idade: 28,
ativo: true,
} as const satisfies Pessoa;
// pessoa é { readonly nome: "Maria"; readonly idade: 28; readonly ativo: true }
// Verifica que satisfaz Pessoa, mas mantém literais e readonly
4.3. Caso de uso: mapeamento de chaves para valores com tipos literais
type CoresValidas = "vermelho" | "azul" | "verde";
type MapaCores = Record<string, CoresValidas>;
const mapa = {
fundo: "azul",
texto: "vermelho",
borda: "verde",
} as const satisfies MapaCores;
// mapa.fundo é "azul" (literal), não string
5. Casos de uso avançados
5.1. Validação de objetos com tipos de união e discriminated unions
type Evento =
| { tipo: "clique"; x: number; y: number }
| { tipo: "tecla"; tecla: string };
const evento = {
tipo: "clique",
x: 100,
y: 200,
} satisfies Evento;
// evento.tipo é "clique" (literal), e sabemos que x e y existem
5.2. Garantir que um objeto implemente uma interface, mas manter tipos de propriedades
interface Animal {
nome: string;
emitirSom(): string;
}
const gato = {
nome: "Mimi",
emitirSom: () => "Miau",
idade: 3,
} satisfies Animal;
// gato.idade é 3 (literal), e gato satisfaz Animal
5.3. Uso com tipos genéricos e mapeamento de tipos
type Opcoes<T extends string> = Record<T, boolean>;
const opcoes = {
darkMode: true,
notificacoes: false,
som: true,
} satisfies Opcoes<"darkMode" | "notificacoes" | "som">;
// opcoes.darkMode é true (literal)
6. satisfies em funções e parâmetros
6.1. Verificar que um retorno de função satisfaz um tipo sem perder inferência
type Resultado<T> = { dados: T; erro: null } | { dados: null; erro: string };
function buscarUsuario(id: number) {
return {
dados: { id, nome: "João" },
erro: null,
} satisfies Resultado<{ id: number; nome: string }>;
}
const resultado = buscarUsuario(1);
// resultado.dados.nome é "João" (literal), e sabemos que erro é null
6.2. Validar parâmetros de callback com tipos complexos
type Transformador<T> = (valor: T) => T;
function aplicarTransformacao<T>(valor: T, fn: Transformador<T>): T {
return fn(valor);
}
const resultado = aplicarTransformacao(5, (x) => x * 2 satisfies number);
// resultado é 10 (literal), mas verificamos que o callback retorna number
6.3. Exemplo: funções que retornam objetos de configuração tipados
type ConfiguracaoServidor = {
porta: number;
host: string;
ssl: boolean;
};
function criarConfiguracao(ambiente: "dev" | "prod") {
if (ambiente === "dev") {
return {
porta: 3000,
host: "localhost",
ssl: false,
} satisfies ConfiguracaoServidor;
}
return {
porta: 443,
host: "api.exemplo.com",
ssl: true,
} satisfies ConfiguracaoServidor;
}
7. Limitações e cuidados ao usar satisfies
7.1. satisfies não é validação em runtime — apenas tempo de compilação
const valor: unknown = JSON.parse('{"nome": "João"}');
// Isso NÃO funciona em runtime:
// const pessoa = valor satisfies { nome: string };
// TypeError: valor.satisfies is not a function
7.2. Não substitui as para type assertions quando necessário
const elemento = document.getElementById("app") satisfies HTMLElement;
// Erro: pode ser null
// Use as para afirmar que não é null:
const elemento2 = document.getElementById("app") as HTMLElement;
7.3. Problemas com tipos condicionais e distribuição em uniões
type IsString<T> = T extends string ? true : false;
const teste = "hello" satisfies IsString<typeof "hello">;
// Isso funciona, mas tipos condicionais complexos podem causar erros inesperados
8. Boas práticas e padrões recomendados
8.1. Quando preferir satisfies em vez de anotação ou as
- Use
satisfiesquando precisar de validação de tipo sem perder inferência estreita - Prefira anotação (
: Tipo) quando quiser ampliar deliberadamente o tipo - Use
asapenas para type assertions quando você sabe mais que o compilador
8.2. Combinando satisfies com zod ou envalid para validação dupla
import { z } from "zod";
const SchemaUsuario = z.object({
nome: z.string(),
idade: z.number().positive(),
});
type Usuario = z.infer<typeof SchemaUsuario>;
const usuario = {
nome: "Ana",
idade: 25,
} satisfies Usuario;
// Validação em runtime:
SchemaUsuario.parse(usuario);
8.3. Refatorando código existente para usar satisfies com segurança
// Antes: perdendo tipos literais
type Cores = "red" | "green" | "blue";
const cor: Cores = "red"; // cor é Cores
// Depois: mantendo tipos literais
const cor = "red" satisfies Cores; // cor é "red"
// Refatoração segura:
// 1. Substitua `: Tipo` por `satisfies Tipo`
// 2. Verifique se o código downstream não depende do tipo ampliado
// 3. Ajuste funções que esperam tipos literais
O satisfies é uma ferramenta poderosa para escrever TypeScript mais expressivo e preciso. Ele permite validar estruturas complexas sem sacrificar a riqueza dos tipos inferidos, resultando em código mais seguro e auto-documentado.
Referências
- Documentação oficial do TypeScript: Operador satisfies — Notas de lançamento do TypeScript 4.9 com exemplos detalhados do operador satisfies
- TypeScript Deep Dive: satisfies operator — Guia abrangente sobre o operador satisfies com casos de uso práticos
- Total TypeScript: When to use satisfies — Artigo técnico explorando diferentes cenários onde o satisfies é útil
- TypeScript Handbook: Type Inference — Documentação oficial sobre inferência de tipos, base conceitual para entender o satisfies
- Dev.to: Understanding TypeScript's satisfies operator — Tutorial prático com exemplos comparativos entre satisfies, anotações e type assertions
- TypeScript 4.9 satisfies: The Complete Guide — Playground oficial do TypeScript com exemplos interativos do operador satisfies
- Marius Schulz: TypeScript 4.9's satisfies Operator — Análise técnica detalhada por um membro da comunidade TypeScript