# `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` 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 a `Promise` of either. - Unsubscribe returned from `hook(...)` — calling twice is safe. - `clear(key?)` drops handlers for a single key or every key. - Singleton `notificationHooks` so cross-cutting integrations (audit, A/B routing) can hook globally. - Shorthand `onNotificationRouting(handler)` for the singleton's primary key. ## Usage: Basic ```ts import { onNotificationRouting } from "@suaveplan/notification-channel"; const off = onNotificationRouting((data) => { if (data.input.priority === "urgent") { return { resolvedChannel: "sms" }; } return undefined; }); // later off(); ``` ## Usage: Advanced ```ts import { Hooks, type NotificationHookMap, } from "@suaveplan/notification-channel"; const hooks = new Hooks(); 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` — class. - `hook(key, fn)` → `() => void` — register, returns unsubscribe. - `clear(key?)` — drop handlers for `key`, or every key when omitted. - `count(key)` → `number` — number of registered handlers. - `applyHooksAsync(key, data)` → `Promise` — run pipeline and return composed data. - `notificationHooks` — singleton `Hooks`. - `onNotificationRouting(handler)` — shorthand for `notificationHooks.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.