QModel
La clase QModel es el corazón de la librería. Es una clase base abstracta que proporciona todas las capacidades de serialización, deserialización y mocking a tus modelos.
Uso
Extiende QModel pasando tu interfaz de datos como tipo genérico:
import { QModel } from 'quickmodel';
interface IUser {
id: number;
name: string;
}
class User extends QModel<IUser> {
// Tus propiedades de clase
}Métodos de Instanciación
QuickModel ofrece varias formas de crear instancias, dependiendo de tus necesidades.
1. Constructor (Recomendado)
La forma más común y estándar.
const user = new User({
id: 1,
name: 'Juan',
});Seguridad de Tipos (Recomendado)
Para una verificación de tipos estricta entre tu interfaz y tu clase, es altamente rrecomendable usar IQImplements. Este ayudante asegura que las propiedades de tu clase coincidan con la definición de tu interfaz. Aprende más.
class User extends QModel<IUser> implements IQImplements<IUser, IUserTransform> { ... }2. Método Factoría (create)
Útil para patrones de programación funcional o mapeo de arrays.
const user = User.create({
id: 1,
name: 'Juan',
});
// Ejemplo de mapeo
const users = dataArray.map(User.create);3. Desde Cadena JSON
Parsea automáticamente JSON y luego transforma los tipos.
const json = '{"id":1,"name":"Juan","createdAt":"2024-01-01"}';
const user = User.fromJSON(json);4. Clonación
Crea una copia profunda de una instancia existente.
const clone = user.$qCopy();5. Instancia de Solo Lectura (Readonly)
Crea una instancia profundamente congelada (inmutable). Cualquier intento de modificarla lanzará un error en modo estricto.
const readonlyUser = User.createReadonly({
id: 1,
name: 'Juan',
});
// readonlyUser.name = 'Ana'; // ¡Error!6. Creación masiva (createMany)
Crea múltiples instancias a partir de un array. Todos los items se procesan aunque algunos fallen — los que no superan isValid() van a errors[] y quedan excluidos de instances[] por defecto.
const rawList = [
{ name: 'Alice', age: 30 },
{ name: 'Menor', age: 10 }, // falla @QRule
{ name: 'Bob', age: 25 },
];
const { instances, errors } = UsuarioModel.createMany(rawList);
console.log(instances.length); // 2 (Alice + Bob)
console.log(errors.length); // 1
console.log(errors[0].index); // 1
console.log(errors[0].instance.name); // 'Menor'
console.log(errors[0].errors); // [{ field: 'age', message: 'Debe ser mayor de edad', value: 10 }]Para incluir las instancias inválidas también en el resultado:
const { instances, errors } = UsuarioModel.createMany(rawList, {
includeErrorInstances: true,
});
// instances.length === 3 — las tres, incluyendo Menor
// errors.length === 1 — la lista de errores sigue informada| Opción | Tipo | Por defecto | Descripción |
|---|---|---|---|
includeErrorInstances | boolean | false | Incluir instancias inválidas también en instances[] |
Ciclo de Vida
Cuando se instancia un modelo, sucede lo siguiente:
- Constructor Llamado: Se reciben los datos.
- Inicialización: Se llama internamente a
this.initialize(). - Deserialización: Se procesan los datos, se aplican transformadores (
string->Date). - Hidratación: Se asignan las propiedades a la instancia.
- Validación: Se ejecutan los pasos opcionales de validación.
Métodos Principales
serialize()
Convierte el modelo de vuelta a un objeto JavaScript plano, revirtiendo las transformaciones (e.g., Date -> ISO string).
const plain = user.$qSerialize();toJSON()
Implementa el protocolo JS toJSON. Devuelve un objeto plano (igual que $qSerialize()) para que JSON.stringify(model) funcione correctamente. Nota: llamar directamente a user.toJSON() también devuelve un objeto plano — usa user.$qToJSON() si necesitas una cadena JSON.
// Protocolo JS — funciona con JSON.stringify:
const jsonStr = JSON.stringify(user);
// '{"id": 1, "name": "John", ... }'
// Llamada directa devuelve objeto plano:
const plain = user.toJSON();
// { id: 1, name: 'John', ... }
// Cadena JSON explícita:
const jsonString = user.$qToJSON();
// '{"id": 1, "name": "John", ... }'toInterface()
Devuelve los datos en su formato original (definido por la interfaz), preservando los tipos originales.
// Si User se creó con { createdAt: '2024-01-01' }
const rawData = user.$qToInterface();
// rawData.createdAt es '2024-01-01' (string)static getMetadata()
Devuelve un mapa de todas las propiedades decoradas y su configuración. Útil para formularios dinámicos o herramientas de inspección.
const meta = User.getMetadata();
console.log(meta.get('createdAt').type); // 'Date'static deserialize(data)
Método de bajo nivel para hidratar un objeto plano en una instancia. Equivalente a new Model(data).
const user = User.deserialize(plainObject);validationReport()
Devuelve un informe detallado de todos los fallos de validación, incluidos los errores de integridad de transformers y las violaciones de reglas de negocio @QRule.
const report = user.$qValidationReport();
if (!report.valid) {
console.error(report.errors);
}isValid()
Devuelve true si el modelo supera todas las comprobaciones de integridad y las reglas de negocio. Combina checkIntegrity() y checkRules() en una sola llamada.
if (!user.$qIsValid()) {
console.error('El modelo no es válido');
}Gestión de Estado y Control de Cambios
QModel incluye herramientas integradas potentes para rastrear cambios, comparar estados y gestionar actualizaciones.
hasChanges() / isDirty(field?)
Sin argumentos, devuelve true si algún campo ha cambiado desde que se instanció.
Con un nombre de campo, devuelve true si ese campo concreto está modificado.
const user = new User({ name: 'John', age: 30 });
console.log(user.$qIsDirty()); // false
console.log(user.$qIsDirty('name')); // false
user.name = 'Jane';
console.log(user.$qIsDirty()); // true — algo cambió
console.log(user.$qIsDirty('name')); // true — 'name' cambió
console.log(user.$qIsDirty('age')); // false — 'age' NO cambióTIP
hasChanges() e isDirty() (sin argumento) son equivalentes. isDirty(field) es la nueva variante por campo.
getChanges()
Devuelve un objeto parcial que contiene solo los campos que han cambiado. Perfecto para generar payloads PATCH.
const user = new User({ id: 1, name: 'John', age: 30 });
user.age = 31;
const changes = user.$qGetChanges();
// Resultado: { age: 31 }$qGetChangedFields()
Devuelve un array con los nombres de las propiedades modificadas.
const fields = user.$qGetChangedFields();
// Resultado: ['age']$qReset()
Revierte la instancia del modelo a su estado inicial (los datos proporcionados al constructor).
user.name = 'Modificado';
user.$qReset();
console.log(user.name); // 'John' (Valor original)patch(data)
Aplica actualizaciones parciales al modelo. Útil para procesar respuestas de API o actualizaciones parciales de formularios.
user.$qPatch({ age: 32 });
// Solo se actualiza 'age', el resto permanece igual$qGetInitInterface()
Devuelve los datos originales usados para crear la instancia, en su formato original (preservando strings en lugar de Dates, etc.).
// Entrada inicial: { createdAt: '2024-01-01' }
const original = user.$qGetInitInterface();
console.log(original.createdAt); // '2024-01-01' (String)copy(partial?)
Crea una nueva instancia (inmutable) fusionando el estado actual con los datos parciales opcionales. La instancia original nunca se modifica. Sin argumentos realiza una copia profunda (deep copy) del estado actual.
const user = new User({ id: 1, name: 'John', age: 30 });
const updated = user.$qCopy({ age: 31 });
console.log(user.age); // 30 — original intacto
console.log(updated.age); // 31 — nueva instancia
console.log(updated.name); // 'John' — preservado
// La nueva instancia tiene su propio tracking de cambios
updated.name = 'Jane';
console.log(updated.$qIsDirty()); // true
console.log(updated.$qIsDirty('age')); // false — 31 es su baseline
console.log(updated.$qIsDirty('name')); // true — cambió tras el merge
// Sin argumentos: copia profunda completa
const clone = user.$qCopy();
console.log(clone.age); // 30 — misma copia, independienteNOTE
copy() devuelve una instancia completamente independiente con su propio tracking de cambios. El estado copiado se convierte en el nuevo baseline — isDirty() es false inmediatamente tras copy(), y reset() revierte al estado copiado (no al original).
Mocking
Cada QModel tiene un generador de mocks estático incorporado.
// Generar una instancia
const fakeUser = User.mock().random();
// Generar array de 10 instancias
const fakeUsers = User.mock().array(10);
// Generar con sobrescrituras específicas
const admin = User.mock().random({ role: 'admin' });TIP
Para más detalles sobre las potentes funciones de generación de mocks, consulta la Guía de Mocks.
Rendimiento
⚡ Comparativa de rendimiento
Pasa el ratón sobre las barras para ver detalles
5 ciclos de 500 objetos — importación masiva tipada desde JSON crudo. Plain JS: spread de objeto plano — el más rápido pero sin tipado ni validación. class-transformer: plainToInstance array — mapea a clase pero sin checks de integridad, BigInt no soportado. Zod: safeParse por item — valida pero retorna objetos planos, no instancias tipadas con métodos. QuickModel createMany(): instancias tipadas + integridad + reglas de negocio, separa válidos/inválidos automáticamente.
No aplica en este escenario: Immer