Now.js Framework Documentation

Now.js Framework Documentation

EventManager - Custom Event System

EN 31 Oct 2025 01:20

EventManager - Custom Event System

Documentation for EventManager, a Custom Event System for managing Application-level Events

📋 Table of Contents

  1. Overview
  2. Installation and Import
  3. Basic Usage
  4. Core API
  5. Patterns and Wildcards
  6. Event Groups
  7. Priority System
  8. Middleware
  9. Async Events
  10. Context Management
  11. Cleanup and Memory Management
  12. Integration with Now.js
  13. Usage Examples
  14. API Reference
  15. Best Practices

Overview

EventManager is a Custom Event System designed for managing Application-level Events (not DOM Events) with advanced capabilities beyond standard EventEmitter

Key Features

  • Event Bus Pattern: Centralized Publish/Subscribe system
  • Pattern Matching: Wildcards and Regular Expression support
  • Event Hierarchy: Namespaced Events (dot notation)
  • Event Groups: Group events for collective management
  • Priority System: Control listener execution order
  • Middleware: Intercept and modify events
  • Async Support: Async/Await and Promises
  • Context Control: Event propagation control
  • Auto Cleanup: Automatic memory management
  • Debug Mode: Event history tracking and debug info

Differences from EventSystemManager

Feature EventManager EventSystemManager
Purpose Application Events DOM Events
Core API on(), emit(), off() addEventListener()
Event Types Custom events DOM events (click, submit, etc.)
Target Application logic DOM elements
Patterns ✅ Wildcards support ❌ Not supported
Middleware ✅ Supported ❌ Not supported
Groups ✅ Supported ❌ Not supported
Priority ✅ Supported ✅ Supported

When to Use EventManager

Use EventManager when:

  • You need an Event Bus for your application
  • You need communication between Components
  • You need to decouple Modules
  • You need Pattern Matching (wildcards)
  • You need Event Groups
  • You need Middleware

Don't use EventManager when:

  • You need to handle DOM Events (use EventSystemManager)
  • You need Event Delegation
  • You need Native DOM Event Features

Installation and Import

EventManager is loaded with the Now.js Framework and is ready to use immediately via the window object:

// No import needed - ready to use immediately
console.log(window.EventManager); // EventManager object

// Or access via Now.js
const eventManager = Now.getManager('event');

Dependencies

EventManager requires these dependencies:

  • ErrorManager - For error handling
  • Utils - For UUID generation (optional)

Initialization

// Initialize with default config
await EventManager.init();

// Or initialize with custom config
await EventManager.init({
  asyncTimeout: 10000,
  maxListeners: 1000,
  traceEvents: true,
  cleanup: {
    enabled: true,
    interval: 60000,
    maxAge: 1800000
  }
});

Basic Usage

1. Register Listener (on)

// Basic
EventManager.on('user:login', (context) => {
  console.log('User logged in:', context.data);
});

// With options
EventManager.on('user:login', (context) => {
  console.log('User logged in:', context.data.userId);
}, {
  priority: 10,
  once: false
});

2. Emit Event (emit)

// Emit event with data
await EventManager.emit('user:login', {
  userId: 123,
  username: 'john_doe',
  timestamp: Date.now()
});

// Get results from listeners
const results = await EventManager.emit('calculate:sum', {
  numbers: [1, 2, 3, 4, 5]
});
console.log('Results:', results); // [15]

3. Remove Listener (off)

// Remove all listeners for event
EventManager.off('user:login');

// Remove specific listener
const unsubscribe = EventManager.on('user:login', handler);
unsubscribe(); // Remove this listener

// Or use listener ID
EventManager.off('user:login', listenerId);

4. Listen Once (once)

// Execute once then remove
EventManager.once('app:initialized', (context) => {
  console.log('App initialized!');
});

// Or use once option
EventManager.on('popup:shown', handler, {once: true});

Core API

on(event, callback, options?)

Register a listener for an event

const unsubscribe = EventManager.on(eventName, callback, options);

Parameters:

  • event (string) - Event name or pattern
  • callback (function) - Function to call, receives context object
  • options (object) - optional
    • priority (number) - Priority (higher = first) default: 0
    • once (boolean) - Execute once default: false
    • async (boolean) - Async handler support default: false
    • group (string) - Group name
    • timeout (number) - Async timeout (ms) default: 5000
    • maxRetries (number) - Retry attempts on error default: 1
    • onError (function) - Error handler

Returns: Function to unsubscribe

Examples:

// Basic
EventManager.on('message:received', (context) => {
  console.log('Message:', context.data.text);
});

