<h2>Por que TypeScript é Essencial para Bibliotecas</h2>
<p>Quando você cria uma biblioteca JavaScript, seus usuários não têm visibilidade do código interno — apenas da interface pública. TypeScript resolve esse problema fornecendo um contrato explícito através de tipos. Arquivos <code>.d.ts</code> (declaration files) são a ponte entre sua implementação e quem usa sua biblioteca. Sem eles, desenvolvedores enfrentam falta de autocompletar, erros silenciosos em tempo de desenvolvimento e documentação incompleta.</p>
<p>A diferença é imediata: uma biblioteca sem tipos força o usuário a ler documentação externa ou fazer tentativas; uma com tipos bem definidos oferece autocompletar inteligente, detecção de erros antes da execução e melhor experiência geral. Vamos aprender como criar tipos públicos que sua comunidade agradecerá.</p>
<h2>Estruturando Tipos Públicos com .d.ts</h2>
<h3>Criando Declaration Files</h3>
<p>O arquivo <code>.d.ts</code> contém apenas tipos, interfaces e assinaturas de funções — nenhuma implementação. Se você já tem código TypeScript compilado, o compilador pode gerar automaticamente esses arquivos com <code>declaration: true</code> no <code>tsconfig.json</code>. Mas entender como escrever manualmente é crucial para controlar exatamente o que exponha.</p>
<pre><code class="language-typescript">// src/types/index.d.ts
export interface User {
id: number;
name: string;
email: string;
createdAt: Date;
}
export interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
export declare class UserService {
constructor(apiUrl: string);
getUser(id: number): Promise<ApiResponse<User>>;
createUser(user: Omit<User, 'id' | 'createdAt'>): Promise<User>;
deleteUser(id: number): Promise<void>;
}
export declare function validateEmail(email: string): boolean;</code></pre>
<p>Aqui definimos tipos (<code>User</code>, <code>ApiResponse</code>), uma classe (<code>UserService</code>) e uma função (<code>validateEmail</code>). Tudo é declarado publicamente. A correspondência com a implementação real deve ser exata — TypeScript verificará isso na compilação.</p>
<h3>Controlando Visibilidade com Modificadores</h3>
<p>Nem tudo precisa ser público. Use <code>private</code>, <code>protected</code> e membros não exportados para esconder detalhes internos. Sua biblioteca fica mais limpa e o contrato mais claro.</p>
<pre><code class="language-typescript">// src/userService.ts
class UserService {
private apiUrl: string;
private cache: Map<number, User> = new Map();
constructor(apiUrl: string) {
this.apiUrl = apiUrl;
}
private validateApiConnection(): Promise<boolean> {
// lógica interna
return Promise.resolve(true);
}
public async getUser(id: number): Promise<ApiResponse<User>> {
if (this.cache.has(id)) {
return {
data: this.cache.get(id)!,
status: 200,
message: 'From cache'
};
}
// chamada real
const response = await fetch(${this.apiUrl}/users/${id});
const data = await response.json();
this.cache.set(id, data);
return { data, status: 200, message: 'Success' };
}
}</code></pre>
<p>Propriedades <code>private</code> como <code>apiUrl</code> e <code>cache</code> não aparecem no <code>.d.ts</code> gerado automaticamente. Apenas <code>getUser</code> é exposto. Isso é exatamente o que você quer — apenas a interface pública.</p>
<h2>Configuração do tsconfig.json para Geração Automática</h2>
<h3>Habilitando Declaration Emission</h3>
<p>Para gerar <code>.d.ts</code> automaticamente durante a compilação, configure seu <code>tsconfig.json</code>:</p>
<pre><code class="language-json">{
"compilerOptions": {
"declaration": true,
"declarationDir": "./dist/types",
"emitDeclarationOnly": false,
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"module": "commonjs",
"target": "ES2020"
},
"include": ["src/*/"],
"exclude": ["node_modules", "dist", "*/.test.ts"]
}</code></pre>
<p>A opção <code>declaration: true</code> instrui o TypeScript a gerar um arquivo <code>.d.ts</code> para cada arquivo <code>.ts</code>. Use <code>declarationDir</code> para organizá-los separadamente. No <code>package.json</code>, aponte para esses tipos:</p>
<pre><code class="language-json">{
"name": "minha-biblioteca",
"version": "1.0.0",
"main": "dist/index.js",
"types": "dist/types/index.d.ts",
"scripts": {
"build": "tsc"
}
}</code></pre>
<p>O campo <code>"types"</code> é a chave — ele diz ao TypeScript e IDEs exatamente onde encontrar seus tipos.</p>
<h2>Padrões Avançados para Tipos Robustos</h2>
<h3>Tipos Genéricos e Utilitários</h3>
<p>Para bibliotecas reutilizáveis, genéricos e tipos utilitários são indispensáveis. Permitem flexibilidade mantendo segurança de tipo.</p>
<pre><code class="language-typescript">// src/database.d.ts
export interface Repository<T> {
findById(id: string): Promise<T | null>;
findAll(): Promise<T[]>;
create(item: Omit<T, 'id'>): Promise<T>;
update(id: string, item: Partial<T>): Promise<T>;
delete(id: string): Promise<boolean>;
}
export interface Entity {
id: string;
createdAt: Date;
updatedAt: Date;
}
export interface Product extends Entity {
name: string;
price: number;
stock: number;
}
export declare class Database {
getRepository<T extends Entity>(
collection: string
): Repository<T>;
}
// Uso:
// const db = new Database();
// const products = db.getRepository<Product>('products');
// products.findById('123'); // Promise<Product | null> ✓</code></pre>
<p>Aqui <code>Repository<T></code> é genérico — adapta-se a qualquer tipo que estenda <code>Entity</code>. Usuários ganham type-safety específico para seu domínio sem duplicar código.</p>
<h3>Overloads para Flexibilidade</h3>
<p>Funções com comportamentos diferentes conforme argumentos precisam de overloads:</p>
<pre><code class="language-typescript">export interface SearchOptions {
limit?: number;
offset?: number;
}
// Sobrecargas
export declare function search(
query: string
): Promise<User[]>;
export declare function search(
query: string,
options: SearchOptions
): Promise<User[]>;
export declare function search(
query: string,
options?: SearchOptions
): Promise<User[]>;</code></pre>
<p>TypeScript escolhe automaticamente o overload correto baseado nos argumentos passados. Sem overloads, a IDE não saberia se <code>options</code> é obrigatório ou não.</p>
<h2>Conclusão</h2>
<p>Aprender a escrever tipos públicos com <code>.d.ts</code> é o diferencial entre uma biblioteca amadora e uma profissional. Três pontos essenciais ficaram claros: <strong>(1)</strong> tipos explícitos transformam a experiência do usuário através de autocompletar e detecção de erros; <strong>(2)</strong> controle de visibilidade (público vs. privado) mantém sua API limpa e documentada naturalmente; <strong>(3)</strong> padrões como genéricos e overloads permitem flexibilidade sem sacrificar segurança de tipo. Configure seu <code>tsconfig.json</code> corretamente e deixe o compilador fazer o trabalho pesado de gerar tipos automaticamente. Sua comunidade agradecerá.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html" target="_blank" rel="noopener noreferrer">TypeScript Handbook: Declaration Files</a></li>
<li><a href="https://www.typescriptlang.org/docs/handbook/declaration-files/by-example.html" target="_blank" rel="noopener noreferrer">TypeScript: Writing Declaration Files</a></li>
<li><a href="https://www.typescriptlang.org/docs/handbook/declaration-files/publishing.html" target="_blank" rel="noopener noreferrer">npm package.json: types field</a></li>
<li><a href="https://effectivetypescript.com/" target="_blank" rel="noopener noreferrer">Effective TypeScript: Item 46 - Understand the Three Versions</a></li>
<li><a href="https://www.typescriptlang.org/tsconfig" target="_blank" rel="noopener noreferrer">TypeScript tsconfig.json Documentation</a></li>
</ul>