Skip to main content

Event System

The Ever Works platform uses a two-layer event system: domain events in the agent package built on NestJS EventEmitter2, and typed plugin events defined in the plugin package for cross-boundary communication.

Architecture Overview

Domain Events (Agent Package)

The agent package defines domain events in packages/agent/src/events/. These are lightweight event classes that extend a BaseEvent abstract class.

BaseEvent

export abstract class BaseEvent {
static EVENT_NAME: string;
}

Every domain event declares a static EVENT_NAME string that serves as the event key for the NestJS event emitter.

Defined Domain Events

Event ClassEvent NamePayloadEmitted When
WorkCreatedEventwork.created{ work: Work }A new work is created
WorkGenerationCompletedEventwork.generation.completed{ work: Work }Work generation finishes

These events carry full TypeORM entity instances, allowing listeners to access all work properties including relations.

Emitting Domain Events

Services emit events using NestJS EventEmitter2:

import { EventEmitter2 } from '@nestjs/event-emitter';
import { WorkCreatedEvent } from '../events';

@Injectable()
export class WorkLifecycleService {
constructor(private readonly eventEmitter: EventEmitter2) {}

async createWork(/* ... */): Promise<Work> {
const work = await this.save(/* ... */);
this.eventEmitter.emit(WorkCreatedEvent.EVENT_NAME, new WorkCreatedEvent(work));
return work;
}
}

Listening to Domain Events

Listeners use the @OnEvent decorator from @nestjs/event-emitter:

import { OnEvent } from '@nestjs/event-emitter';
import { WorkCreatedEvent } from '@ever-works/agent/events';

@Injectable()
export class WorkEventListener {
@OnEvent(WorkCreatedEvent.EVENT_NAME)
handleWorkCreated(event: WorkCreatedEvent) {
// Handle the event
}
}

Plugin Event Types (Plugin Package)

The plugin package (packages/plugin/src/events/) defines a comprehensive typed event system for cross-plugin communication. This provides type-safe event names, payload interfaces, and handler types.

Event Categories

Events are organized into five categories:

Plugin Lifecycle Events

Event NamePayloadDescription
plugin:loadedPluginLoadedPayloadPlugin loaded successfully
plugin:enabledPluginLoadedPayloadPlugin enabled
plugin:disabledPluginLoadedPayloadPlugin disabled
plugin:unloadedPluginLoadedPayloadPlugin unloaded
plugin:errorPluginErrorPayloadPlugin encountered an error
plugin:settings-changedPluginSettingsChangedPayloadPlugin settings updated

Work Events

Event NamePayloadDescription
work:createdWorkEventPayloadWork created
work:updatedWorkEventPayloadWork updated
work:deletedWorkEventPayloadWork deleted
work:deployedWorkEventPayloadWork deployed
work:generation-startedWorkGenerationStartedPayloadGeneration started
work:generation-completedWorkGenerationCompletedPayloadGeneration completed
work:generation-failedWorkGenerationFailedPayloadGeneration failed

Item Events

Event NamePayloadDescription
item:createdItemEventPayloadItem created
item:updatedItemEventPayloadItem updated
item:deletedItemEventPayloadItem deleted
item:extractedItemEventPayloadItem data extracted from source
item:validatedItemValidatedPayloadItem validation completed

Pipeline Events

Event NamePayloadDescription
pipeline:startedPipelineEventPayloadPipeline execution started
pipeline:step-startedPipelineStepEventPayloadPipeline step started
pipeline:step-completedPipelineStepCompletedPayloadPipeline step completed
pipeline:step-failedPipelineStepFailedPayloadPipeline step failed
pipeline:completedPipelineCompletedPayloadPipeline completed
pipeline:failedPipelineFailedPayloadPipeline failed
pipeline:cancelledPipelineEventPayloadPipeline cancelled

System Events

Event NamePayloadDescription
system:startupSystemEventPayloadSystem started
system:shutdownSystemEventPayloadSystem shutting down
system:health-checkSystemEventPayloadHealth check performed

Payload Hierarchy

All event payloads extend BaseEventPayload:

interface BaseEventPayload {
readonly timestamp: string; // ISO timestamp
readonly correlationId?: string; // Optional trace ID
}

More specific payloads add context-relevant fields:

interface PipelineStepCompletedPayload extends PipelineStepEventPayload {
readonly duration: number;
readonly result?: Record<string, unknown>;
}

interface PipelineStepEventPayload extends PipelineEventPayload {
readonly stepId: string;
readonly stepName: string;
readonly stepIndex: number;
readonly totalSteps: number;
}

interface PipelineEventPayload extends BaseEventPayload {
readonly workId: string;
readonly pipelineId?: string;
}

Type-Safe Event Handler

The plugin package provides a typed EventHandler type and PluginEventEmitter interface:

type EventHandler<T extends PluginEventName> = (payload: PluginEventPayloads[T]) => void | Promise<void>;

interface PluginEventEmitter {
on<T extends PluginEventName>(event: T, handler: EventHandler<T>): EventSubscription;
once<T extends PluginEventName>(event: T, handler: EventHandler<T>): EventSubscription;
emit<T extends PluginEventName>(event: T, payload: PluginEventPayloads[T]): void;
}

The PluginEventPayloads interface provides a complete mapping from event names to their payload types, enabling full IntelliSense and compile-time safety.

EventSubscription

Event subscriptions return an EventSubscription object for cleanup:

interface EventSubscription {
readonly unsubscribe: () => void;
}

Integration: Agent and Plugin Events

The pipeline system in the agent package emits events using the plugin event types. The StepPipelineExecutorService and FullPipelineExecutorService both emit typed pipeline events through EventEmitter2:

// Inside StepPipelineExecutorService
this.eventEmitter.emit(PipelineEvents.STEP_COMPLETED, {
timestamp: new Date().toISOString(),
stepId: step.id,
stepName: step.name,
stepIndex: index,
totalSteps: total,
duration
} as PipelineStepCompletedPayload);

The PipelineEvents constant maps to the same event names defined in the plugin package, ensuring consistency between the two layers.

Event Flow Diagram