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 🙏

© 2024 – Pkg Stats / Ryan Hefner

@webex/ts-events

v1.1.0

Published

A library for subscribing to and emitting type-safe events using a familiar, javascript-style API:

Downloads

2,189

Readme

ts-events

A library for subscribing to and emitting type-safe events using a familiar, javascript-style API:

myClass.on('eventName', (value: number) { ... });
// Error: handler signature is wrong
myClass.on('eventName', (value: boolean) { ... });
// Error: event name is wrong
myClass.on('eventNameWithATypo', (value: number) { ... });

Why does this library exist?

There exist multiple typesafe event emitter libraries for typescript (we were previously using this one and also tried this one), so why is this library necessary?

When using existing solutions, we were unable to implement an event 'hierarchy': where a class inheritance chain could have each class defining appropriate events for their level. Some links to issues others have had around this can be found here, and here. Playgrounds with our own attempts to make the above libraries work can be found below:

  1. An attempt still using EventEmitter from typed-emitter, but adding generics to allow subclasses to add events: Playground
  2. A slightly different approach using the tiny-typed-emitter lib: Playground

An additional downside of the above libraries is that, since they involve inheriting from the EventEmitter class, the emit method was exposed as part of the class' API; we'd like to be able to prevent outside entities from emitting a class' events.

When developing this library, we had the following requirements:

  1. As much type safety as possible should be provided. Referring to an invalid event name or passing a handler whose signature doesn't match should fail at compile time.
  2. Class hierarchies should allow classes at each level to add events that make sense for them.
  3. We want to preserve the 'familiar' event subscription API: class.on('eventName', <handler>).
  4. We don't want to have to manually implement the event subscription API on each class (minimize the need for boilerplate code).
  5. Classes should have the flexibility to decide whether or not a subclass can emit their events.

The implementation of this library accomplishes the above requirements, but not without tradeoffs. A summary of what is required to use this library can be found below.

Methodology

This library implements the behaviors described above via the use of a mixin and a helper type.

The mixin defines the event subscription APIs (on, off, once, etc.) and is generic around a type defining the event names and their handler signatures. This is what provides the type safety when calling the event subscription methods. The helper type helps make sure the resulting type (after adding the mixin) is defined as a type not a value, such that it can be used just like a normal class.

Given a class:

interface ParentEvents {
  eventOne: TypedEvent<(value: number) => void>;
}

class _Parent {
    protected eventOne = new TypedEvent<(value: number) => void>();    
}

NOTE: The property names between the events definition (ParentEvents) and the class (_Parent) must match. I.e. it needs to be eventOne in both places.

The type that should be exposed can be generated like so:

// Create and export the mixin type by adding the event subscription APIs
export const Parent = AddEvents<typeof _Parent, ParentEvents(_Parent);

// Export it as a type as well, to avoid "'Parent' refers to a value, but is being used as a type".
export const Parent = _Parent & WithEventsDummyType(ParentEvents);

This type (Parent) is what should be exported and used by other code (not _Parent).

FAQ

Rather than requiring constraints on the type U in AddEvents via documentation, why not express them in the type system?

This was attempted. We originally borrowed a type defined in the EventEmitter libraries above, like so:

type EventMap = {
  [key: string]: (...args: any[]) => void
}

Which could enforce that the events type always had keys that were strings and values which were functions. But unfortunately this will allow undefined event names to be passed without an error. A playground illustrating this issue can be found here. This is something I'd love to improve, so would be happy to learn of a solution here.

Development

Setup

  1. Run yarn to install dependencies.
  2. Run yarn prepare to prepare dependencies.
  3. Run yarn watch to build and watch for updates.
  4. Run yarn test to build, run tests, lint, and run test coverage.