5.2 KiB
5.2 KiB
@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.
Documentation tree
- bot-client — Implements the bot-token half of
@suaveplan/discord-client. Wraps the Discord Bot REST API (/channels/:id/messages,…- discord-bot-client — Implements the bot-token half of
@suaveplan/discord-client. Wraps the Discord Bot REST API (/channels/:id/messages,…
- discord-bot-client — Implements the bot-token half of
- webhook — Implements the webhook half of
@suaveplan/discord-client. Discord webhooks are URLs that anyone with the URL can POST…- discord-webhook — Implements the webhook half of
@suaveplan/discord-client. Discord webhooks are URLs that anyone with the URL can POST…
- discord-webhook — Implements the webhook half of
- errors — Defines the namespaced, frozen error-code registry and the typed
DiscordClientErrorclass used by every runtime failu…- discord-client-error — Defines the namespaced, frozen error-code registry and the typed
DiscordClientErrorclass used by every runtime failu…
- discord-client-error — Defines the namespaced, frozen error-code registry and the typed
- http — Single funnel through which every Discord API and webhook request flows. It maps Discord response codes onto the `Disco…
- rate-limit — Parses Discord's three rate-limit response headers into a single millisecond back-off duration that callers can use to…
Installation
bun add @suaveplan/discord-client
Quick Start
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 | DiscordBotClient — send/edit/delete messages, reactions, channels, guilds |
| Webhook | src/core/webhook/discord-webhook.md | DiscordWebhook — webhook-URL delivery with structured reports |
| HTTP Helper | src/utils/http.md | Shared request funnel: error mapping, timeout, OTel metrics |
| Rate-Limit | src/utils/rate-limit.md | parseRetryAfterMs parser for Discord's three rate-limit headers |
| Errors | src/errors/discord-client-error.md | DISCORD_CODES registry and DiscordClientError |
| Types | src/types/discord-client.types.md | Public type surface + Zod boundary schemas |
Subpath Exports
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.
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 |