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)
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
Attaches tags for grouping and filtering.
.tags<TTags>(
tags: TagType[],
options?: { override?: boolean }
)
Array of tag definitions.const criticalTag = r.tag('app.tags.critical').build();
.tags([criticalTag, globals.tags.debug])
When true, replaces existing tags. When false (default), appends to existing.
Returns: New builder with tags attached
Attaches metadata for documentation and tooling.
.meta<TMeta>(metadata: IEventMeta)
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)
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();
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.