package integration import ( "context" "errors" roomeventsv1 "hyapp.local/api/proto/events/room/v1" ) // CompositeOutboxPublisher fans one committed room outbox event to every // asynchronous consumer. Consumers must be idempotent by event_id because a // partial failure retries the full fanout on the next worker pass. type CompositeOutboxPublisher struct { publishers []OutboxPublisher } // NewCompositeOutboxPublisher removes nil publishers and returns a no-op when // no asynchronous consumers are configured. func NewCompositeOutboxPublisher(publishers ...OutboxPublisher) OutboxPublisher { filtered := make([]OutboxPublisher, 0, len(publishers)) for _, publisher := range publishers { if publisher != nil { filtered = append(filtered, publisher) } } if len(filtered) == 0 { return NewNoopOutboxPublisher() } return &CompositeOutboxPublisher{publishers: filtered} } // PublishOutboxEvent calls every configured consumer before returning. Returning // a joined error keeps the event retryable until every consumer has accepted it. func (p *CompositeOutboxPublisher) PublishOutboxEvent(ctx context.Context, envelope *roomeventsv1.EventEnvelope) error { if p == nil || len(p.publishers) == 0 { return nil } var joined error for _, publisher := range p.publishers { if err := publisher.PublishOutboxEvent(ctx, envelope); err != nil { joined = errors.Join(joined, err) } } return joined }