andywer / typed-emitter

🔩 Type-safe event emitter interface for TypeScript
MIT License
268 stars 24 forks source link

Consider exporting a class #36

Open ehaynes99 opened 1 year ago

ehaynes99 commented 1 year ago

I had struggled with this a few times... The cast to the interface is fine when you just want an emitter, but I had issues extending EventEmitter. #30 gets to the root of it. There's really not a way to have an abstract emitter such as:

import EventEmitter from 'node:events'
import TypedEmitter, { EventMap } from 'typed-emitter'

// Base class expressions cannot reference class type parameters. ts(2562)            
//                                                                                    V
class BaseEmitter<T extends EventMap> extends (EventEmitter as new () => TypedEmitter<T>) {
  // ...
}

I did ultimately find a really ugly way to get a class definition that could be extended as normal:

import NodeEventEmitter from 'node:events'
import TypedEventEmitter from 'typed-emitter'

const EventEmitter = NodeEventEmitter as unknown as {
  new <Events extends EventMap>(): TypedEventEmitter<Events> extends infer O
    ? { [K in keyof O]: O[K] }
    : never
}

export default class TypedEmitter<
  Events extends EventMap,
> extends EventEmitter<Events> {}

I think the library could export a class without making things very complicated, though. Have a look at the linked PR and see what you think.

The only thing I can see as being a problem is if there is some use for this outside of node. Even then, you could likely have the original as a separate definition and leverage the exports field to accommodate that like:

  "exports": {
    ".": {
      "node": {
        "require": "./index.cjs",
        "import": "./index.mjs",
        "types": "./index.d.ts"
      },
      "default": {
        "type": "./interface-only.d.ts"
      }
    },
    "./rxjs": {
      "node": {
        "require": "./rxjs/index.cjs",
        "import": "./rxjs/index.mjs",
        "types": "./rxjs/index.d.ts"
      },
      "default": {
        "type": "./interface-only.d.ts"
      }
    }
  },