first commit
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
# `hooks` — Pre-routing hook pipeline
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## 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 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<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 for `key`, or every key when omitted.
|
||||
- `count(key)` → `number` — number of registered handlers.
|
||||
- `applyHooksAsync(key, data)` → `Promise<data>` — run pipeline and return composed data.
|
||||
- `notificationHooks` — singleton `Hooks<NotificationHookMap>`.
|
||||
- `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.
|
||||
Reference in New Issue
Block a user