emittery 0.8.1 → 0.10.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.
Files changed (4) hide show
  1. package/index.d.ts +171 -6
  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
 
@@ -14,6 +16,122 @@ declare const listenerAdded: unique symbol;
14
16
  declare const listenerRemoved: unique symbol;
15
17
  type OmnipresentEventData = {[listenerAdded]: Emittery.ListenerChangedData; [listenerRemoved]: Emittery.ListenerChangedData};
16
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
+
17
135
  /**
18
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`.
19
137
 
@@ -50,6 +168,39 @@ declare class Emittery<
50
168
  AllEventData = EventData & OmnipresentEventData,
51
169
  DatalessEvents = DatalessEventNames<EventData>
52
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
+
53
204
  /**
54
205
  Fires when an event listener was added.
55
206
 
@@ -104,6 +255,18 @@ declare class Emittery<
104
255
  */
105
256
  static readonly listenerRemoved: typeof listenerRemoved;
106
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>);
269
+
107
270
  /**
108
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.
109
272
 
@@ -122,7 +285,7 @@ declare class Emittery<
122
285
  static mixin(
123
286
  emitteryPropertyName: string | symbol,
124
287
  methodNames?: readonly string[]
125
- ): <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
126
289
 
127
290
  /**
128
291
  Subscribe to one or more events.
@@ -140,6 +303,7 @@ declare class Emittery<
140
303
  emitter.on('🦄', data => {
141
304
  console.log(data);
142
305
  });
306
+
143
307
  emitter.on(['🦄', '🐶'], data => {
144
308
  console.log(data);
145
309
  });
@@ -149,7 +313,7 @@ declare class Emittery<
149
313
  ```
150
314
  */
151
315
  on<Name extends keyof AllEventData>(
152
- eventName: Name,
316
+ eventName: Name | Name[],
153
317
  listener: (eventData: AllEventData[Name]) => void | Promise<void>
154
318
  ): Emittery.UnsubscribeFn;
155
319
 
@@ -264,7 +428,7 @@ declare class Emittery<
264
428
  ```
265
429
  */
266
430
  off<Name extends keyof AllEventData>(
267
- eventName: Name,
431
+ eventName: Name | Name[],
268
432
  listener: (eventData: AllEventData[Name]) => void | Promise<void>
269
433
  ): void;
270
434
 
@@ -284,6 +448,7 @@ declare class Emittery<
284
448
  console.log(data);
285
449
  //=> '🌈'
286
450
  });
451
+
287
452
  emitter.once(['🦄', '🐶']).then(data => {
288
453
  console.log(data);
289
454
  });
@@ -292,7 +457,7 @@ declare class Emittery<
292
457
  emitter.emit('🐶', '🍖'); // Nothing happens
293
458
  ```
294
459
  */
295
- once<Name extends keyof AllEventData>(eventName: Name): Promise<AllEventData[Name]>;
460
+ once<Name extends keyof AllEventData>(eventName: Name | Name[]): Promise<AllEventData[Name]>;
296
461
 
297
462
  /**
298
463
  Trigger an event asynchronously, optionally with some data. Listeners are called in the order they were added, but executed concurrently.
@@ -383,12 +548,12 @@ declare class Emittery<
383
548
 
384
549
  If `eventName` is given, only the listeners for that event are cleared.
385
550
  */
386
- clearListeners(eventName?: keyof EventData): void;
551
+ clearListeners<Name extends keyof EventData>(eventName?: Name | Name[]): void;
387
552
 
388
553
  /**
389
554
  The number of listeners for the `eventName` or all events if not specified.
390
555
  */
391
- listenerCount(eventName?: keyof EventData): number;
556
+ listenerCount<Name extends keyof EventData>(eventName?: Name | Name[]): number;
392
557
 
393
558
  /**
394
559
  Bind the given `methodNames`, or all `Emittery` methods if `methodNames` is not defined, into the `target` object.
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.1",
3
+ "version": "0.10.0",
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?