or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

async-iteration.mddebug-system.mdevent-emission.mdevent-subscription.mdindex.mdutilities.md
tile.json

debug-system.mddocs/

Debug System

Development and debugging capabilities with configurable logging and meta-event tracking for monitoring emitter behavior.

Capabilities

Global Debug Control

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 debugging

Constructor and Options

Create 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>;
}

Instance Debug Configuration

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
  }
});

Default Debug Logger

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 added
  • unsubscribe - When a listener is removed
  • subscribeAny - When an onAny listener is added
  • unsubscribeAny - When an onAny listener is removed
  • emit - When an event is emitted
  • emitSerial - When an event is emitted serially
  • clear - When listeners are cleared

Custom Debug Loggers

Create 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
  }
});

Class Mixin Support

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
}

Meta-Event Symbols

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());
  }
}

Development Patterns

Conditional Debug Configuration

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');

Debug Middleware

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
  }
}

Testing with Debug

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'));