v1.0.4 — Now available

Reflect.
Map.
Persist.

A TypeScript ORM built for raw performance. Stage 3 decorators, JIT-compiled hydrators, zero N+1 — and no reflect-metadata.

Get Started → View Examples
39 ns
per row overhead
faster than TypeORM
0 N+1
queries
TS 5.x
Stage 3 decorators

Everything you need,
nothing you don't.

Mirror ORM is a unipurpose tool. It maps entities. It does it exceptionally well.

39 ns/row

JIT-Compiled Hydrator

buildHydrator() uses new Function() to generate a monomorphic function per entity at startup. V8 applies Inline Caches to every field access — 6× faster than Drizzle and 8× faster than TypeORM in the same benchmark.

Stage 3 Decorators

Uses the TC39 Stage 3 proposal natively in TypeScript 5.x. No reflect-metadata. No legacy flags.

Named Prepared Statements

findAll and findById use stable named queries, letting the pg driver skip parse & plan on every subsequent call.

🧠

Dirty Checking

Entities hydrated from the DB carry a column snapshot. save() only emits SET for changed columns — no unnecessary writes.

🔒

Implicit Transactions

AsyncLocalStorage propagates the transaction context automatically — any repository created inside connection.transaction() uses it without explicit wiring.

🔄

Lifecycle Hooks

Built-in hooks for before/after persist events. Timestamps, soft delete, and upsert are first-class features — not afterthoughts.

#

UUIDv7 · ULID · CUID2

Modern identifier strategies built in. Choose the right one for your ordering and collision requirements.

Familiar syntax.
Zero compromises.

Class-based, decorator-driven. If you've used TypeORM, it feels instantly familiar — but faster.

user.entity.ts
import { Entity, Column, PrimaryColumn }
    from 'mirror-orm';

  @Entity()
  export class User {
    @PrimaryColumn()
    id: string;

    @Column()
    name: string;

    @Column({ type: 'datetime' })
    createdAt: Date;

    @Column({ select: false })
    passwordHash: string;
  }
repository usage
const repo = connection.getRepository(User);

  // find with filters
  const users = await repo.find({
    where: { name: Like('%alice%') },
    order: { createdAt: 'DESC' },
    limit: 10,
  });

  // eager relations
  const post = await repo.findOne({
    where: { id },
    relations: ['author', 'tags'],
  });

Explicit SELECT clause

Queries emit SELECT "col1", "col2" — never SELECT *. PostgreSQL skips unrequested columns. Use select: false to permanently hide sensitive fields like password hashes.

🔍

Rich filter operators

Like, In, Between, IsNull, Not — plus OR-group composition for expressive WHERE clauses without a query builder DSL.

💾

Bulk operations

saveMany() issues a single multi-row INSERT. removeMany() uses WHERE pk = ANY($1) — O(1) SQL regardless of array size.

⚙️

Type casting built in

Declare type: 'datetime' and get a real Date object back. bigint, boolean, number, iso — woven directly into the JIT hydrator, zero runtime branching per row.

Every relationship,
batched and lean.

Opt-in loading, no N+1, no surprises. Mirror resolves all relation types with a single batch query each.

@ManyToOne

Many to One

Generates a LEFT JOIN in find({ relations }). Related columns are aliased with a mirror__prop__ prefix to prevent name collisions across multiple joins.

@ManyToOne(Post, 'authorId')
author: User;
@OneToMany

One to Many

After the main query, a single batch SELECT … WHERE fk = ANY($1) fetches all children for all parent IDs at once. Guaranteed zero N+1 queries.

@OneToMany(Comment, 'postId')
comments: Comment[];
@OneToOne

One to One

Owner side resolved via FK presence on the entity — uses LEFT JOIN (same path as ManyToOne). Inverse side uses a batch query returning T | null.

@OneToOne(Profile, 'userId')
profile: Profile | null;
@ManyToMany

Many to Many

Batch INNER JOIN through the join table with owner FK aliased internally for grouping. Returns T[]. No hidden junction queries.

@ManyToMany(Tag, 'post_tags',
'postId', 'tagId')
tags: Tag[];

Three focused classes.
One clear contract.

Mirror's repository layer is split by responsibility — nothing ends up in the wrong place.

01 / RepositoryState<T>

Compiled Metadata

Column maps, select clauses, JIT hydrators, and prepared statement objects — all built once at getRepository() time. Never on the hot path.

02 / SqlAssembler<T>

SQL Builder

Builds SQL strings. Each method returns { sql, params } — no side effects, no mutation. Pure functions, easy to test.

03 / Repository<T>

Public API

A thin orchestration layer. Calls SqlAssembler for SQL, executes via IQueryRunner, hydrates via RepositoryState. Zero SQL strings inline.

Start building today.

Mirror ORM is production-ready and actively maintained. Install it, try it, and open an issue — contributions are welcome.

pnpm add mirror-orm click to copy
Read the Docs → ★ Star on GitHub