Skip to main content

Lifecycle

The lifecycle is available on TServiceParams, and helps to coordinate the way various elements of the application load relative to each other. It is divided into distinct phases, and different callbacks may be prioritized relative to each other to ensure all your dependencies have what they need, when they need it.

🌐 Phase overview

Lifecycle PhasePhaseDescriptionExample Use
constructbootstrapAn informal phase, describes the time when services are building their return output. Code should be limited to general configuration & wiring. Note: This is not a formal lifecycle event - there is no onConstruct hook.Code definitions & function wiring
onPreInitbootstrapRan prior to gathering user configuration. Frequently used with alternate application flows.1. check for --help flag, print configuration info, exit
(configure)bootstrapWhen the Configuration does its processing to load user configurations.
onPostConfigbootstrapUser configs are populated into the library, libraries & applications can start utilizing that information to further self-configure / initialize dependencies.Create a reference to an external library (ex: fastify)
onBootstrapbootstrapConfigured libraries are available, and can be interacted with.Perform wiring actions with that library (ex: add routes)
onReadybootstrapLast chance to perform work before the application is officially "running".Start servers / listen to ports / emit "hello world" messages to connected services
(running)running
onPreShutdownteardownApplication just received teardown call, and intends to start the processEmit goodbye messages, save caches
onShutdownStartteardownThe application has received a request to shut down.Stop servers, close connections
onShutdownCompleteteardownLast second to do something before the process exits (or the test completes).Log a goodbye message that shows the total uptime

🎖 With priorities

Callbacks for lifecycle events have no guaranteed order for when they run relative to each other (within the same event), unless explicitly provided an order.

If you attach a callback to a lifecycle event after it already occurred, the callback will be run immediately. This can be used as an "at least this far in the startup process" guarantee for code

function MyService({ logger, lifecycle }: TServiceParams) {
// sorted into execution order

lifecycle.onBootstrap(() => {
logger.info("I happen first");
}, 2);

lifecycle.onBootstrap(() => {
logger.info("I happen early");
}, 1);

lifecycle.onBootstrap(() => {
logger.info("I exist too");
}, 0);

// in between 0 & negative numbers
lifecycle.onBootstrap(() => {
logger.info("I happen after the priority callbacks");
});

lifecycle.onBootstrap(() => {
logger.info("I happen late");
}, -1);

lifecycle.onBootstrap(() => {
logger.info("I happen really late");
}, -2);
}

⚙️ Implementation Details

Lifecycle events are executed in three phases:

  1. Positive priorities (high to low) - executed sequentially
  2. No priority - executed in parallel
  3. Negative priorities (high to low) - executed sequentially

After execution, events are cleaned up and removed from the lifecycle instance.

🏎️ Boot Libraries First

bootLibrariesFirst is a flag that can be passed to the bootstrap command of your application.

By passing this flag, the construction phase will be moved:

  • from: just after all the libraries are built
  • to: just after onBootstrap has completed

Applications

By setting this flag, you get to ignore a lot of the library bootstrap process.

  • everything is configured from the start
  • home assistant services can be called any time
  • entities already have state

💡 For home automation applications, it is recommended to set this

myApplication.bootstrap({ bootLibrariesFirst: true });

Libraries

Working with the lifecycle events to ensure your resources are available at the correct time is essential. Internally wrapping a lifecycle call can be an easy way to provide a clean api while accounting for the lifecycle complexities.

function SpecialBuilder({ logger, lifecycle }: TServiceParams) {

return function(data: ExampleOptions) {
lifecycle.onReady(async () => {
// waits for ready before running
// run immediately if ready already complete
})
}
}

🔧 Advanced Usage

Conditional Lifecycle Registration

function ConditionalService({ logger, lifecycle, internal }: TServiceParams) {
// Only register if we're in a specific phase
if (internal.boot.phase === "bootstrap") {
lifecycle.onReady(() => {
logger.info("Application is ready!");
});
}
}

Debugging Lifecycle Issues

function DebugService({ logger, lifecycle, internal }: TServiceParams) {
lifecycle.onBootstrap(() => {
// Check what events have completed
const completed = [...internal.boot.completedLifecycleEvents];
logger.debug("Completed events:", completed);

// Check current application phase
logger.debug("Current phase:", internal.boot.phase);
});
}

Performance Optimization

function OptimizedService({ logger, lifecycle }: TServiceParams) {
// Use high priority for critical initialization
lifecycle.onPostConfig(() => {
logger.info("Critical config processing");
}, 1000);

// Use low priority for non-critical tasks
lifecycle.onReady(() => {
logger.info("Non-critical startup task");
}, -100);
}