@roostjs/events

In-process event dispatching with listener registration, subscriber classes, optional queue integration, and broadcast interop.

Installation

bun add @roostjs/events

Event API

Event is an abstract base class. Extend it to create typed event classes. All dispatch and testing methods are static.

Static Methods

static async dispatch<T extends Event>(event: T): Promise<void>

Dispatch the event to all registered listeners. If a fake is active, records the event without calling listeners. If the event implements BroadcastableEvent (has a broadcastOn() method), the dispatcher also forwards it to BroadcastManager if @roostjs/broadcast is installed.

static fake(): void

Enable fake mode for this event class. Dispatches are recorded but listeners are not called.

static restore(): void

Disable fake mode for this event class.

static assertDispatched<T extends Event>(callback?: (event: T) => boolean): void

Assert that this event was dispatched. Pass an optional predicate to assert a specific dispatch. Throws if fake() was not called first.

static assertNotDispatched<T extends Event>(): void

Assert that this event was not dispatched. Throws if fake() was not called first.

EventDispatcher API

EventDispatcher is the internal router that maps event classes to listener classes. The service provider creates and configures a dispatcher instance; application code typically does not need to use this directly.

static get(): EventDispatcher

Return the current singleton dispatcher. Creates a default instance if none has been set.

static set(dispatcher: EventDispatcher): void

Replace the singleton dispatcher. Called by EventServiceProvider.register().

registerListener(eventClass: EventClass<Event>, listenerClass: ListenerClass): void

Register a listener class for an event class. Called during bootstrap by the service provider.

async dispatch(event: Event): Promise<void>

Instantiate and call all listeners registered for event's class. If a listener implements ShouldQueue, the listener is dispatched as a Job via @roostjs/queue instead of being called synchronously.

Listener Interface

interface Listener<T = unknown> {
  handle(event: T): void | Promise<void>;
}

Implement this interface on any class used as a listener. The handle() method receives the dispatched event instance.

ShouldQueue Interface

interface ShouldQueue {
  readonly shouldQueue: true;
}

A marker interface. Add this to a Listener class that also extends Job<TEvent> from @roostjs/queue to have the listener dispatched as a background job instead of called inline. Requires @roostjs/queue to be installed.

Subscriber API

Subscriber maps multiple event classes to handler methods on a single class. Useful when a domain module needs to react to several events.

abstract subscribe(): Map<EventClass<Event>, string>

Return a map of event class to method name string. The method must exist on the subscriber instance.

export class OrderSubscriber extends Subscriber {
  subscribe(): Map<EventClass<Event>, string> {
    return new Map([
      [OrderPlaced, 'onOrderPlaced'],
      [OrderCancelled, 'onOrderCancelled'],
    ]);
  }

  async onOrderPlaced(event: OrderPlaced): Promise<void> {
    // handle event
  }

  async onOrderCancelled(event: OrderCancelled): Promise<void> {
    // handle event
  }
}

EventServiceProvider API

EventServiceProvider is abstract. Extend it in your application to declare event-listener mappings.

protected listen(): Map<EventClass<Event>, ListenerClass[]>

Override to return a map of event classes to arrays of listener classes. Defaults to an empty map.

protected subscribers(): SubscriberClass[]

Override to return an array of subscriber classes to register. Defaults to an empty array.

register(): void

Creates a new EventDispatcher, registers all listeners and subscribers, calls EventDispatcher.set(), and registers the dispatcher in the service container under the key events.dispatcher.

Types

type EventClass<T extends Event = Event> = {
  new (...args: unknown[]): T;
  dispatch(event: T): Promise<void>;
  fake(): void;
  restore(): void;
};

type ListenerClass = {
  new (): { handle(event: unknown): void | Promise<void> };
  name: string;
};

type SubscriberClass = {
  new (): Subscriber;
};

type ListenerMap = Map<EventClass<Event>, ListenerClass[]>;

Testing

EventFake

Internal state managed by Event.fake().

class EventFake {
  dispatched: Event[];
  recordDispatch(event: Event): void;
}