Schema System
How VibORM's schema definition system works
Schema System
The schema system is the foundation of VibORM. It provides a chainable, type-safe API for defining database models.
Core Components
Model Definition
Basic Model
const user = s.model({
id: s.string().id().ulid(),
name: s.string(),
email: s.string().unique(),
age: s.int().nullable(),
});What Happens Internally
Model State Generic
The Model class uses a single State generic that encapsulates all model metadata:
interface ModelState<
TFields extends FieldRecord = FieldRecord,
TCompoundId extends CompoundConstraint | undefined = undefined,
TCompoundUniques extends readonly CompoundConstraint[] = []
> {
fields: TFields;
compoundId: TCompoundId;
compoundUniques: TCompoundUniques;
}
class Model<State extends ModelState = ModelState> {
// State is used for type inference
}This allows chainable methods to update the state:
const user = s.model({ ... }) // Model<{ fields: {...}, compoundId: undefined, compoundUniques: [] }>
.id(["email", "orgId"]) // Model<{ fields: {...}, compoundId: { fields: [...], name: undefined }, ... }>
.unique(["name"], { name: "uq" }) // Model<{ fields: {...}, ..., compoundUniques: [{ fields: [...], name: "uq" }] }>Field System
Field State Machine
Each field has a state that tracks its configuration:
Field Types
Field Schema Generation
Each field generates multiple schemas based on its state:
// StringField with state { nullable: false, hasDefault: false }
get schemas() {
return {
base: stringBase, // string
filter: stringFilter, // { equals?, contains?, ... } | string
create: stringCreate, // string (required)
update: stringUpdate, // { set?: string } | string
};
}
// StringField with state { nullable: true, hasDefault: true }
get schemas() {
return {
base: stringNullable, // string | null
filter: stringNullableFilter, // { equals?, ... } | string | null
create: stringOptionalNullableCreate, // string | null | undefined
update: stringNullableUpdate, // { set?: string | null } | string | null
};
}Relation System
Relation Types
Relation Class Hierarchy
// Base relation with common functionality
abstract class Relation<M, RT, Optional> {
protected _getter: () => Model<any>;
protected _relationType: RT;
protected _isOptional: Optional;
// ...
}
// To-one relations (oneToOne, manyToOne)
class ToOneRelation<M, RT, Optional> extends Relation<M, RT, Optional> {
optional(): ToOneRelation<M, RT, true>;
fields(...fields: string[]): this;
references(...refs: string[]): this;
onDelete(action: ReferentialAction): this;
onUpdate(action: ReferentialAction): this;
}
// OneToMany relation
class OneToManyRelation<M> extends Relation<M, "oneToMany", false> {
// No optional() - always returns array
}
// ManyToMany relation
class ManyToManyRelation<M> extends Relation<M, "manyToMany", false> {
through(tableName: string): this;
A(columnName: string): this;
B(columnName: string): this;
}The ~ Property
Every model, field, and relation exposes internal state via the ~ property:
// Access model internals
model["~"].fields; // Map<string, Field>
model["~"].relations; // Map<string, Relation>
model["~"].fieldMap; // Same as fields
model["~"].compoundId; // CompoundConstraint | undefined
model["~"].schemas; // TypedModelSchemas (lazy built)
// Access field internals
field["~"].state; // FieldState
field["~"].schemas; // { base, filter, create, update }
field["~"].nativeType; // NativeType | undefined
// Access relation internals
relation["~"].getter; // () => Model
relation["~"].relationType; // "oneToOne" | "oneToMany" | ...
relation["~"].isOptional; // booleanThis convention:
- Keeps public API clean
- Provides access for internal tools
- Uses
~to indicate "internal" (like Rust'sunsafe)
Lazy Schema Building
Model schemas are built lazily on first access:
This enables:
- Circular reference handling (relations between models)
- Performance (schemas built once per model)
- Memory efficiency (unused models don't build schemas)