// With priority
EventManager.on('data:processing', handleData, {
  priority: 100 // Execute before other listeners
});

// With group
EventManager.on('notification:*', handleNotification, {
  group: 'notifications'
});

// Async handler
EventManager.on('user:register', async (context) => {
  await sendWelcomeEmail(context.data.email);
  return {success: true};
}, {
  async: true,
  timeout: 10000
});

emit(event, data?)

Emit an event with data

const results = await EventManager.emit(eventName, data);

Parameters:

  • event (string) - Event name
  • data (object) - Data to send optional

Returns: Promise - Array of results from listeners

Examples:

// Emit event
await EventManager.emit('cart:item-added', {
  productId: 123,
  quantity: 2,
  price: 99.99
});

// Get results
const results = await EventManager.emit('validate:form', {
  formData: {...}
});

const isValid = results.every(r => r === true);

// Emit without data
await EventManager.emit('app:ready');

off(event, identifier?)

Remove listeners

const removed = EventManager.off(eventName, identifier);

Parameters:

  • event (string) - Event name or pattern
  • identifier (string|function) - optional
    • If not specified: remove all
    • string: remove by ID
    • function: remove by callback reference

Returns: boolean - true if removed successfully

Examples:

// Remove all
EventManager.off('user:login');

// Remove specific listener
const handler = (ctx) => console.log(ctx.data);
EventManager.on('event', handler);
EventManager.off('event', handler);

// Use unsubscribe function
const unsubscribe = EventManager.on('event', handler);
unsubscribe();

once(event, callback, options?)

Register a one-time listener

const unsubscribe = EventManager.once(eventName, callback, options);

Same as on() but automatically sets once: true

Examples:

// Execute once
EventManager.once('app:loaded', (context) => {
  console.log('App loaded at:', context.timestamp);
});

// With priority
EventManager.once('init:complete', handler, {
  priority: 100
});

Patterns and Wildcards

EventManager supports pattern matching for event names

Wildcard Operators

Operator Meaning Example Matches
* Match any characters user:* user:login, user:logout, user:register
? Match single character user:log?n user:login
+ Match preceding character 1+ times (use with regex) -
| OR operator (use with regex) -

Pattern Examples

// Match all events starting with user:
EventManager.on('user:*', (context) => {
  console.log('User event:', context.event);
});

// Match all notification types
EventManager.on('notification:*', (context) => {
  showNotification(context.data.message);
});

// Match multi-level events
EventManager.on('api:*:success', (context) => {
  // Matches api:user:success, api:product:success, etc.
  console.log('API success:', context.event);
});

// Use single character wildcard
EventManager.on('page:?', (context) => {
  // Matches page:1, page:2, page:a, page:b
});

Custom Regex Patterns

// Use regex pattern directly
EventManager.on(/^user:(login|logout|register)$/, (context) => {
  console.log('Auth event:', context.event);
});

// Complex pattern
EventManager.on(/^api:\w+:(get|post|put|delete)$/, (context) => {
  logApiCall(context);
});

Pattern Priority

When an event matches multiple patterns:

  1. Exact match executes first
  2. Pattern matches execute by priority
  3. Listeners within same pattern execute by priority
// Exact match - highest priority
EventManager.on('user:login', handler1, {priority: 10});

// Pattern match - executes after exact match
EventManager.on('user:*', handler2, {priority: 100}); // Even with higher priority

// When emit('user:login')
// Executes: handler1 -> handler2

Event Groups

Group events for collective management

Create Group

// Add listeners to group
EventManager.on('notification:info', handler1, {group: 'notifications'});
EventManager.on('notification:warning', handler2, {group: 'notifications'});
EventManager.on('notification:error', handler3, {group: 'notifications'});

Emit Group

Emit to all events in group

// Emit to all events in group
const results = await EventManager.emitGroup('notifications', {
  message: 'System update completed',
  level: 'info'
});

Use Cases for Groups

1. Notification System

// Group notification handlers
EventManager.on('notification:info', showInfoNotification, {
  group: 'notifications'
});

EventManager.on('notification:success', showSuccessNotification, {
  group: 'notifications'
});

EventManager.on('notification:error', showErrorNotification, {
  group: 'notifications'
});

// Emit to entire group
await EventManager.emitGroup('notifications', {
  message: 'Operation completed'
});

2. Analytics Tracking

// Group analytics events
EventManager.on('analytics:pageview', trackPageView, {
  group: 'analytics'
});

EventManager.on('analytics:click', trackClick, {
  group: 'analytics'
});

EventManager.on('analytics:conversion', trackConversion, {
  group: 'analytics'
});

// Track all analytics
await EventManager.emitGroup('analytics', {
  session: sessionId,
  timestamp: Date.now()
});

