npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@supercat1337/event-emitter

v2.1.1

Published

Lightweight typed event emitter with Symbol support, one-time listeners, and zero dependencies. Works in Node.js and browsers.

Readme

@supercat1337/event-emitter

A modern, feature-rich EventEmitter implementation for JavaScript and TypeScript with advanced capabilities and industry-leading type safety.


Features

  • Dual Implementation – choose between lightweight EventEmitterLite or full-featured EventEmitter.
  • First‑class TypeScript – deep generic support for event names and argument validation.
  • Promise‑based Waiting – native waitForEvent and waitForAnyEvent with built‑in timeout support.
  • Lifecycle Tracking – monitor when events gain or lose listeners (onHasEventListeners, onNoEventListeners) – only in EventEmitter.
  • Centralized Error Handling – intercept listener errors globally via onListenerError.
  • Global Listeners – subscribe to all events with onAny – perfect for logging, debugging, or metrics.
  • AbortSignal Support – cancel subscriptions using standard AbortSignal in on, once, and onAny.
  • Introspection Methods – inspect listeners, counts, and event names with hasListeners, listenerCount, eventNames, getListeners.
  • Memory‑Efficient – automatic cleanup of unused event keys and dedicated destroy() lifecycle.
  • Immutable Emission – listener arrays are snapshotted during emission, making it safe to modify listeners inside callbacks.
  • Modern ES2022+ – leverages native private fields and optimised logic.

Installation

npm install @supercat1337/event-emitter

Quick Start

Lightweight version (EventEmitterLite)

For simple scenarios where you only need on / once / off / emit:

import { EventEmitterLite } from '@supercat1337/event-emitter';

const emitter = new EventEmitterLite();
const unsubscribe = emitter.on('data', msg => console.log(msg));
emitter.emit('data', 'Hello, World!');
unsubscribe(); // remove listener

Full‑featured version (EventEmitter)

With all advanced features:

import { EventEmitter } from '@supercat1337/event-emitter';

const emitter = new EventEmitter();
emitter.on('ready', () => console.log('Ready!'));
emitter.emit('ready');

Classes

| Class | Description | | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | EventEmitterLite | Core implementation: on, once, off, removeListener, emit, onAny, offAny, introspection, and cleanup. | | EventEmitter | Extends EventEmitterLite, adding: waitForEvent, waitForAnyEvent, lifecycle hooks (onHasEventListeners, onNoEventListeners, onListenerError), and destroy. |

Key difference: EventEmitter adds async waiting and lifecycle monitoring.


API

Common Properties

| Property | Type | Description | | ------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | logErrors | boolean | If true (default), errors in listeners are logged to console.error. Even when false, errors can still be caught via onListenerError (in EventEmitter). | | isDestroyed | boolean | EventEmitter only. true after destroy() is called. |


Methods available in both classes

| Method | Description | | --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | on(event, listener, options?) | Subscribes to an event. Returns an unsubscribe() function. options.signal accepts an AbortSignal. | | once(event, listener, options?) | Subscribes for a single invocation, then auto‑removes. Supports AbortSignal. | | off(event, listener) | Removes a specific listener. | | removeListener(event, listener) | Alias for off(). | | emit(event, ...args) | Triggers all listeners for the event with provided arguments. | | onAny(listener, options?) | Subscribes a listener that is invoked for every emitted event. Receives (eventName, ...args). Supports AbortSignal. | | offAny(listener) | Removes a listener added via onAny. | | hasListeners(event) | Returns true if the event has any listeners. | | listenerCount(event) | Returns the number of listeners for a specific event. | | eventNames() | Returns an array of event names that have at least one listener (including symbols). | | getListeners(event) | Debug only. Returns a copy of the listeners array for the event. | | removeAllListeners() | Removes all listeners from all events. The emitter remains functional. In EventEmitter, this also emits #no‑listeners for each event that had listeners. | | removeAllListenersOf(event) | Removes all listeners for the specified event. In EventEmitter, emits #no‑listeners if any listener was removed. | | clear() | Deprecated. Use removeAllListeners() instead. | | clearEventListeners(event) | Deprecated. Use removeAllListenersOf(event) instead. |


Methods available only in EventEmitter

