🚧 AvleonJs is in active development and not ready for production use.

Events

Dispatching and handling events in an Avleon application.

Events let one part of your application announce that something happened without knowing who reacts to it. Avleon's event system is typed, supports synchronous and queued delivery, and can broadcast over Socket.IO, Kafka or RabbitMQ.

Defining an event

An event extends AvleonEvent<TPayload>. The payload type is yours; the base class fills in name and timestamp.

events/user-created.event.ts
import { AvleonEvent } from '@avleon/core';

export type UserCreatedPayload = {
  userId: number;
  name: string;
};

export class UserCreatedEvent extends AvleonEvent<UserCreatedPayload> {}

Defining a listener

A listener extends AvleonEventListener<TEvent> and implements handler. It may be synchronous or return a promise.

listeners/user-created.listener.ts
import { AvleonEventListener } from '@avleon/core';
import { UserCreatedEvent } from '../events/user-created.event';

export class UserCreatedListener extends AvleonEventListener<UserCreatedEvent> {
  async handler(event: UserCreatedEvent): Promise<void> {
    console.log(`User created → id=${event.payload.userId}, name=${event.payload.name}`);
  }
}

Registering listeners

EventRegistry is a singleton. Register listeners once during app setup.

app.ts
import { EventRegistry } from '@avleon/core';
import { UserCreatedEvent } from './events/user-created.event';
import { UserCreatedListener } from './listeners/user-created.listener';

const registry = EventRegistry.getInstance();

registry.register(UserCreatedEvent, new UserCreatedListener());

An event may have several listeners — they run in registration order.

Use once for a listener that should fire a single time and then be discarded:

registry.once(UserCreatedEvent, {
  handler(event: UserCreatedEvent) {
    console.log(`Sending welcome email to ${event.payload.name}`);
  },
});

Dispatching

services/user.service.ts
import { AppService, EventDispatcher } from '@avleon/core';
import { UserCreatedEvent } from '../events/user-created.event';

@AppService
export class UserService {
  private readonly dispatcher = new EventDispatcher();

  async create(name: string) {
    const userId = 1; // ...persist the user

    await this.dispatcher.dispatch(new UserCreatedEvent({ userId, name }));

    return { userId, name };
  }
}

By default listeners run synchronously, in the current tick, before dispatch resolves.

Queued delivery

Pass queue: true to hand the event to the in-memory queue instead. Listener failures are then retried:

await dispatcher.dispatch(new UserCreatedEvent({ userId: 1, name: 'Tareq' }), {
  queue: true,
  retry: 3,
  retryDelay: 500, // milliseconds between attempts
});

retry and retryDelay are only accepted together with queue: true.

Delayed delivery

await dispatcher.dispatch(new UserCreatedEvent({ userId: 1, name: 'Tareq' }), {
  delay: 5000, // fire listeners 5 seconds from now
});

Broadcasting

Add a broadcast option to forward the event to a transport as well as to the local listeners:

Socket.IO
Kafka
RabbitMQ
await dispatcher.dispatch(event, {
  broadcast: { type: 'socket', channel: 'user:create' },
});

Socket broadcasting also accepts a room. The Kafka and RabbitMQ transports need kafkajs / an AMQP client installed — they are optional peers.

Note: A listener that throws is logged and does not stop the other listeners for that event. With queue: true, the failure is retried according to retry.