@zudojs/events 0.1.0 → 1.0.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.
Files changed (119) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +70 -11
  3. package/dist/eventBus/eventBus.core.d.ts +28 -2
  4. package/dist/eventBus/eventBus.core.js +104 -31
  5. package/dist/eventBus/eventBus.publish.d.ts +23 -5
  6. package/dist/eventBus/eventBus.publish.js +72 -32
  7. package/dist/eventBus/eventBus.registration.d.ts +24 -13
  8. package/dist/eventBus/eventBus.registration.js +31 -28
  9. package/dist/eventBus/eventBus.type.d.ts +75 -3
  10. package/dist/eventBus/eventBus.type.js +9 -0
  11. package/dist/eventBus/index.d.ts +2 -0
  12. package/dist/eventBus/index.js +2 -0
  13. package/dist/eventEmitter/eventEmitter.abort.d.ts +11 -2
  14. package/dist/eventEmitter/eventEmitter.abort.js +20 -5
  15. package/dist/eventEmitter/eventEmitter.core.d.ts +14 -3
  16. package/dist/eventEmitter/eventEmitter.core.js +63 -55
  17. package/dist/eventEmitter/eventEmitter.parallel.d.ts +7 -1
  18. package/dist/eventEmitter/eventEmitter.parallel.js +31 -13
  19. package/dist/eventEmitter/eventEmitter.sequential.d.ts +17 -1
  20. package/dist/eventEmitter/eventEmitter.sequential.js +19 -12
  21. package/dist/eventEmitter/eventEmitter.type.d.ts +60 -0
  22. package/dist/eventErrors/eventError.base.d.ts +50 -3
  23. package/dist/eventErrors/eventError.base.js +64 -3
  24. package/dist/eventHandler/eventHandler.core.d.ts +19 -0
  25. package/dist/eventHandler/eventHandler.core.js +67 -4
  26. package/dist/eventMiddleware/eventMiddleware.builder.js +5 -11
  27. package/dist/eventMiddleware/eventMiddleware.helper.js +0 -1
  28. package/dist/eventMiddleware/eventMiddleware.pipeline.d.ts +5 -0
  29. package/dist/eventMiddleware/eventMiddleware.pipeline.js +33 -9
  30. package/dist/eventRegistry/eventRegistry.lifecycle.d.ts +10 -5
  31. package/dist/eventRegistry/eventRegistry.lifecycle.js +23 -10
  32. package/dist/eventRegistry/eventRegistry.queries.d.ts +16 -7
  33. package/dist/eventRegistry/eventRegistry.queries.js +22 -13
  34. package/dist/eventRegistry/eventRegistry.registration.d.ts +14 -7
  35. package/dist/eventRegistry/eventRegistry.registration.js +120 -41
  36. package/dist/eventRegistry/eventRegistry.store.d.ts +12 -5
  37. package/dist/eventRegistry/eventRegistry.store.js +37 -12
  38. package/dist/eventRegistry/eventRegistry.type.d.ts +87 -2
  39. package/dist/eventRegistry/eventRegistry.type.js +5 -0
  40. package/dist/eventSubscription/eventSubscription.core.d.ts +5 -0
  41. package/dist/eventSubscription/eventSubscription.core.js +17 -3
  42. package/dist/eventTypes/eventDefinition.type.d.ts +11 -4
  43. package/dist/eventTypes/eventDefinition.type.js +49 -17
  44. package/dist/eventTypes/eventPayload.type.d.ts +5 -0
  45. package/dist/eventTypes/eventPayload.type.js +56 -15
  46. package/dist/eventTypes/eventType.type.d.ts +22 -5
  47. package/dist/eventTypes/eventType.type.js +33 -11
  48. package/dist/eventTypes/index.d.ts +2 -2
  49. package/dist/eventTypes/index.js +2 -2
  50. package/package.json +22 -14
  51. package/dist/.tsbuildinfo +0 -1
  52. package/dist/eventBus/eventBus.core.d.ts.map +0 -1
  53. package/dist/eventBus/eventBus.core.js.map +0 -1
  54. package/dist/eventBus/eventBus.factory.d.ts.map +0 -1
  55. package/dist/eventBus/eventBus.factory.js.map +0 -1
  56. package/dist/eventBus/eventBus.publish.d.ts.map +0 -1
  57. package/dist/eventBus/eventBus.publish.js.map +0 -1
  58. package/dist/eventBus/eventBus.registration.d.ts.map +0 -1
  59. package/dist/eventBus/eventBus.registration.js.map +0 -1
  60. package/dist/eventBus/eventBus.type.d.ts.map +0 -1
  61. package/dist/eventBus/eventBus.type.js.map +0 -1
  62. package/dist/eventBus/index.d.ts.map +0 -1
  63. package/dist/eventBus/index.js.map +0 -1
  64. package/dist/eventEmitter/eventEmitter.abort.d.ts.map +0 -1
  65. package/dist/eventEmitter/eventEmitter.abort.js.map +0 -1
  66. package/dist/eventEmitter/eventEmitter.core.d.ts.map +0 -1
  67. package/dist/eventEmitter/eventEmitter.core.js.map +0 -1
  68. package/dist/eventEmitter/eventEmitter.parallel.d.ts.map +0 -1
  69. package/dist/eventEmitter/eventEmitter.parallel.js.map +0 -1
  70. package/dist/eventEmitter/eventEmitter.sequential.d.ts.map +0 -1
  71. package/dist/eventEmitter/eventEmitter.sequential.js.map +0 -1
  72. package/dist/eventEmitter/eventEmitter.type.d.ts.map +0 -1
  73. package/dist/eventEmitter/eventEmitter.type.js.map +0 -1
  74. package/dist/eventEmitter/index.d.ts.map +0 -1
  75. package/dist/eventEmitter/index.js.map +0 -1
  76. package/dist/eventErrors/eventError.base.d.ts.map +0 -1
  77. package/dist/eventErrors/eventError.base.js.map +0 -1
  78. package/dist/eventErrors/index.d.ts.map +0 -1
  79. package/dist/eventErrors/index.js.map +0 -1
  80. package/dist/eventHandler/eventHandler.core.d.ts.map +0 -1
  81. package/dist/eventHandler/eventHandler.core.js.map +0 -1
  82. package/dist/eventHandler/index.d.ts.map +0 -1
  83. package/dist/eventHandler/index.js.map +0 -1
  84. package/dist/eventMiddleware/eventMiddleware.builder.d.ts.map +0 -1
  85. package/dist/eventMiddleware/eventMiddleware.builder.js.map +0 -1
  86. package/dist/eventMiddleware/eventMiddleware.helper.d.ts.map +0 -1
  87. package/dist/eventMiddleware/eventMiddleware.helper.js.map +0 -1
  88. package/dist/eventMiddleware/eventMiddleware.pipeline.d.ts.map +0 -1
  89. package/dist/eventMiddleware/eventMiddleware.pipeline.js.map +0 -1
  90. package/dist/eventMiddleware/eventMiddleware.type.d.ts.map +0 -1
  91. package/dist/eventMiddleware/eventMiddleware.type.js.map +0 -1
  92. package/dist/eventMiddleware/index.d.ts.map +0 -1
  93. package/dist/eventMiddleware/index.js.map +0 -1
  94. package/dist/eventRegistry/eventRegistry.lifecycle.d.ts.map +0 -1
  95. package/dist/eventRegistry/eventRegistry.lifecycle.js.map +0 -1
  96. package/dist/eventRegistry/eventRegistry.queries.d.ts.map +0 -1
  97. package/dist/eventRegistry/eventRegistry.queries.js.map +0 -1
  98. package/dist/eventRegistry/eventRegistry.registration.d.ts.map +0 -1
  99. package/dist/eventRegistry/eventRegistry.registration.js.map +0 -1
  100. package/dist/eventRegistry/eventRegistry.store.d.ts.map +0 -1
  101. package/dist/eventRegistry/eventRegistry.store.js.map +0 -1
  102. package/dist/eventRegistry/eventRegistry.type.d.ts.map +0 -1
  103. package/dist/eventRegistry/eventRegistry.type.js.map +0 -1
  104. package/dist/eventRegistry/index.d.ts.map +0 -1
  105. package/dist/eventRegistry/index.js.map +0 -1
  106. package/dist/eventSubscription/eventSubscription.core.d.ts.map +0 -1
  107. package/dist/eventSubscription/eventSubscription.core.js.map +0 -1
  108. package/dist/eventSubscription/index.d.ts.map +0 -1
  109. package/dist/eventSubscription/index.js.map +0 -1
  110. package/dist/eventTypes/eventDefinition.type.d.ts.map +0 -1
  111. package/dist/eventTypes/eventDefinition.type.js.map +0 -1
  112. package/dist/eventTypes/eventPayload.type.d.ts.map +0 -1
  113. package/dist/eventTypes/eventPayload.type.js.map +0 -1
  114. package/dist/eventTypes/eventType.type.d.ts.map +0 -1
  115. package/dist/eventTypes/eventType.type.js.map +0 -1
  116. package/dist/eventTypes/index.d.ts.map +0 -1
  117. package/dist/eventTypes/index.js.map +0 -1
  118. package/dist/index.d.ts.map +0 -1
  119. package/dist/index.js.map +0 -1
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Zudojs Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -11,32 +11,91 @@ npm install @zudojs/events
11
11
  ## Quick Start
