@h3ravel/events 0.1.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 h3ravel
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,43 @@
1
+ <div align="center">
2
+ <a href="https://h3ravel.toneflix.net" target="_blank">
3
+ <img src="https://raw.githubusercontent.com/h3ravel/assets/refs/heads/main/logo-full.svg" width="200" alt="H3ravel Logo">
4
+ </a>
5
+ <h1 align="center"><a href="https://h3ravel.toneflix.net/events">H3ravel Events</a></h1>
6
+
7
+ [![Framework][ix]][lx]
8
+ [![Events Package Version][i1]][l1]
9
+ [![Downloads][d1]][d1]
10
+ [![Tests][tei]][tel]
11
+ [![License][lini]][linl]
12
+
13
+ </div>
14
+
15
+ # About H3ravel/events
16
+
17
+ This is the events handling system for the [H3ravel](https://h3ravel.toneflix.net) framework.
18
+
19
+ ## Contributing
20
+
21
+ Thank you for considering contributing to the H3ravel framework! The [Contribution Guide](https://h3ravel.toneflix.net/contributing) can be found in the H3ravel documentation and will provide you with all the information you need to get started.
22
+
23
+ ## Code of Conduct
24
+
25
+ In order to ensure that the H3ravel community is welcoming to all, please review and abide by the [Code of Conduct](#).
26
+
27
+ ## Security Vulnerabilities
28
+
29
+ If you discover a security vulnerability within H3ravel, please send an e-mail to Legacy via hamzas.legacy@toneflix.ng. All security vulnerabilities will be promptly addressed.
30
+
31
+ ## License
32
+
33
+ The H3ravel framework is open-sourced software licensed under the [MIT license](LICENSE).
34
+
35
+ [ix]: https://img.shields.io/npm/v/%40h3ravel%2Fcore?style=flat-square&label=Framework&color=%230970ce
36
+ [lx]: https://www.npmjs.com/package/@h3ravel/core
37
+ [i1]: https://img.shields.io/npm/v/%40h3ravel%2Fevents?style=flat-square&label=@h3ravel/events&color=%230970ce
38
+ [l1]: https://www.npmjs.com/package/@h3ravel/events
39
+ [d1]: https://img.shields.io/npm/dt/%40h3ravel%2Fevents?style=flat-square&label=Downloads&link=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2F%40h3ravel%2Fevents
40
+ [linl]: https://github.com/h3ravel/framework/blob/main/LICENSE
41
+ [lini]: https://img.shields.io/github/license/h3ravel/framework
42
+ [tel]: https://github.com/h3ravel/framework/actions/workflows/test.yml
43
+ [tei]: https://github.com/h3ravel/framework/actions/workflows/test.yml/badge.svg
package/dist/index.cjs ADDED
@@ -0,0 +1,346 @@
1
+ let __h3ravel_support = require("@h3ravel/support");
2
+ let __h3ravel_core = require("@h3ravel/core");
3
+ let __h3ravel_contracts = require("@h3ravel/contracts");
4
+
5
+ //#region src/Dispatcher.ts
6
+ var Dispatcher = class {
7
+ /**
8
+ * The IoC container instance.
9
+ */
10
+ container;
11
+ /**
12
+ * The registered event listeners.
13
+ */
14
+ listeners = {};
15
+ /**
16
+ * The wildcard listeners.
17
+ */
18
+ wildcards = {};
19
+ /**
20
+ * The cached wildcard listeners.
21
+ */
22
+ wildcardsCache = {};
23
+ /**
24
+ * The queue resolver instance.
25
+ */
26
+ queueResolver;
27
+ /**
28
+ * The database transaction manager resolver instance.
29
+ */
30
+ transactionManagerResolver;
31
+ /**
32
+ * The currently deferred events.
33
+ */
34
+ deferredEvents = {};
35
+ /**
36
+ * Indicates if events should be deferred.
37
+ */
38
+ deferringEvents = false;
39
+ /**
40
+ * The specific events to defer (null means defer all events).
41
+ */
42
+ eventsToDefer;
43
+ /**
44
+ * Create a new event dispatcher instance.
45
+ */
46
+ constructor(container) {
47
+ this.container = container ?? new __h3ravel_core.Container();
48
+ }
49
+ /**
50
+ * Register an event listener with the dispatcher.
51
+ *
52
+ * @param events
53
+ * @param listener
54
+ */
55
+ listen(events, listener) {
56
+ for (const event of __h3ravel_support.Arr.wrap(events)) if (typeof event === "string" && listener) if (event.includes("*")) this.setupWildcardListen(event, listener);
57
+ else this.listeners[event].push(listener);
58
+ else if (typeof event === "function") event(listener);
59
+ else if (typeof listener === "function") listener();
60
+ }
61
+ /**
62
+ * Setup a wildcard listener callback.
63
+ *
64
+ * @param event
65
+ * @param listener
66
+ */
67
+ setupWildcardListen(event, listener) {
68
+ this.wildcards[event].push(listener);
69
+ this.wildcardsCache = {};
70
+ }
71
+ /**
72
+ * Determine if a given event has listeners.
73
+ *
74
+ * @param eventName
75
+ * @return bool
76
+ */
77
+ hasListeners(eventName) {
78
+ return this.listeners[eventName] || this.wildcards[eventName] || this.hasWildcardListeners(eventName);
79
+ }
80
+ /**
81
+ * Determine if the given event has any wildcard listeners.
82
+ *
83
+ * @param eventName
84
+ */
85
+ hasWildcardListeners(eventName) {
86
+ for (const [key] of Object.entries(this.wildcards)) if (__h3ravel_support.Str.is(key, eventName)) return true;
87
+ return false;
88
+ }
89
+ /**
90
+ * Register an event and payload to be fired later.
91
+ *
92
+ * @para event
93
+ * @param payload
94
+ * @return void
95
+ */
96
+ push(event, payload = []) {
97
+ this.listen(event + "_pushed", () => {
98
+ this.dispatch(event, payload);
99
+ });
100
+ }
101
+ /**
102
+ * Flush a set of pushed events.
103
+ *
104
+ * @param event
105
+ */
106
+ flush(event) {
107
+ this.dispatch(event + "_pushed");
108
+ }
109
+ /**
110
+ * Resolve the subscriber instance.
111
+ *
112
+ * @param subscriber
113
+ */
114
+ resolveSubscriber(subscriber) {
115
+ if (typeof subscriber === "string") return this.container.make(subscriber);
116
+ return subscriber;
117
+ }
118
+ /**
119
+ * Fire an event until the first non-null response is returned.
120
+ *
121
+ * @param event
122
+ * @param mixed payload
123
+ * @return mixed
124
+ */
125
+ until(event, payload = {}) {
126
+ return this.dispatch(event, payload, true);
127
+ }
128
+ /**
129
+ * Fire an event and call the listeners.
130
+ *
131
+ * @param event
132
+ * @param payload
133
+ * @param halt
134
+ */
135
+ dispatch(event, _payload = [], _halt = false) {}
136
+ /**
137
+ * Remove a set of listeners from the dispatcher.
138
+ *
139
+ * @param event
140
+ */
141
+ forget(event) {
142
+ if (event.includes("*")) delete this.wildcards[event];
143
+ else delete this.listeners[event];
144
+ for (const [key] of Object.entries(this.wildcardsCache)) if (__h3ravel_support.Str.is(event, key)) delete this.wildcardsCache[key];
145
+ }
146
+ /**
147
+ * Forget all of the pushed listeners.
148
+ *
149
+ * @return void
150
+ */
151
+ forgetPushed() {
152
+ for (const [key] of Object.entries(this.listeners)) if (key.endsWith("_pushed")) this.forget(key);
153
+ }
154
+ /**
155
+ * Get the queue implementation from the resolver.
156
+ */
157
+ resolveQueue() {
158
+ return this.queueResolver?.();
159
+ }
160
+ /**
161
+ * Set the queue resolver implementation.
162
+ *
163
+ * @param callable $resolver
164
+ * @return this
165
+ */
166
+ setQueueResolver(resolver) {
167
+ this.queueResolver = resolver;
168
+ return this;
169
+ }
170
+ /**
171
+ * Get the database transaction manager implementation from the resolver.
172
+ */
173
+ resolveTransactionManager() {
174
+ return this.transactionManagerResolver?.();
175
+ }
176
+ /**
177
+ * Set the database transaction manager resolver implementation.
178
+ *
179
+ * @param resolver
180
+ */
181
+ setTransactionManagerResolver(resolver) {
182
+ this.transactionManagerResolver = resolver;
183
+ return this;
184
+ }
185
+ /**
186
+ * Execute the given callback while deferring events, then dispatch all deferred events.
187
+ *
188
+ * @param callback
189
+ * @param events
190
+ */
191
+ defer(callback, events) {
192
+ const wasDeferring = this.deferringEvents;
193
+ const previousDeferredEvents = this.deferredEvents;
194
+ const previousEventsToDefer = this.eventsToDefer;
195
+ this.deferringEvents = true;
196
+ this.deferredEvents = {};
197
+ this.eventsToDefer = events;
198
+ try {
199
+ const result = callback();
200
+ this.deferringEvents = false;
201
+ for (const args of Object.entries(this.deferredEvents)) this.dispatch(...args);
202
+ return result;
203
+ } finally {
204
+ this.deferringEvents = wasDeferring;
205
+ this.deferredEvents = previousDeferredEvents;
206
+ this.eventsToDefer = previousEventsToDefer;
207
+ }
208
+ }
209
+ /**
210
+ * Determine if the given event should be deferred.
211
+ *
212
+ * @param event
213
+ */
214
+ shouldDeferEvent(event) {
215
+ return this.deferringEvents && (this.eventsToDefer === null || this.eventsToDefer?.includes(event));
216
+ }
217
+ /**
218
+ * Gets the raw, unprepared listeners.
219
+ *
220
+ * @return array
221
+ */
222
+ getRawListeners() {
223
+ return this.listeners;
224
+ }
225
+ };
226
+
227
+ //#endregion
228
+ //#region src/Providers/EventsServiceProvider.ts
229
+ /**
230
+ * Events handling.
231
+ */
232
+ var EventsServiceProvider = class extends __h3ravel_core.ServiceProvider {
233
+ static priority = 992;
234
+ static order = "before:RouteServiceProvider";
235
+ register() {
236
+ this.app.singleton("app.events", (app) => {
237
+ return new Dispatcher(app).setQueueResolver(() => {}).setTransactionManagerResolver(function() {});
238
+ });
239
+ this.app.alias([
240
+ ["events", "app.events"],
241
+ [Dispatcher, "app.events"],
242
+ [__h3ravel_contracts.IDispatcher, "app.events"]
243
+ ]);
244
+ }
245
+ };
246
+
247
+ //#endregion
248
+ //#region src/QueuedListenerCalller.ts
249
+ var QueuedListenerCalller = class {
250
+ /**
251
+ * The underlying queue job instance.
252
+ */
253
+ job;
254
+ /**
255
+ * The listener class.
256
+ */
257
+ className;
258
+ /**
259
+ * The listener method.
260
+ */
261
+ method;
262
+ /**
263
+ * The data to be passed to the listener.
264
+ */
265
+ data;
266
+ /**
267
+ * The number of times the job may be attempted.
268
+ */
269
+ tries;
270
+ /**
271
+ * The maximum number of exceptions allowed, regardless of attempts.
272
+ */
273
+ maxExceptions;
274
+ /**
275
+ * The number of seconds to wait before retrying a job that encountered an uncaught exception.
276
+ */
277
+ backoff;
278
+ /**
279
+ * The timestamp indicating when the job should timeout.
280
+ */
281
+ retryUntil;
282
+ /**
283
+ * The number of seconds the job can run before timing out.
284
+ */
285
+ timeout;
286
+ /**
287
+ * Indicates if the job should fail if the timeout is exceeded.
288
+ */
289
+ failOnTimeout = false;
290
+ /**
291
+ * Indicates if the job should be encrypted.
292
+ */
293
+ shouldBeEncrypted = false;
294
+ /**
295
+ * Create a new job instance.
296
+ *
297
+ * @param class
298
+ * @param method
299
+ * @param data
300
+ */
301
+ constructor(className, method, data) {
302
+ this.data = data;
303
+ this.className = className;
304
+ this.method = method;
305
+ }
306
+ /**
307
+ * Handle the queued job.
308
+ */
309
+ handle(_container) {}
310
+ /**
311
+ * Set the job instance of the given class if necessary.
312
+ *
313
+ * @param job
314
+ * @param instance
315
+ */
316
+ setJobInstanceIfNecessary(job, instance) {
317
+ return {};
318
+ }
319
+ /**
320
+ * Call the failed method on the job instance.
321
+ *
322
+ * The event instance and the exception will be passed.
323
+ *
324
+ * @param e
325
+ */
326
+ failed(_e) {}
327
+ /**
328
+ * Unserialize the data if needed.
329
+ *
330
+ * @return void
331
+ */
332
+ prepareData() {}
333
+ /**
334
+ * Get the display name for the queued job.
335
+ *
336
+ * @return string
337
+ */
338
+ displayName() {
339
+ return this.className;
340
+ }
341
+ };
342
+
343
+ //#endregion
344
+ exports.Dispatcher = Dispatcher;
345
+ exports.EventsServiceProvider = EventsServiceProvider;
346
+ exports.QueuedListenerCalller = QueuedListenerCalller;
@@ -0,0 +1,268 @@
1
+ /// <reference path="./app.globals.d.ts" />
2
+ import { Container, ServiceProvider } from "@h3ravel/core";
3
+ import { IJob } from "@h3ravel/contracts";
4
+
5
+ //#region src/Contracts/EventsContract.d.ts
6
+ type ListenerClassConstructor = (new (...args: any) => any) & {
7
+ subscribe?(...args: any[]): any;
8
+ };
9
+ type AppEvent = (...args: any[]) => any;
10
+ type AppListener = (...args: any[]) => any;
11
+ //#endregion
12
+ //#region src/Dispatcher.d.ts
13
+ declare class Dispatcher {
14
+ /**
15
+ * The IoC container instance.
16
+ */
17
+ protected container: Container;
18
+ /**
19
+ * The registered event listeners.
20
+ */
21
+ protected listeners: Record<string, any[]>;
22
+ /**
23
+ * The wildcard listeners.
24
+ */
25
+ protected wildcards: Record<string, any[]>;
26
+ /**
27
+ * The cached wildcard listeners.
28
+ */
29
+ protected wildcardsCache: Record<string, any[]>;
30
+ /**
31
+ * The queue resolver instance.
32
+ */
33
+ protected queueResolver?: (...a: any[]) => any;
34
+ /**
35
+ * The database transaction manager resolver instance.
36
+ */
37
+ protected transactionManagerResolver?: (...a: any[]) => any;
38
+ /**
39
+ * The currently deferred events.
40
+ */
41
+ protected deferredEvents: Record<string, any[]>;
42
+ /**
43
+ * Indicates if events should be deferred.
44
+ */
45
+ protected deferringEvents: boolean;
46
+ /**
47
+ * The specific events to defer (null means defer all events).
48
+ */
49
+ protected eventsToDefer?: AppEvent[];
50
+ /**
51
+ * Create a new event dispatcher instance.
52
+ */
53
+ constructor(container: Container);
54
+ /**
55
+ * Register an event listener with the dispatcher.
56
+ *
57
+ * @param events
58
+ * @param listener
59
+ */
60
+ listen(events: AppEvent | AppEvent[] | string | string[], listener?: AppListener | AppListener[] | string | string[]): void;
61
+ /**
62
+ * Setup a wildcard listener callback.
63
+ *
64
+ * @param event
65
+ * @param listener
66
+ */
67
+ protected setupWildcardListen(event: string, listener: AppListener | AppListener[] | string | string[]): void;
68
+ /**
69
+ * Determine if a given event has listeners.
70
+ *
71
+ * @param eventName
72
+ * @return bool
73
+ */
74
+ hasListeners(eventName: string): any[];
75
+ /**
76
+ * Determine if the given event has any wildcard listeners.
77
+ *
78
+ * @param eventName
79
+ */
80
+ hasWildcardListeners(eventName: string): boolean;
81
+ /**
82
+ * Register an event and payload to be fired later.
83
+ *
84
+ * @para event
85
+ * @param payload
86
+ * @return void
87
+ */
88
+ push(event: string, payload?: Record<string, any> | any[]): void;
89
+ /**
90
+ * Flush a set of pushed events.
91
+ *
92
+ * @param event
93
+ */
94
+ flush(event: string): void;
95
+ /**
96
+ * Resolve the subscriber instance.
97
+ *
98
+ * @param subscriber
99
+ */
100
+ protected resolveSubscriber(subscriber: string | ListenerClassConstructor): any;
101
+ /**
102
+ * Fire an event until the first non-null response is returned.
103
+ *
104
+ * @param event
105
+ * @param mixed payload
106
+ * @return mixed
107
+ */
108
+ until(event: AppEvent, payload?: {}): void;
109
+ /**
110
+ * Fire an event and call the listeners.
111
+ *
112
+ * @param event
113
+ * @param payload
114
+ * @param halt
115
+ */
116
+ dispatch(event: Record<string, any> | string, _payload?: Record<string, any> | any[], _halt?: boolean): void;
117
+ /**
118
+ * Remove a set of listeners from the dispatcher.
119
+ *
120
+ * @param event
121
+ */
122
+ forget(event: string): void;
123
+ /**
124
+ * Forget all of the pushed listeners.
125
+ *
126
+ * @return void
127
+ */
128
+ forgetPushed(): void;
129
+ /**
130
+ * Get the queue implementation from the resolver.
131
+ */
132
+ protected resolveQueue(): any;
133
+ /**
134
+ * Set the queue resolver implementation.
135
+ *
136
+ * @param callable $resolver
137
+ * @return this
138
+ */
139
+ setQueueResolver(resolver: (...a: any[]) => any): this;
140
+ /**
141
+ * Get the database transaction manager implementation from the resolver.
142
+ */
143
+ protected resolveTransactionManager(): any;
144
+ /**
145
+ * Set the database transaction manager resolver implementation.
146
+ *
147
+ * @param resolver
148
+ */
149
+ setTransactionManagerResolver(resolver: (...a: any[]) => any): this;
150
+ /**
151
+ * Execute the given callback while deferring events, then dispatch all deferred events.
152
+ *
153
+ * @param callback
154
+ * @param events
155
+ */
156
+ defer(callback: (...a: any[]) => any, events: AppEvent[]): any;
157
+ /**
158
+ * Determine if the given event should be deferred.
159
+ *
160
+ * @param event
161
+ */
162
+ protected shouldDeferEvent(event: AppEvent): boolean | undefined;
163
+ /**
164
+ * Gets the raw, unprepared listeners.
165
+ *
166
+ * @return array
167
+ */
168
+ getRawListeners(): Record<string, any[]>;
169
+ }
170
+ //#endregion
171
+ //#region src/Providers/EventsServiceProvider.d.ts
172
+ /**
173
+ * Events handling.
174
+ */
175
+ declare class EventsServiceProvider extends ServiceProvider {
176
+ static priority: number;
177
+ static order: string;
178
+ register(): void;
179
+ }
180
+ //#endregion
181
+ //#region src/QueuedListenerCalller.d.ts
182
+ declare class QueuedListenerCalller {
183
+ /**
184
+ * The underlying queue job instance.
185
+ */
186
+ job: IJob;
187
+ /**
188
+ * The listener class.
189
+ */
190
+ className: ListenerClassConstructor;
191
+ /**
192
+ * The listener method.
193
+ */
194
+ method: string;
195
+ /**
196
+ * The data to be passed to the listener.
197
+ */
198
+ data: Record<string, any>;
199
+ /**
200
+ * The number of times the job may be attempted.
201
+ */
202
+ tries?: number;
203
+ /**
204
+ * The maximum number of exceptions allowed, regardless of attempts.
205
+ */
206
+ maxExceptions?: number;
207
+ /**
208
+ * The number of seconds to wait before retrying a job that encountered an uncaught exception.
209
+ */
210
+ backoff?: number;
211
+ /**
212
+ * The timestamp indicating when the job should timeout.
213
+ */
214
+ retryUntil?: number;
215
+ /**
216
+ * The number of seconds the job can run before timing out.
217
+ */
218
+ timeout?: number;
219
+ /**
220
+ * Indicates if the job should fail if the timeout is exceeded.
221
+ */
222
+ failOnTimeout?: boolean;
223
+ /**
224
+ * Indicates if the job should be encrypted.
225
+ */
226
+ shouldBeEncrypted?: boolean;
227
+ /**
228
+ * Create a new job instance.
229
+ *
230
+ * @param class
231
+ * @param method
232
+ * @param data
233
+ */
234
+ constructor(className: ListenerClassConstructor, method: string, data: Record<string, any>);
235
+ /**
236
+ * Handle the queued job.
237
+ */
238
+ handle(_container: Container): void;
239
+ /**
240
+ * Set the job instance of the given class if necessary.
241
+ *
242
+ * @param job
243
+ * @param instance
244
+ */
245
+ protected setJobInstanceIfNecessary(job: IJob, instance: any): {};
246
+ /**
247
+ * Call the failed method on the job instance.
248
+ *
249
+ * The event instance and the exception will be passed.
250
+ *
251
+ * @param e
252
+ */
253
+ failed(_e: Error): void;
254
+ /**
255
+ * Unserialize the data if needed.
256
+ *
257
+ * @return void
258
+ */
259
+ protected prepareData(): void;
260
+ /**
261
+ * Get the display name for the queued job.
262
+ *
263
+ * @return string
264
+ */
265
+ displayName(): ListenerClassConstructor;
266
+ }
267
+ //#endregion
268
+ export { AppEvent, AppListener, Dispatcher, EventsServiceProvider, ListenerClassConstructor, QueuedListenerCalller };
package/dist/index.js ADDED
@@ -0,0 +1,344 @@
1
+ import { Arr, Str } from "@h3ravel/support";
2
+ import { Container, ServiceProvider } from "@h3ravel/core";
3
+ import { IDispatcher } from "@h3ravel/contracts";
4
+
5
+ //#region src/Dispatcher.ts
6
+ var Dispatcher = class {
7
+ /**
8
+ * The IoC container instance.
9
+ */
10
+ container;
11
+ /**
12
+ * The registered event listeners.
13
+ */
14
+ listeners = {};
15
+ /**
16
+ * The wildcard listeners.
17
+ */
18
+ wildcards = {};
19
+ /**
20
+ * The cached wildcard listeners.
21
+ */
22
+ wildcardsCache = {};
23
+ /**
24
+ * The queue resolver instance.
25
+ */
26
+ queueResolver;
27
+ /**
28
+ * The database transaction manager resolver instance.
29
+ */
30
+ transactionManagerResolver;
31
+ /**
32
+ * The currently deferred events.
33
+ */
34
+ deferredEvents = {};
35
+ /**
36
+ * Indicates if events should be deferred.
37
+ */
38
+ deferringEvents = false;
39
+ /**
40
+ * The specific events to defer (null means defer all events).
41
+ */
42
+ eventsToDefer;
43
+ /**
44
+ * Create a new event dispatcher instance.
45
+ */
46
+ constructor(container) {
47
+ this.container = container ?? new Container();
48
+ }
49
+ /**
50
+ * Register an event listener with the dispatcher.
51
+ *
52
+ * @param events
53
+ * @param listener
54
+ */
55
+ listen(events, listener) {
56
+ for (const event of Arr.wrap(events)) if (typeof event === "string" && listener) if (event.includes("*")) this.setupWildcardListen(event, listener);
57
+ else this.listeners[event].push(listener);
58
+ else if (typeof event === "function") event(listener);
59
+ else if (typeof listener === "function") listener();
60
+ }
61
+ /**
62
+ * Setup a wildcard listener callback.
63
+ *
64
+ * @param event
65
+ * @param listener
66
+ */
67
+ setupWildcardListen(event, listener) {
68
+ this.wildcards[event].push(listener);
69
+ this.wildcardsCache = {};
70
+ }
71
+ /**
72
+ * Determine if a given event has listeners.
73
+ *
74
+ * @param eventName
75
+ * @return bool
76
+ */
77
+ hasListeners(eventName) {
78
+ return this.listeners[eventName] || this.wildcards[eventName] || this.hasWildcardListeners(eventName);
79
+ }
80
+ /**
81
+ * Determine if the given event has any wildcard listeners.
82
+ *
83
+ * @param eventName
84
+ */
85
+ hasWildcardListeners(eventName) {
86
+ for (const [key] of Object.entries(this.wildcards)) if (Str.is(key, eventName)) return true;
87
+ return false;
88
+ }
89
+ /**
90
+ * Register an event and payload to be fired later.
91
+ *
92
+ * @para event
93
+ * @param payload
94
+ * @return void
95
+ */
96
+ push(event, payload = []) {
97
+ this.listen(event + "_pushed", () => {
98
+ this.dispatch(event, payload);
99
+ });
100
+ }
101
+ /**
102
+ * Flush a set of pushed events.
103
+ *
104
+ * @param event
105
+ */
106
+ flush(event) {
107
+ this.dispatch(event + "_pushed");
108
+ }
109
+ /**
110
+ * Resolve the subscriber instance.
111
+ *
112
+ * @param subscriber
113
+ */
114
+ resolveSubscriber(subscriber) {
115
+ if (typeof subscriber === "string") return this.container.make(subscriber);
116
+ return subscriber;
117
+ }
118
+ /**
119
+ * Fire an event until the first non-null response is returned.
120
+ *
121
+ * @param event
122
+ * @param mixed payload
123
+ * @return mixed
124
+ */
125
+ until(event, payload = {}) {
126
+ return this.dispatch(event, payload, true);
127
+ }
128
+ /**
129
+ * Fire an event and call the listeners.
130
+ *
131
+ * @param event
132
+ * @param payload
133
+ * @param halt
134
+ */
135
+ dispatch(event, _payload = [], _halt = false) {}
136
+ /**
137
+ * Remove a set of listeners from the dispatcher.
138
+ *
139
+ * @param event
140
+ */
141
+ forget(event) {
142
+ if (event.includes("*")) delete this.wildcards[event];
143
+ else delete this.listeners[event];
144
+ for (const [key] of Object.entries(this.wildcardsCache)) if (Str.is(event, key)) delete this.wildcardsCache[key];
145
+ }
146
+ /**
147
+ * Forget all of the pushed listeners.
148
+ *
149
+ * @return void
150
+ */
151
+ forgetPushed() {
152
+ for (const [key] of Object.entries(this.listeners)) if (key.endsWith("_pushed")) this.forget(key);
153
+ }
154
+ /**
155
+ * Get the queue implementation from the resolver.
156
+ */
157
+ resolveQueue() {
158
+ return this.queueResolver?.();
159
+ }
160
+ /**
161
+ * Set the queue resolver implementation.
162
+ *
163
+ * @param callable $resolver
164
+ * @return this
165
+ */
166
+ setQueueResolver(resolver) {
167
+ this.queueResolver = resolver;
168
+ return this;
169
+ }
170
+ /**
171
+ * Get the database transaction manager implementation from the resolver.
172
+ */
173
+ resolveTransactionManager() {
174
+ return this.transactionManagerResolver?.();
175
+ }
176
+ /**
177
+ * Set the database transaction manager resolver implementation.
178
+ *
179
+ * @param resolver
180
+ */
181
+ setTransactionManagerResolver(resolver) {
182
+ this.transactionManagerResolver = resolver;
183
+ return this;
184
+ }
185
+ /**
186
+ * Execute the given callback while deferring events, then dispatch all deferred events.
187
+ *
188
+ * @param callback
189
+ * @param events
190
+ */
191
+ defer(callback, events) {
192
+ const wasDeferring = this.deferringEvents;
193
+ const previousDeferredEvents = this.deferredEvents;
194
+ const previousEventsToDefer = this.eventsToDefer;
195
+ this.deferringEvents = true;
196
+ this.deferredEvents = {};
197
+ this.eventsToDefer = events;
198
+ try {
199
+ const result = callback();
200
+ this.deferringEvents = false;
201
+ for (const args of Object.entries(this.deferredEvents)) this.dispatch(...args);
202
+ return result;
203
+ } finally {
204
+ this.deferringEvents = wasDeferring;
205
+ this.deferredEvents = previousDeferredEvents;
206
+ this.eventsToDefer = previousEventsToDefer;
207
+ }
208
+ }
209
+ /**
210
+ * Determine if the given event should be deferred.
211
+ *
212
+ * @param event
213
+ */
214
+ shouldDeferEvent(event) {
215
+ return this.deferringEvents && (this.eventsToDefer === null || this.eventsToDefer?.includes(event));
216
+ }
217
+ /**
218
+ * Gets the raw, unprepared listeners.
219
+ *
220
+ * @return array
221
+ */
222
+ getRawListeners() {
223
+ return this.listeners;
224
+ }
225
+ };
226
+
227
+ //#endregion
228
+ //#region src/Providers/EventsServiceProvider.ts
229
+ /**
230
+ * Events handling.
231
+ */
232
+ var EventsServiceProvider = class extends ServiceProvider {
233
+ static priority = 992;
234
+ static order = "before:RouteServiceProvider";
235
+ register() {
236
+ this.app.singleton("app.events", (app) => {
237
+ return new Dispatcher(app).setQueueResolver(() => {}).setTransactionManagerResolver(function() {});
238
+ });
239
+ this.app.alias([
240
+ ["events", "app.events"],
241
+ [Dispatcher, "app.events"],
242
+ [IDispatcher, "app.events"]
243
+ ]);
244
+ }
245
+ };
246
+
247
+ //#endregion
248
+ //#region src/QueuedListenerCalller.ts
249
+ var QueuedListenerCalller = class {
250
+ /**
251
+ * The underlying queue job instance.
252
+ */
253
+ job;
254
+ /**
255
+ * The listener class.
256
+ */
257
+ className;
258
+ /**
259
+ * The listener method.
260
+ */
261
+ method;
262
+ /**
263
+ * The data to be passed to the listener.
264
+ */
265
+ data;
266
+ /**
267
+ * The number of times the job may be attempted.
268
+ */
269
+ tries;
270
+ /**
271
+ * The maximum number of exceptions allowed, regardless of attempts.
272
+ */
273
+ maxExceptions;
274
+ /**
275
+ * The number of seconds to wait before retrying a job that encountered an uncaught exception.
276
+ */
277
+ backoff;
278
+ /**
279
+ * The timestamp indicating when the job should timeout.
280
+ */
281
+ retryUntil;
282
+ /**
283
+ * The number of seconds the job can run before timing out.
284
+ */
285
+ timeout;
286
+ /**
287
+ * Indicates if the job should fail if the timeout is exceeded.
288
+ */
289
+ failOnTimeout = false;
290
+ /**
291
+ * Indicates if the job should be encrypted.
292
+ */
293
+ shouldBeEncrypted = false;
294
+ /**
295
+ * Create a new job instance.
296
+ *
297
+ * @param class
298
+ * @param method
299
+ * @param data
300
+ */
301
+ constructor(className, method, data) {
302
+ this.data = data;
303
+ this.className = className;
304
+ this.method = method;
305
+ }
306
+ /**
307
+ * Handle the queued job.
308
+ */
309
+ handle(_container) {}
310
+ /**
311
+ * Set the job instance of the given class if necessary.
312
+ *
313
+ * @param job
314
+ * @param instance
315
+ */
316
+ setJobInstanceIfNecessary(job, instance) {
317
+ return {};
318
+ }
319
+ /**
320
+ * Call the failed method on the job instance.
321
+ *
322
+ * The event instance and the exception will be passed.
323
+ *
324
+ * @param e
325
+ */
326
+ failed(_e) {}
327
+ /**
328
+ * Unserialize the data if needed.
329
+ *
330
+ * @return void
331
+ */
332
+ prepareData() {}
333
+ /**
334
+ * Get the display name for the queued job.
335
+ *
336
+ * @return string
337
+ */
338
+ displayName() {
339
+ return this.className;
340
+ }
341
+ };
342
+
343
+ //#endregion
344
+ export { Dispatcher, EventsServiceProvider, QueuedListenerCalller };
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@h3ravel/events",
3
+ "version": "0.1.0-alpha.1",
4
+ "description": "Events package for H3ravel.",
5
+ "h3ravel": {
6
+ "providers": [
7
+ "EventsServiceProvider"
8
+ ]
9
+ },
10
+ "type": "module",
11
+ "main": "./dist/index.cjs",
12
+ "types": "./dist/index.d.ts",
13
+ "module": "./dist/index.js",
14
+ "exports": {
15
+ ".": {
16
+ "import": "./dist/index.js",
17
+ "require": "./dist/index.cjs"
18
+ },
19
+ "./*": "./*"
20
+ },
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "homepage": "https://h3ravel.toneflix.net",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/h3ravel/framework.git",
31
+ "directory": "packages/events"
32
+ },
33
+ "keywords": [
34
+ "h3ravel",
35
+ "modern",
36
+ "web",
37
+ "H3",
38
+ "events",
39
+ "framework",
40
+ "nodejs",
41
+ "typescript",
42
+ "laravel"
43
+ ],
44
+ "peerDependencies": {
45
+ "@h3ravel/core": "^1.22.0-alpha.1"
46
+ },
47
+ "devDependencies": {
48
+ "typescript": "^5.4.0"
49
+ },
50
+ "scripts": {
51
+ "build": "tsdown --config-loader unconfig",
52
+ "dev": "tsx watch src/index.ts",
53
+ "start": "node dist/index.js",
54
+ "lint": "eslint . --ext .ts",
55
+ "test": "jest --passWithNoTests",
56
+ "version-patch": "pnpm version patch"
57
+ }
58
+ }