packages feed

eventium-core-0.6.0: src/Eventium/Telemetry.hs

-- | A generic, framework-free, structured telemetry sink. The host app
-- supplies one interpreter ('Telemetry'); eventium emits typed 'Signal's
-- through it. No logging-framework dependency — a contravariant-style sink over
-- a domain signal type.
module Eventium.Telemetry
  ( Telemetry (..),
    Signal (..),
    ConflictInfo (..),
    silentTelemetry,
  )
where

import Eventium.Store.Types
  ( EventMetadata,
    EventVersion,
    EventWriteResult,
    ExpectedPosition,
  )
import Eventium.UUID (UUID)

-- | A structured telemetry sink. @emit@ runs in the caller's monad @m@.
-- Interpreters MUST NOT throw: a write-path emit may run inside the write
-- transaction, so a throwing interpreter could roll back a committed write.
newtype Telemetry m = Telemetry {emit :: Signal -> m ()}

-- | Everything eventium can report, across all subsystems. One growing closed
-- sum type. This slice introduces only the write-path constructors.
--
-- Write-path constructors carry the versioned-stream key as a 'UUID' (the
-- aggregate id), eventium's own stream identifier — interpreters render it as
-- they see fit.
data Signal
  = -- | Events durably written on the versioned (aggregate) write path:
    -- stream 'UUID', per-event metadata (each carries @eventType@,
    -- @correlationId@, @custom@), and assigned per-stream versions + global
    -- positions.
    EventsPersisted !UUID ![EventMetadata] !EventWriteResult
  | -- | An expected-position (optimistic concurrency) check failed; nothing
    -- was written.
    WriteConflict !UUID !ConflictInfo
  deriving (Show, Eq)

-- | Optimistic-concurrency conflict detail. @expected@ is the caller's asserted
-- position; @actual@ is the stream's real end version.
data ConflictInfo = ConflictInfo
  { expected :: !(ExpectedPosition EventVersion),
    actual :: !EventVersion
  }
  deriving (Show, Eq)

-- | No-op sink — the default everywhere; guarantees silent, zero-cost behaviour
-- unless the app opts in.
silentTelemetry :: (Applicative m) => Telemetry m
silentTelemetry = Telemetry (const (pure ()))