@homer0/events-hub 3.0.14 → 5.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.
package/dist/index.js CHANGED
@@ -1,309 +1,2 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
-
20
- // src/index.ts
21
- var index_exports = {};
22
- __export(index_exports, {
23
- EventsHub: () => EventsHub,
24
- eventsHub: () => eventsHub
25
- });
26
- module.exports = __toCommonJS(index_exports);
27
- var EventsHub = class {
28
- /**
29
- * A dictionary of the events and their listeners.
30
- */
31
- events = {};
32
- /**
33
- * A dictionary of wrappers that were created for "one time subscriptions". This is
34
- * used by the {@link EventsHub.off}: if it doesn't find the subscriber as it is, it
35
- * will look for a wrapper and remove it.
36
- */
37
- onceWrappers = {};
38
- /**
39
- * Gets all the listeners for a specific event.
40
- * The list is returned by reference, so it can be modified once obtained.
41
- *
42
- * @param event The name of the event.
43
- */
44
- getSubscribers(event) {
45
- if (!this.events[event]) {
46
- this.events[event] = [];
47
- }
48
- return this.events[event];
49
- }
50
- /**
51
- * Adds a new event listener.
52
- *
53
- * @param event An event name or a list of them.
54
- * @param listener The listener function.
55
- * @returns An unsubscribe function to remove the listene(s).
56
- * @template ListenerFn The type of the listener function.
57
- * @example
58
- *
59
- * const events = new EventsHub();
60
- * type Listener = (arg0: string) => void;
61
- * const unsubscribe = events.on<Listener>('event', (arg0) => {
62
- * console.log(`Event received: ${arg0}`);
63
- * });
64
- *
65
- */
66
- on(event, listener) {
67
- const events = Array.isArray(event) ? event : [event];
68
- events.forEach((name) => {
69
- const subscribers = this.getSubscribers(name);
70
- if (!subscribers.includes(listener)) {
71
- subscribers.push(listener);
72
- }
73
- });
74
- return () => this.off(event, listener);
75
- }
76
- /**
77
- * Adds an event listener that will only be executed once.
78
- *
79
- * @param event An event name or a list of them.
80
- * @param listener The listener function.
81
- * @returns An unsubscribe function to remove the listener(s).
82
- * @template ListenerFn The type of the listener function.
83
- * @example
84
- *
85
- * const events = new EventsHub();
86
- * type Listener = (arg0: string) => void;
87
- * const unsubscribe = events.once<Listener>('event', (arg0) => {
88
- * console.log(`Event received: ${arg0}`);
89
- * });
90
- *
91
- */
92
- once(event, listener) {
93
- const events = Array.isArray(event) ? event : [event];
94
- let wrapper = events.reduce((acc, name) => {
95
- if (acc) return acc;
96
- const onceWrapper = this.onceWrappers[name];
97
- if (Array.isArray(onceWrapper)) {
98
- const existing = onceWrapper.find((item) => item.original === listener);
99
- if (existing) {
100
- return existing.wrapper;
101
- }
102
- return null;
103
- }
104
- this.onceWrappers[name] = [];
105
- return null;
106
- }, null);
107
- if (!wrapper) {
108
- const newWrapper = (...args) => listener(...args);
109
- newWrapper.once = true;
110
- wrapper = newWrapper;
111
- events.forEach((name) => {
112
- this.onceWrappers[name].push({
113
- wrapper,
114
- original: listener
115
- });
116
- });
117
- }
118
- return this.on(event, wrapper);
119
- }
120
- /**
121
- * Removes an event listener.
122
- *
123
- * @param event An event name or a list of them.
124
- * @param listener The listener function.
125
- * @returns If `event` was a `string`, it will return whether or not the listener was
126
- * found and removed; but if `event`
127
- * was an `Array`, it will return a list of boolean values.
128
- * @template ListenerFn The type of the listener function.
129
- * @example
130
- *
131
- * const events = new EventsHub();
132
- * const listener = (arg0) => {
133
- * console.log(`Event received: ${arg0}`);
134
- * };
135
- * events.on('event', listener); // subscribe.
136
- * events.off('event', listener); // manually unsubscribe.
137
- *
138
- */
139
- off(event, listener) {
140
- const isArray = Array.isArray(event);
141
- const events = isArray ? event : [event];
142
- const result = events.map((name) => {
143
- const subscribers = this.getSubscribers(name);
144
- const onceSubscribers = this.onceWrappers[name];
145
- let found = false;
146
- let index = subscribers.indexOf(listener);
147
- if (index > -1) {
148
- found = true;
149
- if ("once" in listener && onceSubscribers) {
150
- const wrapperIndex = onceSubscribers.findIndex(
151
- (item) => item.wrapper === listener
152
- );
153
- onceSubscribers.splice(wrapperIndex, 1);
154
- }
155
- subscribers.splice(index, 1);
156
- } else if (onceSubscribers) {
157
- index = onceSubscribers.findIndex((item) => item.original === listener);
158
- if (index > -1) {
159
- found = true;
160
- const originalIndex = subscribers.indexOf(onceSubscribers[index].original);
161
- subscribers.splice(originalIndex, 1);
162
- onceSubscribers.splice(index, 1);
163
- }
164
- }
165
- return found;
166
- });
167
- return isArray ? result : result[0];
168
- }
169
- /**
170
- * Emits an event and call all its listeners.
171
- *
172
- * @param event An event name or a list of them.
173
- * @param args A list of parameters to send to the listeners.
174
- * @template Args The type of the parameters to send to the listeners.
175
- * @example
176
- *
177
- * const events = new EventsHub();
178
- * events.on('event', (arg0) => {
179
- * console.log(`Event received: ${arg0}`);
180
- * });
181
- * events.emit('event', 'Hello'); // prints "Event received: Hello"
182
- *
183
- */
184
- emit(event, ...args) {
185
- const toClean = [];
186
- const events = Array.isArray(event) ? event : [event];
187
- events.forEach((name) => {
188
- this.getSubscribers(name).forEach((subscriber) => {
189
- subscriber(...args);
190
- if ("once" in subscriber) {
191
- toClean.push({
192
- event: name,
193
- listener: subscriber
194
- });
195
- }
196
- });
197
- });
198
- toClean.forEach((info) => this.off(info.event, info.listener));
199
- }
200
- /**
201
- * Asynchronously reduces a target using an event. It's like emit, but the events
202
- * listener return a modified (or not) version of the `target`.
203
- *
204
- * @param event An event name or a list of them.
205
- * @param target The variable to reduce with the reducers/listeners.
206
- * @param args A list of parameters to send to the reducers/listeners.
207
- * @returns A version of the `target` processed by the listeners.
208
- * @template Target The type of the target.
209
- * @template Args The type of the parameters to send to the reducers/listeners.
210
- * @example
211
- *
212
- * const events = new EventsHub();
213
- * events.on('event', async (target, arg0) => {
214
- * const data = await fetch(`https://api.example.com/${arg0}`);
215
- * target.push(data);
216
- * return target;
217
- * });
218
- * const result = await events.reduce('event', [], 'Hello');
219
- * // result would be a list of data fetched from the API.
220
- *
221
- */
222
- async reduce(event, target, ...args) {
223
- const events = Array.isArray(event) ? event : [event];
224
- const toClean = [];
225
- const result = await events.reduce(
226
- (eventAcc, name) => eventAcc.then((eventCurrent) => {
227
- const subscribers = this.getSubscribers(name);
228
- return subscribers.reduce(
229
- (subAcc, subscriber) => subAcc.then((subCurrent) => {
230
- let useCurrent;
231
- if (Array.isArray(subCurrent)) {
232
- useCurrent = subCurrent.slice();
233
- } else if (typeof subCurrent === "object") {
234
- useCurrent = { ...subCurrent };
235
- } else {
236
- useCurrent = subCurrent;
237
- }
238
- const nextStep = subscriber(...[useCurrent, ...args]);
239
- if ("once" in subscriber) {
240
- toClean.push({
241
- event: name,
242
- listener: subscriber
243
- });
244
- }
245
- return nextStep;
246
- }),
247
- Promise.resolve(eventCurrent)
248
- );
249
- }),
250
- Promise.resolve(target)
251
- );
252
- toClean.forEach((info) => this.off(info.event, info.listener));
253
- return result;
254
- }
255
- /**
256
- * Synchronously reduces a target using an event. It's like emit, but the events
257
- * listener return a modified (or not) version of the `target`.
258
- *
259
- * @param event An event name or a list of them.
260
- * @param target The variable to reduce with the reducers/listeners.
261
- * @param args A list of parameters to send to the reducers/listeners.
262
- * @returns A version of the `target` processed by the listeners.
263
- * @template Target The type of the target.
264
- * @template Args The type of the parameters to send to the reducers/listeners.
265
- * @example
266
- *
267
- * const events = new EventsHub();
268
- * events.on('event', (target, arg0) => {
269
- * target.push(arg0);
270
- * return target;
271
- * });
272
- * events.reduce('event', [], 'Hello'); // returns ['Hello']
273
- *
274
- */
275
- reduceSync(event, target, ...args) {
276
- const events = Array.isArray(event) ? event : [event];
277
- const toClean = [];
278
- const result = events.reduce((eventAcc, name) => {
279
- const subscribers = this.getSubscribers(name);
280
- return subscribers.reduce((subAcc, subscriber) => {
281
- let useCurrent;
282
- if (Array.isArray(subAcc)) {
283
- useCurrent = subAcc.slice();
284
- } else if (typeof subAcc === "object") {
285
- useCurrent = { ...subAcc };
286
- } else {
287
- useCurrent = subAcc;
288
- }
289
- const nextStep = subscriber(...[useCurrent, ...args]);
290
- if ("once" in subscriber) {
291
- toClean.push({
292
- event: name,
293
- listener: subscriber
294
- });
295
- }
296
- return nextStep;
297
- }, eventAcc);
298
- }, target);
299
- toClean.forEach((info) => this.off(info.event, info.listener));
300
- return result;
301
- }
302
- };
303
- var eventsHub = () => new EventsHub();
304
- // Annotate the CommonJS export names for ESM import in node:
305
- 0 && (module.exports = {
306
- EventsHub,
307
- eventsHub
308
- });
1
+ var d=class{events={};onceWrappers={};getSubscribers(e){return this.events[e]||(this.events[e]=[]),this.events[e]}on(e,r){return(Array.isArray(e)?e:[e]).forEach(a=>{let n=this.getSubscribers(a);n.includes(r)||n.push(r)}),()=>this.off(e,r)}once(e,r){let o=Array.isArray(e)?e:[e],a=o.reduce((n,s)=>{if(n)return n;let t=this.onceWrappers[s];if(Array.isArray(t)){let i=t.find(F=>F.original===r);return i?i.wrapper:null}return this.onceWrappers[s]=[],null},null);if(!a){let n=(...s)=>r(...s);n.once=!0,a=n,o.forEach(s=>{this.onceWrappers[s].push({wrapper:a,original:r})})}return this.on(e,a)}off(e,r){let o=Array.isArray(e),n=(o?e:[e]).map(s=>{let t=this.getSubscribers(s),i=this.onceWrappers[s],F=!1,c=t.indexOf(r);if(c>-1){if(F=!0,"once"in r&&i){let l=i.findIndex(p=>p.wrapper===r);i.splice(l,1)}t.splice(c,1)}else if(i&&(c=i.findIndex(l=>l.original===r),c>-1)){F=!0;let l=t.indexOf(i[c].original);t.splice(l,1),i.splice(c,1)}return F});return o?n:n[0]}emit(e,...r){let o=[];(Array.isArray(e)?e:[e]).forEach(n=>{this.getSubscribers(n).forEach(s=>{s(...r),"once"in s&&o.push({event:n,listener:s})})}),o.forEach(n=>this.off(n.event,n.listener))}async reduce(e,r,...o){let a=Array.isArray(e)?e:[e],n=[],s=await a.reduce((t,i)=>t.then(F=>this.getSubscribers(i).reduce((l,p)=>l.then(g=>{let f;Array.isArray(g)?f=g.slice():typeof g=="object"?f={...g}:f=g;let u=p(f,...o);return"once"in p&&n.push({event:i,listener:p}),u}),Promise.resolve(F))),Promise.resolve(r));return n.forEach(t=>this.off(t.event,t.listener)),s}reduceSync(e,r,...o){let a=Array.isArray(e)?e:[e],n=[],s=a.reduce((t,i)=>this.getSubscribers(i).reduce((c,l)=>{let p;Array.isArray(c)?p=c.slice():typeof c=="object"?p={...c}:p=c;let g=l(p,...o);return"once"in l&&n.push({event:i,listener:l}),g},t),r);return n.forEach(t=>this.off(t.event,t.listener)),s}},h=()=>new d;export{d as EventsHub,h as eventsHub};
309
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/no-explicit-any -- It's a generic function.\ntype GenericFn = (...args: any[]) => any;\ntype GenericParams = Parameters<GenericFn>;\ntype OnceWrapperFn = GenericFn & {\n once: boolean;\n};\n/**\n * When the service receives a \"once subscription\", it needs to track the listener so it\n * gets removed after being called once, and to avoid modifying the original function, it\n * creates a wrapper function that has the \"once\" property set to true. The wrapper and\n * the original function are stored in case `off` is called before the wrapper gets\n * triggered; it will receive the original function, not the wrapper, so the class needs a\n * way to map them together.\n */\ntype OnceWrapper = {\n /**\n * A simple wrapper for the original listener, with the difference that it has a `once`\n * property set to `true`.\n */\n wrapper: OnceWrapperFn;\n /**\n * The original listener that will be called once.\n */\n original: GenericFn;\n};\n/**\n * A minimal implementation of an events handler service.\n */\nexport class EventsHub {\n /**\n * A dictionary of the events and their listeners.\n */\n protected events: Record<string, GenericFn[]> = {};\n /**\n * A dictionary of wrappers that were created for \"one time subscriptions\". This is\n * used by the {@link EventsHub.off}: if it doesn't find the subscriber as it is, it\n * will look for a wrapper and remove it.\n */\n protected onceWrappers: Record<string, OnceWrapper[]> = {};\n /**\n * Gets all the listeners for a specific event.\n * The list is returned by reference, so it can be modified once obtained.\n *\n * @param event The name of the event.\n */\n protected getSubscribers(event: string): (GenericFn | OnceWrapperFn)[] {\n if (!this.events[event]) {\n this.events[event] = [];\n }\n\n return this.events[event]!;\n }\n on<ListenerFn extends GenericFn = GenericFn>(\n event: string,\n listener: ListenerFn,\n ): () => boolean;\n on<ListenerFn extends GenericFn = GenericFn>(\n event: string[],\n listener: ListenerFn,\n ): () => boolean[];\n on<ListenerFn extends GenericFn = GenericFn>(\n event: string | string[],\n listener: ListenerFn,\n ): () => boolean | boolean[];\n /**\n * Adds a new event listener.\n *\n * @param event An event name or a list of them.\n * @param listener The listener function.\n * @returns An unsubscribe function to remove the listene(s).\n * @template ListenerFn The type of the listener function.\n * @example\n *\n * const events = new EventsHub();\n * type Listener = (arg0: string) => void;\n * const unsubscribe = events.on<Listener>('event', (arg0) => {\n * console.log(`Event received: ${arg0}`);\n * });\n *\n */\n on<ListenerFn extends GenericFn = GenericFn>(\n event: string | string[],\n listener: ListenerFn,\n ): () => boolean | boolean[] {\n const events = Array.isArray(event) ? event : [event];\n events.forEach((name) => {\n const subscribers = this.getSubscribers(name);\n if (!subscribers.includes(listener)) {\n subscribers.push(listener);\n }\n });\n\n return () => this.off<ListenerFn>(event, listener);\n }\n once<ListenerFn extends GenericFn = GenericFn>(\n event: string,\n listener: ListenerFn,\n ): () => boolean;\n once<ListenerFn extends GenericFn = GenericFn>(\n event: string[],\n listener: ListenerFn,\n ): () => boolean[];\n once<ListenerFn extends GenericFn = GenericFn>(\n event: string | string[],\n listener: ListenerFn,\n ): () => boolean | boolean[];\n /**\n * Adds an event listener that will only be executed once.\n *\n * @param event An event name or a list of them.\n * @param listener The listener function.\n * @returns An unsubscribe function to remove the listener(s).\n * @template ListenerFn The type of the listener function.\n * @example\n *\n * const events = new EventsHub();\n * type Listener = (arg0: string) => void;\n * const unsubscribe = events.once<Listener>('event', (arg0) => {\n * console.log(`Event received: ${arg0}`);\n * });\n *\n */\n once<ListenerFn extends GenericFn = GenericFn>(\n event: string | string[],\n listener: ListenerFn,\n ): () => boolean | boolean[] {\n const events = Array.isArray(event) ? event : [event];\n // Try to find an existing wrapper.\n let wrapper = events.reduce<OnceWrapperFn | null>((acc, name) => {\n // A previous iteration found a wrapper, so `continue`.\n if (acc) return acc;\n\n // A list of wrappers exists for the event, so, let's try an find one for this function.\n const onceWrapper = this.onceWrappers[name];\n if (Array.isArray(onceWrapper)) {\n const existing = onceWrapper.find((item) => item.original === listener);\n if (existing) {\n return existing.wrapper;\n }\n\n return null;\n }\n\n // The list didn't even exists, let's at least create it.\n this.onceWrappers[name] = [];\n return null;\n }, null);\n\n if (!wrapper) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- It's a generic function.\n const newWrapper = (...args: any[]) => listener(...args);\n newWrapper.once = true;\n wrapper = newWrapper;\n events.forEach((name) => {\n this.onceWrappers[name]!.push({\n wrapper: wrapper!,\n original: listener,\n });\n });\n }\n\n return this.on(event, wrapper);\n }\n off<ListenerFn extends GenericFn | OnceWrapperFn = GenericFn>(\n event: string[],\n listener: ListenerFn,\n ): boolean[];\n off<ListenerFn extends GenericFn | OnceWrapperFn = GenericFn>(\n event: string,\n listener: ListenerFn,\n ): boolean;\n off<ListenerFn extends GenericFn | OnceWrapperFn = GenericFn>(\n event: string | string[],\n listener: ListenerFn,\n ): boolean | boolean[];\n /**\n * Removes an event listener.\n *\n * @param event An event name or a list of them.\n * @param listener The listener function.\n * @returns If `event` was a `string`, it will return whether or not the listener was\n * found and removed; but if `event`\n * was an `Array`, it will return a list of boolean values.\n * @template ListenerFn The type of the listener function.\n * @example\n *\n * const events = new EventsHub();\n * const listener = (arg0) => {\n * console.log(`Event received: ${arg0}`);\n * };\n * events.on('event', listener); // subscribe.\n * events.off('event', listener); // manually unsubscribe.\n *\n */\n off<ListenerFn extends GenericFn | OnceWrapperFn = GenericFn>(\n event: string | string[],\n listener: ListenerFn,\n ): boolean | boolean[] {\n const isArray = Array.isArray(event);\n const events = isArray ? event : [event];\n const result = events.map((name) => {\n const subscribers = this.getSubscribers(name);\n const onceSubscribers = this.onceWrappers[name];\n let found = false;\n let index = subscribers.indexOf(listener);\n if (index > -1) {\n found = true;\n /**\n * If the listener had the `once` flag, then it's a wrapper, so it needs to remove\n * it from the wrappers list too.\n */\n if ('once' in listener && onceSubscribers) {\n const wrapperIndex = onceSubscribers.findIndex(\n (item) => item.wrapper === listener,\n );\n onceSubscribers.splice(wrapperIndex, 1);\n }\n subscribers.splice(index, 1);\n } else if (onceSubscribers) {\n /**\n * If it couldn't found the subscriber, maybe it's because it's the original\n * listener of a wrapper.\n */\n index = onceSubscribers.findIndex((item) => item.original === listener);\n if (index > -1) {\n found = true;\n const originalIndex = subscribers.indexOf(onceSubscribers[index]!.original);\n subscribers.splice(originalIndex, 1);\n onceSubscribers.splice(index, 1);\n }\n }\n\n return found;\n });\n\n return isArray ? result : result[0]!;\n }\n /**\n * Emits an event and call all its listeners.\n *\n * @param event An event name or a list of them.\n * @param args A list of parameters to send to the listeners.\n * @template Args The type of the parameters to send to the listeners.\n * @example\n *\n * const events = new EventsHub();\n * events.on('event', (arg0) => {\n * console.log(`Event received: ${arg0}`);\n * });\n * events.emit('event', 'Hello'); // prints \"Event received: Hello\"\n *\n */\n emit<Args extends GenericParams>(event: string | string[], ...args: Args): void {\n const toClean: { event: string; listener: GenericFn }[] = [];\n const events = Array.isArray(event) ? event : [event];\n events.forEach((name) => {\n this.getSubscribers(name).forEach((subscriber) => {\n subscriber(...args);\n if ('once' in subscriber) {\n toClean.push({\n event: name,\n listener: subscriber,\n });\n }\n });\n });\n\n toClean.forEach((info) => this.off(info.event, info.listener));\n }\n /**\n * Asynchronously reduces a target using an event. It's like emit, but the events\n * listener return a modified (or not) version of the `target`.\n *\n * @param event An event name or a list of them.\n * @param target The variable to reduce with the reducers/listeners.\n * @param args A list of parameters to send to the reducers/listeners.\n * @returns A version of the `target` processed by the listeners.\n * @template Target The type of the target.\n * @template Args The type of the parameters to send to the reducers/listeners.\n * @example\n *\n * const events = new EventsHub();\n * events.on('event', async (target, arg0) => {\n * const data = await fetch(`https://api.example.com/${arg0}`);\n * target.push(data);\n * return target;\n * });\n * const result = await events.reduce('event', [], 'Hello');\n * // result would be a list of data fetched from the API.\n *\n */\n async reduce<Target, Args extends GenericParams>(\n event: string | string[],\n target: Target,\n ...args: Args\n ): Promise<Target> {\n const events = Array.isArray(event) ? event : [event];\n const toClean: { event: string; listener: GenericFn }[] = [];\n const result = await events.reduce<Promise<Target>>(\n (eventAcc, name) =>\n eventAcc.then((eventCurrent) => {\n const subscribers = this.getSubscribers(name);\n return subscribers.reduce(\n (subAcc, subscriber) =>\n subAcc.then((subCurrent) => {\n let useCurrent;\n if (Array.isArray(subCurrent)) {\n useCurrent = subCurrent.slice();\n } else if (typeof subCurrent === 'object') {\n useCurrent = { ...subCurrent };\n } else {\n useCurrent = subCurrent;\n }\n\n const nextStep = subscriber(...[useCurrent, ...args]);\n if ('once' in subscriber) {\n toClean.push({\n event: name,\n listener: subscriber,\n });\n }\n\n return nextStep;\n }),\n Promise.resolve(eventCurrent),\n );\n }),\n Promise.resolve(target),\n );\n\n toClean.forEach((info) => this.off(info.event, info.listener));\n\n return result;\n }\n /**\n * Synchronously reduces a target using an event. It's like emit, but the events\n * listener return a modified (or not) version of the `target`.\n *\n * @param event An event name or a list of them.\n * @param target The variable to reduce with the reducers/listeners.\n * @param args A list of parameters to send to the reducers/listeners.\n * @returns A version of the `target` processed by the listeners.\n * @template Target The type of the target.\n * @template Args The type of the parameters to send to the reducers/listeners.\n * @example\n *\n * const events = new EventsHub();\n * events.on('event', (target, arg0) => {\n * target.push(arg0);\n * return target;\n * });\n * events.reduce('event', [], 'Hello'); // returns ['Hello']\n *\n */\n reduceSync<Target, ReducerArgs extends GenericParams>(\n event: string | string[],\n target: Target,\n ...args: ReducerArgs\n ): Target {\n const events = Array.isArray(event) ? event : [event];\n const toClean: { event: string; listener: GenericFn }[] = [];\n const result = events.reduce((eventAcc, name) => {\n const subscribers = this.getSubscribers(name);\n return subscribers.reduce((subAcc, subscriber) => {\n let useCurrent;\n if (Array.isArray(subAcc)) {\n useCurrent = subAcc.slice();\n } else if (typeof subAcc === 'object') {\n useCurrent = { ...subAcc };\n } else {\n useCurrent = subAcc;\n }\n\n const nextStep = subscriber(...[useCurrent, ...args]);\n if ('once' in subscriber) {\n toClean.push({\n event: name,\n listener: subscriber,\n });\n }\n\n return nextStep;\n }, eventAcc);\n }, target);\n\n toClean.forEach((info) => this.off(info.event, info.listener));\n return result;\n }\n}\n/**\n * Shorthand for `new EventsHub()`.\n *\n * @returns A new instance of {@link EventsHub}.\n */\nexport const eventsHub = () => new EventsHub();\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/no-explicit-any -- It's a generic function.\ntype GenericFn = (...args: any[]) => any;\ntype GenericParams = Parameters<GenericFn>;\ntype OnceWrapperFn = GenericFn & {\n once: boolean;\n};\n/**\n * When the service receives a \"once subscription\", it needs to track the listener so it\n * gets removed after being called once, and to avoid modifying the original function, it\n * creates a wrapper function that has the \"once\" property set to true. The wrapper and\n * the original function are stored in case `off` is called before the wrapper gets\n * triggered; it will receive the original function, not the wrapper, so the class needs a\n * way to map them together.\n */\ntype OnceWrapper = {\n /**\n * A simple wrapper for the original listener, with the difference that it has a `once`\n * property set to `true`.\n */\n wrapper: OnceWrapperFn;\n /**\n * The original listener that will be called once.\n */\n original: GenericFn;\n};\n/**\n * A minimal implementation of an events handler service.\n */\nexport class EventsHub {\n /**\n * A dictionary of the events and their listeners.\n */\n protected events: Record<string, GenericFn[]> = {};\n /**\n * A dictionary of wrappers that were created for \"one time subscriptions\". This is\n * used by the {@link EventsHub.off}: if it doesn't find the subscriber as it is, it\n * will look for a wrapper and remove it.\n */\n protected onceWrappers: Record<string, OnceWrapper[]> = {};\n /**\n * Gets all the listeners for a specific event.\n * The list is returned by reference, so it can be modified once obtained.\n *\n * @param event The name of the event.\n */\n protected getSubscribers(event: string): (GenericFn | OnceWrapperFn)[] {\n if (!this.events[event]) {\n this.events[event] = [];\n }\n\n return this.events[event]!;\n }\n on<ListenerFn extends GenericFn = GenericFn>(\n event: string,\n listener: ListenerFn,\n ): () => boolean;\n on<ListenerFn extends GenericFn = GenericFn>(\n event: string[],\n listener: ListenerFn,\n ): () => boolean[];\n on<ListenerFn extends GenericFn = GenericFn>(\n event: string | string[],\n listener: ListenerFn,\n ): () => boolean | boolean[];\n /**\n * Adds a new event listener.\n *\n * @param event An event name or a list of them.\n * @param listener The listener function.\n * @returns An unsubscribe function to remove the listene(s).\n * @template ListenerFn The type of the listener function.\n * @example\n *\n * const events = new EventsHub();\n * type Listener = (arg0: string) => void;\n * const unsubscribe = events.on<Listener>('event', (arg0) => {\n * console.log(`Event received: ${arg0}`);\n * });\n *\n */\n on<ListenerFn extends GenericFn = GenericFn>(\n event: string | string[],\n listener: ListenerFn,\n ): () => boolean | boolean[] {\n const events = Array.isArray(event) ? event : [event];\n events.forEach((name) => {\n const subscribers = this.getSubscribers(name);\n if (!subscribers.includes(listener)) {\n subscribers.push(listener);\n }\n });\n\n return () => this.off<ListenerFn>(event, listener);\n }\n once<ListenerFn extends GenericFn = GenericFn>(\n event: string,\n listener: ListenerFn,\n ): () => boolean;\n once<ListenerFn extends GenericFn = GenericFn>(\n event: string[],\n listener: ListenerFn,\n ): () => boolean[];\n once<ListenerFn extends GenericFn = GenericFn>(\n event: string | string[],\n listener: ListenerFn,\n ): () => boolean | boolean[];\n /**\n * Adds an event listener that will only be executed once.\n *\n * @param event An event name or a list of them.\n * @param listener The listener function.\n * @returns An unsubscribe function to remove the listener(s).\n * @template ListenerFn The type of the listener function.\n * @example\n *\n * const events = new EventsHub();\n * type Listener = (arg0: string) => void;\n * const unsubscribe = events.once<Listener>('event', (arg0) => {\n * console.log(`Event received: ${arg0}`);\n * });\n *\n */\n once<ListenerFn extends GenericFn = GenericFn>(\n event: string | string[],\n listener: ListenerFn,\n ): () => boolean | boolean[] {\n const events = Array.isArray(event) ? event : [event];\n // Try to find an existing wrapper.\n let wrapper = events.reduce<OnceWrapperFn | null>((acc, name) => {\n // A previous iteration found a wrapper, so `continue`.\n if (acc) return acc;\n\n // A list of wrappers exists for the event, so, let's try an find one for this function.\n const onceWrapper = this.onceWrappers[name];\n if (Array.isArray(onceWrapper)) {\n const existing = onceWrapper.find((item) => item.original === listener);\n if (existing) {\n return existing.wrapper;\n }\n\n return null;\n }\n\n // The list didn't even exists, let's at least create it.\n this.onceWrappers[name] = [];\n return null;\n }, null);\n\n if (!wrapper) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- It's a generic function.\n const newWrapper = (...args: any[]) => listener(...args);\n newWrapper.once = true;\n wrapper = newWrapper;\n events.forEach((name) => {\n this.onceWrappers[name]!.push({\n wrapper: wrapper!,\n original: listener,\n });\n });\n }\n\n return this.on(event, wrapper);\n }\n off<ListenerFn extends GenericFn | OnceWrapperFn = GenericFn>(\n event: string[],\n listener: ListenerFn,\n ): boolean[];\n off<ListenerFn extends GenericFn | OnceWrapperFn = GenericFn>(\n event: string,\n listener: ListenerFn,\n ): boolean;\n off<ListenerFn extends GenericFn | OnceWrapperFn = GenericFn>(\n event: string | string[],\n listener: ListenerFn,\n ): boolean | boolean[];\n /**\n * Removes an event listener.\n *\n * @param event An event name or a list of them.\n * @param listener The listener function.\n * @returns If `event` was a `string`, it will return whether or not the listener\n * was found and removed; but if `event`\n * was an `Array`, it will return a list of boolean values.\n * @template ListenerFn The type of the listener function.\n * @example\n *\n * const events = new EventsHub();\n * const listener = (arg0) => {\n * console.log(`Event received: ${arg0}`);\n * };\n * events.on('event', listener); // subscribe.\n * events.off('event', listener); // manually unsubscribe.\n *\n */\n off<ListenerFn extends GenericFn | OnceWrapperFn = GenericFn>(\n event: string | string[],\n listener: ListenerFn,\n ): boolean | boolean[] {\n const isArray = Array.isArray(event);\n const events = isArray ? event : [event];\n const result = events.map((name) => {\n const subscribers = this.getSubscribers(name);\n const onceSubscribers = this.onceWrappers[name];\n let found = false;\n let index = subscribers.indexOf(listener);\n if (index > -1) {\n found = true;\n /**\n * If the listener had the `once` flag, then it's a wrapper, so it needs to remove\n * it from the wrappers list too.\n */\n if ('once' in listener && onceSubscribers) {\n const wrapperIndex = onceSubscribers.findIndex(\n (item) => item.wrapper === listener,\n );\n onceSubscribers.splice(wrapperIndex, 1);\n }\n subscribers.splice(index, 1);\n } else if (onceSubscribers) {\n /**\n * If it couldn't found the subscriber, maybe it's because it's the original\n * listener of a wrapper.\n */\n index = onceSubscribers.findIndex((item) => item.original === listener);\n if (index > -1) {\n found = true;\n const originalIndex = subscribers.indexOf(onceSubscribers[index]!.original);\n subscribers.splice(originalIndex, 1);\n onceSubscribers.splice(index, 1);\n }\n }\n\n return found;\n });\n\n return isArray ? result : result[0]!;\n }\n /**\n * Emits an event and call all its listeners.\n *\n * @param event An event name or a list of them.\n * @param args A list of parameters to send to the listeners.\n * @template Args The type of the parameters to send to the listeners.\n * @example\n *\n * const events = new EventsHub();\n * events.on('event', (arg0) => {\n * console.log(`Event received: ${arg0}`);\n * });\n * events.emit('event', 'Hello'); // prints \"Event received: Hello\"\n *\n */\n emit<Args extends GenericParams>(event: string | string[], ...args: Args): void {\n const toClean: { event: string; listener: GenericFn }[] = [];\n const events = Array.isArray(event) ? event : [event];\n events.forEach((name) => {\n this.getSubscribers(name).forEach((subscriber) => {\n subscriber(...args);\n if ('once' in subscriber) {\n toClean.push({\n event: name,\n listener: subscriber,\n });\n }\n });\n });\n\n toClean.forEach((info) => this.off(info.event, info.listener));\n }\n /**\n * Asynchronously reduces a target using an event. It's like emit, but the events\n * listener return a modified (or not) version of the `target`.\n *\n * @param event An event name or a list of them.\n * @param target The variable to reduce with the reducers/listeners.\n * @param args A list of parameters to send to the reducers/listeners.\n * @returns A version of the `target` processed by the listeners.\n * @template Target The type of the target.\n * @template Args The type of the parameters to send to the reducers/listeners.\n * @example\n *\n * const events = new EventsHub();\n * events.on('event', async (target, arg0) => {\n * const data = await fetch(`https://api.example.com/${arg0}`);\n * target.push(data);\n * return target;\n * });\n * const result = await events.reduce('event', [], 'Hello');\n * // result would be a list of data fetched from the API.\n *\n */\n async reduce<Target, Args extends GenericParams>(\n event: string | string[],\n target: Target,\n ...args: Args\n ): Promise<Target> {\n const events = Array.isArray(event) ? event : [event];\n const toClean: { event: string; listener: GenericFn }[] = [];\n const result = await events.reduce<Promise<Target>>(\n (eventAcc, name) =>\n eventAcc.then((eventCurrent) => {\n const subscribers = this.getSubscribers(name);\n return subscribers.reduce(\n (subAcc, subscriber) =>\n subAcc.then((subCurrent) => {\n let useCurrent;\n if (Array.isArray(subCurrent)) {\n useCurrent = subCurrent.slice();\n } else if (typeof subCurrent === 'object') {\n useCurrent = { ...subCurrent };\n } else {\n useCurrent = subCurrent;\n }\n\n const nextStep = subscriber(...[useCurrent, ...args]);\n if ('once' in subscriber) {\n toClean.push({\n event: name,\n listener: subscriber,\n });\n }\n\n return nextStep;\n }),\n Promise.resolve(eventCurrent),\n );\n }),\n Promise.resolve(target),\n );\n\n toClean.forEach((info) => this.off(info.event, info.listener));\n\n return result;\n }\n /**\n * Synchronously reduces a target using an event. It's like emit, but the events\n * listener return a modified (or not) version of the `target`.\n *\n * @param event An event name or a list of them.\n * @param target The variable to reduce with the reducers/listeners.\n * @param args A list of parameters to send to the reducers/listeners.\n * @returns A version of the `target` processed by the listeners.\n * @template Target The type of the target.\n * @template Args The type of the parameters to send to the reducers/listeners.\n * @example\n *\n * const events = new EventsHub();\n * events.on('event', (target, arg0) => {\n * target.push(arg0);\n * return target;\n * });\n * events.reduce('event', [], 'Hello'); // returns ['Hello']\n *\n */\n reduceSync<Target, ReducerArgs extends GenericParams>(\n event: string | string[],\n target: Target,\n ...args: ReducerArgs\n ): Target {\n const events = Array.isArray(event) ? event : [event];\n const toClean: { event: string; listener: GenericFn }[] = [];\n const result = events.reduce((eventAcc, name) => {\n const subscribers = this.getSubscribers(name);\n return subscribers.reduce((subAcc, subscriber) => {\n let useCurrent;\n if (Array.isArray(subAcc)) {\n useCurrent = subAcc.slice();\n } else if (typeof subAcc === 'object') {\n useCurrent = { ...subAcc };\n } else {\n useCurrent = subAcc;\n }\n\n const nextStep = subscriber(...[useCurrent, ...args]);\n if ('once' in subscriber) {\n toClean.push({\n event: name,\n listener: subscriber,\n });\n }\n\n return nextStep;\n }, eventAcc);\n }, target);\n\n toClean.forEach((info) => this.off(info.event, info.listener));\n return result;\n }\n}\n/**\n * Shorthand for `new EventsHub()`.\n *\n * @returns A new instance of {@link EventsHub}.\n */\nexport const eventsHub = () => new EventsHub();\n"],"mappings":"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","names":["EventsHub","event","listener","name","subscribers","events","wrapper","acc","onceWrapper","existing","item","newWrapper","args","isArray","result","onceSubscribers","found","index","wrapperIndex","originalIndex","toClean","subscriber","info","target","eventAcc","eventCurrent","subAcc","subCurrent","useCurrent","nextStep","eventsHub"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@homer0/events-hub",
3
3
  "description": "A simple implementation of a pubsub service for handling events",
4
- "version": "3.0.14",
4
+ "version": "5.0.0",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://github.com/homer0/packages.git",
@@ -19,30 +19,42 @@
19
19
  "publishConfig": {
20
20
  "access": "public"
21
21
  },
22
+ "type": "module",
23
+ "main": "./dist/index.js",
24
+ "types": "./dist/index.d.ts",
25
+ "exports": {
26
+ ".": {
27
+ "import": "./dist/index.js",
28
+ "types": "./src/index.ts"
29
+ },
30
+ "./package.json": "./package.json"
31
+ },
22
32
  "files": [
23
- "dist/"
33
+ "dist/",
34
+ "src/"
24
35
  ],
25
36
  "devDependencies": {
26
- "@homer0/eslint-plugin": "12.0.13",
27
- "@homer0/tsconfig": "2.0.2",
28
- "jest": "catalog:",
29
- "ts-jest": "catalog:",
37
+ "@homer0/eslint-plugin": "14.0.0",
38
+ "@homer0/tsconfig": "4.0.0",
39
+ "@vitest/coverage-v8": "catalog:",
40
+ "eslint": "catalog:",
30
41
  "tsup": "catalog:",
31
- "typescript": "catalog:"
42
+ "typescript": "catalog:",
43
+ "unplugin-swc": "catalog:",
44
+ "vite-tsconfig-paths": "catalog:",
45
+ "vitest": "catalog:"
32
46
  },
33
47
  "engine-strict": true,
34
48
  "engines": {
35
- "node": ">=18 <23"
49
+ "node": ">=20 <23"
36
50
  },
37
- "main": "./dist/index.js",
38
- "module": "./dist/esm/index.js",
39
- "types": "./dist/index.d.ts",
40
51
  "scripts": {
41
52
  "lint": "eslint .",
42
- "test": "jest -c ./.jestrc.js",
53
+ "lint:file": "eslint $@",
54
+ "test": "vitest run $@",
43
55
  "types:check": "tsc --noEmit && tsc --noEmit --project ./tests/tsconfig.json",
44
- "build": "tsup src/index.ts",
56
+ "build": "tsup",
45
57
  "prepublishOnly": "pnpm run build"
46
58
  },
47
- "gitHead": "9e804863e566d60148217835a2d5f7556bb57a45"
59
+ "gitHead": "fece630639760a2ea3b9f191ff203ac33bb88a09"
48
60
  }
