packages feed

eventium-sql-common 0.1.0 → 0.2.1

raw patch · 7 files changed

+296/−92 lines, 7 filesdep +timedep −persistent-templatedep ~eventium-coredep ~persistent

Dependencies added: time

Dependencies removed: persistent-template

Dependency ranges changed: eventium-core, persistent

Files

eventium-sql-common.cabal view
@@ -1,11 +1,11 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.37.0.+-- This file has been generated from package.yaml by hpack version 0.38.3. -- -- see: https://github.com/sol/hpack  name:           eventium-sql-common-version:        0.1.0+version:        0.2.1 synopsis:       Common library for SQL event stores description:    Eventium-sql-common provides shared utilities and abstractions for SQL-based event stores.                 It includes Persistent entity definitions, JSON serialization helpers, and common database@@ -25,8 +25,14 @@   type: git   location: https://github.com/aleks-sidorenko/eventium +flag ci+  description: Enable -Werror for CI builds+  manual: True+  default: False+ library   exposed-modules:+      Eventium.ProjectionCache.Sql       Eventium.Store.Sql       Eventium.Store.Sql.DefaultEntity       Eventium.Store.Sql.JSONString@@ -36,15 +42,21 @@       Paths_eventium_sql_common   hs-source-dirs:       src-  ghc-options: -Wall+  default-extensions:+      NoFieldSelectors+      DuplicateRecordFields+      OverloadedRecordDot+  ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wredundant-constraints -Wno-type-defaults -Wno-incomplete-uni-patterns   build-depends:       aeson >=1.5 && <2.3     , base >=4.9 && <5     , bytestring >=0.10 && <0.13-    , eventium-core+    , eventium-core >=0.2.0 && <0.3.0     , mtl >=2.2 && <2.4-    , persistent >=2.13 && <2.15-    , persistent-template >=2.12 && <2.13+    , persistent >=2.14 && <2.18     , text >=1.2 && <2.2-    , uuid >=1.3 && <1.4+    , time >=1.9 && <1.14+    , uuid ==1.3.*   default-language: Haskell2010+  if flag(ci)+    ghc-options: -Werror
+ src/Eventium/ProjectionCache/Sql.hs view
@@ -0,0 +1,150 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedRecordDot #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE NoFieldSelectors #-}++module Eventium.ProjectionCache.Sql+  ( ProjectionName (..),+    CheckpointName (..),+    ProjectionSnapshotEntity (..),+    migrateProjectionSnapshot,+    sqlVersionedProjectionCache,+    sqlGlobalProjectionCache,+    sqlCheckpointStore,+  )+where++import Control.Monad.IO.Class (MonadIO, liftIO)+import Data.Aeson (FromJSON, ToJSON, Value (Object))+import Data.Text (Text)+import Data.Time (UTCTime, getCurrentTime)+import Database.Persist+import Database.Persist.Sql+import Database.Persist.TH+import Eventium.EventSubscription (CheckpointStore (..))+import Eventium.ProjectionCache.Types+import Eventium.Store.Class (EventVersion (..), SequenceNumber (..))+import Eventium.Store.Sql.JSONString (JSONString, encodeJSON)+import Eventium.Store.Sql.Orphans ()+import Eventium.UUID (UUID, nil)++-- | A name identifying a projection in the projection cache.+-- Used as a discriminator so multiple projections can share one storage table.+newtype ProjectionName = ProjectionName Text+  deriving (Show, Read, Eq, Ord, ToJSON, FromJSON, PersistField, PersistFieldSql)++-- | A name identifying a checkpoint in the projection snapshots table.+-- Distinct from 'ProjectionName' to maintain semantic clarity — checkpoint+-- stores track subscription position, while projection caches store+-- aggregate snapshots.+newtype CheckpointName = CheckpointName Text+  deriving (Show, Read, Eq, Ord, ToJSON, FromJSON, PersistField, PersistFieldSql)++share+  [mkPersist sqlSettings {mpsFieldLabelModifier = const id}, mkMigrate "migrateProjectionSnapshot"]+  [persistLowerCase|+ProjectionSnapshotEntity sql=projection_snapshots+    projectionName ProjectionName+    entityId UUID sql=key+    position Int+    state JSONString+    updatedAt UTCTime+    Primary projectionName entityId+    deriving Show+|]++-- | A SQL-backed 'ProjectionCache' for per-entity snapshots.+--+-- Stores one row per entity. The @key@ is a UUID (aggregate/entity ID)+-- and @position@ is an EventVersion.+--+-- Composes with 'codecProjectionCache' for state encoding.+-- Works with both PostgreSQL and SQLite via Persistent.+sqlVersionedProjectionCache ::+  (MonadIO m) =>+  ProjectionName ->+  ProjectionCache UUID EventVersion JSONString (SqlPersistT m)+sqlVersionedProjectionCache name =+  ProjectionCache+    { storeSnapshot = \uuid (EventVersion ver) state -> do+        now <- liftIO getCurrentTime+        repsert (ProjectionSnapshotEntityKey name uuid) $+          ProjectionSnapshotEntity+            { projectionName = name,+              entityId = uuid,+              position = ver,+              state = state,+              updatedAt = now+            },+      loadSnapshot = \uuid -> do+        mEntity <- get (ProjectionSnapshotEntityKey name uuid)+        return $ fmap (\e -> (EventVersion e.position, e.state)) mEntity+    }++-- | A SQL-backed 'ProjectionCache' for global blob snapshots.+--+-- Stores one row per read model using 'nil' UUID as the key.+-- The @position@ is a SequenceNumber.+--+-- Composes with 'codecProjectionCache' for state encoding.+-- Works with both PostgreSQL and SQLite via Persistent.+sqlGlobalProjectionCache ::+  (MonadIO m) =>+  ProjectionName ->+  ProjectionCache () SequenceNumber JSONString (SqlPersistT m)+sqlGlobalProjectionCache name =+  ProjectionCache+    { storeSnapshot = \() (SequenceNumber sn) state -> do+        now <- liftIO getCurrentTime+        repsert (ProjectionSnapshotEntityKey name nil) $+          ProjectionSnapshotEntity+            { projectionName = name,+              entityId = nil,+              position = sn,+              state = state,+              updatedAt = now+            },+      loadSnapshot = \() -> do+        mEntity <- get (ProjectionSnapshotEntityKey name nil)+        return $ fmap (\e -> (SequenceNumber e.position, e.state)) mEntity+    }++-- | A SQL-backed 'CheckpointStore' for tracking 'SequenceNumber' position.+--+-- Reuses the @projection_snapshots@ table. Stores one row with the+-- 'CheckpointName' as the projection name and 'nil' UUID as the entity key.+-- The @position@ column stores the sequence number; the @state@ column+-- stores an empty JSON string.+sqlCheckpointStore ::+  (MonadIO m) =>+  CheckpointName ->+  CheckpointStore (SqlPersistT m) SequenceNumber+sqlCheckpointStore (CheckpointName name) =+  let projName = ProjectionName name+   in CheckpointStore+        { getCheckpoint = do+            mEntity <- get (ProjectionSnapshotEntityKey projName nil)+            return $ maybe 0 (SequenceNumber . (.position)) mEntity,+          saveCheckpoint = \(SequenceNumber sn) -> do+            now <- liftIO getCurrentTime+            repsert (ProjectionSnapshotEntityKey projName nil) $+              ProjectionSnapshotEntity+                { projectionName = projName,+                  entityId = nil,+                  position = sn,+                  state = encodeJSON (Object mempty),+                  updatedAt = now+                }+        }
src/Eventium/Store/Sql.hs view
@@ -3,6 +3,7 @@   ) where +import Eventium.ProjectionCache.Sql as X import Eventium.Store.Sql.DefaultEntity as X import Eventium.Store.Sql.JSONString as X import Eventium.Store.Sql.Operations as X
src/Eventium/Store/Sql/DefaultEntity.hs view
@@ -36,14 +36,15 @@ SqlEvent sql=events     uuid UUID     version EventVersion-    event JSONString+    payload JSONString+    metadata JSONString Maybe     UniqueUuidVersion uuid version     deriving Show |]  sqlEventMakeKey :: SequenceNumber -> Key SqlEvent-sqlEventMakeKey sequenceNumber =-  toSqlKey (fromIntegral (unSequenceNumber sequenceNumber))+sqlEventMakeKey (SequenceNumber n) =+  toSqlKey (fromIntegral n)  sqlEventUnKey :: Key SqlEvent -> SequenceNumber sqlEventUnKey key =@@ -52,13 +53,16 @@ defaultSqlEventStoreConfig :: SqlEventStoreConfig SqlEvent JSONString defaultSqlEventStoreConfig =   SqlEventStoreConfig-    SqlEvent-    sqlEventMakeKey-    sqlEventUnKey-    sqlEventUuid-    sqlEventVersion-    sqlEventEvent-    SqlEventId-    SqlEventUuid-    SqlEventVersion-    SqlEventEvent+    { sequenceMakeEntity = SqlEvent,+      makeKey = sqlEventMakeKey,+      unKey = sqlEventUnKey,+      uuid = \(SqlEvent u _ _ _) -> u,+      version = \(SqlEvent _ v _ _) -> v,+      payload = \(SqlEvent _ _ p _) -> p,+      metadata = \(SqlEvent _ _ _ m) -> m,+      sequenceNumberField = SqlEventId,+      uuidField = SqlEventUuid,+      versionField = SqlEventVersion,+      payloadField = SqlEventPayload,+      metadataField = SqlEventMetadata+    }
src/Eventium/Store/Sql/JSONString.hs view
@@ -3,39 +3,37 @@  module Eventium.Store.Sql.JSONString   ( JSONString,-    jsonStringSerializer,+    jsonStringCodec,+    encodeJSON,+    decodeJSON,   ) where -import Data.Aeson+import qualified Data.Aeson as Aeson import Data.Text.Lazy (Text) import qualified Data.Text.Lazy.Encoding as TLE import Database.Persist import Database.Persist.Sql-import Eventium.Serializer+import Eventium.Codec (Codec (..))  -- | A more specific type than just ByteString for JSON data.-newtype JSONString = JSONString {unJSONString :: Text}+newtype JSONString = JSONString Text   deriving (Eq, PersistField)  instance PersistFieldSql JSONString where   sqlType _ = SqlOther "jsonb"  instance Show JSONString where-  show = show . unJSONString+  show (JSONString t) = show t -jsonStringSerializer :: (ToJSON a, FromJSON a) => Serializer a JSONString-jsonStringSerializer =-  Serializer+jsonStringCodec :: (Aeson.ToJSON a, Aeson.FromJSON a) => Codec a JSONString+jsonStringCodec =+  Codec     encodeJSON     decodeJSON-    decodeJSONEither -encodeJSON :: (ToJSON a) => a -> JSONString-encodeJSON = JSONString . TLE.decodeUtf8 . encode--decodeJSON :: (FromJSON a) => JSONString -> Maybe a-decodeJSON = decode . TLE.encodeUtf8 . unJSONString+encodeJSON :: (Aeson.ToJSON a) => a -> JSONString+encodeJSON = JSONString . TLE.decodeUtf8 . Aeson.encode -decodeJSONEither :: (FromJSON a) => JSONString -> Either String a-decodeJSONEither = eitherDecode . TLE.encodeUtf8 . unJSONString+decodeJSON :: (Aeson.FromJSON a) => JSONString -> Maybe a+decodeJSON (JSONString t) = Aeson.decode . TLE.encodeUtf8 $ t
src/Eventium/Store/Sql/Operations.hs view
@@ -1,5 +1,4 @@ {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-}@@ -12,37 +11,39 @@     sqlGetStreamEvents,     sqlMaxEventVersion,     sqlStoreEvents,+    sqlStoreEventsTagged,     unsafeSqlStoreGlobalStreamEvents,   ) where  import Control.Monad.IO.Class import Data.Foldable (for_)-import Data.Maybe (listToMaybe)+import Data.Maybe (fromMaybe, listToMaybe) import Data.Text (Text) import Database.Persist-import Database.Persist.Class (SafeToInsert)-import Database.Persist.Names (EntityNameDB (..), FieldNameDB (..)) import Database.Persist.Sql import Eventium.Store.Class+import Eventium.Store.Sql.JSONString (JSONString, decodeJSON, encodeJSON) import Eventium.Store.Sql.Orphans as X () import Eventium.UUID  data SqlEventStoreConfig entity serialized   = SqlEventStoreConfig-  { sqlEventStoreConfigSequenceMakeEntity :: UUID -> EventVersion -> serialized -> entity,+  { sequenceMakeEntity :: UUID -> EventVersion -> serialized -> Maybe JSONString -> entity,     -- Key manipulation-    sqlEventStoreConfigMakeKey :: SequenceNumber -> Key entity,-    sqlEventStoreConfigUnKey :: Key entity -> SequenceNumber,+    makeKey :: SequenceNumber -> Key entity,+    unKey :: Key entity -> SequenceNumber,     -- Record functions-    sqlEventStoreConfigUUID :: entity -> UUID,-    sqlEventStoreConfigVersion :: entity -> EventVersion,-    sqlEventStoreConfigData :: entity -> serialized,+    uuid :: entity -> UUID,+    version :: entity -> EventVersion,+    payload :: entity -> serialized,+    metadata :: entity -> Maybe JSONString,     -- EntityFields-    sqlEventStoreConfigSequenceNumberField :: EntityField entity (Key entity),-    sqlEventStoreConfigUUIDField :: EntityField entity UUID,-    sqlEventStoreConfigVersionField :: EntityField entity EventVersion,-    sqlEventStoreConfigDataField :: EntityField entity serialized+    sequenceNumberField :: EntityField entity (Key entity),+    uuidField :: EntityField entity UUID,+    versionField :: EntityField entity EventVersion,+    payloadField :: EntityField entity serialized,+    metadataField :: EntityField entity (Maybe JSONString)   }  sqlEventStoreReader ::@@ -62,82 +63,89 @@   SqlEventStoreConfig entity serialized ->   Entity entity ->   GlobalStreamEvent serialized-sqlEventToGlobalStream config@SqlEventStoreConfig {..} (Entity key event) =-  StreamEvent () (sqlEventStoreConfigUnKey key) (sqlEventToVersioned config event)+sqlEventToGlobalStream config (Entity eKey event) =+  let versioned = sqlEventToVersioned config event+   in StreamEvent () (config.unKey eKey) versioned.metadata versioned +-- | Decode metadata from a stored JSONString. Returns empty metadata on NULL+-- or decode failure, ensuring events are always readable.+decodeMetadata :: Maybe JSONString -> EventMetadata+decodeMetadata = fromMaybe (emptyMetadata "") . (>>= decodeJSON)+ sqlEventToVersioned ::   SqlEventStoreConfig entity serialized ->   entity ->   VersionedStreamEvent serialized-sqlEventToVersioned SqlEventStoreConfig {..} entity =+sqlEventToVersioned config entity =   StreamEvent-    (sqlEventStoreConfigUUID entity)-    (sqlEventStoreConfigVersion entity)-    (sqlEventStoreConfigData entity)+    (config.uuid entity)+    (config.version entity)+    (decodeMetadata $ config.metadata entity)+    (config.payload entity)  sqlGetProjectionIds ::-  (MonadIO m, PersistEntity entity, PersistEntityBackend entity ~ SqlBackend) =>+  (MonadIO m, PersistEntity entity) =>   SqlEventStoreConfig entity serialized ->   SqlPersistT m [UUID]-sqlGetProjectionIds SqlEventStoreConfig {..} =+sqlGetProjectionIds config =   fmap unSingle <$> rawSql ("SELECT DISTINCT " <> uuidFieldName <> " FROM " <> tableName) []   where-    tableName = unEntityNameDB $ tableDBName (sqlEventStoreConfigSequenceMakeEntity nil 0 undefined)-    uuidFieldName = unFieldNameDB $ fieldDBName sqlEventStoreConfigSequenceNumberField+    tableName = unEntityNameDB $ tableDBName (config.sequenceMakeEntity nil 0 undefined Nothing)+    uuidFieldName = unFieldNameDB $ fieldDBName config.uuidField  sqlGetStreamEvents ::   (MonadIO m, PersistEntity entity, PersistEntityBackend entity ~ SqlBackend) =>   SqlEventStoreConfig entity serialized ->   QueryRange UUID EventVersion ->   SqlPersistT m [VersionedStreamEvent serialized]-sqlGetStreamEvents config@SqlEventStoreConfig {..} QueryRange {..} = do+sqlGetStreamEvents config query = do   entities <- selectList filters selectOpts   return $ sqlEventToVersioned config . entityVal <$> entities   where     startFilter =-      case queryRangeStart of+      case query.start of         StartFromBeginning -> []-        StartQueryAt start -> [sqlEventStoreConfigVersionField >=. start]+        StartQueryAt s -> [config.versionField >=. s]     (endFilter, endSelectOpt) =-      case queryRangeLimit of+      case query.limit of         NoQueryLimit -> ([], [])         MaxNumberOfEvents maxNum -> ([], [LimitTo maxNum])-        StopQueryAt stop -> ([sqlEventStoreConfigVersionField <=. stop], [])-    filters = (sqlEventStoreConfigUUIDField ==. queryRangeKey) : startFilter ++ endFilter-    selectOpts = Asc sqlEventStoreConfigSequenceNumberField : endSelectOpt+        StopQueryAt stop -> ([config.versionField <=. stop], [])+    filters = (config.uuidField ==. query.key) : startFilter ++ endFilter+    selectOpts = Asc config.sequenceNumberField : endSelectOpt  sqlGetAllEventsInRange ::   (MonadIO m, PersistEntity entity, PersistEntityBackend entity ~ SqlBackend) =>   SqlEventStoreConfig entity serialized ->   QueryRange () SequenceNumber ->   SqlPersistT m [GlobalStreamEvent serialized]-sqlGetAllEventsInRange config@SqlEventStoreConfig {..} QueryRange {..} = do+sqlGetAllEventsInRange config query = do   entities <- selectList filters selectOpts   return $ sqlEventToGlobalStream config <$> entities   where     startFilter =-      case queryRangeStart of+      case query.start of         StartFromBeginning -> []-        StartQueryAt start -> [sqlEventStoreConfigSequenceNumberField >=. sqlEventStoreConfigMakeKey start]+        StartQueryAt s -> [config.sequenceNumberField >=. config.makeKey s]     (endFilter, endSelectOpt) =-      case queryRangeLimit of+      case query.limit of         NoQueryLimit -> ([], [])         MaxNumberOfEvents maxNum -> ([], [LimitTo maxNum])-        StopQueryAt stop -> ([sqlEventStoreConfigSequenceNumberField <=. sqlEventStoreConfigMakeKey stop], [])+        StopQueryAt stop -> ([config.sequenceNumberField <=. config.makeKey stop], [])     filters = startFilter ++ endFilter-    selectOpts = Asc sqlEventStoreConfigSequenceNumberField : endSelectOpt+    selectOpts = Asc config.sequenceNumberField : endSelectOpt  sqlMaxEventVersion ::-  (MonadIO m, PersistEntity entity, PersistEntityBackend entity ~ SqlBackend) =>+  (MonadIO m, PersistEntity entity) =>   SqlEventStoreConfig entity serialized ->   (FieldNameDB -> FieldNameDB -> FieldNameDB -> Text) ->   UUID ->   SqlPersistT m EventVersion-sqlMaxEventVersion SqlEventStoreConfig {..} maxVersionSql uuid =-  let tableName = FieldNameDB $ unEntityNameDB $ tableDBName (sqlEventStoreConfigSequenceMakeEntity nil 0 undefined)-      uuidFieldName = fieldDBName sqlEventStoreConfigUUIDField-      versionFieldName = fieldDBName sqlEventStoreConfigVersionField-      rawVals = rawSql (maxVersionSql tableName uuidFieldName versionFieldName) [toPersistValue uuid]+sqlMaxEventVersion config maxVersionSql uid =+  let tableName = FieldNameDB $ unEntityNameDB $ tableDBName (config.sequenceMakeEntity nil 0 undefined Nothing)+      uuidFieldName = fieldDBName config.uuidField+      versionFieldName = fieldDBName config.versionField+      rawVals = rawSql (maxVersionSql tableName uuidFieldName versionFieldName) [toPersistValue uid]    in maybe 0 unSingle . listToMaybe <$> rawVals  sqlStoreEvents ::@@ -148,17 +156,46 @@   UUID ->   [serialized] ->   SqlPersistT m EventVersion-sqlStoreEvents config@SqlEventStoreConfig {..} mLockCommand maxVersionSql uuid events = do-  versionNum <- sqlMaxEventVersion config maxVersionSql uuid-  let entities = zipWith (sqlEventStoreConfigSequenceMakeEntity uuid) [versionNum + 1 ..] events+sqlStoreEvents config mLockCommand maxVersionSql uid events = do+  versionNum <- sqlMaxEventVersion config maxVersionSql uid+  let entities = zipWith (\v e -> config.sequenceMakeEntity uid v e Nothing) [versionNum + 1 ..] events   -- NB: We need to take a lock on the events table or else the global sequence   -- numbers may not increase monotonically over time.   for_ mLockCommand $ \lockCommand -> rawExecute (lockCommand tableName) []   _ <- insertMany entities   return $ versionNum + EventVersion (length events)   where-    tableName = unEntityNameDB $ tableDBName (sqlEventStoreConfigSequenceMakeEntity nil 0 undefined)+    tableName = unEntityNameDB $ tableDBName (config.sequenceMakeEntity nil 0 undefined Nothing) +-- | Like 'sqlStoreEvents' but accepts 'TaggedEvent's, using their metadata+-- for event type name, correlation ID, causation ID, and timestamp.+sqlStoreEventsTagged ::+  (MonadIO m, PersistEntity entity, PersistEntityBackend entity ~ SqlBackend, SafeToInsert entity) =>+  SqlEventStoreConfig entity serialized ->+  Maybe (Text -> Text) ->+  (FieldNameDB -> FieldNameDB -> FieldNameDB -> Text) ->+  UUID ->+  [TaggedEvent serialized] ->+  SqlPersistT m EventVersion+sqlStoreEventsTagged config mLockCommand maxVersionSql uid taggedEvents = do+  versionNum <- sqlMaxEventVersion config maxVersionSql uid+  let entities =+        zipWith+          ( \v (TaggedEvent meta e) ->+              config.sequenceMakeEntity+                uid+                v+                e+                (Just $ encodeJSON meta)+          )+          [versionNum + 1 ..]+          taggedEvents+  for_ mLockCommand $ \lockCommand -> rawExecute (lockCommand tableName) []+  _ <- insertMany entities+  return $ versionNum + EventVersion (length taggedEvents)+  where+    tableName = unEntityNameDB $ tableDBName (config.sequenceMakeEntity nil 0 undefined Nothing)+ -- | Useful if you have some 'GlobalStreamEvent's and you want to shove them in -- a SQL event store. This can happen when you are moving events between event -- stores, or you somehow generate the events outside of the current SQL event@@ -168,10 +205,15 @@   SqlEventStoreConfig entity serialized ->   [GlobalStreamEvent serialized] ->   SqlPersistT m ()-unsafeSqlStoreGlobalStreamEvents SqlEventStoreConfig {..} events =+unsafeSqlStoreGlobalStreamEvents config events =   insertEntityMany $ fmap mkEventEntity events   where-    mkEventEntity (StreamEvent () seqNum (StreamEvent uuid vers event)) =+    mkEventEntity (StreamEvent () seqNum _ (StreamEvent uid vers meta evtPayload)) =       Entity-        (sqlEventStoreConfigMakeKey seqNum)-        (sqlEventStoreConfigSequenceMakeEntity uuid vers event)+        (config.makeKey seqNum)+        ( config.sequenceMakeEntity+            uid+            vers+            evtPayload+            (Just $ encodeJSON meta)+        )
src/Eventium/Store/Sql/Orphans.hs view
@@ -6,7 +6,6 @@   ) where -import qualified Data.ByteString as BS import Data.Proxy import qualified Data.Text as T import qualified Data.Text.Encoding as TE@@ -24,8 +23,6 @@       Nothing -> Left "Invalid UUID"   fromPersistValue (PersistByteString bs) =     maybe (Left "Invalid UUID") Right (uuidFromText (TE.decodeUtf8 bs))-  fromPersistValue (PersistDbSpecific bs) =-    maybe (Left "Invalid UUID") Right (uuidFromText (TE.decodeUtf8 bs))   fromPersistValue (PersistLiteral_ _ bs) =     maybe (Left "Invalid UUID") Right (uuidFromText (TE.decodeUtf8 bs))   fromPersistValue v = Left $ "Expected UUID-compatible PersistValue, got: " <> T.pack (show v)@@ -34,14 +31,14 @@   sqlType _ = SqlOther "uuid"  instance PersistField EventVersion where-  toPersistValue = toPersistValue . unEventVersion+  toPersistValue (EventVersion n) = toPersistValue n   fromPersistValue = fmap EventVersion . fromPersistValue  instance PersistFieldSql EventVersion where   sqlType _ = sqlType (Proxy :: Proxy Int)  instance PersistField SequenceNumber where-  toPersistValue = toPersistValue . unSequenceNumber+  toPersistValue (SequenceNumber n) = toPersistValue n   fromPersistValue = fmap SequenceNumber . fromPersistValue  instance PersistFieldSql SequenceNumber where