lever-ui-eventbus
Version:
A minimal TypeScript event bus: subscribe(Class, handler), async delivery, dead events, and polymorphic dispatch.
398 lines (298 loc) • 9.85 kB
Markdown
# lever-ui-eventbus
[](https://www.npmjs.com/package/lever-ui-eventbus)
[](https://www.typescriptlang.org/)
[](https://opensource.org/licenses/MIT)
[](https://bundlephobia.com/package/lever-ui-eventbus)
**A minimal TypeScript event bus with zero dependencies** featuring type-safe subscriptions, async delivery, polymorphic dispatch, dead event handling, and React integration.
## Features
- **Type-Safe** - Full TypeScript support with generic event types
- **Zero Dependencies** - Lightweight core with no external dependencies
- **Polymorphic Dispatch** - Base classes receive derived event types
- **Async Support** - Non-blocking event delivery with custom executors
- **React Integration** - Hooks and context for seamless React usage
- **Dead Events** - Handle undelivered events gracefully
- **Error Resilience** - Robust error handling with custom error handlers
- **Universal** - Works in Node.js, browsers, and React Native
- **Tree Shakeable** - Import only what you need
## Quick Start
### Installation
```bash
npm install lever-ui-eventbus
```
### Basic Usage
```typescript
import { EventBus } from 'lever-ui-eventbus';
// Define event classes
class UserLoggedIn {
constructor(public userId: string, public email: string) {}
}
class OrderPlaced {
constructor(public orderId: string, public total: number) {}
}
// Create event bus
const bus = new EventBus();
// Subscribe to events
bus.subscribe(UserLoggedIn, (event) => {
console.log(`Welcome ${event.email}!`);
});
bus.subscribe(OrderPlaced, (event) => {
console.log(`Order ${event.orderId}: $${event.total}`);
});
// Publish events
bus.post(new UserLoggedIn('123', 'alice@example.com'));
bus.post(new OrderPlaced('ORD-456', 99.99));
```
### React Integration
```typescript
import { EventBusProvider, useEventSubscription, useEventPost } from 'lever-ui-eventbus/react';
function App() {
const bus = new EventBus();
return (
<EventBusProvider bus={bus}>
<UserComponent />
<OrderComponent />
</EventBusProvider>
);
}
function UserComponent() {
const [user, setUser] = useState(null);
const postEvent = useEventPost();
useEventSubscription(UserLoggedIn, (event) => {
setUser({ id: event.userId, email: event.email });
});
const handleLogin = () => {
postEvent(new UserLoggedIn('123', 'alice@example.com'));
};
return (
<div>
{user ? `Welcome ${user.email}` : 'Not logged in'}
<button onClick={handleLogin}>Login</button>
</div>
);
}
```
## Core Concepts
### Event Classes
Events are simple TypeScript classes. Use classes instead of plain objects for better type safety and polymorphic dispatch:
```typescript
class UserEvent {
constructor(public userId: string, public timestamp = Date.now()) {}
}
class UserLoggedIn extends UserEvent {
constructor(userId: string, public email: string) {
super(userId);
}
}
class UserLoggedOut extends UserEvent {
constructor(userId: string, public reason: string) {
super(userId);
}
}
```
### Polymorphic Dispatch
Base classes automatically receive events from derived classes:
```typescript
// Base handler receives ALL user events
bus.subscribe(UserEvent, (event) => {
console.log(`User ${event.userId} event at ${event.timestamp}`);
});
// Specific handlers for specialized behavior
bus.subscribe(UserLoggedIn, (event) => {
console.log(`${event.email} logged in`);
});
// This triggers BOTH UserEvent and UserLoggedIn handlers
bus.post(new UserLoggedIn('123', 'alice@example.com'));
```
### Async Event Bus
For non-blocking event delivery:
```typescript
import { AsyncEventBus } from 'lever-ui-eventbus';
// Use default microtask executor
const asyncBus = new AsyncEventBus();
// Or provide custom executor
const customBus = new AsyncEventBus((task) => {
setTimeout(task, 0); // Next tick delivery
});
asyncBus.subscribe(UserLoggedIn, (event) => {
// This runs asynchronously
performExpensiveOperation(event);
});
// Post and continue immediately
asyncBus.post(new UserLoggedIn('123', 'alice@example.com'));
console.log('This logs before the handler executes!');
```
### Error Handling
Handle subscriber errors gracefully:
```typescript
import { DeadEvent } from 'lever-ui-eventbus';
const errorHandler = (error, context) => {
console.error(`Handler failed for ${context.eventType.name}:`, error.message);
// Send to error reporting service
};
const robustBus = new EventBus(errorHandler);
// Handle undelivered events
bus.subscribe(DeadEvent, (deadEvent) => {
console.warn('Unhandled event:', deadEvent.event);
});
```
## React Hooks
Import React hooks and components from the `/react` subpath:
```typescript
import {
EventBusProvider,
useEventBus,
useEventSubscription,
useEventPost,
useEventState,
useLatestEvent,
useEventCollection,
useEventBusManager
} from 'lever-ui-eventbus/react';
```
### Available Hooks
| Hook | Description |
|------|-------------|
| `useEventSubscription` | Subscribe to events with automatic cleanup |
| `useEventPost` | Get a stable function to post events |
| `useEventState` | Maintain reactive state based on events |
| `useLatestEvent` | Track the most recent event of a type |
| `useEventCollection` | Collect events into an array with size limit |
| `useEventBusManager` | Access bus management utilities |
### Hook Examples
```typescript
// Reactive state based on events
function UserProfile() {
const user = useEventState(
UserUpdated,
null,
(currentUser, event) => event.user
);
return <div>{user?.name || 'Loading...'}</div>;
}
// Collect recent notifications
function NotificationList() {
const notifications = useEventCollection(NotificationShown, 5);
return (
<ul>
{notifications.map((notif, i) => (
<li key={i}>{notif.message}</li>
))}
</ul>
);
}
// Debug panel
function DebugPanel() {
const { subscriptionCount, activeTypes, clear } = useEventBusManager();
return (
<div>
<p>Active types: {activeTypes.length}</p>
<p>Subscriptions: {subscriptionCount}</p>
<button onClick={clear}>Clear All</button>
</div>
);
}
```
## Interactive Demo
Try the **[Interactive Demo](./demo)** to see the event bus in action with:
- **Event Publishers** - Login, shopping cart, orders, errors
- **Live Subscribers** - Real-time UI updates
- **Event Log** - Watch events flow through the system
- **Code Examples** - Learn different patterns and use cases
```bash
# Run the demo locally
npm run demo
npx http-server demo -p 8000
# Open http://localhost:8000
```
## API Reference
### EventBus
```typescript
class EventBus {
constructor(errorHandler?: ErrorHandler)
// Core methods
subscribe<T>(eventType: Constructor<T>, handler: (event: T) => void): Subscription
post<T>(event: T): number
// Management methods
unsubscribeAll(eventType: Constructor): number
clear(): void
getSubscriptionCount(eventType: Constructor): number
getActiveEventTypes(): Constructor[]
}
```
### AsyncEventBus
```typescript
class AsyncEventBus extends EventBus {
constructor(executor?: (task: () => void) => void, errorHandler?: ErrorHandler)
}
```
### Subscription
```typescript
interface Subscription {
unsubscribe(): void
}
```
### DeadEvent
```typescript
class DeadEvent<T = any> {
constructor(public event: T, public reason: string)
}
```
## Architecture
The event bus uses a **type-based subscription system** where:
1. **Events are classes** - Better type safety than string-based systems
2. **Polymorphic dispatch** - Base classes receive derived events automatically
3. **Weak references** - Prevents memory leaks with automatic cleanup
4. **Error isolation** - Failed handlers don't break other subscribers
5. **Dead events** - Undelivered events are captured for debugging
## Use Cases
**Perfect for:**
- Component communication in React/Vue applications
- Decoupling modules in large applications
- Event-driven architectures
- State management patterns
- Real-time UI updates
- Analytics and telemetry
- Notification systems
**Consider alternatives for:**
- High-frequency events (>1000/sec) - use specialized solutions
- Cross-process communication - use message queues
- Persistent event streams - use event sourcing libraries
## Bundle Size
| Package | Size (gzipped) |
|---------|----------------|
| Core EventBus | ~3 KB |
| React Integration | ~5 KB |
| **Total** | **~8 KB** |
## Development
```bash
# Install dependencies
npm install
# Run tests
npm test
# Run tests in watch mode
npm run test:watch
# Build library
npm run build
# Run demo
npm run demo
# Lint code
npm run lint
```
## Contributing
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Make your changes
4. Add tests for new functionality
5. Ensure all tests pass (`npm test`)
6. Commit your changes (`git commit -m 'Add amazing feature'`)
7. Push to the branch (`git push origin feature/amazing-feature`)
8. Open a Pull Request
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## Acknowledgments
- Inspired by EventBus patterns from Android development
- Built with modern TypeScript and React best practices
- Designed for performance and developer experience
---
**Pro Tip**: Use your browser's developer tools to explore the EventBus instance in the [demo](./demo) (`window.demoBus`) for hands-on learning!