Safe Haskell | None |
---|---|
Language | Haskell2010 |
Data.Mergeful
Contents
Description
A way to synchronise a single item with safe merge conflicts.
The 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 client should operate as follows:
The client starts with an initialClientStore
.
- The client produces a
SyncRequest
withmakeSyncRequest
. - The client sends that request to the central server and gets a
SyncResponse
. - The client then updates its local store with
mergeSyncResponseIgnoreProblems
.
The central server should operate as follows:
The server starts with an initialServerStore
.
- The server accepts a
SyncRequest
. - The server performs operations according to the functionality of
processServerSync
. - The server respons with a
SyncResponse
.
WARNING: This whole approach can break down if a server resets its server times or if a client syncs with two different servers using the same server times.
Synopsis
- initialClientStore :: ClientStore i a
- addItemToClientStore :: a -> ClientStore i a -> ClientStore i a
- markItemDeletedInClientStore :: Ord i => i -> ClientStore i a -> ClientStore i a
- changeItemInClientStore :: Ord i => i -> a -> ClientStore i a -> ClientStore i a
- deleteItemFromClientStore :: ClientId -> ClientStore i a -> ClientStore i a
- initialSyncRequest :: SyncRequest i a
- makeSyncRequest :: ClientStore i a -> SyncRequest i a
- mergeSyncResponseIgnoreProblems :: Ord i => ClientStore i a -> SyncResponse i a -> ClientStore i a
- mergeSyncResponseFromServer :: Ord i => ClientStore i a -> SyncResponse i a -> ClientStore i a
- data ItemMergeStrategy a = ItemMergeStrategy {
- itemMergeStrategyMergeChangeConflict :: a -> Timed a -> Timed a
- itemMergeStrategyMergeClientDeletedConflict :: Timed a -> Maybe (Timed a)
- itemMergeStrategyMergeServerDeletedConflict :: a -> Maybe a
- mergeSyncResponseUsingStrategy :: Ord i => ItemMergeStrategy a -> ClientStore i a -> SyncResponse i a -> ClientStore i a
- initialServerStore :: ServerStore i a
- processServerSync :: forall i a m. (Ord i, Monad m) => m i -> ServerStore i a -> SyncRequest i a -> m (SyncResponse i a, ServerStore i a)
- data ClientStore i a = ClientStore {
- clientStoreAddedItems :: Map ClientId a
- clientStoreSyncedItems :: Map i (Timed a)
- clientStoreSyncedButChangedItems :: Map i (Timed a)
- clientStoreDeletedItems :: Map i ServerTime
- data SyncRequest i a = SyncRequest {}
- data SyncResponse i a = SyncResponse {
- syncResponseClientAdded :: Map ClientId (i, ServerTime)
- syncResponseClientChanged :: Map i ServerTime
- syncResponseClientDeleted :: Set i
- syncResponseServerAdded :: Map i (Timed a)
- syncResponseServerChanged :: Map i (Timed a)
- syncResponseServerDeleted :: Set i
- syncResponseConflicts :: Map i (Timed a)
- syncResponseConflictsClientDeleted :: Map i (Timed a)
- syncResponseConflictsServerDeleted :: Set i
- emptySyncResponse :: SyncResponse i a
- newtype ServerStore i a = ServerStore {
- serverStoreItems :: Map i (Timed a)
- newtype ClientId = ClientId {
- unClientId :: Word64
- mergeAddedItems :: forall i a. Ord i => Map ClientId a -> Map ClientId (i, ServerTime) -> (Map ClientId a, Map i (Timed a))
- mergeSyncedButChangedItems :: forall i a. Ord i => Map i (Timed a) -> Map i ServerTime -> (Map i (Timed a), Map i (Timed a))
- mergeDeletedItems :: Ord i => Map i b -> Set i -> Map i b
- addToSyncResponse :: Ord i => SyncResponse i a -> Identifier i -> ItemSyncResponse a -> SyncResponse i a
Documentation
initialClientStore :: ClientStore i a Source #
A client store to start with.
This store contains no items.
addItemToClientStore :: a -> ClientStore i a -> ClientStore i a Source #
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.
markItemDeletedInClientStore :: Ord i => i -> ClientStore i a -> ClientStore i a Source #
Mark an item deleted in a client store.
This function will not delete the item, but mark it as deleted instead.
changeItemInClientStore :: Ord i => i -> a -> ClientStore i a -> ClientStore i a Source #
Replace the given item with a new value.
This function will correctly mark the item as changed, if it exist.
It will not add an item to the store with the given id, because the server may not have been the origin of that id.
deleteItemFromClientStore :: ClientId -> ClientStore i a -> ClientStore i a Source #
Delete an unsynced item from a client store.
This function will immediately delete the item, because it has never been synced.
initialSyncRequest :: SyncRequest i a Source #
An intial SyncRequest
to start with.
It just asks the server to send over whatever it knows.
makeSyncRequest :: ClientStore i a -> SyncRequest i a Source #
Produce an SyncRequest
from a ClientStore
.
Send this to the server for synchronisation.
mergeSyncResponseIgnoreProblems :: Ord i => ClientStore i a -> SyncResponse i a -> ClientStore i a Source #
Merge an SyncResponse
into the current ClientStore
.
This function ignores any problems that may occur. In the case of a conclict, it will just not update the client item. The next sync request will then produce a conflict again.
Pro: No data will be lost
Con: Clients will diverge when conflicts occur.
mergeSyncResponseFromServer :: Ord i => ClientStore i a -> SyncResponse i a -> ClientStore i a Source #
Merge an SyncResponse
into the current ClientStore
by taking whatever the server gave the client.
Pro: Clients will converge on the same value.
Con: Conflicting updates will be lost.
Custom merging
data ItemMergeStrategy a Source #
A strategy to merge conflicts for item synchronisation
Constructors
ItemMergeStrategy | |
Fields
|
Instances
Generic (ItemMergeStrategy a) Source # | |
Defined in Data.Mergeful.Item Associated Types type Rep (ItemMergeStrategy a) :: Type -> Type # Methods from :: ItemMergeStrategy a -> Rep (ItemMergeStrategy a) x # to :: Rep (ItemMergeStrategy a) x -> ItemMergeStrategy a # | |
type Rep (ItemMergeStrategy a) Source # | |
Defined in Data.Mergeful.Item type Rep (ItemMergeStrategy a) = D1 (MetaData "ItemMergeStrategy" "Data.Mergeful.Item" "mergeful-0.0.0.0-LTwAvhb2uEKNxaCunTl9g" False) (C1 (MetaCons "ItemMergeStrategy" PrefixI True) (S1 (MetaSel (Just "itemMergeStrategyMergeChangeConflict") NoSourceUnpackedness NoSourceStrictness DecidedLazy) (Rec0 (a -> Timed a -> Timed a)) :*: (S1 (MetaSel (Just "itemMergeStrategyMergeClientDeletedConflict") NoSourceUnpackedness NoSourceStrictness DecidedLazy) (Rec0 (Timed a -> Maybe (Timed a))) :*: S1 (MetaSel (Just "itemMergeStrategyMergeServerDeletedConflict") NoSourceUnpackedness NoSourceStrictness DecidedLazy) (Rec0 (a -> Maybe a))))) |
mergeSyncResponseUsingStrategy :: Ord i => ItemMergeStrategy a -> ClientStore i a -> SyncResponse i a -> ClientStore i a Source #
Merge an SyncResponse
into the current ClientStore
with the given merge strategy.
In order for clients to converge on the same collection correctly, this function must be:
- Associative
- Idempotent
- The same on all clients
This function ignores mismatches.
Server side
initialServerStore :: ServerStore i a Source #
A server store to start with
This store contains no items.
Arguments
:: (Ord i, Monad m) | |
=> m i | The action that is guaranteed to generate unique identifiers |
-> ServerStore i a | |
-> SyncRequest i a | |
-> m (SyncResponse i a, ServerStore i a) |
Serve an SyncRequest
using the current ServerStore
, producing an SyncResponse
and a new ServerStore
.
Types, for reference
data ClientStore i a Source #
Constructors
ClientStore | |
Fields
|
Instances
data SyncRequest i a Source #
Constructors
SyncRequest | |
Fields
|
Instances
data SyncResponse i a Source #
Constructors
SyncResponse | |
Fields
|
Instances
emptySyncResponse :: SyncResponse i a Source #
newtype ServerStore i a Source #
Constructors
ServerStore | |
Fields
|
Instances
A Client-side identifier for items.
These only need to be unique at the client.
Constructors
ClientId | |
Fields
|
Instances
Bounded ClientId Source # | |
Enum ClientId Source # | |
Eq ClientId Source # | |
Ord ClientId Source # | |
Defined in Data.Mergeful | |
Show ClientId Source # | |
Generic ClientId Source # | |
ToJSON ClientId Source # | |
Defined in Data.Mergeful | |
ToJSONKey ClientId Source # | |
Defined in Data.Mergeful | |
FromJSON ClientId Source # | |
FromJSONKey ClientId Source # | |
Defined in Data.Mergeful Methods | |
Validity ClientId Source # | |
Defined in Data.Mergeful Methods validate :: ClientId -> Validation # | |
type Rep ClientId Source # | |
Defined in Data.Mergeful |
Utility functions for implementing client-side merging
mergeAddedItems :: forall i a. Ord i => Map ClientId a -> Map ClientId (i, ServerTime) -> (Map ClientId a, Map i (Timed a)) Source #
Merge the local added items with the ones that the server has acknowledged as added.
mergeSyncedButChangedItems :: forall i a. Ord i => Map i (Timed a) -> Map i ServerTime -> (Map i (Timed a), Map i (Timed a)) Source #
Merge the local synced but changed items with the ones that the server has acknowledged as changed.
mergeDeletedItems :: Ord i => Map i b -> Set i -> Map i b Source #
Merge the local deleted items with the ones that the server has acknowledged as deleted.
Utility functions for implementing server-side responding
addToSyncResponse :: Ord i => SyncResponse i a -> Identifier i -> ItemSyncResponse a -> SyncResponse i a Source #
Given an incomplete SyncResponse
, an id, possibly a client ID too, and
an ItemSyncResponse
, produce a less incomplete SyncResponse
.