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.
@@ -12,7 +12,7 @@ import {
12
12
  import { _runWithTimeout } from "./timing.js";
13
13
  import { extractZodShape, normalizeEventResultType, toJsonSchema } from "./types.js";
14
14
  import { monotonicDatetime } from "./helpers.js";
15
- const RESERVED_USER_EVENT_FIELDS = /* @__PURE__ */ new Set(["bus", "first", "toString", "toJSON", "fromJSON"]);
15
+ const RESERVED_USER_EVENT_FIELDS = /* @__PURE__ */ new Set(["bus", "emit", "first", "toString", "toJSON", "fromJSON"]);
16
16
  function assertNoReservedUserEventFields(data, context) {
17
17
  for (const field_name of RESERVED_USER_EVENT_FIELDS) {
18
18
  if (Object.prototype.hasOwnProperty.call(data, field_name)) {
@@ -51,6 +51,7 @@ const BaseEventSchema = z.object({
51
51
  event_slow_timeout: z.number().positive().nullable().optional(),
52
52
  event_handler_timeout: z.number().positive().nullable().optional(),
53
53
  event_handler_slow_timeout: z.number().positive().nullable().optional(),
54
+ event_blocks_parent_completion: z.boolean().optional(),
54
55
  event_parent_id: z.string().uuid().nullable().optional(),
55
56
  event_path: z.array(z.string()).optional(),
56
57
  event_result_type: z.unknown().optional(),
@@ -84,6 +85,8 @@ class BaseEvent {
84
85
  // optional per-event handler timeout override in seconds
85
86
  event_handler_slow_timeout;
86
87
  // optional per-event slow handler warning threshold in seconds
88
+ event_blocks_parent_completion;
89
+ // true only for children explicitly awaited via done()/eventCompleted()
87
90
  event_parent_id;
88
91
  // id of the parent event that triggered this event, if this event was emitted during handling of another event, else null
89
92
  event_path;
@@ -112,17 +115,14 @@ class BaseEvent {
112
115
  static schema = BaseEventSchema;
113
116
  // zod schema for the event data fields, used to parse and validate event data when creating a new event
114
117
  // internal runtime state
115
- bus;
116
- // shortcut to the bus that dispatched this event, for event.bus.emit(event) auto-child tracking via proxy wrapping
118
+ event_bus;
119
+ // bus that dispatched this event, also used by event.emit(child)
117
120
  _event_original;
118
121
  // underlying event object that was dispatched, if this is a bus-scoped proxy wrapping it
119
122
  _event_dispatch_context;
120
123
  // captured AsyncLocalStorage context at dispatch site, used to restore that context when running handlers
121
124
  _event_completed_signal;
122
125
  _lock_for_event_handler;
123
- get event_bus() {
124
- return this.bus;
125
- }
126
126
  constructor(data = {}) {
127
127
  assertNoReservedUserEventFields(data, "BaseEvent");
128
128
  assertNoUnknownEventPrefixedFields(data, "BaseEvent");
@@ -140,6 +140,7 @@ class BaseEvent {
140
140
  const event_id = merged_data.event_id ?? uuidv7();
141
141
  const event_created_at = monotonicDatetime(merged_data.event_created_at);
142
142
  const event_timeout = merged_data.event_timeout ?? null;
143
+ const event_blocks_parent_completion = merged_data.event_blocks_parent_completion ?? false;
143
144
  const base_data = {
144
145
  ...merged_data,
145
146
  event_id,
@@ -147,6 +148,7 @@ class BaseEvent {
147
148
  event_type,
148
149
  event_version,
149
150
  event_timeout,
151
+ event_blocks_parent_completion,
150
152
  event_result_type
151
153
  };
152
154
  const schema = ctor.schema ?? BaseEventSchema;
@@ -235,7 +237,7 @@ class BaseEvent {
235
237
  toJSON() {
236
238
  const record = {};
237
239
  for (const [key, value] of Object.entries(this)) {
238
- if (key.startsWith("_") || key === "bus" || key === "event_results") continue;
240
+ if (key.startsWith("_") || key === "bus" || key === "event_bus" || key === "event_results") continue;
239
241
  if (value === void 0 || typeof value === "function") continue;
240
242
  record[key] = value;
241
243
  }
@@ -254,6 +256,7 @@ class BaseEvent {
254
256
  event_handler_completion: this.event_handler_completion,
255
257
  event_handler_slow_timeout: this.event_handler_slow_timeout,
256
258
  event_handler_timeout: this.event_handler_timeout,
259
+ event_blocks_parent_completion: this.event_blocks_parent_completion,
257
260
  // mutable parent/child/bus tracking runtime state
258
261
  event_parent_id: this.event_parent_id,
259
262
  event_path: this.event_path,
@@ -269,12 +272,12 @@ class BaseEvent {
269
272
  };
270
273
  }
271
274
  _createSlowEventWarningTimer() {
272
- const event_slow_timeout = this.event_slow_timeout ?? this.bus?.event_slow_timeout ?? null;
275
+ const event_slow_timeout = this.event_slow_timeout ?? this.event_bus?.event_slow_timeout ?? null;
273
276
  const event_warn_ms = event_slow_timeout === null ? null : event_slow_timeout * 1e3;
274
277
  if (event_warn_ms === null) {
275
278
  return null;
276
279
  }
277
- const name = this.bus?.name ?? "EventBus";
280
+ const name = this.event_bus?.name ?? "EventBus";
278
281
  return setTimeout(() => {
279
282
  if (this.event_status === "completed") {
280
283
  return;
@@ -294,8 +297,8 @@ class BaseEvent {
294
297
  let handler_entry;
295
298
  if (handler instanceof EventHandler) {
296
299
  handler_entry = handler;
297
- if (!resolved_eventbus && handler_entry.eventbus_id !== ROOT_EVENTBUS_ID && original_event.bus) {
298
- 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);
300
+ if (!resolved_eventbus && handler_entry.eventbus_id !== ROOT_EVENTBUS_ID && original_event.event_bus) {
301
+ 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);
299
302
  }
300
303
  } else {
301
304
  handler_entry = EventHandler.fromCallable({
@@ -354,10 +357,10 @@ class BaseEvent {
354
357
  if (pending_entries) {
355
358
  return pending_entries.map((entry) => entry.result);
356
359
  }
357
- if (!this.bus?.id) {
360
+ if (!this.event_bus?.id) {
358
361
  return Array.from(original.event_results.values());
359
362
  }
360
- return Array.from(original.event_results.values()).filter((result) => result.eventbus_id === this.bus.id);
363
+ return Array.from(original.event_results.values()).filter((result) => result.eventbus_id === this.event_bus.id);
361
364
  }
362
365
  _isFirstModeWinningResult(entry) {
363
366
  return entry.status === "completed" && entry.result !== void 0 && entry.result !== null && !(entry.result instanceof BaseEvent);
@@ -370,10 +373,10 @@ class BaseEvent {
370
373
  original._markRemainingFirstModeResultCancelled(entry);
371
374
  }
372
375
  async _runHandlerWithLock(original, entry) {
373
- if (!this.bus) {
376
+ if (!this.event_bus) {
374
377
  throw new Error("event has no bus attached");
375
378
  }
376
- await this.bus.locks._runWithHandlerLock(original, this.bus.event_handler_concurrency, async (handler_lock) => {
379
+ await this.event_bus.locks._runWithHandlerLock(original, this.event_bus.event_handler_concurrency, async (handler_lock) => {
377
380
  await entry.runHandler(handler_lock);
378
381
  });
379
382
  }
@@ -384,9 +387,9 @@ class BaseEvent {
384
387
  if (pending_results.length === 0) {
385
388
  return;
386
389
  }
387
- const resolved_completion = original.event_handler_completion ?? this.bus?.event_handler_completion ?? "all";
390
+ const resolved_completion = original.event_handler_completion ?? this.event_bus?.event_handler_completion ?? "all";
388
391
  if (resolved_completion === "first") {
389
- if (original._getHandlerLock(this.bus?.event_handler_concurrency) !== null) {
392
+ if (original._getHandlerLock(this.event_bus?.event_handler_concurrency) !== null) {
390
393
  for (const entry of pending_results) {
391
394
  await this._runHandlerWithLock(original, entry);
392
395
  if (!this._isFirstModeWinningResult(entry)) {
@@ -413,7 +416,7 @@ class BaseEvent {
413
416
  }
414
417
  _getHandlerLock(default_concurrency) {
415
418
  const original = this._event_original ?? this;
416
- const resolved = original.event_handler_concurrency ?? default_concurrency ?? original.bus?.event_handler_concurrency ?? "serial";
419
+ const resolved = original.event_handler_concurrency ?? default_concurrency ?? original.event_bus?.event_handler_concurrency ?? "serial";
417
420
  if (resolved === "parallel") {
418
421
  return null;
419
422
  }
@@ -441,7 +444,7 @@ class BaseEvent {
441
444
  if (!parent_id) {
442
445
  return void 0;
443
446
  }
444
- return original.bus?.findEventById(parent_id) ?? void 0;
447
+ return original.event_bus?.findEventById(parent_id) ?? void 0;
445
448
  }
446
449
  // get all direct children of this event
447
450
  get event_children() {
@@ -483,6 +486,17 @@ class BaseEvent {
483
486
  }
484
487
  return descendants;
485
488
  }
489
+ emit(event) {
490
+ const original_parent = this._event_original ?? this;
491
+ const original_child = event._event_original ?? event;
492
+ if (!original_child.event_parent_id && original_child.event_id !== original_parent.event_id) {
493
+ original_child.event_parent_id = original_parent.event_id;
494
+ }
495
+ if (!this.event_bus) {
496
+ throw new Error("event has no bus attached");
497
+ }
498
+ return this.event_bus.emit(original_child);
499
+ }
486
500
  // force-abort processing of all pending descendants of an event regardless of whether they have already started
487
501
  _cancelPendingChildProcessing(reason) {
488
502
  const original = this._event_original ?? this;
@@ -495,6 +509,10 @@ class BaseEvent {
495
509
  }
496
510
  visited.add(original_child.event_id);
497
511
  for (const grandchild of original_child.event_children) {
512
+ const original_grandchild = grandchild._event_original ?? grandchild;
513
+ if (!original_grandchild.event_blocks_parent_completion) {
514
+ continue;
515
+ }
498
516
  cancelChildEvent(grandchild);
499
517
  }
500
518
  original_child._markCancelled(cancellation_cause);
@@ -503,6 +521,10 @@ class BaseEvent {
503
521
  }
504
522
  };
505
523
  for (const child of original.event_children) {
524
+ const original_child = child._event_original ?? child;
525
+ if (!original_child.event_blocks_parent_completion) {
526
+ continue;
527
+ }
506
528
  cancelChildEvent(child);
507
529
  }
508
530
  }
@@ -525,6 +547,9 @@ class BaseEvent {
525
547
  } else if (result.status === "started") {
526
548
  for (const child of result.event_children) {
527
549
  const original_child = child._event_original ?? child;
550
+ if (!original_child.event_blocks_parent_completion) {
551
+ continue;
552
+ }
528
553
  original_child._cancelPendingChildProcessing(cause);
529
554
  original_child._markCancelled(cause);
530
555
  }
@@ -541,7 +566,7 @@ class BaseEvent {
541
566
  // force-abort processing of this event regardless of whether it is pending or has already started
542
567
  _markCancelled(cause) {
543
568
  const original = this._event_original ?? this;
544
- if (!this.bus) {
569
+ if (!this.event_bus) {
545
570
  if (original.event_status !== "completed") {
546
571
  original._markCompleted();
547
572
  }
@@ -549,7 +574,7 @@ class BaseEvent {
549
574
  }
550
575
  const path = Array.isArray(original.event_path) ? original.event_path : [];
551
576
  const buses_to_cancel = new Set(path);
552
- for (const bus of this.bus.all_instances) {
577
+ for (const bus of this.event_bus.all_instances) {
553
578
  if (!buses_to_cancel.has(bus.label)) {
554
579
  continue;
555
580
  }
@@ -588,14 +613,14 @@ class BaseEvent {
588
613
  }
589
614
  _notifyEventParentsOfCompletion() {
590
615
  const original = this._event_original ?? this;
591
- if (!this.bus) {
616
+ if (!this.event_bus) {
592
617
  return;
593
618
  }
594
619
  const visited = /* @__PURE__ */ new Set();
595
620
  let parent_id = original.event_parent_id;
596
621
  while (parent_id && !visited.has(parent_id)) {
597
622
  visited.add(parent_id);
598
- const parent = this.bus.findEventById(parent_id);
623
+ const parent = this.event_bus.findEventById(parent_id);
599
624
  if (!parent) {
600
625
  break;
601
626
  }
@@ -609,12 +634,13 @@ class BaseEvent {
609
634
  // awaitable that triggers immediate (queue-jump) processing of the event on all buses where it is queued
610
635
  // use eventCompleted() to wait for normal queue-order completion without queue-jumping.
611
636
  done(options = {}) {
612
- if (!this.bus) {
637
+ if (!this.event_bus) {
613
638
  return Promise.reject(new Error("event has no bus attached"));
614
639
  }
615
640
  const original = this._event_original ?? this;
641
+ original._markBlocksParentCompletionIfAwaitedFromEmittingHandler();
616
642
  const raise_if_any = options.raise_if_any ?? true;
617
- const completion_promise = this.event_status === "completed" ? Promise.resolve(original) : this.bus._processEventImmediately(this);
643
+ const completion_promise = this.event_status === "completed" ? Promise.resolve(original) : this.event_bus._processEventImmediately(this);
618
644
  if (!raise_if_any) {
619
645
  return completion_promise;
620
646
  }
@@ -634,7 +660,7 @@ class BaseEvent {
634
660
  // parallel: races all handlers, returns first non-undefined, aborts the rest
635
661
  // serial: runs handlers sequentially, returns first non-undefined, skips remaining
636
662
  first() {
637
- if (!this.bus) {
663
+ if (!this.event_bus) {
638
664
  return Promise.reject(new Error("event has no bus attached"));
639
665
  }
640
666
  const original = this._event_original ?? this;
@@ -667,7 +693,7 @@ class BaseEvent {
667
693
  const raise_if_any = options.raise_if_any ?? true;
668
694
  const raise_if_none = options.raise_if_none ?? true;
669
695
  const original = this._event_original ?? this;
670
- const resolved_timeout_seconds = options.timeout ?? original.event_timeout ?? this.bus?.event_timeout ?? null;
696
+ const resolved_timeout_seconds = options.timeout ?? original.event_timeout ?? this.event_bus?.event_timeout ?? null;
671
697
  let completed_event;
672
698
  if (resolved_timeout_seconds === null) {
673
699
  completed_event = await this.done({ raise_if_any: false });
@@ -715,12 +741,29 @@ class BaseEvent {
715
741
  }
716
742
  // awaitable that waits for the event to be processed in normal queue order by the _runloop
717
743
  eventCompleted() {
744
+ const original = this._event_original ?? this;
745
+ original._markBlocksParentCompletionIfAwaitedFromEmittingHandler();
718
746
  if (this.event_status === "completed") {
719
747
  return Promise.resolve(this);
720
748
  }
721
749
  this._notifyDoneListeners();
722
750
  return this._event_completed_signal.promise;
723
751
  }
752
+ _markBlocksParentCompletionIfAwaitedFromEmittingHandler() {
753
+ const original = this._event_original ?? this;
754
+ if (original.event_blocks_parent_completion || !original.event_bus) {
755
+ return;
756
+ }
757
+ const active_result = original.event_bus.locks._getActiveHandlerResultForCurrentAsyncContext();
758
+ if (!active_result || active_result.status !== "started") {
759
+ return;
760
+ }
761
+ const active_parent = active_result.event._event_original ?? active_result.event;
762
+ 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);
763
+ if (is_child_of_active_handler) {
764
+ original.event_blocks_parent_completion = true;
765
+ }
766
+ }
724
767
  _markPending() {
725
768
  const original = this._event_original ?? this;
726
769
  original.event_status = "pending";
@@ -731,7 +774,7 @@ class BaseEvent {
731
774
  original._setDispatchContext(void 0);
732
775
  original._event_completed_signal = null;
733
776
  original._lock_for_event_handler = null;
734
- original.bus = void 0;
777
+ original.event_bus = void 0;
735
778
  return this;
736
779
  }
737
780
  eventReset() {
@@ -748,8 +791,8 @@ class BaseEvent {
748
791
  }
749
792
  original.event_status = "started";
750
793
  original.event_started_at = started_at === null ? monotonicDatetime() : monotonicDatetime(started_at);
751
- if (notify_hook && original.bus) {
752
- const bus_for_hook = original.bus;
794
+ if (notify_hook && original.event_bus) {
795
+ const bus_for_hook = original.event_bus;
753
796
  const event_for_bus = bus_for_hook._getEventProxyScopedToThisBus(original);
754
797
  void bus_for_hook.onEventChange(event_for_bus, "started");
755
798
  }
@@ -769,8 +812,8 @@ class BaseEvent {
769
812
  }
770
813
  original.event_status = "completed";
771
814
  original.event_completed_at = monotonicDatetime();
772
- if (original.bus) {
773
- const bus_for_hook = original.bus;
815
+ if (original.event_bus) {
816
+ const bus_for_hook = original.event_bus;
774
817
  const event_for_bus = bus_for_hook._getEventProxyScopedToThisBus(original);
775
818
  void bus_for_hook.onEventChange(event_for_bus, "completed");
776
819
  }
@@ -779,16 +822,16 @@ class BaseEvent {
779
822
  original._event_completed_signal.resolve(original);
780
823
  original._event_completed_signal = null;
781
824
  original.dropFromZeroHistoryBuses();
782
- if (notify_parents && original.bus) {
825
+ if (notify_parents && original.event_bus) {
783
826
  original._notifyEventParentsOfCompletion();
784
827
  }
785
828
  }
786
829
  dropFromZeroHistoryBuses() {
787
- if (!this.bus) {
830
+ if (!this.event_bus) {
788
831
  return;
789
832
  }
790
833
  const original = this._event_original ?? this;
791
- for (const bus of this.bus.all_instances) {
834
+ for (const bus of this.event_bus.all_instances) {
792
835
  if (bus.event_history.max_history_size !== 0) {
793
836
  continue;
794
837
  }
@@ -816,8 +859,25 @@ class BaseEvent {
816
859
  get event_result() {
817
860
  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);
818
861
  }
819
- _areAllChildrenComplete() {
820
- return this.event_descendants.every((descendant) => descendant.event_status === "completed");
862
+ _areAllChildrenComplete(visited = /* @__PURE__ */ new Set()) {
863
+ const original = this._event_original ?? this;
864
+ if (visited.has(original.event_id)) {
865
+ return true;
866
+ }
867
+ visited.add(original.event_id);
868
+ for (const child of original.event_children) {
869
+ const original_child = child._event_original ?? child;
870
+ if (!original_child.event_blocks_parent_completion) {
871
+ continue;
872
+ }
873
+ if (original_child.event_status !== "completed") {
874
+ return false;
875
+ }
876
+ if (!original_child._areAllChildrenComplete(visited)) {
877
+ return false;
878
+ }
879
+ }
880
+ return true;
821
881
  }
822
882
  _notifyDoneListeners() {
823
883
  if (this._event_completed_signal) {
@@ -830,7 +890,7 @@ class BaseEvent {
830
890
  _gc() {
831
891
  this._event_completed_signal = null;
832
892
  this._setDispatchContext(null);
833
- this.bus = void 0;
893
+ this.event_bus = void 0;
834
894
  this._lock_for_event_handler = null;
835
895
  for (const result of this.event_results.values()) {
836
896
  result.event_children = [];