packages feed

mergeless 0.1.0.0 → 0.2.0.0

raw patch · 5 files changed

+599/−505 lines, 5 filesdep +deepseqdep −timedep −validity-timePVP ok

version bump matches the API change (PVP)

Dependencies added: deepseq

Dependencies removed: time, validity-time

API changes (from Hackage documentation)

- Data.Mergeless: Added :: !a -> !UTCTime -> Added a
- Data.Mergeless: CentralItem :: !a -> !UTCTime -> !UTCTime -> CentralItem a
- Data.Mergeless: CentralStore :: Map i (CentralItem a) -> CentralStore i a
- Data.Mergeless: Store :: Set (StoreItem i a) -> Store i a
- Data.Mergeless: SyncProcessor :: Set i -> m () -> Set i -> m (Set i) -> Set i -> m (Set (Synced i a)) -> Set (Synced i a) -> m () -> SyncProcessor i a m
- Data.Mergeless: SyncRequest :: !(Set (Added a)) -> !(Set i) -> !(Set i) -> SyncRequest i a
- Data.Mergeless: SyncResponse :: !(Set (Synced i a)) -> !(Set (Synced i a)) -> !(Set i) -> SyncResponse i a
- Data.Mergeless: Synced :: i -> !a -> !UTCTime -> !UTCTime -> Synced i a
- Data.Mergeless: SyncedItem :: !(Synced i a) -> StoreItem i a
- Data.Mergeless: UndeletedItem :: !i -> StoreItem i a
- Data.Mergeless: UnsyncedItem :: !(Added a) -> StoreItem i a
- Data.Mergeless: [addedCreated] :: Added a -> !UTCTime
- Data.Mergeless: [addedValue] :: Added a -> !a
- Data.Mergeless: [centralCreated] :: CentralItem a -> !UTCTime
- Data.Mergeless: [centralStoreItems] :: CentralStore i a -> Map i (CentralItem a)
- Data.Mergeless: [centralSynced] :: CentralItem a -> !UTCTime
- Data.Mergeless: [centralValue] :: CentralItem a -> !a
- Data.Mergeless: [storeItems] :: Store i a -> Set (StoreItem i a)
- Data.Mergeless: [syncProcessorDeleteMany] :: SyncProcessor i a m -> Set i -> m ()
- Data.Mergeless: [syncProcessorInsertMany] :: SyncProcessor i a m -> Set (Synced i a) -> m ()
- Data.Mergeless: [syncProcessorQueryNewRemote] :: SyncProcessor i a m -> Set i -> m (Set (Synced i a))
- Data.Mergeless: [syncProcessorQuerySynced] :: SyncProcessor i a m -> Set i -> m (Set i)
- Data.Mergeless: [syncRequestAddedItems] :: SyncRequest i a -> !(Set (Added a))
- Data.Mergeless: [syncRequestSyncedItems] :: SyncRequest i a -> !(Set i)
- Data.Mergeless: [syncRequestUndeletedItems] :: SyncRequest i a -> !(Set i)
- Data.Mergeless: [syncResponseAddedItems] :: SyncResponse i a -> !(Set (Synced i a))
- Data.Mergeless: [syncResponseItemsToBeDeletedLocally] :: SyncResponse i a -> !(Set i)
- Data.Mergeless: [syncResponseNewRemoteItems] :: SyncResponse i a -> !(Set (Synced i a))
- Data.Mergeless: [syncedCreated] :: Synced i a -> !UTCTime
- Data.Mergeless: [syncedSynced] :: Synced i a -> !UTCTime
- Data.Mergeless: [syncedUuid] :: Synced i a -> i
- Data.Mergeless: [syncedValue] :: Synced i a -> !a
- Data.Mergeless: addItemToStore :: (Ord i, Ord a) => Added a -> Store i a -> Store i a
- Data.Mergeless: data Added a
- Data.Mergeless: data CentralItem a
- Data.Mergeless: data StoreItem i a
- Data.Mergeless: data SyncProcessor i a m
- Data.Mergeless: data SyncRequest i a
- Data.Mergeless: data SyncResponse i a
- Data.Mergeless: data Synced i a
- Data.Mergeless: deleteSynced :: (Ord i, Ord a) => Synced i a -> Store i a -> Store i a
- Data.Mergeless: deleteUnsynced :: (Ord i, Ord a) => Added a -> Store i a -> Store i a
- Data.Mergeless: emptyStore :: Store i a
- Data.Mergeless: instance (Data.Aeson.Types.FromJSON.FromJSON i, Data.Aeson.Types.FromJSON.FromJSON a) => Data.Aeson.Types.FromJSON.FromJSON (Data.Mergeless.StoreItem i a)
- Data.Mergeless: instance (Data.Aeson.Types.FromJSON.FromJSON i, Data.Aeson.Types.FromJSON.FromJSON a) => Data.Aeson.Types.FromJSON.FromJSON (Data.Mergeless.Synced i a)
- Data.Mergeless: instance (Data.Aeson.Types.FromJSON.FromJSON i, Data.Aeson.Types.FromJSON.FromJSON a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.Aeson.Types.FromJSON.FromJSON (Data.Mergeless.SyncRequest i a)
- Data.Mergeless: instance (Data.Aeson.Types.FromJSON.FromJSON i, Data.Aeson.Types.FromJSON.FromJSON a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.Aeson.Types.FromJSON.FromJSON (Data.Mergeless.SyncResponse i a)
- Data.Mergeless: instance (Data.Aeson.Types.FromJSON.FromJSONKey i, GHC.Classes.Ord i, Data.Aeson.Types.FromJSON.FromJSON a) => Data.Aeson.Types.FromJSON.FromJSON (Data.Mergeless.CentralStore i a)
- Data.Mergeless: instance (Data.Aeson.Types.ToJSON.ToJSON a, Data.Aeson.Types.ToJSON.ToJSONKey i) => Data.Aeson.Types.ToJSON.ToJSON (Data.Mergeless.CentralStore i a)
- Data.Mergeless: instance (Data.Aeson.Types.ToJSON.ToJSON i, Data.Aeson.Types.ToJSON.ToJSON a) => Data.Aeson.Types.ToJSON.ToJSON (Data.Mergeless.Store i a)
- Data.Mergeless: instance (Data.Aeson.Types.ToJSON.ToJSON i, Data.Aeson.Types.ToJSON.ToJSON a) => Data.Aeson.Types.ToJSON.ToJSON (Data.Mergeless.StoreItem i a)
- Data.Mergeless: instance (Data.Aeson.Types.ToJSON.ToJSON i, Data.Aeson.Types.ToJSON.ToJSON a) => Data.Aeson.Types.ToJSON.ToJSON (Data.Mergeless.SyncRequest i a)
- Data.Mergeless: instance (Data.Aeson.Types.ToJSON.ToJSON i, Data.Aeson.Types.ToJSON.ToJSON a) => Data.Aeson.Types.ToJSON.ToJSON (Data.Mergeless.SyncResponse i a)
- Data.Mergeless: instance (Data.Aeson.Types.ToJSON.ToJSON i, Data.Aeson.Types.ToJSON.ToJSON a) => Data.Aeson.Types.ToJSON.ToJSON (Data.Mergeless.Synced i a)
- Data.Mergeless: instance (Data.Validity.Validity i, Data.Validity.Validity a) => Data.Validity.Validity (Data.Mergeless.StoreItem i a)
- Data.Mergeless: instance (Data.Validity.Validity i, Data.Validity.Validity a) => Data.Validity.Validity (Data.Mergeless.Synced i a)
- Data.Mergeless: instance (Data.Validity.Validity i, Data.Validity.Validity a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.Validity.Validity (Data.Mergeless.CentralStore i a)
- Data.Mergeless: instance (Data.Validity.Validity i, Data.Validity.Validity a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.Validity.Validity (Data.Mergeless.Store i a)
- Data.Mergeless: instance (Data.Validity.Validity i, Data.Validity.Validity a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.Validity.Validity (Data.Mergeless.SyncRequest i a)
- Data.Mergeless: instance (Data.Validity.Validity i, Data.Validity.Validity a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.Validity.Validity (Data.Mergeless.SyncResponse i a)
- Data.Mergeless: instance (GHC.Classes.Eq a, GHC.Classes.Eq i) => GHC.Classes.Eq (Data.Mergeless.Store i a)
- Data.Mergeless: instance (GHC.Classes.Eq a, GHC.Classes.Eq i) => GHC.Classes.Eq (Data.Mergeless.StoreItem i a)
- Data.Mergeless: instance (GHC.Classes.Eq a, GHC.Classes.Eq i) => GHC.Classes.Eq (Data.Mergeless.SyncRequest i a)
- Data.Mergeless: instance (GHC.Classes.Eq i, GHC.Classes.Eq a) => GHC.Classes.Eq (Data.Mergeless.CentralStore i a)
- Data.Mergeless: instance (GHC.Classes.Eq i, GHC.Classes.Eq a) => GHC.Classes.Eq (Data.Mergeless.SyncResponse i a)
- Data.Mergeless: instance (GHC.Classes.Eq i, GHC.Classes.Eq a) => GHC.Classes.Eq (Data.Mergeless.Synced i a)
- Data.Mergeless: instance (GHC.Classes.Ord a, GHC.Classes.Ord i) => GHC.Classes.Ord (Data.Mergeless.Store i a)
- Data.Mergeless: instance (GHC.Classes.Ord a, GHC.Classes.Ord i) => GHC.Classes.Ord (Data.Mergeless.StoreItem i a)
- Data.Mergeless: instance (GHC.Classes.Ord a, GHC.Classes.Ord i) => GHC.Classes.Ord (Data.Mergeless.SyncRequest i a)
- Data.Mergeless: instance (GHC.Classes.Ord a, GHC.Classes.Ord i, Data.Aeson.Types.FromJSON.FromJSON i, Data.Aeson.Types.FromJSON.FromJSON a) => Data.Aeson.Types.FromJSON.FromJSON (Data.Mergeless.Store i a)
- Data.Mergeless: instance (GHC.Classes.Ord i, GHC.Classes.Ord a) => GHC.Classes.Ord (Data.Mergeless.CentralStore i a)
- Data.Mergeless: instance (GHC.Classes.Ord i, GHC.Classes.Ord a) => GHC.Classes.Ord (Data.Mergeless.SyncResponse i a)
- Data.Mergeless: instance (GHC.Classes.Ord i, GHC.Classes.Ord a) => GHC.Classes.Ord (Data.Mergeless.Synced i a)
- Data.Mergeless: instance (GHC.Show.Show a, GHC.Show.Show i) => GHC.Show.Show (Data.Mergeless.Store i a)
- Data.Mergeless: instance (GHC.Show.Show a, GHC.Show.Show i) => GHC.Show.Show (Data.Mergeless.StoreItem i a)
- Data.Mergeless: instance (GHC.Show.Show a, GHC.Show.Show i) => GHC.Show.Show (Data.Mergeless.SyncRequest i a)
- Data.Mergeless: instance (GHC.Show.Show i, GHC.Show.Show a) => GHC.Show.Show (Data.Mergeless.CentralStore i a)
- Data.Mergeless: instance (GHC.Show.Show i, GHC.Show.Show a) => GHC.Show.Show (Data.Mergeless.SyncResponse i a)
- Data.Mergeless: instance (GHC.Show.Show i, GHC.Show.Show a) => GHC.Show.Show (Data.Mergeless.Synced i a)
- Data.Mergeless: instance Data.Aeson.Types.FromJSON.FromJSON a => Data.Aeson.Types.FromJSON.FromJSON (Data.Mergeless.Added a)
- Data.Mergeless: instance Data.Aeson.Types.FromJSON.FromJSON a => Data.Aeson.Types.FromJSON.FromJSON (Data.Mergeless.CentralItem a)
- Data.Mergeless: instance Data.Aeson.Types.ToJSON.ToJSON a => Data.Aeson.Types.ToJSON.ToJSON (Data.Mergeless.Added a)
- Data.Mergeless: instance Data.Aeson.Types.ToJSON.ToJSON a => Data.Aeson.Types.ToJSON.ToJSON (Data.Mergeless.CentralItem a)
- Data.Mergeless: instance Data.Validity.Validity a => Data.Validity.Validity (Data.Mergeless.Added a)
- Data.Mergeless: instance Data.Validity.Validity a => Data.Validity.Validity (Data.Mergeless.CentralItem a)
- Data.Mergeless: instance GHC.Classes.Eq a => GHC.Classes.Eq (Data.Mergeless.Added a)
- Data.Mergeless: instance GHC.Classes.Eq a => GHC.Classes.Eq (Data.Mergeless.CentralItem a)
- Data.Mergeless: instance GHC.Classes.Ord a => GHC.Classes.Ord (Data.Mergeless.Added a)
- Data.Mergeless: instance GHC.Classes.Ord a => GHC.Classes.Ord (Data.Mergeless.CentralItem a)
- Data.Mergeless: instance GHC.Generics.Generic (Data.Mergeless.Added a)
- Data.Mergeless: instance GHC.Generics.Generic (Data.Mergeless.CentralItem a)
- Data.Mergeless: instance GHC.Generics.Generic (Data.Mergeless.CentralStore i a)
- Data.Mergeless: instance GHC.Generics.Generic (Data.Mergeless.Store i a)
- Data.Mergeless: instance GHC.Generics.Generic (Data.Mergeless.StoreItem i a)
- Data.Mergeless: instance GHC.Generics.Generic (Data.Mergeless.SyncProcessor i a m)
- Data.Mergeless: instance GHC.Generics.Generic (Data.Mergeless.SyncRequest i a)
- Data.Mergeless: instance GHC.Generics.Generic (Data.Mergeless.SyncResponse i a)
- Data.Mergeless: instance GHC.Generics.Generic (Data.Mergeless.Synced i a)
- Data.Mergeless: instance GHC.Show.Show a => GHC.Show.Show (Data.Mergeless.Added a)
- Data.Mergeless: instance GHC.Show.Show a => GHC.Show.Show (Data.Mergeless.CentralItem a)
- Data.Mergeless: makeSyncRequest :: (Ord i, Ord a) => Store i a -> SyncRequest i a
- Data.Mergeless: mergeSyncResponse :: (Ord i, Ord a) => Store i a -> SyncResponse i a -> Store i a
- Data.Mergeless: newtype CentralStore i a
- Data.Mergeless: newtype Store i a
- Data.Mergeless: processSync :: (Ord i, Ord a, MonadIO m) => m i -> CentralStore i a -> SyncRequest i a -> m (SyncResponse i a, CentralStore i a)
- Data.Mergeless: processSyncCustom :: forall i a m. (Ord i, Ord a, Monad m) => m i -> UTCTime -> SyncProcessor i a m -> SyncRequest i a -> m (SyncResponse i a)
- Data.Mergeless: processSyncWith :: forall i a m. (Ord i, Ord a, Monad m) => m i -> UTCTime -> CentralStore i a -> SyncRequest i a -> m (SyncResponse i a, CentralStore i a)
- Data.Mergeless: storeSize :: Store i a -> Int
+ Data.Mergeless.Collection: ClientId :: Word64 -> ClientId
+ Data.Mergeless.Collection: ClientStore :: !Map ClientId a -> !Map i a -> !Set i -> ClientStore i a
+ Data.Mergeless.Collection: ServerStore :: Map i a -> ServerStore i a
+ Data.Mergeless.Collection: ServerSyncProcessor :: (Set i -> m (Set i)) -> (Set i -> m (Set i)) -> (Set i -> m (Map i a)) -> (Map ClientId a -> m (Map ClientId i)) -> ServerSyncProcessor i a m
+ Data.Mergeless.Collection: SyncRequest :: !Map ClientId a -> !Set i -> !Set i -> SyncRequest i a
+ Data.Mergeless.Collection: SyncResponse :: !Map ClientId i -> !Set i -> !Map i a -> !Set i -> SyncResponse i a
+ Data.Mergeless.Collection: [clientStoreAdded] :: ClientStore i a -> !Map ClientId a
+ Data.Mergeless.Collection: [clientStoreDeleted] :: ClientStore i a -> !Set i
+ Data.Mergeless.Collection: [clientStoreSynced] :: ClientStore i a -> !Map i a
+ Data.Mergeless.Collection: [serverStoreItems] :: ServerStore i a -> Map i a
+ Data.Mergeless.Collection: [serverSyncProcessorDeleteMany] :: ServerSyncProcessor i a m -> Set i -> m (Set i)
+ Data.Mergeless.Collection: [serverSyncProcessorInsertMany] :: ServerSyncProcessor i a m -> Map ClientId a -> m (Map ClientId i)
+ Data.Mergeless.Collection: [serverSyncProcessorQueryNewRemote] :: ServerSyncProcessor i a m -> Set i -> m (Map i a)
+ Data.Mergeless.Collection: [serverSyncProcessorQueryNoLongerSynced] :: ServerSyncProcessor i a m -> Set i -> m (Set i)
+ Data.Mergeless.Collection: [syncRequestAdded] :: SyncRequest i a -> !Map ClientId a
+ Data.Mergeless.Collection: [syncRequestDeleted] :: SyncRequest i a -> !Set i
+ Data.Mergeless.Collection: [syncRequestSynced] :: SyncRequest i a -> !Set i
+ Data.Mergeless.Collection: [syncResponseClientAdded] :: SyncResponse i a -> !Map ClientId i
+ Data.Mergeless.Collection: [syncResponseClientDeleted] :: SyncResponse i a -> !Set i
+ Data.Mergeless.Collection: [syncResponseServerAdded] :: SyncResponse i a -> !Map i a
+ Data.Mergeless.Collection: [syncResponseServerDeleted] :: SyncResponse i a -> !Set i
+ Data.Mergeless.Collection: [unClientId] :: ClientId -> Word64
+ Data.Mergeless.Collection: addAddedItems :: forall i a. (Ord i, Ord a) => Map ClientId i -> ClientStore i a -> ClientStore i a
+ Data.Mergeless.Collection: addItemToClientStore :: (Ord i, Ord a) => a -> ClientStore i a -> ClientStore i a
+ Data.Mergeless.Collection: addRemotelyAddedItems :: (Ord i, Ord a) => Map i a -> ClientStore i a -> ClientStore i a
+ Data.Mergeless.Collection: data ClientStore i a
+ Data.Mergeless.Collection: data ServerSyncProcessor i a m
+ Data.Mergeless.Collection: data SyncRequest i a
+ Data.Mergeless.Collection: data SyncResponse i a
+ Data.Mergeless.Collection: deleteItemsToBeDeletedLocally :: (Ord i, Ord a) => Set i -> ClientStore i a -> ClientStore i a
+ Data.Mergeless.Collection: deleteLocalUndeletedItems :: (Ord i, Ord a) => Set i -> ClientStore i a -> ClientStore i a
+ Data.Mergeless.Collection: deleteSyncedFromClientStore :: (Ord i, Ord a) => i -> ClientStore i a -> ClientStore i a
+ Data.Mergeless.Collection: deleteUnsyncedFromClientStore :: (Ord i, Ord a) => ClientId -> ClientStore i a -> ClientStore i a
+ Data.Mergeless.Collection: emptyClientStore :: ClientStore i a
+ Data.Mergeless.Collection: emptyServerStore :: ServerStore i a
+ Data.Mergeless.Collection: emptySyncResponse :: SyncResponse i ia
+ Data.Mergeless.Collection: instance (Control.DeepSeq.NFData i, Control.DeepSeq.NFData a) => Control.DeepSeq.NFData (Data.Mergeless.Collection.ClientStore i a)
+ Data.Mergeless.Collection: instance (Control.DeepSeq.NFData i, Control.DeepSeq.NFData a) => Control.DeepSeq.NFData (Data.Mergeless.Collection.ServerStore i a)
+ Data.Mergeless.Collection: instance (Control.DeepSeq.NFData i, Control.DeepSeq.NFData a) => Control.DeepSeq.NFData (Data.Mergeless.Collection.SyncRequest i a)
+ Data.Mergeless.Collection: instance (Control.DeepSeq.NFData i, Control.DeepSeq.NFData a) => Control.DeepSeq.NFData (Data.Mergeless.Collection.SyncResponse i a)
+ Data.Mergeless.Collection: instance (Data.Aeson.Types.FromJSON.FromJSON i, Data.Aeson.Types.FromJSON.FromJSON a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.Aeson.Types.FromJSON.FromJSON (Data.Mergeless.Collection.SyncRequest i a)
+ Data.Mergeless.Collection: instance (Data.Aeson.Types.FromJSON.FromJSONKey i, GHC.Classes.Ord i, Data.Aeson.Types.FromJSON.FromJSON a) => Data.Aeson.Types.FromJSON.FromJSON (Data.Mergeless.Collection.ServerStore i a)
+ Data.Mergeless.Collection: instance (Data.Aeson.Types.ToJSON.ToJSON a, Data.Aeson.Types.ToJSON.ToJSONKey i) => Data.Aeson.Types.ToJSON.ToJSON (Data.Mergeless.Collection.ServerStore i a)
+ Data.Mergeless.Collection: instance (Data.Aeson.Types.ToJSON.ToJSON i, Data.Aeson.Types.ToJSON.ToJSON a) => Data.Aeson.Types.ToJSON.ToJSON (Data.Mergeless.Collection.SyncRequest i a)
+ Data.Mergeless.Collection: instance (Data.Aeson.Types.ToJSON.ToJSON i, Data.Aeson.Types.ToJSON.ToJSONKey i, Data.Aeson.Types.ToJSON.ToJSON a) => Data.Aeson.Types.ToJSON.ToJSON (Data.Mergeless.Collection.SyncResponse i a)
+ Data.Mergeless.Collection: instance (Data.Validity.Validity i, Data.Validity.Validity a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.Validity.Validity (Data.Mergeless.Collection.SyncRequest i a)
+ Data.Mergeless.Collection: instance (Data.Validity.Validity i, Data.Validity.Validity a, GHC.Show.Show i, GHC.Show.Show a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.Validity.Validity (Data.Mergeless.Collection.ClientStore i a)
+ Data.Mergeless.Collection: instance (Data.Validity.Validity i, Data.Validity.Validity a, GHC.Show.Show i, GHC.Show.Show a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.Validity.Validity (Data.Mergeless.Collection.ServerStore i a)
+ Data.Mergeless.Collection: instance (Data.Validity.Validity i, Data.Validity.Validity a, GHC.Show.Show i, GHC.Show.Show a, GHC.Classes.Ord i, GHC.Classes.Ord a) => Data.Validity.Validity (Data.Mergeless.Collection.SyncResponse i a)
+ Data.Mergeless.Collection: instance (GHC.Classes.Eq a, GHC.Classes.Eq i) => GHC.Classes.Eq (Data.Mergeless.Collection.ClientStore i a)
+ Data.Mergeless.Collection: instance (GHC.Classes.Eq a, GHC.Classes.Eq i) => GHC.Classes.Eq (Data.Mergeless.Collection.SyncRequest i a)
+ Data.Mergeless.Collection: instance (GHC.Classes.Eq i, GHC.Classes.Eq a) => GHC.Classes.Eq (Data.Mergeless.Collection.ServerStore i a)
+ Data.Mergeless.Collection: instance (GHC.Classes.Eq i, GHC.Classes.Eq a) => GHC.Classes.Eq (Data.Mergeless.Collection.SyncResponse i a)
+ Data.Mergeless.Collection: instance (GHC.Classes.Ord a, GHC.Classes.Ord i) => GHC.Classes.Ord (Data.Mergeless.Collection.ClientStore i a)
+ Data.Mergeless.Collection: instance (GHC.Classes.Ord a, GHC.Classes.Ord i) => GHC.Classes.Ord (Data.Mergeless.Collection.SyncRequest i a)
+ Data.Mergeless.Collection: instance (GHC.Classes.Ord i, Data.Aeson.Types.FromJSON.FromJSON i, Data.Aeson.Types.FromJSON.FromJSONKey i, Data.Aeson.Types.FromJSON.FromJSON a) => Data.Aeson.Types.FromJSON.FromJSON (Data.Mergeless.Collection.ClientStore i a)
+ Data.Mergeless.Collection: instance (GHC.Classes.Ord i, Data.Aeson.Types.FromJSON.FromJSON i, Data.Aeson.Types.FromJSON.FromJSONKey i, GHC.Classes.Ord a, Data.Aeson.Types.FromJSON.FromJSON a) => Data.Aeson.Types.FromJSON.FromJSON (Data.Mergeless.Collection.SyncResponse i a)
+ Data.Mergeless.Collection: instance (GHC.Classes.Ord i, Data.Aeson.Types.ToJSON.ToJSON i, Data.Aeson.Types.ToJSON.ToJSONKey i, Data.Aeson.Types.ToJSON.ToJSON a) => Data.Aeson.Types.ToJSON.ToJSON (Data.Mergeless.Collection.ClientStore i a)
+ Data.Mergeless.Collection: instance (GHC.Classes.Ord i, GHC.Classes.Ord a) => GHC.Classes.Ord (Data.Mergeless.Collection.ServerStore i a)
+ Data.Mergeless.Collection: instance (GHC.Classes.Ord i, GHC.Classes.Ord a) => GHC.Classes.Ord (Data.Mergeless.Collection.SyncResponse i a)
+ Data.Mergeless.Collection: instance (GHC.Show.Show a, GHC.Show.Show i) => GHC.Show.Show (Data.Mergeless.Collection.ClientStore i a)
+ Data.Mergeless.Collection: instance (GHC.Show.Show a, GHC.Show.Show i) => GHC.Show.Show (Data.Mergeless.Collection.SyncRequest i a)
+ Data.Mergeless.Collection: instance (GHC.Show.Show i, GHC.Show.Show a) => GHC.Show.Show (Data.Mergeless.Collection.ServerStore i a)
+ Data.Mergeless.Collection: instance (GHC.Show.Show i, GHC.Show.Show a) => GHC.Show.Show (Data.Mergeless.Collection.SyncResponse i a)
+ Data.Mergeless.Collection: instance Control.DeepSeq.NFData Data.Mergeless.Collection.ClientId
+ Data.Mergeless.Collection: instance Data.Aeson.Types.FromJSON.FromJSON Data.Mergeless.Collection.ClientId
+ Data.Mergeless.Collection: instance Data.Aeson.Types.FromJSON.FromJSONKey Data.Mergeless.Collection.ClientId
+ Data.Mergeless.Collection: instance Data.Aeson.Types.ToJSON.ToJSON Data.Mergeless.Collection.ClientId
+ Data.Mergeless.Collection: instance Data.Aeson.Types.ToJSON.ToJSONKey Data.Mergeless.Collection.ClientId
+ Data.Mergeless.Collection: instance Data.Validity.Validity Data.Mergeless.Collection.ClientId
+ Data.Mergeless.Collection: instance GHC.Classes.Eq Data.Mergeless.Collection.ClientId
+ Data.Mergeless.Collection: instance GHC.Classes.Ord Data.Mergeless.Collection.ClientId
+ Data.Mergeless.Collection: instance GHC.Enum.Bounded Data.Mergeless.Collection.ClientId
+ Data.Mergeless.Collection: instance GHC.Enum.Enum Data.Mergeless.Collection.ClientId
+ Data.Mergeless.Collection: instance GHC.Generics.Generic (Data.Mergeless.Collection.ClientStore i a)
+ Data.Mergeless.Collection: instance GHC.Generics.Generic (Data.Mergeless.Collection.ServerStore i a)
+ Data.Mergeless.Collection: instance GHC.Generics.Generic (Data.Mergeless.Collection.ServerSyncProcessor i a m)
+ Data.Mergeless.Collection: instance GHC.Generics.Generic (Data.Mergeless.Collection.SyncRequest i a)
+ Data.Mergeless.Collection: instance GHC.Generics.Generic (Data.Mergeless.Collection.SyncResponse i a)
+ Data.Mergeless.Collection: instance GHC.Generics.Generic Data.Mergeless.Collection.ClientId
+ Data.Mergeless.Collection: instance GHC.Show.Show Data.Mergeless.Collection.ClientId
+ Data.Mergeless.Collection: makeSyncRequest :: (Ord i, Ord a) => ClientStore i a -> SyncRequest i a
+ Data.Mergeless.Collection: mergeSyncResponse :: forall i a. (Ord i, Ord a) => ClientStore i a -> SyncResponse i a -> ClientStore i a
+ Data.Mergeless.Collection: newtype ClientId
+ Data.Mergeless.Collection: newtype ServerStore i a
+ Data.Mergeless.Collection: processServerSync :: forall m i a. (Ord i, Ord a, Monad m) => m i -> ServerStore i a -> SyncRequest i a -> m (SyncResponse i a, ServerStore i a)
+ Data.Mergeless.Collection: processServerSyncCustom :: forall i a m. (Ord i, Ord a, Monad m) => ServerSyncProcessor i a m -> SyncRequest i a -> m (SyncResponse i a)
+ Data.Mergeless.Collection: storeSize :: ClientStore i a -> Int
+ Data.Mergeless.Item: ClientAdded :: !a -> ClientItem a
+ Data.Mergeless.Item: ClientDeleted :: ClientItem a
+ Data.Mergeless.Item: ClientEmpty :: ClientItem a
+ Data.Mergeless.Item: ClientSynced :: !a -> ClientItem a
+ Data.Mergeless.Item: ItemSyncRequestDeleted :: ItemSyncRequest a
+ Data.Mergeless.Item: ItemSyncRequestKnown :: ItemSyncRequest a
+ Data.Mergeless.Item: ItemSyncRequestNew :: a -> ItemSyncRequest a
+ Data.Mergeless.Item: ItemSyncRequestPoll :: ItemSyncRequest a
+ Data.Mergeless.Item: ItemSyncResponseClientAdded :: ItemSyncResponse a
+ Data.Mergeless.Item: ItemSyncResponseClientDeleted :: ItemSyncResponse a
+ Data.Mergeless.Item: ItemSyncResponseInSyncEmpty :: ItemSyncResponse a
+ Data.Mergeless.Item: ItemSyncResponseInSyncFull :: ItemSyncResponse a
+ Data.Mergeless.Item: ItemSyncResponseServerAdded :: !a -> ItemSyncResponse a
+ Data.Mergeless.Item: ItemSyncResponseServerDeleted :: ItemSyncResponse a
+ Data.Mergeless.Item: ServerItemEmpty :: ServerItem a
+ Data.Mergeless.Item: ServerItemFull :: !a -> ServerItem a
+ Data.Mergeless.Item: data ClientItem a
+ Data.Mergeless.Item: data ItemSyncRequest a
+ Data.Mergeless.Item: data ItemSyncResponse a
+ Data.Mergeless.Item: data ServerItem a
+ Data.Mergeless.Item: instance Control.DeepSeq.NFData a => Control.DeepSeq.NFData (Data.Mergeless.Item.ClientItem a)
+ Data.Mergeless.Item: instance Control.DeepSeq.NFData a => Control.DeepSeq.NFData (Data.Mergeless.Item.ItemSyncRequest a)
+ Data.Mergeless.Item: instance Control.DeepSeq.NFData a => Control.DeepSeq.NFData (Data.Mergeless.Item.ItemSyncResponse a)
+ Data.Mergeless.Item: instance Control.DeepSeq.NFData a => Control.DeepSeq.NFData (Data.Mergeless.Item.ServerItem 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.Item: instance Data.Validity.Validity a => Data.Validity.Validity (Data.Mergeless.Item.ClientItem a)
+ Data.Mergeless.Item: instance Data.Validity.Validity a => Data.Validity.Validity (Data.Mergeless.Item.ItemSyncRequest a)
+ Data.Mergeless.Item: instance Data.Validity.Validity a => Data.Validity.Validity (Data.Mergeless.Item.ItemSyncResponse a)
+ Data.Mergeless.Item: instance Data.Validity.Validity a => Data.Validity.Validity (Data.Mergeless.Item.ServerItem a)
+ Data.Mergeless.Item: instance GHC.Classes.Eq a => GHC.Classes.Eq (Data.Mergeless.Item.ClientItem a)
+ Data.Mergeless.Item: instance GHC.Classes.Eq a => GHC.Classes.Eq (Data.Mergeless.Item.ItemSyncRequest a)
+ Data.Mergeless.Item: instance GHC.Classes.Eq a => GHC.Classes.Eq (Data.Mergeless.Item.ItemSyncResponse a)
+ Data.Mergeless.Item: instance GHC.Classes.Eq a => GHC.Classes.Eq (Data.Mergeless.Item.ServerItem a)
+ Data.Mergeless.Item: instance GHC.Classes.Ord a => GHC.Classes.Ord (Data.Mergeless.Item.ClientItem a)
+ Data.Mergeless.Item: instance GHC.Classes.Ord a => GHC.Classes.Ord (Data.Mergeless.Item.ItemSyncRequest a)
+ Data.Mergeless.Item: instance GHC.Classes.Ord a => GHC.Classes.Ord (Data.Mergeless.Item.ItemSyncResponse a)
+ Data.Mergeless.Item: instance GHC.Classes.Ord a => GHC.Classes.Ord (Data.Mergeless.Item.ServerItem a)
+ Data.Mergeless.Item: instance GHC.Generics.Generic (Data.Mergeless.Item.ClientItem a)
+ Data.Mergeless.Item: instance GHC.Generics.Generic (Data.Mergeless.Item.ItemSyncRequest a)
+ Data.Mergeless.Item: instance GHC.Generics.Generic (Data.Mergeless.Item.ItemSyncResponse a)
+ Data.Mergeless.Item: instance GHC.Generics.Generic (Data.Mergeless.Item.ServerItem a)
+ Data.Mergeless.Item: instance GHC.Show.Show a => GHC.Show.Show (Data.Mergeless.Item.ClientItem a)
+ Data.Mergeless.Item: instance GHC.Show.Show a => GHC.Show.Show (Data.Mergeless.Item.ItemSyncRequest a)
+ Data.Mergeless.Item: instance GHC.Show.Show a => GHC.Show.Show (Data.Mergeless.Item.ItemSyncResponse a)
+ Data.Mergeless.Item: instance GHC.Show.Show a => GHC.Show.Show (Data.Mergeless.Item.ServerItem a)
+ Data.Mergeless.Item: makeItemSyncRequest :: ClientItem a -> ItemSyncRequest a
+ Data.Mergeless.Item: mergeItemSyncResponse :: ClientItem a -> ItemSyncResponse a -> ClientItem a
+ Data.Mergeless.Item: processServerItemSync :: ServerItem a -> ItemSyncRequest a -> (ItemSyncResponse a, ServerItem a)

Files

ChangeLog.md view
@@ -1,3 +1,7 @@ # Changelog for mergeless  ## Unreleased changes++## [0.2.0.0] - 2020-02-12++* Got rid of the UTCTimes. The client should take care of that.
mergeless.cabal view
@@ -1,11 +1,13 @@--- This file has been generated from package.yaml by hpack version 0.28.2.+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.33.0. -- -- see: https://github.com/sol/hpack ----- hash: 749159cfb0e485429b445319f97126579e191fed759cadc0903f884e813159a7+-- hash: 7b7549a7b1a7d5ca5767941adb11f773af2e6727a37c8fdceb9367c0147b4d91  name:           mergeless-version:        0.1.0.0+version:        0.2.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@@ -14,10 +16,9 @@ copyright:      Copyright: (c) 2018 Tom Sydney Kerckhove license:        MIT build-type:     Simple-cabal-version:  >= 1.10 extra-source-files:-    ChangeLog.md     README.md+    ChangeLog.md  source-repository head   type: git@@ -26,6 +27,8 @@ library   exposed-modules:       Data.Mergeless+      Data.Mergeless.Collection+      Data.Mergeless.Item   other-modules:       Paths_mergeless   hs-source-dirs:@@ -34,9 +37,8 @@       aeson     , base >=4.7 && <5     , containers+    , deepseq     , mtl-    , time     , validity     , validity-containers-    , validity-time   default-language: Haskell2010
src/Data/Mergeless.hs view
@@ -1,500 +1,5 @@-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE ScopedTypeVariables #-}---- | A way to synchronise items without merge conflicts.------ This concept has a few requirements:------ * Items must be immutable.--- * Items must allow for a centrally unique identifier.--- * Identifiers for items must be generatable in such a way that they are certainly unique.------ Should mutation be a requirement, then it can be build such that it entails deleting the old version and creating a new version that is the modification of the old version.--------- There are a few obvious candidates for identifiers:------ * incremental identifiers--- * universally unique identifiers (recommended).------------ The typical setup is as follows:------ * A central server is set up to synchronise with--- * Each client synchronises with the central server, but never with eachother--------- A central server should operate as follows:------ * The server accepts a 'SyncRequest'.--- * The server performs operations according to the functionality of 'processSync'.--- * The server respons with a 'SyncResponse'.--------- A client should operate as follows:------ * The client produces a 'SyncRequest' with 'makeSyncRequest'.--- * The client sends that request to the central server and gets a 'SyncResponse'.--- * The client then updates its local store with 'mergeSyncResponse'. module Data.Mergeless-    ( Added(..)-    , Synced(..)-    , StoreItem(..)-    , Store(..)-    , emptyStore-    , storeSize-    , addItemToStore-    , deleteUnsynced-    , deleteSynced-    , SyncRequest(..)-    , SyncResponse(..)-    -- * Client-side Synchronisation-    , makeSyncRequest-    , mergeSyncResponse-    -- * Server-side Synchronisation-    -- ** General synchronisation-    , SyncProcessor(..)-    , processSyncCustom-    -- ** Synchronisation with a simple central store-    , CentralStore(..)-    , CentralItem(..)-    , processSyncWith-    , processSync-    ) where--import Control.Applicative-import Control.Monad.IO.Class-import Control.Monad.State.Strict-import Data.Aeson-import Data.List-import Data.Map.Strict (Map)-import qualified Data.Map.Strict as M-import Data.Maybe-import qualified Data.Set as S-import Data.Set (Set)-import Data.Time-import Data.Validity-import Data.Validity.Containers ()-import Data.Validity.Time ()-import GHC.Generics (Generic)--{-# ANN module ("HLint: ignore Use lambda-case" :: String) #-}---- | A local item of type @a@ that has been added but not synchronised yet-data Added a = Added-    { addedValue :: !a-    , addedCreated :: !UTCTime-    } deriving (Show, Eq, Ord, Generic)--instance Validity a => Validity (Added a)--instance FromJSON a => FromJSON (Added a) where-    parseJSON =-        withObject "Added" $ \o -> Added <$> o .: "value" <*> o .: "added"--instance ToJSON a => ToJSON (Added a) where-    toJSON Added {..} = object ["value" .= addedValue, "added" .= addedCreated]---- | A local item of type @a@ with an identifier of type @a@ that has been synchronised-data Synced i a = Synced-    { syncedUuid :: i-    , syncedValue :: !a-    , syncedCreated :: !UTCTime-    , syncedSynced :: !UTCTime-    } deriving (Show, Eq, Ord, Generic)--instance (Validity i, Validity a) => Validity (Synced i a)--instance (FromJSON i, FromJSON a) => FromJSON (Synced i a) where-    parseJSON =-        withObject "Synced" $ \o ->-            Synced <$> o .: "id" <*> o .: "value" <*> o .: "created" <*>-            o .: "synced"--instance (ToJSON i, ToJSON a) => ToJSON (Synced i a) where-    toJSON Synced {..} =-        object-            [ "id" .= syncedUuid-            , "value" .= syncedValue-            , "created" .= syncedCreated-            , "synced" .= syncedSynced-            ]---- | A store item with an Id of type @i@ and a value of type @a@-data StoreItem i a-    = UnsyncedItem !(Added a) -- ^ A local item that has not been synchronised to the central store yet-    | SyncedItem !(Synced i a) -- ^ A local item that has been synchronised to the central store already-    | UndeletedItem !i -- ^ An item that has been synchronised to the central store, was subsequently deleted locally but this deletion has not been synchronised to the central store yet.-    deriving (Show, Eq, Ord, Generic)--instance (Validity i, Validity a) => Validity (StoreItem i a)--instance (FromJSON i, FromJSON a) => FromJSON (StoreItem i a) where-    parseJSON v =-        (SyncedItem <$> parseJSON v) <|> (UnsyncedItem <$> parseJSON v) <|>-        (UndeletedItem <$> parseJSON v)--instance (ToJSON i, ToJSON a) => ToJSON (StoreItem i a) where-    toJSON (UnsyncedItem a) = toJSON a-    toJSON (SyncedItem a) = toJSON a-    toJSON (UndeletedItem a) = toJSON a---- | A client-side store of items with Id's of type @i@ and values of type @a@-newtype Store i a = Store-    { storeItems :: Set (StoreItem i a)-    } deriving (Show, Eq, Ord, Generic, FromJSON, ToJSON)--instance (Validity i, Validity a, Ord i, Ord a) => Validity (Store i a) where-    validate Store {..} =-        mconcat-            [ annotate storeItems "storeItems"-            , declare "the store items have distinct uuids" $-              distinct $-              flip mapMaybe (S.toList storeItems) $ \case-                  UnsyncedItem _ -> Nothing-                  SyncedItem Synced {..} -> Just syncedUuid-                  UndeletedItem u -> Just u-            ]---- | The store with no items.-emptyStore :: Store i a-emptyStore = Store S.empty---- | The number of items in a store-storeSize :: Store i a -> Int-storeSize (Store s) =-    S.size $-    S.filter-        (\si ->-             case si of-                 UndeletedItem _ -> False-                 _ -> True)-        s---- | Add a new (unsynced) item to the store-addItemToStore :: (Ord i, Ord a) => Added a -> Store i a -> Store i a-addItemToStore a (Store s) = Store $ S.insert (UnsyncedItem a) s--deleteUnsynced :: (Ord i, Ord a) => Added a -> Store i a -> Store i a-deleteUnsynced a (Store s) =-    Store $-    flip mapSetMaybe s $ \si ->-        case si of-            UnsyncedItem i ->-                if i == a-                    then Nothing-                    else Just si-            SyncedItem _ -> Just si-            UndeletedItem _ -> Just si--deleteSynced :: (Ord i, Ord a) => Synced i a -> Store i a -> Store i a-deleteSynced a (Store s) =-    Store $-    flip mapSetMaybe s $ \si ->-        case si of-            UnsyncedItem _ -> Just si-            SyncedItem i ->-                if i == a-                    then Just $ UndeletedItem $ syncedUuid i-                    else Just si-            UndeletedItem _ -> Just si---- | A synchronisation request for items with identifiers of type @i@ and values of type @a@-data SyncRequest i a = SyncRequest-    { syncRequestAddedItems :: !(Set (Added a))-    , syncRequestSyncedItems :: !(Set i)-    , syncRequestUndeletedItems :: !(Set i)-    } deriving (Show, Eq, Ord, Generic)--instance (Validity i, Validity a, Ord i, Ord a) =>-         Validity (SyncRequest i a) where-    validate SyncRequest {..} =-        mconcat-            [ annotate syncRequestAddedItems "syncRequestAddedItems"-            , annotate syncRequestSyncedItems "syncRequestSyncedItems"-            , annotate syncRequestUndeletedItems "syncRequestUndeletedItems"-            , declare "the sync request items have distinct ids" $-              distinct $-              S.toList syncRequestSyncedItems ++-              S.toList syncRequestUndeletedItems-            ]--instance (FromJSON i, FromJSON a, Ord i, Ord a) =>-         FromJSON (SyncRequest i a) where-    parseJSON =-        withObject "SyncRequest" $ \o ->-            SyncRequest <$> o .: "unsynced" <*> o .: "synced" <*>-            o .: "undeleted"--instance (ToJSON i, ToJSON a) => ToJSON (SyncRequest i a) where-    toJSON SyncRequest {..} =-        object-            [ "unsynced" .= syncRequestAddedItems-            , "synced" .= syncRequestSyncedItems-            , "undeleted" .= syncRequestUndeletedItems-            ]---- | A synchronisation response for items with identifiers of type @i@ and values of type @a@-data SyncResponse i a = SyncResponse-    { syncResponseAddedItems :: !(Set (Synced i a))-    , syncResponseNewRemoteItems :: !(Set (Synced i a))-    , syncResponseItemsToBeDeletedLocally :: !(Set i)-    } deriving (Show, Eq, Ord, Generic)--instance (Validity i, Validity a, Ord i, Ord a) =>-         Validity (SyncResponse i a) where-    validate SyncResponse {..} =-        mconcat-            [ annotate syncResponseAddedItems "syncResponseAddedItems"-            , annotate syncResponseNewRemoteItems "syncResponseNewRemoteItems"-            , annotate-                  syncResponseItemsToBeDeletedLocally-                  "syncResponseItemsToBeDeletedLocally"-            , declare "the sync response items have distinct uuids" $-              distinct $-              map-                  syncedUuid-                  (S.toList syncResponseAddedItems ++-                   S.toList syncResponseNewRemoteItems) ++-              S.toList syncResponseItemsToBeDeletedLocally-            ]--instance (FromJSON i, FromJSON a, Ord i, Ord a) =>-         FromJSON (SyncResponse i a) where-    parseJSON =-        withObject "SyncResponse" $ \o ->-            SyncResponse <$> o .: "added" <*> o .: "new" <*> o .: "deleted"--instance (ToJSON i, ToJSON a) => ToJSON (SyncResponse i a) where-    toJSON SyncResponse {..} =-        object-            [ "added" .= syncResponseAddedItems-            , "new" .= syncResponseNewRemoteItems-            , "deleted" .= syncResponseItemsToBeDeletedLocally-            ]---- | Produce a synchronisation request for a client-side store.------ This request can then be sent to a central store for synchronisation.-makeSyncRequest :: (Ord i, Ord a) => Store i a -> SyncRequest i a-makeSyncRequest Store {..} =-    SyncRequest-        { syncRequestAddedItems =-              flip mapSetMaybe storeItems $ \case-                  UnsyncedItem a -> Just a-                  _ -> Nothing-        , syncRequestSyncedItems =-              flip mapSetMaybe storeItems $ \case-                  SyncedItem i -> Just $ syncedUuid i-                  _ -> Nothing-        , syncRequestUndeletedItems =-              flip mapSetMaybe storeItems $ \case-                  UndeletedItem uuid -> Just uuid-                  _ -> Nothing-        }---- | Merge a synchronisation response back into a client-side store.-mergeSyncResponse ::-       (Ord i, Ord a) => Store i a -> SyncResponse i a -> Store i a-mergeSyncResponse s SyncResponse {..} =-    let withNewOwnItems =-            flip mapSetMaybe (storeItems s) $ \si ->-                case si of-                    UnsyncedItem Added {..} ->-                        case find-                                 (\Synced {..} ->-                                      syncedCreated == addedCreated &&-                                      syncedValue == addedValue)-                                 syncResponseAddedItems of-                            Nothing -> Just si -- If it wasn't added (for whatever reason), just leave it as unsynced-                            Just ii -> Just $ SyncedItem ii -- If it was added, then it becomes synced-                    SyncedItem ii ->-                        case find-                                 (== syncedUuid ii)-                                 syncResponseItemsToBeDeletedLocally of-                            Nothing -> Just si -- If it wasn't deleted, don't delete it.-                            Just _ -> Nothing -- If it was deleted, delete it here.-                    UndeletedItem _ -> Nothing -- Delete all locally deleted items after sync-     in Store-            { storeItems =-                  S.fromList .-                  nubBy-                      (\i1 i2 ->-                           case (i1, i2) of-                               (UnsyncedItem _, _) -> False-                               (_, UnsyncedItem _) -> False-                               (SyncedItem s1, SyncedItem s2) ->-                                   syncedUuid s1 == syncedUuid s2-                               (SyncedItem s1, UndeletedItem u2) ->-                                   syncedUuid s1 == u2-                               (UndeletedItem u1, SyncedItem s2) ->-                                   u1 == syncedUuid s2-                               (UndeletedItem u1, UndeletedItem u2) -> u1 == u2) .-                  S.toList $-                  S.map SyncedItem syncResponseNewRemoteItems `S.union`-                  withNewOwnItems-            }---- | A record of the basic operations that are necessary to build a synchronisation processor.-data SyncProcessor i a m = SyncProcessor-    { syncProcessorDeleteMany :: Set i -> m () -- ^ Delete the items with an identifier in the given set.-    , syncProcessorQuerySynced :: Set i -> m (Set i) -- ^ Query the identifiers of the items that are in store, of the given set.-    , syncProcessorQueryNewRemote :: Set i -> m (Set (Synced i a)) -- ^ Query the items that are in store, but not in the given set.-    , syncProcessorInsertMany :: Set (Synced i a) -> m () -- ^ Insert a set of items into the stor.-    } deriving (Generic)---- | Process a server-side synchronisation request using a custom synchronisation processor------ WARNING: The identifier generation function must produce newly unique identifiers such that each new item gets a unique identifier.------ You can use this function with deterministically-random identifiers or incrementing identifiers.-processSyncCustom ::-       forall i a m. (Ord i, Ord a, Monad m)-    => m i-    -> UTCTime-    -> SyncProcessor i a m-    -> SyncRequest i a-    -> m (SyncResponse i a)-processSyncCustom genUuid now SyncProcessor {..} SyncRequest {..} = do-    deleteUndeleted-        -- First we delete the items that were deleted locally but not yet remotely.-        -- Then we find the items that have been deleted remotely but not locally-    deletedRemotely <- syncItemsToBeDeletedLocally-        -- Then we find the items that have appeared remotely but aren't known locally-    newRemoteItems <- syncNewRemoteItems-        -- Then we add the items that should be added.-    newLocalItems <- syncAddedItems-    pure-        SyncResponse-            { syncResponseNewRemoteItems = newRemoteItems-            , syncResponseAddedItems = newLocalItems-            , syncResponseItemsToBeDeletedLocally = deletedRemotely-            }-  where-    deleteUndeleted :: m ()-    deleteUndeleted = syncProcessorDeleteMany syncRequestUndeletedItems-    syncItemsToBeDeletedLocally :: m (Set i)-    syncItemsToBeDeletedLocally = do-        foundItems <- syncProcessorQuerySynced syncRequestSyncedItems-        pure $ syncRequestSyncedItems `S.difference` foundItems-    syncNewRemoteItems :: m (Set (Synced i a))-    syncNewRemoteItems = syncProcessorQueryNewRemote syncRequestSyncedItems-    syncAddedItems :: m (Set (Synced i a))-    syncAddedItems = do-        is <--            fmap S.fromList $-            forM (S.toList syncRequestAddedItems) $ \Added {..} -> do-                uuid <- genUuid-                pure-                    Synced-                        { syncedUuid = uuid-                        , syncedCreated = addedCreated-                        , syncedSynced = now-                        , syncedValue = addedValue-                        }-        syncProcessorInsertMany is-        pure is---- | An item in a central store with a value of type @a@-data CentralItem a = CentralItem-    { centralValue :: !a-    , centralSynced :: !UTCTime-    , centralCreated :: !UTCTime-    } deriving (Show, Eq, Ord, Generic)--instance Validity a => Validity (CentralItem a)--instance FromJSON a => FromJSON (CentralItem a)--instance ToJSON a => ToJSON (CentralItem a)---- | A central store of items with identifiers of type @i@ and values of type @a@-newtype CentralStore i a = CentralStore-    { centralStoreItems :: Map i (CentralItem a)-    } deriving (Show, Eq, Ord, Generic, FromJSON, ToJSON)--instance (Validity i, Validity a, Ord i, Ord a) =>-         Validity (CentralStore i a)---- | Process a server-side synchronisation request using @getCurrentTime@------ see 'processSyncCustom'-processSync ::-       (Ord i, Ord a, MonadIO m)-    => m i-    -> CentralStore i a-    -> SyncRequest i a-    -> m (SyncResponse i a, CentralStore i a)-processSync genId cs sr = do-    now <- liftIO getCurrentTime-    processSyncWith genId now cs sr---- | Process a server-side synchronisation request using a time of syncing, and an identifier generation function.------ see 'processSyncCustom'-processSyncWith ::-       forall i a m. (Ord i, Ord a, Monad m)-    => m i-    -> UTCTime-    -> CentralStore i a-    -> SyncRequest i a-    -> m (SyncResponse i a, CentralStore i a)-processSyncWith genUuid now cs sr =-    flip runStateT cs $-    processSyncCustom-        (lift genUuid)-        now-        SyncProcessor-            { syncProcessorDeleteMany = deleteMany-            , syncProcessorQuerySynced = querySynced-            , syncProcessorQueryNewRemote = queryNewRemote-            , syncProcessorInsertMany = insertMany-            }-        sr-  where-    deleteMany :: Set i -> StateT (CentralStore i a) m ()-    deleteMany s = modC (`M.withoutKeys` s)-    querySynced :: Set i -> StateT (CentralStore i a) m (Set i)-    querySynced s = M.keysSet <$> query (`M.restrictKeys` s)-    queryNewRemote :: Set i -> StateT (CentralStore i a) m (Set (Synced i a))-    queryNewRemote s = do-        m <- query (`M.withoutKeys` s)-        pure $-            S.fromList $-            flip map (M.toList m) $ \(i, CentralItem {..}) ->-                Synced-                    { syncedUuid = i-                    , syncedCreated = centralCreated-                    , syncedSynced = centralSynced-                    , syncedValue = centralValue-                    }-    query :: (Map i (CentralItem a) -> b) -> StateT (CentralStore i a) m b-    query func = gets $ func . centralStoreItems-    insertMany :: Set (Synced i a) -> StateT (CentralStore i a) m ()-    insertMany s =-        forM_ (S.toList s) $ \Synced {..} ->-            ins-                syncedUuid-                CentralItem-                    { centralValue = syncedValue-                    , centralCreated = syncedCreated-                    , centralSynced = syncedSynced-                    }-    ins :: i -> CentralItem a -> StateT (CentralStore i a) m ()-    ins i val = modC $ M.insert i val-    modC ::-           (Map i (CentralItem a) -> Map i (CentralItem a))-        -> StateT (CentralStore i a) m ()-    modC func = modify (\(CentralStore m) -> CentralStore $ func m)--mapSetMaybe :: Ord b => (a -> Maybe b) -> Set a -> Set b-mapSetMaybe func = S.map fromJust . S.filter isJust . S.map func+  ( module Data.Mergeless.Collection+  ) where -distinct :: Ord a => [a] -> Bool-distinct ls = sort ls == S.toAscList (S.fromList ls)+import Data.Mergeless.Collection
+ src/Data/Mergeless/Collection.hs view
@@ -0,0 +1,435 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}++-- | A way to synchronise items without merge conflicts.+--+-- This concept has a few requirements:+--+-- * Items must be immutable.+-- * Items must allow for a centrally unique identifier.+-- * Identifiers for items must be generatable in such a way that they are certainly unique.+--+-- Should mutation be a requirement, then it can be build such that it entails deleting the old version and creating a new version that is the modification of the old version.+--+--+-- There are a few obvious candidates for identifiers:+--+-- * incremental identifiers+-- * universally unique identifiers (recommended).+--+--+--+-- The typical setup is as follows:+--+-- * A central server is set up to synchronise with+-- * Each client synchronises with the central server, but never with eachother+--+--+-- A central server should operate as follows:+--+-- * The server accepts a 'SyncRequest'.+-- * The server performs operations according to the functionality of 'processServerSync'.+-- * The server respons with a 'SyncResponse'.+--+--+-- A client should operate as follows:+--+-- * The client produces a 'SyncRequest' with 'makeSyncRequest'.+-- * The client sends that request to the central server and gets a 'SyncResponse'.+-- * The client then updates its local store with 'mergeSyncResponse'.+module Data.Mergeless.Collection+  ( ClientId(..)+  , ClientStore(..)+  , emptyClientStore+  , storeSize+  , addItemToClientStore+  , deleteUnsyncedFromClientStore+  , deleteSyncedFromClientStore+  , SyncRequest(..)+  , SyncResponse(..)+  , emptySyncResponse+    -- * Client-side Synchronisation+  , makeSyncRequest+  , mergeSyncResponse+  , addRemotelyAddedItems+  , addAddedItems+  , deleteItemsToBeDeletedLocally+  , deleteLocalUndeletedItems+    -- * Server-side Synchronisation+    -- ** General synchronisation+  , ServerSyncProcessor(..)+  , processServerSyncCustom+    -- ** Synchronisation with a simple central store+  , ServerStore(..)+  , emptyServerStore+  , processServerSync+  ) where++import GHC.Generics (Generic)++import Data.Validity+import Data.Validity.Containers ()++import Data.Aeson+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 Data.Word++import Control.DeepSeq+import Control.Monad.State.Strict++{-# ANN module ("HLint: ignore Use lambda-case" :: String) #-}++-- | A Client-side identifier for items.+--+-- 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)++instance Validity ClientId++instance NFData ClientId++-- | A client-side store of items with Id's of type @i@ and values of type @a@+data ClientStore i a =+  ClientStore+    { clientStoreAdded :: !(Map ClientId a)+    , clientStoreSynced :: !(Map i a)+    , clientStoreDeleted :: !(Set i)+    }+  deriving (Show, Eq, Ord, Generic)++instance (NFData i, NFData a) => NFData (ClientStore i a)++instance (Validity i, Validity a, Show i, Show a, Ord i, Ord a) => Validity (ClientStore i a) where+  validate cs@ClientStore {..} =+    mconcat+      [ genericValidate cs+      , declare "the store items have distinct ids" $+        distinct $ M.keys clientStoreSynced ++ S.toList clientStoreDeleted+      ]++instance (Ord i, FromJSON i, FromJSONKey i, FromJSON a) => FromJSON (ClientStore i a) where+  parseJSON =+    withObject "ClientStore" $ \o ->+      ClientStore <$> o .:? "added" .!= M.empty <*> o .:? "synced" .!= M.empty <*>+      o .:? "deleted" .!= S.empty++instance (Ord i, ToJSON i, ToJSONKey i, ToJSON a) => ToJSON (ClientStore i a) where+  toJSON ClientStore {..} =+    object+      ["added" .= clientStoreAdded, "synced" .= clientStoreSynced, "deleted" .= clientStoreDeleted]++-- | The store with no items.+emptyClientStore :: ClientStore i a+emptyClientStore =+  ClientStore+    {clientStoreAdded = M.empty, clientStoreSynced = M.empty, clientStoreDeleted = S.empty}++-- | The number of items in a store+--+-- This does not count the deleted items, so that those really look deleted.+storeSize :: ClientStore i a -> Int+storeSize ClientStore {..} = M.size clientStoreAdded + M.size clientStoreSynced++clientStoreIds :: Ord i => ClientStore i a -> Set i+clientStoreIds ClientStore {..} = M.keysSet clientStoreSynced `S.union` clientStoreDeleted++-- | Add an item to a client store as an added item.+--+-- This will take care of the uniqueness constraint of the 'ClientId's in the map.+addItemToClientStore :: (Ord i, Ord a) => a -> ClientStore i a -> ClientStore i a+addItemToClientStore a cs =+  let oldAddedItems = clientStoreAdded cs+      newAddedItems =+        let newKey = findFreeSpot oldAddedItems+         in M.insert newKey a oldAddedItems+   in cs {clientStoreAdded = newAddedItems}++-- | Find a free client id to use+--+-- You shouldn't need this function, 'addItemToClientStore' takes care of this.+findFreeSpot :: Map ClientId a -> ClientId+findFreeSpot m =+  if M.null m+    then ClientId 0+    else let (i, _) = M.findMax m+          in go (next i)+  where+    go i =+      if M.member i m+        then go (next i)+        else i+    next (ClientId w)+      | w == maxBound = ClientId 0+      | otherwise = ClientId $ succ w++deleteUnsyncedFromClientStore :: (Ord i, Ord a) => ClientId -> ClientStore i a -> ClientStore i a+deleteUnsyncedFromClientStore cid cs = cs {clientStoreAdded = M.delete cid $ clientStoreAdded cs}++deleteSyncedFromClientStore :: (Ord i, Ord a) => i -> ClientStore i a -> ClientStore i a+deleteSyncedFromClientStore i cs =+  let syncedBefore = clientStoreSynced cs+   in case M.lookup i syncedBefore of+        Nothing -> cs+        Just _ ->+          cs+            { clientStoreSynced = M.delete i syncedBefore+            , clientStoreDeleted = S.insert i $ clientStoreDeleted cs+            }++-- | A synchronisation request for items with identifiers of type @i@ and values of type @a@+data SyncRequest i a =+  SyncRequest+    { syncRequestAdded :: !(Map ClientId a)+    , syncRequestSynced :: !(Set i)+    , syncRequestDeleted :: !(Set i)+    }+  deriving (Show, Eq, Ord, Generic)++instance (NFData i, NFData a) => NFData (SyncRequest i a)++instance (Validity i, Validity a, Ord i, Ord a) => Validity (SyncRequest i a) where+  validate sr@SyncRequest {..} =+    mconcat+      [ genericValidate sr+      , declare "the sync request items have distinct ids" $+        distinct $ S.toList syncRequestSynced ++ S.toList syncRequestDeleted+      ]++instance (FromJSON i, FromJSON a, Ord i, Ord a) => FromJSON (SyncRequest i a) where+  parseJSON =+    withObject "SyncRequest" $ \o ->+      SyncRequest <$> o .: "added" <*> o .: "synced" <*> o .: "undeleted"++instance (ToJSON i, ToJSON a) => ToJSON (SyncRequest i a) where+  toJSON SyncRequest {..} =+    object+      [ "added" .= syncRequestAdded+      , "synced" .= syncRequestSynced+      , "undeleted" .= syncRequestDeleted+      ]++-- | Produce a synchronisation request for a client-side store.+--+-- This request can then be sent to a central store for synchronisation.+makeSyncRequest :: (Ord i, Ord a) => ClientStore i a -> SyncRequest i a+makeSyncRequest ClientStore {..} =+  SyncRequest+    { syncRequestAdded = clientStoreAdded+    , syncRequestSynced = M.keysSet clientStoreSynced+    , syncRequestDeleted = clientStoreDeleted+    }++-- | A synchronisation response for items with identifiers of type @i@ and values of type @a@+data SyncResponse i a =+  SyncResponse+    { syncResponseClientAdded :: !(Map ClientId i)+    , syncResponseClientDeleted :: !(Set i)+    , syncResponseServerAdded :: !(Map i a)+    , syncResponseServerDeleted :: !(Set i)+    }+  deriving (Show, Eq, Ord, Generic)++instance (NFData i, NFData a) => NFData (SyncResponse i a)++instance (Validity i, Validity a, Show i, Show a, Ord i, Ord a) => Validity (SyncResponse i a) where+  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+          ]+      ]++instance (Ord i, FromJSON i, FromJSONKey i, Ord a, FromJSON a) => FromJSON (SyncResponse i a) where+  parseJSON =+    withObject "SyncResponse" $ \o ->+      SyncResponse <$> o .: "client-added" <*> o .: "client-deleted" <*> o .: "server-added" <*>+      o .: "server-deleted"++instance (ToJSON i, ToJSONKey i, ToJSON a) => ToJSON (SyncResponse i a) where+  toJSON SyncResponse {..} =+    object+      [ "client-added" .= syncResponseClientAdded+      , "client-deleted" .= syncResponseClientDeleted+      , "server-added" .= syncResponseServerAdded+      , "server-deleted" .= syncResponseServerDeleted+      ]++emptySyncResponse :: SyncResponse i ia+emptySyncResponse =+  SyncResponse+    { syncResponseClientAdded = M.empty+    , syncResponseClientDeleted = S.empty+    , syncResponseServerAdded = M.empty+    , syncResponseServerDeleted = S.empty+    }++-- | Merge a synchronisation response back into a client-side store.+mergeSyncResponse ::+     forall i a. (Ord i, Ord a)+  => ClientStore i a+  -> SyncResponse i a+  -> ClientStore i a+mergeSyncResponse s SyncResponse {..} =+  addRemotelyAddedItems syncResponseServerAdded .+  addAddedItems syncResponseClientAdded .+  deleteItemsToBeDeletedLocally syncResponseServerDeleted .+  deleteLocalUndeletedItems syncResponseClientDeleted $+  s++addRemotelyAddedItems :: (Ord i, Ord a) => Map i a -> ClientStore i a -> ClientStore i a+addRemotelyAddedItems m cs =+  cs {clientStoreSynced = M.union (clientStoreSynced cs) (m `diffSet` clientStoreIds cs)}++addAddedItems ::+     forall i a. (Ord i, Ord a)+  => Map ClientId i+  -> ClientStore i a+  -> ClientStore i a+addAddedItems addedItems cs =+  let oldAdded = clientStoreAdded cs+      oldSynced = clientStoreSynced cs+      go :: (Map ClientId a, Map i a) -> ClientId -> i -> (Map ClientId a, Map i a)+      go (added, synced) cid i =+        case M.lookup cid added of+          Nothing -> (added, synced)+          Just a -> (M.delete cid added, M.insert i a synced)+      (newAdded, newSynced) = M.foldlWithKey go (oldAdded, oldSynced) addedItems+   in cs {clientStoreAdded = newAdded, clientStoreSynced = newSynced}++deleteItemsToBeDeletedLocally :: (Ord i, Ord a) => Set i -> ClientStore i a -> ClientStore i a+deleteItemsToBeDeletedLocally toBeDeletedLocally cs =+  cs {clientStoreSynced = clientStoreSynced cs `diffSet` toBeDeletedLocally}++deleteLocalUndeletedItems :: (Ord i, Ord a) => Set i -> ClientStore i a -> ClientStore i a+deleteLocalUndeletedItems cd cs = cs {clientStoreDeleted = clientStoreDeleted cs `S.difference` cd}++-- | A record of the basic operations that are necessary to build a synchronisation processor.+data ServerSyncProcessor i a m =+  ServerSyncProcessor+    { serverSyncProcessorDeleteMany :: Set i -> m (Set i)+      -- ^ Delete the items with an identifier in the given set, return the set that was indeed deleted or did not exist.+      -- In particular, return the identifiers of the items that the client should forget about.+    , serverSyncProcessorQueryNoLongerSynced :: Set i -> m (Set i) -- ^ Query the identifiers of the items that are in the given set but not in the store.+    , serverSyncProcessorQueryNewRemote :: Set i -> m (Map i a) -- ^ Query the items that are in store, but not in the given set.+    , serverSyncProcessorInsertMany :: Map ClientId a -> m (Map ClientId i) -- ^ Insert a set of items into the store.+    }+  deriving (Generic)++-- | Process a server-side synchronisation request using a custom synchronisation processor+--+-- WARNING: The identifier generation function must produce newly unique identifiers such that each new item gets a unique identifier.+--+-- You can use this function with deterministically-random identifiers or incrementing identifiers.+processServerSyncCustom ::+     forall i a m. (Ord i, Ord a, Monad m)+  => ServerSyncProcessor i a m+  -> SyncRequest i a+  -> m (SyncResponse i a)+processServerSyncCustom ServerSyncProcessor {..} SyncRequest {..} = do+  deletedFromClient <- deleteUndeleted+        -- First we delete the items that were deleted locally but not yet remotely.+        -- Then we find the items that have been deleted remotely but not locally+  deletedRemotely <- syncItemsToBeDeletedLocally+        -- Then we find the items that have appeared remotely but aren't known locally+  newRemoteItems <- syncNewRemoteItems+        -- Then we add the items that should be added.+  newLocalItems <- syncAddedItems+  pure+    SyncResponse+      { syncResponseClientAdded = newLocalItems+      , syncResponseClientDeleted = deletedFromClient+      , syncResponseServerAdded = newRemoteItems+      , syncResponseServerDeleted = deletedRemotely+      }+  where+    deleteUndeleted :: m (Set i)+    deleteUndeleted = serverSyncProcessorDeleteMany syncRequestDeleted+    syncItemsToBeDeletedLocally :: m (Set i)+    syncItemsToBeDeletedLocally = serverSyncProcessorQueryNoLongerSynced syncRequestSynced+    syncNewRemoteItems :: m (Map i a)+    syncNewRemoteItems = serverSyncProcessorQueryNewRemote syncRequestSynced+    syncAddedItems :: m (Map ClientId i)+    syncAddedItems = serverSyncProcessorInsertMany syncRequestAdded++-- | A central store of items with identifiers of type @i@ and values of type @a@+newtype ServerStore i a =+  ServerStore+    { serverStoreItems :: Map i a+    }+  deriving (Show, Eq, Ord, Generic, FromJSON, ToJSON)++instance (NFData i, NFData a) => NFData (ServerStore i a)++instance (Validity i, Validity a, Show i, Show a, Ord i, Ord a) => Validity (ServerStore i a)++-- | An empty central store to start with+emptyServerStore :: ServerStore i a+emptyServerStore = ServerStore {serverStoreItems = M.empty}++-- | Process a server-side synchronisation request using @getCurrentTime@+--+-- see 'processSyncCustom'+processServerSync ::+     forall m i a. (Ord i, Ord a, Monad m)+  => m i+  -> ServerStore i a+  -> SyncRequest i a+  -> m (SyncResponse i a, ServerStore i a)+processServerSync genUuid cs sr =+  flip runStateT cs $+  processServerSyncCustom+    ServerSyncProcessor+      { serverSyncProcessorDeleteMany = deleteMany+      , serverSyncProcessorQueryNoLongerSynced = queryNoLongerSynced+      , serverSyncProcessorQueryNewRemote = queryNewRemote+      , serverSyncProcessorInsertMany = insertMany+      }+    sr+  where+    deleteMany :: Set i -> StateT (ServerStore i a) m (Set i)+    deleteMany s = do+      modC (`diffSet` s)+      pure s+    queryNoLongerSynced :: Set i -> StateT (ServerStore i a) m (Set i)+    queryNoLongerSynced s = query ((s `S.difference`) . M.keysSet)+    queryNewRemote :: Set i -> StateT (ServerStore i a) m (Map i a)+    queryNewRemote s = query (`diffSet` s)+    query :: (Map i a -> b) -> StateT (ServerStore i a) m b+    query func = gets $ func . serverStoreItems+    insertMany :: Map ClientId a -> StateT (ServerStore i a) m (Map ClientId i)+    insertMany =+      traverse $ \a -> do+        u <- lift genUuid+        ins u a+        pure u+    ins :: i -> a -> StateT (ServerStore i a) m ()+    ins i val = modC $ M.insert i val+    modC :: (Map i a -> Map i a) -> StateT (ServerStore i a) m ()+    modC func = modify (\(ServerStore m) -> ServerStore $ func m)++diffSet :: Ord i => Map i a -> Set i -> Map i a+diffSet m s = m `M.difference` toMap s++toMap :: Set i -> Map i ()+toMap = M.fromSet (const ())++distinct :: Ord a => [a] -> Bool+distinct ls = sort ls == S.toAscList (S.fromList ls)
+ src/Data/Mergeless/Item.hs view
@@ -0,0 +1,148 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Data.Mergeless.Item where++import Control.DeepSeq+import Data.Aeson+import Data.Validity+import Data.Validity.Containers ()+import GHC.Generics (Generic)++{-# ANN module ("HLint: ignore Use lambda-case" :: String) #-}++data ClientItem a+  = ClientEmpty+  | ClientAdded !a+  | ClientSynced !a+  | ClientDeleted+  deriving (Show, Eq, Ord, Generic)++instance Validity a => Validity (ClientItem a)++instance NFData a => NFData (ClientItem a)++instance FromJSON a => FromJSON (ClientItem a)++instance ToJSON a => ToJSON (ClientItem a)++-- | A synchronisation request for items with identifiers of type @i@ and values of type @a@+data ItemSyncRequest a+  = ItemSyncRequestPoll+  | ItemSyncRequestNew a+  | ItemSyncRequestKnown+  | ItemSyncRequestDeleted+  deriving (Show, Eq, Ord, Generic)++instance Validity a => Validity (ItemSyncRequest a)++instance NFData a => NFData (ItemSyncRequest a)++instance FromJSON a => FromJSON (ItemSyncRequest a)++instance ToJSON a => ToJSON (ItemSyncRequest a)++makeItemSyncRequest :: ClientItem a -> ItemSyncRequest a+makeItemSyncRequest ci =+  case ci of+    ClientEmpty -> ItemSyncRequestPoll+    ClientAdded a -> ItemSyncRequestNew a+    ClientSynced _ -> ItemSyncRequestKnown+    ClientDeleted -> ItemSyncRequestDeleted++-- | A synchronisation response for items with identifiers of type @i@ and values of type @a@+data ItemSyncResponse a+  = ItemSyncResponseInSyncEmpty+  | ItemSyncResponseInSyncFull+  | ItemSyncResponseClientAdded+  | ItemSyncResponseClientDeleted+  | ItemSyncResponseServerAdded !a+  | ItemSyncResponseServerDeleted+  deriving (Show, Eq, Ord, Generic)++instance Validity a => Validity (ItemSyncResponse a)++instance NFData a => NFData (ItemSyncResponse a)++instance FromJSON a => FromJSON (ItemSyncResponse a)++instance ToJSON a => ToJSON (ItemSyncResponse a)++-- | Merge a synchronisation response back into a client-side store.+mergeItemSyncResponse :: ClientItem a -> ItemSyncResponse a -> ClientItem a+mergeItemSyncResponse ci sr =+  let mismatch = ci+   in case ci of+        ClientEmpty ->+          case sr of+            ItemSyncResponseInSyncEmpty -> ClientEmpty+            ItemSyncResponseServerAdded s -> ClientSynced s+            _ -> mismatch+        ClientAdded a ->+          case sr of+            ItemSyncResponseClientAdded -> ClientSynced a+            ItemSyncResponseServerAdded s -> ClientSynced s+            -- For completeness sake.+            -- This can only happen if two clients make the item at the same time.+            -- In practice, with named items in a collection, this will never happen.+            _ -> mismatch+        ClientSynced _ ->+          case sr of+            ItemSyncResponseInSyncFull -> ci -- No change+            ItemSyncResponseServerDeleted -> ClientEmpty+            _ -> mismatch+        ClientDeleted ->+          case sr of+            ItemSyncResponseClientDeleted -> ClientEmpty+            _ -> mismatch++-- | An item in a central store with a value of type @a@+data ServerItem a+  = ServerItemEmpty+  | ServerItemFull !a+  deriving (Show, Eq, Ord, Generic)++instance Validity a => Validity (ServerItem a)++instance NFData a => NFData (ServerItem a)++instance FromJSON a => FromJSON (ServerItem a)++instance ToJSON a => ToJSON (ServerItem a)++processServerItemSync :: ServerItem a -> ItemSyncRequest a -> (ItemSyncResponse a, ServerItem a)+processServerItemSync si sr =+  case si of+    ServerItemEmpty ->+      case sr of+        ItemSyncRequestPoll+          -- Both the client and the server think the item is empty, fine.+         -> (ItemSyncResponseInSyncEmpty, si)+        ItemSyncRequestNew a+          -- The client has a new item and the server has space for it, add it.+         -> (ItemSyncResponseClientAdded, ServerItemFull a)+        ItemSyncRequestKnown+          -- The client has an item that the server doesn't, so the server must have+          -- deleted it when another client asked to do that.+          -- Leave it deleted.+         -> (ItemSyncResponseServerDeleted, si)+        ItemSyncRequestDeleted+          -- The server has deleted an item but the current client hasn't been made aware of that+          -- AND this server also deleted that item in the meantime.+          -- Just leave it deleted.+         -> (ItemSyncResponseClientDeleted, si)+    ServerItemFull s ->+      case sr of+        ItemSyncRequestPoll+          -- The server has an item that the client doesn't, send it to the client.+         -> (ItemSyncResponseServerAdded s, si)+        ItemSyncRequestNew _+          -- The client wants to add an item that the server already has.+          -- That means that another client has added that same item in the meantime.+          -- This wouldn't happen if the items were named.+          -- In this case, for completeness sake,+         -> (ItemSyncResponseServerAdded s, si)+        ItemSyncRequestKnown -> (ItemSyncResponseInSyncFull, si)+        ItemSyncRequestDeleted -> (ItemSyncResponseClientDeleted, ServerItemEmpty)