typed-emitter

Zero-runtime, strictly typed EventEmitter interface for TypeScript.

Library
npm
v2.1.0
285stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
35/100Needs Attention
Development Activity0
Maintenance20
Community40
Maturity60
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
57/100Fair
Architecture65
Code Quality50
Innovation78
Learning Curve35

typed-emitter provides a single TypeScript interface, TypedEventEmitter<Events>, that you cast onto any existing event emitter (Node’s built-in events.EventEmitter or a compatible custom implementation) to get compile-time type checking of event names and listener argument types. It ships no runtime code at all — just .d.ts declaration files — so it adds zero bytes to your bundle while catching typos in event names and mismatched listener signatures that the default @types/node EventEmitter typings let through silently.

The built-in EventEmitter types accept any string as an event name and any function as a listener, so a typo like emit("mesage", ...) or a listener with the wrong argument types only fails at runtime, if at all. typed-emitter fixes this by parameterizing every method (on, once, emit, off, removeAllListeners, etc.) over a declared EventMap, so mismatches are caught by tsc before the code ships. It also ships an optional typed-emitter/rxjs submodule with a FromEvent type that restores the type inference RxJS’s fromEvent otherwise loses when used against a typed emitter.

What You Get

  • A TypedEventEmitter<Events> generic interface you cast onto any EventEmitter instance
  • Compile-time checking of event names against your declared EventMap
  • Compile-time checking of listener argument types for on, once, emit, and related methods
  • An rxjs submodule with a FromEvent type that fixes fromEvent’s lost type inference
  • Zero runtime code — pure .d.ts typings with no bundle-size cost

Common Use Cases

  • Wrapping Node’s events.EventEmitter to get typed pub/sub for internal service or module events
  • Extending EventEmitter in a custom class with TypedEmitter<T> generics to ship a typed public API
  • Adding type-safe events to socket, stream, or other custom emitter-shaped implementations
  • Getting correctly typed RxJS Observables out of fromEvent for a typed emitter

Under The Hood

Architecture typed-emitter has no runtime layer — its entire surface is a hand-written index.d.ts declaring one generic interface, TypedEventEmitter<Events extends EventMap>, whose methods (on, once, emit, off, removeAllListeners, listeners, eventNames, etc.) are all parameterized over keyof Events and Parameters<Events[E]> so each method call is checked against the caller-supplied event map. A second, optional module (rxjs/index.d.ts) extends that base interface with an __events marker field and declares a FromEvent type that intersects a hand-written overload with RxJS’s own fromEvent signature, restoring the Observable<T> inference that would otherwise degrade to unknown. Because the entire package is one composed generic type, any change to the EventMap/TypedEventEmitter shape would break every consumer relying on the cast pattern.

Tech Stack The package has zero production dependencies; RxJS is only an optionalDependency/devDependency (^7.5.2) used solely by the optional rxjs submodule. There is no build step — package.json points main at an effectively empty types.js stub and types at the hand-authored index.d.ts, so the published package is the raw TypeScript declaration files with no transpilation or bundling involved.

Code Quality No test files or test framework are present in the repository, so correctness is enforced entirely by TypeScript’s own type checker rather than by an automated test suite. There is no committed linter or formatter configuration, though an inline /* eslint-disable no-use-before-define */ comment in rxjs/index.d.ts implies ESLint is used in some external workflow. The declaration files themselves are internally consistent, narrowly scoped, and rely on precise generics (keyof, Parameters<>) rather than any, which is what the package’s whole value proposition depends on.

API Design The package solves a specific, well-known gap: neither @types/node’s EventEmitter typings nor eventemitter3’s bundled types let you parameterize event names and listener argument types together. typed-emitter’s fix is a single, low-boilerplate cast — new EventEmitter() as TypedEmitter<Events> — that mirrors Node’s own EventEmitter method names exactly, so there’s no new API to learn and no wrapper class or runtime cost. The README documents the base cast, the class-extension pattern (including a generic-class variant), and the RxJS interop case with runnable snippets, though the project has had no commits since late 2022 and issues around type-inference edge cases (e.g. the linked fromEvent compatibility discussion) remain open.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search