📣 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
.
- Instantiating BasicEventBus
- Implementing EventBus
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);
import { EventBus } from '@nyx-discord/core';
class MyInterfaceEventBus implements EventBus<MyEventsArgs> {
// ...
}
const interfaceBus = new MyInterfaceEventBus();
await bot.getEventManager().addEventBus(interfaceBus);
There are two options to instantiate a default BasicEventBus
.
Either with:
BasicEventBus#createAsync()
which uses anAsyncEventDispatcher
.BasicEventBus#createSync()
which uses anSyncEventDispatcher
.
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
.
- Instantiating BasicEventEmitterBus
- Implementing EventEmitterBus
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);
import { EventEmitterBus } from '@nyx-discord/core';
class MyEventEmitterBus implements EventEmitterBus<MyEventsArgs> {
// ...
}
const bus = new MyEventEmitterBus();
await bot.getEventManager().addEventBus(bus);
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 anEventSubscriber
is subscribed to the bus. Passes the addedEventSubscriber
.EventSubscriberRemove
- Emitted when anEventSubscriber
is unsubscribed from the bus. Passes the removedEventSubscriber
.
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.