yieldless/pubsub
In-process broadcast channels with async-iterable subscriptions and optional replay.
yieldless/pubsub broadcasts values to many local subscribers. It is intentionally in-process and small: publish values, subscribe with for await, close subscriptions when they are no longer needed, and optionally replay the latest values to late subscribers.
Use it for CLI progress, Electron main-process status streams, local job updates, or tests that need observable fanout.
Exports
createPubSub<T, E = Error>({ replay, subscriberCapacity }): PubSub<T, E>type PubSub<T, E> = { publish, subscribe, close, subscriberCount, closed }type PubSubSubscription<T> = AsyncIterable<T> & { next, close }
Example
import { createPubSub } from "yieldless/pubsub";
const progress = createPubSub<{ id: string; percent: number }>({
replay: 1,
});
const subscription = progress.subscribe();
void (async () => {
for await (const update of subscription) {
renderProgress(update);
}
})();
progress.publish({ id: "index", percent: 25 });Behavior notes
publish(value)returns the number of active subscribers.replaydefaults to0.subscribe()returns an async iterable subscription.subscription.close()removes that subscriber and closes its internal queue.close(reason)closes all subscribers and prevents future publishes.- Each subscriber has its own queue.
subscriberCapacitybounds per-subscriber buffering as a sliding window: when a slow subscriber's queue is full, its oldest buffered value is dropped so the publisher never waits and memory stays bounded.
Good
Use pubsub for local fanout, not remote messaging.
const buildEvents = createPubSub<BuildEvent>({ replay: 1 });Close subscriptions owned by short-lived UI or request scopes.
const subscription = buildEvents.subscribe();
try {
for await (const event of subscription) {
send(event);
}
} finally {
subscription.close();
}Avoid
Do not treat it as a durable queue.
const orders = createPubSub<Order>();Use yieldless/queue when every item must be processed by a worker. Use yieldless/pubsub when every current listener should hear the same update.