3. Feature Toggles

// Group feature events
EventManager.on('feature:enabled', enableFeature, {
  group: 'features'
});

EventManager.on('feature:disabled', disableFeature, {
  group: 'features'
});

// Emit to all features
await EventManager.emitGroup('features', {
  feature: 'dark-mode',
  enabled: true
});

Priority System

Control listener execution order

Priority Values

  • Higher number = Higher priority (executes first)
  • Lower number = Lower priority (executes later)
  • Default = 0
// High priority - executes first
EventManager.on('data:process', validateData, {
  priority: 100
});

// Medium priority
EventManager.on('data:process', transformData, {
  priority: 50
});

// Low priority - executes later
EventManager.on('data:process', saveData, {
  priority: 10
});

// Default priority (0) - executes last
EventManager.on('data:process', logData);

// When emit('data:process')
// Executes in order: validateData -> transformData -> saveData -> logData

Use Cases for Priority

1. Data Processing Pipeline

// Validation must run first
EventManager.on('form:submit', validateForm, {
  priority: 100
});

// Transformation after validation
EventManager.on('form:submit', transformData, {
  priority: 80
});

// Sanitization
EventManager.on('form:submit', sanitizeData, {
  priority: 60
});

// Save data - executes last
EventManager.on('form:submit', saveToDatabase, {
  priority: 10
});

2. Authentication Flow

// Verify token first
EventManager.on('request:*', verifyToken, {
  priority: 1000
});

// Check permissions
EventManager.on('request:*', checkPermissions, {
  priority: 900
});

// Rate limiting
EventManager.on('request:*', checkRateLimit, {
  priority: 800
});

// Actual work - lower priority
EventManager.on('request:create', createResource, {
  priority: 100
});

3. Error Handling

// Log error first
EventManager.on('error:*', logError, {
  priority: 100
});

// Notify admin
EventManager.on('error:critical', notifyAdmin, {
  priority: 90
});

// Show error to user
EventManager.on('error:*', showErrorMessage, {
  priority: 50
});

Middleware

Intercept and modify events before passing to listeners

Creating Middleware

// Function middleware
EventManager.use((context) => {
  console.log('Event:', context.event);
  console.log('Data:', context.data);

  // return false to stop event
  if (context.data.blocked) {
    return false;
  }

  // return true or undefined to continue
  return true;
});

// Object middleware with hooks
EventManager.use({
  beforeEmit: (context) => {
    // Run before emit
    console.log('Before emit:', context.event);

    // Modify data
    context.data.timestamp = Date.now();

    return true; // Continue
  },

  afterEmit: (context) => {
    // Run after emit
    console.log('After emit:', context.event);
    console.log('Results:', context.results);
  }
});

Middleware Hooks

Hook When Use Case
beforeEmit Before emitting event Validation, logging, data transformation
afterEmit After emit completes Logging results, cleanup, notifications

Middleware Examples

1. Logging Middleware

EventManager.use({
  beforeEmit: (context) => {
    console.log(`[Event] ${context.event}`, {
      timestamp: context.timestamp,
      data: context.data
    });
  },

  afterEmit: (context) => {
    console.log(`[Event Complete] ${context.event}`, {
      duration: Date.now() - context.timestamp,
      results: context.results.length
    });
  }
});

2. Authentication Middleware

EventManager.use((context) => {
  // Check authentication for events requiring auth
  if (context.event.startsWith('api:')) {
    if (!isAuthenticated()) {
      console.error('Unauthorized event:', context.event);
      return false; // block event
    }
  }

  return true;
});

3. Data Validation Middleware

EventManager.use({
  beforeEmit: (context) => {
    // Validate data structure
    if (context.data && typeof context.data !== 'object') {
      console.error('Invalid data type for event:', context.event);
      return false;
    }

    // Add default values
    context.data.timestamp = context.data.timestamp || Date.now();
    context.data.source = context.data.source || 'app';

    return true;
  }
});

4. Rate Limiting Middleware

const eventCounts = new Map();

EventManager.use((context) => {
  const key = `${context.event}:${Date.now() / 1000 | 0}`;
  const count = eventCounts.get(key) || 0;

  if (count >= 100) {
    console.warn('Rate limit exceeded for:', context.event);
    return false;
  }

  eventCounts.set(key, count + 1);
  return true;
});

5. Error Tracking Middleware

EventManager.use({
  afterEmit: (context) => {
    // Track errors in results
    const errors = context.results.filter(r => r instanceof Error);

    if (errors.length > 0) {
      console.error('Errors in event:', context.event, errors);

      // Send to error tracking service
      ErrorManager.handle(new Error('Event execution errors'), {
        context: context.event,
        data: {errors}
      });
    }
  }
});

