
Security News
Frontier AI Is Now Critical Infrastructure
The Fable shutdown shows how quickly model access can become a business continuity risk for AI-dependent engineering teams.
signal-controller
Advanced tools
A lightweight event emitter with separation of concerns between emitter and listener inspired by the AbortController interface.
A lightweight event emitter with separation of concerns between emitter and listener,
inspired by the AbortController interface.
The term "signal", in the context of this library, is equivalent to "event". The lib is called
signal-controllerbecauseevent-controllerwas already taken. Both terms are used interchangeably here.
AbortSignal.ReadableStream into the
SignalController, converting chunks into events; or create a ReadableStream that is fed by events from an
SignalEmitter..js file. Zero dependencies.import { SignalController } from 'signal-controller';
// Define your event emitter interface (TypeScript only)
interface MySignals {
userLoggedIn: (user: { id: string; name: string }) => void;
dataUpdated: (data: any[]) => void;
error: (error: Error) => void;
}
// Create a controller
const controller = new SignalController<MySignals>();
// Each controller instance has an `emitter` field that can be used to listen to events emitted by the controller.
// You expose only the `emitter` to clients interested in your events.
controller.emitter.on('userLoggedIn', (user) => { // `user` is of type `{ id: string; name: string }`
console.log(`Welcome, ${user.name}!`);
});
controller.emitter.on('error', (error) => { // `error` is of type `Error`
console.error('An error occurred:', error.message);
});
// Emit events
controller.emit('userLoggedIn', { id: '123', name: 'John Doe' }); // Arguments are type-checked
controller.emit('error', new Error('Something went wrong'));
npm install signal-controller
See api.md file.
Using an AbortController:
const abortController = new AbortController();
{
emitter.on('userLoggedIn', { signal: abortController.signal }, (user) => {
console.log(`User ${user.name} logged in`);
});
emitter.on('error', { signal: abortController.signal }, (error) => {
console.error('Error:', error.message);
});
}
// Remove all listeners at once
abortController.abort();
Using off():
emitter.on('userLoggedIn', function onUserLoggedIn(user) {
console.log(`User ${user.name} logged in`);
});
emitter.on('error', function onError(error) {
console.error('Error:', error.message);
});
emitter.off(onUserLoggedIn);
emitter.off(onError);
Convert signals to streams:
signalController.emitter.createReadableStream('dataUpdated').pipeTo(sinkStream);
signalController.emit('dataUpdated', someData); // This event will pushed a chunk into the sink stream
sourceStream.pipeTo(signalController.createWritableStream('dataUpdated'));
signalController.emitter.on('dataUpdated', (data) => {
// Chunks of data produced by `sourceStream` will trigger this event
});
// Transform events into an async iterator, transforms the data, then pipe into a sink stream.
emitter.iterate('dataUpdated')
.map(data => JSON.stringify(data))
.toStream()
.pipeTo(sinkStream);
Signal listeners that throw errors will have their errors logged to the console, but won't stop other listeners from executing:
/// When a listener throws an exception...
controller.emitter.on('userLoggedIn', (user) => {
throw new Error('This will be logged but won\'t stop other listeners');
});
// This listener from the controller is called
controller.onError = (errors, signalName, args) => {
if (signalName === 'userLoggedIn') {
const [user] = args;
// `errors` is an array of the errors that had been thrown for each listener that threw
for (const error of errors) {
console.error(
'A listener for the signal', signalName, 'threw this error:', error,
'This happened when the signal was emitted with these arguments:', args,
);
}
}
}
When immediate: true is set, new listeners will immediately receive the last emitted arguments:
const controller = new SignalController<MySignals>({ immediate: true });
// Emit a signal with no listeners
// The emitter will hold onto the last emitted data for each signal type
controller.emit('userLoggedIn', { id: '123', name: 'John' });
// Add a listener after the signal had been emitted
controller.emitter.on('userLoggedIn', (user) => {
console.log(`Welcome back, ${user.name}!`); // Runs immediately
});
MIT
Contributions are welcome! Please feel free to submit a Pull Request.
FAQs
A lightweight event emitter with separation of concerns between emitter and listener inspired by the AbortController interface.
The npm package signal-controller receives a total of 2 weekly downloads. As such, signal-controller popularity was classified as not popular.
We found that signal-controller demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer 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 Fable shutdown shows how quickly model access can become a business continuity risk for AI-dependent engineering teams.

Security News
AI agents are pulling packages into environments no scanner is watching, creating exposure before security teams can see it.

Security News
GitHub Actions checkout now blocks risky pull_request_target checkouts by default to help prevent pwn request supply chain attacks.