bubus 1.8.1 → 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 (67) hide show
  1. package/README.md +510 -75
  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 +32 -16559
  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 +1 -1
  47. package/dist/types/base_event.d.ts +96 -79
  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 +88 -41
  55. package/dist/types/event_handler.d.ts +47 -18
  56. package/dist/types/event_history.d.ts +45 -0
  57. package/dist/types/event_result.d.ts +37 -33
  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 +10 -1
  61. package/dist/types/lock_manager.d.ts +27 -18
  62. package/dist/types/logging.d.ts +4 -1
  63. package/dist/types/middlewares.d.ts +13 -0
  64. package/dist/types/optional_deps.d.ts +3 -0
  65. package/dist/types/timing.d.ts +3 -0
  66. package/dist/types/types.d.ts +18 -7
  67. package/package.json +25 -11
@@ -0,0 +1,825 @@
1
+ import { z } from "zod";
2
+ import { v7 as uuidv7 } from "uuid";
3
+ import { EventResult } from "./event_result.js";
4
+ import { EventHandler, EventHandlerAbortedError, EventHandlerCancelledError, EventHandlerTimeoutError } from "./event_handler.js";
5
+ import {
6
+ AsyncLock,
7
+ EVENT_CONCURRENCY_MODES,
8
+ EVENT_HANDLER_CONCURRENCY_MODES,
9
+ EVENT_HANDLER_COMPLETION_MODES,
10
+ withResolvers
11
+ } from "./lock_manager.js";
12
+ import { _runWithTimeout } from "./timing.js";
13
+ import { extractZodShape, normalizeEventResultType, toJsonSchema } from "./types.js";
14
+ import { monotonicDatetime } from "./helpers.js";
15
+ const RESERVED_USER_EVENT_FIELDS = /* @__PURE__ */ new Set(["bus", "first", "toString", "toJSON", "fromJSON"]);
16
+ function assertNoReservedUserEventFields(data, context) {
17
+ for (const field_name of RESERVED_USER_EVENT_FIELDS) {
18
+ if (Object.prototype.hasOwnProperty.call(data, field_name)) {
19
+ throw new Error(`${context} field "${field_name}" is reserved for EventBus runtime context and cannot be set in event payload`);
20
+ }
21
+ }
22
+ }
23
+ function assertNoUnknownEventPrefixedFields(data, context) {
24
+ for (const field_name of Object.keys(data)) {
25
+ if (field_name.startsWith("event_") && !KNOWN_BASE_EVENT_FIELDS.has(field_name)) {
26
+ throw new Error(`${context} field "${field_name}" starts with "event_" but is not a recognized BaseEvent field`);
27
+ }
28
+ }
29
+ }
30
+ function assertNoModelPrefixedFields(data, context) {
31
+ for (const field_name of Object.keys(data)) {
32
+ if (field_name.startsWith("model_")) {
33
+ throw new Error(`${context} field "${field_name}" starts with "model_" and is reserved for model internals`);
34
+ }
35
+ }
36
+ }
37
+ function compareIsoDatetime(left, right) {
38
+ const left_value = left ?? "";
39
+ const right_value = right ?? "";
40
+ if (left_value === right_value) {
41
+ return 0;
42
+ }
43
+ return left_value < right_value ? -1 : 1;
44
+ }
45
+ const BaseEventSchema = z.object({
46
+ event_id: z.string().uuid(),
47
+ event_created_at: z.string().datetime(),
48
+ event_type: z.string(),
49
+ event_version: z.string().default("0.0.1"),
50
+ event_timeout: z.number().positive().nullable(),
51
+ event_slow_timeout: z.number().positive().nullable().optional(),
52
+ event_handler_timeout: z.number().positive().nullable().optional(),
53
+ event_handler_slow_timeout: z.number().positive().nullable().optional(),
54
+ event_parent_id: z.string().uuid().nullable().optional(),
55
+ event_path: z.array(z.string()).optional(),
56
+ event_result_type: z.unknown().optional(),
57
+ event_emitted_by_handler_id: z.string().uuid().nullable().optional(),
58
+ event_pending_bus_count: z.number().nonnegative().optional(),
59
+ event_status: z.enum(["pending", "started", "completed"]).optional(),
60
+ event_started_at: z.string().datetime().nullable().optional(),
61
+ event_completed_at: z.string().datetime().nullable().optional(),
62
+ event_results: z.array(z.unknown()).optional(),
63
+ event_concurrency: z.enum(EVENT_CONCURRENCY_MODES).nullable().optional(),
64
+ event_handler_concurrency: z.enum(EVENT_HANDLER_CONCURRENCY_MODES).nullable().optional(),
65
+ event_handler_completion: z.enum(EVENT_HANDLER_COMPLETION_MODES).nullable().optional()
66
+ }).loose();
67
+ const KNOWN_BASE_EVENT_FIELDS = new Set(Object.keys(BaseEventSchema.shape));
68
+ const EVENT_CLASS_DEFAULTS = /* @__PURE__ */ new WeakMap();
69
+ const ROOT_EVENTBUS_ID = "00000000-0000-0000-0000-000000000000";
70
+ class BaseEvent {
71
+ // event metadata fields
72
+ event_id;
73
+ // unique uuidv7 identifier for the event
74
+ event_created_at;
75
+ event_type;
76
+ // should match the class name of the event, e.g. BaseEvent.extend("MyEvent").event_type === "MyEvent"
77
+ event_version;
78
+ // event schema/version tag managed by callers for migration-friendly payload handling
79
+ event_timeout;
80
+ // maximum time in seconds that the event is allowed to run before it is aborted
81
+ event_slow_timeout;
82
+ // optional per-event slow warning threshold in seconds
83
+ event_handler_timeout;
84
+ // optional per-event handler timeout override in seconds
85
+ event_handler_slow_timeout;
86
+ // optional per-event slow handler warning threshold in seconds
87
+ event_parent_id;
88
+ // id of the parent event that triggered this event, if this event was emitted during handling of another event, else null
89
+ event_path;
90
+ // list of bus labels (name#id) that the event has been dispatched to, including the current bus
91
+ event_result_type;
92
+ // optional zod schema to enforce the shape of return values from handlers
93
+ event_results;
94
+ // map of handler ids to EventResult objects for the event
95
+ event_emitted_by_handler_id;
96
+ // if event was emitted inside a handler while it was running, this is set to the enclosing handler's handler id, else null
97
+ event_pending_bus_count;
98
+ // number of buses that have accepted this event and not yet finished processing or removed it from their queues (for queue-jump processing)
99
+ event_status;
100
+ // processing status of the event as a whole, no separate 'error' state because events can not error, only individual handlers can
101
+ event_started_at;
102
+ event_completed_at;
103
+ event_concurrency;
104
+ // concurrency mode for the event as a whole in relation to other events
105
+ event_handler_concurrency;
106
+ // concurrency mode for the handlers within the event
107
+ event_handler_completion;
108
+ // completion strategy: 'all' (default) waits for every handler, 'first' returns earliest non-undefined result and cancels the rest
109
+ static event_type;
110
+ // class name of the event, e.g. BaseEvent.extend("MyEvent").event_type === "MyEvent"
111
+ static event_version = "0.0.1";
112
+ static schema = BaseEventSchema;
113
+ // zod schema for the event data fields, used to parse and validate event data when creating a new event
114
+ // 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
117
+ _event_original;
118
+ // underlying event object that was dispatched, if this is a bus-scoped proxy wrapping it
119
+ _event_dispatch_context;
120
+ // captured AsyncLocalStorage context at dispatch site, used to restore that context when running handlers
121
+ _event_completed_signal;
122
+ _lock_for_event_handler;
123
+ get event_bus() {
124
+ return this.bus;
125
+ }
126
+ constructor(data = {}) {
127
+ assertNoReservedUserEventFields(data, "BaseEvent");
128
+ assertNoUnknownEventPrefixedFields(data, "BaseEvent");
129
+ assertNoModelPrefixedFields(data, "BaseEvent");
130
+ const ctor = this.constructor;
131
+ const ctor_defaults = EVENT_CLASS_DEFAULTS.get(ctor) ?? {};
132
+ const merged_data = {
133
+ ...ctor_defaults,
134
+ ...data
135
+ };
136
+ const event_type = merged_data.event_type ?? ctor.event_type ?? ctor.name;
137
+ const event_version = merged_data.event_version ?? ctor.event_version ?? "0.0.1";
138
+ const raw_event_result_type = merged_data.event_result_type ?? ctor.event_result_type;
139
+ const event_result_type = normalizeEventResultType(raw_event_result_type);
140
+ const event_id = merged_data.event_id ?? uuidv7();
141
+ const event_created_at = monotonicDatetime(merged_data.event_created_at);
142
+ const event_timeout = merged_data.event_timeout ?? null;
143
+ const base_data = {
144
+ ...merged_data,
145
+ event_id,
146
+ event_created_at,
147
+ event_type,
148
+ event_version,
149
+ event_timeout,
150
+ event_result_type
151
+ };
152
+ const schema = ctor.schema ?? BaseEventSchema;
153
+ const parsed = schema.parse(base_data);
154
+ Object.assign(this, parsed);
155
+ const parsed_path = parsed.event_path;
156
+ this.event_path = Array.isArray(parsed_path) ? [...parsed_path] : [];
157
+ this.event_results = hydrateEventResults(this, parsed.event_results);
158
+ this.event_pending_bus_count = typeof parsed.event_pending_bus_count === "number" ? Math.max(0, Number(parsed.event_pending_bus_count)) : 0;
159
+ const parsed_status = parsed.event_status;
160
+ this.event_status = parsed_status === "pending" || parsed_status === "started" || parsed_status === "completed" ? parsed_status : "pending";
161
+ this.event_started_at = parsed.event_started_at === null || parsed.event_started_at === void 0 ? null : monotonicDatetime(parsed.event_started_at);
162
+ this.event_completed_at = parsed.event_completed_at === null || parsed.event_completed_at === void 0 ? null : monotonicDatetime(parsed.event_completed_at);
163
+ this.event_parent_id = typeof parsed.event_parent_id === "string" ? parsed.event_parent_id : null;
164
+ this.event_emitted_by_handler_id = typeof parsed.event_emitted_by_handler_id === "string" ? parsed.event_emitted_by_handler_id : null;
165
+ this.event_result_type = event_result_type;
166
+ this._event_completed_signal = null;
167
+ this._lock_for_event_handler = null;
168
+ this._event_dispatch_context = void 0;
169
+ }
170
+ // "MyEvent#a48f"
171
+ toString() {
172
+ return `${this.event_type}#${this.event_id.slice(-4)}`;
173
+ }
174
+ static extend(event_type, shape = {}) {
175
+ const raw_shape = shape;
176
+ assertNoReservedUserEventFields(raw_shape, `BaseEvent.extend(${event_type})`);
177
+ assertNoUnknownEventPrefixedFields(raw_shape, `BaseEvent.extend(${event_type})`);
178
+ assertNoModelPrefixedFields(raw_shape, `BaseEvent.extend(${event_type})`);
179
+ const raw_event_result_type = raw_shape.event_result_type;
180
+ const event_result_type = normalizeEventResultType(raw_event_result_type);
181
+ const event_version = typeof raw_shape.event_version === "string" ? raw_shape.event_version : void 0;
182
+ const event_defaults = Object.fromEntries(
183
+ Object.entries(raw_shape).filter(
184
+ ([key, value]) => key !== "event_result_type" && key !== "event_version" && !(value instanceof z.ZodType)
185
+ )
186
+ );
187
+ const zod_shape = extractZodShape(raw_shape);
188
+ const full_schema = BaseEventSchema.extend(zod_shape);
189
+ class ExtendedEvent extends BaseEvent {
190
+ static schema = full_schema;
191
+ static event_type = event_type;
192
+ static event_version = event_version ?? BaseEvent.event_version;
193
+ static event_result_type = event_result_type;
194
+ constructor(data) {
195
+ super(data);
196
+ }
197
+ }
198
+ function EventFactory(data) {
199
+ return new ExtendedEvent(data);
200
+ }
201
+ EventFactory.schema = full_schema;
202
+ EventFactory.event_type = event_type;
203
+ EventFactory.event_version = event_version ?? BaseEvent.event_version;
204
+ EventFactory.event_result_type = event_result_type;
205
+ EventFactory.class = ExtendedEvent;
206
+ EventFactory.fromJSON = (data) => ExtendedEvent.fromJSON(data);
207
+ EventFactory.prototype = ExtendedEvent.prototype;
208
+ EVENT_CLASS_DEFAULTS.set(ExtendedEvent, event_defaults);
209
+ return EventFactory;
210
+ }
211
+ static fromJSON(data) {
212
+ if (!data || typeof data !== "object") {
213
+ const schema = this.schema ?? BaseEventSchema;
214
+ const parsed = schema.parse(data);
215
+ return new this(parsed);
216
+ }
217
+ const record = { ...data };
218
+ if (record.event_result_type !== void 0 && record.event_result_type !== null) {
219
+ record.event_result_type = normalizeEventResultType(record.event_result_type);
220
+ }
221
+ return new this(record);
222
+ }
223
+ static toJSONArray(events) {
224
+ return Array.from(events, (event) => {
225
+ const original = event._event_original ?? event;
226
+ return original.toJSON();
227
+ });
228
+ }
229
+ static fromJSONArray(data) {
230
+ if (!Array.isArray(data)) {
231
+ return [];
232
+ }
233
+ return data.map((item) => BaseEvent.fromJSON(item));
234
+ }
235
+ toJSON() {
236
+ const record = {};
237
+ for (const [key, value] of Object.entries(this)) {
238
+ if (key.startsWith("_") || key === "bus" || key === "event_results") continue;
239
+ if (value === void 0 || typeof value === "function") continue;
240
+ record[key] = value;
241
+ }
242
+ const event_results = Array.from(this.event_results.values()).map((result) => result.toJSON());
243
+ return {
244
+ ...record,
245
+ event_id: this.event_id,
246
+ event_type: this.event_type,
247
+ event_version: this.event_version,
248
+ event_result_type: this.event_result_type ? toJsonSchema(this.event_result_type) : this.event_result_type,
249
+ // static configuration options
250
+ event_timeout: this.event_timeout,
251
+ event_slow_timeout: this.event_slow_timeout,
252
+ event_concurrency: this.event_concurrency,
253
+ event_handler_concurrency: this.event_handler_concurrency,
254
+ event_handler_completion: this.event_handler_completion,
255
+ event_handler_slow_timeout: this.event_handler_slow_timeout,
256
+ event_handler_timeout: this.event_handler_timeout,
257
+ // mutable parent/child/bus tracking runtime state
258
+ event_parent_id: this.event_parent_id,
259
+ event_path: this.event_path,
260
+ event_emitted_by_handler_id: this.event_emitted_by_handler_id,
261
+ event_pending_bus_count: this.event_pending_bus_count,
262
+ // mutable runtime status and timestamps
263
+ event_status: this.event_status,
264
+ event_created_at: this.event_created_at,
265
+ event_started_at: this.event_started_at ?? null,
266
+ event_completed_at: this.event_completed_at ?? null,
267
+ // mutable result state
268
+ ...event_results.length > 0 ? { event_results } : {}
269
+ };
270
+ }
271
+ _createSlowEventWarningTimer() {
272
+ const event_slow_timeout = this.event_slow_timeout ?? this.bus?.event_slow_timeout ?? null;
273
+ const event_warn_ms = event_slow_timeout === null ? null : event_slow_timeout * 1e3;
274
+ if (event_warn_ms === null) {
275
+ return null;
276
+ }
277
+ const name = this.bus?.name ?? "EventBus";
278
+ return setTimeout(() => {
279
+ if (this.event_status === "completed") {
280
+ return;
281
+ }
282
+ const running_handler_count = [...this.event_results.values()].filter((result) => result.status === "started").length;
283
+ const started_at = this.event_started_at ?? this.event_created_at;
284
+ const elapsed_ms = Math.max(0, Date.now() - Date.parse(started_at));
285
+ const elapsed_seconds = (elapsed_ms / 1e3).toFixed(2);
286
+ console.warn(
287
+ `[bubus] Slow event processing: ${name}.on(${this.event_type}#${this.event_id.slice(-4)}, ${running_handler_count} handlers) still running after ${elapsed_seconds}s`
288
+ );
289
+ }, event_warn_ms);
290
+ }
291
+ eventResultUpdate(handler, options = {}) {
292
+ const original_event = this._event_original ?? this;
293
+ let resolved_eventbus = options.eventbus;
294
+ let handler_entry;
295
+ if (handler instanceof EventHandler) {
296
+ 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);
299
+ }
300
+ } else {
301
+ handler_entry = EventHandler.fromCallable({
302
+ handler,
303
+ event_pattern: original_event.event_type,
304
+ eventbus_name: resolved_eventbus?.name ?? "EventBus",
305
+ eventbus_id: resolved_eventbus?.id ?? ROOT_EVENTBUS_ID
306
+ });
307
+ }
308
+ const scoped_event = resolved_eventbus ? resolved_eventbus._getEventProxyScopedToThisBus(original_event) : original_event;
309
+ const handler_id = handler_entry.id;
310
+ const existing = original_event.event_results.get(handler_id);
311
+ const event_result = existing ?? new EventResult({ event: scoped_event, handler: handler_entry });
312
+ if (!existing) {
313
+ original_event.event_results.set(handler_id, event_result);
314
+ } else {
315
+ if (existing.event !== scoped_event) {
316
+ existing.event = scoped_event;
317
+ }
318
+ if (existing.handler.id !== handler_entry.id) {
319
+ existing.handler = handler_entry;
320
+ }
321
+ }
322
+ if (options.status !== void 0 || options.result !== void 0 || options.error !== void 0) {
323
+ const update_params = {};
324
+ if (options.status !== void 0) update_params.status = options.status;
325
+ if (options.result !== void 0) update_params.result = options.result;
326
+ if (options.error !== void 0) update_params.error = options.error;
327
+ event_result.update(update_params);
328
+ if (event_result.status === "started" && event_result.started_at !== null) {
329
+ original_event._markStarted(event_result.started_at, false);
330
+ }
331
+ if (options.status === "pending" || options.status === "started") {
332
+ original_event.event_completed_at = null;
333
+ }
334
+ }
335
+ return event_result;
336
+ }
337
+ _createPendingHandlerResults(bus) {
338
+ const original_event = this._event_original ?? this;
339
+ const scoped_event = bus._getEventProxyScopedToThisBus(original_event);
340
+ const handlers = bus._getHandlersForEvent(original_event);
341
+ return handlers.map((entry) => {
342
+ const handler_id = entry.id;
343
+ const existing = original_event.event_results.get(handler_id);
344
+ const result = existing ?? new EventResult({ event: scoped_event, handler: entry });
345
+ if (!existing) {
346
+ original_event.event_results.set(handler_id, result);
347
+ } else if (existing.event !== scoped_event) {
348
+ existing.event = scoped_event;
349
+ }
350
+ return { handler: entry, result };
351
+ });
352
+ }
353
+ _collectPendingResults(original, pending_entries) {
354
+ if (pending_entries) {
355
+ return pending_entries.map((entry) => entry.result);
356
+ }
357
+ if (!this.bus?.id) {
358
+ return Array.from(original.event_results.values());
359
+ }
360
+ return Array.from(original.event_results.values()).filter((result) => result.eventbus_id === this.bus.id);
361
+ }
362
+ _isFirstModeWinningResult(entry) {
363
+ return entry.status === "completed" && entry.result !== void 0 && entry.result !== null && !(entry.result instanceof BaseEvent);
364
+ }
365
+ _markFirstModeWinnerIfNeeded(original, entry, first_state) {
366
+ if (first_state.found || !this._isFirstModeWinningResult(entry)) {
367
+ return;
368
+ }
369
+ first_state.found = true;
370
+ original._markRemainingFirstModeResultCancelled(entry);
371
+ }
372
+ async _runHandlerWithLock(original, entry) {
373
+ if (!this.bus) {
374
+ throw new Error("event has no bus attached");
375
+ }
376
+ await this.bus.locks._runWithHandlerLock(original, this.bus.event_handler_concurrency, async (handler_lock) => {
377
+ await entry.runHandler(handler_lock);
378
+ });
379
+ }
380
+ // Run all pending handler results for the current bus context.
381
+ async _runHandlers(pending_entries) {
382
+ const original = this._event_original ?? this;
383
+ const pending_results = this._collectPendingResults(original, pending_entries);
384
+ if (pending_results.length === 0) {
385
+ return;
386
+ }
387
+ const resolved_completion = original.event_handler_completion ?? this.bus?.event_handler_completion ?? "all";
388
+ if (resolved_completion === "first") {
389
+ if (original._getHandlerLock(this.bus?.event_handler_concurrency) !== null) {
390
+ for (const entry of pending_results) {
391
+ await this._runHandlerWithLock(original, entry);
392
+ if (!this._isFirstModeWinningResult(entry)) {
393
+ continue;
394
+ }
395
+ original._markRemainingFirstModeResultCancelled(entry);
396
+ break;
397
+ }
398
+ return;
399
+ }
400
+ const first_state = { found: false };
401
+ const handler_promises = pending_results.map((entry) => this._runHandlerWithLock(original, entry));
402
+ const monitored = pending_results.map(
403
+ (entry, index) => handler_promises[index].then(() => {
404
+ this._markFirstModeWinnerIfNeeded(original, entry, first_state);
405
+ })
406
+ );
407
+ await Promise.all(monitored);
408
+ return;
409
+ } else {
410
+ const handler_promises = pending_results.map((entry) => this._runHandlerWithLock(original, entry));
411
+ await Promise.all(handler_promises);
412
+ }
413
+ }
414
+ _getHandlerLock(default_concurrency) {
415
+ const original = this._event_original ?? this;
416
+ const resolved = original.event_handler_concurrency ?? default_concurrency ?? original.bus?.event_handler_concurrency ?? "serial";
417
+ if (resolved === "parallel") {
418
+ return null;
419
+ }
420
+ if (!original._lock_for_event_handler) {
421
+ original._lock_for_event_handler = new AsyncLock(1);
422
+ }
423
+ return original._lock_for_event_handler;
424
+ }
425
+ _setHandlerLock(lock) {
426
+ const original = this._event_original ?? this;
427
+ original._lock_for_event_handler = lock;
428
+ }
429
+ _getDispatchContext() {
430
+ const original = this._event_original ?? this;
431
+ return original._event_dispatch_context;
432
+ }
433
+ _setDispatchContext(dispatch_context) {
434
+ const original = this._event_original ?? this;
435
+ original._event_dispatch_context = dispatch_context;
436
+ }
437
+ // Get parent event object from event_parent_id (checks across all buses)
438
+ get event_parent() {
439
+ const original = this._event_original ?? this;
440
+ const parent_id = original.event_parent_id;
441
+ if (!parent_id) {
442
+ return void 0;
443
+ }
444
+ return original.bus?.findEventById(parent_id) ?? void 0;
445
+ }
446
+ // get all direct children of this event
447
+ get event_children() {
448
+ const children = [];
449
+ const seen = /* @__PURE__ */ new Set();
450
+ for (const result of this.event_results.values()) {
451
+ for (const child of result.event_children) {
452
+ if (!seen.has(child.event_id)) {
453
+ seen.add(child.event_id);
454
+ children.push(child);
455
+ }
456
+ }
457
+ }
458
+ return children;
459
+ }
460
+ // get all children grandchildren etc. recursively
461
+ get event_descendants() {
462
+ const descendants = [];
463
+ const visited = /* @__PURE__ */ new Set();
464
+ const root_id = this.event_id;
465
+ const stack = [...this.event_children];
466
+ while (stack.length > 0) {
467
+ const child = stack.pop();
468
+ if (!child) {
469
+ continue;
470
+ }
471
+ const child_id = child.event_id;
472
+ if (child_id === root_id) {
473
+ continue;
474
+ }
475
+ if (visited.has(child_id)) {
476
+ continue;
477
+ }
478
+ visited.add(child_id);
479
+ descendants.push(child);
480
+ if (child.event_children.length > 0) {
481
+ stack.push(...child.event_children);
482
+ }
483
+ }
484
+ return descendants;
485
+ }
486
+ // force-abort processing of all pending descendants of an event regardless of whether they have already started
487
+ _cancelPendingChildProcessing(reason) {
488
+ const original = this._event_original ?? this;
489
+ const cancellation_cause = reason instanceof EventHandlerTimeoutError ? reason : reason instanceof EventHandlerCancelledError || reason instanceof EventHandlerAbortedError ? reason.cause instanceof Error ? reason.cause : reason : reason instanceof Error ? reason : new Error(String(reason));
490
+ const visited = /* @__PURE__ */ new Set();
491
+ const cancelChildEvent = (child) => {
492
+ const original_child = child._event_original ?? child;
493
+ if (visited.has(original_child.event_id)) {
494
+ return;
495
+ }
496
+ visited.add(original_child.event_id);
497
+ for (const grandchild of original_child.event_children) {
498
+ cancelChildEvent(grandchild);
499
+ }
500
+ original_child._markCancelled(cancellation_cause);
501
+ if (original_child.event_status !== "completed") {
502
+ original_child._markCompleted();
503
+ }
504
+ };
505
+ for (const child of original.event_children) {
506
+ cancelChildEvent(child);
507
+ }
508
+ }
509
+ // Cancel all handler results for an event except the winner, used by first() mode.
510
+ // Cancels pending handlers immediately, aborts started handlers via _signalAbort(),
511
+ // and cancels any child events emitted by the losing handlers.
512
+ _markRemainingFirstModeResultCancelled(winner) {
513
+ const cause = new Error("first() resolved: another handler returned a result first");
514
+ const bus_id = winner.eventbus_id;
515
+ for (const result of this.event_results.values()) {
516
+ if (result === winner) continue;
517
+ if (result.eventbus_id !== bus_id) continue;
518
+ if (result.status === "pending") {
519
+ result._markError(
520
+ new EventHandlerCancelledError(`Cancelled: first() resolved`, {
521
+ event_result: result,
522
+ cause
523
+ })
524
+ );
525
+ } else if (result.status === "started") {
526
+ for (const child of result.event_children) {
527
+ const original_child = child._event_original ?? child;
528
+ original_child._cancelPendingChildProcessing(cause);
529
+ original_child._markCancelled(cause);
530
+ }
531
+ result._lock?.exitHandlerRun();
532
+ const aborted_error = new EventHandlerAbortedError(`Aborted: first() resolved`, {
533
+ event_result: result,
534
+ cause
535
+ });
536
+ result._markError(aborted_error);
537
+ result._signalAbort(aborted_error);
538
+ }
539
+ }
540
+ }
541
+ // force-abort processing of this event regardless of whether it is pending or has already started
542
+ _markCancelled(cause) {
543
+ const original = this._event_original ?? this;
544
+ if (!this.bus) {
545
+ if (original.event_status !== "completed") {
546
+ original._markCompleted();
547
+ }
548
+ return;
549
+ }
550
+ const path = Array.isArray(original.event_path) ? original.event_path : [];
551
+ const buses_to_cancel = new Set(path);
552
+ for (const bus of this.bus.all_instances) {
553
+ if (!buses_to_cancel.has(bus.label)) {
554
+ continue;
555
+ }
556
+ const handler_entries = original._createPendingHandlerResults(bus);
557
+ let updated = false;
558
+ for (const entry of handler_entries) {
559
+ if (entry.result.status === "pending") {
560
+ const cancelled_error = new EventHandlerCancelledError(`Cancelled pending handler due to parent error: ${cause.message}`, {
561
+ event_result: entry.result,
562
+ cause
563
+ });
564
+ entry.result._markError(cancelled_error);
565
+ updated = true;
566
+ } else if (entry.result.status === "started") {
567
+ entry.result._lock?.exitHandlerRun();
568
+ const aborted_error = new EventHandlerAbortedError(`Aborted running handler due to parent error: ${cause.message}`, {
569
+ event_result: entry.result,
570
+ cause
571
+ });
572
+ entry.result._markError(aborted_error);
573
+ entry.result._signalAbort(aborted_error);
574
+ updated = true;
575
+ }
576
+ }
577
+ const removed = bus.removeEventFromPendingQueue(original);
578
+ if (removed > 0 && !bus.isEventInFlightOrQueued(original.event_id)) {
579
+ original.event_pending_bus_count = Math.max(0, original.event_pending_bus_count - 1);
580
+ }
581
+ if (updated || removed > 0) {
582
+ original._markCompleted(false);
583
+ }
584
+ }
585
+ if (original.event_status !== "completed") {
586
+ original._markCompleted();
587
+ }
588
+ }
589
+ _notifyEventParentsOfCompletion() {
590
+ const original = this._event_original ?? this;
591
+ if (!this.bus) {
592
+ return;
593
+ }
594
+ const visited = /* @__PURE__ */ new Set();
595
+ let parent_id = original.event_parent_id;
596
+ while (parent_id && !visited.has(parent_id)) {
597
+ visited.add(parent_id);
598
+ const parent = this.bus.findEventById(parent_id);
599
+ if (!parent) {
600
+ break;
601
+ }
602
+ parent._markCompleted(false, false);
603
+ if (parent.event_status !== "completed") {
604
+ break;
605
+ }
606
+ parent_id = parent.event_parent_id;
607
+ }
608
+ }
609
+ // awaitable that triggers immediate (queue-jump) processing of the event on all buses where it is queued
610
+ // use eventCompleted() to wait for normal queue-order completion without queue-jumping.
611
+ done() {
612
+ if (!this.bus) {
613
+ return Promise.reject(new Error("event has no bus attached"));
614
+ }
615
+ if (this.event_status === "completed") {
616
+ return Promise.resolve(this);
617
+ }
618
+ return this.bus._processEventImmediately(this);
619
+ }
620
+ // returns the first non-undefined handler result value, cancelling remaining handlers
621
+ // when any handler completes. Works with all event_handler_concurrency modes:
622
+ // parallel: races all handlers, returns first non-undefined, aborts the rest
623
+ // serial: runs handlers sequentially, returns first non-undefined, skips remaining
624
+ first() {
625
+ if (!this.bus) {
626
+ return Promise.reject(new Error("event has no bus attached"));
627
+ }
628
+ const original = this._event_original ?? this;
629
+ original.event_handler_completion = "first";
630
+ return this.done().then((completed_event) => {
631
+ const orig = completed_event._event_original ?? completed_event;
632
+ return Array.from(orig.event_results.values()).filter(
633
+ (result) => result.status === "completed" && result.result !== void 0 && result.result !== null && !(result.result instanceof BaseEvent)
634
+ ).sort((a, b) => compareIsoDatetime(a.completed_at, b.completed_at)).map((result) => result.result).at(0);
635
+ });
636
+ }
637
+ async eventResultsList(include_or_options, maybe_options) {
638
+ const default_include = (_result, event_result) => event_result.status === "completed" && event_result.result !== void 0 && event_result.result !== null && !(event_result.result instanceof Error) && !(event_result.result instanceof BaseEvent) && event_result.error === void 0;
639
+ let options;
640
+ let include;
641
+ if (typeof include_or_options === "function") {
642
+ options = maybe_options ?? {};
643
+ include = include_or_options;
644
+ } else {
645
+ options = include_or_options ?? {};
646
+ include = options.include ?? default_include;
647
+ }
648
+ const raise_if_any = options.raise_if_any ?? true;
649
+ const raise_if_none = options.raise_if_none ?? true;
650
+ const original = this._event_original ?? this;
651
+ const resolved_timeout_seconds = options.timeout ?? original.event_timeout ?? this.bus?.event_timeout ?? null;
652
+ let completed_event;
653
+ if (resolved_timeout_seconds === null) {
654
+ completed_event = await this.done();
655
+ } else {
656
+ completed_event = await _runWithTimeout(
657
+ resolved_timeout_seconds,
658
+ () => new Error(`Timed out waiting for ${original.event_type} results after ${resolved_timeout_seconds}s`),
659
+ () => this.done()
660
+ );
661
+ }
662
+ const all_results = Array.from(completed_event.event_results.values());
663
+ const error_results = all_results.filter((event_result) => event_result.error !== void 0 || event_result.result instanceof Error);
664
+ if (raise_if_any && error_results.length > 0) {
665
+ if (error_results.length === 1) {
666
+ const first_error = error_results[0];
667
+ if (first_error.error instanceof Error) {
668
+ throw first_error.error;
669
+ }
670
+ if (first_error.result instanceof Error) {
671
+ throw first_error.result;
672
+ }
673
+ throw new Error(String(first_error.error ?? first_error.result));
674
+ }
675
+ const errors = error_results.map((event_result) => {
676
+ if (event_result.error instanceof Error) {
677
+ return event_result.error;
678
+ }
679
+ if (event_result.result instanceof Error) {
680
+ return event_result.result;
681
+ }
682
+ return new Error(String(event_result.error ?? event_result.result));
683
+ });
684
+ throw new AggregateError(
685
+ errors,
686
+ `Event ${completed_event.event_type}#${completed_event.event_id.slice(-4)} had ${errors.length} handler error(s)`
687
+ );
688
+ }
689
+ const included_results = all_results.filter((event_result) => include(event_result.result, event_result));
690
+ if (raise_if_none && included_results.length === 0) {
691
+ throw new Error(
692
+ `Expected at least one handler to return a non-null result, but none did: ${completed_event.event_type}#${completed_event.event_id.slice(-4)}`
693
+ );
694
+ }
695
+ return included_results.map((event_result) => event_result.result);
696
+ }
697
+ // awaitable that waits for the event to be processed in normal queue order by the _runloop
698
+ eventCompleted() {
699
+ if (this.event_status === "completed") {
700
+ return Promise.resolve(this);
701
+ }
702
+ this._notifyDoneListeners();
703
+ return this._event_completed_signal.promise;
704
+ }
705
+ _markPending() {
706
+ const original = this._event_original ?? this;
707
+ original.event_status = "pending";
708
+ original.event_started_at = null;
709
+ original.event_completed_at = null;
710
+ original.event_results.clear();
711
+ original.event_pending_bus_count = 0;
712
+ original._setDispatchContext(void 0);
713
+ original._event_completed_signal = null;
714
+ original._lock_for_event_handler = null;
715
+ original.bus = void 0;
716
+ return this;
717
+ }
718
+ eventReset() {
719
+ const original = this._event_original ?? this;
720
+ const ctor = original.constructor;
721
+ const fresh_event = ctor.fromJSON(original.toJSON());
722
+ fresh_event.event_id = uuidv7();
723
+ return fresh_event._markPending();
724
+ }
725
+ _markStarted(started_at = null, notify_hook = true) {
726
+ const original = this._event_original ?? this;
727
+ if (original.event_status !== "pending") {
728
+ return;
729
+ }
730
+ original.event_status = "started";
731
+ original.event_started_at = started_at === null ? monotonicDatetime() : monotonicDatetime(started_at);
732
+ if (notify_hook && original.bus) {
733
+ const bus_for_hook = original.bus;
734
+ const event_for_bus = bus_for_hook._getEventProxyScopedToThisBus(original);
735
+ void bus_for_hook.onEventChange(event_for_bus, "started");
736
+ }
737
+ }
738
+ _markCompleted(force = true, notify_parents = true) {
739
+ const original = this._event_original ?? this;
740
+ if (original.event_status === "completed") {
741
+ return;
742
+ }
743
+ if (!force) {
744
+ if (original.event_pending_bus_count > 0) {
745
+ return;
746
+ }
747
+ if (!original._areAllChildrenComplete()) {
748
+ return;
749
+ }
750
+ }
751
+ original.event_status = "completed";
752
+ original.event_completed_at = monotonicDatetime();
753
+ if (original.bus) {
754
+ const bus_for_hook = original.bus;
755
+ const event_for_bus = bus_for_hook._getEventProxyScopedToThisBus(original);
756
+ void bus_for_hook.onEventChange(event_for_bus, "completed");
757
+ }
758
+ original._setDispatchContext(null);
759
+ original._notifyDoneListeners();
760
+ original._event_completed_signal.resolve(original);
761
+ original._event_completed_signal = null;
762
+ original.dropFromZeroHistoryBuses();
763
+ if (notify_parents && original.bus) {
764
+ original._notifyEventParentsOfCompletion();
765
+ }
766
+ }
767
+ dropFromZeroHistoryBuses() {
768
+ if (!this.bus) {
769
+ return;
770
+ }
771
+ const original = this._event_original ?? this;
772
+ for (const bus of this.bus.all_instances) {
773
+ if (bus.event_history.max_history_size !== 0) {
774
+ continue;
775
+ }
776
+ bus.removeEventFromHistory(original.event_id);
777
+ }
778
+ }
779
+ get event_errors() {
780
+ return Array.from(this.event_results.values()).filter((event_result) => event_result.error !== void 0 && event_result.completed_at !== null).sort((event_result_a, event_result_b) => compareIsoDatetime(event_result_a.completed_at, event_result_b.completed_at)).map((event_result) => event_result.error);
781
+ }
782
+ // Returns the first non-undefined completed handler result, sorted by completion time.
783
+ // Useful after first() or done() to get the winning result value.
784
+ get event_result() {
785
+ 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);
786
+ }
787
+ _areAllChildrenComplete() {
788
+ return this.event_descendants.every((descendant) => descendant.event_status === "completed");
789
+ }
790
+ _notifyDoneListeners() {
791
+ if (this._event_completed_signal) {
792
+ return;
793
+ }
794
+ this._event_completed_signal = withResolvers();
795
+ }
796
+ // Break internal reference chains so a completed event can be GC'd when
797
+ // Evicted from event_history. Called by EventHistory.trimEventHistory().
798
+ _gc() {
799
+ this._event_completed_signal = null;
800
+ this._setDispatchContext(null);
801
+ this.bus = void 0;
802
+ this._lock_for_event_handler = null;
803
+ for (const result of this.event_results.values()) {
804
+ result.event_children = [];
805
+ }
806
+ this.event_results.clear();
807
+ }
808
+ }
809
+ const hydrateEventResults = (event, raw_event_results) => {
810
+ const event_results = /* @__PURE__ */ new Map();
811
+ if (!Array.isArray(raw_event_results)) {
812
+ return event_results;
813
+ }
814
+ for (const item of raw_event_results) {
815
+ const result = EventResult.fromJSON(event, item);
816
+ const map_key = typeof result.handler_id === "string" && result.handler_id.length > 0 ? result.handler_id : result.id;
817
+ event_results.set(map_key, result);
818
+ }
819
+ return event_results;
820
+ };
821
+ export {
822
+ BaseEvent,
823
+ BaseEventSchema
824
+ };
825
+ //# sourceMappingURL=base_event.js.map