{"id":47611615,"url":"https://github.com/archivebox/abxbus","last_synced_at":"2026-05-16T23:22:21.758Z","repository":{"id":318735597,"uuid":"1069379944","full_name":"ArchiveBox/abxbus","owner":"ArchiveBox","description":"📢 Fast in-memory Python \u0026 Typescript event bus library with support for advanced concurrency control features, nested event tracking, Pydantic \u0026 Zod type enforcement, and more...","archived":false,"fork":false,"pushed_at":"2026-04-19T04:46:46.000Z","size":3059,"stargazers_count":5,"open_issues_count":4,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-19T06:30:40.346Z","etag":null,"topics":["abx","archivebox","async","celery","event-bus","event-driven","event-sourcing","eventbus","events","kafka","nats","postgres","pydantic","python","redis","sqlite","typescript","zod"],"latest_commit_sha":null,"homepage":"https://abxbus.archivebox.io","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"browser-use/bubus","license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ArchiveBox.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-10-03T20:57:26.000Z","updated_at":"2026-04-19T04:46:49.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ArchiveBox/abxbus","commit_stats":null,"previous_names":["pirate/bubus","pirate/bbus","archivebox/abx-bus"],"tags_count":40,"template":false,"template_full_name":null,"purl":"pkg:github/ArchiveBox/abxbus","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ArchiveBox%2Fabxbus","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ArchiveBox%2Fabxbus/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ArchiveBox%2Fabxbus/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ArchiveBox%2Fabxbus/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ArchiveBox","download_url":"https://codeload.github.com/ArchiveBox/abxbus/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ArchiveBox%2Fabxbus/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32209897,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-24T03:15:14.334Z","status":"ssl_error","status_checked_at":"2026-04-24T03:15:11.608Z","response_time":64,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["abx","archivebox","async","celery","event-bus","event-driven","event-sourcing","eventbus","events","kafka","nats","postgres","pydantic","python","redis","sqlite","typescript","zod"],"created_at":"2026-04-01T20:27:10.332Z","updated_at":"2026-05-16T23:22:21.749Z","avatar_url":"https://github.com/ArchiveBox.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# `abxbus`: 📢 Production-ready multi-language event bus\n\n\u003cimg width=\"200\" alt=\"image\" src=\"https://github.com/user-attachments/assets/b3525c24-51ba-496c-b327-ccdfe46a7362\" align=\"right\" /\u003e\n\n[![DeepWiki: Python](https://img.shields.io/badge/DeepWiki-abxbus%2FPython-yellow.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAyCAYAAAAnWDnqAAAAAXNSR0IArs4c6QAAA05JREFUaEPtmUtyEzEQhtWTQyQLHNak2AB7ZnyXZMEjXMGeK/AIi+QuHrMnbChYY7MIh8g01fJoopFb0uhhEqqcbWTp06/uv1saEDv4O3n3dV60RfP947Mm9/SQc0ICFQgzfc4CYZoTPAswgSJCCUJUnAAoRHOAUOcATwbmVLWdGoH//PB8mnKqScAhsD0kYP3j/Yt5LPQe2KvcXmGvRHcDnpxfL2zOYJ1mFwrryWTz0advv1Ut4CJgf5uhDuDj5eUcAUoahrdY/56ebRWeraTjMt/00Sh3UDtjgHtQNHwcRGOC98BJEAEymycmYcWwOprTgcB6VZ5JK5TAJ+fXGLBm3FDAmn6oPPjR4rKCAoJCal2eAiQp2x0vxTPB3ALO2CRkwmDy5WohzBDwSEFKRwPbknEggCPB/imwrycgxX2NzoMCHhPkDwqYMr9tRcP5qNrMZHkVnOjRMWwLCcr8ohBVb1OMjxLwGCvjTikrsBOiA6fNyCrm8V1rP93iVPpwaE+gO0SsWmPiXB+jikdf6SizrT5qKasx5j8ABbHpFTx+vFXp9EnYQmLx02h1QTTrl6eDqxLnGjporxl3NL3agEvXdT0WmEost648sQOYAeJS9Q7bfUVoMGnjo4AZdUMQku50McDcMWcBPvr0SzbTAFDfvJqwLzgxwATnCgnp4wDl6Aa+Ax283gghmj+vj7feE2KBBRMW3FzOpLOADl0Isb5587h/U4gGvkt5v60Z1VLG8BhYjbzRwyQZemwAd6cCR5/XFWLYZRIMpX39AR0tjaGGiGzLVyhse5C9RKC6ai42ppWPKiBagOvaYk8lO7DajerabOZP46Lby5wKjw1HCRx7p9sVMOWGzb/vA1hwiWc6jm3MvQDTogQkiqIhJV0nBQBTU+3okKCFDy9WwferkHjtxib7t3xIUQtHxnIwtx4mpg26/HfwVNVDb4oI9RHmx5WGelRVlrtiw43zboCLaxv46AZeB3IlTkwouebTr1y2NjSpHz68WNFjHvupy3q8TFn3Hos2IAk4Ju5dCo8B3wP7VPr/FGaKiG+T+v+TQqIrOqMTL1VdWV1DdmcbO8KXBz6esmYWYKPwDL5b5FA1a0hwapHiom0r/cKaoqr+27/XcrS5UwSMbQAAAABJRU5ErkJggg==)](https://deepwiki.com/ArchiveBox/abxbus) [![PyPI - Version](https://img.shields.io/pypi/v/abxbus)](https://pypi.org/project/abxbus/) [![PyPi Downloads/week](https://static.pepy.tech/badge/bubus/week)](https://pepy.tech/projects/abxbus) ![GitHub last commit](https://img.shields.io/github/last-commit/ArchiveBox/abxbus)\n\n[![DeepWiki: TS](https://img.shields.io/badge/DeepWiki-abxbus%2FTypescript-blue.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAyCAYAAAAnWDnqAAAAAXNSR0IArs4c6QAAA05JREFUaEPtmUtyEzEQhtWTQyQLHNak2AB7ZnyXZMEjXMGeK/AIi+QuHrMnbChYY7MIh8g01fJoopFb0uhhEqqcbWTp06/uv1saEDv4O3n3dV60RfP947Mm9/SQc0ICFQgzfc4CYZoTPAswgSJCCUJUnAAoRHOAUOcATwbmVLWdGoH//PB8mnKqScAhsD0kYP3j/Yt5LPQe2KvcXmGvRHcDnpxfL2zOYJ1mFwrryWTz0advv1Ut4CJgf5uhDuDj5eUcAUoahrdY/56ebRWeraTjMt/00Sh3UDtjgHtQNHwcRGOC98BJEAEymycmYcWwOprTgcB6VZ5JK5TAJ+fXGLBm3FDAmn6oPPjR4rKCAoJCal2eAiQp2x0vxTPB3ALO2CRkwmDy5WohzBDwSEFKRwPbknEggCPB/imwrycgxX2NzoMCHhPkDwqYMr9tRcP5qNrMZHkVnOjRMWwLCcr8ohBVb1OMjxLwGCvjTikrsBOiA6fNyCrm8V1rP93iVPpwaE+gO0SsWmPiXB+jikdf6SizrT5qKasx5j8ABbHpFTx+vFXp9EnYQmLx02h1QTTrl6eDqxLnGjporxl3NL3agEvXdT0WmEost648sQOYAeJS9Q7bfUVoMGnjo4AZdUMQku50McDcMWcBPvr0SzbTAFDfvJqwLzgxwATnCgnp4wDl6Aa+Ax283gghmj+vj7feE2KBBRMW3FzOpLOADl0Isb5587h/U4gGvkt5v60Z1VLG8BhYjbzRwyQZemwAd6cCR5/XFWLYZRIMpX39AR0tjaGGiGzLVyhse5C9RKC6ai42ppWPKiBagOvaYk8lO7DajerabOZP46Lby5wKjw1HCRx7p9sVMOWGzb/vA1hwiWc6jm3MvQDTogQkiqIhJV0nBQBTU+3okKCFDy9WwferkHjtxib7t3xIUQtHxnIwtx4mpg26/HfwVNVDb4oI9RHmx5WGelRVlrtiw43zboCLaxv46AZeB3IlTkwouebTr1y2NjSpHz68WNFjHvupy3q8TFn3Hos2IAk4Ju5dCo8B3wP7VPr/FGaKiG+T+v+TQqIrOqMTL1VdWV1DdmcbO8KXBz6esmYWYKPwDL5b5FA1a0hwapHiom0r/cKaoqr+27/XcrS5UwSMbQAAAABJRU5ErkJggg==)](https://deepwiki.com/ArchiveBox/abxbus/3-typescript-implementation) [![NPM Version](https://img.shields.io/npm/v/abxbus)](https://www.npmjs.com/package/abxbus) [![PyPi Downloads/month](https://static.pepy.tech/badge/bubus/month)](https://pepy.tech/projects/abxbus) [![GitHub License](https://img.shields.io/github/license/ArchiveBox/abxbus)](https://github.com/ArchiveBox/abxbus)\n\nAbxBus is an in-memory event bus library for async Python, TypeScript (node/browser), Rust, and Go.\n\nIt's designed for quickly building resilient, predictable, complex event-driven apps.\n\nIt \"just works\" with an intuitive, but powerful event JSON format + emit API that's consistent across runtimes and scales consistently from one event up to millions (~0.2ms/event):\n\n```python\nclass SomeEvent(BaseEvent):\n    some_data: int\n\ndef handle_some_event(event: SomeEvent):\n    print('hi!')\n\nbus.on(SomeEvent, some_function)\nawait bus.emit(SomeEvent(some_data=132)).now()\n# \"hi!\"\"\n```\n\nIt's async native, has proper automatic nested event tracking, and powerful concurrency control options. The API is inspired by `EventEmitter` or [`emittery`](https://github.com/sindresorhus/emittery) in JS, but it takes it a step further:\n\n- nice Pydantic / Zod schemas for events that can be exchanged between runtimes\n- automatic UUIDv7s and monotonic nanosecond timestamps for ordering events globally\n- built in locking options to force strict global FIFO processing or fully parallel processing\n\n---\n\n♾️ It's inspired by the simplicity of async and events in `JS` but with baked-in features that allow to eliminate most of the tedious repetitive complexity in event-driven codebases:\n\n- correct timeout enforcement across multiple levels of events, including cancellation of awaited/blocking child work when a parent times out\n- ability to strongly type hint and enforce the return type of event handlers at compile-time\n- ability to queue events on the bus, or inline await them for immediate execution like a normal function call\n- handles thousands of events/sec/core; see the runtime matrix below for current measured numbers\n\n\u003cbr/\u003e\n\n## 🔢 Quickstart\n\nInstall abxbus and get started with a simple event-driven application:\n\n```bash\npip install abxbus      # see ./abxbus-ts/README.md for JS instructions\n```\n\n```python\nimport asyncio\nfrom abxbus import EventBus, BaseEvent\nfrom your_auth_events import AuthRequestEvent, AuthResponseEvent\n\nclass UserLoginEvent(BaseEvent[str]):\n    username: str\n    is_admin: bool\n\nasync def handle_login(event: UserLoginEvent) -\u003e str:\n    auth_request = await event.emit(AuthRequestEvent(...)).now()  # nested events supported\n    auth_response = await event.event_bus.find(AuthResponseEvent, child_of=auth_request, future=30)\n    return f\"User {event.username} logged in admin={event.is_admin} with API response: {await auth_response.event_result()}\"\n\nbus = EventBus()\nbus.on(UserLoginEvent, handle_login)\nbus.on(AuthRequestEvent, AuthAPI.post)\n\nevent = bus.emit(UserLoginEvent(username=\"alice\", is_admin=True))\nprint(await event.event_result())\n# User alice logged in admin=True with API response: {...}\n```\n\n\u003cbr/\u003e\n\n---\n\n\u003cbr/\u003e\n\n## ✨ Features\n\n\u003cbr/\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003e🔎 Event Pattern Matching\u003c/strong\u003e\u003c/summary\u003e\n\n[Subscribe to events](https://abxbus.archivebox.io/features/event-pattern-matching) using multiple patterns:\n\n```python\n# By event model class (recommended for best type hinting)\nbus.on(UserActionEvent, handler)\n\n# By event type string\nbus.on('UserActionEvent', handler)\n\n# Wildcard - handle all events\nbus.on('*', universal_handler)\n```\n\n\u003cbr/\u003e\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003e🔀 Async and Sync Handler Support\u003c/strong\u003e\u003c/summary\u003e\n\nRegister both [synchronous and asynchronous handlers](https://abxbus.archivebox.io/features/async-sync-handlers) for maximum flexibility:\n\n```python\n# Async handler\nasync def async_handler(event: SomeEvent) -\u003e str:\n    await asyncio.sleep(0.1)  # Simulate async work\n    return \"async result\"\n\n# Sync handler\ndef sync_handler(event: SomeEvent) -\u003e str:\n    return \"sync result\"\n\nbus.on(SomeEvent, async_handler)\nbus.on(SomeEvent, sync_handler)\n```\n\nHandlers can also be defined under classes for easier organization:\n\n```python\nclass SomeService:\n    some_value = 'this works'\n\n    async def handlers_can_be_methods(self, event: SomeEvent) -\u003e str:\n        return self.some_value\n\n    @classmethod\n    async def handler_can_be_classmethods(cls, event: SomeEvent) -\u003e str:\n        return cls.some_value\n\n    @staticmethod\n    async def handlers_can_be_staticmethods(event: SomeEvent) -\u003e str:\n        return 'this works too'\n\n# All usage patterns behave the same:\nbus.on(SomeEvent, SomeService().handlers_can_be_methods)\nbus.on(SomeEvent, SomeService.handler_can_be_classmethods)\nbus.on(SomeEvent, SomeService.handlers_can_be_staticmethods)\n```\n\n\u003cbr/\u003e\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003e🔠 Type-Safe Events with Pydantic\u003c/strong\u003e\u003c/summary\u003e\n\nDefine events as Pydantic models with [full type checking and validation](https://abxbus.archivebox.io/features/typed-events):\n\n```python\nfrom typing import Any\nfrom abxbus import BaseEvent\n\nclass OrderCreatedEvent(BaseEvent):\n    order_id: str\n    customer_id: str\n    total_amount: float\n    items: list[dict[str, Any]]\n\n# Events are automatically validated\nevent = OrderCreatedEvent(\n    order_id=\"ORD-123\",\n    customer_id=\"CUST-456\",\n    total_amount=99.99,\n    items=[{\"sku\": \"ITEM-1\", \"quantity\": 2}]\n)\n```\n\n\u003e [!TIP]\n\u003e You can also enforce the types of [event handler return values](https://abxbus.archivebox.io/features/return-value-handling#typed-return-values).\n\n\u003cbr/\u003e\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003e⏩ Forward `Events` Between `EventBus`s\u003c/strong\u003e\u003c/summary\u003e\n\nYou can define separate `EventBus` instances in different \"microservices\" to separate different areas of concern.\n`EventBus`s can be set up to [forward events between each other](https://abxbus.archivebox.io/features/forwarding-between-buses) (with automatic loop prevention):\n\n```python\n# Create a hierarchy of buses\nmain_bus = EventBus(name='MainBus')\nauth_bus = EventBus(name='AuthBus')\ndata_bus = EventBus(name='DataBus')\n\n# Share all or specific events between buses\nmain_bus.on('*', auth_bus.emit)  # if main bus gets LoginEvent, will forward to AuthBus\nauth_bus.on('*', data_bus.emit)  # auth bus will forward everything to DataBus\ndata_bus.on('*', main_bus.emit)  # don't worry! event will only be processed once by each, no infinite loop occurs\n\n# Events flow through the hierarchy with tracking\nevent = main_bus.emit(LoginEvent())\nawait event.now()\nprint(event.event_path)  # ['MainBus#ab12', 'AuthBus#cd34', 'DataBus#ef56']  # list of bus labels that already processed the event\n```\n\n\u003cbr/\u003e\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003e🔱 Event Handler Return Value Support\u003c/strong\u003e\u003c/summary\u003e\n\n[Collect results](https://abxbus.archivebox.io/features/return-value-handling) from multiple handlers:\n\n```python\nasync def load_user_config(event: GetConfigEvent) -\u003e dict[str, Any]:\n    return {\"debug\": True, \"port\": 8080}\n\nasync def load_system_config(event: GetConfigEvent) -\u003e dict[str, Any]:\n    return {\"debug\": False, \"timeout\": 30}\n\nbus.on(GetConfigEvent, load_user_config)\nbus.on(GetConfigEvent, load_system_config)\n\n# Get all handler result values\nevent = await bus.emit(GetConfigEvent()).now()\nresults = await event.event_results_list()\n\n# Inspect per-handler metadata when needed\nfor handler_id, event_result in event.event_results.items():\n    print(handler_id, event_result.handler_name, event_result.result)\n```\n\n\u003cbr/\u003e\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003e🚦 FIFO / Parallel Event Processing\u003c/strong\u003e\u003c/summary\u003e\n\nBy default, events and their handlers are processed in [strict serial FIFO order](https://abxbus.archivebox.io/concurrency/events-bus-serial), maintaining consistency:\n\n```python\n# Events are processed in the order they were emitted\nfor i in range(10):\n    bus.emit(ProcessTaskEvent(task_id=i))\n\n# Even with async handlers, order is preserved\nawait bus.wait_until_idle(timeout=30.0)\n```\n\nIf a handler emits and awaits any child events during execution, those events will [jump the FIFO queue](https://abxbus.archivebox.io/concurrency/immediate-execution) and be processed immediately:\n\n```python\ndef child_handler(event: SomeOtherEvent) -\u003e str:\n    return 'xzy123'\n\nasync def main_handler(event: MainEvent) -\u003e str:\n    # emit a linked child event\n    child_event = event.emit(SomeOtherEvent())\n\n    # now() marks it as parent-completion-blocking and can queue-jump it\n    completed_child_event = await child_event.now()\n    return f'result from awaiting child event: {await completed_child_event.event_result()}'  # 'xyz123'\n\nbus.on(SomeOtherEvent, child_handler)\nbus.on(MainEvent, main_handler)\n\nmain_event = await bus.emit(MainEvent()).now()\nprint(await main_event.event_result())\n# result from awaiting child event: xyz123\n```\n\nYou can also set [`event_concurrency='parallel'`](https://abxbus.archivebox.io/concurrency/events-parallel) and [`event_handler_concurrency='parallel'`](https://abxbus.archivebox.io/concurrency/handlers-parallel) options per-bus, per-event, or per-handler enable parallel processing when needed.\n\n\u003cbr/\u003e\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003e🪆 Emit Nested Child Events From Handlers\u003c/strong\u003e\u003c/summary\u003e\n\n[Automatically track event relationships](https://abxbus.archivebox.io/features/parent-child-tracking) and causality tree:\n\n```python\nasync def parent_handler(event: BaseEvent):\n    # Most handler code should use this: linked child work that blocks parent completion.\n    blocking_child = await event.emit(ChildEvent()).now()\n    assert blocking_child.event_parent_id == event.event_id\n    assert blocking_child.event_blocks_parent_completion is True\n\n    # Linked background work keeps ancestry but does not hold the parent open.\n    linked_background_child = event.emit(ChildEvent())\n    assert linked_background_child.event_parent_id == event.event_id\n    assert linked_background_child.event_blocks_parent_completion is False\n\n    # Awaiting bus.emit(...) blocks this handler naturally, but creates a top-level event.\n    detached_blocking_event = await event.event_bus.emit(ChildEvent()).now()\n    assert detached_blocking_event.event_parent_id is None\n    assert detached_blocking_event.event_blocks_parent_completion is False\n\n    # Un-awaited bus.emit(...) is a true detached background event.\n    detached_background_event = event.event_bus.emit(ChildEvent())\n    assert detached_background_event.event_parent_id is None\n    assert detached_background_event.event_blocks_parent_completion is False\n\nasync def run_main():\n    bus.on(ChildEvent, child_handler)\n    bus.on(ParentEvent, parent_handler)\n\n    parent_event = bus.emit(ParentEvent())\n    print(parent_event.event_children)           # show all the child events emitted during handling of an event\n    await parent_event.now()\n    print(bus.log_tree())\n    await bus.destroy()\n\nif __name__ == '__main__':\n    asyncio.run(run_main())\n```\n\n\u003cimg width=\"100%\" alt=\"show the whole tree of events at any time using the logging helpers\" src=\"https://github.com/user-attachments/assets/f94684a6-7694-4066-b948-46925f47b56c\" /\u003e\u003cbr/\u003e\n\u003cimg width=\"100%\" alt=\"intelligent timeout handling to differentiate handler that timed out from handler that was interrupted\" src=\"https://github.com/user-attachments/assets/8da341fd-6c26-4c68-8fec-aef1ca55c189\" /\u003e\n\n\u003cbr/\u003e\u003cbr/\u003e\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003e🔎 Find Events in History or Wait for Future Events\u003c/strong\u003e\u003c/summary\u003e\n\n[`find()`](https://abxbus.archivebox.io/features/find-events) is the single lookup API: search history, wait for future events, or combine both to check for an existing recent event before emitting a new one.\n\n```python\n# Default: non-blocking history lookup (past=True, future=False)\nexisting = await bus.find(ResponseEvent)\n\n# Wait only for future matches\nfuture = await bus.find(ResponseEvent, past=False, future=5)\n\n# Combine event predicate + event metadata filters\nmatch = await bus.find(\n    ResponseEvent,\n    where=lambda e: e.request_id == my_id,\n    event_status='completed',\n    future=5,\n)\n\n# Wildcard: match any event type, filtered by metadata/predicate\nany_completed = await bus.find(\n    '*',\n    where=lambda e: e.event_type.endswith('ResultEvent'),\n    event_status='completed',\n    future=5,\n)\n```\n\n#### Finding Child Events\n\nWhen you emit an event that triggers child events, use `child_of` to find specific descendants:\n\n```python\n# Emit a parent event that triggers child events\nnav_event = await bus.emit(NavigateToUrlEvent(url=\"https://example.com\")).now()\n\n# Find a child event (already fired while NavigateToUrlEvent was being handled)\nnew_tab = await bus.find(TabCreatedEvent, child_of=nav_event, past=5)\nif new_tab:\n    print(f\"New tab created: {new_tab.tab_id}\")\n```\n\nThis solves race conditions where child events fire before you start waiting for them.\n\n#### Returning Multiple Matches with `filter()`\n\n`filter()` takes the same arguments as `find()` but returns the list of all matching events\n(newest to oldest), plus an optional `limit` argument to cap the result count.\n\n```python\nrecent = await bus.filter(ResponseEvent, past=10, future=False, limit=5)\n```\n\nSee the `EventBus.find(...)` API section below for full parameter details.\n\n\u003e [!IMPORTANT]\n\u003e `find()` resolves when the event is first _emitted_ to the `EventBus`, not when it completes.\n\u003e Use `await event.now()` for immediate-await semantics (queue-jumps when called inside a handler), or `await event.wait()` to always wait in normal queue order.\n\u003e Python also supports `await event` as a Python-only shortcut for `await event.now()`.\n\u003e If no match is found (or future timeout elapses), `find()` returns `None`.\n\n\u003cbr/\u003e\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003e🔁 Event Debouncing\u003c/strong\u003e\u003c/summary\u003e\n\nAvoid re-running expensive work by reusing recent events. The `find()` method makes [debouncing](https://abxbus.archivebox.io/features/find-events#7-debounce-expensive-work) simple:\n\n```python\n# Simple debouncing: reuse event from last 10 seconds, or emit new\nevent = await bus.find(ScreenshotEvent, past=10, future=False) or bus.emit(ScreenshotEvent())\nevent = await event.now()\n\n# Advanced: check history, wait briefly for new event to appear, fallback to emit new event\nevent = (\n    await bus.find(SyncEvent, past=True, future=False)   # Check all history (instant)\n    or await bus.find(SyncEvent, past=False, future=5)   # Wait up to 5s for in-flight\n    or bus.emit(SyncEvent())                         # Fallback: emit new\n)\nawait event.now()                                              # get completed event\n```\n\n\u003cbr/\u003e\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003e🎯 Event Handler Return Values\u003c/strong\u003e\u003c/summary\u003e\n\nThere are two ways to get [return values](https://abxbus.archivebox.io/features/return-value-handling) from event handlers:\n\n**1. Have handlers return their values directly, which puts them in `event.event_results`:**\n\n```python\nclass DoSomeMathEvent(BaseEvent[int]):  # BaseEvent[int] = handlers are validated as returning int\n    a: int\n    b: int\n\n    # int passed above gets saved to:\n    # event_result_type = int\n\ndef do_some_math(event: DoSomeMathEvent) -\u003e int:\n    return event.a + event.b\n\nevent_bus.on(DoSomeMathEvent, do_some_math)\nevent = await event_bus.emit(DoSomeMathEvent(a=100, b=120)).now(first_result=True)\nprint(await event.event_result())\n# 220\n```\n\nYou can use these helpers to interact with the results returned by handlers:\n\n- `BaseEvent.event_result()`\n- `BaseEvent.event_results_list()`\n- Inspect raw per-handler entries via `BaseEvent.event_results`\n\n**2. Have the handler do the work, then emit another event containing the result value, which other code can find:**\n\n```python\ndef do_some_math(event: DoSomeMathEvent[int]) -\u003e int:\n    result = event.a + event.b\n    event.emit(MathCompleteEvent(final_sum=result))\n\nevent_bus.on(DoSomeMathEvent, do_some_math)\nawait event_bus.emit(DoSomeMathEvent(a=100, b=120)).now()\nresult_event = await event_bus.find(MathCompleteEvent, past=False, future=30)\nprint(result_event.final_sum)\n# 220\n```\n\nThese events can also be emitted automatically for you if you enable the [`AutoReturnEventMiddleware`](https://abxbus.archivebox.io/integrations/middleware-auto-return).\n\n#### Annotating Event Handler Return Value Types\n\nAbxBus supports optional [strict typing for Event handler return values](https://abxbus.archivebox.io/features/return-value-handling#typed-return-values) using a generic parameter passed to `BaseEvent[ReturnTypeHere]`.\nFor example if you use `BaseEvent[str]`, abxbus would enforce that all handler functions must return `str | None` at compile-time via IDE/`mypy`/`pyright`/`ty` type hints, and at runtime when each handler finishes.\n\n```python\nclass ScreenshotEvent(BaseEvent[bytes]):  # BaseEvent[bytes] will enforce that handlers can only return bytes\n    width: int\n    height: int\n\nasync def on_ScreenshotEvent(event: ScreenshotEvent) -\u003e bytes:\n    return b'someimagebytes...'  # ✅ IDE type-hints \u0026 runtime both enforce return type matches expected: bytes\n    return 123                   # ❌ will show mypy/pyright issue + raise TypeError if the wrong type is returned\n\nevent_bus.on(ScreenshotEvent, on_ScreenshotEvent)\n\n# Handler return values are automatically validated against the bytes type\nevent = await event_bus.emit(ScreenshotEvent(...)).now(first_result=True)\nreturned_bytes = await event.event_result()\nassert isinstance(returned_bytes, bytes)\n```\n\n**Important:** The validation uses Pydantic's `TypeAdapter`, which validates but does not coerce types. Handlers must return the exact type specified or `None`:\n\n```python\nclass StringEvent(BaseEvent[str]):\n    pass\n\n# ✅ This works - returns the expected str type\ndef good_handler(event: StringEvent) -\u003e str:\n    return \"hello\"\n\n# ❌ This fails validation - returns int instead of str\ndef bad_handler(event: StringEvent) -\u003e str:\n    return 42  # ValidationError: expected str, got int\n```\n\nThis also works with complex types and Pydantic models:\n\n```python\nclass EmailMessage(BaseModel):\n    subject: str\n    content_len: int\n    email_from: str\n\nclass FetchInboxEvent(BaseEvent[list[EmailMessage]]):\n    account_id: UUID\n    auth_key: str\n\nasync def fetch_from_gmail(event: FetchInboxEvent) -\u003e list[EmailMessage]:\n    return [EmailMessage(subject=msg.subj, ...) for msg in GmailAPI.get_msgs(event.account_id, ...)]\n\nevent_bus.on(FetchInboxEvent, fetch_from_gmail)\n\n# Return values are automatically validated as list[EmailMessage]\nevent = await event_bus.emit(FetchInboxEvent(account_id='124', ...)).now(first_result=True)\nemail_list = await event.event_result()\n```\n\nFor pure Python usage, `event_result_type` can be any Python/Pydantic type you want. For cross-language JSON roundtrips, object-like shapes (e.g. `TypedDict`, `dataclass`, model-like dict schemas) rehydrate on Python as Pydantic models, map keys are constrained to JSON object string keys, and fine-grained string constraints/custom field validator logic is not preserved.\n\n\u003cbr/\u003e\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003e🧵 ContextVar Propagation\u003c/strong\u003e\u003c/summary\u003e\n\nContextVars set before `emit()` are [automatically propagated to event handlers](https://abxbus.archivebox.io/features/context-propagation). This is essential for request-scoped context like request IDs, user sessions, or tracing spans:\n\n```python\nfrom contextvars import ContextVar\n\n# Define your context variables\nrequest_id: ContextVar[str] = ContextVar('request_id', default='\u003cunset\u003e')\nuser_id: ContextVar[str] = ContextVar('user_id', default='\u003cunset\u003e')\n\nasync def handler(event: MyEvent) -\u003e str:\n    # Handler sees the context values that were set before emit()\n    print(f\"Request: {request_id.get()}, User: {user_id.get()}\")\n    return \"done\"\n\nbus.on(MyEvent, handler)\n\n# Set context before emit (e.g., in FastAPI middleware)\nrequest_id.set('req-12345')\nuser_id.set('user-abc')\n\n# Handler will see request_id='req-12345' and user_id='user-abc'\nawait bus.emit(MyEvent()).now()\n```\n\n**Context propagates through nested handlers:**\n\n```python\nasync def parent_handler(event: ParentEvent) -\u003e str:\n    # Context is captured at emit time\n    print(f\"Parent sees: {request_id.get()}\")  # 'req-12345'\n\n    # Child events inherit the same context\n    await event.emit(ChildEvent()).now()\n    return \"parent_done\"\n\nasync def child_handler(event: ChildEvent) -\u003e str:\n    # Child also sees the original emit context\n    print(f\"Child sees: {request_id.get()}\")  # 'req-12345'\n    return \"child_done\"\n```\n\n**Context isolation between emits:**\n\nEach emit captures its own context snapshot. Concurrent emits with different context values are properly isolated:\n\n```python\nrequest_id.set('req-A')\nevent_a = bus.emit(MyEvent())  # Handler A sees 'req-A'\n\nrequest_id.set('req-B')\nevent_b = bus.emit(MyEvent())  # Handler B sees 'req-B'\n\nawait event_a.now()  # Still sees 'req-A'\nawait event_b.now()  # Still sees 'req-B'\n```\n\n\u003e [!NOTE]\n\u003e Context is captured at `emit()` time, not when the handler executes. This ensures handlers see the context from the call site, even if the event is processed later from a queue.\n\n\u003cbr/\u003e\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003e🧹 Memory Management\u003c/strong\u003e\u003c/summary\u003e\n\nEventBus includes [automatic memory management](https://abxbus.archivebox.io/api/eventbus#shared-configuration-semantics) to prevent unbounded growth in long-running applications:\n\n```python\n# Create a bus with memory limits (default: 100 events)\nbus = EventBus(max_history_size=100)  # Keep max 100 events in history\n\n# Or disable memory limits for unlimited history\nbus = EventBus(max_history_size=None)\n\n# Or keep only in-flight events in history (drop each event as soon as it completes)\nbus = EventBus(max_history_size=0)\n\n# Or reject new emits when history is full (instead of dropping old history)\nbus = EventBus(max_history_size=100, max_history_drop=False)\n```\n\n**Automatic Cleanup:**\n\n- When `max_history_size` is set and `max_history_drop=True`, EventBus removes old events when the limit is exceeded\n- If `max_history_size=0`, history keeps only pending/started events and drops each event immediately after completion\n- If `max_history_drop=True`, the bus may drop oldest history entries even if they are uncompleted events\n- Completed events are removed first (oldest first), then started events, then pending events\n- This ensures active events are preserved while cleaning up old completed events\n\n**Manual Memory Management:**\n\n```python\n# For request-scoped buses (e.g. web servers), clear all memory after each request\ntry:\n    event_service = EventService()  # Creates internal EventBus\n    await event_service.process_request()\nfinally:\n    # Clear all event history and remove from global tracking\n    await event_service.eventbus.destroy(clear=True)\n```\n\n**Memory Monitoring:**\n\n- EventBus automatically monitors total memory usage across all instances\n- Warnings are logged when total memory exceeds 50MB\n- Use `bus.destroy(clear=True)` to completely free memory for unused buses\n- To avoid memory leaks from big events, the default limits are intentionally kept low. events are normally processed as they come in, and there is rarely a need to keep every event in memory longer after its complete. long-term storage should be accomplished using other mechanisms, like the WAL\n\n\u003cbr/\u003e\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003e⛓️ Parallel Handler Execution\u003c/strong\u003e\u003c/summary\u003e\n\n\u003e [!CAUTION]\n\u003e **Not Recommended.** Only for advanced users willing to implement their own concurrency control.\n\nEnable [parallel processing](https://abxbus.archivebox.io/concurrency/handlers-parallel) of handlers for better performance.  \nThe harsh tradeoff is less deterministic ordering as handler execution order will not be guaranteed when run in parallel.\n(It's very hard to write non-flaky/reliable applications when handler execution order is not guaranteed.)\n\n```python\n# Create bus with parallel handler execution\nbus = EventBus(event_handler_concurrency='parallel')\n\n# Multiple handlers run concurrently for each event\nbus.on('DataEvent', slow_handler_1)  # Takes 1 second\nbus.on('DataEvent', slow_handler_2)  # Takes 1 second\n\nstart = time.time()\nawait bus.emit(DataEvent()).now()\n# Total time: ~1 second (not 2)\n```\n\n\u003cbr/\u003e\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003e🧩 Middlewares\u003c/strong\u003e\u003c/summary\u003e\n\n[Middlewares](https://abxbus.archivebox.io/integrations/middlewares) can observe or mutate the `EventResult` at each step, emit additional events, or trigger other side effects (metrics, retries, auth checks, etc.).\n\n```python\nfrom abxbus import EventBus\nfrom abxbus.middlewares import LoggerEventBusMiddleware, WALEventBusMiddleware, SQLiteHistoryMirrorMiddleware, OtelTracingMiddleware\n\nbus = EventBus(\n    name='MyBus',\n    middlewares=[\n        SQLiteHistoryMirrorMiddleware('./events.sqlite3'),\n        WALEventBusMiddleware('./events.jsonl'),\n        LoggerEventBusMiddleware('./events.log'),\n        OtelTracingMiddleware(),\n        # ...\n    ],\n)\n\nawait bus.emit(SecondEventAbc(some_key=\"banana\")).now()\n# will persist all events to sqlite + events.jsonl + events.log\n```\n\nBuilt-in middlewares you can import from `abxbus.middlewares`:\n\n- `AutoErrorEventMiddleware`: on handler error, fire-and-forget emits `OriginalEventTypeErrorEvent` with `{error, error_type}` (skips `*ErrorEvent`/`*ResultEvent` sources). Useful when downstream/remote consumers only see events and need explicit failure notifications.\n- `AutoReturnEventMiddleware`: on non-`None` handler return, fire-and-forget emits `OriginalEventTypeResultEvent` with `{data}` (skips `*ErrorEvent`/`*ResultEvent` sources). Useful for bridges/remote systems since handler return values do not cross bridge boundaries, but events do.\n- `AutoHandlerChangeEventMiddleware`: emits `BusHandlerRegisteredEvent({handler})` / `BusHandlerUnregisteredEvent({handler})` when handlers are added/removed via `.on()` / `.off()`.\n- `OtelTracingMiddleware`: emits OpenTelemetry spans for events and handlers with parent-child linking; can be exported to Sentry via Sentry's OpenTelemetry integration.\n- `WALEventBusMiddleware`: persists completed events to JSONL for replay/debugging.\n- `LoggerEventBusMiddleware`: writes event/handler transitions to stdout and optionally to file.\n- `SQLiteHistoryMirrorMiddleware`: mirrors event and handler snapshots into append-only SQLite `events_log` and `event_results_log` tables for auditing/debugging.\n\n#### Defining a custom middleware\n\nHandler middlewares subclass `EventBusMiddleware` and override whichever lifecycle hooks they need (`on_event_change`, `on_event_result_change`, `on_bus_handlers_change`):\n\n```python\nfrom abxbus.middlewares import EventBusMiddleware\n\nclass AnalyticsMiddleware(EventBusMiddleware):\n    async def on_event_result_change(self, eventbus, event, event_result, status):\n        if status == 'started':\n            await analytics_bus.emit(HandlerStartedAnalyticsEvent(event_id=event_result.event_id)).now()\n        elif status == 'completed':\n            await analytics_bus.emit(\n                HandlerCompletedAnalyticsEvent(\n                    event_id=event_result.event_id,\n                    error=repr(event_result.error) if event_result.error else None,\n                )\n            ).now()\n\n    async def on_bus_handlers_change(self, eventbus, handler, registered):\n        await analytics_bus.emit(\n            HandlerRegistryChangedEvent(handler_id=handler.id, registered=registered, bus=eventbus.name)\n        ).now()\n```\n\n\u003cbr/\u003e\n\n---\n\n---\n\n\u003cbr/\u003e\n\n\u003c/details\u003e\n\n## 📚 API Documentation\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003e\u003ccode\u003eEventBus\u003c/code\u003e\u003c/strong\u003e\u003c/summary\u003e\n\nThe main event bus class that manages event processing and handler execution.\n\n```python\nEventBus(\n    name: str | None = None,\n    event_concurrency: Literal['global-serial', 'bus-serial', 'parallel'] = 'bus-serial',\n    event_handler_concurrency: Literal['serial', 'parallel'] = 'serial',\n    event_handler_completion: Literal['all', 'first'] = 'all',\n    event_timeout: float | None = 60.0,\n    event_slow_timeout: float | None = 300.0,\n    event_handler_slow_timeout: float | None = 30.0,\n    event_handler_detect_file_paths: bool = True,\n    max_history_size: int | None = 100,\n    max_history_drop: bool = False,\n    middlewares: Sequence[EventBusMiddleware | type[EventBusMiddleware]] | None = None,\n)\n```\n\n**Parameters:**\n\n- `name`: Optional unique name for the bus (auto-generated if not provided)\n- `event_concurrency`: Default event scheduling mode: `'global-serial'`, `'bus-serial'` (default), or `'parallel'` (resolved at processing time when `event.event_concurrency` is unset)\n- `event_handler_concurrency`: Default handler execution mode for events on this bus: `'serial'` (default) or `'parallel'` (resolved at processing time when `event.event_handler_concurrency` is unset)\n- `event_handler_completion`: Handler completion mode for each event: `'all'` (default, wait for all handlers) or `'first'` (complete once first successful non-`None` result is available), resolved at processing time when `event.event_handler_completion` is unset\n- `event_timeout`: Default per-event timeout in seconds resolved at processing time when `event.event_timeout` is `None`\n- `event_slow_timeout`: Default slow-event warning threshold in seconds resolved at processing time when `event.event_slow_timeout` is `None`\n- `event_handler_slow_timeout`: Default slow-handler warning threshold in seconds resolved at processing time when `event.event_handler_slow_timeout` is `None`\n- `event_handler_detect_file_paths`: Whether to auto-detect handler source file paths at registration time (slightly slower when enabled)\n- `max_history_size`: Maximum number of events to keep in history (default: 100, `None` = unlimited, `0` = keep only in-flight events and drop completed events immediately)\n- `max_history_drop`: If `True`, drop oldest history entries when full (even uncompleted events). If `False` (default), reject new emits once history reaches `max_history_size` (except when `max_history_size=0`, which never rejects on history size)\n- `middlewares`: Optional list of `EventBusMiddleware` subclasses or instances that hook into handler execution for analytics, logging, retries, etc. (see [Middlewares](#middlewares) for more info)\n\nTimeout precedence matches TS:\n\n- Effective handler timeout = `min(resolved_handler_timeout, event_timeout)` where `resolved_handler_timeout` resolves in order: `handler.handler_timeout` -\u003e `event.event_handler_timeout` -\u003e `bus.event_timeout`.\n- Slow handler warning threshold resolves in order: `handler.handler_slow_timeout` -\u003e `event.event_handler_slow_timeout` -\u003e `bus.event_handler_slow_timeout`.\n- Bus defaults are applied at execution time by the bus currently processing the event. Unset event fields stay unset on the event object so forwarded events can inherit the target bus defaults.\n\n#### `EventBus` Properties\n\n- `name`: The bus identifier\n- `id`: Unique UUID7 for this bus instance\n- `event_history`: Dict of all events the bus has seen by event_id (limited by `max_history_size`)\n- `events_pending`: List of events waiting to be processed\n- `events_started`: List of events currently being processed\n- `events_completed`: List of completed events\n- `all_instances`: Class-level WeakSet tracking all active EventBus instances (for memory monitoring)\n\n#### `EventBus` Methods\n\n##### `on(event_type: str | Type[BaseEvent], handler: Callable)`\n\nSubscribe a handler to events matching a specific event type or `'*'` for all events.\n\n```python\nbus.on('UserEvent', handler_func)  # By event type string\nbus.on(UserEvent, handler_func)    # By event class\nbus.on('*', handler_func)          # Wildcard - all events\n```\n\n##### `emit(event: BaseEvent) -\u003e BaseEvent`\n\nEnqueue an event for processing and return the pending `Event` immediately (synchronous).\n\n```python\nevent = bus.emit(MyEvent(data=\"test\"))\nresult = await event.now()  # immediate path (queue-jumps when called inside a handler)\nresult_in_queue_order = await event.wait()  # always waits in normal queue order\n```\n\n**Note:** Queueing is unbounded. History pressure is controlled by `max_history_size` + `max_history_drop`:\n\n- `max_history_drop=True`: absorb new events and trim old history entries (even uncompleted events).\n- `max_history_drop=False`: raise `RuntimeError` when history is full.\n- `max_history_size=0`: keep pending/in-flight events only; completed events are immediately removed from history.\n\n##### `find(event_type: str | Literal['*'] | Type[BaseEvent], *, where: Callable[[BaseEvent], bool]=None, child_of: BaseEvent | None=None, past: bool | float | timedelta=True, future: bool | float=False, **event_fields) -\u003e BaseEvent | None`\n\nFind an event matching criteria in history and/or future. This is the recommended unified method for event lookup.\n\n**Parameters:**\n\n- `event_type`: The event type string, `'*'` wildcard, or model class to find\n- `where`: Predicate function for filtering (default: matches all)\n- `child_of`: Only match events that are descendants of this parent event\n- `past`: Controls history search behavior (default: `True`)\n  - `True`: search all history\n  - `False`: skip history search\n  - `float`/`timedelta`: search events from last N seconds only\n- `future`: Controls future wait behavior (default: `False`)\n  - `True`: wait forever for matching event\n  - `False`: don't wait for future events\n  - `float`: wait up to N seconds for matching event\n- `**event_fields`: Optional equality filters for any event fields (for example `event_status='completed'`, `user_id='u-1'`)\n\n```python\n# Default call is non-blocking history lookup (past=True, future=False)\nevent = await bus.find(ResponseEvent)\n\n# Find child of a specific parent event\nchild = await bus.find(ChildEvent, child_of=parent_event, future=5)\n\n# Wait only for future events (ignore history)\nevent = await bus.find(ResponseEvent, past=False, future=5)\n\n# Search recent history + optionally wait\nevent = await bus.find(ResponseEvent, past=5, future=5)\n\n# Filter by event metadata\ncompleted = await bus.find(ResponseEvent, event_status='completed')\n\n# Wildcard match across all event types\nany_completed = await bus.find('*', event_status='completed', past=True, future=False)\n```\n\n##### `filter(event_type, *, limit: int | None=None, ...) -\u003e list[BaseEvent]`\n\nSame as [`find()`](#find-event-type-str--literal--type-base-event--where-callable-base-event-bool-none-child-of-base-event--none-none-past-bool--float--timedelta-true-future-bool--float-false-event-fields---base-event--none)\nbut returns the list of all matching events (newest to oldest) instead of just the first match.\nAccepts an additional `limit` argument to cap the result count.\n\n```python\nrecent = await bus.filter(ResponseEvent, past=10, future=False, limit=5)\n```\n\n##### `event_is_child_of(event: BaseEvent, ancestor: BaseEvent) -\u003e bool`\n\nCheck if event is a descendant of ancestor (child, grandchild, etc.).\n\n```python\nif bus.event_is_child_of(child_event, parent_event):\n    print(\"child_event is a descendant of parent_event\")\n```\n\n##### `event_is_parent_of(event: BaseEvent, descendant: BaseEvent) -\u003e bool`\n\nCheck if event is an ancestor of descendant (parent, grandparent, etc.).\n\n```python\nif bus.event_is_parent_of(parent_event, child_event):\n    print(\"parent_event is an ancestor of child_event\")\n```\n\n##### `wait_until_idle(timeout: float | None=None)`\n\nWait until all events are processed and the bus is idle.\n\n```python\nawait bus.wait_until_idle()             # wait indefinitely until EventBus has finished processing all events\n\nawait bus.wait_until_idle(timeout=5.0)  # wait up to 5 seconds\n```\n\n##### `destroy(clear: bool=True)`\n\nDestroy the event bus immediately. In-flight work is cancelled best-effort, future waiters are resolved, and the bus cannot be used again.\n\n```python\nawait bus.destroy()                  # destroy immediately and clear handlers/history/runtime state\nawait bus.destroy(clear=False)       # destroy immediately but keep handlers/history for inspection\n```\n\n---\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003e\u003ccode\u003eBaseEvent\u003c/code\u003e\u003c/strong\u003e\u003c/summary\u003e\n\nBase class for all events. Subclass `BaseEvent` to define your own events.\n\nMake sure none of your own event data fields start with `event_` or `model_` to avoid clashing with `BaseEvent` or `pydantic` builtin attrs.\n\n#### `BaseEvent` Fields\n\n```python\nT_EventResultType = TypeVar('T_EventResultType', bound=Any, default=None)\n\nclass BaseEvent(BaseModel, Generic[T_EventResultType]):\n    # special config fields\n    event_id: str                # Unique UUID7 identifier, auto-generated if not provided\n    event_type: str              # Defaults to class name e.g. 'BaseEvent'\n    event_result_type: Any | None  # Pydantic model/python type to validate handler return values, defaults to T_EventResultType\n    event_version: str           # Defaults to '0.0.1' (override per class/instance for event payload versioning)\n    event_timeout: float | None = None # Event timeout in seconds (bus default resolved at processing time if None)\n    event_handler_timeout: float | None = None # Optional per-event handler timeout cap in seconds\n    event_slow_timeout: float | None = None # Optional per-event slow-event warning threshold\n    event_handler_slow_timeout: float | None = None # Optional per-event slow-handler warning threshold\n    event_concurrency: Literal['global-serial', 'bus-serial', 'parallel'] | None = None  # optional per-event scheduling override (None -\u003e bus default at processing time)\n    event_handler_concurrency: Literal['serial', 'parallel'] | None = None  # optional per-event handler scheduling override (None -\u003e bus default at processing time)\n    event_handler_completion: Literal['all', 'first'] | None = None  # optional per-event completion override (None -\u003e bus default at processing time)\n\n    # runtime state fields\n    event_status: Literal['pending', 'started', 'completed']  # event processing status (auto-set)\n    event_created_at: str        # Canonical ISO timestamp with 9 fractional digits (auto-set)\n    event_started_at: str | None # Set when first handler starts\n    event_completed_at: str | None # Set when event processing completes\n    event_parent_id: str | None  # Parent event ID that led to this event during handling (auto-set)\n    event_path: list[str]        # List of bus labels traversed, e.g. BusName#ab12 (auto-set)\n    event_results: dict[str, EventResult]   # Handler results {\u003chandler uuid\u003e: EventResult} (auto-set)\n    event_children: list[BaseEvent] # getter property to list any child events emitted during handling\n    event_bus: EventBus          # getter property to get the bus the event was emitted on\n\n    # payload fields\n    # ... subclass BaseEvent to add your own event payload fields here ...\n    # some_key: str\n    # some_other_key: dict[str, int]\n    # ...\n    # (they should not start with event_* to avoid conflict with special built-in fields)\n```\n\n#### `BaseEvent` Methods\n\n##### `now(first_result: bool=False, timeout: float | None=None) -\u003e Self`\n\nImmediate path for the `Event` object.\n\n- Outside a handler: processes the event immediately when it has not started yet, otherwise waits for completion.\n- Inside a handler: queue-jumps this child event so it can run immediately, then returns the event.\n- `first_result=True` waits only until the first valid result is available; remaining handlers continue running.\n- `timeout` limits this wait call only. Use `event_timeout=0` / `event_handler_timeout=0` to disable execution timeouts.\n- Python-only shortcut: `await event` is equivalent to `await event.now()`.\n\n```python\nevent = bus.emit(MyEvent())\ncompleted_event = await event.now()\nfirst_result_event = await event.now(first_result=True, timeout=0.25)\n\nraw_result_values = [event_result.result for event_result in completed_event.event_results.values()]\n# equivalent to: completed_event.event_results_list()  (see below)\n```\n\n##### `wait(first_result: bool=False, timeout: float | None=None) -\u003e Self`\n\n- Never queue-jumps.\n- Waits until the event is completed by normal runloop queue order.\n- `first_result=True` waits only until the first valid result is available; remaining handlers continue running.\n- `timeout` limits this wait call only.\n\n```python\nevent = bus.emit(MyEvent())\ncompleted_event = await event.wait()\nfirst_result_event = await event.wait(first_result=True)\n```\n\n##### `reset() -\u003e Self`\n\nReturn a fresh event copy with runtime processing state reset back to pending.\n\n- Intended for re-emitting an already-seen event as a fresh event (for example after crossing a bridge boundary).\n- The original event object is not mutated, it returns a new copy with some fields reset.\n- A new UUIDv7 `event_id` is generated for the returned copy (to allow it to process as a separate event it needs a new unique uuid)\n- Runtime completion state is cleared (`event_results`, completion signal/flags, processed timestamp, emit context).\n\n##### `event_result_update(handler, eventbus: EventBus | None=None, **kwargs) -\u003e EventResult`\n\nCreate or update a single `EventResult` entry for a handler.\n\n- If no entry exists yet for the handler id, a pending result row is created.\n- Useful for deterministic seeding/rehydration before normal processing resumes.\n- Supports `status`, `result`, `error`, and `timeout` updates through `**kwargs`.\n\n```python\nseeded = event.event_result_update(handler=handler_entry, eventbus=bus, status='pending')\nseeded.update(status='completed', result='seeded')\n```\n\n##### `event_result(include: EventResultFilter=None, raise_if_any: bool=True, raise_if_none: bool=False) -\u003e Any`\n\nUtility method helper to execute all the handlers and return the first handler's raw result value.\n\n**Parameters:**\n\n- `include`: Filter function `(result, event_result) -\u003e bool` to include only specific results (default: only non-None, non-exception results)\n- `raise_if_any`: If `True`, raise exception if any handler raises any `Exception` (`default: True`)\n- `raise_if_none`: If `True`, raise exception if results are empty / all results are `None` or `Exception` (`default: False`)\n- If every handler errors, only `raise_if_any=False` plus `raise_if_none=False` suppresses the error and returns `None`; every other option combination raises.\n\n```python\n# by default it returns the first successful non-None result value\nresult = await event.event_result()\n\n# Get result from first handler that returns a string\nvalid_result = await event.event_result(include=lambda result, _: isinstance(result, str) and len(result) \u003e 100)\n\n# Get result but don't raise exceptions or error for 0 results, just return None\nresult_or_none = await event.event_result(raise_if_any=False, raise_if_none=False)\n```\n\n##### `event_results_list(include: EventResultFilter=None, raise_if_any: bool=True, raise_if_none: bool=False) -\u003e list[Any]`\n\nUtility method helper to get all raw result values in a list.\n\n**Parameters:**\n\n- `include`: Filter function `(result, event_result) -\u003e bool` to include only specific results (default: only non-None, non-exception results)\n- `raise_if_any`: If `True`, raise exception if any handler raises any `Exception` (`default: True`)\n- `raise_if_none`: If `True`, raise exception if results are empty / all results are `None` or `Exception` (`default: False`)\n- If every handler errors, only `raise_if_any=False` plus `raise_if_none=False` suppresses the error and returns `[]`; every other option combination raises.\n\n```python\n# by default it returns all successful non-None result values\nresults = await event.event_results_list()\n# [result1, result2]\n\n# Only include results that are strings longer than 10 characters\nfiltered_results = await event.event_results_list(include=lambda result, _: isinstance(result, str) and len(result) \u003e 10)\n\n# Get all results without raising on errors\nall_results = await event.event_results_list(raise_if_any=False, raise_if_none=False)\n```\n\n`event_results_list()` is the canonical collection helper for multiple handler return values.\n\n##### `event_bus` (property)\n\nShortcut to get the `EventBus` that is currently processing this event. Can be used to avoid having to pass an `EventBus` instance to your handlers.\n\n```python\nbus = EventBus()\n\nasync def some_handler(event: MyEvent):\n    # Most handler code should do this: linked child work that blocks parent completion.\n    child_event = await event.emit(ChildEvent()).now()\n\n    # Un-awaited event.emit(...) keeps parentage without holding the parent open.\n    background_child = event.emit(ChildEvent())\n\n    # Use bus.emit(...) for detached root/background work.\n    detached_event = bus.emit(ChildEvent())\n```\n\n---\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003e\u003ccode\u003eEventResult\u003c/code\u003e\u003c/strong\u003e\u003c/summary\u003e\n\nThe placeholder object that represents the pending result from a single handler executing an event.  \n`Event.event_results` contains a `dict[PythonIdStr, EventResult]` in the shape of `{handler_id: EventResult()}`.\n\nYou generally won't interact with this class directly—the bus instantiates and updates it for you—but its API is documented here for advanced integrations and custom emit loops.\n\n#### `EventResult` Fields\n\n```python\nclass EventResult(BaseModel):\n    id: str                    # Unique identifier\n    handler_id: str           # Handler function ID\n    handler_name: str         # Handler function name\n    eventbus_id: str          # Bus that executed this handler\n    eventbus_name: str        # Bus name\n\n    status: str               # 'pending', 'started', 'completed', 'error'\n    result: Any               # Handler return value\n    error: BaseException | None  # Captured exception if the handler failed\n\n    started_at: str | None      # Canonical ISO timestamp when handler started\n    completed_at: str | None    # Canonical ISO timestamp when handler completed\n    timeout: float | None            # Handler timeout in seconds\n    event_children: list[BaseEvent] # child events emitted during handler execution\n```\n\n#### `EventResult` Methods\n\n##### `await result`\n\nAwait the `EventResult` object directly to get the raw result value.\n\n```python\nhandler_result = event.event_results['handler_id']\nvalue = await handler_result  # Returns result or raises an exception if handler hits an error\n```\n\n- Handler execution is managed by the bus. User code normally reads `status`, `result`, `error`, and timing fields through `event.event_results`, or uses the higher-level event result helpers.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003e\u003ccode\u003eEventHandler\u003c/code\u003e\u003c/strong\u003e\u003c/summary\u003e\n\nSerializable metadata wrapper around a registered handler callable.\n\nYou usually get an `EventHandler` back from `bus.on(...)`, can pass it to `bus.off(...)`, and may see it in middleware hooks like `on_bus_handlers_change(...)`.\n\n#### `EventHandler` Fields\n\n```python\nclass EventHandler(BaseModel):\n    id: str                          # Stable handler identifier\n    handler_name: str                # Callable name\n    handler_file_path: str | None    # Source file path (if known)\n    handler_timeout: float | None    # Optional per-handler timeout override\n    handler_slow_timeout: float | None  # Optional \"slow handler\" threshold\n    handler_registered_at: str       # Registration timestamp (ISO string, 9 fractional digits)\n    event_pattern: str               # Registered event pattern (type name or '*')\n    eventbus_name: str               # Owning EventBus name\n    eventbus_id: str                 # Owning EventBus ID\n```\n\nThe raw callable is stored on `handler`, but is excluded from JSON serialization (`model_dump(mode='json', exclude={'handler'})`).\n\n#### `EventHandler` Properties and Methods\n\n- `label` (property): Short display label like `my_handler#abcd`.\n- `model_dump(mode='json', exclude={'handler'}) -\u003e dict[str, Any]`: JSON-compatible metadata dict (callable excluded).\n- `from_json_dict(data, handler=None) -\u003e EventHandler`: Rebuilds metadata; optional callable reattachment.\n- `from_callable(...) -\u003e EventHandler`: Build a new handler entry from a callable plus bus/pattern metadata.\n\n---\n\n\u003c/details\u003e\n\n## 🏃 Performance\n\n```bash\nuv run tests/performance_runtime.py --no-json\npnpm --dir abxbus-ts run perf:node\ncargo test --manifest-path abxbus-rust/Cargo.toml --release --test test_eventbus_performance -- --nocapture\n(cd abxbus-go \u0026\u0026 go test ./tests -run TestPerformance -count=1 -timeout=180s -v)\n```\n\n| Runtime | 1 bus x 50k events x 1 handler   | 500 buses x 100 events x 1 handler | 1 bus x 1 event x 50k parallel handlers | 1 bus x 50k events x 50k one-off handlers | Worst case (N buses x N events x N handlers) |\n| ------- | -------------------------------- | ---------------------------------- | --------------------------------------- | ----------------------------------------- | -------------------------------------------- |\n| Python  | `0.366ms/event`, `0.188kb/event` | `0.408ms/event`, `0.153kb/event`   | `0.093ms/handler`, `10.197kb/handler`   | `0.633ms/event`, `0.145kb/event`          | `0.504ms/event`, `4.171kb/event`             |\n| Rust    | `0.067ms/event`                  | `0.070ms/event`                    | `0.062ms/handler`                       | `0.077ms/event`                           | `0.227ms/event`                              |\n| Go      | `0.016ms/event`                  | `0.011ms/event`                    | `0.085ms/handler`                       | `0.011ms/event`                           | `0.041ms/event`                              |\n| TypeScript (Node) | `0.065ms/event`, `4.145kb/event` | `0.078ms/event`, `1.562kb/event`   | `0.065ms/handler`, `11.631kb/handler`   | `0.123ms/event`, `2.182kb/event`          | `0.344ms/event`, `12.619kb/event`            |\n\n\u003cbr/\u003e\n\n---\n\n---\n\n\u003cbr/\u003e\n\n## 👾 Development\n\nSet up the python development environment using `uv`:\n\n```bash\ngit clone https://github.com/ArchiveBox/abxbus \u0026\u0026 cd abxbus\n\n# Create virtual environment with Python 3.12\nuv venv --python 3.12\n\n# Activate virtual environment (varies by OS)\nsource .venv/bin/activate  # On Unix/macOS\n# or\n.venv\\Scripts\\activate  # On Windows\n\n# Install dependencies\nuv sync --dev --all-extras\n```\n\nRecommended once per clone:\n\n```bash\nprek install           # install pre-commit hooks\nprek run --all-files   # run pre-commit hooks on all files manually\n```\n\n```bash\n# Run linter \u0026 type checker\nuv run ruff check --fix\nuv run ruff format\nuv run pyright\n\n# Run all tests\nuv run pytest -vxs --full-trace tests/\n\n# Run specific test file\nuv run pytest tests/test_eventbus.py\n\n# Run Python perf suite\nuv run tests/performance_runtime.py\n\n# Run the entire lint+test+examples+perf suite for both python and ts\n./test.sh\n```\n\n\u003e For AbxBus-TS development see the `abxbus-ts/README.md` `# Development` section.\n\u003e For Rust crate development see `abxbus-rust/README.md`.\n\u003e For AbxBus-Go development run `go test ./...` from `abxbus-go/`; cross-runtime Go parity is covered by `tests/test_cross_runtime_roundtrip.py` and `abxbus-ts/tests/cross_runtime_roundtrip.test.ts`.\n\n## 🔗 Inspiration\n\n- https://www.cosmicpython.com/book/chapter_08_events_and_message_bus.html#message_bus_diagram ⭐️\n- https://developer.mozilla.org/en-US/docs/Web/API/EventTarget ⭐️\n- https://github.com/sindresorhus/emittery ⭐️, https://github.com/EventEmitter2/EventEmitter2, https://github.com/vitaly-t/sub-events\n- https://github.com/pytest-dev/pluggy ⭐️\n- https://github.com/teamhide/fastapi-event ⭐️\n- https://github.com/ethereum/lahja ⭐️\n- https://github.com/enricostara/eventure ⭐️\n- https://github.com/akhundMurad/diator ⭐️\n- https://github.com/n89nanda/pyeventbus\n- https://github.com/iunary/aioemit\n- https://github.com/dboslee/evently\n- https://github.com/faust-streaming/faust\n- https://github.com/ArcletProject/Letoderea\n- https://github.com/seanpar203/event-bus\n- https://github.com/n89nanda/pyeventbus\n- https://github.com/nicolaszein/py-async-bus\n- https://github.com/AngusWG/simple-event-bus\n- https://www.joeltok.com/posts/2021-03-building-an-event-bus-in-python/\n- See more here: https://abxbus.archivebox.io/further-reading/similar-projects\n\n\u003e [!TIP]\n\u003e **Don't like working with event-driven interfaces?**\n\u003e Check out our [`abxbus.events_suck`](https://abxbus.archivebox.io/further-reading/events-suck) wrapper utils that can help wrap events workflows in a simpler imperative API...\n\n---\n\n\u003e [🍃 Main Documentation](https://abxbus.archivebox.io) | [🧠 DeepWiki | Get AI Help](https://deepwiki.com/ArchiveBox/abxbus) | [🐍 PyPI Package](https://pypi.org/project/abxbus) | [📦 NPM Package](https://npmjs.com/package/abxbus) | [\u003c/\u003e Github](https://github.com/ArchiveBox/abxbus)\n\u003e\n\u003e \u003cimg width=\"400\" alt=\"image\" src=\"https://github.com/user-attachments/assets/cedb5a2e-0643-4240-9a3d-5f27cb8b5741\" /\u003e\u003cimg width=\"400\" alt=\"image\" src=\"https://github.com/user-attachments/assets/3ee0ee8c-8322-449f-979b-5c99ba6bd960\" /\u003e\n\n## 🏛️ License\n\nThis project is licensed under the MIT License.\n\nThis repo is a fork that adds many new features and performance enhancements over the [original project named `bubus`](https://github.com/browser-use/bubus), which was built to power the [Browser-Use Agent](https://github.com/browser-use/browser-use/tree/main/browser_use/browser/watchdogs) (but has since gone stale).\n\nTimeline:\n\n- 2025-06 `v1.0.1`: Original library released https://github.com/browser-use/bubus\n- 2025-10 `v1.5.1`: Browser-Use v0.6.0 released, first version powered by `bubus`\n- 2025-11 `v1.7.1`: `bubus` forked to `pirate/bbus` temporarily; `ContextVar` support, `Middlewares`, and `bus.find()` added\n- 2026-01 `v2.3.2`: `bubus-ts` Typescript implementation released, cross-compatible with Python version (now `abxbus-ts`)\n- 2026-03 `v2.4.1`: Fork renamed from `pirate/bbus -\u003e ArchiveBox/abxbus`; added dual `CJS`/`ESM` support, bugfixes and perf improvements\n- 2026-03 `v2.4.9`: Added `update()`, `uninstall()`, and support for `uv`, `gem`, `cargo`, `go get`, `docker`, and `nix`. Used in new [`abx-dl`](https://github.com/ArchiveBox/abx-dl) project and [ArchiveBox](https://github.com/ArchiveBox/ArchiveBox).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farchivebox%2Fabxbus","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Farchivebox%2Fabxbus","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farchivebox%2Fabxbus/lists"}