📸 Events
Hey there 👋! This guide is here to give you a fast understanding of how nyx's event system works, so you can use it right away. For in-depth details, see the respective guides of each event related object.
📚 Description
The event system in nyx is powered by EventBuses
. These are objects that store and notify their subscribers when an
event is called either internally or from another source.
Specifically, the event related objects are:
EventManager
: The entry point for the event system, holding all the event-related objects and methods that use all of these objects.EventBuses
: Store and notify their subscribers when an event happens, using itsEventDispatcher
.EventDispatcher
: Stored by a bus, dispatches an event to the subscribers its passed, checking itsSubscriberMiddlewareList
and passing any errors to itsErrorHandler
.EventSubscribers
: Objects that subscribe to an event emitted by anEventBus
.
🔢 Event call sequence
- A dashed step means it's executed asynchronously, so the next one is inmediately executed.
- You can hover over steps with
(?)
to see extra details.
✨ Quick examples
Imports are omitted on these examples for simplicity.
- Listening to interactionCreate
- Making an EventBus
- Listening to command runs
- Extend
AbstractDJSClientSubscriber
, implementinghandleEvent()
andevent
. - Register it to a bot's
EventManager
.
class InteractionCreateSubscriber
extends AbstractDJSClientSubscriber<Events.InteractionCreate> {
protected readonly event = Events.InteractionCreate;
public handleEvent(
meta: EventDispatchMeta,
interaction: Interaction,
): void {
const bot = meta.getBot(true);
bot.getLogger().info(`Interaction ${interaction.id} received.`);
}
}
const subscriber = new InteractionCreateSubscriber();
await bot.getEventManager().subscribeClient(subscriber);
- Instantiate a bus with either
BasicEventBus#createAsync()
orBasicEventBus#createSync()
. - Register it to a bot's
EventManager
.
// Or BasicEventBus#createSync()
const myBus = BasicEventBus.createAsync(
bot,
Symbol('myBus'), // Bus ID
);
await bot.getEventManager().addEventBus(myBus);
- Extend
AbstractEventSubscriber
, implementinghandleEvent()
andevent
. - Register it to a bot's
CommandManager
event bus.
class CommandCallSubscriber
extends AbstractEventSubscriber<CommandEventArgs, typeof CommandEventEnum.CommandRun> {
protected readonly event = CommandEventEnum.CommandRun;
// All of these arguments are safely typed
public handleEvent(
meta: EventDispatchMeta,
command: ExecutableCommand<CommandData>,
interaction: CommandExecutableInteraction,
commandMeta: CommandExecutionMeta,
) {
const bot = commandMeta.getBot(true);
bot.getLogger().info(`Command '${command.getData().name}' called.`);
}
}
await bot.getCommandManager().getEventBus().subscribe(subscriber);
👂 Subscribing to an event
To subscribe to an event you first need an EventSubscriber
object.
You can create these by either:
- Extending
AbstractEventSubscriber
from@framework
(recommended). - Instantiating a
SubscriberCallbackWrapper
from@framework
, passing a callback function. - Implementing the
EventSubscriber
interface from@core
.
After creating it, you finish by subscribing it to the bus that you want with EventBus#subscribe()
.
- Extending AbstractEventSubscriber
- Instantiating SubscriberCallbackWrapper
- Implementing EventSubscriber
import { AbstractEventSubscriber } from '@nyx-discord/framework';
import type { EventDispatchMeta } from '@nyx-discord/core';
class MyEventSubscriber extends AbstractEventSubscriber<MyEventsArgs, 'someEvent'> {
protected readonly event = 'someEvent';
public handleEvent(meta: EventDispatchMeta, ...args: MyEventsArgs['someEvent']) {
// Null if you didn't pass a bot while creating your bus
const bot = meta.getBot();
if (!bot) {
console.log('Hello world');
return;
}
bot.getLogger().log('Hello world');
}
}
const frameworkSubscriber = new MyEventSubscriber();
await myBus.subscribe(frameworkSubscriber);
import { SubscriberCallbackWrapper } from '@nyx-discord/framework';
const callbackSubscriber = new SubscriberCallbackWrapper < MyEventsArgs, 'someEvent'>(
'someEvent',
(meta: EventDispatchMeta, ...args: MyEventsArgs['someEvent']) => {
// Null if you didn't pass a bot while creating your bus
const bot = meta.getBot();
if (!bot) {
console.log('Hello world');
return;
}
bot.getLogger().log('Hello world');
}
);
await myBus.subscribe(callbackSubscriber);
import { EventSubscriber } from '@nyx-discord/core';
class MyInterfaceEventSubscriber implements EventSubscriber {
// ...
}
const interfaceSubscriber = new MyInterfaceEventSubscriber();
await myBus.subscribe(interfaceSubscriber);
🤖 Subscribing to the Client
A common operation is to subscribe to the Discord.js Client to listen to client events. To do so, you need an
EventSubscriber
that receives any of discord.js' ClientEvents
as arguments, then just register said subscriber to
the manager's client event bus.
There's a utility class on @nyx-discord/framework
for a Client subscriber, the AbstractDJSClientSubscriber
. It
receives the handled event as a generic, where you can use the discord.js Event
enum. This value is used to safely
type the arguments of the event handler.
import { Events } from 'discord.js';
import type { Interaction } from 'discord.js';
import { AbstractDJSClientSubscriber } from '@nyx-discord/framework';
import type { EventDispatchMeta } from '@nyx-discord/core';
class InteractionCreateSubscriber
extends AbstractDJSClientSubscriber<Events.InteractionCreate> {
protected override readonly event = Events.InteractionCreate;
public handleEvent(meta: EventDispatchMeta, interaction: Interaction) {
const bot = meta.getBot(true);
bot.getLogger().info(`Interaction ${interaction.id} received.`);
}
}
const subscriber = new InteractionCreateSubscriber();
await bot.getEventManager().subscribeClient(subscriber);
🚌 Creating an event bus
You can create your own event buses by either:
- Instantiating an
BasicEventBus
from@framework
(recommended). - Implementing the
EventBus
interface from@core
.
There are two options to instantiate a default BasicEventBus
.
Either with:
BasicEventBus#createAsync()
which uses anAsyncEventDispatcher
.BasicEventBus#createSync()
which uses anSyncEventDispatcher
.
In
- An
AsyncEventDispatcher
executes multiple subscribers at a time, on a fixed amount. - An
SyncEventDispatcher
executes one subscriber at a time, with a fixed execution time until it passes to the next.
It's advised that you check the ⚡ Event Dispatcher documentation for more information.
After creating it, you finish by registering it with EventManager#addEventBus()
.
- Instantiating BasicEventBus
- Implementing EventBus
import { BasicEventBus } from '@nyx-discord/framework';
// Uses an AsyncEventDispatcher
const asyncBus = BasicEventBus.createAsync(
bot,
Symbol('myAsyncBus'), // Bus ID
);
// Uses a SyncEventDispatcher
const syncBus = BasicEventBus.createSync(
bot,
Symbol('mySyncBus'), // Bus ID
);
await bot.getEventManager()
.addEventBus(asyncBus)
.addEventBus(syncBus);
import { EventBus } from '@nyx-discord/core';
class MyInterfaceEventBus implements EventBus {
// ...
}
const interfaceBus = new MyInterfaceEventBus();
await bot.getEventManager().addEventBus(interfaceBus);
📦 Event Type Safety
To ensure type safety when emitting events, event buses take a generic type of Record<string, unknown[]>
, that
represents the events that the bus emits, and their arguments.
A typed event bus will also cause a type error when trying to add a EventSubscriber
whose arguments don't match any
event that the bus emits.
import { BasicEventBus } from '@nyx-discord/framework';
interface TicketEventsArgs {
ticketCreate: [ticket: Ticket],
ticketClose: [closedTicket: Ticket, closer: TicketUser],
}
const ticketBus = BasicEventBus.createAsync<TicketEventsArgs>(
bot,
Symbol('TicketEventBus'),
);
const createdTicket: Ticket = {};
const closedTicket: Ticket = {};
const closerUser: TicketUser = {};
await ticketBus.emit('ticketCreate', [createdTicket]);
await ticketBus.emit('ticketClose', [closedTicket, closerUser]);
await ticketBus.emit('ticketCreate', ['Ticket']);
await ticketBus.emit('ticketClose', [ticketUser]);
await ticketBus.emit('ticketClose', []);
const validSubscriber = new SubscriberCallbackWrapper<TicketEventsArgs, 'ticketCreate'>(
'ticketCreate',
(meta: EventDispatchMeta, createdTicket: Ticket) => {
// Do something
}
);
const invalidSubscriber = new SubscriberCallbackWrapper<TicketEventsArgs, 'ticketCreate'>(
'ticketCreate',
// Errors since TicketEventsArgs['ticketCreate'] doesn't match [number]
(meta: EventDispatchMeta, createdTicket: number) => {
// Do something
}
);
ticketBus
.subscribe(validSubscriber)
// Errors since [number] doesn't match any event argument on TicketEventsArgs
.subscribe(invalidSubscriber);