@yaebal/toml
declarative toml routes for yaebal bots. describe simple commands, text routes, message filters, callback queries and replies in toml, then keep real logic in typescript handlers.
install
pnpm add @yaebal/tomlbot.toml
all route arrays are optional. an empty config is valid and registers nothing. every route must
define at least reply or handler. hears takes exactly one
of text (exact match) or regex; callbacks takes exactly
one of data or regex. a regex value is compiled with new RegExp() at install time — an invalid pattern is a startup error, not a dead
route.
[bot]
name = "demo"
[[commands]]
name = "start"
description = "say hello"
reply = "привет! я бот из toml."
[[commands]]
name = "ping"
description = "check the bot is alive"
handler = "ping"
[[hears]]
regex = "^p[io]ng$"
reply = "pong"
[[messages]]
on = "message:text"
contains = "yaebal"
reply = "yaebal мощь"
[[callbacks]]
data = "profile"
handler = "profileCallback"
[[callbacks]]
regex = "^item:\\d+$"
reply = "открываю товар…"usage
call installToml once before bot.start(). it accepts a file path,
a raw toml string, or an already parsed object, and returns the same bot or composer instance.
the whole config is validated up front, so a bad route can never leave the bot half-wired.
import { Bot } from "@yaebal/core";
import { installToml } from "@yaebal/toml";
const bot = new Bot(process.env.BOT_TOKEN!);
installToml(bot, "./bot.toml", {
syncCommands: true, // push described commands to the / menu on start
handlers: {
ping: async (ctx) => {
await ctx.reply("pong from typescript");
},
profileCallback: async (ctx) => {
await ctx.answerCallbackQuery();
await ctx.reply("profile");
},
},
});
await bot.start();command menu
with syncCommands: true, commands that have a description are pushed
to the telegram command menu (setMyCommands) once the bot starts. commands without
a description are still routed — they just stay out of the menu. syncCommands needs a Bot target (it hooks onStart); installing on a plain Composer fails with a readable error.
handler registry
handler = "name" looks up options.handlers.name. if the handler is
missing, installation fails with a readable startup error instead of silently skipping the route.
| toml | registered as |
|---|---|
[[commands]] name = "start" reply = "hi" | bot.command("start", ctx => ctx.reply("hi")) |
[[hears]] text = "ping" reply = "pong" | bot.hears("ping", ...) — exact match |
[[hears]] regex = "^p[io]ng$" | bot.hears(/^p[io]ng$/, ...) |
[[messages]] on = "message:text" contains = "x" | bot.on("message:text", ...) plus a text filter |
[[callbacks]] data = "profile" handler = "profile" | bot.callbackQuery("profile", handlers.profile) |
[[callbacks]] regex = "^item:\d+$" reply = "…" | bot.callbackQuery(/^item:\d+$/, ...) |
handler and reply are present, the handler wins. the reply is
only used when no handler is configured. callback routes that use reply answer the
callback query first (answerCallbackQuery), so the button never hangs on a spinner
— handler-based callback routes answer it themselves. example errors:
Missing handler "ping" referenced in commands[1], commands[0] must define either reply or handler, hears[0] must define either text or regex, messages[0].on unknown update type "mesage".validation
the config is validated with zod before anything touches the bot: every route needs a response, regex patterns must compile, and the update-type prefix of messages[].on is checked against the real bot api update names (generated from the
telegram schema), so a typo like "mesage:text" fails at install instead of
registering a route that never matches.
plugin usage
createTomlPlugin returns a normal yaebal plugin, so it can be installed through bot.install() or composed with other feature modules.
import { createTomlPlugin } from "@yaebal/toml";
bot.install(createTomlPlugin("./bot.toml", { handlers }));api
| export | signature | description |
|---|---|---|
installToml | (bot, configPathOrObject, options?) => bot | parse, validate and register routes on an existing bot or composer |
createTomlPlugin | (configPathOrObject, options?) => Plugin | create a yaebal-compatible plugin that installs the toml routes |
parseTomlConfig | (input) => TomlBotConfig | parse from file path, raw toml string or parsed object, then validate |
validateTomlConfig | (input) => TomlBotConfig | runtime validation through zod with human-readable error paths |
options.handlers is the named handler registry; options.syncCommands enables the command-menu sync described above.
raw strings and objects
file paths are the common case, but tests and generated configs can pass raw toml or an object. a path that does not exist fails with a clear read error; a path-looking string that cannot be parsed as toml gets a "is this a missing file?" hint.
import { parseTomlConfig, validateTomlConfig } from "@yaebal/toml";
const config = parseTomlConfig(`[[commands]]
name = "start"
reply = "hi"
`);
validateTomlConfig(config);limits
toml is not a replacement for typescript. use it for routes and simple replies; use the handler registry for database calls, external services, branching flows, permissions and anything that needs compile-time types.