Contribution Guide
Complete guide for developers contributing to the QuickModel project.
📋 Table of Contents
🚀 Environment Setup
Requirements
- Bun >= 1.0 (runtime and package manager)
- TypeScript >= 5.7
- Node.js >= 20 (for documentation tools)
Installation
# Clone repository
git clone https://github.com/CartagoGit/quickmodel.git
cd quickmodel
# Install dependencies
bun install
# Verify installation
bun test
bun run build🏗️ Architecture
QuickModel follows SOLID principles with a clear and maintainable architecture.
Project Structure
src/
├── index.ts # Public API exports
├── advanced.ts # Advanced API exports
├── forms.ts # Forms submodule exports
├── matchers.ts # Matchers submodule exports
├── types.ts # Type exports
├── utils.ts # Utility exports
├── mcp-cli.ts # MCP CLI entry point
├── compat/
│ └── ts5/
│ └── forms.ts # TypeScript 5 compatibility
├── core/
│ ├── models/
│ │ └── quick.model.ts # QModel base class
│ ├── decorators/
│ │ ├── quick.decorator.ts # @Quick() - Bulk decorator
│ │ ├── qtype.decorator.ts # @QType() - Per-property decorator
│ │ ├── qrule.decorator.ts # @QRule() - Validation decorator
│ │ ├── qfield.decorator.ts # @QField() - Form field decorator
│ │ ├── qgroup.decorator.ts # @QGroup() - Form group decorator
│ │ ├── qalias.decorator.ts # @QAlias() - Alias decorator
│ │ ├── qcomputed.decorator.ts # @QComputed() - Computed property
│ │ └── validators.ts # Built-in validators (14)
│ ├── services/
│ │ ├── serializer.service.ts
│ │ ├── deserializer.service.ts
│ │ ├── mock-builder.service.ts
│ │ ├── mock-generator.service.ts # faker loaded lazily (createRequire)
│ │ ├── schema-generators.service.ts # JSON, Mongo, TS, GraphQL, OpenAPI, AJV
│ │ ├── zod-schema-generator.service.ts # Zod — loaded lazily, NOT in static import graph
│ │ ├── integrity.service.ts
│ │ ├── to-interface.service.ts
│ │ ├── security-inspector.service.ts
│ │ └── ...
│ ├── registry/
│ │ └── transformer.registry.ts
│ ├── bases/
│ │ └── base-transformer.ts
│ ├── helpers/
│ │ ├── q-check-rules.ts
│ │ ├── q-check-rules-async.ts
│ │ └── ...
│ ├── config/
│ │ └── quick.config.ts
│ ├── constants/
│ ├── types/
│ └── interfaces/
│ ├── model.interface.ts
│ ├── transformer.interface.ts
│ ├── serialization-types.interface.ts
│ └── ...
├── transformers/
│ ├── bigint.transformer.ts
│ ├── buffer.transformer.ts
│ ├── date.transformer.ts
│ ├── error.transformer.ts
│ ├── map-set.transformer.ts
│ ├── primitive.transformer.ts
│ ├── regexp.transformer.ts
│ ├── special-float.transformer.ts
│ ├── symbol.transformer.ts
│ ├── typed-array.transformer.ts
│ ├── weak-collections.transformer.ts
│ └── web-apis.transformer.ts
└── mcp/
├── server.ts # MCP server entry point
├── locales/ # i18n (en.mcp.ts, es.mcp.ts)
├── prompts/ # 20 MCP prompt templates
└── tools/
├── public/ # 20 public MCP tools
└── internal/ # 20 internal MCP toolsSOLID Principles
1. Single Responsibility Principle (SRP)
- Transformers: Each transformer handles ONE specific type
- Services: Separate services for serialization, deserialization, and validation
- Decorators: Only register metadata, do not contain transformation logic
Bundle note:
ZodSchemaGeneratorlives in its own file (zod-schema-generator.service.ts) and loadszodlazily viacreateRequire(same pattern as@faker-js/fakerin the mock service). Consumers whose bundlers follow static imports (webpack, Vite/Rollup, esbuild) will not includezodin their output unlessQModel.getSchema('zod')orQZodSchemaGeneratoris actually called. Similarly,QMockGeneratoris instantiated lazily insideQModel— its constructor runs only on the first call to.mock(), not when the class is loaded.A dedicated subpath
quickmodel/schema/zodis also available for consumers that only need the Zod generator standalone, without importing the rest of the model machinery:typescriptimport { ZodSchemaGenerator } from 'quickmodel/schema/zod';Two additional granular subpaths are available to avoid loading unrelated code in CJS environments:
Subpath Contents Excludes quickmodel/mockQMockGenerator,QMockBuilderschema generators, serializer, QModelquickmodel/schemaAll 7 schema generators (Zod lazy) mock, serializer, QModelquickmodel/schema/zodOnly ZodSchemaGenerator(Zod lazy)everything else
2. Open/Closed Principle (OCP)
- Extensible system via registration of new transformers
- Does not require modifying existing code to add types
- Registry pattern allows injection of custom transformers
3. Liskov Substitution Principle (LSP)
- All transformers implement
IQTransformer<TInput, TSerialized> - Models behave like standard TypeScript classes
- Transparent substitution in inheritance hierarchies
4. Interface Segregation Principle (ISP)
- Separate interfaces for serialization (
IUser) and runtime (IUserTransform) - Clients do not depend on interfaces they do not use
- Small and specific contracts
5. Dependency Inversion Principle (DIP)
- Services depend on abstractions (
IQTransformer), not implementations - Registry acts as a dependency injection container
- Transformers do not know serialization details
Data Flow
┌─────────────┐
│ Constructor │ → Incoming Data (JSON from backend)
└──────┬──────┘
↓
┌────────────────────┐
│ @Quick/@QType │ → Transformation Metadata
│ (Decorators) │
└─────────┬──────────┘
↓
┌──────────────────────┐
│ Deserializer │ → Applies transformations
│ Service │
└──────────┬───────────┘
↓
┌─────────────────────┐
│ Transformers │ → Transform specific types
│ (Registry lookup) │ (string → Date, array → Set, etc.)
└──────────┬──────────┘
↓
┌─────────────────┐
│ QModel Instance │ → Properties with correct runtime types
└─────────────────┘🔨 Build System
Main Scripts
# Compile project (clean, test, and build)
bun run build
# Development with watch mode
bun run dev
# Clean dist/
bun run clean
# Verify types without emitting
bun run typecheckTypeScript Configuration
tsconfig.json - Source code compilation:
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"lib": ["ES2022"],
"moduleResolution": "node",
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
},
"rootDir": "./src",
"outDir": "./dist",
"strict": true,
"useDefineForClassFields": false,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"types": ["bun", "node"]
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "tests", "run", "docs"]
}tsconfig.test.json - Test configuration:
{
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": true,
"noUnusedLocals": true,
"noUnusedParameters": true
},
"include": ["src/**/*", "tests/**/*"]
}Bundling with tsup
tsup.config.ts:
export default defineConfig({
entry: { index: 'src/index.ts' },
format: ['cjs', 'esm'],
dts: true,
sourcemap: true,
clean: true,
treeshake: true,
external: ['reflect-metadata'],
esbuildOptions(options) {
options.alias = { '@': './src' };
},
});Path Aliases
Always use path aliases instead of relative imports. Available aliases (defined in tsconfig.json):
| Alias | Maps to |
|---|---|
@/* | src/* |
@mcp/* | src/mcp/* |
@tests/* | tests/* |
@scripts/* | scripts/* |
// ✅ CORRECT
import { QModel } from '@/core/models/quick.model';
import { Serializer } from '@/core/services/serializer.service';
import { McpServer } from '@mcp/server';
// ❌ INCORRECT
import { QModel } from '../../core/models/quick.model';
import { Serializer } from '../services/serializer.service';Barrel Files Convention
Module-level index.ts re-export files exist at module boundaries (src/transformers/index.ts, src/core/index.ts, src/mcp/tools/public/index.ts, etc.) and are intentional — they aggregate exports for bundling and module discovery.
Within a module, always import directly from the specific source file rather than going through an intermediate barrel:
// ✅ Import directly from the source file
import { BigIntTransformer } from '@/transformers/bigint.transformer';
import { Serializer } from '@/core/services/serializer.service';
// ❌ Do NOT import through a module barrel within src/
import { BigIntTransformer } from '@/transformers'; // avoid
import { Serializer } from '@/core/services'; // avoidDo NOT create new barrel files inside sub-directories (e.g. src/core/services/index.ts, src/core/interfaces/index.ts). They can cause circular dependencies and slow down builds.
Reasons:
- Avoids circular dependencies
- Faster build (fewer module resolutions)
- Better tree-shaking
- Explicit and clear imports
🧪 Testing
Framework
We use Bun Test (native, ultra-fast, compatible with Jest/Vitest API)
Test Structure
tests/
├── unit/ # Unit tests
│ ├── decorators/
│ ├── models/
│ ├── transformers/
│ └── ...
├── integration/ # Feature integration tests
│ ├── decorators/
│ ├── inheritance/
│ ├── models/
│ ├── patterns/
│ └── ...
├── system/ # Full workflow tests
│ └── full-workflow/
├── e2e/ # End-to-end tests
│ └── user-scenarios/
├── performance/ # Performance benchmarks
│ └── benchmarks/
├── security/ # Security tests (XSS, DoS, injections, etc.)
└── mcp/ # MCP server tests
├── unit/
└── integration/Running Tests
# All tests
bun test
# With coverage
bun run test:coverage
# Only unit tests (fast)
bun test tests/unit
# Only integration tests
bun test tests/integration
# Specific test
bun test tests/unit/primitives/bigint.test.ts
# Watch mode
bun test --watchWriting Tests
Basic Pattern:
import { describe, test, expect } from 'bun:test';
import { QModel, Quick } from '@';
describe('Feature Name', () => {
test('should do something specific', () => {
// Arrange
const data = {
/* ... */
};
// Act
const model = new Model(data);
// Assert
expect(model.property).toBe(expected);
});
});Conventions:
- Name files with pattern:
feature-scenario.test.ts - Use
describeto group related tests - Each
testshould validate ONE specific thing - Use Arrange/Act/Assert comments in complex tests
🎨 Code Style
Tools
- ESLint: Static analysis
- Prettier: Automatic formatting
- TypeScript: Type checking
Configuration
package.json (Prettier):
{
"prettier": {
"useTabs": true,
"tabWidth": 2,
"singleQuote": true,
"printWidth": 100,
"trailingComma": "es5",
"semi": true
}
}Main Rules
- Indentation: Tabs (no spaces)
- Quotes: Single quotes (
') - Line Length: Max 100 characters
- Semicolons: Yes (always)
- Trailing commas: ES5 style
Linting Scripts
# Check code
bun run lint
# Auto-fix problems
bun run lint:fix
# Check format
bun run format:check
# Auto-format
bun run formatTypeScript Conventions
Interfaces:
// ✅ I Prefix for data interfaces
interface IUser { ... }
// ✅ I Prefix for contract interfaces
interface IQTransformer<T, S> { ... }Types vs Interfaces:
// ✅ Use interface for objects and contracts
interface IUser {
id: number;
name: string;
}
// ✅ Use types for unions, tuples, utilities — always with I prefix
type IStatus = 'active' | 'inactive';
type IPoint = [number, number];
// ❌ Type aliases without I prefix are forbidden
type Status = 'active' | 'inactive';Identifier length (ESLint id-length):
// ✅ Min 3 characters required
const age = 25;
const idx = 0;
const err = new Error();
// ✅ Allowed short names: id, on, fs, cb, md, ts, err, _
const id = model.id;
const cb = () => {};
const [_, second] = list;
// ❌ Short name violations
const a = 1;
const fn = () => {};Maximum function parameters (ESLint max-params):
// ✅ Up to 3 params OK
function create(name: string, age: number, active: boolean) {}
// ✅ More than 3 — use an options object
function create(options: ICreateOptions) {}
// ❌ 4+ positional params forbidden (except in src/transformers/ and src/core/bases/)
function process(a: string, b: number, c: boolean, d: object) {}Restricted imports:
// ✅ Use internal path aliases
import { QModel } from '@/core/models/quick.model';
import { McpServer } from '@mcp/server';
// ❌ Never auto-import the published package from inside src/
import { QModel } from 'quickmodel';
// ❌ Never import bare @mcp barrel
import { something } from '@mcp';Quick automated validation
Use the MCP check_project_rules tool before completing any task. It statically checks all the rules above without running ESLint:
# Via MCP tool (fast, no process spawn)
mcp: check_project_rules
# Full validation (lint + typecheck + tests)
bun run checkProperty Declaration:
// ✅ Option 1: declare (recommended)
class User extends QModel<IUser> {
declare id: number;
declare name: string;
}
// ✅ Option 2: definite assignment (!)
class User extends QModel<IUser> {
id!: number;
name!: string;
}📝 Commits and Releases
Conventional Commits
Mandatory Format:
<type>(<scope>): <subject>
<body>
<footer>Main Types:
feat: New feature (MINOR bump)fix: Bug fix (PATCH bump)docs: Documentation onlystyle: Formatting, no code changesrefactor: Refactoringtest: Adding or modifying testschore: Maintenanceperf: Performance improvement (PATCH bump)
Project Scopes:
transformers,decorators,services,core,tests,docs,build,deps
Examples:
feat(transformers): add URL transformer support
fix(serializer): correct BigInt serialization bug
docs(readme): update installation instructions
chore(deps): update typescript to 5.7.2Release Workflow
Before Releasing:
# 1. Verify commits since last tag
bun run release:check
# 2. Run tests
bun test
# 3. Verify build
bun run buildRelease Process (Automated):
# 1. Merge to main
git checkout main
git merge develop
git push origin main
# 2. GitHub Actions handles:
# - Running tests
# - Project build
# - Analyzing commits (semantic-release)
# - Calculating new version
# - Creating tag
# - Updating CHANGELOG
# - Publishing to npm
# - Creating GitHub releaseSemantic Versioning
MAJOR.MINOR.PATCH- MAJOR (2.0.0): Breaking changes (
feat!:orBREAKING CHANGE:) - MINOR (1.1.0): New features (
feat:) - PATCH (1.0.1): Bug fixes (
fix:,perf:)
📚 Documentation
Tools
- TypeDoc: Generates API reference from JSDoc comments
- VitePress: Static site for guides and tutorials
Generating Documentation
# API Reference (TypeDoc)
bun run docs:api
# VitePress dev server
bun run docs:dev
# Build VitePress
bun run docs:build
# Preview VitePress build
bun run docs:previewWriting JSDoc
/**
* Transforms BigInt values for serialization
*
* @remarks
* Serializes as string to maintain precision in JSON
*
* @example
* ```ts
* const transformer = new BigIntTransformer();
* transformer.serialize(123n); // "123"
* transformer.deserialize("123"); // 123n
* ```
*/
export class BigIntTransformer implements IQTransformer<bigint, string> {
// ...
}🤝 Contributing
Recommended Workflow
- Fork and clone
- Create branch:
git checkout -b feat/new-feature - Develop with tests
- Commit following Conventional Commits
- Push and create Pull Request
- Review and merge
Checklist before PR
- ✅ Tests pass:
bun test - ✅ Build works:
bun run build - ✅ Lint OK:
bun run lint - ✅ Format OK:
bun run format:check - ✅ Types OK:
bun run typecheck - ✅ Commits follow Conventional Commits
- ✅ Documentation updated (if applicable)