@zudojs/events 0.0.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/README.md +70 -11
  2. package/dist/eventBus/eventBus.core.d.ts +28 -2
  3. package/dist/eventBus/eventBus.core.js +104 -31
  4. package/dist/eventBus/eventBus.publish.d.ts +23 -5
  5. package/dist/eventBus/eventBus.publish.js +72 -32
  6. package/dist/eventBus/eventBus.registration.d.ts +24 -13
  7. package/dist/eventBus/eventBus.registration.js +31 -28
  8. package/dist/eventBus/eventBus.type.d.ts +75 -3
  9. package/dist/eventBus/eventBus.type.js +9 -0
  10. package/dist/eventBus/index.d.ts +2 -0
  11. package/dist/eventBus/index.js +2 -0
  12. package/dist/eventEmitter/eventEmitter.abort.d.ts +11 -2
  13. package/dist/eventEmitter/eventEmitter.abort.js +20 -5
  14. package/dist/eventEmitter/eventEmitter.core.d.ts +14 -3
  15. package/dist/eventEmitter/eventEmitter.core.js +63 -55
  16. package/dist/eventEmitter/eventEmitter.parallel.d.ts +7 -1
  17. package/dist/eventEmitter/eventEmitter.parallel.js +31 -13
  18. package/dist/eventEmitter/eventEmitter.sequential.d.ts +17 -1
  19. package/dist/eventEmitter/eventEmitter.sequential.js +19 -12
  20. package/dist/eventEmitter/eventEmitter.type.d.ts +60 -0
  21. package/dist/eventErrors/eventError.base.d.ts +50 -3
  22. package/dist/eventErrors/eventError.base.js +64 -3
  23. package/dist/eventHandler/eventHandler.core.d.ts +19 -0
  24. package/dist/eventHandler/eventHandler.core.js +67 -4
  25. package/dist/eventMiddleware/eventMiddleware.builder.js +5 -11
  26. package/dist/eventMiddleware/eventMiddleware.helper.js +0 -1
  27. package/dist/eventMiddleware/eventMiddleware.pipeline.d.ts +5 -0
  28. package/dist/eventMiddleware/eventMiddleware.pipeline.js +33 -9
  29. package/dist/eventRegistry/eventRegistry.lifecycle.d.ts +10 -5
  30. package/dist/eventRegistry/eventRegistry.lifecycle.js +23 -10
  31. package/dist/eventRegistry/eventRegistry.queries.d.ts +16 -7
  32. package/dist/eventRegistry/eventRegistry.queries.js +22 -13
  33. package/dist/eventRegistry/eventRegistry.registration.d.ts +14 -7
  34. package/dist/eventRegistry/eventRegistry.registration.js +120 -41
  35. package/dist/eventRegistry/eventRegistry.store.d.ts +12 -5
  36. package/dist/eventRegistry/eventRegistry.store.js +37 -12
  37. package/dist/eventRegistry/eventRegistry.type.d.ts +87 -2
  38. package/dist/eventRegistry/eventRegistry.type.js +5 -0
  39. package/dist/eventSubscription/eventSubscription.core.d.ts +5 -0
  40. package/dist/eventSubscription/eventSubscription.core.js +17 -3
  41. package/dist/eventTypes/eventDefinition.type.d.ts +11 -4
  42. package/dist/eventTypes/eventDefinition.type.js +49 -17
  43. package/dist/eventTypes/eventPayload.type.d.ts +5 -0
  44. package/dist/eventTypes/eventPayload.type.js +56 -15
  45. package/dist/eventTypes/eventType.type.d.ts +22 -5
  46. package/dist/eventTypes/eventType.type.js +33 -11
  47. package/dist/eventTypes/index.d.ts +2 -2
  48. package/dist/eventTypes/index.js +2 -2
  49. package/package.json +15 -7
  50. package/dist/.tsbuildinfo +0 -1
  51. package/dist/eventBus/eventBus.core.d.ts.map +0 -1
  52. package/dist/eventBus/eventBus.core.js.map +0 -1
  53. package/dist/eventBus/eventBus.factory.d.ts.map +0 -1
  54. package/dist/eventBus/eventBus.factory.js.map +0 -1
  55. package/dist/eventBus/eventBus.publish.d.ts.map +0 -1
  56. package/dist/eventBus/eventBus.publish.js.map +0 -1
  57. package/dist/eventBus/eventBus.registration.d.ts.map +0 -1
  58. package/dist/eventBus/eventBus.registration.js.map +0 -1
  59. package/dist/eventBus/eventBus.type.d.ts.map +0 -1
  60. package/dist/eventBus/eventBus.type.js.map +0 -1
  61. package/dist/eventBus/index.d.ts.map +0 -1
  62. package/dist/eventBus/index.js.map +0 -1
  63. package/dist/eventEmitter/eventEmitter.abort.d.ts.map +0 -1
  64. package/dist/eventEmitter/eventEmitter.abort.js.map +0 -1
  65. package/dist/eventEmitter/eventEmitter.core.d.ts.map +0 -1
  66. package/dist/eventEmitter/eventEmitter.core.js.map +0 -1
  67. package/dist/eventEmitter/eventEmitter.parallel.d.ts.map +0 -1
  68. package/dist/eventEmitter/eventEmitter.parallel.js.map +0 -1
  69. package/dist/eventEmitter/eventEmitter.sequential.d.ts.map +0 -1
  70. package/dist/eventEmitter/eventEmitter.sequential.js.map +0 -1
  71. package/dist/eventEmitter/eventEmitter.type.d.ts.map +0 -1
  72. package/dist/eventEmitter/eventEmitter.type.js.map +0 -1
  73. package/dist/eventEmitter/index.d.ts.map +0 -1
  74. package/dist/eventEmitter/index.js.map +0 -1
  75. package/dist/eventErrors/eventError.base.d.ts.map +0 -1
  76. package/dist/eventErrors/eventError.base.js.map +0 -1
  77. package/dist/eventErrors/index.d.ts.map +0 -1
  78. package/dist/eventErrors/index.js.map +0 -1
  79. package/dist/eventHandler/eventHandler.core.d.ts.map +0 -1
  80. package/dist/eventHandler/eventHandler.core.js.map +0 -1
  81. package/dist/eventHandler/index.d.ts.map +0 -1
  82. package/dist/eventHandler/index.js.map +0 -1
  83. package/dist/eventMiddleware/eventMiddleware.builder.d.ts.map +0 -1
  84. package/dist/eventMiddleware/eventMiddleware.builder.js.map +0 -1
  85. package/dist/eventMiddleware/eventMiddleware.helper.d.ts.map +0 -1
  86. package/dist/eventMiddleware/eventMiddleware.helper.js.map +0 -1
  87. package/dist/eventMiddleware/eventMiddleware.pipeline.d.ts.map +0 -1
  88. package/dist/eventMiddleware/eventMiddleware.pipeline.js.map +0 -1
  89. package/dist/eventMiddleware/eventMiddleware.type.d.ts.map +0 -1
  90. package/dist/eventMiddleware/eventMiddleware.type.js.map +0 -1
  91. package/dist/eventMiddleware/index.d.ts.map +0 -1
  92. package/dist/eventMiddleware/index.js.map +0 -1
  93. package/dist/eventRegistry/eventRegistry.lifecycle.d.ts.map +0 -1
  94. package/dist/eventRegistry/eventRegistry.lifecycle.js.map +0 -1
  95. package/dist/eventRegistry/eventRegistry.queries.d.ts.map +0 -1
  96. package/dist/eventRegistry/eventRegistry.queries.js.map +0 -1
  97. package/dist/eventRegistry/eventRegistry.registration.d.ts.map +0 -1
  98. package/dist/eventRegistry/eventRegistry.registration.js.map +0 -1
  99. package/dist/eventRegistry/eventRegistry.store.d.ts.map +0 -1
  100. package/dist/eventRegistry/eventRegistry.store.js.map +0 -1
  101. package/dist/eventRegistry/eventRegistry.type.d.ts.map +0 -1
  102. package/dist/eventRegistry/eventRegistry.type.js.map +0 -1
  103. package/dist/eventRegistry/index.d.ts.map +0 -1
  104. package/dist/eventRegistry/index.js.map +0 -1
  105. package/dist/eventSubscription/eventSubscription.core.d.ts.map +0 -1
  106. package/dist/eventSubscription/eventSubscription.core.js.map +0 -1
  107. package/dist/eventSubscription/index.d.ts.map +0 -1
  108. package/dist/eventSubscription/index.js.map +0 -1
  109. package/dist/eventTypes/eventDefinition.type.d.ts.map +0 -1
  110. package/dist/eventTypes/eventDefinition.type.js.map +0 -1
  111. package/dist/eventTypes/eventPayload.type.d.ts.map +0 -1
  112. package/dist/eventTypes/eventPayload.type.js.map +0 -1
  113. package/dist/eventTypes/eventType.type.d.ts.map +0 -1
  114. package/dist/eventTypes/eventType.type.js.map +0 -1
  115. package/dist/eventTypes/index.d.ts.map +0 -1
  116. package/dist/eventTypes/index.js.map +0 -1
  117. package/dist/index.d.ts.map +0 -1
  118. package/dist/index.js.map +0 -1
