emittery 1.2.0 → 2.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.
Files changed (5) hide show
  1. package/index.d.ts +307 -153
  2. package/index.js +628 -144
  3. package/maps.js +1 -0
  4. package/package.json +13 -19
  5. package/readme.md +293 -122
package/index.d.ts CHANGED
@@ -5,6 +5,31 @@ Symbol event names are preferred given that they can be used to avoid name colli
5
5
  */
6
6
  export type EventName = PropertyKey;
7
7
 
8
+ /**
9
+ The object passed to every event listener. Always includes `name`. Includes `data` only when the event was emitted with data.
10
+
11
+ @example
12
+ ```
13
+ import Emittery from 'emittery';
14
+
15
+ const emitter = new Emittery<{unicorn: string; close: undefined}>();
16
+
17
+ emitter.on('unicorn', ({name, data}) => {
18
+ console.log(name); //=> 'unicorn'
19
+ console.log(data); //=> '🌈'
20
+ });
21
+
22
+ emitter.on('close', ({name}) => {
23
+ console.log(name); //=> 'close'
24
+ });
25
+ ```
26
+ */
27
+ export type EmitteryEvent<Name extends EventName, Data> = [Data] extends [undefined]
28
+ ? {readonly name: Name; readonly data?: undefined}
29
+ : {readonly name: Name; readonly data: Data};
30
+
31
+ type EventDataPair<EventData, Name extends keyof EventData> = Name extends keyof EventData ? EmitteryEvent<Name, EventData[Name]> : never;
32
+
8
33
  // Helper type for turning the passed `EventData` type map into a list of string keys that don't require data alongside the event name when emitting. Uses the same trick that `Omit` does internally to filter keys by building a map of keys to keys we want to keep, and then accessing all the keys to return just the list of keys we want to keep.
