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

zephyr-events

v1.3.0

Published

Tiny typed event emitter with race-condition safety

Readme

Zephyr Events — Tiny TypeScript Event Emitter

A lightweight, type-safe event emitter for TypeScript and JavaScript. Zero dependencies, under 2KB, with built-in race-condition safety that larger alternatives like EventEmitter3 and Node.js EventEmitter lack.

npm version npm downloads bundle size TypeScript license

Why Zephyr Events?

Most event emitter libraries break when handlers modify the listener list during emission — a handler that calls off() on itself can skip the next handler, or adding a new listener mid-emit can cause infinite loops. Zephyr Events solves this with snapshot-based iteration, making it safe for real-world use in UI frameworks, state machines, and plugin systems.

  • Under 2KB — 1.9KB ESM, zero dependencies, tree-shakeable
  • Race-condition safe — handlers can subscribe, unsubscribe, or clear during emit without side effects
  • Fast modezephyrEventsFast drops snapshot safety for up to 82% faster emit
  • Full TypeScript support — generic event maps, strict handler signatures, IDE autocompletion
  • Universal builds — ESM, CommonJS, and UMD for browsers, Node.js, and bundlers
  • Wildcard listeners — subscribe to all events with *
  • Shared state — pass a handler map between emitters for cross-module communication

Installation

npm install zephyr-events
yarn add zephyr-events
pnpm add zephyr-events

Quick Start

TypeScript Event Emitter

import zephyrEvents from 'zephyr-events';

// Define your event types
type AppEvents = {
  'user:login': { id: number; name: string }
  'user:logout': { id: number }
  'error': Error
}

const emitter = zephyrEvents<AppEvents>();

// Subscribe — returns an unsubscribe function for easy cleanup
const unsubscribe = emitter.on('user:login', (user) => {
  console.log(`Welcome, ${user.name}`);
});

// Emit with full type checking
emitter.emit('user:login', { id: 1, name: 'Alice' });

// Clean up when done
unsubscribe();

Fast Mode

When handlers never modify the listener list during emit, use zephyrEventsFast for maximum throughput:

import { zephyrEventsFast } from 'zephyr-events';

const emitter = zephyrEventsFast<AppEvents>();
// Same API — on(), off(), emit(), all

JavaScript Event Emitter

const zephyrEvents = require('zephyr-events');
const { zephyrEventsFast } = require('zephyr-events');

const emitter = zephyrEvents();       // safe (snapshot)
const fast = zephyrEventsFast();       // fast (no snapshot)

emitter.on('message', (data) => {
  console.log('Received:', data);
});

emitter.emit('message', { text: 'Hello' });

API Reference

zephyrEvents<Events>(all?)

Creates a new typed event emitter with snapshot-safe emission. Optionally accepts an existing handler map to share event state between emitters.

const emitter = zephyrEvents<{
  message: string
  data: { value: number }
}>();

// Share handlers between emitters
const shared = new Map();
const emitterA = zephyrEvents(shared);
const emitterB = zephyrEvents(shared);

zephyrEventsFast<Events>(all?)

Creates a non-snapshot event emitter. Same API as zephyrEvents, but emit() iterates the live handler array instead of a copy. Up to 82% faster for single-handler emit. Use when handlers do not add/remove listeners during emission.

import { zephyrEventsFast } from 'zephyr-events';

const emitter = zephyrEventsFast<{ tick: number }>();

emitter.on(type, handler): Unsubscribe

Registers an event handler. Returns an unsubscribe function for automatic cleanup — no need to keep a reference to the handler.

// Type-safe subscription
const unsub = emitter.on('message', (msg) => {
  console.log(msg);
});

// Wildcard listener — receives every event
emitter.on('*', (type, event) => {
  console.log(`[${String(type)}]`, event);
});

// Unsubscribe when no longer needed
unsub();

emitter.off(type, handler?)

Removes a specific handler, or all handlers for an event type.

// Remove a specific handler
emitter.off('message', myHandler);

// Remove all handlers for an event type
emitter.off('message');

emitter.emit(type, event)

Emits an event to all registered handlers. Handlers run synchronously from a snapshot, so the listener list can be safely modified during emission.

emitter.emit('message', 'Hello World!');
emitter.emit('data', { value: 42 });

emitter.all

The underlying Map<EventType, Handler[]> that stores all registered handlers. Can be inspected, serialized, or shared between emitter instances.

Use Cases

Event Bus for React Components

import zephyrEvents from 'zephyr-events';

type UIEvents = {
  'modal:open': { id: string }
  'modal:close': { id: string }
  'toast': { message: string; severity: 'info' | 'error' }
}

// Create a shared event bus
export const uiBus = zephyrEvents<UIEvents>();

