Cuándo crear decorators personalizados en TypeScript
Los decorators te ahorran código repetitivo y hacen tu arquitectura declarativa. Creá uno cuando necesites: aplicar la misma lógica a múltiples clases/métodos (logging, validación, cache), modificar comportamiento sin tocar la implementación, o agregar metadata para reflexión en runtime. Ejemplo clásico: @Authorize('admin') es más limpio que chequear roles en cada endpoint. También son útiles para cross-cutting concerns (seguridad, monitoreo, transacciones) que no pertenecen a la lógica de negocio. Si estás copiando el mismo try-catch o timer en 10 métodos, necesitás un decorator.
Tipos de decorators y sus casos de uso
Class decorators: Modifican constructores, útiles para DI o registrar clases (@Injectable, @Entity). Method decorators: Wrappean métodos, ideales para logging, cache, validación (@Log, @Cache). Property decorators: Modifican propiedades, se usan para metadata o transformaciones (@Column, @Expose). Parameter decorators: Marcan parámetros, comunes en frameworks web (@Body, @Param). NestJS y Angular usan todos los tipos; TypeORM se enfoca en class y property; en librerías custom, method decorators son los más versátiles.
Cómo nombrar decorators para que sean autodocumentados
Usá verbos o estados claros: @Validate dice qué hace, @IsEmail qué verifica, @Authorized qué requiere. Evitá nombres genéricos como @Do o @Handle. Si el decorator recibe parámetros, el nombre debe sugerir qué espera: @Cache(60) (TTL en segundos), @Roles('admin', 'editor') (strings de roles). Prefijos comunes: Is- para validaciones booleanas, Use- para aplicar middleware, Before/After para hooks de lifecycle. Consistencia dentro del proyecto: si usás @LogStart, no uses @TraceEnd para el par; mejor @LogEnd.
Errores comunes al implementar decorators
No preservar metadata: Algunos decorators sobreescriben metadata existente; usá Reflect.defineMetadata con merge. No bindear this: Si el decorator wrappea un método, perdés el contexto de clase; usá arrow functions o .bind(this). Ejecución en build time vs runtime: Decorators se ejecutan al declarar la clase, no al instanciarla; para lógica dinámica, retorná una función. No tipar parámetros: @Custom(options: any) es trampa; definí interfaces claras. Olvidar experimentalDecorators: En tsconfig.json debe estar en true. No documentar comportamiento: Un decorator sin docstring es una caja negra.
Tabla de referencia: utility types de TypeScript
Además de los decorators, TypeScript trae utility types integrados que transforman tipos existentes sin reescribirlos. Los que más vas a usar, qué hacen y un ejemplo (asumí un tipo type T = { id: number; name: string }).
| Tipo | Qué hace | Ejemplo |
|---|---|---|
Partial<T> | Vuelve todas las propiedades opcionales | Partial<T> → { id?: number; name?: string } |
Required<T> | Vuelve todas las propiedades obligatorias | Required<{ id?: number }> → { id: number } |
Readonly<T> | Vuelve todas las propiedades de solo lectura | Readonly<T> → { readonly id: number; ... } |
Pick<T, K> | Toma solo las claves K de T | Pick<T, 'id'> → { id: number } |
Omit<T, K> | Toma T sin las claves K | Omit<T, 'id'> → { name: string } |
Record<K, V> | Crea un objeto con claves K y valores V | Record<string, number> → { [k: string]: number } |
Exclude<U, E> | Quita de la unión U los miembros asignables a E | Exclude<'a' | 'b', 'a'> → 'b' |
Extract<U, E> | Deja de U solo los miembros asignables a E | Extract<'a' | 'b', 'a'> → 'a' |
ReturnType<F> | Extrae el tipo de retorno de una función | ReturnType<() => number> → number |
Parameters<F> | Extrae los parámetros de una función como tupla | Parameters<(a: string) => void> → [string] |
Awaited<T> | Desenvuelve el tipo resuelto de una Promise | Awaited<Promise<string>> → string |