12
12
 
13
13
  ```typescript
14
- import { createEventBus } from "@zudojs/events";
14
+ import { createEventBus, defineEvent } from "@zudojs/events";
15
+
16
+ interface UserCreated {
17
+ readonly id: string;
18
+ readonly name: string;
19
+ }
15
20
 
16
21
  const bus = createEventBus();
17
22
 
23
+ const UserCreatedEvent = defineEvent<"user.created", UserCreated>("user.created");
24
+
18
25
  bus.on("user.created", (event) => {
19
- console.log("New user:", event.payload.id);
26
+ console.log("New user:", (event.payload as UserCreated).id);
20
27
  });
21
28
 
22
- await bus.emit({
29
+ // Publish from an event definition …
30
+ await bus.publish(UserCreatedEvent.create({ id: "123", name: "Alice" }));
31
+
32
+ // … or from plain event input.
33
+ await bus.publishEvent({
23
34
  type: "user.created",
24
- payload: { id: "123", name: "Alice" },
35
+ payload: { id: "124", name: "Bob" },
25
36
  });
26
37
  ```
27
38
 
39
+ `bus.emit()` accepts either a full `Event` or an `EventInput` and behaves like
40
+ `publish` / `publishEvent` respectively.
41
+
28
42
  ## Features
29
43
 
