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

@asaidimu/utils-events

v1.0.3

Published

A lightweight, type-safe event bus implementation for TypeScript applications.

Downloads

469

Readme

@asaidimu/utils-events

npm version license build status

A lightweight, type-safe event bus for TypeScript applications with batching, cross-instance broadcast, and built-in metrics.

Why another event bus? Many existing solutions either lack type safety or force a heavy dependency. This bus provides full TypeScript inference, optional batching to reduce synchronous dispatch overhead, and automatic cross-instance synchronisation – all in a tiny, zero-dependency package.


📚 Table of Contents


Overview & Features

@asaidimu/utils-events is a typed event bus designed for modern frontend and Node.js applications. It lets you define a strongly-typed event map once, then enjoy full autocompletion and compile-time checks for every emit, subscribe, and once call.

Key Features

  • 🔒 Type-safe – infer payload types from a single EventMap interface.
  • Batching (deferred mode) – coalesce rapid-fire events into a single flush to reduce layout thrashing and improve performance.
  • 📡 Cross-instance broadcast – synchronise events across browser tabs using BroadcastChannel (with graceful fallback).
  • 🩺 Built-in metrics – track total events, active subscriptions, per-event counts, and average dispatch duration.
  • 🧹 Cleanup utilities – unsubscribe handles, .clear() to fully reset the bus.
  • 🪶 Zero runtime dependencies – small footprint, easy to audit.

Installation

npm install @asaidimu/utils-events
yarn add @asaidimu/utils-events
pnpm add @asaidimu/utils-events

Requirements: TypeScript 4.7+ (for Record generic inference) and a modern runtime that supports performance.now() and optionally BroadcastChannel.


Quick Start

import { createEventBus } from '@asaidimu/utils-events';

// 1. Define your event map
interface AppEvents {
  userLogin: { userId: string; name: string };
  dataUpdate: { records: number };
  error: { message: string; code?: number };
}

// 2. Create the bus
const bus = createEventBus<AppEvents>();

// 3. Subscribe
const unsubscribe = bus.subscribe('userLogin', (payload) => {
  console.log(`Welcome ${payload.name}!`);
});

// 4. Emit an event
bus.emit({ name: 'userLogin', payload: { userId: '123', name: 'Alice' } });
// Logs: "Welcome Alice!"

// 5. Unsubscribe when done
unsubscribe();

API Reference

createEventBus

function createEventBus<TEventMap extends Record<string, any>>(
  options?: EventBusOptions
): EventBus<TEventMap>

Creates a new event bus instance.

Options

| Option | Type | Default | Description | | -------------------- | ----------------------------- | --------------------------- | --------------------------------------------------------------------------- | | batch.size | number | undefined | Enables deferred mode; flush when queue reaches this size. | | batch.delay | number | 1000 (if batching) | Quiet period (ms) before flushing a batch. | | errorHandler | (error: EventError) => void | console.error | Custom error handler for subscriber callbacks. | | broadcast.channel | string | "event-bus-channel" | Enables cross-instance broadcast using the given BroadcastChannel name. |

If batch.size is provided, the bus runs in deferred mode (events are queued and flushed asynchronously). Otherwise it runs in synchronous mode (events are dispatched immediately).


.subscribe()

subscribe<TEventName extends keyof TEventMap>(
  eventName: TEventName,
  callback: (payload: TEventMap[TEventName]) => void
): () => void

Registers a permanent listener. Returns an unsubscribe function.

const off = bus.subscribe('dataUpdate', ({ records }) => {
  updateUI(records);
});

// Later
off();

.once()

once<TEventName extends keyof TEventMap>(
  eventName: TEventName,
  callback: (payload: TEventMap[TEventName]) => void
): () => void

Registers a one-time listener that automatically unsubscribes after the first emission. Returns a cancel function (to unsubscribe before it fires).

bus.once('userLogin', (payload) => {
  console.log('First login only');
});

// The callback will run at most once.

.emit()

emit<TEventName extends keyof TEventMap>(
  event: {
    name: TEventName;
    payload: TEventMap[TEventName];
  }
): void

Dispatches an event. In synchronous mode all subscribers run immediately. In deferred mode the event is queued and flushed according to batch.size and batch.delay. Cross-instance messages are sent immediately even in deferred mode to avoid latency.

bus.emit({ name: 'dataUpdate', payload: { records: 42 } });

.metrics()

metrics(): EventMetrics

Returns performance and usage statistics.

console.log(bus.metrics());
// {
//   totalEvents: 127,
//   activeSubscriptions: 5,
//   eventCounts: Map { 'userLogin' => 45, 'dataUpdate' => 82 },
//   averageEmitDuration: 0.32   // ms
// }

.clear()

clear(): void

Removes all subscriptions, clears the event queue (if in deferred mode), resets all metrics, and re-opens the BroadcastChannel (if enabled). After calling clear() the bus is fully reusable.

bus.clear(); // fresh start

Advanced Usage

Batching / Deferred Mode

When many events are fired in rapid succession (e.g., keystrokes, scroll handlers), synchronous dispatch can cause performance issues. Batching coalesces them into a single microtask / timer flush.

const batchedBus = createEventBus<MyEvents>({
  batch: {
    size: 20,    // flush after 20 queued events
    delay: 100   // or after 100ms of inactivity
  }
});
  • If the queue reaches batch.size before the timer expires, it flushes immediately.
  • The timer resets on every new event (quiet period).
  • Metrics are still collected per event, so you can measure the real dispatch cost.

Cross-instance Broadcast

Enable the broadcast option to automatically send every emitted event to other instances that share the same channel name. Events received from another instances are dispatched to local subscribers exactly as if they were emitted locally.

const bus = createEventBus<MyEvents>({
  broadcast: { channel: 'my-app-events' }
});

// In tab A
bus.emit({ name: 'userLogin', payload: { userId: '1' } });

// In tab B (same origin)
bus.subscribe('userLogin', (payload) => {
  console.log('Another tab logged in:', payload.userId);
});

⚠️ BroadcastChannel is not supported in Node.js. The bus will log a warning and disable cross-instance functionality gracefully. For Node.js, simply omit the broadcast option.

Custom Error Handling

By default, any error thrown inside a subscriber callback is caught and logged to console.error. Override this to send errors to a monitoring service.

const bus = createEventBus<MyEvents>({
  errorHandler: (err) => {
    myErrorTracker.capture(err, {
      eventName: err.eventName,
      payload: err.payload
    });
  }
});

The EventError interface extends Error and adds optional eventName and payload fields.


Architecture Notes

  • Subscriber snapshotting: When an event is dispatched, the bus iterates over a snapshot of the current subscribers. This prevents bugs where a callback calls unsubscribe() on itself and stops other listeners from running.
  • Metrics overhead: performance.now() is called twice per synchronous event (or per event inside a batch). This overhead is negligible (< 0.01ms) but can be ignored if not needed.
  • Cross-instance isolation: BroadcastChannel does not send messages to the originating tab. Therefore there is no risk of infinite loops when broadcasting.
  • Debouncer: The batching mechanism uses an internal Debouncer utility that guarantees a final flush after the quiet period, even if no new events arrive.

Reporting Issues

Please use the GitHub issue tracker and include:

  • A minimal reproduction (code snippet)
  • Expected vs actual behaviour
  • Environment (browser / Node, version)

License

MIT © Saidimu. See LICENSE for details.


Built with ❤️ for type-safe event-driven architectures.