Async Events

Support for asynchronous event handlers and promises

Async Handlers

// Async handler
EventManager.on('user:register', async (context) => {
  const {email, password} = context.data;

  // Async operations
  await createUserAccount(email, password);
  await sendWelcomeEmail(email);
  await logActivity('user:registered', email);

  return {success: true, userId: 123};
}, {
  async: true,
  timeout: 10000 // 10 seconds timeout
});

// Emit and wait for results
const results = await EventManager.emit('user:register', {
  email: 'user@example.com',
  password: 'securepass'
});

console.log('Registration results:', results);

Promise Handlers

EventManager.on('data:fetch', (context) => {
  // Return promise
  return fetch(context.data.url)
    .then(res => res.json())
    .then(data => {
      console.log('Data fetched:', data);
      return data;
    });
}, {
  async: true
});

// Emit and wait
const results = await EventManager.emit('data:fetch', {
  url: '/api/users'
});

Timeout Configuration

// Global timeout (default: 5000ms)
await EventManager.init({
  asyncTimeout: 10000
});

// Per-listener timeout
EventManager.on('heavy:operation', heavyHandler, {
  async: true,
  timeout: 30000 // 30 seconds
});

Error Handling in Async

EventManager.on('risky:operation', async (context) => {
  try {
    const result = await riskyAsyncOperation(context.data);
    return result;
  } catch (error) {
    console.error('Operation failed:', error);
    return {error: error.message};
  }
}, {
  async: true,
  onError: (error) => {
    // Custom error handler
    console.error('Async handler error:', error);
    notifyAdmin(error);
  }
});

Parallel Async Execution

// Multiple async handlers execute in parallel
EventManager.on('data:sync', async (context) => {
  await syncToDatabase(context.data);
  return 'db-synced';
}, {async: true});

EventManager.on('data:sync', async (context) => {
  await syncToCache(context.data);
  return 'cache-synced';
}, {async: true});

EventManager.on('data:sync', async (context) => {
  await syncToSearch(context.data);
  return 'search-synced';
}, {async: true});

// Wait for all to complete
const results = await EventManager.emit('data:sync', {
  data: {...}
});

console.log('Sync results:', results);
// ['db-synced', 'cache-synced', 'search-synced']

Context Management

Event context object passed to listeners

Context Structure

{
  event: string,              // Event name
  data: object,               // Event data
  timestamp: number,          // Emit timestamp
  preventDefault: boolean,    // Flag to prevent default behavior
  stopPropagation: boolean,   // Flag to stop propagation
  stopImmediatePropagation: boolean, // Stop immediately
  results: array,             // Results from listeners
  source: any,                // Event source
  path: array,                // Event hierarchy path
  currentPath: string         // Current event path
}

Control Propagation

EventManager.on('form:submit', (context) => {
  // Validate data
  if (!validateForm(context.data)) {
    // Stop propagation to other listeners
    context.stopPropagation = true;
    return {valid: false};
  }

  return {valid: true};
}, {priority: 100});

EventManager.on('form:submit', (context) => {
  // Won't execute if validation fails
  saveForm(context.data);
});

Prevent Default Behavior

EventManager.on('link:click', (context) => {
  // Prevent default behavior
  context.preventDefault = true;

  // Custom behavior
  handleCustomNavigation(context.data.url);
});

Stop Immediate Propagation

EventManager.on('critical:error', (context) => {
  // Stop other listeners immediately
  context.stopImmediatePropagation = true;

  // Handle critical error
  handleCriticalError(context.data);
}, {priority: 1000});

// Other listeners won't execute
EventManager.on('critical:error', (context) => {
  // Won't be called
  logError(context.data);
});

Access Event Hierarchy

EventManager.on('api:user:created', (context) => {
  console.log('Event:', context.event);
  // 'api:user:created'

  console.log('Path:', context.path);
  // ['api:user:created', 'api:user', 'api']

  // Listeners called according to hierarchy
});

// All these listeners will be called:
EventManager.on('api:user:created', handler1);
EventManager.on('api:user', handler2);
EventManager.on('api', handler3);

Return Values

EventManager.on('calculate:sum', (context) => {
  const sum = context.data.numbers.reduce((a, b) => a + b, 0);
  return sum; // Return value stored in context.results
});

EventManager.on('calculate:sum', (context) => {
  const avg = context.data.numbers.reduce((a, b) => a + b, 0) / context.data.numbers.length;
  return avg;
});

