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

@mubaraksoft/event-emitter

v1.0.5

Published

Simple libaray to manage events in browser

Downloads

37

Readme

npm version npm downloads bundle size TypeScript License: MIT

Event Emitter

A tiny, strongly‑typed wrapper around the Web Platform’s EventTarget that gives you:

  • End‑to‑end TypeScript types for event names and payloads
  • Namespaced listeners with easy cleanup via AbortController
  • A familiar dispatchEvent / addEventListener API
  • Optional per‑namespace subscriber hook for observability/devtools

Works in modern browsers and in Node.js (v16.8+ where EventTarget and AbortController are available; see Compatibility).


Install

npm i @mubaraksoft/event-emitter
# or
pnpm add @mubaraksoft/event-emitter
# or
yarn add @mubaraksoft/event-emitter

No runtime dependencies!


Quick Start

Define your event map once, get type‑safe emitters and listeners everywhere.

// 1) Define your event map (name -> payload)
export type AppEvents = {
  "user:login": { id: string; email: string };
  "toast:show": { message: string; level?: "info" | "warn" | "error" };
  "counter:changed": { value: number };
};

// 2) Create an emitter
const bus = new EventEmitter<AppEvents>();

// 3) Add a listener (fully typed)
const off = bus.addEventListener({
  name: "counter:changed",
  callback: (e) => {
    // e is CustomEvent<{ value: number } & { eventName?: "counter:changed" }>
    console.log("count is", e.detail.value, "from", e.detail.eventName);
  },
});

// 4) Dispatch an event (payload is validated by TS)
bus.dispatchEvent({ name: "counter:changed", payload: { value: 2 } });

Why this package?

EventTarget is great but untyped. This library layers type safety and ergonomics without reinventing the wheel:

  • You keep CustomEvent semantics, bubbling model, and browser performance.
  • You gain compile‑time checking of event names and payload shapes.
  • You get namespaces for grouping listeners and cleaning them up in one go.

Namespaces & Cleanup

Group listeners by a string namespace and dispose them all at once.

const authNS = bus.namespace("auth"); // same underlying bus

const listeners = authNS.addEventsListener({
  names: ["user:login", "toast:show"],
  callback: (e) => {
    // typed as union of the selected events
  },
});

// Later: remove a single listener
authNS.removeEventListener(listeners[0]);

// Or nuke the whole namespace (uses AbortController under the hood)
authNS.removeAllListeners();

EventEmitter#removeAllListeners(namespace?) aborts the internal AbortController for that namespace, detaching all listeners registered with it.


Subscriber hook (observability)

Use a per‑namespace subscriber to observe every dispatch going through that namespaced emitter—great for logging or devtools.

const log = bus.namespace("log", (evt) => {
  // evt: { name: keyof AppEvents; payload?: AppEvents[keyof AppEvents] }
  console.debug("dispatched:", evt.name, evt.payload);
});

log.dispatchEvent({ name: "toast:show", payload: { message: "Saved" } });

The subscriber runs after the event has been dispatched on the underlying EventTarget.


Strong typing end‑to‑end

  • dispatchEvent validates name and payload.
  • Listener callback receives CustomEvent<Payload & { eventName?: Name }>
  • addEventsListener infers a union of payloads when you listen to multiple names.
bus.addEventListener({
  name: "toast:show",
  callback: (e) => {
    // e.detail.level is "info" | "warn" | "error" | undefined
  },
});

// ❌ Type error – missing required payload property
bus.dispatchEvent({ name: "counter:changed", payload: {} });

API

Types

type EventMapBase = Record<string, unknown>;
type EmittedEventName<M> = keyof M & string;

type GenericEventCallback<M, N> = (
  event: CustomEvent<WithEventName<N, M[N]>>
) => void;

type EventListener<M, N = EmittedEventName<M>> = {
  name: N;
  namespace?: string; // default: "global"
  callback?: GenericEventCallback<M, N>;
};

type EventsListener<M> = Omit<EventListener<M>, "name"> & {
  names: EmittedEventName<M>[];
};

type EventDispatcher<M, N = EmittedEventName<M>> = {
  name: N;
  payload?: M[N];
};

class EventEmitter<M extends EventMapBase>

  • addEventListener(listener)EventListener<M, N>

    • Registers a single listener. Optional namespace (default "global").
  • addEventsListener({ names, callback, namespace })EventListener<M>[]

    • Register the same callback for multiple event names.
  • dispatchEvent({ name, payload })void

    • Dispatches a CustomEvent(name, { detail }) where detail merges the payload with { eventName: name }.
  • removeEventListener(listener)void

    • Removes a previously added listener.
  • removeEventListeners(listeners)void

    • Convenience to remove an array of listeners.
  • removeAllListeners(namespace = "global")void

    • Aborts the namespace’s controller to remove all listeners under it.
  • namespace(name, subscriber?)EventEmitterNamespace<M>

    • Returns a namespaced wrapper sharing the same underlying emitter.

class EventEmitterNamespace<M> implements IEventEmitter<M>

A thin façade that automatically injects its namespace into calls and optionally invokes subscriber on each dispatch.

Methods mirror EventEmitter:

  • addEventListener, addEventsListener, removeEventListener, removeEventListeners, removeAllListeners, dispatchEvent.

Patterns & Recipes

Component‑local bus

export type CounterEvents = { "counter:changed": { value: number } };

export function createCounterBus() {
  return new EventEmitter<CounterEvents>();
}

Feature‑scoped namespaces

const bus = new EventEmitter<AppEvents>();
const auth = bus.namespace("auth");
const ui = bus.namespace("ui");

ui.addEventListener({ name: "toast:show", callback: showToast });

// teardown
ui.removeAllListeners();

Register many, remove later

const ns = bus.namespace("page:settings");
const listeners = ns.addEventsListener({
  names: ["toast:show", "counter:changed"],
  callback: (e) => {
    /* ... */
  },
});

// On unmount
ns.removeEventListeners(listeners);

Forward events between emitters

function forward<M extends EventMapBase>(
  from: EventEmitter<M>,
  to: EventEmitter<M>,
  names: (keyof M & string)[]
) {
  const ns = from.namespace("forward:" + names.join(","));
  ns.addEventsListener({
    names: names as any,
    callback: (e) =>
      to.dispatchEvent({ name: e.detail.eventName as any, payload: e.detail }),
  });
  return () => ns.removeAllListeners();
}

Compatibility

  • Browsers: Modern browsers supporting EventTarget, CustomEvent, and AbortController.
  • Node.js: v16.8+ (global EventTarget & AbortController). For older versions, bring a polyfill/shim (e.g. event-target-shim) or run in a DOM‑like environment.

This package is framework‑agnostic and has zero deps.


FAQ

Why is eventName included in detail? It’s convenient in generic handlers and when forwarding/logging. It’s optional and typed as the concrete name that was dispatched.

Does this support event bubbling or capture? It uses a single EventTarget without a DOM tree, so bubbling/capture semantics aren’t relevant. You can still build higher‑level patterns if you need them.

How do I prevent memory leaks? Use namespaces and call removeAllListeners(namespace) during teardown (e.g., component unmount). Internally we use AbortController so all listeners under that namespace detach at once.

Can I add multiple callbacks to the same name? Yes—just call addEventListener multiple times.


Exported surface

export type {
  EventMapBase,
  EmittedEventName,
  GenericEventCallback,
  GenericEventListener as EventListener,
  GenericEventDispatcher as EventDispatcher,
  EventsListener,
  IEventEmitter,
};
export { EventEmitter, EventEmitterNamespace };

License

MIT © mubaraksoft