@valkyrjaio/valkyrja 26.6.5 → 26.7.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 (23) hide show
  1. package/package.json +1 -1
  2. package/src/Valkyrja/Application/Constant/ApplicationInfo.ts +2 -2
  3. package/src/Valkyrja/Application/Provider/ApplicationComponentProvider.ts +2 -1
  4. package/src/Valkyrja/Container/Manager/Container.ts +4 -15
  5. package/src/Valkyrja/Container/Manager/Contract/ContainerContract.ts +1 -2
  6. package/src/Valkyrja/Event/Collection/Contract/ListenerCollectionContract.ts +111 -0
  7. package/src/Valkyrja/Event/Collection/ListenerCollection.ts +171 -0
  8. package/src/Valkyrja/Event/Constant/EventServiceId.ts +2 -0
  9. package/src/Valkyrja/Event/Contract/ArgumentsCapableEventContract.ts +24 -0
  10. package/src/Valkyrja/Event/Contract/DispatchCollectableEventContract.ts +29 -0
  11. package/src/Valkyrja/Event/Contract/EventContract.ts +20 -0
  12. package/src/Valkyrja/Event/Contract/StoppableEventContract.ts +22 -0
  13. package/src/Valkyrja/Event/Data/Contract/ListenerContract.ts +36 -2
  14. package/src/Valkyrja/Event/Data/EventData.ts +8 -1
  15. package/src/Valkyrja/Event/Data/Listener.ts +41 -0
  16. package/src/Valkyrja/Event/Dispatcher/Contract/EventDispatcherContract.ts +54 -0
  17. package/src/Valkyrja/Event/Dispatcher/EventDispatcher.ts +93 -0
  18. package/src/Valkyrja/Event/Provider/EventComponentProvider.ts +38 -0
  19. package/src/Valkyrja/Event/Provider/EventServiceProvider.ts +69 -0
  20. package/src/Valkyrja/{Container/Enum/InvalidReferenceMode.ts → Event/Throwable/Contract/EventThrowable.ts} +3 -4
  21. package/src/Valkyrja/Event/Throwable/Exception/Abstract/EventInvalidArgumentException.ts +15 -0
  22. package/src/Valkyrja/Event/Throwable/Exception/Abstract/EventRuntimeException.ts +13 -0
  23. package/src/Valkyrja/Event/Throwable/Exception/EventInvalidEventException.ts +19 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@valkyrjaio/valkyrja",
3
- "version": "26.6.5",
3
+ "version": "26.7.0",
4
4
  "description": "The Valkyrja Framework.",
