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.
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 > {}
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 }` );
}
}
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 }` );
},
});
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.
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.
await dispatcher. dispatch ( new UserCreatedEvent ({ userId: 1 , name: 'Tareq' }), {
delay: 5000 , // fire listeners 5 seconds from now
});
Add a broadcast option to forward the event to a transport as well as to the
local listeners:
await dispatcher. dispatch (event, {
broadcast: { type: 'socket' , channel: 'user:create' },
});
await dispatcher. dispatch (event, {
broadcast: { type: 'kafka' , topic: 'user.created' },
});
await dispatcher. dispatch (event, {
broadcast: { type: 'rabbitmq' , queue: 'user.created' },
});
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.