abxbus 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/README.md +647 -0
  2. package/dist/cjs/async_context.d.ts +12 -0
  3. package/dist/cjs/async_context.js +70 -0
  4. package/dist/cjs/async_context.js.map +7 -0
  5. package/dist/cjs/base_event.d.ts +207 -0
  6. package/dist/cjs/base_event.js +871 -0
  7. package/dist/cjs/base_event.js.map +7 -0
  8. package/dist/cjs/bridge_jsonl.d.ts +26 -0
  9. package/dist/cjs/bridge_jsonl.js +170 -0
  10. package/dist/cjs/bridge_jsonl.js.map +7 -0
  11. package/dist/cjs/bridge_nats.d.ts +20 -0
  12. package/dist/cjs/bridge_nats.js +108 -0
  13. package/dist/cjs/bridge_nats.js.map +7 -0
  14. package/dist/cjs/bridge_postgres.d.ts +31 -0
  15. package/dist/cjs/bridge_postgres.js +251 -0
  16. package/dist/cjs/bridge_postgres.js.map +7 -0
  17. package/dist/cjs/bridge_redis.d.ts +34 -0
  18. package/dist/cjs/bridge_redis.js +175 -0
  19. package/dist/cjs/bridge_redis.js.map +7 -0
  20. package/dist/cjs/bridge_sqlite.d.ts +30 -0
  21. package/dist/cjs/bridge_sqlite.js +255 -0
  22. package/dist/cjs/bridge_sqlite.js.map +7 -0
  23. package/dist/cjs/bridges.d.ts +49 -0
  24. package/dist/cjs/bridges.js +326 -0
  25. package/dist/cjs/bridges.js.map +7 -0
  26. package/dist/cjs/event_bus.d.ts +127 -0
  27. package/dist/cjs/event_bus.js +1058 -0
  28. package/dist/cjs/event_bus.js.map +7 -0
  29. package/dist/cjs/event_handler.d.ts +139 -0
  30. package/dist/cjs/event_handler.js +299 -0
  31. package/dist/cjs/event_handler.js.map +7 -0
  32. package/dist/cjs/event_history.d.ts +45 -0
  33. package/dist/cjs/event_history.js +192 -0
  34. package/dist/cjs/event_history.js.map +7 -0
  35. package/dist/cjs/event_result.d.ts +86 -0
  36. package/dist/cjs/event_result.js +446 -0
  37. package/dist/cjs/event_result.js.map +7 -0
  38. package/dist/cjs/events_suck.d.ts +40 -0
  39. package/dist/cjs/events_suck.js +59 -0
  40. package/dist/cjs/events_suck.js.map +7 -0
  41. package/dist/cjs/helpers.d.ts +1 -0
  42. package/dist/cjs/helpers.js +84 -0
  43. package/dist/cjs/helpers.js.map +7 -0
  44. package/dist/cjs/index.d.ts +17 -0
  45. package/dist/cjs/index.js +54 -0
  46. package/dist/cjs/index.js.map +7 -0
  47. package/dist/cjs/lock_manager.d.ts +70 -0
  48. package/dist/cjs/lock_manager.js +343 -0
  49. package/dist/cjs/lock_manager.js.map +7 -0
  50. package/dist/cjs/logging.d.ts +16 -0
  51. package/dist/cjs/logging.js +216 -0
  52. package/dist/cjs/logging.js.map +7 -0
  53. package/dist/cjs/middlewares.d.ts +13 -0
  54. package/dist/cjs/middlewares.js +17 -0
  55. package/dist/cjs/middlewares.js.map +7 -0
  56. package/dist/cjs/optional_deps.d.ts +3 -0
  57. package/dist/cjs/optional_deps.js +64 -0
  58. package/dist/cjs/optional_deps.js.map +7 -0
  59. package/dist/cjs/package.json +5 -0
  60. package/dist/cjs/retry.d.ts +52 -0
  61. package/dist/cjs/retry.js +257 -0
  62. package/dist/cjs/retry.js.map +7 -0
  63. package/dist/cjs/timing.d.ts +3 -0
  64. package/dist/cjs/timing.js +76 -0
  65. package/dist/cjs/timing.js.map +7 -0
  66. package/dist/cjs/type_inference.test.d.ts +1 -0
  67. package/dist/cjs/types.d.ts +36 -0
  68. package/dist/cjs/types.js +104 -0
  69. package/dist/cjs/types.js.map +7 -0
  70. package/dist/esm/async_context.js +50 -0
  71. package/dist/esm/async_context.js.map +7 -0
  72. package/dist/esm/base_event.js +857 -0
  73. package/dist/esm/base_event.js.map +7 -0
  74. package/dist/esm/bridge_jsonl.js +150 -0
  75. package/dist/esm/bridge_jsonl.js.map +7 -0
  76. package/dist/esm/bridge_nats.js +88 -0
  77. package/dist/esm/bridge_nats.js.map +7 -0
  78. package/dist/esm/bridge_postgres.js +231 -0
  79. package/dist/esm/bridge_postgres.js.map +7 -0
  80. package/dist/esm/bridge_redis.js +155 -0
  81. package/dist/esm/bridge_redis.js.map +7 -0
  82. package/dist/esm/bridge_sqlite.js +235 -0
  83. package/dist/esm/bridge_sqlite.js.map +7 -0
  84. package/dist/esm/bridges.js +306 -0
  85. package/dist/esm/bridges.js.map +7 -0
  86. package/dist/esm/event_bus.js +1046 -0
  87. package/dist/esm/event_bus.js.map +7 -0
  88. package/dist/esm/event_handler.js +279 -0
  89. package/dist/esm/event_handler.js.map +7 -0
  90. package/dist/esm/event_history.js +172 -0
  91. package/dist/esm/event_history.js.map +7 -0
  92. package/dist/esm/event_result.js +426 -0
  93. package/dist/esm/event_result.js.map +7 -0
  94. package/dist/esm/events_suck.js +39 -0
  95. package/dist/esm/events_suck.js.map +7 -0
  96. package/dist/esm/helpers.js +64 -0
  97. package/dist/esm/helpers.js.map +7 -0
  98. package/dist/esm/index.js +47 -0
  99. package/dist/esm/index.js.map +7 -0
  100. package/dist/esm/lock_manager.js +323 -0
  101. package/dist/esm/lock_manager.js.map +7 -0
  102. package/dist/esm/logging.js +196 -0
  103. package/dist/esm/logging.js.map +7 -0
  104. package/dist/esm/middlewares.js +1 -0
  105. package/dist/esm/middlewares.js.map +7 -0
  106. package/dist/esm/optional_deps.js +44 -0
  107. package/dist/esm/optional_deps.js.map +7 -0
  108. package/dist/esm/retry.js +237 -0
  109. package/dist/esm/retry.js.map +7 -0
  110. package/dist/esm/timing.js +56 -0
  111. package/dist/esm/timing.js.map +7 -0
  112. package/dist/esm/types.js +84 -0
  113. package/dist/esm/types.js.map +7 -0
  114. package/dist/types/async_context.d.ts +12 -0
  115. package/dist/types/base_event.d.ts +207 -0
  116. package/dist/types/bridge_jsonl.d.ts +26 -0
  117. package/dist/types/bridge_nats.d.ts +20 -0
  118. package/dist/types/bridge_postgres.d.ts +31 -0
  119. package/dist/types/bridge_redis.d.ts +34 -0
  120. package/dist/types/bridge_sqlite.d.ts +30 -0
  121. package/dist/types/bridges.d.ts +49 -0
  122. package/dist/types/event_bus.d.ts +127 -0
  123. package/dist/types/event_handler.d.ts +139 -0
  124. package/dist/types/event_history.d.ts +45 -0
  125. package/dist/types/event_result.d.ts +86 -0
  126. package/dist/types/events_suck.d.ts +40 -0
  127. package/dist/types/helpers.d.ts +1 -0
  128. package/dist/types/index.d.ts +17 -0
  129. package/dist/types/lock_manager.d.ts +70 -0
  130. package/dist/types/logging.d.ts +16 -0
  131. package/dist/types/middlewares.d.ts +13 -0
  132. package/dist/types/optional_deps.d.ts +3 -0
  133. package/dist/types/retry.d.ts +52 -0
  134. package/dist/types/timing.d.ts +3 -0
  135. package/dist/types/type_inference.test.d.ts +1 -0
  136. package/dist/types/types.d.ts +36 -0
  137. package/package.json +87 -0
