Skip to main content

📸 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 its EventDispatcher.
  • EventDispatcher: Stored by a bus, dispatches an event to the subscribers its passed, checking its SubscriberMiddlewareList and passing any errors to its ErrorHandler.
  • EventSubscribers: Objects that subscribe to an event emitted by an EventBus.

🔢 Event call sequence

tip
  • 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

note

Imports are omitted on these examples for simplicity.

  1. Extend AbstractDJSClientSubscriber, implementing handleEvent() and event.
  2. 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);

👂 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().

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);

🤖 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 anBasicEventBus from@framework(recommended).
  • Implementing theEventBus interface from@core.
note

There are two options to instantiate a default BasicEventBus.

Either with:

  • BasicEventBus#createAsync() which uses anAsyncEventDispatcher.
  • BasicEventBus#createSync() which uses anSyncEventDispatcher.

In

  • AnAsyncEventDispatcher executes multiple subscribers at a time, on a fixed amount.
  • AnSyncEventDispatcher 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().

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);

📦 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);