Skip to content

Installation ​

Prerequisites ​

Before installing QuickModel, ensure you have:

  • Node.js >= 18.0.0
  • TypeScript >= 3.4.0
  • A package manager: npm, yarn, pnpm, or bun
TypeScript version compatibility

QuickModel avoids TypeScript built-ins that would raise the minimum version. The table below lists every version-sensitive feature and how it is handled:

FeatureWhere usedIntroduced inHow we handle it
Mapped types, indexed access (T[number])IQGroupsMap<T>, qGroups() return typeTS 2.1Native
Conditional types (INoInfer<T>)polyfill for QModel.create()TS 2.8Native (our own polyfill, no built-in used)
readonly T[] shorthand in genericsqGroups() overloadTS 3.4Native (sets the floor)
ClassFieldDecoratorContext@QType / @QRule TC39 overloadTS 5.0Polyfilled as IClassFieldDecoratorCtx (internal)
NoInfer<T> built-inQModel.create() / QModel.createMany()TS 5.4Polyfilled as INoInfer<T> (internal)
const type parametersqGroups5() / compat/ts5/forms entry pointTS 5.0Isolated in separate /compat/ts5/forms entry point

The main package (quickmodel) works with TypeScript 3.4+. The /compat/ts5/forms entry point requires TypeScript 5.0+ due to const type parameters.

Install QuickModel ​

Choose your preferred package manager:

bash
npm install quickmodel

TypeScript Configuration ​

QuickModel supports two decorator modes. Pick whichever matches your project:

Mode 1 — Legacy decorators (classic, widest tooling compatibility) ​

json
{
	"compilerOptions": {
		"experimentalDecorators": true,
		"target": "ES2022",
		"lib": ["ES2022"],
		"module": "ESNext",
		"moduleResolution": "node"
	}
}

Mode 2 — TC39 standard decorators (TypeScript 5+, no legacy flags) ​

json
{
	"compilerOptions": {
		"target": "ES2022",
		"lib": ["ES2022"],
		"module": "ESNext",
		"moduleResolution": "node"
	}
}

TC39 mode — what changes?

When experimentalDecorators is absent or false, TypeScript compiles decorators using the TC39 Stage-3 spec. QuickModel handles this transparently:

  • @Quick — unchanged. Class decorators still receive the class constructor as their first argument.
  • @QType — metadata is now registered inside an addInitializer callback that fires on first instance creation instead of at class-definition time. For practical purposes this is invisible — the metadata is always ready before QModel.initialize() reads it.
  • @QRule — the predicate parameter type is automatically inferred from the field type. No manual annotation needed:
typescript
// TC39 mode — val inferred as Date automatically ✅
@QRule((val) => val > new Date('2000-01-01'), 'Must be after 2000')
createdAt!: Date;

// Legacy mode — annotation required
@QRule((val: Date) => val > new Date('2000-01-01'), 'Must be after 2000')
declare createdAt: Date;

Field syntax changes with TC39

In TC39 mode, field decorators (@QType, @QRule) cannot be applied to declare fields — use ! (definite assignment assertion) instead:

typescript
// ✅ TC39 mode
@QType(Date)
createdAt!: Date;

// ✅ Legacy mode (experimentalDecorators: true)
@QType(Date)
declare createdAt: Date;

Undecorated fields that only need type-tracking (no @QType / @QRule) can still use declare in both modes.

Required Compiler Options ​

  • experimentalDecorators: true (legacy mode only) — enables legacy PropertyDecorator syntax. Omit it (or set to false) to use TC39 mode.
  • strict: true - Enable all TypeScript strict type-checking options (unrelated to QuickModel's unknownPropertyPolicy)
  • target: "ES2020" - Modern JavaScript features
  • module: "ESNext" - Modern module system

emitDecoratorMetadata is NOT required

Unlike many other libraries, QuickModel does NOT require "emitDecoratorMetadata": true.

QuickModel relies on explicit type mapping (e.g., @Quick({ date: Date })) as the source of truth. This ensures robust behavior regardless of your compiler settings or build tool (esbuild, swc, babel, etc.).

Specific Case: When using @QType() without arguments, QuickModel attempts to read metadata. If emitDecoratorMetadata is disabled, it simply falls back to treating the value "as-is" (no transformation). This is perfectly fine for primitives but means you must use explicit mapping for special types (Date, BigInt, etc.).

Import Structure ​

QuickModel uses a modular import structure to keep your project clean:

  • Core: Main classes and decorators (QModel, Quick, QType)
    typescript
    import { QModel, Quick } from 'quickmodel';
  • Type Definitions: Interfaces and helper types (IQSerializedInterface, IQSpec, etc.)
    typescript
    import type { IQSerializedInterface } from 'quickmodel/types';
  • Advanced Utilities: Runtime utilities for power users (QMockGenerator)
    typescript
    import { QMockGenerator } from 'quickmodel/advanced';

CLI Scaffolding ​

After installing, you can scaffold boilerplate with the built-in CLI:

bash
# Generate a QModel class with typed fields
npx quickmodel generate model User --fields "id:number,name:string,createdAt:Date"

# Generate a custom transformer skeleton
npx quickmodel generate transformer Decimal

# Generate integration boilerplate (prisma | drizzle | zod)
npx quickmodel generate integration prisma

TIP

With Bun you can use bunx quickmodel generate ... instead of npx.

Verify Installation ​

Create a simple test file to verify everything works:

typescript
import { QModel, Quick } from 'quickmodel';

interface IUser {
	id: number;
	name: string;
	createdAt: string;
}

@Quick({ createdAt: Date })
class User extends QModel<IUser> {
	declare id: number;
	declare name: string;
	declare createdAt: Date;
}

const user = new User({
	id: 1,
	name: 'John Doe',
	createdAt: '2026-01-10T00:00:00.000Z',
});

console.log(user.createdAt instanceof Date); // true ✅

If this runs without errors and prints true, you're all set!

Next Steps ​