// Emit and get results
const results = await EventManager.emit('calculate:sum', {
  numbers: [1, 2, 3, 4, 5]
});

console.log('Results:', results); // [15, 3]

Cleanup and Memory Management

EventManager has automatic cleanup system for memory management

Auto Cleanup

// Enable cleanup (default: enabled)
await EventManager.init({
  cleanup: {
    enabled: true,
    interval: 60000,    // Check every 60 seconds
    maxAge: 1800000,    // Remove listeners older than 30 minutes
    batchSize: 100      // Process 100 items at a time
  }
});

Manual Cleanup

// Remove unused listeners
EventManager.cleanup();

// Clear all events
EventManager.clear();

// Destroy and cleanup everything
EventManager.destroy();

Cleanup Stale Listeners

// Remove old listeners for specific event
EventManager.cleanupStaleListeners('old:event');

Best Practices for Memory Management

1. Use Once for One-time Events

// ❌ Bad: listener not removed
EventManager.on('app:initialized', handler);

// ✅ Good: auto-removed after execution
EventManager.once('app:initialized', handler);

2. Unsubscribe When Done

// ✅ Good: keep unsubscribe function
const unsubscribe = EventManager.on('data:updated', handler);

// When component destroys
componentWillUnmount() {
  unsubscribe();
}

3. Use Groups for Batch Cleanup

// Add to group
EventManager.on('temp:event1', handler1, {group: 'temporary'});
EventManager.on('temp:event2', handler2, {group: 'temporary'});
EventManager.on('temp:event3', handler3, {group: 'temporary'});

// Cleanup entire group
// (No direct API but can off each event)
EventManager.off('temp:event1');
EventManager.off('temp:event2');
EventManager.off('temp:event3');

4. Monitor Listener Count

// Check listener count
const info = EventManager.getEventInfo('user:login');
console.log('Listener count:', info.listenerCount);

// Warn if too many
if (info.listenerCount > 50) {
  console.warn('Too many listeners for event:', 'user:login');
}

Integration with Now.js

EventManager is integrated with Now.js Framework

Auto-Registration

// EventManager auto-registered on load
console.log(Now.managers.has('event')); // true

Access via Now.js

// Method 1: Now.emit() wrapper
Now.emit('user:login', {userId: 123});
// Note: Now.emit() automatically adds timestamp

// Method 2: Via Manager system
const eventManager = Now.getManager('event');
eventManager.emit('user:login', {userId: 123});

// Method 3: Direct access
EventManager.emit('user:login', {userId: 123});

Use with Now.js Components

// In component
const MyComponent = {
  async init() {
    // Register listeners
    this.unsubscribe = EventManager.on('data:updated', (context) => {
      this.handleDataUpdate(context.data);
    });
  },

  handleDataUpdate(data) {
    console.log('Data updated:', data);
    this.render();
  },

  async destroy() {
    // Cleanup on destroy
    if (this.unsubscribe) {
      this.unsubscribe();
    }
  }
};

ComponentManager.define('my-component', MyComponent);

Integration Events

Now.js emits these events via EventManager:

// App lifecycle events
EventManager.on('app:mounted', (context) => {
  console.log('App mounted:', context.data.app);
});

EventManager.on('app:ready', (context) => {
  console.log('App ready');
});

// Error events
EventManager.on('error', (context) => {
  console.error('Error:', context.data.error);
});

// Performance events
EventManager.on('performance:recorded', (context) => {
  console.log('Performance metric:', context.data);
});

Usage Examples

1. User Authentication Flow

// Listen for login success
EventManager.on('auth:login:success', async (context) => {
  const {user, token} = context.data;

  // Save token
  localStorage.setItem('token', token);

  // Load user data
  await loadUserData(user.id);

  // Redirect
  window.location.href = '/dashboard';
}, {
  priority: 100
});

// Analytics tracking
EventManager.on('auth:*', (context) => {
  analytics.track(context.event, context.data);
}, {
  priority: 10
});

// Emit login event
await EventManager.emit('auth:login:success', {
  user: {id: 123, name: 'John'},
  token: 'abc123'
});

2. Form Validation Pipeline

// Validation - highest priority
EventManager.on('form:submit', (context) => {
  const errors = validateForm(context.data.formData);

  if (errors.length > 0) {
    context.stopPropagation = true;
    return {valid: false, errors};
  }

  return {valid: true};
}, {
  priority: 100
});

// Sanitization
EventManager.on('form:submit', (context) => {
  context.data.formData = sanitizeFormData(context.data.formData);
}, {
  priority: 80
});

// Transform
EventManager.on('form:submit', (context) => {
  context.data.formData = transformData(context.data.formData);
}, {
  priority: 60
});

