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

mono-event

v0.3.0

Published

A minimal, type-safe single-event management library for JavaScript/TypeScript

Readme

mono-event

mono-event is a minimal, type-safe single-event management library for JavaScript/TypeScript. It allows you to handle individual events with a straightforward API, similar to C# events, while keeping things lightweight and intuitive.

Features

  • Minimal API Easily add, remove, and emit events with a few function calls.
  • Type-Safe Leverage TypeScript generics to ensure that event data types are checked at compile time.
  • Balanced Performance Designed with a focus on balancing overall performance, memory usage, and bundle size, providing excellent results in practical use cases.
  • Tiny Bundle Size Only 4.08 KB minified (1.05 KB gzipped), making it lightweight while maintaining full functionality.
  • Synchronous / Asynchronous Support Choose between mono (synchronous) and monoAsync (asynchronous) versions. With monoAsync, you can control whether asynchronous listeners run sequentially or in parallel.
  • Emission Control Separation With monoRestrict and monoRestrictAsync, you can separate the responsibilities of event registration (add/remove) and event emission, preventing accidental or unauthorized event firing.
  • Flexible Listener Registration Register listeners with or without a caller context, and use the once option for one-time event handling.
  • Comprehensive Listener Management Remove listeners by reference, by caller context, or remove all listeners at once.
  • Direct Event Emitter Integration Use the .emitter property to easily integrate with existing event systems like DOM events.

Installation

Node.js

You can install mono-event via npm or yarn:

npm install mono-event
# or
yarn add mono-event

Bun

bun add mono-event

Deno

// Import from npm
import {mono} from "npm:mono-event";

// Or import from URL
import {mono} from "https://esm.sh/mono-event";

Usage

1. Basic Synchronous Event (mono-event)

import {mono} from 'mono-event';

const event = mono<string>();

// Register a listener (returns an unsubscribe function)
const unsubscribe = event.add((msg) => {
  console.log("Received:", msg);
});

// Register a listener with caller context
class MyHandler {
  value = '';

  handleEvent(msg: string) {
    this.value = msg;
    console.log("Handler received:", msg);
  }
}

const handler = new MyHandler();
event.add(handler, handler.handleEvent);

// Register a one-time listener
event.add((msg) => {
  console.log("One-time event:", msg);
}, {once: true});

// Emit an event
event.emit("Hello, world!");

// Unsubscribe using the returned function
unsubscribe();

// Or remove by reference
event.remove(handler, handler.handleEvent);

// Remove all listeners
event.removeAll();

// Using the emitter property with DOM events
const keydownEvent = mono<KeyboardEvent>();
window.addEventListener('keydown', keydownEvent.emitter);

// Later, to remove:
window.removeEventListener('keydown', keydownEvent.emitter);

2. Asynchronous Event (monoAsync)

For cases where asynchronous processing is required, use monoAsync. You can optionally choose between sequential (default) and parallel execution of async listeners.

import {monoAsync} from 'mono-event';

const asyncEvent = monoAsync<number>();

// Register an async listener
asyncEvent.add(async (num) => {
  await new Promise((resolve) => setTimeout(resolve, 1000));
  console.log("Processed:", num);
});

// Register with caller context
class AsyncProcessor {
  async process(num: number) {
    await new Promise((resolve) => setTimeout(resolve, 500));
    console.log("Processor received:", num);
  }
}

const processor = new AsyncProcessor();
asyncEvent.add(processor, processor.process);

// Register a one-time async listener
asyncEvent.add(async (num) => {
  console.log("One-time async:", num);
}, {once: true});

// Emit an event and wait for all listeners to finish
await asyncEvent.emit(42);

// Using parallel execution
const asyncEventParallel = monoAsync<number>({parallel: true});

3. Restricted Emission (monoRestrict)

When you want to clearly separate event registration from emission, use monoRestrict.

import {monoRestrict} from 'mono-event';

const {event, emit} = monoRestrict<string>();

