Hooks
ModelHooks<T> provides 6 lifecycle hooks passed as the third argument to defineModel(). All hooks support async. onCreateBefore and onUpdateBefore can mutate data by returning a new value.
Hook signatures
All hooks example
// ModelHooks<T> — all 6 lifecycle hooks
// Passed as third argument to orm.defineModel()
const User = orm.defineModel<User>('users', 'User', {
// onCreateBefore: runs before INSERT
// Returning a value replaces the data being inserted
async onCreateBefore(data: User): Promise<User | void> {
return { ...data, email: data.email.toLowerCase() };
},
// onCreateAfter: runs after INSERT, receives the inserted row
async onCreateAfter(item: User): Promise<void> {
console.log('Created user:', item.id);
},
// onUpdateBefore: runs before UPDATE
// Returning a value replaces the newData being applied
async onUpdateBefore(oldData: User, newData: Partial<User>): Promise<Partial<User> | void> {
// No return needed if not mutating
console.log('Updating user', oldData.id);
},
// onUpdateAfter: runs after UPDATE
async onUpdateAfter(oldData: User, newData: Partial<User>): Promise<void> {
console.log('Updated user', oldData.id);
},
// onDeleteBefore: runs before DELETE
onDeleteBefore(deleted: User): void {
console.log('About to delete user:', deleted.id);
},
// onDeleteAfter: runs after DELETE
onDeleteAfter(deleted: User): void {
console.log('Deleted user:', deleted.id);
},
});Password hashing hook
// Hash password before insert and update
import bcrypt from 'bcrypt';
const User = orm.defineModel<User>('users', 'User', {
async onCreateBefore(data) {
if (data.password) {
return { ...data, password: await bcrypt.hash(data.password, 12) };
}
},
async onUpdateBefore(oldData, newData) {
if (newData.password) {
return { ...newData, password: await bcrypt.hash(newData.password, 12) };
}
// No return = newData used as-is
},
});Audit log hook
// Audit log hook — record every change to a separate table
const AuditLog = orm.defineModel<AuditLog>('audit_logs', 'AuditLog');
const Post = orm.defineModel<Post>('posts', 'Post', {
async onCreateAfter(item) {
await AuditLog.insert({
action: 'create',
entityType: 'Post',
entityId: item.id,
data: JSON.stringify(item),
});
},
async onUpdateAfter(oldData, newData) {
await AuditLog.insert({
action: 'update',
entityType: 'Post',
entityId: oldData.id,
oldData: JSON.stringify(oldData),
newData: JSON.stringify(newData),
});
},
async onDeleteAfter(deleted) {
await AuditLog.insert({
action: 'delete',
entityType: 'Post',
entityId: deleted.id,
data: JSON.stringify(deleted),
});
},
});Email notification hook
// Send welcome email after user creation
import { sendEmail } from '@/lib/email';
const User = orm.defineModel<User>('users', 'User', {
async onCreateAfter(item) {
await sendEmail({
to: item.email,
subject: 'Welcome to the platform',
template: 'welcome',
data: { name: item.name },
});
},
});Mutation via return value
// Mutation via return value
// onCreateBefore and onUpdateBefore can return a new object
// that replaces the data being written
const Product = orm.defineModel<Product>('products', 'Product', {
onCreateBefore(data) {
// Normalize slug before insert
return {
...data,
slug: data.name.toLowerCase().replace(/\s+/g, '-'),
createdBy: getCurrentUserId(),
};
},
onUpdateBefore(oldData, newData) {
// If name changed, regenerate slug
if (newData.name && newData.name !== oldData.name) {
return {
...newData,
slug: newData.name.toLowerCase().replace(/\s+/g, '-'),
};
}
// Return nothing = use newData unchanged
},
});