Lifecycle hooks
Merinaa classes can declare four lifecycle hooks by implementing one or more of these interfaces. The framework calls them in a deterministic order during startup.
Order
1. DI container instantiates all providers / controllers
2. For every instance: onModuleInit()
3. Event handlers (@OnClient, @OnServer, @On) are wired to the event bus
4. For every instance: onApplicationBootstrap()
5. DatabaseModule connects + runs migrations
6. For every method decorated with @OnReady: the method is invoked
7. Resource stop: for every instance with onApplicationShutdown: the method is invoked
Example
import { Controller, OnReady } from '@merinaa/core';
import type { OnModuleInit, OnApplicationShutdown } from '@merinaa/core';
@Controller()
export class BankController implements OnModuleInit, OnApplicationShutdown {
private tickId: number | null = null;
onModuleInit(): void {
// DI has wired dependencies but no events are live yet.
this.loadStaticData();
}
@OnReady()
async seedBankAccounts(): Promise<void> {
// DB is connected. Safe to query.
await this.bankService.seedDefaultAccounts();
}
onApplicationShutdown(): void {
if (this.tickId !== null) clearInterval(this.tickId);
}
}
When to use which
-
Config-only work (read
ConfigService, cache a setting):onModuleInit. -
Cross-module coordination (need another module's service to exist):
onApplicationBootstrap. -
Database-dependent setup (seed rows, backfill data):
@OnReady(). -
Cleanup (stop timers, close sockets):
onApplicationShutdown.
@OnReady vs lifecycle interfaces
@OnReady() is a method decorator, not a class interface. It lets you
pick which specific method runs when the DB is ready — useful when a
controller has multiple startup responsibilities and you want them at
different phases.
@Controller()
export class WorldController {
onModuleInit() { /* static setup */ }
@OnReady()
async loadDynamicData() { /* DB query */ }
}