Skip to main content

Overview

The event builder provides a fluent interface for defining strongly-typed events that can be emitted throughout your application. Events trigger hooks and enable decoupled communication between components.
import { r } from "@bluelibs/runner";

const userRegistered = r.event("app.events.user.registered")
  .payloadSchema<{ userId: string; email: string; timestamp: Date }>({
    parse: (v) => v
  })
  .meta({
    title: "User Registered",
    description: "Emitted when a new user successfully registers",
  })
  .build();

Methods

payloadSchema() / schema()

Defines the validation schema for event payload.
.payloadSchema<TPayload>(schema: IValidationSchema<TPayload>)
.schema<TPayload>(schema: IValidationSchema<TPayload>) // Alias
schema
IValidationSchema<TPayload>
required
Validation schema with a parse method that validates and transforms payload.
.payloadSchema<{ orderId: string; amount: number }>({
  parse: (payload) => {
    if (payload.amount < 0) {
      throw new Error('Amount must be positive');
    }
    return payload;
  }
})
Returns: New builder with updated payload type

parallel()

Enables parallel execution for event listeners.
.parallel(enabled?: boolean)
enabled
boolean
default:"true"
When true, listeners with the same order run concurrently within a batch. Batches execute sequentially in ascending order priority.
.parallel(true) // Listeners run in parallel within same order
.parallel(false) // All listeners run sequentially
Returns: New builder with parallel setting configured

tags()

Attaches tags for grouping and filtering.
.tags<TTags>(
  tags: TagType[],
  options?: { override?: boolean }
)
tags
TagType[]
required
Array of tag definitions.
const criticalTag = r.tag('app.tags.critical').build();

.tags([criticalTag, globals.tags.debug])
options.override
boolean
default:"false"
When true, replaces existing tags. When false (default), appends to existing.
Returns: New builder with tags attached

meta()

Attaches metadata for documentation and tooling.
.meta<TMeta>(metadata: IEventMeta)
metadata
IEventMeta
required
Metadata object with optional title and description.
.meta({
  title: "Order Processed",
  description: "Emitted when an order completes processing successfully",
})
Returns: New builder with metadata attached

throws()

Documents which errors hooks listening to this event may throw.
.throws(errorList: ThrowsList)
errorList
ThrowsList
required
Array of error classes that hooks might throw. This is declarative only (for documentation).
.throws([NetworkError, TimeoutError])
Returns: New builder with error documentation

build()

Builds and returns the final event definition.
.build(): IEvent<TPayload>
Returns: Immutable event definition ready to register

Type Signature

interface EventFluentBuilder<TPayload = void> {
  id: string;
  payloadSchema<TNew>(schema: IValidationSchema<TNew>): EventFluentBuilder<TNew>;
  schema<TNew>(schema: IValidationSchema<TNew>): EventFluentBuilder<TNew>;
  parallel(enabled?: boolean): EventFluentBuilder<TPayload>;
  tags<TNewTags extends TagType[]>(t: TNewTags, options?: { override?: boolean }): EventFluentBuilder<TPayload>;
  meta<TNewMeta extends IEventMeta>(m: TNewMeta): EventFluentBuilder<TPayload>;
  throws(list: ThrowsList): EventFluentBuilder<TPayload>;
  build(): IEvent<TPayload>;
}

Examples

Simple Event (No Payload)

const appReady = r.event("app.events.ready")
  .meta({
    title: "Application Ready",
    description: "Emitted when application finishes initialization",
  })
  .build();

// Emit:
await runtime.emitEvent(appReady);

Event with Payload

const userRegistered = r.event("app.events.user.registered")
  .payloadSchema<{ userId: string; email: string }>({
    parse: (v) => {
      if (!v.userId || !v.email) {
        throw new Error('Invalid user registration payload');
      }
      return v;
    }
  })
  .build();

// Emit:
await runtime.emitEvent(userRegistered, {
  userId: "123",
  email: "[email protected]",
});

Event with Parallel Execution

const dataUpdated = r.event("app.events.data.updated")
  .payloadSchema<{ id: string; data: any }>({ parse: (v) => v })
  .parallel(true) // Hooks run in parallel (within same order)
  .meta({
    title: "Data Updated",
    description: "Emitted when data is modified",
  })
  .build();

Event with Tags

const criticalTag = r.tag('app.tags.critical').build();
const securityTag = r.tag('app.tags.security').build();

const securityBreach = r.event("app.events.security.breach")
  .payloadSchema<{ severity: string; details: string }>({ parse: (v) => v })
  .tags([criticalTag, securityTag])
  .parallel(false) // Run hooks sequentially for critical security events
  .meta({
    title: "Security Breach Detected",
    description: "Emitted when a security breach is detected",
  })
  .throws([AlertError, LoggingError])
  .build();

Domain Events

// Order domain events
const orderCreated = r.event("app.events.order.created")
  .payloadSchema<{ orderId: string; customerId: string; total: number }>({ parse: (v) => v })
  .build();

const orderPaid = r.event("app.events.order.paid")
  .payloadSchema<{ orderId: string; paymentId: string }>({ parse: (v) => v })
  .build();

const orderShipped = r.event("app.events.order.shipped")
  .payloadSchema<{ orderId: string; trackingNumber: string }>({ parse: (v) => v })
  .build();

const orderDelivered = r.event("app.events.order.delivered")
  .payloadSchema<{ orderId: string; deliveredAt: Date }>({ parse: (v) => v })
  .build();

Usage Pattern

Define Event

const userLoggedIn = r.event("app.events.user.loggedIn")
  .payloadSchema<{ userId: string; timestamp: Date }>({ parse: (v) => v })
  .build();

Emit Event

// From within a task
.run(async ({ input, runtime }) => {
  // ... login logic
  await runtime.emitEvent(userLoggedIn, {
    userId: input.userId,
    timestamp: new Date(),
  });
});

// From runtime instance
await runtime.emitEvent(userLoggedIn, {
  userId: "123",
  timestamp: new Date(),
});

Listen to Event (Hook)

const trackLogin = r.hook("app.hooks.trackLogin")
  .on(userLoggedIn)
  .run(async ({ payload, logger }) => {
    await logger.info(`User ${payload.userId} logged in at ${payload.timestamp}`);
    await analytics.track('login', { userId: payload.userId });
  })
  .build();

Emission Options

When emitting events, you can pass options:
await runtime.emitEvent(myEvent, payload, {
  report: true, // Get detailed emission report
  failureMode: 'continue', // Continue on hook failure
  throwOnError: false, // Don't throw if hooks fail
});
See Runtime - emitEvent() for details.

Build docs developers (and LLMs) love