6.2 KiB
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:
TelegramBotClientclass — Bot API client wrappingsendMessage,editMessage,deleteMessage,sendMedia,getChat,getMeover nativefetchwithAbortControllertimeouts. - ADDED:
createTelegramBotClientfactory — preferred overnew, accepts injectablefetchandclock. - ADDED:
TelegramClientErrorextendsSuaveplanError<TelegramClientCode>. - ADDED:
TELEGRAM_CLIENT_CODESregistry —API_ERROR,AUTH_ERROR,RATE_LIMITED,CHAT_NOT_FOUND,BOT_BLOCKED,CONFIG_MISSING,TIMEOUT,FORBIDDEN. - ADDED:
TelegramBotConfigSchemaZod 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.totaland histogramtelegram_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.