bubus 1.7.3 → 2.2.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 (68) hide show
  1. package/README.md +775 -266
  2. package/dist/esm/async_context.js +39 -0
  3. package/dist/esm/async_context.js.map +7 -0
  4. package/dist/esm/base_event.js +825 -0
  5. package/dist/esm/base_event.js.map +7 -0
  6. package/dist/esm/bridge_jsonl.js +150 -0
  7. package/dist/esm/bridge_jsonl.js.map +7 -0
  8. package/dist/esm/bridge_nats.js +88 -0
  9. package/dist/esm/bridge_nats.js.map +7 -0
  10. package/dist/esm/bridge_postgres.js +231 -0
  11. package/dist/esm/bridge_postgres.js.map +7 -0
  12. package/dist/esm/bridge_redis.js +155 -0
  13. package/dist/esm/bridge_redis.js.map +7 -0
  14. package/dist/esm/bridge_sqlite.js +235 -0
  15. package/dist/esm/bridge_sqlite.js.map +7 -0
  16. package/dist/esm/bridges.js +306 -0
  17. package/dist/esm/bridges.js.map +7 -0
  18. package/dist/esm/event_bus.js +1046 -0
  19. package/dist/esm/event_bus.js.map +7 -0
  20. package/dist/esm/event_handler.js +279 -0
  21. package/dist/esm/event_handler.js.map +7 -0
  22. package/dist/esm/event_history.js +172 -0
  23. package/dist/esm/event_history.js.map +7 -0
  24. package/dist/esm/event_result.js +426 -0
  25. package/dist/esm/event_result.js.map +7 -0
  26. package/dist/esm/events_suck.js +39 -0
  27. package/dist/esm/events_suck.js.map +7 -0
  28. package/dist/esm/helpers.js +64 -0
  29. package/dist/esm/helpers.js.map +7 -0
  30. package/dist/esm/index.js +37 -16206
  31. package/dist/esm/index.js.map +4 -4
  32. package/dist/esm/lock_manager.js +323 -0
  33. package/dist/esm/lock_manager.js.map +7 -0
  34. package/dist/esm/logging.js +196 -0
  35. package/dist/esm/logging.js.map +7 -0
  36. package/dist/esm/middlewares.js +1 -0
  37. package/dist/esm/middlewares.js.map +7 -0
  38. package/dist/esm/optional_deps.js +34 -0
  39. package/dist/esm/optional_deps.js.map +7 -0
  40. package/dist/esm/retry.js +237 -0
  41. package/dist/esm/retry.js.map +7 -0
  42. package/dist/esm/timing.js +56 -0
  43. package/dist/esm/timing.js.map +7 -0
  44. package/dist/esm/types.js +84 -0
  45. package/dist/esm/types.js.map +7 -0
  46. package/dist/types/async_context.d.ts +4 -2
  47. package/dist/types/base_event.d.ts +124 -76
  48. package/dist/types/bridge_jsonl.d.ts +26 -0
  49. package/dist/types/bridge_nats.d.ts +20 -0
  50. package/dist/types/bridge_postgres.d.ts +31 -0
  51. package/dist/types/bridge_redis.d.ts +34 -0
  52. package/dist/types/bridge_sqlite.d.ts +30 -0
  53. package/dist/types/bridges.d.ts +49 -0
  54. package/dist/types/event_bus.d.ts +99 -68
  55. package/dist/types/event_handler.d.ts +73 -19
  56. package/dist/types/event_history.d.ts +45 -0
  57. package/dist/types/event_result.d.ts +60 -38
  58. package/dist/types/events_suck.d.ts +40 -0
  59. package/dist/types/helpers.d.ts +1 -0
  60. package/dist/types/index.d.ts +13 -2
  61. package/dist/types/lock_manager.d.ts +33 -31
  62. package/dist/types/logging.d.ts +5 -1
  63. package/dist/types/middlewares.d.ts +13 -0
  64. package/dist/types/optional_deps.d.ts +3 -0
  65. package/dist/types/retry.d.ts +52 -0
  66. package/dist/types/timing.d.ts +3 -0
  67. package/dist/types/types.d.ts +18 -7
  68. package/package.json +32 -11
