Result Aggregation (Events Suck)
Relevant source files
The following files were used as context for generating this wiki page:
Result Aggregation, colloquially referred to within the codebase as "Events Suck," is a design pattern and utility suite that bridges the gap between asynchronous event-driven architectures and imperative, request-response programming styles. Its primary purpose is to allow developers to use the EventBus while maintaining the ergonomics of standard method calls that return values directly.
In a traditional event-driven system, dispatching an event is a "fire-and-forget" action. The "Events Suck" module provides wrappers and generation utilities that automatically handle event dispatching, wait for handler execution, and aggregate results into a single return value. This is particularly useful for building SDK-like clients or integrating legacy imperative code with the bubus event system.
Core Architecture and Logic
The "Events Suck" system operates by wrapping BaseEvent definitions into imperative method signatures. When these methods are called, they internally instantiate an event, dispatch it to the EventBus, and await the first() valid result from the available handlers.
The Wrapper Pattern
The wrap function is the primary entry point for creating imperative clients. It takes a class name and a mapping of method names to EventClass definitions. It then generates a new class where each method, when invoked, performs the following:
- Merges positional or keyword arguments into the event payload.
- Dispatches the event via the attached
EventBus. - Awaits the first non-error, non-undefined result from the event's results.
Sources: bubus-ts/src/events_suck.ts:86-104, tests/test_events_suck.py:65-73
Logic Flow for Imperative Invocation
The following diagram illustrates how a wrapped method call translates into event-driven execution and result retrieval.
Component Overview
Key Functions and Classes
| Component | Description | Files |
|---|---|---|
wrap | Generates a class that maps method calls to event dispatches. | bubus-ts/src/events_suck.ts, tests/test_events_suck.py |
make_events | Creates a collection of BaseEvent classes derived from existing function signatures. | bubus-ts/src/events_suck.ts, tests/test_events_suck.py |
make_handler | (Python only) Wraps a standard function into a bubus compatible handler. | tests/test_events_suck.py |
EventsSuckClient | The resulting type/class after wrapping a set of events. | bubus-ts/src/events_suck.ts |
Result Selection Semantics
A critical part of aggregation is determining which handler result "wins" and is returned to the imperative caller. The system follows a "first valid winner" policy.
- TypeScript Winner Criteria: The first result that is not
undefined, not an error, and not aBaseEventinstance.nullis considered a valid winner. - Python Winner Criteria: The first result that is not
None, not an error, and not aBaseEventinstance.
Sources: TODO_ts.md:33-35, TODO_python.md:25-27, TODO_ts.md:165-172
Dynamic Event Generation
The make_events utility allows the system to retrospectively apply an event-driven architecture to existing codebases. It inspects function signatures to define the schema for new BaseEvent types.
Type Mapping (TypeScript)
In TypeScript, make_events uses complex conditional types to extract the parameter shapes and return types from provided functions, ensuring the generated events are strictly typed.
type EventFieldsFromFn<TFunc extends AnyFn> =
Parameters<TFunc> extends [infer TArg]
? (TArg extends Record<string, unknown> ? TArg : ExtraDict)
: ExtraDict;
Sources: bubus-ts/src/events_suck.ts:10-14
Error Taxonomy in Aggregation
When aggregating results, handlers may fail. The system normalizes these failures into a specific taxonomy to ensure the caller receives predictable error information rather than raw internal exceptions.
| Error Class | Triggering Condition |
|---|---|
EventHandlerCancelledError | The handler was in the queue but the event/parent was cancelled before it started. |
EventHandlerTimeoutError | The handler exceeded its allotted event_handler_timeout or the total event_timeout. |
EventHandlerAbortedError | The handler was already running but was force-terminated by a hard timeout. |
EventHandlerResultSchemaError | The handler returned a value that failed the event's event_result_type validation. |
Sources: TODO_ts.md:36-41, TODO_python.md:104-108
Implementation Example (TypeScript)
The following example demonstrates how the wrap utility is used to create a synchronous-feeling SDK client.
const SDKClient = events_suck.wrap('SDKClient', {
create: CreateEvent,
update: UpdateEvent,
});
const client = new SDKClient(bus);
// This looks like a standard call but actually dispatches a CreateEvent
const user_id = await client.create({ name: 'bob', age: 45 });
Sources: bubus-ts/tests/events_suck.test.ts:25-34
Summary
The "Result Aggregation (Events Suck)" module provides an essential abstraction layer for developers who need the power of the bubus event-driven core without sacrificing the simplicity of imperative programming. By centralizing the logic for result selection, error normalization, and payload merging, it ensures that even complex asynchronous workflows can be exposed as clean, reliable API methods.