8000 feat: add support for typed events · socketio/emitter@84397cb · GitHub
[go: up one dir, main page]

Skip to content

Commit 84397cb

Browse files
feat: add support for typed events
The StrictEventEmitter class that was defined in the socket.io-client repository ([1]) is moved here, so we don't need to create an intermediary class (Socket > StrictEventEmitter > Emitter) to get the proper types. As an additional benefit, the final bundle size should be decreased. BREAKING CHANGE: we now use a named export instead of a default export ```js // before import Emitter from "@socket.io/component-emitter" // after import { Emitter } from "@socket.io/component-emitter" ``` [1]: https://github.com/socketio/socket.io-client/blob/a9e5b85580e8edca0b0fd2850c3741d3d86a96e2/lib/typed-events.ts
1 parent 59b4bad commit 84397cb

File tree

3 files changed

+182
-23
lines changed

3 files changed

+182
-23
lines changed

index.d.ts

Lines changed: 176 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,179 @@
1-
interface Emitter<Event = string> {
2-
on(event: Event, listener: Function): Emitter;
3-
once(event: Event, listener: Function): Emitter;
4-
off(event?: Event, listener?: Function): Emitter;
5-
emit(event: Event, ...args: any[]): Emitter;
6-
listeners(event: Event): Function[];
7-
hasListeners(event: Event): boolean;
8-
removeListener(event?: Event, listener?: Function): Emitter;
9-
removeEventListener(event?: Event, listener?: Function): Emitter;
10-
removeAllListeners(event?: Event): Emitter;
1+
/**
2+
* An events map is an interface that maps event names to their value, which
3+
* represents the type of the `on` listener.
4+
*/
5+
export interface EventsMap {
6+
[event: string]: any;
117
}
128

13-
declare const Emitter: {
14-
(obj?: object): Emitter;
15-
new (obj?: object): Emitter;
16-
};
9+
/**
10+
* The default events map, used if no EventsMap is given. Using this EventsMap
11+
* is equivalent to accepting all event names, and any data.
12+
*/
13+
export interface DefaultEventsMap {
14+
[event: string]: (...args: any[]) => void;
15+
}
16+
17+
/**
18+
* Returns a union type containing all the keys of an event map.
19+
*/
20+
export type EventNames<Map extends EventsMap> = keyof Map & (string | symbol);
21+
22+
/** The tuple type representing the parameters of an event listener */
23+
export type EventParams<
24+
Map extends EventsMap,
25+
Ev extends EventNames<Map>
26+
> = Parameters<Map[Ev]>;
27+
28+
/**
29+
* The event names that are either in ReservedEvents or in UserEvents
30+
*/
31+
export type ReservedOrUserEventNames<
32+
ReservedEventsMap extends EventsMap,
33+
UserEvents extends EventsMap
34+
> = EventNames<ReservedEventsMap> | EventNames<UserEvents>;
35+
36+
/**
37+
* Type of a listener of a user event or a reserved event. If `Ev` is in
38+
* `ReservedEvents`, the reserved event listener is returned.
39+
*/
40+
export type ReservedOrUserListener<
41+
ReservedEvents extends EventsMap,
42+
UserEvents extends EventsMap,
43+
Ev extends ReservedOrUserEventNames<ReservedEvents, UserEvents>
44+
> = FallbackToUntypedListener<
45+
Ev extends EventNames<ReservedEvents>
46+
? ReservedEvents[Ev]
47+
: Ev extends EventNames<UserEvents>
48+
? UserEvents[Ev]
49+
: never
50+
>;
51+
52+
/**
53+
* Returns an untyped listener type if `T` is `never`; otherwise, returns `T`.
54+
*
55+
* This is a hack to mitigate https://github.com/socketio/socket.io/issues/3833.
56+
* Needed because of https://github.com/microsoft/TypeScript/issues/41778
57+
*/
58+
type FallbackToUntypedListener<T> = [T] extends [never]
59+
? (...args: any[]) => void | Promise<void>
60+
: T;
61+
62+
/**
63+
* Strictly typed version of an `EventEmitter`. A `TypedEventEmitter` takes type
64+
* parameters for mappings of event names to event data types, and strictly
65+
* types method calls to the `EventEmitter` according to these event maps.
66+
*
67+
* @typeParam ListenEvents - `EventsMap` of user-defined events that can be
68+
* listened to with `on` or `once`
69+
* @typeParam EmitEvents - `EventsMap` of user-defined events that can be
70+
* emitted with `emit`
71+
* @typeParam ReservedEvents - `EventsMap` of reserved events, that can be
72+
* emitted by socket.io with `emitReserved`, and can be listened to with
73+
* `listen`.
74+
*/
75+
export class Emitter<
76+
ListenEvents extends EventsMap,
77+
EmitEvents extends EventsMap,
78+
ReservedEvents extends EventsMap = {}
79+
> {
80+
/**
81+
* Adds the `listener` function as an event listener for `ev`.
82+
*
83+
* @param ev Name of the event
84+
* @param listener Callback function
85+
*/
86+
on<Ev extends ReservedOrUserEventNames<ReservedEvents, ListenEvents>>(
87+
ev: Ev,
88+
listener: ReservedOrUserListener<ReservedEvents, ListenEvents, Ev>
89+
): this;
90+
91+
/**
92+
* Adds a one-time `listener` function as an event listener for `ev`.
93+
*
94+
* @param ev Name of the event
95+
* @param listener Callback function
96+
*/
97+
once<Ev extends ReservedOrUserEventNames<ReservedEvents, ListenEvents>>(
98+
ev: Ev,
99+
listener: ReservedOrUserListener<ReservedEvents, ListenEvents, Ev>
100+
): this;
17101

18-
export default Emitter;
102+
/**
103+
* Removes the `listener` function as an event listener for `ev`.
104+
*
105+
* @param ev Name of the event
106+
* @param listener Callback function
107+
*/
108+
off<Ev extends ReservedOrUserEventNames<ReservedEvents, ListenEvents>>(
109+
ev?: Ev,
110+
listener?: ReservedOrUserListener<ReservedEvents, ListenEvents, Ev>
111+
): this;
112+
113+
/**
114+
* Emits an event.
115+
*
116+
* @param ev Name of the event
117+
* @param args Values to send to listeners of this event
118+
*/
119+
emit<Ev extends EventNames<EmitEvents>>(
120+
ev: Ev,
121+
...args: EventParams<EmitEvents, Ev>
122+
): this;
123+
124+
/**
125+
* Emits a reserved event.
126+
*
127+
* This method is `protected`, so that only a class extending
128+
* `StrictEventEmitter` can emit its own reserved events.
129+
*
130+
* @param ev Reserved event name
131+
* @param args Arguments to emit along with the event
132+
*/
133+
protected emitReserved<Ev extends EventNames<ReservedEvents>>(
134+
ev: Ev,
135+
...args: EventParams<ReservedEvents, Ev>
136+
): this;
137+
138+
/**
139+
* Returns the listeners listening to an event.
140+
*
141+
* @param event Event name
142+
* @returns Array of listeners subscribed to `event`
143+
*/
144+
listeners<Ev extends ReservedOrUserEventNames<ReservedEvents, ListenEvents>>(
145+
event: Ev
146+
): ReservedOrUserListener<ReservedEvents, ListenEvents, Ev>[];
147+
148+
/**
149+
* Returns true if there is a listener for this event.
150+
*
151+
* @param event Event name
152+
* @returns boolean
153+
*/
154+
hasListeners<
155+
Ev extends ReservedOrUserEventNames<ReservedEvents, ListenEvents>
156+
>(event: Ev): boolean;
157+
158+
/**
159+
* Removes the `listener` function as an event listener for `ev`.
160+
*
161+
* @param ev Name of the event
162+
* @param listener Callback function
163+
*/
164+
removeListener<
165+
Ev extends ReservedOrUserEventNames<ReservedEvents, ListenEvents>
166+
>(
167+
ev?: Ev,
168+
listener?: ReservedOrUserListener<ReservedEvents, ListenEvents, Ev>
169+
): this;
170+
171+
/**
172+
* Removes all `listener` function as an event listener for `ev`.
173+
*
174+
* @param ev Name of the event
175+
*/
176+
removeAllListeners<
177+
Ev extends ReservedOrUserEventNames<ReservedEvents, ListenEvents>
178+
>(ev?: Ev): this;
179+
}

index.js

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33
* Expose `Emitter`.
44
*/
55

6-
if (typeof module !== 'undefined') {
7-
module.exports = Emitter;
8-
}
6+
exports.Emitter = Emitter;
97

108
/**
119
* Initialize a new `Emitter`.
@@ -15,10 +13,7 @@ if (typeof module !== 'undefined') {
1513

1614
function Emitter(obj) {
1715
if (obj) return mixin(obj);
18-
};
19-
20 D23E -
// allow default import
21-
Emitter.default = Emitter;
16+
}
2217

2318
/**
2419
* Mixin the emitter properties.
@@ -152,6 +147,9 @@ Emitter.prototype.emit = function(event){
152147
return this;
153148
};
154149

150+
// alias used for reserved events (protected method)
151+
Emitter.prototype.emitReserved = Emitter.prototype.emit;
152+
155153
/**
156154
* Return array of callbacks for `event`.
157155
*

test/emitter.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11

2-
var Emitter = require('..');
2+
var { Emitter } = require('..');
33

44
function Custom() {
55
Emitter.call(this)

0 commit comments

Comments
 (0)
0