Register and call typed hooks
Hookable provides a typed system for managing and executing lifecycle hooks. By defining a hook contract, you can ensure that handlers receive the correct arguments and that hook names are validated at compile time.
Defining and executing hooks
To implement a typed hook system, define an interface where keys are hook names and values are function signatures. Use createHooks to initialize the manager, register handlers with hook, and trigger them using callHook.
import { createHooks } from "hookable";
interface MyHooks {
"start": () => void | Promise<void>;
}
async function runExample() {
const hooks = createHooks<MyHooks>();
const trace: string[] = [];
const handlerOne = () => {
trace.push("handler1");
};
const handlerTwo = () => {
trace.push("handler2");
};
hooks.hook("start", handlerOne);
hooks.hook("start", handlerTwo);
await hooks.callHook("start");
console.assert(trace[0] === "handler1");
console.assert(trace[1] === "handler2");
}
await runExample();
The callHook method executes handlers serially. If a handler returns a Promise, callHook waits for it to resolve before moving to the next handler. If any handler throws an error or returns a rejecting promise, the callHook promise rejects immediately, and subsequent handlers are not executed.
Managing handler lifecycles
The hook method returns an unregister function. Invoking this function removes the specific handler, preventing it from being executed in future callHook calls.
import { createHooks } from "hookable";
interface AppHooks {
"app:shutdown": () => void | Promise<void>;
}
async function runUnregisterExample() {
const hooks = createHooks<AppHooks>();
const trace: string[] = [];
const temporaryHandler = () => {
trace.push("temporary handler was called");
};
const unregister = hooks.hook("app:shutdown", temporaryHandler);
await hooks.callHook("app:shutdown");
console.assert(trace.length === 1);
unregister();
await hooks.callHook("app:shutdown");
console.assert(trace.length === 1);
}
await runUnregisterExample();
When using TypeScript, the generic type provided to createHooks serves as type guidance for hook names and callback signatures. While this assists in development, it does not change runtime behavior. Registered handlers should focus on producing observable effects, as the system is designed for lifecycle orchestration rather than data transformation or result aggregation.