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

6.0 KiB

Add @suaveplan/discord-client

Package: @suaveplan/discord-client Status: PROPOSED Type: new-package

Why

Genesis needs a Discord transport for operations notifications, bot-driven workflows, and webhook-based delivery from server-side jobs. The upstream package at ../suaveplan/packages/universal/discord-client/ covers bot-token messaging plus webhook delivery, but it bakes in globalThis.fetch and Date.now, throws BaseError from a parallel error hierarchy, and ships no OpenTelemetry instruments. Migrating into genesis re-grounds it on SuaveplanError + defineCodes, makes fetch/clock injectable for deterministic testing, and wires OTel counters and histograms so every Discord request is observable from day one.

What changes

  • ADDED: @suaveplan/discord-client package at packages/messaging/discord-client/.
  • ADDED: DiscordBotClient — bot-token client with sendMessage, editMessage, deleteMessage, getChannel, getGuild, createReaction, getStoredMessage.
  • ADDED: DiscordWebhook — webhook-URL delivery with send, sendText, sendEmbed returning DiscordDeliveryReport.
  • ADDED: DISCORD_CODES frozen registry with 11 codes namespaced DISCORD.*.
  • ADDED: DiscordClientError extends SuaveplanError<DiscordClientCode>.
  • ADDED: executeDiscordRequest — shared HTTP funnel emitting discord_client.request.total (Counter) and discord_client.request.duration_ms (Histogram).
  • ADDED: parseRetryAfterMs — header parser for Discord's three rate-limit headers.
  • ADDED: Zod boundary schemas (DiscordBotConfigSchema, DiscordWebhookConfigSchema, DiscordMessageSchema).
  • ADDED: FetchLike injectable contract on every public client.

Out of scope

  • Gateway / WebSocket support — bot REST + webhooks only, matching upstream parity.
  • File / attachment uploads — upstream does not implement them.
  • Slash-command registration — separate concern, future change.

Source mapping

This change derives from ../suaveplan/enterprise/discord-client.md and the upstream implementation at ../suaveplan/packages/universal/discord-client/.

Requirement Source lines Notes
Generic Discord API client for webhooks, bot messaging, embeds enterprise/discord-client.md L10-L13 one-line purpose verbatim
Zero browser/Node/React deps — runs in Bun, Node, browser enterprise/discord-client.md L37 enforced via FetchLike injectable
Zod schemas for all public types enterprise/discord-client.md L38 wired at every constructor + every send/sendMessage
Deterministic behaviour — injectable clock, no module-scope Date.now() enterprise/discord-client.md L40 Clock injectable
OpenTelemetry instrumentation for all public entrypoints enterprise/discord-client.md L41 counter + histogram in utils/http.ts
Structured errors with stable codes enterprise/discord-client.md L42 DISCORD_CODES + DiscordClientError
AbortSignal everywhere async enterprise/discord-client.md L43 AbortController per request
No mocks; real services via deterministic injectables enterprise/discord-client.md L53 FetchLike, Clock stubs in tests
sideEffects: false enterprise/discord-client.md L55 declared in package.json
workspace:* internal, Bun catalog external enterprise/discord-client.md L56 enforced by tests/dep-graph.test.ts
TypeScript strict — no any, no ts-ignore enterprise/discord-client.md L59 inherited from tsconfig.base.json
Public API stability — Zod boundary schemas enterprise/discord-client.md L60 three schemas at every public boundary
Error hierarchy — extends @suaveplan/error enterprise/discord-client.md L61 DiscordClientError extends SuaveplanError
Bot REST surface: send/edit/delete/getChannel/createReaction upstream src/core/discord-bot-client/discord-bot-client.ts L75-L130 reimplemented fresh
Webhook surface: send, sendText, sendEmbed, threaded routing upstream src/core/discord-webhook/discord-webhook.ts L81-L105 reimplemented fresh
Discord rate-limit header parsing (3 header variants) upstream src/core/discord-bot-client/discord-bot-client.ts L26-L48 extracted to utils/rate-limit.ts
401/403/404/429/non-ok response → DISCORD code mapping upstream src/core/discord-bot-client/discord-bot-client.ts L167-L215 concentrated in utils/http.ts
DiscordDeliveryReport for webhook responses upstream src/types/discord-client.types.ts L86-L97 shape preserved

Requirements with no source line

Requirement Justification
100% Istanbul coverage threshold Genesis-wide policy, not package-specific
### DISCORD-NN — requirement heading format OpenSpec conformance contract (openspec-conformance.ts regex)
**Verified by:** lines at draft time Genesis OpenSpec migration skill mandate
Zod parse failure → DISCORD.INVALID_CONFIG vs DISCORD.CONFIG_MISSING split Required so the registry's severity: fatal vs severity: error mapping is callable
OTel histogram alongside the counter Genesis observability convention (matches @suaveplan/webhook-client)

Verification approach

  • src/errors/discord-client-error.test.ts — covers DISCORD-01 (error-code registry).
  • src/utils/rate-limit.test.ts — covers DISCORD-02 (rate-limit header parser).
  • src/utils/http.test.ts — covers DISCORD-03 (response→error mapping), DISCORD-04 (timeout + abort), DISCORD-05 (OTel instrumentation with recording MeterProvider).
  • src/core/bot-client/discord-bot-client.test.ts — covers DISCORD-06 (bot REST surface), DISCORD-07 (Zod config validation), DISCORD-08 (in-memory message cache).
  • src/core/webhook/discord-webhook.test.ts — covers DISCORD-09 (webhook delivery + report), DISCORD-10 (webhook Zod boundary + URL host check).
  • tests/dep-graph.test.ts — pins runtime dep set and sideEffects: false.

Migration impact

None — additive only. No consumer of @suaveplan/discord-client exists yet in genesis.