Skip to content
VibORM
Esc
navigateopen⌘Jpreview
On this page

L9 - Client

The orm.model.operation() interface providing type-safe queries with zero code generation

Location: src/client/

Why This Layer Exists

VibORM’s type safety comes from inferring types from schema definitions. The client layer makes this work:

import { createClient } from "viborm";

const orm = createClient({ schema, driver });

// Fully typed - no codegen!
const users = await orm.user.findMany({
  where: { email: { contains: "@example.com" } },
  select: { id: true, email: true, posts: { select: { title: true } } }
});
// Type: Array<{ id: string; email: string; posts: Array<{ title: string }> }>

How It Works

Recursive Proxy Pattern

The client uses JavaScript Proxy to handle dynamic model access:

orm.user     // Proxy intercepts "user" property access
   .findMany // Proxy intercepts "findMany" method access
   (args)    // Calls actual findMany implementation

This enables orm.<anyModel>.<anyOperation> without defining every combination.

Type Inference Chain

Types flow through a chain:

Schema Definition

Model extracts fields (scalars and relations)

Client infers model names from schema

Operations infer args/return from model

Select/Include modifies return type

No step uses code generation - TypeScript infers at each level.

Select/Include Aware Results

Return types change based on what you select:

// Full model (all fields)
orm.user.findFirst({ where: { id: "1" } });
// Type: { id, email, name, createdAt, ... } | null

// Selected fields only
orm.user.findFirst({ 
  where: { id: "1" },
  select: { id: true, email: true } 
});
// Type: { id, email } | null

// With relations
orm.user.findFirst({
  where: { id: "1" },
  include: { posts: true }
});
// Type: { id, email, ..., posts: Post[] } | null

Available Operations

The full Operations union (see src/client/types.ts):

Operation Description
findMany Get multiple records
findFirst Get first matching record
findFirstOrThrow Get first matching record or throw
findUnique Get record by unique constraint
findUniqueOrThrow Get record by unique constraint or throw
exist Check whether any row matches
create Create single record
createMany Create multiple records; returns { count }, or the created rows when select is present
update Update a record by unique constraint
updateMany Update multiple records; returns { count }, or the updated rows when select is present
upsert Create or update on conflict
delete Delete a record by unique constraint
deleteMany Delete multiple records; returns { count }, or the deleted rows when select is present
count Count matching records
aggregate Aggregate functions (SUM, AVG, etc.)
groupBy Group rows and aggregate per group

Why Zero Codegen?

Code generation ORMs (like Prisma) require a build step:

prisma generate  # Must run after schema changes

VibORM infers types directly from your schema definition:

const schema = {
  user: s.model({ ... }),
  post: s.model({ ... }),
};

const orm = createClient({ schema, driver });  // Types inferred immediately

Benefits:

  • Instant feedback: Types update on save
  • No build step: Simpler CI/CD
  • No generated files: Cleaner repository

Connection to Other Layers

  • L2-L4 (Schema): Client infers types from schema definitions
  • L3 (Query Schemas): Client validates args at runtime
  • L6 (Query Engine): Client routes operations to query engine
  • L8 (Drivers): Query results flow back through client

Was this page helpful?