Models

Define your data models with type-safe schemas

Basic Model Definition

Models represent your database tables and define their structure using defineModel:

models/user.ts
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:

types.ts
DataTypes.STRING // VARCHAR - short text with optional length
DataTypes.TEXT // TEXT - long text content
DataTypes.INTEGER // INTEGER - whole numbers
DataTypes.BIGINT // BIGINT - large whole numbers
DataTypes.FLOAT // FLOAT - single-precision decimal
DataTypes.DOUBLE // DOUBLE - double-precision decimal
DataTypes.DECIMAL // DECIMAL - exact decimal (e.g. currency)
DataTypes.BOOLEAN // BOOLEAN - true/false values
DataTypes.DATE // DATE - date only
DataTypes.DATETIME // DATETIME - date and time
DataTypes.JSON // JSON - JSON data
DataTypes.UUID // UUID - universally unique identifier
DataTypes.BLOB // BLOB - binary data

Column Options

Each column supports these configuration options:

column-options.ts
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:

models/post.ts
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:

models/product.ts
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 timestamp
await repo.recover(id); // Clears deletedAt
await repo.recoverAll(); // Restores all soft-deleted

Model with Versioning

Set versioned: true to enable automatic history tracking. A <table>_history table is created to store all changes:

models/document.ts
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 versions
const past = await repo.asOf(id, someDate); // Time-travel query
await repo.rollback(id, version); // Restore old version

Model with Scopes

Define reusable query filters as scopes. Scopes can accept parameters:

models/task.ts
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:

models/relationships.ts
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