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

white-label-mediator

v5.0.1

Published

Lightweight standards-based TypeScript event bus for loosely coupled browser, Node.js, and progressively enhanced applications with optional typed events.

Readme

white-label-mediator

Application events without application coupling.

white-label-mediator is a lightweight TypeScript and JavaScript event bus for loosely coupled browser and Node.js applications. It is built directly on the web-standard EventTarget, CustomEvent, and AbortSignal APIs and has no runtime dependencies.

Documentation · API reference · Demo site

Responsibility: move application intent between independent pieces. Nothing more.

Why it exists

White Label favors explicit composition over framework-owned communication. Mediator gives cross-module events a clear boundary while leaving event names, payloads, state, rendering, routing, and lifecycle policy in the application.

Use it independently or compose it with the rest of White Label:

The package has no runtime dependencies.

Where it fits

Mediator fits best when modules genuinely need to exchange application intent without directly importing or calling one another. That can be a new TypeScript application, a progressively enhanced server-rendered page, an existing CMS/commerce frontend being modernized one feature at a time, or Node.js modules that want the same standards-based event contract.

Because it is an EventTarget, it can be introduced independently. A host application does not need to adopt White Label Model, View, Router, a component framework, or a new rendering system just to use the event boundary.

Use direct function calls when the caller already owns the callee and no decoupling boundary is needed. Mediator is intentionally not a durable queue, service bus, cross-process broker, state store, or global application API.

Requirements

  • Node.js ^22.18.0 or >=24.11.0 for installation and development.
  • A runtime with EventTarget, CustomEvent, AbortController, and AbortSignal.any.
  • npm, Yarn, and pnpm are supported for installation; see PACKAGE_MANAGERS.md.

Install

npm install white-label-mediator
# or: yarn add white-label-mediator
# or: pnpm add white-label-mediator
import Mediator from 'white-label-mediator';
const mediator = new Mediator();

Publish and subscribe

Subscribe with the standard EventTarget API. Application payloads live in CustomEvent.detail:

function updateMenu(event) {
    document.querySelector('#main-menu').hidden = !event.detail.open;
}

mediator.addEventListener('menu:state', updateMenu);

Publish with a standard CustomEvent:

mediator.dispatchEvent(new CustomEvent('menu:state', {
    detail: {open: true}
}));

Delivery is synchronous. dispatchEvent() uses native EventTarget return semantics: it returns false only when a cancelable event is canceled with preventDefault(); it is not a listener-count signal.

Public API

Mediator extends the platform EventTarget class.

| Method | Behavior | | --- | --- | | addEventListener(name, callback, options?) | Subscribe using standard EventTarget options such as once and signal. Mediator tracks owned registrations for deterministic lifecycle cleanup. | | removeEventListener(name, callback, options?) | Remove a subscription using standard EventTarget matching rules. | | dispatchEvent(event) | Synchronously dispatch an Event or CustomEvent. | | initialize() | Start the lifecycle and return the same mediator instance. | | destroy() | Remove every listener owned by this mediator and return the same reusable instance. |

All other EventTarget behavior is native rather than reimplemented by this package.

Lifecycle and ownership

Keep callback references when a component owns explicit cleanup:

mediator.removeEventListener('menu:state', updateMenu);

Use native listener options for one-time or abortable intent:

mediator.addEventListener('application:ready', handleReady, {once: true});

const controller = new AbortController();
mediator.addEventListener('application:change', handleChange, {signal: controller.signal});
controller.abort();

destroy() is for the mediator itself leaving an application lifecycle. Mediator records its registrations and removes them explicitly during destruction, so cleanup does not depend on a shared lifecycle signal and the instance can be reused if needed.

Router intent

Mediator and Router compose through an ordinary web event:

mediator.dispatchEvent(new CustomEvent('router:navigate', {
    detail: {
        url: '/sign-in',
        reason: 'The session expired.'
    }
}));

Router remains responsible for navigation. Mediator only carries the event.

Model events

Model can relay local state changes to any mediator-compatible EventTarget without importing Mediator directly:

const session = new Model({authenticated: false});
session.name = 'session';
session.mediator = mediator;

mediator.addEventListener('model:session:update', event => {
    console.log(event.detail.authenticated);
});

This composition is structural: applications can use Mediator with Model, with another EventTarget-compatible source, or by itself.

Typed event details

Supply an event-detail map to type listener event names and CustomEvent.detail without adding runtime code:

type Events = {
    ready: {name: string};
    stopped: undefined;
};

const mediator = new Mediator<Events>();
mediator.addEventListener('ready', event => {
    console.log(event.detail.name);
});

mediator.dispatchEvent(new CustomEvent('ready', {
    detail: {name: 'Ada'}
}));

The generic map types listener registration. dispatchEvent() remains the native EventTarget method and accepts Event; applications construct CustomEvent payloads explicitly. TypeScript typing is not runtime payload validation.

Extend it when the application has a vocabulary

Application-specific convenience methods can live in a subclass without changing the package contract:

class ApplicationMediator extends Mediator {
    notifyError(error) {
        this.dispatchEvent(new CustomEvent('application:error', {
            detail: {message: error.message}
        }));
    }
}

This is the preferred place for domain vocabulary. The core package should remain generic rather than accumulating framework-, CMS-, commerce-, or cloud-specific methods.

Browser, server, and accessibility

Mediator has no window or document dependency. It uses web-platform event primitives that are also global in the supported Node.js versions, so browser and server applications use the same contract.

Because Mediator does not render markup, accessibility and indexing remain responsibilities of the consuming application. Mediated UI updates should preserve appropriate focus, announce meaningful asynchronous status when necessary, and avoid making important public content dependent on client-only events.

Serverless and function runtimes

Mediator can coordinate modules inside one serverless invocation without introducing a cloud-specific dependency. When listeners or payloads are request-specific, create the Mediator inside the request handler and call destroy() before that request-owned lifecycle ends.

Do not rely on a module-level Mediator for request isolation merely because the platform is called “serverless.” Warm function processes can handle many requests, so listeners and request data can survive into later invocations when a mutable instance is reused.

Mediator is an in-memory event bus. It does not replace SQS, SNS, EventBridge, Kafka, Pub/Sub, durable queues, retries, or communication between separate processes/function instances.

Event contract

Mediator follows standard EventTarget listener registration, removal, cancellation, and CustomEvent.detail payload semantics. It tracks owned registrations so destroy() can clean them up deterministically while leaving the instance reusable. See docs/events-compatibility.md for detailed compatibility notes.

TypeScript

Implementation uses strict TypeScript and emits JavaScript, source maps, and declarations into dist.

Development

npm ci --ignore-scripts
npm run build
npm run lint
npm run typecheck
npm test
npm run coverage
npm run audit
npm pack --dry-run

Coverage enforces 100% statements, branches, functions, and lines per implementation file. CI builds authored source, audits dependencies, packs the package, and verifies the public API across npm, Yarn, and pnpm.

Edit src/*.ts and regenerate dist; do not edit generated files directly.

Design boundary

Mediator moves named events. It intentionally does not own state, rendering, routing, networking, persistence, durable messaging, or application behavior. Keeping that boundary visible is what lets modules communicate without turning the event bus into the application itself.