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:
// ❌ 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 eventsNexus wildcards let you match patterns:
// ✅ 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:errorSubscribing to all "auth" events or all "user" events individually creates maintenance nightmares:
// ❌ 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:
// 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:
// 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
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
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 codeReal-World Example: Error Handling
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 errorsPattern Matching Rules
Single Wildcard
// Matches: 'user:login', 'user:logout', 'user:anything'
// Does NOT match: 'user:profile:edit' (extra segment)
bus.on('user:*', handler);Multiple Segments
// Matches: 'user:profile:edit', 'user:profile:view'
// Does NOT match: 'user:login' (not enough segments)
bus.on('user:profile:*', handler);Match Everything
// Matches: ANY event
bus.on('*', handler);Multiple Wildcards
// 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:
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 loggingBoth listeners execute: The exact match runs first, then wildcard matches.
Edge Case: Wildcard Performance
Wildcards are powerful but have performance implications:
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:
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 calledComparison with Exact Matches
| Feature | Exact Match | Wildcard Match |
|---|---|---|
| Performance | O(1) - Fast | O(n) - Slower |
| Flexibility | One event only | Multiple events |
| Use case | Known specific event | Event families |
| Maintenance | Must update code | Automatically 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:
- RPC Pattern - Request/Reply doesn't support wildcards
- Priority Lanes - Wildcards respect priority settings
- Cursors & State - Cursors work with exact events only
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:*vsuser:*) - [ ] 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.
