2.8 KiB
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
DeliveryChannelinterface for every transport. DeliveryResultshape carries success flag, channel name, optional messageId / error, and a timestamp.- Synchronous
canSendso routers can probe support without awaiting. - No coupling to a specific provider — the integration point is each channel's own
sendFn/fetchFncallback.
Usage: Basic
import type {
DeliveryChannel,
DeliveryResult,
} from "@suaveplan/notification-channel";
const noop: DeliveryChannel = {
name: "noop",
canSend: () => true,
async send(): Promise<DeliveryResult> {
return {
success: true,
channel: "noop",
messageId: "noop-0",
timestamp: new Date().toISOString(),
};
},
};
Usage: Advanced
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<DeliveryResult>— 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.