Middleware & Hooks
Relevant source files
The following files were used as context for generating this wiki page:
Middleware and hooks provide a centralized mechanism for observing and extending the lifecycle of events and handlers within the bubus event bus. The system allows developers to inject logic at critical transition points—such as when an event is dispatched, when a handler starts, or when a result is finalized—without modifying the core orchestration logic.
The architecture supports both internal notifiers (hooks) and public-facing middleware classes. Middleware can be used for cross-cutting concerns such as logging, tracing (OpenTelemetry), persistence (WAL/SQLite), and automatic error handling. These components are registered per-bus, ensuring that different buses can maintain isolated middleware stacks.
Sources: TODO_ts.md:28-33, TODO_python.md:31-35, tests/test_eventbus.py:734-740
Architecture and Lifecycle Hooks
Middleware execution is centralized within the EventBus. When state transitions occur in BaseEvent or EventResult, the bus triggers specific hooks that iterate through registered middleware in the order they were added.
Hook Execution Flow
The following diagram illustrates the sequence of middleware hook execution during a typical event dispatch and handler lifecycle.
The execution of these hooks is generally sequential. In the TypeScript implementation, hooks land before lock/timeout refactors to ensure lifecycle observability remains stable while internals move. In Python, middleware execution remains centralized in internal notifier methods, and hooks are called sequentially in registration order.
Sources: TODO_ts.md:95-103, TODO_python.md:162-168, bubus-ts/src/base_event.ts:394-405
Core Hook Types
| Hook Name | Trigger Point | Parameters |
|---|---|---|
on_event_change | Triggered when BaseEvent transitions between pending, started, or completed. | event, status |
on_event_result_change | Triggered when EventResult transitions (e.g., markStarted, markCompleted, markError). | event, result, status |
on_handler_change | Triggered when a handler is registered via on() or removed via off(). | handler, registered (bool) |
Sources: TODO_ts.md:126-130, TODO_python.md:197-200
Middleware Implementation
Middleware can be provided to the EventBus as either class constructors (which are auto-instantiated) or as existing instances. This flexibility allows for middleware that requires configuration at runtime.
Registration and Initialization
When an EventBus is initialized, the middlewares option accepts an array of middleware. Class entries are automatically instantiated during bus setup.
# Example of middleware registration in Python
bus = EventBus(middlewares=[
WALEventBusMiddleware(log_path),
AutoErrorEventMiddleware(),
LoggerEventBusMiddleware()
])
Sources: TODO_ts.md:28-29, TODO_python.md:109-114, tests/test_eventbus.py:863-865
Implementation Constraints
- Ordering: Hooks are executed in the order of registration.
- Error Handling: Middleware throw behavior is generally undefined; the core bus does not provide mandatory suppression or guarding for middleware errors.
- Async Behavior: In TypeScript, sync mutation paths (like
markCancelled) emit hooks via a microtask helper to avoid changing synchronous signatures. - Monotonicity: Lifecycle hooks must remain monotonic (e.g.,
started -> completed, never the reverse).
Sources: TODO_ts.md:31-33, TODO_ts.md:144-149, TODO_python.md:31-33
Built-in Middleware Modules
The project includes several standard middleware implementations to handle common requirements.
Monitoring and Persistence
- WALEventBusMiddleware: A Write-Ahead Log implementation that persists every completed event to a JSONL file. This ensures a durable record of activity that can be used for recovery or auditing.
- SQLiteHistoryMirrorMiddleware: Persists event snapshots and handler result metadata into SQLite tables (
events_logandevent_results_log). This is used by the UI Monitoring Dashboard. - LoggerEventBusMiddleware: Provides standardized logging of event transitions and handler outcomes to stdout or a specified file.
Sources: bubus/middlewares.py, tests/test_eventbus.py:863-888, TODO_ts.md:231-236
Automation and Tracing
- AutoErrorEventMiddleware: Automatically emits a new error event (e.g.,
OriginalEventErrorEvent) whenever a handler fails. It includes recursion guards to prevent infinite error loops. - AutoReturnEventMiddleware: Automatically emits a result event (e.g.,
OriginalEventResultEvent) when a handler returns a non-None(Python) or non-undefined(TS) value, provided the value is not itself aBaseEvent. - OtelTracingMiddleware: Integrates with OpenTelemetry to track parent-child event relationships and handler execution spans.
- AutoHandlerChangeEventMiddleware: Emits events whenever handlers are added or removed from the bus.
Sources: bubus/middlewares.py, tests/test_eventbus.py:778-825, TODO_ts.md:231-236
Data Flow: Middleware Integration
Middleware integrates into the system by hooking into the terminal paths of event and handler execution.
At the end of an event's lifecycle, the EventBus performs cleanup. In TypeScript, this involves a _gc() method that breaks internal reference chains so completed events can be garbage collected once evicted from event_history. Middleware like SQLiteHistoryMirrorMiddleware ensures that even after GC, the data remains accessible in external storage.
Sources: bubus-ts/src/base_event.ts:634-644, bubus/middlewares.py, TODO_ts.md:145-149
Conclusion
Middleware and hooks form the observability backbone of the bubus library. By decoupling cross-cutting concerns from the core event processing logic, the system remains extensible while maintaining strict guarantees on event ordering and handler isolation. Whether through built-in tracing or custom logging middleware, the hook system ensures every transition in the event lifecycle is captured and actionable.