first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,100 @@
# @suaveplan/discord-client - Generic Discord API client for webhooks, bot messaging, and embeds with injectable fetch, OpenTelemetry instrumentation, and structured error codes
Generic Discord API client for webhooks, bot messaging, and embeds. Runtime-agnostic via injectable `fetch` and `clock`. Maps every documented Discord failure mode to a stable `DISCORD.*` error code and emits OpenTelemetry counters and histograms for every request.
<!-- begin:auto-docs -->
## Documentation tree
- [bot-client](./src/core/bot-client/discord-bot-client.md) — Implements the bot-token half of `@suaveplan/discord-client`. Wraps the Discord Bot REST API (`/channels/:id/messages`,…
- [discord-bot-client](./src/core/bot-client/discord-bot-client.md) — Implements the bot-token half of `@suaveplan/discord-client`. Wraps the Discord Bot REST API (`/channels/:id/messages`,…
- [webhook](./src/core/webhook/discord-webhook.md) — Implements the webhook half of `@suaveplan/discord-client`. Discord webhooks are URLs that anyone with the URL can POST…
- [discord-webhook](./src/core/webhook/discord-webhook.md) — Implements the webhook half of `@suaveplan/discord-client`. Discord webhooks are URLs that anyone with the URL can POST…
- [errors](./src/errors/discord-client-error.md) — Defines the namespaced, frozen error-code registry and the typed `DiscordClientError` class used by every runtime failu…
- [discord-client-error](./src/errors/discord-client-error.md) — Defines the namespaced, frozen error-code registry and the typed `DiscordClientError` class used by every runtime failu…
- [http](./src/utils/http.md) — Single funnel through which every Discord API and webhook request flows. It maps Discord response codes onto the `Disco…
- [rate-limit](./src/utils/rate-limit.md) — Parses Discord's three rate-limit response headers into a single millisecond back-off duration that callers can use to…
<!-- end:auto-docs -->
## Installation
```bash
bun add @suaveplan/discord-client
```
## Quick Start
```ts
import { DiscordBotClient, DiscordWebhook } from "@suaveplan/discord-client";
const bot = new DiscordBotClient({ token: process.env.DISCORD_BOT_TOKEN! });
await bot.sendMessage("123", { content: "hello from the bot" });
const hook = new DiscordWebhook({
webhookUrl: "https://discord.com/api/webhooks/123/abc",
});
await hook.sendText("hello from a webhook");
```
## Modules
| Module | Path | Description |
|--------|------|-------------|
| Bot Client | [src/core/bot-client/discord-bot-client.md](src/core/bot-client/discord-bot-client.md) | `DiscordBotClient` — send/edit/delete messages, reactions, channels, guilds |
| Webhook | [src/core/webhook/discord-webhook.md](src/core/webhook/discord-webhook.md) | `DiscordWebhook` — webhook-URL delivery with structured reports |
| HTTP Helper | [src/utils/http.md](src/utils/http.md) | Shared request funnel: error mapping, timeout, OTel metrics |
| Rate-Limit | [src/utils/rate-limit.md](src/utils/rate-limit.md) | `parseRetryAfterMs` parser for Discord's three rate-limit headers |
| Errors | [src/errors/discord-client-error.md](src/errors/discord-client-error.md) | `DISCORD_CODES` registry and `DiscordClientError` |
| Types | [src/types/discord-client.types.md](src/types/discord-client.types.md) | Public type surface + Zod boundary schemas |
## Subpath Exports
```ts
import { DiscordBotClient } from "@suaveplan/discord-client/bot";
import { DiscordWebhook } from "@suaveplan/discord-client/webhook";
import { DISCORD_CODES } from "@suaveplan/discord-client/errors";
import { DiscordMessageSchema } from "@suaveplan/discord-client/types";
```
## Error Codes
| Code | HTTP | Meaning |
|------|------|---------|
| `DISCORD.API_ERROR` | 502 | Generic non-2xx bot response |
| `DISCORD.AUTH_ERROR` | 401 | Token rejected |
| `DISCORD.RATE_LIMITED` | 429 | Honour `error.context.retryAfterMs` |
| `DISCORD.CHANNEL_NOT_FOUND` | 404 | Channel missing or bot lacks access |
| `DISCORD.GUILD_NOT_FOUND` | 404 | Guild missing or bot is not a member |
| `DISCORD.CONFIG_MISSING` | 500 | Required config value missing |
| `DISCORD.INVALID_CONFIG` | 500 | Config failed Zod validation |
| `DISCORD.INVALID_MESSAGE` | 400 | Message payload failed Zod validation |
| `DISCORD.WEBHOOK_FAILED` | 502 | Webhook delivery rejected |
| `DISCORD.TIMEOUT` | 504 | Request aborted by timeout |
| `DISCORD.FORBIDDEN` | 403 | Caller lacks permissions |
## Injectable Fetch & Clock
Both `DiscordBotClient` and `DiscordWebhook` accept `fetch` and `clock` injectables — pass deterministic stubs in tests, omit them in production to use `globalThis.fetch` and `Date.now`.
```ts
import { DiscordBotClient, type FetchLike } from "@suaveplan/discord-client";
const stubFetch: FetchLike = async () => ({
ok: true, status: 200, statusText: "OK",
headers: { get: () => null },
text: async () => "", json: async () => ({ id: "1", channelId: "1", content: "x", timestamp: "" }),
});
new DiscordBotClient({
token: "abc",
fetch: stubFetch,
clock: () => 1_700_000_000_000,
});
```
## OTel Metrics
| Metric | Type | Labels |
|--------|------|--------|
| `discord_client.request.total` | Counter | `kind`, `method`, `outcome`, `status` |
| `discord_client.request.duration_ms` | Histogram | `kind`, `method`, `outcome` |