// External code can register listeners using event.add()
event.add((msg) => {
  console.log("Restricted Received:", msg);
});

// With caller context
class Receiver {
  handleMessage(msg: string) {
    console.log("Receiver got:", msg);
  }
}

const receiver = new Receiver();
event.add(receiver, receiver.handleMessage);

// Remove listeners when needed
event.remove(receiver, receiver.handleMessage);
event.removeAll();

// Emission is performed via the emit() function
emit("Restricted Hello");

4. Asynchronous Restricted Emission (monoRestrictAsync)

For async events with restricted emission, use monoRestrictAsync.

import {monoRestrictAsync} from 'mono-event';

const {event, emit} = monoRestrictAsync<number>();

event.add(async (num) => {
  await new Promise((resolve) => setTimeout(resolve, 500));
  console.log("Async Restricted:", num);
});

// With caller context and once option
class AsyncReceiver {
  async process(num: number) {
    await new Promise((resolve) => setTimeout(resolve, 300));
    console.log("AsyncReceiver processed:", num);
  }
}

const receiver = new AsyncReceiver();
event.add(receiver, receiver.process, {once: true});

await emit(123);

5. Using Decorators (monoDebounce, monoThrottle)

Utility functions are provided to easily apply common patterns like debouncing and throttling to your event handlers.

import { mono, monoDebounce, monoThrottle } from 'mono-event';

const event = mono<string>();
const waitMs = 500;

// Debounced handler: Executes only after 500ms of inactivity
const debouncedHandler = monoDebounce((data) => {
  console.log(`Debounced: ${data}`);
}, waitMs);

// Throttled handler: Executes at most once every 500ms (leading + trailing edge)
const throttledHandler = monoThrottle((data) => {
  console.log(`Throttled: ${data}`);
}, waitMs);

event.add(debouncedHandler);
event.add(throttledHandler);

// Example emissions
event.emit('A'); // Throttle executes immediately
event.emit('B');
setTimeout(() => event.emit('C'), 100);
// ... (Debounce executes after 500ms pause, Throttle executes trailing calls)

API Overview

mono<T>()

  • Returns:
    • add(handler: (args: T) => void, options?: { once?: boolean }): () => void
    • add(caller: object, handler: (args: T) => void, options?: { once?: boolean }): () => void
    • remove(handler: (args: T) => void): boolean
    • remove(caller: object, handler: (args: T) => void): boolean
    • removeAll(): void
    • emit(args: T): void
    • emitter: (args: T) => void - A function property that calls emit with the provided argument. Useful for integrating with existing event systems.

monoAsync<T>(options?: { parallel?: boolean })

  • Options:
    • parallel: Determines whether async listeners run in parallel (true) or sequentially (false, default)
  • Returns:
    • add(handler: (args: T) => Promise<void> | void, options?: { once?: boolean }): () => void
    • add(caller: object, handler: (args: T) => Promise<void> | void, options?: { once?: boolean }): () => void
    • remove(handler: (args: T) => Promise<void> | void): boolean
    • remove(caller: object, handler: (args: T) => Promise<void> | void): boolean
    • removeAll(): void
    • emit(args: T): Promise<void>
    • emitter: (args: T) => void - A function property that calls emit with the provided argument. Useful for integrating with existing event systems.

monoRestrict<T>()

  • Returns:
    • An object { event, emit } where:
      • event: An object with the following methods:
        • add(handler: (args: T) => void, options?: { once?: boolean }): () => void
        • add(caller: object, handler: (args: T) => void, options?: { once?: boolean }): () => void
        • remove(handler: (args: T) => void): boolean
        • remove(caller: object, handler: (args: T) => void): boolean
        • removeAll(): void
      • emit(args: T): void: A function dedicated to emitting events. This separation helps clearly define who is responsible for firing the event.

monoRestrictAsync<T>(options?: { parallel?: boolean })

  • Options:
    • parallel: Determines whether async listeners run in parallel (true) or sequentially (false, default)
  • Returns:
    • An object { event, emit } where:
      • event: An object with the same methods as in monoRestrict, but supporting async handlers
      • emit(args: T): Promise<void>: A function dedicated to emitting events, returning a Promise that resolves when all handlers have completed.

