Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,56 @@
# `webhook-channel` — HTTP webhook delivery channel
## Purpose
`webhook-channel.ts` ships the `WebhookChannel` implementation that POSTs notifications to an arbitrary HTTPS endpoint. The notification's `recipient` field IS the destination URL, the body carries the serialised notification payload, and the channel respects a per-request timeout via `AbortController`. The fetch surface is injectable via `FetchLike` from `@suaveplan/types`, so tests run deterministically against `FakeFetch` and downstream consumers can plug their own observability-instrumented fetch.
## Features
- Injectable `FetchLike` — no `globalThis.fetch` patching.
- Configurable timeout (default 10_000 ms) using `AbortController`.
- Custom headers via `WebhookChannelConfig.headers`.
- `canSend` enforces `http://` or `https://` recipient prefix.
- Returns `DeliveryResult { success: false }` on non-2xx, network error, abort, or unexpected throw.
- Injectable `Clock` for deterministic `timestamp` in tests.
## Usage: Basic
```ts
import { WebhookChannel } from "@suaveplan/notification-channel";
const channel = new WebhookChannel({
headers: { Authorization: "Bearer secret" },
});
const r = await channel.send(notification);
```
## Usage: Advanced
```ts
import { FakeFetch, WebhookChannel } from "@suaveplan/notification-channel";
// In tests:
const fake = new FakeFetch();
fake.push({ status: 200 });
const channel = new WebhookChannel({
fetchFn: fake.fetch,
timeoutMs: 500,
clock: () => testNow,
});
// In prod, wire a real instrumented fetch:
const channel2 = new WebhookChannel({
fetchFn: (url, init) => instrumented.fetch(url, init),
headers: { "X-Signed-By": "suaveplan" },
});
```
## API Reference
- `WebhookChannel(config?: WebhookChannelConfig)` — constructor.
- `WebhookChannelConfig` — `{ headers?, fetchFn?, timeoutMs?, clock? }`.
- `canSend(n): boolean`, `send(n): Promise<DeliveryResult>`.
## Implementation Notes
The POST body is built from `{ id, subject, body, priority, metadata, createdAt }` — a stable shape so webhook consumers can lock their parsers. `Content-Type: application/json` is always set; consumer headers override the default. Aborted fetches surface `error: "aborted"` (matching the `AbortError.name`-derived message from the polyfill). Non-2xx responses encode the upstream status into `error: "HTTP <status>: <statusText>"`. Both `Error` and non-`Error` throws inside the fetch callback are stringified into `error` so the `DeliveryResult` contract holds.