feat: implement full lifecycle hooks
Adds onApplicationBootstrap, beforeApplicationShutdown, and onApplicationShutdown hooks with signal support.
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user