30
- - Event bus with middleware pipeline
31
- - Event emitter with typed payloads
32
- - Event registry for documentation and validation
33
- - Wildcard event subscriptions
34
- - Async event handlers
35
- - Event replay and history
44
+ - Event bus with a middleware pipeline (`use()`, constructor and per-publish middleware)
45
+ - Sequential or parallel handler dispatch with `THROW` / `CONTINUE` error modes
46
+ - Handler priorities, one-time handlers, per-handler timeouts
47
+ - Wildcard subscriptions (`"user.*"`, `"*"`)
48
+ - Event registry for typed definitions; handlers registered on the registry are dispatched by the bus
49
+ - Deep-frozen events (`freezeEvents`, on by default) so handlers cannot alter what other handlers see
50
+ - Listener-leak warnings (`maxListeners`) and an `onError` hook for fire-and-forget publishes
51
+ - Typed error classes from `@zudojs/errors` (`EventHandlerError`, `EventMiddlewareError`, `EventDispatchAbortedError`, …)
52
+
53
+ ## Lifecycle
54
+
55
+ ```
56
+ CREATED ──(first use / start)──▶ ACTIVE ◀──(start)── STOPPED
57
+ │ ▲
58
+ └──────(stop)─────────┘
59
+ ▼
60
+ DISPOSED
61
+ ```
62
+
63
+ - A `CREATED` bus starts itself on the first `publish` / `on`.
64
+ - `stop()` moves the bus to `STOPPED`; publishing or subscribing then throws
65
+ `EventBusStoppedError` until `start()` is called. Handlers and definitions are kept.
66
+ - `dispose()` is final; every operation throws `EventBusDisposedError`.
67
+
68
+ ## Publish results
69
+
70
+ ```typescript
71
+ const result = await bus.publish(event);
72
+
73
+ result.handled; // true when at least one handler succeeded
74
+ result.handlerCount; // handlers invoked
75
+ result.succeeded; // handlers that completed
76
+ result.failed; // handlers that threw
77
+ result.errors; // EventHandlerError[] (cause = the raw thrown value)
78
+ result.shortCircuited; // true when a middleware did not call next()
79
+ ```
80
+
81
+ In the default `CONTINUE` error mode handler failures are collected in
82
+ `result.errors` (and forwarded to the `onError` option). In `THROW` mode the first
83
+ `EventHandlerError` rejects the publish. Errors thrown by a middleware itself are
84
+ wrapped as `EventMiddlewareError`; handler errors and aborts pass through unwrapped.
85
+
86
+ ## Registry
87
+
88
+ `bus.register(defineEvent("order.placed"))` records a definition; with
89
+ `requireRegistration: true` the bus rejects unregistered types with
90
+ `EventTypeNotFoundError`. `bus.unregister(type)` removes only the definition; pass
91
+ `{ removeHandlers: true }` to also drop handlers subscribed to exactly that type.
92
+
93
+ Event types are normalised (trimmed, lower-cased) everywhere, so `"User.Created"`
94
+ and `"user.created"` refer to the same event.
36
95
 
37
96
  ## Use Cases
38
97
 
39
98
  - Decoupling application components
40
99
  - Audit logging and change tracking
41
100
  - Real-time notifications
