Files
2026-05-27 23:29:53 +01:00
..
2026-05-27 23:29:53 +01:00
2026-05-27 23:29:53 +01:00
2026-05-27 23:29:53 +01:00
2026-05-27 23:29:53 +01:00
2026-05-27 23:29:53 +01:00
2026-05-27 23:29:53 +01:00
2026-05-27 23:29:53 +01:00
2026-05-27 23:29:53 +01:00

@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,…
  • 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…
  • errors — Defines the namespaced, frozen error-code registry and the typed DiscordClientError class used by every runtime failu…
    • discord-client-error — Defines the namespaced, frozen error-code registry and the typed DiscordClientError class used by every runtime failu…
  • 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