@zudojs/events 0.1.0 → 1.0.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.
- package/LICENSE +21 -0
- package/README.md +70 -11
- package/dist/eventBus/eventBus.core.d.ts +28 -2
- package/dist/eventBus/eventBus.core.js +109 -31
- package/dist/eventBus/eventBus.publish.d.ts +23 -5
- package/dist/eventBus/eventBus.publish.js +78 -32
- package/dist/eventBus/eventBus.registration.d.ts +24 -13
- package/dist/eventBus/eventBus.registration.js +31 -28
- package/dist/eventBus/eventBus.type.d.ts +75 -3
- package/dist/eventBus/eventBus.type.js +9 -0
- package/dist/eventBus/index.d.ts +2 -0
- package/dist/eventBus/index.js +2 -0
- package/dist/eventEmitter/eventEmitter.abort.d.ts +11 -2
- package/dist/eventEmitter/eventEmitter.abort.js +20 -5
- package/dist/eventEmitter/eventEmitter.core.d.ts +14 -3
- package/dist/eventEmitter/eventEmitter.core.js +72 -56
- package/dist/eventEmitter/eventEmitter.parallel.d.ts +7 -1
- package/dist/eventEmitter/eventEmitter.parallel.js +31 -13
- package/dist/eventEmitter/eventEmitter.sequential.d.ts +17 -1
- package/dist/eventEmitter/eventEmitter.sequential.js +19 -12
- package/dist/eventEmitter/eventEmitter.type.d.ts +60 -0
- package/dist/eventErrors/eventError.base.d.ts +50 -3
- package/dist/eventErrors/eventError.base.js +64 -3
- package/dist/eventHandler/eventHandler.core.d.ts +19 -0
- package/dist/eventHandler/eventHandler.core.js +67 -4
- package/dist/eventMiddleware/eventMiddleware.builder.js +5 -11
- package/dist/eventMiddleware/eventMiddleware.helper.js +0 -1
- package/dist/eventMiddleware/eventMiddleware.pipeline.d.ts +5 -0
- package/dist/eventMiddleware/eventMiddleware.pipeline.js +33 -9
- package/dist/eventRegistry/eventRegistry.lifecycle.d.ts +10 -5
- package/dist/eventRegistry/eventRegistry.lifecycle.js +23 -10
- package/dist/eventRegistry/eventRegistry.queries.d.ts +16 -7
- package/dist/eventRegistry/eventRegistry.queries.js +22 -13
- package/dist/eventRegistry/eventRegistry.registration.d.ts +14 -7
- package/dist/eventRegistry/eventRegistry.registration.js +120 -41
- package/dist/eventRegistry/eventRegistry.store.d.ts +12 -5
- package/dist/eventRegistry/eventRegistry.store.js +37 -12
- package/dist/eventRegistry/eventRegistry.type.d.ts +87 -2
- package/dist/eventRegistry/eventRegistry.type.js +5 -0
- package/dist/eventSubscription/eventSubscription.core.d.ts +5 -0
- package/dist/eventSubscription/eventSubscription.core.js +17 -3
- package/dist/eventTypes/eventDefinition.type.d.ts +11 -4
- package/dist/eventTypes/eventDefinition.type.js +49 -17
- package/dist/eventTypes/eventPayload.type.d.ts +5 -0
- package/dist/eventTypes/eventPayload.type.js +62 -22
- package/dist/eventTypes/eventType.type.d.ts +22 -5
- package/dist/eventTypes/eventType.type.js +33 -11
- package/dist/eventTypes/index.d.ts +2 -2
- package/dist/eventTypes/index.js +2 -2
- package/package.json +26 -14
- package/dist/.tsbuildinfo +0 -1
- package/dist/eventBus/eventBus.core.d.ts.map +0 -1
- package/dist/eventBus/eventBus.core.js.map +0 -1
- package/dist/eventBus/eventBus.factory.d.ts.map +0 -1
- package/dist/eventBus/eventBus.factory.js.map +0 -1
- package/dist/eventBus/eventBus.publish.d.ts.map +0 -1
- package/dist/eventBus/eventBus.publish.js.map +0 -1
- package/dist/eventBus/eventBus.registration.d.ts.map +0 -1
- package/dist/eventBus/eventBus.registration.js.map +0 -1
- package/dist/eventBus/eventBus.type.d.ts.map +0 -1
- package/dist/eventBus/eventBus.type.js.map +0 -1
- package/dist/eventBus/index.d.ts.map +0 -1
- package/dist/eventBus/index.js.map +0 -1
- package/dist/eventEmitter/eventEmitter.abort.d.ts.map +0 -1
- package/dist/eventEmitter/eventEmitter.abort.js.map +0 -1
- package/dist/eventEmitter/eventEmitter.core.d.ts.map +0 -1
- package/dist/eventEmitter/eventEmitter.core.js.map +0 -1
- package/dist/eventEmitter/eventEmitter.parallel.d.ts.map +0 -1
- package/dist/eventEmitter/eventEmitter.parallel.js.map +0 -1
- package/dist/eventEmitter/eventEmitter.sequential.d.ts.map +0 -1
- package/dist/eventEmitter/eventEmitter.sequential.js.map +0 -1
- package/dist/eventEmitter/eventEmitter.type.d.ts.map +0 -1
- package/dist/eventEmitter/eventEmitter.type.js.map +0 -1
- package/dist/eventEmitter/index.d.ts.map +0 -1
- package/dist/eventEmitter/index.js.map +0 -1
- package/dist/eventErrors/eventError.base.d.ts.map +0 -1
- package/dist/eventErrors/eventError.base.js.map +0 -1
- package/dist/eventErrors/index.d.ts.map +0 -1
- package/dist/eventErrors/index.js.map +0 -1
- package/dist/eventHandler/eventHandler.core.d.ts.map +0 -1
- package/dist/eventHandler/eventHandler.core.js.map +0 -1
- package/dist/eventHandler/index.d.ts.map +0 -1
- package/dist/eventHandler/index.js.map +0 -1
- package/dist/eventMiddleware/eventMiddleware.builder.d.ts.map +0 -1
- package/dist/eventMiddleware/eventMiddleware.builder.js.map +0 -1
- package/dist/eventMiddleware/eventMiddleware.helper.d.ts.map +0 -1
- package/dist/eventMiddleware/eventMiddleware.helper.js.map +0 -1
- package/dist/eventMiddleware/eventMiddleware.pipeline.d.ts.map +0 -1
- package/dist/eventMiddleware/eventMiddleware.pipeline.js.map +0 -1
- package/dist/eventMiddleware/eventMiddleware.type.d.ts.map +0 -1
- package/dist/eventMiddleware/eventMiddleware.type.js.map +0 -1
- package/dist/eventMiddleware/index.d.ts.map +0 -1
- package/dist/eventMiddleware/index.js.map +0 -1
- package/dist/eventRegistry/eventRegistry.lifecycle.d.ts.map +0 -1
- package/dist/eventRegistry/eventRegistry.lifecycle.js.map +0 -1
- package/dist/eventRegistry/eventRegistry.queries.d.ts.map +0 -1
- package/dist/eventRegistry/eventRegistry.queries.js.map +0 -1
- package/dist/eventRegistry/eventRegistry.registration.d.ts.map +0 -1
- package/dist/eventRegistry/eventRegistry.registration.js.map +0 -1
- package/dist/eventRegistry/eventRegistry.store.d.ts.map +0 -1
- package/dist/eventRegistry/eventRegistry.store.js.map +0 -1
- package/dist/eventRegistry/eventRegistry.type.d.ts.map +0 -1
- package/dist/eventRegistry/eventRegistry.type.js.map +0 -1
- package/dist/eventRegistry/index.d.ts.map +0 -1
- package/dist/eventRegistry/index.js.map +0 -1
- package/dist/eventSubscription/eventSubscription.core.d.ts.map +0 -1
- package/dist/eventSubscription/eventSubscription.core.js.map +0 -1
- package/dist/eventSubscription/index.d.ts.map +0 -1
- package/dist/eventSubscription/index.js.map +0 -1
- package/dist/eventTypes/eventDefinition.type.d.ts.map +0 -1
- package/dist/eventTypes/eventDefinition.type.js.map +0 -1
- package/dist/eventTypes/eventPayload.type.d.ts.map +0 -1
- package/dist/eventTypes/eventPayload.type.js.map +0 -1
- package/dist/eventTypes/eventType.type.d.ts.map +0 -1
- package/dist/eventTypes/eventType.type.js.map +0 -1
- package/dist/eventTypes/index.d.ts.map +0 -1
- package/dist/eventTypes/index.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- 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
|
-
|
|
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: "
|
|
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
|
-
-
|
|
32
|
-
-
|
|
33
|
-
- Wildcard
|
|
34
|
-
-
|
|
35
|
-
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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,23 @@
|
|
|
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 {
|
|
8
|
+
import { normalizeRegistryEventType } from "../eventRegistry/eventRegistry.registration.js";
|
|
9
|
+
import { EventBusDisposedError, EventBusStoppedError, EventError, toEventError, } from "../eventErrors/eventError.base.js";
|
|
8
10
|
import { EventBusState } from "./eventBus.type.js";
|
|
9
11
|
export { EventBusState } from "./eventBus.type.js";
|
|
10
12
|
import { busOn, busOnce, busOnAny, busOff, busUse, registerMiddlewareItem, } from "./eventBus.registration.js";
|
|
11
13
|
import { busPublish, busPublishEvent } from "./eventBus.publish.js";
|
|
12
14
|
/**
|
|
13
15
|
* High-level event bus.
|
|
16
|
+
*
|
|
17
|
+
* Lifecycle: CREATED → ACTIVE ⇄ STOPPED → DISPOSED. A CREATED bus
|
|
18
|
+
* starts itself on first use; a STOPPED bus rejects publishing and
|
|
19
|
+
* subscribing until start() is called; a DISPOSED bus rejects
|
|
20
|
+
* everything.
|
|
14
21
|
*/
|
|
15
22
|
export class EventBus {
|
|
16
23
|
emitter;
|
|
@@ -22,11 +29,27 @@ export class EventBus {
|
|
|
22
29
|
constructor(options = {}) {
|
|
23
30
|
this.options = {
|
|
24
31
|
requireRegistration: options.requireRegistration ?? false,
|
|
32
|
+
onError: options.onError,
|
|
25
33
|
};
|
|
26
|
-
this.registry = new EventRegistry(
|
|
34
|
+
this.registry = new EventRegistry({
|
|
35
|
+
...options.registry,
|
|
36
|
+
maxHandlersPerPattern: options.emitter?.maxListeners,
|
|
37
|
+
onWarning: options.onWarning,
|
|
38
|
+
onError: options.onError
|
|
39
|
+
? (error, context) => options.onError?.(error, {
|
|
40
|
+
source: context.source,
|
|
41
|
+
})
|
|
42
|
+
: undefined,
|
|
43
|
+
});
|
|
44
|
+
/**
|
|
45
|
+
* The emitter stores its handlers in the bus registry so
|
|
46
|
+
* handlers registered through either surface are dispatched.
|
|
47
|
+
*/
|
|
27
48
|
this.emitter = new EventEmitter({
|
|
28
|
-
|
|
49
|
+
mode: options.emitter?.mode,
|
|
29
50
|
errorMode: options.emitter?.errorMode ?? EventErrorMode.CONTINUE,
|
|
51
|
+
freezeEvents: options.emitter?.freezeEvents,
|
|
52
|
+
store: this.registry,
|
|
30
53
|
});
|
|
31
54
|
this.busMiddleware = (options.middleware ?? []).map((m, index) => registerMiddlewareItem(m, index));
|
|
32
55
|
}
|
|
@@ -42,12 +65,17 @@ export class EventBus {
|
|
|
42
65
|
});
|
|
43
66
|
return this;
|
|
44
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Stops the bus. Publishing and subscribing throw
|
|
70
|
+
* EventBusStoppedError until start() is called again; handlers
|
|
71
|
+
* and definitions are kept.
|
|
72
|
+
*/
|
|
45
73
|
stop() {
|
|
46
74
|
this.ensureNotDisposed();
|
|
47
75
|
if (this.state !== EventBusState.ACTIVE) {
|
|
48
76
|
return this;
|
|
49
77
|
}
|
|
50
|
-
this.state = EventBusState.
|
|
78
|
+
this.state = EventBusState.STOPPED;
|
|
51
79
|
this.notify({
|
|
52
80
|
type: "stopped",
|
|
53
81
|
timestamp: new Date(),
|
|
@@ -55,17 +83,17 @@ export class EventBus {
|
|
|
55
83
|
return this;
|
|
56
84
|
}
|
|
57
85
|
register(definition) {
|
|
58
|
-
this.ensureUsable();
|
|
86
|
+
this.ensureUsable("register");
|
|
59
87
|
return this.registry.register(definition);
|
|
60
88
|
}
|
|
61
89
|
on(eventType, handler, options = {}) {
|
|
62
|
-
return busOn(this.emitter, eventType, handler, options, () => this.ensureUsable());
|
|
90
|
+
return busOn(this.emitter, eventType, handler, options, () => this.ensureUsable("subscribe"));
|
|
63
91
|
}
|
|
64
92
|
once(eventType, handler, options = {}) {
|
|
65
|
-
return busOnce(this.emitter, eventType, handler, options, () => this.ensureUsable());
|
|
93
|
+
return busOnce(this.emitter, eventType, handler, options, () => this.ensureUsable("subscribe"));
|
|
66
94
|
}
|
|
67
95
|
onAny(handler, options = {}) {
|
|
68
|
-
return busOnAny(this.emitter, handler, options, () => this.ensureUsable());
|
|
96
|
+
return busOnAny(this.emitter, handler, options, () => this.ensureUsable("subscribe"));
|
|
69
97
|
}
|
|
70
98
|
off(subscription) {
|
|
71
99
|
return busOff(this.emitter, subscription, () => this.ensureNotDisposed());
|
|
@@ -75,41 +103,67 @@ export class EventBus {
|
|
|
75
103
|
return busUse(this.busMiddleware, middleware, options);
|
|
76
104
|
}
|
|
77
105
|
async publish(event, options = {}) {
|
|
78
|
-
return busPublish(event, options, this.
|
|
106
|
+
return busPublish(event, options, this.publishDependencies());
|
|
79
107
|
}
|
|
80
108
|
async publishEvent(input, options = {}) {
|
|
81
|
-
return busPublishEvent(input, options, this.
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
|
|
109
|
+
return busPublishEvent(input, options, this.publishDependencies());
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Publishes an event or event input. A full Event is published
|
|
113
|
+
* as is; an EventInput ({ type, payload, ... }) is turned into
|
|
114
|
+
* an event first.
|
|
115
|
+
*/
|
|
116
|
+
async emit(input, options = {}) {
|
|
117
|
+
if (isEvent(input)) {
|
|
118
|
+
return this.publish(input, options);
|
|
119
|
+
}
|
|
120
|
+
return this.publishEvent(input, options);
|
|
85
121
|
}
|
|
86
122
|
getDefinition(eventType) {
|
|
87
|
-
this.
|
|
123
|
+
this.ensureNotDisposed();
|
|
88
124
|
return this.registry.get(eventType);
|
|
89
125
|
}
|
|
90
126
|
hasEvent(eventType) {
|
|
91
|
-
this.
|
|
127
|
+
this.ensureNotDisposed();
|
|
92
128
|
return this.registry.has(eventType);
|
|
93
129
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
130
|
+
/**
|
|
131
|
+
* Removes an event definition. Handlers are not affected unless
|
|
132
|
+
* `removeHandlers` is true, in which case every handler whose
|
|
133
|
+
* pattern is exactly this event type is unregistered too.
|
|
134
|
+
*/
|
|
135
|
+
unregister(eventType, options = {}) {
|
|
136
|
+
this.ensureNotDisposed();
|
|
137
|
+
const removed = this.registry.unregister(eventType);
|
|
138
|
+
if (options.removeHandlers) {
|
|
139
|
+
/**
|
|
140
|
+
* Compare patterns on the full handler list: the matching
|
|
141
|
+
* query skips disabled handlers, which must be dropped too.
|
|
142
|
+
*/
|
|
143
|
+
const type = normalizeRegistryEventType(eventType);
|
|
144
|
+
for (const handler of this.registry.getHandlers()) {
|
|
145
|
+
if (handler.eventType === type) {
|
|
146
|
+
this.registry.unregisterHandler(handler.id);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return removed;
|
|
97
151
|
}
|
|
98
152
|
getRegistry() {
|
|
99
|
-
this.
|
|
153
|
+
this.ensureNotDisposed();
|
|
100
154
|
return this.registry;
|
|
101
155
|
}
|
|
102
156
|
getEmitter() {
|
|
103
|
-
this.
|
|
157
|
+
this.ensureNotDisposed();
|
|
104
158
|
return this.emitter;
|
|
105
159
|
}
|
|
106
160
|
getDefinitions() {
|
|
107
|
-
this.
|
|
161
|
+
this.ensureNotDisposed();
|
|
108
162
|
return this.registry.getDefinitions();
|
|
109
163
|
}
|
|
110
164
|
getHandlers() {
|
|
111
|
-
this.
|
|
112
|
-
return this.
|
|
165
|
+
this.ensureNotDisposed();
|
|
166
|
+
return this.registry.getHandlers();
|
|
113
167
|
}
|
|
114
168
|
getState() {
|
|
115
169
|
return this.state;
|
|
@@ -121,7 +175,7 @@ export class EventBus {
|
|
|
121
175
|
return this.registry.eventCount;
|
|
122
176
|
}
|
|
123
177
|
get handlerCount() {
|
|
124
|
-
return this.
|
|
178
|
+
return this.registry.handlerCount;
|
|
125
179
|
}
|
|
126
180
|
subscribe(listener) {
|
|
127
181
|
this.ensureNotDisposed();
|
|
@@ -145,17 +199,32 @@ export class EventBus {
|
|
|
145
199
|
eventId: event?.id,
|
|
146
200
|
});
|
|
147
201
|
}
|
|
148
|
-
|
|
202
|
+
publishDependencies() {
|
|
203
|
+
return {
|
|
204
|
+
emitter: this.emitter,
|
|
205
|
+
registry: this.registry,
|
|
206
|
+
busMiddleware: this.busMiddleware,
|
|
207
|
+
requireRegistration: this.options.requireRegistration,
|
|
208
|
+
ensureUsable: () => this.ensureUsable("publish"),
|
|
209
|
+
notify: (e) => this.notify(e),
|
|
210
|
+
onError: this.options.onError,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Auto-starts a CREATED bus; rejects a STOPPED or DISPOSED one.
|
|
215
|
+
*/
|
|
216
|
+
ensureUsable(operation) {
|
|
149
217
|
this.ensureNotDisposed();
|
|
218
|
+
if (this.state === EventBusState.STOPPED) {
|
|
219
|
+
throw new EventBusStoppedError(operation);
|
|
220
|
+
}
|
|
150
221
|
if (this.state === EventBusState.CREATED) {
|
|
151
222
|
this.start();
|
|
152
223
|
}
|
|
153
224
|
}
|
|
154
225
|
ensureNotDisposed() {
|
|
155
226
|
if (this.state === EventBusState.DISPOSED) {
|
|
156
|
-
throw new
|
|
157
|
-
code: "EVENT_BUS_DISPOSED",
|
|
158
|
-
});
|
|
227
|
+
throw new EventBusDisposedError();
|
|
159
228
|
}
|
|
160
229
|
}
|
|
161
230
|
notify(event) {
|
|
@@ -163,11 +232,20 @@ export class EventBus {
|
|
|
163
232
|
try {
|
|
164
233
|
listener(event);
|
|
165
234
|
}
|
|
166
|
-
catch {
|
|
235
|
+
catch (error) {
|
|
167
236
|
/**
|
|
168
|
-
* Observers must never be able to break
|
|
169
|
-
*
|
|
237
|
+
* Observers must never be able to break event bus
|
|
238
|
+
* operations; failures go to the onError hook.
|
|
170
239
|
*/
|
|
240
|
+
try {
|
|
241
|
+
this.options.onError?.(error, {
|
|
242
|
+
source: "observer",
|
|
243
|
+
event: event.event,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
// Ignore failures of the error hook itself.
|
|
248
|
+
}
|
|
171
249
|
}
|
|
172
250
|
}
|
|
173
251
|
}
|
|
@@ -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 {
|
|
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,
|
|
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,
|
|
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,124 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Event bus publish methods for Zudojs.
|
|
3
3
|
*/
|
|
4
|
-
import { createEvent } from "../eventTypes/eventDefinition.type.js";
|
|
5
|
-
import {
|
|
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,
|
|
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
|
|
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
|
});
|
|
45
|
+
/**
|
|
46
|
+
* The emit result is captured here, at the terminal, rather
|
|
47
|
+
* than read back from the pipeline's return value: a middleware
|
|
48
|
+
* that awaits next() and returns nothing (or something else)
|
|
49
|
+
* has still dispatched the handlers, and their outcome must not
|
|
50
|
+
* be reported as a short-circuit.
|
|
51
|
+
*/
|
|
52
|
+
let emitResult;
|
|
36
53
|
const terminal = async () => {
|
|
37
|
-
|
|
54
|
+
const result = await deps.emitter.emit(event, {
|
|
38
55
|
mode: options.mode,
|
|
39
56
|
errorMode: options.errorMode,
|
|
40
57
|
signal: options.signal,
|
|
41
58
|
metadata: options.metadata,
|
|
42
59
|
});
|
|
60
|
+
emitResult = result;
|
|
61
|
+
return result;
|
|
43
62
|
};
|
|
44
|
-
let
|
|
45
|
-
let middlewareResult;
|
|
63
|
+
let middlewareExecutions;
|
|
46
64
|
if (allMiddleware.length > 0) {
|
|
47
65
|
const pipelineResult = await executeEventMiddlewarePipeline(allMiddleware, middlewareContext, terminal);
|
|
48
|
-
|
|
49
|
-
middlewareResult = {
|
|
50
|
-
result: pipelineResult.result,
|
|
51
|
-
executions: pipelineResult.executions,
|
|
52
|
-
};
|
|
66
|
+
middlewareExecutions = pipelineResult.executions;
|
|
53
67
|
}
|
|
54
68
|
else {
|
|
55
|
-
|
|
69
|
+
await terminal();
|
|
56
70
|
}
|
|
57
|
-
notify({
|
|
71
|
+
deps.notify({
|
|
58
72
|
type: "published",
|
|
59
73
|
event,
|
|
60
74
|
timestamp: new Date(),
|
|
61
75
|
});
|
|
76
|
+
if (emitResult === undefined) {
|
|
77
|
+
/**
|
|
78
|
+
* A middleware short-circuited (did not call next()); no
|
|
79
|
+
* handler ran.
|
|
80
|
+
*/
|
|
81
|
+
return {
|
|
82
|
+
event,
|
|
83
|
+
handled: false,
|
|
84
|
+
handlerCount: 0,
|
|
85
|
+
succeeded: 0,
|
|
86
|
+
failed: 0,
|
|
87
|
+
results: [],
|
|
88
|
+
errors: [],
|
|
89
|
+
shortCircuited: true,
|
|
90
|
+
middlewareExecutions,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
if (deps.onError && emitResult.errors.length > 0) {
|
|
94
|
+
for (const error of emitResult.errors) {
|
|
95
|
+
try {
|
|
96
|
+
deps.onError(error, { source: "handler", event });
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
/**
|
|
100
|
+
* A failing error hook must not break publishing.
|
|
101
|
+
*/
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
62
105
|
return {
|
|
63
|
-
event,
|
|
64
|
-
handled:
|
|
65
|
-
handlerCount:
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
106
|
+
event: emitResult.event,
|
|
107
|
+
handled: emitResult.handled,
|
|
108
|
+
handlerCount: emitResult.results.length,
|
|
109
|
+
succeeded: emitResult.succeeded,
|
|
110
|
+
failed: emitResult.failed,
|
|
111
|
+
results: emitResult.results.map((execution) => execution.result),
|
|
112
|
+
errors: emitResult.errors,
|
|
113
|
+
shortCircuited: false,
|
|
114
|
+
middlewareExecutions,
|
|
69
115
|
};
|
|
70
116
|
}
|
|
71
117
|
/**
|
|
72
118
|
* Creates and publishes an event from input data.
|
|
73
119
|
*/
|
|
74
|
-
export async function busPublishEvent(input, options,
|
|
120
|
+
export async function busPublishEvent(input, options, deps) {
|
|
75
121
|
const event = createEvent(input);
|
|
76
|
-
return busPublish(event, options,
|
|
122
|
+
return busPublish(event, options, deps);
|
|
77
123
|
}
|
|
78
124
|
//# sourceMappingURL=eventBus.publish.js.map
|