Skip to content

Adding Handlers

The engine's execution logic is organized as event handlers. Each handler is responsible for one state transition or one type of job result processing. To extend the engine, you typically add a new handler.

The Handler Pattern

Every handler extends the EventHandler base class and implements:

abstract class EventHandler<TEvent extends EngineEvent> {
  abstract handlesEventType(): new (...args: any[]) => TEvent
  abstract eventConditions(): EventCondition[]
  protected abstract executeInTransaction(
    event: TEvent,
    resolvedEntities: WorkflowEngineResolvedEntities,
    em: EntityManager,
    epoch: WorkflowEngineEpoch,
  ): Promise<EventHandlingVoidResultWithRetry>
}

Key Methods

handlesEventType() : Returns the event class this handler processes. Currently two types: WorkflowExecutionUpdatedEvent and JobResultUpdatedEvent.

eventConditions() : Returns an array of conditions that must all be true for this handler to be selected. Conditions can check workflow execution status, job status, and job result status.

executeInTransaction() : The handler's logic, executed within a database transaction. Any entities modified here are flushed atomically.

Registering a Handler

Handlers are registered in CoreEngineService.initEngine():

private initEngine() {
  // Workflow execution handlers
  this.handlerRegistry.register(
    WorkflowExecutionUpdatedEvent,
    new NewWorkflowExecutionHandler(this.orm, this.cmsAccess),
  )
  // ... more handlers

  // Job result handlers
  this.handlerRegistry.register(
    JobResultUpdatedEvent,
    new JobResultRunningResultHandler(this.orm, this.cmsAccess),
  )
}

The registry selects the handler whose event conditions match the incoming event. If multiple handlers match, the first registered one wins -- so order matters.

Example: Adding a New State Handler

To add handling for a new workflow state (e.g., a custom validation step):

  1. Create the handler class:
export class CustomValidationHandler extends EventHandler<WorkflowExecutionUpdatedEvent> {
  handlesEventType() {
    return WorkflowExecutionUpdatedEvent
  }

  eventConditions(): EventCondition[] {
    return [
      {
        workflowExecutionStatus: WorkflowExecutionStatus.VALID,
        // Additional conditions as needed
      },
    ]
  }

  async executeInTransaction(event, resolvedEntities, em, epoch) {
    const execution = await em.findOneOrFail(WorkflowExecution, event.entityId)
    // Custom validation logic...
    execution.status = WorkflowExecutionStatus.READY
    return { retry: false }
  }
}
  1. Register in initEngine():
this.handlerRegistry.register(
  WorkflowExecutionUpdatedEvent,
  new CustomValidationHandler(this.em, ...),
)
  1. Write tests using the Playbook pattern (see E2E Playbooks).

Event Flow Recap

DB flush → Subscriber → EventAggregator → CoreEngine → HandlerRegistry → Handler
                                                                     DB changes
                                                                   DB flush (→ new events)

Each handler modifies entities and flushes. The flush may trigger new events (e.g., changing a workflow's status triggers the next handler), creating a chain of handlers that process the entire lifecycle.