@@ -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[];
@@ -3,13 +3,18 @@
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
- import { DuplicateEventDefinitionError, EventDefinitionNotFoundError, DuplicateEventHandlerError, EventHandlerNotFoundError, } from "../eventErrors/eventError.base.js";
9
- import { registryRegister, registryRegisterHandler, registryUnregister, registryUnregisterHandler, } from "./eventRegistry.registration.js";
9
+ import { DuplicateEventDefinitionError, EventDefinitionNotFoundError, DuplicateEventHandlerError, EventHandlerNotFoundError, EventRegistryDisposedError, } from "../eventErrors/eventError.base.js";
10
+ import { DEFAULT_MAX_HANDLERS_PER_PATTERN } from "./eventRegistry.type.js";
11
+ import { normalizeRegistryEventType, registryRegister, registryRegisterHandler, registryUnregister, registryUnregisterHandler, } from "./eventRegistry.registration.js";
10
12
  import { getHandlersForEvent, getHandlersForType, getAllDefinitions, getAllHandlers, } from "./eventRegistry.queries.js";
11
13
  import { registryClear, registryDispose, registryNotify, } from "./eventRegistry.lifecycle.js";
12
- export { DuplicateEventDefinitionError, EventDefinitionNotFoundError, DuplicateEventHandlerError, EventHandlerNotFoundError, };
14
+ export { DuplicateEventDefinitionError, EventDefinitionNotFoundError, DuplicateEventHandlerError, EventHandlerNotFoundError, EventRegistryDisposedError, };
15
+ function defaultWarning(warning) {
16
+ console.warn(`[@zudojs/events] ${warning.message}`);
17
+ }
13
18
  /**
14
19
  * Main event registry.
15
20
  */
