Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Naor Peled <me@naor.dev>
9.0 KiB
GitHub Copilot Instructions for TypeORM
This document provides guidance for GitHub Copilot when working with the TypeORM codebase.
Project Overview
TypeORM is a TypeScript-based Object-Relational Mapping (ORM) library that supports multiple databases including MySQL/MariaDB, PostgreSQL, MS SQL Server, Oracle, SAP HANA, SQLite, MongoDB, and Google Spanner. It implements both Active Record and Data Mapper patterns and runs on Node.js, Browser, React Native, and Electron platforms.
Architecture & Structure
Core Components
src/data-source/- DataSource (formerly Connection) managementsrc/entity-manager/- Entity management and operationssrc/repository/- Repository pattern implementationsrc/query-builder/- SQL query buildingsrc/decorator/- TypeScript decorators for entities, columns, relationssrc/driver/- Database-specific driverssrc/metadata/- Entity metadata managementsrc/schema-builder/- Schema creation and migrationsrc/migration/- Database migration systemsrc/subscriber/- Event subscriber systemsrc/persistence/- Entity persistence logic
Design Patterns
- Active Record Pattern: Entities have methods to save, remove, and query themselves
- Data Mapper Pattern: Repositories handle entity persistence separately from business logic
- Decorator Pattern: Extensive use of TypeScript decorators for metadata definition
- Builder Pattern: QueryBuilder for constructing complex queries
Coding Standards
TypeScript Configuration
- Target: ES2021+ with CommonJS modules
- Decorators:
experimentalDecoratorsandemitDecoratorMetadataenabled
Code Style
- Formatting: Use Prettier with these settings:
- No semicolons (
"semi": false) - Arrow function parentheses always (
"arrowParens": "always") - Trailing commas everywhere (
"trailingComma": "all")
- No semicolons (
- Linting: ESLint with TypeScript support
- Use
@typescript-eslintrules - Warnings allowed for some
@typescript-eslint/no-*rules - Unused variables starting with
_are ignored
- Use
- Naming Conventions:
- Classes: PascalCase (e.g.,
DataSource,EntityManager) - Interfaces: PascalCase (e.g.,
ColumnOptions,RelationOptions) - Variables/functions: camelCase
- Constants: UPPER_SNAKE_CASE for true constants
- Private members: Use standard camelCase (no underscore prefix)
- Classes: PascalCase (e.g.,
TypeScript Patterns
- Use explicit types for public APIs
- Prefer interfaces over type aliases for object shapes
- Use generics for reusable components
- Avoid
anywhere possible; useunknownor proper types - Use optional chaining (
?.) and nullish coalescing (??) operators - Leverage TypeScript utility types (
Partial<T>,Required<T>,Pick<T>, etc.)
Testing
Test Structure
Tests are organized in test/ directory:
test/functional/- Feature and integration tests organized by functionality (preferred)test/github-issues/- Tests for specific GitHub issuestest/unit/- Unit tests for individual componentstest/utils/- Test utilities and helpers
Note: Prefer writing functional tests over per-issue tests.
Test Writing Guidelines
- Use the standard test template:
import "reflect-metadata"
import { createTestingConnections, closeTestingConnections, reloadTestingDatabases } from "../../utils/test-utils"
import { DataSource } from "../../../src/data-source/DataSource"
import { expect } from "chai"
describe("description of functionality", () => {
let dataSources: DataSource[]
before(async () => dataSources = await createTestingConnections({
entities: [__dirname + "/entity/*{.js,.ts}"],
schemaCreate: true,
dropSchema: true,
}))
beforeEach(() => reloadTestingDatabases(dataSources))
after(() => closeTestingConnections(dataSources))
it("should do something specific", () => Promise.all(dataSources.map(async dataSource => {
// Test implementation
})))
})
-
Test Configuration:
- Tests run against multiple databases (as configured in
ormconfig.json) - Each test should work across all supported databases unless database-specific
- Place entity files in
./entity/relative to test file for automatic loading - Use
Promise.all(dataSources.map(...))pattern to test against all databases
- Tests run against multiple databases (as configured in
-
Test Naming:
- Use descriptive
describe()blocks for features - Use "should..." format for
it()descriptions - Reference GitHub issue numbers when fixing specific issues
- Use descriptive
-
Running Tests:
- Full test suite:
npm test(compiles then runs tests) - Fast iteration:
npm run test:fast(runs without recompiling) - Specific tests:
npm run test:fast -- --grep "pattern" - Watch mode:
npm run compile -- --watch+npm run test:fast
- Full test suite:
Database-Specific Considerations
Multi-Database Support
When writing code or tests:
- Ensure compatibility across all supported databases
- Use driver-specific code only in
src/driver/directory - Test database-agnostic code against multiple databases
- Use
DataSource.options.typeto check database type when needed - Be aware of SQL dialect differences (LIMIT vs TOP, etc.)
Driver Implementation
Each driver in src/driver/ implements common interfaces:
- Connection management
- Query execution
- Schema synchronization
- Type mapping
- Transaction handling
Common Development Tasks
Adding a New Feature
- Create entities in appropriate test directory
- Write tests first (TDD approach encouraged)
- Implement feature in
src/ - Ensure tests pass across all databases
- Update documentation if public API changes
- Follow commit message conventions
Adding a New Decorator
- Create decorator file in
src/decorator/ - Create metadata args in
src/metadata-args/ - Update metadata builder in
src/metadata-builder/ - Export from
src/index.ts - Add comprehensive tests
- Update TypeScript type definitions if needed
Working with Migrations
- Migrations are in
src/migration/ - Migration files should be timestamped
- Support both up and down migrations
- Test migrations against all supported databases
- Ensure schema changes are reversible
Build & Development Workflow
Commands
- Build:
npm run compile- Compiles TypeScript tobuild/compiled/ - Package:
npm run package- Creates distribution inbuild/package/ - Pack:
npm run pack- Creates.tgzfile inbuild/ - Test:
npm test- Compile and run all tests - Lint:
npm run lint- Run ESLint - Format:
npm run format- Run Prettier - Watch:
npm run watch- Watch mode for TypeScript compilation
Development Setup
- Install dependencies:
npm install - Copy config:
cp ormconfig.sample.json ormconfig.json - Configure database connections in
ormconfig.json - Optionally use Docker:
docker-compose upfor database services
Pre-commit Hooks
- Husky runs pre-commit hooks
- Lint-staged runs on staged files
- Format and lint checks must pass
Contribution Guidelines
Commit Message Format
Follow conventional commits:
<type>: <subject>
<body>
<footer>
Types: feat, fix, docs, style, refactor, perf, test, build, chore, revert
Subject:
- Use imperative, present tense
- Don't capitalize first letter
- No period at the end
- Max 100 characters per line
Pull Request Requirements
- All tests must pass
- Include appropriate tests for changes
- Follow existing code style
- Update documentation for API changes
- Reference related GitHub issues
- Get approval before merging
Common Patterns & Idioms
Entity Definition
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number
@Column()
name: string
@OneToMany(() => Photo, photo => photo.user)
photos: Photo[]
}
Repository Usage
const userRepository = dataSource.getRepository(User)
const user = await userRepository.findOne({ where: { id: 1 } })
QueryBuilder
const users = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.leftJoinAndSelect("user.photos", "photo")
.where("user.name = :name", { name: "John" })
.getMany()
Transactions
await dataSource.transaction(async (manager) => {
await manager.save(user)
await manager.save(photo)
})
Important Notes
- Always import
reflect-metadatabefore TypeORM - Be careful with circular dependencies between entities
- Use lazy relations or forward references for circular entity references
- Connection pooling is handled automatically by drivers
- Be mindful of N+1 query problems; use joins or eager loading
- Repository methods are async; always use
await - Entity instances should be plain objects, not class instances with methods (Data Mapper pattern)