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

radiate

v0.3.2

Published

A minimal, type-safe event emitter for TypeScript with zero dependencies

Readme

radiate

A minimal, type-safe event emitter for TypeScript with zero dependencies.

Features

  • Type-safe - Full TypeScript support with typed event maps and listeners
  • Zero dependencies - Lightweight and tree-shakeable
  • Simple API - Easy to use with methods like on(), off(), once(), and emit()
  • Keyed events - Support for events partitioned by ID/key with wildcard listeners
  • Error handling - Optional error handler for production environments
  • Flexible - Use as a base class or compose it into your classes
  • ESM only - Modern ES modules for optimal bundle size

Installation

npm install radiate
# or
pnpm add radiate
# or
yarn add radiate

Usage

Basic Example

import { Emitter } from 'radiate';

// Define your event types
type MyEvents = {
  'item:added': { id: string; name: string };
  'item:removed': string;
  'cleared': void;
};

const emitter = new Emitter<MyEvents>();

// Subscribe to events
const unsubscribe = emitter.on('item:added', (item) => {
  console.log('Added:', item);
});

// Emit events
emitter.emit('item:added', { id: '1', name: 'Item 1' });

// Unsubscribe when done
unsubscribe();

Using the Factory Function

import { createEmitter } from 'radiate';

// Define your event types
type MyEvents = {
  'item:added': { id: string; name: string };
  'item:removed': string;
  'cleared': void;
};

// Create an emitter using the factory function
const emitter = createEmitter<MyEvents>();

// Use it the same way as a class instance
emitter.on('item:added', (item) => {
  console.log('Added:', item);
});

emitter.emit('item:added', { id: '1', name: 'Item 1' });

Extending the Emitter

import { Emitter } from 'radiate';

type MyEvents = {
  'item:added': { id: string; name: string };
  'item:removed': string;
  'cleared': void;
};

class MyStore extends Emitter<MyEvents> {
  addItem(item: { id: string; name: string }) {
    // ... add logic
    this.emit('item:added', item);
  }

  removeItem(id: string) {
    // ... remove logic
    this.emit('item:removed', id);
  }

  clear() {
    // ... clear logic
    this.emit('cleared');
  }
}

const store = new MyStore();
store.on('item:added', (item) => {
  console.log('Item added:', item);
});

One-time Listeners

emitter.once('cleared', () => {
  console.log('This will only fire once');
});

emitter.emit('cleared'); // Logs: "This will only fire once"
emitter.emit('cleared'); // Nothing happens

Error Handling

import { Emitter } from 'radiate';

type MyEvents = {
  'item:added': { id: string; name: string };
};

const emitter = new Emitter<MyEvents>({
  errorHandler: (event, error, listener) => {
    console.error(`Error in ${String(event)}:`, error);
    // Send to error tracking service
    // Sentry.captureException(error);
  },
});

// This error will be caught by the error handler
emitter.on('item:added', (item) => {
  throw new Error('Something went wrong');
});

emitter.emit('item:added', { id: '1', name: 'Item 1' });

Create Subscription Pattern

import { Emitter } from 'radiate';

class Store {
  private $ = new Emitter<{ 'count:changed': number }>();
  private count = 0;

  // Create subscription function once
  subscribe = this.$.createSubscription('count:changed', () => this.count);

  increment() {
    this.count++;
    this.$.emit('count:changed', this.count);
  }
}

const store = new Store();

// Subscriber receives current value immediately
store.subscribe((count) => console.log('Count:', count));
// Logs: "Count: 0" immediately upon subscription

store.increment(); // Logs: "Count: 1"

Checking Listeners

// Check if there are listeners for an event
if (emitter.hasListeners('item:added')) {
  // Prepare expensive data only if someone is listening
  const expensiveData = await prepareData();
  emitter.emit('item:added', expensiveData);
}

// Get the number of listeners
const count = emitter.listenerCount('item:added');
console.log(`There are ${count} listeners`);

Removing Listeners

const listener = (item) => console.log('Listener 1:', item);
emitter.on('item:added', listener);

// Remove a specific listener
emitter.off('item:added', listener);

// Remove all listeners for a specific event
emitter.removeAllListeners('item:added');

// Remove all listeners for all events
emitter.removeAllListeners();

Keyed Events

Keyed events allow you to partition events by an ID/key. This is useful for scenarios like entity-specific events where you want to listen to updates for a specific entity.

import { Emitter, KeyedEventMap } from 'radiate';

// Define regular events
type MyEvents = {
  'user:login': { userId: string; timestamp: number };
  'app:ready': void;
};

// Define keyed events with their ID and data types
type MyKeyedEvents = {
  'entity:updated': { id: string; data: { name: string; value: number } };
  'transaction:status': { id: number; data: 'pending' | 'completed' | 'failed' };
};

// Create emitter with both event types
const emitter = new Emitter<MyEvents, MyKeyedEvents>();

