@Quick Decorator
The @Quick() decorator is the heart of QuickModel. It bridges the gap between static TypeScript types and runtime behavior, defining exactly how your data should be serialized, deserialized, and mocked.
Overview
TypeScript types (: string, : Date) are erased at runtime. Without @Quick, the library sees your properties as plain values.
@Quick restores this lost type information, allowing QuickModel to:
- Transform incoming JSON (e.g., string "2024-01-01" ->
Dateobject). - Validate data structures.
- Generate accurate mocks.
Usage
Class Decoration (Recommended)
The cleanest way to define your model's schema. Pass a Type Map object where keys match your property names.
@Quick({
name: String, // Primitive
age: 'number', // String Literal Alias
birthDate: Date, // Constructor
tags: [Set], // Collection
metadata: Map, // Collection
avatar: URL, // Web API
})
class User extends QModel<IUser> {
// ...
}Property Decoration
Useful for specific cases or if you prefer decorating fields directly.
class User extends QModel<IUser> {
@QType(Date)
declare createdAt: Date;
}Advanced Configuration (Second Argument)
You can pass a second options object to @Quick for advanced control:
@Quick({
items: [Content, Metadata] // 1. Type Mapping
}, {
// 2. Advanced Options
unknownPropertyPolicy: 'error', // Reject unknown properties
discriminators: { ... }, // Polymorphism config
transformers: { ... }, // Custom deserializers
serializers: { ... } // Custom serializers
})
class MyModel extends QModel<IMyInterface> { ... }Options Reference:
unknownPropertyPolicy: (Boolean) Iftrue, throws an error when unknown properties are present in the input.transformers: Custom deserialization logic.serializers: Custom serialization logic.mockers: Custom mock generation.discriminators: Polymorphic type handling.excludeFields: Permanently exclude fields from everyserialize()/toJSON()call.
Property Modifiers (! vs declare)
Because @Quick() wraps your class constructor, it automatically handles property initialization.
- ✅
!(Definite Assignment): Safe to use. The decorator fixes the "undefined overwrite" issue automatically. - ✅
?(Optional): Safe to use. - ✅
declare: Safe to use (and strictly required if using@QTypewithout@Quickin legacy mode).
@Quick({ name: String })
class User extends QModel<IUser> {
// All valid with @Quick
name!: string; // initialized by decorator
age?: number; // optional
declare email: string; // metadata only
}ROBUSTNESS
Recommendation: Even if you use @QType for individual fields, adding @Quick() (even empty) to the class is recommended if you use default values (prop = 123). It guarantees that QuickModel's logic runs before accidental overwrites occur.
TC39 decorator mode compatibility
@Quick is a class decorator and works identically in both legacy (experimentalDecorators: true) and TC39 (standard, TypeScript 5+) modes. No configuration change is required.
The only difference when switching to TC39 mode affects field declarations when @QType is also used:
| Mode | @QType field syntax |
|---|---|
| Legacy | declare fieldName: Type |
| TC39 | fieldName!: Type |
@Quick itself is unaffected — all three modifiers (!, ?, declare) continue to work with @Quick in both modes.
// TC39 mode — @Quick works unchanged
@Quick({ createdAt: Date })
class User extends QModel<IUser> {
declare id: number; // ✅ works in both modes
declare createdAt: Date; // ✅ works in both modes
}
// TC39 mode — @QType fields require ! instead of declare
@Quick()
class Post extends QModel<IPost> {
@QType(Date)
createdAt!: Date; // ✅ TC39: use ! for @QType-decorated fields
@QType(String)
title!: string; // ✅
}See Installation — TC39 Mode for the full tsconfig.json setup.
Advanced Options
@Quick({
items: [Content, Metadata] // 1. Type Mapping
}, {
// 2. Advanced Options
unknownPropertyPolicy: 'error',
transformers: { ... },
serializers: { ... },
mockers: { ... },
discriminators: { ... }
})
class MyModel extends QModel<IMyInterface> { ... }1. Custom Transformers (Deserialization)
Override the default deserialization logic (JSON -> Model) for specific properties.
@Quick({
status: String
}, {
transformers: {
// Force uppercase on receive
status: (val) => String(val).toUpperCase()
}
})2. Custom Serializers
Override the default serialization logic (Model -> JSON/Object) for specific properties.
@Quick({
date: Date
}, {
transformers: {
// Deserialize: seconds -> Date
date: (val) => new Date(Number(val) * 1000)
},
serializers: {
// Serialize: Date -> seconds
date: (val) => Math.floor((val as Date).getTime() / 1000)
}
})3. Custom Mockers
Define how to generate mock data for specific fields, especially when using custom transformers where automatic inference might fail.
@Quick({
sku: (val) => `ITEM-${val}`
}, {
mockers: {
// Generate valid SKU base
sku: () => faker.string.alphanumeric(8)
}
})4. Discriminators (Polymorphism)
Handle arrays containing different model types (Union Types).
@Quick({
// Declare ALL possible types
items: [Content, Metadata]
}, {
discriminators: {
// Option A: Field Name (Simple)
// Uses data.type to decide ('content' -> Content, 'metadata' -> Metadata)
items: 'type',
// Option B: Custom Function (Flexible)
items: (data) => 'text' in (data as any) ? Content : Metadata
}
})5. Unknown Property Policy
QuickModel provides three policies for handling unknown properties: 'strip' (default - removes them), 'keep' (preserves them), or 'error' (throws an error).
@Quick({ name: String }, { unknownPropertyPolicy: 'error' })
class User extends QModel<IUser> {}
// Throws Error: "Property 'unknownProp' is not allowed in strict mode"
new User({ name: 'John', unknownProp: 123 });6. excludeFields — Permanent Field Exclusion
Declare fields that should never appear in serialize() / toJSON() output, regardless of any runtime options.
@Quick(
{
id: 'string',
name: 'string',
password: 'string',
internalCache: WeakMap,
},
{
excludeFields: ['password', 'internalCache'],
}
)
class Account extends QModel<IAccount> {
declare id: string;
declare name: string;
declare password: string; // set on instance, never serialized
declare internalCache: WeakMap<object, any>;
}
const account = new Account({ id: '1', name: 'Alice', password: 's3cr3t' });
console.log(account.password); // 's3cr3t' — still accessible
console.log(account.$qSerialize()); // { id: '1', name: 'Alice' } — password excludedDeserialization is unaffected
excludeFields only removes fields from the output (serialization). The field is still populated from input data when you construct the model. This makes it safe for passwords, tokens and private caches.
Permanent vs. runtime filtering
| Approach | Where declared | Applied | Use case |
|---|---|---|---|
excludeFields | decorator 2nd arg | always | passwords, internal state |
omit | serialize({ omit: [...] }) | on that call | response shaping |
pick | serialize({ pick: [...] }) | on that call | sparse projection |
Supported Types Reference
QuickModel supports a vast array of types and string aliases.
TIP
For a complete list of all supported string aliases (including Web APIs, Binary Data, etc.), see the Aliases Reference.
Cookbook: Common Scenarios
How do I handle Arrays?
Always use bracket notation [Type].
@Quick({
// Array of Dates
dates: [Date],
// Array of Custom Models
posts: [Post],
// Array of Arrays (Matrix)
matrix: [[Number]]
})How do I use Custom Transformers?
You can pass a function to any property. This function acts as a Deserializer.
Data Flow:JSON Input -> Transformer Function -> Class Property
@Quick({
// 1. Data Cleaning
// Input: " john@example.com " -> Output: "john@example.com"
email: (val: string) => val.trim().toLowerCase(),
// 2. Calculation
// Input: "100" -> Output: 121 (Adds 21% Tax)
priceWithTax: (val: string) => Number(val) * 1.21,
// 3. Parsing Complex Data
// Input: "{\"a\":1}" (String) -> Output: { a: 1 } (Object)
config: JSON.parse
})IMCOMPLETE FLOW
Custom transformers only handle Input (Deserialization).
If you use them, QuickModel cannot automatically know how to:
- Serialize the data back to its original format (it will just output the transformed value).
- Mock the data correctly (it will generate a default value that might not satisfy your transformer).
You MUST explicitly define serializers and mockers if you need those features.
How do I math?
You can use native Math functions directly as transformers!
@Quick({
// Input: 10.567 -> Output: 11
score: Math.round,
// Input: -50 -> Output: 50
distance: Math.abs,
// Input: 5.9 -> Output: 5
level: Math.floor
})How do I nest other models?
Just pass the class constructor.
@Quick({
// Single nested model
profile: UserProfile,
// Array of models
friends: [User],
})
class User extends QModel<IUser> {
declare profile: UserProfile;
declare friends: User[];
}Supported Types & Aliases
QuickModel provides string aliases (like 'int8array', 'blob', 'urlsearchparams') for almost every supported type.
👉 View the Complete Aliases Reference for the exhaustive list of all 30+ supported aliases.