Version vector implementation for Clojure and ClojureScript.
This library provides version vectors for building distributed systems in both Clojure (JVM) and ClojureScript.
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.
Add to your deps.edn:
{:deps {net.clojars.bru/version-vector {:mvn/version "0.2.0"}}}This library is written in .cljc (cross-platform Clojure) and works in both:
- Clojure (JVM): Server-side applications, backend services
- ClojureScript: Browser applications, Node.js, React Native
The core algorithm uses only platform-agnostic Clojure functions with no dependencies.
Testing: Tests run on Clojure only. Since the implementation is 100% platform-agnostic with no JavaScript interop or ClojureScript-specific features, Clojure testing should provide sufficient coverage.
(ns my-app.sync
(:require [version-vector.core :as vv]))
;; Create a clock for this device
(def my-clock (vv/make-clock "device-1"))
;; Increment the clock when making changes
(def updated-clock (vv/tick my-clock "device-1"))
; => {"device-1" 1}
;; Compare with another device's clock
(def other-clock {"device-2" 1})
(vv/check updated-clock other-clock)
; => :concurrent (both devices made independent changes)
;; Merge clocks when syncing
(def merged (vv/merge-clocks updated-clock other-clock))
; => {"device-1" 1 "device-2" 1}(make-clock device-id)Create a new vector clock for the given device ID.
(tick clock device-id)Increment the counter for the specified device. Returns a new clock.
(check this-clock other-clock)Compare two clocks and return their causal relationship:
:same- Clocks are identical:ancestor-this-clockcausally precedesother-clock:descendant-this-clockcausally followsother-clock:concurrent- Neither causally precedes the other (conflict!)
(merge-clocks clock1 clock2)Merge two clocks by taking the maximum value for each device. Used when synchronizing state across devices.
(require '[version-vector.core :as vv])
;; Device 1 makes a change
(def device1-clock (vv/tick (vv/make-clock "d1") "d1"))
; => {"d1" 1}
;; Device 2 makes an independent change
(def device2-clock (vv/tick (vv/make-clock "d2") "d2"))
; => {"d2" 1}
;; Check for conflicts
(vv/check device1-clock device2-clock)
; => :concurrent (conflict detected!)Example: distributed note taking app
(defrecord Note [id content clock])
(defn update-note [note new-content device-id]
(->Note (:id note)
new-content
(vv/tick (:clock note) device-id)))
(defn merge-notes [note1 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))
(vv/merge-clocks (:clock note1) (:clock note2)))))Run tests on Clojure:
clojure -X:testAll tests run on the JVM. The library works in ClojureScript projects but is only tested on Clojure since the implementation is platform-agnostic.
Copyright © 2025
Distributed under the Eclipse Public License version 1.0.
- Version Vector
- Why Logical Clocks are Easy
- Time, Clocks, and the Ordering of Events - Lamport's seminal paper