diff --git a/README.md b/README.md index a3bbc70..283e71f 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,22 @@ -# vector-clock +# version-vector -Vector clock implementation for Clojure and ClojureScript. +> **⚠️ Project Renamed:** This project was previously `net.clojars.bru/vector-clock`. +> That artifact is deprecated. Please migrate to `net.clojars.bru/version-vector`. + +Version vector implementation for Clojure and ClojureScript. ## Overview -This library provides vector clocks for building distributed systems in both Clojure (JVM) and ClojureScript. +This library provides version vectors for building distributed systems in both Clojure (JVM) and ClojureScript. -**Vector Clocks** - A mechanism for determining causality and detecting conflicts in distributed systems. +**Version Vectors** - A mechanism for determining causality and detecting conflicts in distributed systems. This implementation uses a map where keys represent devices in a distributed db, and values represent the version last contributed by that device. ## Features -- **CRDT Operations**: tick, check, merge operations for vector clocks +- **CRDT Operations**: tick, check, merge operations for version vectors - **Causality Detection**: Determine if events are concurrent, causally related, or identical - **Well-Tested**: Comprehensive test suite including property-based tests @@ -22,7 +25,7 @@ represent the version last contributed by that device. Add to your `deps.edn`: ```clojure -{:deps {net.clojars.bru/vector-clock {:mvn/version "0.1.0"}}} +{:deps {net.clojars.bru/version-vector {:mvn/version "0.2.0"}}} ``` ## Platform Support @@ -39,26 +42,26 @@ The core algorithm uses only platform-agnostic Clojure functions with no depende ```clojure (ns my-app.sync - (:require [vector-clock.core :as vc])) + (:require [version-vector.core :as vv])) ;; Create a clock for this device -(def my-clock (vc/make-clock "device-1")) +(def my-clock (vv/make-clock "device-1")) ;; Increment the clock when making changes -(def updated-clock (vc/tick my-clock "device-1")) +(def updated-clock (vv/tick my-clock "device-1")) ; => {"device-1" 1} ;; Compare with another device's clock (def other-clock {"device-2" 1}) -(vc/check updated-clock other-clock) +(vv/check updated-clock other-clock) ; => :concurrent (both devices made independent changes) ;; Merge clocks when syncing -(def merged (vc/merge-clocks updated-clock other-clock)) +(def merged (vv/merge-clocks updated-clock other-clock)) ; => {"device-1" 1 "device-2" 1} ``` -## Vector Clock API +## Version Vector API ### Core Operations @@ -103,18 +106,18 @@ Merge two clocks by taking the maximum value for each device. Used when synchron ### Detecting Conflicts ```clojure -(require '[vector-clock.core :as vc]) +(require '[version-vector.core :as vv]) ;; Device 1 makes a change -(def device1-clock (vc/tick (vc/make-clock "d1") "d1")) +(def device1-clock (vv/tick (vv/make-clock "d1") "d1")) ; => {"d1" 1} ;; Device 2 makes an independent change -(def device2-clock (vc/tick (vc/make-clock "d2") "d2")) +(def device2-clock (vv/tick (vv/make-clock "d2") "d2")) ; => {"d2" 1} ;; Check for conflicts -(vc/check device1-clock device2-clock) +(vv/check device1-clock device2-clock) ; => :concurrent (conflict detected!) ``` @@ -128,16 +131,16 @@ Example: distributed note taking app (defn update-note [note new-content device-id] (->Note (:id note) new-content - (vc/tick (:clock note) device-id))) + (vv/tick (:clock note) device-id))) (defn merge-notes [note1 note2] - (case (vc/check (:clock note1) (:clock note2)) + (case (vv/check (:clock note1) (:clock note2)) (:same :ancestor) note2 ; Keep newer version :descendant note1 ; Keep newer version :concurrent ; Conflict - needs resolution strategy (->Note (:id note1) (str (:content note1) "\n---CONFLICT---\n" (:content note2)) - (vc/merge-clocks (:clock note1) (:clock note2))))) + (vv/merge-clocks (:clock note1) (:clock note2))))) ``` ## Testing diff --git a/build.clj b/build.clj index 50adbec..77d7ecd 100644 --- a/build.clj +++ b/build.clj @@ -1,8 +1,8 @@ (ns build (:require [clojure.tools.build.api :as b])) -(def lib 'net.clojars.bru/vector-clock) -(def version "0.1.2") +(def lib 'net.clojars.bru/version-vector) +(def version "0.2.0") (def class-dir "target/classes") (def basis (b/create-basis {:project "deps.edn"})) (def jar-file (format "target/%s-%s.jar" (name lib) version)) @@ -17,12 +17,12 @@ :version version :basis basis :src-dirs ["src"] - :scm {:url "https://github.com/bru/vector-clock" - :connection "scm:git:git://github.com/bru/vector-clock.git" - :developerConnection "scm:git:ssh://git@github.com/bru/vector-clock.git" + :scm {:url "https://github.com/bru/version-vector-clj" + :connection "scm:git:git://github.com/bru/version-vector-clj.git" + :developerConnection "scm:git:ssh://git@github.com/bru/version-vector-clj.git" :tag (str "v" version)} - :pom-data [[:description "Vector clock implementation for Clojure and ClojureScript"] - [:url "https://github.com/bru/vector-clock"] + :pom-data [[:description "Version vector implementation for Clojure and ClojureScript"] + [:url "https://github.com/bru/version-vector-clj"] [:licenses [:license [:name "Eclipse Public License 1.0"] diff --git a/src/vector_clock/core.cljc b/src/version_vector/core.cljc similarity index 93% rename from src/vector_clock/core.cljc rename to src/version_vector/core.cljc index d23389e..dc6405b 100644 --- a/src/vector_clock/core.cljc +++ b/src/version_vector/core.cljc @@ -1,10 +1,10 @@ -(ns vector-clock.core - "Vector clock implementation for distributed causality tracking. +(ns version-vector.core + "Version vector implementation for distributed causality tracking. - Vector clocks are a mechanism for determining the partial ordering of events + Version vectors are a mechanism for determining the partial ordering of events in a distributed system. They are used to detect causality violations and concurrent updates in distributed databases. - + This implementation uses a map where keys represent devices in a distributed db, and values represent the version last contributed by that device.") diff --git a/test/vector_clock/core_test.clj b/test/version_vector/core_test.clj similarity index 64% rename from test/vector_clock/core_test.clj rename to test/version_vector/core_test.clj index 9e3122d..0e57baa 100644 --- a/test/vector_clock/core_test.clj +++ b/test/version_vector/core_test.clj @@ -1,28 +1,28 @@ -(ns vector-clock.core-test +(ns version-vector.core-test (:require [clojure.test :refer [deftest testing is]] - [vector-clock.core :as vc])) + [version-vector.core :as vv])) ;; Vector Clock Tests (deftest test-make-clock (testing "Creating a new vector clock" - (let [clock (vc/make-clock "device-1")] + (let [clock (vv/make-clock "device-1")] (is (map? clock)) (is (= 0 (get clock "device-1")))))) (deftest test-tick (testing "Incrementing a clock" - (let [clock (vc/make-clock "device-1") - ticked (vc/tick clock "device-1")] + (let [clock (vv/make-clock "device-1") + ticked (vv/tick clock "device-1")] (is (= 1 (get ticked "device-1"))) - (let [ticked-twice (vc/tick ticked "device-1")] + (let [ticked-twice (vv/tick ticked "device-1")] (is (= 2 (get ticked-twice "device-1"))))))) (deftest test-tick-new-device (testing "Ticking a clock for a new device" - (let [clock (vc/make-clock "device-1") - ticked (vc/tick clock "device-2")] + (let [clock (vv/make-clock "device-1") + ticked (vv/tick clock "device-2")] (is (= 1 (get ticked "device-2"))) (is (= 0 (get ticked "device-1")))))) @@ -30,41 +30,41 @@ (testing "Checking identical clocks" (let [c1 {"a" 1 "b" 2} c2 {"a" 1 "b" 2}] - (is (= :same (vc/check c1 c2)))))) + (is (= :same (vv/check c1 c2)))))) (deftest test-check-ancestor (testing "Checking ancestor relationship" (let [c1 {"a" 1 "b" 1} c2 {"a" 2 "b" 1}] - (is (= :ancestor (vc/check c1 c2)))))) + (is (= :ancestor (vv/check c1 c2)))))) (deftest test-check-descendant (testing "Checking descendant relationship" (let [c1 {"a" 2 "b" 1} c2 {"a" 1 "b" 1}] - (is (= :descendant (vc/check c1 c2)))))) + (is (= :descendant (vv/check c1 c2)))))) (deftest test-check-concurrent (testing "Checking concurrent clocks" (let [c1 {"a" 2 "b" 1} c2 {"a" 1 "b" 2}] - (is (= :concurrent (vc/check c1 c2)))))) + (is (= :concurrent (vv/check c1 c2)))))) (deftest test-check-with-missing-keys (testing "Checking clocks with different devices" (let [c1 {"a" 1} c2 {"b" 1}] - (is (= :concurrent (vc/check c1 c2)))) + (is (= :concurrent (vv/check c1 c2)))) (let [c1 {"a" 1} c2 {"a" 1 "b" 1}] - (is (= :ancestor (vc/check c1 c2)))))) + (is (= :ancestor (vv/check c1 c2)))))) (deftest test-merge-clocks (testing "Merging two clocks" (let [c1 {"a" 3 "b" 1} c2 {"a" 2 "b" 5 "c" 1} - merged (vc/merge-clocks c1 c2)] + merged (vv/merge-clocks c1 c2)] (is (= 3 (get merged "a"))) (is (= 5 (get merged "b"))) (is (= 1 (get merged "c")))))) @@ -73,7 +73,7 @@ (testing "Merging completely disjoint clocks" (let [c1 {"a" 1} c2 {"b" 2} - merged (vc/merge-clocks c1 c2)] + merged (vv/merge-clocks c1 c2)] (is (= 1 (get merged "a"))) (is (= 2 (get merged "b")))))) @@ -81,9 +81,9 @@ (deftest test-tick-monotonic (testing "Ticking always increases the counter" - (let [clock (vc/make-clock "device-1") - ticked1 (vc/tick clock "device-1") - ticked2 (vc/tick ticked1 "device-1")] + (let [clock (vv/make-clock "device-1") + ticked1 (vv/tick clock "device-1") + ticked2 (vv/tick ticked1 "device-1")] (is (< (get clock "device-1") (get ticked1 "device-1"))) (is (< (get ticked1 "device-1") (get ticked2 "device-1")))))) @@ -91,8 +91,8 @@ (testing "Merge is commutative" (let [c1 {"a" 1 "b" 5} c2 {"a" 3 "c" 2} - merged1 (vc/merge-clocks c1 c2) - merged2 (vc/merge-clocks c2 c1)] + merged1 (vv/merge-clocks c1 c2) + merged2 (vv/merge-clocks c2 c1)] (is (= merged1 merged2))))) (deftest test-merge-associative @@ -100,60 +100,60 @@ (let [c1 {"a" 1} c2 {"b" 2} c3 {"c" 3} - merged1 (vc/merge-clocks (vc/merge-clocks c1 c2) c3) - merged2 (vc/merge-clocks c1 (vc/merge-clocks c2 c3))] + merged1 (vv/merge-clocks (vv/merge-clocks c1 c2) c3) + merged2 (vv/merge-clocks c1 (vv/merge-clocks c2 c3))] (is (= merged1 merged2))))) (deftest test-merge-idempotent (testing "Merging a clock with itself returns the same clock" (let [clock {"a" 1 "b" 2} - merged (vc/merge-clocks clock clock)] + merged (vv/merge-clocks clock clock)] (is (= clock merged))))) (deftest test-check-reflexive (testing "A clock is always :same with itself" (let [clock {"a" 1 "b" 2}] - (is (= :same (vc/check clock clock)))))) + (is (= :same (vv/check clock clock)))))) (deftest test-check-antisymmetric (testing "If c1 is ancestor of c2, then c2 is descendant of c1" (let [c1 {"a" 1} c2 {"a" 2}] - (when (= :ancestor (vc/check c1 c2)) - (is (= :descendant (vc/check c2 c1))))))) + (when (= :ancestor (vv/check c1 c2)) + (is (= :descendant (vv/check c2 c1))))))) (deftest test-check-transitive (testing "If c1 < c2 and c2 < c3, then c1 < c3" (let [c1 {"a" 1} c2 {"a" 2} c3 {"a" 3}] - (is (= :ancestor (vc/check c1 c2))) - (is (= :ancestor (vc/check c2 c3))) - (is (= :ancestor (vc/check c1 c3)))))) + (is (= :ancestor (vv/check c1 c2))) + (is (= :ancestor (vv/check c2 c3))) + (is (= :ancestor (vv/check c1 c3)))))) ;; Practical scenario tests (deftest test-distributed-scenario (testing "Simulating a distributed update scenario" ;; Device 1 makes an update - (let [d1-clock (vc/tick (vc/make-clock "d1") "d1") + (let [d1-clock (vv/tick (vv/make-clock "d1") "d1") ;; Device 2 makes an independent update - d2-clock (vc/tick (vc/make-clock "d2") "d2")] + d2-clock (vv/tick (vv/make-clock "d2") "d2")] ;; These are concurrent - (is (= :concurrent (vc/check d1-clock d2-clock))) + (is (= :concurrent (vv/check d1-clock d2-clock))) ;; Both devices sync and merge - (let [merged-at-d1 (vc/merge-clocks d1-clock d2-clock) - merged-at-d2 (vc/merge-clocks d2-clock d1-clock)] + (let [merged-at-d1 (vv/merge-clocks d1-clock d2-clock) + merged-at-d2 (vv/merge-clocks d2-clock d1-clock)] ;; Merged clocks are identical (is (= merged-at-d1 merged-at-d2)) ;; Device 1 makes another update after sync - (let [d1-after-sync (vc/tick merged-at-d1 "d1")] + (let [d1-after-sync (vv/tick merged-at-d1 "d1")] ;; This new update is a descendant of the merged clock - (is (= :ancestor (vc/check merged-at-d1 d1-after-sync))) - (is (= :descendant (vc/check d1-after-sync merged-at-d1)))))))) + (is (= :ancestor (vv/check merged-at-d1 d1-after-sync))) + (is (= :descendant (vv/check d1-after-sync merged-at-d1))))))))