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 🙏

© 2025 – Pkg Stats / Ryan Hefner

evnty

v5.0.0

Published

Async-first, reactive event handling library for complex event flows in browser and Node.js

Readme

Evnty

Coverage Status Github Build Status NPM version Downloads Snyk

Async-first, reactive event handling library for complex event flows with three powerful primitives: Event (multi-listener broadcast), Signal (promise-like coordination), and Sequence (async queue). Built for both browser and Node.js with full TypeScript support.

Table of Contents

Core Concepts

Evnty provides three complementary async primitives, each designed for specific patterns:

🔊 Event - Multi-Listener Broadcasting

Events allow multiple listeners to react to values. All registered listeners are called for each emission.

const clickEvent = createEvent<{ x: number, y: number }>();

// Multiple listeners can subscribe
clickEvent.on(({ x, y }) => console.log(`Click at ${x},${y}`));
clickEvent.on(({ x, y }) => updateUI(x, y));

// All listeners receive the value
clickEvent({ x: 100, y: 200 });

Use Event when:

  • Multiple components need to react to the same occurrence
  • You need pub/sub or observer pattern
  • Listeners should persist across multiple emissions

📡 Signal - Promise-Based Coordination

Signals are for coordinating async operations. When a value is sent, ALL waiting consumers receive it (broadcast).

const signal = new Signal<string>();

// Multiple consumers can wait
const promise1 = signal.next();
const promise2 = signal.next();

// Send value - all waiting consumers receive it
signal('data');
const [result1, result2] = await Promise.all([promise1, promise2]);
// result1 === 'data' && result2 === 'data'

Use Signal when:

  • You need one-time notifications
  • Multiple async operations need the same trigger
  • Implementing async coordination patterns

📦 Sequence - Async Queue (FIFO)

Sequences are FIFO queues for single-consumer scenarios. Values are consumed in order, with backpressure support.

const taskQueue = new Sequence<Task>();

// Producer adds tasks
taskQueue(task1);
taskQueue(task2);
taskQueue(task3);

// Single consumer processes in order
for await (const task of taskQueue) {
  await processTask(task); // task1, then task2, then task3
}

Use Sequence when:

  • You need ordered processing (FIFO)
  • Only one consumer should handle each value
  • You want backpressure control with reserve()

Key Differences

| | Event | Signal | Sequence | |---|---|---|---| | Consumers | Multiple persistent listeners | Multiple one-time receivers | Single consumer | | Delivery | All listeners called | All waiting get same value | Each value consumed once | | Pattern | Pub/Sub | Broadcast coordination | Queue/Stream | | Persistence | Listeners stay registered | Resolves once per next() | Values queued until consumed |

Motivation

Traditional event handling in JavaScript/TypeScript has limitations:

  • String-based event names lack type safety
  • No built-in async coordination primitives
  • Missing functional transformations for event streams
  • Complex patterns require extensive boilerplate

Evnty solves these problems by providing:

  • Type-safe events with full TypeScript inference
  • Three specialized primitives for different async patterns
  • Rich functional operators (map, filter, reduce, debounce, batch, etc.)
  • Composable abstractions that work together seamlessly

Features

  • Async-First Design: Built from the ground up for asynchronous event handling with full Promise support
  • Functional Programming: Rich set of operators including map, filter, reduce, debounce, batch, and expand for event stream transformations
  • Type-Safe: Full TypeScript support with strong typing and inference throughout the event pipeline
  • Async Iteration: Events can be consumed as async iterables using for-await-of loops
  • Event Composition: Merge, combine, and transform multiple event streams into new events
  • Minimal Dependencies: Lightweight with only essential dependencies for optimal bundle size
  • Universal: Works seamlessly in both browser and Node.js environments, including service workers

Platform Support

| NodeJS | Chrome | Firefox | Safari | Opera | Edge | | --------------------- | ----------------------- | ------------------------- | ----------------------- | --------------------- | ------------------- | | Latest ✔ | Latest ✔ | Latest ✔ | Latest ✔ | Latest ✔ | Latest ✔ |

Installing

Using pnpm:

pnpm add evnty

Using yarn:

yarn add evnty

Using npm:

npm install evnty

Examples

Event - Multi-Listener Pattern

import { createEvent } from 'evnty';

// Create a typed event
const userEvent = createEvent<{ id: number, name: string }>();

// Multiple listeners
userEvent.on(user => console.log('Logger:', user));
userEvent.on(user => updateUI(user));
userEvent.on(user => saveToCache(user));

// Emit - all listeners are called
userEvent({ id: 1, name: 'Alice' });

// Functional transformations
const adminEvent = userEvent
  .filter(user => user.id < 100)
  .map(user => ({ ...user, role: 'admin' }));

// Async iteration
for await (const user of userEvent) {
  console.log('User event:', user);
}

Signal - Async Coordination

import { Signal } from 'evnty';

// Coordinate multiple async operations
const dataSignal = new Signal<Buffer>();

// Multiple operations wait for the same data
async function processA() {
  const data = await dataSignal.next();
  // Process data in way A
}

async function processB() {
  const data = await dataSignal.next();
  // Process data in way B
}

// Start both processors
Promise.all([processA(), processB()]);

// Both receive the same data when it arrives
dataSignal(Buffer.from('shared data'));

Sequence - Task Queue

import { Sequence } from 'evnty';

// Create a task queue
const taskQueue = new Sequence<() => Promise<void>>();

// Single consumer processes tasks in order
(async () => {
  for await (const task of taskQueue) {
    await task();
    console.log('Task completed');
  }
})();

// Multiple producers add tasks
taskQueue(async () => fetchData());
taskQueue(async () => processData());
taskQueue(async () => saveResults());

// Backpressure control
await taskQueue.reserve(10); // Wait until queue has ≤10 items
taskQueue(async () => nonUrgentTask());

Combining Primitives

// Event + Signal for request/response pattern
const requestEvent = createEvent<Request>();
const responseSignal = new Signal<Response>();

requestEvent.on(async (req) => {
  const response = await handleRequest(req);
  responseSignal(response);
});

// Event + Sequence for buffered processing
const dataEvent = createEvent<Data>();
const processQueue = new Sequence<Data>();

dataEvent.on(data => processQueue(data));

// Process with controlled concurrency
for await (const data of processQueue) {
  await processWithRateLimit(data);
}

License

License The MIT License Copyright (c) 2025 Ivan Zakharchanka