VibORM

Internals Overview

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

The 10-Layer Architecture

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


┌─────────────────────────────────────────────────────────────────────────┐
│  L1: Validation       │ v.* primitives, Standard Schema V1 compliance   │
├───────────────────────┼─────────────────────────────────────────────────┤
│  L2: Fields           │ Field classes with State generic pattern        │
├───────────────────────┼─────────────────────────────────────────────────┤
│  L3: Query 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: Migrations      │ Schema diffing and database synchronization     │
└───────────────────────┴─────────────────────────────────────────────────┘

Layer Responsibility Matrix

LayerLocationOwnsDoesn't Own
L1: Validationsrc/validation/v.* primitives, Standard Schema V1Field logic, domain rules
L2: Fieldssrc/schema/fields/Field classes, State genericsQuery schemas
L3: Query Schemassrc/schema/model/schemas/where, create, update, args schemasSQL generation
L4: Relationssrc/schema/relation/Relation types, nested schemasQuery execution
L5: Schema Validationsrc/schema/validation/Definition-time validationRuntime validation
L6: Query Enginesrc/query-engine/Query structure, logicDatabase SQL
L7: Adapterssrc/adapters/Database-specific SQLQuery logic
L8: Driverssrc/drivers/Connection, executionQuery building
L9: Clientsrc/client/Result types, proxiesQuery construction
L10: Migrationssrc/migrations/Schema diffing, pushSchema 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: Field creates:     StringField<{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 field definition.

The Golden Rule

Query Engine / Adapter Separation

Query engine NEVER generates dialect-specific SQL. ALWAYS delegate to adapter.

// ❌ 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

LayerPurpose
L1: ValidationRecursive schemas via thunks + lazy evaluation
L2: FieldsField types with State generic for type tracking
L3: Query SchemasSchema generation for where/create/update
L4: RelationsModel relationships with thunks for circular refs
L6: Query EngineDatabase-agnostic query structure
L7: AdaptersDatabase-specific SQL dialects
L8: DriversConnection management and execution
L9: ClientThe public orm.model.operation() API
L10: MigrationsSchema diffing and synchronization

Next Steps

On this page