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

@infra-blocks/emitter

v0.3.0

Published

This package offers utilities related to Node.Js' event emitters.

Readme

ts-emitter

Build Release codecov

This package contains a few type safe emitter related utilites. First, it does expose yet another freaking emitter. It also offers various types, such as the EmitterLike interface, to maximize code reuse across projects. There is also a base class that client code can implement to automatically inherit the on and once methods, properly typed.

Another emitter

Why? Because I thought that the Node.js packaged one lacked some features (async emission, for example), and the ones I'd been looking at in other NPM packages were missing just a little bit of stuff. I don't claim that this is the best, nor the fastest event emitter (although it is quite simple in its implementation, so it could be among the fastest). I do claim, however, that it is configurable as fuck.

No nonsense (Keith Peterson)

The only methods we keep from the Node.js interface are on, once and emit. We do away with the aliases (such as addListener) and pretty much every other method I've never actually had to use in my career.

The default behavior when emitting an error event without any listener registered is the same as any other fucking event: nothing happens. There. Not trying to be smart here.

Emit as a dependency injection

This emitter is designed such that the emit method can be easily switched out to change the semantics. By default, when using the base factory function, it behaves just like the Node.js one: when an event is emitted, listeners are called in sequence and their results are completely ignored (vanilla behavior before the support for handling rejections).

import { Emitter } from "@infra-blocks/emitter";

type MyEvents = {
  gogo: (gadget: string) => void;
}

const emitter = Emitter.create<MyEvents>();
emitter.on("gogo", (gadget) => console.log("gadget is: ", gadget));
emitter.on("gogo", () => console.log("was that show ever good?"));
emitter.emit("gogo", "retractablePenus");

// Prints out:
// gadget is: retractablePenus
// was that show ever good?

In addition, the default strategy also exports weird members on emit, just to showcase what you could do yourself if you wanted to support more than one strategy for the same emitter.

// Wraps all listener results in a Promise.all and returns that.
const results = await emitter.emit.awaitAll("gogo", "retractablePenus");
// Same but for Promise.allSettled. Try it. The typing works esé.
const results = await emitter.emit.awaitAllSettled("gogo", "retractablePenus");
// They are executed one at a time, when a listener is only invoked if all
// previous listeners have already resolved.
await emitter.emit.awaitEach("gogo", "retractablePenus");

If you know you always want the awaitAll logic, for example, and don't want the weird emit attributes, you can also create an emitter that does only that:

type MyEvents = {
  // Does something asynchronous with the poop quantity.
  poop: (quantityInLitres: number) => Promise<boolean>;
}

const emitter = Emitter.awaitingAll<MyEvents>();
// ...
const results: Array<boolean> = await emitter.emit("poop", 2.3); // My kid shits a lot. Notice how emit now returns a promise?

You can customize the behavior of emit and its typing. For example you could have different strategy on different events, by doing so:

type Events = {
  left: () => number;
  right: (v: number) => string;
};

type Results = {
  left: undefined; // We will ignore the return values.
  right: Promise<string[]>; // We will Promise.all them here.
};

const emitter = Emitter.withStrategyFactory<
  Events,
  Results,
  EmitStrategy<Events, Results>
>((listeners) => {
  return <K extends keyof Events>(
    event: K,
    ...args: Parameters<Events[K]>
  ) => {
    // Typescript needs a little help with the return typing.
    switch (event) {
      case "left":
        // Fire the listeners and ignore their results.
        for (const _ of listeners.invocations(event, ...args)) {
        }
        return undefined as Results[K];
      case "right":
        return Promise.all(
          listeners.invocations(event, ...args),
        ) as unknown as Results[K];
    }
  };
});

const left = emitter.emit("left"); // Type of left is undefined.
const right = emitter.emit("right", ...); // Type of right is Promise<string[]>

Nuff said about that emitter.

EmitterLike interface

The EmitterLike interface is defined as such:

export type Events = {
  [key: string]: (...args: any[]) => unknown;
};

export interface EmitterLike<E extends Events> {
  on<Event extends keyof E>(event: Event, handler: E[Event]): this;
  once<Event extends keyof E>(event: Event, handler: E[Event]): this;
}

It's basically to provide a common interface for classes that support event registration, but handle emission internally. The interface is automatically type safe with regards to the event type.

EmitterLikeBase base classes

In addition, base classes are provided to implement the EmitterLike interface with straightforward implementations. EmitterLikeBase is a simple base class with an emitter that re-exports the emit function to its subclasses with protected visibility, and implements the EmitterLike interface by dispatching to the internal emitter.

Here is a simple example usage:

// MyEmitterLike class allows subscriptions to "connect", "data" and "disconnect" events.
// NOTE: this should be a "type" and not an "interface" to get string indexing for free, although
// it is also possible with an interface (that extends Record<...>).
export type MyEvents = {
  connect: () => void;
  data: (stuff: number) => Promise<void>;
  disconnect: () => void;
}

// The generics take care of the type-safety, and the emitter like boiler-plate logic
// is taken care of.
export class MyEmitterLiker extends EmitterLikeBase<MyEvents, DefaultStrategy<MyEvents, AlwaysVoid<MyEvents>>> {
  private readonly client: MyClient;

  constructor() {
    super({ emitter: Emitter.create<MyEvents>() })
    this.client = new MyClient();
  }
  
  async callMe() {
    await this.client.connect();
    // The `emit` method is only available within the implementer with the `protected` visibility,
    // and it is type-safe with regards to the expected handler arguments.
    this.emit("connect");
    for await (const stuff of this.client.readStuff()) {
      // The same emit methods that the emitter supports are offered in the subclass.
      await this.emit.awaitEach("data", stuff);
    }
    await this.client.disconnect();
    this.emit("disconnect");
  }
}

const myEmitterLike = new MyEmitterLike();
// The subscription methods are available.
myEmitterLike.once("connect", () => console.log("connected!"));
myEmitterLike.once("disconnect", () => console.log("disconnected!"));
myEmitterLike.on("data", async (stuff) => await db.storeStuff(stuff));
await myEmitterLike.callMe();

EmitterLikeBase is at the top of the hierarchy, and allows maximum configuration. If you're using one of the provided strategies, a matching base class exists as well to minimize that pesky fucking typing:

// As above
export class MyEmitterLiker extends EmitterLikeBase<MyEvents, DefaultStrategy<MyEvents, AlwaysVoid<MyEvents>>> {
  constructor() {
    super({ emitter: Emitter.create<MyEvents>() })
  }
}

// Can be rewritten as:
export class MyEmitterLiker extends DefaultStrategyEmitterLikeBase<MyEvents> {
  // No need to call the super constructor.
}