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

Websocket Intregation

Realtime communication with Socket.IO.

Avleon's realtime layer is built on Socket.IO, an optional peer dependency:

npm i socket.io

Enabling websockets

Call useSocketIo during app setup. Options are passed straight through to the Socket.IO server.

app.ts
import { Avleon } from '@avleon/core';
import { ChatSubscriber } from './subscribers/chat.subscriber';

const app = Avleon.createApplication();

app.useSocketIo({
  cors: { origin: '*' },
});

export default app;

The server is created when the app runs; connecting clients are wired to your subscribers automatically.

Subscribing to events

Mark any service method with @Subscribe('event') to handle an incoming client event. The class must be registered with the container — @AppService does that.

subscribers/chat.subscriber.ts
import { AppService, Subscribe } from '@avleon/core';

@AppService
export class ChatSubscriber {
  @Subscribe('message')
  onMessage(payload: { room: string; text: string }) {
    console.log(`[${payload.room}] ${payload.text}`);
  }

  @Subscribe('typing')
  onTyping(payload: { user: string }) {
    console.log(`${payload.user} is typing`);
  }
}

Handlers run inside a socket context, so the originating socket is available through SocketContextService for the duration of the call.

Emitting to clients

Inject SocketEventDispatcher to push events out:

services/chat.service.ts
import { AppService, SocketEventDispatcher } from '@avleon/core';

@AppService
export class ChatService {
  constructor(private readonly socket: SocketEventDispatcher) {}

  async announce(text: string) {
    await this.socket.dispatch('announcement', { text });
  }

  async toRoom(room: string, text: string) {
    await this.socket.dispatch('message', { text }, { room });
  }

  async toEveryoneElse(room: string, text: string) {
    // skips the socket that triggered the current handler
    await this.socket.dispatch('message', { text }, { room, broadcast: true });
  }
}

dispatch accepts:

OptionDescription
roomEmit only to this room
broadcastExclude the current socket
transportsDefaults to ['socket']
retryAttempts on failure
retryDelayBase delay in ms; backs off per attempt

Private channels

@PrivateChannel restricts a subscriber method to a channel derived from the connecting socket — use it for per-user streams:

import { AppService, Subscribe, PrivateChannel } from '@avleon/core';

@AppService
export class NotificationSubscriber {
  @PrivateChannel((socket) => `user:${socket.handshake.auth.userId}`)
  @Subscribe('notify')
  onNotify(payload: unknown) {
    // only reachable on the caller's own channel
  }
}

Reaching the raw server

For anything the helpers do not cover, resolve the Socket.IO Server itself:

import Container from 'typedi';
import { SocketIoServer } from '@avleon/core';

const io = Container.get(SocketIoServer);
io.emit('ping', Date.now());

Warning: SocketIoServer is only registered once the app is running and websockets are enabled. Resolving it during module load will fail.

Broadcasting events

The event system can forward a dispatched event to Socket.IO, which keeps domain events and realtime delivery in one place:

await dispatcher.dispatch(new UserCreatedEvent({ userId, name }), {
  broadcast: { type: 'socket', channel: 'user:create' },
});