My App
CourseKitBackend

Domain Events

Event-driven architecture with @nestjs/event-emitter integration

Overview

CourseKit emits domain events for all state-changing operations via @nestjs/event-emitter. Subscribe to these events to build notifications, audit logs, sync systems, or other reactive integrations.

import { DOMAIN_EVENTS } from '@hfu.digital/coursekit-nestjs';

Event Constants

const DOMAIN_EVENTS = {
    EVENT_CREATED: 'coursekit.event.created',
    EVENT_UPDATED: 'coursekit.event.updated',
    EVENT_DELETED: 'coursekit.event.deleted',
    EXCEPTION_CREATED: 'coursekit.exception.created',
    EXCEPTION_DELETED: 'coursekit.exception.deleted',
    CONFLICT_DETECTED: 'coursekit.conflict.detected',
    AVAILABILITY_CREATED: 'coursekit.availability.created',
    AVAILABILITY_UPDATED: 'coursekit.availability.updated',
    AVAILABILITY_DELETED: 'coursekit.availability.deleted',
} as const;

Payload Types

EventCreatedPayload

interface EventCreatedPayload {
    event: TimetableEvent;
}

EventUpdatedPayload

interface EventUpdatedPayload {
    previous: TimetableEvent;
    current: TimetableEvent;
    changedFields: string[];
}

EventDeletedPayload

interface EventDeletedPayload {
    event: TimetableEvent;
}

ExceptionCreatedPayload

interface ExceptionCreatedPayload {
    exception: EventException;
    parentEvent: TimetableEvent;
}

ExceptionDeletedPayload

interface ExceptionDeletedPayload {
    exception: EventException;
    parentEvent: TimetableEvent;
}

ConflictDetectedPayload

interface ConflictDetectedPayload {
    result: ConflictCheckResult;
    triggeringEventId: string;
}

AvailabilityCreatedPayload

interface AvailabilityCreatedPayload {
    availability: Availability;
}

AvailabilityUpdatedPayload

interface AvailabilityUpdatedPayload {
    previous: Availability;
    current: Availability;
}

AvailabilityDeletedPayload

interface AvailabilityDeletedPayload {
    availability: Availability;
}

Subscribing to Events

Use the @OnEvent decorator from @nestjs/event-emitter:

import { Injectable } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import {
    DOMAIN_EVENTS,
    type EventCreatedPayload,
    type ConflictDetectedPayload,
    type AvailabilityUpdatedPayload,
} from '@hfu.digital/coursekit-nestjs';

@Injectable()
export class ScheduleNotificationService {
    @OnEvent(DOMAIN_EVENTS.EVENT_CREATED)
    handleEventCreated(payload: EventCreatedPayload) {
        console.log('New event scheduled:', payload.event.title);
    }

    @OnEvent(DOMAIN_EVENTS.CONFLICT_DETECTED)
    handleConflict(payload: ConflictDetectedPayload) {
        const { result, triggeringEventId } = payload;
        console.warn(
            `${result.conflicts.length} conflicts detected for event ${triggeringEventId}`,
        );
    }

    @OnEvent(DOMAIN_EVENTS.AVAILABILITY_UPDATED)
    handleAvailabilityChange(payload: AvailabilityUpdatedPayload) {
        console.log(
            `Availability updated from ${payload.previous.type} to ${payload.current.type}`,
        );
    }
}

DomainEventMap

For fully typed event subscriptions, use the DomainEventMap interface:

interface DomainEventMap {
    [DOMAIN_EVENTS.EVENT_CREATED]: EventCreatedPayload;
    [DOMAIN_EVENTS.EVENT_UPDATED]: EventUpdatedPayload;
    [DOMAIN_EVENTS.EVENT_DELETED]: EventDeletedPayload;
    [DOMAIN_EVENTS.EXCEPTION_CREATED]: ExceptionCreatedPayload;
    [DOMAIN_EVENTS.EXCEPTION_DELETED]: ExceptionDeletedPayload;
    [DOMAIN_EVENTS.CONFLICT_DETECTED]: ConflictDetectedPayload;
    [DOMAIN_EVENTS.AVAILABILITY_CREATED]: AvailabilityCreatedPayload;
    [DOMAIN_EVENTS.AVAILABILITY_UPDATED]: AvailabilityUpdatedPayload;
    [DOMAIN_EVENTS.AVAILABILITY_DELETED]: AvailabilityDeletedPayload;
}

On this page