VibORM

L2 - Fields

Field type definitions with State generic for compile-time tracking

Location: src/schema/fields/

Why This Layer Exists

Fields need to preserve type information as users chain modifiers:

s.string()           // StringField<{type: "string"}>
  .nullable()        // StringField<{type: "string", nullable: true}>  
  .default("hello")  // StringField<{..., default: "hello"}>

Each modifier returns a new instance with updated types. TypeScript tracks this through the State generic, enabling fully typed queries without code generation.

The State Generic Pattern

Every field carries its configuration as a type parameter:

class StringField<S extends StringFieldState> {
  // S contains: type, nullable, optional, default, unique, id, etc.
}

When you call .nullable(), a new field is created:

nullable(): StringField<S & { nullable: true }> {
  return new StringField({ ...this.state, nullable: true });
}

The key insight: immutability enables type tracking. If we mutated the field, TypeScript couldn't know the type changed.

Available Field Types

FactoryDatabase TypeTypeScript Type
s.string()VARCHAR/TEXTstring
s.int()INTEGERnumber
s.bigint()BIGINTbigint
s.float()FLOAT/DOUBLEnumber
s.decimal()DECIMALstring
s.boolean()BOOLEANboolean
s.datetime()DATETIME/TIMESTAMPDate
s.json<T>()JSON/JSONBT
s.enum(values)ENUM/VARCHARvalues[number]
s.blob()BLOB/BYTEAUint8Array
s.point()POINT{ x, y }
s.vector(dim)VECTORnumber[]

Common Modifiers

Modifiers work across field types where applicable:

ModifierPurpose
.nullable()Allow NULL values
.default(value)Set default value
.id()Mark as primary key
.unique()Add unique constraint
.map(columnName)Map to different column name
.auto.uuid()Auto-generate UUIDs
.auto.ulid()Auto-generate ULIDs

Lazy Schema Building

Fields don't build validation schemas immediately - that would be slow. Instead, schemas are built on first access:

// Internal pattern
get ["~"]() {
  this._schemas ??= buildSchemas(this.state);  // Built once, cached
  return this._schemas;
}

This matters because VibORM creates many field instances during schema definition, but only builds validation schemas when actually needed for queries.

Connection to Other Layers

  • L1 (Validation): Fields use v.* primitives internally
  • L3 (Query Schemas): Query schemas compose field schemas for where/create/update
  • L7 (Adapters): Field types determine SQL column types
  • L10 (Migrations): Field state drives DDL generation

On this page