Skip to content

3axap4eHko/evnty

Repository files navigation

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

About

0-Deps, simple, fast, for browser and node js anonymous event library

Topics

Resources

License

Stars

Watchers

Forks

Sponsor this project

  •  

Packages

No packages published

Contributors 2

  •  
  •