Skip to content

@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:

  1. Transform incoming JSON (e.g., string "2024-01-01" -> Date object).
  2. Validate data structures.
  3. Generate accurate mocks.

Usage ​

The cleanest way to define your model's schema. Pass a Type Map object where keys match your property names.

typescript
@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.

typescript
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:

typescript
@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: ​

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 @QType without @Quick in legacy mode).
typescript
@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
Legacydeclare fieldName: Type
TC39fieldName!: Type

@Quick itself is unaffected — all three modifiers (!, ?, declare) continue to work with @Quick in both modes.

typescript
// 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 ​

typescript
@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.

typescript
@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.

typescript
@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.

typescript
@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).

typescript
@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).

typescript
@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.

typescript
@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 excluded

Deserialization 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

ApproachWhere declaredAppliedUse case
excludeFieldsdecorator 2nd argalwayspasswords, internal state
omitserialize({ omit: [...] })on that callresponse shaping
pickserialize({ pick: [...] })on that callsparse 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].

typescript
@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

typescript
@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:

  1. Serialize the data back to its original format (it will just output the transformed value).
  2. 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!

typescript
@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.

typescript
@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.