# How the PicList-Core Event Bus Powers Decoupled Plugin Communication

> Learn how the PicList-Core event bus facilitates decoupled plugin communication using a publish-subscribe model for state changes. Explore a lightweight Nodejs EventEmitter.

- Repository: [Kuingsmile/piclist-core](https://github.com/kuingsmile/piclist-core)
- Tags: internals
- Published: 2026-03-05

---

**The PicList-Core event bus is a lightweight Node.js `EventEmitter` instance that enables loose coupling between the core application, utility modules, and plugins by broadcasting state changes like configuration updates through a centralized publish-subscribe channel.**

The `kuingsmile/piclist-core` repository implements a straightforward yet powerful event-driven architecture to solve plugin coordination challenges. By leveraging a singleton event bus for PicList-Core plugin communication, the core system eliminates direct dependencies between components while ensuring real-time synchronization of critical state changes like proxy settings. This design allows third-party plugins to react to lifecycle events without importing internal core classes or creating circular dependencies.

## What Is the PicList-Core Event Bus?

The event bus in PicList-Core is a single, global `EventEmitter` instance exported from [`src/utils/eventBus.ts`](https://github.com/kuingsmile/piclist-core/blob/main/src/utils/eventBus.ts). Unlike complex message brokers, this implementation uses Node.js’s native events module to provide a lightweight pub/sub mechanism that any module—core, utility, or plugin—can import and use.

```typescript
// src/utils/eventBus.ts
import { EventEmitter } from 'node:events';
const eventBus = new EventEmitter();
export { eventBus };

```

All modules share this identical object reference, ensuring that events emitted by the core are received by listeners in plugins and network layers instantaneously.

## Core Components and Architecture

### The Global Event Bus Instance

The system centralizes all communication through one exported object. In [`src/utils/eventBus.ts`](https://github.com/kuingsmile/piclist-core/blob/main/src/utils/eventBus.ts), the code creates a standard Node.js `EventEmitter` and exports it as a singleton. This approach guarantees that all imports reference the same event bus instance, preventing fragmented state across the application.

### Event Definitions in the Enum Registry

Built-in event names are strictly typed through the `IBusEvent` enum declared in [`src/utils/enum.ts`](https://github.com/kuingsmile/piclist-core/blob/main/src/utils/enum.ts). Currently, the primary event for PicList-Core plugin communication is `CONFIG_CHANGE`, though the architecture supports extension.

```typescript
// src/utils/enum.ts
export enum IBusEvent {
  CONFIG_CHANGE = 'CONFIG_CHANGE',
}

```

Using an enum prevents string-typing errors and provides IDE autocomplete for plugin developers.

## How the Event Bus Facilitates Plugin Communication

### Configuration Change Broadcasting

When the core updates a configuration value, it notifies all subscribers immediately. In [`src/core/PicGo.ts`](https://github.com/kuingsmile/piclist-core/blob/main/src/core/PicGo.ts), the `setConfig` method emits a `CONFIG_CHANGE` event containing the specific key and new value:

```typescript
// src/core/PicGo.ts (setConfig method)
eventBus.emit(IBusEvent.CONFIG_CHANGE, {
  configName: name,
  value: config[name],
});

```

According to the source code at lines 95-100, this broadcast occurs whenever any configuration key is modified, ensuring that no module holds stale configuration data.

### Dynamic State Synchronization

The network layer demonstrates reactive updating through event subscription. In [`src/lib/Request.ts`](https://github.com/kuingsmile/piclist-core/blob/main/src/lib/Request.ts), the constructor registers a listener for `CONFIG_CHANGE` to adjust HTTP proxy settings on-the-fly without requiring a restart:

```typescript
// src/lib/Request.ts (constructor, lines 36-48)
eventBus.on(IBusEvent.CONFIG_CHANGE, (data) => {
  switch (data.configName) {
    case 'picBed':
      if (data.value?.proxy) this.proxy = data.value.proxy;
      break;
    case 'picBed.proxy':
      this.proxy = data.value;
      break;
  }
});

```

This mechanism guarantees that outgoing requests always use the latest proxy configuration without the core directly calling the request instance.

### Plugin Integration Patterns

Plugins interact with the bus by importing the same singleton. Any plugin can subscribe to `CONFIG_CHANGE` or emit custom events for cross-plugin communication. The bus enables plugins to react to configuration updates without knowing the internal implementation of the core, maintaining strict separation of concerns.

## Implementation Details and Code Examples

### Listening to Configuration Changes in Plugins

To react to proxy updates or other configuration changes, import the event bus and register a listener in your plugin’s initialization code:

```typescript
import { eventBus } from '../../utils/eventBus';
import { IBusEvent } from '../../utils/enum';

export default function myUploader(ctx) {
  let currentProxy = ctx.getConfig<string>('picBed.proxy');

  eventBus.on(IBusEvent.CONFIG_CHANGE, (data) => {
    if (data.configName === 'picBed.proxy') {
      currentProxy = data.value as string;
      // Reconfigure the HTTP client with new proxy settings
    }
  });

  // Use currentProxy in upload implementation
}

```

### Emitting Custom Events for Cross-Plugin Coordination

Plugins can also emit custom events to signal completion of long-running tasks or to trigger actions in other plugins:

```typescript
import { eventBus } from '../../utils/eventBus';

export function myTransformer(ctx, file) {
  // Perform transformation logic...
  
  eventBus.emit('CUSTOM_TRANSFORM_DONE', { 
    filePath: file,
    timestamp: Date.now() 
  });
}

```

Other plugins listen to `'CUSTOM_TRANSFORM_DONE'` using the same `eventBus.on()` pattern, enabling decoupled workflows where transformers notify uploaders without direct function calls.

## Benefits of the Event-Driven Architecture

The event bus system in PicList-Core provides four critical advantages for plugin communication:

- **Loose coupling** – The core does not need to maintain references to plugin instances or know which components consume configuration changes; modules simply listen for relevant events.
- **Single source of truth** – All subscribers receive identical event payloads, preventing divergent state across the application when configuration updates occur.
- **Extensibility** – New events can be added to `IBusEvent` without refactoring existing modules, and plugins can introduce custom event types for specialized workflows.
- **Testability** – The bus can be mocked or spied upon in unit tests, allowing developers to verify that modules emit or react to events correctly without invoking actual network requests or file operations.

## Summary

- The PicList-Core event bus is a singleton `EventEmitter` exported from [`src/utils/eventBus.ts`](https://github.com/kuingsmile/piclist-core/blob/main/src/utils/eventBus.ts) that provides global pub/sub functionality.
- Core configuration changes are broadcast via `IBusEvent.CONFIG_CHANGE` from [`src/core/PicGo.ts`](https://github.com/kuingsmile/piclist-core/blob/main/src/core/PicGo.ts) whenever `setConfig` updates values.
- The network layer in [`src/lib/Request.ts`](https://github.com/kuingsmile/piclist-core/blob/main/src/lib/Request.ts) listens for these events to synchronize proxy settings dynamically at runtime.
- Plugins participate in PicList-Core plugin communication by importing the shared `eventBus` instance and subscribing or emitting events as needed.
- This architecture eliminates tight coupling between the core and plugins while maintaining real-time state consistency across the application.

## Frequently Asked Questions

### How do I listen to configuration changes in a PicList-Core plugin?

Import the `eventBus` singleton from `../../utils/eventBus` and the `IBusEvent` enum from `../../utils/enum`, then call `eventBus.on(IBusEvent.CONFIG_CHANGE, callback)` within your plugin’s initialization. The callback receives an object with `configName` and `value` properties that you can inspect to update your plugin’s internal state accordingly.

### Can plugins emit custom events through the PicList-Core event bus?

Yes. Any plugin can call `eventBus.emit('CUSTOM_EVENT_NAME', payload)` to broadcast messages to other plugins or internal modules. While built-in events like `CONFIG_CHANGE` are defined in `IBusEvent`, custom events use string identifiers that other plugins can subscribe to using `eventBus.on()`.

### What events are currently available in the PicList-Core event bus?

As of the current implementation, the primary built-in event is `IBusEvent.CONFIG_CHANGE`, defined in [`src/utils/enum.ts`](https://github.com/kuingsmile/piclist-core/blob/main/src/utils/enum.ts). This event fires whenever the core’s configuration store updates. The architecture is extensible, allowing future versions to add lifecycle events for upload start, transformation complete, or error states.

### How does the event bus improve testability in PicList-Core?

The singleton pattern allows test suites to substitute the real event bus with a mock `EventEmitter` or to use Jest’s `spyOn` to monitor emissions. Developers can verify that their plugins correctly subscribe to `CONFIG_CHANGE` or emit custom signals without requiring integration tests that execute actual file uploads or network requests.