5
5
  "keywords": [
6
6
  "framework",
@@ -7,8 +7,8 @@
7
7
  */
8
8
 
9
9
  export class ApplicationInfo {
10
- static readonly VERSION = '26.6.5' as const;
11
- static readonly VERSION_BUILD_DATE_TIME = 'August 6 2026 08:18:50 MST' as const;
10
+ static readonly VERSION = '26.7.0' as const;
11
+ static readonly VERSION_BUILD_DATE_TIME = 'August 7 2026 07:13:22 MST' as const;
12
12
  static readonly ASCII = `
13
13
  _ _ _
14
14
  /\\ /\\__ _| | | ___ _ _ __(_) __ _
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  import { ContainerComponentProvider } from '../../Container/Provider/ContainerComponentProvider.ts';
10
+ import { EventComponentProvider } from '../../Event/Provider/EventComponentProvider.ts';
10
11
 
11
12
  import type { ApplicationContract } from '../Kernel/Contract/ApplicationContract.ts';
12
13
  import type { ComponentProviderContract } from './Contract/ComponentProviderContract.ts';
@@ -17,7 +18,7 @@ import type { HttpRouteProviderContract } from '../../Http/Routing/Provider/Cont
17
18
 
18
19
  export class ApplicationComponentProvider implements ComponentProviderContract {
19
20
  getComponentProviders(_app: ApplicationContract): ComponentProviderContract[] {
20
- return [new ContainerComponentProvider()];
21
+ return [new ContainerComponentProvider(), new EventComponentProvider()];
21
22
  }
22
23
 
23
24
  getContainerProviders(_app: ApplicationContract): ServiceProviderContract[] {
@@ -7,7 +7,6 @@
7
7
  */
8
8
 
9
9
  import { ContainerData } from '../Data/ContainerData.ts';
10
- import { InvalidReferenceMode } from '../Enum/InvalidReferenceMode.ts';
11
10
  import { ContainerInvalidReferenceException } from '../Throwable/Exception/ContainerInvalidReferenceException.ts';
12
11
  import { ContainerInvalidPublishCallbackException } from '../Throwable/Exception/ContainerInvalidPublishCallbackException.ts';
13
12
 
@@ -96,18 +95,16 @@ export class Container implements ContainerContract {
96
95
  return id in this.instances;
97
96
  }
98
97
 
99
- get<T extends object>(
100
- id: string,
101
- args: unknown[] = [],
102
- mode: InvalidReferenceMode = InvalidReferenceMode.NEW_INSTANCE_OR_THROW_EXCEPTION,
103
- ): T {
98
+ get<T extends object>(id: string, args: unknown[] = []): T {
104
99
  this.publishUnpublishedProvided(id);
105
100
 
106
101
  return (
107
102
  this.getSingletonWithoutChecks<T>(id) ??
108
103
  this.getServiceWithoutChecks<T>(id, args) ??
109
104
  this.getAliasedWithoutChecks<T>(id, args) ??
110
- this.getFallback<T>(id, args, mode)
105
+ (() => {
106
+ throw new ContainerInvalidReferenceException(id);
107
+ })()
111
108
  );
112
109
  }
113
110
 
@@ -227,14 +224,6 @@ export class Container implements ContainerContract {
227
224
  return this.deferredCallback[id];
228
225
  }
229
226
 
230
- protected getFallback<T extends object>(
231
- id: string,
232
- _args: unknown[] = [],
233
- _mode: InvalidReferenceMode = InvalidReferenceMode.NEW_INSTANCE_OR_THROW_EXCEPTION,
234
- ): T {
235
- throw new ContainerInvalidReferenceException(id);
236
- }
237
-
238
227
  protected publishUnpublishedProvided(id: string): void {
239
228
  if (this.isDeferred(id) && !this.isPublished(id)) {
240
229
  this.publish(id);
@@ -7,7 +7,6 @@
7
7
  */
8
8
 
9
9
  import type { ContainerData } from '../../Data/ContainerData.ts';
10
- import type { InvalidReferenceMode } from '../../Enum/InvalidReferenceMode.ts';
11
10
  import type { ProvidersAwareContract } from './ProvidersAwareContract.ts';
12
11
 
13
12
  export interface ContainerContract extends ProvidersAwareContract {
@@ -23,7 +22,7 @@ export interface ContainerContract extends ProvidersAwareContract {
23
22
  isSingleton(id: string): boolean;
24
23
  isSingletonBinding(id: string): boolean;
25
24
  isSingletonInstance(id: string): boolean;
26
- get<T extends object>(id: string, args?: unknown[], mode?: InvalidReferenceMode): T;
25
+ get<T extends object>(id: string, args?: unknown[]): T;
27
26
  getAliased<T extends object>(id: string, args?: unknown[]): T;
28
27
  getService<T extends object>(id: string, args?: unknown[]): T;
29
28
  getSingleton<T extends object>(id: string): T;
@@ -0,0 +1,111 @@
1
+ /*
2
+ * This file is part of the Valkyrja Framework package.
3
+ *
4
+ * Copyright (c) 2016-present Melech Mizrachi
5
+ *
6
+ * Released under the MIT License. See LICENSE.md for details.
7
+ */
8
+
9
+ import type { EventContract } from '../../Contract/EventContract.ts';
10
+ import type { ListenerContract } from '../../Data/Contract/ListenerContract.ts';
11
+ import type { EventData } from '../../Data/EventData.ts';
12
+
13
+ export interface ListenerCollectionContract {
14
+ /**
15
+ * Get a data representation of the collection.
16
+ */
17
+ getData(): EventData;
18
+
19
+ /**
20
+ * Set data from a data object.
21
+ */
22
+ setFromData(data: EventData): void;
23
+
24
+ /**
25
+ * Determine if a listener is registered.
26
+ */
27
+ hasListener(listener: ListenerContract): boolean;
28
+
29
+ /**
30
+ * Determine if a listener is registered by its id.
31
+ */
32
+ hasListenerById(listenerId: string): boolean;
33
+
34
+ /**
35
+ * Add a listener.
36
+ */
37
+ addListener(listener: ListenerContract): void;
38
+
39
+ /**
40
+ * Remove a listener.
41
+ */
42
+ removeListener(listener: ListenerContract): void;
43
+
44
+ /**
45
+ * Remove a listener by id.
46
+ */
47
+ removeListenerById(listenerId: string): void;
48
+
49
+ /**
50
+ * Determine if listeners exist for a given event.
51
+ */
52
+ hasListenersForEvent(event: EventContract): boolean;
53
+
54
+ /**
55
+ * Determine if listeners exist for a given event id.
56
+ */
57
+ hasListenersForEventById(eventId: string): boolean;
58
+
59
+ /**
60
+ * Get all listeners for a given event, in the order that the collection
61
+ * recorded them.
62
+ */
63
+ getListenersForEvent(event: EventContract): ListenerContract[];
64
+
65
+ /**
66
+ * Get all listeners for a given event id, in the order that the collection
67
+ * recorded them.
68
+ */
69
+ getListenersForEventById(eventId: string): ListenerContract[];
70
+
71
+ /**
72
+ * Set listeners for a given event.
73
+ */
74
+ setListenersForEvent(event: EventContract, ...listeners: ListenerContract[]): void;
75
+
76
+ /**
77
+ * Set listeners for a given event id.
78
+ */
79
+ setListenersForEventById(eventId: string, ...listeners: ListenerContract[]): void;
80
+
81
+ /**
82
+ * Remove all listeners for a given event.
83
+ */
84
+ removeListenersForEvent(event: EventContract): void;
85
+
86
+ /**
87
+ * Remove all listeners for a given event id.
88
+ */
89
+ removeListenersForEventById(eventId: string): void;
90
+
91
+ /**
92
+ * Get all listeners.
93
+ */
94
+ getListeners(): ListenerContract[];
95
+
96
+ /**
97
+ * Get the id of every registered event that has listeners.
98
+ */
99
+ getEvents(): string[];
100
+
101
+ /**
102
+ * Get every registered event with its listeners.
103
+ */
104
+ getEventsWithListeners(): Record<string, ListenerContract[]>;
105
+ }
106
+
107
+ export namespace ListenerCollectionContract {
108
+ export function instanceOf(value: unknown): value is ListenerCollectionContract {
109
+ return typeof value === 'object' && value !== null && 'getListenersForEvent' in value;
110
+ }
111
+ }
@@ -0,0 +1,171 @@
1
+ /*
2
+ * This file is part of the Valkyrja Framework package.
3
+ *
4
+ * Copyright (c) 2016-present Melech Mizrachi
5
+ *
6
+ * Released under the MIT License. See LICENSE.md for details.
7
+ */
8
+
9
+ import { EventData } from '../Data/EventData.ts';
10
+
11
+ import type { EventContract } from '../Contract/EventContract.ts';
12
+ import type { ListenerContract, ListenerFactory } from '../Data/Contract/ListenerContract.ts';
13
+ import type { ListenerCollectionContract } from './Contract/ListenerCollectionContract.ts';
14
+
15
+ export class ListenerCollection implements ListenerCollectionContract {
16
+ // A Map, not an object, because an object reorders a key that reads as an integer.
17
+ protected events = new Map<string, string[]>();
18
+ protected listeners = new Map<string, ListenerFactory>();
19
+
20
+ getData(): EventData {
21
+ const events: Record<string, string[]> = {};
22
+
23
+ for (const [eventId, listenerIds] of this.events) {
24
+ events[eventId] = [...listenerIds];
25
+ }
26
+
27
+ return new EventData(events, Object.fromEntries(this.listeners));
28
+ }
29
+
30
+ setFromData(data: EventData): void {
31
+ this.events = new Map(
32
+ Object.entries(data.events).map(([eventId, listenerIds]): [string, string[]] => [
33
+ eventId,
34
+ [...listenerIds],
35
+ ]),
36
+ );
37
+ this.listeners = new Map(Object.entries(data.listeners));
38
+ }
39
+
40
+ hasListener(listener: ListenerContract): boolean {
41
+ return this.hasListenerById(listener.getName());
42
+ }
43
+
44
+ hasListenerById(listenerId: string): boolean {
45
+ return this.listeners.has(listenerId);
46
+ }
47
+
48
+ addListener(listener: ListenerContract): void {
49
+ const listenerId = listener.getName();
50
+ const eventId = listener.getEventId();
51
+
52
+ let listenerIds = this.events.get(eventId);
53
+
54
+ if (listenerIds === undefined) {
55
+ listenerIds = [];
56
+
57
+ this.events.set(eventId, listenerIds);
58
+ }
59
+
60
+ if (!listenerIds.includes(listenerId)) {
61
+ listenerIds.push(listenerId);
62
+ }
63
+
64
+ this.listeners.set(listenerId, (): ListenerContract => listener);
65
+ }
66
+
67
+ removeListener(listener: ListenerContract): void {
68
+ const listenerId = listener.getName();
69
+ const listenerIds = this.events.get(listener.getEventId());
70
+
71
+ if (listenerIds !== undefined) {
72
+ this.removeValue(listenerIds, listenerId);
73
+ }
74
+
75
+ this.listeners.delete(listenerId);
76
+ }
77
+
78
+ removeListenerById(listenerId: string): void {
79
+ for (const listenerIds of this.events.values()) {
80
+ this.removeValue(listenerIds, listenerId);
81
+ }
82
+
83
+ this.listeners.delete(listenerId);
84
+ }
85
+
86
+ hasListenersForEvent(event: EventContract): boolean {
87
+ return this.hasListenersForEventById(event.getEventId());
88
+ }
89
+
90
+ hasListenersForEventById(eventId: string): boolean {
91
+ const listenerIds = this.events.get(eventId);
92
+
93
+ return listenerIds !== undefined && listenerIds.length > 0;
94
+ }
95
+
96
+ getListenersForEvent(event: EventContract): ListenerContract[] {
97
+ return this.getListenersForEventById(event.getEventId());
98
+ }
99
+
100
+ getListenersForEventById(eventId: string): ListenerContract[] {
101
+ return this.getListenersByIds(this.events.get(eventId) ?? []);
102
+ }
103
+
104
+ setListenersForEvent(event: EventContract, ...listeners: ListenerContract[]): void {
105
+ this.setListenersForEventById(event.getEventId(), ...listeners);
106
+ }
107
+
108
+ setListenersForEventById(eventId: string, ...listeners: ListenerContract[]): void {
109
+ for (const listener of listeners) {
110
+ this.addListener(listener.withEventId(eventId));
111
+ }
112
+ }
113
+
114
+ removeListenersForEvent(event: EventContract): void {
115
+ this.removeListenersForEventById(event.getEventId());
116
+ }
117
+
118
+ removeListenersForEventById(eventId: string): void {
119
+ for (const listener of this.getListenersForEventById(eventId)) {
120
+ this.removeListener(listener);
121
+ }
122
+
123
+ this.events.delete(eventId);
124
+ }
125
+
126
+ getListeners(): ListenerContract[] {
127
+ return this.getListenersByIds(this.listeners.keys());
128
+ }
129
+
130
+ getEvents(): string[] {
131
+ return [...this.events.keys()];
132
+ }
133
+
134
+ getEventsWithListeners(): Record<string, ListenerContract[]> {
135
+ const eventsWithListeners: Record<string, ListenerContract[]> = {};
136
+
137
+ for (const eventId of this.events.keys()) {
138
+ eventsWithListeners[eventId] = this.getListenersForEventById(eventId);
139
+ }
140
+
141
+ return eventsWithListeners;
142
+ }
143
+
144
+ /**
145
+ * Build each listener that the names identify.
146
+ */
147
+ protected getListenersByIds(listenerIds: Iterable<string>): ListenerContract[] {
148
+ const listeners: ListenerContract[] = [];
149
+
150
+ for (const listenerId of listenerIds) {
151
+ const factory = this.listeners.get(listenerId);
152
+
153
+ if (factory !== undefined) {
154
+ listeners.push(factory());
155
+ }
156
+ }
157
+
158
+ return listeners;
159
+ }
160
+
161
+ /**
162
+ * Remove one name from an array of listener names, in place.
163
+ */
164
+ protected removeValue(listenerIds: string[], listenerId: string): void {
165
+ const index = listenerIds.indexOf(listenerId);
166
+
167
+ if (index !== -1) {
168
+ listenerIds.splice(index, 1);
169
+ }
170
+ }
171
+ }
@@ -8,4 +8,6 @@
8
8
 
9
9
  export class EventServiceId {
10
10
  static readonly EventData = 'Valkyrja.Event.Data.EventData' as const;
11
+ static readonly ListenerCollectionContract = 'Valkyrja.Event.Collection.ListenerCollectionContract' as const;
12
+ static readonly EventDispatcherContract = 'Valkyrja.Event.Dispatcher.EventDispatcherContract' as const;
11
13
  }
@@ -0,0 +1,24 @@
1
+ /*
2
+ * This file is part of the Valkyrja Framework package.
3
+ *
4
+ * Copyright (c) 2016-present Melech Mizrachi
5
+ *
6
+ * Released under the MIT License. See LICENSE.md for details.
7
+ */
8
+
9
+ import { EventContract } from './EventContract.ts';
10
+
11
+ export interface ArgumentsCapableEventContract extends EventContract {
12
+ /**
13
+ * Set the arguments.
14
+ *
15
+ * @param args The arguments
16
+ */
17
+ setArguments(args: unknown[]): ArgumentsCapableEventContract;
18
+ }
19
+
20
+ export namespace ArgumentsCapableEventContract {
21
+ export function instanceOf(value: unknown): value is ArgumentsCapableEventContract {
22
+ return EventContract.instanceOf(value) && 'setArguments' in value;
23
+ }
24
+ }
@@ -0,0 +1,29 @@
1
+ /*
2
+ * This file is part of the Valkyrja Framework package.
3
+ *
4
+ * Copyright (c) 2016-present Melech Mizrachi
5
+ *
6
+ * Released under the MIT License. See LICENSE.md for details.
7
+ */
8
+
9
+ import { EventContract } from './EventContract.ts';
10
+
11
+ export interface DispatchCollectableEventContract extends EventContract {
12
+ /**
13
+ * Add a dispatch.
14
+ *
15
+ * @param dispatch The value that one listener returned
16
+ */
17
+ addDispatch(dispatch: unknown): void;
18
+
19
+ /**
20
+ * Get what each listener returned, in the order that the dispatcher ran them.
21
+ */
22
+ getDispatches(): unknown[];
23
+ }
24
+
25
+ export namespace DispatchCollectableEventContract {
26
+ export function instanceOf(value: unknown): value is DispatchCollectableEventContract {
27
+ return EventContract.instanceOf(value) && 'addDispatch' in value;
28
+ }
29
+ }
@@ -0,0 +1,20 @@
1
+ /*
2
+ * This file is part of the Valkyrja Framework package.
3
+ *
4
+ * Copyright (c) 2016-present Melech Mizrachi
5
+ *
6
+ * Released under the MIT License. See LICENSE.md for details.
7
+ */
8
+
9
+ export interface EventContract {
10
+ /**
11
+ * Get the identifier of the event.
12
+ */
13
+ getEventId(): string;
14
+ }
15
+
16
+ export namespace EventContract {
17
+ export function instanceOf(value: unknown): value is EventContract {
18
+ return typeof value === 'object' && value !== null && 'getEventId' in value;
19
+ }
20
+ }
@@ -0,0 +1,22 @@
1
+ /*
2
+ * This file is part of the Valkyrja Framework package.
3
+ *
4
+ * Copyright (c) 2016-present Melech Mizrachi
5
+ *
6
+ * Released under the MIT License. See LICENSE.md for details.
7
+ */
8
+
9
+ import { EventContract } from './EventContract.ts';
10
+
11
+ export interface StoppableEventContract extends EventContract {
12
+ /**
13
+ * Determine if the dispatcher stops before it runs the next listener.
14
+ */
15
+ isPropagationStopped(): boolean;
16
+ }
17
+
18
+ export namespace StoppableEventContract {
19
+ export function instanceOf(value: unknown): value is StoppableEventContract {
20
+ return EventContract.instanceOf(value) && 'isPropagationStopped' in value;
21
+ }
22
+ }
@@ -8,13 +8,47 @@
8
8
 
9
9
  import type { ContainerContract } from '../../../Container/Manager/Contract/ContainerContract.ts';
10
10
 
11
+ /** The arguments hold the event under the `event` key. */
12
+ export type ListenerHandler = (container: ContainerContract, args: Record<string, unknown>) => unknown;
13
+
14
+ export type ListenerFactory = () => ListenerContract;
15
+
11
16
  export interface ListenerContract {
17
+ /**
18
+ * Get the identifier of the event that the listener listens for.
19
+ */
12
20
  getEventId(): string;
21
+
22
+ /**
23
+ * Create a new listener with the specified event identifier.
24
+ *
25
+ * @param eventId The event identifier
26
+ */
13
27
  withEventId(eventId: string): ListenerContract;
28
+
29
+ /**
30
+ * Get the unique name that the collection files the listener under.
31
+ */
14
32
  getName(): string;
33
+
34
+ /**
35
+ * Create a new listener with the specified unique name.
36
+ *
37
+ * @param name A unique name for the listener
38
+ */
15
39
  withName(name: string): ListenerContract;
16
- getHandler(): (container: ContainerContract, args?: unknown[]) => unknown;
17
- withHandler(handler: (container: ContainerContract, args?: unknown[]) => unknown): ListenerContract;
40
+
41
+ /**
42
+ * Get the handler.
43
+ */
44
+ getHandler(): ListenerHandler;
45
+
46
+ /**
47
+ * Create a new listener with the specified handler.
48
+ *
49
+ * @param handler The handler
50
+ */
51
+ withHandler(handler: ListenerHandler): ListenerContract;
18
52
  }
19
53
 
20
54
  export namespace ListenerContract {
@@ -6,9 +6,16 @@
6
6
  * Released under the MIT License. See LICENSE.md for details.
7
7
  */
8
8
 
9
+ import type { ListenerFactory } from './Contract/ListenerContract.ts';
10
+
9
11
  export class EventData {
12
+ /**
13
+ * @param events Each event identifier with the names of its listeners, in
14
+ * the order that the collection recorded them
15
+ * @param listeners A factory for each listener, keyed by the listener's name
16
+ */
10
17
  constructor(
11
18
  public readonly events: Record<string, string[]> = {},
12
- public readonly listeners: Record<string, () => object> = {},
19
+ public readonly listeners: Record<string, ListenerFactory> = {},
13
20
  ) {}
14
21
  }
@@ -0,0 +1,41 @@
1
+ /*
2
+ * This file is part of the Valkyrja Framework package.
3
+ *
4
+ * Copyright (c) 2016-present Melech Mizrachi
5
+ *
6
+ * Released under the MIT License. See LICENSE.md for details.
7
+ */
8
+
9
+ import type { ListenerContract, ListenerHandler } from './Contract/ListenerContract.ts';
10
+
11
+ export class Listener implements ListenerContract {
12
+ constructor(
13
+ protected readonly eventId: string,
14
+ protected readonly name: string,
15
+ protected readonly handler: ListenerHandler,
16
+ ) {}
17
+
18
+ getEventId(): string {
19
+ return this.eventId;
20
+ }
21
+
22
+ withEventId(eventId: string): ListenerContract {
23
+ return new Listener(eventId, this.name, this.handler);
24
+ }
25
+
26
+ getName(): string {
27
+ return this.name;
28
+ }
29
+
30
+ withName(name: string): ListenerContract {
31
+ return new Listener(this.eventId, name, this.handler);
32
+ }
33
+
34
+ getHandler(): ListenerHandler {
35
+ return this.handler;
36
+ }
37
+
38
+ withHandler(handler: ListenerHandler): ListenerContract {
39
+ return new Listener(this.eventId, this.name, handler);
40
+ }
41
+ }
@@ -0,0 +1,54 @@
1
+ /*
2
+ * This file is part of the Valkyrja Framework package.
3
+ *
4
+ * Copyright (c) 2016-present Melech Mizrachi
5
+ *
6
+ * Released under the MIT License. See LICENSE.md for details.
7
+ */
8
+
9
+ import type { EventContract } from '../../Contract/EventContract.ts';
10
+ import type { ListenerContract } from '../../Data/Contract/ListenerContract.ts';
11
+
12
+ export interface EventDispatcherContract {
13
+ /**
14
+ * Dispatch an event.
15
+ */
16
+ dispatch(event: EventContract): EventContract;
17
+
18
+ /**
19
+ * Dispatch an event if it has listeners.
20
+ */
21
+ dispatchIfHasListeners(event: EventContract): EventContract;
22
+
23
+ /**
24
+ * Build an event from the container and dispatch it.
25
+ *
26
+ * @param eventId The binding key of the event
27
+ * @param args The arguments to pass to the event
28
+ */
29
+ dispatchById(eventId: string, args?: unknown[]): EventContract;
30
+
31
+ /**
32
+ * Build an event from the container and dispatch it if it has listeners.
33
+ *
34
+ * @param eventId The binding key of the event
35
+ * @param args The arguments to pass to the event
36
+ */
37
+ dispatchByIdIfHasListeners(eventId: string, args?: unknown[]): EventContract;
38
+
39
+ /**
40
+ * Dispatch a set of listeners.
41
+ */
42
+ dispatchListeners(event: EventContract, ...listeners: ListenerContract[]): EventContract;
43
+
44
+ /**
45
+ * Dispatch a listener.
46
+ */
47
+ dispatchListener(event: EventContract, listener: ListenerContract): EventContract;
48
+ }
49
+
50
+ export namespace EventDispatcherContract {
51
+ export function instanceOf(value: unknown): value is EventDispatcherContract {
52
+ return typeof value === 'object' && value !== null && 'dispatchListeners' in value;
53
+ }
54
+ }
@@ -0,0 +1,93 @@
1
+ /*
2
+ * This file is part of the Valkyrja Framework package.
3
+ *
4
+ * Copyright (c) 2016-present Melech Mizrachi
5
+ *
6
+ * Released under the MIT License. See LICENSE.md for details.
7
+ */
8
+
9
+ import { Container } from '../../Container/Manager/Container.ts';
10
+ import { ArgumentsCapableEventContract } from '../Contract/ArgumentsCapableEventContract.ts';
11
+ import { DispatchCollectableEventContract } from '../Contract/DispatchCollectableEventContract.ts';
12
+ import { EventContract } from '../Contract/EventContract.ts';
13
+ import { StoppableEventContract } from '../Contract/StoppableEventContract.ts';
14
+ import { ListenerCollection } from '../Collection/ListenerCollection.ts';
15
+ import { EventInvalidEventException } from '../Throwable/Exception/EventInvalidEventException.ts';
16
+
17
+ import type { ContainerContract } from '../../Container/Manager/Contract/ContainerContract.ts';
18
+ import type { ListenerCollectionContract } from '../Collection/Contract/ListenerCollectionContract.ts';
19
+ import type { ListenerContract } from '../Data/Contract/ListenerContract.ts';
20
+ import type { EventDispatcherContract } from './Contract/EventDispatcherContract.ts';
21
+
22
+ export class EventDispatcher implements EventDispatcherContract {
23
+ constructor(
24
+ protected readonly collection: ListenerCollectionContract = new ListenerCollection(),
25
+ protected readonly container: ContainerContract = new Container(),
26
+ ) {}
27
+
28
+ dispatch(event: EventContract): EventContract {
29
+ return this.dispatchListeners(event, ...this.collection.getListenersForEvent(event));
30
+ }
31
+
32
+ dispatchIfHasListeners(event: EventContract): EventContract {
33
+ if (this.collection.hasListenersForEvent(event)) {
34
+ return this.dispatch(event);
35
+ }
36
+
37
+ return event;
38
+ }
39
+
40
+ dispatchById(eventId: string, args: unknown[] = []): EventContract {
41
+ return this.dispatch(this.getEventFromId(eventId, args));
42
+ }
43
+
44
+ dispatchByIdIfHasListeners(eventId: string, args: unknown[] = []): EventContract {
45
+ if (this.collection.hasListenersForEventById(eventId)) {
46
+ return this.dispatchById(eventId, args);
47
+ }
48
+
49
+ return this.getEventFromId(eventId, args);
50
+ }
51
+
52
+ dispatchListeners(event: EventContract, ...listeners: ListenerContract[]): EventContract {
53
+ let dispatched = event;
54
+
55
+ for (const listener of listeners) {
56
+ dispatched = this.dispatchListener(dispatched, listener);
57
+
58
+ if (StoppableEventContract.instanceOf(dispatched) && dispatched.isPropagationStopped()) {
59
+ return dispatched;
60
+ }
61
+ }
62
+
63
+ return dispatched;
64
+ }
65
+
66
+ dispatchListener(event: EventContract, listener: ListenerContract): EventContract {
67
+ const handler = listener.getHandler();
68
+ const dispatch = handler(this.container, { event });
69
+
70
+ if (DispatchCollectableEventContract.instanceOf(event)) {
71
+ event.addDispatch(dispatch);
72
+ }
73
+
74
+ return event;
75
+ }
76
+
77
+ /**
78
+ * Build the event that the binding key names.
79
+ */
80
+ protected getEventFromId(eventId: string, args: unknown[]): EventContract {
81
+ const resolved: unknown = this.container.get(eventId, args);
82
+
83
+ if (!EventContract.instanceOf(resolved)) {
84
+ throw new EventInvalidEventException(eventId);
85
+ }
86
+
87
+ if (ArgumentsCapableEventContract.instanceOf(resolved)) {
88
+ return resolved.setArguments(args);
89
+ }
90
+
91
+ return resolved;
92
+ }
93
+ }
@@ -0,0 +1,38 @@
1
+ /*
2
+ * This file is part of the Valkyrja Framework package.
3
+ *
4
+ * Copyright (c) 2016-present Melech Mizrachi
5
+ *
6
+ * Released under the MIT License. See LICENSE.md for details.
7
+ */
8
+
9
+ import { EventServiceProvider } from './EventServiceProvider.ts';
10
+
11
+ import type { ApplicationContract } from '../../Application/Kernel/Contract/ApplicationContract.ts';
12
+ import type { ComponentProviderContract } from '../../Application/Provider/Contract/ComponentProviderContract.ts';
13
+ import type { ServiceProviderContract } from '../../Container/Provider/Contract/ServiceProviderContract.ts';
14
+ import type { ListenerProviderContract } from './Contract/ListenerProviderContract.ts';
15
+ import type { CliRouteProviderContract } from '../../Cli/Routing/Provider/Contract/CliRouteProviderContract.ts';
16
+ import type { HttpRouteProviderContract } from '../../Http/Routing/Provider/Contract/HttpRouteProviderContract.ts';
17
+
18
+ export class EventComponentProvider implements ComponentProviderContract {
19
+ getComponentProviders(_app: ApplicationContract): ComponentProviderContract[] {
20
+ return [];
21
+ }
22
+
23
+ getContainerProviders(_app: ApplicationContract): ServiceProviderContract[] {
24
+ return [new EventServiceProvider()];
25
+ }
26
+
27
+ getEventProviders(_app: ApplicationContract): ListenerProviderContract[] {
28
+ return [];
29
+ }
30
+
31
+ getCliProviders(_app: ApplicationContract): CliRouteProviderContract[] {
32
+ return [];
33
+ }
34
+
35
+ getHttpProviders(_app: ApplicationContract): HttpRouteProviderContract[] {
36
+ return [];
37
+ }
38
+ }
@@ -0,0 +1,69 @@
1
+ /*
2
+ * This file is part of the Valkyrja Framework package.
3
+ *
4
+ * Copyright (c) 2016-present Melech Mizrachi
5
+ *
6
+ * Released under the MIT License. See LICENSE.md for details.
7
+ */
8
+
9
+ import { ApplicationServiceId } from '../../Application/Constant/ApplicationServiceId.ts';
10
+ import { ListenerCollection } from '../Collection/ListenerCollection.ts';
11
+ import { EventServiceId } from '../Constant/EventServiceId.ts';
12
+ import { EventDispatcher } from '../Dispatcher/EventDispatcher.ts';
13
+
14
+ import type { ApplicationContract } from '../../Application/Kernel/Contract/ApplicationContract.ts';
15
+ import type { ContainerContract } from '../../Container/Manager/Contract/ContainerContract.ts';
16
+ import type { ServiceProviderContract } from '../../Container/Provider/Contract/ServiceProviderContract.ts';
17
+ import type { ListenerCollectionContract } from '../Collection/Contract/ListenerCollectionContract.ts';
18
+ import type { EventData } from '../Data/EventData.ts';
19
+
20
+ export class EventServiceProvider implements ServiceProviderContract {
21
+ publishers(): Record<string, (container: ContainerContract) => void> {
22
+ return {
23
+ [EventServiceId.EventDispatcherContract]: EventServiceProvider.publishDispatcher,
24
+ [EventServiceId.ListenerCollectionContract]: EventServiceProvider.publishListenerCollection,
25
+ [EventServiceId.EventData]: EventServiceProvider.publishData,
26
+ };
27
+ }
28
+
29
+ static publishDispatcher(this: void, container: ContainerContract): void {
30
+ container.setSingleton(
31
+ EventServiceId.EventDispatcherContract,
32
+ new EventDispatcher(
33
+ container.getSingleton<ListenerCollectionContract>(EventServiceId.ListenerCollectionContract),
34
+ container,
35
+ ),
36
+ );
37
+ }
38
+
39
+ static publishListenerCollection(this: void, container: ContainerContract): void {
40
+ const collection = new ListenerCollection();
41
+
42
+ container.setSingleton(EventServiceId.ListenerCollectionContract, collection);
43
+
44
+ const app = container.getSingleton<ApplicationContract>(ApplicationServiceId.ApplicationContract);
45
+
46
+ if (app.getDebugMode()) {
47
+ EventServiceProvider.publishData(container);
48
+
49
+ return;
50
+ }
51
+
52
+ collection.setFromData(container.getSingleton<EventData>(EventServiceId.EventData));
53
+ }
54
+
55
+ static publishData(this: void, container: ContainerContract): void {
56
+ const collection = container.getSingleton<ListenerCollectionContract>(
57
+ EventServiceId.ListenerCollectionContract,
58
+ );
59
+ const app = container.getSingleton<ApplicationContract>(ApplicationServiceId.ApplicationContract);
60
+
61
+ for (const provider of app.getEventProviders()) {
62
+ for (const listener of provider.getListeners()) {
63
+ collection.addListener(listener);
64
+ }
65
+ }
66
+
67
+ container.setSingleton(EventServiceId.EventData, collection.getData());
68
+ }
69
+ }
@@ -6,7 +6,6 @@
6
6
  * Released under the MIT License. See LICENSE.md for details.
7
7
  */
8
8
 
9
- export enum InvalidReferenceMode {
10
- NEW_INSTANCE_OR_THROW_EXCEPTION,
11
- THROW_EXCEPTION,
12
- }
9
+ import { type ValkyrjaThrowable } from '../../../Throwable/Contract/ValkyrjaThrowable.ts';
10
+
11
+ export type EventThrowable = ValkyrjaThrowable;
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This file is part of the Valkyrja Framework package.
3
+ *
4
+ * Copyright (c) 2016-present Melech Mizrachi
5
+ *
6
+ * Released under the MIT License. See LICENSE.md for details.
7
+ */
8
+
9
+ import { ValkyrjaInvalidArgumentException } from '../../../../Throwable/Exception/Abstract/ValkyrjaInvalidArgumentException.ts';
10
+
11
+ import { type EventThrowable } from '../../Contract/EventThrowable.ts';
12
+
13
+ export abstract class EventInvalidArgumentException
14
+ extends ValkyrjaInvalidArgumentException
15
+ implements EventThrowable {}
@@ -0,0 +1,13 @@
1
+ /*
2
+ * This file is part of the Valkyrja Framework package.
3
+ *
4
+ * Copyright (c) 2016-present Melech Mizrachi
5
+ *
6
+ * Released under the MIT License. See LICENSE.md for details.
7
+ */
8
+
9
+ import { ValkyrjaRuntimeException } from '../../../../Throwable/Exception/Abstract/ValkyrjaRuntimeException.ts';
10
+
11
+ import { type EventThrowable } from '../../Contract/EventThrowable.ts';
12
+
13
+ export abstract class EventRuntimeException extends ValkyrjaRuntimeException implements EventThrowable {}
@@ -0,0 +1,19 @@
1
+ /*
2
+ * This file is part of the Valkyrja Framework package.
3
+ *
4
+ * Copyright (c) 2016-present Melech Mizrachi
5
+ *
6
+ * Released under the MIT License. See LICENSE.md for details.
7
+ */
8
+
9
+ import { EventInvalidArgumentException } from './Abstract/EventInvalidArgumentException.ts';
10
+
11
+ export class EventInvalidEventException extends EventInvalidArgumentException {
12
+ constructor(
13
+ public readonly id: string,
14
+ options?: ErrorOptions,
15
+ ) {
16
+ super(`Service with \`${id}\` is not an event`, options);
17
+ this.name = 'EventInvalidEventException';
18
+ }
19
+ }