@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.
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 +109 -31
  5. package/dist/eventBus/eventBus.publish.d.ts +23 -5
  6. package/dist/eventBus/eventBus.publish.js +78 -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 +72 -56
  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 +62 -22
  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 +26 -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
@@ -1,35 +1,53 @@
1
1
  /**
2
2
  * Parallel event handler dispatch for Zudojs.
3
+ *
4
+ * All handlers start together; an AbortSignal that fires after
5
+ * the handlers have started cannot stop them. Handlers can observe
6
+ * `context.signal` to cooperate. Only a signal that is already
7
+ * aborted when dispatch begins rejects the emit.
3
8
  */
4
- import { executeEventHandler } from "../eventHandler/eventHandler.core.js";
9
+ import { executeRegisteredEventHandler } from "../eventHandler/eventHandler.core.js";
10
+ import { createEventHandlerError } from "../eventErrors/eventError.base.js";
5
11
  import { EventErrorMode } from "./eventEmitter.type.js";
6
12
  import { createAbortError } from "./eventEmitter.abort.js";
7
13
  /**
8
14
  * Executes handlers concurrently.
9
15
  */
10
- export async function emitParallel(handlers, event, context, errorMode, results, errors, onOnceHandler) {
11
- const executions = handlers.map(async (handler) => {
12
- if (context.signal.aborted) {
13
- throw createAbortError();
16
+ export async function emitParallel(handlers, event, context, errorMode, results, errors, hooks) {
17
+ if (context.signal.aborted) {
18
+ throw createAbortError(event, results, errors);
19
+ }
20
+ /**
21
+ * Consume once-handlers synchronously before anything starts so
22
+ * an overlapping dispatch cannot invoke them a second time.
23
+ */
24
+ const runnable = handlers.filter((handler) => {
25
+ if (!handler.once) {
26
+ return true;
27
+ }
28
+ if (!hooks.isRegistered(handler.id)) {
29
+ return false;
14
30
  }
31
+ hooks.removeOnce(handler.id);
32
+ return true;
33
+ });
34
+ const executions = runnable.map(async (handler) => {
15
35
  const started = performance.now();
16
36
  try {
17
- const result = await executeEventHandler(handler.handler, event, context);
18
- const execution = {
37
+ const result = await executeRegisteredEventHandler(handler, event, context);
38
+ return {
19
39
  handlerId: handler.id,
20
40
  eventId: event.id,
41
+ ok: true,
21
42
  result,
22
43
  duration: performance.now() - started,
23
44
  };
24
- if (handler.once) {
25
- onOnceHandler(handler.id);
26
- }
27
- return execution;
28
45
  }
29
46
  catch (error) {
30
47
  return {
31
48
  handlerId: handler.id,
32
49
  eventId: event.id,
50
+ ok: false,
33
51
  result: undefined,
34
52
  duration: performance.now() - started,
35
53
  error,
@@ -39,8 +57,8 @@ export async function emitParallel(handlers, event, context, errorMode, results,
39
57
  const settled = await Promise.all(executions);
40
58
  for (const result of settled) {
41
59
  results.push(result);
42
- if (result.error !== undefined) {
43
- errors.push(result.error);
60
+ if (!result.ok) {
61
+ errors.push(createEventHandlerError(result.handlerId, event.type, event.id, result.error));
44
62
  }
45
63
  }
46
64
  if (errors.length > 0 && errorMode === EventErrorMode.THROW) {
@@ -5,8 +5,24 @@ import type { Event } from "../eventTypes/eventDefinition.type.js";
5
5
  import type { EventHandlerContext, RegisteredEventHandler } from "../eventHandler/eventHandler.core.js";
6
6
  import type { EventHandlerExecutionResult } from "./eventEmitter.type.js";
7
7
  import { EventErrorMode } from "./eventEmitter.type.js";
8
+ /**
9
+ * Callbacks the dispatch strategies use to interact with the
10
+ * handler store.
11
+ */
12
+ export interface DispatchHooks {
13
+ /**
14
+ * Returns whether a handler is still registered.
15
+ */
16
+ readonly isRegistered: (handlerId: string) => boolean;
17
+ /**
18
+ * Removes a once-handler. Called before the handler runs so a
19
+ * throwing or concurrently dispatched once-handler never fires
20
+ * twice.
21
+ */
22
+ readonly removeOnce: (handlerId: string) => void;
23
+ }
8
24
  /**
9
25
  * Executes handlers sequentially.
10
26
  */
11
- export declare function emitSequential<TEvent extends Event>(handlers: readonly RegisteredEventHandler<TEvent>[], event: TEvent, context: EventHandlerContext<TEvent>, errorMode: EventErrorMode, results: EventHandlerExecutionResult[], errors: unknown[], onOnceHandler: (handlerId: string) => void): Promise<void>;
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>;
12
28
  //# sourceMappingURL=eventEmitter.sequential.d.ts.map
@@ -1,42 +1,49 @@
1
1
  /**
2
2
  * Sequential event handler dispatch for Zudojs.
3
3
  */
4
- import { executeEventHandler } from "../eventHandler/eventHandler.core.js";
4
+ import { executeRegisteredEventHandler } from "../eventHandler/eventHandler.core.js";
5
+ import { createEventHandlerError } from "../eventErrors/eventError.base.js";
5
6
  import { EventErrorMode } from "./eventEmitter.type.js";
6
7
  import { createAbortError } from "./eventEmitter.abort.js";
7
8
  /**
8
9
  * Executes handlers sequentially.
9
10
  */
10
- export async function emitSequential(handlers, event, context, errorMode, results, errors, onOnceHandler) {
11
+ export async function emitSequential(handlers, event, context, errorMode, results, errors, hooks) {
11
12
  for (const handler of handlers) {
12
13
  if (context.signal.aborted) {
13
- throw createAbortError();
14
+ throw createAbortError(event, results, errors);
15
+ }
16
+ if (handler.once) {
17
+ if (!hooks.isRegistered(handler.id)) {
18
+ // Already consumed by an overlapping dispatch.
19
+ continue;
20
+ }
21
+ hooks.removeOnce(handler.id);
14
22
  }
15
23
  const started = performance.now();
16
24
  try {
17
- const result = await executeEventHandler(handler.handler, event, context);
25
+ const result = await executeRegisteredEventHandler(handler, event, context);
18
26
  results.push({
19
27
  handlerId: handler.id,
20
28
  eventId: event.id,
29
+ ok: true,
21
30
  result,
22
31
  duration: performance.now() - started,
23
32
  });
24
- if (handler.once) {
25
- onOnceHandler(handler.id);
26
- }
27
33
  }
28
34
  catch (error) {
29
- const execution = {
35
+ results.push({
30
36
  handlerId: handler.id,
31
37
  eventId: event.id,
38
+ ok: false,
32
39
  result: undefined,
33
40
  duration: performance.now() - started,
34
41
  error,
35
- };
36
- results.push(execution);
37
- errors.push(error);
42
+ });
43
+ const wrapped = createEventHandlerError(handler.id, event.type, event.id, error);
44
+ errors.push(wrapped);
38
45
  if (errorMode === EventErrorMode.THROW) {
39
- throw error;
46
+ throw wrapped;
40
47
  }
41
48
  }
42
49
  }
@@ -4,6 +4,7 @@
4
4
  import type { Event } from "../eventTypes/eventDefinition.type.js";
5
5
  import type { RegisteredEventHandler } from "../eventHandler/eventHandler.core.js";
6
6
  import type { EventSubscription } from "../eventSubscription/eventSubscription.core.js";
7
+ import type { EventHandlerStore, EventRegistryWarning } from "../eventRegistry/eventRegistry.type.js";
7
8
  export declare enum EventEmitterMode {
8
9
  SEQUENTIAL = "sequential",
9
10
  PARALLEL = "parallel"
@@ -13,9 +14,39 @@ export declare enum EventErrorMode {
13
14
  CONTINUE = "continue"
14
15
  }
15
16
  export interface EventEmitterOptions {
17
+ /**
18
+ * Dispatch mode. Defaults to SEQUENTIAL.
19
+ */
16
20
  readonly mode?: EventEmitterMode;
21
+ /**
22
+ * Error mode. Defaults to THROW.
23
+ */
17
24
  readonly errorMode?: EventErrorMode;
25
+ /**
26
+ * Deep-freeze events (including their payload) before they are
27
+ * handed to handlers so no handler can alter what other handlers
28
+ * see. Defaults to true. Note that freezing mutates the payload
29
+ * object passed in; clone it first if you need to keep it
30
+ * mutable elsewhere.
31
+ */
18
32
  readonly freezeEvents?: boolean;
33
+ /**
34
+ * Handler store to use. When omitted the emitter creates a
35
+ * private EventRegistry. EventBus passes its registry so that
36
+ * handlers registered on either side are dispatched.
37
+ */
38
+ readonly store?: EventHandlerStore;
39
+ /**
40
+ * Maximum handlers per event pattern before a leak warning is
41
+ * emitted (0 disables). Only applies to the private store.
42
+ * Defaults to 100.
43
+ */
44
+ readonly maxListeners?: number;
45
+ /**
46
+ * Receives leak warnings. Only applies to the private store.
47
+ * Defaults to console.warn.
48
+ */
49
+ readonly onWarning?: (warning: EventRegistryWarning) => void;
19
50
  }
20
51
  export interface EmitOptions {
21
52
  readonly mode?: EventEmitterMode;
@@ -26,16 +57,45 @@ export interface EmitOptions {
26
57
  export interface EventHandlerExecutionResult {
27
58
  readonly handlerId: string;
28
59
  readonly eventId: string;
60
+ /**
61
+ * True when the handler settled without throwing.
62
+ */
63
+ readonly ok: boolean;
29
64
  readonly result: unknown;
30
65
  readonly duration: number;
66
+ /**
67
+ * The raw value thrown by the handler when `ok` is false.
68
+ */
31
69
  readonly error?: unknown;
32
70
  }
33
71
  export interface EventEmitResult<TEvent extends Event = Event> {
34
72
  readonly event: TEvent;
73
+ /**
74
+ * True when at least one handler completed successfully.
75
+ */
35
76
  readonly handled: boolean;
77
+ /**
78
+ * One entry per invoked handler, in invocation order.
79
+ */
36
80
  readonly results: readonly EventHandlerExecutionResult[];
81
+ /**
82
+ * Handler failures wrapped as EventHandlerError (cause holds the
83
+ * raw thrown value).
84
+ */
37
85
  readonly errors: readonly unknown[];
86
+ /**
87
+ * Number of handlers that completed successfully.
88
+ */
89
+ readonly succeeded: number;
90
+ /**
91
+ * Number of handlers that threw.
92
+ */
93
+ readonly failed: number;
38
94
  }
95
+ /**
96
+ * @deprecated Handlers are stored in an EventRegistry; this shape
97
+ * is kept for type compatibility only.
98
+ */
39
99
  export interface EmitterListener {
40
100
  readonly registration: RegisteredEventHandler;
41
101
  readonly subscription: EventSubscription;
@@ -1,8 +1,55 @@
1
1
  /**
2
2
  * @zudojs/events/eventErrors/eventError.base
3
3
  *
4
- * All event error types are centralized in @zudojs/errors.
5
- * This file re-exports them for backward compatibility.
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.
6
7
  */
7
- export { EventError, createEventError, isEventError, toEventError, EventPublishError, InvalidEventError, EventTypeNotFoundError, EventHandlerError, createEventHandlerError, EventHandlerNotFoundError, DuplicateEventHandlerError, DuplicateEventDefinitionError, EventDefinitionNotFoundError, EventDispatchAbortedError, EventEmitterDisposedError, EventRegistryDisposedError, EventSubscriptionClosedError, EventTimeoutError, EventMiddlewareError, EventSerializationError, EventDeserializationError, } from "@zudojs/errors";
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, EventTimeoutError, EventMiddlewareError, EventSerializationError, EventDeserializationError, } from "@zudojs/errors";
10
+ /**
11
+ * Options for EventDispatchAbortedError.
12
+ */
13
+ export interface EventDispatchAbortedErrorOptions {
14
+ readonly eventType?: string;
15
+ readonly eventId?: string;
16
+ /**
17
+ * Execution results collected before the abort was observed.
18
+ */
19
+ readonly results?: readonly unknown[];
20
+ /**
21
+ * Handler errors collected before the abort was observed.
22
+ */
23
+ readonly errors?: readonly unknown[];
24
+ }
25
+ /**
26
+ * Error thrown when event dispatch is aborted through an
27
+ * AbortSignal. Carries the partial results and errors gathered
28
+ * before the abort was observed so callers can see what ran.
29
+ */
30
+ export declare class EventDispatchAbortedError extends BaseEventDispatchAbortedError {
31
+ readonly results: readonly unknown[];
32
+ readonly errors: readonly unknown[];
33
+ constructor(message?: string, options?: EventDispatchAbortedErrorOptions);
34
+ }
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
+ /**
49
+ * Error thrown when the listener limit of an emitter or registry
50
+ * is exceeded and the limit is configured to be enforced.
51
+ */
52
+ export declare class EventListenerLimitExceededError extends EventError {
53
+ constructor(pattern: string, limit: number);
54
+ }
8
55
  //# sourceMappingURL=eventError.base.d.ts.map
@@ -1,8 +1,69 @@
1
1
  /**
2
2
  * @zudojs/events/eventErrors/eventError.base
3
3
  *
4
- * All event error types are centralized in @zudojs/errors.
5
- * This file re-exports them for backward compatibility.
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.
6
7
  */
7
- export { EventError, createEventError, isEventError, toEventError, EventPublishError, InvalidEventError, EventTypeNotFoundError, EventHandlerError, createEventHandlerError, EventHandlerNotFoundError, DuplicateEventHandlerError, DuplicateEventDefinitionError, EventDefinitionNotFoundError, EventDispatchAbortedError, EventEmitterDisposedError, EventRegistryDisposedError, EventSubscriptionClosedError, EventTimeoutError, EventMiddlewareError, EventSerializationError, EventDeserializationError, } from "@zudojs/errors";
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, EventTimeoutError, EventMiddlewareError, EventSerializationError, EventDeserializationError, } from "@zudojs/errors";
10
+ /**
11
+ * Error thrown when event dispatch is aborted through an
12
+ * AbortSignal. Carries the partial results and errors gathered
13
+ * before the abort was observed so callers can see what ran.
14
+ */
15
+ export class EventDispatchAbortedError extends BaseEventDispatchAbortedError {
16
+ results;
17
+ errors;
18
+ constructor(message = "Event dispatch was aborted.", options = {}) {
19
+ super(message, {
20
+ eventType: options.eventType,
21
+ eventId: options.eventId,
22
+ });
23
+ this.results = Object.freeze([...(options.results ?? [])]);
24
+ this.errors = Object.freeze([...(options.errors ?? [])]);
25
+ }
26
+ }
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
+ /**
56
+ * Error thrown when the listener limit of an emitter or registry
57
+ * is exceeded and the limit is configured to be enforced.
58
+ */
59
+ export class EventListenerLimitExceededError extends EventError {
60
+ constructor(pattern, limit) {
61
+ super(`Listener limit of ${limit} exceeded for event pattern "${pattern}".`, {
62
+ code: ErrorCode.LIFECYCLE_STATE,
63
+ statusCode: 500,
64
+ expose: false,
65
+ metadata: { pattern, limit },
66
+ });
67
+ }
68
+ }
8
69
  //# sourceMappingURL=eventError.base.js.map
@@ -94,6 +94,14 @@ export interface EventHandlerOptions {
94
94
  * Whether the handler should only execute once.
95
95
  */
96
96
  readonly once?: boolean;
97
+ /**
98
+ * Optional execution timeout in milliseconds.
99
+ *
100
+ * When the handler does not settle within this time the
101
+ * execution fails with EventTimeoutError. Defaults to no
102
+ * timeout.
103
+ */
104
+ readonly timeoutMs?: number;
97
105
  }
98
106
  /**
99
107
  * Complete registered event handler.
@@ -123,6 +131,10 @@ export interface RegisteredEventHandler<TEvent extends Event = Event> {
123
131
  * Optional description.
124
132
  */
125
133
  readonly description?: string;
134
+ /**
135
+ * Optional execution timeout in milliseconds.
136
+ */
137
+ readonly timeoutMs?: number;
126
138
  /**
127
139
  * Actual handler.
128
140
  */
@@ -157,6 +169,13 @@ export declare function isEventHandler(value: unknown): value is EventHandlerLik
157
169
  * represented as a function or an object.
158
170
  */
159
171
  export declare function executeEventHandler<TEvent extends Event>(handler: EventHandlerLike<TEvent>, event: TEvent, context: EventHandlerContext<TEvent>): Promise<EventHandlerResult>;
172
+ /**
173
+ * Executes a registered handler, applying its timeout when one
174
+ * is configured. A timed-out execution rejects with
175
+ * EventTimeoutError; the underlying handler keeps running but its
176
+ * eventual result is ignored.
177
+ */
178
+ export declare function executeRegisteredEventHandler<TEvent extends Event>(registration: RegisteredEventHandler<TEvent>, event: TEvent, context: EventHandlerContext<TEvent>): Promise<EventHandlerResult>;
160
179
  /**
161
180
  * Determines whether a registered handler should process
162
181
  * a given event.
@@ -4,7 +4,8 @@
4
4
  * Handlers are responsible for processing events.
5
5
  * They do not own event registration or event dispatching.
6
6
  */
7
- import { matchesEventType } from "../eventTypes/eventType.type.js";
7
+ import { isValidEventTypePattern, matchesEventType, normalizeEventTypePattern, } from "../eventTypes/eventType.type.js";
8
+ import { EventTimeoutError } from "../eventErrors/eventError.base.js";
8
9
  /**
9
10
  * Generates a handler identifier.
10
11
  */
@@ -37,17 +38,43 @@ export function createEventHandlerContext(event, options = {}) {
37
38
  * Creates a registered event handler.
38
39
  */
39
40
  export function createEventHandler(handler, options = {}) {
40
- const eventType = options.eventType ?? "*";
41
41
  if (!isValidHandler(handler)) {
42
42
  throw new TypeError("Invalid event handler.");
43
43
  }
44
+ const rawPattern = options.eventType ?? "*";
45
+ let eventType;
46
+ try {
47
+ eventType = normalizeEventTypePattern(rawPattern);
48
+ }
49
+ catch {
50
+ throw new TypeError(`Invalid event type pattern "${String(rawPattern)}".`);
51
+ }
52
+ if (!isValidEventTypePattern(eventType)) {
53
+ throw new TypeError(`Invalid event type pattern "${String(rawPattern)}".`);
54
+ }
55
+ const priority = options.priority ?? 0;
56
+ if (typeof priority !== "number" || !Number.isFinite(priority)) {
57
+ throw new RangeError("Event handler priority must be a finite number.");
58
+ }
59
+ if (options.id !== undefined &&
60
+ (typeof options.id !== "string" || options.id.length === 0)) {
61
+ throw new TypeError("Event handler id must be a non-empty string.");
62
+ }
63
+ const timeoutMs = options.timeoutMs;
64
+ if (timeoutMs !== undefined &&
65
+ (typeof timeoutMs !== "number" ||
66
+ !Number.isFinite(timeoutMs) ||
67
+ timeoutMs <= 0)) {
68
+ throw new RangeError("Event handler timeoutMs must be a positive finite number.");
69
+ }
44
70
  return Object.freeze({
45
71
  id: options.id ?? createEventHandlerId(),
46
72
  eventType,
47
- priority: options.priority ?? 0,
73
+ priority,
48
74
  enabled: options.enabled ?? true,
49
75
  once: options.once ?? false,
50
76
  description: options.description,
77
+ timeoutMs,
51
78
  handler,
52
79
  });
53
80
  }
@@ -87,6 +114,38 @@ export async function executeEventHandler(handler, event, context) {
87
114
  }
88
115
  return handler.handle(event, context);
89
116
  }
117
+ /**
118
+ * Executes a registered handler, applying its timeout when one
119
+ * is configured. A timed-out execution rejects with
120
+ * EventTimeoutError; the underlying handler keeps running but its
121
+ * eventual result is ignored.
122
+ */
123
+ export async function executeRegisteredEventHandler(registration, event, context) {
124
+ const timeoutMs = registration.timeoutMs;
125
+ if (timeoutMs === undefined) {
126
+ return executeEventHandler(registration.handler, event, context);
127
+ }
128
+ let timer;
129
+ const timeout = new Promise((_resolve, reject) => {
130
+ timer = setTimeout(() => {
131
+ reject(new EventTimeoutError(timeoutMs, {
132
+ eventType: event.type,
133
+ eventId: event.id,
134
+ }));
135
+ }, timeoutMs);
136
+ });
137
+ try {
138
+ return await Promise.race([
139
+ executeEventHandler(registration.handler, event, context),
140
+ timeout,
141
+ ]);
142
+ }
143
+ finally {
144
+ if (timer !== undefined) {
145
+ clearTimeout(timer);
146
+ }
147
+ }
148
+ }
90
149
  /**
91
150
  * Determines whether a registered handler should process
92
151
  * a given event.
@@ -104,7 +163,11 @@ export function handlerMatchesEvent(handler, event) {
104
163
  * Registration order is preserved for equal priorities.
105
164
  */
106
165
  export function sortEventHandlers(handlers) {
107
- return [...handlers].sort((first, second) => second.priority - first.priority);
166
+ return [...handlers].sort((first, second) => {
167
+ const a = Number.isFinite(first.priority) ? first.priority : 0;
168
+ const b = Number.isFinite(second.priority) ? second.priority : 0;
169
+ return b - a;
170
+ });
108
171
  }
109
172
  /**
110
173
  * Filters handlers that can process an event.
@@ -1,17 +1,8 @@
1
1
  /**
2
2
  * Event middleware builder functions for Zudojs.
3
3
  */
4
- import { EventMiddlewareError } from "../eventErrors/eventError.base.js";
4
+ import { EventDispatchAbortedError, EventMiddlewareError, } from "../eventErrors/eventError.base.js";
5
5
  import { createEventMiddleware } from "./eventMiddleware.helper.js";
6
- /**
7
- * Creates an AbortError without relying on a runtime-specific
8
- * DOMException implementation.
9
- */
10
- function createAbortError() {
11
- return new EventMiddlewareError("Event middleware execution was aborted.", {
12
- cause: new Error("AbortSignal was aborted."),
13
- });
14
- }
15
6
  /**
16
7
  * Creates a middleware that executes before downstream
17
8
  * middleware and handlers.
@@ -102,7 +93,10 @@ export function disableEventMiddleware(middleware) {
102
93
  export function abortableEventMiddleware(options = {}) {
103
94
  return beforeEvent(async (context) => {
104
95
  if (context.signal.aborted) {
105
- throw createAbortError();
96
+ throw new EventDispatchAbortedError("Event dispatch was aborted.", {
97
+ eventType: context.event?.type,
98
+ eventId: context.event?.id,
99
+ });
106
100
  }
107
101
  }, options);
108
102
  }
@@ -1,7 +1,6 @@
1
1
  /**
2
2
  * Event middleware core helpers for Zudojs.
3
3
  */
4
- import { EventMiddlewareError } from "../eventErrors/eventError.base.js";
5
4
  /**
6
5
  * Generates a middleware identifier.
7
6
  */
@@ -13,6 +13,11 @@ import type { EventMiddlewareContext, EventMiddlewareNext, RegisteredEventMiddle
13
13
  * → handler
14
14
  * ← middleware B
15
15
  * ← middleware A
16
+ *
17
+ * Only errors thrown by a middleware function itself are wrapped
18
+ * in EventMiddlewareError. Errors coming back through next() —
19
+ * handler failures, aborts, downstream middleware errors — are
20
+ * re-thrown untouched so callers can discriminate them.
16
21
  */
17
22
  export declare function executeEventMiddlewarePipeline<TEvent extends Event, TResult>(middleware: readonly RegisteredEventMiddleware<TEvent, TResult>[], context: EventMiddlewareContext<TEvent>, terminal: EventMiddlewareNext<TResult>): Promise<EventMiddlewarePipelineResult<TResult>>;
18
23
  //# sourceMappingURL=eventMiddleware.pipeline.d.ts.map