-
Notifications
You must be signed in to change notification settings - Fork 0
Event System
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.
| 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 |
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
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
}| 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 |
| 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 |
| 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 |
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.
// 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
})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.
- 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
// BAD: Tight coupling
client := downloader.NewClient()
client.AddMagnet(magnet)// GOOD: Loose coupling
bus.Publish(ctx, Event{
Type: "media.download.approved",
Payload: payload,
})