{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE RequiredTypeArguments #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeFamilyDependencies #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE NoFieldSelectors #-}

{- |
Module      : Hindsight.Store
Description : Event store interface and common data types
Copyright   : (c) 2024
License     : BSD3
Maintainer  : maintainer@example.com
Stability   : experimental

This module defines the core event store interface and types used across
all storage backends.

= Overview

An event store is a append-only log of events organized into streams.
Each stream represents the lifecycle of a single aggregate or entity.

= Basic Usage

@
import Hindsight.Store
import Hindsight.Store.Memory (newMemoryStore)

-- Create a store
store <- newMemoryStore

-- Insert events
let batch = StreamWrite
      { expectedVersion = NoStream
      , events = [mkEvent \"user_created\" payload]
      }
result <- insertEvents store Nothing (Map.singleton streamId batch)

-- Subscribe to events
handle <- subscribe store matcher selector
@

= Key Concepts

* __Streams__: Ordered sequences of events for a single entity
* __Cursors__: Opaque positions in the global event log
* __Subscriptions__: Real-time notifications of new events
* __Version Expectations__: Optimistic concurrency control for writes
-}
module Hindsight.Store (
    -- * Core Identifiers

    -- | Unique identifiers for streams, events, and correlations.
    StreamId (..),
    EventId (..),
    CorrelationId (..),
    StreamVersion (..),

    -- * Event Store Interface

    {- | The main type class implemented by all storage backends.

    The 'EventStore' class includes the 'StoreConstraints' type family,
    which defines additional constraints required by each backend.
    -}
    EventStore (..),

    -- ** Backend Types

    {- | Backend-specific handle and cursor types.

    Each backend defines its own 'Cursor' and 'BackendHandle' via type families.
    -}
    BackendHandle,
    Cursor,

    -- * Event Operations

    -- | Types for inserting and querying events.

    -- ** Insertion
    StreamWrite (..),
    Transaction (..),
    InsertionResult (..),
    InsertionSuccess (..),

    -- ** Transaction Helpers

    {- | Helper functions for constructing transactions.

    The 'singleEvent' and 'multiEvent' functions are the primary API,
    requiring explicit version expectations. Convenience helpers like
    'appendAfterAny' are provided for common patterns.
    -}
    singleEvent,
    multiEvent,
    appendAfterAny,
    appendToOrCreateStream,
    fromWrites,

    -- ** Version Control

    {- | Optimistic concurrency control for preventing conflicts.

    When inserting events, you can specify version expectations to ensure
    the stream hasn't changed since you read it. This implements optimistic
    locking without holding database locks during business logic execution.

    See: <https://en.wikipedia.org/wiki/Optimistic_concurrency_control>
    -}
    ExpectedVersion (..),

    -- ** Errors
    EventStoreError (..),
    ErrorInfo (..),
    ConsistencyErrorInfo (..),
    VersionMismatch (..),
    HandlerException (..),

    -- * Event Subscriptions

    -- | Real-time streaming of events as they're inserted.

    -- ** Subscription Configuration
    EventSelector (..),
    StreamSelector (..),
    StartupPosition (..),

    -- ** Subscription Control
    SubscriptionHandle (..),
    SubscriptionResult (..),

    -- * Event Handling

    -- | Processing events in subscriptions.

    -- ** Event Envelopes

    -- | Events with full metadata from the store.
    EventEnvelope (..),

    -- ** Event Matchers

    {- | Type-safe pattern matching on event types.

    Build matchers using 'match' and the '(:?)' operator:

    @
    matcher = match \"user_created\" handleUserCreated
       :? match \"user_updated\" handleUserUpdated
       :? MatchEnd
    @
    -}
    EventHandler,
    EventMatcher (..),
    match,
)
where

import Control.Exception (Exception, SomeException, displayException)
import Data.Aeson (FromJSON, ToJSON)
import Data.Int (Int64)
import Data.Kind (Constraint, Type)
import Data.Map.Strict (Map)
import Data.Map.Strict qualified as Map
import Data.Proxy (Proxy (..))
import Data.Text (Text)
import Data.Text qualified as T
import Data.Time (UTCTime)
import Data.Typeable (Typeable)
import Data.UUID (UUID)
import GHC.Generics (Generic)
import GHC.TypeLits (Symbol)
import Hindsight.Events

{- | Unique identifier for an event stream.

Streams are logical groupings of related events, typically representing
the lifecycle of a single aggregate or entity.
-}
newtype StreamId = StreamId {StreamId -> UUID
toUUID :: UUID}
    deriving (StreamId -> StreamId -> Bool
(StreamId -> StreamId -> Bool)
-> (StreamId -> StreamId -> Bool) -> Eq StreamId
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: StreamId -> StreamId -> Bool
== :: StreamId -> StreamId -> Bool
$c/= :: StreamId -> StreamId -> Bool
/= :: StreamId -> StreamId -> Bool
Eq, Eq StreamId
Eq StreamId =>
(StreamId -> StreamId -> Ordering)
-> (StreamId -> StreamId -> Bool)
-> (StreamId -> StreamId -> Bool)
-> (StreamId -> StreamId -> Bool)
-> (StreamId -> StreamId -> Bool)
-> (StreamId -> StreamId -> StreamId)
-> (StreamId -> StreamId -> StreamId)
-> Ord StreamId
StreamId -> StreamId -> Bool
StreamId -> StreamId -> Ordering
StreamId -> StreamId -> StreamId
forall a.
Eq a =>
(a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
$ccompare :: StreamId -> StreamId -> Ordering
compare :: StreamId -> StreamId -> Ordering
$c< :: StreamId -> StreamId -> Bool
< :: StreamId -> StreamId -> Bool
$c<= :: StreamId -> StreamId -> Bool
<= :: StreamId -> StreamId -> Bool
$c> :: StreamId -> StreamId -> Bool
> :: StreamId -> StreamId -> Bool
$c>= :: StreamId -> StreamId -> Bool
>= :: StreamId -> StreamId -> Bool
$cmax :: StreamId -> StreamId -> StreamId
max :: StreamId -> StreamId -> StreamId
$cmin :: StreamId -> StreamId -> StreamId
min :: StreamId -> StreamId -> StreamId
Ord, Int -> StreamId -> ShowS
[StreamId] -> ShowS
StreamId -> String
(Int -> StreamId -> ShowS)
-> (StreamId -> String) -> ([StreamId] -> ShowS) -> Show StreamId
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> StreamId -> ShowS
showsPrec :: Int -> StreamId -> ShowS
$cshow :: StreamId -> String
show :: StreamId -> String
$cshowList :: [StreamId] -> ShowS
showList :: [StreamId] -> ShowS
Show, Maybe StreamId
Value -> Parser [StreamId]
Value -> Parser StreamId
(Value -> Parser StreamId)
-> (Value -> Parser [StreamId])
-> Maybe StreamId
-> FromJSON StreamId
forall a.
(Value -> Parser a)
-> (Value -> Parser [a]) -> Maybe a -> FromJSON a
$cparseJSON :: Value -> Parser StreamId
parseJSON :: Value -> Parser StreamId
$cparseJSONList :: Value -> Parser [StreamId]
parseJSONList :: Value -> Parser [StreamId]
$comittedField :: Maybe StreamId
omittedField :: Maybe StreamId
FromJSON, [StreamId] -> Value
[StreamId] -> Encoding
StreamId -> Bool
StreamId -> Value
StreamId -> Encoding
(StreamId -> Value)
-> (StreamId -> Encoding)
-> ([StreamId] -> Value)
-> ([StreamId] -> Encoding)
-> (StreamId -> Bool)
-> ToJSON StreamId
forall a.
(a -> Value)
-> (a -> Encoding)
-> ([a] -> Value)
-> ([a] -> Encoding)
-> (a -> Bool)
-> ToJSON a
$ctoJSON :: StreamId -> Value
toJSON :: StreamId -> Value
$ctoEncoding :: StreamId -> Encoding
toEncoding :: StreamId -> Encoding
$ctoJSONList :: [StreamId] -> Value
toJSONList :: [StreamId] -> Value
$ctoEncodingList :: [StreamId] -> Encoding
toEncodingList :: [StreamId] -> Encoding
$comitField :: StreamId -> Bool
omitField :: StreamId -> Bool
ToJSON)

-- | Unique identifier for an individual event.
newtype EventId = EventId {EventId -> UUID
toUUID :: UUID}
    deriving (EventId -> EventId -> Bool
(EventId -> EventId -> Bool)
-> (EventId -> EventId -> Bool) -> Eq EventId
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: EventId -> EventId -> Bool
== :: EventId -> EventId -> Bool
$c/= :: EventId -> EventId -> Bool
/= :: EventId -> EventId -> Bool
Eq, Eq EventId
Eq EventId =>
(EventId -> EventId -> Ordering)
-> (EventId -> EventId -> Bool)
-> (EventId -> EventId -> Bool)
-> (EventId -> EventId -> Bool)
-> (EventId -> EventId -> Bool)
-> (EventId -> EventId -> EventId)
-> (EventId -> EventId -> EventId)
-> Ord EventId
EventId -> EventId -> Bool
EventId -> EventId -> Ordering
EventId -> EventId -> EventId
forall a.
Eq a =>
(a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
$ccompare :: EventId -> EventId -> Ordering
compare :: EventId -> EventId -> Ordering
$c< :: EventId -> EventId -> Bool
< :: EventId -> EventId -> Bool
$c<= :: EventId -> EventId -> Bool
<= :: EventId -> EventId -> Bool
$c> :: EventId -> EventId -> Bool
> :: EventId -> EventId -> Bool
$c>= :: EventId -> EventId -> Bool
>= :: EventId -> EventId -> Bool
$cmax :: EventId -> EventId -> EventId
max :: EventId -> EventId -> EventId
$cmin :: EventId -> EventId -> EventId
min :: EventId -> EventId -> EventId
Ord, Int -> EventId -> ShowS
[EventId] -> ShowS
EventId -> String
(Int -> EventId -> ShowS)
-> (EventId -> String) -> ([EventId] -> ShowS) -> Show EventId
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> EventId -> ShowS
showsPrec :: Int -> EventId -> ShowS
$cshow :: EventId -> String
show :: EventId -> String
$cshowList :: [EventId] -> ShowS
showList :: [EventId] -> ShowS
Show, Maybe EventId
Value -> Parser [EventId]
Value -> Parser EventId
(Value -> Parser EventId)
-> (Value -> Parser [EventId]) -> Maybe EventId -> FromJSON EventId
forall a.
(Value -> Parser a)
-> (Value -> Parser [a]) -> Maybe a -> FromJSON a
$cparseJSON :: Value -> Parser EventId
parseJSON :: Value -> Parser EventId
$cparseJSONList :: Value -> Parser [EventId]
parseJSONList :: Value -> Parser [EventId]
$comittedField :: Maybe EventId
omittedField :: Maybe EventId
FromJSON, [EventId] -> Value
[EventId] -> Encoding
EventId -> Bool
EventId -> Value
EventId -> Encoding
(EventId -> Value)
-> (EventId -> Encoding)
-> ([EventId] -> Value)
-> ([EventId] -> Encoding)
-> (EventId -> Bool)
-> ToJSON EventId
forall a.
(a -> Value)
-> (a -> Encoding)
-> ([a] -> Value)
-> ([a] -> Encoding)
-> (a -> Bool)
-> ToJSON a
$ctoJSON :: EventId -> Value
toJSON :: EventId -> Value
$ctoEncoding :: EventId -> Encoding
toEncoding :: EventId -> Encoding
$ctoJSONList :: [EventId] -> Value
toJSONList :: [EventId] -> Value
$ctoEncodingList :: [EventId] -> Encoding
toEncodingList :: [EventId] -> Encoding
$comitField :: EventId -> Bool
omitField :: EventId -> Bool
ToJSON)

{- | Correlation identifier for tracking related events across streams.

Used to trace causally-related events that span multiple streams
or external system boundaries.
-}
newtype CorrelationId = CorrelationId {CorrelationId -> UUID
toUUID :: UUID}
    deriving (CorrelationId -> CorrelationId -> Bool
(CorrelationId -> CorrelationId -> Bool)
-> (CorrelationId -> CorrelationId -> Bool) -> Eq CorrelationId
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: CorrelationId -> CorrelationId -> Bool
== :: CorrelationId -> CorrelationId -> Bool
$c/= :: CorrelationId -> CorrelationId -> Bool
/= :: CorrelationId -> CorrelationId -> Bool
Eq, Eq CorrelationId
Eq CorrelationId =>
(CorrelationId -> CorrelationId -> Ordering)
-> (CorrelationId -> CorrelationId -> Bool)
-> (CorrelationId -> CorrelationId -> Bool)
-> (CorrelationId -> CorrelationId -> Bool)
-> (CorrelationId -> CorrelationId -> Bool)
-> (CorrelationId -> CorrelationId -> CorrelationId)
-> (CorrelationId -> CorrelationId -> CorrelationId)
-> Ord CorrelationId
CorrelationId -> CorrelationId -> Bool
CorrelationId -> CorrelationId -> Ordering
CorrelationId -> CorrelationId -> CorrelationId
forall a.
Eq a =>
(a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
$ccompare :: CorrelationId -> CorrelationId -> Ordering
compare :: CorrelationId -> CorrelationId -> Ordering
$c< :: CorrelationId -> CorrelationId -> Bool
< :: CorrelationId -> CorrelationId -> Bool
$c<= :: CorrelationId -> CorrelationId -> Bool
<= :: CorrelationId -> CorrelationId -> Bool
$c> :: CorrelationId -> CorrelationId -> Bool
> :: CorrelationId -> CorrelationId -> Bool
$c>= :: CorrelationId -> CorrelationId -> Bool
>= :: CorrelationId -> CorrelationId -> Bool
$cmax :: CorrelationId -> CorrelationId -> CorrelationId
max :: CorrelationId -> CorrelationId -> CorrelationId
$cmin :: CorrelationId -> CorrelationId -> CorrelationId
min :: CorrelationId -> CorrelationId -> CorrelationId
Ord, Int -> CorrelationId -> ShowS
[CorrelationId] -> ShowS
CorrelationId -> String
(Int -> CorrelationId -> ShowS)
-> (CorrelationId -> String)
-> ([CorrelationId] -> ShowS)
-> Show CorrelationId
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> CorrelationId -> ShowS
showsPrec :: Int -> CorrelationId -> ShowS
$cshow :: CorrelationId -> String
show :: CorrelationId -> String
$cshowList :: [CorrelationId] -> ShowS
showList :: [CorrelationId] -> ShowS
Show, Maybe CorrelationId
Value -> Parser [CorrelationId]
Value -> Parser CorrelationId
(Value -> Parser CorrelationId)
-> (Value -> Parser [CorrelationId])
-> Maybe CorrelationId
-> FromJSON CorrelationId
forall a.
(Value -> Parser a)
-> (Value -> Parser [a]) -> Maybe a -> FromJSON a
$cparseJSON :: Value -> Parser CorrelationId
parseJSON :: Value -> Parser CorrelationId
$cparseJSONList :: Value -> Parser [CorrelationId]
parseJSONList :: Value -> Parser [CorrelationId]
$comittedField :: Maybe CorrelationId
omittedField :: Maybe CorrelationId
FromJSON, [CorrelationId] -> Value
[CorrelationId] -> Encoding
CorrelationId -> Bool
CorrelationId -> Value
CorrelationId -> Encoding
(CorrelationId -> Value)
-> (CorrelationId -> Encoding)
-> ([CorrelationId] -> Value)
-> ([CorrelationId] -> Encoding)
-> (CorrelationId -> Bool)
-> ToJSON CorrelationId
forall a.
(a -> Value)
-> (a -> Encoding)
-> ([a] -> Value)
-> ([a] -> Encoding)
-> (a -> Bool)
-> ToJSON a
$ctoJSON :: CorrelationId -> Value
toJSON :: CorrelationId -> Value
$ctoEncoding :: CorrelationId -> Encoding
toEncoding :: CorrelationId -> Encoding
$ctoJSONList :: [CorrelationId] -> Value
toJSONList :: [CorrelationId] -> Value
$ctoEncodingList :: [CorrelationId] -> Encoding
toEncodingList :: [CorrelationId] -> Encoding
$comitField :: CorrelationId -> Bool
omitField :: CorrelationId -> Bool
ToJSON)

-- | Local stream version - simple incrementing number per stream (1, 2, 3, ...)
newtype StreamVersion = StreamVersion Int64
    deriving (Int -> StreamVersion -> ShowS
[StreamVersion] -> ShowS
StreamVersion -> String
(Int -> StreamVersion -> ShowS)
-> (StreamVersion -> String)
-> ([StreamVersion] -> ShowS)
-> Show StreamVersion
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> StreamVersion -> ShowS
showsPrec :: Int -> StreamVersion -> ShowS
$cshow :: StreamVersion -> String
show :: StreamVersion -> String
$cshowList :: [StreamVersion] -> ShowS
showList :: [StreamVersion] -> ShowS
Show, StreamVersion -> StreamVersion -> Bool
(StreamVersion -> StreamVersion -> Bool)
-> (StreamVersion -> StreamVersion -> Bool) -> Eq StreamVersion
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: StreamVersion -> StreamVersion -> Bool
== :: StreamVersion -> StreamVersion -> Bool
$c/= :: StreamVersion -> StreamVersion -> Bool
/= :: StreamVersion -> StreamVersion -> Bool
Eq, Eq StreamVersion
Eq StreamVersion =>
(StreamVersion -> StreamVersion -> Ordering)
-> (StreamVersion -> StreamVersion -> Bool)
-> (StreamVersion -> StreamVersion -> Bool)
-> (StreamVersion -> StreamVersion -> Bool)
-> (StreamVersion -> StreamVersion -> Bool)
-> (StreamVersion -> StreamVersion -> StreamVersion)
-> (StreamVersion -> StreamVersion -> StreamVersion)
-> Ord StreamVersion
StreamVersion -> StreamVersion -> Bool
StreamVersion -> StreamVersion -> Ordering
StreamVersion -> StreamVersion -> StreamVersion
forall a.
Eq a =>
(a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
$ccompare :: StreamVersion -> StreamVersion -> Ordering
compare :: StreamVersion -> StreamVersion -> Ordering
$c< :: StreamVersion -> StreamVersion -> Bool
< :: StreamVersion -> StreamVersion -> Bool
$c<= :: StreamVersion -> StreamVersion -> Bool
<= :: StreamVersion -> StreamVersion -> Bool
$c> :: StreamVersion -> StreamVersion -> Bool
> :: StreamVersion -> StreamVersion -> Bool
$c>= :: StreamVersion -> StreamVersion -> Bool
>= :: StreamVersion -> StreamVersion -> Bool
$cmax :: StreamVersion -> StreamVersion -> StreamVersion
max :: StreamVersion -> StreamVersion -> StreamVersion
$cmin :: StreamVersion -> StreamVersion -> StreamVersion
min :: StreamVersion -> StreamVersion -> StreamVersion
Ord, (forall x. StreamVersion -> Rep StreamVersion x)
-> (forall x. Rep StreamVersion x -> StreamVersion)
-> Generic StreamVersion
forall x. Rep StreamVersion x -> StreamVersion
forall x. StreamVersion -> Rep StreamVersion x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cfrom :: forall x. StreamVersion -> Rep StreamVersion x
from :: forall x. StreamVersion -> Rep StreamVersion x
$cto :: forall x. Rep StreamVersion x -> StreamVersion
to :: forall x. Rep StreamVersion x -> StreamVersion
Generic, Maybe StreamVersion
Value -> Parser [StreamVersion]
Value -> Parser StreamVersion
(Value -> Parser StreamVersion)
-> (Value -> Parser [StreamVersion])
-> Maybe StreamVersion
-> FromJSON StreamVersion
forall a.
(Value -> Parser a)
-> (Value -> Parser [a]) -> Maybe a -> FromJSON a
$cparseJSON :: Value -> Parser StreamVersion
parseJSON :: Value -> Parser StreamVersion
$cparseJSONList :: Value -> Parser [StreamVersion]
parseJSONList :: Value -> Parser [StreamVersion]
$comittedField :: Maybe StreamVersion
omittedField :: Maybe StreamVersion
FromJSON, [StreamVersion] -> Value
[StreamVersion] -> Encoding
StreamVersion -> Bool
StreamVersion -> Value
StreamVersion -> Encoding
(StreamVersion -> Value)
-> (StreamVersion -> Encoding)
-> ([StreamVersion] -> Value)
-> ([StreamVersion] -> Encoding)
-> (StreamVersion -> Bool)
-> ToJSON StreamVersion
forall a.
(a -> Value)
-> (a -> Encoding)
-> ([a] -> Value)
-> ([a] -> Encoding)
-> (a -> Bool)
-> ToJSON a
$ctoJSON :: StreamVersion -> Value
toJSON :: StreamVersion -> Value
$ctoEncoding :: StreamVersion -> Encoding
toEncoding :: StreamVersion -> Encoding
$ctoJSONList :: [StreamVersion] -> Value
toJSONList :: [StreamVersion] -> Value
$ctoEncodingList :: [StreamVersion] -> Encoding
toEncodingList :: [StreamVersion] -> Encoding
$comitField :: StreamVersion -> Bool
omitField :: StreamVersion -> Bool
ToJSON, Integer -> StreamVersion
StreamVersion -> StreamVersion
StreamVersion -> StreamVersion -> StreamVersion
(StreamVersion -> StreamVersion -> StreamVersion)
-> (StreamVersion -> StreamVersion -> StreamVersion)
-> (StreamVersion -> StreamVersion -> StreamVersion)
-> (StreamVersion -> StreamVersion)
-> (StreamVersion -> StreamVersion)
-> (StreamVersion -> StreamVersion)
-> (Integer -> StreamVersion)
-> Num StreamVersion
forall a.
(a -> a -> a)
-> (a -> a -> a)
-> (a -> a -> a)
-> (a -> a)
-> (a -> a)
-> (a -> a)
-> (Integer -> a)
-> Num a
$c+ :: StreamVersion -> StreamVersion -> StreamVersion
+ :: StreamVersion -> StreamVersion -> StreamVersion
$c- :: StreamVersion -> StreamVersion -> StreamVersion
- :: StreamVersion -> StreamVersion -> StreamVersion
$c* :: StreamVersion -> StreamVersion -> StreamVersion
* :: StreamVersion -> StreamVersion -> StreamVersion
$cnegate :: StreamVersion -> StreamVersion
negate :: StreamVersion -> StreamVersion
$cabs :: StreamVersion -> StreamVersion
abs :: StreamVersion -> StreamVersion
$csignum :: StreamVersion -> StreamVersion
signum :: StreamVersion -> StreamVersion
$cfromInteger :: Integer -> StreamVersion
fromInteger :: Integer -> StreamVersion
Num, Int -> StreamVersion
StreamVersion -> Int
StreamVersion -> [StreamVersion]
StreamVersion -> StreamVersion
StreamVersion -> StreamVersion -> [StreamVersion]
StreamVersion -> StreamVersion -> StreamVersion -> [StreamVersion]
(StreamVersion -> StreamVersion)
-> (StreamVersion -> StreamVersion)
-> (Int -> StreamVersion)
-> (StreamVersion -> Int)
-> (StreamVersion -> [StreamVersion])
-> (StreamVersion -> StreamVersion -> [StreamVersion])
-> (StreamVersion -> StreamVersion -> [StreamVersion])
-> (StreamVersion
    -> StreamVersion -> StreamVersion -> [StreamVersion])
-> Enum StreamVersion
forall a.
(a -> a)
-> (a -> a)
-> (Int -> a)
-> (a -> Int)
-> (a -> [a])
-> (a -> a -> [a])
-> (a -> a -> [a])
-> (a -> a -> a -> [a])
-> Enum a
$csucc :: StreamVersion -> StreamVersion
succ :: StreamVersion -> StreamVersion
$cpred :: StreamVersion -> StreamVersion
pred :: StreamVersion -> StreamVersion
$ctoEnum :: Int -> StreamVersion
toEnum :: Int -> StreamVersion
$cfromEnum :: StreamVersion -> Int
fromEnum :: StreamVersion -> Int
$cenumFrom :: StreamVersion -> [StreamVersion]
enumFrom :: StreamVersion -> [StreamVersion]
$cenumFromThen :: StreamVersion -> StreamVersion -> [StreamVersion]
enumFromThen :: StreamVersion -> StreamVersion -> [StreamVersion]
$cenumFromTo :: StreamVersion -> StreamVersion -> [StreamVersion]
enumFromTo :: StreamVersion -> StreamVersion -> [StreamVersion]
$cenumFromThenTo :: StreamVersion -> StreamVersion -> StreamVersion -> [StreamVersion]
enumFromThenTo :: StreamVersion -> StreamVersion -> StreamVersion -> [StreamVersion]
Enum)

-- | General error information with optional exception details.
data ErrorInfo = ErrorInfo
    { ErrorInfo -> Text
errorMessage :: Text
    -- ^ Human-readable error message
    , ErrorInfo -> Maybe SomeException
exception :: Maybe SomeException
    -- ^ Optional underlying exception
    }
    deriving (Int -> ErrorInfo -> ShowS
[ErrorInfo] -> ShowS
ErrorInfo -> String
(Int -> ErrorInfo -> ShowS)
-> (ErrorInfo -> String)
-> ([ErrorInfo] -> ShowS)
-> Show ErrorInfo
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> ErrorInfo -> ShowS
showsPrec :: Int -> ErrorInfo -> ShowS
$cshow :: ErrorInfo -> String
show :: ErrorInfo -> String
$cshowList :: [ErrorInfo] -> ShowS
showList :: [ErrorInfo] -> ShowS
Show)

-- | Consistency violation details containing all version mismatches.
data ConsistencyErrorInfo backend = ConsistencyErrorInfo [VersionMismatch backend]

-- | Details of a version expectation failure.
data VersionMismatch backend = VersionMismatch
    { forall backend. VersionMismatch backend -> StreamId
streamId :: StreamId
    -- ^ Stream that failed the version check
    , forall backend. VersionMismatch backend -> ExpectedVersion backend
expectedVersion :: ExpectedVersion backend
    -- ^ The version expectation that was not met
    , forall backend. VersionMismatch backend -> Maybe (Cursor backend)
actualVersion :: Maybe (Cursor backend)
    -- ^ The actual version found (Nothing if stream doesn't exist)
    }

-- | Possible errors when interacting with the event store.
data EventStoreError backend
    = -- | Version expectation failures
      ConsistencyError (ConsistencyErrorInfo backend)
    | -- | Storage backend errors
      BackendError ErrorInfo
    | -- | Other application errors
      OtherError ErrorInfo

deriving instance (Show (Cursor backend)) => Show (VersionMismatch backend)

deriving instance (Show (Cursor backend)) => Show (ConsistencyErrorInfo backend)

deriving instance (Show (Cursor backend)) => Show (EventStoreError backend)

{- | Exception thrown when an event handler fails during subscription processing.

This exception wraps the original exception with rich event context to aid debugging.
When a handler throws an exception, the subscription will die immediately (fail-fast),
and this enriched exception will be available via the Async handle.

The handler exception represents a bug in event processing code. Higher-level code
(such as projection managers) can implement retry logic if desired.
-}
data HandlerException = HandlerException
    { HandlerException -> SomeException
originalException :: SomeException
    -- ^ The actual exception that was thrown
    , HandlerException -> Text
failedEventPosition :: Text
    -- ^ Cursor position where it failed (serialized)
    , HandlerException -> EventId
failedEventId :: EventId
    -- ^ Unique identifier of the failed event
    , HandlerException -> Text
failedEventName :: Text
    -- ^ The name of the event that failed
    , HandlerException -> StreamId
failedEventStreamId :: StreamId
    -- ^ Which stream the event came from
    , HandlerException -> StreamVersion
failedEventStreamVersion :: StreamVersion
    -- ^ Local version within the stream
    , HandlerException -> Maybe CorrelationId
failedEventCorrelationId :: Maybe CorrelationId
    -- ^ Correlation ID if present
    , HandlerException -> UTCTime
failedEventCreatedAt :: UTCTime
    -- ^ When the event was stored
    }
    deriving (Typeable)

instance Show HandlerException where
    show :: HandlerException -> String
show HandlerException
e =
        String
"Handler exception at position "
            String -> ShowS
forall a. Semigroup a => a -> a -> a
<> Text -> String
T.unpack HandlerException
e.failedEventPosition
            String -> ShowS
forall a. Semigroup a => a -> a -> a
<> String
" for event '"
            String -> ShowS
forall a. Semigroup a => a -> a -> a
<> Text -> String
T.unpack HandlerException
e.failedEventName
            String -> ShowS
forall a. Semigroup a => a -> a -> a
<> String
"'"
            String -> ShowS
forall a. Semigroup a => a -> a -> a
<> String
" (eventId: "
            String -> ShowS
forall a. Semigroup a => a -> a -> a
<> EventId -> String
forall a. Show a => a -> String
show HandlerException
e.failedEventId
            String -> ShowS
forall a. Semigroup a => a -> a -> a
<> String
")"
            String -> ShowS
forall a. Semigroup a => a -> a -> a
<> String
" in stream "
            String -> ShowS
forall a. Semigroup a => a -> a -> a
<> StreamId -> String
forall a. Show a => a -> String
show HandlerException
e.failedEventStreamId
            String -> ShowS
forall a. Semigroup a => a -> a -> a
<> String
" (stream version: "
            String -> ShowS
forall a. Semigroup a => a -> a -> a
<> StreamVersion -> String
forall a. Show a => a -> String
show HandlerException
e.failedEventStreamVersion
            String -> ShowS
forall a. Semigroup a => a -> a -> a
<> String
")"
            String -> ShowS
forall a. Semigroup a => a -> a -> a
<> String
": "
            String -> ShowS
forall a. Semigroup a => a -> a -> a
<> SomeException -> String
forall e. Exception e => e -> String
displayException HandlerException
e.originalException

instance Exception HandlerException

{- | Success data from an event insertion operation.

Contains the global cursor position of the last event inserted
and the per-stream cursor positions.
-}
data InsertionSuccess backend = InsertionSuccess
    { forall backend. InsertionSuccess backend -> Cursor backend
finalCursor :: Cursor backend
    -- ^ Global position of last inserted event
    , forall backend.
InsertionSuccess backend -> Map StreamId (Cursor backend)
streamCursors :: Map StreamId (Cursor backend)
    -- ^ Per-stream final cursors
    }

-- | Result of an event insertion operation.
data InsertionResult backend
    = -- | Success with cursor information
      SuccessfulInsertion (InsertionSuccess backend)
    | -- | Failure with error details
      FailedInsertion (EventStoreError backend)

-- | Control flow for event subscriptions.
data SubscriptionResult
    = -- | Stop processing and cancel the subscription
      Stop
    | -- | Continue processing subsequent events
      Continue
    deriving (SubscriptionResult -> SubscriptionResult -> Bool
(SubscriptionResult -> SubscriptionResult -> Bool)
-> (SubscriptionResult -> SubscriptionResult -> Bool)
-> Eq SubscriptionResult
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: SubscriptionResult -> SubscriptionResult -> Bool
== :: SubscriptionResult -> SubscriptionResult -> Bool
$c/= :: SubscriptionResult -> SubscriptionResult -> Bool
/= :: SubscriptionResult -> SubscriptionResult -> Bool
Eq, Int -> SubscriptionResult -> ShowS
[SubscriptionResult] -> ShowS
SubscriptionResult -> String
(Int -> SubscriptionResult -> ShowS)
-> (SubscriptionResult -> String)
-> ([SubscriptionResult] -> ShowS)
-> Show SubscriptionResult
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> SubscriptionResult -> ShowS
showsPrec :: Int -> SubscriptionResult -> ShowS
$cshow :: SubscriptionResult -> String
show :: SubscriptionResult -> String
$cshowList :: [SubscriptionResult] -> ShowS
showList :: [SubscriptionResult] -> ShowS
Show)

-- | Handle for managing a subscription lifecycle.
data SubscriptionHandle backend = SubscriptionHandle
    { forall {k} (backend :: k). SubscriptionHandle backend -> IO ()
cancel :: IO ()
    -- ^ Cancel the subscription
    , forall {k} (backend :: k). SubscriptionHandle backend -> IO ()
wait :: IO ()
    -- ^ Wait for the subscription to complete or fail. Re-throws any exception from the subscription thread. Useful for testing (to observe handler exceptions) and production (to monitor subscription health).
    }

-- | Event with full metadata as retrieved from the store.
data EventEnvelope event backend = EventWithMetadata
    { forall (event :: Symbol) backend.
EventEnvelope event backend -> Cursor backend
position :: Cursor backend
    -- ^ Global position in the event store
    , forall (event :: Symbol) backend.
EventEnvelope event backend -> EventId
eventId :: EventId
    -- ^ Unique event identifier
    , forall (event :: Symbol) backend.
EventEnvelope event backend -> StreamId
streamId :: StreamId
    -- ^ Stream this event belongs to
    , forall (event :: Symbol) backend.
EventEnvelope event backend -> StreamVersion
streamVersion :: StreamVersion
    -- ^ Local version within the stream (1, 2, 3, ...)
    , forall (event :: Symbol) backend.
EventEnvelope event backend -> Maybe CorrelationId
correlationId :: Maybe CorrelationId
    -- ^ Optional correlation ID
    , forall (event :: Symbol) backend.
EventEnvelope event backend -> UTCTime
createdAt :: UTCTime
    -- ^ Timestamp when event was stored
    , forall (event :: Symbol) backend.
EventEnvelope event backend -> CurrentPayloadType event
payload :: CurrentPayloadType event
    -- ^ The actual event data
    }

deriving instance (Show (CurrentPayloadType event), Show (Cursor backend)) => Show (EventEnvelope event backend)

-- | Criteria for selecting events in a subscription.
data EventSelector backend = EventSelector
    { forall backend. EventSelector backend -> StreamSelector
streamId :: StreamSelector
    -- ^ Which streams to subscribe to
    , forall backend. EventSelector backend -> StartupPosition backend
startupPosition :: StartupPosition backend
    -- ^ Where to start reading from
    }

deriving instance (Show (StartupPosition backend)) => Show (EventSelector backend)

-- | Stream selection criteria for subscriptions.
data StreamSelector
    = -- | Subscribe to all streams
      AllStreams
    | -- | Subscribe to a specific stream only
      SingleStream StreamId
    deriving (Int -> StreamSelector -> ShowS
[StreamSelector] -> ShowS
StreamSelector -> String
(Int -> StreamSelector -> ShowS)
-> (StreamSelector -> String)
-> ([StreamSelector] -> ShowS)
-> Show StreamSelector
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> StreamSelector -> ShowS
showsPrec :: Int -> StreamSelector -> ShowS
$cshow :: StreamSelector -> String
show :: StreamSelector -> String
$cshowList :: [StreamSelector] -> ShowS
showList :: [StreamSelector] -> ShowS
Show)

-- | Starting position for event subscriptions.
data StartupPosition backend
    = -- | Start from the first event
      FromBeginning
    | -- | Start after the specified position
      FromLastProcessed (Cursor backend)

deriving instance (Show (Cursor backend)) => Show (StartupPosition backend)

-- | Handler function for processing events in a subscription.
type EventHandler event m backend = EventEnvelope event backend -> m SubscriptionResult

{- | Type-safe event matcher for handling different event types in subscriptions.

Constructed using the 'match' helper with '(:?)' to build a chain of handlers:

@
matcher = match \"user_created\" handleUserCreated
       :? match \"user_updated\" handleUserUpdated
       :? MatchEnd
@
-}
data EventMatcher (ts :: [Symbol]) backend m where
    (:?) :: (Event event) => (Proxy event, EventHandler event m backend) -> EventMatcher ts backend m -> EventMatcher (event ': ts) backend m
    MatchEnd :: EventMatcher '[] backend m

infixr 5 :?

{- | Helper to construct event handler pairs for 'EventMatcher'.

Uses RequiredTypeArguments for better error messages when the type is omitted.

@
matcher = match "user_created" handleUser :? MatchEnd
@
-}
match ::
    forall event ->
    -- \^ Event name (type-level string)
    forall a.
    -- | Handler function
    a ->
    -- | Handler pair for use with '(:?)'
    (Proxy event, a)
match :: forall {k}. forall (event :: k) -> forall a. a -> (Proxy event, a)
match event = \a
handler -> (forall (t :: k). Proxy t
forall {k} (t :: k). Proxy t
Proxy @event, a
handler)

{- | Position marker within an event store, backend-specific.

For example, PostgreSQL uses a compound cursor of (transaction_no, seq_no),
while a simple implementation might use a single sequence number.

The type family is injective: knowing the cursor type determines the backend
which helps with type inference.
-}
type family Cursor backend = result | result -> backend

{- | Handle to interact with a specific storage backend.

Contains backend-specific configuration like connection pools,
file handles, or in-memory storage references.

The type family is injective: knowing the handle type determines the backend,
which helps with type inference.
-}
type family BackendHandle backend = result | result -> backend

{- | Version expectation for optimistic concurrency control.

Used to prevent concurrent modifications by specifying what version
a stream should be at before inserting new events.

This implements optimistic locking: instead of holding locks during
a read-modify-write cycle, we check at write time that the stream
hasn't changed since we read it.

See: <https://en.wikipedia.org/wiki/Optimistic_concurrency_control>

Common patterns:

* 'NoStream' - Creating a new aggregate (ensure stream doesn't exist)
* 'ExactStreamVersion' - Normal update (check we have latest version)
* 'Any' - Append-only scenarios (no conflict prevention)
-}
data ExpectedVersion backend
    = -- | Stream must not exist (for creating new streams)
      NoStream
    | -- | Stream must exist (any version)
      StreamExists
    | -- | Stream must be at this exact global position
      ExactVersion (Cursor backend)
    | -- | Stream must be at this exact local version
      ExactStreamVersion StreamVersion
    | -- | No version check (always succeeds)
      Any

deriving instance (Show (Cursor backend)) => Show (ExpectedVersion backend)

deriving instance (Eq (Cursor backend)) => Eq (ExpectedVersion backend)

{- | Write operation for inserting events into a single stream.

Groups events with their version expectation for atomic insertion.
The event type 'e' can be 'SomeLatestEvent' for raw events or enriched with metadata.
-}
data StreamWrite t e backend = StreamWrite
    { forall {k} (t :: k -> *) (e :: k) backend.
StreamWrite t e backend -> ExpectedVersion backend
expectedVersion :: ExpectedVersion backend
    -- ^ Version check before insertion
    , forall {k} (t :: k -> *) (e :: k) backend.
StreamWrite t e backend -> t e
events :: t e
    -- ^ Events to insert (in order)
    }

deriving instance (Show (ExpectedVersion backend), Show (t e)) => Show (StreamWrite t e backend)

deriving instance (Eq (ExpectedVersion backend), Eq (t e)) => Eq (StreamWrite t e backend)

{- | A multi-stream transactional write operation.

Represents a set of writes to be inserted atomically across multiple streams.
All version checks must pass for the entire transaction to succeed.

= Ordering Guarantees

* Events within a single stream are __totally ordered__ - they appear in the
  global event log in the order specified.

* Events across different streams within the same transaction have __no ordering
  guarantee__ relative to each other. They are inserted atomically but may
  appear in any interleaving in the global log.

= Composition

Transactions form a 'Monoid', allowing easy composition:

@
transaction1 <> transaction2 <> transaction3
@

When combining transactions:

* Events for the same stream are __concatenated__ (events from tx1, then tx2)
* Version expectations are __left-biased__ (tx1's expectation wins)

= Example

@
-- Simple single-event write
let tx = singleEvent streamId NoStream event
result <- insertEvents store Nothing tx

-- Multi-stream with composition
let tx = singleEvent userStreamId NoStream userCreated
      <> appendAfterAny auditLogId auditEntry
result <- insertEvents store Nothing tx
@
-}
newtype Transaction t backend = Transaction
    { forall (t :: * -> *) backend.
Transaction t backend
-> Map StreamId (StreamWrite t SomeLatestEvent backend)
transactionWrites :: Map StreamId (StreamWrite t SomeLatestEvent backend)
    }

deriving instance (Show (Cursor backend), Show (t SomeLatestEvent)) => Show (Transaction t backend)

deriving instance (Eq (Cursor backend), Eq (t SomeLatestEvent)) => Eq (Transaction t backend)

instance (Semigroup (t SomeLatestEvent)) => Semigroup (Transaction t backend) where
    Transaction Map StreamId (StreamWrite t SomeLatestEvent backend)
m1 <> :: Transaction t backend
-> Transaction t backend -> Transaction t backend
<> Transaction Map StreamId (StreamWrite t SomeLatestEvent backend)
m2 =
        Map StreamId (StreamWrite t SomeLatestEvent backend)
-> Transaction t backend
forall (t :: * -> *) backend.
Map StreamId (StreamWrite t SomeLatestEvent backend)
-> Transaction t backend
Transaction ((StreamWrite t SomeLatestEvent backend
 -> StreamWrite t SomeLatestEvent backend
 -> StreamWrite t SomeLatestEvent backend)
-> Map StreamId (StreamWrite t SomeLatestEvent backend)
-> Map StreamId (StreamWrite t SomeLatestEvent backend)
-> Map StreamId (StreamWrite t SomeLatestEvent backend)
forall k a. Ord k => (a -> a -> a) -> Map k a -> Map k a -> Map k a
Map.unionWith StreamWrite t SomeLatestEvent backend
-> StreamWrite t SomeLatestEvent backend
-> StreamWrite t SomeLatestEvent backend
forall {k} {t :: k -> *} {e :: k} {backend} {backend}.
Semigroup (t e) =>
StreamWrite t e backend
-> StreamWrite t e backend -> StreamWrite t e backend
combineWrites Map StreamId (StreamWrite t SomeLatestEvent backend)
m1 Map StreamId (StreamWrite t SomeLatestEvent backend)
m2)
      where
        -- Left-biased for expectedVersion (first write wins)
        -- Concatenate events using Semigroup (e.g., list append)
        combineWrites :: StreamWrite t e backend
-> StreamWrite t e backend -> StreamWrite t e backend
combineWrites (StreamWrite ExpectedVersion backend
expectedVer t e
events1) (StreamWrite ExpectedVersion backend
_ignored t e
events2) =
            ExpectedVersion backend -> t e -> StreamWrite t e backend
forall {k} (t :: k -> *) (e :: k) backend.
ExpectedVersion backend -> t e -> StreamWrite t e backend
StreamWrite ExpectedVersion backend
expectedVer (t e
events1 t e -> t e -> t e
forall a. Semigroup a => a -> a -> a
<> t e
events2)

instance (Semigroup (t SomeLatestEvent)) => Monoid (Transaction t backend) where
    mempty :: Transaction t backend
mempty = Map StreamId (StreamWrite t SomeLatestEvent backend)
-> Transaction t backend
forall (t :: * -> *) backend.
Map StreamId (StreamWrite t SomeLatestEvent backend)
-> Transaction t backend
Transaction Map StreamId (StreamWrite t SomeLatestEvent backend)
forall k a. Map k a
Map.empty

{- | Create a transaction with a single event to a single stream.

This is the primary API - it forces explicit choice of version expectation.

@
-- Creating a new aggregate
singleEvent accountId NoStream accountCreated

-- Updating with optimistic locking
singleEvent accountId (ExactStreamVersion v) balanceUpdated

-- Append-only log
singleEvent logId Any logEntry
@
-}
singleEvent ::
    -- | Target stream identifier
    StreamId ->
    -- | Version expectation for concurrency control
    ExpectedVersion backend ->
    -- | Event to insert
    SomeLatestEvent ->
    -- | Transaction with single event
    Transaction [] backend
singleEvent :: forall backend.
StreamId
-> ExpectedVersion backend
-> SomeLatestEvent
-> Transaction [] backend
singleEvent StreamId
streamId ExpectedVersion backend
expectedVer SomeLatestEvent
event =
    Map StreamId (StreamWrite [] SomeLatestEvent backend)
-> Transaction [] backend
forall (t :: * -> *) backend.
Map StreamId (StreamWrite t SomeLatestEvent backend)
-> Transaction t backend
Transaction (Map StreamId (StreamWrite [] SomeLatestEvent backend)
 -> Transaction [] backend)
-> Map StreamId (StreamWrite [] SomeLatestEvent backend)
-> Transaction [] backend
forall a b. (a -> b) -> a -> b
$ StreamId
-> StreamWrite [] SomeLatestEvent backend
-> Map StreamId (StreamWrite [] SomeLatestEvent backend)
forall k a. k -> a -> Map k a
Map.singleton StreamId
streamId (ExpectedVersion backend
-> [SomeLatestEvent] -> StreamWrite [] SomeLatestEvent backend
forall {k} (t :: k -> *) (e :: k) backend.
ExpectedVersion backend -> t e -> StreamWrite t e backend
StreamWrite ExpectedVersion backend
expectedVer [SomeLatestEvent
event])

{- | Create a transaction with multiple events to a single stream.

@
multiEvent streamId NoStream [event1, event2, event3]
@
-}
multiEvent ::
    -- | Target stream identifier
    StreamId ->
    -- | Version expectation for concurrency control
    ExpectedVersion backend ->
    -- | Collection of events to insert (typically a list)
    t SomeLatestEvent ->
    -- | Transaction with multiple events
    Transaction t backend
multiEvent :: forall backend (t :: * -> *).
StreamId
-> ExpectedVersion backend
-> t SomeLatestEvent
-> Transaction t backend
multiEvent StreamId
streamId ExpectedVersion backend
expectedVer t SomeLatestEvent
events =
    Map StreamId (StreamWrite t SomeLatestEvent backend)
-> Transaction t backend
forall (t :: * -> *) backend.
Map StreamId (StreamWrite t SomeLatestEvent backend)
-> Transaction t backend
Transaction (Map StreamId (StreamWrite t SomeLatestEvent backend)
 -> Transaction t backend)
-> Map StreamId (StreamWrite t SomeLatestEvent backend)
-> Transaction t backend
forall a b. (a -> b) -> a -> b
$ StreamId
-> StreamWrite t SomeLatestEvent backend
-> Map StreamId (StreamWrite t SomeLatestEvent backend)
forall k a. k -> a -> Map k a
Map.singleton StreamId
streamId (ExpectedVersion backend
-> t SomeLatestEvent -> StreamWrite t SomeLatestEvent backend
forall {k} (t :: k -> *) (e :: k) backend.
ExpectedVersion backend -> t e -> StreamWrite t e backend
StreamWrite ExpectedVersion backend
expectedVer t SomeLatestEvent
events)

{- | Append an event to an existing stream without version checking.

Uses 'StreamExists' for the version expectation - the stream must already
exist, but any version is acceptable. Suitable for append-only scenarios
where multiple processes may be writing concurrently.

= Use Cases

* __Audit logs__ where the log stream is pre-created
* __Event logs__ with concurrent writers
* __Not suitable__ for aggregates with business invariants

@
-- Audit log entry (stream must exist)
appendAfterAny auditLogId auditEntry

-- Multiple concurrent writers OK
tx1 = appendAfterAny logId event1
tx2 = appendAfterAny logId event2  -- No conflict
@
-}
appendAfterAny ::
    -- | Target stream identifier (must exist)
    StreamId ->
    -- | Event to append
    SomeLatestEvent ->
    -- | Transaction with StreamExists expectation
    Transaction [] backend
appendAfterAny :: forall backend.
StreamId -> SomeLatestEvent -> Transaction [] backend
appendAfterAny StreamId
streamId SomeLatestEvent
event =
    StreamId
-> ExpectedVersion backend
-> SomeLatestEvent
-> Transaction [] backend
forall backend.
StreamId
-> ExpectedVersion backend
-> SomeLatestEvent
-> Transaction [] backend
singleEvent StreamId
streamId ExpectedVersion backend
forall backend. ExpectedVersion backend
StreamExists SomeLatestEvent
event

{- | Append an event, creating the stream if it doesn't exist.

Uses 'Any' for the version expectation - no version checking at all.
This is the most permissive option.

__Warning__: This provides no concurrency control. Suitable for testing
or truly append-only scenarios, but dangerous for aggregates with invariants.

= Use Cases

* __Testing__ where you don't care about stream state
* __Idempotent appends__ where duplicates are handled elsewhere
* __Not suitable__ for production aggregates

@
-- Test code
appendToOrCreateStream testStreamId testEvent

-- For production, prefer explicit version expectations
singleEvent streamId NoStream event  -- Better: explicit create
@
-}
appendToOrCreateStream ::
    -- | Target stream identifier (created if missing)
    StreamId ->
    -- | Event to append
    SomeLatestEvent ->
    -- | Transaction with Any expectation (no version check)
    Transaction [] backend
appendToOrCreateStream :: forall backend.
StreamId -> SomeLatestEvent -> Transaction [] backend
appendToOrCreateStream StreamId
streamId SomeLatestEvent
event =
    StreamId
-> ExpectedVersion backend
-> SomeLatestEvent
-> Transaction [] backend
forall backend.
StreamId
-> ExpectedVersion backend
-> SomeLatestEvent
-> Transaction [] backend
singleEvent StreamId
streamId ExpectedVersion backend
forall backend. ExpectedVersion backend
Any SomeLatestEvent
event

{- | Create a transaction from a list of stream writes.

@
fromWrites
  [ (stream1, StreamWrite NoStream [e1])
  , (stream2, StreamWrite NoStream [e2])
  ]
@
-}
fromWrites ::
    -- | List of stream writes
    [(StreamId, StreamWrite t SomeLatestEvent backend)] ->
    -- | Combined transaction
    Transaction t backend
fromWrites :: forall (t :: * -> *) backend.
[(StreamId, StreamWrite t SomeLatestEvent backend)]
-> Transaction t backend
fromWrites = Map StreamId (StreamWrite t SomeLatestEvent backend)
-> Transaction t backend
forall (t :: * -> *) backend.
Map StreamId (StreamWrite t SomeLatestEvent backend)
-> Transaction t backend
Transaction (Map StreamId (StreamWrite t SomeLatestEvent backend)
 -> Transaction t backend)
-> ([(StreamId, StreamWrite t SomeLatestEvent backend)]
    -> Map StreamId (StreamWrite t SomeLatestEvent backend))
-> [(StreamId, StreamWrite t SomeLatestEvent backend)]
-> Transaction t backend
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [(StreamId, StreamWrite t SomeLatestEvent backend)]
-> Map StreamId (StreamWrite t SomeLatestEvent backend)
forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList

{- | Core interface for event store backends.

Provides methods for inserting events and subscribing to event streams.
Each backend defines its own constraints via 'StoreConstraints'.
-}
class EventStore (backend :: Type) where
    {- | Additional constraints required by this backend.

    For example, PostgreSQL requires MonadUnliftIO for async operations.
    -}
    type StoreConstraints backend (m :: Type -> Type) :: Constraint

    {- | Insert event batches atomically into the store.

    All events in the batch are inserted as a single transaction.
    If any version check fails, the entire operation is rolled back.
    -}
    insertEvents ::
        (Traversable t, StoreConstraints backend m) =>
        BackendHandle backend ->
        Maybe CorrelationId ->
        Transaction t backend ->
        m (InsertionResult backend)

    {- | Subscribe to events matching the given criteria.

    The subscription will call the appropriate handler for each matching
    event until a handler returns 'Stop' or the subscription is cancelled.
    -}
    subscribe ::
        (StoreConstraints backend m) =>
        BackendHandle backend ->
        EventMatcher ts backend m ->
        EventSelector backend ->
        m (SubscriptionHandle backend)