@@ -0,0 +1,1046 @@
1
+ import { BaseEvent } from "./base_event.js";
2
+ import { EventHistory } from "./event_history.js";
3
+ import { captureAsyncContext } from "./async_context.js";
4
+ import { _runWithSlowMonitor, _runWithTimeout } from "./timing.js";
5
+ import {
6
+ AsyncLock,
7
+ LockManager
8
+ } from "./lock_manager.js";
9
+ import {
10
+ EventHandler,
11
+ EventHandlerAbortedError,
12
+ EventHandlerCancelledError,
13
+ EventHandlerTimeoutError
14
+ } from "./event_handler.js";
15
+ import { logTree } from "./logging.js";
16
+ import { v7 as uuidv7 } from "uuid";
17
+ import { monotonicDatetime } from "./helpers.js";
18
+ import { normalizeEventPattern } from "./types.js";
19
+ class GlobalEventBusRegistry {
20
+ _bus_refs = /* @__PURE__ */ new Set();
21
+ add(bus) {
22
+ this._bus_refs.add(new WeakRef(bus));
23
+ }
24
+ discard(bus) {
25
+ for (const ref of this._bus_refs) {
26
+ const current = ref.deref();
27
+ if (!current || current === bus) {
28
+ this._bus_refs.delete(ref);
29
+ }
30
+ }
31
+ }
32
+ has(bus) {
33
+ for (const ref of this._bus_refs) {
34
+ const current = ref.deref();
35
+ if (!current) {
36
+ this._bus_refs.delete(ref);
37
+ continue;
38
+ }
39
+ if (current === bus) {
40
+ return true;
41
+ }
42
+ }
43
+ return false;
44
+ }
45
+ get size() {
46
+ let count = 0;
47
+ for (const ref of this._bus_refs) {
48
+ if (ref.deref()) {
49
+ count += 1;
50
+ } else {
51
+ this._bus_refs.delete(ref);
52
+ }
53
+ }
54
+ return count;
55
+ }
56
+ *[Symbol.iterator]() {
57
+ for (const ref of this._bus_refs) {
58
+ const bus = ref.deref();
59
+ if (bus) {
60
+ yield bus;
61
+ } else {
62
+ this._bus_refs.delete(ref);
63
+ }
64
+ }
65
+ }
66
+ findBusById(bus_id) {
67
+ for (const bus of this) {
68
+ if (bus.id === bus_id) {
69
+ return bus;
70
+ }
71
+ }
72
+ return void 0;
73
+ }
74
+ findEventById(event_id) {
75
+ for (const bus of this) {
76
+ const event = bus.event_history.getEvent(event_id);
77
+ if (event) {
78
+ return event;
79
+ }
80
+ }
81
+ return null;
82
+ }
83
+ }
84
+ class EventBus {
85
+ static _registry_by_constructor = /* @__PURE__ */ new WeakMap();
86
+ static _global_event_lock_by_constructor = /* @__PURE__ */ new WeakMap();
87
+ static getRegistryForConstructor(constructor_fn) {
88
+ const existing_registry = EventBus._registry_by_constructor.get(constructor_fn);
89
+ if (existing_registry) {
90
+ return existing_registry;
91
+ }
92
+ const created_registry = new GlobalEventBusRegistry();
93
+ EventBus._registry_by_constructor.set(constructor_fn, created_registry);
94
+ return created_registry;
95
+ }
96
+ static getGlobalEventLockForConstructor(constructor_fn) {
97
+ const existing_lock = EventBus._global_event_lock_by_constructor.get(constructor_fn);
98
+ if (existing_lock) {
99
+ return existing_lock;
100
+ }
101
+ const created_lock = new AsyncLock(1);
102
+ EventBus._global_event_lock_by_constructor.set(constructor_fn, created_lock);
103
+ return created_lock;
104
+ }
105
+ static get all_instances() {
106
+ return EventBus.getRegistryForConstructor(this);
107
+ }
108
+ get all_instances() {
109
+ return EventBus.getRegistryForConstructor(this.constructor);
110
+ }
111
+ get _lock_for_event_global_serial() {
112
+ return EventBus.getGlobalEventLockForConstructor(this.constructor);
113
+ }
114
+ id;
115
+ // unique uuidv7 identifier for the event bus
116
+ name;
117
+ // name of the event bus, recommended to include the word "Bus" in the name for clarity in logs
118
+ // configuration options
119
+ event_timeout;
120
+ event_concurrency;
121
+ event_handler_concurrency;
122
+ event_handler_completion;
123
+ event_handler_detect_file_paths;
124
+ // slow processing warning timeout settings
125
+ event_handler_slow_timeout;
126
+ event_slow_timeout;
127
+ // public runtime state
128
+ handlers;
129
+ // map of handler uuidv5 ids to EventHandler objects
130
+ handlers_by_key;
131
+ // map of normalized event_pattern to ordered handler ids
132
+ event_history;
133
+ // map of event uuidv7 ids to processed BaseEvent objects
134
+ // internal runtime state
135
+ pending_event_queue;
136
+ // queue of events that have been emitted to the bus but not yet processed
137
+ in_flight_event_ids;
138
+ // set of event ids that are currently being processed by the bus
139
+ runloop_running;
140
+ locks;
141
+ find_waiters;
142
+ // set of EphemeralFindEventHandler objects that are waiting for a matching future event
143
+ middlewares;
144
+ static normalizeMiddlewares(middlewares) {
145
+ const normalized = [];
146
+ for (const middleware of middlewares ?? []) {
147
+ if (!middleware) {
148
+ continue;
149
+ }
150
+ if (typeof middleware === "function") {
151
+ normalized.push(new middleware());
152
+ } else {
153
+ normalized.push(middleware);
154
+ }
155
+ }
156
+ return normalized;
157
+ }
158
+ constructor(name = "EventBus", options = {}) {
159
+ this.id = options.id ?? uuidv7();
160
+ this.name = name;
161
+ this.event_concurrency = options.event_concurrency ?? "bus-serial";
162
+ this.event_handler_concurrency = options.event_handler_concurrency ?? "serial";
163
+ this.event_handler_completion = options.event_handler_completion ?? "all";
164
+ this.event_handler_detect_file_paths = options.event_handler_detect_file_paths ?? true;
165
+ this.event_timeout = options.event_timeout === void 0 ? 60 : options.event_timeout;
166
+ this.event_handler_slow_timeout = options.event_handler_slow_timeout === void 0 ? 30 : options.event_handler_slow_timeout;
167
+ this.event_slow_timeout = options.event_slow_timeout === void 0 ? 300 : options.event_slow_timeout;
168
+ this.runloop_running = false;
169
+ this.handlers = /* @__PURE__ */ new Map();
170
+ this.handlers_by_key = /* @__PURE__ */ new Map();
171
+ this.find_waiters = /* @__PURE__ */ new Set();
172
+ this.event_history = new EventHistory({
173
+ max_history_size: options.max_history_size === void 0 ? 100 : options.max_history_size,
174
+ max_history_drop: options.max_history_drop ?? false
175
+ });
176
+ this.pending_event_queue = [];
177
+ this.in_flight_event_ids = /* @__PURE__ */ new Set();
178
+ this.locks = new LockManager(this);
179
+ this.middlewares = EventBus.normalizeMiddlewares(options.middlewares);
180
+ this.all_instances.add(this);
181
+ this.dispatch = this.dispatch.bind(this);
182
+ this.emit = this.emit.bind(this);
183
+ }
184
+ toString() {
185
+ return `${this.name}#${this.id.slice(-4)}`;
186
+ }
187
+ scheduleMicrotask(fn) {
188
+ if (typeof queueMicrotask === "function") {
189
+ queueMicrotask(fn);
190
+ return;
191
+ }
192
+ void Promise.resolve().then(fn);
193
+ }
194
+ async _runMiddlewareHook(hook, args) {
195
+ if (this.middlewares.length === 0) {
196
+ return;
197
+ }
198
+ for (const middleware of this.middlewares) {
199
+ const callback = middleware[hook];
200
+ if (!callback) {
201
+ continue;
202
+ }
203
+ await callback.apply(middleware, args);
204
+ }
205
+ }
206
+ async onEventChange(event, status) {
207
+ await this._onEventChange(event, status);
208
+ }
209
+ async onEventResultChange(event, result, status) {
210
+ await this._onEventResultChange(event, result, status);
211
+ }
212
+ async _onEventChange(event, status) {
213
+ await this._runMiddlewareHook("onEventChange", [this, event, status]);
214
+ }
215
+ async _onEventResultChange(event, result, status) {
216
+ await this._runMiddlewareHook("onEventResultChange", [this, event, result, status]);
217
+ }
218
+ async _onBusHandlersChange(handler, registered) {
219
+ await this._runMiddlewareHook("onBusHandlersChange", [this, handler, registered]);
220
+ }
221
+ _finalizeEventTimeout(event, pending_entries, timeout_error) {
222
+ const timeout_seconds = timeout_error.timeout_seconds ?? event.event_timeout ?? null;
223
+ event._cancelPendingChildProcessing(timeout_error);
224
+ for (const entry of pending_entries) {
225
+ const result = entry.result;
226
+ if (result.status === "completed") {
227
+ continue;
228
+ }
229
+ if (result.status === "error") {
230
+ continue;
231
+ }
232
+ if (result.status === "started") {
233
+ result._lock?.exitHandlerRun();
234
+ result._releaseQueueJumpPauses();
235
+ const aborted_error = new EventHandlerAbortedError(`Aborted running handler due to event timeout`, {
236
+ event_result: result,
237
+ timeout_seconds,
238
+ cause: timeout_error
239
+ });
240
+ result._markError(aborted_error);
241
+ result._signalAbort(aborted_error);
242
+ continue;
243
+ }
244
+ const cancelled_error = new EventHandlerCancelledError(`Cancelled pending handler due to event timeout`, {
245
+ event_result: result,
246
+ timeout_seconds,
247
+ cause: timeout_error
248
+ });
249
+ result._markError(cancelled_error);
250
+ }
251
+ event.event_pending_bus_count = Math.max(0, event.event_pending_bus_count - 1);
252
+ event._markCompleted();
253
+ }
254
+ _createEventTimeoutError(event, pending_entries, timeout_seconds) {
255
+ const timeout_anchor = pending_entries.find((entry) => entry.result.status === "started") ?? pending_entries.find((entry) => entry.result.status === "pending") ?? pending_entries[0];
256
+ return new EventHandlerTimeoutError(
257
+ `${this.toString()}.on(${event.toString()}, ${timeout_anchor.result.handler.toString()}) timed out after ${timeout_seconds}s`,
258
+ {
259
+ event_result: timeout_anchor.result,
260
+ timeout_seconds
261
+ }
262
+ );
263
+ }
264
+ async _runHandlersWithTimeout(event, pending_entries, event_timeout, fn) {
265
+ try {
266
+ if (event_timeout === null || pending_entries.length === 0) {
267
+ await fn();
268
+ } else {
269
+ await _runWithTimeout(event_timeout, () => this._createEventTimeoutError(event, pending_entries, event_timeout), fn);
270
+ }
271
+ } catch (error) {
272
+ if (error instanceof EventHandlerTimeoutError) {
273
+ this._finalizeEventTimeout(event, pending_entries, error);
274
+ return;
275
+ }
276
+ throw error;
277
+ }
278
+ }
279
+ _markEventCompletedIfNeeded(event) {
280
+ if (event.event_status !== "completed") {
281
+ event.event_pending_bus_count = Math.max(0, event.event_pending_bus_count - 1);
282
+ event._markCompleted(false);
283
+ }
284
+ if (this.event_history.max_history_size !== null && this.event_history.max_history_size > 0 && this.event_history.size > this.event_history.max_history_size) {
285
+ this.event_history.trimEventHistory({
286
+ is_event_complete: (candidate_event) => candidate_event.event_status === "completed",
287
+ on_remove: (candidate_event) => candidate_event._gc(),
288
+ owner_label: this.toString(),
289
+ max_history_size: this.event_history.max_history_size,
290
+ max_history_drop: this.event_history.max_history_drop
291
+ });
292
+ }
293
+ }
294
+ toJSON() {
295
+ const handlers = {};
296
+ for (const [handler_id, handler] of this.handlers.entries()) {
297
+ handlers[handler_id] = handler.toJSON();
298
+ }
299
+ const handlers_by_key = {};
300
+ for (const [key, ids] of this.handlers_by_key.entries()) {
301
+ handlers_by_key[key] = [...ids];
302
+ }
303
+ const event_history = {};
304
+ for (const [event_id, event] of this.event_history.entries()) {
305
+ event_history[event_id] = event.toJSON();
306
+ }
307
+ const pending_event_queue = [];
308
+ for (const event of this.pending_event_queue) {
309
+ const event_id = event.event_id;
310
+ if (!event_history[event_id]) {
311
+ event_history[event_id] = event.toJSON();
312
+ }
313
+ pending_event_queue.push(event_id);
314
+ }
315
+ return {
316
+ id: this.id,
317
+ name: this.name,
318
+ max_history_size: this.event_history.max_history_size,
319
+ max_history_drop: this.event_history.max_history_drop,
320
+ event_concurrency: this.event_concurrency,
321
+ event_timeout: this.event_timeout,
322
+ event_slow_timeout: this.event_slow_timeout,
323
+ event_handler_concurrency: this.event_handler_concurrency,
324
+ event_handler_completion: this.event_handler_completion,
325
+ event_handler_slow_timeout: this.event_handler_slow_timeout,
326
+ event_handler_detect_file_paths: this.event_handler_detect_file_paths,
327
+ handlers,
328
+ handlers_by_key,
329
+ event_history,
330
+ pending_event_queue
331
+ };
332
+ }
333
+ static _stubHandlerFn() {
334
+ return (() => void 0);
335
+ }
336
+ static _upsertHandlerIndex(bus, event_pattern, handler_id) {
337
+ const ids = bus.handlers_by_key.get(event_pattern);
338
+ if (ids) {
339
+ if (!ids.includes(handler_id)) {
340
+ ids.push(handler_id);
341
+ }
342
+ return;
343
+ }
344
+ bus.handlers_by_key.set(event_pattern, [handler_id]);
345
+ }
346
+ static _linkEventResultHandlers(event, bus) {
347
+ for (const [map_key, result] of Array.from(event.event_results.entries())) {
348
+ const handler_id = result.handler_id;
349
+ const existing_handler = bus.handlers.get(handler_id);
350
+ if (existing_handler) {
351
+ result.handler = existing_handler;
352
+ } else {
353
+ const source = result.handler;
354
+ const handler_entry = EventHandler.fromJSON(
355
+ {
356
+ ...source.toJSON(),
357
+ id: handler_id,
358
+ event_pattern: source.event_pattern || event.event_type,
359
+ eventbus_name: source.eventbus_name || bus.name,
360
+ eventbus_id: source.eventbus_id || bus.id
361
+ },
362
+ EventBus._stubHandlerFn()
363
+ );
364
+ bus.handlers.set(handler_entry.id, handler_entry);
365
+ EventBus._upsertHandlerIndex(bus, handler_entry.event_pattern, handler_entry.id);
366
+ result.handler = handler_entry;
367
+ }
368
+ if (map_key !== handler_id) {
369
+ event.event_results.delete(map_key);
370
+ event.event_results.set(handler_id, result);
371
+ }
372
+ }
373
+ }
374
+ static fromJSON(data) {
375
+ if (!data || typeof data !== "object") {
376
+ throw new Error("EventBus.fromJSON(data) requires an object");
377
+ }
378
+ const record = data;
379
+ const name = typeof record.name === "string" ? record.name : "EventBus";
380
+ const options = {};
381
+ if (typeof record.id === "string") options.id = record.id;
382
+ if (typeof record.max_history_size === "number" || record.max_history_size === null) options.max_history_size = record.max_history_size;
383
+ if (typeof record.max_history_drop === "boolean") options.max_history_drop = record.max_history_drop;
384
+ if (record.event_concurrency === "global-serial" || record.event_concurrency === "bus-serial" || record.event_concurrency === "parallel") {
385
+ options.event_concurrency = record.event_concurrency;
386
+ }
387
+ if (typeof record.event_timeout === "number" || record.event_timeout === null) options.event_timeout = record.event_timeout;
388
+ if (typeof record.event_slow_timeout === "number" || record.event_slow_timeout === null)
389
+ options.event_slow_timeout = record.event_slow_timeout;
390
+ if (record.event_handler_concurrency === "serial" || record.event_handler_concurrency === "parallel") {
391
+ options.event_handler_concurrency = record.event_handler_concurrency;
392
+ }
393
+ if (record.event_handler_completion === "all" || record.event_handler_completion === "first") {
394
+ options.event_handler_completion = record.event_handler_completion;
395
+ }
396
+ if (typeof record.event_handler_slow_timeout === "number" || record.event_handler_slow_timeout === null) {
397
+ options.event_handler_slow_timeout = record.event_handler_slow_timeout;
398
+ }
399
+ if (typeof record.event_handler_detect_file_paths === "boolean") {
400
+ options.event_handler_detect_file_paths = record.event_handler_detect_file_paths;
401
+ }
402
+ const bus = new EventBus(name, options);
403
+ if (!record.handlers || typeof record.handlers !== "object" || Array.isArray(record.handlers)) {
404
+ throw new Error("EventBus.fromJSON(data) requires handlers as an id-keyed object");
405
+ }
406
+ for (const [handler_id, payload] of Object.entries(record.handlers)) {
407
+ if (!payload || typeof payload !== "object") {
408
+ continue;
409
+ }
410
+ const parsed = EventHandler.fromJSON(
411
+ {
412
+ ...payload,
413
+ id: typeof payload.id === "string" ? payload.id : handler_id
414
+ },
415
+ EventBus._stubHandlerFn()
416
+ );
417
+ bus.handlers.set(parsed.id, parsed);
418
+ }
419
+ if (!record.handlers_by_key || typeof record.handlers_by_key !== "object" || Array.isArray(record.handlers_by_key)) {
420
+ throw new Error("EventBus.fromJSON(data) requires handlers_by_key as an object");
421
+ }
422
+ bus.handlers_by_key.clear();
423
+ for (const [raw_key, raw_ids] of Object.entries(record.handlers_by_key)) {
424
+ if (!Array.isArray(raw_ids)) {
425
+ continue;
426
+ }
427
+ const ids = raw_ids.filter((id) => typeof id === "string");
428
+ bus.handlers_by_key.set(raw_key, ids);
429
+ }
430
+ if (!record.event_history || typeof record.event_history !== "object" || Array.isArray(record.event_history)) {
431
+ throw new Error("EventBus.fromJSON(data) requires event_history as an id-keyed object");
432
+ }
433
+ for (const [event_id, payload] of Object.entries(record.event_history)) {
434
+ if (!payload || typeof payload !== "object") {
435
+ continue;
436
+ }
437
+ const event = BaseEvent.fromJSON({
438
+ ...payload,
439
+ event_id: typeof payload.event_id === "string" ? payload.event_id : event_id
440
+ });
441
+ event.bus = bus;
442
+ bus.event_history.set(event.event_id, event);
443
+ }
444
+ if (!Array.isArray(record.pending_event_queue)) {
445
+ throw new Error("EventBus.fromJSON(data) requires pending_event_queue as an array of event ids");
446
+ }
447
+ const raw_pending_event_queue = record.pending_event_queue;
448
+ const pending_event_ids = [];
449
+ for (const item of raw_pending_event_queue) {
450
+ if (typeof item === "string") {
451
+ pending_event_ids.push(item);
452
+ }
453
+ }
454
+ bus.pending_event_queue = pending_event_ids.map((event_id) => bus.event_history.get(event_id)).filter((event) => Boolean(event));
455
+ for (const event of bus.event_history.values()) {
456
+ EventBus._linkEventResultHandlers(event, bus);
457
+ }
458
+ bus.in_flight_event_ids.clear();
459
+ bus.runloop_running = false;
460
+ bus.locks.clear();
461
+ bus.find_waiters.clear();
462
+ return bus;
463
+ }
464
+ get label() {
465
+ return `${this.name}#${this.id.slice(-4)}`;
466
+ }
467
+ removeEventFromPendingQueue(event) {
468
+ const original_event = event._event_original ?? event;
469
+ let removed_count = 0;
470
+ for (let index = this.pending_event_queue.length - 1; index >= 0; index -= 1) {
471
+ const queued_event = this.pending_event_queue[index];
472
+ const queued_original = queued_event._event_original ?? queued_event;
473
+ if (queued_original.event_id !== original_event.event_id) {
474
+ continue;
475
+ }
476
+ this.pending_event_queue.splice(index, 1);
477
+ removed_count += 1;
478
+ }
479
+ return removed_count;
480
+ }
481
+ isEventInFlightOrQueued(event_id) {
482
+ if (this.in_flight_event_ids.has(event_id)) {
483
+ return true;
484
+ }
485
+ for (const queued_event of this.pending_event_queue) {
486
+ const queued_original = queued_event._event_original ?? queued_event;
487
+ if (queued_original.event_id === event_id) {
488
+ return true;
489
+ }
490
+ }
491
+ return false;
492
+ }
493
+ removeEventFromHistory(event_id) {
494
+ return this.event_history.delete(event_id);
495
+ }
496
+ // destroy the event bus and all its state to allow for garbage collection
497
+ destroy() {
498
+ this.all_instances.discard(this);
499
+ this.handlers.clear();
500
+ this.handlers_by_key.clear();
501
+ for (const event of this.event_history.values()) {
502
+ event._gc();
503
+ }
504
+ this.event_history.clear();
505
+ this.pending_event_queue.length = 0;
506
+ this.in_flight_event_ids.clear();
507
+ this.find_waiters.clear();
508
+ this.locks.clear();
509
+ }
510
+ on(event_pattern, handler, options = {}) {
511
+ const normalized_key = normalizeEventPattern(event_pattern);
512
+ const handler_name = handler.name || "anonymous";
513
+ const handler_entry = new EventHandler({
514
+ handler,
515
+ handler_name,
516
+ handler_registered_at: monotonicDatetime(),
517
+ event_pattern: normalized_key,
518
+ eventbus_name: this.name,
519
+ eventbus_id: this.id,
520
+ ...options
521
+ });
522
+ if (this.event_handler_detect_file_paths) {
523
+ handler_entry._detectHandlerFilePath();
524
+ }
525
+ this.handlers.set(handler_entry.id, handler_entry);
526
+ const ids = this.handlers_by_key.get(handler_entry.event_pattern);
527
+ if (ids) ids.push(handler_entry.id);
528
+ else this.handlers_by_key.set(handler_entry.event_pattern, [handler_entry.id]);
529
+ this.scheduleMicrotask(() => {
530
+ void this._onBusHandlersChange(handler_entry, true);
531
+ });
532
+ return handler_entry;
533
+ }
534
+ off(event_pattern, handler) {
535
+ const normalized_key = normalizeEventPattern(event_pattern);
536
+ if (typeof handler === "object" && handler instanceof EventHandler && handler.id !== void 0) {
537
+ handler = handler.id;
538
+ }
539
+ const match_by_id = typeof handler === "string";
540
+ for (const entry of this.handlers.values()) {
541
+ if (entry.event_pattern !== normalized_key) {
542
+ continue;
543
+ }
544
+ const handler_id = entry.id;
545
+ if (handler === void 0 || (match_by_id ? handler_id === handler : entry.handler === handler)) {
546
+ this.handlers.delete(handler_id);
547
+ this._removeIndexedHandler(entry.event_pattern, handler_id);
548
+ this.scheduleMicrotask(() => {
549
+ void this._onBusHandlersChange(entry, false);
550
+ });
551
+ }
552
+ }
553
+ }
554
+ emit(event) {
555
+ const original_event = event._event_original ?? event;
556
+ if (!original_event.bus) {
557
+ original_event.bus = this;
558
+ }
559
+ if (!Array.isArray(original_event.event_path)) {
560
+ original_event.event_path = [];
561
+ }
562
+ if (original_event._getDispatchContext() === void 0) {
563
+ original_event._setDispatchContext(captureAsyncContext());
564
+ }
565
+ if (original_event.event_path.includes(this.label) || this._hasProcessedEvent(original_event)) {
566
+ return this._getEventProxyScopedToThisBus(original_event);
567
+ }
568
+ if (!original_event.event_path.includes(this.label)) {
569
+ original_event.event_path.push(this.label);
570
+ }
571
+ if (!original_event.event_parent_id && !original_event.event_emitted_by_handler_id) {
572
+ this._resolveImplicitParentHandlerResult()?._linkEmittedChildEvent(original_event);
573
+ }
574
+ if (original_event.event_parent_id && original_event.event_emitted_by_handler_id) {
575
+ const parent_result = original_event.event_parent?.event_results.get(original_event.event_emitted_by_handler_id);
576
+ if (parent_result) {
577
+ parent_result._linkEmittedChildEvent(original_event);
578
+ }
579
+ }
580
+ if (this.event_history.max_history_size !== null && this.event_history.max_history_size > 0 && !this.event_history.max_history_drop && this.event_history.size >= this.event_history.max_history_size) {
581
+ throw new Error(
582
+ `${this.toString()}.emit(${original_event.event_type}) rejected: history limit reached (${this.event_history.size}/${this.event_history.max_history_size}); set event_history.max_history_drop=true to drop old history instead.`
583
+ );
584
+ }
585
+ this.event_history.addEvent(original_event);
586
+ this.event_history.trimEventHistory({
587
+ is_event_complete: (candidate_event) => candidate_event.event_status === "completed",
588
+ on_remove: (candidate_event) => candidate_event._gc(),
589
+ owner_label: this.toString(),
590
+ max_history_size: this.event_history.max_history_size,
591
+ max_history_drop: this.event_history.max_history_drop
592
+ });
593
+ this._resolveFindWaiters(original_event);
594
+ original_event.event_pending_bus_count += 1;
595
+ this.pending_event_queue.push(original_event);
596
+ this._startRunloop();
597
+ return this._getEventProxyScopedToThisBus(original_event);
598
+ }
599
+ // alias for emit
600
+ dispatch(event) {
601
+ return this.emit(event);
602
+ }
603
+ async find(event_pattern, where_or_options = {}, maybe_options = {}) {
604
+ const where = typeof where_or_options === "function" ? where_or_options : () => true;
605
+ const options = typeof where_or_options === "function" ? maybe_options : where_or_options;
606
+ const match = await this.event_history.find(event_pattern, where, {
607
+ ...options,
608
+ event_is_child_of: (event, ancestor) => this.eventIsChildOf(event, ancestor),
609
+ wait_for_future_match: (normalized_event_pattern, matches, future) => this._waitForFutureMatch(normalized_event_pattern, matches, future)
610
+ });
611
+ if (!match) {
612
+ return null;
613
+ }
614
+ return this._getEventProxyScopedToThisBus(match);
615
+ }
616
+ async _waitForFutureMatch(event_pattern, matches, future) {
617
+ if (future === false) {
618
+ return null;
619
+ }
620
+ return await new Promise((resolve) => {
621
+ const waiter = {
622
+ event_pattern,
623
+ matches,
624
+ resolve: (event) => resolve(event)
625
+ };
626
+ if (future !== true) {
627
+ const timeout_ms = Math.max(0, Number(future)) * 1e3;
628
+ waiter.timeout_id = setTimeout(() => {
629
+ this.find_waiters.delete(waiter);
630
+ resolve(null);
631
+ }, timeout_ms);
632
+ }
633
+ this.find_waiters.add(waiter);
634
+ });
635
+ }
636
+ async waitUntilIdle(timeout = null) {
637
+ return await this.locks.waitForIdle(timeout);
638
+ }
639
+ // Weak idle check: only checks if handlers are idle, doesnt check that the queue is empty
640
+ isIdle() {
641
+ for (const event of this.event_history.values()) {
642
+ for (const result of event.event_results.values()) {
643
+ if (result.eventbus_id !== this.id) {
644
+ continue;
645
+ }
646
+ if (result.status === "pending" || result.status === "started") {
647
+ return false;
648
+ }
649
+ }
650
+ }
651
+ return true;
652
+ }
653
+ // Stronger idle check: no queued work, no in-flight processing, _runloop not
654
+ // active, and no handlers pending/running for this bus.
655
+ isIdleAndQueueEmpty() {
656
+ return this.pending_event_queue.length === 0 && this.in_flight_event_ids.size === 0 && this.isIdle() && !this.runloop_running;
657
+ }
658
+ eventIsChildOf(child_event, parent_event) {
659
+ if (child_event.event_id === parent_event.event_id) {
660
+ return false;
661
+ }
662
+ let current_parent_id = child_event.event_parent_id;
663
+ while (current_parent_id) {
664
+ if (current_parent_id === parent_event.event_id) {
665
+ return true;
666
+ }
667
+ const parent = this.event_history.get(current_parent_id);
668
+ if (!parent) {
669
+ return false;
670
+ }
671
+ current_parent_id = parent.event_parent_id;
672
+ }
673
+ return false;
674
+ }
675
+ eventIsParentOf(parent_event, child_event) {
676
+ return this.eventIsChildOf(child_event, parent_event);
677
+ }
678
+ // return a full detailed tree diagram of all events and results on this bus
679
+ logTree() {
680
+ return logTree(this);
681
+ }
682
+ // Resolve an event id from this bus first, then across all known buses.
683
+ findEventById(event_id) {
684
+ return this.event_history.get(event_id) ?? this.all_instances.findEventById(event_id);
685
+ }
686
+ // Walk up the parent event chain to find an in-flight ancestor handler result.
687
+ // Returns the result if found, null otherwise. Used by _processEventImmediately to detect
688
+ // cross-bus queue-jump scenarios where the calling handler is on a different bus.
689
+ _getParentEventResultAcrossAllBuses(event) {
690
+ const original = event._event_original ?? event;
691
+ let current_parent_id = original.event_parent_id;
692
+ let current_handler_id = original.event_emitted_by_handler_id;
693
+ while (current_handler_id && current_parent_id) {
694
+ const parent = this.all_instances.findEventById(current_parent_id);
695
+ if (!parent) break;
696
+ const handler_result = parent.event_results.get(current_handler_id);
697
+ if (handler_result && handler_result.status === "started") return handler_result;
698
+ current_parent_id = parent.event_parent_id;
699
+ current_handler_id = parent.event_emitted_by_handler_id;
700
+ }
701
+ return null;
702
+ }
703
+ _startRunloop() {
704
+ if (this.runloop_running) {
705
+ return;
706
+ }
707
+ this.runloop_running = true;
708
+ this.scheduleMicrotask(() => {
709
+ void this._runloop();
710
+ });
711
+ }
712
+ // schedule the processing of an event on the event bus by its normal _runloop
713
+ // optionally using a pre-acquired lock if we're inside handling of a parent event
714
+ async _processEvent(event, options = {}) {
715
+ let pending_entries = [];
716
+ try {
717
+ if (this._hasProcessedEvent(event)) {
718
+ return;
719
+ }
720
+ const scoped_event = this._getEventProxyScopedToThisBus(event);
721
+ await this._onEventChange(scoped_event, "pending");
722
+ event._markStarted();
723
+ pending_entries = event._createPendingHandlerResults(this);
724
+ const resolved_event_timeout = event.event_timeout ?? this.event_timeout;
725
+ if (this.middlewares.length > 0) {
726
+ for (const entry of pending_entries) {
727
+ await this._onEventResultChange(scoped_event, entry.result, "pending");
728
+ }
729
+ }
730
+ await this.locks._runWithEventLock(
731
+ event,
732
+ () => this._runHandlersWithTimeout(
733
+ event,
734
+ pending_entries,
735
+ resolved_event_timeout,
736
+ () => _runWithSlowMonitor(event._createSlowEventWarningTimer(), () => scoped_event._runHandlers(pending_entries))
737
+ ),
738
+ options
739
+ );
740
+ this._markEventCompletedIfNeeded(event);
741
+ } finally {
742
+ if (options.pre_acquired_lock) {
743
+ options.pre_acquired_lock.release();
744
+ }
745
+ this.in_flight_event_ids.delete(event.event_id);
746
+ this.locks._notifyIdleListeners();
747
+ }
748
+ }
749
+ // Called when a handler does `await child.done()` — processes the child event
750
+ // immediately ("queue-jump") instead of waiting for the _runloop to pick it up.
751
+ //
752
+ // Yield-and-reacquire: if the calling handler holds a handler concurrency lock,
753
+ // we temporarily release it so child handlers on the same bus can acquire it
754
+ // (preventing deadlock for serial handler mode). We re-acquire after
755
+ // the child completes so the parent handler can continue with the lock held.
756
+ async _processEventImmediately(event, handler_result) {
757
+ const original_event = event._event_original ?? event;
758
+ const proxy_result = handler_result?.status === "started" ? handler_result : void 0;
759
+ const currently_active_event_result = proxy_result ?? this.locks._getActiveHandlerResult() ?? this._getParentEventResultAcrossAllBuses(original_event) ?? void 0;
760
+ if (!currently_active_event_result) {
761
+ const queue_index = this.pending_event_queue.indexOf(original_event);
762
+ const can_process_now = queue_index === 0 && !this.locks._isPaused() && !this.in_flight_event_ids.has(original_event.event_id) && !this._hasProcessedEvent(original_event);
763
+ if (can_process_now) {
764
+ const event_lock = this.locks.getLockForEvent(original_event);
765
+ let pre_acquired_lock = null;
766
+ if (event_lock) {
767
+ await event_lock.acquire();
768
+ pre_acquired_lock = event_lock;
769
+ }
770
+ const queue_head = this.pending_event_queue[0];
771
+ const queue_head_original = queue_head?._event_original ?? queue_head;
772
+ const still_can_process_now = queue_head_original === original_event && !this.locks._isPaused() && !this.in_flight_event_ids.has(original_event.event_id) && !this._hasProcessedEvent(original_event);
773
+ if (still_can_process_now) {
774
+ this.pending_event_queue.shift();
775
+ this.in_flight_event_ids.add(original_event.event_id);
776
+ await this._processEvent(original_event, {
777
+ bypass_event_locks: true,
778
+ pre_acquired_lock
779
+ });
780
+ if (original_event.event_status !== "completed") {
781
+ await original_event.eventCompleted();
782
+ }
783
+ return event;
784
+ }
785
+ if (pre_acquired_lock) {
786
+ pre_acquired_lock.release();
787
+ }
788
+ }
789
+ await original_event.eventCompleted();
790
+ return event;
791
+ }
792
+ currently_active_event_result._ensureQueueJumpPause(this);
793
+ if (original_event.event_status === "completed") {
794
+ return event;
795
+ }
796
+ if (currently_active_event_result._lock) {
797
+ await currently_active_event_result._lock.runQueueJump(this._processEventImmediatelyAcrossBuses.bind(this, original_event));
798
+ return event;
799
+ }
800
+ await this._processEventImmediatelyAcrossBuses(original_event);
801
+ return event;
802
+ }
803
+ // Processes a queue-jumped event across all buses that have it emitted.
804
+ // Called from _processEventImmediately after the parent handler's lock has been yielded.
805
+ async _processEventImmediatelyAcrossBuses(event) {
806
+ const ordered = [];
807
+ const seen = /* @__PURE__ */ new Set();
808
+ const event_path = Array.isArray(event.event_path) ? event.event_path : [];
809
+ for (const label of event_path) {
810
+ for (const bus of this.all_instances) {
811
+ if (bus.label !== label) {
812
+ continue;
813
+ }
814
+ if (!bus.event_history.has(event.event_id)) {
815
+ continue;
816
+ }
817
+ if (bus._hasProcessedEvent(event)) {
818
+ continue;
819
+ }
820
+ if (!seen.has(bus)) {
821
+ ordered.push(bus);
822
+ seen.add(bus);
823
+ }
824
+ }
825
+ }
826
+ if (!seen.has(this) && this.event_history.has(event.event_id)) {
827
+ ordered.push(this);
828
+ }
829
+ if (ordered.length === 0) {
830
+ await event.eventCompleted();
831
+ return;
832
+ }
833
+ const initiating_event_lock = this.locks.getLockForEvent(event);
834
+ const pause_releases = [];
835
+ try {
836
+ for (const bus of ordered) {
837
+ if (bus !== this) {
838
+ pause_releases.push(bus.locks._requestRunloopPause());
839
+ }
840
+ }
841
+ for (const bus of ordered) {
842
+ const index = bus.pending_event_queue.indexOf(event);
843
+ if (index >= 0) {
844
+ bus.pending_event_queue.splice(index, 1);
845
+ }
846
+ if (bus._hasProcessedEvent(event)) {
847
+ continue;
848
+ }
849
+ if (bus.in_flight_event_ids.has(event.event_id)) {
850
+ continue;
851
+ }
852
+ bus.in_flight_event_ids.add(event.event_id);
853
+ const bus_event_lock = bus.locks.getLockForEvent(event);
854
+ const should_bypass_event_lock = bus === this || initiating_event_lock !== null && bus_event_lock === initiating_event_lock;
855
+ await bus._processEvent(event, {
856
+ bypass_event_locks: should_bypass_event_lock
857
+ });
858
+ }
859
+ if (event.event_status !== "completed") {
860
+ await event.eventCompleted();
861
+ }
862
+ } finally {
863
+ for (const release of pause_releases) {
864
+ release();
865
+ }
866
+ }
867
+ }
868
+ async _runloop() {
869
+ for (; ; ) {
870
+ while (this.pending_event_queue.length > 0) {
871
+ await Promise.resolve();
872
+ if (this.locks._isPaused()) {
873
+ await this.locks._waitUntilRunloopResumed();
874
+ continue;
875
+ }
876
+ const next_event = this.pending_event_queue[0];
877
+ if (!next_event) {
878
+ continue;
879
+ }
880
+ const original_event = next_event._event_original ?? next_event;
881
+ if (this._hasProcessedEvent(original_event)) {
882
+ this.pending_event_queue.shift();
883
+ continue;
884
+ }
885
+ let pre_acquired_lock = null;
886
+ const event_lock = this.locks.getLockForEvent(original_event);
887
+ if (event_lock) {
888
+ await event_lock.acquire();
889
+ pre_acquired_lock = event_lock;
890
+ }
891
+ const current_head = this.pending_event_queue[0];
892
+ const current_head_original = current_head?._event_original ?? current_head;
893
+ if (current_head_original !== original_event) {
894
+ if (pre_acquired_lock) {
895
+ pre_acquired_lock.release();
896
+ }
897
+ continue;
898
+ }
899
+ this.pending_event_queue.shift();
900
+ if (this.in_flight_event_ids.has(original_event.event_id)) {
901
+ if (pre_acquired_lock) {
902
+ pre_acquired_lock.release();
903
+ }
904
+ continue;
905
+ }
906
+ this.in_flight_event_ids.add(original_event.event_id);
907
+ void this._processEvent(original_event, {
908
+ bypass_event_locks: true,
909
+ pre_acquired_lock
910
+ });
911
+ await Promise.resolve();
912
+ }
913
+ this.runloop_running = false;
914
+ if (this.pending_event_queue.length > 0) {
915
+ this._startRunloop();
916
+ return;
917
+ }
918
+ this.locks._notifyIdleListeners();
919
+ return;
920
+ }
921
+ }
922
+ // check if an event has been processed (and completed) by this bus
923
+ _hasProcessedEvent(event) {
924
+ const results = Array.from(event.event_results.values()).filter((result) => result.eventbus_id === this.id);
925
+ if (results.length === 0) {
926
+ return false;
927
+ }
928
+ return results.every((result) => result.status === "completed" || result.status === "error");
929
+ }
930
+ _resolveImplicitParentHandlerResult() {
931
+ const active_on_target_bus = this.locks._getActiveHandlerResults().filter((result) => result.status === "started");
932
+ if (active_on_target_bus.length === 1) {
933
+ return active_on_target_bus[0];
934
+ }
935
+ const active_globally = [];
936
+ for (const bus of this.all_instances) {
937
+ for (const result of bus.locks._getActiveHandlerResults()) {
938
+ if (result.status === "started") {
939
+ active_globally.push(result);
940
+ }
941
+ }
942
+ }
943
+ if (active_globally.length === 1) {
944
+ return active_globally[0];
945
+ }
946
+ return null;
947
+ }
948
+ // get a proxy wrapper around an Event that will automatically link emitted child events to this bus and handler
949
+ // proxy is what gets passed into the handler, if handler does event.bus.emit(...) to dispatch child events,
950
+ // the proxy auto-sets event.parent_event_id and event.event_emitted_by_handler_id
951
+ _getEventProxyScopedToThisBus(event, handler_result) {
952
+ const original_event = event._event_original ?? event;
953
+ const bus = this;
954
+ const parent_event_id = original_event.event_id;
955
+ const bus_proxy = new Proxy(bus, {
956
+ get(target, prop, receiver) {
957
+ if (prop === "_processEventImmediately") {
958
+ const runner = Reflect.get(target, prop, receiver);
959
+ const process_event_immediately = (child_event) => {
960
+ return runner.call(target, child_event, handler_result);
961
+ };
962
+ return process_event_immediately;
963
+ }
964
+ if (prop === "dispatch" || prop === "emit") {
965
+ const emit_child_event = (child_event) => {
966
+ const original_child = child_event._event_original ?? child_event;
967
+ if (handler_result) {
968
+ handler_result._linkEmittedChildEvent(original_child);
969
+ } else if (!original_child.event_parent_id && original_child.event_id !== parent_event_id) {
970
+ original_child.event_parent_id = parent_event_id;
971
+ }
972
+ const dispatcher = Reflect.get(target, prop, receiver);
973
+ const dispatched = dispatcher.call(target, original_child);
974
+ return target._getEventProxyScopedToThisBus(dispatched, handler_result);
975
+ };
976
+ return emit_child_event;
977
+ }
978
+ return Reflect.get(target, prop, receiver);
979
+ }
980
+ });
981
+ const scoped = new Proxy(original_event, {
982
+ get(target, prop, receiver) {
983
+ if (prop === "bus") {
984
+ return bus_proxy;
985
+ }
986
+ if (prop === "_event_original") {
987
+ return target;
988
+ }
989
+ return Reflect.get(target, prop, receiver);
990
+ },
991
+ set(target, prop, value) {
992
+ if (prop === "bus") {
993
+ return true;
994
+ }
995
+ return Reflect.set(target, prop, value, target);
996
+ },
997
+ has(target, prop) {
998
+ if (prop === "bus") {
999
+ return true;
1000
+ }
1001
+ if (prop === "_event_original") {
1002
+ return true;
1003
+ }
1004
+ return Reflect.has(target, prop);
1005
+ }
1006
+ });
1007
+ return scoped;
1008
+ }
1009
+ _resolveFindWaiters(event) {
1010
+ for (const waiter of Array.from(this.find_waiters)) {
1011
+ if (waiter.event_pattern !== "*" && event.event_type !== waiter.event_pattern || !waiter.matches(event)) {
1012
+ continue;
1013
+ }
1014
+ if (waiter.timeout_id) {
1015
+ clearTimeout(waiter.timeout_id);
1016
+ }
1017
+ this.find_waiters.delete(waiter);
1018
+ waiter.resolve(event);
1019
+ }
1020
+ }
1021
+ _getHandlersForEvent(event) {
1022
+ const handlers = [];
1023
+ for (const key of [event.event_type, "*"]) {
1024
+ const ids = this.handlers_by_key.get(key);
1025
+ if (!ids) continue;
1026
+ for (const id of ids) {
1027
+ const entry = this.handlers.get(id);
1028
+ if (entry) handlers.push(entry);
1029
+ }
1030
+ }
1031
+ return handlers;
1032
+ }
1033
+ _removeIndexedHandler(event_pattern, handler_id) {
1034
+ const ids = this.handlers_by_key.get(event_pattern);
1035
+ if (!ids) return;
1036
+ const idx = ids.indexOf(handler_id);
1037
+ if (idx < 0) return;
1038
+ ids.splice(idx, 1);
1039
+ if (ids.length === 0) this.handlers_by_key.delete(event_pattern);
1040
+ }
1041
+ }
1042
+ export {
1043
+ EventBus,
1044
+ GlobalEventBusRegistry
1045
+ };
1046
+ //# sourceMappingURL=event_bus.js.map