// Listen to a specific entity by ID
const unsub = emitter.onKeyed('entity:updated', 'entity-123', (data) => {
  // data is { name: string; value: number }
  console.log('Entity 123 updated:', data);
});

// Listen to ALL entities (wildcard)
emitter.onKeyed('entity:updated', '*', (key, data) => {
  // key is string, data is { name: string; value: number }
  console.log(`Entity ${key} updated:`, data);
});

// Emit to a specific key (notifies both specific and wildcard listeners)
emitter.emitKeyed('entity:updated', 'entity-123', { name: 'Test', value: 42 });

// One-time keyed listener
emitter.onceKeyed('transaction:status', 12345, (status) => {
  console.log('Transaction completed with status:', status);
});

// Emit with number ID
emitter.emitKeyed('transaction:status', 12345, 'completed');

// Check for keyed listeners
if (emitter.hasKeyedListeners('entity:updated', 'entity-123')) {
  // There are listeners for this specific entity
}

// Get listener count (includes wildcard listeners)
const count = emitter.keyedListenerCount('entity:updated', 'entity-123');

// Remove all keyed listeners for a specific key
emitter.removeAllKeyedListeners('entity:updated', 'entity-123');

// Remove all keyed listeners for an event
emitter.removeAllKeyedListeners('entity:updated');

// Remove all keyed listeners
emitter.removeAllKeyedListeners();

Different ID Types

Each keyed event can have its own ID type:

type MyKeyedEvents = {
  'user:action': { id: string; data: ActionData };      // string IDs
  'order:update': { id: number; data: OrderData };      // number IDs
  'session:event': { id: `session-${string}`; data: SessionData }; // template literal IDs
};

API Reference

createEmitter<Events, KeyedEvents>(options?)

Factory function to create a new Emitter instance. This is an alternative to using new Emitter().

function createEmitter<Events, KeyedEvents>(options?: EmitterOptions<Events>): Emitter<Events, KeyedEvents>
  • options.errorHandler - Optional error handler function for catching listener errors
  • Returns a new Emitter instance

class Emitter<Events, KeyedEvents>

Constructor

constructor(options?: EmitterOptions<Events>)
  • options.errorHandler - Optional error handler function for catching listener errors

Regular Event Methods

  • on(event, listener) - Subscribe to an event, returns an unsubscribe function
  • off(event, listener) - Unsubscribe a specific listener from an event
  • once(event, listener) - Subscribe to an event for a single emission
  • emit(event, data) - Emit an event to all subscribed listeners
  • createSubscription(event, getCurrentValue) - Create a subscription function that calls the listener immediately with the current value, then subscribes for future emissions
  • hasListeners(event) - Check if there are any listeners for an event
  • listenerCount(event) - Get the number of listeners for an event
  • removeAllListeners(event?) - Remove all listeners for a specific event or all events

Keyed Event Methods

  • onKeyed(event, key, listener) - Subscribe to a keyed event for a specific key, returns an unsubscribe function
  • onKeyed(event, '*', listener) - Subscribe to all emissions of a keyed event (wildcard), listener receives (key, data)
  • offKeyed(event, key, listener) - Unsubscribe a specific listener from a keyed event
  • onceKeyed(event, key, listener) - Subscribe to a keyed event for a single emission
  • emitKeyed(event, key, data) - Emit a keyed event to specific key listeners and wildcard listeners
  • createKeyedSubscription(event, getCurrentValue) - Create a subscription factory for keyed events that calls the listener immediately with the current value, then subscribes for future emissions
  • hasKeyedListeners(event, key?) - Check if there are any listeners for a keyed event (optionally for a specific key)
  • keyedListenerCount(event, key?) - Get the number of listeners for a keyed event (optionally for a specific key)
  • removeAllKeyedListeners(event?, key?) - Remove keyed listeners for a specific event/key or all keyed listeners

Types

type Listener<T = unknown> = (data: T) => void;

type ErrorHandler<Events> = (
  event: keyof Events,
  error: Error,
  listener: Listener<unknown>,
) => void;

type EventMap<T extends Record<string, unknown>> = T;

// Keyed event map - each entry has { id: IdType; data: DataType }
type KeyedEventMap<T extends Record<string, { id: unknown; data: unknown }>> = T;

// Extract ID type from keyed event definition
type KeyedEventId<T> = T extends { id: infer Id; data: unknown } ? Id : never;

// Extract data type from keyed event definition
type KeyedEventData<T> = T extends { id: unknown; data: infer Data } ? Data : never;

// Listener for specific key (receives only data)
type KeyedListener<Data> = (data: Data) => void;

// Listener for wildcard (receives key and data)
type WildcardKeyedListener<Id, Data> = (key: Id, data: Data) => void;

type Unsubscribe = () => void;

interface EmitterOptions<Events> {
  errorHandler?: ErrorHandler<Events>;
}

License

MIT