npm package: @onurgvnc/tx2
Description: tx2
Latest version: 1.0.1

# TX2

original project: https://github.com/pm2/tx2 | https://npmjs.com/package/tx2

## 📋 Table of Contents

- [Introduction](#introduction)
- [Installation](#installation)
- [Core Concepts](#core-concepts)
- [Features](#features)
  - [1. Events](#1-events)
  - [2. Actions](#2-actions)
  - [3. Metrics](#3-metrics)
  - [4. Issues](#4-issues)
- [API Reference](#api-reference)
- [Advanced Usage](#advanced-usage)
- [Technical Details](#technical-details)
- [Troubleshooting](#troubleshooting)
- [Performance Considerations](#performance-considerations)

## Introduction

TX2 is a comprehensive monitoring library that bridges your Node.js applications with the PM2 process manager. It provides real-time metrics, event tracking, remote actions, and error reporting capabilities through PM2's IPC (Inter-Process Communication) mechanism.

### What Does TX2 Do?

- **📊 Metrics Collection**: Collects and reports CPU, memory, request rate, and custom metrics
- **📬 Event Notifications**: Sends important application events to PM2 dashboard
- **⚡ Action Handling**: Receives and executes commands from PM2 dashboard
- **🐛 Error Reporting**: Captures and reports errors with full stack traces
- **📈 Performance Monitoring**: Tracks histograms, counters, meters, and custom measurements

### Architecture Overview

TX2 operates through a bidirectional communication channel with PM2:

```
Your Application <--[IPC via process.send/on]--> PM2 Daemon <--[API]--> PM2 Web Dashboard
        |                                            |                         |
    TX2 Library                                 PM2 God Daemon          Monitoring Interface
```

The communication flow:
1. **Metrics**: Automatically sent every 990ms via `axm:monitor` messages
2. **Events**: Instantly transmitted via `human:event` messages
3. **Actions**: Registered via `axm:action` and responded via `axm:reply`
4. **Errors**: Immediately reported via `process:exception` messages

## Installation

### For TypeScript Projects

```bash
# Copy the tx2.ts file to your project
cp tx2.ts your-project/src/

# Install required dependencies
npm install fast-safe-stringify
npm install --save-dev @types/node
```

### For JavaScript Projects

```bash
# Install the original tx2 package
npm install tx2

# Or use the TypeScript version and compile it
npx tsc tx2.ts --target ES2020 --module commonjs
```

### Basic Setup

```typescript
import tx2 from './tx2'

// Initialize monitoring when application starts
tx2.event('app:ready', {
  version: process.env.npm_package_version,
  node: process.version,
  pid: process.pid
})

// Graceful shutdown handling
process.on('SIGINT', () => {
  tx2.event('app:shutdown', { reason: 'SIGINT' })
  process.exit(0)
})
```

## Core Concepts

### Inter-Process Communication (IPC)

TX2 leverages Node.js's built-in IPC capabilities to communicate with PM2:

- **Outbound Messages**: Uses `process.send()` to transmit data to PM2
- **Inbound Messages**: Listens via `process.on('message')` for PM2 commands
- **Message Queue**: No internal buffering - messages are sent immediately
- **Connection Detection**: Automatically detects if running under PM2 via `process.send` availability

### Message Protocol

TX2 implements PM2's monitoring protocol with 5 distinct message types:

| Type                | Direction | Purpose                  | Frequency              |
| ------------------- | --------- | ------------------------ | ---------------------- |
| `axm:monitor`       | App → PM2 | Metric data transmission | Every 990ms            |
| `human:event`       | App → PM2 | Event notifications      | On-demand              |
| `axm:action`        | App → PM2 | Action registration      | Once per action        |
| `axm:reply`         | App → PM2 | Action response          | After action execution |
| `process:exception` | App → PM2 | Error reporting          | On error               |

### Data Serialization

TX2 handles complex data serialization challenges:

- **Circular References**: Safely handled via `fast-safe-stringify`
- **Error Objects**: Converted to plain objects preserving all properties
- **Large Payloads**: No size limits, but large data may impact performance
- **Special Properties**: Handles `__name`, `_length`, and other meta properties

## Features

### 1. Events

Events allow you to track important occurrences in your application lifecycle. They appear in PM2 logs and can trigger alerts.

#### Basic Event Usage

```typescript
// Simple event notification
tx2.event('server:started')

// Event with string data
tx2.event('cache:cleared', 'Redis cache has been flushed')

// Event with numeric data
tx2.event('payment:processed', 150.99)

// Event with boolean data
tx2.event('maintenance:mode', true)
```

#### Structured Event Data

```typescript
// Event with object payload (recommended approach)
tx2.event('user:registered', {
  userId: 'usr_123456',
  email: 'user@example.com',
  plan: 'premium',
  referralSource: 'google',
  timestamp: Date.now(),
  metadata: {
    ip: '192.168.1.1',
    userAgent: 'Mozilla/5.0...',
    country: 'US'
  }
})

// Complex nested event structure
tx2.event('order:completed', {
  orderId: 'ORD-2024-001',
  customer: {
    id: 789,
    name: 'John Doe',
    email: 'john@example.com',
    loyaltyPoints: 250,
    tier: 'gold'
  },
  items: [
    {
      sku: 'PROD-1',
      name: 'Premium Widget',
      quantity: 2,
      price: 29.99,
      discount: 5.00
    },
    {
      sku: 'PROD-2',
      name: 'Standard Gadget',
      quantity: 1,
      price: 49.99,
      discount: 0
    }
  ],
  totals: {
    subtotal: 109.97,
    discount: 10.00,
    tax: 9.90,
    shipping: 5.00,
    total: 114.87
  },
  payment: {
    method: 'credit_card',
    last4: '1234',
    processor: 'stripe',
    transactionId: 'txn_abc123'
  },
  fulfillment: {
    method: 'standard',
    estimatedDelivery: '2024-03-15',
    warehouse: 'EAST-1'
  }
})
```

#### Event Naming Conventions

```typescript
// ✅ Good - Clear namespace:action format
tx2.event('database:connected')
tx2.event('api:request:started')
tx2.event('cache:miss')
tx2.event('auth:login:success')
tx2.event('payment:subscription:renewed')

// ❌ Bad - Ambiguous or unclear names
tx2.event('event1')
tx2.event('something_happened')
tx2.event('data')
tx2.event('update')
```

#### Event Patterns for Different Scenarios

```typescript
// Lifecycle Events
tx2.event('app:initializing', { stage: 'database' })
tx2.event('app:ready', { startupTime: Date.now() - startTime })
tx2.event('app:shutdown', { reason: 'SIGTERM', cleanup: true })

// Performance Events
tx2.event('performance:slow_query', {
  query: 'SELECT * FROM users...',
  duration: 5234,
  threshold: 1000
})

// Security Events
tx2.event('security:login:failed', {
  username: 'admin',
  ip: req.ip,
  attempts: failedAttempts,
  blocked: failedAttempts > 5
})

// Business Events
tx2.event('business:revenue:milestone', {
  type: 'daily',
  amount: 10000,
  currency: 'USD',
  transactions: 156
})
```

### 2. Actions

Actions enable remote control of your application through the PM2 dashboard. They're perfect for administrative tasks, debugging, and operational controls.

#### Simple Actions

```typescript
// Basic action without parameters
tx2.action('clear_cache', (reply) => {
  cache.flush()
  reply({
    success: true,
    message: 'Cache cleared successfully',
    timestamp: new Date().toISOString()
  })
})

// Action with error handling
tx2.action('reload_config', (reply) => {
  try {
    const oldConfig = { ...config }
    config.reload()

    reply({
      success: true,
      changes: diffConfigs(oldConfig, config),
      version: config.version
    })
  } catch (error) {
    reply({
      success: false,
      error: error.message,
      stack: process.env.NODE_ENV === 'development' ? error.stack : undefined
    })
  }
})
```

#### Parameterized Actions

```typescript
// Action with typed parameters
interface DatabaseBackupOptions {
  format: 'sql' | 'json' | 'csv'
  compress: boolean
  tables?: string[]
}

tx2.action('backup_database',
  { format: 'sql', compress: true },
  async (options: DatabaseBackupOptions, reply) => {
    try {
      const backup = await db.backup({
        format: options.format,
        compress: options.compress,
        tables: options.tables || 'all'
      })

      reply({
        success: true,
        filename: backup.filename,
        size: backup.size,
        tables: backup.tables.length,
        compressed: options.compress,
        location: backup.path
      })
    } catch (error) {
      reply({
        success: false,
        error: error.message,
        code: error.code
      })
    }
  }
)
```

#### Asynchronous Actions

```typescript
// Long-running async action
tx2.action('generate_report', async (reply) => {
  const reportId = uuid()

  // Immediate acknowledgment
  reply({
    success: true,
    reportId,
    status: 'processing',
    message: 'Report generation started'
  })

  // Continue processing in background
  generateReportAsync(reportId).catch(error => {
    tx2.issue({
      type: 'report_generation_failed',
      reportId,
      error: error.message
    })
  })
})

// Action with progress updates
tx2.action('migrate_data', async (reply) => {
  const stages = ['backup', 'transform', 'validate', 'apply', 'verify']
  const results = {}

  for (const stage of stages) {
    try {
      tx2.event('migration:stage', { stage, status: 'started' })
      results[stage] = await runMigrationStage(stage)
      tx2.event('migration:stage', { stage, status: 'completed' })
    } catch (error) {
      reply({
        success: false,
        failedStage: stage,
        completedStages: Object.keys(results),
        error: error.message
      })
      return
    }
  }

  reply({
    success: true,
    stages: results,
    duration: Date.now() - startTime
  })
})
```

#### Common Action Patterns

```typescript
// 1. Toggle Pattern
let maintenanceMode = false

tx2.action('toggle_maintenance', (reply) => {
  maintenanceMode = !maintenanceMode

  if (maintenanceMode) {
    app.disable('x-powered-by')
    app.use((req, res) => res.status(503).send('Maintenance in progress'))
  } else {
    app.enable('x-powered-by')
  }

  reply({
    enabled: maintenanceMode,
    message: `Maintenance mode ${maintenanceMode ? 'enabled' : 'disabled'}`,
    affectedServices: ['api', 'websocket']
  })
})

// 2. Health Check Pattern
tx2.action('health_check', async (reply) => {
  const checks = await Promise.allSettled([
    db.ping(),
    redis.ping(),
    elasticsearch.ping(),
    messageQueue.health()
  ])

  reply({
    healthy: checks.every(c => c.status === 'fulfilled'),
    timestamp: new Date().toISOString(),
    services: {
      database: checks[0].status === 'fulfilled',
      redis: checks[1].status === 'fulfilled',
      elasticsearch: checks[2].status === 'fulfilled',
      queue: checks[3].status === 'fulfilled'
    },
    system: {
      memory: process.memoryUsage(),
      uptime: process.uptime(),
      cpu: process.cpuUsage()
    }
  })
})

// 3. Diagnostic Pattern
tx2.action('diagnose', (reply) => {
  const diagnostics = {
    environment: {
      node: process.version,
      platform: process.platform,
      arch: process.arch,
      pid: process.pid,
      ppid: process.ppid
    },
    memory: {
      ...process.memoryUsage(),
      limit: v8.getHeapStatistics().heap_size_limit
    },
    connections: {
      http: server.connections,
      websocket: wss.clients.size,
      database: db.pool.totalCount,
      redis: redis.client.connections
    },
    queues: {
      pending: queue.pending(),
      active: queue.active(),
      completed: queue.completed(),
      failed: queue.failed()
    }
  }

  reply(diagnostics)
})

// 4. Reset Pattern
tx2.action('reset_all_counters', (reply) => {
  const counters = [
    'requests',
    'errors',
    'cache_hits',
    'cache_misses'
  ]

  counters.forEach(name => {
    if (tx2.metricExists(name)) {
      tx2.counter(name).reset()
    }
  })

  reply({
    reset: true,
    counters,
    timestamp: Date.now()
  })
})
```

### 3. Metrics

Metrics provide continuous monitoring of your application's performance. TX2 supports multiple metric types, each optimized for different measurement scenarios.

#### 3.1. Simple Metric

Tracks a single value or function result. Ideal for gauges and instantaneous measurements.

```typescript
// Static value metric
const dbConnections = tx2.metric({
  name: 'db_connections',
  value: 5,
  unit: 'connections'
})

// Update the value
dbConnections.set(10)

// Dynamic value with function
tx2.metric({
  name: 'memory_usage',
  unit: 'MB',
  value: () => {
    const used = process.memoryUsage().heapUsed
    return Math.round(used / 1024 / 1024)
  }
})

// Complex computed metric
let cache = new Map()
tx2.metric({
  name: 'cache_efficiency',
  unit: '%',
  value: () => {
    if (cacheRequests === 0) return 0
    return Math.round((cacheHits / cacheRequests) * 100)
  }
})

// Boolean state metric
tx2.metric({
  name: 'database_connected',
  value: () => db.isConnected() ? 1 : 0
})

// String value metric
tx2.metric({
  name: 'app_version',
  value: () => `v${package.version}-${process.env.NODE_ENV}`
})

// Real-time calculation
const startTime = Date.now()
tx2.metric({
  name: 'uptime_hours',
  unit: 'hours',
  value: () => {
    const uptime = Date.now() - startTime
    return Math.floor(uptime / 1000 / 60 / 60)
  }
})
```

#### 3.2. Counter

Accumulative metrics that can be incremented, decremented, and reset. Perfect for counting events and tracking totals.

```typescript
// Basic counter
const requestCounter = tx2.counter('total_requests')

// Middleware integration
app.use((req, res, next) => {
  requestCounter.inc()

  res.on('finish', () => {
    if (res.statusCode >= 400) {
      errorCounter.inc()
    }
    if (res.statusCode >= 500) {
      criticalErrorCounter.inc()
    }
  })

  next()
})

// Counter with units
const dataTransferred = tx2.counter({
  name: 'data_transferred',
  unit: 'MB'
})

// Batch operations
stream.on('data', (chunk) => {
  dataTransferred.inc(chunk.length / 1024 / 1024)
})

// WebSocket connection tracking
const wsConnections = tx2.counter({
  name: 'websocket_connections',
  unit: 'connections'
})

io.on('connection', (socket) => {
  wsConnections.inc()

  socket.on('disconnect', () => {
    wsConnections.dec()
  })

  socket.on('error', () => {
    wsErrors.inc()
    wsConnections.dec()
  })
})

// Advanced counter usage
class SessionManager {
  private activeSessions = tx2.counter('active_sessions')
  private totalSessions = tx2.counter('total_sessions')

  createSession(userId: string): string {
    const sessionId = generateSessionId()
    this.activeSessions.inc()
    this.totalSessions.inc()
    return sessionId
  }

  destroySession(sessionId: string): void {
    this.activeSessions.dec()
  }

  resetDaily(): void {
    // Keep total, reset active
    this.activeSessions.reset()
  }
}
```

#### 3.3. Histogram

Statistical analysis of values over time. Provides percentiles, averages, and distribution metrics.

```typescript
// Response time tracking
const responseTime = tx2.histogram({
  name: 'response_time',
  measurement: 'mean',
  unit: 'ms'
})

app.use((req, res, next) => {
  const start = Date.now()

  // Monkey-patch res.end
  const originalEnd = res.end
  res.end = function(...args) {
    const duration = Date.now() - start
    responseTime.update(duration)

    // Log slow requests
    if (duration > 1000) {
      tx2.event('slow_request', {
        url: req.url,
        method: req.method,
        duration
      })
    }

    return originalEnd.apply(this, args)
  }

  next()
})
```

##### Available Measurements

```typescript
// Minimum value tracking
const minLatency = tx2.histogram({
  name: 'min_latency',
  measurement: 'min',
  unit: 'ms'
})

// Maximum value tracking
const maxMemory = tx2.histogram({
  name: 'peak_memory',
  measurement: 'max',
  unit: 'MB'
})

// Average calculation
const avgProcessingTime = tx2.histogram({
  name: 'avg_processing',
  measurement: 'mean',
  unit: 'ms'
})

// Median (50th percentile)
const medianQueryTime = tx2.histogram({
  name: 'median_query',
  measurement: 'median',
  unit: 'ms'
})

// 95th percentile (95% of values are below this)
const p95ResponseTime = tx2.histogram({
  name: 'p95_response',
  measurement: 'p95',
  unit: 'ms'
})

// 99th percentile (99% of values are below this)
const p99ResponseTime = tx2.histogram({
  name: 'p99_response',
  measurement: 'p99',
  unit: 'ms'
})

// Variance (spread of data)
const loadVariance = tx2.histogram({
  name: 'load_variance',
  measurement: 'variance'
})

// Standard deviation
const jitter = tx2.histogram({
  name: 'network_jitter',
  measurement: 'stddev',
  unit: 'ms'
})

// Exponential Moving Average (trend indicator)
const trendingValue = tx2.histogram({
  name: 'trending_requests',
  measurement: 'ema'
})

// Sum of all values
const totalDataProcessed = tx2.histogram({
  name: 'total_data',
  measurement: 'sum',
  unit: 'bytes'
})

// Count of measurements
const apiCallCount = tx2.histogram({
  name: 'api_calls',
  measurement: 'count'
})
```

##### Advanced Histogram Usage

```typescript
// Database query analyzer
const queryAnalyzer = tx2.histogram({
  name: 'db_query_time',
  measurement: 'p99',
  unit: 'ms'
})

// Hook into database events
db.on('query', (query) => {
  const start = Date.now()

  query.on('end', () => {
    const duration = Date.now() - start
    queryAnalyzer.update(duration)

    // Track slow queries separately
    if (duration > 100) {
      slowQueryHistogram.update(duration)
    }
  })
})

// Full statistics retrieval
const apiStats = tx2.histogram({
  name: 'api_latency',
  measurement: 'mean',
  unit: 'ms'
})

// Get comprehensive statistics
setInterval(() => {
  const stats = apiStats.fullResults()
  console.log('API Performance Stats:', {
    min: `${stats.min}ms`,
    max: `${stats.max}ms`,
    average: `${stats.mean}ms`,
    median: `${stats.median}ms`,
    p95: `${stats.p95}ms`,
    p99: `${stats.p99}ms`,
    stdDev: `${stats.stddev}ms`,
    total: stats.count
  })

  // Alert on degraded performance
  if (stats.p95 > 500) {
    tx2.event('performance:degraded', {
      p95: stats.p95,
      threshold: 500
    })
  }
}, 60000)

// Custom percentile analysis
const customPercentiles = apiStats.percentiles([0.1, 0.25, 0.5, 0.75, 0.9, 0.99])
// Returns: { 0.1: 10, 0.25: 25, 0.5: 50, 0.75: 75, 0.9: 90, 0.99: 99 }

// Memory-efficient histogram with periodic reset
let histogramUpdateCount = 0
const efficientHistogram = tx2.histogram({
  name: 'efficient_metric',
  measurement: 'mean'
})

function updateHistogram(value: number) {
  efficientHistogram.update(value)
  histogramUpdateCount++

  // Reset every 10000 measurements to prevent memory growth
  if (histogramUpdateCount >= 10000) {
    efficientHistogram.reset()
    histogramUpdateCount = 0
  }
}
```

#### 3.4. Meter

Measures the rate of events over time using an Exponentially Weighted Moving Average (EWMA) algorithm.

```typescript
// Simple request rate meter
const requestsPerSec = tx2.meter({
  name: 'req/sec'
})

app.use((req, res, next) => {
  requestsPerSec.mark()
  next()
})

// Bandwidth meter
const bytesPerSec = tx2.meter({
  name: 'bandwidth',
  unit: 'MB/sec'
})

stream.on('data', (chunk) => {
  // Mark with the number of bytes
  bytesPerSec.mark(chunk.length / 1024 / 1024)
})

// Customized meter with different time windows
const customMeter = tx2.meter({
  name: 'events/sec',
  seconds: 5,      // Calculate rate every 5 seconds
  timeframe: 60    // Use 60-second time window for averaging
})

// Message processing rate
messageQueue.on('message', (msg) => {
  customMeter.mark()

  // Handle batch messages
  if (msg.batch) {
    customMeter.mark(msg.batch.length)
  }
})

// Transaction rate tracking
const transactionRate = tx2.meter({
  name: 'transactions/min',
  seconds: 60,
  unit: 'tx/min'
})

async function processTransaction(tx) {
  transactionRate.mark()

  if (tx.amount > 10000) {
    largeTransactionRate.mark()
  }

  await db.saveTransaction(tx)
}
```

##### Meter vs Counter Comparison

```typescript
// Counter: Cumulative total
const totalRequests = tx2.counter('total_requests')
totalRequests.inc()  // 1, 2, 3, 4, 5...

// Meter: Rate per second
const requestRate = tx2.meter({ name: 'req/sec' })
requestRate.mark()  // 10 req/s, 15 req/s, 8 req/s...

// Using both together for comprehensive monitoring
app.use((req, res, next) => {
  totalRequests.inc()     // Track total count
  requestRate.mark()      // Track current rate

  if (req.path.startsWith('/api')) {
    apiRequests.inc()
    apiRate.mark()
  }

  next()
})
```

#### 3.5. Transpose

Exposes any dynamic value as a metric. Perfect for custom calculations and external service monitoring.

```typescript
// Simple value transposition
tx2.transpose('random_value', () => {
  return Math.random() * 100
})

// Object notation
tx2.transpose({
  name: 'complex_calculation',
  data: () => {
    const result = performComplexCalculation()
    return Math.round(result * 100) / 100
  }
})

// External service monitoring
tx2.transpose('redis_memory', async () => {
  try {
    const info = await redis.info('memory')
    return parseInt(info.used_memory) / 1024 / 1024 // MB
  } catch {
    return -1 // Indicate error
  }
})

// Composite metrics
tx2.transpose('efficiency_score', () => {
  const hitRate = (cacheHits / (cacheHits + cacheMisses)) * 100
  const errorRate = (errors / requests) * 100
  const efficiency = hitRate * (1 - errorRate / 100)
  return Math.round(efficiency)
})

// System status aggregation
tx2.transpose('system_health', () => {
  const checks = {
    database: db.isConnected(),
    redis: redis.status === 'ready',
    queue: queue.isRunning(),
    disk: diskUsage < 90
  }

  const healthScore = Object.values(checks).filter(Boolean).length
  return healthScore / Object.keys(checks).length * 100
})
```

### 4. Issues

Comprehensive error tracking and reporting system that captures errors with full context.

```typescript
// String error
tx2.issue('Critical: Database connection lost')

// Error object with stack trace
try {
  await riskyDatabaseOperation()
} catch (error) {
  tx2.issue(error)
}

// Enhanced error object
class CustomError extends Error {
  constructor(message, code, statusCode) {
    super(message)
    this.code = code
    this.statusCode = statusCode
    this.timestamp = new Date().toISOString()
  }
}

try {
  await apiCall()
} catch (error) {
  const enhancedError = new CustomError(
    error.message,
    'API_ERROR',
    500
  )
  enhancedError.endpoint = '/api/users'
  enhancedError.userId = req.user?.id

  tx2.issue(enhancedError)
}

// Structured error reporting
tx2.issue({
  type: 'payment_failure',
  message: 'Payment processing failed',
  code: 'PAYMENT_DECLINED',
  severity: 'critical',
  user: {
    id: user.id,
    email: user.email
  },
  payment: {
    amount: 99.99,
    currency: 'USD',
    method: 'credit_card',
    processor: 'stripe'
  },
  error: {
    code: stripeError.code,
    message: stripeError.message,
    decline_code: stripeError.decline_code
  },
  timestamp: new Date().toISOString(),
  context: {
    ip: req.ip,
    userAgent: req.get('user-agent'),
    sessionId: req.sessionID
  }
})
```

#### Global Error Handlers

```typescript
// Uncaught exception handler
process.on('uncaughtException', (error) => {
  tx2.issue({
    type: 'uncaughtException',
    error: {
      message: error.message,
      stack: error.stack,
      name: error.name
    },
    fatal: true,
    pid: process.pid
  })

  // Graceful shutdown
  setTimeout(() => {
    process.exit(1)
  }, 1000)
})

// Unhandled promise rejection
process.on('unhandledRejection', (reason, promise) => {
  tx2.issue({
    type: 'unhandledRejection',
    reason: reason instanceof Error ? {
      message: reason.message,
      stack: reason.stack
    } : reason,
    promise: String(promise)
  })
})

// Express error middleware
app.use((err, req, res, next) => {
  const errorReport = {
    message: err.message,
    stack: err.stack,
    status: err.status || 500,
    request: {
      url: req.url,
      method: req.method,
      headers: req.headers,
      body: req.body,
      query: req.query,
      params: req.params
    },
    user: req.user ? {
      id: req.user.id,
      email: req.user.email
    } : null,
    session: req.sessionID,
    ip: req.ip,
    timestamp: new Date().toISOString()
  }

  tx2.issue(errorReport)

  res.status(errorReport.status).json({
    error: process.env.NODE_ENV === 'production'
      ? 'Internal Server Error'
      : errorReport.message
  })
})
```

## API Reference

### TX2 Class

```typescript
class TX2 extends EventEmitter {
  // Singleton instance
  constructor()

  // Event Methods
  event(name: string, data?: any): void

  // Action Methods
  action(name: string, callback: (reply: Function) => void): void
  action<T>(name: string, opts: T, callback: (opts: T, reply: Function) => void): void

  // Issue Methods
  issue(err: string | Error | object): ErrorObject
  _interpretError(err: any): ErrorObject

  // Metric Methods
  metric<T>(name: string, cb: () => T): Metric<T>
  metric(name: string, cb: number): Metric<number>
  metric<T>(name: string, unit: string, cb: () => T): Metric<T>
  metric(name: string, unit: string, cb: number): Metric<number>
  metric<T>(options: MetricOptions<T>): Metric<T>

  counter(name: string): Counter
  counter(options: CounterOptions): Counter

  histogram(options: HistogramOptions): Histogram

  meter(options: MeterOptions): Meter

  transpose(name: string, reporter: () => any): void
  transpose(options: TransposeOptions): void

  // Utility Methods
  metricExists(name: string): boolean
  prepareData(): Record<string, MetricData>
  send(args: SendArgs): void

  // EventEmitter Methods
  on(event: 'data', listener: (msg: TX2Message) => void): this
  once(event: 'data', listener: (msg: TX2Message) => void): this
  emit(event: 'data', msg: TX2Message): boolean
}
```

### Type Definitions

```typescript
// Core Interfaces
interface MetricOptions<T = any> {
  name: string
  value?: T | (() => T)
  val?: T | (() => T)  // Alias for value
  unit?: string
}

interface CounterOptions {
  name: string
  unit?: string
}

interface HistogramOptions {
  name: string
  measurement?: 'min' | 'max' | 'sum' | 'count' | 'mean' |
                'variance' | 'stddev' | 'median' |
                'p75' | 'p95' | 'p99' | 'p999' | 'ema'
  unit?: string
  value?: any | (() => any)
  val?: any | (() => any)
}

interface MeterOptions {
  name: string
  seconds?: number      // Tick interval in seconds (default: 1)
  timeframe?: number    // Time window in seconds (default: 60)
  unit?: string
}

interface TransposeOptions {
  name: string
  data: () => any
}

interface ErrorObject {
  name?: string
  message?: string
  stack?: string
  stackframes?: Array<{
    file_name: string
    line_number: number
  }>
  [key: string]: any
}

// Metric Types
interface Metric<T = any> {
  val: () => T
  set: (data: T) => void
}

interface Counter {
  val: () => number
  inc: (amount?: number) => void
  dec: (amount?: number) => void
  reset: (count?: number) => void
}

interface Histogram {
  update: (value: number) => void
  percentiles: (percentiles: number[]) => { [key: number]: number | null }
  reset: () => void
  val: () => number
  getMin: () => number | null
  getMax: () => number | null
  getSum: () => number
  getCount: () => number
  getEma: () => number
  fullResults: () => HistogramResults
}

interface HistogramResults {
  min: number | null
  max: number | null
  sum: number
  variance: number | null
  mean: number
  stddev: number | null
  count: number
  median: number | null
  p75: number | null
  p95: number | null
  p99: number | null
  p999: number | null
  ema: number
}

interface Meter {
  mark: (n?: number) => void
  val: () => number
}

// Message Types
type TX2EventType =
  | 'axm:monitor'
  | 'human:event'
  | 'axm:action'
  | 'axm:reply'
  | 'process:exception'

interface TX2Message {
  type: TX2EventType
  data: any
}

interface SendArgs {
  type: TX2EventType
  data: any
}
```

## Advanced Usage

### Building a Complete Monitoring Dashboard

```typescript
// Comprehensive monitoring setup
class ApplicationMonitor {
  private metrics = {
    // Counters
    requests: tx2.counter('http_requests_total'),
    errors: tx2.counter('errors_total'),
    cache_hits: tx2.counter('cache_hits'),
    cache_misses: tx2.counter('cache_misses'),

    // Histograms
    responseTime: tx2.histogram({
      name: 'http_response_time',
      measurement: 'p95',
      unit: 'ms'
    }),
    dbQueryTime: tx2.histogram({
      name: 'db_query_time',
      measurement: 'p99',
      unit: 'ms'
    }),

    // Meters
    throughput: tx2.meter({
      name: 'throughput',
      unit: 'req/sec'
    }),
    errorRate: tx2.meter({
      name: 'error_rate',
      unit: 'errors/sec'
    }),

    // Metrics
    connections: tx2.metric({
      name: 'active_connections',
      value: () => this.getActiveConnections()
    }),
    memoryUsage: tx2.metric({
      name: 'memory_usage',
      unit: 'MB',
      value: () => Math.round(process.memoryUsage().heapUsed / 1024 / 1024)
    })
  }

  private getActiveConnections(): number {
    return (
      httpServer.connections +
      wsServer.clients.size +
      db.pool.activeCount
    )
  }

  recordRequest(req: Request, res: Response, duration: number) {
    this.metrics.requests.inc()
    this.metrics.throughput.mark()
    this.metrics.responseTime.update(duration)

    if (res.statusCode >= 400) {
      this.metrics.errors.inc()
      this.metrics.errorRate.mark()

      if (res.statusCode >= 500) {
        tx2.event('http:error:5xx', {
          url: req.url,
          method: req.method,
          status: res.statusCode,
          duration
        })
      }
    }

    // Track slow requests
    if (duration > 1000) {
      tx2.event('http:slow_request', {
        url: req.url,
        duration,
        threshold: 1000
      })
    }
  }

  recordCacheOperation(hit: boolean) {
    if (hit) {
      this.metrics.cache_hits.inc()
    } else {
      this.metrics.cache_misses.inc()
    }
  }

  recordDatabaseQuery(query: string, duration: number) {
    this.metrics.dbQueryTime.update(duration)

    if (duration > 100) {
      tx2.event('db:slow_query', {
        query: query.substring(0, 200),
        duration
      })
    }
  }
}

const monitor = new ApplicationMonitor()

// Express middleware integration
app.use((req, res, next) => {
  const start = Date.now()

  res.on('finish', () => {
    monitor.recordRequest(req, res, Date.now() - start)
  })

  next()
})

// Database hook
db.on('query', (query) => {
  const start = Date.now()
  query.on('end', () => {
    monitor.recordDatabaseQuery(query.sql, Date.now() - start)
  })
})

// Cache wrapper
class CacheWrapper {
  async get(key: string) {
    const value = await cache.get(key)
    monitor.recordCacheOperation(value !== null)
    return value
  }
}
```

### Circuit Breaker Implementation

```typescript
class CircuitBreaker {
  private failures = 0
  private lastFailTime = 0
  private state: 'closed' | 'open' | 'half-open' = 'closed'

  // Metrics
  private failureCounter = tx2.counter(`circuit_breaker_${this.name}_failures`)
  private stateMetric = tx2.metric({
    name: `circuit_breaker_${this.name}_state`,
    value: () => this.state
  })
  private callsMetric = tx2.histogram({
    name: `circuit_breaker_${this.name}_calls`,
    measurement: 'count'
  })

  constructor(
    private name: string,
    private threshold = 5,
    private timeout = 60000,
    private resetTimeout = 30000
  ) {
    // Admin actions
    tx2.action(`reset_circuit_${name}`, (reply) => {
      this.reset()
      reply({
        circuit: this.name,
        previousState: this.state,
        newState: 'closed',
        failures: 0
      })
    })

    tx2.action(`circuit_${name}_status`, (reply) => {
      reply({
        name: this.name,
        state: this.state,
        failures: this.failures,
        lastFailure: this.lastFailTime
          ? new Date(this.lastFailTime).toISOString()
          : null,
        threshold: this.threshold,
        config: {
          timeout: this.timeout,
          resetTimeout: this.resetTimeout
        }
      })
    })
  }

  async call<T>(fn: () => Promise<T>): Promise<T> {
    // Check circuit state
    if (this.state === 'open') {
      const timeSinceFailure = Date.now() - this.lastFailTime

      if (timeSinceFailure > this.timeout) {
        this.state = 'half-open'
        tx2.event(`circuit:half_open`, { circuit: this.name })
      } else {
        throw new Error(`Circuit breaker is open for ${this.name}`)
      }
    }

    try {
      const result = await fn()
      this.onSuccess()
      return result
    } catch (error) {
      this.onFailure(error)
      throw error
    } finally {
      this.callsMetric.update(1)
    }
  }

  private onSuccess() {
    if (this.state === 'half-open') {
      this.reset()
      tx2.event('circuit:closed', {
        circuit: this.name,
        recoveredAfter: Date.now() - this.lastFailTime
      })
    }
  }

  private onFailure(error: Error) {
    this.failures++
    this.failureCounter.inc()
    this.lastFailTime = Date.now()

    if (this.failures >= this.threshold) {
      this.state = 'open'
      tx2.event('circuit:opened', {
        circuit: this.name,
        failures: this.failures,
        threshold: this.threshold,
        error: error.message
      })

      // Auto-recovery timer
      setTimeout(() => {
        if (this.state === 'open') {
          this.state = 'half-open'
        }
      }, this.resetTimeout)
    }
  }

  private reset() {
    this.failures = 0
    this.state = 'closed'
    this.failureCounter.reset()
  }
}

// Usage
const apiCircuit = new CircuitBreaker('external_api', 5, 60000)

async function callExternalAPI(data) {
  return apiCircuit.call(async () => {
    const response = await fetch('https://api.example.com', {
      method: 'POST',
      body: JSON.stringify(data)
    })

    if (!response.ok) {
      throw new Error(`API returned ${response.status}`)
    }

    return response.json()
  })
}
```

### Memory Leak Detection

```typescript
class MemoryLeakDetector {
  private samples: number[] = []
  private increasing = 0
  private threshold = 10

  private memoryHistogram = tx2.histogram({
    name: 'memory_heap_used',
    measurement: 'max',
    unit: 'MB'
  })

  private leakMetric = tx2.metric({
    name: 'memory_leak_probability',
    unit: '%',
    value: () => this.getLeakProbability()
  })

  constructor() {
    // Sample memory every 10 seconds
    setInterval(() => this.sample(), 10000)

    // Force GC action if available
    if (global.gc) {
      tx2.action('force_gc', (reply) => {
        const before = process.memoryUsage()
        global.gc()
        const after = process.memoryUsage()

        reply({
          freed: {
            heap: (before.heapUsed - after.heapUsed) / 1024 / 1024,
            external: (before.external - after.external) / 1024 / 1024,
            total: ((before.heapUsed - after.heapUsed) +
                   (before.external - after.external)) / 1024 / 1024
          },
          unit: 'MB',
          before: this.formatMemory(before),
          after: this.formatMemory(after)
        })
      })
    }

    // Memory snapshot action
    tx2.action('memory_snapshot', (reply) => {
      const mem = process.memoryUsage()
      const heap = v8.getHeapStatistics()

      reply({
        process: this.formatMemory(mem),
        heap: {
          total: heap.total_heap_size / 1024 / 1024,
          executable: heap.total_heap_size_executable / 1024 / 1024,
          physical: heap.total_physical_size / 1024 / 1024,
          available: heap.total_available_size / 1024 / 1024,
          used: heap.used_heap_size / 1024 / 1024,
          limit: heap.heap_size_limit / 1024 / 1024,
          malloced: heap.malloced_memory / 1024 / 1024,
          peak_malloced: heap.peak_malloced_memory / 1024 / 1024
        },
        samples: this.samples.slice(-20),
        trend: this.getTrend(),
        leakProbability: this.getLeakProbability()
      })
    })
  }

  private sample() {
    const mem = process.memoryUsage()
    const heapMB = mem.heapUsed / 1024 / 1024

    this.samples.push(heapMB)
    if (this.samples.length > 100) {
      this.samples.shift()
    }

    this.memoryHistogram.update(heapMB)

    // Detect consistent increases
    if (this.samples.length > 1) {
      const last = this.samples[this.samples.length - 1]
      const prev = this.samples[this.samples.length - 2]

      if (last > prev) {
        this.increasing++

        // Alert on potential leak
        if (this.increasing > this.threshold) {
          tx2.event('memory:potential_leak', {
            current: heapMB,
            samples: this.samples.slice(-10),
            trend: 'increasing',
            consecutiveIncreases: this.increasing
          })
        }
      } else {
        this.increasing = Math.max(0, this.increasing - 1)
      }
    }

    // Check absolute threshold
    if (heapMB > 500) {
      tx2.event('memory:high_usage', {
        current: heapMB,
        threshold: 500,
        unit: 'MB'
      })
    }
  }

  private getTrend(): 'increasing' | 'stable' | 'decreasing' {
    if (this.samples.length < 10) return 'stable'

    const recent = this.samples.slice(-10)
    const avg = recent.reduce((a, b) => a + b, 0) / recent.length
    const older = this.samples.slice(-20, -10)
    const oldAvg = older.reduce((a, b) => a + b, 0) / older.length

    const change = (avg - oldAvg) / oldAvg * 100

    if (change > 10) return 'increasing'
    if (change < -10) return 'decreasing'
    return 'stable'
  }

  private getLeakProbability(): number {
    if (this.samples.length < 20) return 0

    // Calculate linear regression slope
    const n = this.samples.length
    const sumX = (n * (n + 1)) / 2
    const sumY = this.samples.reduce((a, b) => a + b, 0)
    const sumXY = this.samples.reduce((sum, y, x) => sum + x * y, 0)
    const sumX2 = (n * (n + 1) * (2 * n + 1)) / 6

    const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX)

    // Positive slope indicates potential leak
    return Math.min(100, Math.max(0, slope * 100))
  }

  private formatMemory(mem: NodeJS.MemoryUsage) {
    return {
      rss: Math.round(mem.rss / 1024 / 1024),
      heapTotal: Math.round(mem.heapTotal / 1024 / 1024),
      heapUsed: Math.round(mem.heapUsed / 1024 / 1024),
      external: Math.round(mem.external / 1024 / 1024),
      arrayBuffers: Math.round(mem.arrayBuffers / 1024 / 1024)
    }
  }
}

// Initialize detector
const memoryDetector = new MemoryLeakDetector()
```

### Database Connection Pool Monitor

```typescript
class DatabasePoolMonitor {
  private queryHistogram = tx2.histogram({
    name: 'db_query_duration',
    measurement: 'p99',
    unit: 'ms'
  })

  private connectionHistogram = tx2.histogram({
    name: 'db_connection_wait',
    measurement: 'mean',
    unit: 'ms'
  })

  constructor(private pool: Pool) {
    // Pool metrics
    tx2.metric({
      name: 'db_pool_total',
      value: () => pool.totalCount
    })

    tx2.metric({
      name: 'db_pool_idle',
      value: () => pool.idleCount
    })

    tx2.metric({
      name: 'db_pool_active',
      value: () => pool.totalCount - pool.idleCount
    })

    tx2.metric({
      name: 'db_pool_waiting',
      value: () => pool.waitingCount
    })

    // Pool events
    pool.on('acquire', (client) => {
      const waitTime = Date.now() - client._poolAcquireTime
      this.connectionHistogram.update(waitTime)

      if (waitTime > 1000) {
        tx2.event('db:slow_acquire', {
          waitTime,
          poolSize: pool.totalCount,
          waiting: pool.waitingCount
        })
      }
    })

    pool.on('release', () => {
      // Track connection lifecycle
    })

    pool.on('error', (err, client) => {
      tx2.issue({
        type: 'database_pool_error',
        error: err.message,
        code: err.code,
        client: client._clientId,
        pool: {
          total: pool.totalCount,
          idle: pool.idleCount,
          waiting: pool.waitingCount
        }
      })
    })

    // Pool management actions
    tx2.action('db_pool_status', (reply) => {
      reply({
        config: {
          min: pool.options.min,
          max: pool.options.max,
          idleTimeout: pool.options.idleTimeoutMillis,
          connectionTimeout: pool.options.connectionTimeoutMillis
        },
        current: {
          total: pool.totalCount,
          idle: pool.idleCount,
          active: pool.totalCount - pool.idleCount,
          waiting: pool.waitingCount
        },
        statistics: {
          queryTime: this.queryHistogram.fullResults(),
          connectionWait: this.connectionHistogram.fullResults()
        }
      })
    })

    tx2.action('db_pool_resize',
      { min: 2, max: 10 },
      async (opts, reply) => {
        try {
          await pool.resize(opts.min, opts.max)
          reply({
            success: true,
            newSize: {
              min: opts.min,
              max: opts.max,
              current: pool.totalCount
            }
          })
        } catch (error) {
          reply({
            success: false,
            error: error.message
          })
        }
      }
    )
  }

  async query(text: string, params?: any[]): Promise<any> {
    const start = Date.now()
    const client = await this.pool.connect()

    try {
      const result = await client.query(text, params)
      const duration = Date.now() - start

      this.queryHistogram.update(duration)

      if (duration > 100) {
        tx2.event('db:slow_query', {
          query: text.substring(0, 100),
          duration,
          rows: result.rowCount
        })
      }

      return result
    } catch (error) {
      tx2.issue({
        type: 'query_error',
        query: text,
        params,
        error: error.message,
        code: error.code
      })
      throw error
    } finally {
      client.release()
    }
  }
}

// Usage
const dbMonitor = new DatabasePoolMonitor(pgPool)

// Use monitored query method
app.get('/users', async (req, res) => {
  try {
    const result = await dbMonitor.query(
      'SELECT * FROM users WHERE active = $1',
      [true]
    )
    res.json(result.rows)
  } catch (error) {
    res.status(500).json({ error: 'Database error' })
  }
})
```

## Technical Details

### Monitoring Interval Architecture

TX2 uses a fixed 990ms interval for metric transmission. This design choice:

- **Network Latency Compensation**: The 10ms buffer ensures metrics arrive before the 1-second mark
- **Dashboard Synchronization**: Aligns with PM2 dashboard's 1-second refresh rate
- **CPU Optimization**: Prevents excessive CPU usage from too frequent updates
- **Batching Efficiency**: All metrics are collected and sent in a single message

```typescript
// Internal implementation
private _monitorInterval = setInterval(() => {
  this.send({
    type: 'axm:monitor',
    data: this.prepareData()  // Collects all metric values
  })
}, 990)

// The interval is "unref'd" to not keep the process alive
this._monitorInterval.unref()
```

### Message Buffering Strategy

TX2 implements a no-buffer strategy for optimal real-time monitoring:

- **Events**: Sent immediately when `event()` is called
- **Issues**: Transmitted instantly upon `issue()` call
- **Actions**: Replies sent immediately when callback executes
- **Metrics**: Only metrics are batched and sent every 990ms

This ensures critical events and errors are never delayed or lost.

### Memory Management Strategies

#### Histogram Memory Control

```typescript
// Problem: Histograms store all values indefinitely
const histogram = tx2.histogram({
  name: 'api_calls',
  measurement: 'mean'
})

// Each update adds to internal array
histogram.update(100)  // stores [100]
histogram.update(200)  // stores [100, 200]
// After 1M calls: stores [100, 200, ..., 1000000 values]

// Solution 1: Periodic reset
class ManagedHistogram {
  private histogram = tx2.histogram(this.options)
  private updateCount = 0
  private maxSamples = 10000

  update(value: number) {
    this.histogram.update(value)
    this.updateCount++

    if (this.updateCount >= this.maxSamples) {
      // Store summary before reset
      const summary = this.histogram.fullResults()
      tx2.event('histogram:reset', {
        name: this.options.name,
        summary
      })

      this.histogram.reset()
      this.updateCount = 0
    }
  }
}

// Solution 2: Sliding window
class SlidingWindowHistogram {
  private values: number[] = []
  private maxSize = 1000

  update(value: number) {
    this.values.push(value)
    if (this.values.length > this.maxSize) {
      this.values.shift()  // Remove oldest
    }

    // Update actual histogram with windowed data
    this.histogram.reset()
    this.values.forEach(v => this.histogram.update(v))
  }
}
```

#### Action Listener Management

TX2's improved implementation prevents memory leaks from duplicate action registrations:

```typescript
// Internal implementation uses a Map to store single listener per action
private _actionListeners: Map<string, Function> = new Map()

action(name: string, handler: Function) {
  // Replaces existing handler if present
  this._actionListeners.set(name, handler)

  // Single global listener handles all actions
  if (!this._globalListenerRegistered) {
    process.on('message', (data) => {
      for (const [actionName, handler] of this._actionListeners) {
        if (data === actionName || data.msg === actionName) {
          handler(data)
        }
      }
    })
    this._globalListenerRegistered = true
  }
}

// Safe to call multiple times
for (let i = 0; i < 100; i++) {
  tx2.action('test', handler)  // Only one listener remains
}
```

### Error Serialization Deep Dive

TX2's error handling preserves all error properties:

```typescript
// How TX2 serializes errors
function serializeError(err: any): object {
  if (typeof err === 'string') {
    return { message: err, stack: err }
  }

  if (err instanceof Error) {
    // Get all properties including non-enumerable
    const props = Object.getOwnPropertyNames(err)
    const serialized: any = {}

    props.forEach(prop => {
      serialized[prop] = err[prop]
    })

    // Handle special Error properties
    if (err.stack) serialized.stack = err.stack
    if (err.message) serialized.message = err.message
    if (err.name) serialized.name = err.name

    // Handle custom properties
    if (err.__error_callsites) {
      serialized.stackframes = err.__error_callsites.map(site => ({
        file_name: site.getFileName(),
        line_number: site.getLineNumber()
      }))
    }

    return serialized
  }

  return err  // Already an object
}

// Usage example
class CustomError extends Error {
  code: string
  statusCode: number
  details: any

  constructor(message: string, code: string, statusCode: number) {
    super(message)
    this.code = code
    this.statusCode = statusCode
    this.name = 'CustomError'
  }
}

const error = new CustomError('Not found', 'RESOURCE_NOT_FOUND', 404)
error.details = { resource: 'user', id: 123 }

tx2.issue(error)
// Sends: {
//   message: 'Not found',
//   stack: '...',
//   name: 'CustomError',
//   code: 'RESOURCE_NOT_FOUND',
//   statusCode: 404,
//   details: { resource: 'user', id: 123 }
// }
```

### Circular Reference Handling

TX2 uses `fast-safe-stringify` to handle circular references:

```typescript
// Example of circular reference
const user = { id: 1, name: 'John' }
const post = { id: 1, title: 'Hello', author: user }
user.posts = [post]  // Circular reference

// Safe to send
tx2.event('data:circular', user)
// Serializes to: {"id":1,"name":"John","posts":[{"id":1,"title":"Hello","author":"[Circular]"}]}

// Complex circular structures
const obj1 = { name: 'obj1' }
const obj2 = { name: 'obj2' }
const obj3 = { name: 'obj3' }
obj1.ref = obj2
obj2.ref = obj3
obj3.ref = obj1  // Three-way circular

tx2.event('complex:circular', obj1)
// Safely serialized without infinite recursion
```

### Statistical Algorithms

#### Welford's Algorithm for Variance

TX2 uses Welford's online algorithm for numerically stable variance calculation:

```typescript
// Welford's algorithm implementation
class WelfordVariance {
  private n = 0
  private mean = 0
  private M2 = 0

  update(value: number) {
    this.n++
    const delta = value - this.mean
    this.mean += delta / this.n
    const delta2 = value - this.mean
    this.M2 += delta * delta2
  }

  get variance() {
    return this.n < 2 ? 0 : this.M2 / (this.n - 1)
  }

  get stddev() {
    return Math.sqrt(this.variance)
  }
}
```

#### Exponentially Weighted Moving Average (EWMA)

Used in meters for rate calculation:

```typescript
// EWMA implementation
class EWMA {
  private rate = 0
  private uncounted = 0
  private alpha: number

  constructor(
    private interval: number,  // Tick interval
    private timeframe: number  // Time window
  ) {
    this.alpha = 1 - Math.exp(-interval / timeframe)
  }

  update(n: number) {
    this.uncounted += n
  }

  tick() {
    const instantRate = this.uncounted / (this.interval / 1000)
    this.uncounted = 0

    // Exponential decay
    this.rate += this.alpha * (instantRate - this.rate)
  }

  getRate(): number {
    return this.rate
  }
}

// Usage in Meter
const ewma = new EWMA(1000, 60000)  // 1s tick, 1min window
setInterval(() => ewma.tick(), 1000)

// Mark events
ewma.update(1)  // Event occurred
ewma.getRate()  // Current rate
```

## Performance Considerations

### Metric Function Performance

```typescript
// ❌ Bad - Heavy computation in metric function
tx2.metric({
  name: 'expensive_metric',
  value: () => {
    // This runs every 990ms!
    let sum = 0
    for (let i = 0; i < 1000000; i++) {
      sum += Math.sqrt(i)
    }
    return sum
  }
})

// ✅ Good - Cache expensive calculations
let cachedValue = 0
const updateInterval = setInterval(() => {
  let sum = 0
  for (let i = 0; i < 1000000; i++) {
    sum += Math.sqrt(i)
  }
  cachedValue = sum
}, 10000)  // Update every 10 seconds

tx2.metric({
  name: 'expensive_metric',
  value: () => cachedValue  // Fast retrieval
})
```

### Event Throttling

```typescript
// ❌ Bad - Unthrottled events
app.use((req, res, next) => {
  tx2.event('request', { url: req.url })  // Fires for every request!
  next()
})

// ✅ Good - Throttled events
const throttle = (fn: Function, delay: number) => {
  let last = 0
  return (...args: any[]) => {
    const now = Date.now()
    if (now - last >= delay) {
      last = now
      fn(...args)
    }
  }
}

const throttledEvent = throttle((data) => {
  tx2.event('request_batch', data)
}, 1000)

let requestBatch: any[] = []
app.use((req, res, next) => {
  requestBatch.push({ url: req.url, method: req.method })

  if (requestBatch.length >= 100) {
    throttledEvent({ count: requestBatch.length, requests: requestBatch })
    requestBatch = []
  }

  next()
})
```

### Histogram Optimization

```typescript
// Optimized histogram with automatic memory management
class OptimizedHistogram {
  private histogram: Histogram
  private sampleCount = 0
  private summaryStats = {
    total: 0,
    min: Infinity,
    max: -Infinity
  }

  constructor(
    private options: HistogramOptions,
    private maxSamples = 1000
  ) {
    this.histogram = tx2.histogram(options)
  }

  update(value: number) {
    // Always track summary stats
    this.summaryStats.total++
    this.summaryStats.min = Math.min(this.summaryStats.min, value)
    this.summaryStats.max = Math.max(this.summaryStats.max, value)

    // Sample for detailed stats
    if (this.sampleCount < this.maxSamples) {
      this.histogram.update(value)
      this.sampleCount++
    } else {
      // Reservoir sampling for even distribution
      const rand = Math.floor(Math.random() * this.summaryStats.total)
      if (rand < this.maxSamples) {
        this.histogram.reset()
        this.histogram.update(value)
        this.sampleCount = 1
      }
    }
  }
}
```

## Troubleshooting

### 1. Metrics Not Appearing in PM2 Dashboard

```typescript
// Check if running under PM2
if (!process.send) {
  console.warn('WARNING: Not running under PM2 - metrics will not be sent')
  console.warn('Start with: pm2 start app.js')
}

// Verify PM2 configuration
// ecosystem.config.js
module.exports = {
  apps: [{
    name: 'my-app',
    script: './dist/app.js',
    instances: 1,
    exec_mode: 'fork',  // Required for IPC
    max_memory_restart: '500M',

    // Enable monitoring
    pmx: true,
    instance_var: 'INSTANCE_ID',

    // Error handling
    error_file: './logs/err.log',
    out_file: './logs/out.log',
    merge_logs: true,
    time: true
  }]
}

// Debug TX2 messages
tx2.on('data', (msg) => {
  console.log('[TX2 Debug]', JSON.stringify(msg))
})
```

### 2. Actions Not Triggering

```typescript
// Debug action registration
const originalAction = tx2.action.bind(tx2)
tx2.action = function(name: string, ...args: any[]) {
  console.log(`[Action Registered] ${name}`)
  return originalAction(name, ...args)
}

// Test action
tx2.action('test_action', (reply) => {
  console.log('[Action Triggered] test_action')
  reply({
    success: true,
    timestamp: Date.now(),
    pid: process.pid
  })
})

// Monitor incoming messages
process.on('message', (msg) => {
  console.log('[IPC Message]', msg)
})
```

### 3. Memory Leak Warnings

```typescript
// Warning: Possible EventEmitter memory leak detected
(tx2 as any).setMaxListeners(50)

// Or globally
require('events').EventEmitter.defaultMaxListeners = 50

// Track listener count
console.log('TX2 Listeners:', tx2.listenerCount('data'))
console.log('Process Listeners:', process.listenerCount('message'))

// Clean up histograms periodically
class HistogramManager {
  private histograms: Map<string, Histogram> = new Map()

  create(options: HistogramOptions): Histogram {
    const histogram = tx2.histogram(options)
    this.histograms.set(options.name, histogram)

    // Auto-cleanup timer
    setInterval(() => {
      if (histogram.getCount() > 10000) {
        console.log(`Resetting histogram: ${options.name}`)
        histogram.reset()
      }
    }, 300000)  // Every 5 minutes

    return histogram
  }

  cleanup() {
    this.histograms.forEach(h => h.reset())
    this.histograms.clear()
  }
}
```

### 4. High CPU Usage

```typescript
// Profile metric functions
const metricsProfile: Map<string, number> = new Map()

function profiledMetric(options: MetricOptions) {
  const name = options.name
  const originalValue = options.value

  if (typeof originalValue === 'function') {
    options.value = () => {
      const start = process.hrtime.bigint()
      const result = originalValue()
      const duration = Number(process.hrtime.bigint() - start) / 1000000  // ms

      metricsProfile.set(name, duration)

      if (duration > 10) {
        console.warn(`[Slow Metric] ${name} took ${duration}ms`)
      }

      return result
    }
  }

  return tx2.metric(options)
}

// Monitor metric performance
setInterval(() => {
  const slowMetrics = Array.from(metricsProfile.entries())
    .filter(([_, duration]) => duration > 5)
    .sort((a, b) => b[1] - a[1])

  if (slowMetrics.length > 0) {
    console.log('Slow Metrics:', slowMetrics)
  }
}, 60000)
```

### 5. TypeScript Configuration

```typescript
// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "lib": ["ES2020"],
    "outDir": "./dist",
    "rootDir": "./src",

    // Strict settings
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,

    // Module resolution
    "moduleResolution": "node",
    "resolveJsonModule": true,

    // Source maps for debugging
    "sourceMap": true,
    "inlineSources": true,

    // Decorators if needed
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,

    // Type roots
    "types": ["node"],
    "typeRoots": ["./node_modules/@types", "./types"]
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist", "test"]
}

// Type declarations for TX2
// types/tx2.d.ts
declare module 'tx2' {
  // ... (include all interfaces from this document)
}
```

## Best Practices Summary

### ✅ Do's

1. **Meaningful Naming**: Use clear namespace:action format for events
2. **Specify Units**: Always define units for metrics
3. **Error Handling**: Wrap all actions in try-catch blocks
4. **Memory Management**: Periodically reset histograms with high volume
5. **Event Throttling**: Implement throttling for high-frequency events
6. **Cache Calculations**: Cache expensive metric calculations
7. **Graceful Shutdown**: Clean up metrics and send final events on shutdown
8. **Debug Mode**: Add debug logging in development
9. **Monitor Performance**: Track metric calculation times
10. **Document Actions**: Provide clear action descriptions and expected parameters

### ❌ Don'ts

1. **Heavy Computations**: Avoid expensive operations in metric value functions
2. **Excessive Events**: Don't send events for every single operation
3. **Large Payloads**: Avoid sending huge objects in events or action replies
4. **Synchronous I/O**: Never use sync I/O in metric functions
5. **Global State Reliance**: Don't depend on mutable global state in actions
6. **Unhandled Promises**: Always handle promise rejections in actions
7. **Memory Leaks**: Don't store unlimited data in memory
8. **Tight Loops**: Avoid tight loops in metric calculations
9. **Blocking Operations**: Never block the event loop in metrics
10. **Sensitive Data**: Never log passwords or tokens in events

## Complete Real-World Example

```typescript
// Complete e-commerce monitoring setup
import tx2 from './tx2'
import express from 'express'
import { Pool } from 'pg'
import Redis from 'ioredis'

const app = express()
const db = new Pool({ /* config */ })
const redis = new Redis()
const cache = new Map()

// Initialize monitoring
class EcommerceMonitor {
  // Counters
  private orders = tx2.counter('orders_total')
  private revenue = tx2.counter({ name: 'revenue_total', unit: 'USD' })
  private refunds = tx2.counter({ name: 'refunds_total', unit: 'USD' })
  private cartAbandoned = tx2.counter('carts_abandoned')

  // Histograms
  private cartValue = tx2.histogram({
    name: 'cart_value',
    measurement: 'mean',
    unit: 'USD'
  })

  private checkoutDuration = tx2.histogram({
    name: 'checkout_duration',
    measurement: 'p95',
    unit: 'seconds'
  })

  private searchLatency = tx2.histogram({
    name: 'search_latency',
    measurement: 'p99',
    unit: 'ms'
  })

  // Meters
  private orderRate = tx2.meter({
    name: 'order_rate',
    unit: 'orders/min',
    seconds: 60
  })

  private searchRate = tx2.meter({
    name: 'search_rate',
    unit: 'searches/sec'
  })

  // Metrics
  private conversionRate = tx2.metric({
    name: 'conversion_rate',
    unit: '%',
    value: () => {
      const rate = (this.orders.val() / this.sessions.val()) * 100
      return Math.round(rate * 100) / 100
    }
  })

  private avgOrderValue = tx2.metric({
    name: 'average_order_value',
    unit: 'USD',
    value: () => {
      const orders = this.orders.val()
      if (orders === 0) return 0
      return Math.round(this.revenue.val() / orders * 100) / 100
    }
  })

  private inventoryLow = tx2.metric({
    name: 'inventory_low_items',
    value: async () => {
      const result = await db.query(
        'SELECT COUNT(*) FROM products WHERE stock < 10'
      )
      return result.rows[0].count
    }
  })

  // Sessions tracking
  private sessions = tx2.counter('sessions_total')
  private activeSessions = new Set<string>()

  // Actions
  constructor() {
    // Daily reset action
    tx2.action('reset_daily_metrics', (reply) => {
      const previousData = {
        orders: this.orders.val(),
        revenue: this.revenue.val(),
        sessions: this.sessions.val()
      }

      this.orders.reset()
      this.revenue.reset()
      this.sessions.reset()
      this.cartAbandoned.reset()

      reply({
        reset: true,
        previousData,
        timestamp: new Date().toISOString()
      })
    })

    // Cache management
    tx2.action('clear_cache', async (reply) => {
      try {
        await redis.flushdb()
        cache.clear()

        reply({
          success: true,
          cleared: {
            redis: true,
            memory: true
          }
        })
      } catch (error) {
        reply({
          success: false,
          error: error.message
        })
      }
    })

    // Inventory check
    tx2.action('inventory_report', async (reply) => {
      const [low, outOfStock, total] = await Promise.all([
        db.query('SELECT * FROM products WHERE stock < 10'),
        db.query('SELECT * FROM products WHERE stock = 0'),
        db.query('SELECT COUNT(*) FROM products')
      ])

      reply({
        total: total.rows[0].count,
        lowStock: low.rows.map(p => ({
          id: p.id,
          name: p.name,
          stock: p.stock
        })),
        outOfStock: outOfStock.rows.map(p => ({
          id: p.id,
          name: p.name
        })),
        critical: outOfStock.rows.length > 0
      })
    })

    // Performance snapshot
    tx2.action('performance_snapshot', (reply) => {
      reply({
        metrics: {
          orders: this.orders.val(),
          revenue: this.revenue.val(),
          avgOrderValue: this.avgOrderValue.val(),
          conversionRate: this.conversionRate.val()
        },
        performance: {
          checkoutTime: this.checkoutDuration.fullResults(),
          searchLatency: this.searchLatency.fullResults()
        },
        rates: {
          orders: this.orderRate.val(),
          searches: this.searchRate.val()
        },
        system: {
          memory: process.memoryUsage(),
          uptime: process.uptime(),
          connections: {
            database: db.totalCount,
            redis: redis.status === 'ready'
          }
        }
      })
    })
  }

  // Track order
  recordOrder(order: any) {
    this.orders.inc()
    this.revenue.inc(order.total)
    this.orderRate.mark()
    this.cartValue.update(order.total)

    tx2.event('order:completed', {
      orderId: order.id,
      total: order.total,
      items: order.items.length,
      customer: order.customerId,
      paymentMethod: order.paymentMethod
    })

    // Track high-value orders
    if (order.total > 1000) {
      tx2.event('order:high_value', {
        orderId: order.id,
        total: order.total,
        customer: order.customerId
      })
    }
  }

  // Track search
  recordSearch(query: string, results: number, duration: number) {
    this.searchRate.mark()
    this.searchLatency.update(duration)

    if (results === 0) {
      tx2.event('search:no_results', { query })
    }

    if (duration > 500) {
      tx2.event('search:slow', {
        query,
        duration,
        results
      })
    }
  }

  // Track checkout
  recordCheckout(sessionId: string, duration: number, success: boolean) {
    if (success) {
      this.checkoutDuration.update(duration / 1000)
    } else {
      this.cartAbandoned.inc()
      tx2.event('checkout:abandoned', {
        sessionId,
        duration,
        stage: 'payment'
      })
    }
  }

  // Session management
  startSession(sessionId: string) {
    this.sessions.inc()
    this.activeSessions.add(sessionId)
  }

  endSession(sessionId: string) {
    this.activeSessions.delete(sessionId)
  }
}

// Initialize monitor
const monitor = new EcommerceMonitor()

// Middleware
app.use((req, res, next) => {
  // Session tracking
  if (!req.session.started) {
    monitor.startSession(req.sessionID)
    req.session.started = true
  }

  next()
})

// Routes
app.post('/api/orders', async (req, res) => {
  const startTime = Date.now()

  try {
    const order = await processOrder(req.body)
    const duration = Date.now() - startTime

    monitor.recordOrder(order)
    monitor.recordCheckout(req.sessionID, duration, true)

    res.json({ success: true, order })
  } catch (error) {
    monitor.recordCheckout(req.sessionID, Date.now() - startTime, false)

    tx2.issue({
      type: 'order_processing_error',
      error: error.message,
      sessionId: req.sessionID,
      cart: req.body
    })

    res.status(500).json({ error: 'Order processing failed' })
  }
})

app.get('/api/search', async (req, res) => {
  const start = Date.now()

  try {
    const results = await searchProducts(req.query.q)
    const duration = Date.now() - start

    monitor.recordSearch(req.query.q, results.length, duration)

    res.json({ results, count: results.length })
  } catch (error) {
    tx2.issue({
      type: 'search_error',
      query: req.query.q,
      error: error.message
    })

    res.status(500).json({ error: 'Search failed' })
  }
})

// Startup
app.listen(3000, () => {
  tx2.event('app:started', {
    port: 3000,
    environment: process.env.NODE_ENV,
    version: process.env.npm_package_version,
    node: process.version
  })

  console.log('E-commerce API running on port 3000')
})

// Graceful shutdown
process.on('SIGTERM', async () => {
  tx2.event('app:shutdown:started'