// Save - lowest priority
EventManager.on('form:submit', async (context) => {
  await saveToDatabase(context.data.formData);
  return {saved: true};
}, {
  priority: 10,
  async: true
});

// Submit form
const results = await EventManager.emit('form:submit', {
  formData: {...}
});

3. Real-time Notification System

// Group notification handlers
EventManager.on('notification:info', (context) => {
  showToast(context.data.message, 'info');
}, {group: 'notifications'});

EventManager.on('notification:success', (context) => {
  showToast(context.data.message, 'success');
}, {group: 'notifications'});

EventManager.on('notification:warning', (context) => {
  showToast(context.data.message, 'warning');
}, {group: 'notifications'});

EventManager.on('notification:error', (context) => {
  showToast(context.data.message, 'error');
}, {group: 'notifications'});

// Send notification
await EventManager.emit('notification:success', {
  message: 'Data saved successfully!'
});

// Or emit to all notifications
await EventManager.emitGroup('notifications', {
  message: 'System updated'
});

4. Shopping Cart Events

// Item added
EventManager.on('cart:item-added', async (context) => {
  const {product, quantity} = context.data;

  // Update UI
  updateCartBadge();

  // Save
  await saveCart();

  // Show notification
  await EventManager.emit('notification:success', {
    message: `Added ${product.name} to cart`
  });
});

// Item removed
EventManager.on('cart:item-removed', async (context) => {
  updateCartBadge();
  await saveCart();
});

// Quantity changed
EventManager.on('cart:quantity-changed', async (context) => {
  updateCartTotal();
  await saveCart();
});

// Use pattern for analytics
EventManager.on('cart:*', (context) => {
  analytics.track(context.event, context.data);
});

// Usage
await EventManager.emit('cart:item-added', {
  product: {id: 123, name: 'Product A'},
  quantity: 2
});

5. API Request Lifecycle

// Before request
EventManager.on('api:request:before', (context) => {
  // Show loading
  showLoadingSpinner();

  // Add headers
  context.data.headers = {
    ...context.data.headers,
    'Authorization': `Bearer ${getToken()}`
  };
}, {
  priority: 100
});

// Success
EventManager.on('api:request:success', async (context) => {
  hideLoadingSpinner();

  await EventManager.emit('notification:success', {
    message: 'Request successful'
  });
});

// Error
EventManager.on('api:request:error', async (context) => {
  hideLoadingSpinner();

  await EventManager.emit('notification:error', {
    message: context.data.error.message
  });

  // Retry logic
  if (context.data.error.code === 401) {
    await refreshToken();
    return {retry: true};
  }
});

// After request (always)
EventManager.on('api:request:after', (context) => {
  logApiCall(context.data);
});

6. WebSocket Message Handler

// Connect WebSocket
const ws = new WebSocket('ws://localhost:8080');

ws.onmessage = (event) => {
  const message = JSON.parse(event.data);

  // Forward as events
  EventManager.emit(`ws:${message.type}`, message.data);
};

// Handle message types
EventManager.on('ws:user-online', (context) => {
  updateUserStatus(context.data.userId, 'online');
});

EventManager.on('ws:new-message', (context) => {
  displayMessage(context.data);
  playNotificationSound();
});

EventManager.on('ws:typing', (context) => {
  showTypingIndicator(context.data.userId);
});

// Pattern for logging
EventManager.on('ws:*', (context) => {
  console.log('WebSocket event:', context.event, context.data);
});

7. Theme Switcher

// Listen for theme changes
EventManager.on('theme:changed', (context) => {
  const {theme} = context.data;

  // Update DOM
  document.body.className = theme;

  // Save preference
  localStorage.setItem('theme', theme);

  // Update components
  reloadComponents();
}, {
  once: false
});

// Switch theme
async function switchTheme(theme) {
  await EventManager.emit('theme:changed', {theme});
}

// Toggle dark mode
document.getElementById('dark-mode-toggle').addEventListener('click', () => {
  const currentTheme = document.body.className;
  const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
  switchTheme(newTheme);
});

8. Data Synchronization

// Sync when data changes
EventManager.on('data:changed', async (context) => {
  const {entity, data} = context.data;

  // Sync to multiple destinations in parallel
  await Promise.all([
    syncToLocalStorage(entity, data),
    syncToIndexedDB(entity, data),
    syncToServer(entity, data)
  ]);

  return {synced: true};
}, {
  async: true,
  timeout: 10000
});

// Conflict resolution
EventManager.on('data:conflict', async (context) => {
  const {local, remote} = context.data;

  // Show UI to resolve conflict
  const resolved = await showConflictDialog(local, remote);

  return {resolved};
}, {
  async: true
});

