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

strictly-typed-events

v0.0.9

Published

An Event emitting/subscription library designed for simplicity, convenience, and type-safety in TypeScript projects.

Downloads

1,758

Readme

npm version Join the chat at https://gitter.im/ts-enum-util/Lobby Build Status Coverage Status

strictly-typed-events

An Event emitting/subscription library designed for simplicity, convenience, and type-safety in TypeScript projects.

Contents

Installation

Install via npm:

npm i -s strictly-typed-events

Why?

Despite the many event libraries already available on npm, I could not find one that met my desires. My goal is to easily setup a class that emits well-defined events, with type-safety while emitting and subscribing/handling, using minimal boilerplate. It should also be simple and intuitive to emit the events, subscribe to events, and cancel subscriptions to events.

Some design goals:

  • Simplicity of an "event" just being a call to a named handler function, with the "payload" being represented as one or more function parameters.
  • Define all events for a given class in terms of a single interface of event handler function signatures.
  • Support subscribing to multiple events with one call, which subsequently supports cancelling that entire subscription to multiple events with one call.
  • Easily support exposing only the means to subscribe to events, while keeping the means to emit events private.

Example Usage/Patterns

Here's some basic usage examples of strictly-typed-events and suggested patterns to get you started. See documentation in the source code (detailed TSDoc/JSDoc style comments on all types/classes/methods/etc.) for full details.

Subscribing and Cancelling Subscriptions

For the following examples, assume there is a variable source that implements this library's EventSource interface (has the on()/once()/subscribe() methods to subscribe to events).

Subscribe to one event at a time:

// Event name will be type-safe based on valid event names for the
// event source (IDE can auto-complete it!).
// Event handler parameter types will be inferred based on
// signature of event.
// Hold onto the "cancel" function returned when subscribing.
const cancel = source.on("nameChanged", (newname, oldName) => {
    // do stuff
});

// Simply call the cancel function to cancel the subscription
// to the event.
cancel();

Subscribe to one event with a one-time-only handler:

// This handler will self-cancel its own subscription when it is called.
// You can still store the returned cancel function and call it in case you
// need to cancel the subscription before the first time it is called.
source.once("nameChanged", (newname, oldName) => {
    // do stuff
});

Get a Promise that will be resolved the next time an event is emitted:

// Similar to `once()`, except that it returns a promise that resolves to a
// tuple of the event handler arguments.
source.onceAsPromise("nameChanged").then(([newname, oldName]) => {
    // do stuff
});

Or subscribe to multiple events at once:

// Hold onto the "cancel" function returned when subscribing.
const cancel = source.subscribe({
    // Provide handlers for any number of events in this object.
    // All type-safe, of course.
    nameChanged: (newname, oldName) => {
        // do stuff
    },
    // Wrap the handler in the `once()` function to make it a
    // one-time-only handler
    anotherEvent: once((whatever) => {
        // do stuff
    }),
});

// Simply call the cancel function to cancel the subscription
// to ALL events that were originally included in the subscription.
cancel();

Add Events via Inheritence

Here's the simplest, lowest-effort way to add events to a class. This works well for simple situations where your class is not already extending another class, and you want the on() subscription method to be directly on your class.

import { WithEventEmitter } from "strictly-typed-events";

// Simply extend `WithEventEmitter<>`, and define your events
// in the type parameter.
// Your class will now be an implementation of `EventSource`,
class Foo extends WithEventEmitter<{
    /**
     * You can document your event signatures, and IDEs
     * will be able to show this documentation in various
     * contexts where you emit this event or subscribe to
     * this event.
     * @param newName - The new name.
     * @param oldName - The old name.
     */
    nameChanged(newName: string, oldName: string): void;
    /**
     * Another event.
     * Define all events conveniently in one place
     */
    anotherEvent(whatever: number): void;
}> {
    public constructor(private readonly name: string) {
        super();
    }

    public setName(newName: string): void {
        const oldName = this.name;
        this.name = newName;
        // `this.emit` is a special protected property inherited from
        // `WithEventEmitter` with a method for each event.
        // Just call the method (strictly typed for IDE autocomplete, etc.)
        this.emit.nameChanged(newName, oldName);
    }
}

// Sample instance
const foo = new Foo();

// Your class itself is an `EventSource` with the "on()" method
// for subscribing to events.
const cancel = foo.on("nameChanged", (newname, oldName) => {
    // do stuff
});

// Cancel subscription
cancel();

Add Events via Composition

If you either don't want to, or are unable to, use inheritence to add events to your class, then you can do it through composition instead.

This approach guarantees that you have no conflicts between properties/methods on your class and WithEventEmitter.

import { EventEmitter } from "strictly-typed-events";

class Foo {
    // Initialize a private `EventEmitter` instance, and define your
    // events in the type parameter.
    private readonly emitter = new EventEmitter<{
        /**
         * You can document your event signatures, and IDEs
         * will be able to show this documentation in various
         * contexts where you emit this event or subscribe to
         * this event.
         * @param newName - The new name.
         * @param oldName - The old name.
         */
        nameChanged(newName: string, oldName: string): void;
        /**
         * Another event.
         * Define all events conveniently in one place
         */
        anotherEvent(whatever: number): void;
    }>();

    // Also expose your `EventEmitter` publicly, but as an
    // `EventSource` implementationthat only exposes the
    // ability subscribe to events.
    public readonly events = this.emitter.toEventSource();

    public constructor(private readonly name: string) {}

    public setName(newName: string): void {
        const oldName = this.name;
        this.name = newName;
        // Use the private EventEmitter instance to emit
        // events.
        this.emitter.emit.nameChanged(newName, oldName);
    }
}

// Sample instance
const foo = new Foo();

// Use the public `events` property on your class to
// subscribe to events.
const cancel = foo.events.on("nameChanged", (newname, oldName) => {
    // do stuff
});

// Cancel subscription
cancel();