Events & Listeners

Svelar provides a Laravel-inspired event system for decoupling your application. Events represent things that happened; listeners react to them.

Creating Events

npx svelar make:event UserRegistered --module=auth

This generates src/lib/modules/auth/UserRegistered.ts (or src/lib/events/UserRegistered.ts in flat projects):

export class UserRegistered {
  constructor(
    public readonly user: User,
    public readonly source?: string,
  ) {}
}

Events are plain classes — no base class required. They carry the data that listeners need.

Creating Listeners

npx svelar make:listener SendWelcomeEmail --event=UserRegistered --module=auth

This generates src/lib/modules/auth/SendWelcomeEmail.ts (or src/lib/listeners/SendWelcomeEmail.ts in flat projects):

import { Listener } from '@beeblock/svelar/events';
import type { UserRegistered } from './UserRegistered.js';

export class SendWelcomeEmail extends Listener<UserRegistered> {
  async handle(event: UserRegistered): Promise<void> {
    await Mailer.sendMailable(new WelcomeEmail(event.user));
  }

  // Optionally filter which events to handle:
  shouldHandle(event: UserRegistered): boolean {
    return event.source !== 'import'; // Skip imported users
  }
}

Dispatching Events

import { Event } from '@beeblock/svelar/events';

// Class-based event
await Event.dispatch(new UserRegistered(user));

// String-based event
await Event.emit('order.shipped', { orderId: 123, trackingNumber: 'ABC' });

Inline Listeners

For quick one-offs, register listeners directly:

import { Event } from '@beeblock/svelar/events';

// String-based
Event.listen('user.created', async (user) => {
  console.log('New user:', user.getAttribute('email'));
});

// Class-based (typed)
Event.listen(UserRegistered, async (event) => {
  await analytics.track('signup', { email: event.user.email });
});

// One-time listener
Event.once('app.booted', async () => {
  console.log('Application started');
});

// Wildcard listener (fires for every event)
Event.onAny(async (eventName, payload) => {
  console.log(`[Event] ${eventName}`, payload);
});

EventServiceProvider

For production apps, register all event mappings and observers in an EventServiceProvider. This gives you a single place to see all event wiring.

npx svelar make:provider EventServiceProvider

Then extend the built-in EventServiceProvider base:

// src/lib/shared/providers/EventServiceProvider.ts
import { EventServiceProvider as BaseProvider } from '@beeblock/svelar/events';
import { UserRegistered } from '$lib/modules/auth/UserRegistered.js';
import { OrderPlaced } from '$lib/modules/orders/OrderPlaced.js';
import { SendWelcomeEmail } from '$lib/modules/auth/SendWelcomeEmail.js';
import { CreateUserProfile } from '$lib/modules/auth/CreateUserProfile.js';
import { NotifyWarehouse } from '$lib/modules/orders/NotifyWarehouse.js';
import { User } from '$lib/modules/auth/User.js';
import { UserObserver } from '$lib/modules/auth/UserObserver.js';
import { AuditObserver } from '$lib/modules/auth/AuditObserver.js';

export class EventServiceProvider extends BaseProvider {
  // Map events to their listeners
  protected listen = {
    // Class-based events (use .name for the key)
    [UserRegistered.name]: [SendWelcomeEmail, CreateUserProfile],
    [OrderPlaced.name]: [NotifyWarehouse],

    // String-based events (model lifecycle, custom, etc.)
    'user.updated': [
      async (user: any) => { await invalidateUserCache(user); },
    ],
  };

  // Map models to their observers
  protected observers = {
    [User.name]: [UserObserver, AuditObserver],
  };
}

Register the provider and models in your app bootstrap:

// src/app.ts
import { Application } from '@beeblock/svelar/container';
import { EventServiceProvider as BaseProvider } from '@beeblock/svelar/events';
import { EventServiceProvider } from './lib/shared/providers/EventServiceProvider.js';
import { User } from './lib/modules/users/User.js';
import { Post } from './lib/modules/posts/Post.js';

// Register models so observers can be attached by name
BaseProvider.registerModels(User, Post);

const app = new Application();
app.register(EventServiceProvider);
await app.bootstrap();

Subscribers

Subscribers let a single class listen to multiple events:

import { type EventDispatcher, type Subscriber } from '@beeblock/svelar/events';

export class UserEventSubscriber implements Subscriber {
  subscribe(events: EventDispatcher): void {
    events.listen('user.created', this.onCreated.bind(this));
    events.listen('user.deleted', this.onDeleted.bind(this));
    events.listen('user.updated', this.onUpdated.bind(this));
  }

  async onCreated(user: any) { /* ... */ }
  async onDeleted(user: any) { /* ... */ }
  async onUpdated(user: any) { /* ... */ }
}

// Register directly
Event.subscribe(new UserEventSubscriber());

// Or via EventServiceProvider:
protected subscribe = [UserEventSubscriber];

Model Lifecycle Events

Every model automatically dispatches events through the Event system. See Model Observers for the full reference.

// These fire automatically — no setup needed
Event.listen('user.creating', async (user) => { /* before insert */ });
Event.listen('user.created', async (user) => { /* after insert */ });
Event.listen('user.updating', async (user) => { /* before update */ });
Event.listen('user.updated', async (user) => { /* after update */ });
Event.listen('user.deleting', async (user) => { /* before delete */ });
Event.listen('user.deleted', async (user) => { /* after delete */ });

Removing Listeners

// The listen() method returns an unsubscribe function
const unsubscribe = Event.listen('user.created', handler);
unsubscribe(); // Remove this specific listener

// Remove all listeners for an event
Event.forget('user.created');
Event.forget(UserRegistered); // Class-based

// Remove everything
Event.flush();

// Check if listeners exist
Event.hasListeners('user.created');     // boolean
Event.listenerCount(UserRegistered);    // number
Svelar © 2026 · MIT License