@@ -1,27 +1,67 @@
1
- // src/index.ts
2
- var EventsHub = class {
1
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- It's a generic function.
2
+ type GenericFn = (...args: any[]) => any;
3
+ type GenericParams = Parameters<GenericFn>;
4
+ type OnceWrapperFn = GenericFn & {
5
+ once: boolean;
6
+ };
7
+ /**
8
+ * When the service receives a "once subscription", it needs to track the listener so it
9
+ * gets removed after being called once, and to avoid modifying the original function, it
10
+ * creates a wrapper function that has the "once" property set to true. The wrapper and
11
+ * the original function are stored in case `off` is called before the wrapper gets
12
+ * triggered; it will receive the original function, not the wrapper, so the class needs a
13
+ * way to map them together.
14
+ */
15
+ type OnceWrapper = {
16
+ /**
17
+ * A simple wrapper for the original listener, with the difference that it has a `once`
18
+ * property set to `true`.
19
+ */
20
+ wrapper: OnceWrapperFn;
21
+ /**
22
+ * The original listener that will be called once.
23
+ */
24
+ original: GenericFn;
25
+ };
26
+ /**
27
+ * A minimal implementation of an events handler service.
28
+ */
29
+ export class EventsHub {
3
30
  /**
4
31
  * A dictionary of the events and their listeners.
5
32
  */
6
- events = {};
33
+ protected events: Record<string, GenericFn[]> = {};
7
34
  /**
8
35
  * A dictionary of wrappers that were created for "one time subscriptions". This is
9
36
  * used by the {@link EventsHub.off}: if it doesn't find the subscriber as it is, it
10
37
  * will look for a wrapper and remove it.
11
38
  */
12
- onceWrappers = {};
39
+ protected onceWrappers: Record<string, OnceWrapper[]> = {};
13
40
  /**
14
41
  * Gets all the listeners for a specific event.
15
42
  * The list is returned by reference, so it can be modified once obtained.
16
43
  *
17
44
  * @param event The name of the event.
18
45
  */
19
- getSubscribers(event) {
46
+ protected getSubscribers(event: string): (GenericFn | OnceWrapperFn)[] {
20
47
  if (!this.events[event]) {
21
48
  this.events[event] = [];
22
49
  }
23
- return this.events[event];
50
+
51
+ return this.events[event]!;
24
52
  }
53
+ on<ListenerFn extends GenericFn = GenericFn>(
54
+ event: string,
55
+ listener: ListenerFn,
56
+ ): () => boolean;
57
+ on<ListenerFn extends GenericFn = GenericFn>(
58
+ event: string[],
59
+ listener: ListenerFn,
60
+ ): () => boolean[];
61
+ on<ListenerFn extends GenericFn = GenericFn>(
62
+ event: string | string[],
63
+ listener: ListenerFn,
64
+ ): () => boolean | boolean[];
25
65
  /**
26
66
  * Adds a new event listener.
27
67
  *
@@ -38,7 +78,10 @@ var EventsHub = class {
38
78
  * });
39
79
  *
40
80
  */
41
- on(event, listener) {
81
+ on<ListenerFn extends GenericFn = GenericFn>(
82
+ event: string | string[],
83
+ listener: ListenerFn,
84
+ ): () => boolean | boolean[] {
42
85
  const events = Array.isArray(event) ? event : [event];
43
86
  events.forEach((name) => {
44
87
  const subscribers = this.getSubscribers(name);
@@ -46,8 +89,21 @@ var EventsHub = class {
46
89
  subscribers.push(listener);
47
90
  }
48
91
  });
49
- return () => this.off(event, listener);
92
+
93
+ return () => this.off<ListenerFn>(event, listener);
50
94
  }
95
+ once<ListenerFn extends GenericFn = GenericFn>(
96
+ event: string,
97
+ listener: ListenerFn,
98
+ ): () => boolean;
99
+ once<ListenerFn extends GenericFn = GenericFn>(
100
+ event: string[],
101
+ listener: ListenerFn,
102
+ ): () => boolean[];
103
+ once<ListenerFn extends GenericFn = GenericFn>(
104
+ event: string | string[],
105
+ listener: ListenerFn,
106
+ ): () => boolean | boolean[];
51
107
  /**
52
108
  * Adds an event listener that will only be executed once.
53
109
  *
@@ -64,41 +120,66 @@ var EventsHub = class {
64
120
  * });
65
121
  *
66
122
  */
67
- once(event, listener) {
123
+ once<ListenerFn extends GenericFn = GenericFn>(
124
+ event: string | string[],
125
+ listener: ListenerFn,
126
+ ): () => boolean | boolean[] {
68
127
  const events = Array.isArray(event) ? event : [event];
69
- let wrapper = events.reduce((acc, name) => {
128
+ // Try to find an existing wrapper.
129
+ let wrapper = events.reduce<OnceWrapperFn | null>((acc, name) => {
130
+ // A previous iteration found a wrapper, so `continue`.
70
131
  if (acc) return acc;
132
+
133
+ // A list of wrappers exists for the event, so, let's try an find one for this function.
71
134
  const onceWrapper = this.onceWrappers[name];
72
135
  if (Array.isArray(onceWrapper)) {
73
136
  const existing = onceWrapper.find((item) => item.original === listener);
74
137
  if (existing) {
75
138
  return existing.wrapper;
76
139
  }
140
+
77
141
  return null;
78
142
  }
143
+
144
+ // The list didn't even exists, let's at least create it.
79
145
  this.onceWrappers[name] = [];
80
146
  return null;
81
147
  }, null);
148
+
82
149
  if (!wrapper) {
83
- const newWrapper = (...args) => listener(...args);
150
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- It's a generic function.
151
+ const newWrapper = (...args: any[]) => listener(...args);
84
152
  newWrapper.once = true;
85
153
  wrapper = newWrapper;
86
154
  events.forEach((name) => {
87
- this.onceWrappers[name].push({
88
- wrapper,
89
- original: listener
155
+ this.onceWrappers[name]!.push({
156
+ wrapper: wrapper!,
157
+ original: listener,
90
158
  });
91
159
  });
92
160
  }
161
+
93
162
  return this.on(event, wrapper);
94
163
  }
164
+ off<ListenerFn extends GenericFn | OnceWrapperFn = GenericFn>(
165
+ event: string[],
166
+ listener: ListenerFn,
167
+ ): boolean[];
168
+ off<ListenerFn extends GenericFn | OnceWrapperFn = GenericFn>(
169
+ event: string,
170
+ listener: ListenerFn,
171
+ ): boolean;
172
+ off<ListenerFn extends GenericFn | OnceWrapperFn = GenericFn>(
173
+ event: string | string[],
174
+ listener: ListenerFn,
175
+ ): boolean | boolean[];
95
176
  /**
96
177
  * Removes an event listener.
97
178
  *
98
179
  * @param event An event name or a list of them.
99
180
  * @param listener The listener function.
100
- * @returns If `event` was a `string`, it will return whether or not the listener was
101
- * found and removed; but if `event`
181
+ * @returns If `event` was a `string`, it will return whether or not the listener
182
+ * was found and removed; but if `event`
102
183
  * was an `Array`, it will return a list of boolean values.
103
184
  * @template ListenerFn The type of the listener function.
104
185
  * @example
@@ -111,7 +192,10 @@ var EventsHub = class {
111
192
  * events.off('event', listener); // manually unsubscribe.
112
193
  *
113
194
  */
114
- off(event, listener) {
195
+ off<ListenerFn extends GenericFn | OnceWrapperFn = GenericFn>(
196
+ event: string | string[],
197
+ listener: ListenerFn,
198
+ ): boolean | boolean[] {
115
199
  const isArray = Array.isArray(event);
116
200
  const events = isArray ? event : [event];
117
201
  const result = events.map((name) => {
@@ -121,25 +205,35 @@ var EventsHub = class {
121
205
  let index = subscribers.indexOf(listener);
122
206
  if (index > -1) {
123
207
  found = true;
124
- if ("once" in listener && onceSubscribers) {
208
+ /**
209
+ * If the listener had the `once` flag, then it's a wrapper, so it needs to remove
210
+ * it from the wrappers list too.
211
+ */
212
+ if ('once' in listener && onceSubscribers) {
125
213
  const wrapperIndex = onceSubscribers.findIndex(
126
- (item) => item.wrapper === listener
214
+ (item) => item.wrapper === listener,
127
215
  );
128
216
  onceSubscribers.splice(wrapperIndex, 1);
129
217
  }
130
218
  subscribers.splice(index, 1);
131
219
  } else if (onceSubscribers) {
220
+ /**
221
+ * If it couldn't found the subscriber, maybe it's because it's the original
222
+ * listener of a wrapper.
223
+ */
132
224
  index = onceSubscribers.findIndex((item) => item.original === listener);
133
225
  if (index > -1) {
134
226
  found = true;
135
- const originalIndex = subscribers.indexOf(onceSubscribers[index].original);
227
+ const originalIndex = subscribers.indexOf(onceSubscribers[index]!.original);
136
228
  subscribers.splice(originalIndex, 1);
137
229
  onceSubscribers.splice(index, 1);
138
230
  }
139
231
  }
232
+
140
233
  return found;
141
234
  });
142
- return isArray ? result : result[0];
235
+
236
+ return isArray ? result : result[0]!;
143
237
  }
144
238
  /**
145
239
  * Emits an event and call all its listeners.
@@ -156,20 +250,21 @@ var EventsHub = class {
156
250
  * events.emit('event', 'Hello'); // prints "Event received: Hello"
157
251
  *
158
252
  */
159
- emit(event, ...args) {
160
- const toClean = [];
253
+ emit<Args extends GenericParams>(event: string | string[], ...args: Args): void {
254
+ const toClean: { event: string; listener: GenericFn }[] = [];
161
255
  const events = Array.isArray(event) ? event : [event];
162
256
  events.forEach((name) => {
163
257
  this.getSubscribers(name).forEach((subscriber) => {
164
258
  subscriber(...args);
165
- if ("once" in subscriber) {
259
+ if ('once' in subscriber) {
166
260
  toClean.push({
167
261
  event: name,
168
- listener: subscriber
262
+ listener: subscriber,
169
263
  });
170
264
  }
171
265
  });
172
266
  });
267
+
173
268
  toClean.forEach((info) => this.off(info.event, info.listener));
174
269
  }
175
270
  /**
@@ -194,37 +289,47 @@ var EventsHub = class {
194
289
  * // result would be a list of data fetched from the API.
195
290
  *
196
291
  */
197
- async reduce(event, target, ...args) {
292
+ async reduce<Target, Args extends GenericParams>(
293
+ event: string | string[],
294
+ target: Target,
295
+ ...args: Args
296
+ ): Promise<Target> {
198
297
  const events = Array.isArray(event) ? event : [event];
199
- const toClean = [];
200
- const result = await events.reduce(
201
- (eventAcc, name) => eventAcc.then((eventCurrent) => {
202
- const subscribers = this.getSubscribers(name);
203
- return subscribers.reduce(
204
- (subAcc, subscriber) => subAcc.then((subCurrent) => {
205
- let useCurrent;
206
- if (Array.isArray(subCurrent)) {
207
- useCurrent = subCurrent.slice();
208
- } else if (typeof subCurrent === "object") {
209
- useCurrent = { ...subCurrent };
210
- } else {
211
- useCurrent = subCurrent;
212
- }
213
- const nextStep = subscriber(...[useCurrent, ...args]);
214
- if ("once" in subscriber) {
215
- toClean.push({
216
- event: name,
217
- listener: subscriber
218
- });
219
- }
220
- return nextStep;
221
- }),
222
- Promise.resolve(eventCurrent)
223
- );
224
- }),
225
- Promise.resolve(target)
298
+ const toClean: { event: string; listener: GenericFn }[] = [];
299
+ const result = await events.reduce<Promise<Target>>(
300
+ (eventAcc, name) =>
301
+ eventAcc.then((eventCurrent) => {
302
+ const subscribers = this.getSubscribers(name);
303
+ return subscribers.reduce(
304
+ (subAcc, subscriber) =>
305
+ subAcc.then((subCurrent) => {
306
+ let useCurrent;
307
+ if (Array.isArray(subCurrent)) {
308
+ useCurrent = subCurrent.slice();
309
+ } else if (typeof subCurrent === 'object') {
310
+ useCurrent = { ...subCurrent };
311
+ } else {
312
+ useCurrent = subCurrent;
313
+ }
314
+
315
+ const nextStep = subscriber(...[useCurrent, ...args]);
316
+ if ('once' in subscriber) {
317
+ toClean.push({
318
+ event: name,
319
+ listener: subscriber,
320
+ });
321
+ }
322
+
323
+ return nextStep;
324
+ }),
325
+ Promise.resolve(eventCurrent),
326
+ );
327
+ }),
328
+ Promise.resolve(target),
226
329
  );
330
+
227
331
  toClean.forEach((info) => this.off(info.event, info.listener));
332
+
228
333
  return result;
229
334
  }
230
335
  /**
@@ -247,37 +352,44 @@ var EventsHub = class {
247
352
  * events.reduce('event', [], 'Hello'); // returns ['Hello']
248
353
  *
249
354
  */
250
- reduceSync(event, target, ...args) {
355
+ reduceSync<Target, ReducerArgs extends GenericParams>(
356
+ event: string | string[],
357
+ target: Target,
358
+ ...args: ReducerArgs
359
+ ): Target {
251
360
  const events = Array.isArray(event) ? event : [event];
252
- const toClean = [];
361
+ const toClean: { event: string; listener: GenericFn }[] = [];
253
362
  const result = events.reduce((eventAcc, name) => {
254
363
  const subscribers = this.getSubscribers(name);
255
364
  return subscribers.reduce((subAcc, subscriber) => {
256
365
  let useCurrent;
257
366
  if (Array.isArray(subAcc)) {
258
367
  useCurrent = subAcc.slice();
259
- } else if (typeof subAcc === "object") {
368
+ } else if (typeof subAcc === 'object') {
260
369
  useCurrent = { ...subAcc };
261
370
  } else {
262
371
  useCurrent = subAcc;
263
372
  }
373
+
264
374
  const nextStep = subscriber(...[useCurrent, ...args]);
265
- if ("once" in subscriber) {
375
+ if ('once' in subscriber) {
266
376
  toClean.push({
267
377
  event: name,
268
- listener: subscriber
378
+ listener: subscriber,
269
379
  });
270
380
  }
381
+
271
382
  return nextStep;
272
383
  }, eventAcc);
273
384
  }, target);
385
+
274
386
  toClean.forEach((info) => this.off(info.event, info.listener));
275
387
  return result;
276
388
  }
277
- };
278
- var eventsHub = () => new EventsHub();
279
- export {
280
- EventsHub,
281
- eventsHub
282
- };
283
- //# sourceMappingURL=index.js.map
389
+ }
390
+ /**
391
+ * Shorthand for `new EventsHub()`.
392
+ *
393
+ * @returns A new instance of {@link EventsHub}.
394
+ */
395
+ export const eventsHub = () => new EventsHub();
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/index.ts"],"sourcesContent":["// eslint-disable-next-line @typescript-eslint/no-explicit-any -- It's a generic function.\ntype GenericFn = (...args: any[]) => any;\ntype GenericParams = Parameters<GenericFn>;\ntype OnceWrapperFn = GenericFn & {\n once: boolean;\n};\n/**\n * When the service receives a \"once subscription\", it needs to track the listener so it\n * gets removed after being called once, and to avoid modifying the original function, it\n * creates a wrapper function that has the \"once\" property set to true. The wrapper and\n * the original function are stored in case `off` is called before the wrapper gets\n * triggered; it will receive the original function, not the wrapper, so the class needs a\n * way to map them together.\n */\ntype OnceWrapper = {\n /**\n * A simple wrapper for the original listener, with the difference that it has a `once`\n * property set to `true`.\n */\n wrapper: OnceWrapperFn;\n /**\n * The original listener that will be called once.\n */\n original: GenericFn;\n};\n/**\n * A minimal implementation of an events handler service.\n */\nexport class EventsHub {\n /**\n * A dictionary of the events and their listeners.\n */\n protected events: Record<string, GenericFn[]> = {};\n /**\n * A dictionary of wrappers that were created for \"one time subscriptions\". This is\n * used by the {@link EventsHub.off}: if it doesn't find the subscriber as it is, it\n * will look for a wrapper and remove it.\n */\n protected onceWrappers: Record<string, OnceWrapper[]> = {};\n /**\n * Gets all the listeners for a specific event.\n * The list is returned by reference, so it can be modified once obtained.\n *\n * @param event The name of the event.\n */\n protected getSubscribers(event: string): (GenericFn | OnceWrapperFn)[] {\n if (!this.events[event]) {\n this.events[event] = [];\n }\n\n return this.events[event]!;\n }\n on<ListenerFn extends GenericFn = GenericFn>(\n event: string,\n listener: ListenerFn,\n ): () => boolean;\n on<ListenerFn extends GenericFn = GenericFn>(\n event: string[],\n listener: ListenerFn,\n ): () => boolean[];\n on<ListenerFn extends GenericFn = GenericFn>(\n event: string | string[],\n listener: ListenerFn,\n ): () => boolean | boolean[];\n /**\n * Adds a new event listener.\n *\n * @param event An event name or a list of them.\n * @param listener The listener function.\n * @returns An unsubscribe function to remove the listene(s).\n * @template ListenerFn The type of the listener function.\n * @example\n *\n * const events = new EventsHub();\n * type Listener = (arg0: string) => void;\n * const unsubscribe = events.on<Listener>('event', (arg0) => {\n * console.log(`Event received: ${arg0}`);\n * });\n *\n */\n on<ListenerFn extends GenericFn = GenericFn>(\n event: string | string[],\n listener: ListenerFn,\n ): () => boolean | boolean[] {\n const events = Array.isArray(event) ? event : [event];\n events.forEach((name) => {\n const subscribers = this.getSubscribers(name);\n if (!subscribers.includes(listener)) {\n subscribers.push(listener);\n }\n });\n\n return () => this.off<ListenerFn>(event, listener);\n }\n once<ListenerFn extends GenericFn = GenericFn>(\n event: string,\n listener: ListenerFn,\n ): () => boolean;\n once<ListenerFn extends GenericFn = GenericFn>(\n event: string[],\n listener: ListenerFn,\n ): () => boolean[];\n once<ListenerFn extends GenericFn = GenericFn>(\n event: string | string[],\n listener: ListenerFn,\n ): () => boolean | boolean[];\n /**\n * Adds an event listener that will only be executed once.\n *\n * @param event An event name or a list of them.\n * @param listener The listener function.\n * @returns An unsubscribe function to remove the listener(s).\n * @template ListenerFn The type of the listener function.\n * @example\n *\n * const events = new EventsHub();\n * type Listener = (arg0: string) => void;\n * const unsubscribe = events.once<Listener>('event', (arg0) => {\n * console.log(`Event received: ${arg0}`);\n * });\n *\n */\n once<ListenerFn extends GenericFn = GenericFn>(\n event: string | string[],\n listener: ListenerFn,\n ): () => boolean | boolean[] {\n const events = Array.isArray(event) ? event : [event];\n // Try to find an existing wrapper.\n let wrapper = events.reduce<OnceWrapperFn | null>((acc, name) => {\n // A previous iteration found a wrapper, so `continue`.\n if (acc) return acc;\n\n // A list of wrappers exists for the event, so, let's try an find one for this function.\n const onceWrapper = this.onceWrappers[name];\n if (Array.isArray(onceWrapper)) {\n const existing = onceWrapper.find((item) => item.original === listener);\n if (existing) {\n return existing.wrapper;\n }\n\n return null;\n }\n\n // The list didn't even exists, let's at least create it.\n this.onceWrappers[name] = [];\n return null;\n }, null);\n\n if (!wrapper) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- It's a generic function.\n const newWrapper = (...args: any[]) => listener(...args);\n newWrapper.once = true;\n wrapper = newWrapper;\n events.forEach((name) => {\n this.onceWrappers[name]!.push({\n wrapper: wrapper!,\n original: listener,\n });\n });\n }\n\n return this.on(event, wrapper);\n }\n off<ListenerFn extends GenericFn | OnceWrapperFn = GenericFn>(\n event: string[],\n listener: ListenerFn,\n ): boolean[];\n off<ListenerFn extends GenericFn | OnceWrapperFn = GenericFn>(\n event: string,\n listener: ListenerFn,\n ): boolean;\n off<ListenerFn extends GenericFn | OnceWrapperFn = GenericFn>(\n event: string | string[],\n listener: ListenerFn,\n ): boolean | boolean[];\n /**\n * Removes an event listener.\n *\n * @param event An event name or a list of them.\n * @param listener The listener function.\n * @returns If `event` was a `string`, it will return whether or not the listener was\n * found and removed; but if `event`\n * was an `Array`, it will return a list of boolean values.\n * @template ListenerFn The type of the listener function.\n * @example\n *\n * const events = new EventsHub();\n * const listener = (arg0) => {\n * console.log(`Event received: ${arg0}`);\n * };\n * events.on('event', listener); // subscribe.\n * events.off('event', listener); // manually unsubscribe.\n *\n */\n off<ListenerFn extends GenericFn | OnceWrapperFn = GenericFn>(\n event: string | string[],\n listener: ListenerFn,\n ): boolean | boolean[] {\n const isArray = Array.isArray(event);\n const events = isArray ? event : [event];\n const result = events.map((name) => {\n const subscribers = this.getSubscribers(name);\n const onceSubscribers = this.onceWrappers[name];\n let found = false;\n let index = subscribers.indexOf(listener);\n if (index > -1) {\n found = true;\n /**\n * If the listener had the `once` flag, then it's a wrapper, so it needs to remove\n * it from the wrappers list too.\n */\n if ('once' in listener && onceSubscribers) {\n const wrapperIndex = onceSubscribers.findIndex(\n (item) => item.wrapper === listener,\n );\n onceSubscribers.splice(wrapperIndex, 1);\n }\n subscribers.splice(index, 1);\n } else if (onceSubscribers) {\n /**\n * If it couldn't found the subscriber, maybe it's because it's the original\n * listener of a wrapper.\n */\n index = onceSubscribers.findIndex((item) => item.original === listener);\n if (index > -1) {\n found = true;\n const originalIndex = subscribers.indexOf(onceSubscribers[index]!.original);\n subscribers.splice(originalIndex, 1);\n onceSubscribers.splice(index, 1);\n }\n }\n\n return found;\n });\n\n return isArray ? result : result[0]!;\n }\n /**\n * Emits an event and call all its listeners.\n *\n * @param event An event name or a list of them.\n * @param args A list of parameters to send to the listeners.\n * @template Args The type of the parameters to send to the listeners.\n * @example\n *\n * const events = new EventsHub();\n * events.on('event', (arg0) => {\n * console.log(`Event received: ${arg0}`);\n * });\n * events.emit('event', 'Hello'); // prints \"Event received: Hello\"\n *\n */\n emit<Args extends GenericParams>(event: string | string[], ...args: Args): void {\n const toClean: { event: string; listener: GenericFn }[] = [];\n const events = Array.isArray(event) ? event : [event];\n events.forEach((name) => {\n this.getSubscribers(name).forEach((subscriber) => {\n subscriber(...args);\n if ('once' in subscriber) {\n toClean.push({\n event: name,\n listener: subscriber,\n });\n }\n });\n });\n\n toClean.forEach((info) => this.off(info.event, info.listener));\n }\n /**\n * Asynchronously reduces a target using an event. It's like emit, but the events\n * listener return a modified (or not) version of the `target`.\n *\n * @param event An event name or a list of them.\n * @param target The variable to reduce with the reducers/listeners.\n * @param args A list of parameters to send to the reducers/listeners.\n * @returns A version of the `target` processed by the listeners.\n * @template Target The type of the target.\n * @template Args The type of the parameters to send to the reducers/listeners.\n * @example\n *\n * const events = new EventsHub();\n * events.on('event', async (target, arg0) => {\n * const data = await fetch(`https://api.example.com/${arg0}`);\n * target.push(data);\n * return target;\n * });\n * const result = await events.reduce('event', [], 'Hello');\n * // result would be a list of data fetched from the API.\n *\n */\n async reduce<Target, Args extends GenericParams>(\n event: string | string[],\n target: Target,\n ...args: Args\n ): Promise<Target> {\n const events = Array.isArray(event) ? event : [event];\n const toClean: { event: string; listener: GenericFn }[] = [];\n const result = await events.reduce<Promise<Target>>(\n (eventAcc, name) =>\n eventAcc.then((eventCurrent) => {\n const subscribers = this.getSubscribers(name);\n return subscribers.reduce(\n (subAcc, subscriber) =>\n subAcc.then((subCurrent) => {\n let useCurrent;\n if (Array.isArray(subCurrent)) {\n useCurrent = subCurrent.slice();\n } else if (typeof subCurrent === 'object') {\n useCurrent = { ...subCurrent };\n } else {\n useCurrent = subCurrent;\n }\n\n const nextStep = subscriber(...[useCurrent, ...args]);\n if ('once' in subscriber) {\n toClean.push({\n event: name,\n listener: subscriber,\n });\n }\n\n return nextStep;\n }),\n Promise.resolve(eventCurrent),\n );\n }),\n Promise.resolve(target),\n );\n\n toClean.forEach((info) => this.off(info.event, info.listener));\n\n return result;\n }\n /**\n * Synchronously reduces a target using an event. It's like emit, but the events\n * listener return a modified (or not) version of the `target`.\n *\n * @param event An event name or a list of them.\n * @param target The variable to reduce with the reducers/listeners.\n * @param args A list of parameters to send to the reducers/listeners.\n * @returns A version of the `target` processed by the listeners.\n * @template Target The type of the target.\n * @template Args The type of the parameters to send to the reducers/listeners.\n * @example\n *\n * const events = new EventsHub();\n * events.on('event', (target, arg0) => {\n * target.push(arg0);\n * return target;\n * });\n * events.reduce('event', [], 'Hello'); // returns ['Hello']\n *\n */\n reduceSync<Target, ReducerArgs extends GenericParams>(\n event: string | string[],\n target: Target,\n ...args: ReducerArgs\n ): Target {\n const events = Array.isArray(event) ? event : [event];\n const toClean: { event: string; listener: GenericFn }[] = [];\n const result = events.reduce((eventAcc, name) => {\n const subscribers = this.getSubscribers(name);\n return subscribers.reduce((subAcc, subscriber) => {\n let useCurrent;\n if (Array.isArray(subAcc)) {\n useCurrent = subAcc.slice();\n } else if (typeof subAcc === 'object') {\n useCurrent = { ...subAcc };\n } else {\n useCurrent = subAcc;\n }\n\n const nextStep = subscriber(...[useCurrent, ...args]);\n if ('once' in subscriber) {\n toClean.push({\n event: name,\n listener: subscriber,\n });\n }\n\n return nextStep;\n }, eventAcc);\n }, target);\n\n toClean.forEach((info) => this.off(info.event, info.listener));\n return result;\n }\n}\n/**\n * Shorthand for `new EventsHub()`.\n *\n * @returns A new instance of {@link EventsHub}.\n */\nexport const eventsHub = () => new EventsHub();\n"],"mappings":"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","names":[]}
package/dist/index.d.mts DELETED
@@ -1,123 +0,0 @@
1
- type GenericFn = (...args: any[]) => any;
2
- type GenericParams = Parameters<GenericFn>;
3
- type OnceWrapperFn = GenericFn & {
4
- once: boolean;
5
- };
6
- /**
7
- * When the service receives a "once subscription", it needs to track the listener so it
8
- * gets removed after being called once, and to avoid modifying the original function, it
9
- * creates a wrapper function that has the "once" property set to true. The wrapper and
10
- * the original function are stored in case `off` is called before the wrapper gets
11
- * triggered; it will receive the original function, not the wrapper, so the class needs a
12
- * way to map them together.
13
- */
14
- type OnceWrapper = {
15
- /**
16
- * A simple wrapper for the original listener, with the difference that it has a `once`
17
- * property set to `true`.
18
- */
19
- wrapper: OnceWrapperFn;
20
- /**
21
- * The original listener that will be called once.
22
- */
23
- original: GenericFn;
24
- };
25
- /**
26
- * A minimal implementation of an events handler service.
27
- */
28
- declare class EventsHub {
29
- /**
30
- * A dictionary of the events and their listeners.
31
- */
32
- protected events: Record<string, GenericFn[]>;
33
- /**
34
- * A dictionary of wrappers that were created for "one time subscriptions". This is
35
- * used by the {@link EventsHub.off}: if it doesn't find the subscriber as it is, it
36
- * will look for a wrapper and remove it.
37
- */
38
- protected onceWrappers: Record<string, OnceWrapper[]>;
39
- /**
40
- * Gets all the listeners for a specific event.
41
- * The list is returned by reference, so it can be modified once obtained.
42
- *
43
- * @param event The name of the event.
44
- */
45
- protected getSubscribers(event: string): (GenericFn | OnceWrapperFn)[];
46
- on<ListenerFn extends GenericFn = GenericFn>(event: string, listener: ListenerFn): () => boolean;
47
- on<ListenerFn extends GenericFn = GenericFn>(event: string[], listener: ListenerFn): () => boolean[];
48
- on<ListenerFn extends GenericFn = GenericFn>(event: string | string[], listener: ListenerFn): () => boolean | boolean[];
49
- once<ListenerFn extends GenericFn = GenericFn>(event: string, listener: ListenerFn): () => boolean;
50
- once<ListenerFn extends GenericFn = GenericFn>(event: string[], listener: ListenerFn): () => boolean[];
51
- once<ListenerFn extends GenericFn = GenericFn>(event: string | string[], listener: ListenerFn): () => boolean | boolean[];
52
- off<ListenerFn extends GenericFn | OnceWrapperFn = GenericFn>(event: string[], listener: ListenerFn): boolean[];
53
- off<ListenerFn extends GenericFn | OnceWrapperFn = GenericFn>(event: string, listener: ListenerFn): boolean;
54
- off<ListenerFn extends GenericFn | OnceWrapperFn = GenericFn>(event: string | string[], listener: ListenerFn): boolean | boolean[];
55
- /**
56
- * Emits an event and call all its listeners.
57
- *
58
- * @param event An event name or a list of them.
59
- * @param args A list of parameters to send to the listeners.
60
- * @template Args The type of the parameters to send to the listeners.
61
- * @example
62
- *
63
- * const events = new EventsHub();
64
- * events.on('event', (arg0) => {
65
- * console.log(`Event received: ${arg0}`);
66
- * });
67
- * events.emit('event', 'Hello'); // prints "Event received: Hello"
68
- *
69
- */
70
- emit<Args extends GenericParams>(event: string | string[], ...args: Args): void;
71
- /**
72
- * Asynchronously reduces a target using an event. It's like emit, but the events
73
- * listener return a modified (or not) version of the `target`.
74
- *
75
- * @param event An event name or a list of them.
76
- * @param target The variable to reduce with the reducers/listeners.
77
- * @param args A list of parameters to send to the reducers/listeners.
78
- * @returns A version of the `target` processed by the listeners.
79
- * @template Target The type of the target.
80
- * @template Args The type of the parameters to send to the reducers/listeners.
81
- * @example
82
- *
83
- * const events = new EventsHub();
84
- * events.on('event', async (target, arg0) => {
85
- * const data = await fetch(`https://api.example.com/${arg0}`);
86
- * target.push(data);
87
- * return target;
88
- * });
89
- * const result = await events.reduce('event', [], 'Hello');
90
- * // result would be a list of data fetched from the API.
91
- *
92
- */
93
- reduce<Target, Args extends GenericParams>(event: string | string[], target: Target, ...args: Args): Promise<Target>;
94
- /**
95
- * Synchronously reduces a target using an event. It's like emit, but the events
96
- * listener return a modified (or not) version of the `target`.
97
- *
98
- * @param event An event name or a list of them.
99
- * @param target The variable to reduce with the reducers/listeners.
100
- * @param args A list of parameters to send to the reducers/listeners.
101
- * @returns A version of the `target` processed by the listeners.
102
- * @template Target The type of the target.
103
- * @template Args The type of the parameters to send to the reducers/listeners.
104
- * @example
105
- *
106
- * const events = new EventsHub();
107
- * events.on('event', (target, arg0) => {
108
- * target.push(arg0);
109
- * return target;
110
- * });
111
- * events.reduce('event', [], 'Hello'); // returns ['Hello']
112
- *
113
- */
114
- reduceSync<Target, ReducerArgs extends GenericParams>(event: string | string[], target: Target, ...args: ReducerArgs): Target;
115
- }
116
- /**
117
- * Shorthand for `new EventsHub()`.
118
- *
119
- * @returns A new instance of {@link EventsHub}.
120
- */
121
- declare const eventsHub: () => EventsHub;
122
-
123
- export { EventsHub, eventsHub };
package/dist/index.d.ts DELETED
@@ -1,123 +0,0 @@
1
- type GenericFn = (...args: any[]) => any;
2
- type GenericParams = Parameters<GenericFn>;
3
- type OnceWrapperFn = GenericFn & {
4
- once: boolean;
5
- };
6
- /**
7
- * When the service receives a "once subscription", it needs to track the listener so it
8
- * gets removed after being called once, and to avoid modifying the original function, it
9
- * creates a wrapper function that has the "once" property set to true. The wrapper and
10
- * the original function are stored in case `off` is called before the wrapper gets
11
- * triggered; it will receive the original function, not the wrapper, so the class needs a
12
- * way to map them together.
13
- */
14
- type OnceWrapper = {
15
- /**
16
- * A simple wrapper for the original listener, with the difference that it has a `once`
17
- * property set to `true`.
18
- */
19
- wrapper: OnceWrapperFn;
20
- /**
21
- * The original listener that will be called once.
22
- */
23
- original: GenericFn;
24
- };
25
- /**
26
- * A minimal implementation of an events handler service.
27
- */
28
- declare class EventsHub {
29
- /**
30
- * A dictionary of the events and their listeners.
31
- */
32
- protected events: Record<string, GenericFn[]>;
33
- /**
34
- * A dictionary of wrappers that were created for "one time subscriptions". This is
35
- * used by the {@link EventsHub.off}: if it doesn't find the subscriber as it is, it
36
- * will look for a wrapper and remove it.
37
- */
38
- protected onceWrappers: Record<string, OnceWrapper[]>;
39
- /**
40
- * Gets all the listeners for a specific event.
41
- * The list is returned by reference, so it can be modified once obtained.
42
- *
43
- * @param event The name of the event.
44
- */
45
- protected getSubscribers(event: string): (GenericFn | OnceWrapperFn)[];
46
- on<ListenerFn extends GenericFn = GenericFn>(event: string, listener: ListenerFn): () => boolean;
47
- on<ListenerFn extends GenericFn = GenericFn>(event: string[], listener: ListenerFn): () => boolean[];
48
- on<ListenerFn extends GenericFn = GenericFn>(event: string | string[], listener: ListenerFn): () => boolean | boolean[];
49
- once<ListenerFn extends GenericFn = GenericFn>(event: string, listener: ListenerFn): () => boolean;
50
- once<ListenerFn extends GenericFn = GenericFn>(event: string[], listener: ListenerFn): () => boolean[];
51
- once<ListenerFn extends GenericFn = GenericFn>(event: string | string[], listener: ListenerFn): () => boolean | boolean[];
52
- off<ListenerFn extends GenericFn | OnceWrapperFn = GenericFn>(event: string[], listener: ListenerFn): boolean[];
53
- off<ListenerFn extends GenericFn | OnceWrapperFn = GenericFn>(event: string, listener: ListenerFn): boolean;
54
- off<ListenerFn extends GenericFn | OnceWrapperFn = GenericFn>(event: string | string[], listener: ListenerFn): boolean | boolean[];
55
- /**
56
- * Emits an event and call all its listeners.
57
- *
58
- * @param event An event name or a list of them.
59
- * @param args A list of parameters to send to the listeners.
60
- * @template Args The type of the parameters to send to the listeners.
61
- * @example
62
- *
63
- * const events = new EventsHub();
64
- * events.on('event', (arg0) => {
65
- * console.log(`Event received: ${arg0}`);
66
- * });
67
- * events.emit('event', 'Hello'); // prints "Event received: Hello"
68
- *
69
- */
70
- emit<Args extends GenericParams>(event: string | string[], ...args: Args): void;
71
- /**
72
- * Asynchronously reduces a target using an event. It's like emit, but the events
73
- * listener return a modified (or not) version of the `target`.
74
- *
75
- * @param event An event name or a list of them.
76
- * @param target The variable to reduce with the reducers/listeners.
77
- * @param args A list of parameters to send to the reducers/listeners.
78
- * @returns A version of the `target` processed by the listeners.
79
- * @template Target The type of the target.
80
- * @template Args The type of the parameters to send to the reducers/listeners.
81
- * @example
82
- *
83
- * const events = new EventsHub();
84
- * events.on('event', async (target, arg0) => {
85
- * const data = await fetch(`https://api.example.com/${arg0}`);
86
- * target.push(data);
87
- * return target;
88
- * });
89
- * const result = await events.reduce('event', [], 'Hello');
90
- * // result would be a list of data fetched from the API.
91
- *
92
- */
93
- reduce<Target, Args extends GenericParams>(event: string | string[], target: Target, ...args: Args): Promise<Target>;
94
- /**
95
- * Synchronously reduces a target using an event. It's like emit, but the events
96
- * listener return a modified (or not) version of the `target`.
97
- *
98
- * @param event An event name or a list of them.
99
- * @param target The variable to reduce with the reducers/listeners.
100
- * @param args A list of parameters to send to the reducers/listeners.
101
- * @returns A version of the `target` processed by the listeners.
102
- * @template Target The type of the target.
103
- * @template Args The type of the parameters to send to the reducers/listeners.
104
- * @example
105
- *
106
- * const events = new EventsHub();
107
- * events.on('event', (target, arg0) => {
108
- * target.push(arg0);
109
- * return target;
110
- * });
111
- * events.reduce('event', [], 'Hello'); // returns ['Hello']
112
- *
113
- */
114
- reduceSync<Target, ReducerArgs extends GenericParams>(event: string | string[], target: Target, ...args: ReducerArgs): Target;
115
- }
116
- /**
117
- * Shorthand for `new EventsHub()`.
118
- *
119
- * @returns A new instance of {@link EventsHub}.
120
- */
121
- declare const eventsHub: () => EventsHub;
122
-
123
- export { EventsHub, eventsHub };