Skip to content
VibORM
Esc
navigateopen⌘Jpreview
On this page

Internals Overview

The 12-layer architecture transforming type-safe schema definitions into database queries without code generation

The 12-Layer Architecture

┌─────────────────────────────────────────────────────────────────────────┐
│                          User Code                                       │
│   s.model({ name: s.string() })  →  orm.user.findMany({...})            │
└─────────────────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────────────┐
│  L1: Validation       │ v.* primitives, Standard Schema V1, registry     │
├───────────────────────┼─────────────────────────────────────────────────┤
│  L2: Scalars          │ Scalar classes with State generic pattern        │
├───────────────────────┼─────────────────────────────────────────────────┤
│  L3: Operation Schemas│ where, create, update, args schema generation   │
├───────────────────────┼─────────────────────────────────────────────────┤
│  L4: Relations        │ oneToOne, manyToOne, oneToMany, manyToMany      │
├───────────────────────┼─────────────────────────────────────────────────┤
│  L5: Schema Validation│ Definition-time schema correctness checks       │
├───────────────────────┼─────────────────────────────────────────────────┤
│  L6: Query Engine     │ Database-agnostic query structure building      │
├───────────────────────┼─────────────────────────────────────────────────┤
│  L7: Adapters         │ Database-specific SQL dialect generation        │
├───────────────────────┼─────────────────────────────────────────────────┤
│  L8: Drivers          │ Connection management and query execution       │
├───────────────────────┼─────────────────────────────────────────────────┤
│  L9: Client           │ Type-safe ORM interface via recursive proxies   │
├───────────────────────┼─────────────────────────────────────────────────┤
│  L10: Cache           │ Query caching, invalidation, and SWR             │
├───────────────────────┼─────────────────────────────────────────────────┤
│  L11: Instrumentation │ Tracing, structured logging, and diagnostics     │
├───────────────────────┼─────────────────────────────────────────────────┤
│  L12: Migrations      │ Schema diffing and database synchronization     │
└───────────────────────┴─────────────────────────────────────────────────┘

Layer Responsibility Matrix

Layer Location Owns Doesn’t Own
L1: Validation src/validation/ v.* primitives, Standard Schema V1, SchemaRegistry Scalar logic, domain rules
L2: Scalars src/schema/scalars/ Scalar classes, State generics Query schemas
L3: Operation Schemas src/validation/model/, src/validation/relations/ where, create, update, args schemas SQL generation
L4: Relations src/schema/relation/ Relation types, nested schemas Query execution
L5: Schema Validation src/schema/validation/ Definition-time validation Runtime validation
L6: Query Engine src/query-engine/ Query structure, logic Database SQL
L7: Adapters src/adapters/ Database-specific SQL Query logic
L8: Drivers src/drivers/ Connection, execution Query building
L9: Client src/client/ Result types, proxies Query construction
L10: Cache src/cache/ Query caching, invalidation Query execution
L11: Instrumentation src/instrumentation/ Tracing, logging, diagnostics Query logic
L12: Migrations src/migrations/ Schema sync, migration files, DDL Schema definition

Why This Architecture?

VibORM was designed to solve three problems that plagued existing ORMs:

1. Code Generation Lock-in

Prisma requires prisma generate after every schema change. We wanted instant type updates on save.

// VibORM: Types update instantly when you change this
const user = s.model({
  email: s.string().unique(),  // Change here → types update immediately
});

2. Multi-Database Support Without Forking

Drizzle has separate packages per database. We wanted one codebase supporting PostgreSQL, MySQL, and SQLite through the adapter pattern.

// Same code, different adapters
const pg = new PostgresAdapter();
const mysql = new MySQLAdapter();
const sqlite = new SQLiteAdapter();

3. Types From Runtime Validation (Single Source of Truth)

We wanted types inferred from runtime schema validators - a single source of truth for both compile-time types AND runtime validation. Maintaining separate type definitions and validation logic would make the codebase difficult to evolve.

The key challenge: ORM models reference each other recursively. User has Posts, Post has Author. None of the existing libraries handled this well:

  • Zod - z.lazy() loses type info, becomes any
  • ArkType - Good types, but thunks evaluated eagerly causing issues
  • Valibot - Poor lazy evaluation caused infinite recursion at runtime

So we built our own validation layer using thunks + lazy evaluation (inspired by ArkType’s approach) that correctly handles recursive schemas.

Type Flow Through Layers

User writes:           s.string().nullable()

L2: Scalar creates:     StringScalar<{type: "string", nullable: true}>

L1: Schema factory:    v.string({nullable: true})  (lazy, on first access)

Type inference:        InferInput<schema> → string | null

L9: Client uses:       orm.user.findMany({ where: { name: ... }})  // Fully typed!

Key insight: Types flow DOWN through this chain. If types are wrong at the client level, the bug is upstream in schema or scalar definition.

The Golden Rule

// ❌ WRONG: Hardcoded PostgreSQL in query-engine
sql`COALESCE(json_agg(...), '[]'::json)`

// ✅ RIGHT: Delegate to adapter
ctx.adapter.json.agg(subquery)

This is the most important architectural boundary. The query engine decides WHAT to query. The adapter decides HOW to express it.

Explore the Layers

Layer Purpose
L1: Validation Recursive schemas via thunks + lazy evaluation
L2: Scalars Scalar types with State generic for type tracking
L3: Operation Schemas Registry schema generation for where/create/update
L4: Relations Model relationships with thunks for circular refs
L5: Schema Validation Definition-time structural correctness checks
L6: Query Engine Database-agnostic query structure
L7: Adapters Database-specific SQL dialects
L8: Drivers Connection management and execution
L9: Client The public orm.model.operation() API
L10: Cache Query result caching and invalidation
L11: Instrumentation Tracing, logging, and diagnostics
L12: Migrations Schema diffing and synchronization

Next Steps

Was this page helpful?