3.5 KiB
hooks — Pre-routing hook pipeline
(no submodules)
Purpose
hooks.ts exposes a small typed hook bus that runs before NotificationRouter commits a notification to a channel. The single key today is notification:routing; handlers receive the original input, the currently resolved channel, and a metadata bag, and may return a partial override that merges into the data flowing forward. The bus is intentionally local to this package — the @suaveplan/events foundation does not yet export a Hooks primitive, and inlining keeps the package universal with no extra workspace dep.
Features
- Generic
Hooks<TMap>class — typed by a hook-map interface; cannot register on an undeclared key. - Handlers run in registration order (FIFO).
- Each handler may return
undefined(no-op), a full hook-map value, a partial override, or aPromiseof either. - Unsubscribe returned from
hook(...)— calling twice is safe. clear(key?)drops handlers for a single key or every key.- Singleton
notificationHooksso cross-cutting integrations (audit, A/B routing) can hook globally. - Shorthand
onNotificationRouting(handler)for the singleton's primary key.
Usage: Basic
import { onNotificationRouting } from "@suaveplan/notification-channel";
const off = onNotificationRouting((data) => {
if (data.input.priority === "urgent") {
return { resolvedChannel: "sms" };
}
return undefined;
});
// later
off();
Usage: Advanced
import {
Hooks,
type NotificationHookMap,
} from "@suaveplan/notification-channel";
const hooks = new Hooks<NotificationHookMap>();
hooks.hook("notification:routing", async (data) => {
const enriched = await enrichRecipient(data.input.recipient);
return {
metadata: { ...data.metadata, geo: enriched.geo, segment: enriched.segment },
};
});
const result = await hooks.applyHooksAsync("notification:routing", {
input: { recipient: "u@x.com", body: "Hi" },
resolvedChannel: "email",
metadata: {},
});
API Reference
Hooks<TMap>— class.hook(key, fn)→() => void— register, returns unsubscribe.clear(key?)— drop handlers forkey, or every key when omitted.count(key)→number— number of registered handlers.applyHooksAsync(key, data)→Promise<data>— run pipeline and return composed data.
notificationHooks— singletonHooks<NotificationHookMap>.onNotificationRouting(handler)— shorthand fornotificationHooks.hook("notification:routing", handler).NotificationHookMap—{ "notification:routing": NotificationRoutingHookData }.NotificationRoutingHookData—{ input, resolvedChannel, metadata }.
Implementation Notes
Handlers compose via Object.assign-style spread, so a handler that returns { resolvedChannel: "sms" } overrides only that field while preserving input and metadata. Returning undefined is the explicit "no change" signal — handlers used for telemetry or audit can side-effect freely without returning anything. The Hooks class accepts a generic TMap so packages with their own hook map can reuse the same implementation; the singleton is pinned to NotificationHookMap so the singleton's only legal key is notification:routing. The bus is intentionally minimal: it has no priority, timeout, or interceptor surface — those concerns belong to @suaveplan/events's richer EventBus interceptor stack, which the router itself uses for lifecycle events.