@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,7 +1,7 @@
1
1
  /**
2
2
  * Event middleware pipeline execution for Zudojs.
3
3
  */
4
- import { EventMiddlewareError, toEventError, } from "../eventErrors/eventError.base.js";
4
+ import { EventDispatchAbortedError, EventMiddlewareError, toEventError, } from "../eventErrors/eventError.base.js";
5
5
  import { sortEventMiddleware, executeEventMiddleware, } from "./eventMiddleware.helper.js";
6
6
  /**
7
7
  * Executes a middleware pipeline.
@@ -13,6 +13,11 @@ import { sortEventMiddleware, executeEventMiddleware, } from "./eventMiddleware.
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 async function executeEventMiddlewarePipeline(middleware, context, terminal) {
18
23
  const started = performance.now();
@@ -21,7 +26,7 @@ export async function executeEventMiddlewarePipeline(middleware, context, termin
21
26
  let index = -1;
22
27
  const dispatch = async (currentIndex) => {
23
28
  if (context.signal.aborted) {
24
- throw createAbortError();
29
+ throw createAbortError(context);
25
30
  }
26
31
  if (currentIndex === activeMiddleware.length) {
27
32
  return terminal();
@@ -39,6 +44,13 @@ export async function executeEventMiddlewarePipeline(middleware, context, termin
39
44
  }
40
45
  const middlewareStarted = performance.now();
41
46
  let nextCalled = false;
47
+ /**
48
+ * Errors that surfaced through next() belong to downstream
49
+ * code, not to this middleware; they must pass through
50
+ * unwrapped.
51
+ */
52
+ let downstreamThrew = false;
53
+ let downstreamError;
42
54
  const next = async () => {
43
55
  if (nextCalled) {
44
56
  throw new EventMiddlewareError(`Middleware "${current.id}" called next() more than once.`, {
@@ -48,7 +60,14 @@ export async function executeEventMiddlewarePipeline(middleware, context, termin
48
60
  });
49
61
  }
50
62
  nextCalled = true;
51
- return dispatch(currentIndex + 1);
63
+ try {
64
+ return await dispatch(currentIndex + 1);
65
+ }
66
+ catch (error) {
67
+ downstreamThrew = true;
68
+ downstreamError = error;
69
+ throw error;
70
+ }
52
71
  };
53
72
  try {
54
73
  const result = await executeEventMiddleware(current.middleware, context, next);
@@ -60,7 +79,11 @@ export async function executeEventMiddlewarePipeline(middleware, context, termin
60
79
  return result;
61
80
  }
62
81
  catch (error) {
63
- if (error instanceof EventMiddlewareError) {
82
+ if (downstreamThrew && error === downstreamError) {
83
+ throw error;
84
+ }
85
+ if (error instanceof EventMiddlewareError ||
86
+ error instanceof EventDispatchAbortedError) {
64
87
  throw error;
65
88
  }
66
89
  throw new EventMiddlewareError(`Event middleware "${current.id}" failed.`, {
@@ -82,12 +105,13 @@ export async function executeEventMiddlewarePipeline(middleware, context, termin
82
105
  };
83
106
  }
84
107
  /**
85
- * Creates an AbortError without relying on a runtime-specific
86
- * DOMException implementation.
108
+ * Creates the abort error thrown when the pipeline observes an
109
+ * aborted signal.
87
110
  */
88
- function createAbortError() {
89
- return new EventMiddlewareError("Event middleware execution was aborted.", {
90
- cause: new Error("AbortSignal was aborted."),
111
+ function createAbortError(context) {
112
+ return new EventDispatchAbortedError("Event dispatch was aborted.", {
113
+ eventType: context.event?.type,
114
+ eventId: context.event?.id,
91
115
  });
92
116
  }
93
117
  //# sourceMappingURL=eventMiddleware.pipeline.js.map
@@ -2,18 +2,23 @@
2
2
  * Event registry lifecycle methods for Zudojs.
3
3
  */
4
4
  import type { EventType } from "../eventTypes/eventDefinition.type.js";
5
- import type { RegisteredEventHandler } from "../eventHandler/eventHandler.core.js";
6
- import type { EventRegistryChange, EventRegistryListener, RegisteredEventDefinition } from "./eventRegistry.type.js";
5
+ import type { EventHandlerEntry, EventRegistryChange, EventRegistryErrorContext, EventRegistryListener, RegisteredEventDefinition } from "./eventRegistry.type.js";
7
6
  /**
8
7
  * Clears all handlers and definitions from the registry.
8
+ *
9
+ * Every handler subscription is cancelled, so subscriptions held
10
+ * by callers report `active: false` afterwards.
9
11
  */
10
- export declare function registryClear(definitions: Map<EventType, RegisteredEventDefinition>, handlers: Map<string, RegisteredEventHandler>, ensureActive: () => void, notify: (change: EventRegistryChange) => void): void;
12
+ export declare function registryClear(definitions: Map<EventType, RegisteredEventDefinition>, handlers: Map<string, EventHandlerEntry>, ensureActive: () => void, notify: (change: EventRegistryChange) => void): void;
11
13
  /**
12
14
  * Disposes the registry.
13
15
  */
14
- export declare function registryDispose(disposed: boolean, definitions: Map<EventType, RegisteredEventDefinition>, handlers: Map<string, RegisteredEventHandler>, listeners: Set<EventRegistryListener>, ensureActive: () => void, notify: (change: EventRegistryChange) => void): void;
16
+ export declare function registryDispose(disposed: boolean, definitions: Map<EventType, RegisteredEventDefinition>, handlers: Map<string, EventHandlerEntry>, listeners: Set<EventRegistryListener>, ensureActive: () => void, notify: (change: EventRegistryChange) => void): void;
15
17
  /**
16
18
  * Notifies registry listeners.
19
+ *
20
+ * Observer failures never break registry mutations; they are
21
+ * forwarded to the `onError` hook when one is configured.
17
22
  */
18
- export declare function registryNotify(change: EventRegistryChange, listeners: Set<EventRegistryListener>): void;
23
+ export declare function registryNotify(change: EventRegistryChange, listeners: Set<EventRegistryListener>, onError?: (error: unknown, context: EventRegistryErrorContext) => void): void;
19
24
  //# sourceMappingURL=eventRegistry.lifecycle.d.ts.map
@@ -1,16 +1,20 @@
1
1
  /**
2
2
  * Event registry lifecycle methods for Zudojs.
3
3
  */
4
- import { registryUnregister, registryUnregisterHandler, } from "./eventRegistry.registration.js";
4
+ import { registryUnregister } from "./eventRegistry.registration.js";
5
5
  /**
6
6
  * Clears all handlers and definitions from the registry.
7
+ *
8
+ * Every handler subscription is cancelled, so subscriptions held
9
+ * by callers report `active: false` afterwards.
7
10
  */
8
11
  export function registryClear(definitions, handlers, ensureActive, notify) {
9
12
  ensureActive();
10
- const handlerIds = [...handlers.keys()];
11
- for (const handlerId of handlerIds) {
12
- registryUnregisterHandler(handlerId, handlers, ensureActive, notify);
13
+ const entries = [...handlers.values()];
14
+ for (const entry of entries) {
15
+ entry.subscription.unsubscribe();
13
16
  }
17
+ handlers.clear();
14
18
  const eventTypes = [...definitions.keys()];
15
19
  for (const eventType of eventTypes) {
16
20
  registryUnregister(eventType, definitions, ensureActive, notify);
@@ -28,17 +32,26 @@ export function registryDispose(disposed, definitions, handlers, listeners, ensu
28
32
  }
29
33
  /**
30
34
  * Notifies registry listeners.
35
+ *
36
+ * Observer failures never break registry mutations; they are
37
+ * forwarded to the `onError` hook when one is configured.
31
38
  */
32
- export function registryNotify(change, listeners) {
39
+ export function registryNotify(change, listeners, onError) {
33
40
  for (const listener of listeners) {
34
41
  try {
35
42
  listener(change);
36
43
  }
37
- catch {
38
- /**
39
- * Registry observers must not be able to break
40
- * registry mutations.
41
- */
44
+ catch (error) {
45
+ if (onError) {
46
+ try {
47
+ onError(error, { source: "observer", change });
48
+ }
49
+ catch {
50
+ /**
51
+ * A failing error hook must not break the mutation either.
52
+ */
53
+ }
54
+ }
42
55
  }
43
56
  }
44
57
  }
@@ -3,21 +3,30 @@
3
3
  */
4
4
  import type { Event, EventType } from "../eventTypes/eventDefinition.type.js";
5
5
  import type { RegisteredEventHandler } from "../eventHandler/eventHandler.core.js";
6
- import type { RegisteredEventDefinition } from "./eventRegistry.type.js";
6
+ import type { EventHandlerEntry, RegisteredEventDefinition } from "./eventRegistry.type.js";
7
7
  /**
8
- * Returns handlers matching an event.
8
+ * Returns enabled handlers matching an event, sorted by priority,
9
+ * exactly as the emitter would dispatch them.
10
+ *
11
+ * The event's type is normalized before matching, mirroring
12
+ * getHandlersForType(). Handler patterns are normalized when they
13
+ * are registered, so an event whose `type` preserves its original
14
+ * casing (as CQRS domain events do) still routes to the handlers
15
+ * that registered for it. Only the routing key is normalized; the
16
+ * event object handed to handlers is left untouched.
9
17
  */
10
- export declare function getHandlersForEvent(handlers: Map<string, RegisteredEventHandler>, event: Event): readonly RegisteredEventHandler[];
18
+ export declare function getHandlersForEvent(handlers: Map<string, EventHandlerEntry>, event: Event): readonly RegisteredEventHandler[];
11
19
  /**
12
- * Returns handlers matching an event type.
20
+ * Returns enabled handlers matching an event type, sorted by
21
+ * priority. The type is normalized before matching.
13
22
  */
14
- export declare function getHandlersForType(handlers: Map<string, RegisteredEventHandler>, eventType: EventType): readonly RegisteredEventHandler[];
23
+ export declare function getHandlersForType(handlers: Map<string, EventHandlerEntry>, eventType: EventType): readonly RegisteredEventHandler[];
15
24
  /**
16
25
  * Returns all event definitions as an array.
17
26
  */
18
27
  export declare function getAllDefinitions(definitions: Map<EventType, RegisteredEventDefinition>): readonly RegisteredEventDefinition[];
19
28
  /**
20
- * Returns all handlers as an array.
29
+ * Returns all handlers as an array (registration order).
21
30
  */
22
- export declare function getAllHandlers(handlers: Map<string, RegisteredEventHandler>): readonly RegisteredEventHandler[];
31
+ export declare function getAllHandlers(handlers: Map<string, EventHandlerEntry>): readonly RegisteredEventHandler[];
23
32
  //# sourceMappingURL=eventRegistry.queries.d.ts.map
@@ -1,24 +1,33 @@
1
1
  /**
2
2
  * Event registry query functions for Zudojs.
3
3
  */
4
- import { normalizeEventType, matchesEventType, } from "../eventTypes/eventType.type.js";
4
+ import { getMatchingEventHandlers } from "../eventHandler/eventHandler.core.js";
5
+ import { normalizeRegistryEventType } from "./eventRegistry.registration.js";
5
6
  /**
6
- * Returns handlers matching an event.
7
+ * Returns enabled handlers matching an event, sorted by priority,
8
+ * exactly as the emitter would dispatch them.
9
+ *
10
+ * The event's type is normalized before matching, mirroring
11
+ * getHandlersForType(). Handler patterns are normalized when they
12
+ * are registered, so an event whose `type` preserves its original
13
+ * casing (as CQRS domain events do) still routes to the handlers
14
+ * that registered for it. Only the routing key is normalized; the
15
+ * event object handed to handlers is left untouched.
7
16
  */
8
17
  export function getHandlersForEvent(handlers, event) {
9
- return [...handlers.values()].filter((handler) => {
10
- if (!handler.enabled) {
11
- return false;
12
- }
13
- return matchesEventType(event.type, handler.eventType);
14
- });
18
+ const type = normalizeRegistryEventType(event.type);
19
+ const routed = event.type === type ? event : { ...event, type };
20
+ return getMatchingEventHandlers(getAllHandlers(handlers), routed);
15
21
  }
16
22
  /**
17
- * Returns handlers matching an event type.
23
+ * Returns enabled handlers matching an event type, sorted by
24
+ * priority. The type is normalized before matching.
18
25
  */
19
26
  export function getHandlersForType(handlers, eventType) {
20
- const type = normalizeEventType(eventType);
21
- return [...handlers.values()].filter((handler) => matchesEventType(type, handler.eventType));
27
+ const type = normalizeRegistryEventType(eventType);
28
+ return getMatchingEventHandlers(getAllHandlers(handlers), {
29
+ type,
30
+ });
22
31
  }
23
32
  /**
24
33
  * Returns all event definitions as an array.
@@ -27,9 +36,9 @@ export function getAllDefinitions(definitions) {
27
36
  return [...definitions.values()];
28
37
  }
29
38
  /**
30
- * Returns all handlers as an array.
39
+ * Returns all handlers as an array (registration order).
31
40
  */
32
41
  export function getAllHandlers(handlers) {
33
- return [...handlers.values()];
42
+ return [...handlers.values()].map((entry) => entry.registration);
34
43
  }
35
44
  //# sourceMappingURL=eventRegistry.queries.js.map
@@ -3,9 +3,14 @@
3
3
  */
4
4
  import type { Event, EventDefinition, EventType } from "../eventTypes/eventDefinition.type.js";
5
5
  import type { EventTypePattern } from "../eventTypes/eventType.type.js";
6
- import type { EventHandlerLike, EventHandlerOptions, RegisteredEventHandler } from "../eventHandler/eventHandler.core.js";
6
+ import type { EventHandlerLike, EventHandlerOptions } from "../eventHandler/eventHandler.core.js";
7
7
  import type { EventSubscription } from "../eventSubscription/eventSubscription.core.js";
8
- import type { EventRegistryChange, RegisteredEventDefinition } from "./eventRegistry.type.js";
8
+ import type { DuplicateHandlerIdPolicy, EventHandlerEntry, EventRegistryChange, EventRegistryWarning, RegisteredEventDefinition } from "./eventRegistry.type.js";
9
+ /**
10
+ * Normalizes an event type for registry lookups, converting
11
+ * validation failures into InvalidEventError.
12
+ */
13
+ export declare function normalizeRegistryEventType(eventType: string): EventType;
9
14
  /**
10
15
  * Registers an event definition.
11
16
  */
@@ -15,15 +20,17 @@ export declare function registryRegister<TType extends EventType, TPayload>(defi
15
20
  /**
16
21
  * Registers a handler.
17
22
  */
18
- export declare function registryRegisterHandler<TEvent extends Event = Event>(eventType: EventTypePattern, handler: EventHandlerLike<TEvent>, handlerOptions: Omit<EventHandlerOptions, "eventType">, handlers: Map<string, RegisteredEventHandler>, options: {
19
- allowDuplicateHandlerIds: boolean;
20
- }, ensureActive: () => void, notify: (change: EventRegistryChange) => void): EventSubscription;
23
+ export declare function registryRegisterHandler<TEvent extends Event = Event>(eventType: EventTypePattern, handler: EventHandlerLike<TEvent>, handlerOptions: Omit<EventHandlerOptions, "eventType">, handlers: Map<string, EventHandlerEntry>, options: {
24
+ onDuplicateHandlerId: DuplicateHandlerIdPolicy;
25
+ maxHandlersPerPattern: number;
26
+ onWarning: (warning: EventRegistryWarning) => void;
27
+ }, ensureActive: () => void, notify: (change: EventRegistryChange) => void, warnedPatterns: Set<string>): EventSubscription;
21
28
  /**
22
29
  * Unregisters an event definition.
23
30
  */
24
31
  export declare function registryUnregister(eventType: EventType, definitions: Map<EventType, RegisteredEventDefinition>, ensureActive: () => void, notify: (change: EventRegistryChange) => void): boolean;
25
32
  /**
26
- * Unregisters a handler.
33
+ * Unregisters a handler by id, cancelling its subscription.
27
34
  */
28
- export declare function registryUnregisterHandler(handlerId: string, handlers: Map<string, RegisteredEventHandler>, ensureActive: () => void, notify: (change: EventRegistryChange) => void): boolean;
35
+ export declare function registryUnregisterHandler(handlerId: string, handlers: Map<string, EventHandlerEntry>, ensureActive: () => void): boolean;
29
36
  //# sourceMappingURL=eventRegistry.registration.d.ts.map
@@ -1,26 +1,57 @@
1
1
  /**
2
2
  * Event registry registration methods for Zudojs.
3
3
  */
4
+ import { normalizeEventType } from "../eventTypes/eventType.type.js";
4
5
  import { createEventHandler } from "../eventHandler/eventHandler.core.js";
5
- import { isValidEventType, isValidEventTypePattern, normalizeEventType, } from "../eventTypes/eventType.type.js";
6
6
  import { createEventSubscription } from "../eventSubscription/eventSubscription.core.js";
7
- import { DuplicateEventDefinitionError, DuplicateEventHandlerError, } from "../eventErrors/eventError.base.js";
7
+ import { DuplicateEventDefinitionError, DuplicateEventHandlerError, InvalidEventError, } from "../eventErrors/eventError.base.js";
8
8
  import { EventRegistryChangeType } from "./eventRegistry.type.js";
9
+ /**
10
+ * Normalizes an event type for registry lookups, converting
11
+ * validation failures into InvalidEventError.
12
+ */
13
+ export function normalizeRegistryEventType(eventType) {
14
+ try {
15
+ return normalizeEventType(eventType);
16
+ }
17
+ catch (error) {
18
+ throw new InvalidEventError(`Invalid event type "${String(eventType)}".`, {
19
+ eventType: typeof eventType === "string" ? eventType : undefined,
20
+ cause: error,
21
+ });
22
+ }
23
+ }
9
24
  /**
10
25
  * Registers an event definition.
11
26
  */
12
27
  export function registryRegister(definition, definitions, options, ensureActive, notify) {
13
28
  ensureActive();
14
- const type = normalizeEventType(definition.type);
15
- if (!isValidEventType(type)) {
16
- throw new TypeError(`Invalid event type "${definition.type}".`);
29
+ if (typeof definition !== "object" ||
30
+ definition === null ||
31
+ typeof definition.type !== "string" ||
32
+ typeof definition.create !== "function") {
33
+ throw new InvalidEventError("Event definition must be created with defineEvent().");
17
34
  }
35
+ const type = normalizeRegistryEventType(definition.type);
18
36
  if (definitions.has(type) && !options.allowDuplicateDefinitions) {
19
37
  throw new DuplicateEventDefinitionError(type);
20
38
  }
39
+ /**
40
+ * The stored definition always stamps the normalized type so
41
+ * events it creates are publishable under the registered type.
42
+ */
43
+ const normalizedDefinition = definition.type === type
44
+ ? definition
45
+ : Object.freeze({
46
+ type: type,
47
+ create: (payload, createOptions) => ({
48
+ ...definition.create(payload, createOptions),
49
+ type,
50
+ }),
51
+ });
21
52
  const registered = Object.freeze({
22
53
  type: type,
23
- definition,
54
+ definition: normalizedDefinition,
24
55
  registeredAt: new Date(),
25
56
  });
26
57
  definitions.set(type, registered);
@@ -34,43 +65,96 @@ export function registryRegister(definition, definitions, options, ensureActive,
34
65
  /**
35
66
  * Registers a handler.
36
67
  */
37
- export function registryRegisterHandler(eventType, handler, handlerOptions, handlers, options, ensureActive, notify) {
68
+ export function registryRegisterHandler(eventType, handler, handlerOptions, handlers, options, ensureActive, notify, warnedPatterns) {
38
69
  ensureActive();
39
- if (!isValidEventTypePattern(eventType)) {
40
- throw new TypeError(`Invalid event type pattern "${eventType}".`);
41
- }
42
- const normalizedPattern = eventType === "*"
43
- ? "*"
44
- : eventType.endsWith(".*")
45
- ? `${normalizeEventType(eventType.slice(0, -2))}.*`
46
- : normalizeEventType(eventType);
70
+ /**
71
+ * createEventHandler validates the handler, the pattern and the
72
+ * options, and normalizes the pattern.
73
+ */
47
74
  const registration = createEventHandler(handler, {
48
75
  ...handlerOptions,
49
- eventType: normalizedPattern,
76
+ eventType,
50
77
  });
51
- if (handlers.has(registration.id) && !options.allowDuplicateHandlerIds) {
52
- throw new DuplicateEventHandlerError(registration.id);
78
+ const existing = handlers.get(registration.id);
79
+ if (existing) {
80
+ if (options.onDuplicateHandlerId === "throw") {
81
+ throw new DuplicateEventHandlerError(registration.id);
82
+ }
83
+ existing.subscription.unsubscribe();
53
84
  }
54
- handlers.set(registration.id, registration);
85
+ const subscription = createEventSubscription(() => {
86
+ /**
87
+ * Identity check: only remove the entry if it still belongs
88
+ * to this registration. A replacement registered under the
89
+ * same id must not be removed by the old subscription. No
90
+ * ensureActive here so teardown after dispose is a no-op.
91
+ */
92
+ if (handlers.get(registration.id)?.registration !== registration) {
93
+ return;
94
+ }
95
+ handlers.delete(registration.id);
96
+ notify({
97
+ type: EventRegistryChangeType.HANDLER_UNREGISTERED,
98
+ eventType: registration.eventType,
99
+ handler: registration,
100
+ timestamp: new Date(),
101
+ });
102
+ }, {
103
+ id: registration.id,
104
+ description: registration.description,
105
+ });
106
+ handlers.set(registration.id, { registration, subscription });
107
+ checkHandlerLimit(registration.eventType, handlers, options.maxHandlersPerPattern, options.onWarning, warnedPatterns);
55
108
  notify({
56
109
  type: EventRegistryChangeType.HANDLER_REGISTERED,
57
- eventType: normalizedPattern,
110
+ eventType: registration.eventType,
58
111
  handler: registration,
59
112
  timestamp: new Date(),
60
113
  });
61
- return createEventSubscription(() => {
62
- registryUnregisterHandler(registration.id, handlers, ensureActive, notify);
63
- }, {
64
- id: registration.id,
65
- description: registration.description,
66
- });
114
+ return subscription;
115
+ }
116
+ /**
117
+ * Emits a leak warning (once per pattern) when the number of
118
+ * handlers for a pattern exceeds the configured limit.
119
+ */
120
+ function checkHandlerLimit(pattern, handlers, limit, onWarning, warnedPatterns) {
121
+ if (limit <= 0 || warnedPatterns.has(pattern)) {
122
+ return;
123
+ }
124
+ let count = 0;
125
+ for (const entry of handlers.values()) {
126
+ if (entry.registration.eventType === pattern) {
127
+ count++;
128
+ }
129
+ }
130
+ if (count <= limit) {
131
+ return;
132
+ }
133
+ warnedPatterns.add(pattern);
134
+ const warning = {
135
+ type: "handler.limit",
136
+ pattern,
137
+ count,
138
+ limit,
139
+ message: `Possible event handler leak: ${count} handlers registered for ` +
140
+ `"${pattern}" (limit ${limit}). Unsubscribe handlers you no longer ` +
141
+ "need or raise maxHandlersPerPattern / maxListeners.",
142
+ };
143
+ try {
144
+ onWarning(warning);
145
+ }
146
+ catch {
147
+ /**
148
+ * A failing warning hook must not break registration.
149
+ */
150
+ }
67
151
  }
68
152
  /**
69
153
  * Unregisters an event definition.
70
154
  */
71
155
  export function registryUnregister(eventType, definitions, ensureActive, notify) {
72
156
  ensureActive();
73
- const type = normalizeEventType(eventType);
157
+ const type = normalizeRegistryEventType(eventType);
74
158
  const removed = definitions.delete(type);
75
159
  if (removed) {
76
160
  notify({
@@ -82,23 +166,18 @@ export function registryUnregister(eventType, definitions, ensureActive, notify)
82
166
  return removed;
83
167
  }
84
168
  /**
85
- * Unregisters a handler.
169
+ * Unregisters a handler by id, cancelling its subscription.
86
170
  */
87
- export function registryUnregisterHandler(handlerId, handlers, ensureActive, notify) {
171
+ export function registryUnregisterHandler(handlerId, handlers, ensureActive) {
88
172
  ensureActive();
89
- const handler = handlers.get(handlerId);
90
- if (!handler) {
173
+ const entry = handlers.get(handlerId);
174
+ if (!entry) {
91
175
  return false;
92
176
  }
93
- const removed = handlers.delete(handlerId);
94
- if (removed) {
95
- notify({
96
- type: EventRegistryChangeType.HANDLER_UNREGISTERED,
97
- eventType: handler.eventType,
98
- handler,
99
- timestamp: new Date(),
100
- });
101
- }
102
- return removed;
177
+ /**
178
+ * The subscription callback removes the entry and notifies.
179
+ */
180
+ entry.subscription.unsubscribe();
181
+ return !handlers.has(handlerId);
103
182
  }
104
183
  //# sourceMappingURL=eventRegistry.registration.js.map
@@ -3,22 +3,24 @@
3
3
  *
4
4
  * The registry owns event definitions and registered handlers.
5
5
  * It does not perform event dispatching. Dispatching belongs to
6
- * EventEmitter and higher-level routing belongs to EventBus.
6
+ * EventEmitter (which stores its handlers in a registry) and
7
+ * higher-level routing belongs to EventBus.
7
8
  */
8
9
  import type { Event, EventDefinition, EventType } from "../eventTypes/eventDefinition.type.js";
9
10
  import type { EventTypePattern } from "../eventTypes/eventType.type.js";
10
11
  import type { EventHandlerLike, EventHandlerOptions, RegisteredEventHandler } from "../eventHandler/eventHandler.core.js";
11
12
  import type { EventSubscription } from "../eventSubscription/eventSubscription.core.js";
12
- import { DuplicateEventDefinitionError, EventDefinitionNotFoundError, DuplicateEventHandlerError, EventHandlerNotFoundError } from "../eventErrors/eventError.base.js";
13
- import type { EventRegistryOptions, EventRegistryListener, RegisteredEventDefinition } from "./eventRegistry.type.js";
14
- export { DuplicateEventDefinitionError, EventDefinitionNotFoundError, DuplicateEventHandlerError, EventHandlerNotFoundError, };
13
+ import { DuplicateEventDefinitionError, EventDefinitionNotFoundError, DuplicateEventHandlerError, EventHandlerNotFoundError, EventRegistryDisposedError } from "../eventErrors/eventError.base.js";
14
+ import type { EventHandlerStore, EventRegistryOptions, EventRegistryListener, RegisteredEventDefinition } from "./eventRegistry.type.js";
15
+ export { DuplicateEventDefinitionError, EventDefinitionNotFoundError, DuplicateEventHandlerError, EventHandlerNotFoundError, EventRegistryDisposedError, };
15
16
  /**
16
17
  * Main event registry.
17
18
  */
18
- export declare class EventRegistry {
19
+ export declare class EventRegistry implements EventHandlerStore {
19
20
  private readonly definitions;
20
21
  private readonly handlers;
21
22
  private readonly listeners;
23
+ private readonly warnedPatterns;
22
24
  private readonly options;
23
25
  private disposed;
24
26
  constructor(options?: EventRegistryOptions);
@@ -31,6 +33,11 @@ export declare class EventRegistry {
31
33
  getHandler(handlerId: string): RegisteredEventHandler | undefined;
32
34
  requireHandler(handlerId: string): RegisteredEventHandler;
33
35
  hasHandler(handlerId: string): boolean;
36
+ /**
37
+ * Returns the subscription created for a handler id, if the
38
+ * handler is still registered.
39
+ */
40
+ getHandlerSubscription(handlerId: string): EventSubscription | undefined;
34
41
  unregisterHandler(handlerId: string): boolean;
35
42
  getDefinitions(): readonly RegisteredEventDefinition[];
36
43
  getHandlers(): readonly RegisteredEventHandler[];