// Usage
await EventManager.emit('data:changed', {
  entity: 'user',
  data: {id: 123, name: 'John Updated'}
});

9. Feature Toggle System

// Define features
const features = new Map();

// Enable feature
EventManager.on('feature:enable', (context) => {
  const {feature} = context.data;
  features.set(feature, true);

  // Reload affected components
  reloadFeatureDependentComponents(feature);

  console.log('Feature enabled:', feature);
});

// Disable feature
EventManager.on('feature:disable', (context) => {
  const {feature} = context.data;
  features.set(feature, false);

  reloadFeatureDependentComponents(feature);

  console.log('Feature disabled:', feature);
});

// Check feature
function isFeatureEnabled(feature) {
  return features.get(feature) || false;
}

// Toggle
async function toggleFeature(feature) {
  const enabled = isFeatureEnabled(feature);
  const event = enabled ? 'feature:disable' : 'feature:enable';

  await EventManager.emit(event, {feature});
}

// Use in code
if (isFeatureEnabled('dark-mode')) {
  enableDarkMode();
}

10. Performance Monitoring

// Track performance metrics
const performanceMetrics = [];

EventManager.use({
  beforeEmit: (context) => {
    context.startTime = performance.now();
  },

  afterEmit: (context) => {
    const duration = performance.now() - context.startTime;

    performanceMetrics.push({
      event: context.event,
      duration,
      timestamp: context.timestamp,
      listenerCount: context.results.length
    });

    // Warn if too slow
    if (duration > 100) {
      console.warn('Slow event:', context.event, `${duration}ms`);
    }
  }
});

// Report metrics
function getPerformanceReport() {
  const avgDuration = performanceMetrics.reduce((sum, m) =>
    sum + m.duration, 0) / performanceMetrics.length;

  const slowEvents = performanceMetrics
    .filter(m => m.duration > 100)
    .sort((a, b) => b.duration - a.duration);

  return {
    totalEvents: performanceMetrics.length,
    averageDuration: avgDuration,
    slowEvents: slowEvents.slice(0, 10)
  };
}

// View report
console.log('Performance Report:', getPerformanceReport());

API Reference

Configuration

{
  asyncTimeout: number,        // Timeout for async handlers (ms) default: 5000
  maxListeners: number,        // Max listeners per event default: 2048
  traceEvents: boolean,        // Keep history for debug default: false
  cleanup: {
    enabled: boolean,          // Enable auto cleanup default: true
    interval: number,          // Check every x ms default: 60000
    maxAge: number,            // Remove listeners older than x ms default: 1800000
    batchSize: number          // Process x items at a time default: 100
  }
}

Methods

Core Methods

// Initialize
init(options?: object): Promise<EventManager>

// Register listener
on(event: string, callback: function, options?: object): function

// Register one-time listener
once(event: string, callback: function, options?: object): function

// Emit event
emit(event: string, data?: object): Promise<array>

// Remove listener(s)
off(event: string, identifier?: string|function): boolean

// Emit to group
emitGroup(group: string, data?: object): Promise<array>

Middleware Methods

// Add middleware
use(middleware: function|object): EventManager

// Run middleware hook
runMiddleware(hook: string, context: object): Promise<boolean>

Pattern Methods

// Create regex pattern from string
createPattern(pattern: string): RegExp

// Add pattern listener
addPatternListener(pattern: string, callback: function, options?: object): function

// Remove pattern listener
removePatternListener(pattern: string, identifier?: string|function): boolean

// Check if string is pattern
isPattern(str: string): boolean

Group Methods

// Add listener to group
addToGroup(group: string, event: string, listener: object): void

// Remove listener from group
removeFromGroup(group: string, event: string, listener: object): void

Cleanup Methods

// Manual cleanup
cleanup(): void

// Cleanup specific event
cleanupStaleListeners(event: string): void

// Clear all
clear(): void

// Destroy
destroy(): void

Info Methods

// Get event info
getEventInfo(event: string): object

// Get debug info
getDebugInfo(): object

Context Object

{
  event: string,                    // Event name
  data: object,                     // Event data
  timestamp: number,                // Emit timestamp
  preventDefault: boolean,          // Prevent default flag
  stopPropagation: boolean,         // Stop propagation flag
  stopImmediatePropagation: boolean,// Stop immediately flag
  results: array,                   // Results from listeners
  source: any,                      // Event source
  path: array,                      // Event hierarchy path
  currentPath: string               // Current path in hierarchy
}

Listener Options

