@zudojs/events 1.2.0 → 1.3.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/README.md CHANGED
@@ -50,13 +50,39 @@ await bus.publishEvent({
50
50
  - Event bus with a middleware pipeline (`use()`, constructor and per-publish middleware)
51
51
  - Sequential or parallel handler dispatch with `THROW` / `CONTINUE` error modes
52
52
  - Handler priorities, one-time handlers, per-handler timeouts
53
- - Wildcard subscriptions (`"user.*"`, `"*"`)
53
+ - Wildcard subscriptions (`"user.*"`, `"*"`) — multi-level, see [Wildcard patterns](#wildcard-patterns)
54
54
  - Event registry for typed definitions; handlers registered on the registry are dispatched by the bus
55
55
  - Deep-frozen events (`freezeEvents`, on by default) so handlers cannot alter what other handlers see: handlers receive a frozen *copy* (`createFrozenEventSnapshot`), so the publisher's own objects are never frozen, and Map, Set and Date values (including `event.timestamp`) become read-only variants that throw on mutation. Class instances are passed by reference.
56
56
  - A handler unsubscribed by an earlier handler in the same dispatch, or left over after the bus is disposed mid-dispatch, does not run
57
57
  - Listener-leak warnings (`maxListeners`, reported through `onWarning` or, by default, `process.emitWarning` with type `ZudojsEventsWarning`) and an `onError` hook for fire-and-forget publishes
58
58
  - Typed error classes from `@zudojs/errors` (`EventHandlerError`, `EventMiddlewareError`, `EventDispatchAbortedError`, …)
59
59
 
60
+ ## Wildcard patterns
61
+
62
+ A handler's event type can be an exact type, a namespace wildcard, or the
63
+ catch-all `"*"`:
64
+
65
+ | Pattern | Matches | Does not match |
66
+ |---------|---------|----------------|
67
+ | `"task.created"` | `task.created` | `task.created.v2`, `task` |
68
+ | `"task.*"` | `task`, `task.created`, `task.sub.created` (any depth) | `tasks.created`, `order.created` |
69
+ | `"*"` | every event | — |
70
+
71
+ A namespace wildcard is **multi-level**: `"task.*"` matches every event
72
+ under `task` at any depth, plus the bare `task` event, like a prefix match
73
+ on whole segments. There is no single-level form — `"task.*.created"`,
74
+ `"task.cre*"` and other positions are rejected as invalid patterns. To
75
+ handle only direct children, filter in the handler:
76
+
77
+ ```typescript
78
+ import { getEventTypeSegments } from "@zudojs/events";
79
+
80
+ bus.on("task.*", (event) => {
81
+ if (getEventTypeSegments(event.type).length !== 2) return; // task.x only
82
+ // ...
83
+ });
84
+ ```
85
+
60
86
  ## Lifecycle
61
87
 
62
88
  ```
@@ -71,6 +97,9 @@ CREATED ──(first use / start)──▶ ACTIVE ◀──(start)── STOPPED
71
97
  - `stop()` moves the bus to `STOPPED`; publishing or subscribing then throws
72
98
  `EventBusStoppedError` until `start()` is called. Handlers and definitions are kept.
73
99
  - `dispose()` is final; every operation throws `EventBusDisposedError`.
100
+ - Both errors are defined in `@zudojs/errors` (as `EventError` subclasses) and
101
+ re-exported from `@zudojs/events`, so `instanceof` works whichever package you
102
+ import them from.
74
103
 
75
104
  ## Publish results
76
105
 
@@ -90,6 +119,33 @@ In the default `CONTINUE` error mode handler failures are collected in
90
119
  `EventHandlerError` rejects the publish. Errors thrown by a middleware itself are
91
120
  wrapped as `EventMiddlewareError`; handler errors and aborts pass through unwrapped.
92
121
 
122
+ ## Middleware
123
+
124
+ `bus.use()` accepts everything the `middleware` constructor option does: a
125
+ middleware function or `{ handle }` object, or a registered middleware from
126
+ `createEventMiddleware()` or a builder helper (`validateEventMiddleware`,
127
+ `beforeEvent`, `aroundEvent`, `timingEventMiddleware`, …). It returns a function
128
+ that removes the middleware again.
129
+
130
+ ```typescript
131
+ const remove = bus.use(validateEventMiddleware((event) => event.payload != null));
132
+ ```
133
+
134
+ ## Timeouts and aborts
135
+
136
+ A handler registered with `timeoutMs` fails with `EventTimeoutError` when it
137
+ does not settle in time, and the `context.signal` it received is aborted with
138
+ that error as its `reason`, so a handler that listens to the signal can stop
139
+ its work. Timing out one handler does not abort the dispatch or other handlers.
140
+
141
+ Pass `signal` to `publish()` to abort a sequential dispatch. The publish rejects
142
+ with `EventDispatchAbortedError` (carrying the partial `results` and `errors`)
143
+ when the signal is aborted before a handler starts **or while any handler is
144
+ running — including the last or only one**, even if that handler then returns
145
+ normally. In `PARALLEL` mode every handler has already started, so only a
146
+ signal that is aborted before dispatch begins rejects; handlers can still
147
+ observe `context.signal`.
148
+
93
149
  ## Registry
94
150
 
95
151
  `bus.register(defineEvent("order.placed"))` records a definition; with
@@ -8,8 +8,8 @@ import type { EventSubscription } from "../eventSubscription/eventSubscription.c
8
8
  import { EventEmitter } from "../eventEmitter/eventEmitter.core.js";
9
9
  import { EventRegistry } from "../eventRegistry/eventRegistry.store.js";
10
10
  import { EventError } from "../eventErrors/eventError.base.js";
11
- import type { EventMiddlewareLike, EventMiddlewareOptions } from "../eventMiddleware/eventMiddleware.type.js";
12
- import type { EventBusOptions, PublishOptions, EventPublishResult, EventBusListener } from "./eventBus.type.js";
11
+ import type { EventMiddlewareOptions } from "../eventMiddleware/eventMiddleware.type.js";
12
+ import type { EventBusMiddlewareItem, EventBusOptions, PublishOptions, EventPublishResult, EventBusListener } from "./eventBus.type.js";
13
13
  import { EventBusState } from "./eventBus.type.js";
14
14
  export { EventBusState } from "./eventBus.type.js";
15
15
  /**
@@ -40,7 +40,12 @@ export declare class EventBus {
40
40
  once<TEvent extends Event = Event>(eventType: EventTypePattern, handler: EventHandlerLike<TEvent>, options?: Omit<EventHandlerOptions, "eventType" | "once">): EventSubscription;
41
41
  onAny<TEvent extends Event = Event>(handler: EventHandlerLike<TEvent>, options?: Omit<EventHandlerOptions, "eventType">): EventSubscription;
42
42
  off(subscription: EventSubscription): boolean;
43
- use(middleware: EventMiddlewareLike, options?: EventMiddlewareOptions): () => void;
43
+ /**
44
+ * Adds bus middleware: a middleware function or object, or a
45
+ * registered middleware from createEventMiddleware() or a builder
46
+ * helper such as validateEventMiddleware().
47
+ */
48
+ use(middleware: EventBusMiddlewareItem, options?: EventMiddlewareOptions): () => void;
44
49
  publish<TEvent extends Event>(event: TEvent, options?: PublishOptions): Promise<EventPublishResult<TEvent>>;
45
50
  publishEvent<TPayload>(input: EventInput<TPayload>, options?: PublishOptions): Promise<EventPublishResult<Event<TPayload>>>;
46
51
  /**
@@ -100,6 +100,11 @@ export class EventBus {
100
100
  off(subscription) {
101
101
  return busOff(this.emitter, subscription, () => this.ensureNotDisposed());
102
102
  }
103
+ /**
104
+ * Adds bus middleware: a middleware function or object, or a
105
+ * registered middleware from createEventMiddleware() or a builder
106
+ * helper such as validateEventMiddleware().
107
+ */
103
108
  use(middleware, options = {}) {
104
109
  this.ensureNotDisposed();
105
110
  return busUse(this.busMiddleware, middleware, options);
@@ -5,7 +5,7 @@ import type { Event, EventDefinition, EventType } from "../eventTypes/eventDefin
5
5
  import type { EventTypePattern } from "../eventTypes/eventType.type.js";
6
6
  import type { EventHandlerLike, EventHandlerOptions } from "../eventHandler/eventHandler.core.js";
7
7
  import type { EventSubscription } from "../eventSubscription/eventSubscription.core.js";
8
- import type { EventMiddlewareLike, EventMiddlewareOptions, RegisteredEventMiddleware } from "../eventMiddleware/eventMiddleware.type.js";
8
+ import type { EventMiddlewareOptions, RegisteredEventMiddleware } from "../eventMiddleware/eventMiddleware.type.js";
9
9
  import type { RegisteredEventDefinition } from "../eventRegistry/eventRegistry.type.js";
10
10
  import type { EventBusMiddlewareItem } from "./eventBus.type.js";
11
11
  /**
@@ -39,8 +39,15 @@ export declare function busOff(emitter: {
39
39
  * Adds middleware to the bus. The middleware is validated
40
40
  * eagerly (invalid middleware or a non-finite priority throw
41
41
  * here, not on the next publish).
42
+ *
43
+ * Accepts the same items as the `middleware` constructor option: a
44
+ * plain middleware function or object, or a registered middleware
45
+ * made by createEventMiddleware() or a builder helper such as
46
+ * validateEventMiddleware(). For a registered middleware its own id,
47
+ * description, priority and enabled flag are kept unless `options`
48
+ * overrides them.
42
49
  */
43
- export declare function busUse(busMiddleware: RegisteredEventMiddleware[], middleware: EventMiddlewareLike, options?: EventMiddlewareOptions): () => void;
50
+ export declare function busUse(busMiddleware: RegisteredEventMiddleware[], middleware: EventBusMiddlewareItem, options?: EventMiddlewareOptions): () => void;
44
51
  /**
45
52
  * Determines whether a value is an already registered middleware
46
53
  * (created by createEventMiddleware or a builder helper).
@@ -42,9 +42,23 @@ export function busOff(emitter, subscription, ensureNotDisposed) {
42
42
  * Adds middleware to the bus. The middleware is validated
43
43
  * eagerly (invalid middleware or a non-finite priority throw
44
44
  * here, not on the next publish).
45
+ *
46
+ * Accepts the same items as the `middleware` constructor option: a
47
+ * plain middleware function or object, or a registered middleware
48
+ * made by createEventMiddleware() or a builder helper such as
49
+ * validateEventMiddleware(). For a registered middleware its own id,
50
+ * description, priority and enabled flag are kept unless `options`
51
+ * overrides them.
45
52
  */
46
53
  export function busUse(busMiddleware, middleware, options = {}) {
47
- const registered = createEventMiddleware(middleware, options);
54
+ const registered = isRegisteredEventMiddleware(middleware)
55
+ ? createEventMiddleware(middleware.middleware, {
56
+ id: options.id ?? middleware.id,
57
+ description: options.description ?? middleware.description,
58
+ priority: options.priority ?? middleware.priority,
59
+ enabled: options.enabled ?? middleware.enabled,
60
+ })
61
+ : createEventMiddleware(middleware, options);
48
62
  busMiddleware.push(registered);
49
63
  return () => {
50
64
  const idx = busMiddleware.indexOf(registered);
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Event bus type definitions for Zudojs.
3
3
  */
4
+ import type { EventHandlerError } from "@zudojs/errors";
4
5
  import type { Event } from "../eventTypes/eventDefinition.type.js";
5
6
  import type { EventEmitterMode, EventErrorMode } from "../eventEmitter/eventEmitter.type.js";
6
7
  import type { EventMiddlewareLike, RegisteredEventMiddleware } from "../eventMiddleware/eventMiddleware.type.js";
@@ -93,7 +94,11 @@ export interface EventPublishResult<TEvent extends Event = Event> {
93
94
  */
94
95
  readonly failed: number;
95
96
  readonly results: readonly unknown[];
96
- readonly errors: readonly unknown[];
97
+ /**
98
+ * Handler failures, each wrapped as EventHandlerError (its
99
+ * `cause` is the raw thrown value).
100
+ */
101
+ readonly errors: readonly EventHandlerError[];
97
102
  /**
98
103
  * True when a middleware did not call next(), so no handler ran.
99
104
  */
@@ -8,11 +8,12 @@
8
8
  */
9
9
  import type { Event } from "../eventTypes/eventDefinition.type.js";
10
10
  import type { EventHandlerContext, RegisteredEventHandler } from "../eventHandler/eventHandler.core.js";
11
+ import { type EventHandlerError } from "../eventErrors/eventError.base.js";
11
12
  import type { EventHandlerExecutionResult } from "./eventEmitter.type.js";
12
13
  import { EventErrorMode } from "./eventEmitter.type.js";
13
14
  import type { DispatchHooks } from "./eventEmitter.sequential.js";
14
15
  /**
15
16
  * Executes handlers concurrently.
16
17
  */
17
- export declare function emitParallel<TEvent extends Event>(handlers: readonly RegisteredEventHandler<TEvent>[], event: TEvent, context: EventHandlerContext<TEvent>, errorMode: EventErrorMode, results: EventHandlerExecutionResult[], errors: unknown[], hooks: DispatchHooks): Promise<void>;
18
+ export declare function emitParallel<TEvent extends Event>(handlers: readonly RegisteredEventHandler<TEvent>[], event: TEvent, context: EventHandlerContext<TEvent>, errorMode: EventErrorMode, results: EventHandlerExecutionResult[], errors: EventHandlerError[], hooks: DispatchHooks): Promise<void>;
18
19
  //# sourceMappingURL=eventEmitter.parallel.d.ts.map
@@ -7,7 +7,7 @@
7
7
  * aborted when dispatch begins rejects the emit.
8
8
  */
9
9
  import { executeRegisteredEventHandler } from "../eventHandler/eventHandler.core.js";
10
- import { createEventHandlerError } from "../eventErrors/eventError.base.js";
10
+ import { createEventHandlerError, } from "../eventErrors/eventError.base.js";
11
11
  import { EventErrorMode } from "./eventEmitter.type.js";
12
12
  import { createAbortError } from "./eventEmitter.abort.js";
13
13
  /**
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import type { Event } from "../eventTypes/eventDefinition.type.js";
5
5
  import type { EventHandlerContext, RegisteredEventHandler } from "../eventHandler/eventHandler.core.js";
6
+ import { type EventHandlerError } from "../eventErrors/eventError.base.js";
6
7
  import type { EventHandlerExecutionResult } from "./eventEmitter.type.js";
7
8
  import { EventErrorMode } from "./eventEmitter.type.js";
8
9
  /**
@@ -23,6 +24,11 @@ export interface DispatchHooks {
23
24
  }
24
25
  /**
25
26
  * Executes handlers sequentially.
27
+ *
28
+ * Rejects with EventDispatchAbortedError when the dispatch signal is
29
+ * aborted before a handler starts or while any handler — including
30
+ * the last or only one — is running, even if that handler then
31
+ * returns normally.
26
32
  */
27
- export declare function emitSequential<TEvent extends Event>(handlers: readonly RegisteredEventHandler<TEvent>[], event: TEvent, context: EventHandlerContext<TEvent>, errorMode: EventErrorMode, results: EventHandlerExecutionResult[], errors: unknown[], hooks: DispatchHooks): Promise<void>;
33
+ export declare function emitSequential<TEvent extends Event>(handlers: readonly RegisteredEventHandler<TEvent>[], event: TEvent, context: EventHandlerContext<TEvent>, errorMode: EventErrorMode, results: EventHandlerExecutionResult[], errors: EventHandlerError[], hooks: DispatchHooks): Promise<void>;
28
34
  //# sourceMappingURL=eventEmitter.sequential.d.ts.map
@@ -2,11 +2,16 @@
2
2
  * Sequential event handler dispatch for Zudojs.
3
3
  */
4
4
  import { executeRegisteredEventHandler } from "../eventHandler/eventHandler.core.js";
5
- import { createEventHandlerError } from "../eventErrors/eventError.base.js";
5
+ import { createEventHandlerError, } from "../eventErrors/eventError.base.js";
6
6
  import { EventErrorMode } from "./eventEmitter.type.js";
7
7
  import { createAbortError } from "./eventEmitter.abort.js";
8
8
  /**
9
9
  * Executes handlers sequentially.
10
+ *
11
+ * Rejects with EventDispatchAbortedError when the dispatch signal is
12
+ * aborted before a handler starts or while any handler — including
13
+ * the last or only one — is running, even if that handler then
14
+ * returns normally.
10
15
  */
11
16
  export async function emitSequential(handlers, event, context, errorMode, results, errors, hooks) {
12
17
  for (const handler of handlers) {
@@ -49,5 +54,12 @@ export async function emitSequential(handlers, event, context, errorMode, result
49
54
  }
50
55
  }
51
56
  }
57
+ // The check at the top of the loop only runs before the *next*
58
+ // handler, so an abort during the last (or only) handler used to
59
+ // resolve as a normal, successful dispatch — while the same abort
60
+ // with a handler still to come rejected.
61
+ if (context.signal.aborted) {
62
+ throw createAbortError(event, results, errors);
63
+ }
52
64
  }
53
65
  //# sourceMappingURL=eventEmitter.sequential.js.map
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Event emitter type definitions for Zudojs.
3
3
  */
4
+ import type { EventHandlerError } from "@zudojs/errors";
4
5
  import type { Event } from "../eventTypes/eventDefinition.type.js";
5
6
  import type { RegisteredEventHandler } from "../eventHandler/eventHandler.core.js";
6
7
  import type { EventSubscription } from "../eventSubscription/eventSubscription.core.js";
@@ -87,7 +88,7 @@ export interface EventEmitResult<TEvent extends Event = Event> {
87
88
  * Handler failures wrapped as EventHandlerError (cause holds the
88
89
  * raw thrown value).
89
90
  */
90
- readonly errors: readonly unknown[];
91
+ readonly errors: readonly EventHandlerError[];
91
92
  /**
92
93
  * Number of handlers that completed successfully.
93
94
  */
@@ -2,11 +2,12 @@
2
2
  * @zudojs/events/eventErrors/eventError.base
3
3
  *
4
4
  * Event error types are centralized in @zudojs/errors and
5
- * re-exported here. A few event-bus specific errors that the
6
- * errors package does not define yet live in this file.
5
+ * re-exported here, including EventBusStoppedError and
6
+ * EventBusDisposedError. EventDispatchAbortedError is extended here
7
+ * to carry the partial results of an aborted dispatch.
7
8
  */
8
- import { EventError, EventDispatchAbortedError as BaseEventDispatchAbortedError } from "@zudojs/errors";
9
- export { EventError, createEventError, isEventError, toEventError, EventPublishError, InvalidEventError, EventTypeNotFoundError, EventHandlerError, createEventHandlerError, EventHandlerNotFoundError, DuplicateEventHandlerError, DuplicateEventDefinitionError, EventDefinitionNotFoundError, EventEmitterDisposedError, EventRegistryDisposedError, EventSubscriptionClosedError, EventListenerLimitExceededError, EventTimeoutError, EventMiddlewareError, EventSerializationError, EventDeserializationError, } from "@zudojs/errors";
9
+ import { EventDispatchAbortedError as BaseEventDispatchAbortedError } from "@zudojs/errors";
10
+ export { EventError, createEventError, isEventError, toEventError, EventPublishError, InvalidEventError, EventTypeNotFoundError, EventHandlerError, createEventHandlerError, EventHandlerNotFoundError, DuplicateEventHandlerError, DuplicateEventDefinitionError, EventDefinitionNotFoundError, EventEmitterDisposedError, EventRegistryDisposedError, EventSubscriptionClosedError, EventListenerLimitExceededError, EventTimeoutError, EventMiddlewareError, EventSerializationError, EventDeserializationError, EventBusDisposedError, EventBusStoppedError, } from "@zudojs/errors";
10
11
  /**
11
12
  * Options for EventDispatchAbortedError.
12
13
  */
@@ -32,17 +33,4 @@ export declare class EventDispatchAbortedError extends BaseEventDispatchAbortedE
32
33
  readonly errors: readonly unknown[];
33
34
  constructor(message?: string, options?: EventDispatchAbortedErrorOptions);
34
35
  }
35
- /**
36
- * Error thrown when an EventBus is used after dispose().
37
- */
38
- export declare class EventBusDisposedError extends EventError {
39
- constructor();
40
- }
41
- /**
42
- * Error thrown when publishing or subscribing on a stopped
43
- * EventBus. Call start() to resume.
44
- */
45
- export declare class EventBusStoppedError extends EventError {
46
- constructor(operation: string);
47
- }
48
36
  //# sourceMappingURL=eventError.base.d.ts.map
@@ -2,11 +2,12 @@
2
2
  * @zudojs/events/eventErrors/eventError.base
3
3
  *
4
4
  * Event error types are centralized in @zudojs/errors and
5
- * re-exported here. A few event-bus specific errors that the
6
- * errors package does not define yet live in this file.
5
+ * re-exported here, including EventBusStoppedError and
6
+ * EventBusDisposedError. EventDispatchAbortedError is extended here
7
+ * to carry the partial results of an aborted dispatch.
7
8
  */
8
- import { ErrorCode, EventError, EventDispatchAbortedError as BaseEventDispatchAbortedError, } from "@zudojs/errors";
9
- export { EventError, createEventError, isEventError, toEventError, EventPublishError, InvalidEventError, EventTypeNotFoundError, EventHandlerError, createEventHandlerError, EventHandlerNotFoundError, DuplicateEventHandlerError, DuplicateEventDefinitionError, EventDefinitionNotFoundError, EventEmitterDisposedError, EventRegistryDisposedError, EventSubscriptionClosedError, EventListenerLimitExceededError, EventTimeoutError, EventMiddlewareError, EventSerializationError, EventDeserializationError, } from "@zudojs/errors";
9
+ import { EventDispatchAbortedError as BaseEventDispatchAbortedError } from "@zudojs/errors";
10
+ export { EventError, createEventError, isEventError, toEventError, EventPublishError, InvalidEventError, EventTypeNotFoundError, EventHandlerError, createEventHandlerError, EventHandlerNotFoundError, DuplicateEventHandlerError, DuplicateEventDefinitionError, EventDefinitionNotFoundError, EventEmitterDisposedError, EventRegistryDisposedError, EventSubscriptionClosedError, EventListenerLimitExceededError, EventTimeoutError, EventMiddlewareError, EventSerializationError, EventDeserializationError, EventBusDisposedError, EventBusStoppedError, } from "@zudojs/errors";
10
11
  /**
11
12
  * Error thrown when event dispatch is aborted through an
12
13
  * AbortSignal. Carries the partial results and errors gathered
@@ -24,32 +25,4 @@ export class EventDispatchAbortedError extends BaseEventDispatchAbortedError {
24
25
  this.errors = Object.freeze([...(options.errors ?? [])]);
25
26
  }
26
27
  }
27
- /**
28
- * Error thrown when an EventBus is used after dispose().
29
- */
30
- export class EventBusDisposedError extends EventError {
31
- constructor() {
32
- super("Event bus has already been disposed.", {
33
- code: ErrorCode.LIFECYCLE_DISPOSED,
34
- statusCode: 500,
35
- expose: false,
36
- isOperational: false,
37
- });
38
- }
39
- }
40
- /**
41
- * Error thrown when publishing or subscribing on a stopped
42
- * EventBus. Call start() to resume.
43
- */
44
- export class EventBusStoppedError extends EventError {
45
- constructor(operation) {
46
- super(`Cannot ${operation} on a stopped event bus. Call start() first.`, {
47
- code: ErrorCode.LIFECYCLE_STATE,
48
- statusCode: 500,
49
- expose: false,
50
- isOperational: true,
51
- metadata: { operation },
52
- });
53
- }
54
- }
55
28
  //# sourceMappingURL=eventError.base.js.map
@@ -172,8 +172,15 @@ export declare function executeEventHandler<TEvent extends Event>(handler: Event
172
172
  /**
173
173
  * Executes a registered handler, applying its timeout when one
174
174
  * is configured. A timed-out execution rejects with
175
- * EventTimeoutError; the underlying handler keeps running but its
176
- * eventual result is ignored.
175
+ * EventTimeoutError and aborts the `context.signal` the handler
176
+ * received (its `reason` is that EventTimeoutError), so a handler
177
+ * that observes the signal can stop its work. A handler that
178
+ * ignores the signal keeps running, but its eventual result is
179
+ * ignored.
180
+ *
181
+ * The handler's signal also follows the dispatch signal: aborting
182
+ * the publish aborts it too. Timing out one handler does not abort
183
+ * the dispatch or any other handler.
177
184
  */
178
185
  export declare function executeRegisteredEventHandler<TEvent extends Event>(registration: RegisteredEventHandler<TEvent>, event: TEvent, context: EventHandlerContext<TEvent>): Promise<EventHandlerResult>;
179
186
  /**
@@ -117,8 +117,15 @@ export async function executeEventHandler(handler, event, context) {
117
117
  /**
118
118
  * Executes a registered handler, applying its timeout when one
119
119
  * is configured. A timed-out execution rejects with
120
- * EventTimeoutError; the underlying handler keeps running but its
121
- * eventual result is ignored.
120
+ * EventTimeoutError and aborts the `context.signal` the handler
121
+ * received (its `reason` is that EventTimeoutError), so a handler
122
+ * that observes the signal can stop its work. A handler that
123
+ * ignores the signal keeps running, but its eventual result is
124
+ * ignored.
125
+ *
126
+ * The handler's signal also follows the dispatch signal: aborting
127
+ * the publish aborts it too. Timing out one handler does not abort
128
+ * the dispatch or any other handler.
122
129
  */
123
130
  export async function executeRegisteredEventHandler(registration, event, context) {
124
131
  const timeoutMs = registration.timeoutMs;
@@ -126,17 +133,24 @@ export async function executeRegisteredEventHandler(registration, event, context
126
133
  return executeEventHandler(registration.handler, event, context);
127
134
  }
128
135
  let timer;
136
+ const deadline = new AbortController();
137
+ const handlerContext = Object.freeze({
138
+ ...context,
139
+ signal: AbortSignal.any([context.signal, deadline.signal]),
140
+ });
129
141
  const timeout = new Promise((_resolve, reject) => {
130
142
  timer = setTimeout(() => {
131
- reject(new EventTimeoutError(timeoutMs, {
143
+ const error = new EventTimeoutError(timeoutMs, {
132
144
  eventType: event.type,
133
145
  eventId: event.id,
134
- }));
146
+ });
147
+ deadline.abort(error);
148
+ reject(error);
135
149
  }, timeoutMs);
136
150
  });
137
151
  try {
138
152
  return await Promise.race([
139
- executeEventHandler(registration.handler, event, context),
153
+ executeEventHandler(registration.handler, event, handlerContext),
140
154
  timeout,
141
155
  ]);
142
156
  }
@@ -55,6 +55,10 @@ export declare function isValidEventType(value: unknown): value is EventType;
55
55
  * Supported forms are an exact event type, a namespace wildcard
56
56
  * ("user.*") and the catch-all "*". Wildcards in any other
57
57
  * position ("user.*.created", "user.cre*") are rejected.
58
+ *
59
+ * A namespace wildcard is multi-level: "user.*" matches every event
60
+ * under "user" at any depth, and "user" itself (see matchesEventType).
61
+ * There is no single-level form.
58
62
  */
59
63
  export declare function isValidEventTypePattern(value: unknown): value is EventTypePattern;
60
64
  /**
@@ -112,8 +116,16 @@ export declare function normalizeEventTypePattern(pattern: string): EventTypePat
112
116
  * "user.created" matches "user.*"
113
117
  * "user" matches "user.*" (a namespace pattern also
114
118
  * matches the bare namespace event)
119
+ * "user.profile.updated" matches "user.*" (multi-level: the
120
+ * wildcard covers every depth below the namespace)
115
121
  * "user.created" matches "*"
116
122
  * "order.created" does not match "user.*"
123
+ * "username.set" does not match "user.*" (segments, not prefixes)
124
+ *
125
+ * A "*" segment stands for the rest of the type, not for one segment,
126
+ * and there is no single-level wildcard. To handle only direct children,
127
+ * subscribe to "user.*" and check
128
+ * `getEventTypeSegments(event.type).length === 2` in the handler.
117
129
  *
118
130
  * Both arguments are expected to be normalized (see
119
131
  * normalizeEventType / normalizeEventTypePattern); no
@@ -133,10 +145,13 @@ export declare function isChildEventType(type: EventType, parent: EventType): bo
133
145
  /**
134
146
  * Creates a wildcard pattern for an event namespace.
135
147
  *
148
+ * The pattern is multi-level: it matches every event type under the
149
+ * namespace at any depth, and the bare namespace itself.
150
+ *
136
151
  * Example:
137
152
  *
138
153
  * createEventTypePattern("user")
139
- * → "user.*"
154
+ * → "user.*" (matches "user", "user.created", "user.profile.updated")
140
155
  */
141
156
  export declare function createEventTypePattern(namespace: string): EventTypePattern;
142
157
  /**
@@ -38,6 +38,10 @@ export function isValidEventType(value) {
38
38
  * Supported forms are an exact event type, a namespace wildcard
39
39
  * ("user.*") and the catch-all "*". Wildcards in any other
40
40
  * position ("user.*.created", "user.cre*") are rejected.
41
+ *
42
+ * A namespace wildcard is multi-level: "user.*" matches every event
43
+ * under "user" at any depth, and "user" itself (see matchesEventType).
44
+ * There is no single-level form.
41
45
  */
42
46
  export function isValidEventTypePattern(value) {
43
47
  if (value === "*") {
@@ -164,8 +168,16 @@ export function normalizeEventTypePattern(pattern) {
164
168
  * "user.created" matches "user.*"
165
169
  * "user" matches "user.*" (a namespace pattern also
166
170
  * matches the bare namespace event)
171
+ * "user.profile.updated" matches "user.*" (multi-level: the
172
+ * wildcard covers every depth below the namespace)
167
173
  * "user.created" matches "*"
168
174
  * "order.created" does not match "user.*"
175
+ * "username.set" does not match "user.*" (segments, not prefixes)
176
+ *
177
+ * A "*" segment stands for the rest of the type, not for one segment,
178
+ * and there is no single-level wildcard. To handle only direct children,
179
+ * subscribe to "user.*" and check
180
+ * `getEventTypeSegments(event.type).length === 2` in the handler.
169
181
  *
170
182
  * Both arguments are expected to be normalized (see
171
183
  * normalizeEventType / normalizeEventTypePattern); no
@@ -201,10 +213,13 @@ export function isChildEventType(type, parent) {
201
213
  /**
202
214
  * Creates a wildcard pattern for an event namespace.
203
215
  *
216
+ * The pattern is multi-level: it matches every event type under the
217
+ * namespace at any depth, and the bare namespace itself.
218
+ *
204
219
  * Example:
205
220
  *
206
221
  * createEventTypePattern("user")
207
- * → "user.*"
222
+ * → "user.*" (matches "user", "user.created", "user.profile.updated")
208
223
  */
209
224
  export function createEventTypePattern(namespace) {
210
225
  const normalized = normalizeEventType(namespace);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zudojs/events",
3
- "version": "1.2.0",
3
+ "version": "1.3.1",
4
4
  "description": "Event-driven architecture with event bus, emitter, middleware, and registry for decoupled communication.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -20,15 +20,15 @@
20
20
  ],
21
21
  "sideEffects": false,
22
22
  "dependencies": {
23
- "@zudojs/constants": "1.1.1",
24
- "@zudojs/errors": "1.2.0",
25
- "@zudojs/middleware": "1.0.3"
23
+ "@zudojs/constants": "1.1.2",
24
+ "@zudojs/errors": "1.3.0",
25
+ "@zudojs/middleware": "1.1.0"
26
26
  },
27
27
  "engines": {
28
28
  "node": ">=24.0.0"
29
29
  },
30
30
  "devDependencies": {
31
- "vitest": "^4.1.11",
31
+ "vitest": "^5.0.1",
32
32
  "typescript": "7.0.2"
33
33
  },
34
34
  "license": "MIT",
@@ -45,7 +45,7 @@
45
45
  "event-bus",
46
46
  "pubsub"
47
47
  ],
48
- "homepage": "https://github.com/oyinlola-tech/zudo#readme",
48
+ "homepage": "https://zudojs.oyinlola.site/docs/packages-events",
49
49
  "bugs": {
50
50
  "url": "https://github.com/oyinlola-tech/zudo/issues"
51
51
  },