Module augmentation: estendendo tipos de bibliotecas
1. O que é Module Augmentation e por que usar?
Module augmentation é um recurso do TypeScript que permite estender declarações de tipos existentes em módulos de terceiros sem modificar o código original da biblioteca. Em vez de criar definições de tipo paralelas ou recorrer a any, você pode adicionar propriedades, métodos ou sobrescrever comportamentos tipados de forma segura.
Casos de uso comuns incluem:
- Adicionar propriedades a objetos globais como Express.Request.user
- Estender protótipos nativos com métodos customizados
- Complementar tipos parciais de bibliotecas que não exportam tudo que você precisa
A principal diferença entre module augmentation e declaration merging é o escopo: enquanto declaration merging ocorre globalmente em arquivos .d.ts (scripts), module augmentation acontece dentro de módulos, respeitando o sistema de módulos do TypeScript.
2. Sintaxe básica de Module Augmentation
A estrutura fundamental utiliza declare module 'nome-do-modulo' para reabrir o módulo e adicionar novas declarações:
// types/express-augmentation.d.ts
import 'express';
declare module 'express' {
interface Request {
user?: {
id: string;
name: string;
email: string;
};
}
}
Para que a augmentation seja reconhecida, o arquivo deve ser um módulo (conter pelo menos um import ou export) e estar incluído no tsconfig.json:
{
"compilerOptions": {
"typeRoots": ["./node_modules/@types", "./types"]
},
"include": ["src/**/*", "types/**/*"]
}
3. Estendendo tipos de bibliotecas populares
Express: adicionando propriedades a Request e Response
// types/express-augmentation.d.ts
import 'express';
declare module 'express' {
interface Request {
user?: { id: string; role: 'admin' | 'user' };
startTime: number;
}
interface Response {
sendSuccess(data: unknown): void;
sendError(message: string, code?: number): void;
}
}
React: estendendo atributos HTML
// types/react-augmentation.d.ts
import 'react';
declare module 'react' {
interface HTMLAttributes<T> {
'data-testid'?: string;
'aria-label'?: string;
}
// Estendendo IntrinsicElements para elementos customizados
interface IntrinsicElements {
'my-component': React.DetailedHTMLProps<
React.HTMLAttributes<HTMLElement> & { prop: string },
HTMLElement
>;
}
}
Prisma: adicionando métodos a modelos
// types/prisma-augmentation.d.ts
import { PrismaClient } from '@prisma/client';
declare module '@prisma/client' {
interface User {
fullName(): string;
isActive(): boolean;
}
}
Lodash: estendendo funções utilitárias
// types/lodash-augmentation.d.ts
import 'lodash';
declare module 'lodash' {
interface LoDashStatic {
// Adiciona um método customizado
deepClone<T>(obj: T): T;
}
}
4. Trabalhando com módulos que exportam namespaces
Para estender namespaces, a sintaxe é similar, mas você precisa referenciar o namespace dentro do módulo:
// types/jquery-augmentation.d.ts
import 'jquery';
declare module 'jquery' {
interface JQueryStatic {
// Adiciona método estático
myCustomPlugin(options: Record<string, unknown>): void;
}
interface JQuery {
// Adiciona método de instância
customMethod(): this;
}
}
Quando o namespace está aninhado, use a mesma estrutura:
declare module 'biblioteca' {
namespace NamespaceExterno {
interface NamespaceInterno {
novaPropriedade: string;
}
}
}
5. Augmentação de tipos genéricos e utilitários
Estender tipos genéricos requer cuidado com a poluição do escopo global:
// types/array-augmentation.d.ts
interface Array<T> {
first(): T | undefined;
last(): T | undefined;
pluck<K extends keyof T>(key: K): T[K][];
}
// Implementação (arquivo .ts)
if (!Array.prototype.first) {
Array.prototype.first = function<T>(this: T[]): T | undefined {
return this[0];
};
}
Para tipos que não são de módulo, use declare global:
// types/global-augmentation.d.ts
declare global {
interface String {
capitalize(): string;
toTitleCase(): string;
}
interface Window {
myAppConfig: { version: string; debug: boolean };
}
}
export {}; // Torna o arquivo um módulo
6. Boas práticas e armadilhas comuns
Manter augmentations em arquivos separados
Organize as augmentações em types/augmentations.d.ts ou em arquivos por biblioteca:
// types/augmentations/express.d.ts
// types/augmentations/react.d.ts
// types/augmentations/prisma.d.ts
Evitar sobrescrever tipos existentes
Use interseção & quando precisar combinar tipos:
declare module 'express' {
interface Request {
// Não sobrescreva, adicione
user?: { id: string } & { role: string };
}
}
Problemas com versões da biblioteca
Sempre verifique a versão da biblioteca e suas definições de tipo. Atualizações podem quebrar augmentations existentes.
Testando se a augmentation está sendo reconhecida
// Em qualquer arquivo .ts
import express from 'express';
const app = express();
app.use((req, res, next) => {
// Se o TypeScript reconhecer 'user', a augmentation funcionou
console.log(req.user?.name);
next();
});
7. Casos avançados: merged types com mapped types e utilitários
Combinando module augmentation com mapped types para estender dinamicamente:
// types/orm-augmentation.d.ts
import 'some-orm';
declare module 'some-orm' {
interface Model {
// Adiciona método de auditoria a todos os modelos
auditTrail(): Promise<AuditEntry[]>;
}
}
// Usando mapped types para adicionar métodos a propriedades específicas
type ModelWithTimestamps<T> = T & {
createdAt: Date;
updatedAt: Date;
};
declare module 'some-orm' {
interface User extends ModelWithTimestamps<User> {}
}
Uso de utilitários dentro da augmentation:
declare module 'biblioteca' {
interface Config {
// Garante que propriedades opcionais sejam requeridas
options: Required<Pick<OriginalOptions, 'host' | 'port'>>;
}
}
Integração com satisfies para segurança adicional:
const config = {
host: 'localhost',
port: 3000,
} satisfies Required<Pick<Config['options'], 'host' | 'port'>>;
8. Resumo e checklist de implementação
Checklist para implementar module augmentation:
- ✅ Verifique se o módulo alvo é declarado com
declare module 'nome' - ✅ Crie um arquivo
.d.tsseparado com pelo menos umimport - ✅ Adicione o arquivo ao
tsconfig.json(include ou typeRoots) - ✅ Estenda interfaces ou namespaces, não sobrescreva
- ✅ Teste se o compilador reconhece as novas propriedades
- ✅ Para tipos globais, use
declare globaleexport {}
Quando preferir declaration merging vs module augmentation:
- Declaration merging: para tipos globais (sem módulo) ou quando o arquivo é um script
- Module augmentation: para módulos com
import/export, respeitando o escopo do módulo
Próximos passos:
- Explore
satisfiespara validação de tipos sem widening - Aprofunde-se em declaration merging para tipos globais
- Estude mapped types e conditional types para augmentations mais complexas
Referências
- TypeScript Official Documentation: Module Augmentation — Documentação oficial explicando a sintaxe e casos de uso de module augmentation
- TypeScript Deep Dive: Module Augmentation — Guia prático com exemplos detalhados de estender módulos de terceiros
- Stack Overflow: How to extend Express Request in TypeScript — Discussão técnica sobre a abordagem correta para estender Express.Request
- Prisma Documentation: Extending Prisma Client Models — Guia oficial da Prisma para estender tipos dos modelos gerados
- React TypeScript Cheatsheet: Extending HTML Elements — Padrões para estender atributos HTML e componentes em React com TypeScript
- TypeScript Handbook: Declaration Merging — Documentação completa sobre declaration merging, conceito fundamental para entender module augmentation
- Dev.to: Module Augmentation in TypeScript — Tutorial prático com exemplos de augmentação em bibliotecas populares