{
  priority: number,      // Priority (higher = first) default: 0
  once: boolean,         // Run once and remove default: false
  async: boolean,        // Async handler default: false
  group: string,         // Group name default: null
  timeout: number,       // Async timeout (ms) default: 5000
  maxRetries: number,    // Max retry attempts default: 1
  onError: function      // Error handler default: null
}

Best Practices

1. ✅ Use Namespaced Events

// ❌ Bad: generic names
EventManager.on('success', handler);
EventManager.on('error', handler);

// ✅ Good: clear names
EventManager.on('auth:login:success', handler);
EventManager.on('api:user:error', handler);

2. ✅ Cleanup Listeners

// ❌ Bad: no cleanup
EventManager.on('data:updated', handler);

// ✅ Good: cleanup when done
const unsubscribe = EventManager.on('data:updated', handler);

// When component destroys
componentWillUnmount() {
  unsubscribe();
}

3. ✅ Use Once for One-time Events

// ❌ Bad: must off manually
EventManager.on('app:ready', handler);
EventManager.off('app:ready', handler);

// ✅ Good: auto-removed
EventManager.once('app:ready', handler);

4. ✅ Use Priority Appropriately

// ❌ Bad: no priority
EventManager.on('form:submit', saveData);
EventManager.on('form:submit', validateData);

// ✅ Good: validate before save
EventManager.on('form:submit', validateData, {priority: 100});
EventManager.on('form:submit', saveData, {priority: 10});

5. ✅ Handle Errors in Async

// ❌ Bad: no error handling
EventManager.on('data:fetch', async (context) => {
  const data = await fetch(context.data.url);
  return data.json();
}, {async: true});

// ✅ Good: with error handling
EventManager.on('data:fetch', async (context) => {
  try {
    const data = await fetch(context.data.url);
    return await data.json();
  } catch (error) {
    console.error('Fetch error:', error);
    return {error: error.message};
  }
}, {async: true});

6. ✅ Use Patterns Wisely

// ❌ Bad: register each event
EventManager.on('notification:info', handler);
EventManager.on('notification:success', handler);
EventManager.on('notification:warning', handler);
EventManager.on('notification:error', handler);

// ✅ Good: use pattern
EventManager.on('notification:*', handler);
// ✅ Good: group related events
EventManager.on('analytics:pageview', trackPageView, {
  group: 'analytics'
});

EventManager.on('analytics:click', trackClick, {
  group: 'analytics'
});

// Emit entire group
await EventManager.emitGroup('analytics', data);

8. ✅ Document Event Names

// ✅ Good: write documentation
/**
 * Events:
 * - user:login - When user logs in successfully
 * - user:logout - When user logs out
 * - user:register - When user registers new account
 *
 * Data structure:
 * {
 *   userId: number,
 *   username: string,
 *   email: string
 * }
 */
const AuthEvents = {
  LOGIN: 'user:login',
  LOGOUT: 'user:logout',
  REGISTER: 'user:register'
};

// Use constants
EventManager.on(AuthEvents.LOGIN, handler);

9. ✅ Test Event Flow

// ✅ Good: write tests
describe('EventManager', () => {
  it('should handle user login', async () => {
    const handler = jest.fn();

    EventManager.on('user:login', handler);

    await EventManager.emit('user:login', {
      userId: 123
    });

    expect(handler).toHaveBeenCalled();
    expect(handler).toHaveBeenCalledWith(
      expect.objectContaining({
        event: 'user:login',
        data: {userId: 123}
      })
    );
  });
});

10. ✅ Monitor Performance

// ✅ Good: track performance
EventManager.use({
  beforeEmit: (context) => {
    context._startTime = performance.now();
  },

  afterEmit: (context) => {
    const duration = performance.now() - context._startTime;

    if (duration > 100) {
      console.warn('Slow event:', context.event, `${duration}ms`);
    }
  }
});

Summary

When to Use EventManager

Use Case Use EventManager? Notes
Communication between Components ✅ Yes Event Bus Pattern
Manage Application Events ✅ Yes Custom events
Decouple Modules ✅ Yes Loose coupling
Pattern Matching ✅ Yes Wildcards support
Event Groups ✅ Yes Built-in support
Middleware ✅ Yes Interceptors
Handle DOM Events ❌ No Use EventSystemManager
Event Delegation ❌ No Use EventSystemManager

Key Features

Feature Supported Notes
Event Bus Publish/Subscribe
Pattern Matching Wildcards, Regex
Event Hierarchy Dot notation
Event Groups Grouped events
Priority Ordered execution
Middleware beforeEmit, afterEmit
Async Support Promises, Async/Await
Context Control stopPropagation, preventDefault
Auto Cleanup Memory management
Debug Mode Event history