abxbus 2.4.1 → 2.4.15

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.
package/README.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  <img width="200" alt="image" src="https://github.com/user-attachments/assets/b3525c24-51ba-496c-b327-ccdfe46a7362" align="right" />
4
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)
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/abxbus) [![PyPI - Version](https://img.shields.io/pypi/v/abxbus)](https://pypi.org/project/abxbus/) [![GitHub License](https://img.shields.io/github/license/ArchiveBox/abxbus)](https://github.com/ArchiveBox/abxbus) ![GitHub last commit](https://img.shields.io/github/last-commit/ArchiveBox/abxbus)
6
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)
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/abxbus/3-typescript-implementation) [![NPM Version](https://img.shields.io/npm/v/abxbus)](https://www.npmjs.com/package/abxbus)
8
8
 
9
9
  AbxBus is an in-memory event bus library for async Python and TS (node/bun/deno/browser).
10
10
 
@@ -27,17 +27,17 @@ It's async native, has proper automatic nested event tracking, and powerful conc
27
27
 
28
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
29
 
30
- - correct timeout enforcement across multiple levels of events, if a parent times out it correctly aborts all child event processing
30
+ - correct timeout enforcement across multiple levels of events, including cancellation of awaited/blocking child work when a parent times out
31
31
  - ability to strongly type hint and enforce the return type of event handlers at compile-time
32
32
  - ability to queue events on the bus, or inline await them for immediate execution like a normal function call
33
33
  - handles ~5,000 events/sec/core in both languages, with ~2kb/event RAM consumed per event during active processing
34
34
 
35
35
  ## 🔗 Links
36
36
 
37
- - Issue Tracker: https://github.com/ArchiveBox/abx-bus/issues
37
+ - Issue Tracker: https://github.com/ArchiveBox/abxbus/issues
38
38
  - Documentation: https://abxbus.archivebox.io
39
- - DeepWiki: https://deepwiki.com/ArchiveBox/abx-bus
40
- - PyPI: https://pypi.org/project/abx-bus/
39
+ - DeepWiki: https://deepwiki.com/ArchiveBox/abxbus
40
+ - PyPI: https://pypi.org/project/abxbus/
41
41
  - NPM: https://www.npmjs.com/package/abxbus
42
42
 
43
43
  <br/>
@@ -294,6 +294,17 @@ await bus.waitUntilIdle() // this resolves once all three events have finished
294
294
  await bus.waitUntilIdle(5) // wait up to 5 seconds, then continue even if work is still in-flight
295
295
  ```
296
296
 
297
+ #### Emit styles from handlers
298
+
299
+ Most handler code should use `await event.emit(ChildEvent({})).done()`. That creates a linked child and marks it as blocking parent completion.
300
+
301
+ | Style | `event_parent_id` | `event_blocks_parent_completion` | Blocks current handler? | Effect |
302
+ | --- | --- | --- | --- | --- |
303
+ | `await event.emit(ChildEvent({})).done()` | Parent event id | `true` | Yes | Linked child work; parent completion waits too. |
304
+ | `event.emit(ChildEvent({}))` without awaiting | Parent event id | `false` | No | Linked background child; visible in ancestry but parent completion does not wait. |
305
+ | `await bus.emit(TopLevelEvent({})).done()` | `null` | `false` | Yes | Detached top-level event; the handler waits naturally because it is awaited. |
306
+ | `bus.emit(TopLevelEvent({}))` without awaiting | `null` | `false` | No | True detached background event with no retained parent relationship. |
307
+
297
308
  #### Parent/child/event lookup helpers
298
309
 
299
310
  ```ts
@@ -389,6 +400,7 @@ Special configuration fields you can set on each event to control processing:
389
400
  - `event_path: string[]` (bus labels like `BusName#ab12`)
390
401
  - `event_parent_id: string | null`
391
402
  - `event_emitted_by_handler_id: string | null`
403
+ - `event_blocks_parent_completion: boolean`
392
404
  - `event_status: 'pending' | 'started' | 'completed'`
393
405
  - `event_results: Map<string, EventResult>`
394
406
  - `event_pending_bus_count: number`
@@ -634,7 +646,7 @@ Notes:
634
646
  ## 👾 Development
635
647
 
636
648
  ```bash
637
- git clone https://github.com/ArchiveBox/abx-bus abxbus && cd abxbus
649
+ git clone https://github.com/ArchiveBox/abxbus abxbus && cd abxbus
638
650
 
639
651
  cd ./abxbus-ts
640
652
  pnpm install
@@ -14,6 +14,7 @@ export declare const BaseEventSchema: z.ZodObject<{
14
14
  event_slow_timeout: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
15
15
  event_handler_timeout: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
16
16
  event_handler_slow_timeout: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
17
+ event_blocks_parent_completion: z.ZodOptional<z.ZodBoolean>;
17
18
  event_parent_id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
18
19
  event_path: z.ZodOptional<z.ZodArray<z.ZodString>>;
19
20
  event_result_type: z.ZodOptional<z.ZodUnknown>;
@@ -43,7 +44,7 @@ export declare const BaseEventSchema: z.ZodObject<{
43
44
  }, z.core.$loose>;
44
45
  export type BaseEventData = z.infer<typeof BaseEventSchema>;
45
46
  export type BaseEventJSON = BaseEventData & Record<string, unknown>;
46
- type BaseEventFields = Pick<BaseEventData, 'event_id' | 'event_created_at' | 'event_type' | 'event_version' | 'event_timeout' | 'event_slow_timeout' | 'event_handler_timeout' | 'event_handler_slow_timeout' | 'event_parent_id' | 'event_path' | 'event_result_type' | 'event_emitted_by_handler_id' | 'event_pending_bus_count' | 'event_status' | 'event_started_at' | 'event_completed_at' | 'event_results' | 'event_concurrency' | 'event_handler_concurrency' | 'event_handler_completion'>;
47
+ type BaseEventFields = Pick<BaseEventData, 'event_id' | 'event_created_at' | 'event_type' | 'event_version' | 'event_timeout' | 'event_slow_timeout' | 'event_handler_timeout' | 'event_handler_slow_timeout' | 'event_blocks_parent_completion' | 'event_parent_id' | 'event_path' | 'event_result_type' | 'event_emitted_by_handler_id' | 'event_pending_bus_count' | 'event_status' | 'event_started_at' | 'event_completed_at' | 'event_results' | 'event_concurrency' | 'event_handler_concurrency' | 'event_handler_completion'>;
47
48
  export type BaseEventInit<TFields extends Record<string, unknown>> = TFields & Partial<BaseEventFields>;
48
49
  type BaseEventSchemaShape = typeof BaseEventSchema.shape;
49
50
  export type EventSchema<TShape extends z.ZodRawShape> = z.ZodObject<BaseEventSchemaShape & TShape>;
@@ -95,6 +96,7 @@ export declare class BaseEvent {
95
96
  event_slow_timeout?: number | null;
96
97
  event_handler_timeout?: number | null;
97
98
  event_handler_slow_timeout?: number | null;
99
+ event_blocks_parent_completion: boolean;
98
100
  event_parent_id: string | null;
99
101
  event_path: string[];
100
102
  event_result_type?: z.ZodTypeAny;
@@ -118,6 +120,7 @@ export declare class BaseEvent {
118
120
  event_slow_timeout: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
119
121
  event_handler_timeout: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
120
122
  event_handler_slow_timeout: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
123
+ event_blocks_parent_completion: z.ZodOptional<z.ZodBoolean>;
121
124
  event_parent_id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
122
125
  event_path: z.ZodOptional<z.ZodArray<z.ZodString>>;
123
126
  event_result_type: z.ZodOptional<z.ZodUnknown>;
@@ -145,12 +148,11 @@ export declare class BaseEvent {
145
148
  first: "first";
146
149
  }>>>;
147
150
  }, z.core.$loose>;
148
- bus?: EventBus;
151
+ event_bus?: EventBus;
149
152
  _event_original?: BaseEvent;
150
153
  _event_dispatch_context?: unknown | null;
151
154
  _event_completed_signal: Deferred<this> | null;
152
155
  _lock_for_event_handler: AsyncLock | null;
153
- get event_bus(): EventBus;
154
156
  constructor(data?: BaseEventInit<Record<string, unknown>>);
155
157
  toString(): string;
156
158
  static extend<TShape extends z.ZodRawShape>(event_type: string, shape?: TShape): EventFactory<TShape, ResultSchemaFromShape<TShape>>;
@@ -180,6 +182,7 @@ export declare class BaseEvent {
180
182
  get event_parent(): BaseEvent | undefined;
181
183
  get event_children(): BaseEvent[];
182
184
  get event_descendants(): BaseEvent[];
185
+ emit<T extends BaseEvent>(event: T): T;
183
186
  _cancelPendingChildProcessing(reason: unknown): void;
184
187
  _markRemainingFirstModeResultCancelled(winner: EventResult): void;
185
188
  _markCancelled(cause: Error): void;
@@ -189,6 +192,7 @@ export declare class BaseEvent {
189
192
  eventResultsList(include: EventResultsListInclude<this>, options?: EventResultsListOptions<this>): Promise<Array<EventResultType<this> | undefined>>;
190
193
  eventResultsList(options?: EventResultsListOptions<this>): Promise<Array<EventResultType<this> | undefined>>;
191
194
  eventCompleted(): Promise<this>;
195
+ _markBlocksParentCompletionIfAwaitedFromEmittingHandler(): void;
192
196
  _markPending(): this;
193
197
  eventReset(): this;
194
198
  _markStarted(started_at?: string | null, notify_hook?: boolean): void;
@@ -200,7 +204,7 @@ export declare class BaseEvent {
200
204
  ignore_first_mode_control_errors?: boolean;
201
205
  }): unknown | undefined;
202
206
  get event_result(): EventResultType<this> | undefined;
203
- _areAllChildrenComplete(): boolean;
207
+ _areAllChildrenComplete(visited?: Set<string>): boolean;
204
208
  private _notifyDoneListeners;
205
209
  _gc(): void;
206
210
  }
@@ -30,7 +30,7 @@ var import_lock_manager = require("./lock_manager.js");
30
30
  var import_timing = require("./timing.js");
31
31
  var import_types = require("./types.js");
32
32
  var import_helpers = require("./helpers.js");
33
- const RESERVED_USER_EVENT_FIELDS = /* @__PURE__ */ new Set(["bus", "first", "toString", "toJSON", "fromJSON"]);
33
+ const RESERVED_USER_EVENT_FIELDS = /* @__PURE__ */ new Set(["bus", "emit", "first", "toString", "toJSON", "fromJSON"]);
34
34
  function assertNoReservedUserEventFields(data, context) {
35
35
  for (const field_name of RESERVED_USER_EVENT_FIELDS) {
36
36
  if (Object.prototype.hasOwnProperty.call(data, field_name)) {
@@ -69,6 +69,7 @@ const BaseEventSchema = import_zod.z.object({
69
69
  event_slow_timeout: import_zod.z.number().positive().nullable().optional(),
70
70
  event_handler_timeout: import_zod.z.number().positive().nullable().optional(),
71
71
  event_handler_slow_timeout: import_zod.z.number().positive().nullable().optional(),
72
+ event_blocks_parent_completion: import_zod.z.boolean().optional(),
72
73
  event_parent_id: import_zod.z.string().uuid().nullable().optional(),
73
74
  event_path: import_zod.z.array(import_zod.z.string()).optional(),
74
75
  event_result_type: import_zod.z.unknown().optional(),
@@ -102,6 +103,8 @@ class BaseEvent {
102
103
  // optional per-event handler timeout override in seconds
103
104
  event_handler_slow_timeout;
104
105
  // optional per-event slow handler warning threshold in seconds
106
+ event_blocks_parent_completion;
107
+ // true only for children explicitly awaited via done()/eventCompleted()
105
108
  event_parent_id;
106
109
  // id of the parent event that triggered this event, if this event was emitted during handling of another event, else null
107
110
  event_path;
@@ -130,17 +133,14 @@ class BaseEvent {
130
133
  static schema = BaseEventSchema;
131
134
  // zod schema for the event data fields, used to parse and validate event data when creating a new event
132
135
  // internal runtime state
133
- bus;
134
- // shortcut to the bus that dispatched this event, for event.bus.emit(event) auto-child tracking via proxy wrapping
136
+ event_bus;
137
+ // bus that dispatched this event, also used by event.emit(child)
135
138
  _event_original;
136
139
  // underlying event object that was dispatched, if this is a bus-scoped proxy wrapping it
137
140
  _event_dispatch_context;
138
141
  // captured AsyncLocalStorage context at dispatch site, used to restore that context when running handlers
139
142
  _event_completed_signal;
140
143
  _lock_for_event_handler;
141
- get event_bus() {
142
- return this.bus;
143
- }
144
144
  constructor(data = {}) {
145
145
  assertNoReservedUserEventFields(data, "BaseEvent");
146
146
  assertNoUnknownEventPrefixedFields(data, "BaseEvent");
@@ -158,6 +158,7 @@ class BaseEvent {
158
158
  const event_id = merged_data.event_id ?? (0, import_uuid.v7)();
159
159
  const event_created_at = (0, import_helpers.monotonicDatetime)(merged_data.event_created_at);
160
160
  const event_timeout = merged_data.event_timeout ?? null;
161
+ const event_blocks_parent_completion = merged_data.event_blocks_parent_completion ?? false;
161
162
  const base_data = {
162
163
  ...merged_data,
163
164
  event_id,
@@ -165,6 +166,7 @@ class BaseEvent {
165
166
  event_type,
166
167
  event_version,
167
168
  event_timeout,
169
+ event_blocks_parent_completion,
168
170
  event_result_type
169
171
  };
170
172
  const schema = ctor.schema ?? BaseEventSchema;
@@ -253,7 +255,7 @@ class BaseEvent {
253
255
  toJSON() {
254
256
  const record = {};
255
257
  for (const [key, value] of Object.entries(this)) {
256
- if (key.startsWith("_") || key === "bus" || key === "event_results") continue;
258
+ if (key.startsWith("_") || key === "bus" || key === "event_bus" || key === "event_results") continue;
257
259
  if (value === void 0 || typeof value === "function") continue;
258
260
  record[key] = value;
259
261
  }
@@ -272,6 +274,7 @@ class BaseEvent {
272
274
  event_handler_completion: this.event_handler_completion,
273
275
  event_handler_slow_timeout: this.event_handler_slow_timeout,
274
276
  event_handler_timeout: this.event_handler_timeout,
277
+ event_blocks_parent_completion: this.event_blocks_parent_completion,
275
278
  // mutable parent/child/bus tracking runtime state
276
279
  event_parent_id: this.event_parent_id,
277
280
  event_path: this.event_path,
@@ -287,12 +290,12 @@ class BaseEvent {
287
290
  };
288
291
  }
289
292
  _createSlowEventWarningTimer() {
290
- const event_slow_timeout = this.event_slow_timeout ?? this.bus?.event_slow_timeout ?? null;
293
+ const event_slow_timeout = this.event_slow_timeout ?? this.event_bus?.event_slow_timeout ?? null;
291
294
  const event_warn_ms = event_slow_timeout === null ? null : event_slow_timeout * 1e3;
292
295
  if (event_warn_ms === null) {
293
296
  return null;
294
297
  }
295
- const name = this.bus?.name ?? "EventBus";
298
+ const name = this.event_bus?.name ?? "EventBus";
296
299
  return setTimeout(() => {
297
300
  if (this.event_status === "completed") {
298
301
  return;
@@ -312,8 +315,8 @@ class BaseEvent {
312
315
  let handler_entry;
313
316
  if (handler instanceof import_event_handler.EventHandler) {
314
317
  handler_entry = handler;
315
- if (!resolved_eventbus && handler_entry.eventbus_id !== ROOT_EVENTBUS_ID && original_event.bus) {
316
- resolved_eventbus = original_event.bus.all_instances.findBusById(handler_entry.eventbus_id) ?? (original_event.bus.id === handler_entry.eventbus_id ? original_event.bus : void 0);
318
+ if (!resolved_eventbus && handler_entry.eventbus_id !== ROOT_EVENTBUS_ID && original_event.event_bus) {
319
+ resolved_eventbus = original_event.event_bus.all_instances.findBusById(handler_entry.eventbus_id) ?? (original_event.event_bus.id === handler_entry.eventbus_id ? original_event.event_bus : void 0);
317
320
  }
318
321
  } else {
319
322
  handler_entry = import_event_handler.EventHandler.fromCallable({
@@ -372,10 +375,10 @@ class BaseEvent {
372
375
  if (pending_entries) {
373
376
  return pending_entries.map((entry) => entry.result);
374
377
  }
375
- if (!this.bus?.id) {
378
+ if (!this.event_bus?.id) {
376
379
  return Array.from(original.event_results.values());
377
380
  }
378
- return Array.from(original.event_results.values()).filter((result) => result.eventbus_id === this.bus.id);
381
+ return Array.from(original.event_results.values()).filter((result) => result.eventbus_id === this.event_bus.id);
379
382
  }
380
383
  _isFirstModeWinningResult(entry) {
381
384
  return entry.status === "completed" && entry.result !== void 0 && entry.result !== null && !(entry.result instanceof BaseEvent);
@@ -388,10 +391,10 @@ class BaseEvent {
388
391
  original._markRemainingFirstModeResultCancelled(entry);
389
392
  }
390
393
  async _runHandlerWithLock(original, entry) {
391
- if (!this.bus) {
394
+ if (!this.event_bus) {
392
395
  throw new Error("event has no bus attached");
393
396
  }
394
- await this.bus.locks._runWithHandlerLock(original, this.bus.event_handler_concurrency, async (handler_lock) => {
397
+ await this.event_bus.locks._runWithHandlerLock(original, this.event_bus.event_handler_concurrency, async (handler_lock) => {
395
398
  await entry.runHandler(handler_lock);
396
399
  });
397
400
  }
@@ -402,9 +405,9 @@ class BaseEvent {
402
405
  if (pending_results.length === 0) {
403
406
  return;
404
407
  }
405
- const resolved_completion = original.event_handler_completion ?? this.bus?.event_handler_completion ?? "all";
408
+ const resolved_completion = original.event_handler_completion ?? this.event_bus?.event_handler_completion ?? "all";
406
409
  if (resolved_completion === "first") {
407
- if (original._getHandlerLock(this.bus?.event_handler_concurrency) !== null) {
410
+ if (original._getHandlerLock(this.event_bus?.event_handler_concurrency) !== null) {
408
411
  for (const entry of pending_results) {
409
412
  await this._runHandlerWithLock(original, entry);
410
413
  if (!this._isFirstModeWinningResult(entry)) {
@@ -431,7 +434,7 @@ class BaseEvent {
431
434
  }
432
435
  _getHandlerLock(default_concurrency) {
433
436
  const original = this._event_original ?? this;
434
- const resolved = original.event_handler_concurrency ?? default_concurrency ?? original.bus?.event_handler_concurrency ?? "serial";
437
+ const resolved = original.event_handler_concurrency ?? default_concurrency ?? original.event_bus?.event_handler_concurrency ?? "serial";
435
438
  if (resolved === "parallel") {
436
439
  return null;
437
440
  }
@@ -459,7 +462,7 @@ class BaseEvent {
459
462
  if (!parent_id) {
460
463
  return void 0;
461
464
  }
462
- return original.bus?.findEventById(parent_id) ?? void 0;
465
+ return original.event_bus?.findEventById(parent_id) ?? void 0;
463
466
  }
464
467
  // get all direct children of this event
465
468
  get event_children() {
@@ -501,6 +504,17 @@ class BaseEvent {
501
504
  }
502
505
  return descendants;
503
506
  }
507
+ emit(event) {
508
+ const original_parent = this._event_original ?? this;
509
+ const original_child = event._event_original ?? event;
510
+ if (!original_child.event_parent_id && original_child.event_id !== original_parent.event_id) {
511
+ original_child.event_parent_id = original_parent.event_id;
512
+ }
513
+ if (!this.event_bus) {
514
+ throw new Error("event has no bus attached");
515
+ }
516
+ return this.event_bus.emit(original_child);
517
+ }
504
518
  // force-abort processing of all pending descendants of an event regardless of whether they have already started
505
519
  _cancelPendingChildProcessing(reason) {
506
520
  const original = this._event_original ?? this;
@@ -513,6 +527,10 @@ class BaseEvent {
513
527
  }
514
528
  visited.add(original_child.event_id);
515
529
  for (const grandchild of original_child.event_children) {
530
+ const original_grandchild = grandchild._event_original ?? grandchild;
531
+ if (!original_grandchild.event_blocks_parent_completion) {
532
+ continue;
533
+ }
516
534
  cancelChildEvent(grandchild);
517
535
  }
518
536
  original_child._markCancelled(cancellation_cause);
@@ -521,6 +539,10 @@ class BaseEvent {
521
539
  }
522
540
  };
523
541
  for (const child of original.event_children) {
542
+ const original_child = child._event_original ?? child;
543
+ if (!original_child.event_blocks_parent_completion) {
544
+ continue;
545
+ }
524
546
  cancelChildEvent(child);
525
547
  }
526
548
  }
@@ -543,6 +565,9 @@ class BaseEvent {
543
565
  } else if (result.status === "started") {
544
566
  for (const child of result.event_children) {
545
567
  const original_child = child._event_original ?? child;
568
+ if (!original_child.event_blocks_parent_completion) {
569
+ continue;
570
+ }
546
571
  original_child._cancelPendingChildProcessing(cause);
547
572
  original_child._markCancelled(cause);
548
573
  }
@@ -559,7 +584,7 @@ class BaseEvent {
559
584
  // force-abort processing of this event regardless of whether it is pending or has already started
560
585
  _markCancelled(cause) {
561
586
  const original = this._event_original ?? this;
562
- if (!this.bus) {
587
+ if (!this.event_bus) {
563
588
  if (original.event_status !== "completed") {
564
589
  original._markCompleted();
565
590
  }
@@ -567,7 +592,7 @@ class BaseEvent {
567
592
  }
568
593
  const path = Array.isArray(original.event_path) ? original.event_path : [];
569
594
  const buses_to_cancel = new Set(path);
570
- for (const bus of this.bus.all_instances) {
595
+ for (const bus of this.event_bus.all_instances) {
571
596
  if (!buses_to_cancel.has(bus.label)) {
572
597
  continue;
573
598
  }
@@ -606,14 +631,14 @@ class BaseEvent {
606
631
  }
607
632
  _notifyEventParentsOfCompletion() {
608
633
  const original = this._event_original ?? this;
609
- if (!this.bus) {
634
+ if (!this.event_bus) {
610
635
  return;
611
636
  }
612
637
  const visited = /* @__PURE__ */ new Set();
613
638
  let parent_id = original.event_parent_id;
614
639
  while (parent_id && !visited.has(parent_id)) {
615
640
  visited.add(parent_id);
616
- const parent = this.bus.findEventById(parent_id);
641
+ const parent = this.event_bus.findEventById(parent_id);
617
642
  if (!parent) {
618
643
  break;
619
644
  }
@@ -627,12 +652,13 @@ class BaseEvent {
627
652
  // awaitable that triggers immediate (queue-jump) processing of the event on all buses where it is queued
628
653
  // use eventCompleted() to wait for normal queue-order completion without queue-jumping.
629
654
  done(options = {}) {
630
- if (!this.bus) {
655
+ if (!this.event_bus) {
631
656
  return Promise.reject(new Error("event has no bus attached"));
632
657
  }
633
658
  const original = this._event_original ?? this;
659
+ original._markBlocksParentCompletionIfAwaitedFromEmittingHandler();
634
660
  const raise_if_any = options.raise_if_any ?? true;
635
- const completion_promise = this.event_status === "completed" ? Promise.resolve(original) : this.bus._processEventImmediately(this);
661
+ const completion_promise = this.event_status === "completed" ? Promise.resolve(original) : this.event_bus._processEventImmediately(this);
636
662
  if (!raise_if_any) {
637
663
  return completion_promise;
638
664
  }
@@ -652,7 +678,7 @@ class BaseEvent {
652
678
  // parallel: races all handlers, returns first non-undefined, aborts the rest
653
679
  // serial: runs handlers sequentially, returns first non-undefined, skips remaining
654
680
  first() {
655
- if (!this.bus) {
681
+ if (!this.event_bus) {
656
682
  return Promise.reject(new Error("event has no bus attached"));
657
683
  }
658
684
  const original = this._event_original ?? this;
@@ -685,7 +711,7 @@ class BaseEvent {
685
711
  const raise_if_any = options.raise_if_any ?? true;
686
712
  const raise_if_none = options.raise_if_none ?? true;
687
713
  const original = this._event_original ?? this;
688
- const resolved_timeout_seconds = options.timeout ?? original.event_timeout ?? this.bus?.event_timeout ?? null;
714
+ const resolved_timeout_seconds = options.timeout ?? original.event_timeout ?? this.event_bus?.event_timeout ?? null;
689
715
  let completed_event;
690
716
  if (resolved_timeout_seconds === null) {
691
717
  completed_event = await this.done({ raise_if_any: false });
@@ -733,12 +759,29 @@ class BaseEvent {
733
759
  }
734
760
  // awaitable that waits for the event to be processed in normal queue order by the _runloop
735
761
  eventCompleted() {
762
+ const original = this._event_original ?? this;
763
+ original._markBlocksParentCompletionIfAwaitedFromEmittingHandler();
736
764
  if (this.event_status === "completed") {
737
765
  return Promise.resolve(this);
738
766
  }
739
767
  this._notifyDoneListeners();
740
768
  return this._event_completed_signal.promise;
741
769
  }
770
+ _markBlocksParentCompletionIfAwaitedFromEmittingHandler() {
771
+ const original = this._event_original ?? this;
772
+ if (original.event_blocks_parent_completion || !original.event_bus) {
773
+ return;
774
+ }
775
+ const active_result = original.event_bus.locks._getActiveHandlerResultForCurrentAsyncContext();
776
+ if (!active_result || active_result.status !== "started") {
777
+ return;
778
+ }
779
+ const active_parent = active_result.event._event_original ?? active_result.event;
780
+ const is_child_of_active_handler = original.event_parent_id === active_parent.event_id && original.event_emitted_by_handler_id === active_result.handler_id && active_result.event_children.some((child) => (child._event_original ?? child).event_id === original.event_id);
781
+ if (is_child_of_active_handler) {
782
+ original.event_blocks_parent_completion = true;
783
+ }
784
+ }
742
785
  _markPending() {
743
786
  const original = this._event_original ?? this;
744
787
  original.event_status = "pending";
@@ -749,7 +792,7 @@ class BaseEvent {
749
792
  original._setDispatchContext(void 0);
750
793
  original._event_completed_signal = null;
751
794
  original._lock_for_event_handler = null;
752
- original.bus = void 0;
795
+ original.event_bus = void 0;
753
796
  return this;
754
797
  }
755
798
  eventReset() {
@@ -766,8 +809,8 @@ class BaseEvent {
766
809
  }
767
810
  original.event_status = "started";
768
811
  original.event_started_at = started_at === null ? (0, import_helpers.monotonicDatetime)() : (0, import_helpers.monotonicDatetime)(started_at);
769
- if (notify_hook && original.bus) {
770
- const bus_for_hook = original.bus;
812
+ if (notify_hook && original.event_bus) {
813
+ const bus_for_hook = original.event_bus;
771
814
  const event_for_bus = bus_for_hook._getEventProxyScopedToThisBus(original);
772
815
  void bus_for_hook.onEventChange(event_for_bus, "started");
773
816
  }
@@ -787,8 +830,8 @@ class BaseEvent {
787
830
  }
788
831
  original.event_status = "completed";
789
832
  original.event_completed_at = (0, import_helpers.monotonicDatetime)();
790
- if (original.bus) {
791
- const bus_for_hook = original.bus;
833
+ if (original.event_bus) {
834
+ const bus_for_hook = original.event_bus;
792
835
  const event_for_bus = bus_for_hook._getEventProxyScopedToThisBus(original);
793
836
  void bus_for_hook.onEventChange(event_for_bus, "completed");
794
837
  }
@@ -797,16 +840,16 @@ class BaseEvent {
797
840
  original._event_completed_signal.resolve(original);
798
841
  original._event_completed_signal = null;
799
842
  original.dropFromZeroHistoryBuses();
800
- if (notify_parents && original.bus) {
843
+ if (notify_parents && original.event_bus) {
801
844
  original._notifyEventParentsOfCompletion();
802
845
  }
803
846
  }
804
847
  dropFromZeroHistoryBuses() {
805
- if (!this.bus) {
848
+ if (!this.event_bus) {
806
849
  return;
807
850
  }
808
851
  const original = this._event_original ?? this;
809
- for (const bus of this.bus.all_instances) {
852
+ for (const bus of this.event_bus.all_instances) {
810
853
  if (bus.event_history.max_history_size !== 0) {
811
854
  continue;
812
855
  }
@@ -834,8 +877,25 @@ class BaseEvent {
834
877
  get event_result() {
835
878
  return Array.from(this.event_results.values()).filter((event_result) => event_result.completed_at !== null && event_result.result !== void 0).sort((event_result_a, event_result_b) => compareIsoDatetime(event_result_a.completed_at, event_result_b.completed_at)).map((event_result) => event_result.result).at(0);
836
879
  }
837
- _areAllChildrenComplete() {
838
- return this.event_descendants.every((descendant) => descendant.event_status === "completed");
880
+ _areAllChildrenComplete(visited = /* @__PURE__ */ new Set()) {
881
+ const original = this._event_original ?? this;
882
+ if (visited.has(original.event_id)) {
883
+ return true;
884
+ }
885
+ visited.add(original.event_id);
886
+ for (const child of original.event_children) {
887
+ const original_child = child._event_original ?? child;
888
+ if (!original_child.event_blocks_parent_completion) {
889
+ continue;
890
+ }
891
+ if (original_child.event_status !== "completed") {
892
+ return false;
893
+ }
894
+ if (!original_child._areAllChildrenComplete(visited)) {
895
+ return false;
896
+ }
897
+ }
898
+ return true;
839
899
  }
840
900
  _notifyDoneListeners() {
841
901
  if (this._event_completed_signal) {
@@ -848,7 +908,7 @@ class BaseEvent {
848
908
  _gc() {
849
909
  this._event_completed_signal = null;
850
910
  this._setDispatchContext(null);
851
- this.bus = void 0;
911
+ this.event_bus = void 0;
852
912
  this._lock_for_event_handler = null;
853
913
  for (const result of this.event_results.values()) {
854
914
  result.event_children = [];