emittery 0.8.0 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/index.d.ts +179 -16
  2. package/index.js +57 -1
  3. package/package.json +5 -5
  4. package/readme.md +162 -2
package/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ /* eslint-disable no-redeclare */
2
+
1
3
  /**
2
4
  Emittery accepts strings and symbols as event names.
3
5
 
@@ -10,6 +12,126 @@ type DatalessEventNames<EventData> = {
10
12
  [Key in keyof EventData]: EventData[Key] extends undefined ? Key : never;
11
13
  }[keyof EventData];
12
14
 
15
+ declare const listenerAdded: unique symbol;
16
+ declare const listenerRemoved: unique symbol;
17
+ type OmnipresentEventData = {[listenerAdded]: Emittery.ListenerChangedData; [listenerRemoved]: Emittery.ListenerChangedData};
18
+
19
+ /**
20
+ Emittery can collect and log debug information.
21
+
22
+ To enable this feature set the `DEBUG` environment variable to `emittery` or `*`. Additionally, you can set the static `isDebugEnabled` variable to true on the Emittery class, or `myEmitter.debug.enabled` on an instance of it for debugging a single instance.
23
+
24
+ See API for more information on how debugging works.
25
+ */
26
+ type DebugLogger<EventData, Name extends keyof EventData> = (type: string, debugName: string, eventName?: Name, eventData?: EventData[Name]) => void;
27
+
28
+ /**
29
+ Configure debug options of an instance.
30
+ */
31
+ interface DebugOptions<EventData> {
32
+ /**
33
+ Define a name for the instance of Emittery to use when outputting debug data.
34
+
35
+ @default undefined
36
+
37
+ @example
38
+ ```
39
+ import Emittery = require('emittery');
40
+
41
+ Emittery.isDebugEnabled = true;
42
+
43
+ const emitter = new Emittery({debug: {name: 'myEmitter'}});
44
+
45
+ emitter.on('test', data => {
46
+ // …
47
+ });
48
+
49
+ emitter.emit('test');
50
+ //=> [16:43:20.417][emittery:subscribe][myEmitter] Event Name: test
51
+ // data: undefined
52
+ ```
53
+ */
54
+ readonly name: string;
55
+
56
+ /**
57
+ Toggle debug logging just for this instance.
58
+
59
+ @default false
60
+
61
+ @example
62
+ ```
63
+ import Emittery = require('emittery');
64
+
65
+ const emitter1 = new Emittery({debug: {name: 'emitter1', enabled: true}});
66
+ const emitter2 = new Emittery({debug: {name: 'emitter2'}});
67
+
68
+ emitter1.on('test', data => {
69
+ // …
70
+ });
71
+
72
+ emitter2.on('test', data => {
73
+ // …
74
+ });
75
+
76
+ emitter1.emit('test');
77
+ //=> [16:43:20.417][emittery:subscribe][emitter1] Event Name: test
78
+ // data: undefined
79
+
80
+ emitter2.emit('test');
81
+ ```
82
+ */
83
+ enabled?: boolean;
84
+
85
+ /**
86
+ Function that handles debug data.
87
+
88
+ @default
89
+ ```
90
+ (type, debugName, eventName, eventData) => {
91
+ eventData = JSON.stringify(eventData);
92
+
93
+ if (typeof eventName === 'symbol') {
94
+ eventName = eventName.toString();
95
+ }
96
+
97
+ const currentTime = new Date();
98
+ const logTime = `${currentTime.getHours()}:${currentTime.getMinutes()}:${currentTime.getSeconds()}.${currentTime.getMilliseconds()}`;
99
+ console.log(`[${logTime}][emittery:${type}][${debugName}] Event Name: ${eventName}\n\tdata: ${eventData}`);
100
+ }
101
+ ```
102
+
103
+ @example
104
+ ```
105
+ import Emittery = require('emittery');
106
+
107
+ const myLogger = (type, debugName, eventName, eventData) => console.log(`[${type}]: ${eventName}`);
108
+
109
+ const emitter = new Emittery({
110
+ debug: {
111
+ name: 'myEmitter',
112
+ enabled: true,
113
+ logger: myLogger
114
+ }
115
+ });
116
+
117
+ emitter.on('test', data => {
118
+ // …
119
+ });
120
+
121
+ emitter.emit('test');
122
+ //=> [subscribe]: test
123
+ ```
124
+ */
125
+ logger?: DebugLogger<EventData, keyof EventData>;
126
+ }
127
+
128
+ /**
129
+ Configuration options for Emittery.
130
+ */
131
+ interface Options<EventData> {
132
+ debug?: DebugOptions<EventData>;
133
+ }
134
+
13
135
  /**
14
136
  Emittery is a strictly typed, fully async EventEmitter implementation. Event listeners can be registered with `on` or `once`, and events can be emitted with `emit`.
15
137
 
@@ -43,8 +165,42 @@ emitter.emit('other');
43
165
  */
