Skip to content

Repository files navigation

version-vector

Version vector implementation for Clojure and ClojureScript.

Overview

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.

Installation

Add to your deps.edn:

{:deps {net.clojars.bru/version-vector {:mvn/version"0.2.0"}}}

Platform Support

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.

Quick Start

(nsmy-app.sync
(:require [version-vector.core :as vv]))
;; Create a clock for this device
(defmy-clock (vv/make-clock"device-1"))
;; Increment the clock when making changes
(defupdated-clock (vv/tick my-clock "device-1"))
; => {"device-1" 1};; Compare with another device's clock
(defother-clock {"device-2"1})
(vv/check updated-clock other-clock)
; => :concurrent (both devices made independent changes);; Merge clocks when syncing
(defmerged (vv/merge-clocks updated-clock other-clock))
; => {"device-1" 1 "device-2" 1}

Version Vector API

Core Operations

make-clock

(make-clock device-id)

Create a new vector clock for the given device ID.

tick

(tick clock device-id)

Increment the counter for the specified device. Returns a new clock.

check

(check this-clock other-clock)

Compare two clocks and return their causal relationship:

  • :same - Clocks are identical
  • :ancestor - this-clock causally precedes other-clock
  • :descendant - this-clock causally follows other-clock
  • :concurrent - Neither causally precedes the other (conflict!)

merge-clocks

(merge-clocks clock1 clock2)

Merge two clocks by taking the maximum value for each device. Used when synchronizing state across devices.

Usage Examples

Detecting Conflicts

(require '[version-vector.core :as vv])
;; Device 1 makes a change
(defdevice1-clock (vv/tick (vv/make-clock"d1") "d1"))
; => {"d1" 1};; Device 2 makes an independent change
(defdevice2-clock (vv/tick (vv/make-clock"d2") "d2"))
; => {"d2" 1};; Check for conflicts
(vv/check device1-clock device2-clock)
; => :concurrent (conflict detected!)

Synchronization Workflow

Example: distributed note taking app

(defrecordNote [id content clock])
(defnupdate-note [note new-content device-id]
(->Note (:id note)
new-content
(vv/tick (:clock note) device-id)))
(defnmerge-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)))))

Testing

Run tests on Clojure:

clojure -X:test

All tests run on the JVM. The library works in ClojureScript projects but is only tested on Clojure since the implementation is platform-agnostic.

License

Copyright © 2025

Distributed under the Eclipse Public License version 1.0.

References

About

An interpretation of vector clocks for clojure(script)

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages