The local replica state.
Not two replicas should have the same id across a network, a server should be used to assign unique numeric ids to each replica.
The timestamp for the replica is a vector clock, encoded as an integer, consisting in the id of the replica taking the first bits and the number of the last operation taking the last 32 bits.
The path of a node in the tree is represented as a List Int
.
A Replicated Tree, see int.
init : Basics.Int -> CRDTree a
Build a CRDTree providing the replica id.
tree : CRDTree String
tree = init 1
Failure to apply an operation
add : a -> CRDTree a -> Result (Error a) (CRDTree a)
Add a node after tree cursor, the cursor is set at the added node path.
init 1
|> add "a"
|> Result.andThen (add "b")
|> Result.andThen (add "c")
addAfter : List Basics.Int -> a -> CRDTree a -> Result (Error a) (CRDTree a)
Add a node after another node at given path, the cursor is set at the added node path.
init 1
|> add "a"
|> Result.andThen (add "b")
|> Result.andThen (addAfter [1] "c")
-- node with value "c" is inserted between nodes "a" and "b"
addBranch : a -> CRDTree a -> Result (Error a) (CRDTree a)
Add a branch after tree cursor, subsequent additions are added to the branch.
init 1
|> addBranch "a"
|> Result.andThen (add "a,b")
|> Result.andThen (add "a,c")
batch : List (CRDTree a -> Result (Error a) (CRDTree a)) -> CRDTree a -> Result (Error a) (CRDTree a)
Apply a list of operations
init 1 |> batch [ add "a", add "b", add "c" ]
delete : List Basics.Int -> CRDTree a -> Result (Error a) (CRDTree a)
Mark a node at a path as deleted.
init 1
|> batch [ add "a", add "b" ]
|> Result.andThen (\tree -> delete (cursor tree) tree)
Nodes are not actually deleted but marked and their children discarded.
apply : Operation a -> CRDTree a -> Result (Error a) (CRDTree a)
Apply a remote operation
treeA : CRDTree String
treeA =
let
tree = init 1
in
tree
|> batch [ add "a", add "b", add "c" ]
|> Result.withDefault tree
operation : Operation String
operation =
lastOperation treeA
treeB : CRDTree String
treeB =
let
tree = init 1
in
tree
|> apply operation
|> Result.withDefault tree
(root treeA) == (root treeB)
(operations treeA) == (operations treeB)
(path treeA) /= (path treeB)
(timestamp treeA) /= (timestamp treeB)
operationsSince : Basics.Int -> CRDTree a -> Operation a
Return a batch of operations after a known timestamp
treeA : CRDTree String
treeA =
let
tree = init 1
in
tree
|> batch [ add "a", add "b" ]
|> Result.withDefault tree
(List.length (Operation.toList <| operationsSince 0 treeA)) == 2
(List.length (Operation.toList <| operationsSince 1 treeA)) == 2
(List.length (Operation.toList <| operationsSince 2 treeA)) == 1
(List.length (Operation.toList <| operationsSince 5 treeA)) == 0
lastOperation : CRDTree a -> Operation a
Return the last successfully applied operation or batch or if operation was not succesfull an empty batch.
import Operation exposing (Operation(..))
-- success
init 1
|> batch [ add "a", add "b", add "c" ]
|> Result.map (\tree ->
(lastOperation tree) /= Batch [])
-- failure
init 1
|> delete [1,2,3]
|> Result.map (\tree ->
(lastOperation tree) == Batch [])
id : CRDTree a -> Basics.Int
The local replica id
timestamp : CRDTree a -> Basics.Int
The local replica timestamp
root : CRDTree a -> Node a
Root node of the CRDTree
get : List Basics.Int -> CRDTree a -> Maybe (Node a)
Get a value at path
treeA : CRDTree String
treeA =
let
tree = init 1
in
tree
|> batch [ addBranch "a", addBranch "b", add "c" ]
|> Result.withDefault tree
(get [1] treeA) == (Just (Node.init "a" [1]))
(get [1, 2] treeA) == (Just (Node.init "b" [1,2]))
(get [1, 2, 3] treeA) == (Just (Node.init "c" [1, 2, 3]))
(get [4] treeA) == Nothing
getValue : List Basics.Int -> CRDTree a -> Maybe a
Get a value at path
treeA : CRDTree String
treeA =
let
tree = init 1
in
tree
|> batch [ addBranch "a", addBranch "b", add "c" ]
|> Result.withDefault tree
(get [1] treeA) == (Just "a")
(get [1, 2] treeA) == (Just "b")
(get [1, 2, 3] treeA) == (Just "c")
(get [4] treeA) == Nothing
cursor : CRDTree a -> List Basics.Int
Return the tree cursor
treeA : CRDTree String
treeA =
let
tree = init 1
in
tree
|> batch [ add "a", add "b", add "c" ]
|> Result.withDefault tree
(cursor treeA) == [3]
treeB : CRDTree String
treeB =
let
tree = init 1
in
tree
|> batch [ addBranch "a", addBranch "b" ]
|> Result.withDefault tree
(cursor treeB) == [1, 2, 0]
moveCursorUp : CRDTree a -> CRDTree a
Move the tree cursor one level up
treeA : CRDTree String
treeA =
let
tree = init 1
in
tree
|> batch [ addBranch "a", addBranch "b", add "c" ]
|> Result.withDefault tree
(cursor treeA) == [1, 2, 3]
(cursor (moveCursorUp treeA)) == [1, 2]
lastReplicaTimestamp : Basics.Int -> CRDTree a -> Basics.Int
Last know timestamp for a replica