first commit
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
# `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<DeliveryResult> {
|
||||
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<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.
|
||||
Reference in New Issue
Block a user