<h2>Entendendo Metadata e Reflect-Metadata</h2>
<p>Metadata é simplesmente informação <em>sobre</em> informação. Em TypeScript, trata-se de dados que descrevem características de classes, métodos, propriedades e parâmetros, sem fazer parte da lógica principal do programa. Quando você escreve uma classe com tipos específicos, o TypeScript remove toda essa informação de tipo durante a compilação para JavaScript — porque JavaScript não tem tipos nativos. O <code>reflect-metadata</code> é uma biblioteca que recupera e armazena essa informação em tempo de execução, permitindo que você a consulte dinamicamente.</p>
<p>O <code>reflect-metadata</code> implementa o padrão de reflexão do JavaScript (parte da proposta TC39) e funciona através de uma API global chamada <code>Reflect</code>. Esta biblioteca é essencial quando você precisa fazer coisas como serialização automática, injeção de dependência, validação de dados ou criação de ORMs. Sem ela, seria extremamente complexo descobrir quais são os tipos de propriedades de uma classe em tempo de execução — especialmente em cenários avançados com decorators.</p>
<h2>Decorators: O Mecanismo de Injeção de Metadata</h2>
<h3>O que são Decorators</h3>
<p>Decorators são funções que modificam ou anotam classes, métodos, propriedades e parâmetros. Eles são executados em tempo de compilação (ou mais precisamente, quando a classe é definida) e podem injetar metadata que será consultada depois. Um decorator é essencialmente uma função que recebe um alvo e retorna uma versão modificada dele.</p>
<p>Para usar decorators em TypeScript, você precisa ativar a opção <code>experimentalDecorators</code> no arquivo <code>tsconfig.json</code>:</p>
<pre><code class="language-json">{
"compilerOptions": {
"target": "ES2020",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"lib": ["ES2020"],
"strict": true
}
}</code></pre>
<p>A opção <code>emitDecoratorMetadata</code> é crucial — ela faz o TypeScript compilar automaticamente metadata de tipos nas classes que usam decorators.</p>
<h3>Decorators de Classe</h3>
<p>Um decorator de classe recebe o construtor da classe como argumento e pode modificá-lo ou envolver-o. Vamos ver um exemplo prático:</p>
<pre><code class="language-typescript">import 'reflect-metadata';
function Serializable(): ClassDecorator {
return function (constructor: Function) {
// Armazena metadata indicando que esta classe é serializável
Reflect.defineMetadata('serializable', true, constructor);
};
}
@Serializable()
class Usuario {
nome: string = 'João';
idade: number = 30;
}
const usuario = new Usuario();
const ehSerializavel = Reflect.getMetadata('serializable', Usuario);
console.log(ehSerializavel); // true</code></pre>
<p>Neste exemplo, o decorator <code>@Serializable()</code> marca a classe <code>Usuario</code> como serializável armazenando um booleano na metadata da classe. Quando você consulta com <code>Reflect.getMetadata()</code>, recupera essa informação.</p>
<h3>Decorators de Propriedade</h3>
<p>Decorators de propriedade recebem o protótipo do objeto (ou o construtor, se for propriedade estática) e a chave da propriedade. Eles são úteis para validação ou transformação de dados:</p>
<pre><code class="language-typescript">import 'reflect-metadata';
function Validar(tipo: Function): PropertyDecorator {
return function (target: Object, propertyKey: string | symbol | undefined) { const metadataTypes = Reflect.getOwnMetadata('design:type', target, propertyKey) || [];
Reflect.defineMetadata('validation:type', tipo, target, propertyKey);
};
}
class Produto {
@Validar(String)
nome: string;
@Validar(Number)
preco: number;
constructor(nome: string, preco: number) {
this.nome = nome;
this.preco = preco;
}
}
const produto = new Produto('Notebook', 3000);
const tipoValidacao = Reflect.getMetadata('validation:type', produto, 'nome');
console.log(tipoValidacao === String); // true</code></pre>
<p>Aqui o decorator <code>@Validar()</code> armazena o tipo esperado de cada propriedade, permitindo validações posteriores.</p>
<h3>Decorators de Método</h3>
<p>Decorators de método recebem o protótipo, o nome do método e o descritor de propriedade. São frequentemente usados para logging, caching ou controle de acesso:</p>
<pre><code class="language-typescript">import 'reflect-metadata';
function Log(): MethodDecorator {
return function (target: Object, propertyKey: string | symbol | undefined, descriptor: PropertyDescriptor) {
const metodoOriginal = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(Chamando ${String(propertyKey)} com argumentos:, args);
const resultado = metodoOriginal.apply(this, args);
console.log(${String(propertyKey)} retornou:, resultado);
return resultado;
};
return descriptor;
};
}
class Calculadora {
@Log()
somar(a: number, b: number): number {
return a + b;
}
}
const calc = new Calculadora();
calc.somar(5, 3);
// Saída:
// Chamando somar com argumentos: [5, 3]
// somar retornou: 8</code></pre>
<h2>Reflect-Metadata: Consultando e Manipulando Metadata</h2>
<h3>Armazenando Metadata com Reflect</h3>
<p>O módulo <code>reflect-metadata</code> fornece uma API para armazenar e recuperar metadata. Os métodos principais são <code>defineMetadata()</code>, <code>getMetadata()</code>, <code>getOwnMetadata()</code> e <code>hasMetadata()</code>. A diferença entre <code>getMetadata()</code> e <code>getOwnMetadata()</code> é que o primeiro procura na cadeia de herança, enquanto o segundo busca apenas na metadata definida diretamente no alvo.</p>
<pre><code class="language-typescript">import 'reflect-metadata';
class Animal {}
class Cachorro extends Animal {}
// Define metadata na classe Animal
Reflect.defineMetadata('tipo', 'Animal', Animal);
// Consultando
console.log(Reflect.getMetadata('tipo', Animal)); // "Animal"
console.log(Reflect.getMetadata('tipo', Cachorro)); // "Animal" (herança)
console.log(Reflect.getOwnMetadata('tipo', Cachorro)); // undefined (não foi definida diretamente em Cachorro)</code></pre>
<h3>Exemplo Prático: Construindo um Sistema de Validação</h3>
<p>Vamos criar um validador simples que usa metadata para validar objetos:</p>
<pre><code class="language-typescript">import 'reflect-metadata';
function IsString(): PropertyDecorator {
return function (target: Object, propertyKey: string | symbol | undefined) {
Reflect.defineMetadata('validation:type', 'string', target, propertyKey);
};
}
function IsNumber(): PropertyDecorator {
return function (target: Object, propertyKey: string | symbol | undefined) {
Reflect.defineMetadata('validation:type', 'number', target, propertyKey);
};
}
function ValidarObjeto(objeto: any): boolean {
const propiedades = Object.getOwnPropertyNames(objeto);
for (const prop of propiedades) {
const tipoEsperado = Reflect.getMetadata('validation:type', objeto, prop);
if (tipoEsperado) {
const tipoReal = typeof objeto[prop];
if (tipoReal !== tipoEsperado) {
console.error(Propriedade "${prop}" deveria ser ${tipoEsperado}, mas é ${tipoReal});
return false;
}
}
}
return true;
}
class Pessoa {
@IsString()
nome: string;
@IsNumber()
idade: number;
constructor(nome: string, idade: number) {
this.nome = nome;
this.idade = idade;
}
}
const pessoa1 = new Pessoa('Maria', 28);
console.log(ValidarObjeto(pessoa1)); // true
const pessoa2 = new Pessoa('João', '30' as any);
console.log(ValidarObjeto(pessoa2)); // false e exibe erro</code></pre>
<h3>Acessando Metadata de Tipos com Design:Type</h3>
<p>Quando você ativa <code>emitDecoratorMetadata</code> no TypeScript, a compilação automaticamente injeta metadata especial chamada <code>design:type</code>, <code>design:paramtypes</code> e <code>design:returntype</code>. Estas contêm os tipos das propriedades e parâmetros:</p>
<pre><code class="language-typescript">import 'reflect-metadata';
function Inspecionar(): ClassDecorator {
return function (constructor: Function) {
const propiedades = Object.getOwnPropertyNames(constructor.prototype);
for (const prop of propiedades) {
const tipo = Reflect.getMetadata('design:type', constructor.prototype, prop);
if (tipo) {
console.log(${prop}: ${tipo.name});
}
}
};
}
@Inspecionar()
class Veiculo {
marca: string;
ano: number;
ativo: boolean;
}
// Saída:
// marca: String
// ano: Number
// ativo: Boolean</code></pre>
<p>O TypeScript injeta automaticamente a metadata <code>design:type</code> em cada propriedade decorada. Isso permite que você inspecione tipos em tempo de execução, algo impossível de fazer de outra forma em JavaScript puro.</p>
<h2>Caso de Uso Real: ORM Simplificado com Decorators e Metadata</h2>
<h3>Estrutura Básica</h3>
<p>Vamos construir um mini-ORM que simula operações de banco de dados usando metadata para mapear propriedades de classes para colunas de tabelas:</p>
<pre><code class="language-typescript">import 'reflect-metadata';
interface ColumnOptions {
name?: string;
type?: string;
}
function Column(opcoes?: ColumnOptions): PropertyDecorator {
return function (target: Object, propertyKey: string | symbol | undefined) { const colunaNome = opcoes?.name || String(propertyKey); const tipoDb = opcoes?.type || 'TEXT';
Reflect.defineMetadata('column:name', colunaNome, target, propertyKey);
Reflect.defineMetadata('column:type', tipoDb, target, propertyKey);
};
}
function Tabela(nomeDaTabela: string): ClassDecorator {
return function (constructor: Function) {
Reflect.defineMetadata('table:name', nomeDaTabela, constructor);
};
}
@Tabela('usuarios')
class Usuario {
@Column({ name: 'user_id', type: 'INTEGER' })
id: number;
@Column({ name: 'user_name', type: 'VARCHAR' })
nome: string;
@Column({ name: 'user_email', type: 'VARCHAR' })
email: string;
constructor(id: number, nome: string, email: string) {
this.id = id;
this.nome = nome;
this.email = email;
}
}</code></pre>
<h3>Gerador de SQL Automático</h3>
<p>Com a metadata armazenada, podemos gerar SQL dinamicamente:</p>
<pre><code class="language-typescript">function GerarCreateTable(classe: Function): string {
const nomeDaTabela = Reflect.getMetadata('table:name', classe);
const propiedades = Object.getOwnPropertyNames(classe.prototype);
const colunas = propiedades
.filter(prop => Reflect.hasMetadata('column:name', classe.prototype, prop))
.map(prop => {
const colunaNome = Reflect.getMetadata('column:name', classe.prototype, prop);
const tipoDb = Reflect.getMetadata('column:type', classe.prototype, prop);
return ${colunaNome} ${tipoDb};
})
.join(', ');
return CREATE TABLE ${nomeDaTabela} (${colunas});;
}
console.log(GerarCreateTable(Usuario));
// Saída: CREATE TABLE usuarios (user_id INTEGER, user_name VARCHAR, user_email VARCHAR);</code></pre>
<h3>Persistência Simulada</h3>
<p>Podemos criar um repositório que automaticamente serializa e deserializa objetos baseado na metadata:</p>
<pre><code class="language-typescript">class Repositorio<T> {
private dados: Map<number, any> = new Map();
private proximoId: number = 1;
salvar(entidade: T): T {
const id = this.proximoId++;
const nomeDaTabela = Reflect.getMetadata('table:name', entidade.constructor);
const propiedades = Object.getOwnPropertyNames(entidade.constructor.prototype);
const registro: any = { id };
for (const prop of propiedades) {
if (Reflect.hasMetadata('column:name', entidade.constructor.prototype, prop)) {
const colunaNome = Reflect.getMetadata('column:name', entidade.constructor.prototype, prop);
registro[colunaNome] = (entidade as any)[prop];
}
}
this.dados.set(id, registro);
console.log(Salvo em ${nomeDaTabela}:, registro);
return entidade;
}
obter(id: number): any {
return this.dados.get(id);
}
}
const repo = new Repositorio<Usuario>();
const usuario = new Usuario(1, 'Carlos', 'carlos@email.com');
repo.salvar(usuario);
console.log(repo.obter(1));
// Saída: { id: 1, user_id: 1, user_name: 'Carlos', user_email: 'carlos@email.com' }</code></pre>
<h2>Conclusão</h2>
<p>Aprendemos que <strong>metadata e decorators trabalham em conjunto para adicionar camadas de introspection e automação</strong> sem comprometer a clareza do código. Decorators são o mecanismo de injeção, enquanto <code>reflect-metadata</code> é a API que permite consultar essa informação posteriormente. Este padrão é tão poderoso que frameworks como NestJS, TypeORM e class-validator construem suas arquiteturas inteiras sobre ele.</p>
<p>A segunda grande lição é que <strong>TypeScript com <code>emitDecoratorMetadata</code> habilitado recupera informações de tipo que seriam perdidas na compilação</strong>, permitindo validações, transformações e mapeamentos dinâmicos que um JavaScript puro nunca conseguiria fazer. Isso transforma TypeScript de simplesmente "um JavaScript com tipos" em uma linguagem com capacidades de metaprogramação.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://github.com/rbuckton/reflect-metadata" target="_blank" rel="noopener noreferrer">Reflect-Metadata GitHub</a></li>
<li><a href="https://www.typescriptlang.org/docs/handbook/decorators.html" target="_blank" rel="noopener noreferrer">TypeScript Handbook - Decorators</a></li>
<li><a href="https://github.com/tc39/proposal-decorator-metadata" target="_blank" rel="noopener noreferrer">TC39 Proposal - Decorator Metadata</a></li>
<li><a href="https://docs.nestjs.com/custom-decorators" target="_blank" rel="noopener noreferrer">NestJS Documentation - Custom Decorators</a></li>
<li><a href="https://typeorm.io/" target="_blank" rel="noopener noreferrer">TypeORM - Active Record Data Mapper</a></li>
</ul>
<p><!-- FIM --></p>