Skip to content

Wildcard Event Patterns

The Package Delivery Analogy

Imagine you're managing a distribution center:

  • Specific address: "123 Main Street, Apt 4B" - Only one delivery driver handles this
  • Wildcard address: "All apartments on Main Street" - Multiple drivers can match this pattern

Traditional event buses force you to subscribe to each event individually:

typescript
// ❌ Repetitive: Subscribe to every user event separately
bus.on('user:login', handleUserEvent);
bus.on('user:logout', handleUserEvent);
bus.on('user:update', handleUserEvent);
bus.on('user:delete', handleUserEvent);
// ... 20 more user events

Nexus wildcards let you match patterns:

typescript
// ✅ Efficient: One subscription catches all user events
bus.on('user:*', handleUserEvent);

The Problem: Event Namespace Explosion

In real-world applications, events naturally form hierarchies:

auth:login
auth:logout
auth:refresh
auth:verify
user:create
user:update
user:delete
user:profile:view
user:profile:edit
analytics:pageview
analytics:click
analytics:error

Subscribing to all "auth" events or all "user" events individually creates maintenance nightmares:

typescript
// ❌ Hard to maintain: What if we add auth:reset?
bus.on('auth:login', logAuthEvent);
bus.on('auth:logout', logAuthEvent);
bus.on('auth:refresh', logAuthEvent);
// Forgot to add auth:verify!

The Solution: Pattern Matching with Wildcards

Nexus supports asterisk wildcards (*) that match any text in that position:

typescript
// Match ALL auth events
bus.on('auth:*', (payload) => {
  console.log('Auth event occurred:', payload);
});

// Match ALL user profile events
bus.on('user:profile:*', (payload) => {
  console.log('Profile event:', payload);
});

// Match ANY event (global listener)
bus.on('*', (payload) => {
  console.log('Any event:', payload);
});

How Wildcards Work

When you subscribe with a wildcard pattern, Nexus converts it to a regular expression for efficient matching:

Behind the scenes:

typescript
// Simplified implementation
on(event: string, fn: Listener) {
  if (event.includes('*')) {
    // Convert 'user:*' → /^user:.*$/
    const regex = new RegExp(`^${event.replace(/\*/g, '.*')}$`);
    this.wildcards.add({ regex, fn });
  } else {
    // Exact match
    this.listeners.set(event, fn);
  }
}

emit(event: string, payload: any) {
  // Check exact matches (fast O(1))
  this.listeners.get(event)?.forEach(fn => fn(payload));
  
  // Check wildcards (slower O(n))
  this.wildcards.forEach(({ regex, fn }) => {
    if (regex.test(event)) fn(payload);
  });
}

Performance note: Exact matches are checked first (O(1) Map lookup), then wildcards (O(n) regex tests). Use exact matches when possible for maximum performance.

Basic Example: Logging System

typescript
import { Nexus } from '@caeligo/nexus-orchestrator';

const bus = new Nexus();

// Global logger: Catch EVERYTHING
bus.on('*', (payload) => {
  console.log(`[Global Log]`, payload);
});

// API logger: Only API-related events
bus.on('api:*', (payload) => {
  console.log(`[API Log]`, payload);
});

// Error logger: Only errors
bus.on('*:error', (payload) => {
  console.error(`[Error Log]`, payload);
  sendToErrorTracking(payload);
});

// Emit various events
bus.emit('api:fetch', { url: '/users' });
// Logs:
// [Global Log] { url: '/users' }
// [API Log] { url: '/users' }

bus.emit('auth:error', { message: 'Invalid token' });
// Logs:
// [Global Log] { message: 'Invalid token' }
// [Error Log] { message: 'Invalid token' }

Real-World Example: Multi-Module Analytics

typescript
import { Nexus } from '@caeligo/nexus-orchestrator';

interface AnalyticsEvent {
  module: string;
  action: string;
  timestamp: number;
  metadata?: any;
}

const bus = new Nexus();

// Track all analytics events from any module
bus.on('analytics:*', (event: AnalyticsEvent) => {
  // Send to analytics service
  fetch('https://analytics.example.com/track', {
    method: 'POST',
    body: JSON.stringify(event)
  });
});

// Different modules emit their own analytics
class ShoppingCart {
  addItem(productId: number) {
    // ... add to cart logic
    
    bus.emit('analytics:cart:add', {
      module: 'cart',
      action: 'add_item',
      timestamp: Date.now(),
      metadata: { productId }
    });
  }
  
  checkout() {
    bus.emit('analytics:cart:checkout', {
      module: 'cart',
      action: 'checkout',
      timestamp: Date.now()
    });
  }
}

class UserAuth {
  login(username: string) {
    // ... authentication logic
    
    bus.emit('analytics:auth:login', {
      module: 'auth',
      action: 'login',
      timestamp: Date.now(),
      metadata: { username }
    });
  }
}

