Skip to main content

Database Hooks

Hooks let you intercept database mutations (insert, update, delete) without modifying repository code. The system uses a Proxy to wrap the Drizzle database instance.

wrapDbWithHooks

Wraps a getDbContext function so every db instance it returns is proxied with hooks.

import { wrapDbWithHooks } from '@repo/db'
import type { DatabaseHooks } from '@repo/db'

const hooks: DatabaseHooks = {
onMutation: async (context, execute) => {
console.log(`${context.operation} on ${context.table}`)
return execute()
},
}

const getDbContextWithHooks = wrapDbWithHooks(getDbContext, hooks)

The returned function has the same signature as the original getDbContext. The db property on its return value is proxied.

DatabaseHooks

interface DatabaseHooks {
onMutation?: MutationHook
onSetUpdate?: SetUpdateHook
onSetInsert?: SetInsertHook
}

onMutation

Called before execution of any insert, update, or delete. Receives a HooksContext and an execute function. You must call execute() to proceed.

import type { MutationHook } from '@repo/db'

const onMutation: MutationHook = async (context, execute) => {
// context.db -- the database instance
// context.table -- the PgTable being operated on
// context.operation -- 'insert' | 'update' | 'delete'
// context.values -- values for insert/update (if applicable)
// context.where -- SQL where clause (if applicable)

const result = await execute()
return result
}

onSetUpdate

Intercepts the .set(values) call on an update query. Return the (possibly modified) values.

import type { SetUpdateHook } from '@repo/db'

const onSetUpdate: SetUpdateHook = (values, context) => {
// context.db, context.table, context.values available
return { ...values, updatedAt: new Date() }
}

onSetInsert

Intercepts the .values(values) call on an insert query. Return the (possibly modified) values.

import type { SetInsertHook } from '@repo/db'

const onSetInsert: SetInsertHook = (values, context) => {
return { ...values, createdAt: new Date() }
}

HooksContext

interface HooksContext<TTable, TDb> {
db: TDb
table: TTable
operation: 'insert' | 'update' | 'delete'
values?: TTable['$inferInsert'] | TTable['$inferInsert'][]
where?: SQL
}

Transaction Support

The proxy also wraps transactions. Any db.transaction(...) call will proxy the tx object with the same hooks:

await db.transaction(async (tx) => {
// tx is also proxied -- hooks fire for operations inside the transaction
await tx.insert(users).values({ name: 'Alice' })
})