@yaebal/router
file-based routing — load command and event handlers from a routes/ directory by convention.
install
pnpm add @yaebal/routerdirectory convention
place handler files under two sub-directories inside your routes folder. each file must export default a handler function. the filename determines the trigger.
routes/
commands/
start.ts # → bot.command("start", handler)
help.ts # → bot.command("help", handler)
on/
message.text.ts # → bot.on("message:text", handler)
callback_query.data.ts # → bot.on("callback_query:data", handler)files under commands/ map to bot.command(name). files under on/ map to bot.on(query). because : is not a legal
character in filenames on all operating systems, use . as a separator — dots
are converted to colons automatically (message.text.ts → "message:text").
handler files
// routes/commands/start.ts
import type { Context } from "@yaebal/core";
export default async (ctx: Context) => {
await ctx.reply("welcome!");
};// routes/on/message.text.ts
import type { Context } from "@yaebal/core";
export default async (ctx: Context) => {
await ctx.reply(`you said: ${ctx.text}`);
};usage
call loadRoutes(bot, dir) once after constructing the bot. it scans both
sub-directories, dynamically imports each file, and registers the default export on the
bot. it returns the list of registered route strings — useful for a startup log.
import { Bot } from "@yaebal/core";
import { loadRoutes } from "@yaebal/router";
import { fileURLToPath } from "node:url";
import { join, dirname } from "node:path";
const bot = new Bot(process.env.BOT_TOKEN!);
const __dirname = dirname(fileURLToPath(import.meta.url));
const registered = await loadRoutes(bot, join(__dirname, "routes"));
console.log("registered routes:", registered);
// ["command:start", "command:help", "on:message:text"]
bot.start();api
| export | signature | description |
|---|---|---|
loadRoutes | (bot: RouteTarget, dir: string) => Promise<string[]> | scans dir/commands/ and dir/on/, registers handlers, returns registered route names |
routeFromFile | (kind: "commands" | "on", file: string) => \{ method: "command" | "on"; trigger: string \} | pure mapping from filename to registration params — exported for testing |
RouteTarget | interface | minimal interface satisfied by Bot and Composer: command() + on() |
RouteTarget
| method | description |
|---|---|
command(name, ...handlers) | register a command handler |
on(query, ...handlers) | register an event handler |
routeFromFile
the mapping function is exported so you can test your own filename conventions or preview what trigger a filename will produce without touching the filesystem.
import { routeFromFile } from "@yaebal/router";
routeFromFile("commands", "start.js");
// => { method: "command", trigger: "start" }
routeFromFile("on", "message.text.js");
// => { method: "on", trigger: "message:text" }
routeFromFile("on", "callback_query.data.ts");
// => { method: "on", trigger: "callback_query:data" }routes/commands/ or routes/on/ does not exist, loadRoutes returns an empty list
rather than throwing — useful during incremental setup. files without a default export are also skipped. only files that export a function as their default export are registered.
.d.ts declaration files are
always excluded from scanning. supported extensions:
.ts, .js, .mjs.