Decorators

  • monoDebounce<F extends Function>(func: F, wait: number): F
    • Creates a debounced function that delays invoking func until after wait milliseconds have elapsed since the last time the debounced function was invoked.
  • monoThrottle<F extends Function>(func: F, wait: number): F
    • Creates a throttled function that only invokes func at most once per every wait milliseconds (leading + trailing edge behavior).

Contributing

Contributions, feedback, and bug reports are welcome! Please see CONTRIBUTING.md for details on our code of conduct and the process for submitting pull requests.

Performance

mono-event is designed with a focus on balancing overall performance, memory usage, and bundle size, rather than pursuing the absolute fastest speed for specific operations. This approach provides excellent performance and a good developer experience in many practical use cases.

The latest benchmark results are as follows:

Performance Summary (Node.js)

| Library | Init (ms) | Register (Single) (ms) | Register (Multi) (ms) | Removal (Fwd) (ms) | Removal (Bwd) (ms) | Removal (Rnd) (ms) | Emit (ms) | Emit Once (ms) | Memory (Empty) (KB/inst) | Memory (100 Listeners) (KB/inst) | Comprehensive (ms) | |:-----------------|----------:|-------------------:|------------------:|------------------:|------------------:|------------------:|----------:|---------------:|-----------------:|--------------------:|------------:| | mono-event | 5.76 | 6.17 | 6.04 | 7.35 | 75.13 | 49.36 | 205.42 | 1.12 | 0.13 | 8.44 | 1977.62 | | Restrict | - | - | - | - | - | - | 209.24 | - | - | - | - | | EventEmitter3 | 3.99 | 1.72 | 2.86 | 218.14 | 210.16 | 238.02 | 240.84 | 224.64 | 0.09 | 7.33 | 1936.56 | | mitt | 58.50 | 3.85 | 8.18 | 6.09 | 10.68 | 9.01 | 217.67 | 6.31 | 0.49 | 2.94 | 1999.64 | | nanoevents | 2.84 | 3.85 | 1.68 | 220.79 | 172.63 | 178.82 | 164.89 | 186.16 | 0.23 | 13.49 | 1341.75 | | RxJS | 4.44 | 41.54 | 79.27 | 10.73 | 12.67 | 12.83 | 372.83 | 9.33 | 0.15 | 52.50 | 5047.33 | | Node Events | 101.13 | 2.64 | 30.17 | 58.35 | 1.95 | 64.40 | 237.65 | 119.43 | 0.28 | 5.76 | 1651.50 | | EventTarget | 164.45 | 12229.94 | 48.62 | 156.30 | 311.80 | 262.19 | 318.72 | 339.96 | 0.44 | 9.34 | 2735.08 |

Performance Summary (Bun)

| Library | Init (ms) | Register (Single) (ms) | Register (Multi) (ms) | Removal (Fwd) (ms) | Removal (Bwd) (ms) | Removal (Rnd) (ms) | Emit (ms) | Emit Once (ms) | Comprehensive (ms) | |:-----------------|----------:|-------------------:|------------------:|------------------:|------------------:|------------------:|----------:|---------------:|------------:| | mono-event | 2.38 | 4.59 | 12.91 | 88.04 | 0.67 | 58.90 | 121.48 | 0.86 | 696.29 | | Restrict | - | - | - | - | - | - | 126.51 | - | - | | EventEmitter3 | 1.98 | 1.43 | 4.88 | 178.34 | 152.54 | 195.17 | 132.41 | 160.25 | 992.84 | | mitt | 23.35 | 0.77 | 2.89 | 6.35 | 9.94 | 9.64 | 180.11 | 5.94 | 2197.11 | | nanoevents | 2.20 | 1.47 | 2.91 | 158.35 | 152.67 | 182.38 | 136.22 | 160.28 | 1259.63 | | RxJS | 42.25 | 12.16 | 15.99 | 7.36 | 11.22 | 11.83 | 205.36 | 6.57 | 2005.92 | | Node Events | 118.95 | 1.28 | 4.19 | 60.20 | 0.91 | 34.83 | 145.26 | 75.15 | 989.63 | | EventTarget | 566.28 | 13155.77 | 34.42 | 127.79 | 252.76 | 204.71 | 1164.29 | 132.11 | 12307.24 |

