@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

terminal
pnpm add @yaebal/toml

bot.toml

all route arrays are optional. an empty config is valid and registers nothing. every route must define at least reply or handler.

bot.toml
[bot]
name = "demo"

[[commands]]
name = "start"
description = "start command"
reply = "привет! я бот из toml."

[[commands]]
name = "ping"
handler = "ping"

[[hears]]
text = "ping"
reply = "pong"

[[messages]]
on = "message:text"
contains = "yaebal"
reply = "yaebal мощь"

[[callbacks]]
data = "profile"
handler = "profileCallback"

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.

index.ts
import { Bot } from "@yaebal/core";
import { installToml } from "@yaebal/toml";

const bot = new Bot(process.env.BOT_TOKEN!);

await installToml(bot, "./bot.toml", {
  handlers: {
    ping: async (ctx) => {
      await ctx.reply("pong from typescript");
    },
    profileCallback: async (ctx) => {
      await ctx.reply("profile");
    },
  },
});

await bot.start();

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.

tomlregistered as
[[commands]] name = "start" reply = "hi"bot.command("start", ctx => ctx.reply("hi"))
[[hears]] text = "ping" reply = "pong"bot.hears("ping", ...)
[[messages]] on = "message:text" contains = "x"bot.on("message:text", ...) plus a text filter
[[callbacks]] data = "profile" handler = "profile"bot.callbackQuery("profile", handlers.profile)
if both handler and reply are present, the handler wins. the reply is only used when no handler is configured.

example errors: Missing handler "ping" referenced in commands[1], commands[0] must define either reply or handler, messages[0].on is required.

plugin usage

createTomlPlugin returns a normal yaebal plugin, so it can be installed through bot.install() or composed with other feature modules.

plugin.ts
import { createTomlPlugin } from "@yaebal/toml";

bot.install(createTomlPlugin("./bot.toml", { handlers }));

api

exportsignaturedescription
installToml(bot, configPathOrObject, options?) => botparse, validate and register routes on an existing bot or composer
createTomlPlugin(configPathOrObject, options?) => Plugincreate a yaebal-compatible plugin that installs the toml routes
parseTomlConfig(input) => TomlBotConfigparse from file path, raw toml string or parsed object, then validate
validateTomlConfig(input) => TomlBotConfigruntime validation through zod with human-readable error paths

raw strings and objects

file paths are the common case, but tests and generated configs can pass raw toml or an object.

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

status: experimental. the format is intentionally small: commands, hears, message filters and callback queries. no hidden dependency graph, no dynamic import magic, no second routing engine.