Skip to content
VibORM
Esc
navigateopen⌘Jpreview
On this page

Architecture

Design principles, data flow, and critical architectural rules for VibORM's layered system

Design Principles

1. Types From Runtime Validation

VibORM infers TypeScript types from runtime validation schemas - a single source of truth for both compile-time types AND runtime validation.

const user = s.model({
  id: s.string().id().ulid(),
  name: s.string(),
  email: s.string().unique(),
});

// Types are inferred from the schema definition
// No code generation needed
const users = await orm.user.findMany({
  where: { name: "Alice" }  // ← Fully typed!
});

2. Dual Validation

Every operation is validated twice:

  1. Compile-time - TypeScript catches type errors
  2. Runtime - VibORM’s validation engine validates actual data

3. Separation of Concerns

The architecture separates what to query from how to express it:

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│  Schema Layer   │────▶│  Client Layer   │────▶│  Query Engine   │
│  (Definition)   │     │  (Operations)   │     │  (WHAT)         │
└─────────────────┘     └─────────────────┘     └─────────────────┘


                               ┌─────────────────┐     ┌─────────────┐
                               │    Database     │◀────│  Adapters   │
                               │    (Execute)    │     │  (HOW)      │
                               └─────────────────┘     └─────────────┘

Critical Architectural Rules

Rule 1: Query Engine / Adapter Separation

The Golden Rule: Query engine decides WHAT to query. Adapters decide HOW to express it.

This enables VibORM to support PostgreSQL, MySQL, and SQLite with a single codebase. The query engine builds query structure; adapters translate to database-specific syntax.

Rule 2: Immutable State for Type Tracking

Every scalar modifier returns a NEW instance:

s.string()           // StringScalar<{type: "string"}>
  .nullable()        // StringScalar<{type: "string", nullable: true}>  ← NEW instance
  .default("hello")  // StringScalar<{..., default: "hello"}>           ← NEW instance

TypeScript tracks the State generic through each transformation. If we mutated the scalar instead, the type system couldn’t track changes.

Rule 3: Lazy Evaluation for Circular References

Relations use thunks () => Model to break circular dependencies:

const user = s.model({
  posts: s.oneToMany(() => post),  // Thunk defers evaluation
});
const post = s.model({
  author: s.manyToOne(() => user),
});

JavaScript can’t reference a variable before it’s declared. Thunks defer resolution until the model is actually used.

Data Flow

Query Execution

Layer Boundaries

Schema Layers (L1-L5)

These layers handle definition and validation - no SQL generation.

Layer Purpose
L1: Validation Standard Schema V1 primitives (v.*) and SchemaRegistry
L2: Scalars Scalar type definitions with State generic and base schemas
L3: Operation Schemas Validation schemas for where/create/update
L4: Relations Relationship definitions between models
L5: Schema Validation Definition-time correctness checking

Execution Layers (L6-L8)

These layers handle query building and execution.

Layer Purpose
L6: Query Engine Compiles one database-agnostic operation program and resolves its declared result (WHAT)
L7: Adapters Database-specific SQL syntax (HOW)
L8: Drivers Connection management and execution

Interface Layer (L9)

The client layer provides the public API and type inference.

Extension and Management Layers (L10-L12)

Layer Purpose
L10: Cache Caches read results and coordinates invalidation without owning query execution
L11: Instrumentation Observes operations through tracing, logging, and diagnostics without owning query logic
L12: Migrations Handles schema evolution through snapshots, diffs, and database-specific DDL

File Structure

src/
├── validation/           # L1: Validation primitives
│   ├── model/            # L3: Operation schemas (where/create/update/args)
│   └── relations/        # L3: Relation operation schemas
├── schema/               # L2, L4, L5: Schema system
│   ├── scalars/          # L2: Scalar types
│   ├── relation/         # L4: Relations
│   └── validation/       # L5: Schema validation
├── query-engine/         # L6: Query engine
├── adapters/             # L7: Database adapters
├── drivers/              # L8: Database drivers
├── client/               # L9: ORM client
├── cache/                # L10: Query cache and invalidation
├── instrumentation/      # L11: Tracing, logging, and diagnostics
└── migrations/           # L12: Schema migrations

Was this page helpful?