9
34
  type DatalessEventNames<EventData> = {
10
35
  [Key in keyof EventData]: EventData[Key] extends undefined ? Key : never;
@@ -21,7 +46,7 @@ To enable this feature set the `DEBUG` environment variable to `emittery` or `*`
21
46
 
22
47
  See API for more information on how debugging works.
23
48
  */
24
- export type DebugLogger<EventData, Name extends keyof EventData> = (type: string, debugName: string, eventName?: Name, eventData?: EventData[Name]) => void;
49
+ export type DebugLogger<EventData, Name extends keyof EventData> = (type: string, debugName?: string, eventName?: Name, eventData?: EventData[Name]) => void;
25
50
 
26
51
  /**
27
52
  Configure debug options of an instance.
@@ -40,7 +65,7 @@ export type DebugOptions<EventData> = {
40
65
 
41
66
  const emitter = new Emittery({debug: {name: 'myEmitter'}});
42
67
 
43
- emitter.on('test', data => {
68
+ emitter.on('test', () => {
44
69
  // …
45
70
  });
46
71
 
@@ -49,7 +74,7 @@ export type DebugOptions<EventData> = {
49
74
  // data: undefined
50
75
  ```
51
76
  */
52
- readonly name: string;
77
+ readonly name?: string;
53
78
 
54
79
  /**
55
80
  Toggle debug logging just for this instance.
@@ -63,11 +88,11 @@ export type DebugOptions<EventData> = {
63
88
  const emitter1 = new Emittery({debug: {name: 'emitter1', enabled: true}});
64
89
  const emitter2 = new Emittery({debug: {name: 'emitter2'}});
65
90
 
66
- emitter1.on('test', data => {
91
+ emitter1.on('test', () => {
67
92
  // …
68
93
  });
69
94
 
70
- emitter2.on('test', data => {
95
+ emitter2.on('test', () => {
71
96
  // …
72
97
  });
73
98
 
@@ -86,7 +111,11 @@ export type DebugOptions<EventData> = {
86
111
  @default
87
112
  ```
88
113
  (type, debugName, eventName, eventData) => {
89
- eventData = JSON.stringify(eventData);
114
+ try {
115
+ eventData = JSON.stringify(eventData);
116
+ } catch {
117
+ eventData = `Object with the following keys failed to stringify: ${Object.keys(eventData).join(',')}`;
118
+ }
90
119
 
91
120
  if (typeof eventName === 'symbol' || typeof eventName === 'number') {
92
121
  eventName = eventName.toString();
@@ -114,7 +143,7 @@ export type DebugOptions<EventData> = {
114
143
  }
115
144
  });
116
145
 
117
- emitter.on('test', data => {
146
+ emitter.on('test', () => {
118
147
  // …
119
148
  });
120
149
 
@@ -141,8 +170,24 @@ export type EmitteryOncePromise<T> = {
141
170
 
142
171
  /**
143
172
  Removes an event subscription.
173
+
174
+ Can be used with the `using` keyword for automatic cleanup:
175
+
176
+ @example
177
+ ```
178
+ import Emittery from 'emittery';
179
+
180
+ const emitter = new Emittery();
181
+
182
+ {
183
+ using off = emitter.on('event', ({data}) => {
184
+ console.log(data);
185
+ });
186
+ // auto-unsubscribes when leaving scope
187
+ }
188
+ ```
144
189
  */
145
- export type UnsubscribeFunction = () => void;
190
+ export type UnsubscribeFunction = (() => void) & Disposable;
146
191
 
147
192
  /**
148
193
  The data provided as `eventData` when listening for `Emittery.listenerAdded` or `Emittery.listenerRemoved`.
@@ -151,7 +196,7 @@ export type ListenerChangedData = {
151
196
  /**
152
197
  The listener that was added or removed.
153
198
  */
154
- listener: (eventData?: unknown) => (void | Promise<void>);
199
+ listener: (event: unknown) => (void | Promise<void>);
155
200
 
156
201
  /**
157
202
  The name of the event that was added or removed if `.on()` or `.off()` was used, or `undefined` if `.onAny()` or `.offAny()` was used.
@@ -191,8 +236,7 @@ emitter.emit('other');
191
236
  ```
192
237
  */
193
238
  export default class Emittery<
194
- EventData = Record<EventName, any>, // TODO: Use `unknown` instead of `any`.
195
- AllEventData = EventData & OmnipresentEventData,
239
+ EventData = Record<EventName, unknown>, AllEventData = EventData & OmnipresentEventData,
196
240
  DatalessEvents = DatalessEventNames<EventData>,
197
241
  > {
198
242
  /**
@@ -209,11 +253,11 @@ export default class Emittery<
209
253
  const emitter1 = new Emittery({debug: {name: 'myEmitter1'}});
210
254
  const emitter2 = new Emittery({debug: {name: 'myEmitter2'}});
211
255
 
212
- emitter1.on('test', data => {
256
+ emitter1.on('test', () => {
213
257
  // …
214
258
  });
215
259
 
216
- emitter2.on('otherTest', data => {
260
+ emitter2.on('otherTest', () => {
217
261
  // …
218
262
  });
219
263
 
@@ -239,15 +283,15 @@ export default class Emittery<
239
283
 
240
284
  const emitter = new Emittery();
241
285
 
242
- emitter.on(Emittery.listenerAdded, ({listener, eventName}) => {
286
+ emitter.on(Emittery.listenerAdded, ({data: {listener, eventName}}) => {
243
287
  console.log(listener);
244
- //=> data => {}
288
+ //=> ({data}) => {}
245
289
 
246
290
  console.log(eventName);
247
291
  //=> '🦄'
248
292
  });
249
293
 
250
- emitter.on('🦄', data => {
294
+ emitter.on('🦄', ({data}) => {
251
295
  // Handle data
252
296
  });
253
297
  ```
@@ -265,13 +309,13 @@ export default class Emittery<
265
309
 
266
310
  const emitter = new Emittery();
267
311
 
268
- const off = emitter.on('🦄', data => {
312
+ const off = emitter.on('🦄', ({data}) => {
269
313
  // Handle data
270
314
  });
271
315
 
272
- emitter.on(Emittery.listenerRemoved, ({listener, eventName}) => {
316
+ emitter.on(Emittery.listenerRemoved, ({data: {listener, eventName}}) => {
273
317
  console.log(listener);
274
- //=> data => {}
318
+ //=> ({data}) => {}
275
319
 
276
320
  console.log(eventName);
277
321
  //=> '🦄'
@@ -300,7 +344,7 @@ export default class Emittery<
300
344
  static mixin(
301
345
  emitteryPropertyName: string | symbol,
302
346
  methodNames?: readonly string[]
303
- ): <T extends {new (...arguments_: readonly any[]): any}>(klass: T) => T; // eslint-disable-line @typescript-eslint/prefer-function-type
347
+ ): <T extends abstract new (...arguments_: readonly any[]) => any>(klass: T, context?: ClassDecoratorContext<T>) => T;
304
348
 
305
349
  /**
306
350
  Debugging options for the current instance.
@@ -319,7 +363,7 @@ export default class Emittery<
319
363
 
320
364
  Using the same listener multiple times for the same event will result in only one method call per emitted event.
321
365
 
322
- @returns An unsubscribe method.
366
+ @returns An unsubscribe method, which is also {@link Disposable} (can be used with `using`).
323
367
 
324
368
  @example
325
369
  ```
@@ -327,110 +371,92 @@ export default class Emittery<
327
371
 
328
372
  const emitter = new Emittery();
329
373
 
330
- emitter.on('🦄', data => {
374
+ emitter.on('🦄', ({data}) => {
331
375
  console.log(data);
332
376
  });
333
377
 
334
- emitter.on(['🦄', '🐶'], data => {
335
- console.log(data);
378
+ emitter.on(['🦄', '🐶'], ({name, data}) => {
379
+ console.log(name, data);
336
380
  });
337
381
 
338
- emitter.emit('🦄', '🌈'); // log => '🌈' x2
339
- emitter.emit('🐶', '🍖'); // log => '🍖'
382
+ emitter.emit('🦄', '🌈'); // log => '🌈' and '🦄 🌈'
383
+ emitter.emit('🐶', '🍖'); // log => '🐶 🍖'
384
+ ```
385
+
386
+ @example
387
+ ```
388
+ // With AbortSignal
389
+ const abortController = new AbortController();
390
+
391
+ emitter.on('🐗', ({data}) => {
392
+ console.log(data);
393
+ }, {signal: abortController.signal});
394
+
395
+ abortController.abort();
396
+ ```
397
+
398
+ @example
399
+ ```
400
+ // With `using` for automatic cleanup
401
+ {
402
+ using off = emitter.on('🦄', ({data}) => {
403
+ console.log(data);
404
+ });
405
+ await emitter.emit('🦄', '🌈'); // Logs '🌈'
406
+ }
407
+
408
+ await emitter.emit('🦄', '🌈'); // Nothing happens
340
409
  ```
341
410
  */
342
411
  on<Name extends keyof AllEventData>(
343
412
  eventName: Name | readonly Name[],
344
- listener: (eventData: AllEventData[Name]) => void | Promise<void>,
413
+ listener: (event: EventDataPair<AllEventData, Name>) => void | Promise<void>,
345
414
  options?: {signal?: AbortSignal}
346
415
  ): UnsubscribeFunction;
347
416
 
348
417
  /**
349
418
  Get an async iterator which buffers data each time an event is emitted.
350
419
 
351
- Call `return()` on the iterator to remove the subscription.
420
+ Call `return()` on the iterator to remove the subscription. You can also pass an {@link AbortSignal} to cancel the subscription externally, or use `await using` for automatic cleanup.
352
421
 
353
422
  @example
354
423
  ```
355
424
  import Emittery from 'emittery';
356
425
 
357
426
  const emitter = new Emittery();
358
- const iterator = emitter.events('🦄');
359
-
360
- emitter.emit('🦄', '🌈1'); // Buffered
361
- emitter.emit('🦄', '🌈2'); // Buffered
362
-
363
- iterator
364
- .next()
365
- .then(({value, done}) => {
366
- // done === false
367
- // value === '🌈1'
368
- return iterator.next();
369
- })
370
- .then(({value, done}) => {
371
- // done === false
372
- // value === '🌈2'
373
- // Revoke subscription
374
- return iterator.return();
375
- })
376
- .then(({done}) => {
377
- // done === true
378
- });
379
- ```
380
-
381
- In practice you would usually consume the events using the [for await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of) statement. In that case, to revoke the subscription simply break the loop.
382
427
 
383
- @example
384
- ```
385
- import Emittery from 'emittery';
386
-
387
- const emitter = new Emittery();
388
- const iterator = emitter.events('🦄');
389
-
390
- emitter.emit('🦄', '🌈1'); // Buffered
391
- emitter.emit('🦄', '🌈2'); // Buffered
428
+ for await (const {data} of emitter.events('🦄')) {
429
+ console.log(data);
392
430
 
393
- // In an async context.
394
- for await (const data of iterator) {
395
431
  if (data === '🌈2') {
396
- break; // Revoke the subscription when we see the value `🌈2`.
432
+ break; // Revoke the subscription when we see the value '🌈2'.
397
433
  }
398
434
  }
399
435
  ```
400
436
 
401
- It accepts multiple event names.
402
-
403
437
  @example
404
438
  ```
405
- import Emittery from 'emittery';
439
+ // With multiple event names
440
+ for await (const {name, data} of emitter.events(['🦄', '🦊'])) {
441
+ console.log(name, data);
442
+ }
443
+ ```
406
444
 
407
- const emitter = new Emittery();
408
- const iterator = emitter.events(['🦄', '🦊']);
409
-
410
- emitter.emit('🦄', '🌈1'); // Buffered
411
- emitter.emit('🦊', '🌈2'); // Buffered
412
-
413
- iterator
414
- .next()
415
- .then(({value, done}) => {
416
- // done === false
417
- // value === '🌈1'
418
- return iterator.next();
419
- })
420
- .then(({value, done}) => {
421
- // done === false
422
- // value === '🌈2'
423
- // Revoke subscription
424
- return iterator.return();
425
- })
426
- .then(({done}) => {
427
- // done === true
428
- });
445
+ @example
446
+ ```
447
+ // With `await using` for automatic cleanup
448
+ {
449
+ await using iterator = emitter.events('🦄');
450
+ for await (const {data} of iterator) {
451
+ console.log(data);
452
+ }
453
+ } // Subscription is automatically revoked
429
454
  ```
430
455
  */
431
456
  events<Name extends keyof EventData>(
432
- eventName: Name | readonly Name[]
433
- ): AsyncIterableIterator<EventData[Name]>;
457
+ eventName: Name | readonly Name[],
458
+ options?: {signal?: AbortSignal}
459
+ ): AsyncIterableIterator<EventDataPair<EventData, Name>> & AsyncDisposable;
434
460
 
435
461
  /**
436
462
  Remove one or more event subscriptions.
@@ -441,7 +467,7 @@ export default class Emittery<
441
467
 
442
468
  const emitter = new Emittery();
443
469
 
444
- const listener = data => {
470
+ const listener = ({data}) => {
445
471
  console.log(data);
446
472
  };
447
473
 
@@ -451,23 +477,22 @@ export default class Emittery<
451
477
  await emitter.emit('🦊', 'c');
452
478
  emitter.off('🦄', listener);
453
479
  emitter.off(['🐶', '🦊'], listener);
454
- await emitter.emit('🦄', 'a'); // nothing happens
455
- await emitter.emit('🐶', 'b'); // nothing happens
456
- await emitter.emit('🦊', 'c'); // nothing happens
480
+ await emitter.emit('🦄', 'a'); // Nothing happens
481
+ await emitter.emit('🐶', 'b'); // Nothing happens
482
+ await emitter.emit('🦊', 'c'); // Nothing happens
457
483
  ```
458
484
  */
459
485
  off<Name extends keyof AllEventData>(
460
486
  eventName: Name | readonly Name[],
461
- listener: (eventData: AllEventData[Name]) => void | Promise<void>
487
+ listener: (event: EventDataPair<AllEventData, Name>) => void | Promise<void>
462
488
  ): void;
463
489
 
464
490
  /**
465
491
  Subscribe to one or more events only once. It will be unsubscribed after the first event that matches the predicate (if provided).
466
492
 
467
- @param eventName - The event name(s) to subscribe to.
468
- @param predicate - Optional predicate function to filter event data. The event will only be emitted if the predicate returns true.
493
+ The second argument can be a predicate function or an options object with `predicate` and/or `signal`.
469
494
 
470
- @returns The promise of event data when `eventName` is emitted and predicate matches (if provided). This promise is extended with an `off` method.
495
+ @returns The promise of event data when `eventName` is emitted and predicate matches (if provided). The promise has an `off` method to cancel the subscription.
471
496
 
472
497
  @example
473
498
  ```
@@ -475,33 +500,55 @@ export default class Emittery<
475
500
 
476
501
  const emitter = new Emittery();
477
502
 
478
- emitter.once('🦄').then(data => {
479
- console.log(data);
480
- //=> '🌈'
481
- });
503
+ const {data} = await emitter.once('🦄');
504
+ console.log(data);
505
+ //=> '🌈'
506
+ ```
482
507
 
483
- emitter.once(['🦄', '🐶']).then(data => {
484
- console.log(data);
485
- });
508
+ @example
509
+ ```
510
+ // With multiple event names
511
+ const {name, data} = await emitter.once(['🦄', '🐶']);
512
+ console.log(name, data);
513
+ ```
486
514
 
515
+ @example
516
+ ```
487
517
  // With predicate
488
- emitter.once('data', data => data.ok === true).then(data => {
489
- console.log(data);
490
- //=> {ok: true, value: 42}
491
- });
518
+ const event = await emitter.once('data', ({data}) => data.ok === true);
519
+ console.log(event.data);
520
+ //=> {ok: true, value: 42}
521
+ ```
492
522
 
493
- emitter.emit('🦄', '🌈'); // Logs `🌈` twice
494
- emitter.emit('🐶', '🍖'); // Nothing happens
495
- emitter.emit('data', {ok: false}); // Nothing happens
496
- emitter.emit('data', {ok: true, value: 42}); // Logs {ok: true, value: 42}
523
+ @example
524
+ ```
525
+ // With AbortSignal for timeout
526
+ await emitter.once('ready', {signal: AbortSignal.timeout(5000)});
527
+ ```
528
+
529
+ @example
530
+ ```
531
+ // Cancel with .off()
532
+ const promise = emitter.once('🦄');
533
+ promise.off();
497
534
  ```
498
535
  */
499
- once<Name extends keyof AllEventData>(eventName: Name | readonly Name[], predicate?: (eventData: AllEventData[Name]) => boolean): EmitteryOncePromise<AllEventData[Name]>;
536
+ once<Name extends keyof AllEventData>(
537
+ eventName: Name | readonly Name[],
538
+ predicate?: (event: EventDataPair<AllEventData, Name>) => boolean
539
+ ): EmitteryOncePromise<EventDataPair<AllEventData, Name>>;
540
+ once<Name extends keyof AllEventData>(
541
+ eventName: Name | readonly Name[],
542
+ options?: {
543
+ predicate?: (event: EventDataPair<AllEventData, Name>) => boolean;
544
+ signal?: AbortSignal;
545
+ }
546
+ ): EmitteryOncePromise<EventDataPair<AllEventData, Name>>;
500
547
 
501
548
  /**
502
549
  Trigger an event asynchronously, optionally with some data. Listeners are called in the order they were added, but executed concurrently.
503
550
 
504
- @returns A promise that resolves when all the event listeners are done. *Done* meaning executed if synchronous or resolved when an async/promise-returning function. You usually wouldn't want to wait for this, but you could for example catch possible errors. If any of the listeners throw/reject, the returned promise will be rejected with the error, but the other listeners will not be affected.
551
+ @returns A promise that resolves when all the event listeners are done. *Done* meaning executed if synchronous or resolved when an async/promise-returning function. You usually wouldn't want to wait for this, but you could for example catch possible errors. If any listeners throw/reject, the returned promise rejects with an `AggregateError` all listener errors are collected in `error.errors`, so no errors are silently lost. All listeners always run to completion, even if some throw or reject.
505
552
  */
506
553
  emit<Name extends DatalessEvents>(eventName: Name): Promise<void>;
507
554
  emit<Name extends keyof EventData>(
@@ -515,6 +562,25 @@ export default class Emittery<
515
562
  If any of the listeners throw/reject, the returned promise will be rejected with the error and the remaining listeners will *not* be called.
516
563
 
517
564
  @returns A promise that resolves when all the event listeners are done.
565
+
566
+ @example
567
+ ```
568
+ import Emittery from 'emittery';
569
+
570
+ const emitter = new Emittery();
571
+
572
+ emitter.on('🦄', async () => {
573
+ console.log('listener 1 start');
574
+ await new Promise(resolve => setTimeout(resolve, 100));
575
+ console.log('listener 1 done');
576
+ });
577
+
578
+ emitter.on('🦄', () => {
579
+ console.log('listener 2'); // Only runs after listener 1 is done
580
+ });
581
+
582
+ await emitter.emitSerial('🦄');
583
+ ```
518
584
  */
519
585
  emitSerial<Name extends DatalessEvents>(eventName: Name): Promise<void>;
520
586
  emitSerial<Name extends keyof EventData>(
@@ -525,76 +591,164 @@ export default class Emittery<
525
591
  /**
526
592
  Subscribe to be notified about any event.
527
593
 
528
- @returns A method to unsubscribe.
594
+ @returns A method to unsubscribe, which is also {@link Disposable}.
595
+
596
+ @example
597
+ ```
598
+ import Emittery from 'emittery';
599
+
600
+ const emitter = new Emittery();
601
+
602
+ const off = emitter.onAny(({name, data}) => {
603
+ console.log(name, data);
604
+ });
605
+
606
+ emitter.emit('🦄', '🌈'); // log => '🦄 🌈'
607
+
608
+ off();
609
+ ```
529
610
  */
530
611
  onAny(
531
- listener: (
532
- eventName: keyof EventData,
533
- eventData: EventData[keyof EventData]
534
- ) => void | Promise<void>,
612
+ listener: (event: EventDataPair<EventData, keyof EventData>) => void | Promise<void>,
535
613
  options?: {signal?: AbortSignal}
536
614
  ): UnsubscribeFunction;
537
615
 
538
616
  /**
539
- Get an async iterator which buffers a tuple of an event name and data each time an event is emitted.
540
-
541
- Call `return()` on the iterator to remove the subscription.
617
+ Get an async iterator which buffers an event object each time an event is emitted.
542
618
 
543
- In the same way as for `events`, you can subscribe by using the `for await` statement.
619
+ Call `return()` on the iterator to remove the subscription. You can also pass an {@link AbortSignal} to cancel the subscription externally, or use `await using` for automatic cleanup.
544
620
 
545
621
  @example
546
622
  ```
547
623
  import Emittery from 'emittery';
548
624
 
549
625
  const emitter = new Emittery();
550
- const iterator = emitter.anyEvent();
551
-
552
- emitter.emit('🦄', '🌈1'); // Buffered
553
- emitter.emit('🌟', '🌈2'); // Buffered
554
-
555
- iterator.next()
556
- .then(({value, done}) => {
557
- // done is false
558
- // value is ['🦄', '🌈1']
559
- return iterator.next();
560
- })
561
- .then(({value, done}) => {
562
- // done is false
563
- // value is ['🌟', '🌈2']
564
- // revoke subscription
565
- return iterator.return();
566
- })
567
- .then(({done}) => {
568
- // done is true
569
- });
626
+
627
+ for await (const {name, data} of emitter.anyEvent()) {
628
+ console.log(name, data);
629
+ }
630
+ ```
631
+
632
+ @example
633
+ ```
634
+ // With `await using` for automatic cleanup
635
+ {
636
+ await using iterator = emitter.anyEvent();
637
+ for await (const {name, data} of iterator) {
638
+ console.log(name, data);
639
+ }
640
+ } // Subscription is automatically revoked
570
641
  ```
571
642
  */
572
- anyEvent(): AsyncIterableIterator<
573
- [keyof EventData, EventData[keyof EventData]]
574
- >;
643
+ anyEvent(options?: {signal?: AbortSignal}): AsyncIterableIterator<EventDataPair<EventData, keyof EventData>> & AsyncDisposable;
575
644
 
576
645
  /**
577
646
  Remove an `onAny` subscription.
578
647
  */
579
648
  offAny(
580
- listener: (
581
- eventName: keyof EventData,
582
- eventData: EventData[keyof EventData]
583
- ) => void | Promise<void>
649
+ listener: (event: EventDataPair<EventData, keyof EventData>) => void | Promise<void>
584
650
  ): void;
585
651
 
586
652
  /**
587
653
  Clear all event listeners on the instance.
588
654
 
589
- If `eventName` is given, only the listeners for that event are cleared.
655
+ If `eventNames` is given, only the listeners for those events are cleared. Accepts a single event name or an array.
656
+
657
+ @example
658
+ ```
659
+ import Emittery from 'emittery';
660
+
661
+ const emitter = new Emittery();
662
+
663
+ emitter.on('🦄', listener);
664
+ emitter.on('🐶', listener);
665
+
666
+ emitter.clearListeners('🦄'); // Clear a single event
667
+ emitter.clearListeners(['🐶', '🦊']); // Clear multiple events
668
+ emitter.clearListeners(); // Clear all events
669
+ ```
590
670
  */
591
671
  clearListeners<Name extends keyof EventData>(eventName?: Name | readonly Name[]): void;
592
672
 
673
+ /**
674
+ Register a function to be called when the first `.on()` listener subscribes to `eventName`. The `initFn` can optionally return a cleanup (deinit) function, which is called when the last `.on()` listener unsubscribes (or when `clearListeners()` removes all listeners for that event).
675
+
676
+ If `.on()` listeners already exist when `init()` is called, `initFn` is called immediately.
677
+
678
+ Note: Lifecycle hooks only apply to `.on()` listeners. Subscriptions via `.events()` async iterators do not trigger the init or deinit functions.
679
+
680
+ @returns An unsubscribe function. Calling it removes the init/deinit hooks, and if the init is currently active, it calls deinit immediately.
681
+
682
+ @example
683
+ ```
684
+ import Emittery from 'emittery';
685
+
686
+ const emitter = new Emittery();
687
+
688
+ emitter.init('mouse', () => {
689
+ terminal.grabInput({mouse: 'button'});
690
+
691
+ terminal.on('mouse', (name, data) => {
692
+ emitter.emit('mouse', data);
693
+ });
694
+
695
+ return () => {
696
+ terminal.releaseInput();
697
+ };
698
+ });
699
+
700
+ // Init is called when the first listener subscribes
701
+ const off = emitter.on('mouse', handler);
702
+
703
+ // Adding more listeners does not call init again
704
+ emitter.on('mouse', anotherHandler);
705
+
706
+ // Removing one listener does not call deinit yet
707
+ off();
708
+
709
+ // Deinit is called when the last listener unsubscribes
710
+ emitter.off('mouse', anotherHandler);
711
+ ```
712
+
713
+ @example
714
+ ```
715
+ // With `using` for automatic cleanup of hooks
716
+ {
717
+ using removeInit = emitter.init('mouse', () => {
718
+ startListening();
719
+ return () => stopListening();
720
+ });
721
+ } // init/deinit hooks are automatically removed
722
+ ```
723
+ */
724
+ init<Name extends keyof EventData>(
725
+ eventName: Name,
726
+ initFn: () => (() => void) | void
727
+ ): UnsubscribeFunction;
728
+
593
729
  /**
594
730
  The number of listeners for the `eventName` or all events if not specified.
731
+
732
+ @example
733
+ ```
734
+ import Emittery from 'emittery';
735
+
736
+ const emitter = new Emittery();
737
+
738
+ emitter.on('🦄', listener);
739
+ emitter.on('🐶', listener);
740
+
741
+ emitter.listenerCount('🦄'); // 1
742
+ emitter.listenerCount(); // 2
743
+ ```
595
744
  */
596
745
  listenerCount<Name extends keyof EventData>(eventName?: Name | readonly Name[]): number;
597
746
 
747
+ /**
748
+ Log debug information if debug mode is enabled (either globally via `Emittery.isDebugEnabled` or per-instance via `debug.enabled`).
749
+ */
750
+ logIfDebugEnabled<Name extends keyof EventData>(type: string, eventName?: Name, eventData?: EventData[Name]): void;
751
+
598
752
  /**
599
753
  Bind the given `methodNames`, or all `Emittery` methods if `methodNames` is not defined, into the `target` object.
600
754