@@ -17,23 +22,34 @@ export class EventRegistry {
17
22
  definitions = new Map();
18
23
  handlers = new Map();
19
24
  listeners = new Set();
25
+ warnedPatterns = new Set();
20
26
  options;
21
27
  disposed = false;
22
28
  constructor(options = {}) {
29
+ const maxHandlersPerPattern = options.maxHandlersPerPattern ?? DEFAULT_MAX_HANDLERS_PER_PATTERN;
30
+ if (typeof maxHandlersPerPattern !== "number" ||
31
+ !Number.isFinite(maxHandlersPerPattern) ||
32
+ maxHandlersPerPattern < 0) {
33
+ throw new RangeError("maxHandlersPerPattern must be a non-negative finite number.");
34
+ }
23
35
  this.options = {
24
36
  allowDuplicateDefinitions: options.allowDuplicateDefinitions ?? false,
25
- allowDuplicateHandlerIds: options.allowDuplicateHandlerIds ?? false,
37
+ onDuplicateHandlerId: options.onDuplicateHandlerId ??
38
+ (options.allowDuplicateHandlerIds ? "replace" : "throw"),
39
+ maxHandlersPerPattern,
40
+ onWarning: options.onWarning ?? defaultWarning,
41
+ onError: options.onError,
26
42
  };
27
43
  }
28
44
  register(definition) {
29
45
  return registryRegister(definition, this.definitions, this.options, () => this.ensureActive(), (c) => this.notify(c));
30
46
  }
31
47
  registerHandler(eventType, handler, options = {}) {
32
- return registryRegisterHandler(eventType, handler, options, this.handlers, this.options, () => this.ensureActive(), (c) => this.notify(c));
48
+ return registryRegisterHandler(eventType, handler, options, this.handlers, this.options, () => this.ensureActive(), (c) => this.notify(c), this.warnedPatterns);
33
49
  }
34
50
  get(eventType) {
35
51
  this.ensureActive();
36
- return this.definitions.get(eventType);
52
+ return this.definitions.get(normalizeRegistryEventType(eventType));
37
53
  }
38
54
  require(eventType) {
39
55
  const definition = this.get(eventType);
@@ -44,14 +60,14 @@ export class EventRegistry {
44
60
  }
45
61
  has(eventType) {
46
62
  this.ensureActive();
47
- return this.definitions.has(eventType);
63
+ return this.definitions.has(normalizeRegistryEventType(eventType));
48
64
  }
49
65
  unregister(eventType) {
50
66
  return registryUnregister(eventType, this.definitions, () => this.ensureActive(), (c) => this.notify(c));
51
67
  }
52
68
  getHandler(handlerId) {
53
69
  this.ensureActive();
54
- return this.handlers.get(handlerId);
70
+ return this.handlers.get(handlerId)?.registration;
55
71
  }
56
72
  requireHandler(handlerId) {
57
73
  const handler = this.getHandler(handlerId);
@@ -64,8 +80,16 @@ export class EventRegistry {
64
80
  this.ensureActive();
65
81
  return this.handlers.has(handlerId);
66
82
  }
83
+ /**
84
+ * Returns the subscription created for a handler id, if the
85
+ * handler is still registered.
86
+ */
87
+ getHandlerSubscription(handlerId) {
88
+ this.ensureActive();
89
+ return this.handlers.get(handlerId)?.subscription;
90
+ }
67
91
  unregisterHandler(handlerId) {
68
- return registryUnregisterHandler(handlerId, this.handlers, () => this.ensureActive(), (c) => this.notify(c));
92
+ return registryUnregisterHandler(handlerId, this.handlers, () => this.ensureActive());
69
93
  }
70
94
  getDefinitions() {
71
95
  this.ensureActive();
@@ -98,6 +122,7 @@ export class EventRegistry {
98
122
  }
99
123
  clear() {
100
124
  registryClear(this.definitions, this.handlers, () => this.ensureActive(), (c) => this.notify(c));
125
+ this.warnedPatterns.clear();
101
126
  }
102
127
  dispose() {
103
128
  registryDispose(this.disposed, this.definitions, this.handlers, this.listeners, () => this.ensureActive(), (c) => this.notify(c));
@@ -107,11 +132,11 @@ export class EventRegistry {
107
132
  return this.disposed;
108
133
  }
109
134
  notify(change) {
110
- registryNotify(change, this.listeners);
135
+ registryNotify(change, this.listeners, this.options.onError);
111
136
  }
112
137
  ensureActive() {
113
138
  if (this.disposed) {
114
- throw new Error("EventRegistry has already been disposed.");
139
+ throw new EventRegistryDisposedError();
115
140
  }
116
141
  }
117
142
  }
@@ -1,11 +1,73 @@
1
1
  /**
2
2
  * Event registry type definitions for Zudojs.
3
3
  */
4
- import type { EventDefinition, EventType } from "../eventTypes/eventDefinition.type.js";
5
- import type { RegisteredEventHandler } from "../eventHandler/eventHandler.core.js";
4
+ import type { Event, EventDefinition, EventType } from "../eventTypes/eventDefinition.type.js";
5
+ import type { EventTypePattern } from "../eventTypes/eventType.type.js";
6
+ import type { EventHandlerLike, EventHandlerOptions, RegisteredEventHandler } from "../eventHandler/eventHandler.core.js";
7
+ import type { EventSubscription } from "../eventSubscription/eventSubscription.core.js";
8
+ /**
9
+ * Policy applied when a handler is registered with an id that
10
+ * is already in use.
11
+ *
12
+ * - "throw" → DuplicateEventHandlerError (default)
13
+ * - "replace" → the previous handler is unregistered (its
14
+ * subscription is cancelled) and the new one stored
15
+ */
16
+ export type DuplicateHandlerIdPolicy = "throw" | "replace";
17
+ /**
18
+ * Default number of handlers allowed per event pattern before a
19
+ * leak warning is emitted.
20
+ */
21
+ export declare const DEFAULT_MAX_HANDLERS_PER_PATTERN = 100;
22
+ /**
23
+ * Warning emitted when a registry limit is exceeded.
24
+ */
25
+ export interface EventRegistryWarning {
26
+ readonly type: "handler.limit";
27
+ readonly pattern: EventTypePattern;
28
+ readonly count: number;
29
+ readonly limit: number;
30
+ readonly message: string;
31
+ }
32
+ /**
33
+ * Context passed to onError hooks.
34
+ */
35
+ export interface EventRegistryErrorContext {
36
+ readonly source: "observer";
37
+ readonly change?: EventRegistryChange;
38
+ }
6
39
  export interface EventRegistryOptions {
40
+ /**
41
+ * Allow re-registering an event type. The previous definition
42
+ * is replaced.
43
+ */
7
44
  readonly allowDuplicateDefinitions?: boolean;
45
+ /**
46
+ * @deprecated Use `onDuplicateHandlerId: "replace"`. When true,
47
+ * registering a handler id that already exists replaces the
48
+ * previous handler (it never allowed two handlers to coexist).
49
+ */
8
50
  readonly allowDuplicateHandlerIds?: boolean;
51
+ /**
52
+ * What to do when a handler id is already registered.
53
+ * Defaults to "throw".
54
+ */
55
+ readonly onDuplicateHandlerId?: DuplicateHandlerIdPolicy;
56
+ /**
57
+ * Maximum handlers per event pattern before a leak warning is
58
+ * emitted through `onWarning` (or console.warn). Use 0 to
59
+ * disable. Defaults to 100.
60
+ */
61
+ readonly maxHandlersPerPattern?: number;
62
+ /**
63
+ * Receives limit warnings. Defaults to console.warn.
64
+ */
65
+ readonly onWarning?: (warning: EventRegistryWarning) => void;
66
+ /**
67
+ * Receives errors thrown by registry observers, which are
68
+ * otherwise swallowed so they cannot break registry mutations.
69
+ */
70
+ readonly onError?: (error: unknown, context: EventRegistryErrorContext) => void;
9
71
  }
10
72
  export declare enum EventRegistryChangeType {
11
73
  EVENT_REGISTERED = "event.registered",
@@ -25,4 +87,27 @@ export interface RegisteredEventDefinition<TType extends EventType = EventType,
25
87
  readonly definition: EventDefinition<TType, TPayload>;
26
88
  readonly registeredAt: Date;
27
89
  }
90
+ /**
91
+ * Internal handler entry: the registration plus the subscription
92
+ * handed to the caller, so the registry can cancel it.
93
+ */
94
+ export interface EventHandlerEntry {
95
+ readonly registration: RegisteredEventHandler;
96
+ readonly subscription: EventSubscription;
97
+ }
98
+ /**
99
+ * Minimal handler store contract used by EventEmitter.
100
+ *
101
+ * EventRegistry implements it; the emitter stores its handlers in
102
+ * a registry so that a bus has a single source of truth for
103
+ * handlers.
104
+ */
105
+ export interface EventHandlerStore {
106
+ registerHandler<TEvent extends Event = Event>(eventType: EventTypePattern, handler: EventHandlerLike<TEvent>, options?: Omit<EventHandlerOptions, "eventType">): EventSubscription;
107
+ unregisterHandler(handlerId: string): boolean;
108
+ hasHandler(handlerId: string): boolean;
109
+ getHandlers(): readonly RegisteredEventHandler[];
110
+ getHandlersForEvent(event: Event): readonly RegisteredEventHandler[];
111
+ readonly handlerCount: number;
112
+ }
28
113
  //# sourceMappingURL=eventRegistry.type.d.ts.map
@@ -1,6 +1,11 @@
1
1
  /**
2
2
  * Event registry type definitions for Zudojs.
3
3
  */
4
+ /**
5
+ * Default number of handlers allowed per event pattern before a
6
+ * leak warning is emitted.
7
+ */
8
+ export const DEFAULT_MAX_HANDLERS_PER_PATTERN = 100;
4
9
  export var EventRegistryChangeType;
5
10
  (function (EventRegistryChangeType) {
6
11
  EventRegistryChangeType["EVENT_REGISTERED"] = "event.registered";
@@ -135,6 +135,11 @@ export declare class EventSubscriptionGroup implements EventSubscription {
135
135
  remove(subscription: EventSubscription): boolean;
136
136
  /**
137
137
  * Unsubscribes every subscription in the group.
138
+ *
139
+ * Every subscription is attempted even if one throws; the
140
+ * failures are then re-thrown together as an AggregateError and
141
+ * the failed subscriptions stay in the group so the call can be
142
+ * retried.
138
143
  */
139
144
  unsubscribe(): void;
140
145
  /**