JSPM

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

A high-performance event system for React applications

Package Exports

  • @devforgets/pulse

Readme

@devforgets/pulse

A high-performance event system for React applications. 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 specifically for React applications. 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 for React with automatic batching and cleanup
  • ๐Ÿงฉ React Integration: Seamless integration with React's lifecycle and hooks
  • ๐Ÿ“Š 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
  • 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

import { Pulse } from '@devforgets/pulse';

// Define your events
interface AppEvents {
  'user:login': { userId: string };
  'data:update': { value: number };
}

// Create your pulse system
const AppPulse = Pulse.createContext<AppEvents>();

// Use in your App
function App() {
  const { PulseProvider } = AppPulse;

  return (
    <PulseProvider>
      <YourComponents />
    </PulseProvider>
  );
}

// Use in components
function LoginButton() {
  const { useEvent, emitEvent } = AppPulse;
  const emit = emitEvent();

  // Listen to events
  useEvent('user:login', (data) => {
    console.log('User logged in:', data.userId);
  });

  // Emit events
  const handleLogin = () => {
    emit('user:login', { userId: '123' });
  };

  return <button onClick={handleLogin}>Login</button>;
}

With DevTools (Development Only)

import { Pulse, PulseDevTools } from '@devforgets/pulse';

function App() {
  const { PulseProvider, usePulse } = AppPulse;

  return (
    <PulseProvider>
      <YourComponents />
      {process.env.NODE_ENV === 'development' && (
        <PulseDevTools
          pulse={usePulse()}
          position="bottom-right"
          theme="light"
        />
      )}
    </PulseProvider>
  );
}

Core Concepts

Event System

Pulse uses a type-safe event system where events are defined through TypeScript interfaces. Each event has a unique name and associated data type.

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:

// Basic handler
useEvent('user:login', (data) => {
  console.log('User logged in:', data.userId);
});

// High priority handler
useEvent('user:login', (data) => {
  console.log('First to know about login:', data.userId);
}, { priority: 10 });

// Persistent handler
useEvent('data:update', (data) => {
  console.log('Data updated:', data);
}, { keepAlive: true });

Event Emission

Events can be emitted individually or batched:

function UserActions() {
  const { emitEvent, batchEvents } = AppPulse;
  const emit = emitEvent();

  // Single event
  const handleLogin = () => {
    emit('user:login', {
      userId: '123',
      timestamp: Date.now()
    });
  };

  // Batched events
  const handleFullUpdate = () => {
    batchEvents(() => {
      emit('user:login', {
        userId: '123',
        timestamp: Date.now()
      });
      emit('data:update', {
        id: 'user-123',
        value: { status: 'active' }
      });
    });
  };

  return (
    <div>
      <button onClick={handleLogin}>Login</button>
      <button onClick={handleFullUpdate}>Full Update</button>
    </div>
  );
}

Advanced Features

Priority Handlers

Handlers can be assigned priorities to ensure critical handlers execute first:

function CriticalComponent() {
  const { useEvent } = AppPulse;

  // 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);
  });
}

Event Batching

Group multiple events to optimize performance:

function DataSync() {
  const { emitEvent, batchEvents } = AppPulse;
  const emit = emitEvent();

  const syncData = () => {
    batchEvents(() => {
      // 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 });
    });
  };
}

Subscription Management

Control event subscriptions with fine-grained options:

function SubscriptionDemo() {
  const { useEvent } = AppPulse;

  useEffect(() => {
    // Create subscription with options
    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();
  }, []);
}

Component-Aware Cleanup

Pulse automatically manages subscriptions based on component lifecycle:

function AutoCleanupDemo() {
  const { useEvent } = AppPulse;

  // 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 });
}

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 { PulseProvider, usePulse, useEvent, emitEvent } = Pulse.createContext<AppEvents>();

// Wrapper component to access Pulse instance
const DevToolsWrapper = () => {
  const pulse = usePulse();
  return <PulseDevTools pulseInstance={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
  pulseInstance={pulse}
/>

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

Integration Examples

Basic Integration

function App() {
  return (
    <PulseProvider>
      <YourApp />
      <DevToolsWrapper />
    </PulseProvider>
  );
}

With Real-time Monitoring

function App() {
  return (
    <PulseProvider>
      <YourApp />
      <EventsGenerator /> {/* Add continuous events */}
      <DevToolsWrapper />
    </PulseProvider>
  );
}

The DevTools provide valuable insights into your application's event system, helping you debug, optimize, and monitor your event-driven architecture in real-time.

API Reference

Core API

Pulse.createContext<T>()

Creates a new Pulse context with typed events.

const AppPulse = Pulse.createContext<AppEvents>();

Returns:

  • PulseProvider: Context provider component
  • usePulse: Hook to access Pulse instance
  • useEvent: Hook to subscribe to events
  • emitEvent: Hook to emit events
  • batchEvents: Hook to batch multiple events

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;
  keepAlive?: boolean;
  disabled?: boolean;
}

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);
});
  1. Proper Handler Cleanup
// Let Pulse handle cleanup
useEvent('data:update', handler);

// Or manage manually
useEffect(() => {
  const subscription = useEvent('data:update', handler);
  return () => subscription.unsubscribe();
}, []);
  1. Use Priority for Critical Handlers
// Critical updates first
useEvent('data:update', criticalHandler, { priority: 100 });

// Normal updates
useEvent('data:update', normalHandler, { priority: 0 });

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 };
}

function FormComponent() {
  const { useEvent, emitEvent } = AppPulse;
  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 };
}

function RealtimeComponent() {
  const { useEvent, emitEvent } = AppPulse;
  const emit = emitEvent();

  // 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);
  });

  // Periodic sync
  useEffect(() => {
    const interval = setInterval(() => {
      emit('data:sync', { timestamp: Date.now() });
    }, 5000);

    return () => clearInterval(interval);
  }, []);

  return <div>{/* Component content */}</div>;
}

Theme Management

interface ThemeEvents {
  'theme:change': { mode: 'light' | 'dark' };
  'theme:update': { colors: Record<string, string> };
}

function ThemeProvider() {
  const { useEvent, emitEvent } = AppPulse;
  const emit = emitEvent();

  // Listen for theme changes
  useEvent('theme:change', ({ mode }) => {
    document.documentElement.classList.toggle('dark', mode === 'dark');
  });

  // Listen for color updates
  useEvent('theme:update', ({ colors }) => {
    Object.entries(colors).forEach(([key, value]) => {
      document.documentElement.style.setProperty(`--color-${key}`, value);
    });
  });

  return <div>{/* Theme provider content */}</div>;
}

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.