42
- - CQRS event sourcing
101
+ - CQRS event publishing (see `@zudojs/cqrs`)
@@ -14,6 +14,11 @@ import { EventBusState } from "./eventBus.type.js";
14
14
  export { EventBusState } from "./eventBus.type.js";
15
15
  /**
16
16
  * High-level event bus.
17
+ *
18
+ * Lifecycle: CREATED → ACTIVE ⇄ STOPPED → DISPOSED. A CREATED bus
19
+ * starts itself on first use; a STOPPED bus rejects publishing and
20
+ * subscribing until start() is called; a DISPOSED bus rejects
21
+ * everything.
17
22
  */
18
23
  export declare class EventBus {
19
24
  private readonly emitter;
@@ -24,6 +29,11 @@ export declare class EventBus {
24
29
  private state;
25
30
  constructor(options?: EventBusOptions);
26
31
  start(): this;
32
+ /**
33
+ * Stops the bus. Publishing and subscribing throw
34
+ * EventBusStoppedError until start() is called again; handlers
35
+ * and definitions are kept.
36
+ */
27
37
  stop(): this;
28
38
  register<TType extends EventType, TPayload>(definition: EventDefinition<TType, TPayload>): import("../index.js").RegisteredEventDefinition<TType, TPayload>;
29
39
  on<TEvent extends Event = Event>(eventType: EventTypePattern, handler: EventHandlerLike<TEvent>, options?: Omit<EventHandlerOptions, "eventType">): EventSubscription;
@@ -33,10 +43,22 @@ export declare class EventBus {
33
43
  use(middleware: EventMiddlewareLike, options?: EventMiddlewareOptions): () => void;
34
44
  publish<TEvent extends Event>(event: TEvent, options?: PublishOptions): Promise<EventPublishResult<TEvent>>;
35
45
  publishEvent<TPayload>(input: EventInput<TPayload>, options?: PublishOptions): Promise<EventPublishResult<Event<TPayload>>>;
36
- emit<TEvent extends Event>(event: TEvent, options?: PublishOptions): Promise<EventPublishResult<TEvent>>;
46
+ /**
47
+ * Publishes an event or event input. A full Event is published
48
+ * as is; an EventInput ({ type, payload, ... }) is turned into
49
+ * an event first.
50
+ */
51
+ emit<TPayload>(input: Event<TPayload> | EventInput<TPayload>, options?: PublishOptions): Promise<EventPublishResult<Event<TPayload>>>;
37
52
  getDefinition<TType extends EventType, TPayload = unknown>(eventType: TType): import("../index.js").RegisteredEventDefinition<TType, TPayload> | undefined;
38
53
  hasEvent(eventType: EventType): boolean;
39
- unregister(eventType: EventType): boolean;
54
+ /**
55
+ * Removes an event definition. Handlers are not affected unless
56
+ * `removeHandlers` is true, in which case every handler whose
57
+ * pattern is exactly this event type is unregistered too.
58
+ */
59
+ unregister(eventType: EventType, options?: {
60
+ readonly removeHandlers?: boolean;
61
+ }): boolean;
40
62
  getRegistry(): EventRegistry;
41
63
  getEmitter(): EventEmitter;
42
64
  getDefinitions(): readonly import("../index.js").RegisteredEventDefinition<string, unknown>[];
@@ -48,6 +70,10 @@ export declare class EventBus {
48
70
  subscribe(listener: EventBusListener): () => void;
49
71
  dispose(): void;
50
72
  toError(error: unknown, event?: Event): EventError;
73
+ private publishDependencies;
74
+ /**
75
+ * Auto-starts a CREATED bus; rejects a STOPPED or DISPOSED one.
76
+ */
51
77
  private ensureUsable;
52
78
  private ensureNotDisposed;
53
79
  private notify;
@@ -1,16 +1,22 @@
1
1
  /**
2
2
  * Event bus core class for Zudojs.
3
3
  */
4
+ import { isEvent } from "../eventTypes/eventDefinition.type.js";
4
5
  import { EventEmitter } from "../eventEmitter/eventEmitter.core.js";
5
6
  import { EventErrorMode } from "../eventEmitter/eventEmitter.type.js";
6
7
  import { EventRegistry } from "../eventRegistry/eventRegistry.store.js";
7
- import { EventError, toEventError } from "../eventErrors/eventError.base.js";
8
+ import { EventBusDisposedError, EventBusStoppedError, EventError, toEventError, } from "../eventErrors/eventError.base.js";
8
9
  import { EventBusState } from "./eventBus.type.js";
9
10
  export { EventBusState } from "./eventBus.type.js";
10
11
  import { busOn, busOnce, busOnAny, busOff, busUse, registerMiddlewareItem, } from "./eventBus.registration.js";
11
12
  import { busPublish, busPublishEvent } from "./eventBus.publish.js";
12
13
  /**
13
14
  * High-level event bus.
15
+ *
16
+ * Lifecycle: CREATED → ACTIVE ⇄ STOPPED → DISPOSED. A CREATED bus
17
+ * starts itself on first use; a STOPPED bus rejects publishing and
18
+ * subscribing until start() is called; a DISPOSED bus rejects
19
+ * everything.
14
20
  */
15
21
  export class EventBus {
16
22
  emitter;
@@ -22,11 +28,27 @@ export class EventBus {
22
28
  constructor(options = {}) {
23
29
  this.options = {
24
30
  requireRegistration: options.requireRegistration ?? false,
31
+ onError: options.onError,
25
32
  };
26
- this.registry = new EventRegistry(options.registry);
33
+ this.registry = new EventRegistry({
34
+ ...options.registry,
35
+ maxHandlersPerPattern: options.emitter?.maxListeners,
36
+ onWarning: options.onWarning,
37
+ onError: options.onError
38
+ ? (error, context) => options.onError?.(error, {
39
+ source: context.source,
40
+ })
41
+ : undefined,
42
+ });
43
+ /**
44
+ * The emitter stores its handlers in the bus registry so
45
+ * handlers registered through either surface are dispatched.
46
+ */
27
47
  this.emitter = new EventEmitter({
28
- ...options.emitter,
48
+ mode: options.emitter?.mode,
29
49
  errorMode: options.emitter?.errorMode ?? EventErrorMode.CONTINUE,
50
+ freezeEvents: options.emitter?.freezeEvents,
51
+ store: this.registry,
30
52
  });
31
53
  this.busMiddleware = (options.middleware ?? []).map((m, index) => registerMiddlewareItem(m, index));
32
54
  }
@@ -42,12 +64,17 @@ export class EventBus {
42
64
  });
43
65
  return this;
44
66
  }
67
+ /**
68
+ * Stops the bus. Publishing and subscribing throw
69
+ * EventBusStoppedError until start() is called again; handlers
70
+ * and definitions are kept.
71
+ */
45
72
  stop() {
46
73
  this.ensureNotDisposed();
47
74
  if (this.state !== EventBusState.ACTIVE) {
48
75
  return this;
49
76
  }
50
- this.state = EventBusState.CREATED;
77
+ this.state = EventBusState.STOPPED;
51
78
  this.notify({
52
79
  type: "stopped",
53
80
  timestamp: new Date(),
@@ -55,17 +82,17 @@ export class EventBus {
55
82
  return this;
56
83
  }
57
84
  register(definition) {
58
- this.ensureUsable();
85
+ this.ensureUsable("register");
59
86
  return this.registry.register(definition);
60
87
  }
61
88
  on(eventType, handler, options = {}) {
62
- return busOn(this.emitter, eventType, handler, options, () => this.ensureUsable());
89
+ return busOn(this.emitter, eventType, handler, options, () => this.ensureUsable("subscribe"));
63
90
  }
64
91
  once(eventType, handler, options = {}) {
65
- return busOnce(this.emitter, eventType, handler, options, () => this.ensureUsable());
92
+ return busOnce(this.emitter, eventType, handler, options, () => this.ensureUsable("subscribe"));
66
93
  }
67
94
  onAny(handler, options = {}) {
68
- return busOnAny(this.emitter, handler, options, () => this.ensureUsable());
95
+ return busOnAny(this.emitter, handler, options, () => this.ensureUsable("subscribe"));
69
96
  }
70
97
  off(subscription) {
71
98
  return busOff(this.emitter, subscription, () => this.ensureNotDisposed());
@@ -75,41 +102,63 @@ export class EventBus {
75
102
  return busUse(this.busMiddleware, middleware, options);
76
103
  }
77
104
  async publish(event, options = {}) {
78
- return busPublish(event, options, this.emitter, this.registry, this.busMiddleware, this.options.requireRegistration, () => this.ensureUsable(), (e) => this.notify(e));
105
+ return busPublish(event, options, this.publishDependencies());
79
106
  }
80
107
  async publishEvent(input, options = {}) {
81
- return busPublishEvent(input, options, this.emitter, this.registry, this.busMiddleware, this.options.requireRegistration, () => this.ensureUsable(), (e) => this.notify(e));
82
- }
83
- async emit(event, options = {}) {
84
- return this.publish(event, options);
108
+ return busPublishEvent(input, options, this.publishDependencies());
109
+ }
110
+ /**
111
+ * Publishes an event or event input. A full Event is published
112
+ * as is; an EventInput ({ type, payload, ... }) is turned into
113
+ * an event first.
114
+ */
115
+ async emit(input, options = {}) {
116
+ if (isEvent(input)) {
117
+ return this.publish(input, options);
118
+ }
119
+ return this.publishEvent(input, options);
85
120
  }
86
121
  getDefinition(eventType) {
87
- this.ensureUsable();
122
+ this.ensureNotDisposed();
88
123
  return this.registry.get(eventType);
89
124
  }
90
125
  hasEvent(eventType) {
91
- this.ensureUsable();
126
+ this.ensureNotDisposed();
92
127
  return this.registry.has(eventType);
93
128
  }
94
- unregister(eventType) {
95
- this.ensureUsable();
96
- return this.registry.unregister(eventType);
129
+ /**
130
+ * Removes an event definition. Handlers are not affected unless
131
+ * `removeHandlers` is true, in which case every handler whose
132
+ * pattern is exactly this event type is unregistered too.
133
+ */
134
+ unregister(eventType, options = {}) {
135
+ this.ensureNotDisposed();
136
+ const removed = this.registry.unregister(eventType);
137
+ if (options.removeHandlers) {
138
+ const definition = this.registry.getHandlersForType(eventType);
139
+ for (const handler of definition) {
140
+ if (handler.eventType !== "*" && !handler.eventType.endsWith(".*")) {
141
+ this.registry.unregisterHandler(handler.id);
142
+ }
143
+ }
144
+ }
145
+ return removed;
97
146
  }
98
147
  getRegistry() {
99
- this.ensureUsable();
148
+ this.ensureNotDisposed();
100
149
  return this.registry;
101
150
  }
102
151
  getEmitter() {
103
- this.ensureUsable();
152
+ this.ensureNotDisposed();
104
153
  return this.emitter;
105
154
  }
106
155
  getDefinitions() {
107
- this.ensureUsable();
156
+ this.ensureNotDisposed();
108
157
  return this.registry.getDefinitions();
109
158
  }
110
159
  getHandlers() {
111
- this.ensureUsable();
112
- return this.emitter.getRegistrations();
160
+ this.ensureNotDisposed();
161
+ return this.registry.getHandlers();
113
162
  }
114
163
  getState() {
115
164
  return this.state;
@@ -121,7 +170,7 @@ export class EventBus {
121
170
  return this.registry.eventCount;
122
171
  }
123
172
  get handlerCount() {
124
- return this.emitter.listenerCount;
173
+ return this.registry.handlerCount;
125
174
  }
126
175
  subscribe(listener) {
127
176
  this.ensureNotDisposed();
@@ -145,17 +194,32 @@ export class EventBus {
145
194
  eventId: event?.id,
146
195
  });
147
196
  }
148
- ensureUsable() {
197
+ publishDependencies() {
198
+ return {
199
+ emitter: this.emitter,
200
+ registry: this.registry,
201
+ busMiddleware: this.busMiddleware,
202
+ requireRegistration: this.options.requireRegistration,
203
+ ensureUsable: () => this.ensureUsable("publish"),
204
+ notify: (e) => this.notify(e),
205
+ onError: this.options.onError,
206
+ };
207
+ }
208
+ /**
209
+ * Auto-starts a CREATED bus; rejects a STOPPED or DISPOSED one.
210
+ */
211
+ ensureUsable(operation) {
149
212
  this.ensureNotDisposed();
213
+ if (this.state === EventBusState.STOPPED) {
214
+ throw new EventBusStoppedError(operation);
215
+ }
150
216
  if (this.state === EventBusState.CREATED) {
151
217
  this.start();
152
218
  }
153
219
  }
154
220
  ensureNotDisposed() {
155
221
  if (this.state === EventBusState.DISPOSED) {
156
- throw new EventError("Event bus has already been disposed.", {
157
- code: "EVENT_BUS_DISPOSED",
158
- });
222
+ throw new EventBusDisposedError();
159
223
  }
160
224
  }
161
225
  notify(event) {
@@ -163,11 +227,20 @@ export class EventBus {
163
227
  try {
164
228
  listener(event);
165
229
  }
166
- catch {
230
+ catch (error) {
167
231
  /**
168
- * Observers must never be able to break
169
- * event bus operations.
232
+ * Observers must never be able to break event bus
233
+ * operations; failures go to the onError hook.
170
234
  */
235
+ try {
236
+ this.options.onError?.(error, {
237
+ source: "observer",
238
+ event: event.event,
239
+ });
240
+ }
241
+ catch {
242
+ // Ignore failures of the error hook itself.
243
+ }
171
244
  }
172
245
  }
173
246
  }
@@ -2,16 +2,34 @@
2
2
  * Event bus publish methods for Zudojs.
3
3
  */
4
4
  import type { Event, EventInput } from "../eventTypes/eventDefinition.type.js";
5
- import { EventEmitter } from "../eventEmitter/eventEmitter.core.js";
6
- import { EventRegistry } from "../eventRegistry/eventRegistry.store.js";
5
+ import type { EventEmitter } from "../eventEmitter/eventEmitter.core.js";
6
+ import type { EventEmitResult } from "../eventEmitter/eventEmitter.type.js";
7
+ import type { EventRegistry } from "../eventRegistry/eventRegistry.store.js";
7
8
  import type { RegisteredEventMiddleware } from "../eventMiddleware/eventMiddleware.type.js";
8
- import type { PublishOptions, EventPublishResult, EventBusEvent } from "./eventBus.type.js";
9
+ import type { PublishOptions, EventPublishResult, EventBusEvent, EventBusErrorContext } from "./eventBus.type.js";
10
+ /**
11
+ * Dependencies the publish functions need from the bus.
12
+ */
13
+ export interface BusPublishDependencies {
14
+ readonly emitter: EventEmitter;
15
+ readonly registry: EventRegistry;
16
+ readonly busMiddleware: readonly RegisteredEventMiddleware[];
17
+ readonly requireRegistration: boolean;
18
+ readonly ensureUsable: () => void;
19
+ readonly notify: (e: EventBusEvent) => void;
20
+ readonly onError?: (error: unknown, context: EventBusErrorContext) => void;
21
+ }
22
+ /**
23
+ * Determines whether a middleware pipeline result is the emit
24
+ * result produced by the terminal handler dispatch.
25
+ */
26
+ export declare function isEventEmitResult(value: unknown): value is EventEmitResult;
9
27
  /**
10
28
  * Publishes an event through the bus.
11
29
  */
12
- export declare function busPublish<TEvent extends Event>(event: TEvent, options: PublishOptions, emitter: EventEmitter, registry: EventRegistry, busMiddleware: RegisteredEventMiddleware[], requireRegistration: boolean, ensureUsable: () => void, notify: (e: EventBusEvent) => void): Promise<EventPublishResult<TEvent>>;
30
+ export declare function busPublish<TEvent extends Event>(event: TEvent, options: PublishOptions, deps: BusPublishDependencies): Promise<EventPublishResult<TEvent>>;
13
31
  /**
14
32
  * Creates and publishes an event from input data.
15
33
  */
16
- export declare function busPublishEvent<TPayload>(input: EventInput<TPayload>, options: PublishOptions, emitter: EventEmitter, registry: EventRegistry, busMiddleware: RegisteredEventMiddleware[], requireRegistration: boolean, ensureUsable: () => void, notify: (e: EventBusEvent) => void): Promise<EventPublishResult<Event<TPayload>>>;
34
+ export declare function busPublishEvent<TPayload>(input: EventInput<TPayload>, options: PublishOptions, deps: BusPublishDependencies): Promise<EventPublishResult<Event<TPayload>>>;
17
35
  //# sourceMappingURL=eventBus.publish.d.ts.map
@@ -1,78 +1,118 @@
1
1
  /**
2
2
  * Event bus publish methods for Zudojs.
3
3
  */
4
- import { createEvent } from "../eventTypes/eventDefinition.type.js";
5
- import { EventEmitter } from "../eventEmitter/eventEmitter.core.js";
6
- import { EventRegistry } from "../eventRegistry/eventRegistry.store.js";
7
- import { EventDispatchAbortedError, EventError, toEventError, } from "../eventErrors/eventError.base.js";
4
+ import { createEvent, isEvent } from "../eventTypes/eventDefinition.type.js";
5
+ import { EventDispatchAbortedError, EventTypeNotFoundError, InvalidEventError, } from "../eventErrors/eventError.base.js";
8
6
  import { createEventMiddlewareContext } from "../eventMiddleware/eventMiddleware.helper.js";
9
7
  import { executeEventMiddlewarePipeline } from "../eventMiddleware/eventMiddleware.pipeline.js";
10
8
  import { registerMiddlewareItem } from "./eventBus.registration.js";
9
+ /**
10
+ * Determines whether a middleware pipeline result is the emit
11
+ * result produced by the terminal handler dispatch.
12
+ */
13
+ export function isEventEmitResult(value) {
14
+ return (typeof value === "object" &&
15
+ value !== null &&
16
+ typeof value.handled === "boolean" &&
17
+ Array.isArray(value.results) &&
18
+ Array.isArray(value.errors));
19
+ }
11
20
  /**
12
21
  * Publishes an event through the bus.
13
22
  */
14
- export async function busPublish(event, options, emitter, registry, busMiddleware, requireRegistration, ensureUsable, notify) {
15
- ensureUsable();
23
+ export async function busPublish(event, options, deps) {
24
+ deps.ensureUsable();
25
+ if (!isEvent(event)) {
26
+ throw new InvalidEventError("publish() requires an Event (use publishEvent() for event input).");
27
+ }
16
28
  if (options.signal?.aborted) {
17
29
  throw new EventDispatchAbortedError("Event dispatch was aborted.", {
18
- eventType: event?.type,
19
- eventId: event?.id,
20
- });
21
- }
22
- if (requireRegistration && !registry.has(event.type)) {
23
- throw new EventError(`Event type "${event.type}" is not registered.`, {
24
30
  eventType: event.type,
25
31
  eventId: event.id,
26
32
  });
27
33
  }
34
+ if (deps.requireRegistration && !deps.registry.has(event.type)) {
35
+ throw new EventTypeNotFoundError(event.type);
36
+ }
28
37
  const allMiddleware = [
29
- ...busMiddleware,
30
- ...(options.middleware ?? []).map((m, index) => registerMiddlewareItem(m, index + busMiddleware.length)),
38
+ ...deps.busMiddleware,
39
+ ...(options.middleware ?? []).map((m, index) => registerMiddlewareItem(m, index, "publish-mw")),
31
40
  ];
32
41
  const middlewareContext = createEventMiddlewareContext(event, {
33
42
  signal: options.signal,
34
43
  metadata: options.metadata,
35
44
  });
36
45
  const terminal = async () => {
37
- return emitter.emit(event, {
46
+ return deps.emitter.emit(event, {
38
47
  mode: options.mode,
39
48
  errorMode: options.errorMode,
40
49
  signal: options.signal,
41
50
  metadata: options.metadata,
42
51
  });
43
52
  };
44
- let result;
45
- let middlewareResult;
53
+ let emitResult;
54
+ let middlewareExecutions;
46
55
  if (allMiddleware.length > 0) {
47
56
  const pipelineResult = await executeEventMiddlewarePipeline(allMiddleware, middlewareContext, terminal);
48
- result = pipelineResult.result;
49
- middlewareResult = {
50
- result: pipelineResult.result,
51
- executions: pipelineResult.executions,
52
- };
57
+ middlewareExecutions = pipelineResult.executions;
58
+ if (isEventEmitResult(pipelineResult.result)) {
59
+ emitResult = pipelineResult.result;
60
+ }
53
61
  }
54
62
  else {
55
- result = await terminal();
63
+ emitResult = await terminal();
56
64
  }
57
- notify({
65
+ deps.notify({
58
66
  type: "published",
59
67
  event,
60
68
  timestamp: new Date(),
61
69
  });
70
+ if (emitResult === undefined) {
71
+ /**
72
+ * A middleware short-circuited (did not call next()); no
73
+ * handler ran.
74
+ */
75
+ return {
76
+ event,
77
+ handled: false,
78
+ handlerCount: 0,
79
+ succeeded: 0,
80
+ failed: 0,
81
+ results: [],
82
+ errors: [],
83
+ shortCircuited: true,
84
+ middlewareExecutions,
85
+ };
86
+ }
87
+ if (deps.onError && emitResult.errors.length > 0) {
88
+ for (const error of emitResult.errors) {
89
+ try {
90
+ deps.onError(error, { source: "handler", event });
91
+ }
92
+ catch {
93
+ /**
94
+ * A failing error hook must not break publishing.
95
+ */
96
+ }
97
+ }
98
+ }
62
99
  return {
63
- event,
64
- handled: result.handled,
65
- handlerCount: result.results.length,
66
- results: result.results.map((execution) => execution.result),
67
- errors: result.errors,
68
- middlewareExecutions: middlewareResult?.executions,
100
+ event: emitResult.event,
101
+ handled: emitResult.handled,
102
+ handlerCount: emitResult.results.length,
103
+ succeeded: emitResult.succeeded,
104
+ failed: emitResult.failed,
105
+ results: emitResult.results.map((execution) => execution.result),
106
+ errors: emitResult.errors,
107
+ shortCircuited: false,
108
+ middlewareExecutions,
69
109
  };
70
110
  }
71
111
  /**
72
112
  * Creates and publishes an event from input data.
73
113
  */
74
- export async function busPublishEvent(input, options, emitter, registry, busMiddleware, requireRegistration, ensureUsable, notify) {
114
+ export async function busPublishEvent(input, options, deps) {
75
115
  const event = createEvent(input);
76
- return busPublish(event, options, emitter, registry, busMiddleware, requireRegistration, ensureUsable, notify);
116
+ return busPublish(event, options, deps);
77
117
  }
78
118
  //# sourceMappingURL=eventBus.publish.js.map