44
166
  declare class Emittery<
45
167
  EventData = Record<string, any>, // When https://github.com/microsoft/TypeScript/issues/1863 ships, we can switch this to have an index signature including Symbols. If you want to use symbol keys right now, you need to pass an interface with those symbol keys explicitly listed.
168
+ AllEventData = EventData & OmnipresentEventData,
46
169
  DatalessEvents = DatalessEventNames<EventData>
47
170
  > {
171
+ /**
172
+ Toggle debug mode for all instances.
173
+
174
+ Default: `true` if the `DEBUG` environment variable is set to `emittery` or `*`, otherwise `false`.
175
+
176
+ @example
177
+ ```
178
+ import Emittery = require('emittery');
179
+
180
+ Emittery.isDebugEnabled = true;
181
+
182
+ const emitter1 = new Emittery({debug: {name: 'myEmitter1'}});
183
+ const emitter2 = new Emittery({debug: {name: 'myEmitter2'}});
184
+
185
+ emitter1.on('test', data => {
186
+ // …
187
+ });
188
+
189
+ emitter2.on('otherTest', data => {
190
+ // …
191
+ });
192
+
193
+ emitter1.emit('test');
194
+ //=> [16:43:20.417][emittery:subscribe][myEmitter1] Event Name: test
195
+ // data: undefined
196
+
197
+ emitter2.emit('otherTest');
198
+ //=> [16:43:20.417][emittery:subscribe][myEmitter2] Event Name: otherTest
199
+ // data: undefined
200
+ ```
201
+ */
202
+ static isDebugEnabled: boolean;
203
+
48
204
  /**
49
205
  Fires when an event listener was added.
50
206
 
@@ -69,7 +225,7 @@ declare class Emittery<
69
225
  });
70
226
  ```
71
227
  */
72
- static readonly listenerAdded: unique symbol;
228
+ static readonly listenerAdded: typeof listenerAdded;
73
229
 
74
230
  /**
75
231
  Fires when an event listener was removed.
@@ -97,7 +253,19 @@ declare class Emittery<
97
253
  off();
98
254
  ```
99
255
  */
100
- static readonly listenerRemoved: unique symbol;
256
+ static readonly listenerRemoved: typeof listenerRemoved;
257
+
258
+ /**
259
+ Debugging options for the current instance.
260
+ */
261
+ debug: DebugOptions<EventData>;
262
+
263
+ /**
264
+ Create a new Emittery instance with the specified options.
265
+
266
+ @returns An instance of Emittery that you can use to listen for and emit events.
267
+ */
268
+ constructor(options?: Options<EventData>);
101
269
 
102
270
  /**
103
271
  In TypeScript, it returns a decorator which mixins `Emittery` as property `emitteryPropertyName` and `methodNames`, or all `Emittery` methods if `methodNames` is not defined, into the target class.
@@ -117,7 +285,7 @@ declare class Emittery<
117
285
  static mixin(
118
286
  emitteryPropertyName: string | symbol,
119
287
  methodNames?: readonly string[]
120
- ): <T extends { new (): any }>(klass: T) => T; // eslint-disable-line @typescript-eslint/prefer-function-type
288
+ ): <T extends {new (): any}>(klass: T) => T; // eslint-disable-line @typescript-eslint/prefer-function-type
121
289
 
122
290
  /**
123
291
  Subscribe to one or more events.
@@ -135,6 +303,7 @@ declare class Emittery<
135
303
  emitter.on('🦄', data => {
136
304
  console.log(data);
137
305
  });
306
+
138
307
  emitter.on(['🦄', '🐶'], data => {
139
308
  console.log(data);
140
309
  });
@@ -143,13 +312,9 @@ declare class Emittery<
143
312
  emitter.emit('🐶', '🍖'); // log => '🍖'
144
313
  ```
145
314
  */
146
- on<Name extends keyof EventData>(
315
+ on<Name extends keyof AllEventData>(
147
316
  eventName: Name,
148
- listener: (eventData: EventData[Name]) => void | Promise<void>
149
- ): Emittery.UnsubscribeFn;
150
- on(
151
- eventName: typeof Emittery.listenerAdded | typeof Emittery.listenerRemoved,
152
- listener: (eventData: Emittery.ListenerChangedData) => void | Promise<void>
317
+ listener: (eventData: AllEventData[Name]) => void | Promise<void>
153
318
  ): Emittery.UnsubscribeFn;
154
319
 
155
320
  /**
@@ -236,7 +401,7 @@ declare class Emittery<
236
401
  ```
237
402
  */
238
403
  events<Name extends keyof EventData>(
239
- eventName: Name
404
+ eventName: Name | Name[]
240
405
  ): AsyncIterableIterator<EventData[Name]>;
241
406
 
242
407
  /**
@@ -262,9 +427,9 @@ declare class Emittery<
262
427
  })();
263
428
  ```
264
429
  */
265
- off<Name extends keyof EventData>(
430
+ off<Name extends keyof AllEventData>(
266
431
  eventName: Name,
267
- listener: (eventData: EventData[Name]) => void | Promise<void>
432
+ listener: (eventData: AllEventData[Name]) => void | Promise<void>
268
433
  ): void;
269
434
 
270
435
  /**
@@ -283,6 +448,7 @@ declare class Emittery<
283
448
  console.log(data);
284
449
  //=> '🌈'
285
450
  });
451
+
286
452
  emitter.once(['🦄', '🐶']).then(data => {
287
453
  console.log(data);
288
454
  });
@@ -291,10 +457,7 @@ declare class Emittery<
291
457
  emitter.emit('🐶', '🍖'); // Nothing happens
292
458
  ```
293
459
  */
294
- once<Name extends keyof EventData>(eventName: Name): Promise<EventData[Name]>;
295
- once(
296
- eventName: typeof Emittery.listenerAdded | typeof Emittery.listenerRemoved
297
- ): Promise<Emittery.ListenerChangedData>;
460
+ once<Name extends keyof AllEventData>(eventName: Name): Promise<AllEventData[Name]>;
298
461
 
299
462
  /**
300
463
  Trigger an event asynchronously, optionally with some data. Listeners are called in the order they were added, but executed concurrently.
package/index.js CHANGED
@@ -9,6 +9,8 @@ const resolvedPromise = Promise.resolve();
9
9
  const listenerAdded = Symbol('listenerAdded');
10
10
  const listenerRemoved = Symbol('listenerRemoved');
11
11
 
12
+ let isGlobalDebugEnabled = false;
13
+
12
14
  function assertEventName(eventName) {
13
15
  if (typeof eventName !== 'string' && typeof eventName !== 'symbol') {
14
16
  throw new TypeError('eventName must be a string or a symbol');
@@ -189,10 +191,48 @@ class Emittery {
189
191
  };
190
192
  }
191
193
 
192
- constructor() {
194
+ static get isDebugEnabled() {
195
+ if (typeof process !== 'object') {
196
+ return isGlobalDebugEnabled;
197
+ }
198
+
199
+ const {env} = process || {env: {}};
200
+ return env.DEBUG === 'emittery' || env.DEBUG === '*' || isGlobalDebugEnabled;
201
+ }
202
+
203
+ static set isDebugEnabled(newValue) {
204
+ isGlobalDebugEnabled = newValue;
205
+ }
206
+
207
+ constructor(options = {}) {
193
208
  anyMap.set(this, new Set());
194
209
  eventsMap.set(this, new Map());
195
210
  producersMap.set(this, new Map());
211
+ this.debug = options.debug || {};
212
+
213
+ if (this.debug.enabled === undefined) {
214
+ this.debug.enabled = false;
215
+ }
216
+
217
+ if (!this.debug.logger) {
218
+ this.debug.logger = (type, debugName, eventName, eventData) => {
219
+ eventData = JSON.stringify(eventData);
220
+
221
+ if (typeof eventName === 'symbol') {
222
+ eventName = eventName.toString();
223
+ }
224
+
225
+ const currentTime = new Date();
226
+ const logTime = `${currentTime.getHours()}:${currentTime.getMinutes()}:${currentTime.getSeconds()}.${currentTime.getMilliseconds()}`;
227
+ console.log(`[${logTime}][emittery:${type}][${debugName}] Event Name: ${eventName}\n\tdata: ${eventData}`);
228
+ };
229
+ }
230
+ }
231
+
232
+ logIfDebugEnabled(type, eventName, eventData) {
233
+ if (Emittery.isDebugEnabled || this.debug.enabled) {
234
+ this.debug.logger(type, this.debug.name, eventName, eventData);
235
+ }
196
236
  }
197
237
 
198
238
  on(eventNames, listener) {
@@ -203,6 +243,8 @@ class Emittery {
203
243
  assertEventName(eventName);
204
244
  getListeners(this, eventName).add(listener);
205
245
 
246
+ this.logIfDebugEnabled('subscribe', eventName, undefined);
247
+
206
248
  if (!isListenerSymbol(eventName)) {
207
249
  this.emit(listenerAdded, {eventName, listener});
208
250
  }
@@ -219,6 +261,8 @@ class Emittery {
219
261
  assertEventName(eventName);
220
262
  getListeners(this, eventName).delete(listener);
221
263
 
264
+ this.logIfDebugEnabled('unsubscribe', eventName, undefined);
265
+
222
266
  if (!isListenerSymbol(eventName)) {
223
267
  this.emit(listenerRemoved, {eventName, listener});
224
268
  }
@@ -246,6 +290,8 @@ class Emittery {
246
290
  async emit(eventName, eventData) {
247
291
  assertEventName(eventName);
248
292
 
293
+ this.logIfDebugEnabled('emit', eventName, eventData);
294
+
249
295
  enqueueProducers(this, eventName, eventData);
250
296
 
251
297
  const listeners = getListeners(this, eventName);
@@ -271,6 +317,8 @@ class Emittery {
271
317
  async emitSerial(eventName, eventData) {
272
318
  assertEventName(eventName);
273
319
 
320
+ this.logIfDebugEnabled('emitSerial', eventName, eventData);
321
+
274
322
  const listeners = getListeners(this, eventName);
275
323
  const anyListeners = anyMap.get(this);
276
324
  const staticListeners = [...listeners];
@@ -294,6 +342,9 @@ class Emittery {
294
342
 
295
343
  onAny(listener) {
296
344
  assertListener(listener);
345
+
346
+ this.logIfDebugEnabled('subscribeAny', undefined, undefined);
347
+
297
348
  anyMap.get(this).add(listener);
298
349
  this.emit(listenerAdded, {listener});
299
350
  return this.offAny.bind(this, listener);
@@ -305,6 +356,9 @@ class Emittery {
305
356
 
306
357
  offAny(listener) {
307
358
  assertListener(listener);
359
+
360
+ this.logIfDebugEnabled('unsubscribeAny', undefined, undefined);
361
+
308
362
  this.emit(listenerRemoved, {listener});
309
363
  anyMap.get(this).delete(listener);
310
364
  }
@@ -313,6 +367,8 @@ class Emittery {
313
367
  eventNames = Array.isArray(eventNames) ? eventNames : [eventNames];
314
368
 
315
369
  for (const eventName of eventNames) {
370
+ this.logIfDebugEnabled('clear', eventName, undefined);
371
+
316
372
  if (typeof eventName === 'string' || typeof eventName === 'symbol') {
317
373
  getListeners(this, eventName).clear();
318
374
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "emittery",
3
- "version": "0.8.0",
3
+ "version": "0.9.2",
4
4
  "description": "Simple and modern async event emitter",
5
5
  "license": "MIT",
6
6
  "repository": "sindresorhus/emittery",
@@ -11,7 +11,7 @@
11
11
  "url": "https://sindresorhus.com"
12
12
  },
13
13
  "engines": {
14
- "node": ">=10"
14
+ "node": ">=12"
15
15
  },
16
16
  "scripts": {
17
17
  "test": "xo && nyc ava && tsd"
@@ -48,13 +48,13 @@
48
48
  "typed"
49
49
  ],
50
50
  "devDependencies": {
51
- "@types/node": "^13.7.5",
51
+ "@types/node": "^15.6.1",
52
52
  "ava": "^2.4.0",
53
53
  "delay": "^4.3.0",
54
54
  "nyc": "^15.0.0",
55
55
  "p-event": "^4.1.0",
56
- "tsd": "^0.14.0",
57
- "xo": "^0.36.1"
56
+ "tsd": "^0.16.0",
57
+ "xo": "^0.39.0"
58
58
  },
59
59
  "nyc": {
60
60
  "reporter": [
package/readme.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > Simple and modern async event emitter
4
4
 
5
- [![Coverage Status](https://codecov.io/gh/sindresorhus/emittery/branch/master/graph/badge.svg)](https://codecov.io/gh/sindresorhus/emittery)
5
+ [![Coverage Status](https://codecov.io/gh/sindresorhus/emittery/branch/main/graph/badge.svg)](https://codecov.io/gh/sindresorhus/emittery)
6
6
  [![](https://badgen.net/bundlephobia/minzip/emittery)](https://bundlephobia.com/result?p=emittery)
7
7
 
8
8
  It works in Node.js and the browser (using a bundler).
@@ -44,7 +44,156 @@ Emittery accepts strings and symbols as event names.
44
44
 
45
45
  Symbol event names can be used to avoid name collisions when your classes are extended, especially for internal events.
46
46
 
47
- ### emitter = new Emittery()
47
+ ### isDebugEnabled
48
+
49
+ Toggle debug mode for all instances.
50
+
51
+ Default: `true` if the `DEBUG` environment variable is set to `emittery` or `*`, otherwise `false`.
52
+
53
+ Example:
54
+
55
+ ```js
56
+ const Emittery = require('emittery');
57
+
58
+ Emittery.isDebugEnabled = true;
59
+
60
+ const emitter1 = new Emittery({debug: {name: 'myEmitter1'}});
61
+ const emitter2 = new Emittery({debug: {name: 'myEmitter2'}});
62
+
63
+ emitter1.on('test', data => {
64
+ // …
65
+ });
66
+
67
+ emitter2.on('otherTest', data => {
68
+ // …
69
+ });
70
+
71
+ emitter1.emit('test');
72
+ //=> [16:43:20.417][emittery:subscribe][myEmitter1] Event Name: test
73
+ // data: undefined
74
+
75
+ emitter2.emit('otherTest');
76
+ //=> [16:43:20.417][emittery:subscribe][myEmitter2] Event Name: otherTest
77
+ // data: undefined
78
+ ```
79
+
80
+ ### emitter = new Emittery(options?)
81
+
82
+ Create a new instance of Emittery.
83
+
84
+ #### options?
85
+
86
+ Type: `object`
87
+
88
+ Configure the new instance of Emittery.
89
+
90
+ ##### debug?
91
+
92
+ Type: `objcect`
93
+
94
+ Configure the debugging options for this instance.
95
+
96
+ ###### name
97
+
98
+ Type: `string`\
99
+ Default: `undefined`
100
+
101
+ Define a name for the instance of Emittery to use when outputting debug data.
102
+
103
+ Example:
104
+
105
+ ```js
106
+ const Emittery = require('emittery');
107
+
108
+ Emittery.isDebugEnabled = true;
109
+
110
+ const emitter = new Emittery({debug: {name: 'myEmitter'}});
111
+
112
+ emitter.on('test', data => {
113
+ // …
114
+ });
115
+
116
+ emitter.emit('test');
117
+ //=> [16:43:20.417][emittery:subscribe][myEmitter] Event Name: test
118
+ // data: undefined
119
+ ```
120
+
121
+ ###### enabled?
122
+
123
+ Type: `boolean`\
124
+ Default: `false`
125
+
126
+ Toggle debug logging just for this instance.
127
+
128
+ Example:
129
+
130
+ ```js
131
+ const Emittery = require('emittery');
132
+
133
+ const emitter1 = new Emittery({debug: {name: 'emitter1', enabled: true}});
134
+ const emitter2 = new Emittery({debug: {name: 'emitter2'}});
135
+
136
+ emitter1.on('test', data => {
137
+ // …
138
+ });
139
+
140
+ emitter2.on('test', data => {
141
+ // …
142
+ });
143
+
144
+ emitter1.emit('test');
145
+ //=> [16:43:20.417][emittery:subscribe][emitter1] Event Name: test
146
+ // data: undefined
147
+
148
+ emitter2.emit('test');
149
+ ```
150
+
151
+ ###### logger?
152
+
153
+ Type: `Function(string, string, EventName?, Record<string, any>?) => void`
154
+
155
+ Default:
156
+
157
+ ```js
158
+ (type, debugName, eventName, eventData) => {
159
+ if (typeof eventData === 'object') {
160
+ eventData = JSON.stringify(eventData);
161
+ }
162
+
163
+ if (typeof eventName === 'symbol') {
164
+ eventName = eventName.toString();
165
+ }
166
+
167
+ const currentTime = new Date();
168
+ const logTime = `${currentTime.getHours()}:${currentTime.getMinutes()}:${currentTime.getSeconds()}.${currentTime.getMilliseconds()}`;
169
+ console.log(`[${logTime}][emittery:${type}][${debugName}] Event Name: ${eventName}\n\tdata: ${eventData}`);
170
+ }
171
+ ```
172
+
173
+ Function that handles debug data.
174
+
175
+ Example:
176
+
177
+ ```js
178
+ const Emittery = require('emittery');
179
+
180
+ const myLogger = (type, debugName, eventName, eventData) => console.log(`[${type}]: ${eventName}`);
181
+
182
+ const emitter = new Emittery({
183
+ debug: {
184
+ name: 'myEmitter',
185
+ enabled: true,
186
+ logger: myLogger
187
+ }
188
+ });
189
+
190
+ emitter.on('test', data => {
191
+ // …
192
+ });
193
+
194
+ emitter.emit('test');
195
+ //=> [subscribe]: test
196
+ ```
48
197
 
49
198
  #### on(eventName | eventName[], listener)
50
199
 
@@ -62,6 +211,7 @@ const emitter = new Emittery();
62
211
  emitter.on('🦄', data => {
63
212
  console.log(data);
64
213
  });
214
+
65
215
  emitter.on(['🦄', '🐶'], data => {
66
216
  console.log(data);
67
217
  });
@@ -114,6 +264,7 @@ const Emittery = require('emittery');
114
264
  const emitter = new Emittery();
115
265
 
116
266
  const listener = data => console.log(data);
267
+
117
268
  (async () => {
118
269
  emitter.on(['🦄', '🐶', '🦊'], listener);
119
270
  await emitter.emit('🦄', 'a');
@@ -144,6 +295,7 @@ emitter.once('🦄').then(data => {
144
295
  console.log(data);
145
296
  //=> '🌈'
146
297
  });
298
+
147
299
  emitter.once(['🦄', '🐶']).then(data => {
148
300
  console.log(data);
149
301
  });
@@ -365,6 +517,14 @@ Listeners are not invoked for events emitted *before* the listener was added. Re
365
517
 
366
518
  Note that when using `.emitSerial()`, a slow listener will delay invocation of subsequent listeners. It's possible for newer events to overtake older ones.
367
519
 
520
+ ## Debugging
521
+
522
+ Emittery can collect and log debug information.
523
+
524
+ To enable this feature set the DEBUG environment variable to 'emittery' or '*'. Additionally you can set the static `isDebugEnabled` variable to true on the Emittery class, or `myEmitter.debug.enabled` on an instance of it for debugging a single instance.
525
+
526
+ See [API](#api) for more details on how debugging works.
527
+
368
528
  ## FAQ
369
529
 
370
530
  ### How is this different than the built-in `EventEmitter` in Node.js?