Models
Define your data models with type-safe schemas
Basic Model Definition
Models represent your database tables and define their structure using defineModel:
import { defineModel, DataTypes } from "stabilize-orm";
const User = defineModel({ tableName: "users", timestamps: { createdAt: "createdAt", updatedAt: "updatedAt" }, columns: { id: { type: DataTypes.STRING, required: true, unique: true, }, email: { type: DataTypes.STRING, length: 255, required: true, unique: true, }, name: { type: DataTypes.STRING, length: 100, required: true, }, bio: { type: DataTypes.TEXT, }, isActive: { type: DataTypes.BOOLEAN, defaultValue: true, }, },});
export { User };Available Data Types
Stabilize provides these database-agnostic data types. They map to database-specific types automatically:
DataTypes.STRING // VARCHAR - short text with optional lengthDataTypes.TEXT // TEXT - long text contentDataTypes.INTEGER // INTEGER - whole numbersDataTypes.BIGINT // BIGINT - large whole numbersDataTypes.FLOAT // FLOAT - single-precision decimalDataTypes.DOUBLE // DOUBLE - double-precision decimalDataTypes.DECIMAL // DECIMAL - exact decimal (e.g. currency)DataTypes.BOOLEAN // BOOLEAN - true/false valuesDataTypes.DATE // DATE - date onlyDataTypes.DATETIME // DATETIME - date and timeDataTypes.JSON // JSON - JSON dataDataTypes.UUID // UUID - universally unique identifierDataTypes.BLOB // BLOB - binary dataColumn Options
Each column supports these configuration options:
const User = defineModel({ tableName: "users", columns: { id: { type: DataTypes.STRING, required: true, // NOT NULL constraint unique: true, // UNIQUE constraint }, email: { type: DataTypes.STRING, length: 255, // VARCHAR(255) required: true, unique: true, pattern: /^[^@]+@[^@]+\.[^@]+$/, // Regex validation customValidator: (val: string) => val.includes("@") || "Must be a valid email", }, name: { type: DataTypes.STRING, minLength: 2, // Minimum string length maxLength: 100, // Maximum string length }, status: { type: DataTypes.STRING, defaultValue: "active", // Default value on insert }, metadata: { type: DataTypes.JSON, encrypted: true, // Field-level encryption }, version: { type: DataTypes.INTEGER, optimisticLock: true, // Optimistic concurrency control }, },});Model with Timestamps
Enable automatic timestamp management with the timestamps config. These fields are set automatically on create and update:
const Post = defineModel({ tableName: "posts", timestamps: { createdAt: "createdAt", updatedAt: "updatedAt", }, columns: { id: { type: DataTypes.STRING, required: true, unique: true }, title: { type: DataTypes.STRING, length: 255, required: true }, body: { type: DataTypes.TEXT }, },});Who assigns the id? A DataTypes.STRING or DataTypes.UUID id is supplied by the caller, so it is required — create({ title: "x" }) fails with Field id is required. Use DataTypes.INTEGER instead and the database generates it, so you omit it from the payload. The type alone decides which, and it also changes the emitted column — a STRING id becomes UUID on Postgres but a text key elsewhere. See Data Types.
Model with Soft Deletes
Add a deletedAt column with softDelete: true to enable soft deletes. Queries automatically exclude soft-deleted rows:
const Product = defineModel({ tableName: "products", columns: { id: { type: DataTypes.STRING, required: true, unique: true }, name: { type: DataTypes.STRING, length: 255, required: true }, price: { type: DataTypes.DECIMAL, required: true }, deletedAt: { type: DataTypes.DATETIME, softDelete: true }, },});
// Usage:const repo = orm.getRepository(Product);await repo.delete(id); // Sets deletedAt timestampawait repo.recover(id); // Clears deletedAtawait repo.recoverAll(); // Restores all soft-deletedModel with Versioning
Set versioned: true to enable automatic history tracking. A <table>_history table is created to store all changes:
const Document = defineModel({ tableName: "documents", versioned: true, columns: { id: { type: DataTypes.STRING, required: true, unique: true }, title: { type: DataTypes.STRING, length: 255, required: true }, content: { type: DataTypes.TEXT }, },});
// Usage:const repo = orm.getRepository(Document);const history = await repo.history(id); // All versionsconst past = await repo.asOf(id, someDate); // Time-travel queryawait repo.rollback(id, version); // Restore old versionModel with Scopes
Define reusable query filters as scopes. Scopes can accept parameters:
const Task = defineModel({ tableName: "tasks", columns: { id: { type: DataTypes.STRING, required: true, unique: true }, title: { type: DataTypes.STRING, length: 200, required: true }, status: { type: DataTypes.STRING, defaultValue: "todo" }, priority: { type: DataTypes.STRING, defaultValue: "medium" }, dueDate: { type: DataTypes.DATETIME }, deletedAt: { type: DataTypes.DATETIME, softDelete: true }, }, scopes: { todo: (qb) => qb.where("status = ?", "todo"), inProgress: (qb) => qb.where("status = ?", "in_progress"), done: (qb) => qb.where("status = ?", "done"), highPriority: (qb) => qb.where("priority = ?", "high"), overdue: (qb) => qb.where("dueDate < ?", new Date().toISOString()), byPriority: (qb, level: string) => qb.where("priority = ?", level), },});
// Usage:const repo = orm.getRepository(Task);const todos = await repo.scope("todo").execute(orm.client);const urgent = await repo.scope("highPriority").scope("overdue").execute(orm.client);Model with Relationships
Define relationships between models using RelationType:
import { defineModel, DataTypes, RelationType } from "stabilize-orm";
const User = defineModel({ tableName: "users", columns: { id: { type: DataTypes.STRING, required: true, unique: true }, name: { type: DataTypes.STRING, length: 100 }, }, relations: [ { type: RelationType.OneToMany, target: () => Post, property: "posts", foreignKey: "authorId", }, ],});
const Post = defineModel({ tableName: "posts", columns: { id: { type: DataTypes.STRING, required: true, unique: true }, title: { type: DataTypes.STRING, length: 200 }, authorId: { type: DataTypes.STRING, required: true }, }, relations: [ { type: RelationType.ManyToOne, target: () => User, property: "author", foreignKey: "authorId", }, ],});
// Relation types: OneToOne, ManyToOne, OneToMany, ManyToMany