# hooks _(no submodules)_ ## Purpose Implements the package's `email:processing` hook pipeline: every parsed email passes through a sequence of handlers that may sanitise, redact, or enrich the parsed result. The implementation is intentionally local to this package — the foundation `@suaveplan/events` library does not (yet) export a shared `Hooks` substrate, so a focused async pipeline lives here. The public shape matches the upstream `@suaveplan/email-processing` package so consumer code does not change. ## Features - Generic `Hooks` class with explicit type-safe `hook` / `applyHooksAsync` / `clear` methods. - Handlers run in `order` (default 100, lower first) and may be sync or async — `applyHooksAsync` awaits each return value. - Errors thrown from any handler short-circuit the pipeline and are wrapped in an `EmailProcessingError` with code `EMAIL_PROCESSING.HOOK_FAILURE`. The original throw is preserved on `.cause` when it is an `Error`. - Each invocation increments the `email_processing.hook.invocation_total` OTel counter with `event` and `handlers` attributes. - Returned data is threaded through every handler so transforms compose. - Module-level `emailHooks` singleton + `onEmailProcessing` shorthand. ## Usage: Basic ```ts import { onEmailProcessing } from "@suaveplan/email-processing/hooks"; onEmailProcessing((data) => ({ ...data, parsed: { ...data.parsed, html: data.parsed.html?.replace( /]*tracking[^>]*>/gi, "" ), }, })); ``` ## Usage: Advanced ```ts import { Hooks, type EmailHookMap } from "@suaveplan/email-processing/hooks"; const hooks = new Hooks(); const offFirst = hooks.hook( "email:processing", async (d) => { await audit.log({ messageId: d.parsed.messageId }); return d; }, { order: 10 } ); const offRedact = hooks.hook( "email:processing", (d) => ({ ...d, parsed: { ...d.parsed, text: redact(d.parsed.text ?? "") }, }), { order: 200 } ); try { const out = await hooks.applyHooksAsync("email:processing", incoming); return out; } finally { offFirst(); offRedact(); } ``` ## API Reference - `EmailProcessingHookData` — pipeline payload (`parsed`, `rawInput?`, `metadata`). - `EmailHookMap` — `{ "email:processing": EmailProcessingHookData }`. - `HookHandler` — `(data: T) => T | Promise`. - `HookHandlerOptions` — `{ order?: number }`. - `Hooks` — class with `hook`, `applyHooksAsync`, `clear`. - `emailHooks` — singleton `Hooks` instance. - `onEmailProcessing(handler, options?)` — shorthand registration on `emailHooks`; returns an idempotent unregister. ## Implementation Notes The unregister function returned from `hook(...)` is idempotent: calling it twice is a no-op so cleanup code in shared test fixtures can run without ordering pitfalls. The pipeline does not call the OTel counter when a handler throws — failures are observed via the `EMAIL_PROCESSING.HOOK_FAILURE` error path, and a separate consumer counter is the appropriate place to record those.