📣 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
EventSubscribercollection for storing subscribers, mapped by their IDs. - An
EventDispatcherto notify event calls to subscribers.
As well as methods to interact with them.
👷 Creation
You can create an event bus by either:
- Instantiating an
BasicEventBusfrom@framework(recommended). - Implementing the
EventBusinterface 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
AsyncEventDispatcherexecutes multiple subscribers at a time, on a fixed amount. - An
SyncEventDispatcherexecutes 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
BasicEventEmitterBusfrom@framework(recommended). - Implementing the
EventEmitterBusinterface 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 anEventSubscriberis subscribed to the bus. Passes the addedEventSubscriber.EventSubscriberRemove- Emitted when anEventSubscriberis 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.