quickmodel / QModel
Abstract Class: QModel<TInterface, TAliasMap, TSensitiveKeys>
Type Parameters
TInterface
TInterface extends IQAnyRecord
TAliasMap
TAliasMap extends Record<string, string> = Record<never, never>
TSensitiveKeys
TSensitiveKeys extends keyof TInterface = never
Accessors
$qHistory
Get Signature
get $qHistory():
IQHistoryHandle
Returns the history tracking handle for this instance.
When history is enabled ({ history: { enabled: true } }), returns an IQHistoryHandle that records one entry per operation (or per field when recordMode: 'field') with shape { method, at, changes: { field: { from, to } } }.
When history is disabled (the default), returns a shared no-op handle whose value is always [] and all methods are no-ops.
Example
@Quick({ name: String }, { history: { enabled: true } })
class Contract extends QModel<IContract> { declare name: string; }
const c = new Contract({ name: 'v1' });
c.$qPatch({ name: 'v2' });
c.$qHistory.value;
// [{ method: 'patch', at: Date, changes: { name: { from: 'v1', to: 'v2' } } }]See
- IQHistoryHandle — the handle type
- IQHistoryEntry — shape of each recorded entry
Returns
$qSignal
Get Signature
get $qSignal():
IQModelSignal<this>
The built-in reactive signal for this model instance.
Provides the minimal contract (peek(), version, subscribe()) needed to bridge with any framework's reactive system without adding any framework dependency to QuickModel.
The version counter increments automatically on every property change, even before any subscriber is registered.
The signal object is created lazily on first access and then cached — repeated accesses always return the same instance.
See
IQModelSignal — the public interface type
Examples
Angular 17+
// adapter (5 lines, no framework dependency in QuickModel)
const sig = signal(model);
model.$qSignal.subscribe(() => sig.set(model));
return sig.asReadonly();React
useSyncExternalStore(
(cb) => model.$qSignal.subscribe(cb as any),
() => model.$qSerialize(),
);Returns
IQModelSignal<this>
Constructors
Constructor
new QModel<
TInterface,TAliasMap,TSensitiveKeys>(data):QModel<TInterface,TAliasMap,TSensitiveKeys>
Constructs a new model instance from interface data or another instance. Automatically deserializes complex types (Date, BigInt, etc.) based on @QType decorators.
new Model(data) and Model.create(data) are exactly equivalent — create is a typed wrapper that adds stricter inference at the call-site. Both respect unknownPropertyPolicy and all @Quick options.
Parameters
data
IQAliasInput<TInterface, TAliasMap> | IQModelData<TInterface> | QModel<TInterface, Record<never, never>, never>
Either a plain interface object or another model instance (for cloning)
Returns
QModel<TInterface, TAliasMap, TSensitiveKeys>
See
QModel.create — preferred factory alias with stricter type inference
Example
// From interface data
const user = new User({
id: '1',
name: 'John',
createdAt: new Date() // or '2024-01-01T00:00:00.000Z'
});
// Equivalent using the factory:
const user2 = User.create({ id: '2', name: 'Jane', createdAt: new Date() });
// Clone from another instance
const clonedUser = new User(user);Methods
$qCheckFieldAsync()
$qCheckFieldAsync(
fieldName,options?):Promise<IQRulesResult>
Evaluates only the @QRule predicates attached to a single field asynchronously.
Equivalent to $qCheckRulesAsync({ field }) but available directly on the instance for ergonomic step-by-step form validation — validate one input at a time without re-running the entire model.
Supports all async options: timeoutMs, signal, globalTimeoutMs, retry, etc.
Parameters
fieldName
string
The property name whose @QRule decorators to evaluate.
options?
IQRulesAsyncOptions
Optional async execution options (same as $qCheckRulesAsync).
Returns
Promise<IQRulesResult>
Promise<IQRulesResult> — valid: true when all rules for this field pass.
Example
// Validate only the 'email' field on input change:
const result = await form.$qCheckFieldAsync('email');
if (!result.valid) {
showErrors(result.errors);
}
// With per-predicate timeout:
const result = await form.$qCheckFieldAsync('username', { timeoutMs: 500 });See
- QModel.$qCheckRulesAsync — validates all fields
- IQCheckRulesAsyncOptions.field — underlying option used internally
$qCheckIntegrity()
$qCheckIntegrity():
IQIntegrityResult[]
Checks the model instance for type integrity.
Returns
IQIntegrityResult[]
Array of integrity errors (empty if all pass)
$qCheckRules()
$qCheckRules():
IQRulesResult
Evaluates all @QRule business-logic rules synchronously.
Returns
$qCheckRulesAsync()
$qCheckRulesAsync(
options?):Promise<IQRulesResult>
Async version of $qCheckRules().
Parameters
options?
IQRulesAsyncOptions
Returns
Promise<IQRulesResult>
$qCopy()
$qCopy(
partial?):this
Returns a new instance that is an immutable copy of the current state.
Parameters
partial?
Partial<IQModelData<TInterface>>
Returns
this
$qDiff()
$qDiff(
other):Record<string, {after:unknown;before:unknown; }>
Compares this instance with another and returns a field-by-field diff.
Parameters
other
this
Returns
Record<string, { after: unknown; before: unknown; }>
$qEquals()
$qEquals(
other):boolean
Returns true when this instance is deeply equal to other.
Parameters
other
this
Returns
boolean
$qFrom()
$qFrom(
data):this
Creates a new instance of this model's class from a plain object.
Instance-level counterpart of the static QModel.deserialize method. Useful when you have a model instance and need to construct a sibling from raw data without referencing the class name explicitly.
Parameters
data
IQModelData<TInterface>
Plain object matching the model's interface
Returns
this
A new instance of the same model class
See
- QModel.deserialize — static equivalent
- QModel.$qFromJSON — same but accepts a JSON string
Example
const user = new User({ id: 1, name: 'Alice' });
const other = user.$qFrom({ id: 2, name: 'Bob' });
other instanceof User; // true$qFromJSON()
$qFromJSON(
json):this
Creates a new instance of this model's class from a JSON string.
Instance-level counterpart of the static QModel.fromJSON method. Useful when you have a model instance and need to reconstruct a sibling from a serialized JSON string without referencing the class name explicitly.
Parameters
json
string
JSON string produced by $qToJSON() or any compatible source
Returns
this
A new instance of the same model class
Throws
If json is not valid JSON
See
- QModel.fromJSON — static equivalent
- QModel.$qFrom — same but accepts a plain object
Example
const json = user.$qToJSON();
const restored = user.$qFromJSON(json);
restored instanceof User; // true
restored.createdAt instanceof Date; // true$qGetChangedFields()
$qGetChangedFields():
string[]
Returns an array of field names that have changed since construction.
Returns
string[]
$qGetChanges()
$qGetChanges():
Partial<IQSerializedInterface<TInterface>>
Returns an object containing only the fields that have changed.
Returns
Partial<IQSerializedInterface<TInterface>>
$qGetDirtyFields()
$qGetDirtyFields():
Set<string>
Returns a Set of field names that have changed since construction.
Returns
Set<string>
$qGetFormSchema()
$qGetFormSchema():
IQFormSchemaEntry[]
Returns the form schema for this instance, built from @QField decorators.
Returns
Ordered array of IQFormSchemaEntry — one per @QField-decorated property.
See
- QModel.getFormSchema — static variant (no instance required)
- QModel.$qGetFormSchemaGrouped — same schema organised by
@QGroupsections
$qGetFormSchemaGrouped()
$qGetFormSchemaGrouped():
IQFormSchemaGroup[]
Returns the form schema grouped by @QGroup sections.
Returns
Ordered array of { group, fields } entries — see IQFormSchemaGroup.
See
- QModel.$qGetFormSchema — flat (un-grouped) list
- QModel.getFormSchemaGrouped — static variant (no instance required)
$qGetInitInterface()
$qGetInitInterface():
IQSerializedInterface<TInterface>
Returns the initial state as passed to the constructor.
Returns
IQSerializedInterface<TInterface>
$qGetMetadata()
$qGetMetadata():
Map<string, {transformer:unknown;type:string; }>
Instance alias for static getMetadata. Useful for inspecting model state and configuration from an instance.
Includes Dynamic Auto-discovery: Returns both statically defined fields AND instance-specific fields found on this object (e.g. declare properties without decorators).
Returns
Map<string, { transformer: unknown; type: string; }>
See
$qGetSchema()
$qGetSchema<
T>(type):IQSchemaReturnType<T>
Generate schema with examples from instance values.
For JSON/OpenAPI schemas, adds example fields with actual values from the instance.
Type Parameters
T
T extends IQSchemaType
Parameters
type
T
Schema type to generate
Returns
IQSchemaReturnType<T>
Generated schema with examples
See
QModel.getSchema — static variant (no examples, returns raw schema)
Example
Generate JSON Schema with examples
const user = new User({
id: 42,
name: 'John Doe',
createdAt: '2024-01-01',
balance: '999999'
});
const schema = user.$qGetSchema('json');
// {
// ...
// properties: {
// id: { type: 'number', example: 42 },
// name: { type: 'string', example: 'John Doe' },
// createdAt: { type: 'string', format: 'date-time', example: '2024-01-01T00:00:00.000Z' },
// balance: { type: 'string', pattern: '^-?\\d+$', example: '999999' }
// }
// }$qHasChanges()
$qHasChanges():
boolean
Returns true if any field has changed since construction.
Returns
boolean
$qHasIntegrity()
$qHasIntegrity():
boolean
Returns true when integrity check passes (no errors).
Returns
boolean
$qIsDirty()
$qIsDirty(
field?):boolean
Checks if the model (or a specific field) has been modified since construction.
Parameters
field?
string
Returns
boolean
$qIsValid()
$qIsValid():
boolean
Returns true when both integrity and sync rules pass.
Returns
boolean
$qIsValidAsync()
$qIsValidAsync(
options?):Promise<boolean>
Async version of $qIsValid().
Parameters
options?
IQRulesAsyncOptions
Returns
Promise<boolean>
$qPatch()
$qPatch(
patch):void
Applies partial updates to the model.
Parameters
patch
Partial<IQModelData<TInterface>>
Returns
void
$qReset()
$qReset():
void
Resets the model to its initial state.
Returns
void
$qSerialize()
Call Signature
$qSerialize(
options):IQAliasedSerializedInterface<TInterface,TAliasMap>
Serializes the model instance to a plain interface object.
Complex types (Date, BigInt, Map, Set, RegExp, …) are converted to JSON-serializable primitives according to each transformer's serialize() logic. @QAlias remapping is applied after serialization.
Parameters
options
IQSerializationOptions & object
Optional pick/omit field list to filter the result.
Returns
IQAliasedSerializedInterface<TInterface, TAliasMap>
The IQAliasedSerializedInterface snapshot with all complex types converted to primitives.
See
- QModel.$qToJSON for a JSON-string shortcut
- QModel.$qToPlain for a plain snapshot that keeps runtime types
- QModel.$qToInterface for the original-input-format snapshot
Call Signature
$qSerialize(
seen,options):IQAliasedSerializedInterface<TInterface,TAliasMap>
Serializes the model instance to a plain interface object.
Complex types (Date, BigInt, Map, Set, RegExp, …) are converted to JSON-serializable primitives according to each transformer's serialize() logic. @QAlias remapping is applied after serialization.
Parameters
seen
WeakSet<object>
options
IQSerializationOptions & object
Optional pick/omit field list to filter the result.
Returns
IQAliasedSerializedInterface<TInterface, TAliasMap>
The IQAliasedSerializedInterface snapshot with all complex types converted to primitives.
See
- QModel.$qToJSON for a JSON-string shortcut
- QModel.$qToPlain for a plain snapshot that keeps runtime types
- QModel.$qToInterface for the original-input-format snapshot
Call Signature
$qSerialize(
options?):IQSafeSerializedInterface<TInterface,TAliasMap,TSensitiveKeys>
Serializes the model instance to a plain interface object.
Complex types (Date, BigInt, Map, Set, RegExp, …) are converted to JSON-serializable primitives according to each transformer's serialize() logic. @QAlias remapping is applied after serialization.
Parameters
options?
IQSerializationOptions
Optional pick/omit field list to filter the result.
Returns
IQSafeSerializedInterface<TInterface, TAliasMap, TSensitiveKeys>
The IQAliasedSerializedInterface snapshot with all complex types converted to primitives.
See
- QModel.$qToJSON for a JSON-string shortcut
- QModel.$qToPlain for a plain snapshot that keeps runtime types
- QModel.$qToInterface for the original-input-format snapshot
Call Signature
$qSerialize(
seen?,options?):IQSafeSerializedInterface<TInterface,TAliasMap,TSensitiveKeys>
Serializes the model instance to a plain interface object.
Complex types (Date, BigInt, Map, Set, RegExp, …) are converted to JSON-serializable primitives according to each transformer's serialize() logic. @QAlias remapping is applied after serialization.
Parameters
seen?
WeakSet<object>
options?
IQSerializationOptions
Optional pick/omit field list to filter the result.
Returns
IQSafeSerializedInterface<TInterface, TAliasMap, TSensitiveKeys>
The IQAliasedSerializedInterface snapshot with all complex types converted to primitives.
See
- QModel.$qToJSON for a JSON-string shortcut
- QModel.$qToPlain for a plain snapshot that keeps runtime types
- QModel.$qToInterface for the original-input-format snapshot
$qSubscribe()
$qSubscribe(
callback): () =>void
Registers an observer callback that fires every time any property on this model instance changes through the smart setter.
The callback receives an IQChange payload with the field name, the value before the change (already type-transformed), and the value after.
Observers are instance-scoped — they are NOT copied by $qCopy().
Parameters
callback
Returns
An unsubscribe function. Call it to deregister the observer.
() => void
Example
const user = new UserModel({ name: 'Alice', age: 30 });
const unsub = user.$qSubscribe(({ field, prev, next }) => {
console.log(`${field}: ${String(prev)} → ${String(next)}`);
});
user.name = 'Bob'; // logs: "name: Alice → Bob"
unsub(); // stop receiving notifications$qToFormData()
$qToFormData(
options?):Promise<FormData>
Serializes the model to FormData.
Parameters
options?
IToFormDataOptions
Returns
Promise<FormData>
See
QModel.fromFormData — inverse: parse a FormData into a model instance
$qToInterface()
$qToInterface(
seen?,depth?):TInterface
Converts the current state to interface format (preserving original input types).
Parameters
seen?
WeakSet<object>
depth?
number
Returns
TInterface
$qToJSON()
$qToJSON(
options?):string
QuickModel-specific helper that serializes the model to a JSON string.
Combines $qSerialize() and JSON.stringify() in one call, with full support for QuickModel serialization options (@QAlias remapping, @QSensitive exclusion, omit, etc.).
This is NOT the JS toJSON protocol. For JSON.stringify(model) to work correctly, see toJSON() below.
SOLID - Single Responsibility: Delegates to Serializer service.
Parameters
options?
IQSerializationOptions
Returns
string
JSON string representation of the model
See
- QModel.toJSON — JS protocol counterpart (returns plain object)
- QModel.$qSerialize — plain object form (before JSON.stringify)
- QModel.fromJSON — parse a JSON string back to a model instance
Example
const user = new User({ id: '1', name: 'John', createdAt: new Date() });
// Explicit JSON string — use this when you need a string:
const jsonStr = user.$qToJSON();
// '{"id":"1","name":"John","createdAt":"2024-01-01T00:00:00.000Z"}'
// Restore from string:
const restored = User.fromJSON(jsonStr);
// Or use JSON.stringify(model) which calls toJSON() automatically:
const jsonStr2 = JSON.stringify(user); // same result$qToPlain()
$qToPlain():
Record<string,unknown>
Returns a plain Record<string, unknown> with current runtime values (dates stay as Date, bigints as bigint, etc. — no serialization).
Returns
Record<string, unknown>
See
- QModel.$qSerialize — JSON-safe form (converts Date → string, etc.)
- QModel.$qToInterface — original-input-format snapshot
Example
const plain = user.$qToPlain();
plain.createdAt instanceof Date; // true$qToReadableStream()
Call Signature
$qToReadableStream(
options):IQMultipartStream
Creates a ReadableStream<Uint8Array> from a binary field, or a full multipart/form-data stream from all model fields.
Parameters
options
IToReadableStreamMultipart
Returns
IQMultipartStream
See
QModel.fromStream — inverse: populate a field from a readable stream
Call Signature
$qToReadableStream(
options):ReadableStream<Uint8Array<ArrayBufferLike>>
Creates a ReadableStream<Uint8Array> from a binary field, or a full multipart/form-data stream from all model fields.
Parameters
options
IToReadableStreamSingleField
Returns
ReadableStream<Uint8Array<ArrayBufferLike>>
See
QModel.fromStream — inverse: populate a field from a readable stream
$qUnsubscribe()
$qUnsubscribe(
callback):void
Removes a previously registered observer callback.
If the callback was not registered, this method is a safe no-op.
Parameters
callback
Returns
void
Example
const onChange = ({ field }: IQChange) => console.log(field);
user.$qSubscribe(onChange);
// Later:
user.$qUnsubscribe(onChange);$qValidate()
Call Signature
$qValidate(
options):Promise<IQValidationReport>
Unified validation method combining integrity checks and @QRule evaluation.
Parameters
options
IQValidateOptions & object
Returns
Promise<IQValidationReport>
Call Signature
$qValidate(
options?):IQValidationReport
Unified validation method combining integrity checks and @QRule evaluation.
Parameters
options?
IQValidateOptions & object
Returns
$qValidationReport()
$qValidationReport():
IQValidationReport
Returns the combined integrity + rules validation report.
Returns
$qValidationReportAsync()
$qValidationReportAsync(
options?):Promise<IQValidationReport>
Async version of $qValidationReport().
Parameters
options?
IQRulesAsyncOptions
Returns
Promise<IQValidationReport>
collection()
staticcollection<TInst>(this,data):QModelCollection<TInst>
Creates a QModelCollection<T> from a raw data array.
This is a static alias for QModelCollection.from(Model, data) that can be called directly on the subclass.
Type Parameters
TInst
TInst extends IQCollectionItem
Parameters
this
(data) => TInst
data
Record<string, unknown>[]
Array of raw plain-object rows for this model.
Returns
QModelCollection<TInst>
A typed QModelCollection wrapping the instantiated models.
Example
const users = UserModel.collection(rawRows);
users.$qWhere(u => u.active).$qSortBy('name').$qPaginate(1, 10);See
- QModelCollection.from — underlying factory method
- QModel.createMany — similar but returns a plain array with error reporting
configure()
staticconfigure(opts):IQClassConfig
Produces a per-class configuration descriptor to be assigned to the static readonly config property of a QModel subclass.
Class-level config overrides specific QConfig.defaults settings for that class only, without affecting sibling classes or the global configuration. If the same option is set both here and in the @Quick() second parameter, the @Quick() option takes precedence.
Parameters
opts
A subset of IQAdvancedOptions to apply to this class.
Returns
The options object (passed through; used as-is by the population service).
Example
@Quick()
class InternalDto extends QModel<IInternalDto> {
static override readonly config = QModel.configure({
unknownPropertyPolicy: 'keep',
coercionStrategy: 'strict',
});
declare id: string;
}
// InternalDto keeps unknown properties; other models are unaffected.See
- IQClassConfig — the type returned by this method
- QConfig — global configuration singleton
create()
staticcreate<TClass,TInterface,TAliasMap,TResult>(this,data):TResult
Factory method to create a model instance. Functionally identical to new ModelClass(data).
The main benefit over the constructor is stricter type inference — TypeScript will enforce that data matches TInterface exactly (no extra unknown properties at the call-site when unknownPropertyPolicy: 'error' is active).
Use declare in your class to inform TypeScript of the transformed runtime types.
Type Parameters
TClass
TClass extends QModel<any, any, never>
TInterface
TInterface = TClass extends QModel<I, any, never> ? I : never
TAliasMap
TAliasMap extends Record<string, string> = TClass extends QModel<any, A, never> ? A : Record<never, never>
TResult
TResult = TClass
Parameters
this
(data) => TClass
data
INoInfer<IQAliasInput<TInterface, TAliasMap>>
Data strictly matching the model interface
Returns
TResult
Model instance with type-safe property access
See
- QModel.constructor — equivalent low-level form
- QModel.createReadonly — immutable variant
- QModel.createMany — batch variant
Example
interface IPost {
id: number;
title: string;
createdAt: string; // Backend sends ISO string
}
@Quick({ createdAt: Date })
class Post extends QModel<IPost> {
declare id: number;
declare title: string;
declare createdAt: Date; // Runtime type after transformation
}
// Both forms are completely equivalent:
const post1 = Post.create({ id: 1, title: 'Hello', createdAt: '2026-01-10T00:00:00.000Z' });
const post2 = new Post({ id: 2, title: 'World', createdAt: '2026-01-10T00:00:00.000Z' });
post1.createdAt instanceof Date; // true
post2.createdAt instanceof Date; // truecreateMany()
Call Signature
staticcreateMany<TClass,TInterface,TAliasMap,TResult>(this,data,options?):IQCreateManyResult<TResult>
Creates multiple model instances from an array of plain objects.
All items are processed regardless of validation failures — no fail-fast. Items that fail $qIsValid() (integrity checks or @QRule violations) are collected in errors[] and excluded from instances[] by default.
Type Parameters
TClass
TClass extends QModel<any, any, never>
TInterface
TInterface = TClass extends QModel<I, any, never> ? I : never
TAliasMap
TAliasMap extends Record<string, string> = TClass extends QModel<any, A, never> ? A : Record<never, never>
TResult
TResult = TClass
Parameters
this
(data) => TClass
data
INoInfer<IQAliasInput<TInterface, TAliasMap>>[]
Array of plain objects to deserialize
options?
Optional configuration
Returns
IQCreateManyResult<TResult>
{ instances, errors } — see IQCreateManyResult
See
- QModel.create — single-instance variant
- QModel.createReadonly — single immutable instance
- QModel.deserialize — low-level single deserialization
Example
const { instances, errors } = UserModel.createMany(rawList);
// errors[n].index — position in original array
// errors[n].instance — the (invalid) model instance
// errors[n].errors — combined integrity + rule failures
// Include invalid instances in the result too:
const { instances } = UserModel.createMany(rawList, { includeErrorInstances: true });Call Signature
staticcreateMany(data,options?):IQCreateManyResult<any>
Creates multiple model instances from an array of plain objects.
All items are processed regardless of validation failures — no fail-fast. Items that fail $qIsValid() (integrity checks or @QRule violations) are collected in errors[] and excluded from instances[] by default.
Parameters
data
Record<string, unknown>[]
Array of plain objects to deserialize
options?
Optional configuration
Returns
IQCreateManyResult<any>
{ instances, errors } — see IQCreateManyResult
See
- QModel.create — single-instance variant
- QModel.createReadonly — single immutable instance
- QModel.deserialize — low-level single deserialization
Example
const { instances, errors } = UserModel.createMany(rawList);
// errors[n].index — position in original array
// errors[n].instance — the (invalid) model instance
// errors[n].errors — combined integrity + rule failures
// Include invalid instances in the result too:
const { instances } = UserModel.createMany(rawList, { includeErrorInstances: true });createReadonly()
staticcreateReadonly<T,TClass,TResult>(this,data):Readonly<TResult>
Creates a READ-ONLY (immutable) instance of the model. The instance and all nested properties will be recursively frozen.
Equivalent to Object.freeze(new Model(data)) but applied deeply — all nested objects are frozen recursively. Use this when you want compile-time and runtime guarantees that the instance cannot be mutated.
Type Parameters
T
T extends IQAnyRecord = IQAnyRecord
TClass
TClass extends object = object
TResult
TResult = TClass
Parameters
this
(...args) => TClass
data
T
Data to initialize the model
Returns
Readonly<TResult>
Deeply frozen model instance
See
QModel.create — mutable variant (new Model(data) equivalent)
Example
// createReadonly is equivalent to a deep-frozen `new User(data)`:
const user = User.createReadonly({ name: 'John' });
user.name = 'Jane'; // ❌ Throws TypeError in strict mode
// For a mutable instance use `new` or `create`:
const mutableUser = new User({ name: 'John' });
mutableUser.name = 'Jane'; // ✅ OKdeserialize()
staticdeserialize<T>(this,data):T
Creates a model instance from a plain interface object.
Deserializes a plain JavaScript object into a fully typed model instance, applying all type transformations (string → Date, string → BigInt, etc.) according to the @QType decorators defined in the model.
SOLID - Open/Closed: Allows creating instances from interfaces without modification.
Type Parameters
T
T extends QModel<IQAnyRecord, Record<never, never>, never>
The model class type
Parameters
this
(data) => T
data
IQModelData<IQAnyRecord>
Plain object matching the model's interface structure
Returns
T
A new, fully typed model instance
See
- QModel.fromJSON — deserialize from a JSON string
- QModel.deserializeJson — alias for
fromJSON - QModel.$qSerialize — serialize a model instance back to a plain object
- QModel.create — factory alias that wraps the constructor
Examples
Basic deserialization
const userData = {
id: '1',
name: 'John',
createdAt: '2024-01-01T00:00:00.000Z'
};
const user = User.deserialize(userData);
user instanceof User; // → true
user.createdAt instanceof Date; // → trueDeserialization with type transformations
const accountData = {
id: '123',
balance: '999999', // Will transform to bigint
pattern: { source: 'test', flags: 'gi' } // Will transform to RegExp
};
const account = Account.deserialize(accountData);
typeof account.balance; // → 'bigint'
account.pattern instanceof RegExp; // → truedeserializeJson()
staticdeserializeJson<T>(this,json):T
Alias for fromJSON. Creates a model instance from a JSON string.
Parses a JSON string and deserializes it into a fully typed model instance. Use this when you prefer a deserialize-style naming convention over fromJSON.
Type Parameters
T
T extends QModel<IQAnyRecord, Record<never, never>, never>
The model class type
Parameters
this
(data) => T
json
string
JSON string representation of the model
Returns
T
A new, fully typed model instance
Throws
If json is not valid JSON
See
- QModel.fromJSON — identical method with JS-convention naming
- QModel.$qToJSON — produce the JSON string to pass here
Example
const json = user.$qToJSON();
const restored = User.deserializeJson(json);
restored.createdAt instanceof Date; // trueextends()
staticextends<TRuntime>(ExternalBase): typeofQModel& (...args) =>TRuntime&QModel<IQAnyRecord,Record<never,never>,never>
Mixin factory that injects full QModel functionality into a class that must extend an external (third-party / framework) base class.
Use this when you cannot extend QModel directly because you already need to extend another class (e.g. an Angular component, a NestJS entity, a custom base).
The returned class:
- Extends
ExternalBase(prototype chain intact:instance instanceof ExternalBase === true) - Exposes all static QModel methods:
create(),createReadonly(),mock(),getMetadata(),deserialize(),deserializeJson() - Exposes all instance QModel methods:
$qSerialize(),$qToJSON(),$qToInterface(),$qIsDirty(),$qGetChangedFields(),$qReset(),$qPatch(),$qCopy() - Works with
@Quickand@QTypedecorators on the derived class - Is NOT an
instanceof QModel(different prototype chain — this is expected)
Pass a single TRuntime generic describing the expected instance type:
- For an external base with no field-type changes: pass the class directly.
- To change the type of inherited fields: compose with IQImplements.
Type Parameters
TRuntime
TRuntime extends object = object
The instance type of the resulting mixin. Use the external base class directly when there are no field overrides, or IQImplements<Base, { field: NewType }> to change the type of specific inherited fields.
Parameters
ExternalBase
(...args) => any
The external class to extend
Returns
typeof QModel & (...args) => TRuntime & QModel<IQAnyRecord, Record<never, never>, never>
A mixin base class ready to be extended
Examples
Basic usage — external base, no field overrides
class NgComponent { ngOnInit(): void {} }
@Quick({ createdAt: Date })
class UserModel extends QModel.extends<NgComponent>(NgComponent) {
declare name: string;
declare createdAt: Date;
}
instance.ngOnInit(); // ✅ TypeScript knows about NgComponent methodsOverriding inherited field types with IQImplements
@Quick({ value: Date })
class BaseModel extends QModel<any> { declare value: Date; }
@Quick({ value: BigInt })
class Derived extends QModel.extends<IQImplements<BaseModel, { value: bigint }>>(BaseModel) {
declare value: bigint; // ✅ no conflict — value: Date removed from intersection
}fromJSON()
staticfromJSON<T>(this,json):T
Creates a model instance from a JSON string.
Parses a JSON string and deserializes it into a fully typed model instance, applying all type transformations (string → Date, string → BigInt, etc.).
Naming convention: fromJSON follows the standard JS/ecosystem convention (e.g. Date.prototype.toJSON → restore with Model.fromJSON). It does NOT follow any special QuickModel protocol — for that, see $qToJSON() / serialize().
Typical round-trip:
// Serialize → string
const str = user.$qToJSON(); // explicit string
const str = JSON.stringify(user); // via JS protocol (calls toJSON())
// Restore → model
const user2 = User.fromJSON(str);Type Parameters
T
T extends QModel<IQAnyRecord, Record<never, never>, never>
The model class type
Parameters
this
(data) => T
json
string
JSON string representation of the model
Returns
T
A new, fully typed model instance
Throws
If json is not valid JSON
See
- QModel.$qToJSON — produce the JSON string to pass here
- QModel.deserializeJson — alias with
deserialize-style naming - QModel.deserialize — restore from a plain object instead of string
Example
const json = user.$qToJSON();
// or: const json = JSON.stringify(user);
const restored = User.fromJSON(json);
restored instanceof User; // → true
restored.createdAt instanceof Date; // → truefromSchema()
staticfromSchema<T>(format,schema,className?):string
Converts a formal schema back into a QuickModel class definition (TypeScript source).
This is the inverse of QModel.getSchema():
// Forward: model → schema
const json = User.getSchema('json');
// Reverse: schema → model class source
const code = QModel.fromSchema('json', json, 'User');Supported formats:
'json'— JSON Schema Draft-07 object'openapi'— OpenAPI 3.0 component schema or full document'ajv'— AJV-compatible JSON Schema (same structure as'json')'typescript'— TypeScript interface source string
Type Parameters
T
T extends IFromSchemaFormat
Parameters
format
T
Schema format to parse.
schema
IFromSchemaInput<T>
The schema to convert (type depends on format).
className?
string
Optional class name override. Falls back to schema.title or 'GeneratedModel'.
Returns
string
TypeScript source code string for a class extending QModel.
Example
import 'quickmodel/schema'; // register schema generators first
const jsonSchema = User.getSchema('json');
const code = QModel.fromSchema('json', jsonSchema, 'User');
// → complete TypeScript source for class User extends QModel<IUser>See
- SchemaToModelService — service powering this method
QFromSchemaTool— MCP tool wrapping this method
getFormSchema()
staticgetFormSchema():IQFormSchemaEntry[]
Static version of getFormSchema() — no instance required.
Returns
Ordered array of IQFormSchemaEntry — one per @QField-decorated property.
See
QModel.getFormSchemaGrouped — same schema organised by @QGroup sections
Example
const schema = ProfileModel.getFormSchema();getFormSchemaGrouped()
staticgetFormSchemaGrouped():IQFormSchemaGroup[]
Static version of getFormSchemaGrouped() — no instance required.
Returns
Ordered array of { group, fields } entries — see IQFormSchemaGroup.
See
QModel.getFormSchema — flat (un-grouped) list
getMetadata()
staticgetMetadata():Map<string, {transformer:unknown;type:string; }>
Gets metadata information for all registered qtypes in this model class. Returns a Map with field names as keys and metadata objects containing type and transformer info.
Returns
Map<string, { transformer: unknown; type: string; }>
Map of field metadata with type names and transformer information
Example
const metadata = User.getMetadata();
metadata.forEach((meta, fieldName) => {
`${fieldName}: type=${meta.type}, transformer=${meta.transformer?.name}`;
// → 'id: type=String, transformer=undefined'
// → 'createdAt: type=Date, transformer=DateTransformer'
});getSchema()
staticgetSchema<T>(type):IQSchemaReturnType<T>
Generate schema in multiple formats for validation, documentation, and integration.
Unified API - One method for all schema types:
'json'- JSON Schema Draft-07 (universal standard)'zod'- Zod validation schema (popular TypeScript validator)'mongo'- MongoDB/Mongoose schema definition'typescript'- TypeScript interface string'graphql'- GraphQL SDL type definition'openapi'- OpenAPI 3.0 schema'ajv'- AJV validator schema
SOLID - Open/Closed: Extensible to new schema types without modifying core.
Type Parameters
T
T extends IQSchemaType
Parameters
type
T
Schema type to generate
Returns
IQSchemaReturnType<T>
Generated schema in the requested format
Throws
Error if schema type is unknown
See
QModel.$qGetSchema — instance variant that enriches the schema with actual example values
Examples
Generate JSON Schema
@Quick({ createdAt: Date, balance: BigInt })
class User extends QModel<IUser> {
declare id: number;
declare name: string;
declare createdAt: Date;
declare balance: bigint;
}
const jsonSchema = User.getSchema('json');
// {
// $schema: 'http://json-schema.org/draft-07/schema#',
// type: 'object',
// title: 'User',
// properties: {
// id: { type: 'number' },
// name: { type: 'string' },
// createdAt: { type: 'string', format: 'date-time' },
// balance: { type: 'string', pattern: '^-?\\d+$' }
// },
// required: ['id', 'name', 'createdAt', 'balance']
// }Generate Zod Schema
const zodSchema = User.getSchema('zod');
// Validate data
const result = zodSchema.safeParse({
id: 1,
name: 'John',
createdAt: '2024-01-01T00:00:00.000Z',
balance: '999999999999'
});
result.success; // → trueGenerate MongoDB Schema
const mongoSchema = User.getSchema('mongo');
// {
// id: { type: Number, required: true },
// name: { type: String, required: true },
// createdAt: { type: Date, required: true },
// balance: { type: String, required: true }
// }Generate TypeScript Interface
const tsInterface = User.getSchema('typescript');
// "interface IUser {\n\tid: number;\n\tname: string;\n\tcreatedAt: Date;\n\tbalance: bigint;\n}"Generate GraphQL Type
const graphqlType = User.getSchema('graphql');
// "type User {\n\tid: Float!\n\tname: String!\n\tcreatedAt: DateTime!\n\tbalance: String!\n}"initialize()
protectedinitialize(inputData?):void
Initializes the model instance by deserializing the input data.
SOLID - Single Responsibility: Only initializes, delegates deserialization to service.
Parameters
inputData?
IQModelData<TInterface> | QModel<TInterface, Record<never, never>, never>
Returns
void
mock()
staticmock<T>(this):QMockBuilder<IQModelInstance<T>,IQModelInterface<T>>
Creates a type-safe mock builder for generating test data. Each derived class automatically infers its correct types.
Type Parameters
T
T extends (...args) => QModel<IQAnyRecord>
The model class constructor type
Parameters
this
T
Returns
QMockBuilder<IQModelInstance<T>, IQModelInterface<T>>
A QMockBuilder instance specialized for this model class
Example
const user = User.mock().random(); // returns User
const users = User.mock().array(5); // returns User[]toJSON()
Call Signature
toJSON(
options):IQAliasedSerializedInterface<TInterface,TAliasMap>
Implements the JS toJSON protocol — returns the plain serialized object.
When you call JSON.stringify(model), JavaScript internally calls model.toJSON() and serializes the returned value. This method returns the plain object produced by $qSerialize(), so JSON.stringify(model) produces the correct JSON string without any double-encoding.
Key distinction from $qToJSON():
| Method | Returns | Use case |
|---|---|---|
toJSON() | Plain object | JS protocol — JSON.stringify(model) |
$qToJSON(options?) | JSON string | Explicit string with QuickModel options |
For QuickModel serialization options (@QSensitive, omit, etc.), use $qToJSON(options) or serialize(undefined, options) directly.
Parameters
options
IQSerializationOptions & object
Returns
IQAliasedSerializedInterface<TInterface, TAliasMap>
Plain serialized object (same as $qSerialize())
See
- QModel.$qToJSON — explicit JSON string with options
- QModel.$qSerialize — same result, more control
- QModel.fromJSON — parse a JSON string back to a model instance
Example
const user = new User({ id: '1', name: 'John', createdAt: new Date() });
// JS protocol: JSON.stringify calls toJSON() automatically
const jsonStr = JSON.stringify(user);
// '{"id":"1","name":"John","createdAt":"2024-01-01T00:00:00.000Z"}'
// Direct access: toJSON() returns a plain object (sensitive fields excluded)
const plain = user.toJSON();
// { id: '1', name: 'John', createdAt: '2024-01-01T00:00:00.000Z' }
// Include sensitive fields explicitly:
const full = user.toJSON({ includeSensitive: true });Call Signature
toJSON(
options?):IQSafeSerializedInterface<TInterface,TAliasMap,TSensitiveKeys>
Implements the JS toJSON protocol — returns the plain serialized object.
When you call JSON.stringify(model), JavaScript internally calls model.toJSON() and serializes the returned value. This method returns the plain object produced by $qSerialize(), so JSON.stringify(model) produces the correct JSON string without any double-encoding.
Key distinction from $qToJSON():
| Method | Returns | Use case |
|---|---|---|
toJSON() | Plain object | JS protocol — JSON.stringify(model) |
$qToJSON(options?) | JSON string | Explicit string with QuickModel options |
For QuickModel serialization options (@QSensitive, omit, etc.), use $qToJSON(options) or serialize(undefined, options) directly.
Parameters
options?
IQSerializationOptions
Returns
IQSafeSerializedInterface<TInterface, TAliasMap, TSensitiveKeys>
Plain serialized object (same as $qSerialize())
See
- QModel.$qToJSON — explicit JSON string with options
- QModel.$qSerialize — same result, more control
- QModel.fromJSON — parse a JSON string back to a model instance
Example
const user = new User({ id: '1', name: 'John', createdAt: new Date() });
// JS protocol: JSON.stringify calls toJSON() automatically
const jsonStr = JSON.stringify(user);
// '{"id":"1","name":"John","createdAt":"2024-01-01T00:00:00.000Z"}'
// Direct access: toJSON() returns a plain object (sensitive fields excluded)
const plain = user.toJSON();
// { id: '1', name: 'John', createdAt: '2024-01-01T00:00:00.000Z' }
// Include sensitive fields explicitly:
const full = user.toJSON({ includeSensitive: true });Properties
config?
readonlystaticoptionalconfig?:Partial<IQAdvancedOptions<Record<string,unknown>>>
Optional per-class configuration override.
Assign the result of QModel.configure({...}) here to override specific QConfig.defaults settings for this class only, without affecting siblings or the global configuration.
Example
@Quick()
class InternalDto extends QModel<IInternalDto> {
static override readonly config = QModel.configure({
unknownPropertyPolicy: 'keep',
});
}See
QModel.configure — factory method that produces this value
Serialization
fromFormData()
staticfromFormData<TClass>(this,formData,options?):TClass
Creates a model instance from a FormData object.
Converts the FormData entries into a plain object and passes it to the model constructor. File and Blob entries are resolved according to the fileSource option (default: 'auto').
Auto-detection tree (default fileSource: 'auto'):
Fileinstance → kept asFileBlobinstance → kept asBlobstring "data:..."→ decoded base64 →Blobstring "https://..."→ string (URL reference)- Any other string → string as-is
Type Parameters
TClass
TClass extends QModel<IQAnyRecord, Record<never, never>, never>
Parameters
this
(data) => TClass
formData
FormData
Source FormData
options?
IFromFormDataOptions
Conversion options (fileSource, per-field overrides)
Returns
TClass
Model instance with type-safe property access
See
QModel.$qToFormData — inverse: serialize a model instance back to FormData
Example
// Default auto-detection
const dto = UploadDto.fromFormData(formData);
// Force all binary fields to be treated as references
const dto = UploadDto.fromFormData(formData, { fileSource: 'reference' });
// Per-field overrides
const dto = UploadDto.fromFormData(formData, {
fields: { avatar: 'binary', document: 'reference' },
});fromStream()
staticfromStream<TClass>(this,stream,options):Promise<TClass>
Creates a model instance with a binary field populated from a ReadableStream<Uint8Array>.
All stream chunks are accumulated into a single Blob and assigned to the specified field. Use when you need the full binary data in memory (e.g. after receiving a small upload).
Type Parameters
TClass
TClass extends QModel<IQAnyRecord, Record<never, never>, never>
Parameters
this
(data) => TClass
stream
ReadableStream<Uint8Array<ArrayBufferLike>>
Source ReadableStream<Uint8Array>
options
IFromStreamOptions
Must include field (name of the target binary field)
Returns
Promise<TClass>
Promise<TClass> — model instance with the binary field set
Throws
RangeError if maxBytes is set and the stream exceeds it
See
- QModel.$qToReadableStream — inverse: stream out from a model field
- QModel.pipeStream — zero-copy alternative when accumulation is not needed
Example
const dto = await UploadDto.fromStream(req.body, { field: 'video', maxBytes: 500 * 1024 * 1024 });
// dto.video → Blob with all stream chunksfromURL()
staticfromURL<TClass>(this,params):TClass
Creates a model instance from a URLSearchParams object.
Reads each key from params using the correct strategy:
- Fields declared as array specs in
@Quick({ field: [Type] })→params.getAll(key) - All other fields →
params.get(key)
After extraction, the plain object is passed to the normal constructor so all registered type coercions (Number, Date, [Number], custom transformers…) are applied automatically.
Type Parameters
TClass
TClass extends QModel<IQAnyRecord, Record<never, never>, never>
Parameters
this
(data) => TClass
params
URLSearchParams
Source URLSearchParams (e.g. new URL(req.url).searchParams, request.nextUrl.searchParams, or new URLSearchParams(window.location.search))
Returns
TClass
Model instance with type-safe, coerced property access
See
- QModel.fromFormData — equivalent helper for
FormData - Quick —
@Quick({ field: [Type] })marks array fields
Example
// URL: /search?role=admin&age=25&tags=read&tags=write
@Quick({ age: Number, tags: [String] })
class SearchDto extends QModel<ISearch> {
declare role: string;
declare age: number;
declare tags: string[];
}
// Next.js App Router
const dto = SearchDto.fromURL(request.nextUrl.searchParams);
dto.age; // 25 (number, not '25')
dto.tags; // ['read', 'write']
// Hono / Express
const dto = SearchDto.fromURL(new URL(req.url).searchParams);pipeStream()
staticpipeStream(src,dst,options?):Promise<void>
Pipes all bytes from a ReadableStream<Uint8Array> to a WritableStream<Uint8Array> without accumulating anything in memory.
QModel acts as a zero-copy conduit. Use for large files or server-side proxy uploads.
Parameters
src
ReadableStream<Uint8Array<ArrayBufferLike>>
Source readable stream
dst
WritableStream<Uint8Array<ArrayBufferLike>>
Destination writable stream
options?
IPipeStreamOptions
Optional limits and progress callback
Returns
Promise<void>
Promise<void> — resolves when all bytes have been piped
Throws
RangeError if maxBytes is set and the stream exceeds it
See
- QModel.$qToReadableStream — create a readable stream from a model field
- QModel.fromStream — accumulate a stream into a model field
Example
await UploadDto.pipeStream(req.body, s3UploadStream, { maxBytes: 500 * 1024 * 1024 });