TypeORM
Portable TypeORM mapping and API conventions. Engine-specific decisions
(identifiers, primary keys, logical-column types, index syntax) live in
dedicated per-engine skills; load the one that matches the project's database,
determined by the type: field of its TypeORM DataSource (mssql →
typeorm-mssql, postgres → typeorm-pg). Add a new per-engine skill the day
a project runs on that engine.
Entities
- Every column has an explicit type as the first positional argument and an explicit
name:: @Column('varchar', { name: 'email', length: 50 }).
- Logical columns are manual and live in SQL (see the engine skill for exact types). Do not introduce
@CreateDateColumn / @UpdateDateColumn / @DeleteDateColumn where existing entities do not already use them.
- Relations are explicit:
@ManyToOne(() => UserEntity, user => user.createdProfiles) with @JoinColumn([{ name: 'createdBy', referencedColumnName: 'id' }]); FK columns are named in the engine's identifier style (<entity>Id on MSSQL, <entity>_id on Postgres).
- No
cascade or lazy loading unless the workflow requires it.
- Repeated references to the same entity get intent-named inverse properties (
createdByUser / updatedByUser). Legacy entities generated by typeorm-model-generator keep the numbered form (users, users2); do not introduce new numbered names.
- Declare indexes exactly as the SQL does, spelling out the same
where: condition: @Index('UQ_player_email', ['email'], { unique: true, where: '...' }). Constraint and index names are explicit (PK_, UQ_, IX_, FK_).
- Store TS enums as varchar with a literal default:
@Column('varchar', { name: 'kycStatus', length: 20, default: () => '${KycStatus.NOT_STARTED}' }). Keep enum values synchronized across TS, SQL, and seeds. The interpolated values must be simple identifiers, never expressions or runtime values.
Repositories
- Register entities per feature module with
TypeOrmModule.forFeature([...]) and inject them directly: @InjectRepository(Entity).
- Use the project's repository layer when the project has one and keep new repository code inside that layer's conventions;
@InjectRepository(Entity) is the default only when that layer does not exist.
- Write a custom repository when there is real query logic. Extend
Repository<Entity> with an explicit wiring constructor and expose intent-named methods (findResumable, applyTerminal) built on the project layer, find* options, or the query builder.
Queries and transactions
- Prefer the
find* options API; reserve createQueryBuilder for shapes it cannot express (raw projections, engine-specific output/returning).
- Paginate with
{ take, skip }.
- Simple transactions use
dataSource.transaction(async (manager) => { ... }); when several repositories must join one transaction, thread an explicit QueryRunner through the calls and run queries through manager.
Configuration
synchronize: false — TypeORM never mutates the schema.
autoLoadEntities: true and no entities: / migrations: globs; migrations are versioned SQL applied by the project runner (see the database skill).
1---2name: typeorm3description: TypeORM entity, relation, repository, query, and configuration conventions shared across engines. Load when designing entities, relations, queries, or TypeORM setup; load the matching per-engine skill alongside it.4---56# TypeORM78Portable TypeORM mapping and API conventions. Engine-specific decisions9(identifiers, primary keys, logical-column types, index syntax) live in10dedicated per-engine skills; load the one that matches the project's database,11determined by the `type:` field of its TypeORM DataSource (`mssql` →12`typeorm-mssql`, `postgres` → `typeorm-pg`). Add a new per-engine skill the day13a project runs on that engine.1415## Entities1617- Every column has an explicit type as the first positional argument and an explicit `name:`: `@Column('varchar', { name: 'email', length: 50 })`.18- Logical columns are manual and live in SQL (see the engine skill for exact types). Do not introduce `@CreateDateColumn` / `@UpdateDateColumn` / `@DeleteDateColumn` where existing entities do not already use them.19- Relations are explicit: `@ManyToOne(() => UserEntity, user => user.createdProfiles)` with `@JoinColumn([{ name: 'createdBy', referencedColumnName: 'id' }])`; FK columns are named in the engine's identifier style (`<entity>Id` on MSSQL, `<entity>_id` on Postgres).20- No `cascade` or lazy loading unless the workflow requires it.21- Repeated references to the same entity get intent-named inverse properties (`createdByUser` / `updatedByUser`). Legacy entities generated by `typeorm-model-generator` keep the numbered form (`users`, `users2`); do not introduce new numbered names.22- Declare indexes exactly as the SQL does, spelling out the same `where:` condition: `@Index('UQ_player_email', ['email'], { unique: true, where: '...' })`. Constraint and index names are explicit (`PK_`, `UQ_`, `IX_`, `FK_`).23- Store TS enums as varchar with a literal default: `@Column('varchar', { name: 'kycStatus', length: 20, default: () => `'${KycStatus.NOT_STARTED}'` })`. Keep enum values synchronized across TS, SQL, and seeds. The interpolated values must be simple identifiers, never expressions or runtime values.2425## Repositories2627- Register entities per feature module with `TypeOrmModule.forFeature([...])` and inject them directly: `@InjectRepository(Entity)`.28- Use the project's repository layer when the project has one and keep new repository code inside that layer's conventions; `@InjectRepository(Entity)` is the default only when that layer does not exist.29- Write a custom repository when there is real query logic. Extend `Repository<Entity>` with an explicit wiring constructor and expose intent-named methods (`findResumable`, `applyTerminal`) built on the project layer, `find*` options, or the query builder.3031## Queries and transactions3233- Prefer the `find*` options API; reserve `createQueryBuilder` for shapes it cannot express (raw projections, engine-specific output/returning).34- Paginate with `{ take, skip }`.35- Simple transactions use `dataSource.transaction(async (manager) => { ... })`; when several repositories must join one transaction, thread an explicit `QueryRunner` through the calls and run queries through `manager`.3637## Configuration3839- `synchronize: false` — TypeORM never mutates the schema.40- `autoLoadEntities: true` and no `entities:` / `migrations:` globs; migrations are versioned SQL applied by the project runner (see the `database` skill).