yaebal meta

the batteries-included entry point — the core engine, the auto-generated per-update contexts, and the most-used plugins behind a single import. media.path() works on node, bun and deno, and the same bot can run behind long polling or a fetch webhook. for a minimal build, use @yaebal/core directly — the meta package adds the generated context layer, the formatting/keyboard/callback-data helpers and a handful of the most-used plugins on top; nothing in core changes underneath it.

install

terminal
pnpm add yaebal

quick start

bot.ts
import { createBot, html } from "yaebal";

const bot = createBot(process.env.BOT_TOKEN!)
  .command("start", (ctx) => ctx.send("hi"))
  .on("message:text", (ctx) => ctx.reply(html`you said: <b>${ctx.text}</b>`));

await bot.start();

use createBot() for normal app code: it wires runtime rich contexts, so generated shortcut methods like ctx.react() are both typed and actually present when handlers run.

try it — bot.ts
import { createBot } from "yaebal";

const bot = createBot(process.env.BOT_TOKEN!);

bot.command("start", (ctx) => ctx.reply("hello from yaebal"));

bot.on("message:text", (ctx) => {
  console.log("got message:", ctx.text);
  ctx.reply(`you said: ${ctx.text}`);
});

bot.onStart((me) => console.log("bot ready"));

bot.start();

Bot vs createBot

bot-vs-createbot.ts
import { Bot, createBot, richContext } from "yaebal";

const token = process.env.BOT_TOKEN!;

// recommended for app code: rich generated contexts at runtime.
const bot = createBot(token);

// advanced: same Bot class, but you choose the context factory yourself.
const custom = new Bot(token, { contextFactory: richContext });

// bare new Bot(token) still has auto readFile and typed router overloads,
// but does not graft generated context methods at runtime unless you pass
// contextFactory or use createBot().
important: new Bot(token) from yaebal adds the meta package conveniences such as auto file reading and richer router typings, but it does not install richContext automatically. use createBot(token), or pass { contextFactory: richContext } yourself, when you want generated context methods at runtime.

rich, typed contexts

createBot() grafts the auto-generated shortcut methods onto every update — ctx.react, ctx.editText, ctx.pin, … — typed to the matching update and backed by the generated context classes.

rich.ts
import { createBot } from "yaebal";

const bot = createBot(process.env.BOT_TOKEN!);

bot.on("message:text", (ctx) => ctx.react("🔥"));          // MessageContext
bot.on("callback_query:data", (ctx) => ctx.answer("ok")); // CallbackQueryContext

exports

fromexportswhat
coreBot, Composer, Context, media, format, createApi, TelegramError, typesthe engine, base context, media, entity formatting and API client
contextsall generated context classes and contextForper-update context classes and shortcuts
yaebalcreateBot, richContextmeta-package runtime wiring for generated contexts
fmthtml, md, htmlToEntities, mdToEntitiesHTML/Markdown parsing into Telegram entities
filtersfilters, and, or, notcomposable, type-narrowing filters
keyboardInlineKeyboard, Keyboardfluent keyboard builders
callback-datacallbackDatatyped callback_data pack / unpack
sessionsessionper-chat state, pluggable storage
i18ni18nper-chat locale, ctx.t
skladMemoryStorage, redisStorage, sqliteStorage, kvStorage, StorageAdapterthe pluggable storage interface session (and other stateful plugins) build on, plus its adapters
againautoRetry, decideRetryretries 429 flood-waits and transient 5xx
auto-answerautoAnswerclears the callback-query spinner, alerts still win the race
hydratehydrate, hydrateApi, hydrateMessagea sent message carries editText/delete/pin/react
typingtypingctx.typing(fn) holds the indicator for as long as fn runs
filesfiles, createFiles, resolveFileId, FileDownloadctx.files: inspect, stream and download telegram files
file-idFileId, FileUniqueId, fileUniqueIdFromFileIddecode a file_id locally — no api call
splitsplitter, splitText, splitCaption, MAX_MESSAGE_LENGTHlong text as several messages, entity-aware
inline-resultsInlineQueryResult, InputMessageContenttyped answerInlineQuery payloads
webwebhook, serve, setWebhook, deleteWebhook, getWebhookInfo, dedupe, expressAdapter, fastifyAdapter, elysiaAdapter, awsLambdaAdapter, azureAdapter, gcfAdapter, cloudflareAdapterfetch/webhook helpers plus adapters for the most common node http frameworks and serverless platforms
the rule. stateless, first-party, no dependency footprint of its own → it ships in the meta package. not re-exported here: stateful/UI packages — @yaebal/scenes, @yaebal/conversation, @yaebal/prompt, @yaebal/morda, @yaebal/router and others — stay outside the meta package on purpose. import them directly; see the plugin catalog.

a quick tour

keyboards + typed callback data:

callbacks.ts
import { createBot, InlineKeyboard, callbackData } from "yaebal";

const vote = callbackData("vote", { id: Number });
const bot = createBot(process.env.BOT_TOKEN!);

bot.command("poll", (ctx) =>
  ctx.send("pick one", {
    reply_markup: new InlineKeyboard()
      .text("👍", vote.pack({ id: 1 }))
      .text("👎", vote.pack({ id: 2 }))
      .build(),
  }),
);

bot.callbackQuery(vote.pattern, (ctx) => {
  const data = vote.unpack(ctx.callbackQuery.data!);
  if (data) return ctx.answer("voted " + data.id);
});

per-chat sessions and i18n:

stateful.ts
import { createBot, session, i18n } from "yaebal";

const bot = createBot(process.env.BOT_TOKEN!)
  .install(session({ initial: () => ({ count: 0 }) }))
  .install(i18n({
    defaultLocale: "en",
    locales: { en: { hi: "hello" }, ru: { hi: "привет" } },
  }))
  .command("count", (ctx) => ctx.reply("#" + ++ctx.session.count))
  .command("start", (ctx) => ctx.reply(ctx.t("hi")));

media — no platform package to pick:

media.ts
import { createBot, media } from "yaebal";

const bot = createBot(process.env.BOT_TOKEN!);

bot.command("pic", (ctx) => ctx.sendPhoto(media.path("./cat.jpg"))); // node/bun/deno
// on edge, send media.url(...) / media.buffer(...) instead

run on the edge over webhooks:

worker.ts
import { createBot, webhook } from "yaebal";

export default {
  fetch(request: Request, env: { BOT_TOKEN: string; SECRET: string }) {
    const bot = createBot(env.BOT_TOKEN);
    bot.command("start", (ctx) => ctx.reply("running on the edge"));

    return webhook(bot, { secretToken: env.SECRET })(request);
  },
};
how the rich context works. core's Bot exposes a contextFactory hook; richContext builds the base Context and grafts the matching generated context's shortcut methods, accessors and payload fields onto it. core stays decoupled from @yaebal/contexts — the meta-package does the wiring.