@open-wa/hyperemitter
HyperEmitter: high-performance, schema-driven event emitter for the open-wa v5 stack.
@open-wa/hyperemitterSource:
packages/hyperemitter/README.md
@open-wa/hyperemitter is a typed event emitter with direct synchronous dispatch and Effect-owned asynchronous work. It supports exact event names, + and trailing # wildcard subscriptions, one-time listeners, catch-all listeners, and listener cleanup with an AbortSignal or an Effect scope. Listeners can return a Promise or an Effect.
Use
import { HyperEmitter } from '@open-wa/hyperemitter';
type Events = {
'message.received': { id: string; body: string };
'session.state.changed': { state: string };
};
const events = new HyperEmitter<Events>();
events.on('message.received', (message) => {
console.log(message.id, message.body);
});
events.emit('message.received', { id: 'message-id', body: 'Hello' });emit invokes ordinary callbacks directly, entering Effect only for asynchronous listener work in fibers owned by the emitter. Callback-only use creates no PubSub. emitEffect batches callbacks in a lazy Effect, awaits every listener, and exposes uncaptured failures in the Effect error channel:
import { Effect } from 'effect';
await Effect.runPromise(Effect.scoped(Effect.gen(function* () {
yield* events.onScoped('message.received', (message) =>
Effect.sync(() => console.log(message.body)),
);
yield* events.emitEffect('message.received', { id: 'message-id', body: 'Hello' });
})));onScoped and onAnyScoped remove their registrations and interrupt their listener fibers when the caller's scope closes. Effect.runPromise(events.close()) removes all registrations and interrupts work owned by the emitter. A closed emitter cannot accept new subscriptions.
Each event carries one payload. + matches one name segment; a trailing # matches the rest, including an empty suffix. Set captureRejections: true to report listener failures through onError or the emitter's logger while continuing delivery.
The Effect benchmark compares callback dispatch, Effect dispatch, asynchronous listeners, and scope cleanup on completed deliveries. Its results are measurements from that runner, not a performance guarantee.
License
Apache-2.0
Shared OpenWA emitter
The exported EventEmitter backs OpenWA's global ev and the socket clients'
ev properties. It preserves the callback API: variadic arguments, synchronous
emit, * and ** namespace wildcards, this.event, listener ordering and
removal, emitAsync result arrays, waitFor, and listenTo. It adapts that
surface to the same tuned HyperEmitter rather than creating a second runtime.
onScoped, onAnyScoped, emitEffect, and close add Effect ownership.
The PubSub comparison measures completed deliveries with both per-event acknowledgements and batches of 256, including consumers that drain queued messages in batches. PubSub is a useful asynchronous stream option, but publishing does not guarantee immediate callback invocation. A busy subscriber queues later events, which differs from the shared emitter's dispatch contract.
Optional PubSub and Streams
Both HyperEmitter and the shared EventEmitter facade own a lazy native
Effect PubSub. Calling pubsub(), running subscribe(), or consuming stream()
enables queued observation. Ordinary callback users create no PubSub or
persistent consumer fibers.
import { Effect, PubSub } from 'effect';
import { ev } from '@open-wa/wa-automate';
await Effect.runPromise(Effect.scoped(Effect.gen(function* () {
// Direct native access; ev.subscribe() is the equivalent convenience API.
const subscription = yield* PubSub.subscribe(ev.pubsub());
ev.emit('app.job.finished', { jobId: 'daily-summary' });
const { event, args } = yield* PubSub.take(subscription);
console.log(event, args[0]);
})));The public facade publishes { event, args }; typed HyperEmitter<TMap>
publishes { event, payload }, preserving the relationship between event names
and payload types. Envelopes and argument arrays are immutable; payload objects
keep their original identity. Observations are published before callbacks, so
a recursive emission preserves the original queued order. They describe
emissions, including ones whose callbacks later fail.
emit, emitEffect, and emitAsync deliver through both interfaces once.
emit still returns whether callback listeners ran. emitAsync returns those
callbacks' results and does not wait for arbitrary PubSub consumers. Raw
PubSub.publish(emitter.pubsub(), frame) addresses the queued feed directly;
use emitter methods when callbacks should also run. The owning emitter shuts
the hub down on close(), interrupting queued readers. Consumer scopes remove
their own subscriptions. Closing does not promise to drain buffered work.
The default is unbounded with replay disabled. A slow active observer can retain a growing backlog. Choose options on first access for an explicit finite loss policy, or retain recent emissions for later subscribers:
import { EventEmitter } from '@open-wa/hyperemitter';
const emitter = new EventEmitter();
const hub = emitter.pubsub({ strategy: 'sliding', capacity: 256, replay: 1 });
// Later calls return the same hub; options cannot silently replace it.dropping rejects new observations when full; sliding removes older ones.
pubsubStatus counts accepted and rejected emitter publications separately from
callback results; raw publication bypasses these counters. Sliding accepts the
new publication, so its rejected count does not count evicted older frames.
Lossless bounded backpressure cannot sit transparently beneath synchronous
emit, so observation options offer only non-blocking strategies. Use an
integration-owned Queue and effectful producer when producer suspension is
part of that integration's design.
stream() supplies the same observations through Stream.fromPubSub, with
normal Effect filtering, batching, and consumer concurrency controls. Each
stream starts its subscription when consumed; subscribe before emitting when
replay is disabled. The global ev feed forwards native events and enabled
session aliases even when no callback is registered. Session shutdown detaches
its bridge without closing the shared global hub.
See the source-backed architecture review for the responsibilities of PubSub, Queue, Stream, SubscriptionRef, services, and scoped fibers.
The Effect event guide includes complete examples; the HyperEmitter reference documents method returns and lifecycle semantics. The Effect hybrid and optional PubSub APIs are available from OpenWA 5.6.0.
Was this helpful?
Your answer includes the page path and docs version.
