Part 8 · 1 chapters · ~8 min

Capstone: Build Your Own ORM

Building a small TypeScript ORM: a schema definition with inferred types, a query builder producing parameterised SQL, an identity map with unit of work and dirty tracking, batched relation loading that makes N+1 impossible, a migration engine with diffing, and transactions with pooling.

12

The build

code
mini-orm/
  src/schema.ts       table('accounts', { id: text().primaryKey(), balanceKobo: bigint() }) → InferRow<typeof accounts>
  src/query.ts        select().from(t).where(eq(t.ownerId, x)) → { sql, params }; Postgres dialect
  src/session.ts      identity map (Map<table:id, entity>), snapshots, flush(): diff → ordered statements in one tx
  src/loader.ts       session.load(Account, id) batched per tick → WHERE id = ANY($1)
  src/migrate.ts      read information_schema, diff, emit SQL; refuse DROP COLUMN without --allow-destructive
  test/               exact-SQL snapshots; a flush-order test with FK constraints; an N+1 test that counts queries
stretch: optimistic locking with a version column; soft deletes; a raw-SQL escape hatch that stays parameterised
BUILD AN ORM
the capstone, in six components
schema DSLtypes inferred from definitionsquery builderAST → parameterised SQLidentity mapunit of workdirty tracking, ordered flushrelation loaderbatched, no N+1migration engineschema diffing
swipe the figure sideways, or tap expand for full screen
1/5
schema with inference
Define tables in TypeScript (like Drizzle) so row types are inferred from the definition.
definitions → inferred typesno codegen