Skip to content

Event System

TheMinecraftGuyGuru edited this page Jun 8, 2026 · 6 revisions

Event System

Philosophy

Event-driven everything. Do NOT tightly couple modules.

MuxCore uses events as the primary integration pattern. Modules publish events when state changes; other modules subscribe to react.

Why Events?

Benefit Description
Loose coupling Publishers don't know about subscribers
Extensibility New modules subscribe to existing events without modifying anything
Resilience Subscriber failures don't affect publishers
Observability Every state change is a recorded event
Automation Workflow modules compose events into pipelines
Replay Events can be replayed for testing or recovery

Event Bus

MuxCore provides an in-memory event bus by default. NATS is available as an optional module (eventbus-nats) for distributed messaging.

When using the NATS module:

  • Pub/Sub — Fire and forget events
  • Request/Reply — Synchronous queries
  • Streaming (JetStream) — Ordered, persistent event streams
  • Clustering — Multi-node NATS for HA

Event Structure

type Event struct {
    ID          string
    Type        string    // e.g., "download.completed"
    Source      string    // e.g., "module:downloader-qbittorrent"
    Payload     []byte    // JSON or protobuf
    Metadata    map[string]string
    Timestamp   time.Time
}

Well-Known Event Types

Media Lifecycle

Event Publisher Typical Subscriber
media.requested UI / API Workflow Engine
media.found Indexer Workflow Engine
media.download.approved (planned) Media Manager Downloader
download.started Downloader UI, Notifier
download.progress (planned) Downloader UI
download.completed Downloader Workflow Engine, Verifier
download.failed Downloader Workflow Engine, Notifier
media.verified (planned) Verifier Workflow Engine
media.extracted (planned) Extractor Workflow Engine
media.analyzed Analyzer Workflow Engine, Transcoder
transcode.started Transcoder UI, Notifier
transcode.completed Transcoder Workflow Engine, Library
transcode.failed Transcoder Workflow Engine, Notifier
quality.decision Release Decider Workflow Engine
format.matched Format Matcher Release Decider
content.missing Media Manager Supplementary Content Provider
content.fetched Supplementary Content Provider Media Manager
library.item.added Media Manager UI, Notifier, Playback
library.item.removed Media Manager UI, Playback
storage.tier.transition Storage Module UI, Notifier

Playback

Event Publisher Typical Subscriber
playback.started Playback Module Watch State, Analytics
playback.stopped Playback Module Watch State
playback.progress (planned) Playback Module Watch State
playback.transcode.requested (planned) Playback Module Transcoder

System

Event Publisher Typical Subscriber
module.registered Module Registry, UI
module.unregistered Module Registry, UI
module.degraded Module Registry, Notifier
cluster.node.joined Cluster Module Registry, UI
cluster.node.left Cluster Module Registry, UI
cluster.leader.changed Cluster Module Registry, Scheduler
worker.available (planned) Worker Agent Scheduler
worker.offline (planned) Worker Agent Scheduler
storage.rebalanced (planned) Storage Orchestrator UI
system.backup.completed (planned) Backup Module UI, Notifier

Event-Driven Workflow Example

Movie Request Flow

User clicks "Request Movie"
  → publishes media.requested

Workflow Engine receives media.requested
  → starts "movie-request" workflow

Step 1: Metadata Lookup
  → publishes metadata.lookup (request/reply to metadata provider)

Step 2: Indexer Search
  → publishes indexer.search (request/reply to all indexer modules)

Step 3: Download
  → publishes media.download.approved (consumed by preferred downloader)

Step 4: Wait for download.completed
  → downloader publishes download.completed when done

Step 5: Verification
  → publishes media.verify (request/reply to verifier)

Step 6: Import
  → publishes library.item.added

Step 7: Notify
  → publishes notification.send (consumed by notification modules)

Each step is an event. The workflow engine orchestrates but doesn't implement any step directly.

Subscribing to Events

// In a module's Start() method:
bus.Subscribe(ctx, "download.completed", func(ctx context.Context, event Event) error {
    var payload DownloadCompletedPayload
    json.Unmarshal(event.Payload, &payload)

    // React: start verification, send notification, etc.
    return nil
})

Notification Routing (planned — #70)

Events are routed to notification channels based on event type, not which module published them. Users configure per-event-type rules:

Event type Channels
download.completed Discord
download.failed Discord, Email
media.requested Telegram
system.health.degraded Email, Pushover
transcode.failed Discord, Email
library.item.added Discord
system.backup.completed (silent)

A notification router (core or module) reads these rules and dispatches each event to the matching NotificationProvider modules. The router subscribes to ALL notifiable events; each notification module only receives the events it's been routed.

This means multiple notification modules can coexist — Discord gets download events, Email gets system alerts, Telegram gets request notifications — all from the same event stream, with no module knowing about any other module's configuration.

Event Persistence & Replay

  • JetStream stores events with configurable retention
  • Events can be replayed for debugging or recovery
  • Workflow engines can resume from the last completed step
  • Audit logs are built from the event stream

Anti-Patterns

Don't: Direct Module Calls

// BAD: Tight coupling
client := downloader.NewClient()
client.AddMagnet(magnet)

Do: Event-Driven

// GOOD: Loose coupling
bus.Publish(ctx, Event{
    Type: "media.download.approved",
    Payload: payload,
})

Clone this wiki locally