Os tipos utilitários que valem a pena (e os que só complicam)
Há um tipo neste projeto que paga sozinho o preço de aprender isto:
export type Sondagem = NonNullable<Awaited<ReturnType<typeof lerSondagem>>>;Lê-se de dentro para fora: o que a função devolve, retirado de dentro da promise, com o null removido. Três utilitários encadeados, uma linha, e zero manutenção.
1. Derivar em vez de escrever
A alternativa seria escrever a forma à mão — doze campos, com os tipos todos. Funciona hoje. E no dia em que a consulta passar a trazer outro campo, há duas descrições da mesma coisa, e uma delas está errada sem avisar.
É esta a ideia que torna os utilitários úteis, e não a economia de escrita: a forma dos dados tem um dono. Aqui o dono é a função que consulta, porque é ela que decide que campos existem. Tudo o resto deve ser derivado dela — e então deixa de poder divergir.
2. Pick e Omit, e a assimetria que ninguém conta
type Resumo = Pick<Artigo, 'id' | 'titulo'>;
type SemCorpo = Omit<Artigo, 'corpo'>;Parecem simétricos e não são. O Pick dá erro se o campo não existir. O Omit não: Omit<Artigo, 'titlo'> compila alegremente e não remove nada.
Ou seja, um erro de escrita num Omit é silencioso, e o tipo resultante tem um campo a mais do que se pensava. É a diferença entre os dois que mais vezes morde, e por isso o Pick é preferível sempre que a lista de campos a manter for curta.
3. Record, com uma ressalva
É o mais usado aqui — seis sítios, para mapas de nome para valor. A ressalva liga-se ao artigo das opções do strict:
const mapa: Record<string, string> = { a: '1' };
const x = mapa['inexistente']; // tipo: string. Valor: undefined.O Record<string, T> afirma que qualquer chave existe, o que é falso por definição num mapa. Com noUncheckedIndexedAccess desligado, o compilador concorda com a mentira. Para chaves conhecidas, Record<'a' | 'b', T> diz a verdade e apanha a chave inventada.
4. O Partial que quase sempre está errado
// Nao: agora nada e obrigatorio, nem o id
function actualizar(dados: Partial<Artigo>) {}
// Sim: o que e obrigatorio continua a ser
function actualizar(id: number, dados: Partial<Omit<Artigo, 'id'>>) {}O Partial é a ferramenta certa para uma actualização parcial e a errada para um parâmetro de função, porque torna opcional também o que tem de estar lá. O resultado é um tipo que deixa passar chamadas sem sentido, e verificações em execução a compensar.
5. Os que servem para embrulhar código de outros
Parameters<T>, ReturnType<T> e Awaited<T> ganham quando se escreve algo em volta de uma função que não é nossa: um wrapper com registo, uma versão em cache, um retry. Repetir a assinatura à mão é assinar a manutenção dela; derivá-la é acompanhar automaticamente quem a muda.
6. Os que só complicam
A ginástica de tipos condicionais recursivos, os que se escrevem para provar que é possível. Há um teste simples: se for preciso ler o tipo para entender o que a função recebe, o tipo falhou. Era para explicar, e passou a precisar de explicação.
Nesses casos um tipo mais simples, ainda que menos exacto, mais uma verificação explícita — da família dos type guards — resolve melhor e lê-se daqui a um ano.
O resumo
Derivar da fonte de verdade, que é quase sempre a função que traz os dados. Preferir Pick a Omit, porque um engano no segundo é silencioso. Desconfiar do Record<string, T> e do Partial em parâmetros. E parar antes da ginástica: um tipo existe para dispensar explicação, não para a exigir.