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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions