package service import ( "sync" "sync/atomic" "time" ) const requestEventBufSize = 64 // RequestEvent is published for every gateway dispatch completion. type RequestEvent struct { Timestamp time.Time `json:"timestamp"` Method string `json:"method"` Path string `json:"path"` Model string `json:"model"` AccountID int64 `json:"account_id"` // Status is "success", "error", or "rate_limited". Status string `json:"status"` LatencyMS int64 `json:"latency_ms"` } // RequestEventBus is a fan-out hub for real-time request events. // Publishers call Publish; subscribers call Subscribe/Unsubscribe. // Each subscriber gets its own buffered channel. If the buffer is full // the event is dropped for that subscriber (non-blocking publish). type RequestEventBus struct { mu sync.RWMutex subscribers map[uint64]chan RequestEvent nextID atomic.Uint64 } func NewRequestEventBus() *RequestEventBus { return &RequestEventBus{ subscribers: make(map[uint64]chan RequestEvent), } } // Subscribe registers a new subscriber and returns its ID and a receive-only channel. func (b *RequestEventBus) Subscribe() (uint64, <-chan RequestEvent) { id := b.nextID.Add(1) ch := make(chan RequestEvent, requestEventBufSize) b.mu.Lock() b.subscribers[id] = ch b.mu.Unlock() return id, ch } // Unsubscribe removes a subscriber and closes its channel. func (b *RequestEventBus) Unsubscribe(id uint64) { b.mu.Lock() ch, ok := b.subscribers[id] if ok { delete(b.subscribers, id) } b.mu.Unlock() if ok { close(ch) } } // Publish sends an event to all current subscribers without blocking. func (b *RequestEventBus) Publish(e RequestEvent) { if b == nil { return } b.mu.RLock() defer b.mu.RUnlock() for _, ch := range b.subscribers { select { case ch <- e: default: } } }