Important
Pre-release / active development
This project is not published to npm yet. The public API and installation process may change before the first stable release.
TypeScript model system with automatic type transformation and SOLID architecture.
- π Explicit Type Transformation - Transform 30+ JavaScript/TypeScript types (Date, BigInt, Symbol, RegExp, Set, Map, WeakMap, WeakSet, etc.)
- π― Simple API - Use
@Quick({})decorator to specify transformations explicitly - π‘ Type-Safe - Full TypeScript support with interface segregation
- π¦ Nested Models - Infinite nesting with automatic transformation
- β
Business Validation -
@QRuledeclarative rules +@QGroupgroup filtering. Works on any class via thequickmodel/formssubpath β noQModelrequired - π Schema Generation - Export your model as JSON Schema, Zod, OpenAPI, Mongoose, TypeScript, GraphQL, or AJV via
getSchema() - π€ MCP Server - AI assistant integration with 20 public tools, 20 internal tools, and 20 guided prompts (Claude, Copilot, etc.)
- ποΈ SOLID Architecture - Clean, maintainable, extensible code
- π Built-in Mocking - Testing utilities with @faker-js/faker
- π§ͺ Well Tested - 3300+ tests covering all features
- π·οΈ TC39 Decorator Support - Works with both legacy (
experimentalDecorators: true) and TC39 standard decorators (TypeScript 5+ default mode)
QuickModel is currently in pre-release development and is not available on npm yet.
git clone https://github.com/CartagoGit/quickmodel.git
cd quickmodel
bun install
bun test
## π¦ Installation
```bash
npm install quickmodel
# or: yarn add / pnpm add / bun addQuickModel transforms JSON data into TypeScript runtime types. All special types must be explicitly declared - no automatic detection.
import { QModel } from 'quickmodel';
interface IUser {
id: number;
name: string;
}
class User extends QModel<IUser> {
declare id: number;
declare name: string;
}
const user = new User({ id: 1, name: 'John' });import { QModel, Quick } from 'quickmodel';
interface IUser {
id: number;
name: string;
}
@Quick() // Automatically decorates all properties
class User extends QModel<IUser> {
declare id: number;
declare name: string;
}import { QModel, Quick, IQImplements } from 'quickmodel';
// Backend interface (JSON-compatible types)
interface IUser {
id: number;
createdAt: string; // ISO date string from backend
balance: string; // BigInt as string from backend
tags: string[]; // Array from backend
metadata: [string, any][]; // Map as array from backend
}
// Specify transformations explicitly
// β οΈ IMPORTANT: Use [Type] syntax for arrays: [Date], [BigInt], etc.
@Quick({
createdAt: Date, // Single Date (not array)
balance: BigInt, // Single BigInt (not array)
tags: Set, // Single Set (receives array of strings)
metadata: Map, // Single Map (receives array of tuples)
})
class User extends QModel<IUser> {
declare id: number; // No transformation needed
declare createdAt: Date; // Explicitly mapped
declare balance: bigint; // Explicitly mapped
declare tags: Set<string>; // Explicitly mapped
declare metadata: Map<string, any>; // Explicitly mapped
}
// Use with JSON data
const user = new User({
id: 1,
createdAt: '2026-01-08T10:00:00.000Z',
balance: '999999999999999',
tags: ['typescript', 'node'],
metadata: [
['key1', 'value1'],
['key2', 'value2'],
],
});
// Access transformed types
console.log(user.createdAt); // Date object
console.log(user.balance); // bigint: 999999999999999n
console.log(user.tags); // Set<string>
console.log(user.metadata); // Map<string, any>Although optional, using
IQImplementsis highly recommended to ensure your class definitions match your data contracts and transformations, preventing silent type errors.
import { QModel, Quick, IQImplements } from 'quickmodel';
// Backend interface (JSON types)
interface IUser {
id: number;
createdAt: string;
balance: string;
tags: string[];
metadata: [string, any][];
}
// Transformation interface (runtime types)
interface IUserTransform {
createdAt: Date;
balance: bigint;
tags: Set<string>;
metadata: Map<string, any>;
}
@Quick({
createdAt: Date,
balance: BigInt,
tags: Set,
metadata: Map,
})
class User
extends QModel<IUser>
implements IQImplements<IUser, IUserTransform>
{
declare id: number;
declare createdAt: Date; // TypeScript enforces this matches IUserTransform
declare balance: bigint; // TypeScript enforces this matches IUserTransform
declare tags: Set<string>; // TypeScript enforces this matches IUserTransform
declare metadata: Map<string, any>; // TypeScript enforces this matches IUserTransform
}The static create() method provides a convenient factory for your models.
Option A: Explicit declare (RECOMMENDED)
For automatic type inference of transformed properties, use declare keywords in your class. This is the standard, most robust way and works with both new User() and User.create().
import { QModel, Quick } from 'quickmodel';
interface IUser {
id: number;
createdAt: string; // Backend: ISO string
}
@Quick({ createdAt: Date })
class User extends QModel<IUser> {
declare id: number;
declare createdAt: Date; // β Explicit runtime type (REQUIRED for inference)
}
// Automatic type inference works perfectly
const user = User.create({ id: 1, createdAt: '2026-01-01' });
user.createdAt; // β
DateOption B: Using IQTransform (Alternative)
If you prefer NOT to use declare properties (e.g. to keep classes smaller), you can use the IQTransform helper to manually specify the transformed type in the create() call.
import { QModel, Quick, IQTransform } from 'quickmodel';
interface IUser {
id: number;
createdAt: string;
}
// 1. Define your runtime transformations
type UserTransforms = { createdAt: Date };
@Quick({ createdAt: Date })
class User extends QModel<IUser> {
// No declare needed
}
// 2. Pass transformation type to create()
const user = User.create<IQTransform<IUser, UserTransforms>>({
id: 1,
createdAt: '2026-01-08',
});
user.createdAt; // β
Date (inferred via IQTransform)user.email // β TypeScript: string user.createdAt // β TypeScript: Date (transformed)
**When to use `create()`:**
- β
You want concise code (no property declarations)
- β
Your interface already defines all types
- β
You prefer DRY (Don't Repeat Yourself)
**When to use `declare`:**
- β
You prefer explicit property declarations
- β
You want standard constructor usage (`new`)
- β
You need property visibility in IDE
If QuickModel saves you time or adds value to your work, consider buying me a coffee! Every contribution helps keep the project maintained and growing.
QuickModel does NOT auto-detect types from data. All special types must be explicitly declared:
// β WRONG - Date won't be transformed automatically
@Quick()
class User extends QModel<IUser> {
declare createdAt: Date; // Will stay as string!
}
// β
CORRECT - Explicit mapping required
@Quick({ createdAt: Date })
class User extends QModel<IUser> {
declare createdAt: Date; // Will transform string β Date
}QuickModel uses interface segregation (SOLID principle):
IUser- Serialization format (JSON-compatible):string,number,boolean, arraysIUserTransform- Runtime types:Date,bigint,RegExp,Set,Map
This allows type-safe serialization while maintaining clean runtime code.
Primitives:
BigInt- Large integers (from string)Date- Dates and timestamps (from ISO string)RegExp- Regular expressions (from string/object)Symbol- Symbols (using Symbol.for)Error- Error objects
Collections:
Set<T>- Unique values (from array)Map<K, V>- Key-value pairs (from array of tuples)Array<T>- Arrays with nested transformationsWeakMap<object, V>- Runtime-only cache, not serialized (GC-friendly)WeakSet<object>- Runtime-only object set, not serialized (GC-friendly)
Binary:
ArrayBuffer, TypedArrays (Int8Array, etc.),DataView
Web APIs:
URL,URLSearchParams
Always use explicit array syntax [Type] for arrays:
// β
CORRECT - Explicit array syntax
@Quick({
dates: [Date], // Date[] - array of dates
tags: [Set], // Set[] - array of sets
posts: [Post], // Post[] - array of models
matrix: [[Date]] // Date[][] - 2D array of dates
})
class Data extends QModel<IData> {
declare dates: Date[];
declare tags: Set<string>[];
declare posts: Post[];
declare matrix: Date[][];
}
// β WRONG - Ambiguous without brackets
@Quick({
dates: Date, // This means single Date, not Date[]
tags: Set, // This means single Set, not Set[]
posts: Post // This means single Post, not Post[]
})Why? Clear distinction between:
tags: Setβ Single Set receiving['a', 'b', 'c']tags: [Set]β Array of Sets receiving[['a', 'b'], ['c', 'd']]metadata: Mapβ Single Map receiving[['k1', 'v1'], ['k2', 'v2']]metadata: [Map]β Array of Maps receiving[[['k1', 'v1']], [['k2', 'v2']]]
Nesting depth is explicit:
@Quick({
posts: [Post], // Post[] - 1D array
matrix: [[Post]], // Post[][] - 2D array
cube: [[[Post]]] // Post[][][] - 3D array
})All three TypeScript property declaration styles work identically with @Quick:
// β
Style 1: declare (cleaner, no runtime code)
@Quick({ createdAt: Date, dates: [Date] })
class User extends QModel<IUser> {
declare id: number;
declare createdAt: Date;
declare dates: Date[];
}
// β
Style 2: Definite assignment (!)
@Quick({ createdAt: Date, dates: [Date] })
class User extends QModel<IUser> {
id!: number;
createdAt!: Date;
dates!: Date[];
}
// β
Style 3: Optional (?)
@Quick({ createdAt: Date, dates: [Date] })
class User extends QModel<IUser> {
id?: number;
createdAt?: Date;
dates?: Date[];
}All three styles produce identical behavior - choose based on your preference or team conventions.
TC39 mode note: When using
@QTypeas a field decorator in TC39 mode (experimentalDecoratorsabsent), decorated fields require!instead ofdeclare. Fields decorated only via the@Quickclass decorator work with all three styles in both modes. See TC39 decorator support below.
interface IPost {
tags: string[]; // Array β Set (single Set)
categories: string[][]; // Array β Set[] (array of Sets)
metadata: [string, any][]; // Tuples β Map (single Map)
}
interface IPostTransform {
tags: Set<string>;
categories: Set<string>[];
metadata: Map<string, any>;
}
// β οΈ Note the [Set] syntax for arrays of Sets
@Quick({
tags: Set, // Single Set
categories: [Set], // Array of Sets - explicit syntax!
metadata: Map, // Single Map
})
class Post
extends QModel<IPost>
implements IQImplements<IPost, IPostTransform>
{
declare id: string;
declare tags: Set<string>; // Single Set
declare categories: Set<string>[]; // Array of Sets
declare metadata: Map<string, any>; // Single Map
}
const post = new Post({
id: '1',
tags: ['typescript', 'node'], // Single Set from array
categories: [['js', 'ts'], ['node']], // Array of Sets
metadata: [['key', 'value']], // Single Map from tuples
});
// Access transformed types
console.log(post.tags); // Set { 'typescript', 'node' }
console.log(post.categories[0]); // Set { 'js', 'ts' }
console.log(post.metadata); // Map { 'key' => 'value' }@Quick({ birthDate: Date })
class Profile extends QModel<IProfile> {
declare birthDate: Date;
declare address: Address;
}
@Quick({ profile: Profile })
class User extends QModel<IUser> {
declare id: string;
declare profile: Profile;
}
const user = new User({
id: '1',
profile: {
birthDate: '1990-01-01',
address: { city: 'NYC' },
},
});Use WeakMap and WeakSet for runtime-only data that should not be serialized (garbage-collection-friendly caches, event listener sets, etc.).
interface ISession {
id: string;
}
@Quick(
{ cache: WeakMap, listeners: WeakSet },
{ excludeFields: ['cache', 'listeners'] } // never appear in toJSON()
)
class Session extends QModel<ISession> {
declare id: string;
declare cache: WeakMap<object, any>; // Auto GC when keys die
declare listeners: WeakSet<object>; // Auto GC when objects die
}
const session = new Session({ id: 'abc', cache: [], listeners: [] } as any);
console.log(session.cache); // WeakMap {} β available on instance
console.log(JSON.stringify(session)); // { "id": "abc" } β WeakMap/WeakSet excluded
β οΈ WeakMapandWeakSetare never serialized to JSON. UseMap/Setif you need persistence.
Use excludeFields in @Quick() options to permanently exclude fields from all serialization output. Unlike omit (which is per-call), excludeFields is declared once and always applied.
@Quick({}, { excludeFields: ['password', '_checksum'] })
class User extends QModel<IUser> {
declare id: number;
declare name: string;
declare password: string; // always excluded from toJSON() / serialize()
declare _checksum: string; // always excluded
}
const user = new User({
id: 1,
name: 'Alice',
password: 'secret',
_checksum: 'abc',
});
user.password; // β
'secret' β available on instance
JSON.stringify(user); // β
{ "id": 1, "name": "Alice" } β no password/checksum
user.serialize(undefined, { omit: ['name'] }); // also applies omit at runtime| Option | Scope | Declaration |
|---|---|---|
excludeFields |
Always excluded (per-model decorator) | @Quick({}, { excludeFields: [...] }) |
omit |
Excluded per-call | model.serialize(undefined, { omit: [...] }) |
pick |
Keep only these per-call | model.serialize(undefined, { pick: [...] }) |
QuickModel supports dot notation to specify transformations for nested properties without decorating the nested class:
// Option 1: Decorate nested class (recommended for reusable models)
@Quick({ price: BigInt, createdAt: Date })
class Product extends QModel<IProduct> {
price!: bigint;
createdAt!: Date;
}
@Quick({ product: Product })
class CartItem extends QModel<ICartItem> {
product!: Product; // Product already decorated
}
// Option 2: Use dot notation (useful for third-party classes or context-specific transforms)
@Quick({
product: Product,
'product.price': BigInt, // β Dot notation
'product.createdAt': Date, // β Dot notation
})
class CartItem extends QModel<ICartItem> {
product!: Product; // All transformations in one place
}π Complete Dot Notation Guide - Learn when and how to use nested transformations
QuickModel includes built-in protections for robust serialization:
- Circular Reference Protection:
toJSON()calls safely handle circular references in Objects, Arrays, Maps, and Sets by returning a{ __circular: true }marker instead of crashing. - Deep Serialization: Collections like
MapandSetare IQSerialized recursively, ensuring that nested complex types (likeBigIntorDate) are properly converted to their JSON-compatible formats. - Internal Property Protection: Properties starting with
__are automatically excluded from serialization to prevent leaking internal state. - Injection Protection: Automatic validation for URLs (blocks
javascript:) and limits on RegExp length.
β οΈ Security Notice: QuickModel strips unknown properties by default (unknownPropertyPolicy: 'strip'). For strictest validation on public APIs, use error policy:@Quick({}, { unknownPropertyPolicy: 'error' }). See SECURITY.md for full security guidelines and best practices.
QuickModel has two independent validation layers:
Checks that each property value conforms to its declared transformer type (no type mismatches, no DoS limits exceeded).
@Quick({
birthDate: Date,
tags: [Set], // Array of Sets
})
class User extends QModel<IUser> {
declare birthDate: Date;
declare tags: Set<string>[];
}
const invalidUser = new User({
birthDate: 'invalid-date',
tags: 'not-an-array',
});
const errors = invalidUser.checkIntegrity();
// [
// { isValid: false, error: "User.birthDate: Invalid Date string: invalid-date" },
// { isValid: false, error: "User.tags: Expected array for Set[], got string" }
// ]Declarative per-field rules for your own business logic. Works on any class β no need to extend QModel.
import { QRule, QGroup } from 'quickmodel';
import { qGroups, qCheckRules, qCheckRulesByGroup } from 'quickmodel/forms';
const Groups = qGroups('identity', 'security');
class SignupForm {
@QRule((val: string) => val.length >= 2, 'Name too short')
@QGroup(Groups.identity)
name = '';
@QRule((val: string) => /^[^@]+@[^@]+\.[^@]+$/.test(val), 'Invalid email')
@QGroup(Groups.identity)
email = '';
@QRule((val: string) => val.length >= 8, 'Password too short')
@QRule((val: string) => /[A-Z]/.test(val), 'Must contain uppercase')
@QGroup(Groups.security)
password = '';
}
const form = new SignupForm();
form.name = 'A';
form.email = 'alice@example.com';
form.password = 'weak';
// All rules:
const result = qCheckRules(form);
// { valid: false, errors: [{ field: 'name', message: 'Name too short', value: 'A' }, ...] }
// Only a specific group (e.g. step-by-step form):
const identityResult = qCheckRules(form, { group: Groups.identity });
// { valid: false, errors: [{ field: 'name', ... }] }
// All groups as a map β one entry per @QGroup:
const byGroup = qCheckRulesByGroup(form);
// { identity: { valid: false, errors: [...] }, security: { valid: false, errors: [...] } }Async rules (e.g. DB uniqueness checks), timeout per predicate, and serial/parallel execution modes are supported via qCheckRulesAsync.
π Form Validation guide β Full reference for the
/formssubpath:qGroups,qCheckRules,qCheckRulesAsync,qCheckRulesByGroup,qCheckRulesByGroupAsync
When using QModel subclasses, isValid() and validationReport() combine both checks in a single call:
const user = new User({ name: 'Alice', age: -1, email: 'alice@example.com' });
if (!user.isValid()) {
const report = user.validationReport();
report.integrity; // transformer-level failures
report.rules.errors; // @QRule business failures
}QuickModel can export your model's structure as different schema formats for documentation, validation, and interoperability. Available via the static getSchema() method or the instance method:
@Quick({ createdAt: Date, tags: Set })
class User extends QModel<IUser> {
declare id: number;
declare name: string;
declare createdAt: Date;
declare tags: Set<string>;
}
// Static: generate schema from class definition
const jsonSchema = User.getSchema('json'); // JSON Schema Draft-07
const openapiComp = User.getSchema('openapi'); // OpenAPI 3.0 component
const zodSchema = User.getSchema('zod'); // Zod validator string
const mongoSchema = User.getSchema('mongo'); // Mongoose SchemaTypes
const tsInterface = User.getSchema('typescript'); // TypeScript interface
const graphqlType = User.getSchema('graphql'); // GraphQL SDL type
const ajvSchema = User.getSchema('ajv'); // AJV validator schema
// Instance: same output, works on a live model
const user = new User({
id: 1,
name: 'Alice',
createdAt: '2025-01-01',
tags: ['ts'],
});
const schema = user.getSchema('json');Supported formats:
| Format | Description |
|---|---|
'json' |
JSON Schema Draft-07 |
'openapi' |
OpenAPI 3.0 schema component |
'zod' |
Zod validation schema string |
'mongo' |
Mongoose / MongoDB SchemaTypes |
'typescript' |
TypeScript interface string |
'graphql' |
GraphQL SDL type definition |
'ajv' |
AJV-compatible validator schema |
// Generate mock with defaults
const mockUser = User.mock();
// Override specific fields
const customUser = User.mock({
name: 'Custom Name',
});
// Generate array of mocks
const users = User.mock(5);Powered by @faker-js/faker.
QuickModel ships a built-in Model Context Protocol (MCP) server that gives AI assistants (Claude, GitHub Copilot, etc.) direct access to QuickModel capabilities.
bun run mcp
# or: npx quickmodel mcp{
"mcpServers": {
"quickmodel": {
"command": "npx",
"args": ["-y", "quickmodel", "mcp"]
}
}
}| Tool | Description |
|---|---|
create_model |
Generate TypeScript QModel class from properties |
validate_usage |
Validate a snippet: detects @Quick, @QRule, @QField, @QAlias, @QGroup, @QComputed; returns detectedDecorators[] and warns on @QField without @QRule |
list_transformers |
List all available type transformers |
list_validators |
List all built-in validator decorators with usage signatures and descriptions |
generate_mock |
Generate mock data for a model |
inspect_model |
Inspect a model's structure, transformers, and detected QuickModel decorators (decorators[]) |
search_docs |
Search the QuickModel documentation |
interface_to_model |
Convert a TypeScript interface to a QModel class |
export_json_schema |
Export a model as JSON Schema |
explain_error |
Explain a validation error in plain language |
simulate_transformation |
Simulate a type transformation on sample data |
json_to_model |
Generate a QModel class from a JSON object |
simulate_validation |
Simulate @QRule-style predicate validation on a data object; returns { valid, errors[], evaluated } |
get_model_schema |
Generate a model schema in any of the 7 formats (json, openapi, zod, mongo, typescript, graphql, ajv) using the real QModel.getSchema() API |
get_form_schema |
Extract form schema from @QField / @QGroup decorators using the real QModel.getFormSchema() / getFormSchemaGrouped() API |
check_integrity |
Run transformer-level integrity checks (invalid Date, BigInt range, RegExp) using instance.checkIntegrity(); returns { valid, errors[], evaluated, summary } |
simulate_rules |
Run business-logic rules via the real instance.checkRules() API; predicates have access to value and data; returns { valid, errors[], evaluated } |
simulate_async_rules |
Run async business-logic rules via instance.checkRulesAsync(); supports timeoutMs, timeoutMessage, mode (parallel/serial); returns { valid, errors[], evaluated } |
roundtrip |
Verify that serialize() β re-create β serialize() is lossless; returns { lossless, serialized, roundtrip_serialized, diff, summary } |
diff_models |
Compare two QModel class definitions (as source strings) and report added/removed fields, changed transformers, and decorator changes; pure static analysis |
| Skill | Description |
|---|---|
quickmodel_from_typescript |
Generate a QModel from a TypeScript interface (step-by-step) |
quickmodel_debug |
Diagnose and fix a QuickModel issue |
quickmodel_generate_test_data |
Create test data strategies for a model |
quickmodel_inspect_and_schema |
Inspect a model and export its schema in all formats |
quickmodel_form_validation |
Guided workflow to add @QField, @QRule and @QGroup to a model |
quickmodel_full_pipeline |
Walk the complete pipeline: create() β checkIntegrity() β checkRules() β serialize() |
quickmodel_mixin |
Extend a base class (TypeORM entity, NestJS DTO) with QModel via QModel.extends(BaseClass) |
quickmodel_alias_computed |
Explain and apply @QAlias (field name remapping) and @QComputed (getter in serialize output) |
quickmodel_migration |
Migrate legacy TypeScript classes to idiomatic QuickModel patterns |
quickmodel_async_rules |
checkRulesAsync() with timeoutMs, parallel/serial mode, NestJS context |
quickmodel_add_qgroup |
Add @QGroup field grouping to a model and use checkGroups() for group-level validation |
quickmodel_security_review |
Security audit: mass assignment, DoS limits, prototype pollution, ReDoS via check_security |
quickmodel_transformer_guide |
Choose the right transformer for a TypeScript type and validate it with simulate_transformation |
quickmodel_form_data |
fromFormData() / toFormData() / streaming (toReadableStream, fromStream, pipeStream) |
quickmodel_implement_feature |
Full TDD cycle: write test β implement β lint_check gate β typecheck gate β done |
quickmodel_fix_lint |
Step-by-step ESLint fix with lint_check + pre_commit_check gates; use after any hook failure |
quickmodel_fix_typecheck |
Step-by-step TS type fix with typecheck + pre_commit_check gates; use after typecheck fails |
quickmodel_refactor |
Safe refactor cycle: green baseline β apply β run_tests + lint_check + typecheck gates |
quickmodel_apply_solid |
Guided SOLID review: analyse each principle, propose targeted refactors, gated by all checks |
quickmodel_sync_project |
Sync all project layers: project_status health snapshot β fix failures β regenerate docs |
π MCP Documentation β Full tool reference and AI integration guide
- Single Responsibility: Each transformer handles one type
- Open/Closed: Extensible via transformer registry
- Liskov Substitution: Models work like TypeScript classes
- Interface Segregation: Separate serialization/runtime interfaces
- Dependency Inversion: Depends on abstractions
- Installation
- API Reference
- Validation (@QRule)
- Form Validation (/forms)
- Architecture
- Development Guide
- MCP Integration
Contributions welcome! See development guide.
Mario Cabrero Volarich
- GitHub: @CartagoGit
QuickModel Custom License Β© 2026 Cartago β free to use, not to sell or fork without permission. See LICENSE for full terms.
