JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 14
  • Score
    100M100P100Q61055F
  • License MIT

High-performance event system for React applications and services. Features type-safe event handling, automatic cleanup, real-time monitoring, and service integration.

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.

npm version TypeScript License: MIT

Table of Contents

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

  1. Development Only
{process.env.NODE_ENV === 'development' && <DevToolsWrapper />}
  1. Event Monitoring
  • Use meaningful event names
  • Include relevant event data
  • Monitor handler counts
  • Track memory usage
  1. 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 instance
  • hooks: 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

  1. 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', {});
});
  1. 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());
  }
}
  1. 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

  1. Centralize Pulse Creation
// services/pulse.ts
export const { pulse, hooks } = PulseService.createInstance<AppEvents>();
export const { PulseProvider, useEvent, emitEvent } = hooks;
  1. 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 = [];
  }
}
  1. 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 });
  }
}
  1. 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

  1. Clone the repository:
git clone https://github.com/devforgets/pulse.git
cd pulse
  1. Install dependencies:
pnpm install
  1. Run tests:
pnpm test
  1. Start development:
pnpm dev

Guidelines

  1. Code Style

    • Use TypeScript
    • Follow existing patterns
    • Include JSDoc comments
    • Write tests for new features
  2. Commits

    • Use conventional commits
    • Keep commits focused
    • Include tests
    • Update documentation
  3. 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


Part of the DevForgeTS toolkit.