JavaScript Avançado

Guia Completo de TypeScript para Bibliotecas: Escrevendo .d.ts e Tipos Públicos

9 min de leitura

Guia Completo de TypeScript para Bibliotecas: Escrevendo .d.ts e Tipos Públicos

Por que TypeScript é Essencial para Bibliotecas 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 (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. 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á. Estruturando Tipos Públicos com .d.ts Criando Declaration Files O arquivo 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 no . Mas entender como escrever manualmente é crucial para controlar

<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&lt;T&gt; {

data: T;

status: number;

message: string;

}

export declare class UserService {

constructor(apiUrl: string);

getUser(id: number): Promise&lt;ApiResponse&lt;User&gt;&gt;;

createUser(user: Omit&lt;User, &#039;id&#039; | &#039;createdAt&#039;&gt;): Promise&lt;User&gt;;

deleteUser(id: number): Promise&lt;void&gt;;

}

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&lt;number, User&gt; = new Map();

constructor(apiUrl: string) {

this.apiUrl = apiUrl;

}

private validateApiConnection(): Promise&lt;boolean&gt; {

// lógica interna

return Promise.resolve(true);

}

public async getUser(id: number): Promise&lt;ApiResponse&lt;User&gt;&gt; {

if (this.cache.has(id)) {

return {

data: this.cache.get(id)!,

status: 200,

message: &#039;From cache&#039;

};

}

// 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: &#039;Success&#039; };

}

}</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">{

&quot;compilerOptions&quot;: {

&quot;declaration&quot;: true,

&quot;declarationDir&quot;: &quot;./dist/types&quot;,

&quot;emitDeclarationOnly&quot;: false,

&quot;outDir&quot;: &quot;./dist&quot;,

&quot;rootDir&quot;: &quot;./src&quot;,

&quot;strict&quot;: true,

&quot;esModuleInterop&quot;: true,

&quot;skipLibCheck&quot;: true,

&quot;forceConsistentCasingInFileNames&quot;: true,

&quot;module&quot;: &quot;commonjs&quot;,

&quot;target&quot;: &quot;ES2020&quot;

},

&quot;include&quot;: [&quot;src/*/&quot;],

&quot;exclude&quot;: [&quot;node_modules&quot;, &quot;dist&quot;, &quot;*/.test.ts&quot;]

}</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">{

&quot;name&quot;: &quot;minha-biblioteca&quot;,

&quot;version&quot;: &quot;1.0.0&quot;,

&quot;main&quot;: &quot;dist/index.js&quot;,

&quot;types&quot;: &quot;dist/types/index.d.ts&quot;,

&quot;scripts&quot;: {

&quot;build&quot;: &quot;tsc&quot;

}

}</code></pre>

<p>O campo <code>&quot;types&quot;</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&lt;T&gt; {

findById(id: string): Promise&lt;T | null&gt;;

findAll(): Promise&lt;T[]&gt;;

create(item: Omit&lt;T, &#039;id&#039;&gt;): Promise&lt;T&gt;;

update(id: string, item: Partial&lt;T&gt;): Promise&lt;T&gt;;

delete(id: string): Promise&lt;boolean&gt;;

}

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&lt;T extends Entity&gt;(

collection: string

): Repository&lt;T&gt;;

}

// Uso:

// const db = new Database();

// const products = db.getRepository&lt;Product&gt;(&#039;products&#039;);

// products.findById(&#039;123&#039;); // Promise&lt;Product | null&gt; ✓</code></pre>

<p>Aqui <code>Repository&lt;T&gt;</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&lt;User[]&gt;;

export declare function search(

query: string,

options: SearchOptions

): Promise&lt;User[]&gt;;

export declare function search(

query: string,

options?: SearchOptions

): Promise&lt;User[]&gt;;</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>

Comentários

Mais em JavaScript Avançado

Guia Completo de Estratégias de Connection Pooling e Query Optimization em Node.js
Guia Completo de Estratégias de Connection Pooling e Query Optimization em Node.js

Connection Pooling em Node.js Connection pooling é uma técnica fundamental pa...

O que Todo Dev Deve Saber sobre SharedArrayBuffer e Atomics: Memória Compartilhada entre Workers
O que Todo Dev Deve Saber sobre SharedArrayBuffer e Atomics: Memória Compartilhada entre Workers

SharedArrayBuffer: O Que É e Por Que Usar SharedArrayBuffer é um objeto JavaS...

Como Usar TypeScript Compiler API: tsconfig Avançado e Project References em Produção
Como Usar TypeScript Compiler API: tsconfig Avançado e Project References em Produção

TypeScript Compiler API: tsconfig Avançado e Project References Entendendo o...