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

76 lines
6.0 KiB
Markdown

# 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.