The framework's bus keys
Subscribe to the keys seedcord publishes on the bus. Covers which transport publishes each key, deploys, incoming interactions and events, unhandled errors, and faults.
seedcord publishes its own keys on core.bus, alongside the keys you declare yourself. Without them, logging every deploy, tracing each interaction, or posting faults to a channel means wrapping your own handlers and deploy script. You subscribe to them with @Subscribe or core.bus.on, the same way you subscribe to your own keys. You can't publish them yourself.
The where column says which transport publishes each key.
| key | where | fires when |
|---|---|---|
unknownException | both | a raw throw reaches the fault boundary, or the Node process catches an unhandled error |
handledException | both | a Notice with report true is caught |
unhandledInteractionError | both | an interaction dispatch throws past the boundary |
interactionDispatched | both | one dispatch finishes |
responseAttempted | both | any write through the reply surface |
commandsDeployed | both | a command deploy finishes |
anyInteraction | both | an interaction arrives, before routing |
unhandledEventError | gateway | an event dispatch throws past the boundary |
eventDispatching | gateway | before the handlers for an event run |
eventDispatched | gateway | those handlers finish |
Gateway and http differ
anyInteraction carries a discord.js Interaction on gateway and an APIInteraction on http. If a subscriber calls a discord.js method on it, that subscriber compiles on gateway and fails to compile on http.
On http, seedcord publishes it only after the request passes Ed25519 verification and the replay checks. A forged or repeated request never reaches it.
After a deploy
seedcord logs every deploy and fills Commands with each command's mention, so your handlers don't need this key. commandsDeployed is for sending your commands somewhere outside the bot, like a bot listing site such as Top.gg that shows your command list. It fires after every deploy, including the redeploy that follows a hot reload.
import { Subscribe, Subscriber } from '@seedcord/gateway';
@Subscribe('commandsDeployed')
export class ListCommands extends Subscriber<'commandsDeployed'> {
public async execute(): Promise<void> {
// topgg is a client from Top.gg's own SDK
await topgg.postCommands(this.data.global);
}
}global and the values in guilds are APIApplicationCommand[] straight from Discord, so you can pass them on as they are. A guild command only exists in its own guild, which is why ListCommands sends global alone.
Watching everything come in
A trace of everything your bot receives helps when a command doesn't seem to run at all. anyInteraction fires before seedcord routes an interaction anywhere. eventDispatching fires for client events, ahead of the handlers.
@Subscribe('eventDispatching')
export class Trace extends Subscriber<'eventDispatching'> {
public async execute(): Promise<void> {
const { name, args } = this.data;
this.logger.debug(
`${name} arrived with ${args.length} args`
);
}
}Trace reads only args.length, which works for any event. name and args narrow together, so a check like name === 'messageCreate' gives you that event's own tuple in args.
Warning
eventDispatching fires only when at least one handler is about to run. It doesn't fire for an event that no handler registered, or for an event whose handlers were all once and have already run. It fires before your middleware runs, so if a middleware stops the event, this key has already been published.
eventDispatched fires once the handlers finish. Its fields, including each handler's outcome, are on the telemetry page.
The two unhandled errors
A throw from your handlers, gates, or middleware reaches the fault boundary, which catches and reports it. unhandledInteractionError and unhandledEventError cover a throw the boundary never sees, like one inside seedcord's own dispatch code (report it (opens in a new tab)). Both carry only { error: Error }. Subscribe to them to hear about those failures in production, since your handlers' own reporting never sees them.
The fault keys
These two keys carry a fault out of your bot, to a log service or a Discord channel. unknownException fires on a raw throw.
| field | type | what it holds |
|---|---|---|
uuid | UUID | this fault, and the id the error card shows the user |
dispatchId | string | null | the dispatch it came from, null on a process-level throw such as an unhandled rejection |
error | Error | the throw itself |
origin | string | where it came from, for example slash:ban or event:messageCreate:AutoMod |
guild | { id, name } | optional, the guild the dispatch ran in |
user | { id, username } | optional, whoever ran it |
metadata | unknown | optional. On an event fault it holds the event name, the handler, and the args |
A reported Notice thrown from an autocomplete also publishes here, since FaultSource only has shapes for interactions and events. Check error with instanceof Notice if your subscriber treats the two differently.
handledException fires on a Notice with report true.
| field | type | what it holds |
|---|---|---|
uuid | UUID | this fault, and the id the error card shows the user |
dispatchId | string | the dispatch that reported it |
origin | string | the same shape unknownException carries |
denial | Notice | the Notice itself |
source | FaultSource | where the throw came from, with kind of interaction or event (http only produces interaction) |
An event origin carries a third segment naming the handler, since one dispatch runs several of them.
seedcord ships a subscriber for each of these keys and registers it for you. Each one posts a card to a Discord webhook once you set its environment variable.
UNKNOWN_EXCEPTION_WEBHOOK_URL=https://discord.com/api/webhooks/...
HANDLED_EXCEPTION_WEBHOOK_URL=https://discord.com/api/webhooks/...If a variable isn't set, that reporter stays off. seedcord logs a warning about it at startup.