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

events-typed

v2.0.0

Published

A typesafe EventEmitter for TypeScript that wraps Node.js EventEmitter.

Downloads

24

Readme

events.ts

A typesafe EventEmitter for TypeScript that wraps Node.js EventEmitter.

Works in browser environments too.

This gives you the same EventEmitter from Node, wrapped, so the API is almost the same. As usual, any can be used as an escape hatch (see Caveats and TODO below).

Why?

When using @types/node, event names can be any string and are not checked against a known list of event names, and event payloads all have type any. Because of this you can not enforce strict typing with Node.js EventEmitter. :(

The advantage of this package over @types/node is that event names are checked against a list of known event names that you define (otherwise you get a type error if you provide an invalid event name) and event payloads all have types that you define (and you'll get a type error if you pass a callback that doesn't have a signature that accepts the payload type).

Usage

It's easiest to explain with code. event.ts lets you do the following:

import { makeEventEmitterClass } from 'events.ts'

// define the event names and their payloads:
type EventTypes = {
  SOME_EVENT: number
  OTHER_EVENT: string
  ANOTHER_EVENT: undefined
  READONLY_EVENT: ReadonlyArray<number>
}

// parens required, because EventEmitter is a factory that returns a class
const EventEmitter = makeEventEmitterClass<EventTypes>()
const emitter = new EventEmitter()

// GOOD --------------- :
emitter.on('SOME_EVENT', payload => testString(payload))
emitter.on('OTHER_EVENT', payload => testString(payload))
emitter.on('ANOTHER_EVENT', (/* no payload */) => {})
emitter.on('READONLY_EVENT', payload => {
  testReadonlyArray(payload)
})
emitter.emit('SOME_EVENT', 42)
emitter.emit('OTHER_EVENT', 'foo')
emitter.emit('ANOTHER_EVENT')
emitter.emit('READONLY_EVENT', Object.freeze([1, 2, 3]))

// BAD --------------- :
emitter.on('SOME_EVENT', payload => testString(payload)) // ERROR: Argument of type 'number' is not assignable to parameter of type 'string'.
emitter.on('OTHER_EVENT', payload => testNumber(payload)) // ERROR: Argument of type 'string' is not assignable to parameter of type 'number'.
emitter.on('ANOTHER_EVENT', (payload: number) => {}) // ERROR: Argument of type '(payload: number) => void' is not assignable to parameter of type '() => void'.
emitter.on('READONLY_EVENT', (payload: number) => {
  testNumber(payload) // ERROR, payload parameter is not ReadonlyArray<number>
})
emitter.emit('foo', 123) // ERROR: Argument of type '"FOOBAR"' is not assignable to parameter of type '"SOME_EVENT" | "OTHER_EVENT" | "ANOTHER_EVENT" | "READONLY_EVENT"'.
emitter.emit('SOME_EVENT', 'foo') // ERROR: Argument of type '"foo"' is not assignable to parameter of type 'number'.
emitter.emit('OTHER_EVENT', 42) // ERROR: Argument of type '42' is not assignable to parameter of type 'string'.
emitter.emit('ANOTHER_EVENT', 'bar') // ERROR: Expected 1 arguments, but got 2.
emitter.emit('READONLY_EVENT', ['1', '2', '3']) // ERROR: Type 'string' is not assignable to type 'number'.

declare function testNumber(value: number): void
declare function testString(value: string): void
declare function testReadonlyArray(value: ReadonlyArray<number>): void

Here's the a simple playground example showing the concept (with a subset of the EventEmitter API).

You might be accustomed to using enums for event names, for example something like the following so that you perhaps get better autocompletion:

emitter.emit(Events.SOME_EVENT, payload)

One way you can achieve this is to also write an enum alongside your event types:

import { makeEventEmitterClass } from 'events.ts'

// define the event names and their payloads:
type EventTypes = {
  SOME_EVENT: number
  OTHER_EVENT: string
  ANOTHER_EVENT: undefined
  READONLY_EVENT: ReadonlyArray<number>
}

// define an enum so we don't have to pass string literals into API calls
enum Events = {
  SOME_EVENT: number
  OTHER_EVENT: string
  ANOTHER_EVENT: undefined
  READONLY_EVENT: ReadonlyArray<number>
}

// parens required, because EventEmitter is a factory that returns a class
const EventEmitter = makeEventEmitterClass<EventTypes>()
const emitter = new EventEmitter()

emitter.emit(Events.SOME_EVENT, 42) // autocompletion works well

(playground example)

But you may notice that if the list of event names gets long, that you'll now have two lists of events: one for the types, and one for the enum. You might rather have things be DRY and only mention each event name exactly once instead of mentioning each event name three times. There a way to do that using a class hack. The above example becomes:

import { makeEventEmitterClass } from 'events.ts'

// define all event names and types in a class as constructor args:
class EventTypes {
  constructor(
    public SOME_EVENT: number,
    public OTHER_EVENT: string,
    public ANOTHER_EVENT: undefined,
    public READONLY_EVENT: ReadonlyArray<number>,
  ) {}
}

// Make an empty Events object, which will be like an enum
const Events = {} as { [k in keyof EventTypes]: k }

// loop on the keys of a dummy EventTypes instance in order to create the
// enum-like Events object keys.
for (const key in new (EventTypes as any)()) {
  Events[key] = key
}

// parens required, because EventEmitter is a factory that returns a class
const EventEmitter = makeEventEmitterClass<EventTypes>()
const emitter = new EventEmitter()

emitter.emit(Events.SOME_EVENT, 42) // autocompletion works well

(playground example)

Caveats

  • Due to how TypeScript works, it is not possible to implement this feature as strictly a type declaration file. It requires runtime output in the form of a class, and thus can not be simply merged into @types/node. See the bottom of src/index.ts to see the part that is required and which emits the runtime code.

  • To keep things DRY, you will have to make a dummy class hack like in the last example.

  • Symbols for event names are not currently supported.

  • Multiple event payload arguments are not currently supported.

    The following,

    emitter.emit('foo', singleArg)

    is supported, but

    emitter.emit('foo', arg1, arg2, arg3)

    is not yet possible.

TODO

  • [ ] Support Symbol event names (how?)
  • [ ] Support multiple event payload args (how?)