@yaebal/runner
process updates concurrently instead of the built-in sequential long-poll — while keeping each chat's updates in order, so sessions stay safe.
install
pnpm add @yaebal/runnerusage
run(bot) replaces bot.start(). It polls in batches and dispatches to a
bounded pool, so a slow handler no longer blocks the whole queue.
import { run } from "@yaebal/runner";
// instead of bot.start(): drive the bot concurrently
const handle = run(bot, { concurrency: 50 });
// later, drain in-flight work and stop:
await handle.stop();per-chat ordering
Updates that share a key — the chat id by default — run strictly in submit order and never overlap,
so per-chat state (sessions) is race-free. Unrelated chats run in parallel up to concurrency.
run(bot, {
concurrency: 100,
sequentializeBy: (u) => u.message?.chat.id, // default: chat id
limit: 100, // getUpdates batch size
timeout: 30, // long-poll seconds
onError: (err, update) => log.error(err),
});options
| option | default | meaning |
|---|---|---|
concurrency | 50 | max updates processed at once |
sequentializeBy | chat id | key whose updates stay ordered; undefined = no ordering |
limit | 100 | getUpdates batch size |
timeout | 30 | long-poll seconds |
allowedUpdates | — | restrict update types |
onError | — | handler / polling error callback |
the scheduler
The core is a reusable bounded-concurrency scheduler with per-key sequentialization — exported in case you want it directly.
import { createScheduler } from "@yaebal/runner";
const s = createScheduler(8);
s.submit("chat-42", () => doWork()); // same key → strict order; different keys → parallel
await s.idle(); // resolves when everything drainsWorkers help only when handlers are CPU-bound. Most bots are I/O-bound, and this runner already
saturates the event loop. For genuinely heavy CPU work, offload it with @yaebal/workers.