Migration & Seeding Events
Migrations and seeders emit structured events through the shared EventBus. Subscribe once to stream progress to logs, metrics, or tracing.
Prerequisites
What You’ll Learn
- Which migration/seeding events are emitted
- How to subscribe and route event streams
- How to use lifecycle events for observability and auditing
Step Outcome
By the end of this page, you should be able to:
- Subscribe to migration/seeding lifecycle streams once at startup
- Route failures and progress into logs/metrics/traces
- Correlate migration/seeding events with runtime query telemetry
Snippet context
The snippets below are split into “subscribe” and “run” pieces so you can reuse the listener wiring in your own bootstrap code.
Migration Lifecycle
Events (all emitted by MigrationRunner):
MigrationBatchStartedEvent/MigrationBatchCompletedEventMigrationStartedEvent/MigrationCompletedEventMigrationFailedEvent(includeserrorandstackTrace)
Example listener + runner setup:
- Subscribe
- Run
bus.on<MigrationBatchStartedEvent>((event) {
print('Migration batch ${event.batch} started');
});
bus.on<MigrationStartedEvent>((event) {
print('Applying ${event.id}');
});
bus.on<MigrationCompletedEvent>((event) {
print('Applied ${event.id} in ${event.duration.inMilliseconds}ms');
});
bus.on<MigrationFailedEvent>((event) {
print('Failed ${event.id}: ${event.error}');
});
final descriptors = MigrationEntry.buildDescriptors([
MigrationEntry(
id: MigrationId.parse('m_20241201000000_create_users_table'),
migration: const CreateUsersTable(),
),
]);
final runner = MigrationRunner(
schemaDriver: schemaDriver,
ledger: SqlMigrationLedger(schemaDriver, tableName: '_orm_migrations'),
migrations: descriptors,
events: bus,
);
await runner.applyAll();
Seeding Lifecycle
Events (emitted by SeederRunner and database seeders):
SeedingStartedEvent/SeedingCompletedEventSeederStartedEvent/SeederCompletedEventSeederFailedEvent
Example with a custom seeder and event hooks:
Define a seeder
class DemoUserSeeder extends DatabaseSeeder {
DemoUserSeeder(super.connection);
Future<void> run() async {
await seed<User>([
{'name': 'Demo', 'email': 'demo@example.com'},
]);
}
}
- Subscribe
- Run
bus.on<SeedingStartedEvent>((event) {
print('Seeding started: ${event.seederNames.join(', ')}');
});
bus.on<SeederStartedEvent>((event) {
print('Running seeder ${event.seederName} (${event.index}/${event.total})');
});
bus.on<SeederCompletedEvent>((event) {
print('Seeder ${event.seederName} finished in ${event.duration}');
});
bus.on<SeederFailedEvent>((event) {
print('Seeder ${event.seederName} failed: ${event.error}');
});
bus.on<SeedingCompletedEvent>((event) {
print('Seeding complete (${event.count} seeders) in ${event.duration}');
});
final runner = SeederRunner(events: bus);
await runner.run(
connection: dataSource.connection,
seeders: const [
SeederRegistration(name: 'DemoUserSeeder', factory: DemoUserSeeder.new),
],
);
Tips:
- Use the same
EventBusfor migrations, seeders, and runtime queries to correlate logs. SeederFailedEventfires before the error is rethrown—log here, then let your process fail.- Combine with
pretend: trueonSeederRunner.runto capture SQL without mutating the database.
Verify Event Stream
- Run one migration batch and confirm batch + per-migration events are emitted.
- Run one seed flow and confirm start/completion/failure hooks.
- Confirm failed events include enough context for alerting.