Decorador @QType
Aunque el decorador de clase @Quick es la forma recomendada de definir modelos (es más limpio y evita redundancia), QuickModel también proporciona el decorador de propiedad @QType para un control granular.
RECOMENDACIÓN
Recomendamos encarecidamente usar @Quick para el 95% de los casos. Mantiene los tipos ordenados en un solo lugar y evita ensuciar la clase con decoradores. Usa @QType solo cuando necesites sobrescrituras específicas para un solo campo o prefieras una decoración explícita.
Uso
Coloca @QType antes de una declaración de propiedad para indicar explícitamente a QuickModel cómo manejar esa propiedad.
class User extends QModel<IUser> {
// 1. Uso Estándar (Constructor)
@QType(Date)
declare createdAt: Date;
// 2. Alias de Cadena
@QType('bigint')
declare balance: bigint;
// 3. Transformador Personalizado (Deserializador)
@QType((val) => String(val).toUpperCase())
declare code: string;
// 4. Opciones Avanzadas (Transformador + Serializador)
@QType(Date, {
serializer: (date) => date.toISOString(),
})
declare modifiedAt: Date;
}Sintaxis de campos — declare vs !
La sintaxis correcta para los campos depende del modo de decoradores de TypeScript que estés usando.
| Modo | tsconfig.json | Sintaxis de campo requerida |
|---|---|---|
| Legacy | experimentalDecorators: true | declare nombreCampo: Tipo |
| TC39 | (sin configuración) | nombreCampo!: Tipo |
Modo legacy — usa declare
Cuando experimentalDecorators: true está configurado, los campos decorados deben usar la palabra clave declare.
// Modo legacy (experimentalDecorators: true)
class User extends QModel<IUser> {
@QType(String)
declare name: string; // ✅ Solo emite metadatos — sin código inicializador JS
}Esto asegura que TypeScript emita los metadatos de tipo pero no genere código de inicialización que sobrescribiría los getters/setters de QuickModel.
❌ Incorrecto en modo legacy
// ❌ MAL: Sin `@Quick`, el inicializador '!' corre DESPUÉS del decorador
// y sobrescribe el getter/setter que QuickModel puso en el prototipo.
class User extends QModel<IUser> {
@QType(String)
name!: string; // el inicializador pone this.name = undefined → sombrea el getter ❌
}
// ✅ BIEN: @Quick() limpia los efectos secundarios del inicializador.
@Quick()
class User extends QModel<IUser> {
@QType(String)
name!: string; // Funciona — @Quick gestiona el ciclo de vida
}
// ❌ MAL: Los inicializadores de valor también se ejecutan después de la deserialización.
class User extends QModel<IUser> {
@QType(String)
status = 'Default'; // sobrescribe los datos deserializados ❌
}RECOMENDACIÓN
Siempre usa @Quick() en la clase si defines valores por defecto (name = "default") o inicialización estricta (name!). Esto garantiza un comportamiento robusto al dejar que QuickModel gestione el ciclo de vida de la propiedad.
¿Por qué falla esto?
- Los decoradores corren primero — QuickModel reemplaza la propiedad con un getter/setter en el prototipo.
- Los inicializadores corren segundo —
name!: stringoname = "x"generathis.name = void 0/this.name = "x"dentro del constructor. - Sombreado — La asignación directa en la instancia oculta el getter/setter del prototipo. La propiedad se convierte en una propiedad plana desconectada de QuickModel.
Modo TC39 — usa !
En el modo de decoradores TC39 (experimentalDecorators ausente o false), TypeScript no llama a los decoradores de campo en campos declare. Debes usar ! en cada campo decorado con @QType.
// Modo TC39 (sin experimentalDecorators)
@Quick()
class User extends QModel<IUser> {
@QType(Date)
createdAt!: Date; // ✅ Modo TC39 — usa ! (no declare)
@QType(String)
name!: string; // ✅
}TC39 sin @Quick
Incluso en modo TC39, omitir @Quick() junto con useDefineForClassFields: true (por defecto en targets ES2022+) puede hacer que el inicializador del campo sombree el getter de QuickModel. Siempre combina @Quick() con @QType() en modo TC39.
Modo TC39 — comportamiento de addInitializer
En modo TC39, @QType registra los metadatos del campo mediante el callback addInitializer provisto por el contexto del decorador TC39. Esto significa:
- Los metadatos se registran en la primera creación de instancia, no en tiempo de definición de la clase.
- Las instanciaciones posteriores están protegidas por una guardia de deduplicación interna (WeakSet), por lo que los metadatos se escriben una sola vez por clase.
- El timing es seguro:
addInitializerse ejecuta antes de queQModel.initialize()lea los metadatos.
// Modo TC39 — los metadatos se registran cuando se crea la primera instancia
@Quick()
class Post extends QModel<IPost> {
@QType(Date)
publishedAt!: Date;
@QType([String]) // array de strings
tags!: string[];
}
// ← En este punto la clase está definida pero los metadatos AÚN NO están registrados
const post = Post.create({
publishedAt: '2025-01-01',
tags: ['ts', 'decorators'],
});
// ← addInitializer ejecutado: metadatos registrados → publishedAt es Date ✅¿Cuándo usar @QType?
Aunque @Quick es preferible, @QType brilla en escenarios específicos:
1. Estrategias Mixtas
Quieres auto-mapear todo con @Quick() pero sobrescribir manualmente un campo específico.
@Quick() // Auto-detecta la mayoría de campos
class Product extends QModel<IProduct> {
declare name: string;
declare price: number;
// Sobrescribir específicamente para este campo
@QType((val) => Number(val) * 100)
declare centAmount: number;
}2. Control Manual
Prefieres la decoración explícita en cada campo para mayor visibilidad, similar a librerías como TypeORM o class-validator.
3. Uso Aislado
Estás modificando una clase heredada y solo quieres introducir propiedades de QuickModel una por una.
Opciones Avanzadas
@QType acepta un segundo argumento para control avanzado localizado, similar a las opciones globales de @Quick pero con alcance a la propiedad individual.
@QType(String, {
// Deserialización Personalizada (JSON -> Modelo)
transformer: (val) => val.trim(),
// Serialización Personalizada (Modelo -> JSON)
serializer: (val) => val + "_serialized",
// Generación de Mock Personalizada
mocker: () => "mocked_value",
// Modo de serialización binaria para campos File/Blob
fileMode: 'reference', // 'auto' | 'binary' | 'reference' | 'base64'
})
declare myField: string;fileMode — modo de serialización binaria por campo
Controla cómo se serializan los campos File, Blob, ArrayBuffer y TypedArray al llamar a serialize() o toFormData(). Úsalo para fijar la estrategia de un campo concreto sin repetir la opción en cada punto de llamada.
class UploadDto extends QModel<IUploadDto> {
declare name: string;
// Siempre emitir como base64 — consumidor de API legacy
@QType(File, { fileMode: 'base64' })
declare signature: File;
// Siempre mantener referencia de ruta — asset gestionado por CDN
@QType(File, { fileMode: 'reference' })
declare thumbnail: File;
}| Valor | Comportamiento |
|---|---|
'auto' (por defecto) | POJO de metadatos ({ name, size, type, lastModified }) |
'binary' | Igual que 'auto' — preserva File/Blob como metadatos |
'reference' | Cadena: file.name para File, '[Blob]' / '[binary]' para otros |
'base64' | Cadena base64 del contenido binario |
TIP
Una opción de llamada serialize({ fileMode }) o toFormData({ fileMode }) siempre sobreescribe el valor del decorador. El decorador establece el valor por defecto por campo cuando no se proporciona ninguna opción de llamada.