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;
|
name?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type LifecycleHook = "onModuleInit" | "onModuleDestroy";
|
type LifecycleHook =
|
||||||
type LifecycleHookFn = () => Promise<void> | void;
|
| "onModuleInit"
|
||||||
|
| "onApplicationBootstrap"
|
||||||
|
| "onModuleDestroy"
|
||||||
|
| "beforeApplicationShutdown"
|
||||||
|
| "onApplicationShutdown";
|
||||||
|
type LifecycleHookFn = (signal?: string) => Promise<void> | void;
|
||||||
type LifecycleAware = Partial<Record<LifecycleHook, LifecycleHookFn>>;
|
type LifecycleAware = Partial<Record<LifecycleHook, LifecycleHookFn>>;
|
||||||
|
|
||||||
export class Container {
|
export class Container {
|
||||||
@@ -172,7 +177,8 @@ export class Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async callLifecycleHook(
|
public async callLifecycleHook(
|
||||||
hook: "onModuleInit" | "onModuleDestroy",
|
hook: LifecycleHook,
|
||||||
|
signal?: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
for (const instances of this.instances.values()) {
|
for (const instances of this.instances.values()) {
|
||||||
for (const instance of instances.values()) {
|
for (const instance of instances.values()) {
|
||||||
@@ -180,7 +186,7 @@ export class Container {
|
|||||||
const lifecycleInstance = instance as LifecycleAware;
|
const lifecycleInstance = instance as LifecycleAware;
|
||||||
const hookFn = lifecycleInstance[hook];
|
const hookFn = lifecycleInstance[hook];
|
||||||
if (typeof hookFn === "function") {
|
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().
|
* This is called automatically by AlveoFactory.create().
|
||||||
*/
|
*/
|
||||||
public async init(): Promise<this> {
|
public async init(): Promise<this> {
|
||||||
await this.container.callLifecycleHook("onModuleInit");
|
await this.container.callLifecycleHook("onModuleInit");
|
||||||
|
await this.container.callLifecycleHook("onApplicationBootstrap");
|
||||||
return this;
|
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> {
|
public async close(signal?: string): Promise<void> {
|
||||||
await this.container.callLifecycleHook("onModuleDestroy");
|
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>;
|
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.
|
* 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.
|
* Useful for cleanup logic like closing database connections or stopping intervals.
|
||||||
@@ -13,3 +21,17 @@ export interface OnModuleInit {
|
|||||||
export interface OnModuleDestroy {
|
export interface OnModuleDestroy {
|
||||||
onModuleDestroy(): void | Promise<void>;
|
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");
|
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