Satisfies operator: validação sem widening
1. O Problema do Widening em TypeScript
Um dos desafios mais sutis no TypeScript é o fenômeno chamado widening — o alargamento automático de tipos literais para tipos mais genéricos. Quando você declara uma variável com let, o TypeScript infere o tipo mais amplo possível:
let status = "ativo"; // tipo inferido: string, não "ativo"
status = "inativo"; // OK
status = "desligado"; // OK (qualquer string é aceita)
Com const, o comportamento muda — o tipo literal é preservado:
const status = "ativo"; // tipo: "ativo" (literal)
// status = "inativo"; // Erro! Não pode reatribuir const
O problema surge em contextos mais complexos, como objetos:
const config = {
mode: "dark", // tipo inferido: string (widening!)
timeout: 3000 // tipo inferido: number (widening!)
};
// config.mode aceitaria qualquer string
// config.timeout aceitaria qualquer número
Anotações de tipo tradicionais resolvem o widening, mas criam outro problema:
const config: { mode: "dark" | "light"; timeout: number } = {
mode: "dark",
timeout: 3000
};
// Agora config.mode é "dark" | "light", mas perdemos o literal exato "dark"
Precisávamos de uma forma de validar que um valor está em conformidade com um tipo, sem perder os tipos literais inferidos.
2. Introdução ao Operador satisfies
O operador satisfies, introduzido no TypeScript 4.9, resolve exatamente esse problema. Sua sintaxe é simples:
const valor = expressão satisfies Tipo;
Ele verifica se a expressão à esquerda é compatível com o tipo à direita, sem alterar o tipo inferido da expressão.
const status = "ativo" satisfies string; // tipo: "ativo"
const timeout = 3000 satisfies number; // tipo: 3000
Compare com as alternativas:
// Type assertion (perigoso):
const a = "hello" as string; // tipo: string (perdeu literal)
// Anotação explícita:
const b: string = "hello"; // tipo: string (perdeu literal)
// satisfies:
const c = "hello" satisfies string; // tipo: "hello" (preservado!)
3. Preservação de Tipos Literais com satisfies
O poder do satisfies brilha quando combinado com tipos literais e uniões:
type Status = "ativo" | "inativo" | "pendente";
const userStatus = "ativo" satisfies Status;
// typeof userStatus = "ativo" (literal preservado)
// userStatus só pode ser "ativo", "inativo" ou "pendente"
// Isso falharia em tempo de compilação:
// const invalidStatus = "cancelado" satisfies Status;
// Erro: '"cancelado"' não é atribuível a 'Status'
Em objetos, a diferença é ainda mais notável:
type Theme = "dark" | "light";
type Config = {
theme: Theme;
timeout: number;
debug: boolean;
};
const appConfig = {
theme: "dark", // tipo: "dark" (não Theme)
timeout: 5000, // tipo: 5000 (não number)
debug: true // tipo: true (não boolean)
} satisfies Config;
// Acessando propriedades com tipos precisos:
appConfig.theme; // tipo: "dark"
appConfig.timeout; // tipo: 5000
appConfig.debug; // tipo: true
// Mas ainda temos validação contra Config:
// appConfig.theme = "blue"; // Erro! "blue" não é Theme
4. Uso com Objetos e Tipos Complexos
O satisfies é particularmente útil para validar objetos complexos sem perder detalhes:
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
type Route = {
method: HttpMethod;
path: string;
handler: string;
};
const routes = {
users: {
method: "GET",
path: "/api/users",
handler: "getUsers"
},
createUser: {
method: "POST",
path: "/api/users",
handler: "createUser"
}
} satisfies Record<string, Route>;
// Tipo preservado: { users: { method: "GET", ... }, createUser: { method: "POST", ... } }
routes.users.method; // tipo: "GET"
routes.createUser.method; // tipo: "POST"
Com uniões discriminadas, o satisfies permite validação rigorosa:
type SuccessResponse = { status: "success"; data: unknown };
type ErrorResponse = { status: "error"; message: string };
type ApiResponse = SuccessResponse | ErrorResponse;
const response1 = {
status: "success",
data: { id: 1, name: "Alice" }
} satisfies ApiResponse;
const response2 = {
status: "error",
message: "Not found"
} satisfies ApiResponse;
// response1.data é unknown (preciso), response2.message é string
5. Validação de Mapeamentos e Dicionários
Para dicionários onde queremos garantir que todos os valores seguem um padrão:
type ColorHex = `#${string}`;
const themeColors = {
primary: "#3498db",
secondary: "#2ecc71",
danger: "#e74c3c",
background: "#ffffff"
} satisfies Record<string, ColorHex>;
// Todos os valores são validados como hexadecimais
// Mas as chaves e tipos literais são preservados
// Isso falharia:
// const badColors = {
// primary: "blue" // Erro! "blue" não é `#${string}`
// } satisfies Record<string, ColorHex>;
Para mapeamentos mais específicos:
type Permission = "read" | "write" | "admin";
type RolePermissions = Record<string, Permission[]>;
const roles = {
viewer: ["read"],
editor: ["read", "write"],
owner: ["read", "write", "admin"]
} satisfies RolePermissions;
// roles.viewer é do tipo ["read"] (tupla literal)
// roles.owner é ["read", "write", "admin"]
6. Combinação com typeof e Inferência Avançada
O satisfies funciona perfeitamente com typeof para extrair tipos precisos:
const user = {
name: "Alice",
age: 30,
roles: ["admin", "editor"]
} satisfies {
name: string;
age: number;
roles: string[];
};
type UserType = typeof user;
// { name: "Alice"; age: 30; roles: ["admin", "editor"] }
function processUser(u: typeof user) {
console.log(u.name); // tipo: "Alice"
}
Em funções, para validar retornos complexos:
type ValidationResult = {
valid: boolean;
errors?: string[];
};
function validateUser(data: unknown) {
return {
valid: true,
errors: [],
user: { id: 1, name: "Bob" }
} satisfies ValidationResult & { user: { id: number; name: string } };
}
const result = validateUser({});
result.valid; // tipo: true
result.user.name; // tipo: "Bob"
Com genéricos:
function createTypedObject<T extends Record<string, unknown>>(
obj: T satisfies Record<string, string | number>
) {
return obj;
}
const obj = createTypedObject({
name: "Alice",
age: 30
});
// obj.name: string, obj.age: number
7. Diferenças e Boas Práticas em Relação a Alternativas
satisfies vs as (type assertion)
| Aspecto | satisfies |
as |
|---|---|---|
| Segurança | Valida em tempo de compilação | Afirmação cega |
| Preservação de tipos | Mantém literais | Perde literais |
| Erro em invalidação | Sim, em tempo de compilação | Não (pode causar bugs) |
// Perigoso:
const x = { value: "hello" } as { value: string | number };
x.value = 42; // OK, mas pode quebrar código que espera string
// Seguro:
const y = { value: "hello" } satisfies { value: string | number };
// y.value = 42; // Erro! "hello" é string, não pode receber number
satisfies vs anotação de tipo direta
// Anotação direta: perde detalhes
const a: { key: string } = { key: "value" };
// a.key é string (perdeu "value")
// satisfies: preserva
const b = { key: "value" } satisfies { key: string };
// b.key é "value" (literal preservado)
Armadilhas comuns
-
satisfiesnão valida em runtime — é apenas uma verificação de tipo em tempo de compilação. -
Não use
satisfiespara tipos que você quer que sejam o tipo final — use anotação direta nesse caso. -
Cuidado com objetos muito grandes —
satisfiesverifica cada propriedade, o que pode impactar performance em objetos enormes. -
Não substitui validação de dados externos — para dados de API ou input de usuário, continue usando bibliotecas de validação em runtime.
Padrões recomendados
- Use
satisfiesquando precisar validar contra um tipo e preservar tipos literais - Prefira
satisfiesaaspara validação de objetos complexos - Combine
satisfiescomtypeofpara extrair tipos precisos - Use
satisfiesem definições de configuração e constantes
O satisfies operator é uma ferramenta elegante que preenche uma lacuna importante no sistema de tipos do TypeScript, permitindo validação sem widening e preservando a riqueza dos tipos literais inferidos.
Referências
- TypeScript 4.9 Release Notes: satisfies operator — Documentação oficial do TypeScript sobre o operador satisfies, incluindo exemplos e casos de uso
- TypeScript Handbook: Type Inference — Guia completo sobre inferência de tipos, incluindo widening e narrowing
- TypeScript Deep Dive: satisfies operator — Explicação detalhada do operador satisfies com exemplos práticos
- Total TypeScript: The satisfies operator — Tutorial interativo sobre o satisfies operator com exercícios práticos
- TypeScript Playground: satisfies examples — Exemplos interativos no TypeScript Playground para experimentar o satisfies operator
- Dev.to: Understanding TypeScript's satisfies operator — Artigo técnico explicando o satisfies operator com exemplos do mundo real
- TypeScript Evolution: The satisfies operator — Análise detalhada do satisfies operator por Marius Schulz, com comparações e casos de uso avançados