A naive Common Lisp implementation of the Automerge runtime protocol, using the Bramble Sync Protocol's "Eager mode" for the underlying message passing. Intended for educational purposes.

avon 2d6ffc61eb Update README 1 rok temu
README.org 2d6ffc61eb Update README 1 rok temu
automerge.lisp ff742fd61f init 1 rok temu
test.lisp ff742fd61f init 1 rok temu

README.org

A naive Common Lisp implementation of the Automerge runtime protocol, using the Bramble Sync Protocol's "Eager mode" for the underlying message passing instead of the normal Automerge sync protocol. Intended for educational purposes.

Demo:

Initialize two users, `Alice` and `Bob`.


(defvar *alice* (create-schema))
(defvar *bob* (create-schema))

Exchange contact ID info:


(add-contact *alice* "bob" (getf *bob* :id))
(add-contact *bob* "alice" (getf *alice* :id))

Now let's say Alice adds a key value pair, then updates it:


(put-msg *alice* (list 0 (getf *alice* :id)) "food" "pizza")
(put-msg *alice* (list 0 (getf *alice* :id)) "food" "tofu")

Alice can now create an outgoing report of messages for Bob:


(send-eager-update *alice* (getf *bob* :id))

in this case, the report looks like this:


((5 2)
(0 NIL)
 (1
  (:OPID (2 7926) :OBJID (0 7926) :PROP "food" :ACTION "set value" :VALUE
   "tofu" :DEP ((1 7926))))
 (1
  (:OPID (1 7926) :OBJID (0 7926) :PROP "food" :ACTION "set value" :VALUE
   "pizza" :DEP NIL)))
  • Report with a leading number of `5` corresponds to the custom lamport clock report.
  • The rest of the reports use the flags described in the BSP Documentation.

Bob can process this report to recieve the updates Alice made:


(process-eager-update *bob* (getf *alice* :id)
		      (send-eager-update *alice* (getf *bob* :id)))

Now that Alice and Bob's states are synced, we can try and create a merge conflict:


;;;; Create a merge conflict
(put-msg *bob* (list 0 (getf *alice* :id)) "food" "salad")
(put-msg *alice* (list 0 (getf *alice* :id)) "food" "pasta")

Now the state's of both trees are diverged, let's reconcile them now using a two-way sync.


;;;; TWO WAY SYNC
;;;; note that order does not matter
(process-eager-update *alice*
		      (getf *bob* :id)
		      (send-eager-update *bob* (getf *alice* :id)))
(process-eager-update *bob*
		      (getf *alice* :id)
		      (send-eager-update *alice* (getf *bob* :id)))

Thanks to the Automerge runtime protocol which uses "Last Writer Wins" + using Contact ID as the tiebreaker, we can see that both structures show the same value of "salad" for the "food" key.


;;;; Should both return "salad"
(get-msg *alice* (list 0 (getf *alice* :id)) "food")
(get-msg *bob* (list 0 (getf *alice* :id)) "food")

Nesting Objects

You can also set a key to point to another Map object


;;;; Put objects into slots
(let ((table (put-obj *bob* (list 0 (getf *alice* :id)) "contact")))
  (put-msg *bob* table "email" "someemail@email.com"))
(let ((table (get-msg *bob* (list 0 (getf *alice* :id)) "contact")))
  (get-msg *bob* table "email")) ; "someemail@email.com