Skip to main content

Model Hooks & Lifecycle

Hooks allow you to run logic before or after certain model actions. Define them in the hooks key of defineModel.

note

Hooks do not apply to joined models in queries from other models.

// Post model hooks won't run here
const users = await sql
.from(User)
.join("posts", "posts.userId", "users.id")
.many();

Available Hooks

HookSignatureDescription
beforeFetch(qb: ModelQueryBuilder) => void | Promise<void>Modify query before fetching
afterFetch(data: T[]) => T[] | Promise<T[]>Transform results after fetching
beforeInsert(data: Partial<T>) => void | Promise<void>Modify data before insert
beforeInsertMany(data: Partial<T>[]) => void | Promise<void>Modify data before bulk insert
beforeUpdate(qb: ModelQueryBuilder) => void | Promise<void>Modify query before update
beforeDelete(qb: ModelQueryBuilder) => void | Promise<void>Modify query before delete

Where T is inferred from your columns definition, giving you typed data inside hook callbacks.

Example: Soft Delete Filtering

import { defineModel, col } from "hysteria-orm";

const User = defineModel("users", {
columns: {
id: col.increment(),
name: col.string(),
isAdmin: col.boolean(),
deletedAt: col.datetime(),
},
hooks: {
beforeFetch(qb) {
qb.whereNull("users.deleted_at");
},
beforeInsert(data) {
data.isAdmin = false;
},
afterFetch(data) {
return data.filter((user) => user.deletedAt === null);
},
beforeUpdate(qb) {
// e.g., add conditions before any update
},
beforeDelete(qb) {
// e.g., add conditions before any delete
},
},
});

Ignoring Hooks

You can bypass hooks when needed using the ignoreHooks option:

// Fetch soft-deleted records by ignoring beforeFetch hook
const allUsers = await sql.from(User).many({ ignoreHooks: ["beforeFetch"] });

Next: Model Mixins