Skip to main content

📣 Event Buses

EventBuses are objects responsible for containing and notifying subscribed EventSubscribers about events. These events can be emitted internally, typically via an EventEmitter, or externally, via EventBus#emit().

An EventBus consists of:

  • An EventSubscriber collection for storing subscribers, mapped by their IDs.
  • An EventDispatcher to notify event calls to subscribers.

As well as methods to interact with them.

👷 Creation

You can create an event bus by either:

  • Instantiating an BasicEventBus from @framework (recommended).
  • Implementing the EventBus interface from @core.
import { BasicEventBus } from '@nyx-discord/framework';

// Uses an AsyncEventDispatcher
const asyncBus = BasicEventBus.createAsync(
bot, // Can be null
Symbol('myAsyncBus'), // Bus ID
);

// Uses a SyncEventDispatcher
const syncBus = BasicEventBus.createSync(
bot, // Can be null
Symbol('mySyncBus'), // Bus ID
);

await bot.getEventManager()
.addEventBus(asyncBus)
.addEventBus(syncBus);
note

There are two options to instantiate a default BasicEventBus.

Either with:

  • BasicEventBus#createAsync() which uses an AsyncEventDispatcher.
  • BasicEventBus#createSync() which uses an SyncEventDispatcher.

In summary:

  • An AsyncEventDispatcher executes multiple subscribers at a time, on a fixed amount.
  • An SyncEventDispatcher executes one subscriber at a time, but has a fixed execution time until it passes to the next.

It's advised that you check the ⚡ Event Dispatcher documentation for more information.

📩 Subscription

Read the 📩 Event Subscribers documentation for more details on how to subscribe to a bus.

📦 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 for the event they listen don't match the event arguments on the bus.

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

🔃 Subscriber Sorting

By default, subscribers are sorted by their priority on EventSubscriber#getPriority(). You can override this by calling the EventBus#sortSubscribers() method, which takes a comparator function.

myEventBus.sortSubscribers((subscriberA, subscriberB) => {
return subscriberA.getPriority() - subscriberB.getPriority();
});

This method is saved and called automatically when adding a new subscriber to the bus, ensuring that subscribers are always sorted.

🔒 Locking

Event buses extend the Lockable interface, so that they can be locked and unlocked.

If an event bus is locked, the EventManager#removeEventBus() method will throw an LockedObjectError when trying to remove it.

You can set an event bus as locked by calling the BasicEventBus#lock() method (or overriding the locked property when extending).

import { BasicEventBus } from '@nyx-discord/framework';

const myEventBus = BasicEventBus.createAsync<MyEventsArgs>(bot, Symbol('MyEventBus'));
myEventBus.lock();

await bot.getEventManager().addEventBus(myEventBus);
await bot.getEventManager().removeEventBus(myEventBus); // throws LockedObjectError

myEventBus.unlock();
await bot.getEventManager().removeEventBus(myEventBus); // passes

⏰ Using EventEmitters

Event buses are designed to notify subscribers only when the EventBus#emit() method is manually called.

However, in certain cases, a third party Node EventEmitter can be used to emit events, like the Discord.js Client. In such cases, the event bus can act as a "wrapper" for the specified emitter.

You can create such buses by either:

  • Instantiating an BasicEventEmitterBus from @framework (recommended).
  • Implementing the EventEmitterBus interface from @core.
import { BasicEventEmitterBus } from '@nyx-discord/framework';

// Example using the client emitter
const clientEventBus = BasicEventEmitterBus.createAsyncWithEmitter<MyEventArgs>(
bot, // Can be null
Symbol('ClientEventBus'),
bot.client
);

// Example using a custom event emitter
const myEventEmitterBus = BasicEventEmitterBus.createAsyncWithEmitter<MyEventArgs>(
bot, // Can be null
Symbol('MyEventEmitterBus'),
new EventEmitter(),
)

await bot.getEventManager()
.addEventBus(clientEventBus)
.addEventBus(myEventEmitterBus);
info

The DefaultEventManager#create() method uses a SyncEventDispatcher for the client bus.

This is to avoid confusion with developers that are already familiar with Node's EventEmitter, which is sync in nature.

👂 Bus events

Apart from the events specified on the generic argument, a bus can emit "bus related" events.

Currently, these events are:

  • EventSubscriberAdd - Emitted when an EventSubscriber is subscribed to the bus. Passes the added EventSubscriber.
  • EventSubscriberRemove - Emitted when an EventSubscriber is unsubscribed from the bus. Passes the removed EventSubscriber.

You can get these events from the EventBusEventEnum enum on @core.

⚡ Event Dispatcher

The actual call of subscribers is done by the bus' EventDispatcher, which handles middleware and error handling.

Currently, there are two types of event dispatchers:

  • SyncEventDispatcher - For notifying subscribers synchronously, with a timeout.
  • AsyncEventDispatcher - For notifying subscribers asynchronously, with a concurrency limit.

For more information, check the ⚡ EventDispatcher documentation.