CLAUDE.md · yagop/node-telegram-bot-api
CLAUDE.md from yagop/node-telegram-bot-api: 184 lines covering What this is, Commands, Architecture, Critical invariants and more.
Template by yagop
Install
Works with Claude Code. Read the repository before you give any tool access to your data.
curl -fsSL https://raw.githubusercontent.com/yagop/node-telegram-bot-api/master/CLAUDE.md -o CLAUDE.md
Read it and adapt it to your project before use.
Get the weekly diff
The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.
About
Use cases: prompt-engineering
Reviews
Write a reviewNo reviews yet. Installed it? Reviews come from verified installers only. How reviews work
What's in it
A CLAUDE.md file: project memory Claude Code loads at the start of every session.
- For
- Claude Code
- Length
- 184 lines
- File
CLAUDE.md
Sections
- What this is
- Commands
- Architecture
- Critical invariants
- Testing
- Project skills (.claude/skills/)
First lines (shown under the repo's MIT license)
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## What this is
`node-telegram-bot-api` v2: a from-scratch, runtime-agnostic TypeScript Telegram Bot API client.
Node 18.2+, also runs on Bun, Deno, Cloudflare Workers, and Vercel/Deno Edge. The source is
ESM/web-standard; the **published** package is dual-module - `zshy` emits both ESM (`*.js`/`*.d.ts`)
and CJS (`*.cjs`/`*.d.cts`), exposed via the `exports` map's `import`/`require` conditions, so it can
be `import`ed or `require`d. `src/core` stays Node-free, so the edge story is unchanged. There is
**no v1 compatibility**; the v1->v2 migration cheatsheet lives in `CHANGELOG.md`.
Current development happens on `feat/v2-core`; the base/release branch is `master`.
## Commands
Bun is the primary toolchain (it is a devDependency and runs the unit suite and generator).
```sh
bun install # install deps (CI uses --frozen-lockfile)
npm run check # full local gate: typecheck (x3) + lint:core + check:edge + unit tests
npm run typecheck # tsc --noEmit over src (strict)
npm run typecheck:test # tsc over test/ (needs bun types installed)
npm run lint:core # FAILS if src/core touches node:* or Node globals (see below)
npm run check:edge # FAILS if src/core bundles a Node builtin (even transitively)
npm test # unit tests via Bun (test/unit)
npm run test:node:unit # unit tests via Node's node:test + tsx (CI runs this on Node 22/24/26)
npm run test:e2e # LIVE tests against api.telegram.org (see Testing)
npm run build # zshy -> dist/ dual ESM+CJS (core, node, types subpaths); postbuild repairs CJS source-map refs
npm run generate:types # regenerate generated sources from live Bot API docs
```
Run a single unit test:
```sh
bun test test/unit/transport.test.ts # one file
bun test -t "429 then success retries" # by test-name substring
```
## Architecture
Three source folders, one published package, exposed via subpath exports (`.`, `./node`, `./types`):
```text
src/
core/ runtime-agnostic. Bot, Context, compose, the single Api client, Transport,
serializeParams + encodeForm, InputFile, the markup / entity / media builders
(optional sugar), longPoll, webhookCallback, framework webhook adapters
(Express / Next.js), errors. Web-standard APIs only.
-> zero node:* imports; runs on Node 18.2+ / Bun / Deno / Workers / edge.
node/ the ONLY folder allowed to import node:*. Node-only sugar: fromPath() (fs
uploads), createWebhookServer() (node:http -> delegates to core's
webhookCallback), the managed run() polling runner, the DEBUG stderr sink.
types/ the generated schema (re-exported by core): discriminated Update, the generated
Api method signatures / *Params / *Result types, expanded MessageEntity.
```
The `exports` map points `.` at the core, `./node` at the Node helpers, and `./types` at the
schema; each subpath ships ESM + CJS with matching typings:
```jsonc
// package.json
"exports": {
".": {
"import": { "types": "./dist/core/index.d.ts", "default": "./dist/core/index.js" },
"require": { "types": "./dist/core/index.d.cts", "default": "./dist/core/index.cjs" }
},
"./node": { /* same shape -> dist/node/* */ },
"./types": { /* same shape -> dist/types/* */ }
}
```
`import { Bot } from "node-telegram-bot-api"` is the runtime-agnostic core; `import { fromPath }
from "node-telegram-bot-api/node"` opts into the Node helpers. The core-vs-node isolation is a CI
rule (`lint:core` + `check:edge`), not a package boundary, so the edge bundle never drags in a Node
builtin.
### The Api client
One **generated** `Api` class - no `Proxy`, no `RawApi`/`Api` split. One concrete method per Bot
API method, each a one-liner over a shared `request()`, taking a single params object plus an
optional trailing `AbortSignal` (`getMe(signal?)`, `sendMessage(params, signal?)`). Real methods
give correct stack traces, are greppable, and need no casts; adding a method is a regenerate, never
a hand-written body. `Bot` holds an `Api`; `ctx.api` and `bot.api` expose it. The whole client
ships regardless of which methods a bot calls (no per-method tree-shaking - a deliberate trade for
the single discoverable `api.*` namespace).
### Request pipeline (the only place serialization happens)
`Api.request` -> `serializeParams` (one `JSON.stringify` + `attach://` walk for nested files) ->
`encodeForm` -> `Transport` (injectable `fetch`, 429/transient retry with jittered backoff,
`{ok,result}` envelope unwrap). Structured fields (`reply_markup`, entities, `reply_parameters`,
media, ...) are **plain typed objects/arrays** that `serializeParams` stringifies once. Builders
(`InlineKeyboardBuilder`, `EntityBuilder`, `MediaGroupBuilder`, plus the sticker / profile-photo /
story builders) are **optional sugar** whose `.build()` returns the same plain shape - a literal
object works identically. `EntityType` is the typo-proof constant for entity kinds.
Every request goes out as form encoding: `x-www-form-urlencoded`, or `multipart/form-data` iff an
`InputFile` is present (the only thing that flips the body type). `encodeForm` has exactly three
branches: attach an `InputFile` as a part / spread a form-part composite (its JSON string + nested
parts) / set a string. `InputFile` wraps web-standard data only (`Blob | Uint8Array |
ReadableStream<Uint8Array>`); `fromPath()` (`/node`) is the only fs-backed constructor. File-bearing
params are `InputFile | string` - a bare string is always a `file_id`/URL, never a path. Nested
files (`sendMediaGroup`, sticker sets, ...) are hoisted to `attach://<name>` parts by
`serializeParams`.
### Dispatch
koa-style middleware over a per-update `Context`. `bot.use(mw)` registers middleware; `bot.on(kind)`,
`bot.command(name)`, `bot.hears(trigger)` are filter middleware in the same chain - registration
order wins, and each can wrap everything downstream via `await next()` (sessions, auth, rate-limit,
error boundaries). `Context` bundles the raw `update`, the typed `api`, a mutable `state` bag, and
chat-inferring shortcuts (`ctx.reply`, `ctx.answerCallbackQuery`). Update sources are async
generators - `longPoll(api, opts, signal)` replaces the old polling class. Two entry points share
**one** dispatch path: `bot.startPolling()` pumps `longPoll`; `bot.handleUpdate(update)` handles a
single update and is what `webhookCallback` calls.
### Webhooks
`webhookCallback(bot)` is a pure `(Request) => Promise<Response>` (Cloudflare Workers, Deno Deploy,
Vercel Edge, Bun.serve, Next.js App Router). Thin adapters mount it on an existing server:
`registerExpressWebhook`, `nodeFrameworkWebhook` (an `(req, res)` handler), `nextAppWebhook`. The
Node `createWebhookServer` (`/node`) adapts a `node:http` request and delegates to the same
callback - no duplicate request-handling logic.
### Transport & errors
`Transport` is the only module touching `fetch`; it is injectable (`opts.fetch`) so tests pass a
fake instead of monkeypatching globals. It merges the per-request timeout with the caller's signal
via `combineSignals` over `AbortSignal.timeout` (deliberately not `AbortSignal.any`, which needs
Node 18.17/20.3), unwraps the envelope, and retries: `429`s honor `retry_after` up to
`maxRetryAfterMs` (default 60s; a longer flood-wait surfaces immediately as a `TelegramApiError`),
transient failures (`NetworkError`/`TimeoutError`/5xx, classified by `isTransientError`) retry up to
`maxRetries` (default 2) with exponential jittered backoff. Opt-in proactive rate limiting
(`opts.rateLimit: { global?, perChat? }`) via a token-bucket keyed on `chat_id` - off by default,
zero overhead when unset.
Errors (`src/core/errors.ts`): a `TelegramBotError` base preserving `cause`, plus `NetworkError`,
`TimeoutError`, `ParseError`, `TelegramApiError` (structured `errorCode`/`description`/`parameters`
+ a `retryAfter` getter). Callers branch on values (`err.errorCode === 429`, `err.retryAfter`),
never on message text.
## Critical invariants
- **`src/core/api.ts` and `src/types/schemas.ts` are GENERATED** by `scripts/api-parser.ts` from
the live Bot API docs. Do not hand-edit them; change the generator and run `npm run generate:types`.
Adding a Bot API method = regenerate, never a hand-written method body. (See the `update-bot-api`
skill for the full flow, including the hand-added `Bot`/`Context` sugar.)
- **`src/core` must stay Node-free.** No `node:*` imports, no bare-builtin imports, no Node globals
(`Buffer`, `process`, `setImmediate`, ...). Enforced by both `lint:core` (static) and `check:edge`
(bundler). Anything needing Node goes in `src/node`.
- **Keep generated runtime lists in lockstep with their source types** (e.g. `UPDATE_TYPES`,
`MESSAGE_TYPES`) via the both-direction compile-time asserts already in place; `satisfies` alone
misses absent keys.
- **ASCII only** in code, comments, and commit messages: use `-`, `->`, `...` - never em dashes or
smart quotes (a commit hook rejects them; emoji are fine).Signals
- Stars
- 9,214
- Forks
- 1,645
- Open issues
- 0
- Last commit (this folder)
- Sep 6, 2026 (1 month ago)
- Last release
- Aug 24, 2026 (2 months ago)
From GitHub (fetched Oct 24, 2026).
Details
- License
- MIT
- Repository
- github.com/yagop/node-telegram-bot-api
- First seen
- Oct 24, 2026
- Last checked
- Oct 24, 2026
- Source
- GitHubgithub-graphql
Cite this page
CLAUDE.md · yagop/node-telegram-bot-api. Agent Mart. https://agentmart.dev/templates/yagop-node-telegram-bot-api-claude-md (last updated 2026-10-24)