Package Exports
- @devforgets/pulse
Readme
@devforgets/pulse
A high-performance event system for React applications and services. Originally developed as an internal tool for managing complex event-driven architectures, now available as part of the DevForge toolkit.
Table of Contents
- @devforgets/pulse
- DevTools
Introduction
Pulse is a powerful event system designed for both React applications and standalone services. It provides a type-safe, efficient, and developer-friendly way to handle events across your application. With built-in performance monitoring and development tools, Pulse makes it easy to manage complex event-driven architectures.
Why Pulse?
- ๐ฏ Type Safety: Full TypeScript support with extensive type inference
- โก High Performance: Optimized event handling with automatic batching and cleanup
- ๐งฉ Flexible Integration: Use in React components or standalone services
- ๐ DevTools: Built-in debugging and performance monitoring tools
- ๐งน Smart Cleanup: Automatic memory management and inactive handler cleanup
- ๐จ Developer Experience: Intuitive API with comprehensive tooling
Key Features
- Type-safe event handling
- Service-based and React component usage
- Automatic event batching
- Priority-based handlers
- Component-aware cleanup
- Real-time performance monitoring
- Built-in developer tools
- Memory leak prevention
- React hooks integration
Installation
# Using npm
npm install @devforgets/pulse
# Using yarn
yarn add @devforgets/pulse
# Using pnpm
pnpm add @devforgets/pulse
Quick Start
React Usage
import { createPulse } from '@devforgets/pulse';
// Define your events
interface AppEvents {
'user:login': { userId: string };
'data:update': { value: number };
}
// Create your pulse system
export const { pulse, hooks } = createPulse<AppEvents>();
export const { PulseProvider, useEvent, emitEvent, batchEvents } = hooks;
// Use in your App
function App() {
return (
<PulseProvider>
<YourComponents />
</PulseProvider>
);
}
// Use in components
function LoginButton() {
const emit = emitEvent();
useEvent('user:login', (data) => {
console.log('User logged in:', data.userId);
});
return <button onClick={() => emit('user:login', { userId: '123' })}>Login</button>;
}
Service Usage
import { PulseService } from '@devforgets/pulse';
interface AppEvents {
'user:login': { userId: string };
'data:update': { value: number };
}
// Create and export pulse instance
export const { pulse, hooks } = PulseService.createInstance<AppEvents>();
// Service usage
class AuthService {
private pulse = PulseService.getInstance<AppEvents>();
private subscription;
constructor() {
// Subscribe to events
this.subscription = this.pulse.on('user:login', this.handleLogin, 'authService');
}
private handleLogin = (data: AppEvents['user:login']) => {
console.log('Auth service received login:', data);
};
public emitLogin(userId: string) {
this.pulse.emit('user:login', { userId });
}
public cleanup() {
this.subscription?.unsubscribe();
}
}
With DevTools (Development Only)
import { PulseDevTools } from '@devforgets/pulse';
function App() {
return (
<PulseProvider>
<YourComponents />
{process.env.NODE_ENV === 'development' && (
<PulseDevTools
pulse={pulse}
position="bottom-right"
theme="light"
/>
)}
</PulseProvider>
);
}
Core Concepts
Event System
Pulse uses a type-safe event system where events are defined through TypeScript interfaces:
interface AppEvents {
'user:login': { userId: string; timestamp: number };
'user:logout': void;
'data:update': { id: string; value: any };
}
Event Handlers
Handlers can be registered with different priorities and options:
// In React components
useEvent('user:login', (data) => {
console.log('User logged in:', data.userId);
});
// In services
pulse.on('user:login', (data) => {
console.log('Service received login:', data);
}, 'serviceId', { priority: 10 });
Event Emission
Events can be emitted individually or batched:
// In React components
function UserActions() {
const emit = emitEvent();
const batch = batchEvents();
const handleFullUpdate = () => {
batch(() => {
emit('user:login', { userId: '123', timestamp: Date.now() });
emit('data:update', { id: 'user-123', value: { status: 'active' } });
});
};
}
// In services
class DataService {
private pulse = PulseService.getInstance<AppEvents>();
public syncData() {
this.pulse.batch(() => {
this.pulse.emit('data:update', { id: 'user', value: userData });
this.pulse.emit('data:update', { id: 'settings', value: settings });
});
}
}
Service Integration
Services can directly use the Pulse instance:
export class NotificationService {
private pulse = PulseService.getInstance<AppEvents>();
private subscriptions: Array<() => void> = [];
constructor() {
// High-priority notification handler
const sub = this.pulse.on('user:login', this.sendNotification, 'notifications', {
priority: 100
});
this.subscriptions.push(() => sub.unsubscribe());
}
private sendNotification = (data: AppEvents['user:login']) => {
// Handle notification logic
};
public cleanup() {
this.subscriptions.forEach(unsub => unsub());
}
}
Advanced Features
Priority Handlers
Handlers can be assigned priorities to ensure critical handlers execute first:
// React component with priority handlers
function CriticalComponent() {
// High priority handler (executes first)
useEvent('data:update', (data) => {
console.log('Critical update:', data);
}, { priority: 100 });
// Normal priority handler
useEvent('data:update', (data) => {
console.log('Normal update:', data);
});
}
// Service with priority handlers
class CriticalService {
private pulse = PulseService.getInstance<AppEvents>();
initialize() {
// High priority handler
this.pulse.on('data:update', this.handleCritical, 'critical', { priority: 100 });
// Normal priority handler
this.pulse.on('data:update', this.handleNormal, 'normal');
}
}
Event Batching
Group multiple events to optimize performance:
// React component batching
function DataSync() {
const emit = emitEvent();
const batch = batchEvents();
const syncData = () => {
batch(() => {
// All these events will be batched and processed together
emit('data:update', { id: 'user', value: userData });
emit('data:update', { id: 'settings', value: settings });
emit('data:update', { id: 'preferences', value: prefs });
});
};
}
// Service batching
class DataService {
private pulse = PulseService.getInstance<AppEvents>();
public batchUpdates(updates: any[]) {
this.pulse.batch(() => {
updates.forEach(update => {
this.pulse.emit('data:update', update);
});
});
}
}
Subscription Management
Control event subscriptions with fine-grained options:
// React component subscriptions
function SubscriptionDemo() {
useEffect(() => {
const subscription = useEvent('data:update', (data) => {
console.log('Data:', data);
}, {
priority: 10,
keepAlive: true
});
// Pause/Resume subscription
subscription.pause();
subscription.resume();
// Check status
console.log('Is paused:', subscription.isPaused());
return () => subscription.unsubscribe();
}, []);
}
// Service subscriptions
class ServiceWithSubscriptions {
private pulse = PulseService.getInstance<AppEvents>();
private subscriptions: Array<{ unsubscribe: () => void }> = [];
public initialize() {
const sub = this.pulse.on('data:update', this.handler, 'service', {
priority: 10,
keepAlive: true
});
this.subscriptions.push(sub);
// Subscription control
sub.pause(); // Pause handling events
sub.resume(); // Resume handling events
if (sub.isPaused()) {
console.log('Subscription is paused');
}
}
public cleanup() {
this.subscriptions.forEach(sub => sub.unsubscribe());
}
}
Component-Aware Cleanup
Pulse automatically manages subscriptions based on component lifecycle:
// Automatic cleanup in components
function AutoCleanupDemo() {
// This subscription will be automatically cleaned up
// when the component unmounts
useEvent('user:login', (data) => {
console.log('User:', data);
});
// This subscription will persist even after unmount
useEvent('system:status', (data) => {
console.log('System:', data);
}, { keepAlive: true });
}
// Manual cleanup in services
class ServiceWithCleanup {
private pulse = PulseService.getInstance<AppEvents>();
private cleanupFunctions: Array<() => void> = [];
public initialize() {
// Regular subscription
const sub1 = this.pulse.on('event1', this.handler1, 'service');
this.cleanupFunctions.push(() => sub1.unsubscribe());
// Persistent subscription
const sub2 = this.pulse.on('event2', this.handler2, 'service', { keepAlive: true });
this.cleanupFunctions.push(() => sub2.unsubscribe());
}
public destroy() {
// Clean up all subscriptions
this.cleanupFunctions.forEach(cleanup => cleanup());
this.cleanupFunctions = [];
}
}
DevTools
Pulse includes a lightweight yet powerful development tool for monitoring and debugging your event system in real-time.
Setup DevTools
import { Pulse, PulseDevTools } from '@devforgets/pulse';
// Create your Pulse instance with types
const { pulse, hooks } = PulseService.createInstance<AppEvents>();
export const { PulseProvider, useEvent, emitEvent } = hooks;
// Wrapper component to access Pulse instance
const DevToolsWrapper = () => {
return <PulseDevTools pulse={pulse} />;
};
function App() {
return (
<PulseProvider>
<YourComponents />
{process.env.NODE_ENV === 'development' && <DevToolsWrapper />}
</PulseProvider>
);
}
Features
Real-Time Event Monitoring
- Live event counter (events/second)
- Active event handlers tracking
- Memory usage monitoring
- Event history and data inspection
Event Metrics
- Total active handlers
- Memory consumption
- Event frequency charts
- Handler distribution
Event Inspection
- Real-time event logs
- Event data inspection
- Handler counts per event
- Event timing information
Visual Features
- Floating status indicator
- Expandable detailed view
- Real-time performance charts
- Event type categorization
Example with Active Monitoring
interface AppEvents {
'system:heartbeat': { timestamp: number };
'user:activity': { userId: string; action: string; timestamp: number };
'performance:update': { memory: number; eventCount: number; timestamp: number };
}
// Event Generator Component
function EventsGenerator() {
const emit = emitEvent();
useEffect(() => {
// System heartbeat
const heartbeat = setInterval(() => {
emit('system:heartbeat', { timestamp: Date.now() });
}, 1000);
// User activity simulation
const activity = setInterval(() => {
emit('user:activity', {
userId: 'user1',
action: 'interaction',
timestamp: Date.now()
});
}, 3000);
// Performance metrics
const performance = setInterval(() => {
emit('performance:update', {
memory: Math.round(window.performance.memory?.usedJSHeapSize / 1024 / 1024) || 0,
eventCount: 0,
timestamp: Date.now()
});
}, 5000);
return () => {
clearInterval(heartbeat);
clearInterval(activity);
clearInterval(performance);
};
}, [emit]);
return null;
}
Visualization Options
Metrics View
<PulseDevTools
pulse={pulse}
position="bottom-right"
theme="light"
/>
Shows:
- Events per second count
- Active handlers count
- Memory usage
- Recent events log
- Performance trends
Best Practices
- Development Only
{process.env.NODE_ENV === 'development' && <DevToolsWrapper />}
- Event Monitoring
- Use meaningful event names
- Include relevant event data
- Monitor handler counts
- Track memory usage
- Performance Optimization
- Watch for high event frequencies
- Monitor memory consumption
- Check handler cleanup
- Use event batching when needed
API Reference
Core API
createPulse<T>()
Creates a new Pulse instance with typed events.
const { pulse, hooks } = createPulse<AppEvents>();
Returns
pulse
: Core Pulse instancehooks
: React hooks and components
Service API
PulseService.createInstance<T>()
Creates a new Pulse instance for service usage.
const { pulse, hooks } = PulseService.createInstance<AppEvents>();
PulseService.getInstance<T>()
Gets the existing Pulse instance.
const pulse = PulseService.getInstance<AppEvents>();
Instance Methods
// Emit events
pulse.emit(eventName, eventData);
// Subscribe to events
const subscription = pulse.on(eventName, handler, serviceId, {
priority?: number;
keepAlive?: boolean;
});
// Batch events
pulse.batch(() => {
// Emit multiple events
});
React Hooks
useEvent(eventName, handler, options?)
Subscribe to events with options.
useEvent('eventName', (data) => {
// Handle event
}, {
priority?: number; // Handler priority (default: 0)
keepAlive?: boolean; // Keep subscription after unmount (default: false)
disabled?: boolean; // Temporarily disable handler (default: false)
});
emitEvent()
Get function to emit events.
const emit = emitEvent();
emit('eventName', eventData);
batchEvents()
Get function to batch multiple events.
const batch = batchEvents();
batch(() => {
emit('event1', data1);
emit('event2', data2);
});
usePulse()
Access the Pulse instance directly.
const pulse = usePulse();
Types
PulseSubscription
interface PulseSubscription {
unsubscribe: () => void;
pause: () => void;
resume: () => void;
isPaused: () => boolean;
}
HandlerOptions
interface HandlerOptions {
priority?: number; // Handler execution priority
keepAlive?: boolean; // Keep subscription after cleanup
disabled?: boolean; // Temporarily disable handler
}
PulseInstance
type PulseInstance<T> = {
emit: <K extends keyof T>(eventName: K, data: T[K]) => void;
on: <K extends keyof T>(
eventName: K,
handler: (data: T[K]) => void,
id: string,
options?: HandlerOptions
) => PulseSubscription;
batch: (callback: () => void) => void;
};
DevToolsProps
interface DevToolsProps {
pulse: Pulse<any>;
position?: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left';
theme?: 'light' | 'dark';
}
Best Practices
Event Naming
Follow a consistent event naming pattern:
interface AppEvents {
// Format: 'domain:action'
'user:login': LoginData;
'user:logout': void;
// Group related events
'data:fetch': FetchParams;
'data:update': UpdateData;
'data:delete': DeleteParams;
// UI events
'ui:modal:open': ModalConfig;
'ui:modal:close': void;
'ui:theme:change': ThemeData;
}
Performance Optimization
- Use Batching for Multiple Events
// Instead of this
emit('data:update', data1);
emit('ui:refresh', {});
emit('stats:update', stats);
// Do this
batchEvents(() => {
emit('data:update', data1);
emit('ui:refresh', {});
emit('stats:update', stats);
});
// In services
pulse.batch(() => {
pulse.emit('data:update', data1);
pulse.emit('ui:refresh', {});
});
- Proper Handler Cleanup
// In React components - automatic cleanup
useEvent('data:update', handler);
// In React components - manual cleanup
useEffect(() => {
const subscription = useEvent('data:update', handler);
return () => subscription.unsubscribe();
}, []);
// In services - managed cleanup
class Service {
private subscriptions: Array<() => void> = [];
public initialize() {
const sub = pulse.on('event', handler, 'serviceId');
this.subscriptions.push(() => sub.unsubscribe());
}
public destroy() {
this.subscriptions.forEach(cleanup => cleanup());
}
}
- Use Priority for Critical Handlers
// Critical updates first
useEvent('data:update', criticalHandler, { priority: 100 });
// Normal updates
useEvent('data:update', normalHandler, { priority: 0 });
// In services
pulse.on('data:update', criticalHandler, 'critical', { priority: 100 });
Service Organization
- Centralize Pulse Creation
// services/pulse.ts
export const { pulse, hooks } = PulseService.createInstance<AppEvents>();
export const { PulseProvider, useEvent, emitEvent } = hooks;
- Manage Service Lifecycles
class Service {
private pulse = PulseService.getInstance<AppEvents>();
private subscriptions: Array<() => void> = [];
public initialize() {
// Subscribe to events
const sub1 = this.pulse.on('event1', this.handler1, 'serviceId');
const sub2 = this.pulse.on('event2', this.handler2, 'serviceId');
this.subscriptions.push(
() => sub1.unsubscribe(),
() => sub2.unsubscribe()
);
}
public destroy() {
this.subscriptions.forEach(cleanup => cleanup());
this.subscriptions = [];
}
}
- Type Safety
// Define strict event types
type EventName = `${string}:${string}`;
interface AppEvents extends Record<EventName, unknown> {
'user:login': { userId: string };
'user:logout': void;
}
// Use type inference
class UserService {
private pulse = PulseService.getInstance<AppEvents>();
public emitLogin(userId: string) {
// Type safe - will error if event or data shape is wrong
this.pulse.emit('user:login', { userId });
}
}
- Error Handling
class Service {
private handleEvent = (data: AppEvents['event']) => {
try {
// Handle event
} catch (error) {
console.error('Error handling event:', error);
// Optionally emit error event
this.pulse.emit('error:service', { error });
}
};
}
Real-World Examples
Form Management
interface FormEvents {
'form:submit': { values: Record<string, any> };
'form:validate': { field: string; value: any };
'form:error': { field: string; error: string };
}
// Form Service
class FormService {
private pulse = PulseService.getInstance<FormEvents>();
public validateField(field: string, value: any) {
try {
this.validate(field, value);
} catch (error) {
this.pulse.emit('form:error', {
field,
error: error.message
});
}
}
public submitForm(values: Record<string, any>) {
this.pulse.emit('form:submit', { values });
}
}
// Form Component
function FormComponent() {
const { useEvent, emitEvent } = hooks;
const emit = emitEvent();
const [errors, setErrors] = useState({});
// Listen for validation errors
useEvent('form:error', ({ field, error }) => {
setErrors(prev => ({ ...prev, [field]: error }));
});
const handleSubmit = (values) => {
emit('form:submit', { values });
};
return (
<form onSubmit={handleSubmit}>
{/* Form fields */}
</form>
);
}
Real-time Updates
interface RealtimeEvents {
'data:update': { path: string; value: any };
'data:sync': { timestamp: number };
'connection:status': { online: boolean };
}
// Realtime Service
class RealtimeService {
private pulse = PulseService.getInstance<RealtimeEvents>();
private connection: WebSocket;
constructor() {
this.connection = new WebSocket('ws://your-server');
this.setupWebSocket();
}
private setupWebSocket() {
this.connection.onmessage = (event) => {
const data = JSON.parse(event.data);
this.pulse.emit('data:update', {
path: data.path,
value: data.value
});
};
this.connection.onopen = () => {
this.pulse.emit('connection:status', { online: true });
};
this.connection.onclose = () => {
this.pulse.emit('connection:status', { online: false });
};
}
public syncData() {
this.pulse.emit('data:sync', { timestamp: Date.now() });
}
}
// Real-time Component
function RealtimeComponent() {
const { useEvent } = hooks;
// High-priority connection status
useEvent('connection:status',
({ online }) => {
console.log('Connection:', online);
},
{ priority: 100 }
);
// Regular data updates
useEvent('data:update', ({ path, value }) => {
console.log(`Update at ${path}:`, value);
});
return <div>{/* Component content */}</div>;
}
Theme Management
interface ThemeEvents {
'theme:change': { mode: 'light' | 'dark' };
'theme:update': { colors: Record<string, string> };
}
// Theme Service
class ThemeService {
private pulse = PulseService.getInstance<ThemeEvents>();
public initialize() {
// Load theme from storage
const savedTheme = localStorage.getItem('theme') as 'light' | 'dark';
if (savedTheme) {
this.setTheme(savedTheme);
}
}
public setTheme(mode: 'light' | 'dark') {
localStorage.setItem('theme', mode);
this.pulse.emit('theme:change', { mode });
}
public updateColors(colors: Record<string, string>) {
this.pulse.emit('theme:update', { colors });
}
}
// Theme Provider Component
function ThemeProvider() {
const { useEvent } = hooks;
useEvent('theme:change', ({ mode }) => {
document.documentElement.classList.toggle('dark', mode === 'dark');
});
useEvent('theme:update', ({ colors }) => {
Object.entries(colors).forEach(([key, value]) => {
document.documentElement.style.setProperty(`--color-${key}`, value);
});
});
return <div>{/* Theme provider content */}</div>;
}
Service Integration Examples
// Analytics Service
class AnalyticsService {
private pulse = PulseService.getInstance<AppEvents>();
constructor() {
// Track all events with 'user:' prefix
this.pulse.on('*', (data, eventName) => {
if (eventName.startsWith('user:')) {
this.trackUserEvent(eventName, data);
}
}, 'analytics');
}
private trackUserEvent(event: string, data: any) {
// Analytics implementation
analytics.track(event, data);
}
}
// Cache Service
class CacheService {
private pulse = PulseService.getInstance<AppEvents>();
private cache = new Map<string, any>();
public initialize() {
this.pulse.on('data:update', this.updateCache, 'cache', {
priority: 90 // High priority for cache updates
});
}
private updateCache = (data: AppEvents['data:update']) => {
this.cache.set(data.id, data.value);
};
public get(id: string) {
return this.cache.get(id);
}
}
// Error Tracking Service
class ErrorTrackingService {
private pulse = PulseService.getInstance<AppEvents>();
public initialize() {
this.pulse.on('error:*', (data, eventName) => {
this.logError(eventName, data);
}, 'errorTracking', { priority: 100 });
}
private logError(event: string, data: any) {
// Error tracking implementation
errorTracker.capture(event, data);
}
}
Contributing
We welcome contributions to Pulse! Here's how you can help:
Development Setup
- Clone the repository:
git clone https://github.com/devforgets/pulse.git
cd pulse
- Install dependencies:
pnpm install
- Run tests:
pnpm test
- Start development:
pnpm dev
Guidelines
Code Style
- Use TypeScript
- Follow existing patterns
- Include JSDoc comments
- Write tests for new features
Commits
- Use conventional commits
- Keep commits focused
- Include tests
- Update documentation
Pull Requests
- Create feature branch
- Add tests
- Update documentation
- Request review
Running Tests
# Run all tests
pnpm test
# Run specific tests
pnpm test:unit
pnpm test:integration
# Run with coverage
pnpm test:coverage
Building
# Build package
pnpm build
# Build documentation
pnpm docs:build
License
MIT ยฉ DevForgeTS