Memory Usage Summary (Node.js)

| Library | Per Instance (KB) | With 10,000 Handlers (KB) | 1,000 Events × 100 Instances (KB) | 1,000,000 Instances (Total KB) | |:---------------|-------------------:|--------------------------:|------------------------------------:|-------------------------------:| | mono-event | 0.09 | 2,461.63 | - | 279,654.78 | | EventEmitter3 | 0.09 | 746.95 | 4,376.42 | 162,788.71 | | mitt | 0.29 | 254.66 | 2,287.19 | 526,686.86 | | nanoevents | 0.24 | 1,344.7 | 15,854.79 | 401,915.58 | | RxJS | 0.13 | 6,340.63 | 71,008.05 | 765,941.68 | | Node Events | 0.05 | 279.98 | 0 | 247,240 | | EventTarget | 0.2 | 1,044.61 | - | 535,361.14 |

Note: nodeEvents and eventTarget results are only available in Node.js environment. Bundle size for Node.js built-ins is N/A. CV: Coefficient of Variation (lower is better, indicates stability).

You can find detailed performance benchmarks comparing mono-event with other popular event libraries (EventEmitter3, mitt, nanoevents, RxJS, Node Events, EventTarget) in the docs/performance directory.

Note: While mono-event aims for a good balance, each library has its own strengths. EventEmitter3 offers a familiar Node.js-like API, mitt and nanoevents prioritize minimal bundle size, and RxJS provides powerful reactive programming capabilities beyond simple event handling.

Benchmark Environment: Performance tests were conducted on macOS with an Apple M2 Ultra processor. Results are the average of 3 runs with the configurations specified in docs/performance/benchmark.js.

To run the benchmarks yourself:

# Install benchmark dependencies
npm install eventemitter3 mitt nanoevents rxjs --save-dev

# Build the library
npm run build

# Run benchmarks with Node.js (all scenarios)
node --expose-gc docs/performance/benchmark.js
# Or run specific scenarios by number (e.g., 7 for Emission, 11 for Comprehensive)
node --expose-gc docs/performance/benchmark.js 7 11

# Run benchmarks with Bun (all scenarios)
# Note: Memory results might be inaccurate in Bun due to GC differences.
bun --gc-expose docs/performance/benchmark.js
# Or run specific scenarios by number
bun --gc-expose docs/performance/benchmark.js 7 11

# Run benchmarks with Deno (all scenarios)
# Note: Memory results might be inaccurate in Deno due to GC differences.
# Requires network, read, and env permissions for dependencies and timing.
deno run --import-map deno.importmap.json --allow-net --allow-read --allow-env docs/performance/benchmark.js
# Or run specific scenarios by number
deno run --import-map deno.importmap.json --allow-net --allow-read --allow-env docs/performance/benchmark.js 7 11

Note: The benchmark dependencies are not included in the package by default to keep it lightweight.

Testing with Bun and Deno

This library is fully compatible with Bun and Deno. You can run the tests in each environment:

Bun

# Install Bun if you haven't already
curl -fsSL https://bun.sh/install | bash

# Run tests with Bun
npm run test:bun
# or directly
bun test bun.test.ts

Deno

# Install Deno if you haven't already
curl -fsSL https://deno.land/install.sh | sh

# Run tests with Deno
npm run test:deno
# or directly
deno test deno.test.ts --import-map=deno.importmap.json

Credits

mono-event was designed by yukimi-inu with coding assistance from Roo Code (Claude 3.7 Sonnet).

License

This project is licensed under the MIT License.