@quilted/events

Tiny helpers for working with events in any JavaScript environment — promises, async generators, and reactive signals.

Library
npm
v2.1.5
57stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
72/100Good
Development Activity80
Maintenance96
Community40
Maturity60
Momentum12

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
80/100Excellent
Architecture86
Code Quality74
Innovation74
Learning Curve85

@quilted/events is a lightweight utility library for working with events across any JavaScript environment — the browser, Node.js, and beyond. It centers on the EventEmitter class, a minimal alternative to the DOM’s EventTarget that converts event handlers into Promises and AsyncGenerators, so consumers can await a single event with once() or iterate a stream of events with for await...of via on(). Both methods integrate cleanly with AbortSignal, letting callers cancel a subscription with the same primitive used everywhere else in modern JavaScript.

Beyond the emitter, the package exposes standalone on() and once() functions that wrap any existing EventTarget or Node.js EventEmitter-compatible object without requiring you to adopt the library’s own emitter class, plus a small signals entry point that bridges DOM events into Preact signals for reactive state that updates as events fire. It’s built as part of the Quilt framework but ships as an independent, dependency-light package (its only runtime dependency is @preact/signals-core) that any JavaScript or TypeScript project can use standalone.

What You Get

  • An EventEmitter class with typed on(), once(), and emit() methods
  • Standalone on()/once() functions that wrap any existing EventTarget or Node EventEmitter-style object
  • AbortSignal-based cancellation across every listening API, including an optional reject-on-abort mode
  • A /signals entry point (eventTargetSignal()) that bridges DOM events into Preact Signal values
  • Supporting abort utilities: AbortError, NestedAbortController, TimedAbortController, and raceAgainstAbortSignal

Common Use Cases

  • Consuming DOM or custom events as an async-iterable stream instead of nested callbacks
  • Awaiting a single event with built-in timeout/cancellation via AbortSignal
  • Wrapping third-party event sources (DOM, Node) with a consistent Promise/async-generator API
  • Deriving reactive Preact signal state directly from native browser events

Under The Hood

Architecture The package is organized into small, single-responsibility modules under source/: emitter.ts holds the EventEmitter class, on.ts and once.ts hold the standalone async-generator/promise adapters, handler.ts centralizes the logic for attaching a listener to any of three supported event-source shapes (addEventListener-style, Node’s on/off-style, or a plain listener function), and abort.ts/types.ts hold shared cancellation and typing primitives. EventEmitter itself is a thin wrapper that lazily attaches to an optionally-wrapped eventTarget only when the first handler is registered (via its own internal add/remove event pair), and tears the underlying listener down again once the last handler is removed — a self-managing subscription pattern that avoids leaking listeners on the wrapped object. There is no shared mutable state outside each emitter instance, so the design composes cleanly and is easy to reason about in isolation.

Tech Stack The package is pure TypeScript (ESM-only, type: module), with a single runtime dependency on @preact/signals-core for the /signals entry point. It is built with the shared @quilted/rollup configuration and published with separate esm, esnext, and typescript build outputs plus a quilt:source export condition for consuming the raw TypeScript directly inside the monorepo. Tests run on Vitest with a jsdom environment, and the whole workspace is managed with pnpm and Changesets for versioning.

Code Quality The core EventEmitter behavior (event streaming via on(), one-shot resolution via once(), and abort/cancellation semantics in both 'resolve' and 'reject' modes) is covered by a dedicated Vitest suite in source/tests/emitter.test.ts; the lower-level standalone on()/once() functions and the handler.ts adapter logic are exercised indirectly through the same suite rather than tested in isolation. Every public class and function carries detailed JSDoc with usage examples, and the public API leans on TypeScript function overloads (rather than runtime branching alone) to give callers precise return types depending on whether they pass a callback or omit one. The monorepo enforces tsc --build type-checking and Prettier formatting in CI, though there is no dedicated ESLint configuration visible for this package.

API Design The library keeps its surface area intentionally small: a single EventEmitter class plus two standalone functions (on, once) cover nearly every use case, and the same two method names are reused whether you’re working with the class or the free functions, keeping the mental model consistent. Overloaded signatures let on()/once() return either an AsyncGenerator/Promise or accept a plain callback depending on how they’re called, so callers opt into whichever style fits their code without needing separate APIs. Wrapping an existing event source requires zero adapter code — EventEmitter and the standalone functions detect addEventListener, on/off, or plain-function listener shapes automatically — which keeps the boilerplate for adopting the library close to zero.

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