Version v2.0.0 Size 2.2 KB gzip Dependencies Zero dependencies
Why Herald?
Raw event emitters lose payload inference and leave waiting, streaming, cancellation, and teardown to every caller. Herald keeps events temporal: use Ripple when you need retained state.
ts
// Before
const listeners = new Set<(payload: unknown) => void>();
listeners.add((payload) => loadProfile((payload as { id: string }).id));
// After
import { createBus } from '@vielzeug/herald';
interface AppEvents {
'user:login': { id: string };
}
function loadProfile(id: string): void {
console.log(id);
}
const bus = createBus<AppEvents>();
bus.on('user:login', ({ id }) => loadProfile(id));| Feature | Herald | mitt | EventEmitter3 |
|---|---|---|---|
| Bundle size | 2.2 KB | ~200 B | ~1.5 kB |
| Typed payloads | |||
| Async wait and streams | |||
| AbortSignal lifecycle | |||
| Typed event pipes | |||
| Zero dependencies |
Use Herald when modules need typed temporal event delivery with owned lifecycle.
Consider Ripple when consumers need current state and replayed values.
Installation
sh
pnpm add @vielzeug/heraldsh
npm install @vielzeug/heraldsh
yarn add @vielzeug/heraldQuick Start
ts
import { createBus } from '@vielzeug/herald';
interface AppEvents {
'user:login': { id: string };
'user:logout': void;
}
const bus = createBus<AppEvents>();
const stop = bus.on('user:login', ({ id }) => console.log(id));
bus.emit('user:login', { id: '42' });
stop();
bus.dispose();Features
on()/once()— typed subscriptions with explicit teardownonAny()— cross-cutting event observationwait()/waitAny()— one-shot async coordinationevents()— bounded async event streamspipeEvents()— compatible cross-bus forwardingAbortSignal— cancellation and disposal ownershipcreateTestBus()— emitted-payload recording for testsdebugBus()— development logging from/devtools