package/README.md ADDED
@@ -0,0 +1,647 @@
1
+ # `abxbus`: 📢 Production-ready multi-language event bus
2
+
3
+ <img width="200" alt="image" src="https://github.com/user-attachments/assets/b3525c24-51ba-496c-b327-ccdfe46a7362" align="right" />
4
+
5
+ [![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/abx-bus) [![PyPI - Version](https://img.shields.io/pypi/v/abx-bus)](https://pypi.org/project/abx-bus/) [![GitHub License](https://img.shields.io/github/license/ArchiveBox/abx-bus)](https://github.com/ArchiveBox/abx-bus) ![GitHub last commit](https://img.shields.io/github/last-commit/ArchiveBox/abx-bus)
6
+
7
+ [![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/abx-bus/3-typescript-implementation) [![NPM Version](https://img.shields.io/npm/v/abxbus)](https://www.npmjs.com/package/abxbus)
8
+
9
+ AbxBus is an in-memory event bus library for async Python and TS (node/bun/deno/browser).
10
+
11
+ It's designed for quickly building resilient, predictable, complex event-driven apps.
12
+
13
+ It "just works" with an intuitive, but powerful event JSON format + emit API that's consistent across both languages and scales consistently from one event to millions (~0.2ms/event):
14
+
15
+ ```python
16
+ bus.on(SomeEvent, some_function)
17
+ bus.emit(SomeEvent({some_data: 132}))
18
+ ```
19
+
20
+ It'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:
21
+
22
+ - nice Zod / Pydantic schemas for events that can be exchanged between both languages
23
+ - automatic UUIDv7s and monotonic nanosecond timestamps for ordering events globally
24
+ - built in locking options to force strict global FIFO processing or fully parallel processing
25
+
26
+ ---
27
+
28
+ ♾️ 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:
29
+
30
+ - correct timeout enforcement across multiple levels of events, if a parent times out it correctly aborts all child event processing
31
+ - ability to strongly type hint and enforce the return type of event handlers at compile-time
32
+ - ability to queue events on the bus, or inline await them for immediate execution like a normal function call
33
+ - handles ~5,000 events/sec/core in both languages, with ~2kb/event RAM consumed per event during active processing
34
+
35
+ ## 🔗 Links
36
+
37
+ - Issue Tracker: https://github.com/ArchiveBox/abx-bus/issues
38
+ - Documentation: https://abxbus.archivebox.io
39
+ - DeepWiki: https://deepwiki.com/ArchiveBox/abx-bus
40
+ - PyPI: https://pypi.org/project/abx-bus/
41
+ - NPM: https://www.npmjs.com/package/abxbus
42
+
43
+ <br/>
44
+
45
+ ## 🔢 Quickstart
46
+
47
+ ```bash
48
+ npm install abxbus
49
+ ```
50
+
51
+ ```ts
52
+ import { BaseEvent, EventBus } from 'abxbus'
53
+ import { z } from 'zod'
54
+
55
+ const CreateUserEvent = BaseEvent.extend('CreateUserEvent', {
56
+ email: z.string(),
57
+ event_result_type: z.object({ user_id: z.string() }),
58
+ })
59
+
60
+ const bus = new EventBus('MyAuthEventBus')
61
+
62
+ bus.on(CreateUserEvent, async (event) => {
63
+ const user = await yourCreateUserLogic(event.email)
64
+ return { user_id: user.id }
65
+ })
66
+
67
+ const event = bus.emit(CreateUserEvent({ email: 'someuser@example.com' }))
68
+ await event.done()
69
+ console.log(event.event_result) // { user_id: 'some-user-uuid' }
70
+ ```
71
+
72
+ <br/>
73
+
74
+ ---
75
+
76
+ <br/>
77
+
78
+ ## ✨ Features
79
+
80
+ The features offered in TS are broadly similar to the ones offered in the python library.
81
+
82
+ - Typed events with Zod schemas (cross-compatible with Pydantic events from python library)
83
+ - FIFO event queueing with configurable concurrency
84
+ - Nested event support with automatic parent/child tracking
85
+ - Cross-bus forwarding with loop prevention
86
+ - Handler result tracking + validation + timeout enforcement
87
+ - History retention controls (`max_history_size`) for memory bounds
88
+ - Optional `@retry` decorator for easy management of per-handler retries, timeouts, and semaphore-limited execution
89
+
90
+ See the [Python README](../README.md) for more details.
91
+
92
+ <br/>
93
+
94
+ ---
95
+
96
+ <br/>
97
+
98
+ ## 📚 API Documentation
99
+
100
+ <details>
101
+ <summary><strong><code>EventBus</code></strong></summary>
102
+
103
+ The main bus class that registers handlers, schedules events, and tracks results.
104
+
105
+ Constructor:
106
+
107
+ ```ts
108
+ new EventBus(name?: string, options?: {
109
+ id?: string
110
+ max_history_size?: number | null
111
+ event_concurrency?: 'global-serial' | 'bus-serial' | 'parallel' | null
112
+ event_timeout?: number | null
113
+ event_slow_timeout?: number | null
114
+ event_handler_concurrency?: 'serial' | 'parallel' | null
115
+ event_handler_completion?: 'all' | 'first'
116
+ event_handler_slow_timeout?: number | null
117
+ event_handler_detect_file_paths?: boolean
118
+ })
119
+ ```
120
+
121
+ #### Constructor options
122
+
123
+ | Option | Type | Default | Purpose |
124
+ | --------------------------------- | ------------------------------------------------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
125
+ | `id` | `string` | `uuidv7()` | Override bus UUID (mostly for serialization/tests). |
126
+ | `max_history_size` | `number \| null` | `100` | Max events kept in `event_history`; `null` = unbounded; `0` = keep only in-flight events and drop completed events immediately. |
127
+ | `max_history_drop` | `boolean` | `false` | If `true`, when history is full drop oldest history entries (including uncompleted if needed). If `false`, reject new emits when history reaches `max_history_size`. |
128
+ | `event_concurrency` | `'global-serial' \| 'bus-serial' \| 'parallel' \| null` | `'bus-serial'` | Event-level scheduling policy. |
129
+ | `event_handler_concurrency` | `'serial' \| 'parallel' \| null` | `'serial'` | Per-event handler scheduling policy. |
130
+ | `event_handler_completion` | `'all' \| 'first'` | `'all'` | Event completion mode if event does not override it. |
131
+ | `event_timeout` | `number \| null` | `60` | Default per-handler timeout budget in seconds (unless overridden). |
132
+ | `event_handler_slow_timeout` | `number \| null` | `30` | Slow handler warning threshold (seconds). |
133
+ | `event_slow_timeout` | `number \| null` | `300` | Slow event warning threshold (seconds). |
134
+ | `event_handler_detect_file_paths` | `boolean` | `true` | Capture source file:line for handlers (slower, better logs). |
135
+
136
+ #### Runtime state properties
137
+
138
+ - `id: string`
139
+ - `name: string`
140
+ - `label: string` (`${name}#${id.slice(-4)}`)
141
+ - `handlers: Map<string, EventHandler>`
142
+ - `handlers_by_key: Map<string, string[]>`
143
+ - `event_history: Map<string, BaseEvent>`
144
+ - `pending_event_queue: BaseEvent[]`
145
+ - `in_flight_event_ids: Set<string>`
146
+ - `locks: LockManager`
147
+
148
+ #### `on()`
149
+
150
+ ```ts
151
+ on<T extends BaseEvent>(
152
+ event_pattern: string | '*' | EventClass<T>,
153
+ handler: EventHandlerCallable<T>,
154
+ options?: Partial<EventHandler>
155
+ ): EventHandler
156
+ ```
157
+
158
+ Use during startup/composition to register handlers.
159
+
160
+ Advanced `options` fields, these can be used to override defaults per-handler if needed:
161
+
162
+ - `handler_timeout?: number | null` hard delay before handler execution is aborted with a `HandlerTimeoutError`
163
+ - `handler_slow_timeout?: number | null` delay before emitting a slow handler warning log line
164
+ - `handler_name?: string` optional name to use instead of `anonymous` if handler is an unnamed arrow function
165
+ - `handler_file_path?: string | null` optional path/to/source/file.js:lineno where the handler is defined, used for logging only
166
+ - `id?: string` unique UUID for the handler (normally a hash of bus_id + event_pattern + handler_name + handler_registered_at)
167
+
168
+ Notes:
169
+
170
+ - Prefer class/factory keys (`bus.on(MyEvent, handler)`) for typed payload/result inference.
171
+ - String and `'*'` matching are supported (`bus.on('MyEvent', ...)`, `bus.on('*', ...)`).
172
+ - Returns an `EventHandler` object you can later pass to `off()` to de-register the handler if needed.
173
+
174
+ #### `off()`
175
+
176
+ ```ts
177
+ off<T extends BaseEvent>(
178
+ event_pattern: EventPattern<T> | '*',
179
+ handler?: EventHandlerCallable<T> | string | EventHandler
180
+ ): void
181
+ ```
182
+
183
+ Use when tearing down subscriptions (tests, plugin unload, hot-reload).
184
+
185
+ - Omit `handler` to remove all handlers for `event_pattern`.
186
+ - Pass handler function reference to remove one by function identity.
187
+ - Pass handler id (`string`) or `EventHandler` object to remove by id.
188
+ - use `bus.off('*')` to remove _all_ registered handlers from the bus
189
+
190
+ #### `emit()`
191
+
192
+ ```ts
193
+ emit<T extends BaseEvent>(event: T): T
194
+ ```
195
+
196
+ Behavior notes:
197
+
198
+ - Per-event config fields stay on the event as provided; when unset (`null`/`undefined`), each bus resolves its own defaults at processing time.
199
+ - If same event ends up forwarded through multiple buses, it is loop-protected using `event_path`.
200
+ - Emit is synchronous and returns immediately with the same event object (`event.event_status` is initially `'pending'`).
201
+
202
+ Normal lifecycle:
203
+
204
+ 1. Create event instance (`const event = MyEvent({...})`).
205
+ 2. Emit (`const queued = bus.emit(event)`).
206
+ 3. Await with `await queued.done()` (immediate/queue-jump semantics) or `await queued.eventCompleted()` (bus queue order).
207
+ 4. Inspect `queued.event_results`, `queued.event_result`, `queued.event_errors`, etc. if you need to access handler return values
208
+
209
+ #### `find()`
210
+
211
+ ```ts
212
+ find<T extends BaseEvent>(event_pattern: EventPattern<T> | '*', options?: FindOptions): Promise<T | null>
213
+ find<T extends BaseEvent>(
214
+ event_pattern: EventPattern<T> | '*',
215
+ where: (event: T) => boolean,
216
+ options?: FindOptions
217
+ ): Promise<T | null>
218
+ ```
219
+
220
+ Where:
221
+
222
+ ```ts
223
+ type FindOptions = {
224
+ past?: boolean | number // true to look through all past events, or number in seconds to filter time range
225
+ future?: boolean | number // true to wait for event to appear indefinitely, or number in seconds to wait for event to appear
226
+ child_of?: BaseEvent | null // filter to only match events that are a child_of: some_parent_event
227
+ } & {
228
+ // event_status: 'pending' | 'started' | 'completed'
229
+ // event_id: 'some-exact-event-uuid-here',
230
+ // event_started_at: string | null (exact iso datetime string or null)
231
+ // ... any event field can be passed to filter events using simple equality checks
232
+ [key: string]: unknown
233
+ }
234
+ ```
235
+
236
+ `bus.find()` returns the first matching event (in emit timestamp order).
237
+ To find multiple matching events, iterate through `bus.event_history.filter((event) => ...some condition...)` manually.
238
+
239
+ `where` behavior:
240
+ Any filter predicate function in the form of `(event) => true | false`, returning true to consider the event a match.
241
+
242
+ ```ts
243
+ const matching_event = bus.find(SomeEvent, (event) => event.some_field == 123)
244
+ // or to match all event types:
245
+ const matching_event = bus.find('*', (event) => event.some_field == 123)
246
+ ```
247
+
248
+ `past` behavior:
249
+
250
+ - `true`: search all history.
251
+ - `false`: skip searching past event history.
252
+ - `number`: search events emitted within last `N` seconds.
253
+
254
+ `future` behavior:
255
+
256
+ - `true`: wait forever for future match.
257
+ - `false`: do not wait.
258
+ - `number`: wait up to `N` seconds.
259
+
260
+ Lifecycle use:
261
+
262
+ - Use for idempotency / de-dupe before emit (`past: ...`).
263
+ - Use for synchronization/waiting (`future: ...`).
264
+ - Combine both to "check recent then wait".
265
+ - Add `child_of` to constrain by parent/ancestor event chain.
266
+ - Add any event field (e.g. `event_status`, `event_id`, `event_timeout`, `user_id`) to filter by strict equality.
267
+ - Use wildcard matching with predicates when you want to search all event types: `bus.find('*', (event) => ...)`.
268
+
269
+ Debouncing expensive events with `find()`:
270
+
271
+ ```ts
272
+ const some_expensive_event = (await bus.find(ExpensiveEvent, { past: 15, future: 5 })) ?? bus.emit(ExpensiveEvent({}))
273
+ await some_expensive_event.done()
274
+ ```
275
+
276
+ Important semantics:
277
+
278
+ - Past lookup matches any emitted events, not just completed events.
279
+ - Past/future matches resolve as soon as event is emitted. If you need the completed event, await `event.done()` or pass `{event_status: 'completed'}` to filter only for completed events.
280
+ - If both `past` and `future` are omitted, defaults are `past: true, future: false`.
281
+ - If both `past` and `future` are `false`, it returns `null` immediately.
282
+ - Detailed behavior matrix is covered in `abxbus-ts/tests/eventbus_find.test.ts`.
283
+
284
+ #### `waitUntilIdle(timeout?)`
285
+
286
+ `await bus.waitUntilIdle()` is the normal "drain bus work" call to wait until bus is done processing everything queued.
287
+ Pass an optional timeout in seconds (`await bus.waitUntilIdle(5)`) for a bounded wait.
288
+
289
+ ```ts
290
+ bus.emit(OneEvent(...))
291
+ bus.emit(TwoEvent(...))
292
+ bus.emit(ThreeEvent(...))
293
+ await bus.waitUntilIdle() // this resolves once all three events have finished processing
294
+ await bus.waitUntilIdle(5) // wait up to 5 seconds, then continue even if work is still in-flight
295
+ ```
296
+
297
+ #### Parent/child/event lookup helpers
298
+
299
+ ```ts
300
+ eventIsChildOf(child_event: BaseEvent, paret_event: BaseEvent): boolean
301
+ eventIsParentOf(parent_event: BaseEvent, child_event: BaseEvent): boolean
302
+ findEventById(event_id: string): BaseEvent | null
303
+ ```
304
+
305
+ #### `toString()` / `toJSON()` / `fromJSON()`
306
+
307
+ ```ts
308
+ toString(): string
309
+ toJSON(): EventBusJSON
310
+ EventBus.fromJSON(data: unknown): EventBus
311
+ ```
312
+
313
+ - `toString()` returns `BusName#abcd` style labels used in logs/errors.
314
+ - `toJSON()` exports full bus state snapshot (config, handlers, indexes, event_history, pending queue, in-flight ids, find-waiter snapshots).
315
+ - `fromJSON()` restores a new bus instance from that payload (handler functions are restored as no-op stubs).
316
+
317
+ #### `logTree()`
318
+
319
+ ```ts
320
+ logTree(): string
321
+ ```
322
+
323
+ - `logTree()` returns a full event log hierarchy tree diagram for debugging.
324
+
325
+ #### `destroy()`
326
+
327
+ ```ts
328
+ destroy(): void
329
+ ```
330
+
331
+ - `destroy()` clears handlers/history/locks and removes this bus from global weak registry.
332
+ - `destroy()`/GC behavior is exercised in `abxbus-ts/tests/eventbus.test.ts` and `abxbus-ts/tests/eventbus_performance.test.ts`.
333
+
334
+ </details>
335
+
336
+ <details>
337
+ <summary><strong><code>BaseEvent</code></strong></summary>
338
+
339
+ Base class + factory builder for typed event models.
340
+
341
+ Define your own strongly typed events with `BaseEvent.extend('EventName', {...zod fields...})`:
342
+
343
+ ```ts
344
+ const MyEvent = BaseEvent.extend('MyEvent', {
345
+ some_key: z.string(),
346
+ some_other_key: z.number(),
347
+ // ...
348
+ // any other payload fields you want to include can go here
349
+
350
+ // fields that start with event_* are reserved for metadata used by the library
351
+ event_result_type: z.string().optional(),
352
+ event_timeout: 60,
353
+ // ...
354
+ })
355
+
356
+ const pending_event = MyEvent({ some_key: 'abc', some_other_key: 234 })
357
+ const queued_event = bus.emit(pending_event)
358
+ const completed_event = await queued_event.done()
359
+ ```
360
+
361
+ API behavior and lifecycle examples:
362
+
363
+ - `abxbus-ts/examples/simple.ts`
364
+ - `abxbus-ts/examples/immediate_event_processing.ts`
365
+ - `abxbus-ts/examples/forwarding_between_busses.ts`
366
+ - `abxbus-ts/tests/eventbus.test.ts`
367
+ - `abxbus-ts/tests/eventbus_find.test.ts`
368
+ - `abxbus-ts/tests/event_handler_first.test.ts`
369
+ - `abxbus-ts/tests/base_event_event_bus_proxy.test.ts`
370
+ - `abxbus-ts/tests/eventbus_timeout.test.ts`
371
+ - `abxbus-ts/tests/event_result.test.ts`
372
+
373
+ #### Event configuration fields
374
+
375
+ Special configuration fields you can set on each event to control processing:
376
+
377
+ - `event_result_type?: z.ZodTypeAny | String | Number | Boolean | Array | Object`
378
+ - `event_version?: string` (default: `'0.0.1'`; useful for your own schema/data migrations)
379
+ - `event_timeout?: number | null`
380
+ - `event_handler_timeout?: number | null`
381
+ - `event_handler_slow_timeout?: number | null`
382
+ - `event_concurrency?: 'global-serial' | 'bus-serial' | 'parallel' | null`
383
+ - `event_handler_concurrency?: 'serial' | 'parallel' | null`
384
+ - `event_handler_completion?: 'all' | 'first'`
385
+
386
+ #### Runtime state fields
387
+
388
+ - `event_id`, `event_type`, `event_version`
389
+ - `event_path: string[]` (bus labels like `BusName#ab12`)
390
+ - `event_parent_id: string | null`
391
+ - `event_emitted_by_handler_id: string | null`
392
+ - `event_status: 'pending' | 'started' | 'completed'`
393
+ - `event_results: Map<string, EventResult>`
394
+ - `event_pending_bus_count: number`
395
+ - `event_created_at: string`
396
+ - `event_started_at: string | null`
397
+ - `event_completed_at: string | null`
398
+
399
+ #### Read-only attributes
400
+
401
+ - `event_parent` -> `BaseEvent | undefined`
402
+ - `event_children` -> `BaseEvent[]`
403
+ - `event_descendants` -> `BaseEvent[]`
404
+ - `event_errors` -> `Error[]`
405
+ - `event_result` -> `EventResultType<this> | undefined`
406
+
407
+ #### `done()`
408
+
409
+ ```ts
410
+ done(options?: { raise_if_any?: boolean }): Promise<this>
411
+ ```
412
+
413
+ - If called from inside a running handler, it queue-jumps child processing immediately.
414
+ - If called outside handler context, it waits for normal completion (or processes immediately if already next).
415
+ - Re-raises the first handler exception encountered after processing completes.
416
+ - Pass `{ raise_if_any: false }` to only wait for completion without re-raising handler exceptions.
417
+ - Rejects if event is not attached to a bus (`event has no bus attached`).
418
+ - Queue-jump behavior is demonstrated in `abxbus-ts/examples/immediate_event_processing.ts` and `abxbus-ts/tests/base_event_event_bus_proxy.test.ts`.
419
+
420
+ #### `eventCompleted()`
421
+
422
+ ```ts
423
+ eventCompleted(): Promise<this>
424
+ ```
425
+
426
+ - Waits for completion in normal runloop order.
427
+ - Use inside handlers when you explicitly do not want queue-jump behavior.
428
+
429
+ #### `first()`
430
+
431
+ ```ts
432
+ first(): Promise<EventResultType<this> | undefined>
433
+ ```
434
+
435
+ - Forces `event_handler_completion = 'first'` for this run.
436
+ - Returns temporally first non-`undefined` successful handler result.
437
+ - Cancels pending/running losing handlers on the same bus.
438
+ - Re-raises the first non-cancellation handler exception encountered after processing completes.
439
+ - Returns `undefined` only when no handler produces a successful non-`undefined` value and no handler raises.
440
+ - Cancellation and winner-selection behavior is covered in `abxbus-ts/tests/event_handler_first.test.ts`.
441
+
442
+ #### `eventResultsList(include?, options?)`
443
+
444
+ ```ts
445
+ eventResultsList(
446
+ include?: (result: EventResultType<this> | undefined, event_result: EventResult<this>) => boolean,
447
+ options?: {
448
+ timeout?: number | null
449
+ include?: (result: EventResultType<this> | undefined, event_result: EventResult<this>) => boolean
450
+ raise_if_any?: boolean
451
+ raise_if_none?: boolean
452
+ }
453
+ ): Promise<Array<EventResultType<this> | undefined>>
454
+ ```
455
+
456
+ - Returns handler result values in `event_results` order.
457
+ - Default filter includes completed non-`null`/non-`undefined` non-error, non-forwarded (`BaseEvent`) values.
458
+ - `raise_if_any` defaults to `true` and throws when any handler result has an error.
459
+ - `raise_if_none` defaults to `true` and throws when no results match `include`.
460
+ - `timeout` is in seconds and bounds how long to wait for completion.
461
+ - Examples:
462
+ - `await event.eventResultsList({ raise_if_any: false, raise_if_none: false })`
463
+ - `await event.eventResultsList((result) => typeof result === 'object', { raise_if_any: false })`
464
+
465
+ #### `eventResultUpdate(handler, options?)`
466
+
467
+ ```ts
468
+ eventResultUpdate(
469
+ handler: EventHandler | EventHandlerCallable<this>,
470
+ options?: {
471
+ eventbus?: EventBus
472
+ status?: 'pending' | 'started' | 'completed' | 'error'
473
+ result?: EventResultType<this> | BaseEvent | undefined
474
+ error?: unknown
475
+ }
476
+ ): EventResult<this>
477
+ ```
478
+
479
+ - Creates (if missing) or updates one `event_results` entry for the given handler id.
480
+ - Useful for deterministic seeding/rehydration paths before resuming normal dispatch.
481
+ - Example:
482
+ - `const seeded = event.eventResultUpdate(handler_entry, { eventbus: bus, status: 'pending' })`
483
+ - `seeded.update({ status: 'completed', result: 'seeded' })`
484
+
485
+ #### `reset()`
486
+
487
+ ```ts
488
+ reset(): this
489
+ ```
490
+
491
+ - Returns a fresh event copy with runtime state reset to pending so it can be emitted again safely.
492
+ - Original event object is unchanged.
493
+ - Generates a new UUIDv7 `event_id` for the returned copy.
494
+ - Clears runtime completion state (`event_results`, status/timestamps, captured async context, done signal, local bus binding).
495
+
496
+ #### `toString()` / `toJSON()` / `fromJSON()`
497
+
498
+ ```ts
499
+ toString(): string
500
+ toJSON(): BaseEventData
501
+ BaseEvent.fromJSON(data: unknown): BaseEvent
502
+ EventFactory.fromJSON?.(data: unknown): TypedEvent
503
+ ```
504
+
505
+ - JSON format is cross-language compatible with Python implementation.
506
+ - `event_result_type` is serialized as JSON Schema when possible and rehydrated on `fromJSON`.
507
+ - In TypeScript-only usage, `event_result_type` can be any Zod schema shape or base type like `number | string | boolean | etc.`. For cross-language roundtrips, object-like schemas (including Python `TypedDict`/`dataclass`-style shapes) are reconstructed on Python as Pydantic models, JSON object keys are always strings, and some fine-grained string-shape constraints may be normalized between Zod and Pydantic.
508
+ - Round-trip coverage is in `abxbus-ts/tests/event_result_typed_results.test.ts` and `abxbus-ts/tests/eventbus.test.ts`.
509
+
510
+ </details>
511
+
512
+ <details>
513
+ <summary><strong><code>EventResult</code></strong></summary>
514
+
515
+ Each handler execution creates one `EventResult` stored in `event.event_results`.
516
+
517
+ #### Main fields
518
+
519
+ - `id: string` (uuidv7 string)
520
+ - `status: 'pending' | 'started' | 'completed' | 'error'`
521
+ - `event: BaseEvent`
522
+ - `handler: EventHandler`
523
+ - `result: EventResultType<this> | undefined`
524
+ - `error: unknown | undefined`
525
+ - `started_at: string | null` (ISO datetime string)
526
+ - `completed_at: string | null` (ISO datetime string)
527
+ - `event_children: BaseEvent[]`
528
+
529
+ #### Read-only getters
530
+
531
+ - `event_id` -> `string` uuiv7 of the event the result is for
532
+ - `bus` -> `EventBus` instance it's associated with
533
+ - `handler_id` -> `string` uuidv5 of the `EventHandler`
534
+ - `handler_name` -> `string | 'anonymous'` function name of the handler method
535
+ - `handler_file_path` -> `string | null` path/to/file.js:lineno where the handler method is defined
536
+ - `eventbus_name` -> `string` name, same as `this.bus.name`
537
+ - `eventbus_id` -> `string` uuidv7, same as `this.bus.id`
538
+ - `eventbus_label` -> `string` label, same as `this.bus.label`
539
+ - `value` -> `EventResultType<this> | undefined` alias of `this.result`
540
+ - `raw_value` -> `any` raw result value before schema validation, available when handler return value validation fails
541
+ - `handler_timeout` -> `number` seconds before handler execution is aborted (precedence: handler config -> event config -> bus level defaults)
542
+ - `handler_slow_timeout` -> `number` seconds before logging a slow execution warning (same prececence as `handler_timeout`)
543
+
544
+ #### `toString()` / `toJSON()` / `fromJSON()`
545
+
546
+ ```ts
547
+ toString(): string
548
+ toJSON(): EventResultJSON
549
+ EventResult.fromJSON(event, data): EventResult
550
+ ```
551
+
552
+ </details>
553
+
554
+ <details>
555
+ <summary><strong><code>EventHandler</code></strong></summary>
556
+
557
+ Represents one registered handler entry on a bus. You usually get these from `bus.on(...)`, then pass them to `bus.off(...)` to remove.
558
+
559
+ #### Main fields
560
+
561
+ - `id` unique handler UUIDv5 (deterministic hash from bus/event/handler metadata unless overridden)
562
+ - `handler` function reference that executes for matching events
563
+ - `handler_name` function name (or `'anonymous'`)
564
+ - `handler_file_path` detected source path (`~/path/file.ts:line`) or `null`
565
+ - `handler_timeout` optional timeout override in seconds (`null` disables timeout limit)
566
+ - `handler_slow_timeout` optional slow-warning threshold in seconds (`null` disables slow warning)
567
+ - `handler_registered_at` ISO timestamp
568
+ - `event_pattern` subscribed key (`'SomeEvent'` or `'*'`)
569
+ - `eventbus_name` bus name where this handler was registered
570
+ - `eventbus_id` bus UUID where this handler was registered
571
+
572
+ #### `toString()` / `toJSON()` / `fromJSON()`
573
+
574
+ ```ts
575
+ toString(): string
576
+ toJSON(): EventHandlerJSON
577
+ EventHandler.fromJSON(data: unknown, handler?: EventHandlerCallable): EventHandler
578
+ ```
579
+
580
+ - `toString()` returns `handlerName() (path:line)` when path/name are available, otherwise `function#abcd()`.
581
+ - `toJSON()` emits only serializable handler metadata (never function bodies).
582
+ - `fromJSON()` reconstructs the handler entry and accepts an optional real function to re-bind execution behavior.
583
+
584
+ <br/>
585
+
586
+ ---
587
+
588
+ <br/>
589
+
590
+ </details>
591
+
592
+ ## 🏃 Runtimes
593
+
594
+ `abxbus-ts` supports all major JS runtimes.
595
+
596
+ - Node.js (default development and test runtime)
597
+ - Browsers (ESM)
598
+ - Bun
599
+ - Deno
600
+
601
+ ### Browser support notes
602
+
603
+ - The package output is ESM (`./dist/esm`) which is supported by all browsers [released after 2018](https://caniuse.com/?search=ESM)
604
+ - `AsyncLocalStorage` is preserved at emit and used during handling when available (Node/Bun), otel/tracing context will work normally in those environments
605
+
606
+ ### Performance comparison (local run, per-event)
607
+
608
+ Measured locally on an `Apple M4 Pro` with:
609
+
610
+ - `pnpm run perf:node` (`node v22.21.1`)
611
+ - `pnpm run perf:bun` (`bun v1.3.9`)
612
+ - `pnpm run perf:deno` (`deno v2.6.8`)
613
+ - `pnpm run perf:browser` (`chrome v145.0.7632.6`)
614
+
615
+ | 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) |
616
+ | ------------------ | ------------------------------ | ---------------------------------- | --------------------------------------- | ----------------------------------------- | -------------------------------------------- |
617
+ | Node | `0.015ms/event`, `0.6kb/event` | `0.058ms/event`, `0.1kb/event` | `0.021ms/handler`, `3.8kb/handler` | `0.028ms/event`, `0.6kb/event` | `0.442ms/event`, `0.9kb/event` |
618
+ | Bun | `0.011ms/event`, `2.5kb/event` | `0.054ms/event`, `1.0kb/event` | `0.006ms/handler`, `4.5kb/handler` | `0.019ms/event`, `2.8kb/event` | `0.441ms/event`, `3.1kb/event` |
619
+ | Deno | `0.018ms/event`, `1.2kb/event` | `0.063ms/event`, `0.4kb/event` | `0.024ms/handler`, `3.1kb/handler` | `0.064ms/event`, `2.6kb/event` | `0.461ms/event`, `7.9kb/event` |
620
+ | Browser (Chromium) | `0.030ms/event` | `0.197ms/event` | `0.022ms/handler` | `0.022ms/event` | `1.566ms/event` |
621
+
622
+ Notes:
623
+
624
+ - `kb/event` is peak RSS delta per event during active processing (most representative of OS-visible RAM in Activity Monitor / Task Manager, with `EventBus.max_history_size=1`)
625
+ - In `1 bus x 1 event x 50k parallel handlers` stats are shown per-handler for clarity, `0.02ms/handler * 50k handlers ~= 1000ms` for the entire event
626
+ - Browser runtime does not expose memory usage directly, in practice memory performance in-browser is comparable to Node (they both use V8)
627
+
628
+ <br/>
629
+
630
+ ---
631
+
632
+ <br/>
633
+
634
+ ## 👾 Development
635
+
636
+ ```bash
637
+ git clone https://github.com/ArchiveBox/abx-bus abxbus && cd abxbus
638
+
639
+ cd ./abxbus-ts
640
+ pnpm install
641
+
642
+ prek install # install pre-commit hooks
643
+ prek run --all-files # run pre-commit hooks on all files manually
644
+
645
+ pnpm lint
646
+ pnpm test
647
+ ```
@@ -0,0 +1,12 @@
1
+ type AsyncLocalStorageLike = {
2
+ getStore(): unknown;
3
+ run<T>(store: unknown, callback: () => T): T;
4
+ enterWith?(store: unknown): void;
5
+ };
6
+ export type { AsyncLocalStorageLike };
7
+ /** Create a new AsyncLocalStorage instance, or null if unavailable (e.g. in browsers). */
8
+ export declare const createAsyncLocalStorage: () => AsyncLocalStorageLike | null;
9
+ export declare let async_local_storage: AsyncLocalStorageLike | null;
10
+ export declare const captureAsyncContext: () => unknown | null;
11
+ export declare const _runWithAsyncContext: <T>(context: unknown | null, fn: () => T) => T;
12
+ export declare const hasAsyncLocalStorage: () => boolean;