@azurioh/discord-kernel
v0.1.0
Published
Framework kernel for Discord bots: command DSL, guards, interaction routing, composed components, i18n, ports (Presenter, Logger, DatabaseConnection, Authorizer, Scheduler).
Maintainers
Readme
@azurioh/discord-kernel
The framework half of a Discord bot, without the bot: a command DSL, guards, interaction routing, composed components, localisation, and the ports an application implements. It knows discord.js and nothing else — no database, no logger implementation, no opinion about what your bot does.
Built for Clean Architecture with vertically-sliced feature modules: the kernel is the innermost ring, and the fact that it is a package rather than a folder is what enforces that — an outer ring simply is not in its dependency graph.
pnpm add @azurioh/discord-kernel discord.jsdiscord.js is a peer dependency, never a dependency: two copies of the
library in one tree would break every instanceof the gateway relies on.
What is in it
| Area | What it gives you |
| ---- | ----------------- |
| discord/command/ | createCommand (flat or grouped), typed options, guards (allOf/anyOf, permissions, roles, cooldowns), the per-interaction Context, autocomplete, the CommandRouter |
| discord/interaction/ | createButton, createModal, the four typed select menus (createStringSelect, createChannelSelect, createRoleSelect, createUserSelect), the InteractionRouter/InteractionDispatcher seam |
| discord/components/ | ComponentRouter for persistent customId routing, and collector-backed screens: paginator, interactive-message, settings-editor |
| discord/events/ | createEvent and the EventRouter — the one place client.on is called |
| discord/ui/ | Embeds with Discord's size limits handled, createCard containers, a configurable palette, a colour-input parser |
| i18n/ | Catalogs ({ en, fr? }, en mandatory), a registry that fails the boot on a duplicate key, per-interaction locale resolution |
| authz/ | An Authorizer port with viewer/editor grants and a requireLevel guard |
| errors/ | BusinessError and friends, carrying an optional translation key beside the English source |
| scheduler/ | A Scheduler port and a node-cron implementation |
| Ports | Logger, DatabaseConnection, Presenter, Clock, ChannelExporter — declared here, implemented by your app |
Every path is imported directly; there is no root barrel:
import { createCommand } from "@azurioh/discord-kernel/discord/command/create-command";
import type { Logger } from "@azurioh/discord-kernel/logger";
import { type Catalog, createTranslator } from "@azurioh/discord-kernel/i18n";What is deliberately not in it
- A database.
DatabaseConnectionis a lifecycle port — connect, close — and nothing more. Query APIs differ too much between engines to abstract usefully, so an adapter exposes its own and your repositories are the only code that touches it. - A logger.
Loggeris a port; pino, Sentry orconsolelive in your app. - A configuration shape. The kernel ships env-reading primitives
(
requireEnv,enumEnv, …); which keys exist is your composition root's call. - A palette. Embeds render in Discord's blurple until you say otherwise, so a bot never starts out wearing someone else's identity.
Making it yours
Two things are global by nature — a bot has one look, decided once — and are set from the composition root before any module is constructed:
import { configureEmbedColors } from "@azurioh/discord-kernel/discord/ui/colors";
import { registerColorAliases } from "@azurioh/discord-kernel/discord/ui/color-input";
configureEmbedColors({ brand: 0xf3c909 });
registerColorAliases({ gold: "#f3c909", or: "#f3c909" });Everything else is injected: your modules are factories receiving a container you define, so nothing in the kernel is reachable as a singleton.
Requirements
Node 22.12+, discord.js 14.27+, TypeScript 5.9. The published build is
CommonJS with declaration files, resolved through explicit subpath exports.
License
MIT
