QModel
The QModel class is the heart of the library. It's an abstract base class that provides all the serialization, deserialization, and mocking capabilities to your models.
Usage
Extend QModel and pass your data interface as a generic type:
import { QModel } from 'quickmodel';
interface IUser {
id: number;
name: string;
}
class User extends QModel<IUser> {
// Your class properties
}Instantiation Methods
QuickModel offers several ways to create instances, depending on your needs.
1. Constructor (Recommended)
The most common and standard way.
const user = new User({
id: 1,
name: 'John',
});Type Safety (Recommended)
For strict type checking between your interface and your class, it is highly recommended to use IQImplements. This helper ensures your class properties match your interface definition. Learn more.
class User extends QModel<IUser> implements IQImplements<IUser, IUserTransform> { ... }2. Factory Method (create)
Useful for functional programming patterns or when mapping arrays.
const user = User.create({
id: 1,
name: 'John',
});
// Mapping example
const users = dataArray.map(User.create);3. From JSON String
Automatically parses JSON string and then transforms types.
const json = '{"id":1,"name":"John","createdAt":"2024-01-01"}';
const user = User.fromJSON(json);4. Cloning
Creates a deep copy of an existing instance.
const clone = user.$qCopy();5. Readonly Instance
Creates a deeply frozen (immutable) instance. Any attempt to modify it will throw an error in strict mode.
const readonlyUser = User.createReadonly({
id: 1,
name: 'John',
});
// readonlyUser.name = 'Jane'; // Error!6. Bulk Creation (createMany)
Creates multiple instances from an array. All items are processed regardless of individual failures — items that fail isValid() are collected in errors[] and excluded from instances[] by default.
const rawList = [
{ name: 'Alice', age: 30 },
{ name: 'Minor', age: 10 }, // fails @QRule
{ name: 'Bob', age: 25 },
];
const { instances, errors } = UserModel.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); // 'Minor'
console.log(errors[0].errors); // [{ field: 'age', message: 'Must be adult', value: 10 }]To include invalid instances in the result too:
const { instances, errors } = UserModel.createMany(rawList, {
includeErrorInstances: true,
});
// instances.length === 3 — all three, including Minor
// errors.length === 1 — error list still populated| Option | Type | Default | Description |
|---|---|---|---|
includeErrorInstances | boolean | false | Also put invalid instances in instances[] |
Lifecycle
When a model is instantiated, the following happens:
- Constructor Called: Data is received.
- Initialization:
this.initialize()is called internally. - Deserialization: Data is processed, transformers are applied (
string->Date). - Hydration: Properties are assigned to the instance.
- Validation: Optional validation steps are run.
Core Methods
serialize()
Converts the model back to a plain JavaScript object, reversing transformations (e.g., Date -> ISO string).
const plain = user.$qSerialize();toJSON()
Implements the JS toJSON protocol. Returns a plain object (same as $qSerialize()) so that JSON.stringify(model) works correctly. Note: calling user.toJSON() directly also returns a plain object — use user.$qToJSON() if you need a JSON string.
// JS protocol — works with JSON.stringify:
const jsonStr = JSON.stringify(user);
// '{ "id": 1, "name": "John", ... }'
// Direct call returns a plain object:
const plain = user.toJSON();
// { id: 1, name: 'John', ... }
// Explicit JSON string:
const jsonString = user.$qToJSON();
// '{ "id": 1, "name": "John", ... }'toInterface()
Returns the data in its original raw format (as defined by the interface), preserving original types (e.g., keeping strings as strings). Useful for forms or checking initial state.
// If User was created with { createdAt: '2024-01-01' }
const rawData = user.$qToInterface();
// rawData.createdAt is '2024-01-01' (string)static getMetadata()
Returns a map of all decorated properties and their configuration. Useful for building dynamic forms or inspection tools.
const meta = User.getMetadata();
console.log(meta.get('createdAt').type); // 'Date'static deserialize(data)
Low-level method to hydrate a plain object into a model instance. Equivalent to new Model(data).
const user = User.deserialize(plainObject);validationReport()
Returns a detailed report of all validation failures, including transformer integrity checks and @QRule business rule violations.
const report = user.$qValidationReport();
if (!report.valid) {
console.error(report.errors);
}isValid()
Returns true if the model passes all integrity checks and business rules. Combines checkIntegrity() and checkRules() in a single call.
if (!user.$qIsValid()) {
console.error('Model is not valid');
}State Management & Change Tracking
QModel includes powerful built-in tools to track changes, compare states, and manage updates.
hasChanges() / isDirty(field?)
Without arguments, returns true if any field has changed since instantiation.
With a field name, returns true if that specific field is dirty.
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 — something changed
console.log(user.$qIsDirty('name')); // true — 'name' changed
console.log(user.$qIsDirty('age')); // false — 'age' did NOT changeTIP
hasChanges() and isDirty() (no argument) are equivalent. isDirty(field) is the new per-field variant.
getChanges()
Returns a partial object containing only the fields that have changed. Perfect for generating PATCH payloads.
const user = new User({ id: 1, name: 'John', age: 30 });
user.age = 31;
const changes = user.$qGetChanges();
// Result: { age: 31 }$qGetChangedFields()
Returns an array of the names of modified properties.
const fields = user.$qGetChangedFields();
// Result: ['age']$qReset()
Reverts the model instance back to its initial state (the data provided to the constructor).
user.name = 'Modified';
user.$qReset();
console.log(user.name); // 'John' (Original value)patch(data)
Applies partial updates to the model. Useful for processing API responses or partial form updates.
user.$qPatch({ age: 32 });
// Only 'age' is updated, other fields remain unchanged$qGetInitInterface()
Returns the original data used to create the instance, in its original format (preserving strings instead of Dates, etc.).
// Initial input: { createdAt: '2024-01-01' }
const original = user.$qGetInitInterface();
console.log(original.createdAt); // '2024-01-01' (String)copy(partial?)
Creates a new independent instance. Optionally merges the current state with partial data. The original instance is never modified.
const user = new User({ id: 1, name: 'John', age: 30 });
// Deep copy without changes
const clone = user.$qCopy();
// Copy with partial overrides
const updated = user.$qCopy({ age: 31 });
console.log(user.age); // 30 — original untouched
console.log(updated.age); // 31 — new instance
console.log(updated.name); // 'John' — preserved
// The new instance has its own change tracking
updated.name = 'Jane';
console.log(updated.$qIsDirty()); // true
console.log(updated.$qIsDirty('age')); // false — 31 is its baseline
console.log(updated.$qIsDirty('name')); // true — changed after mergeNOTE
copy() returns a fully independent instance with its own change tracking. The copied state becomes the new baseline — isDirty() is false immediately after copy(), and reset() reverts to the copied state (not the original).
Mocking
Every QModel has a built-in static mock generator.
// Generate one instance
const fakeUser = User.mock().random();
// Generate array of 10 instances
const fakeUsers = User.mock().array(10);
// Generate with specific overrides
const admin = User.mock().random({ role: 'admin' });TIP
For more details on powerful mock generation features, check out the Mocks Guide.
Performance
⚡ Performance Benchmark
Hover the bars to see details
10 cycles of 1k objects — throughput validation. class-transformer excluded (not a validator — needs class-validator separately). Plain JS excluded (no validation). arktype is fastest native TS validator. joi and vest are focused on form/business logic rules.
Not applicable in this scenario: Immer