Class for emitting events with strong TypeScript support
npm
npm install events-constructoryarn
yarn add events-constructorimport Events from 'events-constructor';
const eventNames = ['event1', 'event2'];
const events = new Events(eventNames);
events.on('event1', (data) => {
console.log('event1 is called with data:', data);
});
events.trigger('event1', 'some data');import { TypedEvents } from 'events-constructor';
type EventMap = {
userLoaded: { id: string; name: string };
logout: never;
};
const eventNames = ['userLoaded', 'logout'] as const;
const events = new TypedEvents<EventMap>(eventNames);
// TypeScript will enforce correct payload types
events.on('userLoaded', (user) => {
console.log('User loaded:', user.id, user.name);
});
// ✅ Correct - TypeScript knows the payload type
events.trigger('userLoaded', { id: '1', name: 'John' });
// ❌ TypeScript error - wrong payload type
events.trigger('userLoaded', { wrong: true });
// ✅ Correct - logout event has no payload
events.trigger('logout');
// ❌ TypeScript error - logout doesn't accept payload
events.trigger('logout', { data: 'wrong' });Handle multiple events with onRace conditions:
// onceRace - triggers only once, then automatically unsubscribes
events.onceRace(['userLogin', 'userError'], (data, eventName) => {
console.log(`First event: ${eventName}`, data);
});
// onRace - triggers every time any of the events occur, stays active
const unsubscribe = events.onRace(['userLogin', 'userError'], (data, eventName) => {
console.log(`Event occurred: ${eventName}`, data);
});
// Manually unsubscribe when no longer needed
unsubscribe();Add listener to event
events.on('event1', (data) => {
console.log('event1 is called with data:', data);
});Add a listener that will be called only once per event
events.once('event1', (data) => {
console.log('event1 is called with data:', data);
});Remove listener from event
events.off('event1', handler);Trigger event with data
events.trigger('event1', 'some data');Alias for trigger (Node.js EventEmitter compatibility)
events.emit('event1', 'some data');Wait for an event to be triggered (returns Promise)
const data = await events.wait('event1');
console.log('Event triggered with:', data);Listen for the first occurrence of any event from a list (automatically unsubscribes after first trigger)
events.onceRace(['event1', 'event2'], (data, eventName) => {
console.log(`${eventName} was triggered first with:`, data);
});Listen for any occurrence of events from a list (stays active until manually unsubscribed)
const unsubscribe = events.onRace(['event1', 'event2'], (data, eventName) => {
console.log(`${eventName} was triggered with:`, data);
});
// Unsubscribe when no longer needed
unsubscribe();Remove all listeners
events.removeEventHandlers();Remove all listeners for specific event or all events
// Remove all listeners for specific event
events.offAll('event1');
// Remove all listeners for all events
events.offAll();Get all triggers
events.triggers;Iterate through all triggers
events.eachTriggers((trigger, eventName) => {
console.log(`Trigger for ${eventName}:`, trigger);
});Disable event handlers. The trigger method will not produce any effect.
events.on('event1', (data) => {
console.log('event1 is called with data:', data); //it's not called
});
events.deactivate();
events.trigger('event1', 'some data');Enable event handlers.
events.on('event1', (data) => {
console.log('event1 is called with data:', data);
});
events.deactivate();
events.trigger('event1', 'some data'); // no effect
events.activate();
events.trigger('event1', 'some data'); // handler is calledCheck if event has any handlers
if (events.hasHandlers('event1')) {
console.log('Event has handlers');
}Destroy the events instance and clean up all handlers
events.destroy();
// All handlers are removed and triggers are undefinedTypedEvents extends the base Events class with strong TypeScript support:
type EventMap = {
userLoaded: { id: string };
logout: never;
dataUpdated: { value: number };
};
const events = new TypedEvents<EventMap>(['userLoaded', 'logout', 'dataUpdated']);
// TypeScript enforces correct payload types
events.trigger('userLoaded', { id: '123' }); // ✅
events.trigger('logout'); // ✅ (no payload for 'never' type)
events.trigger('dataUpdated', { value: 42 }); // ✅
// TypeScript errors for incorrect usage
events.trigger('userLoaded', { wrong: true }); // ❌
events.trigger('logout', { data: 'wrong' }); // ❌Strongly typed version of eachTriggers
events.eachTriggersTyped((trigger, eventName) => {
// trigger is properly typed based on the event
if (eventName === 'userLoaded') {
trigger({ id: 'test' }); // TypeScript knows this is correct
}
});Strongly typed version of onRace with payload type safety
type EventMap = {
userLoaded: { id: string; name: string };
userError: { error: string };
logout: never;
};
const events = new TypedEvents<EventMap>(['userLoaded', 'userError', 'logout']);
const unsubscribe = events.onRace(['userLoaded', 'userError'], (data, eventName) => {
// TypeScript knows the exact payload type for each event
if (eventName === 'userLoaded') {
console.log('User loaded:', data.id, data.name); // data is { id: string; name: string }
} else if (eventName === 'userError') {
console.log('Error:', data.error); // data is { error: string }
}
});
// Unsubscribe when no longer needed
unsubscribe();- Type Safety: Full TypeScript support with compile-time type checking
- Event Validation: Ensures only predefined events can be used
- Memory Management: Automatic cleanup of one-time listeners
- Error Handling: Built-in error handling with debug support
- Promise Support: Async/await support with
wait()method - Race Conditions: Handle multiple events with
onceRace()andonRace() - Lifecycle Management: Activate/deactivate events as needed
- Performance: Efficient event handling with Set-based listeners
👤 Krivega Dmitriy
- Website: https://krivega.com
- Github: @Krivega
Contributions, issues and feature requests are welcome!
Feel free to check issues page. You can also take a look at the contributing guide.
Copyright © 2025 Krivega Dmitriy.
This project is MIT licensed.