# Event Bus API Reference

Complete API reference for WildDuck Event Bus components.

## Table of Contents

1. [EventBus Class](#eventbus-class)
2. [EventAttachment Base Class](#eventattachment-base-class)
3. [Event Factory](#event-factory)
4. [Event Type Constants](#event-type-constants)
5. [Test Tracker](#test-tracker)
6. [Debug Logger](#debug-logger)

---

## EventBus Class

**Location:** `lib/event-bus.js`

**Import:**
```javascript
const eventBus = require('./lib/event-bus');
```

**Description:** Singleton event bus that distributes events to registered attachments.

### Methods

#### registerAttachment(attachment)

Register an attachment to receive events.

**Parameters:**
- `attachment` (EventAttachment) - Instance of EventAttachment

**Throws:**
- Error if attachment doesn't have `name` property
- Error if attachment doesn't implement `onEvent()` method

**Example:**
```javascript
const MyAttachment = require('./my-attachment');
const attachment = new MyAttachment();
eventBus.registerAttachment(attachment);
```

---

#### unregisterAttachment(name)

Unregister an attachment by name.

**Parameters:**
- `name` (string) - Attachment name

**Returns:**
- `boolean` - `true` if attachment was removed, `false` if not found

**Example:**
```javascript
const removed = eventBus.unregisterAttachment('my-attachment');
```

---

#### emit(eventData)

Emit an event to all registered attachments (async, non-blocking).

**Parameters:**
- `eventData` (object) - Raw event data from `lib/events.js`
  - `ev` (string) - Event type (e.g., 'message.added')
  - `time` (number) - Timestamp in milliseconds
  - `user` (string) - User ID
  - `source` (string) - Event source
  - Additional event-specific fields

**Returns:** void

**Example:**
```javascript
eventBus.emit({
  ev: 'message.added',
  user: '507f1f77bcf86cd799439011',
  mailbox: '507f1f77bcf86cd799439012',
  time: Date.now(),
  source: 'lmtp',
  size: 1024
});
```

**Note:** Events are emitted asynchronously using `setImmediate()`. Attachment errors are caught and logged, but don't affect event emission.

---

#### shouldEmit(eventType)

Check if an event type should be emitted to the event bus.

**Parameters:**
- `eventType` (string) - Event type (e.g., 'message.added')

**Returns:**
- `boolean` - `true` if event should be emitted

**Example:**
```javascript
if (eventBus.shouldEmit('message.added')) {
  // Event will be emitted
}
```

---

#### getRegisteredAttachments()

Get list of registered attachment names.

**Returns:**
- `string[]` - Array of attachment names

**Example:**
```javascript
const attachments = eventBus.getRegisteredAttachments();
// ['test-tracker', 'debug-logger']
```

---

#### transformEvent(eventData)

Transform raw event data to normalized format (internal method).

**Parameters:**
- `eventData` (object) - Raw event data

**Returns:**
- `object` - Normalized event object

**Example:**
```javascript
const normalized = eventBus.transformEvent({
  ev: 'message.added',
  user: '123',
  time: 1234567890000,
  source: 'api',
  size: 1024
});

// Returns:
// {
//   type: 'message.added',
//   category: 'message',
//   action: 'added',
//   timestamp: Date,
//   source: 'api',
//   user: '123',
//   data: { size: 1024 },
//   metadata: {}
// }
```

---

## EventAttachment Base Class

**Location:** `lib/event-bus/attachment.js`

**Import:**
```javascript
const EventAttachment = require('./lib/event-bus/attachment');
```

**Description:** Base class for event attachments. Extend this class to create custom attachments.

### Constructor

```javascript
constructor(name)
```

**Parameters:**
- `name` (string, required) - Unique attachment name

**Throws:**
- Error if name is not provided

**Example:**
```javascript
class MyAttachment extends EventAttachment {
  constructor() {
    super('my-attachment');
  }
}
```

---

### Methods to Override

#### onEvent(event) [Required]

Handle an event. Must be implemented by subclass.

**Parameters:**
- `event` (object) - Normalized event object

**Returns:** `Promise<void>` or `void`

**Throws:** Error if not implemented

**Example:**
```javascript
async onEvent(event) {
  console.log('Event:', event.type);
  console.log('User:', event.user);

  // Async operations allowed
  await this.saveToDatabase(event);
}
```

---

#### shouldHandle(event) [Optional]

Filter which events this attachment should handle. Default returns `true` for all events.

**Parameters:**
- `event` (object) - Normalized event object

**Returns:** `boolean` - `true` to handle event, `false` to skip

**Example:**
```javascript
shouldHandle(event) {
  // Only handle message events
  return event.category === 'message';
}

// Or more complex logic
shouldHandle(event) {
  // Only handle message additions via LMTP
  return event.type === 'message.added' && event.source === 'lmtp';
}
```

---

#### onStop() [Optional]

Cleanup when event bus shuts down (SIGTERM/SIGINT). Default does nothing.

**Returns:** `Promise<void>` or `void`

**Example:**
```javascript
async onStop() {
  console.log('Shutting down attachment...');
  await this.closeConnection();
  this.cleanup();
}
```

---

### Properties

#### name (readonly)

Get attachment name.

**Type:** `string`

**Example:**
```javascript
const attachment = new MyAttachment();
console.log(attachment.name); // 'my-attachment'
```

---

## Event Factory

**Location:** `lib/event-bus/event-factory.js`

**Import:**
```javascript
const eventFactory = require('./lib/event-bus/event-factory');
```

**Description:** Factory methods for creating and validating events.

### Methods

#### createEvent(params)

Create a normalized event object.

**Parameters:**
- `params` (object)
  - `type` (string, required) - Event type (format: 'category.action')
  - `source` (string, required) - Event source
  - `user` (string) - User ID
  - `mailbox` (string) - Mailbox ID
  - `message` (string) - Message ID
  - `data` (object) - Event-specific data
  - `metadata` (object) - Optional metadata
  - `timestamp` (Date) - Optional timestamp (defaults to now)

**Returns:** `object` - Normalized event

**Throws:**
- Error if type is missing
- Error if source is missing
- Error if type format is invalid

**Example:**
```javascript
const event = eventFactory.createEvent({
  type: 'message.added',
  source: 'lmtp',
  user: '507f1f77bcf86cd799439011',
  mailbox: '507f1f77bcf86cd799439012',
  data: { size: 1024, uid: 42 },
  metadata: { encrypted: true }
});
```

---

#### createMessageEvent(params)

Create a message-specific event (convenience method).

**Parameters:**
- `params` (object) - Same as `createEvent()`, plus:
  - `message` (string, required) - Message ID

**Returns:** `object` - Normalized event

**Example:**
```javascript
const event = eventFactory.createMessageEvent({
  type: 'message.flags.changed',
  source: 'imap',
  user: '123',
  mailbox: '456',
  message: '789',
  data: {
    oldFlags: ['\\Seen'],
    newFlags: ['\\Seen', '\\Flagged']
  }
});
```

---

#### validateEvent(event)

Validate event structure.

**Parameters:**
- `event` (object) - Event to validate

**Throws:**
- Error if required fields are missing
- Error if field types are incorrect

**Example:**
```javascript
const event = { type: 'user.created', source: 'api' };
eventFactory.validateEvent(event); // OK

const badEvent = { type: 'test' };
eventFactory.validateEvent(badEvent); // Throws error
```

---

## Event Type Constants

**Location:** `lib/event-bus/event-types.js`

**Import:**
```javascript
const eventTypes = require('./lib/event-bus/event-types');
```

**Description:** Constants for event types, categories, actions, and sources.

### Event Type Constants

```javascript
// Message events
MESSAGE_ADDED: 'message.added'
MESSAGE_FLAGS_CHANGED: 'message.flags.changed'
MESSAGE_MOVED: 'message.moved'
MESSAGE_DELETED: 'message.deleted'

// Settings events
SETTINGS_UPDATED: 'settings.updated'

// Category constants
CATEGORY = {
  MESSAGE: 'message',
  USER: 'user',
  MAILBOX: 'mailbox',
  ADDRESS: 'address',
  FILTER: 'filter',
  SPAM: 'spam',
  AUTOREPLY: 'autoreply',
  SETTINGS: 'settings'
}

// Action constants
ACTION = {
  CREATED: 'created',
  UPDATED: 'updated',
  DELETED: 'deleted',
  ADDED: 'added',
  MOVED: 'moved',
  RENAMED: 'renamed',
  CHANGED: 'changed',
  ENABLED: 'enabled',
  DISABLED: 'disabled',
  MARKED: 'marked'
}

// Source constants
SOURCE = {
  API: 'api',
  IMAP: 'imap',
  LMTP: 'lmtp',
  POP3: 'pop3',
  AUTO: 'auto'
}
```

**Example:**
```javascript
const { MESSAGE_ADDED, CATEGORY, SOURCE } = require('./lib/event-bus/event-types');

// Use in emit
eventBus.emit({
  ev: MESSAGE_ADDED,
  source: SOURCE.LMTP,
  ...
});

// Use in shouldHandle
shouldHandle(event) {
  return event.category === CATEGORY.MESSAGE;
}
```

---

## Test Tracker

**Location:** `lib/event-bus/attachments/test-tracker.js`

**Import:**
```javascript
const testTracker = require('./lib/event-bus/attachments/test-tracker');
```

**Description:** Test attachment for storing events in memory. Auto-registers when `NODE_ENV=test`.

### Methods

#### clear()

Clear all stored events.

**Example:**
```javascript
testTracker.clear();
```

---

#### getAllEvents()

Get all stored events.

**Returns:** `object[]` - Array of events

**Example:**
```javascript
const events = testTracker.getAllEvents();
console.log(`Total events: ${events.length}`);
```

---

#### getEventsByType(type)

Get events by type.

**Parameters:**
- `type` (string) - Event type (e.g., 'message.added')

**Returns:** `object[]` - Matching events

**Example:**
```javascript
const messageEvents = testTracker.getEventsByType('message.added');
```

---

#### getEventsByCategory(category)

Get events by category.

**Parameters:**
- `category` (string) - Category (e.g., 'message')

**Returns:** `object[]` - Matching events

**Example:**
```javascript
const messageEvents = testTracker.getEventsByCategory('message');
```

---

#### getEventsBySource(source)

Get events by source.

**Parameters:**
- `source` (string) - Source (e.g., 'api')

**Returns:** `object[]` - Matching events

**Example:**
```javascript
const apiEvents = testTracker.getEventsBySource('api');
```

---

#### getEventsByUser(userId)

Get events by user ID.

**Parameters:**
- `userId` (string) - User ID

**Returns:** `object[]` - Matching events

**Example:**
```javascript
const userEvents = testTracker.getEventsByUser('507f1f77bcf86cd799439011');
```

---

#### getLastEvent()

Get the most recent event.

**Returns:** `object|null` - Last event or null if none

**Example:**
```javascript
const lastEvent = testTracker.getLastEvent();
if (lastEvent) {
  console.log('Last event:', lastEvent.type);
}
```

---

#### getEventCount()

Get total event count.

**Returns:** `number` - Total events

**Example:**
```javascript
const count = testTracker.getEventCount();
expect(count).to.be.greaterThan(0);
```

---

#### hasEventOfType(type)

Check if event type was emitted.

**Parameters:**
- `type` (string) - Event type

**Returns:** `boolean` - `true` if emitted

**Example:**
```javascript
if (testTracker.hasEventOfType('message.added')) {
  console.log('Message was added');
}
```

---

#### assertEventEmitted(type)

Assert event was emitted (throws if not).

**Parameters:**
- `type` (string) - Event type

**Throws:** Error if event not found

**Example:**
```javascript
// In tests
testTracker.assertEventEmitted('mailbox.created'); // OK

testTracker.assertEventEmitted('nonexistent');
// Throws: 'Expected event "nonexistent" to be emitted but it was not'
```

---

#### getSummary()

Get summary statistics.

**Returns:** `object` - Summary with counts
  - `total` (number) - Total events
  - `byType` (object) - Count by type
  - `byCategory` (object) - Count by category
  - `bySource` (object) - Count by source

**Example:**
```javascript
const summary = testTracker.getSummary();
console.log('Total events:', summary.total);
console.log('Message events:', summary.byCategory.message);
console.log('API events:', summary.bySource.api);
```

---

#### waitForEvent(type, timeout)

Wait for event with timeout (async).

**Parameters:**
- `type` (string) - Event type to wait for
- `timeout` (number, default: 1000) - Timeout in milliseconds

**Returns:** `Promise<object>` - Event object

**Throws:** Error if timeout

**Example:**
```javascript
// Start async operation
server.post('/users/123/mailboxes').send({ path: 'Sent' });

// Wait for event
const event = await testTracker.waitForEvent('mailbox.created', 2000);
expect(event.data.path).to.equal('Sent');
```

---

## Debug Logger

**Location:** `lib/event-bus/attachments/debug-logger.js`

**Import:**
```javascript
const DebugLogger = require('./lib/event-bus/attachments/debug-logger');
```

**Description:** Debug attachment that logs events to console. Auto-registers when `DEBUG_EVENTS=1`.

### Constructor

```javascript
new DebugLogger(options)
```

**Parameters:**
- `options` (object, optional)
  - `logLevel` (string) - Log level: 'info', 'debug', 'verbose' (default: 'info')
  - `includeData` (boolean) - Include event data (default: true)
  - `includeMetadata` (boolean) - Include metadata (default: false)

**Example:**
```javascript
const logger = new DebugLogger({
  logLevel: 'verbose',
  includeData: true,
  includeMetadata: true
});

eventBus.registerAttachment(logger);
```

---

### Methods

#### onEvent(event)

Log event to console.

**Output format:**
```
[Event Bus] message.added
  User: 507f1f77bcf86cd799439011
  Mailbox: 507f1f77bcf86cd799439012
  Source: lmtp
  Data: { size: 1024, uid: 42 }
```

---

## Complete Example

Putting it all together:

```javascript
const EventAttachment = require('./lib/event-bus/attachment');
const eventBus = require('./lib/event-bus');
const { MESSAGE_ADDED, CATEGORY } = require('./lib/event-bus/event-types');

// Create custom attachment
class MetricsCollector extends EventAttachment {
  constructor() {
    super('metrics-collector');
    this.counts = {};
  }

  shouldHandle(event) {
    // Only track message events
    return event.category === CATEGORY.MESSAGE;
  }

  async onEvent(event) {
    // Count events by type
    this.counts[event.type] = (this.counts[event.type] || 0) + 1;
  }

  getMetrics() {
    return this.counts;
  }

  async onStop() {
    console.log('Final metrics:', this.counts);
  }
}

// Register attachment
const metrics = new MetricsCollector();
eventBus.registerAttachment(metrics);

// Later...
console.log('Metrics:', metrics.getMetrics());
// { 'message.added': 42, 'message.deleted': 5 }
```

---

## TypeScript Support

Type definitions (if added):

```typescript
import { EventBus, EventAttachment, Event } from './lib/event-bus';

class MyAttachment extends EventAttachment {
  constructor() {
    super('my-attachment');
  }

  shouldHandle(event: Event): boolean {
    return event.category === 'message';
  }

  async onEvent(event: Event): Promise<void> {
    console.log(event.type);
  }
}
```

---

## See Also

- [Event Bus User Guide](./event-bus.md)
- [Event Bus Examples](./event-bus-examples.md)
- [Testing Documentation](../TESTING.md)