// All analytics events are automatically tracked
// without touching individual module code

Real-World Example: Error Handling

typescript
import { Nexus } from '@caeligo/nexus-orchestrator';

const bus = new Nexus({ debug: true });

// Catch all errors from any module
bus.on('*:error', (error) => {
  // Log to error tracking service
  console.error('Error caught by wildcard:', error);
  
  // Show user-friendly notification
  showNotification({
    type: 'error',
    message: error.userMessage || 'Something went wrong'
  });
  
  // Send to monitoring service
  sendToSentry(error);
});

// Different modules can emit their own errors
class APIClient {
  async fetch(endpoint: string) {
    try {
      const response = await fetch(endpoint);
      if (!response.ok) {
        bus.emit('api:error', {
          endpoint,
          status: response.status,
          userMessage: 'Failed to load data'
        });
      }
    } catch (error) {
      bus.emit('api:error', {
        endpoint,
        error: error.message,
        userMessage: 'Network error'
      });
    }
  }
}

class AuthService {
  async login(credentials: any) {
    try {
      // ... authentication logic
    } catch (error) {
      bus.emit('auth:error', {
        type: 'login_failed',
        userMessage: 'Invalid username or password'
      });
    }
  }
}

// One wildcard listener handles all errors

Pattern Matching Rules

Single Wildcard

typescript
// Matches: 'user:login', 'user:logout', 'user:anything'
// Does NOT match: 'user:profile:edit' (extra segment)
bus.on('user:*', handler);

Multiple Segments

typescript
// Matches: 'user:profile:edit', 'user:profile:view'
// Does NOT match: 'user:login' (not enough segments)
bus.on('user:profile:*', handler);

Match Everything

typescript
// Matches: ANY event
bus.on('*', handler);

Multiple Wildcards

typescript
// Matches: 'api:v1:users', 'api:v2:posts', 'api:v1:comments'
bus.on('api:*:*', handler);

Combining Exact and Wildcard Listeners

You can subscribe to both exact events and wildcard patterns simultaneously:

typescript
const bus = new Nexus();

// Specific handler for login
bus.on('auth:login', (payload) => {
  console.log('Login specific logic');
  redirectToHome();
});

// Generic handler for all auth events
bus.on('auth:*', (payload) => {
  console.log('Generic auth logging');
  logToAnalytics(payload);
});

// Emit login event
bus.emit('auth:login', { username: 'alice' });

// Output:
// Login specific logic
// Generic auth logging

Both listeners execute: The exact match runs first, then wildcard matches.

Edge Case: Wildcard Performance

Wildcards are powerful but have performance implications:

typescript
const bus = new Nexus();

// ✅ GOOD: Few wildcards (< 10)
bus.on('user:*', handler);
bus.on('api:*', handler);
bus.on('analytics:*', handler);

// ❌ BAD: Too many wildcards (> 50)
for (let i = 0; i < 100; i++) {
  bus.on(`module${i}:*`, handler); // Slow!
}

Why? Each emitted event must test against all wildcard patterns. If you have 100 wildcards, every emit performs 100 regex tests.

Best practice:

  • Use exact matches when possible
  • Limit wildcards to < 20 patterns
  • Group related events under common prefixes
  • Use specific wildcards (user:profile:*) over general ones (*)

Edge Case: Unsubscribing Wildcards

Wildcard subscriptions can be unsubscribed just like exact matches:

typescript
const bus = new Nexus();

// Subscribe with wildcard
const subscription = bus.on('api:*', handler);

// Later, unsubscribe
subscription.unsubscribe();

// No more api:* events will trigger handler
bus.emit('api:fetch', {}); // Handler NOT called

Comparison with Exact Matches

FeatureExact MatchWildcard Match
PerformanceO(1) - FastO(n) - Slower
FlexibilityOne event onlyMultiple events
Use caseKnown specific eventEvent families
MaintenanceMust update codeAutomatically includes new events

When to Use Wildcards

Use wildcards when:

  • ✅ You have a family of related events (user:*)
  • ✅ You need cross-cutting concerns (logging, analytics, errors)
  • ✅ You want future events to be automatically included
  • ✅ You're building a monitoring or debugging tool

Use exact matches when:

  • ✅ Performance is critical
  • ✅ You need fine-grained control
  • ✅ The event is unique and specific
  • ✅ You have a small, fixed set of events

Next Steps

Now that you understand wildcard patterns, explore how they combine with other features:

Wildcard Checklist

When using wildcards, ask:

  • [ ] Do I really need to match multiple events, or is this one specific event?
  • [ ] Am I using the narrowest possible pattern? (user:profile:* vs user:*)
  • [ ] Do I have too many wildcard listeners? (< 20 recommended)
  • [ ] Could I use a more specific naming convention instead?
  • [ ] Have I documented why this wildcard is needed?

Wildcards are powerful but should be used intentionally, not by default.

Released under the MIT License.