abxbus 2.4.32 → 2.5.0
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 +74 -51
- package/dist/cjs/BaseEvent.d.ts +45 -55
- package/dist/cjs/BaseEvent.js +350 -169
- package/dist/cjs/BaseEvent.js.map +3 -3
- package/dist/cjs/EventBus.d.ts +8 -1
- package/dist/cjs/EventBus.js +153 -85
- package/dist/cjs/EventBus.js.map +2 -2
- package/dist/cjs/EventHandler.d.ts +3 -3
- package/dist/cjs/EventHandler.js.map +1 -1
- package/dist/cjs/EventResult.js +16 -22
- package/dist/cjs/EventResult.js.map +2 -2
- package/dist/cjs/LockManager.d.ts +1 -0
- package/dist/cjs/LockManager.js +4 -1
- package/dist/cjs/LockManager.js.map +2 -2
- package/dist/cjs/base_event.d.ts +2 -2
- package/dist/cjs/bridge_ipc.d.ts +45 -0
- package/dist/cjs/event_handler.d.ts +1 -0
- package/dist/cjs/events_suck.js +1 -1
- package/dist/cjs/events_suck.js.map +2 -2
- package/dist/cjs/index.d.ts +1 -0
- package/dist/cjs/index.js.map +2 -2
- package/dist/cjs/middleware_otel_tracing.d.ts +49 -0
- package/dist/cjs/timing.js +1 -1
- package/dist/cjs/timing.js.map +2 -2
- package/dist/esm/BaseEvent.js +351 -170
- package/dist/esm/BaseEvent.js.map +3 -3
- package/dist/esm/EventBus.js +153 -85
- package/dist/esm/EventBus.js.map +2 -2
- package/dist/esm/EventHandler.js.map +1 -1
- package/dist/esm/EventResult.js +16 -22
- package/dist/esm/EventResult.js.map +2 -2
- package/dist/esm/LockManager.js +4 -1
- package/dist/esm/LockManager.js.map +2 -2
- package/dist/esm/events_suck.js +1 -1
- package/dist/esm/events_suck.js.map +2 -2
- package/dist/esm/index.js.map +2 -2
- package/dist/esm/timing.js +1 -1
- package/dist/esm/timing.js.map +2 -2
- package/dist/types/BaseEvent.d.ts +45 -55
- package/dist/types/EventBus.d.ts +8 -1
- package/dist/types/EventHandler.d.ts +3 -3
- package/dist/types/LockManager.d.ts +1 -0
- package/dist/types/base_event.d.ts +2 -2
- package/dist/types/bridge_ipc.d.ts +45 -0
- package/dist/types/event_handler.d.ts +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/middleware_otel_tracing.d.ts +49 -0
- package/package.json +4 -3
- package/src/BaseEvent.ts +452 -219
- package/src/EventBus.ts +186 -99
- package/src/EventHandler.ts +3 -3
- package/src/EventResult.ts +18 -22
- package/src/LockManager.ts +5 -1
- package/src/events_suck.ts +1 -1
- package/src/index.ts +1 -0
- package/src/timing.ts +1 -1
package/src/EventBus.ts
CHANGED
|
@@ -44,6 +44,10 @@ export type EventBusOptions = {
|
|
|
44
44
|
middlewares?: EventBusMiddlewareInput[]
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
export type EventBusDestroyOptions = {
|
|
48
|
+
clear?: boolean
|
|
49
|
+
}
|
|
50
|
+
|
|
47
51
|
export type EventBusJSON = {
|
|
48
52
|
id: string
|
|
49
53
|
name: string
|
|
@@ -199,6 +203,7 @@ export class EventBus {
|
|
|
199
203
|
locks: LockManager
|
|
200
204
|
find_waiters: Set<EphemeralFindEventHandler> // set of EphemeralFindEventHandler objects that are waiting for a matching future event
|
|
201
205
|
middlewares: EventBusMiddleware[]
|
|
206
|
+
private destroyed: boolean
|
|
202
207
|
|
|
203
208
|
private static normalizeMiddlewares(middlewares?: EventBusMiddlewareInput[]): EventBusMiddleware[] {
|
|
204
209
|
const normalized: EventBusMiddleware[] = []
|
|
@@ -224,9 +229,9 @@ export class EventBus {
|
|
|
224
229
|
this.event_handler_concurrency = options.event_handler_concurrency ?? 'serial'
|
|
225
230
|
this.event_handler_completion = options.event_handler_completion ?? 'all'
|
|
226
231
|
this.event_handler_detect_file_paths = options.event_handler_detect_file_paths ?? true
|
|
227
|
-
this.event_timeout = options.event_timeout
|
|
228
|
-
this.event_handler_slow_timeout = options.event_handler_slow_timeout
|
|
229
|
-
this.event_slow_timeout = options.event_slow_timeout
|
|
232
|
+
this.event_timeout = options.event_timeout ?? 60
|
|
233
|
+
this.event_handler_slow_timeout = options.event_handler_slow_timeout ?? 30
|
|
234
|
+
this.event_slow_timeout = options.event_slow_timeout ?? 300
|
|
230
235
|
|
|
231
236
|
// initialize runtime state
|
|
232
237
|
this.runloop_running = false
|
|
@@ -241,6 +246,7 @@ export class EventBus {
|
|
|
241
246
|
this.in_flight_event_ids = new Set()
|
|
242
247
|
this.locks = new LockManager(this)
|
|
243
248
|
this.middlewares = EventBus.normalizeMiddlewares(options.middlewares)
|
|
249
|
+
this.destroyed = false
|
|
244
250
|
|
|
245
251
|
this.all_instances.add(this)
|
|
246
252
|
|
|
@@ -367,7 +373,7 @@ export class EventBus {
|
|
|
367
373
|
fn: () => Promise<void>
|
|
368
374
|
): Promise<void> {
|
|
369
375
|
try {
|
|
370
|
-
if (event_timeout === null || pending_entries.length === 0) {
|
|
376
|
+
if (event_timeout === null || event_timeout <= 0 || pending_entries.length === 0) {
|
|
371
377
|
await fn()
|
|
372
378
|
} else {
|
|
373
379
|
await _runWithTimeout(event_timeout, () => this._createEventTimeoutError(event, pending_entries, event_timeout), fn)
|
|
@@ -525,10 +531,11 @@ export class EventBus {
|
|
|
525
531
|
}
|
|
526
532
|
const bus = new EventBus(name, options)
|
|
527
533
|
|
|
528
|
-
|
|
534
|
+
const raw_handlers = record.handlers ?? {}
|
|
535
|
+
if (!raw_handlers || typeof raw_handlers !== 'object' || Array.isArray(raw_handlers)) {
|
|
529
536
|
throw new Error('EventBus.fromJSON(data) requires handlers as an id-keyed object')
|
|
530
537
|
}
|
|
531
|
-
for (const [handler_id, payload] of Object.entries(
|
|
538
|
+
for (const [handler_id, payload] of Object.entries(raw_handlers as Record<string, unknown>)) {
|
|
532
539
|
if (!payload || typeof payload !== 'object') {
|
|
533
540
|
continue
|
|
534
541
|
}
|
|
@@ -542,11 +549,12 @@ export class EventBus {
|
|
|
542
549
|
bus.handlers.set(parsed.id, parsed)
|
|
543
550
|
}
|
|
544
551
|
|
|
545
|
-
|
|
552
|
+
const raw_handlers_by_key = record.handlers_by_key ?? {}
|
|
553
|
+
if (!raw_handlers_by_key || typeof raw_handlers_by_key !== 'object' || Array.isArray(raw_handlers_by_key)) {
|
|
546
554
|
throw new Error('EventBus.fromJSON(data) requires handlers_by_key as an object')
|
|
547
555
|
}
|
|
548
556
|
bus.handlers_by_key.clear()
|
|
549
|
-
for (const [raw_key, raw_ids] of Object.entries(
|
|
557
|
+
for (const [raw_key, raw_ids] of Object.entries(raw_handlers_by_key as Record<string, unknown>)) {
|
|
550
558
|
if (!Array.isArray(raw_ids)) {
|
|
551
559
|
continue
|
|
552
560
|
}
|
|
@@ -633,21 +641,51 @@ export class EventBus {
|
|
|
633
641
|
return this.event_history.delete(event_id)
|
|
634
642
|
}
|
|
635
643
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
this.handlers.clear()
|
|
640
|
-
this.handlers_by_key.clear()
|
|
641
|
-
for (const event of this.event_history.values()) {
|
|
642
|
-
event._gc()
|
|
644
|
+
private _raiseIfDestroyed(): void {
|
|
645
|
+
if (this.destroyed) {
|
|
646
|
+
throw new Error(`${this.toString()} has been destroyed and cannot be used again`)
|
|
643
647
|
}
|
|
644
|
-
this.event_history.clear()
|
|
645
|
-
this.pending_event_queue.length = 0
|
|
646
|
-
this.in_flight_event_ids.clear()
|
|
647
|
-
this.find_waiters.clear()
|
|
648
|
-
this.locks.clear()
|
|
649
648
|
}
|
|
650
649
|
|
|
650
|
+
// destroy the event bus and all its state to allow for garbage collection
|
|
651
|
+
destroy(clear?: boolean): Promise<void>
|
|
652
|
+
destroy(options?: EventBusDestroyOptions): Promise<void>
|
|
653
|
+
destroy(clear_or_options: boolean | EventBusDestroyOptions = true): Promise<void> {
|
|
654
|
+
const clear = typeof clear_or_options === 'object' && clear_or_options !== null ? (clear_or_options.clear ?? true) : clear_or_options
|
|
655
|
+
if (this.destroyed) {
|
|
656
|
+
if (clear) {
|
|
657
|
+
this.handlers.clear()
|
|
658
|
+
this.handlers_by_key.clear()
|
|
659
|
+
this.event_history.clear()
|
|
660
|
+
this.middlewares.length = 0
|
|
661
|
+
}
|
|
662
|
+
return Promise.resolve()
|
|
663
|
+
}
|
|
664
|
+
const finish = (): void => {
|
|
665
|
+
this.destroyed = true
|
|
666
|
+
this.all_instances.discard(this)
|
|
667
|
+
this.runloop_running = false
|
|
668
|
+
for (const waiter of Array.from(this.find_waiters)) {
|
|
669
|
+
if (waiter.timeout_id) {
|
|
670
|
+
clearTimeout(waiter.timeout_id)
|
|
671
|
+
}
|
|
672
|
+
this.find_waiters.delete(waiter)
|
|
673
|
+
waiter.resolve(null)
|
|
674
|
+
}
|
|
675
|
+
this.pending_event_queue.length = 0
|
|
676
|
+
this.in_flight_event_ids.clear()
|
|
677
|
+
this.locks.clear()
|
|
678
|
+
if (!clear) {
|
|
679
|
+
return
|
|
680
|
+
}
|
|
681
|
+
this.handlers.clear()
|
|
682
|
+
this.handlers_by_key.clear()
|
|
683
|
+
this.event_history.clear()
|
|
684
|
+
this.middlewares.length = 0
|
|
685
|
+
}
|
|
686
|
+
finish()
|
|
687
|
+
return Promise.resolve()
|
|
688
|
+
}
|
|
651
689
|
on<T extends BaseEvent>(event_pattern: EventClass<T>, handler: EventHandlerCallable<T>, options?: Partial<EventHandler>): EventHandler
|
|
652
690
|
on<T extends BaseEvent>(
|
|
653
691
|
event_pattern: string | '*',
|
|
@@ -659,6 +697,7 @@ export class EventBus {
|
|
|
659
697
|
handler: EventHandlerCallable | UntypedEventHandlerFunction,
|
|
660
698
|
options: Partial<EventHandler> = {}
|
|
661
699
|
): EventHandler {
|
|
700
|
+
this._raiseIfDestroyed()
|
|
662
701
|
const normalized_key = normalizeEventPattern(event_pattern) // get string event_type or '*'
|
|
663
702
|
const handler_name = EventHandler.handlerNameFromCallable(handler as EventHandlerCallable)
|
|
664
703
|
const handler_entry = new EventHandler({
|
|
@@ -687,6 +726,7 @@ export class EventBus {
|
|
|
687
726
|
}
|
|
688
727
|
|
|
689
728
|
off<T extends BaseEvent>(event_pattern: EventPattern<T> | '*', handler?: EventHandlerCallable<T> | string | EventHandler): void {
|
|
729
|
+
this._raiseIfDestroyed()
|
|
690
730
|
const normalized_key = normalizeEventPattern(event_pattern)
|
|
691
731
|
if (typeof handler === 'object' && handler instanceof EventHandler && handler.id !== undefined) {
|
|
692
732
|
handler = handler.id
|
|
@@ -708,7 +748,19 @@ export class EventBus {
|
|
|
708
748
|
}
|
|
709
749
|
|
|
710
750
|
emit<T extends BaseEvent>(event: T): T {
|
|
751
|
+
this._raiseIfDestroyed()
|
|
711
752
|
const original_event = event._event_original ?? event // if event is a bus-scoped proxy already, get the original underlying event object
|
|
753
|
+
const current_result = this.locks._getRawActiveHandlerResultForCurrentAsyncContext()
|
|
754
|
+
if (
|
|
755
|
+
current_result &&
|
|
756
|
+
current_result.status !== 'pending' &&
|
|
757
|
+
current_result.status !== 'started' &&
|
|
758
|
+
(current_result.error instanceof EventHandlerTimeoutError ||
|
|
759
|
+
current_result.error instanceof EventHandlerCancelledError ||
|
|
760
|
+
current_result.error instanceof EventHandlerAbortedError)
|
|
761
|
+
) {
|
|
762
|
+
return original_event as T
|
|
763
|
+
}
|
|
712
764
|
if (!original_event.event_bus) {
|
|
713
765
|
// if we are the first bus to emit this event, set the event_bus property on the original event object
|
|
714
766
|
original_event.event_bus = this
|
|
@@ -760,7 +812,11 @@ export class EventBus {
|
|
|
760
812
|
|
|
761
813
|
original_event.event_pending_bus_count += 1
|
|
762
814
|
this.pending_event_queue.push(original_event)
|
|
763
|
-
this.
|
|
815
|
+
if (this.locks.getLockForEvent(original_event) === null) {
|
|
816
|
+
this._startParallelEventTaskFromQueue(original_event)
|
|
817
|
+
} else {
|
|
818
|
+
this._startRunloop()
|
|
819
|
+
}
|
|
764
820
|
|
|
765
821
|
return this._getEventProxyScopedToThisBus(original_event) as T
|
|
766
822
|
}
|
|
@@ -806,6 +862,7 @@ export class EventBus {
|
|
|
806
862
|
where_or_options: ((event: T) => boolean) | FilterOptions<T> = {},
|
|
807
863
|
maybe_options: FilterOptions<T> = {}
|
|
808
864
|
): Promise<T[]> {
|
|
865
|
+
this._raiseIfDestroyed()
|
|
809
866
|
const where = typeof where_or_options === 'function' ? where_or_options : () => true
|
|
810
867
|
const options = typeof where_or_options === 'function' ? maybe_options : where_or_options
|
|
811
868
|
const matches = await this.event_history.filter(event_pattern as EventPattern<T> | '*', where, {
|
|
@@ -843,6 +900,7 @@ export class EventBus {
|
|
|
843
900
|
}
|
|
844
901
|
|
|
845
902
|
async waitUntilIdle(timeout: number | null = null): Promise<boolean> {
|
|
903
|
+
this._raiseIfDestroyed()
|
|
846
904
|
return await this.locks.waitForIdle(timeout)
|
|
847
905
|
}
|
|
848
906
|
|
|
@@ -901,6 +959,7 @@ export class EventBus {
|
|
|
901
959
|
}
|
|
902
960
|
|
|
903
961
|
private _startRunloop(): void {
|
|
962
|
+
this._raiseIfDestroyed()
|
|
904
963
|
if (this.runloop_running) {
|
|
905
964
|
return
|
|
906
965
|
}
|
|
@@ -910,6 +969,22 @@ export class EventBus {
|
|
|
910
969
|
})
|
|
911
970
|
}
|
|
912
971
|
|
|
972
|
+
private _startParallelEventTaskFromQueue(event: BaseEvent): void {
|
|
973
|
+
if (this.in_flight_event_ids.has(event.event_id)) {
|
|
974
|
+
return
|
|
975
|
+
}
|
|
976
|
+
const queue_index = this.pending_event_queue.indexOf(event)
|
|
977
|
+
if (queue_index >= 0) {
|
|
978
|
+
this.pending_event_queue.splice(queue_index, 1)
|
|
979
|
+
} else if (event.event_status === 'completed') {
|
|
980
|
+
return
|
|
981
|
+
}
|
|
982
|
+
this.in_flight_event_ids.add(event.event_id)
|
|
983
|
+
this.scheduleMicrotask(() => {
|
|
984
|
+
void this._processEvent(event)
|
|
985
|
+
})
|
|
986
|
+
}
|
|
987
|
+
|
|
913
988
|
// schedule the processing of an event on the event bus by its normal _runloop
|
|
914
989
|
// optionally using a pre-acquired lock if we're inside handling of a parent event
|
|
915
990
|
private async _processEvent(
|
|
@@ -932,6 +1007,7 @@ export class EventBus {
|
|
|
932
1007
|
event._markStarted()
|
|
933
1008
|
pending_entries = event._createPendingHandlerResults(this)
|
|
934
1009
|
const resolved_event_timeout = event.event_timeout ?? this.event_timeout
|
|
1010
|
+
const resolved_event_slow_timeout = event.event_slow_timeout ?? this.event_slow_timeout
|
|
935
1011
|
if (this.middlewares.length > 0) {
|
|
936
1012
|
for (const entry of pending_entries) {
|
|
937
1013
|
await this._onEventResultChange(scoped_event, entry.result, 'pending')
|
|
@@ -941,7 +1017,9 @@ export class EventBus {
|
|
|
941
1017
|
event,
|
|
942
1018
|
() =>
|
|
943
1019
|
this._runHandlersWithTimeout(event, pending_entries, resolved_event_timeout, () =>
|
|
944
|
-
_runWithSlowMonitor(event._createSlowEventWarningTimer(), () =>
|
|
1020
|
+
_runWithSlowMonitor(event._createSlowEventWarningTimer(resolved_event_slow_timeout, this.name), () =>
|
|
1021
|
+
scoped_event._runHandlers(pending_entries)
|
|
1022
|
+
)
|
|
945
1023
|
),
|
|
946
1024
|
options
|
|
947
1025
|
)
|
|
@@ -955,7 +1033,7 @@ export class EventBus {
|
|
|
955
1033
|
}
|
|
956
1034
|
}
|
|
957
1035
|
|
|
958
|
-
// Called when a handler does `await child.
|
|
1036
|
+
// Called when a handler does `await child.now()` — processes the child event
|
|
959
1037
|
// immediately ("queue-jump") instead of waiting for the _runloop to pick it up.
|
|
960
1038
|
//
|
|
961
1039
|
// Yield-and-reacquire: if the calling handler holds a handler concurrency lock,
|
|
@@ -970,18 +1048,15 @@ export class EventBus {
|
|
|
970
1048
|
const proxy_result = handler_result?.status === 'started' ? handler_result : undefined
|
|
971
1049
|
const currently_active_event_result = proxy_result ?? this.locks._getActiveHandlerResultForCurrentAsyncContext()
|
|
972
1050
|
if (!currently_active_event_result) {
|
|
973
|
-
// Not inside any handler scope — avoid queue-jump, but if this event is
|
|
974
|
-
// next in line we can process it immediately without waiting on the _runloop.
|
|
975
|
-
// We must acquire/revalidate the event lock first to avoid racing the runloop
|
|
976
|
-
// and accidentally reordering/removing the wrong queue head.
|
|
977
1051
|
const queue_index = this.pending_event_queue.indexOf(original_event)
|
|
978
|
-
const
|
|
1052
|
+
const event_lock = this.locks.getLockForEvent(original_event)
|
|
1053
|
+
const can_process_queue_head_normally =
|
|
979
1054
|
queue_index === 0 &&
|
|
980
1055
|
!this.locks._isPaused() &&
|
|
981
1056
|
!this.in_flight_event_ids.has(original_event.event_id) &&
|
|
982
|
-
!this._hasProcessedEvent(original_event)
|
|
983
|
-
|
|
984
|
-
|
|
1057
|
+
!this._hasProcessedEvent(original_event) &&
|
|
1058
|
+
(event_lock === null || event_lock.in_use === 0)
|
|
1059
|
+
if (can_process_queue_head_normally) {
|
|
985
1060
|
let pre_acquired_lock: AsyncLock | null = null
|
|
986
1061
|
if (event_lock) {
|
|
987
1062
|
await event_lock.acquire()
|
|
@@ -989,12 +1064,12 @@ export class EventBus {
|
|
|
989
1064
|
}
|
|
990
1065
|
const queue_head = this.pending_event_queue[0]
|
|
991
1066
|
const queue_head_original = queue_head?._event_original ?? queue_head
|
|
992
|
-
|
|
1067
|
+
if (
|
|
993
1068
|
queue_head_original === original_event &&
|
|
994
1069
|
!this.locks._isPaused() &&
|
|
995
1070
|
!this.in_flight_event_ids.has(original_event.event_id) &&
|
|
996
1071
|
!this._hasProcessedEvent(original_event)
|
|
997
|
-
|
|
1072
|
+
) {
|
|
998
1073
|
this.pending_event_queue.shift()
|
|
999
1074
|
this.in_flight_event_ids.add(original_event.event_id)
|
|
1000
1075
|
await this._processEvent(original_event, {
|
|
@@ -1002,15 +1077,13 @@ export class EventBus {
|
|
|
1002
1077
|
pre_acquired_lock,
|
|
1003
1078
|
})
|
|
1004
1079
|
if (original_event.event_status !== 'completed') {
|
|
1005
|
-
await
|
|
1080
|
+
await this._processEventImmediatelyAcrossBuses(original_event)
|
|
1006
1081
|
}
|
|
1007
1082
|
return event
|
|
1008
1083
|
}
|
|
1009
|
-
|
|
1010
|
-
pre_acquired_lock.release()
|
|
1011
|
-
}
|
|
1084
|
+
pre_acquired_lock?.release()
|
|
1012
1085
|
}
|
|
1013
|
-
await
|
|
1086
|
+
await this._processEventImmediatelyAcrossBuses(original_event)
|
|
1014
1087
|
return event
|
|
1015
1088
|
}
|
|
1016
1089
|
|
|
@@ -1047,78 +1120,83 @@ export class EventBus {
|
|
|
1047
1120
|
// Processes a queue-jumped event across all buses that have it emitted.
|
|
1048
1121
|
// Called from _processEventImmediately after the parent handler's lock has been yielded.
|
|
1049
1122
|
private async _processEventImmediatelyAcrossBuses(event: BaseEvent): Promise<void> {
|
|
1050
|
-
// Use event_path ordering to pick candidate buses and filter out buses that
|
|
1051
|
-
// haven't seen the event or already processed it.
|
|
1052
|
-
const ordered: EventBus[] = []
|
|
1053
|
-
const seen = new Set<EventBus>()
|
|
1054
|
-
const event_path = Array.isArray(event.event_path) ? event.event_path : []
|
|
1055
|
-
for (const label of event_path) {
|
|
1056
|
-
for (const bus of this.all_instances) {
|
|
1057
|
-
if (bus.label !== label) {
|
|
1058
|
-
continue
|
|
1059
|
-
}
|
|
1060
|
-
if (!bus.event_history.has(event.event_id)) {
|
|
1061
|
-
continue
|
|
1062
|
-
}
|
|
1063
|
-
if (bus._hasProcessedEvent(event)) {
|
|
1064
|
-
continue
|
|
1065
|
-
}
|
|
1066
|
-
if (!seen.has(bus)) {
|
|
1067
|
-
ordered.push(bus)
|
|
1068
|
-
seen.add(bus)
|
|
1069
|
-
}
|
|
1070
|
-
}
|
|
1071
|
-
}
|
|
1072
|
-
if (!seen.has(this) && this.event_history.has(event.event_id)) {
|
|
1073
|
-
ordered.push(this)
|
|
1074
|
-
}
|
|
1075
|
-
if (ordered.length === 0) {
|
|
1076
|
-
await event.eventCompleted()
|
|
1077
|
-
return
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
1123
|
// Determine which event lock the initiating bus resolves to, so we can
|
|
1081
1124
|
// detect when other buses share the same instance (global-serial).
|
|
1082
1125
|
const initiating_event_lock = this.locks.getLockForEvent(event)
|
|
1083
|
-
const pause_releases: Array<() => void> = []
|
|
1084
1126
|
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1127
|
+
for (;;) {
|
|
1128
|
+
// Use event_path ordering to pick candidate buses and filter out buses
|
|
1129
|
+
// that haven't seen the event or already processed it. Forwarding
|
|
1130
|
+
// handlers can append new buses while this method is already running, so
|
|
1131
|
+
// this list must be rebuilt until the event is fully complete.
|
|
1132
|
+
const ordered: EventBus[] = []
|
|
1133
|
+
const seen = new Set<EventBus>()
|
|
1134
|
+
const event_path = Array.isArray(event.event_path) ? event.event_path : []
|
|
1135
|
+
for (const label of event_path) {
|
|
1136
|
+
for (const bus of this.all_instances) {
|
|
1137
|
+
if (bus.label !== label) {
|
|
1138
|
+
continue
|
|
1139
|
+
}
|
|
1140
|
+
if (!bus.event_history.has(event.event_id)) {
|
|
1141
|
+
continue
|
|
1142
|
+
}
|
|
1143
|
+
if (bus._hasProcessedEvent(event)) {
|
|
1144
|
+
continue
|
|
1145
|
+
}
|
|
1146
|
+
if (!seen.has(bus)) {
|
|
1147
|
+
ordered.push(bus)
|
|
1148
|
+
seen.add(bus)
|
|
1149
|
+
}
|
|
1089
1150
|
}
|
|
1090
1151
|
}
|
|
1152
|
+
if (!seen.has(this) && this.event_history.has(event.event_id) && !this._hasProcessedEvent(event)) {
|
|
1153
|
+
ordered.push(this)
|
|
1154
|
+
}
|
|
1091
1155
|
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
}
|
|
1100
|
-
if (bus.in_flight_event_ids.has(event.event_id)) {
|
|
1101
|
-
continue
|
|
1156
|
+
const pause_releases: Array<() => void> = []
|
|
1157
|
+
let processed_bus = false
|
|
1158
|
+
try {
|
|
1159
|
+
for (const bus of ordered) {
|
|
1160
|
+
if (bus !== this) {
|
|
1161
|
+
pause_releases.push(bus.locks._requestRunloopPause())
|
|
1162
|
+
}
|
|
1102
1163
|
}
|
|
1103
|
-
bus.in_flight_event_ids.add(event.event_id)
|
|
1104
1164
|
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1165
|
+
for (const bus of ordered) {
|
|
1166
|
+
const index = bus.pending_event_queue.indexOf(event)
|
|
1167
|
+
if (index >= 0) {
|
|
1168
|
+
bus.pending_event_queue.splice(index, 1)
|
|
1169
|
+
}
|
|
1170
|
+
if (bus._hasProcessedEvent(event)) {
|
|
1171
|
+
continue
|
|
1172
|
+
}
|
|
1173
|
+
if (bus.in_flight_event_ids.has(event.event_id)) {
|
|
1174
|
+
continue
|
|
1175
|
+
}
|
|
1176
|
+
bus.in_flight_event_ids.add(event.event_id)
|
|
1177
|
+
processed_bus = true
|
|
1178
|
+
|
|
1179
|
+
// Bypass event lock on the initiating bus (we're already inside a handler
|
|
1180
|
+
// that acquired it). For other buses, only bypass if they resolve to the same
|
|
1181
|
+
// lock instance (global-serial shares one lock across all buses).
|
|
1182
|
+
const bus_event_lock = bus.locks.getLockForEvent(event)
|
|
1183
|
+
const should_bypass_event_lock = bus === this || (initiating_event_lock !== null && bus_event_lock === initiating_event_lock)
|
|
1110
1184
|
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1185
|
+
await bus._processEvent(event, {
|
|
1186
|
+
bypass_event_locks: should_bypass_event_lock,
|
|
1187
|
+
})
|
|
1188
|
+
}
|
|
1189
|
+
} finally {
|
|
1190
|
+
for (const release of pause_releases) {
|
|
1191
|
+
release()
|
|
1192
|
+
}
|
|
1114
1193
|
}
|
|
1115
1194
|
|
|
1116
|
-
if (event.event_status
|
|
1117
|
-
|
|
1195
|
+
if (event.event_status === 'completed') {
|
|
1196
|
+
return
|
|
1118
1197
|
}
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
release()
|
|
1198
|
+
if (!processed_bus) {
|
|
1199
|
+
await new Promise((resolve) => setTimeout(resolve, 1))
|
|
1122
1200
|
}
|
|
1123
1201
|
}
|
|
1124
1202
|
}
|
|
@@ -1147,7 +1225,7 @@ export class EventBus {
|
|
|
1147
1225
|
pre_acquired_lock = event_lock
|
|
1148
1226
|
}
|
|
1149
1227
|
// Queue head may have changed while waiting for the lock
|
|
1150
|
-
// (e.g.
|
|
1228
|
+
// (e.g. now() processing the head immediately). Revalidate
|
|
1151
1229
|
// before mutating the queue to avoid removing the wrong event.
|
|
1152
1230
|
const current_head = this.pending_event_queue[0]
|
|
1153
1231
|
const current_head_original = current_head?._event_original ?? current_head
|
|
@@ -1209,7 +1287,16 @@ export class EventBus {
|
|
|
1209
1287
|
if (prop === 'dispatch' || prop === 'emit') {
|
|
1210
1288
|
const emit_child_event = <TChild extends BaseEvent>(child_event: TChild): TChild => {
|
|
1211
1289
|
const original_child = child_event._event_original ?? child_event
|
|
1212
|
-
|
|
1290
|
+
const handler_result_is_terminal = handler_result && handler_result.status !== 'pending' && handler_result.status !== 'started'
|
|
1291
|
+
if (
|
|
1292
|
+
handler_result_is_terminal &&
|
|
1293
|
+
(handler_result.error instanceof EventHandlerTimeoutError ||
|
|
1294
|
+
handler_result.error instanceof EventHandlerCancelledError ||
|
|
1295
|
+
handler_result.error instanceof EventHandlerAbortedError)
|
|
1296
|
+
) {
|
|
1297
|
+
return original_child as TChild
|
|
1298
|
+
}
|
|
1299
|
+
if (handler_result && !handler_result_is_terminal) {
|
|
1213
1300
|
handler_result._linkEmittedChildEvent(original_child)
|
|
1214
1301
|
} else if (!original_child.event_parent_id && original_child.event_id !== parent_event_id) {
|
|
1215
1302
|
// fallback for non-handler scoped emit/dispatch
|
package/src/EventHandler.ts
CHANGED
|
@@ -24,7 +24,7 @@ export type EphemeralFindEventHandler = {
|
|
|
24
24
|
// Resolved on dispatch, ephemeral, and never shows up in the processing tree.
|
|
25
25
|
event_pattern: string | '*'
|
|
26
26
|
matches: (event: BaseEvent) => boolean
|
|
27
|
-
resolve: (event: BaseEvent) => void
|
|
27
|
+
resolve: (event: BaseEvent | null) => void
|
|
28
28
|
timeout_id?: ReturnType<typeof setTimeout>
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -49,7 +49,7 @@ export class FindWaiter {
|
|
|
49
49
|
data: unknown,
|
|
50
50
|
overrides: {
|
|
51
51
|
matches?: (event: BaseEvent) => boolean
|
|
52
|
-
resolve?: (event: BaseEvent) => void
|
|
52
|
+
resolve?: (event: BaseEvent | null) => void
|
|
53
53
|
} = {}
|
|
54
54
|
): EphemeralFindEventHandler {
|
|
55
55
|
const record = FindWaiterJSONSchema.parse(data)
|
|
@@ -70,7 +70,7 @@ export class FindWaiter {
|
|
|
70
70
|
data: unknown,
|
|
71
71
|
overrides: {
|
|
72
72
|
matches?: (event: BaseEvent) => boolean
|
|
73
|
-
resolve?: (event: BaseEvent) => void
|
|
73
|
+
resolve?: (event: BaseEvent | null) => void
|
|
74
74
|
} = {}
|
|
75
75
|
): EphemeralFindEventHandler[] {
|
|
76
76
|
if (!Array.isArray(data)) {
|
package/src/EventResult.ts
CHANGED
|
@@ -25,8 +25,8 @@ export const EventResultJSONSchema = z
|
|
|
25
25
|
handler_id: z.string(),
|
|
26
26
|
handler_name: z.string(),
|
|
27
27
|
handler_file_path: z.string().nullable().optional(),
|
|
28
|
-
handler_timeout: z.number().nullable().optional(),
|
|
29
|
-
handler_slow_timeout: z.number().nullable().optional(),
|
|
28
|
+
handler_timeout: z.number().nonnegative().nullable().optional(),
|
|
29
|
+
handler_slow_timeout: z.number().nonnegative().nullable().optional(),
|
|
30
30
|
handler_registered_at: z.string().datetime().optional(),
|
|
31
31
|
handler_event_pattern: z.union([z.string(), z.literal('*')]).optional(),
|
|
32
32
|
eventbus_name: z.string(),
|
|
@@ -171,18 +171,24 @@ export class EventResult<TEvent extends BaseEvent = BaseEvent> {
|
|
|
171
171
|
return this.result
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
-
// Resolve handler timeout in seconds using
|
|
174
|
+
// Resolve handler timeout in seconds using event-local values plus the executing bus defaults.
|
|
175
175
|
get handler_timeout(): number | null {
|
|
176
176
|
const original = this.event._event_original ?? this.event
|
|
177
|
-
const
|
|
177
|
+
const raw_event_timeout = original.event_timeout ?? this.bus.event_timeout
|
|
178
|
+
const resolved_event_timeout =
|
|
179
|
+
raw_event_timeout !== null && raw_event_timeout !== undefined && raw_event_timeout > 0 ? raw_event_timeout : null
|
|
178
180
|
|
|
179
181
|
let resolved_handler_timeout: number | null
|
|
180
|
-
if (this.handler.handler_timeout !== undefined) {
|
|
182
|
+
if (this.handler.handler_timeout !== undefined && this.handler.handler_timeout !== null) {
|
|
181
183
|
resolved_handler_timeout = this.handler.handler_timeout
|
|
182
|
-
} else if (original.event_handler_timeout !== undefined) {
|
|
184
|
+
} else if (original.event_handler_timeout !== undefined && original.event_handler_timeout !== null) {
|
|
183
185
|
resolved_handler_timeout = original.event_handler_timeout
|
|
184
186
|
} else {
|
|
185
|
-
resolved_handler_timeout =
|
|
187
|
+
resolved_handler_timeout = resolved_event_timeout
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (resolved_handler_timeout !== null && resolved_handler_timeout <= 0) {
|
|
191
|
+
resolved_handler_timeout = null
|
|
186
192
|
}
|
|
187
193
|
|
|
188
194
|
if (resolved_handler_timeout === null && resolved_event_timeout === null) {
|
|
@@ -197,31 +203,21 @@ export class EventResult<TEvent extends BaseEvent = BaseEvent> {
|
|
|
197
203
|
return Math.min(resolved_handler_timeout, resolved_event_timeout)
|
|
198
204
|
}
|
|
199
205
|
|
|
200
|
-
// Resolve slow handler warning threshold in seconds using
|
|
206
|
+
// Resolve slow handler warning threshold in seconds using event-local values plus the executing bus defaults.
|
|
201
207
|
get handler_slow_timeout(): number | null {
|
|
202
208
|
const original = this.event._event_original ?? this.event
|
|
203
209
|
|
|
204
|
-
if (this.handler.handler_slow_timeout !== undefined) {
|
|
210
|
+
if (this.handler.handler_slow_timeout !== undefined && this.handler.handler_slow_timeout !== null) {
|
|
205
211
|
return this.handler.handler_slow_timeout
|
|
206
212
|
}
|
|
207
|
-
|
|
208
|
-
return original.event_handler_slow_timeout
|
|
209
|
-
}
|
|
210
|
-
const event_slow_timeout = (original as { event_slow_timeout?: number | null }).event_slow_timeout
|
|
211
|
-
if (event_slow_timeout !== undefined) {
|
|
212
|
-
return event_slow_timeout
|
|
213
|
-
}
|
|
214
|
-
if (this.bus?.event_handler_slow_timeout !== undefined) {
|
|
215
|
-
return this.bus.event_handler_slow_timeout
|
|
216
|
-
}
|
|
217
|
-
return this.bus?.event_slow_timeout ?? null
|
|
213
|
+
return original.event_handler_slow_timeout ?? this.bus.event_handler_slow_timeout ?? null
|
|
218
214
|
}
|
|
219
215
|
|
|
220
216
|
// Create a slow-handler warning timer that logs if the handler runs too long.
|
|
221
217
|
_createSlowHandlerWarningTimer(effective_timeout: number | null): ReturnType<typeof setTimeout> | null {
|
|
222
218
|
const handler_warn_timeout = this.handler_slow_timeout
|
|
223
|
-
const warn_ms = handler_warn_timeout === null ? null : handler_warn_timeout * 1000
|
|
224
|
-
const should_warn = warn_ms !== null && (effective_timeout === null || effective_timeout * 1000 > warn_ms)
|
|
219
|
+
const warn_ms = handler_warn_timeout === null || handler_warn_timeout <= 0 ? null : handler_warn_timeout * 1000
|
|
220
|
+
const should_warn = warn_ms !== null && (effective_timeout === null || effective_timeout <= 0 || effective_timeout * 1000 > warn_ms)
|
|
225
221
|
if (!should_warn || warn_ms === null) {
|
|
226
222
|
return null
|
|
227
223
|
}
|
package/src/LockManager.ts
CHANGED
|
@@ -246,10 +246,14 @@ export class LockManager {
|
|
|
246
246
|
}
|
|
247
247
|
|
|
248
248
|
_getActiveHandlerResultForCurrentAsyncContext(): EventResult | undefined {
|
|
249
|
-
const result =
|
|
249
|
+
const result = this._getRawActiveHandlerResultForCurrentAsyncContext()
|
|
250
250
|
return result?.status === 'started' ? result : undefined
|
|
251
251
|
}
|
|
252
252
|
|
|
253
|
+
_getRawActiveHandlerResultForCurrentAsyncContext(): EventResult | undefined {
|
|
254
|
+
return handler_context_storage?.getStore() as EventResult | undefined
|
|
255
|
+
}
|
|
256
|
+
|
|
253
257
|
_getActiveHandlerResults(): EventResult[] {
|
|
254
258
|
return [...this.active_handler_results]
|
|
255
259
|
}
|
package/src/events_suck.ts
CHANGED
|
@@ -75,7 +75,7 @@ export const wrap = <TEvents extends EventMap>(class_name: string, methods: TEve
|
|
|
75
75
|
Object.defineProperty(WrappedClient.prototype, method_name, {
|
|
76
76
|
value: async function (this: DynamicWrappedClient, init?: Record<string, unknown>, extra?: Record<string, unknown>) {
|
|
77
77
|
const payload = { ...(init ?? {}), ...(extra ?? {}) }
|
|
78
|
-
return await this.bus.emit(new EventCtor(payload)).
|
|
78
|
+
return await this.bus.emit(new EventCtor(payload)).now({ first_result: true }).eventResult()
|
|
79
79
|
},
|
|
80
80
|
writable: true,
|
|
81
81
|
configurable: true,
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { BaseEvent, BaseEventSchema } from './BaseEvent.js'
|
|
2
|
+
export type { EventResultInclude, EventResultOptions, EventWaitOptions, EventWaitPromise } from './BaseEvent.js'
|
|
2
3
|
export { EventHistory } from './EventHistory.js'
|
|
3
4
|
export type { EventHistoryFilterOptions, EventHistoryFindOptions, EventHistoryTrimOptions } from './EventHistory.js'
|
|
4
5
|
export { EventResult } from './EventResult.js'
|
package/src/timing.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export async function _runWithTimeout<T>(timeout_seconds: number | null, on_timeout: () => Error, fn: () => Promise<T>): Promise<T> {
|
|
2
2
|
const task = Promise.resolve().then(fn)
|
|
3
|
-
if (timeout_seconds === null) {
|
|
3
|
+
if (timeout_seconds === null || timeout_seconds <= 0) {
|
|
4
4
|
return await task
|
|
5
5
|
}
|
|
6
6
|
const timeout_ms = timeout_seconds * 1000
|