Files
suaveplan/openspec/archive/2026-05-18-add-telegram-client/proposal.md
2026-05-27 23:29:53 +01:00

78 lines
6.2 KiB
Markdown

# Add @suaveplan/telegram-client
**Package**: `@suaveplan/telegram-client`
**Status**: PROPOSED
**Type**: new-package
## Why
The genesis monorepo lacks a Telegram Bot API client. The upstream `@suaveplan/telegram-client` at `../suaveplan/packages/universal/telegram-client/` provides a small, runtime-agnostic client used for transactional bot messaging in production but has zero deterministic-clock / no-mock test discipline, no injectable fetch, no OTel surface, and no Zod boundary. This change ports the upstream functional surface 1:1, then rewrites it to the genesis quality bar (injectable `Clock` + fetch, Zod-validated config, real-fetch test discipline, `SuaveplanError` extension, OTel counters/histograms, 100% istanbul coverage) so downstream `@suaveplan/notifications-*` and ops alerting packages have a production-grade Telegram channel.
## What changes
Additive — new package at `packages/messaging/telegram-client/`.
- ADDED: `TelegramBotClient` class — Bot API client wrapping `sendMessage`, `editMessage`, `deleteMessage`, `sendMedia`, `getChat`, `getMe` over native `fetch` with `AbortController` timeouts.
- ADDED: `createTelegramBotClient` factory — preferred over `new`, accepts injectable `fetch` and `clock`.
- ADDED: `TelegramClientError` extends `SuaveplanError<TelegramClientCode>`.
- ADDED: `TELEGRAM_CLIENT_CODES` registry — `API_ERROR`, `AUTH_ERROR`, `RATE_LIMITED`, `CHAT_NOT_FOUND`, `BOT_BLOCKED`, `CONFIG_MISSING`, `TIMEOUT`, `FORBIDDEN`.
- ADDED: `TelegramBotConfigSchema` Zod schema for `{ token, baseUrl?, timeoutMs? }`.
- ADDED: types `TelegramBotConfig`, `TelegramMessage`, `TelegramReplyMarkup`, `TelegramInlineButton`, `TelegramKeyboardButton`, `TelegramResponse<T>`, `TelegramChat`, `TelegramUser`, `TelegramSentMessage`, `TelegramDeliveryReport`, `TelegramDeliveryStatus`, `TelegramMediaType`.
- ADDED: `buildSendMessageBody`, `buildReplyMarkup`, `parseRetryAfterMs`, `classifyStatus` — pure helpers (exported for testability).
- ADDED: OTel counter `telegram_client.request.total` and histogram `telegram_client.request.duration_ms`.
- ADDED: subpath exports `./errors`, `./types`.
## Out of scope
- Long-polling `getUpdates` / webhook update receivers — upstream does not implement them.
- Bot-on-bot orchestration, conversational state — not in upstream.
- File upload via multipart (only URL-based media) — upstream is URL-only.
## Source mapping
This change derives from `../suaveplan/enterprise/telegram-client.md` AND the upstream functional surface at `../suaveplan/packages/universal/telegram-client/src/`.
| Requirement | Source lines / files | Notes |
|---|---|---|
| Send text messages with full options | `src/core/telegram-bot-client/telegram-bot-client.ts:50-53,287-339` | verbatim API surface (`sendMessage`, `buildSendMessageBody`, `buildReplyMarkup`) |
| Edit / delete messages | `src/core/telegram-bot-client/telegram-bot-client.ts:56-80` | verbatim |
| Send media (photo/document/video/audio/voice/animation) | `src/core/telegram-bot-client/telegram-bot-client.ts:83-101` | verbatim method map |
| Get chat info and bot info | `src/core/telegram-bot-client/telegram-bot-client.ts:104-111` | verbatim |
| Authenticated POST request envelope with AbortController timeout | `src/core/telegram-bot-client/telegram-bot-client.ts:113-163` | verbatim |
| Retry-after parsing (body / headers fallback) | `src/core/telegram-bot-client/telegram-bot-client.ts:179-215` | verbatim |
| Typed error classification by HTTP status + Telegram description | `src/core/telegram-bot-client/telegram-bot-client.ts:218-284` | verbatim |
| Stable error code registry with metadata | `src/error-codes.ts:18-117` | reworded — genesis uses `defineCodes("TELEGRAM_CLIENT", […])` + `SuaveplanError` extension, but the same 8 codes |
| Config token required (throw on missing) | `src/core/telegram-bot-client/telegram-bot-client.ts:36-47` | verbatim |
| Default base URL `https://api.telegram.org` and 10s timeout | `src/core/telegram-bot-client/telegram-bot-client.ts:21-22` | verbatim constants |
| Zero browser / React deps | `enterprise/telegram-client.md` L37 | cross-cutting bar |
| Zod schemas on public types | `enterprise/telegram-client.md` L38 | cross-cutting bar |
| Deterministic — injectable clock | `enterprise/telegram-client.md` L40 | cross-cutting bar |
| OTel instrumentation | `enterprise/telegram-client.md` L41 | cross-cutting bar |
| Structured errors extending shared base | `enterprise/telegram-client.md` L42 | cross-cutting bar |
| `AbortSignal` everywhere async | `enterprise/telegram-client.md` L43 | upstream uses internal `AbortController` for timeout; genesis preserves this |
| No mocks — injectable fetch | `enterprise/telegram-client.md` L53 + `CLAUDE.md` rule 2 | cross-cutting bar |
| `sideEffects: false`, `workspace:^` deps, Bun catalog externals | `enterprise/telegram-client.md` L55-L57 | cross-cutting bar |
### Requirements with no source line
| Requirement | Justification |
|---|---|
| 100 % istanbul coverage on touched modules | Genesis-wide policy (`CLAUDE.md` §Quality). |
| Test files import only from `@suaveplan/testing/runner` | Genesis-wide policy (`CLAUDE.md` rule 11). |
| Sibling `.md` doc per impl file ≥200 words | Genesis-wide policy (`CLAUDE.md` §Documentation). |
## Verification approach
- `src/core/telegram-bot-client/telegram-bot-client.test.ts` — every Bot API method, every error classification branch, retry-after parsing, custom base URL, config validation, OTel emission.
- `src/utils/build-bodies.test.ts` — pure body / reply-markup builders across every option permutation.
- `src/utils/classify-error.test.ts` — pure status classifier and retry-after parser, including header fallbacks.
- `src/errors/telegram-client-error.test.ts` — error class shape, code registry, throwability.
- `src/types/telegram-bot-config.test.ts` — Zod schema accept / reject.
- `tests/dep-graph.test.ts` — runtime deps locked.
All tests use an injectable `fetch` that returns canned `Response` objects (`Response.json(...)`) — no network, no `vi.mock`, no mocks of any kind. The OTel test asserts against a real in-memory `MeterProvider`.
## Migration impact
None — additive only. No existing consumer in genesis imports `@suaveplan/telegram-client`.