first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -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.