@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

terminal
pnpm add @yaebal/runner

usage

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.

bot.ts
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.

options.ts
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

optiondefaultmeaning
concurrency50max updates processed at once
sequentializeBychat idkey whose updates stay ordered; undefined = no ordering
limit100getUpdates batch size
timeout30long-poll seconds
allowedUpdatesrestrict update types
onErrorhandler / polling error callback

the scheduler

The core is a reusable bounded-concurrency scheduler with per-key sequentialization — exported in case you want it directly.

scheduler.ts
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 drains
Workers 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.