runtime support
yaebal is fetch-first and has zero native-module dependencies in the core, so Bun, Deno, and edge runtimes like Cloudflare Workers work out of the box. This page lists what runs where.
support matrix
| feature | Node | Bun | Deno | CF Workers / edge |
|---|---|---|---|---|
| minimum version | ≥20 (18+ for fetch, but the repo targets 20) | latest | latest | n/a — managed by the platform |
@yaebal/core + most plugins | ✅ | ✅ | ✅ | ✅ |
media.path() (local files) | ✅ | ✅ | ✅ (needs --allow-read) | ❌ no filesystem — use media.url() / media.buffer() |
@yaebal/web — webhook() | ✅ | ✅ | ✅ | ✅ |
@yaebal/core/node — nodeWebhookCallback | ✅ | ✅ (compat) | ✅ (compat) | ❌ no node:http |
@yaebal/sklad storage adapters | ✅ all | ✅ all | ✅ all | ✅ memory/KV-backed only — anything wrapping node:fs needs a custom adapter |
@yaebal/panel main handler | ✅ | ✅ | ✅ | ✅ fetch handler |
@yaebal/panel/sqlite | ✅ Node ≥22.5 | ❌ | ❌ | ❌ |
@yaebal/panel/sklad (skladPanelStore) | ✅ any adapter | ✅ any adapter | ✅ any adapter | ✅ with an edge-safe adapter (kvStorage, redisStorage, MemoryStorage) — sqliteStorage still needs node:sqlite |
@yaebal/router (file-based) | ✅ | ✅ | ✅ | ❌ needs fs |
@yaebal/workers (thread pool) | ✅ | ✅ | ⚠️ limited | ❌ no worker_threads |
@yaebal/cron persisted store | ✅ any @yaebal/sklad adapter | ✅ | ✅ | ✅ with a KV-backed store; timers still run only while the isolate is alive |
create-yaebal (CLI) | ✅ | ✅ | ✅ | N/A |
(Request) => Promise<Response> instead of (req, res), yaebal adapters work everywhere without shims.Cloudflare Workers
webhook() from @yaebal/web returns a standard fetch handler. Workers
only give you env inside the fetch() export, so construct the bot
there rather than at module scope — a top-level new Bot(env.BOT_TOKEN) throws
because env doesn't exist yet when the module loads.
// Cloudflare Workers — fetch-first, runs at the edge
import { Bot } from "@yaebal/core";
import { webhook } from "@yaebal/web";
interface Env {
BOT_TOKEN: string;
}
export default {
fetch(request: Request, env: Env) {
// create the bot per-request: Workers give you "env" only inside fetch(),
// never at module scope.
const bot = new Bot(env.BOT_TOKEN);
bot.command("start", (ctx) => ctx.reply("hi from the edge"));
return webhook(bot)(request);
},
};Deno Deploy and Vercel Edge follow the same shape: a fetch handler, no Node globals.
Bun
Pass the same fetch handler to Bun.serve. Bun's Node compatibility layer means
you can also use nodeWebhookCallback with Bun.serve's node:http-style API if you prefer, but the fetch path is simpler.
// Bun — built-in HTTP server
import { Bot } from "@yaebal/core";
import { webhook } from "@yaebal/web";
const bot = new Bot(process.env.BOT_TOKEN!);
bot.on("message:text", (ctx) => ctx.reply(ctx.text));
Bun.serve({ port: 8080, fetch: webhook(bot) });Deno
Deno.serve accepts a fetch handler directly. Import from npm via Deno's npm
specifier support (npm:@yaebal/core) or use a local build.
// Deno — built-in HTTP server
import { Bot } from "@yaebal/core";
import { webhook } from "@yaebal/web";
declare const Deno: {
env: { get(key: string): string | undefined };
serve(options: { port: number }, handler: (request: Request) => Response | Promise<Response>): unknown;
};
const bot = new Bot(Deno.env.get("BOT_TOKEN")!);
bot.on("message:text", (ctx) => ctx.reply(ctx.text));
Deno.serve({ port: 8080 }, webhook(bot));edge limitations
Edge runtimes (Cloudflare Workers, Deno Deploy, Vercel Edge) don't expose the filesystem or worker_threads, so anything that relies on those won't run there:
media.path()— reads local files off disk; usemedia.url()for a public URL ormedia.buffer()for in-memory bytes instead. See media & files.@yaebal/panel/sqlite— optional SQLite store; usesnode:sqliteand requires Node ≥22.5. the main@yaebal/panelfetch handler runs fine on edge — pair it withskladPanelStorefrom@yaebal/panel/skladand a KV/Redis/memory sklad adapter instead of sqlite.@yaebal/router— discovers handler files at startup; needsfs@yaebal/workers— spawns aworker_threadspool; not available at the edge@yaebal/core/node— usesnode:http; keep it out of edge bundles.
Everything else — core, all filter/keyboard/session/i18n plugins, webhook() — runs fine at the edge.