Skip to content

Commit 7668b7c

Browse files
committed
add 'EventEmitter' & 'Event' modules.
1 parent b6426d4 commit 7668b7c

File tree

2 files changed

+117
-0
lines changed

2 files changed

+117
-0
lines changed

src/Event.re

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/**
2+
* The `Event` type represents the strings/symbols used in Node to
3+
* identify event types for `EventEmitter` and its subclasses, including
4+
* streams, sockets, and servers.
5+
*
6+
* Given a type signature `Event.t('a => 'b, 'ty)`, the first type
7+
* variable, `'a => 'b`, denotes the type signature of the event listener
8+
* function, and `'ty` denotes the type of the associated `EventEmitter`.
9+
*
10+
* These abstract `Event.t` types must be passed to `EventEmitter`
11+
* functions to register event listeners or emit events. By encoding the
12+
* listener function type in a type variable, we can ensure that each
13+
* listener has the correct type. The type parameter for the emitter
14+
* prevents two different emitters from using each other's events.
15+
*
16+
* While this gives us some degree of type safety, it is still possible
17+
* to introduce runtime errors with this API. In particular, two or more
18+
* `Event.t` types can be defined from the same string/symbol, but with
19+
* different listener types. Therefore, we strongly recommend using
20+
* 100% unique strings/symbols to define events.
21+
*
22+
*/
23+
type t('listener, 'ty);
24+
external fromString: string => t('a => 'b, 'ty) = "%identity";
25+
external fromSymbol: Js.Types.symbol => t('a => 'b, 'ty) = "%identity";
26+
external unsafeToString: t('a => 'b, 'ty) => string = "%identity";
27+
external unsafeToSymbol: t('a => 'b, 'ty) => Js.Types.symbol = "%identity";
28+
type case =
29+
| String(string)
30+
| Symbol(Js.Types.symbol)
31+
| Unknown;
32+
let classify = evt => {
33+
switch (Js.typeof(evt)) {
34+
| "string" => String(unsafeToString(evt))
35+
| "symbol" => Symbol(unsafeToSymbol(evt))
36+
| _ => Unknown
37+
};
38+
};
39+
let eq = (event1, event2) => {
40+
switch (Js.typeof(event1), Js.typeof(event2)) {
41+
| ("string", "string") => Obj.magic(event1) === Obj.magic(event2)
42+
| ("symbol", "symbol") => Obj.magic(event1) === Obj.magic(event2)
43+
| _ => false
44+
};
45+
};

src/EventEmitter.re

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/**
2+
* `Impl` is a functor which generates FFI bindings to Node's `EventEmitter`
3+
* class for any type `t`. This is not inherently type-safe. Type-safety can
4+
* be achieved by implementing the known `Event.t('a => 'b, t)` types
5+
*/
6+
module Impl = (T: {type t;}) => {
7+
[@bs.send]
8+
external addListener: (T.t, Event.t('a => 'b, T.t), 'a => 'b) => T.t =
9+
"addListener";
10+
[@bs.send]
11+
external emit: (T.t, Event.t('a => 'b, T.t), 'a) => bool = "emit";
12+
[@bs.get] external errorMonitor: T.t => Js.Types.symbol = "errorMonitor";
13+
[@bs.send]
14+
external eventNames:
15+
(T.t, Event.t('a => 'b, T.t)) => array(Event.t('a => 'b, T.t)) =
16+
"eventNames";
17+
[@bs.send] external getMaxListeners: T.t => int = "getMaxListeners";
18+
[@bs.send]
19+
external listenerCount: (T.t, Event.t('a => 'b, T.t)) => int =
20+
"listenerCount";
21+
[@bs.send]
22+
external listeners: (T.t, Event.t('a => 'b, T.t)) => array('a => 'b) =
23+
"listeners";
24+
[@bs.send]
25+
external on: (T.t, Event.t('a => 'b, T.t), 'a => 'b) => T.t = "on";
26+
[@bs.send]
27+
external once: (T.t, Event.t('a => 'b, T.t), 'a => 'b) => T.t = "once";
28+
[@bs.send]
29+
external off: (T.t, Event.t('a => 'b, T.t), 'a => 'b) => T.t = "off";
30+
[@bs.send]
31+
external prependListener: (T.t, Event.t('a => 'b, T.t), 'a => 'b) => T.t =
32+
"prependListener";
33+
[@bs.send]
34+
external prependOnceListener: (T.t, Event.t('a => 'b, T.t), 'a => 'b) => T.t =
35+
"prependOnceListener";
36+
[@bs.send] external removeAllListeners: T.t => T.t = "removeAllListeners";
37+
[@bs.send]
38+
external removeListener: (T.t, Event.t('a => 'b, T.t), 'a => 'b) => T.t =
39+
"removeListener";
40+
[@bs.send] external setMaxListeners: (T.t, int) => T.t = "setMaxListeners";
41+
42+
[@bs.send]
43+
external onNewListener:
44+
(
45+
T.t,
46+
[@bs.as "newListener"] _,
47+
(Event.t('a => 'b, T.t), 'a => 'b) => unit
48+
) =>
49+
T.t =
50+
"on";
51+
[@bs.send]
52+
external onRemoveListener:
53+
(
54+
T.t,
55+
[@bs.as "removeListener"] _,
56+
(Event.t('a => 'b, T.t), 'a => 'b) => unit
57+
) =>
58+
T.t =
59+
"on";
60+
};
61+
62+
/**
63+
* A generative functor that creates a unique type `t` with the `EventEmitter`
64+
* interface bindings.
65+
*/
66+
module Make = (()) => {
67+
type t;
68+
include Impl({
69+
type nonrec t = t;
70+
});
71+
[@bs.module "events"] [@bs.new] external make: unit => t = "EventEmitter";
72+
};

0 commit comments

Comments
 (0)