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' })
})