How do I undo or reverse a transaction in datomic?

The basic procedure:

  1. Retrieve the datoms created in the transaction you want to undo. Use the transaction log to find them.
  2. Remove datoms related to the transaction entity itself: we don't want to retract transaction metadata.
  3. Invert the "added" state of all remaining datoms, i.e., if a datom was added, retract it, and if it was retracted, add it.
  4. Reverse the order of the inverted datoms so the bad-new value is retracted before the old-good value is re-asserted.
  5. Commit a new transaction.

In Clojure, your code would look like this:

(defn rollback
  "Reassert retracted datoms and retract asserted datoms in a transaction,
  effectively \"undoing\" the transaction.

  WARNING: *very* naive function!"
  [conn tx]
  (let [tx-log (-> conn d/log (d/tx-range tx nil) first) ; find the transaction
        txid   (-> tx-log :t d/t->tx) ; get the transaction entity id
        newdata (->> (:data tx-log)   ; get the datoms from the transaction
                     (remove #(= (:e %) txid)) ; remove transaction-metadata datoms
                     ; invert the datoms add/retract state.
                     (map #(do [(if (:added %) :db/retract :db/add) (:e %) (:a %) (:v %)]))
                     reverse)] ; reverse order of inverted datoms.
    @(d/transact conn newdata)))  ; commit new datoms.