// In a React component — clean up on unmount
useEffect(() => {
  const unsub = uiBus.on('toast', (toast) => {
    showToast(toast.message, toast.severity);
  });
  return unsub;
}, []);

Pub/Sub in Node.js Microservices

import zephyrEvents from 'zephyr-events';

type ServiceEvents = {
  'order:created': { orderId: string; total: number }
  'order:shipped': { orderId: string; trackingId: string }
  'inventory:low': { sku: string; remaining: number }
}

const events = zephyrEvents<ServiceEvents>();

// Multiple subscribers
events.on('order:created', sendConfirmationEmail);
events.on('order:created', updateAnalytics);
events.on('inventory:low', notifyWarehouse);

// Audit logging with wildcard
events.on('*', (type, data) => {
  logger.info({ event: type, payload: data });
});

Plugin System

import zephyrEvents from 'zephyr-events';

type PluginEvents = {
  'init': { config: Record<string, unknown> }
  'transform': { input: string }
  'destroy': undefined
}

function createPluginHost() {
  const emitter = zephyrEvents<PluginEvents>();

  return {
    register(plugin: (on: typeof emitter.on) => void) {
      plugin(emitter.on.bind(emitter));
    },
    emit: emitter.emit.bind(emitter),
  };
}

Race-Condition Safety

Unlike most event emitters, Zephyr Events handles listener modification during emission safely. Each emit() call iterates over a snapshot of the handler list, so adding or removing handlers mid-emit never causes skipped handlers or infinite loops:

const emitter = zephyrEvents();

// Safe: handler removes itself during emit
emitter.on('data', function once(data) {
  emitter.off('data', once);
  process(data); // next handler still fires
});

// Safe: handler adds new listeners during emit
emitter.on('init', () => {
  emitter.on('init', () => {
    // this will NOT fire during the current emit cycle
  });
});

Comparison with Other Event Emitters

| Feature | Zephyr Events | mitt | EventEmitter3 | Node.js EventEmitter | |---------|:---:|:---:|:---:|:---:| | Bundle size | ~2KB | ~200B | ~7KB | Built-in | | TypeScript types | Native | Native | Bundled | @types/node | | Race-condition safe | Yes | No | No | No | | Fast (no-snapshot) mode | Yes | No | No | No | | Wildcard listeners | Yes | Yes | No | No | | Unsubscribe function | Yes | No | No | No | | Shared handler maps | Yes | Yes | No | No | | Zero dependencies | Yes | Yes | Yes | N/A | | Tree-shakeable ESM | Yes | Yes | Yes | No |

Performance Benchmarks

Tested on Apple Silicon M-series (ARM64), Node.js v25.2.1. Median of 3 runs in isolated V8 processes. Run node benchmark-compare.js to reproduce.

Safe vs Fast

| Operation | Safe (snapshot) | Fast (no snapshot) | Delta | |-----------|----------------:|-------------------:|------:| | Emit (1 handler) | 49.4M ops/s | 89.9M ops/s | +82% | | Emit (10 handlers) | 14.0M ops/s | 16.9M ops/s | +21% | | Emit (100 handlers) | 1.9M ops/s | 2.1M ops/s | +12% | | Wildcard emit | 38.4M ops/s | 62.7M ops/s | +63% | | Emit (no wildcards) | 36.3M ops/s | 57.5M ops/s | +59% | | On + unsub cycle | 11.0M ops/s | 11.0M ops/s | 0% | | Off (specific handler) | 11.8M ops/s | 11.7M ops/s | 0% | | Mixed (on/emit/unsub) | 8.6M ops/s | 9.3M ops/s | +8% |

All benchmarks use proper setup/run separation with warmup passes. No-op handlers measure emitter overhead only — real-world throughput depends on handler complexity.

Bundle Formats

Zephyr Events ships three bundle formats for maximum compatibility:

| Format | File | Use case | |--------|------|----------| | ESM | dist/zephyr-events.mjs | Modern bundlers (Vite, Rollup, webpack 5+) | | CommonJS | dist/zephyr-events.js | Node.js require(), older bundlers | | UMD | dist/zephyr-events.umd.js | Script tags, AMD loaders, legacy environments |

Requirements

  • Node.js >= 18
  • TypeScript >= 4.7 (for type features; runtime has no TS dependency)
  • Works in all modern browsers (ES2020+ support)

Contributing

Contributions are welcome. Please read CONTRIBUTING.md before opening a pull request.

Acknowledgments

Zephyr Events is a modernization of mitt by Jason Miller. Built on the same simple API with added type safety, race-condition handling, and universal module support.

License

MIT

Original mitt: MIT (c) Jason Miller