Skip to content

[FEATURE] Pass Event Name to Wildcard Event Handlers #50

@prosdev

Description

@prosdev

Summary

When using wildcard patterns in event listeners (e.g., 'trigger:*', 'user.*'), the handler should receive the actual event name that was fired, not just the payload data.

Current Behavior

const emitter = new Emitter();

// Register wildcard listener
emitter.on('user.*', (data) => {
  console.log(data); // { id: 123 }
  // ❌ No way to know if this was 'user.login', 'user.logout', etc.
});

// Emit events
emitter.emit('user.login', { id: 123 });
emitter.emit('user.logout', { id: 123 });

Problem: The handler receives only data, with no information about which specific event triggered it.

Desired Behavior

const emitter = new Emitter();

// Wildcard listener receives event name as first parameter
emitter.on('user.*', (eventName, data) => {
  console.log(eventName); // 'user.login' or 'user.logout'
  console.log(data);      // { id: 123 }
  
  // Now we can branch logic based on the event
  if (eventName === 'user.login') {
    trackLogin(data);
  } else if (eventName === 'user.logout') {
    trackLogout(data);
  }
});

// Exact match listeners work as before (backward compatible)
emitter.on('user.login', (data) => {
  console.log(data); // { id: 123 }
  // Event name implicit - no need to pass it
});

Use Cases

1. Generic Event Logging

// Log all analytics events with their names
sdk.on('analytics:*', (eventName, data) => {
  logger.log(`Analytics Event: ${eventName}`, data);
});

2. Event Routing/Multiplexing

// Route different trigger types to appropriate handlers
sdk.on('trigger:*', (eventName, data) => {
  const triggerType = eventName.replace('trigger:', '');
  
  // Update context with the specific trigger
  context.triggers[triggerType] = {
    triggered: true,
    timestamp: Date.now(),
    ...data
  };
  
  evaluate(context);
});

3. Debugging/Monitoring

// Monitor all events in development
if (DEBUG) {
  sdk.on('*', (eventName, data) => {
    console.log(`[SDK Event] ${eventName}`, data);
  });
}

4. Metrics Collection

// Track event frequency by type
const eventCounts = {};
sdk.on('*', (eventName, data) => {
  eventCounts[eventName] = (eventCounts[eventName] || 0) + 1;
});

Current Workarounds

Without this feature, developers must:

Workaround 1: Register Multiple Listeners (defeats wildcard purpose)

// Instead of one wildcard, register N listeners
sdk.on('trigger:exitIntent', (data) => handleTrigger('exitIntent', data));
sdk.on('trigger:scrollDepth', (data) => handleTrigger('scrollDepth', data));
sdk.on('trigger:timeDelay', (data) => handleTrigger('timeDelay', data));
// ... repeat for every trigger type

Workaround 2: Duplicate Event Name in Payload (DRY violation)

// Plugins must include the event type in the payload
emit('trigger:exitIntent', { 
  trigger: 'exitIntent', // Redundant - emitter already knows this!
  ...data 
});

// Handler extracts it from payload
sdk.on('trigger:*', (data) => {
  const triggerType = data.trigger; // Had to duplicate it
});

Proposed Implementation

emit(event: string, ...args: any[]): void {
  for (const subscription of this.subscriptions) {
    if (subscription.compiledPattern.test(event)) {
      try {
        // Detect if this is a wildcard pattern
        const isWildcard = subscription.pattern.includes('*');
        
        // Wildcard handlers get event name as first param
        // Exact match handlers get only the data (backward compatible)
        const handlerArgs = isWildcard ? [event, ...args] : args;
        
        subscription.handler(...handlerArgs);
      } catch (err) {
        console.error(`Error in event handler for "${event}":`, err);
      }
    }
  }
}

Backward Compatibility

Fully backward compatible:

  • Exact match listeners ('user.login') continue to receive only data
  • Only wildcard listeners ('user.*', '*') receive the event name
  • Existing code without wildcards is unaffected
  • New wildcard handlers can opt-in by accepting the first parameter

Industry Precedent

This pattern is standard in event systems with wildcards:

  • EventEmitter2/EventEmitter3: on('user.*', (event, data) => {})
  • DOM Events: Event object includes .type property
  • Socket.io: Namespace events include event name
  • Redis Pub/Sub: Pattern subscriptions include channel name

Benefits

  1. Enables powerful patterns - Generic handlers for multiple event types
  2. Reduces boilerplate - One listener instead of N listeners
  3. Improves debugging - Clear visibility into which event fired
  4. Follows standards - Aligns with industry best practices
  5. Backward compatible - Existing code continues to work

Alternative Considered

Status Quo: Keep current behavior and require workarounds.

Rejected because:

  • Defeats the purpose of wildcards (handling multiple events generically)
  • Forces code duplication (register N listeners or include type in payload)
  • Creates friction for a core feature (wildcards are widely used)

Related

This issue was discovered while building the Experience SDK, which uses trigger events extensively:

  • Plugins emit trigger:exitIntent, trigger:scrollDepth, trigger:timeDelay, etc.
  • Runtime needs to know which trigger fired to update context appropriately
  • Current workaround: Register 6 separate listeners instead of 1 wildcard

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions