@yaebal/again
auto-retry on 429 / flood-wait and transient 5xx errors.
install
pnpm add @yaebal/againusage
call autoRetry(bot.api) once after constructing the bot. it installs an error hook
on the API layer — every failed call is inspected and, if retryable, waited on and re-issued
automatically. no middleware registration needed.
import { Bot } from "@yaebal/core";
import { autoRetry } from "@yaebal/again";
const bot = new Bot(process.env.BOT_TOKEN!);
// attach the retry handler to the bot's API layer
autoRetry(bot.api);
// all API calls made through bot.api will now be retried automatically
bot.on("message:text", (ctx) => ctx.reply("hello!"));
bot.start();options
autoRetry(bot.api, {
maxRetries: 5, // retry up to 5 times (default: 3)
maxDelayMs: 10_000, // cap each wait at 10 s (default: 30 000)
retryOnInternal: false, // skip 5xx, only handle 429 (default: true)
});api
| export | signature | description |
|---|---|---|
autoRetry | (api: Api, options?: AutoRetryOptions) => void | installs the retry hook on bot.api |
decideRetry | (error: unknown, attempt: number, options?: AutoRetryOptions) => ErrorAction | undefined | pure retry-policy function — exported for unit testing |
AutoRetryOptions | interface | options bag passed to both functions |
AutoRetryOptions
| field | type | default | description |
|---|---|---|---|
maxRetries | number | 3 | max retries after the first attempt |
maxDelayMs | number | 30000 | cap on a single wait in milliseconds |
retryOnInternal | boolean | true | also retry transient 5xx server errors |
retry logic
decideRetry inspects errors in this order:
- attempt exceeds
maxRetries→ no retry - not a
TelegramError→ no retry - code 429 → reads
retry after Nfrom the message; falls back to exponential backoff (2^attemptseconds); capped atmaxDelayMs - code ≥ 500 and
retryOnInternalis true → exponential backoff; capped atmaxDelayMs - anything else (4xx client errors, unknown errors) → no retry
import { decideRetry, type AutoRetryOptions } from "@yaebal/again";
import { TelegramError } from "@yaebal/core";
const opts: AutoRetryOptions = { maxRetries: 3, maxDelayMs: 30_000 };
const action = decideRetry(new TelegramError("sendMessage", 429, "retry after 7"), 1, opts);
// => { retry: true, delayMs: 7000 }4xx errors are never retried. a 400 Bad Request means the call itself is wrong
— retrying it would loop forever. only 429 and 5xx are candidates.
decideRetry is a pure function with no I/O — it is exported
specifically so you can unit-test a custom policy without mocking network calls.