Skip to content

Database Events

Stratal automatically emits events before and after every database operation. The EventEmitterPlugin is auto-registered by the DatabaseModule — no additional setup is required. These events integrate with the core event system and support the same pattern matching, priority, and blocking behavior.

Database events follow the pattern {phase}.{Model}.{operation}:

SegmentValuesExample
Phasebefore, afterbefore, after
ModelAny model name from your schemaUser, Post
Operationcreate, update, delete, etc.create

A full event name looks like after.User.create or before.Post.update.

Use the @Listener() and @On() decorators from the core event system:

import { Listener, On } from 'stratal/events'
import type { EventContext } from 'stratal/events'
@Listener()
export class UserEventListener {
@On('after.User.create')
async onUserCreated(context: EventContext<'after.User.create'>) {
const user = context.result
// send welcome email, create audit log, etc.
}
@On('before.User.delete')
async onBeforeUserDelete(context: EventContext<'before.User.delete'>) {
const data = context.data
// archive user data before deletion
}
}

Register the listener in a module’s providers array:

@Module({
providers: [UserEventListener],
})
export class UsersModule {}

Database events support the same hierarchical pattern matching as core events:

Listen to all operations on a specific model:

@On('after.User')
async onAnyUserChange(context: EventContext<'after.User'>) {
// context.operation tells you which operation triggered the event
console.log(`User ${context.operation} completed`)
}

Listen to a specific operation across all models:

@On('after.create')
async onAnyCreate(context: EventContext<'after.create'>) {
// context.model tells you which model was created
console.log(`${context.model} created`)
}

Listen to all events in a phase:

@On('after')
async onAnyAfterEvent(context: EventContext<'after'>) {
console.log(`${context.model}.${context.operation} completed`)
}

The event context is a discriminated union based on the event pattern:

interface ExactEventContext {
data: UserCreateInput // before: mutable, after: readonly
result: User // only available in after phase
}
interface ModelWildcardContext {
operation: DatabaseOperation // 'create' | 'update' | 'delete' | ...
data: unknown
result: unknown
}
interface OperationWildcardContext {
model: ModelName // 'User' | 'Post' | ...
data: unknown
result: unknown
}
interface PhaseWildcardContext {
model: ModelName
operation: DatabaseOperation
data: unknown
result: unknown
}

Database events follow the same blocking rules as core events:

  • before.* events are always blocking. The database operation waits for all handlers to complete before proceeding.
  • after.* events are non-blocking by default. Handlers run in the background via waitUntil.

This means before handlers can validate or modify data before it reaches the database, while after handlers run without delaying the response.

The framework automatically augments the core CustomEventRegistry with your database events based on the StratalDatabase schema augmentation. Once you’ve set up database type augmentation, your database events are automatically type-safe.

  • Events for the core event system.
  • Database for database configuration.
  • Auth for authentication integration.