| Method | Description | | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | | waitForEvent(event, maxWaitMs = 0) | Returns Promise<boolean>. Resolves true when the event fires, or false on timeout. If maxWaitMs === 0, waits indefinitely. | | waitForAnyEvent(events, maxWaitMs = 0) | Waits for the first occurring event from an array of event names. | | destroy() | Completely destroys the emitter: removes all listeners (including internal), clears internal state, and prevents further operations. | | onHasEventListeners(callback) | Subscribes to the system event emitted when any event gains its first listener. Callback receives the event name. | | onNoEventListeners(callback) | Subscribes to the system event emitted when any event loses its last listener. Callback receives the event name. | | onListenerError(callback) | Subscribes to errors thrown by listeners. Callback receives (error, eventName, ...args). |


Examples

AbortSignal support

import { EventEmitter } from '@supercat1337/event-emitter';

const emitter = new EventEmitter();
const controller = new AbortController();

emitter.on('message', text => console.log(text), { signal: controller.signal });

// Cancel after 5 seconds
setTimeout(() => controller.abort(), 5000);

Works with once and onAny as well.

Global listener with onAny

emitter.onAny((event, ...args) => {
    console.log(`Event "${event}" fired with`, args);
});

emitter.emit('foo', 1, 2); // logs: Event "foo" fired with [1, 2]

Introspection

emitter.on('foo', () => {});
emitter.on('foo', () => {});

console.log(emitter.hasListeners('foo')); // true
console.log(emitter.listenerCount('foo')); // 2
console.log(emitter.eventNames()); // ['foo']

const listeners = emitter.getListeners('foo'); // copy, safe to iterate

Lifecycle monitoring (only in EventEmitter)

emitter.onHasEventListeners(event => {
    console.log(`First listener added for ${event}`);
});

emitter.onNoEventListeners(event => {
    console.log(`Last listener removed for ${event}`);
});

emitter.on('test', () => {});
// => "First listener added for test"

emitter.removeAllListenersOf('test');
// => "Last listener removed for test"

Error handling

emitter.onListenerError((err, event, ...args) => {
    console.error(`Error in "${event}":`, err);
    // send to monitoring service
});

emitter.on('crash', () => {
    throw new Error('Boom!');
});
emitter.emit('crash'); // error is caught and passed to the callback

Cleanup and destruction

// Remove all listeners, but keep the emitter functional
emitter.removeAllListeners();

// Permanently destroy the emitter
emitter.destroy();
console.log(emitter.isDestroyed); // true
emitter.on('test', () => {}); // throws: "EventEmitter is destroyed"

Listener context (this)

When a listener is invoked, the this context inside the listener function refers to the EventEmitter (or EventEmitterLite) instance. This is consistent with Node.js EventEmitter behavior.

emitter.on('event', function () {
    console.log(this); // points to the emitter instance
});

// To preserve a custom context, use an arrow function or .bind()
const obj = { name: 'MyObj' };
emitter.on('event', () => {
    console.log(this); // lexical this
});
emitter.on(
    'event',
    function () {
        console.log(this.name);
    }.bind(obj)
);

TypeScript

Simple string union

type MyEvents = 'start' | 'stop';
const emitter = new EventEmitter<MyEvents>();
emitter.emit('start'); // OK
emitter.emit('unknown'); // Type error

Full type safety with arguments

type AppEvents = {
    'user:created': [id: number, name: string];
    ping: [];
};

const emitter = new EventEmitter<AppEvents>();
emitter.on('user:created', (id, name) => {
    // id: number, name: string
});
emitter.emit('user:created', 1, 'Alice'); // OK
emitter.emit('user:created', '1'); // Type error

Error Handling

All listener errors are caught to prevent the emitter from crashing.

  • If logErrors is true (default), errors are printed to console.error.
  • Even with logErrors: false, you can still intercept errors globally using onListenerError for custom logging or reporting.

Performance Notes

  • Snapshotted iteration: Listener arrays are copied before emission. If a listener calls off() during emission, the current cycle continues safely without skipping elements.
  • Zero dependencies: Ultra‑small bundle size.
  • Memory management: Event keys are deleted when the last listener is removed.
  • Symbol‑based internal events: Internal lifecycle events use Symbol to avoid collisions with user events.

Browser and Node.js Support

| Platform | Version | | --------------- | ------- | | Node.js | 14+ | | Modern browsers | ES2022+ | | TypeScript | 4.0+ |


License

MIT © supercat1337