
Security News
The Hidden Blast Radius of the Axios Compromise
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.
@streetstrider/emitter
Advanced tools
Emitter, MultiEmitter & Slot
This is a simple, easy, and robust implementation of the fundamental JS pattern called «Event Emitter». It is intended to be well-designed, free of misconceptions, and minimalistic. This library splits the pattern into Emitter, MultiEmitter, and Slot. Emitter can emit a single kind of event. MultiEmitter can emit several kinds of events. Slot can emit a single kind of event and guarantee to have no more than one subscription at a time.
You can consult .d.ts files for the exact API. Below is pseudocode. Emitter and Slot have strong similarities, but Slot provides an additional emit_must method.
type Disposer = () => void
type Subscription <Args extends unknown[]> = (...args: Args) => void
type Emitter<Args extends unknown[]> =
{
on (fn: Subscription<Args>): Disposer,
emit (...args: Args): void,
is_empty (): boolean,
}
type Subscription <Args extends unknown[], Return = unknown> = (...args: Args) => Return
type Slot<Args extends unknown[], Return = unknown> =
{
on (fn: Subscription<Args, Return>): Disposer,
emit (...args: Args): Return,
emit_must (...args: Args): Return,
is_empty (): boolean,
}
type Disposer = () => void
type Subscription <Args extends unknown[]> = (...args: Args) => void
type HandlersBase =
{
[key: string]: unknown[],
}
type MultiEmitter <Handlers extends HandlersBase> =
{
on <Key extends keyof Handlers> (key: Key, fn: Subscription<Handlers[Key]>): Disposer,
emit <Key extends keyof Handlers> (key: Key, ...args: Handlers[Key]): void,
is_empty (): boolean,
}
import Emitter from '@streetstrider/emitter'
import once from '@streetstrider/emitter/once'
const emitter = Emitter()
const disposer = emitter.on((a, b) => console.log(a + b))
const disposer = once(emitter, (a, b) => console.log(a + b))
emitter.emit(1, 2)
disposer()
emitter.is_empty() // → true
import Slot from '@streetstrider/emitter/slot'
const slot = Slot()
const disposer = slot.on((a, b) => console.log(a + b))
import MultiEmitter from '@streetstrider/emitter/multi'
import { multi as once_multi } from '@streetstrider/emitter/once'
const emitter = MultiEmitter()
const ds1 = emitter.on('plus', (a, b) => console.log(a + b))
const ds2 = emitter.on('mul', (a, b) => console.log(a * b))
const ds3 = once_multi(emitter, 'plus', (a, b) => console.log(a + b))
const ds4 = once_multi(emitter, 'mul', (a, b) => console.log(a * b))
emitter.emit('plus', 1, 2)
emitter.emit('mul', 3, 4)
ds1()
ds2()
emitter.is_empty() // → true
import Emitter from '@streetstrider/emitter'
import when from '@streetstrider/emitter/when'
const emitter = Emitter()
async function do_async () {
const next_one = await when(emitter) /* would capture first emission */
}
emitter.emit('next_one')
emitter.emit('next_two')
emitter.emit('next_three')
Built-in TypeScript type definitions.
const e1 = Emitter<[number, number]>()
e1.emit(1, 2)
const e2 = MultiEmitter<{ plus: [number, number] }>()
e2.emit('plus', 1, 2)
Disposer is a simple () => void function that can be easily passed around, used as a one-off for an event from another emitter, and composed with other disposers via ordinary compose. You can pass it without wrapping it with an arrow function since disposer is a simple void function. It is much easier and cleaner than storing references to the original function and emitter. The disposer is exactly the same for Emitter, MultiEmitter, and Slot. Disposer make some efforts to disrupt references to prevent memory leaks and open a way for earlier garbage collection.
Most of the approaches (like EventEmitter or nanoevents) converge to the topmost powerful multi-channel emitter since it covers all the cases. However, in many situations, Emitter is just enough. It is much simpler and easier to have one or two separate Emitter instances with clean semantics rather than have some string-keyed channels inside MultiEmitter. It is not smart to use MultiEmitter if you have only one type of event.
In many situations, the Event Emitter pattern is used for a singular subscription as a replacement for a callback. Slot still follows inversion of control like a normal Emitter. Instead of using a callback as an input parameter, we subscribe to a specific explicit Slot instance. A single subscription is guaranteed, so we have additional guarantees.
once and when not in the core?Because they are not part of the minimal API. Making them public methods on the emitter would also make them non-tree-shakeable, and the only gain would be consistent syntax with on (via dot). If you still want this, you can patch your emitters by binding/partialing (bring your own) once and/or when. You can also attach them by currying them.
import once from '@streetstrider/emitter/once'
const emitter = Emitter()
emitter.once = once.bind(null, emitter)
emitter.once = partial(once, emitter)
const disposer = emitter.once((a, b) => console.log(a + b))
import { multi as once_multi } from '@streetstrider/emitter/once'
const emitter = MultiEmitter()
emitter.once = once_multi.bind(null, emitter)
emitter.once = partial(once_multi, emitter)
const disposer = emitter.once('plus', (a, b) => console.log(a + b))
ISC, © Strider, 2025.
FAQs
event emitter
We found that @streetstrider/emitter demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.

Research
A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.

Research
Malicious versions of the Telnyx Python SDK on PyPI delivered credential-stealing malware via a multi-stage supply chain attack.