CORE CONCEPTS
Lifecycle Hooks
Extend Provider to get onInit()/onDestroy() lifecycle hooks called automatically by the DI container.
import { Provider } from '@rasenganjs/server'; export class DatabaseService extends Provider { connection?: Connection; async onInit() { this.connection = await createConnection(process.env.DATABASE_URL); } async onDestroy() { await this.connection?.close(); } }
Why Extend Provider at All?
A plain class works fine as a provider. Extending Provider is purely opt-in for lifecycle hooks. Gateway (@rasenganjs/ws) and Queue (@rasenganjs/queue) both extend Provider themselves, which is exactly why their onInit()/onDestroy() fire like any other provider's.
Every Provider Is Resolved Eagerly at Boot
Unlike lazy-by-default DI containers, Rasengan Server constructs every declared provider during compile() rather than just the ones something happens to inject. This means:
onInit()fires for providers nothing else injects (schedulers, background warmers), you don't need a dummy injection just to trigger startup.- A broken constructor or an unresolvable dependency fails at boot, not on the first request that happens to touch it.
Hook Order
onInit(): called in registration order, once compilation finishes wiring routes and providers together.onDestroy(): called in reverse registration order during graceful shutdown (SIGTERM/SIGINT, handled automatically bybootstrap()), so dependents clean up before their dependencies do.
Circular Dependency Detection
Constructing a provider whose dependency chain loops back on itself is caught immediately, with the full chain in the error:
[rasengan-server] Circular dependency detected: ServiceA → ServiceB → ServiceA
What Runs When
export class MetricsService extends Provider { async onInit() { console.log('MetricsService ready'); // fires during bootstrap(), before requests are served } async onDestroy() { console.log('MetricsService shutting down'); // fires on SIGTERM/SIGINT } }
App-level hooks registered via app.onInit()/app.onDestroy() on ServerApp itself are separate from provider lifecycle hooks. They're forwarded straight to the underlying Futon instance and fire alongside, not instead of, provider hooks.
