Development and debugging capabilities with configurable logging and meta-event tracking for monitoring emitter behavior.
Static property to control debugging for all Emittery instances globally.
/**
* Toggle debug mode for all instances
* Default: true if DEBUG environment variable is set to 'emittery' or '*', otherwise false
*/
static isDebugEnabled: boolean;Usage Examples:
import Emittery from "emittery";
// Enable debugging for all instances
Emittery.isDebugEnabled = true;
const emitter1 = new Emittery({debug: {name: 'emitter1'}});
const emitter2 = new Emittery({debug: {name: 'emitter2'}});
// Both emitters will now log debug information
emitter1.on('test', () => {});
emitter2.emit('test');
// Disable debugging globally
Emittery.isDebugEnabled = false;
// Environment variable support
// Set DEBUG=emittery or DEBUG=* before running your app
// This automatically enables debuggingCreate new Emittery instances with debug configuration options.
/**
* Create a new Emittery instance
* @param options - Optional configuration object
*/
constructor(options?: Options<EventData>);
interface Options<EventData> {
readonly debug?: DebugOptions<EventData>;
}Configure debugging options for individual emitter instances.
/**
* Debug configuration options for the emitter instance
*/
interface DebugOptions<EventData> {
/** Name for this emitter instance in debug output */
readonly name: string;
/** Enable debugging for this instance only */
readonly enabled?: boolean;
/** Custom logger function for debug output */
readonly logger?: DebugLogger<EventData, keyof EventData>;
}
type DebugLogger<EventData, Name extends keyof EventData> = (
type: string,
debugName: string,
eventName?: Name,
eventData?: EventData[Name]
) => void;Usage Examples:
import Emittery from "emittery";
// Basic debug configuration
const emitter = new Emittery({
debug: {
name: 'UserService',
enabled: true
}
});
// Custom logger
const emitter2 = new Emittery({
debug: {
name: 'DataProcessor',
enabled: true,
logger: (type, debugName, eventName, eventData) => {
const timestamp = new Date().toISOString();
console.log(`[${timestamp}][${debugName}] ${type}: ${eventName}`, eventData);
}
}
});
// Per-instance control (overrides global setting)
const quietEmitter = new Emittery({
debug: {
name: 'QuietService',
enabled: false // Won't log even if global debug is enabled
}
});The built-in debug logger provides comprehensive event tracking:
// Default logger format:
[HH:MM:SS.mmm][emittery:TYPE][INSTANCE_NAME] Event Name: EVENT_NAME
data: EVENT_DATA_JSON
// Example output:
[16:43:20.417][emittery:subscribe][UserService] Event Name: user-login
data: undefined
[16:43:21.123][emittery:emit][UserService] Event Name: user-login
data: {"id":"123","name":"Alice"}Debug Event Types:
subscribe - When a listener is addedunsubscribe - When a listener is removedsubscribeAny - When an onAny listener is addedunsubscribeAny - When an onAny listener is removedemit - When an event is emittedemitSerial - When an event is emitted seriallyclear - When listeners are clearedCreate specialized debug loggers for different use cases:
import Emittery from "emittery";
// File-based logger
const fileLogger = (type, debugName, eventName, eventData) => {
const entry = {
timestamp: Date.now(),
type,
emitter: debugName,
event: eventName,
data: eventData
};
fs.appendFileSync('debug.log', JSON.stringify(entry) + '\n');
};
const emitter = new Emittery({
debug: {
name: 'FileLoggingEmitter',
enabled: true,
logger: fileLogger
}
});
// Filtered logger (only log certain event types)
const filteredLogger = (type, debugName, eventName, eventData) => {
if (['emit', 'emitSerial'].includes(type)) {
console.log(`π₯ ${debugName} emitted "${eventName}":`, eventData);
}
};
// Metric collection logger
class MetricsLogger {
constructor() {
this.metrics = {
subscriptions: 0,
emissions: 0,
events: new Map()
};
}
logger = (type, debugName, eventName, eventData) => {
switch (type) {
case 'subscribe':
this.metrics.subscriptions++;
break;
case 'emit':
case 'emitSerial':
this.metrics.emissions++;
const count = this.metrics.events.get(eventName) || 0;
this.metrics.events.set(eventName, count + 1);
break;
}
};
getMetrics() {
return {...this.metrics, events: Object.fromEntries(this.metrics.events)};
}
}
const metricsLogger = new MetricsLogger();
const trackedEmitter = new Emittery({
debug: {
name: 'TrackedEmitter',
enabled: true,
logger: metricsLogger.logger
}
});Static method for adding Emittery capabilities to existing classes:
/**
* Decorator for mixing Emittery methods into a class
* @param emitteryPropertyName - Property name for the internal Emittery instance
* @param methodNames - Optional array of method names to mixin, defaults to all methods
* @returns Class decorator function
*/
static mixin(
emitteryPropertyName: string | symbol,
methodNames?: readonly string[]
): <T extends {new (...args: any[]): any}>(klass: T) => T;Usage Examples:
import Emittery from "emittery";
// Basic mixin usage
@Emittery.mixin('events')
class UserService {
constructor() {
// this.events is automatically created as Emittery instance
}
async login(credentials) {
const user = await authenticate(credentials);
await this.emit('user-login', user); // Mixed-in method
return user;
}
}
const service = new UserService();
service.on('user-login', (user) => console.log('User logged in:', user));
// Selective method mixin
@Emittery.mixin('emitter', ['on', 'emit', 'once'])
class DataProcessor {
constructor() {
// Only specified methods are mixed in
}
async process(data) {
await this.emit('processing-start', data);
const result = await processData(data);
await this.emit('processing-complete', result);
return result;
}
}
// Symbol property names for privacy
const EMITTER = Symbol('emitter');
@Emittery.mixin(EMITTER)
class PrivateEmitterService {
// Emitter is accessible via symbol, not easily discoverable
}Static symbols for tracking listener lifecycle events:
/**
* Fires when an event listener is added
* Provides {listener, eventName} as event data
*/
static readonly listenerAdded: unique symbol;
/**
* Fires when an event listener is removed
* Provides {listener, eventName} as event data
*/
static readonly listenerRemoved: unique symbol;Usage Examples:
import Emittery from "emittery";
const emitter = new Emittery();
// Track listener lifecycle
emitter.on(Emittery.listenerAdded, ({listener, eventName}) => {
console.log(`β Listener added for "${eventName}"`);
});
emitter.on(Emittery.listenerRemoved, ({listener, eventName}) => {
console.log(`β Listener removed for "${eventName}"`);
});
// These operations will trigger the meta-events
const handler = () => console.log('test');
emitter.on('test-event', handler); // Triggers listenerAdded
emitter.off('test-event', handler); // Triggers listenerRemoved
// Build listener registry
class ListenerRegistry {
constructor() {
this.emitter = new Emittery();
this.registry = new Map();
this.emitter.on(Emittery.listenerAdded, ({listener, eventName}) => {
if (!this.registry.has(eventName)) {
this.registry.set(eventName, new Set());
}
this.registry.get(eventName).add(listener);
});
this.emitter.on(Emittery.listenerRemoved, ({listener, eventName}) => {
this.registry.get(eventName)?.delete(listener);
});
}
getListeners(eventName) {
return Array.from(this.registry.get(eventName) || []);
}
getAllEventNames() {
return Array.from(this.registry.keys());
}
}import Emittery from "emittery";
// Development vs production configuration
const createEmitter = (name) => {
const debugConfig = process.env.NODE_ENV === 'development'
? {
name,
enabled: true,
logger: (type, debugName, eventName, eventData) => {
if (type === 'emit') {
console.group(`π ${debugName} emitted "${eventName}"`);
console.log('Data:', eventData);
console.groupEnd();
}
}
}
: {name, enabled: false};
return new Emittery({debug: debugConfig});
};
const emitter = createEmitter('ProductionService');import Emittery from "emittery";
class DebugEmitter extends Emittery {
constructor(options = {}) {
const debugOptions = {
...options.debug,
logger: (type, debugName, eventName, eventData) => {
// Custom debug middleware
this.logToConsole(type, debugName, eventName, eventData);
this.sendToMetrics(type, debugName, eventName, eventData);
// Call original logger if provided
if (options.debug?.logger) {
options.debug.logger(type, debugName, eventName, eventData);
}
}
};
super({...options, debug: debugOptions});
}
logToConsole(type, debugName, eventName, eventData) {
console.log(`[${debugName}] ${type}: ${eventName}`, eventData);
}
sendToMetrics(type, debugName, eventName, eventData) {
// Send to monitoring service
}
}import Emittery from "emittery";
// Capture debug output for testing
class TestDebugCapture {
constructor() {
this.logs = [];
}
logger = (type, debugName, eventName, eventData) => {
this.logs.push({type, debugName, eventName, eventData, timestamp: Date.now()});
};
getLogs(filter) {
return filter ? this.logs.filter(filter) : this.logs;
}
clear() {
this.logs = [];
}
}
// Use in tests
const debugCapture = new TestDebugCapture();
const emitter = new Emittery({
debug: {
name: 'TestEmitter',
enabled: true,
logger: debugCapture.logger
}
});
// Perform operations
emitter.on('test', () => {});
emitter.emit('test', 'data');
// Assert debug behavior
const logs = debugCapture.getLogs();
assert(logs.some(log => log.type === 'subscribe'));
assert(logs.some(log => log.type === 'emit'));