feat: implement full lifecycle hooks

Adds onApplicationBootstrap, beforeApplicationShutdown, and onApplicationShutdown hooks with signal support.
This commit is contained in:
M1000fr
2026-01-11 17:20:14 +01:00
parent deb9e704fb
commit 0c166ab54c
4 changed files with 84 additions and 8 deletions

View File

@@ -19,8 +19,13 @@ interface ParamType {
name?: string;
}
type LifecycleHook = "onModuleInit" | "onModuleDestroy";
type LifecycleHookFn = () => Promise<void> | void;
type LifecycleHook =
| "onModuleInit"
| "onApplicationBootstrap"
| "onModuleDestroy"
| "beforeApplicationShutdown"
| "onApplicationShutdown";
type LifecycleHookFn = (signal?: string) => Promise<void> | void;
type LifecycleAware = Partial<Record<LifecycleHook, LifecycleHookFn>>;
export class Container {
@@ -172,7 +177,8 @@ export class Container {
}
public async callLifecycleHook(
hook: "onModuleInit" | "onModuleDestroy",
hook: LifecycleHook,
signal?: string,
): Promise<void> {
for (const instances of this.instances.values()) {
for (const instance of instances.values()) {
@@ -180,7 +186,7 @@ export class Container {
const lifecycleInstance = instance as LifecycleAware;
const hookFn = lifecycleInstance[hook];
if (typeof hookFn === "function") {
await hookFn.call(lifecycleInstance);
await hookFn.call(lifecycleInstance, signal);
}
}
}

View File

@@ -19,18 +19,26 @@ export class AlveoApplication {
}
/**
* Initializes the application by calling onModuleInit hooks on all providers.
* Initializes the application by calling lifecycle hooks on all providers.
* This is called automatically by AlveoFactory.create().
*/
public async init(): Promise<this> {
await this.container.callLifecycleHook("onModuleInit");
await this.container.callLifecycleHook("onApplicationBootstrap");
return this;
}
/**
* Gracefully shuts down the application by calling onModuleDestroy hooks.
* Gracefully shuts down the application by calling lifecycle hooks.
*
* @param signal The signal that triggered the shutdown
*/
public async close(): Promise<void> {
await this.container.callLifecycleHook("onModuleDestroy");
public async close(signal?: string): Promise<void> {
await this.container.callLifecycleHook("onModuleDestroy", signal);
await this.container.callLifecycleHook(
"beforeApplicationShutdown",
signal,
);
await this.container.callLifecycleHook("onApplicationShutdown", signal);
}
}

View File

@@ -6,6 +6,14 @@ export interface OnModuleInit {
onModuleInit(): void | Promise<void>;
}
/**
* Interface defining a lifecycle hook that is called once all modules have been initialized,
* but before the application starts listening for connections.
*/
export interface OnApplicationBootstrap {
onApplicationBootstrap(): void | Promise<void>;
}
/**
* Interface defining a lifecycle hook that is called when the application is shutting down.
* Useful for cleanup logic like closing database connections or stopping intervals.
@@ -13,3 +21,17 @@ export interface OnModuleInit {
export interface OnModuleDestroy {
onModuleDestroy(): void | Promise<void>;
}
/**
* Interface defining a lifecycle hook that is called after all onModuleDestroy() handlers have completed.
*/
export interface BeforeApplicationShutdown {
beforeApplicationShutdown(signal?: string): void | Promise<void>;
}
/**
* Interface defining a lifecycle hook that is called after connections close (app.close() resolves).
*/
export interface OnApplicationShutdown {
onApplicationShutdown(signal?: string): void | Promise<void>;
}

View File

@@ -373,4 +373,44 @@ describe("Alveo Container & DI", () => {
expect(service.config).toBe("async_dynamic_config");
});
test("should call all lifecycle hooks in the correct order", async () => {
const callOrder: string[] = [];
@Injectable()
class FullLifecycleService {
async onModuleInit() {
callOrder.push("onModuleInit");
}
async onApplicationBootstrap() {
callOrder.push("onApplicationBootstrap");
}
async onModuleDestroy() {
callOrder.push("onModuleDestroy");
}
async beforeApplicationShutdown(signal?: string) {
callOrder.push(`beforeApplicationShutdown:${signal}`);
}
async onApplicationShutdown(signal?: string) {
callOrder.push(`onApplicationShutdown:${signal}`);
}
}
@Module({
providers: [FullLifecycleService],
})
class RootModule {}
const app = await AlveoFactory.create(RootModule);
expect(callOrder).toEqual(["onModuleInit", "onApplicationBootstrap"]);
await app.close("SIGTERM");
expect(callOrder).toEqual([
"onModuleInit",
"onApplicationBootstrap",
"onModuleDestroy",
"beforeApplicationShutdown:SIGTERM",
"onApplicationShutdown:SIGTERM",
]);
});
});