Skip to main content

Hooks

Every service receives lifecycle on TServiceParams. It exposes seven registration methods — one per stage. All accept an optional priority number.

type TLifeCycleRegister = (callback: () => void | Promise<void>, priority?: number) => void;

onPreInit​

Runs at PreInit — after all services are wired, before configuration is sourced from external sources.

lifecycle.onPreInit(() => {
// Config defaults are available; env/file values are not yet applied
// Use to: adjust config sources, register custom config loaders
});

onPostConfig​

Runs at PostConfig — configuration is validated and fully applied. This is the first stage where reading config.* values is meaningful.

lifecycle.onPostConfig(() => {
const url = config.my_app.DATABASE_URL;
logger.info({ url }, "config loaded");
});

If a required: true config entry has no value, bootstrap halts with REQUIRED_CONFIGURATION_MISSING before this stage runs.

onBootstrap​

Runs at Bootstrap — main initialization. Safe to open connections, load data, start background work. Supports async.

lifecycle.onBootstrap(async () => {
await db.connect();
await cache.warmUp();
logger.info("connections established");
});

onReady​

Runs at Ready — application is fully started. Safe to start serving traffic, start cron jobs, emit "application ready" notifications.

lifecycle.onReady(() => {
server.listen(config.my_app.PORT);
logger.info({ port: config.my_app.PORT }, "server listening");
});

Note: the scheduler service only activates at Ready — scheduled jobs registered before this stage fire from Ready onward.

onPreShutdown​

Runs at PreShutdown — first shutdown signal. Stop accepting new work.

lifecycle.onPreShutdown(() => {
server.close(); // stop accepting new connections
queue.pause(); // pause intake
});

onShutdownStart​

Runs at ShutdownStart — flush and close resources. Supports async.

lifecycle.onShutdownStart(async () => {
await db.end(); // close connection pool
await queue.drain(); // finish pending work
await cache.flush(); // flush writes
});

onShutdownComplete​

Runs at ShutdownComplete — final best-effort cleanup. Errors here are logged but don't affect the shutdown sequence.

lifecycle.onShutdownComplete(() => {
logger.info("shutdown complete");
});

Priority​

All hook methods accept an optional second argument: a priority number. See Execution Order for the full priority semantics.

// Runs before unprioritized callbacks in the same stage
lifecycle.onBootstrap(connectDatabase, 100);

// Runs after unprioritized callbacks
lifecycle.onBootstrap(optionalMetrics, -10);

Late registration​

If you register a callback for a startup stage that has already completed, the callback fires immediately. If you register for a shutdown stage that has already completed, the callback is silently dropped.

TParentLifecycle.child()​

Advanced use: lifecycle.child() creates a scoped child lifecycle. The child's callbacks are wired into the parent's stages but can be independently managed. Useful for sub-systems that need to run lifecycle hooks within a larger service.

const childLifecycle = lifecycle.child();
childLifecycle.onBootstrap(() => { /* scoped setup */ });
childLifecycle.onShutdownStart(() => { /* scoped teardown */ });