mergeless 0.3.0.0 → 0.4.0.0
raw patch · 5 files changed
+267/−118 lines, 5 filesdep +autodocodecdep +textPVP ok
version bump matches the API change (PVP)
Dependencies added: autodocodec, text
API changes (from Hackage documentation)
- Data.Mergeless.Collection: instance (Data.Aeson.Types.FromJSON.FromJSON ci, Data.Aeson.Types.FromJSON.FromJSON si, Data.Aeson.Types.FromJSON.FromJSON a, Data.Aeson.Types.FromJSON.FromJSONKey ci, GHC.Classes.Ord ci, GHC.Classes.Ord si, GHC.Classes.Ord a) => Data.Aeson.Types.FromJSON.FromJSON (Data.Mergeless.Collection.SyncRequest ci si a)
- Data.Mergeless.Collection: instance (Data.Aeson.Types.FromJSON.FromJSONKey si, GHC.Classes.Ord si, Data.Aeson.Types.FromJSON.FromJSON a) => Data.Aeson.Types.FromJSON.FromJSON (Data.Mergeless.Collection.ServerStore si a)
- Data.Mergeless.Collection: instance (Data.Aeson.Types.ToJSON.ToJSON a, Data.Aeson.Types.ToJSON.ToJSONKey si) => Data.Aeson.Types.ToJSON.ToJSON (Data.Mergeless.Collection.ServerStore si a)
- Data.Mergeless.Collection: instance (Data.Aeson.Types.ToJSON.ToJSON ci, Data.Aeson.Types.ToJSON.ToJSON si, Data.Aeson.Types.ToJSON.ToJSON a, Data.Aeson.Types.ToJSON.ToJSONKey ci) => Data.Aeson.Types.ToJSON.ToJSON (Data.Mergeless.Collection.SyncRequest ci si a)
- Data.Mergeless.Collection: instance (Data.Aeson.Types.ToJSON.ToJSON ci, Data.Aeson.Types.ToJSON.ToJSON si, Data.Aeson.Types.ToJSON.ToJSONKey ci, Data.Aeson.Types.ToJSON.ToJSONKey si, Data.Aeson.Types.ToJSON.ToJSON a) => Data.Aeson.Types.ToJSON.ToJSON (Data.Mergeless.Collection.SyncResponse ci si a)
- Data.Mergeless.Collection: instance (GHC.Classes.Ord ci, Data.Aeson.Types.FromJSON.FromJSON ci, Data.Aeson.Types.FromJSON.FromJSONKey ci, GHC.Classes.Ord si, Data.Aeson.Types.FromJSON.FromJSON si, Data.Aeson.Types.FromJSON.FromJSONKey si, Data.Aeson.Types.FromJSON.FromJSON a) => Data.Aeson.Types.FromJSON.FromJSON (Data.Mergeless.Collection.ClientStore ci si a)
- Data.Mergeless.Collection: instance (GHC.Classes.Ord ci, Data.Aeson.Types.ToJSON.ToJSON ci, Data.Aeson.Types.ToJSON.ToJSONKey ci, GHC.Classes.Ord si, Data.Aeson.Types.ToJSON.ToJSON si, Data.Aeson.Types.ToJSON.ToJSONKey si, Data.Aeson.Types.ToJSON.ToJSON a) => Data.Aeson.Types.ToJSON.ToJSON (Data.Mergeless.Collection.ClientStore ci si a)
- Data.Mergeless.Collection: instance (GHC.Classes.Ord ci, GHC.Classes.Ord si, Data.Aeson.Types.FromJSON.FromJSON ci, Data.Aeson.Types.FromJSON.FromJSON si, Data.Aeson.Types.FromJSON.FromJSONKey ci, Data.Aeson.Types.FromJSON.FromJSONKey si, GHC.Classes.Ord a, Data.Aeson.Types.FromJSON.FromJSON a) => Data.Aeson.Types.FromJSON.FromJSON (Data.Mergeless.Collection.SyncResponse ci si a)
- Data.Mergeless.Item: instance Data.Aeson.Types.FromJSON.FromJSON a => Data.Aeson.Types.FromJSON.FromJSON (Data.Mergeless.Item.ClientItem a)
- Data.Mergeless.Item: instance Data.Aeson.Types.FromJSON.FromJSON a => Data.Aeson.Types.FromJSON.FromJSON (Data.Mergeless.Item.ItemSyncRequest a)
- Data.Mergeless.Item: instance Data.Aeson.Types.FromJSON.FromJSON a => Data.Aeson.Types.FromJSON.FromJSON (Data.Mergeless.Item.ItemSyncResponse a)
- Data.Mergeless.Item: instance Data.Aeson.Types.FromJSON.FromJSON a => Data.Aeson.Types.FromJSON.FromJSON (Data.Mergeless.Item.ServerItem a)
- Data.Mergeless.Item: instance Data.Aeson.Types.ToJSON.ToJSON a => Data.Aeson.Types.ToJSON.ToJSON (Data.Mergeless.Item.ClientItem a)
- Data.Mergeless.Item: instance Data.Aeson.Types.ToJSON.ToJSON a => Data.Aeson.Types.ToJSON.ToJSON (Data.Mergeless.Item.ItemSyncRequest a)
- Data.Mergeless.Item: instance Data.Aeson.Types.ToJSON.ToJSON a => Data.Aeson.Types.ToJSON.ToJSON (Data.Mergeless.Item.ItemSyncResponse a)
- Data.Mergeless.Item: instance Data.Aeson.Types.ToJSON.ToJSON a => Data.Aeson.Types.ToJSON.ToJSON (Data.Mergeless.Item.ServerItem a)
+ Data.Mergeless.Collection: instance (GHC.Classes.Ord ci, Data.Aeson.Types.FromJSON.FromJSONKey ci, Data.Aeson.Types.ToJSON.ToJSONKey ci, Autodocodec.Class.HasCodec ci, GHC.Classes.Ord si, Data.Aeson.Types.FromJSON.FromJSONKey si, Data.Aeson.Types.ToJSON.ToJSONKey si, Autodocodec.Class.HasCodec si, GHC.Classes.Eq a, Autodocodec.Class.HasCodec a) => Autodocodec.Class.HasCodec (Data.Mergeless.Collection.SyncResponse ci si a)
+ Data.Mergeless.Collection: instance (GHC.Classes.Ord ci, Data.Aeson.Types.FromJSON.FromJSONKey ci, Data.Aeson.Types.ToJSON.ToJSONKey ci, GHC.Classes.Ord si, Data.Aeson.Types.FromJSON.FromJSONKey si, Data.Aeson.Types.ToJSON.ToJSONKey si, Autodocodec.Class.HasCodec si, GHC.Classes.Eq a, Autodocodec.Class.HasCodec a) => Autodocodec.Class.HasCodec (Data.Mergeless.Collection.ClientStore ci si a)
+ Data.Mergeless.Collection: instance (GHC.Classes.Ord ci, Data.Aeson.Types.FromJSON.FromJSONKey ci, Data.Aeson.Types.ToJSON.ToJSONKey ci, GHC.Classes.Ord si, Data.Aeson.Types.FromJSON.FromJSONKey si, Data.Aeson.Types.ToJSON.ToJSONKey si, Autodocodec.Class.HasCodec si, GHC.Classes.Eq a, Autodocodec.Class.HasCodec a) => Autodocodec.Class.HasCodec (Data.Mergeless.Collection.SyncRequest ci si a)
+ Data.Mergeless.Collection: instance (GHC.Classes.Ord ci, GHC.Classes.Ord si, Data.Aeson.Types.FromJSON.FromJSONKey ci, Data.Aeson.Types.FromJSON.FromJSONKey si, Data.Aeson.Types.ToJSON.ToJSONKey ci, Data.Aeson.Types.ToJSON.ToJSONKey si, Autodocodec.Class.HasCodec ci, Autodocodec.Class.HasCodec si, Autodocodec.Class.HasCodec a, GHC.Classes.Eq a) => Data.Aeson.Types.FromJSON.FromJSON (Data.Mergeless.Collection.SyncResponse ci si a)
+ Data.Mergeless.Collection: instance (GHC.Classes.Ord ci, GHC.Classes.Ord si, Data.Aeson.Types.FromJSON.FromJSONKey ci, Data.Aeson.Types.FromJSON.FromJSONKey si, Data.Aeson.Types.ToJSON.ToJSONKey ci, Data.Aeson.Types.ToJSON.ToJSONKey si, Autodocodec.Class.HasCodec ci, Autodocodec.Class.HasCodec si, Autodocodec.Class.HasCodec a, GHC.Classes.Eq a) => Data.Aeson.Types.ToJSON.ToJSON (Data.Mergeless.Collection.SyncResponse ci si a)
+ Data.Mergeless.Collection: instance (GHC.Classes.Ord ci, GHC.Classes.Ord si, Data.Aeson.Types.FromJSON.FromJSONKey ci, Data.Aeson.Types.FromJSON.FromJSONKey si, Data.Aeson.Types.ToJSON.ToJSONKey ci, Data.Aeson.Types.ToJSON.ToJSONKey si, Autodocodec.Class.HasCodec si, Autodocodec.Class.HasCodec a, GHC.Classes.Eq a) => Data.Aeson.Types.FromJSON.FromJSON (Data.Mergeless.Collection.ClientStore ci si a)
+ Data.Mergeless.Collection: instance (GHC.Classes.Ord ci, GHC.Classes.Ord si, Data.Aeson.Types.FromJSON.FromJSONKey ci, Data.Aeson.Types.FromJSON.FromJSONKey si, Data.Aeson.Types.ToJSON.ToJSONKey ci, Data.Aeson.Types.ToJSON.ToJSONKey si, Autodocodec.Class.HasCodec si, Autodocodec.Class.HasCodec a, GHC.Classes.Eq a) => Data.Aeson.Types.FromJSON.FromJSON (Data.Mergeless.Collection.SyncRequest ci si a)
+ Data.Mergeless.Collection: instance (GHC.Classes.Ord ci, GHC.Classes.Ord si, Data.Aeson.Types.FromJSON.FromJSONKey ci, Data.Aeson.Types.FromJSON.FromJSONKey si, Data.Aeson.Types.ToJSON.ToJSONKey ci, Data.Aeson.Types.ToJSON.ToJSONKey si, Autodocodec.Class.HasCodec si, Autodocodec.Class.HasCodec a, GHC.Classes.Eq a) => Data.Aeson.Types.ToJSON.ToJSON (Data.Mergeless.Collection.ClientStore ci si a)
+ Data.Mergeless.Collection: instance (GHC.Classes.Ord ci, GHC.Classes.Ord si, Data.Aeson.Types.FromJSON.FromJSONKey ci, Data.Aeson.Types.FromJSON.FromJSONKey si, Data.Aeson.Types.ToJSON.ToJSONKey ci, Data.Aeson.Types.ToJSON.ToJSONKey si, Autodocodec.Class.HasCodec si, Autodocodec.Class.HasCodec a, GHC.Classes.Eq a) => Data.Aeson.Types.ToJSON.ToJSON (Data.Mergeless.Collection.SyncRequest ci si a)
+ Data.Mergeless.Collection: instance (GHC.Classes.Ord si, Data.Aeson.Types.FromJSON.FromJSONKey si, Data.Aeson.Types.ToJSON.ToJSONKey si, Autodocodec.Class.HasCodec a) => Autodocodec.Class.HasCodec (Data.Mergeless.Collection.ServerStore si a)
+ Data.Mergeless.Collection: instance (GHC.Classes.Ord si, Data.Aeson.Types.FromJSON.FromJSONKey si, Data.Aeson.Types.ToJSON.ToJSONKey si, Autodocodec.Class.HasCodec a) => Data.Aeson.Types.FromJSON.FromJSON (Data.Mergeless.Collection.ServerStore si a)
+ Data.Mergeless.Collection: instance (GHC.Classes.Ord si, Data.Aeson.Types.FromJSON.FromJSONKey si, Data.Aeson.Types.ToJSON.ToJSONKey si, Autodocodec.Class.HasCodec a) => Data.Aeson.Types.ToJSON.ToJSON (Data.Mergeless.Collection.ServerStore si a)
+ Data.Mergeless.Collection: instance Autodocodec.Class.HasCodec Data.Mergeless.Collection.ClientId
+ Data.Mergeless.Item: instance Autodocodec.Class.HasCodec a => Autodocodec.Class.HasCodec (Data.Mergeless.Item.ClientItem a)
+ Data.Mergeless.Item: instance Autodocodec.Class.HasCodec a => Autodocodec.Class.HasCodec (Data.Mergeless.Item.ItemSyncRequest a)
+ Data.Mergeless.Item: instance Autodocodec.Class.HasCodec a => Autodocodec.Class.HasCodec (Data.Mergeless.Item.ItemSyncResponse a)
+ Data.Mergeless.Item: instance Autodocodec.Class.HasCodec a => Autodocodec.Class.HasCodec (Data.Mergeless.Item.ServerItem a)
+ Data.Mergeless.Item: instance Autodocodec.Class.HasCodec a => Data.Aeson.Types.FromJSON.FromJSON (Data.Mergeless.Item.ClientItem a)
+ Data.Mergeless.Item: instance Autodocodec.Class.HasCodec a => Data.Aeson.Types.FromJSON.FromJSON (Data.Mergeless.Item.ItemSyncRequest a)
+ Data.Mergeless.Item: instance Autodocodec.Class.HasCodec a => Data.Aeson.Types.FromJSON.FromJSON (Data.Mergeless.Item.ItemSyncResponse a)
+ Data.Mergeless.Item: instance Autodocodec.Class.HasCodec a => Data.Aeson.Types.FromJSON.FromJSON (Data.Mergeless.Item.ServerItem a)
+ Data.Mergeless.Item: instance Autodocodec.Class.HasCodec a => Data.Aeson.Types.ToJSON.ToJSON (Data.Mergeless.Item.ClientItem a)
+ Data.Mergeless.Item: instance Autodocodec.Class.HasCodec a => Data.Aeson.Types.ToJSON.ToJSON (Data.Mergeless.Item.ItemSyncRequest a)
+ Data.Mergeless.Item: instance Autodocodec.Class.HasCodec a => Data.Aeson.Types.ToJSON.ToJSON (Data.Mergeless.Item.ItemSyncResponse a)
+ Data.Mergeless.Item: instance Autodocodec.Class.HasCodec a => Data.Aeson.Types.ToJSON.ToJSON (Data.Mergeless.Item.ServerItem a)
- Data.Mergeless.Collection: ClientSyncProcessor :: (Map si a -> m ()) -> (Map ci si -> m ()) -> (Set si -> m ()) -> (Set si -> m ()) -> ClientSyncProcessor ci si a m
+ Data.Mergeless.Collection: ClientSyncProcessor :: !Map si a -> m () -> !Map ci si -> m () -> !Set si -> m () -> !Set si -> m () -> ClientSyncProcessor ci si a m
- Data.Mergeless.Collection: ServerSyncProcessor :: m (Map si a) -> (Map ci a -> m (Map ci si)) -> (Set si -> m (Set si)) -> ServerSyncProcessor ci si a m
+ Data.Mergeless.Collection: ServerSyncProcessor :: !m (Map si a) -> !Map ci a -> m (Map ci si) -> !Set si -> m (Set si) -> ServerSyncProcessor ci si a m
- Data.Mergeless.Collection: [clientSyncProcessorSyncClientAdded] :: ClientSyncProcessor ci si a m -> Map ci si -> m ()
+ Data.Mergeless.Collection: [clientSyncProcessorSyncClientAdded] :: ClientSyncProcessor ci si a m -> !Map ci si -> m ()
- Data.Mergeless.Collection: [clientSyncProcessorSyncClientDeleted] :: ClientSyncProcessor ci si a m -> Set si -> m ()
+ Data.Mergeless.Collection: [clientSyncProcessorSyncClientDeleted] :: ClientSyncProcessor ci si a m -> !Set si -> m ()
- Data.Mergeless.Collection: [clientSyncProcessorSyncServerAdded] :: ClientSyncProcessor ci si a m -> Map si a -> m ()
+ Data.Mergeless.Collection: [clientSyncProcessorSyncServerAdded] :: ClientSyncProcessor ci si a m -> !Map si a -> m ()
- Data.Mergeless.Collection: [clientSyncProcessorSyncServerDeleted] :: ClientSyncProcessor ci si a m -> Set si -> m ()
+ Data.Mergeless.Collection: [clientSyncProcessorSyncServerDeleted] :: ClientSyncProcessor ci si a m -> !Set si -> m ()
- Data.Mergeless.Collection: [serverSyncProcessorAddItems] :: ServerSyncProcessor ci si a m -> Map ci a -> m (Map ci si)
+ Data.Mergeless.Collection: [serverSyncProcessorAddItems] :: ServerSyncProcessor ci si a m -> !Map ci a -> m (Map ci si)
- Data.Mergeless.Collection: [serverSyncProcessorDeleteItems] :: ServerSyncProcessor ci si a m -> Set si -> m (Set si)
+ Data.Mergeless.Collection: [serverSyncProcessorDeleteItems] :: ServerSyncProcessor ci si a m -> !Set si -> m (Set si)
- Data.Mergeless.Collection: [serverSyncProcessorRead] :: ServerSyncProcessor ci si a m -> m (Map si a)
+ Data.Mergeless.Collection: [serverSyncProcessorRead] :: ServerSyncProcessor ci si a m -> !m (Map si a)
- Data.Mergeless.Item: ItemSyncRequestNew :: a -> ItemSyncRequest a
+ Data.Mergeless.Item: ItemSyncRequestNew :: !a -> ItemSyncRequest a
Files
- ChangeLog.md +9/−1
- README.md +0/−1
- mergeless.cabal +6/−5
- src/Data/Mergeless/Collection.hs +132/−96
- src/Data/Mergeless/Item.hs +120/−15
ChangeLog.md view
@@ -1,6 +1,14 @@ # Changelog for mergeless -## Unreleased changes+## [0.4.0.0] - 2022-06-19++### Added++* Autodocodec support++### Changed++* Made the json encoding of item sync types more sparse and forward-compatible. ## [0.3.0.0] - 2020-05-21
− README.md
@@ -1,1 +0,0 @@-# mergeless
mergeless.cabal view
@@ -1,23 +1,22 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.33.0.+-- This file has been generated from package.yaml by hpack version 0.34.7. -- -- see: https://github.com/sol/hpack ----- hash: 5f5e451a78c312ccbef6cc567464e0983bc9a8fbba6a7f428016c9f186aeebcb+-- hash: e31706648c86473e95cc860e1d3c9ca33a3e44055ecdd5c0136f1d68a0ab191f name: mergeless-version: 0.3.0.0+version: 0.4.0.0 description: Please see the README on GitHub at <https://github.com/NorfairKing/mergeless#readme> homepage: https://github.com/NorfairKing/mergeless#readme bug-reports: https://github.com/NorfairKing/mergeless/issues author: Tom Sydney Kerckhove maintainer: syd.kerckhove@gmail.com-copyright: Copyright: (c) 2018-2020 Tom Sydney Kerckhove+copyright: Copyright: (c) 2018-2022 Tom Sydney Kerckhove license: MIT build-type: Simple extra-source-files:- README.md ChangeLog.md source-repository head@@ -36,10 +35,12 @@ ghc-options: -Wall -fwarn-redundant-constraints build-depends: aeson+ , autodocodec , base >=4.11 && <5 , containers , deepseq , mtl+ , text , validity , validity-containers default-language: Haskell2010
src/Data/Mergeless/Collection.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingVia #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}@@ -80,14 +81,15 @@ ) where +import Autodocodec import Control.DeepSeq import Control.Monad.State.Strict-import Data.Aeson+import Data.Aeson (FromJSON, FromJSONKey (..), ToJSON, ToJSONKey (..)) import Data.List import Data.Map.Strict (Map) import qualified Data.Map.Strict as M-import qualified Data.Set as S import Data.Set (Set)+import qualified Data.Set as S import Data.Validity import Data.Validity.Containers () import Data.Word@@ -98,24 +100,28 @@ -- | A Client-side identifier for items for use with pure client stores -- -- These only need to be unique at the client.-newtype ClientId- = ClientId- { unClientId :: Word64- }- deriving (Show, Eq, Ord, Enum, Bounded, Generic, ToJSON, ToJSONKey, FromJSON, FromJSONKey)+newtype ClientId = ClientId+ { unClientId :: Word64+ }+ deriving stock (Show, Eq, Ord, Generic)+ deriving newtype (Enum, Bounded, ToJSONKey, FromJSONKey)+ deriving (FromJSON, ToJSON) via (Autodocodec ClientId) instance Validity ClientId instance NFData ClientId +instance HasCodec ClientId where+ codec = dimapCodec ClientId unClientId codec <?> "ClientId"+ -- | A client-side store of items with Client Id's of type @ci@, Server Id's of type @i@ and values of type @a@-data ClientStore ci si a- = ClientStore- { clientStoreAdded :: !(Map ci a),- clientStoreSynced :: !(Map si a),- clientStoreDeleted :: !(Set si)- }+data ClientStore ci si a = ClientStore+ { clientStoreAdded :: !(Map ci a),+ clientStoreSynced :: !(Map si a),+ clientStoreDeleted :: !(Set si)+ } deriving (Show, Eq, Ord, Generic)+ deriving (FromJSON, ToJSON) via (Autodocodec (ClientStore ci si a)) instance (NFData ci, NFData si, NFData a) => NFData (ClientStore ci si a) @@ -123,21 +129,30 @@ validate cs@ClientStore {..} = mconcat [ genericValidate cs,- declare "the store items have distinct ids"- $ distinct- $ M.keys clientStoreSynced ++ S.toList clientStoreDeleted+ declare "the store items have distinct ids" $+ distinct $+ M.keys clientStoreSynced ++ S.toList clientStoreDeleted ] -instance (Ord ci, FromJSON ci, FromJSONKey ci, Ord si, FromJSON si, FromJSONKey si, FromJSON a) => FromJSON (ClientStore ci si a) where- parseJSON =- withObject "ClientStore" $ \o ->- ClientStore <$> o .:? "added" .!= M.empty <*> o .:? "synced" .!= M.empty- <*> o .:? "deleted" .!= S.empty--instance (Ord ci, ToJSON ci, ToJSONKey ci, Ord si, ToJSON si, ToJSONKey si, ToJSON a) => ToJSON (ClientStore ci si a) where- toJSON ClientStore {..} =- object- ["added" .= clientStoreAdded, "synced" .= clientStoreSynced, "deleted" .= clientStoreDeleted]+instance+ ( Ord ci,+ FromJSONKey ci,+ ToJSONKey ci,+ Ord si,+ FromJSONKey si,+ ToJSONKey si,+ HasCodec si,+ Eq a,+ HasCodec a+ ) =>+ HasCodec (ClientStore ci si a)+ where+ codec =+ object "ClientStore" $+ ClientStore+ <$> optionalFieldWithOmittedDefault "added" M.empty "added items" .= clientStoreAdded+ <*> optionalFieldWithOmittedDefault "synced" M.empty "synced items" .= clientStoreSynced+ <*> optionalFieldWithOmittedDefault "deleted" S.empty "deleted items" .= clientStoreDeleted -- | The client store with no items. emptyClientStore :: ClientStore ci si a@@ -206,13 +221,13 @@ } -- | A synchronisation request for items with Client Id's of type @ci@, Server Id's of type @i@ and values of type @a@-data SyncRequest ci si a- = SyncRequest- { syncRequestAdded :: !(Map ci a),- syncRequestSynced :: !(Set si),- syncRequestDeleted :: !(Set si)- }- deriving (Show, Eq, Ord, Generic)+data SyncRequest ci si a = SyncRequest+ { syncRequestAdded :: !(Map ci a),+ syncRequestSynced :: !(Set si),+ syncRequestDeleted :: !(Set si)+ }+ deriving stock (Show, Eq, Ord, Generic)+ deriving (FromJSON, ToJSON) via (Autodocodec (SyncRequest ci si a)) instance (NFData ci, NFData si, NFData a) => NFData (SyncRequest ci si a) @@ -220,23 +235,30 @@ validate sr@SyncRequest {..} = mconcat [ genericValidate sr,- declare "the sync request items have distinct ids"- $ distinct- $ S.toList syncRequestSynced ++ S.toList syncRequestDeleted+ declare "the sync request items have distinct ids" $+ distinct $+ S.toList syncRequestSynced ++ S.toList syncRequestDeleted ] -instance (FromJSON ci, FromJSON si, FromJSON a, FromJSONKey ci, Ord ci, Ord si, Ord a) => FromJSON (SyncRequest ci si a) where- parseJSON =- withObject "SyncRequest" $ \o ->- SyncRequest <$> o .: "added" <*> o .: "synced" <*> o .: "deleted"--instance (ToJSON ci, ToJSON si, ToJSON a, ToJSONKey ci) => ToJSON (SyncRequest ci si a) where- toJSON SyncRequest {..} =- object- [ "added" .= syncRequestAdded,- "synced" .= syncRequestSynced,- "deleted" .= syncRequestDeleted- ]+instance+ ( Ord ci,+ FromJSONKey ci,+ ToJSONKey ci,+ Ord si,+ FromJSONKey si,+ ToJSONKey si,+ HasCodec si,+ Eq a,+ HasCodec a+ ) =>+ HasCodec (SyncRequest ci si a)+ where+ codec =+ object "SyncRequest" $+ SyncRequest+ <$> optionalFieldWithOmittedDefault "added" M.empty "new items" .= syncRequestAdded+ <*> optionalFieldWithOmittedDefault "synced" S.empty "known items" .= syncRequestSynced+ <*> optionalFieldWithOmittedDefault "deleted" S.empty "deleted items" .= syncRequestDeleted emptySyncRequest :: SyncRequest ci si a emptySyncRequest =@@ -258,14 +280,14 @@ } -- | A synchronisation response for items with identifiers of type @i@ and values of type @a@-data SyncResponse ci si a- = SyncResponse- { syncResponseClientAdded :: !(Map ci si),- syncResponseClientDeleted :: !(Set si),- syncResponseServerAdded :: !(Map si a),- syncResponseServerDeleted :: !(Set si)- }+data SyncResponse ci si a = SyncResponse+ { syncResponseClientAdded :: !(Map ci si),+ syncResponseClientDeleted :: !(Set si),+ syncResponseServerAdded :: !(Map si a),+ syncResponseServerDeleted :: !(Set si)+ } deriving (Show, Eq, Ord, Generic)+ deriving (FromJSON, ToJSON) via (Autodocodec (SyncResponse ci si a)) instance (NFData ci, NFData si, NFData a) => NFData (SyncResponse ci si a) @@ -273,31 +295,37 @@ validate sr@SyncResponse {..} = mconcat [ genericValidate sr,- declare "the sync response items have distinct uuids"- $ distinct- $ concat- [ M.elems syncResponseClientAdded,- S.toList syncResponseClientDeleted,- M.keys syncResponseServerAdded,- S.toList syncResponseServerDeleted- ]+ declare "the sync response items have distinct uuids" $+ distinct $+ concat+ [ M.elems syncResponseClientAdded,+ S.toList syncResponseClientDeleted,+ M.keys syncResponseServerAdded,+ S.toList syncResponseServerDeleted+ ] ] -instance (Ord ci, Ord si, FromJSON ci, FromJSON si, FromJSONKey ci, FromJSONKey si, Ord a, FromJSON a) => FromJSON (SyncResponse ci si a) where- parseJSON =- withObject "SyncResponse" $ \o ->- SyncResponse <$> o .: "client-added" <*> o .: "client-deleted" <*> o .: "server-added"- <*> o- .: "server-deleted"--instance (ToJSON ci, ToJSON si, ToJSONKey ci, ToJSONKey si, ToJSON a) => ToJSON (SyncResponse ci si a) where- toJSON SyncResponse {..} =- object- [ "client-added" .= syncResponseClientAdded,- "client-deleted" .= syncResponseClientDeleted,- "server-added" .= syncResponseServerAdded,- "server-deleted" .= syncResponseServerDeleted- ]+instance+ ( Ord ci,+ FromJSONKey ci,+ ToJSONKey ci,+ HasCodec ci,+ Ord si,+ FromJSONKey si,+ ToJSONKey si,+ HasCodec si,+ Eq a,+ HasCodec a+ ) =>+ HasCodec (SyncResponse ci si a)+ where+ codec =+ object "SyncResponse" $+ SyncResponse+ <$> optionalFieldWithOmittedDefault "client-added" M.empty "items added by the client" .= syncResponseClientAdded+ <*> optionalFieldWithOmittedDefault "client-deleted" S.empty "items deleted by the client" .= syncResponseClientDeleted+ <*> optionalFieldWithOmittedDefault "server-added" M.empty "items added by the server" .= syncResponseServerAdded+ <*> optionalFieldWithOmittedDefault "server-deleted" S.empty "items deleted by the server" .= syncResponseServerDeleted emptySyncResponse :: SyncResponse ci si a emptySyncResponse =@@ -342,13 +370,12 @@ cs {clientStoreDeleted = clientStoreDeleted cs `S.difference` cd} } -data ClientSyncProcessor ci si a m- = ClientSyncProcessor- { clientSyncProcessorSyncServerAdded :: Map si a -> m (),- clientSyncProcessorSyncClientAdded :: Map ci si -> m (),- clientSyncProcessorSyncServerDeleted :: Set si -> m (),- clientSyncProcessorSyncClientDeleted :: Set si -> m ()- }+data ClientSyncProcessor ci si a m = ClientSyncProcessor+ { clientSyncProcessorSyncServerAdded :: !(Map si a -> m ()),+ clientSyncProcessorSyncClientAdded :: !(Map ci si -> m ()),+ clientSyncProcessorSyncServerDeleted :: !(Set si -> m ()),+ clientSyncProcessorSyncClientDeleted :: !(Set si -> m ())+ } deriving (Generic) mergeSyncResponseCustom :: Monad m => ClientSyncProcessor ci si a m -> SyncResponse ci si a -> m ()@@ -360,12 +387,11 @@ clientSyncProcessorSyncClientAdded syncResponseClientAdded -- | A record of the basic operations that are necessary to build a synchronisation processor.-data ServerSyncProcessor ci si a m- = ServerSyncProcessor- { serverSyncProcessorRead :: m (Map si a),- serverSyncProcessorAddItems :: Map ci a -> m (Map ci si),- serverSyncProcessorDeleteItems :: Set si -> m (Set si)- }+data ServerSyncProcessor ci si a m = ServerSyncProcessor+ { serverSyncProcessorRead :: !(m (Map si a)),+ serverSyncProcessorAddItems :: !(Map ci a -> m (Map ci si)),+ serverSyncProcessorDeleteItems :: !(Set si -> m (Set si))+ } deriving (Generic) processServerSyncCustom ::@@ -383,15 +409,25 @@ pure SyncResponse {..} -- | A central store of items with identifiers of type @i@ and values of type @a@-newtype ServerStore si a- = ServerStore- { serverStoreItems :: Map si a- }- deriving (Show, Eq, Ord, Generic, FromJSON, ToJSON)+newtype ServerStore si a = ServerStore+ { serverStoreItems :: Map si a+ }+ deriving (Show, Eq, Ord, Generic)+ deriving (FromJSON, ToJSON) via (Autodocodec (ServerStore si a)) instance (NFData si, NFData a) => NFData (ServerStore si a) instance (Validity si, Validity a, Show si, Show a, Ord si) => Validity (ServerStore si a)++instance+ ( Ord si,+ FromJSONKey si,+ ToJSONKey si,+ HasCodec a+ ) =>+ HasCodec (ServerStore si a)+ where+ codec = dimapCodec ServerStore serverStoreItems codec -- | An empty central store to start with emptyServerStore :: ServerStore si a
src/Data/Mergeless/Item.hs view
@@ -1,12 +1,16 @@ {-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingVia #-} {-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} module Data.Mergeless.Item where +import Autodocodec import Control.DeepSeq-import Data.Aeson+import Data.Aeson (FromJSON, ToJSON)+import Data.Text (Text) import Data.Validity import Data.Validity.Containers () import GHC.Generics (Generic)@@ -18,32 +22,85 @@ | ClientAdded !a | ClientSynced !a | ClientDeleted- deriving (Show, Eq, Ord, Generic)+ deriving stock (Show, Eq, Ord, Generic)+ deriving (FromJSON, ToJSON) via (Autodocodec (ClientItem a)) instance Validity a => Validity (ClientItem a) instance NFData a => NFData (ClientItem a) -instance FromJSON a => FromJSON (ClientItem a)+instance HasCodec a => HasCodec (ClientItem a) where+ codec =+ object "ClientItem" $+ dimapCodec f g $+ disjointEitherCodec+ ( disjointEitherCodec+ (typeField "empty" <*> pure ())+ (typeField "added" <*> requiredField "value" "item that was added, client-side")+ )+ ( disjointEitherCodec+ (typeField "synced" <*> requiredField "value" "the item that is known, client-side")+ (typeField "deleted" <*> pure ())+ )+ where+ f :: Either (Either () a) (Either a ()) -> ClientItem a+ f = \case+ Left (Left ()) -> ClientEmpty+ Left (Right v) -> ClientAdded v+ Right (Left v) -> ClientSynced v+ Right (Right ()) -> ClientDeleted+ g :: ClientItem a -> Either (Either () a) (Either a ())+ g = \case+ ClientEmpty -> Left (Left ())+ ClientAdded v -> Left (Right v)+ ClientSynced v -> Right (Left v)+ ClientDeleted -> Right (Right ()) -instance ToJSON a => ToJSON (ClientItem a)+ typeField :: Text -> ObjectCodec b (x -> x)+ typeField typeName = id <$ requiredFieldWith' "type" (literalTextCodec typeName) .= const typeName -- | A synchronisation request for items with identifiers of type @i@ and values of type @a@ data ItemSyncRequest a = ItemSyncRequestPoll- | ItemSyncRequestNew a+ | ItemSyncRequestNew !a | ItemSyncRequestKnown | ItemSyncRequestDeleted- deriving (Show, Eq, Ord, Generic)+ deriving stock (Show, Eq, Ord, Generic)+ deriving (FromJSON, ToJSON) via (Autodocodec (ItemSyncRequest a)) instance Validity a => Validity (ItemSyncRequest a) instance NFData a => NFData (ItemSyncRequest a) -instance FromJSON a => FromJSON (ItemSyncRequest a)+instance HasCodec a => HasCodec (ItemSyncRequest a) where+ codec =+ object "ItemSyncRequest" $+ dimapCodec f g $+ disjointEitherCodec+ ( disjointEitherCodec+ (typeField "empty" <*> pure ())+ (typeField "added" <*> requiredField "value" "item that was added, client-side")+ )+ ( disjointEitherCodec+ (typeField "synced" <*> pure ())+ (typeField "deleted" <*> pure ())+ )+ where+ f = \case+ Left (Left ()) -> ItemSyncRequestPoll+ Left (Right v) -> ItemSyncRequestNew v+ Right (Left ()) -> ItemSyncRequestKnown+ Right (Right ()) -> ItemSyncRequestDeleted -instance ToJSON a => ToJSON (ItemSyncRequest a)+ g = \case+ ItemSyncRequestPoll -> Left (Left ())+ ItemSyncRequestNew v -> Left (Right v)+ ItemSyncRequestKnown -> Right (Left ())+ ItemSyncRequestDeleted -> Right (Right ()) + typeField :: Text -> ObjectCodec b (x -> x)+ typeField typeName = id <$ requiredFieldWith' "type" (literalTextCodec typeName) .= const typeName+ makeItemSyncRequest :: ClientItem a -> ItemSyncRequest a makeItemSyncRequest ci = case ci of@@ -60,16 +117,52 @@ | ItemSyncResponseClientDeleted | ItemSyncResponseServerAdded !a | ItemSyncResponseServerDeleted- deriving (Show, Eq, Ord, Generic)+ deriving stock (Show, Eq, Ord, Generic)+ deriving (FromJSON, ToJSON) via (Autodocodec (ItemSyncResponse a)) instance Validity a => Validity (ItemSyncResponse a) instance NFData a => NFData (ItemSyncResponse a) -instance FromJSON a => FromJSON (ItemSyncResponse a)+instance HasCodec a => HasCodec (ItemSyncResponse a) where+ codec =+ object "ItemSyncResponse" $+ dimapCodec f g $+ disjointEitherCodec+ ( disjointEitherCodec+ (typeField "in-sync-empty" <*> pure ())+ (typeField "in-sync-full" <*> pure ())+ )+ ( disjointEitherCodec+ ( disjointEitherCodec+ (typeField "client-added" <*> pure ())+ (typeField "client-deleted" <*> pure ())+ )+ ( disjointEitherCodec+ (typeField "server-added" <*> requiredField "value" "the value that was added, server-side")+ (typeField "server-deleted" <*> pure ())+ )+ )+ where+ f = \case+ Left (Left ()) -> ItemSyncResponseInSyncEmpty+ Left (Right ()) -> ItemSyncResponseInSyncFull+ Right (Left (Left ())) -> ItemSyncResponseClientAdded+ Right (Left (Right ())) -> ItemSyncResponseClientDeleted+ Right (Right (Left v)) -> ItemSyncResponseServerAdded v+ Right (Right (Right ())) -> ItemSyncResponseServerDeleted -instance ToJSON a => ToJSON (ItemSyncResponse a)+ g = \case+ ItemSyncResponseInSyncEmpty -> Left (Left ())+ ItemSyncResponseInSyncFull -> Left (Right ())+ ItemSyncResponseClientAdded -> Right (Left (Left ()))+ ItemSyncResponseClientDeleted -> Right (Left (Right ()))+ ItemSyncResponseServerAdded v -> Right (Right (Left v))+ ItemSyncResponseServerDeleted -> Right (Right (Right ())) + typeField :: Text -> ObjectCodec b (x -> x)+ typeField typeName = id <$ requiredFieldWith' "type" (literalTextCodec typeName) .= const typeName+ -- | Merge a synchronisation response back into a client-side store. mergeItemSyncResponse :: ClientItem a -> ItemSyncResponse a -> ClientItem a mergeItemSyncResponse ci sr =@@ -102,15 +195,27 @@ data ServerItem a = ServerItemEmpty | ServerItemFull !a- deriving (Show, Eq, Ord, Generic)+ deriving stock (Show, Eq, Ord, Generic)+ deriving (FromJSON, ToJSON) via (Autodocodec (ServerItem a)) instance Validity a => Validity (ServerItem a) instance NFData a => NFData (ServerItem a) -instance FromJSON a => FromJSON (ServerItem a)--instance ToJSON a => ToJSON (ServerItem a)+instance HasCodec a => HasCodec (ServerItem a) where+ codec =+ object "ServerItem" $+ dimapCodec f g $+ possiblyJointEitherCodec+ (requiredField "value" "the item on the server side")+ (pure ())+ where+ f = \case+ Left v -> ServerItemFull v+ Right () -> ServerItemEmpty+ g = \case+ ServerItemFull v -> Left v+ ServerItemEmpty -> Right () processServerItemSync :: ServerItem a -> ItemSyncRequest a -> (ItemSyncResponse a, ServerItem a) processServerItemSync si sr =