VibORM
L1: Validation

Overview

Standard Schema V1-compliant validation primitives (v.*) serving as VibORM's type system foundation

Location: src/validation/

Why This Layer Exists

VibORM needed validation schemas that could:

  1. Serve as both runtime validators AND TypeScript type sources (single source of truth)
  2. Handle recursive schema references correctly - ORM models reference each other circularly

We evaluated existing libraries but none handled recursive schemas well for our use case - either types broke, or runtime evaluation caused infinite loops.

The Core Feature: Recursive Schemas

ORM models reference each other. User has Posts, Post has Author (a User). This creates circular dependencies:

const user = s.model({
  posts: s.oneToMany(() => post),  // User → Post
});

const post = s.model({
  author: s.manyToOne(() => user),  // Post → User (circular!)
});

The validation layer handles this through thunks and lazy evaluation (inspired by ArkType):

const userSchema = v.object({
  name: v.string(),
  posts: () => v.array(postSchema),  // Thunk - not evaluated immediately
});

const postSchema = v.object({
  title: v.string(),
  author: () => userSchema,  // Back-reference works
});

How It Works

  1. Thunks defer evaluation - () => schema isn't called during construction
  2. Lazy evaluation - Thunks are only called during validation, when all schemas exist
  3. TypeScript still infers - TS can infer the return type of a function before it's called

This is the key innovation. Without it, you get either ReferenceError at runtime or any types.

What It Provides

The v.* primitives for building validation schemas:

import { v } from "viborm";

// Scalar types
v.string()
v.number()
v.boolean()

// Modifiers
v.string({ nullable: true, optional: true, array: true })

// Objects with circular references
v.object({
  name: v.string(),
  friend: () => userSchema,  // Deferred evaluation
})

Standard Schema V1 Compliance

Every schema implements the Standard Schema interface, enabling interoperability with tools like tRPC, React Hook Form, and Vercel AI SDK:

schema["~standard"].validate(value)
// Returns { value } on success, { issues } on failure

Two "Validations" in VibORM

LayerPurposeWhen
L1: ValidationRuntime input checkingQuery execution
L5: Schema ValidationDefinition-time correctnessSchema definition

L1 validates data at runtime. L5 validates that your schema definitions are correct (e.g., relations reference valid models).

Design Goals

The validation engine aims for balance, not maximum performance:

  • Schema instantiation - Fast enough for dynamic query schema building
  • Validation performance - Reasonable for typical ORM inputs
  • Bundle size - Minimal footprint

It's not trying to be the fastest validation engine - it's trying to correctly handle recursive ORM schemas while staying performant enough.

On this page