SlintORM
Docsnpm

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

HookReturnsDescription
onCreateBefore(data: T)T | void | Promise<T | void>Runs before INSERT. Return value replaces data.
onCreateAfter(item: T)void | Promise<void>Runs after INSERT with the created row.
onUpdateBefore(oldData: T, newData: Partial<T>)Partial<T> | void | Promise<...>Runs before UPDATE. Return value replaces newData.
onUpdateAfter(oldData: T, newData: Partial<T>)void | Promise<void>Runs after UPDATE.
onDeleteBefore(deleted: T)voidRuns before DELETE with the row being deleted.
onDeleteAfter(deleted: T)voidRuns after DELETE.

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
  },
});
PreviousValidationNextTransactions