# `delivery-channel` — Common transport contract ## Purpose `delivery-channel.ts` defines the structural contract every concrete delivery channel implements. The contract is intentionally tiny: a `name`, a synchronous `canSend(notification)` filter, and an async `send(notification)` that returns a `DeliveryResult`. Channels MUST swallow transport-level exceptions and surface failures via `DeliveryResult { success: false, error }` so the router can decide whether to retry, dead-letter, or emit `notification.failed` without try/catching every channel. ## Features - Single `DeliveryChannel` interface for every transport. - `DeliveryResult` shape carries success flag, channel name, optional messageId / error, and a timestamp. - Synchronous `canSend` so routers can probe support without awaiting. - No coupling to a specific provider — the integration point is each channel's own `sendFn`/`fetchFn` callback. ## Usage: Basic ```ts import type { DeliveryChannel, DeliveryResult, } from "@suaveplan/notification-channel"; const noop: DeliveryChannel = { name: "noop", canSend: () => true, async send(): Promise { return { success: true, channel: "noop", messageId: "noop-0", timestamp: new Date().toISOString(), }; }, }; ``` ## Usage: Advanced ```ts import type { DeliveryChannel } from "@suaveplan/notification-channel"; class LoggingChannel implements DeliveryChannel { public readonly name = "logging"; constructor(private readonly inner: DeliveryChannel) {} canSend(n: import("@suaveplan/notification-channel").Notification) { return this.inner.canSend(n); } async send(n: import("@suaveplan/notification-channel").Notification) { const start = Date.now(); const r = await this.inner.send(n); console.error(`[${this.inner.name}] ${r.success ? "ok" : "fail"} +${Date.now() - start}ms`); return r; } } ``` ## API Reference - `DeliveryChannel` — interface. - `name: string` — unique transport identifier (matches the channel key the router routes to). - `canSend(notification): boolean` — synchronous probe. - `send(notification): Promise` — main entry point; MUST NOT throw on transport failure. - `DeliveryResult` — `{ success, channel, messageId?, error?, timestamp }`. ## Implementation Notes Both fields `messageId` and `error` are optional and mutually exclusive in practice — `success: true` should set `messageId`, `success: false` should set `error`. Tests cover both polarities. The interface deliberately uses `string` for `channel` (not `NotificationChannel`) so wrappers like `DigestChannel` (whose `name` is the literal `"digest"`) and custom decorators can satisfy the contract without widening the package-wide channel enum.