Full Code of aredington/schism for AI

master c4d042f7c5cf cached
28 files
134.9 KB
35.1k tokens
1 requests
Download .txt
Repository: aredington/schism
Branch: master
Commit: c4d042f7c5cf
Files: 28
Total size: 134.9 KB

Directory structure:
gitextract_v04t0_24/

├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── project.clj
├── resources/
│   └── data_readers.cljc
├── src/
│   └── schism/
│       ├── core.cljc
│       ├── impl/
│       │   ├── core.cljc
│       │   ├── protocols.cljc
│       │   ├── types/
│       │   │   ├── list.cljc
│       │   │   ├── map.cljc
│       │   │   ├── nested_map.cljc
│       │   │   ├── nested_vector.cljc
│       │   │   ├── nesting_util.cljc
│       │   │   ├── set.cljc
│       │   │   └── vector.cljc
│       │   └── vector_clock.cljc
│       └── node.cljc
└── test/
    └── schism/
        ├── core_test.cljc
        ├── impl/
        │   ├── types/
        │   │   ├── list_test.cljc
        │   │   ├── map_test.cljc
        │   │   ├── nested_map_test.cljc
        │   │   ├── nested_vector_test.cljc
        │   │   ├── set_test.cljc
        │   │   └── vector_test.cljc
        │   └── vector_clock_test.cljc
        ├── node_test.cljc
        └── test.cljc

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
/target
/classes
/checkouts
pom.xml
pom.xml.asc
*.jar
*.class
/.lein-*
/.nrepl-port
.hgignore
.hg/
node_modules/*


================================================
FILE: CHANGELOG.md
================================================
# Change Log
All notable changes to this project will be documented in this file. This change log follows the conventions of [keepachangelog.com](http://keepachangelog.com/).

## [Unreleased]
### Changed
- Add a new arity to `make-widget-async` to provide a different widget shape.

## [0.1.1] - 2018-01-05
### Changed
- Documentation on how to make the widgets.

### Removed
- `make-widget-sync` - we're all async, all the time.

### Fixed
- Fixed widget maker to keep working when daylight savings switches over.

## 0.1.0 - 2018-01-05
### Added
- Files from the new template.
- Widget maker public API - `make-widget-sync`.

[Unreleased]: https://github.com/your-name/schism/compare/0.1.1...HEAD
[0.1.1]: https://github.com/your-name/schism/compare/0.1.0...0.1.1


================================================
FILE: LICENSE
================================================
Copyright 2021 Alex Redington

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

================================================
FILE: README.md
================================================
# schism

A batteries included library of CRDT implementations of Clojure's core
data types: Sets, Maps, Vectors, and Lists with support for distributed
modification and eventual consistency.

## Dependency Information

Latest release: 0.1.2

[Leiningen](http://github.com/technomancy/leiningen/) and [Boot](http://boot-clj.com)
dependency information:

```
[com.holychao/schism "0.1.2"]
```

## Motivation

Clojure is one of a handful of languages which can be authored and
executed in both a high performance server environment and in a web
browser. This strength affords many benefits, one of which the
language does not cover is allowing for concurrent modification of data
with rich synchronization semantics.

There are some other efforts similar to this. Schism's aims are:

- To minimize the locus of concerns outside of collection data
  structures.
- To provide collection data structures with inferior performance to
  Clojure's own persistent data structures, but which only incur
  sub-linear cost increases, available through the same interfaces.
- To provide collection data structures with greater storage, and
  serialization costs than Clojure's own persistent data structures,
  which grow with an upper bound of the number of elements in the
  collection, independent of the number of operations against that
  collection.
- Allow for a Clojure and ClojureScript processes executing on
  separate nodes to maintain convergent data.


## Limitations

Because schism refuses to use tombstones, some convergence operations
will have different results than structures which will embrace the
costs of tombstones.

For example, Schism's set may drop a recently added element during
convergence with certain vector clock states. While this quality is
undesirable, I accept it more readily than monotonically increasing
storage requirements for data explicitly intended for communication
between nodes. Future work may pursue allowing for the convergence
operation to have some configurability so that vector clocks will
retain information more eagerly to reduce the incidence of this phenomena.

## Usage

`schism.core` contains functions for generating new collections,
e.g. `schism.core/convergent-set`. These functions accept arguments
much like their Clojure core equivalents, so `(convergent-set :a :b
:c)` creates a set equivalent to `#{:a :b :c}`

These collections support Clojure's collection operations with the
same semantic consequences (save for above mentioned performance and
storage costs). Any divergence between a schism collection's behavior
and a Clojure collection's behavior is a bug. Please file a report, as
these should be relatively fast and easy to fix.

String coercion of a schism collection is identical to that of a
Clojure collection, e.g. a convergent set will coerce to a string as
`#{:a :b :c}`. However, `pr-str` will generate a longer
tagged literal containing all synchronization data for the structure
to operate correctly. Schism eagerly enables edn readers for its
structures, so synchronization can be as simple as sending the results
of `pr-str` over the wire, reading on the receiving side and
converging with the receiver's local copy.

Convergence is done with `schism.core/converge`. It accepts two
collections, which it assumes are both copies of the same replicated
collection, and returns a single collection: the result of
convergence. Converge replicates the metadata of its first argument
into its return value, if present.

Each CLJ and CLJS process working with any shared schism collection is
a node in a distributed computing cluster. Each node should have an
identity in order for synchronization to operate correctly. You may
invoke `schism.core/initialize-node!` with no arguments to initialize
the current process to have a randomly generated UUID as its node
identifier. You may use any serializable value as the node identifier
by passing that value to initialize-node. If you can create stable
node identifiers, that can lead to some minor reduction in the storage
requirements for schism's data structures. If two nodes operate on a
replicated collection independently and do NOT have distinct node
identifiers, schism's convergence behavior is undefined. (Don't do
this). If you do not explicitly invoke `initialize-node!`, it is left
at the value `nil`.

## Nested collections

It is common to build up a tree of maps and vectors to create several
addressable values that cohere to a common whole. If one built this
collection up out of individual schism collections, a number of
problems with convergence and serialization would present
themselves. I have provided `schism.core/nested-map` and
`schism.core/nested-vector` to address both these problems. While
these provide best-least-surprise convergence, it's important to
understand the limitations and leverages of best:

- These collections incur substantially more CPU time to conduct
  simple operations as a isomorphic mirror of all modifications must
  be computed on each update.
- While adding empty collections should work, please avoid doing so.
- `clojure.core/assoc-in`, `clojure.core/update-in`, and friends
  should work with the convergent map without any special handling.
- Vectors containing leaf nodes will compose tail insertions, so two
  nodes adding to the tail with `conj` will have both of their
  additions retained in chronological order.
- Vectors at intermediate nodes will treat the child at the index as
  identity.
- Collections must be isomorphic to converge: Do not allow one node to
  place a vector and another node to place a map at the same path in
  the tree.

Given the additional complications I strongly encourage clients to
pursue a strategy of retaining a shallow convergent-vector of entity
ids and one convergent-map for each entity. For those cases where this
combo is not sufficient, please wield `nested-map` and `nested-vector`
with care.

## Further work

- Configurable convergence
- Other good ideas as the community provides them

## Contributing

I don't use CA's or other such things. Bugfixes are welcome and
appreciated.

I reserve the right to dismiss feature requests in the guise of PRs.

All work will be evaluated in light of conformance with motivations as
stated in this document.

## License

Copyright © 2020 Alex Redington

Distributed under the MIT License


================================================
FILE: project.clj
================================================
(defproject com.holychao/schism "0.1.2"
  :description "First Class CRDTs for Clojure"
  :url "https://github.com/aredington/schism"
  :license {:name "MIT License"
            :url "https://opensource.org/licenses/MIT"}
  :dependencies [[org.clojure/clojure "1.10.0" :scope "provided"]
                 [org.clojure/clojurescript "1.10.520" :scope "provided"]]
  :plugins [[lein-cljsbuild "1.1.7"]
            [lein-doo "0.1.11"]]
  :profiles {:dev {:dependencies [[doo "0.1.11"]
                                  [org.clojure/test.check "0.10.0-alpha4"]]}}
  :cljsbuild {:builds [{:id "test"
                        :source-paths ["src" "test"]
                        :compiler {:output-to     "target/test.js"
                                   :main schism.test
                                   :output-dir    "target"
                                   :optimizations :none
                                   :source-map    true
                                   :pretty-print  true
                                   :recompile-dependents false
                                   :parallel-build true
                                   :checked-arrays :warn}}]}
  :clean-targets ^{:protect false} ["target"]
  :aliases {"test-platforms" ["do" "clean," "test," "doo" "chrome-headless" "test" "once"]})


================================================
FILE: resources/data_readers.cljc
================================================
{schism/set           schism.impl.types.set/read-edn-set
 schism/map           schism.impl.types.map/read-edn-map
 schism/list          schism.impl.types.list/read-edn-list
 schism/vector        schism.impl.types.vector/read-edn-vector
 schism/nested-map    schism.impl.types.nested-map/read-edn-map
 schism/nested-vector schism.impl.types.nested-vector/read-edn-vector}


================================================
FILE: src/schism/core.cljc
================================================
(ns schism.core
  (:require [schism.impl.types.set :as sset]
            [schism.impl.types.map :as smap]
            [schism.impl.types.list :as slist]
            [schism.impl.types.vector :as svector]
            [schism.impl.types.nested-map :as nmap]
            [schism.impl.types.nested-vector :as nvector]
            [schism.impl.protocols :as sp]
            [schism.node :as sn]))

(defn convergent-set
  "Create a new ORSWOT containing args. Each arg will be recorded as
  being added to the set by the current node at invocation time."
  [& args]
  (apply sset/new-set args))

(defn convergent-map
  "Create a new ORMWOT, establishing associations between each pair of
  key-value arguments. Each entry will be recorded as being added to
  the map by the current node at invocation time."
  [& args]
  (apply smap/new-map args))

(defn convergent-list
  "Create a new convergent list containing args. Args will be placed
  into the list in the order they appear during invocation, just as
  with `clojure.core/list`. Each entry will be recorded as being added
  to the list by the current node at invocation time."
  [& args]
  (apply slist/new-list args))

(defn convergent-vector
  "Create a new convergent vector containing args. Each entry will be
  recorded as being added to the list by the current node at
  invocation time, in the ordinal position it occupied during
  invocation."
  [& args]
  (apply svector/new-vector args))

(defn nested-map
  "Create a new convergent, nesting map containing args. Each
  non-collection value at any location in the map will be treated as a
  single atomic value, allowing for discrete modification of each
  terminal in the tree. Those collections returned by `nested-map`
  cannot perform as well as those returned by `convergent-map` and
  have higher computational and storage costs."
  [& args]
  (apply nmap/new-map args))

(defn nested-vector
  "Create a new convergent, nesting vector containing args. Each
  non-collection value at any location in the vector will be treated
  as a single atomic value, allowing for discrete modification of each
  terminal in the tree. Those collections returned by `nested-vector`
  cannot perform as well as those returned by `convergent-vector` and
  have higher computational and storage costs."
  [& args]
  (apply nvector/new-vector args))

(defn converge
  "Return a converged copy of `c1` containing the modifications of
  `c2`. Convergence is defined on a per-type basis. If `c1` has
  metadata, retain that metadata on the returned result. Convergence
  ticks the vector clock for the node on which convergence is
  occurring. `c1` and `c2` must be collections of the same type.

  The behavior of `converge` is not defined when either:

  - The current value of `schism.node/*current-node*` is nil

  - The current value of `schism.node/*current-node*` is shared with
  another node making modifications to the same logical collection."
  [c1 c2]
  (sp/synchronize c1 c2))

(def initialize-node!
  "Initialize the current node to an edn serializable value if
  provided. If invoked with no argument, initializes the current node
  to a random UUID."
  sn/initialize-node!)

(defmacro with-node
  "Run `body` with the current node set to `node-id`"
  [id & body]
  `(sn/with-node ~id ~@body))


================================================
FILE: src/schism/impl/core.cljc
================================================
(ns schism.impl.core
  (:require [schism.node :as node]
            [clojure.set :as set])
  #?(:clj (:import (java.util Date))))

(def to-millis (memfn ^Date getTime))

(defn to-date
  [millis]
  #?(:clj (Date. millis)
     :cljs (js/Date. millis)))

(defn now
  []
  #?(:clj (Date.)
     :cljs (js/Date.)))

(defn node-and-threshold
  [data]
  (->> data
       :vector-clock
       (reduce-kv (fn [[node time] candidate-node candidate-time]
                    (if (< (to-millis time) (to-millis candidate-time))
                      [candidate-node candidate-time]
                      [node time]))
                   (-> data :vector-clock first))
       (#(update % 1 to-millis))))

(defn retain-elements
  "Accepts two maps of the form

  {:vector-clock <map of nodes to update times>
   :elements <vector of {:data <opaque value>
                         :author-node <node-id>
                         :record-time <Date object>}>}

  Returns a seq of elements to be retained using ORSWOT merge semantics."
  [own-data other-data]
  (let [other-threshold (-> own-data :vector-clock (get node/*current-node*) to-millis)
        [other-node own-threshold] (node-and-threshold other-data)
        own-vclock-for-other (-> own-data :vector-clock (get other-node))
        other-vclock-limiter (if own-vclock-for-other
                               (fn [{:keys [record-time] :as element}]
                                 (>= (to-millis own-vclock-for-other) (to-millis record-time)))
                               (constantly true))
        other-vclock-for-own (-> other-data :vector-clock (get node/*current-node*))
        own-vclock-limiter (if other-vclock-for-own
                             (fn [{:keys [record-time] :as element}]
                               (>= (to-millis other-vclock-for-own) (to-millis record-time)))
                             (constantly true))
        other-additions (remove #(and (> other-threshold (to-millis (:record-time %)))
                                      (other-vclock-limiter %)) (:elements other-data))
        own-additions (remove #(and (> own-threshold (to-millis (:record-time %)))
                                    (own-vclock-limiter %)) (:elements own-data))]
    (concat other-additions own-additions)))

(defn common-elements
  "Accepts maps of the form

   {:vector-clock <map of nodes to update times>
    :elements <vector of {:data <opaque value>
                          :author-node <node-id>
                          :record-time <Date object>}>}

   Returns a vector of the common elements."
  [& datasets]
  (apply set/intersection (map (comp set :elements) datasets)))

(defn distinct-data
  "Accepts maps of the form

   {:vector-clock <map of nodes to update times>
    :elements <vector of {:data <opaque value>
                          :author-node <node-id>
                          :record-time <Date object>}>}

   Returns a vector of the maps with the common elements entries removed."
  [& datasets]
  (let [common-elements (apply common-elements datasets)]
    (->> datasets
         (map #(update % :elements (fn [elements] (remove common-elements elements))))
         (into []))))

(defn merged-clock
  "Accepts a collection of elements, and two or more datasets of the form

   {:vector-clock <map of nodes to update times>
    :elements <vector of {:data <opaque value>
                          :author-node <node-id>
                          :record-time <Date object>}>}

   Returns a vector clock of the relevant nodes, that being the nodes
  referenced as :author-node in elements."
  [elements & datasets]
  (let [relevant-nodes (set (map :author-node elements))]
    (-> (apply merge-with (partial max-key to-millis) (map :vector-clock datasets))
        (select-keys relevant-nodes))))

(def tail-insertion-sort-value
  "The value to use when sorting insertions by index, and the recorded
  index was -1, indicating the element was inserted at the tail."
  #?(:clj Long/MAX_VALUE
     :cljs (.-MAX_SAFE_INTEGER js/Number)))

(defn assoc-n-with-tail-support
  "Assoc with support to place `v` at the tail of `a` when `n` is -1."
  [a n v]
  (if (= n -1)
    (conj a v)
    (assoc a n v)))


================================================
FILE: src/schism/impl/protocols.cljc
================================================
(ns schism.impl.protocols)

(defprotocol Convergent
  (synchronize [convergent other]
    "Synchronizes `convergent` with `other` such that all changes
    incorporated into `other` will be represented in a new persistent
    structure derived from `convergent`."))

(defprotocol Vclocked
  "A protocol for obtaining the current vector clock of a value, and
  for deriving a new vector clock for a value."
  (get-clock [clocked] "Returns the current vector clock of `clocked`,
  a map of node IDs to timestamps.")
  (with-clock [clocked clock] "Returns a new structure derived from
  `clocked`, associating `clock` with the returned value."))

(extend-protocol Vclocked
  nil
  (get-clock [_] {})
  (with-clock [_ clock] nil))


================================================
FILE: src/schism/impl/types/list.cljc
================================================
(ns schism.impl.types.list
  "Definition and support for Schism's Convergent List type. The
  convergent list is a simple timestamped log of entries with a vector
  clock. Convergence places entries into the resultant list in
  insertion order. The vector clock conveys that an item has been
  removed from the list on another node."
  (:require [schism.impl.protocols :as proto]
            [schism.impl.vector-clock :as vc]
            [schism.impl.core :as ic]
            [schism.node :as node]
            #?(:cljs [cljs.reader :as reader]))
  #?(:cljs (:require-macros [schism.impl.vector-clock :as vc]))
  #?(:clj (:import (clojure.lang IPersistentCollection IPersistentStack IReduce Counted IHashEq Seqable IObj IMeta ISeq)
                   (java.io Writer)
                   (java.util Date Collection)
                   (java.lang Object))))

;; A CLJ & CLJS implementation of a convergent list

;; Each list maintains its own vector clock, and insertion times and
;; nodes for each element of the list. ConvergentList entries and
;; insertions are correlated positionally (as the list may contain the
;; same item multiple times.) Insertion times dictate ordering. The
;; vector clock determines if an entry has been removed.

(declare clist-conj clist-rest clist-empty)

#?(:clj (deftype ConvergentList [data vclock insertions]
          Counted
          (count [this] (.count ^Counted (.-data this)))

          IPersistentCollection
          (cons [this o] (clist-conj this o))
          (empty [this] (clist-empty this))
          (equiv [this other] (.equiv ^IPersistentCollection (.-data this) other))

          Object
          (equals [this o]
            (.equals (.-data this) o))
          (hashCode [this]
            (.hashCode (.-data this)))
          (toString [this]
            (.toString (.-data this)))

          IHashEq
          (hasheq [this]
            (.hasheq ^IHashEq (.-data this)))

          Seqable
          (seq [this]
            this)

          java.util.List
          (add [this o] (.add ^java.util.List (.-data this) o))
          (add [this index o] (.add ^java.util.List (.-data this) index o))
          (addAll [this c] (.addAll ^java.util.List (.-data this) c))
          (clear [this] (.clear ^java.util.List (.-data this)))
          (contains [this o] (.contains ^java.util.List (.-data this) o))
          (containsAll [this c] (.containsAll ^java.util.List (.-data this) c))
          (get [this i] (.get ^java.util.List (.-data this) i))
          (indexOf [this o] (.indexOf ^java.util.List (.-data this) o))
          (isEmpty [this] (.isEmpty ^java.util.List (.-data this)))
          (iterator [this] (.iterator ^java.util.List (.-data this)))
          (lastIndexOf [this o] (.lastIndexOf ^java.util.List (.-data this) o))
          (listIterator [this] (.listIterator ^java.util.List (.-data this)))
          (^Object remove [this ^int i] (.remove ^java.util.List (.-data this) i))
          (^boolean remove [this ^Object o] (.remove ^java.util.List (.-data this) o))
          (removeAll [this c] (.removeAll ^java.util.List (.-data this) c))
          (replaceAll [this op] (.replaceAll ^java.util.List (.-data this) op))
          (retainAll [this c] (.retainAll ^java.util.List (.-data this) c))
          (set [this i e] (.set ^java.util.List (.-data this) i e))
          (size [this] (.size ^java.util.List (.-data this)))
          (sort [this c] (.sort ^java.util.List (.-data this) c))
          (spliterator [this] (.spliterator ^java.util.List (.-data this)))
          (subList [this i j] (.subList ^java.util.List (.-data this) i j))
          (toArray [this] (.toArray ^java.util.List (.-data this)))
          (^"[Ljava.lang.Object;" toArray [this ^"[Ljava.lang.Object;" a]
           (.toArray ^java.util.List (.-data this) a))

          IObj
          (withMeta [this meta]
            (ConvergentList. (with-meta ^IObj (.-data this)
                               meta)
                             (.-vclock this)
                             (.-insertions this)))

          IMeta
          (meta [this]
            (.meta ^IMeta (.-data this)))

          IReduce
          (reduce [this f]
            (.reduce ^IReduce (.-data this) f))

          IPersistentStack
          (peek [this] (.peek ^IPersistentStack (.-data this)))
          (pop [this]
            (clist-rest this))

          ISeq
          (first [this] (.first ^ISeq (.-data this)))
          (next [this] (clist-rest this))
          (more [this] (clist-rest this)))
   :cljs (deftype ConvergentList [data vclock insertions]
           ICounted
           (-count [this] (-count (.-data this)))

           IEmptyableCollection
           (-empty [this] (clist-empty this))

           ICollection
           (-conj [this o] (clist-conj this o))

           IEquiv
           (-equiv [this other]
             (-equiv (.-data this) other))

           IPrintWithWriter
           (-pr-writer [o writer opts]
             (-write writer "#schism/list [")
             (-write writer (pr-str (.-data o)))
             (-write writer ", ")
             (-write writer (pr-str (.-vclock o)))
             (-write writer ", ")
             (-write writer (pr-str (.-insertions o)))
             (-write writer "]"))

           IHash
           (-hash [this] (-hash (.-data this)))

           ISeqable
           (-seq [this] this)

           Object
           (toString [this] (.toString (.-data this)))

           IMeta
           (-meta [this]
             (-meta (.-data this)))

           IWithMeta
           (-with-meta [this meta]
             (ConvergentList. (-with-meta (.-data this)
                                          meta)
                              (.-vclock this)
                              (.-insertions this)))

           ISeq
           (-first [this]
             (-first (.-data this)))
           (-rest [this]
             (clist-rest this))))

(defn clist-conj [^ConvergentList clist o]
  (vc/update-clock now clist
                   (ConvergentList. (conj (.-data clist) o)
                                    (.-vclock clist)
                                    (conj (.-insertions clist) [node/*current-node* now]))))

(defn clist-empty [^ConvergentList clist]
  (vc/update-clock _ clist
                   (ConvergentList. (list)
                                    (hash-map)
                                    (list))))

(defn clist-rest [^ConvergentList clist]
  (vc/update-clock _ clist
                   (ConvergentList. (rest (.-data clist))
                                    (.-vclock clist)
                                    (rest (.-insertions clist)))))

(defn- elemental-data
  [^ConvergentList l]
  {:vector-clock (.-vclock l)
   :elements (into []
                   (for [[datum [author-node record-time]] (map vector (.-data l) (.-insertions l))]
                     {:data datum
                      :author-node author-node
                      :record-time record-time}))})

(extend-type ConvergentList
  proto/Vclocked
  (get-clock [this] (.-vclock this))
  (with-clock [this new-clock] (ConvergentList. (.-data this)
                                                new-clock
                                                (.-insertions this)))

  proto/Convergent
  (synchronize [this ^ConvergentList other]
    (let [own-meta (-> this .-data meta)
          own-data (elemental-data this)
          other-data (elemental-data other)
          retain (->> (reverse (:elements other-data))
                      (map vector (reverse (:elements own-data)))
                      (take-while (partial apply =))
                      reverse
                      (map first))
          completed-elements (concat (sort-by :record-time (apply ic/retain-elements
                                                                  (ic/distinct-data own-data other-data)))
                                     retain)
          completed-data (->> completed-elements
                              (map :data)
                              (into '())
                              reverse)
          completed-insertions (->> completed-elements
                                    (map (fn [{:keys [author-node record-time]}]
                                           [author-node record-time]))
                                    (into '())
                                    reverse)
          completed-vclock (ic/merged-clock completed-elements own-data other-data)]
      (vc/update-clock _ this
                       (ConvergentList. (with-meta completed-data own-meta)
                                        completed-vclock
                                        completed-insertions)))))

#?(:clj (defmethod print-method ConvergentList
          [^ConvergentList l ^Writer writer]
          (.write writer "#schism/list [")
          (.write writer (pr-str (.-data l)))
          (.write writer ", ")
          (.write writer (pr-str (.-vclock l)))
          (.write writer ", ")
          (.write writer (pr-str (.-insertions l)))
          (.write writer "]")))

(defn read-edn-list
  [read-object]
  (let [[data vclock insertions] read-object]
    (ConvergentList. data vclock insertions)))

#?(:cljs (cljs.reader/register-tag-parser! 'schism/list read-edn-list))

(defn new-list
  ([] (ConvergentList. (list)
                       (hash-map)
                       (list)))
  ([& args] (vc/update-clock now nil
                             (ConvergentList. (apply list args)
                                              (hash-map)
                                              (apply list (repeat (count args) [node/*current-node* now]))))))


================================================
FILE: src/schism/impl/types/map.cljc
================================================
(ns schism.impl.types.map
  "Definition and support for Schism's Convergent Map type, an ORMWOT
  implemented on top of Clojure's persistent maps and a Schism Vector
  Clock."
  (:require [schism.impl.protocols :as proto]
            [schism.impl.vector-clock :as vc]
            [schism.impl.core :as ic]
            [schism.node :as node]
            [clojure.set :as set]
            #?(:cljs [cljs.reader :as reader]))
  #?(:cljs (:require-macros [schism.impl.vector-clock :as vc]))
  #?(:clj (:import (clojure.lang IPersistentCollection IPersistentMap IHashEq Associative ILookup Counted Seqable IMapIterable IKVReduce IFn IObj IMeta)
                   (java.io Writer)
                   (java.util Date Collection)
                   (java.lang Object))))

;; A CLJS and CLJ imeplementation of ORMWOT (Observed-Removed Map without Tombstones)

;; Each map maintains its own vector clock, and birth dots for each
;; map entry by key. A birth dot consists of the node adding the
;; entry, and the date at which it was added. The vector clock
;; determines if an entry has been removed: a compared map absent an
;; entry, but with a newer vector clock than the own birthdot on the
;; entry indicates that it was removed; a compared map absent an entry
;; with an older vector clock indicates that the birthdot was never
;; seen and should be in the merged map. Birthdots also arbitrate
;; entries: for each entry the last writer wins.

(declare ormwot-conj ormwot-empty ormwot-assoc ormwot-dissoc)

(def not-found-sym (gensym :not-found))

#?(:clj (deftype Map [data vclock birth-dots]
          Counted
          (count [this] (.count ^Counted (.-data this)))

          IPersistentCollection
          (cons [this o] (ormwot-conj this o))
          (empty [this] (ormwot-empty this))
          (equiv [this other] (.equiv ^IPersistentCollection (.-data this) other))

          IPersistentMap
          (assoc [this k v] (ormwot-assoc this k v))
          (assocEx [this k v]
            (when (not= (get this k not-found-sym) not-found-sym)
              (throw (ex-info "Attempt to assocEx on map with key" {:map this
                                                                    :key k
                                                                    :value v})))
            (ormwot-assoc this k v))
          (without [this k] (ormwot-dissoc this k))

          Object
          (equals [this o]
            (.equals (.-data this) o))
          (hashCode [this]
            (.hashCode (.-data this)))
          (toString [this]
            (.toString (.-data this)))

          ILookup
          (valAt [this k]
            (.valAt ^ILookup (.-data this) k))
          (valAt [this k not-found]
            (.valAt ^ILookup (.-data this) k not-found))

          IMapIterable
          (keyIterator [this]
            (.keyIterator ^IMapIterable (.-data this)))
          (valIterator [this]
            (.valIterator ^IMapIterable (.-data this)))

          IKVReduce
          (kvreduce [this f init]
            (.kvreduce ^IKVReduce (.-data this) f init))

          IHashEq
          (hasheq [this]
            (.hasheq ^IHashEq (.-data this)))

          Seqable
          (seq [this]
            (.seq ^Seqable (.-data this)))

          java.util.Map
          (clear [this] (.clear ^java.util.Map (.-data this)))
          (compute [this k f] (.compute ^java.util.Map (.-data this) k f))
          (computeIfAbsent [this k f] (.computeIfAbsent ^java.util.Map (.-data this) k f))
          (computeIfPresent [this k f] (.computeIfPresent ^java.util.Map (.-data this) k f))
          (containsKey [this k] (.containsKey ^java.util.Map (.-data this) k))
          (containsValue [this v] (.containsValue ^java.util.Map (.-data this) v))
          (entrySet [this] (.entrySet ^java.util.Map (.-data this)))
          (get [this k] (.get ^java.util.Map (.-data this) k))
          (getOrDefault [this k not-found] (.getOrDefault ^java.util.Map (.-data this) k not-found))
          (isEmpty [this] (.isEmpty ^java.util.Map (.-data this)))
          (keySet [this] (.keySet ^java.util.Map (.-data this)))
          (merge [this k v f] (.merge ^java.util.Map (.-data this) k v f))
          (put [this k v] (.put ^java.util.Map (.-data this) k v))
          (putAll [this m] (.putAll ^java.util.Map (.-data this) m))
          (putIfAbsent [this k v] (.putIfAbsent ^java.util.Map (.-data this) k v))
          (remove [this k] (.remove ^java.util.Map (.-data this) k))
          (remove [this k v] (.remove ^java.util.Map (.-data this) k v))
          (replace [this k v] (.replace ^java.util.Map (.-data this) k v))
          (replace [this k ov nv] (.replace ^java.util.Map (.-data this) k ov nv))
          (replaceAll [this f] (.replaceAll ^java.util.Map (.-data this) f))
          (size [this] (.size ^java.util.Map (.-data this)))
          (values [this] (.values ^java.util.Map (.-data this)))

          IFn
          (invoke [this k]
            (.invoke ^IFn (.-data this) k))
          (invoke [this k not-found]
            (.invoke ^IFn (.-data this) k not-found))

          IObj
          (withMeta [this meta]
            (Map. (with-meta ^IObj (.-data this)
                    meta)
                  (.-vclock this) (.-birth-dots this)))

          IMeta
          (meta [this]
            (.meta ^IMeta (.-data this))))
   :cljs (deftype Map [data vclock birth-dots]
           ICounted
           (-count [this] (-count (.-data this)))

           IEmptyableCollection
           (-empty [this] (ormwot-empty this))

           ICollection
           (-conj [this o] (ormwot-conj this o))

           IAssociative
           (-contains-key? [this k]
             (-contains-key? (.-data this) k))
           (-assoc [this k v]
             (ormwot-assoc this k v))

           IFind
           (-find [this k]
             (-find (.-data this) k))

           IMap
           (-dissoc [this k]
             (ormwot-dissoc this k))

           IKVReduce
           (-kv-reduce [this f init]
             (-kv-reduce (.-data this) f init))

           IEquiv
           (-equiv [this other]
             (-equiv (.-data this) other))

           ILookup
           (-lookup [this o]
             (-lookup (.-data this) o))
           (-lookup [this o not-found]
             (-lookup (.-data this) o not-found))

           IPrintWithWriter
           (-pr-writer [o writer opts]
             (-write writer "#schism/map [")
             (-write writer (pr-str (.-data o)))
             (-write writer ", ")
             (-write writer (pr-str (.-vclock o)))
             (-write writer ", ")
             (-write writer (pr-str (.-birth-dots o)))
             (-write writer "]"))

           IHash
           (-hash [this] (-hash (.-data this)))

           IFn
           (-invoke [this o] ((.-data this) o))
           (-invoke [this o not-found] ((.-data this) o not-found))

           ISeqable
           (-seq [this] (-seq (.-data this)))

           Object
           (toString [this] (.toString (.-data this)))

           IMeta
           (-meta [this]
             (-meta (.-data this)))

           IWithMeta
           (-with-meta [this meta]
             (Map. (-with-meta (.-data this)
                               meta)
                   (.-vclock this)
                   (.-birth-dots this)))))

(defn ormwot-conj
  [^Map ormwot pair]
  (vc/update-clock now ormwot
                   (Map. (conj (.-data ormwot) pair)
                         (.-vclock ormwot)
                         (assoc (.-birth-dots ormwot) (first pair) [node/*current-node* now]))))

(defn ormwot-empty
  [^Map ormwot]
  (vc/update-clock _ ormwot
                   (Map. (hash-map)
                         (hash-map)
                         (hash-map))))

(defn ormwot-assoc
  [^Map ormwot k v]
  (vc/update-clock now ormwot
                   (Map. (assoc (.-data ormwot) k v)
                         (.-vclock ormwot)
                         (assoc (.-birth-dots ormwot) k [node/*current-node* now]))))

(defn ormwot-dissoc
  [^Map ormwot k]
  (vc/update-clock _ ormwot
                   (Map. (dissoc (.-data ormwot) k)
                         (.-vclock ormwot)
                         (dissoc (.-birth-dots ormwot) k))))

(defn- elemental-data
  [^Map m]
  {:vector-clock (.-vclock m)
   :elements (into []
                   (for [datum (.-data m)]
                     (let [dot (get (.-birth-dots m) (key datum))]
                       {:data datum
                        :author-node (first dot)
                        :record-time (last dot)})))})

(extend-type Map
  proto/Vclocked
  (get-clock [this] (.-vclock this))
  (with-clock [this new-clock] (Map. (.-data this)
                                     new-clock
                                     (.-birth-dots this)))

  proto/Convergent
  (synchronize [this ^Map other]
    (let [own-meta (-> this .-data meta)
          own-data (elemental-data this)
          other-data (elemental-data other)
          retain (filter (ic/common-elements own-data other-data)
                         (:elements own-data))
          completed-elements (->> (apply ic/retain-elements
                                         (ic/distinct-data own-data other-data))
                                  (concat retain)
                                  (sort-by :record-time))
          completed-data (into {} (map :data completed-elements))
          completed-birth-dots (->> completed-elements
                                    (map (fn [{:keys [data author-node record-time]}]
                                           [(key data) [author-node record-time]]))
                                    (into {}))
          completed-vclock (ic/merged-clock completed-elements own-data other-data)]
      (vc/update-clock _ this
                       (Map. (with-meta completed-data
                               own-meta)
                             completed-vclock
                             completed-birth-dots)))))

#?(:clj (defmethod print-method Map
          [^Map m ^Writer writer]
          (.write writer "#schism/map [")
          (.write writer (pr-str (.-data m)))
          (.write writer ", ")
          (.write writer (pr-str (.-vclock m)))
          (.write writer ", ")
          (.write writer (pr-str (.-birth-dots m)))
          (.write writer "]")))

(defn read-edn-map
  [read-object]
  (let [[data vclock birth-dots] read-object]
    (Map. data vclock birth-dots)))

#?(:cljs (cljs.reader/register-tag-parser! 'schism/map read-edn-map))

(defn new-map
  ([] (Map. (hash-map)
            (hash-map)
            (hash-map)))
  ([& args] (vc/update-clock now nil
                             (Map. (apply hash-map args)
                                   (hash-map)
                                   (apply hash-map
                                          (mapcat (fn [[k _]]
                                                    [k [node/*current-node* now]])
                                                  (partition 2 args)))))))


================================================
FILE: src/schism/impl/types/nested_map.cljc
================================================
(ns schism.impl.types.nested-map
  "Definition and support for Schism's Deeply Convergent Map type, an
  ORMWOT implemented on top of Clojure's persistent maps and a Schism
  Vector Clock. Contrasted with schism.impl.types.map/Map, this incurs
  substantially greater computational costs for assoc type operations
  and cannot guarantee linear time results."
  (:require [schism.impl.types.nesting-util :as nu]
            [schism.impl.protocols :as proto]
            [schism.impl.vector-clock :as vc]
            [schism.impl.core :as ic]
            [schism.node :as node]
            [clojure.set :as set]
            [clojure.data :refer [diff]]
            #?(:cljs [cljs.reader :as reader]))
  #?(:cljs (:require-macros [schism.impl.vector-clock :as vc]))
  #?(:clj (:import (clojure.lang IPersistentCollection IPersistentMap IHashEq Associative ILookup Counted Seqable IMapIterable IKVReduce IFn IObj IMeta)
                   (java.io Writer)
                   (java.util Date Collection)
                   (java.lang Object))))

(declare nested-map-conj nested-map-empty nested-map-assoc nested-map-dissoc)

(def not-found-sym (gensym :not-found))

#?(:clj (deftype NestedMap [data vclock birth-dots]
          Counted
          (count [this] (.count ^Counted (.-data this)))

          IPersistentCollection
          (cons [this o] (nested-map-conj this o))
          (empty [this] (nested-map-empty this))
          (equiv [this other] (.equiv ^IPersistentCollection (.-data this) other))

          IPersistentMap
          (assoc [this k v] (nested-map-assoc this k v))
          (assocEx [this k v]
            (when (not= (get this k not-found-sym) not-found-sym)
              (throw (ex-info "Attempt to assocEx on map with key" {:map this
                                                                    :key k
                                                                    :value v})))
            (nested-map-assoc this k v))
          (without [this k] (nested-map-dissoc this k))

          Object
          (equals [this o]
            (.equals (.-data this) o))
          (hashCode [this]
            (.hashCode (.-data this)))
          (toString [this]
            (.toString (.-data this)))

          ILookup
          (valAt [this k]
            (.valAt ^ILookup (.-data this) k))
          (valAt [this k not-found]
            (.valAt ^ILookup (.-data this) k not-found))

          IMapIterable
          (keyIterator [this]
            (.keyIterator ^IMapIterable (.-data this)))
          (valIterator [this]
            (.valIterator ^IMapIterable (.-data this)))

          IKVReduce
          (kvreduce [this f init]
            (.kvreduce ^IKVReduce (.-data this) f init))

          IHashEq
          (hasheq [this]
            (.hasheq ^IHashEq (.-data this)))

          Seqable
          (seq [this]
            (.seq ^Seqable (.-data this)))

          java.util.Map
          (clear [this] (.clear ^java.util.Map (.-data this)))
          (compute [this k f] (.compute ^java.util.Map (.-data this) k f))
          (computeIfAbsent [this k f] (.computeIfAbsent ^java.util.Map (.-data this) k f))
          (computeIfPresent [this k f] (.computeIfPresent ^java.util.Map (.-data this) k f))
          (containsKey [this k] (.containsKey ^java.util.Map (.-data this) k))
          (containsValue [this v] (.containsValue ^java.util.Map (.-data this) v))
          (entrySet [this] (.entrySet ^java.util.Map (.-data this)))
          (get [this k] (.get ^java.util.Map (.-data this) k))
          (getOrDefault [this k not-found] (.getOrDefault ^java.util.Map (.-data this) k not-found))
          (isEmpty [this] (.isEmpty ^java.util.Map (.-data this)))
          (keySet [this] (.keySet ^java.util.Map (.-data this)))
          (merge [this k v f] (.merge ^java.util.Map (.-data this) k v f))
          (put [this k v] (.put ^java.util.Map (.-data this) k v))
          (putAll [this m] (.putAll ^java.util.Map (.-data this) m))
          (putIfAbsent [this k v] (.putIfAbsent ^java.util.Map (.-data this) k v))
          (remove [this k] (.remove ^java.util.Map (.-data this) k))
          (remove [this k v] (.remove ^java.util.Map (.-data this) k v))
          (replace [this k v] (.replace ^java.util.Map (.-data this) k v))
          (replace [this k ov nv] (.replace ^java.util.Map (.-data this) k ov nv))
          (replaceAll [this f] (.replaceAll ^java.util.Map (.-data this) f))
          (size [this] (.size ^java.util.Map (.-data this)))
          (values [this] (.values ^java.util.Map (.-data this)))

          IFn
          (invoke [this k]
            (.invoke ^IFn (.-data this) k))
          (invoke [this k not-found]
            (.invoke ^IFn (.-data this) k not-found))

          IObj
          (withMeta [this meta]
            (NestedMap. (with-meta ^IObj (.-data this)
                          meta)
                        (.-vclock this) (.-birth-dots this)))

          IMeta
          (meta [this]
            (.meta ^IMeta (.-data this))))

   :cljs (deftype NestedMap [data vclock birth-dots]
           ICounted
           (-count [this] (-count (.-data this)))

           IEmptyableCollection
           (-empty [this] (nested-map-empty this))

           ICollection
           (-conj [this o] (nested-map-conj this o))

           IAssociative
           (-contains-key? [this k]
             (-contains-key? (.-data this) k))
           (-assoc [this k v]
             (nested-map-assoc this k v))

           IFind
           (-find [this k]
             (-find (.-data this) k))

           IMap
           (-dissoc [this k]
             (nested-map-dissoc this k))

           IKVReduce
           (-kv-reduce [this f init]
             (-kv-reduce (.-data this) f init))

           IEquiv
           (-equiv [this other]
             (-equiv (.-data this) other))

           ILookup
           (-lookup [this o]
             (-lookup (.-data this) o))
           (-lookup [this o not-found]
             (-lookup (.-data this) o not-found))

           IPrintWithWriter
           (-pr-writer [o writer opts]
             (-write writer "#schism/nested-map [")
             (-write writer (pr-str (.-data o)))
             (-write writer ", ")
             (-write writer (pr-str (.-vclock o)))
             (-write writer ", ")
             (-write writer (pr-str (.-birth-dots o)))
             (-write writer "]"))

           IHash
           (-hash [this] (-hash (.-data this)))

           IFn
           (-invoke [this o] ((.-data this) o))
           (-invoke [this o not-found] ((.-data this) o not-found))

           ISeqable
           (-seq [this] (-seq (.-data this)))

           Object
           (toString [this] (.toString (.-data this)))

           IMeta
           (-meta [this]
             (-meta (.-data this)))

           IWithMeta
           (-with-meta [this meta]
             (NestedMap. (-with-meta (.-data this)
                                     meta)
                         (.-vclock this)
                         (.-birth-dots this)))))

(defn nested-map-conj
  [^NestedMap nm pair]
  (vc/update-clock now nm
                   (let [[updated updated-dots] (nu/nested-update (.-data nm)
                                                                  (.-birth-dots nm)
                                                                  #(conj % pair)
                                                                  node/*current-node*
                                                                  now)]
                     (NestedMap. updated
                                 (.-vclock nm)
                                 updated-dots))))

(defn nested-map-empty
  [^NestedMap nm]
  (vc/update-clock _ nm
                   (NestedMap. (hash-map)
                               (hash-map)
                               (hash-map))))

(defn nested-map-assoc
  [^NestedMap nm k v]
  (vc/update-clock now nm
                   (let [[updated updated-dots] (nu/nested-update (.-data nm)
                                                                  (.-birth-dots nm)
                                                                  #(assoc % k v)
                                                                  node/*current-node*
                                                                  now)]
                     (NestedMap. updated
                                 (.-vclock nm)
                                 updated-dots))))

(defn nested-map-dissoc
  [^NestedMap nm k]
  (vc/update-clock now nm
                   (let [[updated updated-dots] (nu/nested-update (.-data nm)
                                                                  (.-birth-dots nm)
                                                                  #(dissoc % k)
                                                                  node/*current-node*
                                                                  now)]
                     (NestedMap. updated
                                 (.-vclock nm)
                                 updated-dots))))

(defn- elemental-data
  [^NestedMap nm]

  (let [flat-data (nu/flat (.-data nm))]
    {:vector-clock (.-vclock nm)
     :elements (into []
                     (for [datum flat-data]
                       (let [dot (get-in (.-birth-dots nm) (nu/access-path (key datum)))]
                         {:data {:entry datum
                                 :insert-index (:i dot)}
                          :author-node (:a dot)
                          :record-time (:t dot)})))}))

(extend-type NestedMap
  proto/Vclocked
  (get-clock [this] (.-vclock this))
  (with-clock [this new-clock] (NestedMap. (.-data this)
                                           new-clock
                                           (.-birth-dots this)))
  proto/Convergent
  (synchronize [this ^NestedMap other]
    (let [own-meta (-> this .-data meta)
          own-data (elemental-data this)
          other-data (elemental-data other)
          retain (filter (ic/common-elements own-data other-data)
                         (:elements own-data))
          completed-elements (->> (apply ic/retain-elements
                                         (ic/distinct-data own-data other-data))
                                  (concat retain)
                                  (sort-by :record-time)
                                  (map nu/finalize-projection-key))
          completed-flat-data (map (comp :entry :data) completed-elements)
          completed-flat-birth-dots (map (fn [{:keys [author-node record-time]
                                               {:keys [insert-index entry] :as data} :data}]
                                           (let [dot {:a author-node :t record-time}]
                                             [(first entry) (if insert-index
                                                              (assoc dot :i insert-index)
                                                              dot)]))
                                         completed-elements)
          completed-vclock (ic/merged-clock completed-elements own-data other-data)]
      (vc/update-clock _ this
                       (NestedMap. (with-meta (nu/project completed-flat-data)
                                     own-meta)
                                   completed-vclock
                                   (nu/project completed-flat-birth-dots))))))

#?(:clj (defmethod print-method NestedMap
          [^NestedMap nm ^Writer writer]
          (.write writer "#schism/nested-map [")
          (.write writer (pr-str (.-data nm)))
          (.write writer ", ")
          (.write writer (pr-str (.-vclock nm)))
          (.write writer ", ")
          (.write writer (pr-str (.-birth-dots nm)))
          (.write writer "]")))

(defn read-edn-map
  [read-object]
  (let [[data vclock birth-dots] read-object]
    (NestedMap. data vclock birth-dots)))

#?(:cljs (cljs.reader/register-tag-parser! 'schism/map read-edn-map))

(defn new-map
  ([] (NestedMap. (hash-map)
                  (hash-map)
                  (hash-map)))
  ([& args] (vc/update-clock now nil
                             (let [[updated updated-dots] (nu/nested-update {}
                                                                            {}
                                                                            (fn [_] (apply hash-map args))
                                                                            node/*current-node*
                                                                            now)]
                               (NestedMap. updated
                                           (hash-map)
                                           updated-dots)))))


================================================
FILE: src/schism/impl/types/nested_vector.cljc
================================================
(ns schism.impl.types.nested-vector
  "Definition and support for Schism's Deeply Convergent Vector
  type. The convergent vector is a timestamped log of entries with a
  vector clock & insertion index. Convergence places entries into the
  resultant vector in insertion order, with insertions occurring by
  replaying insertions operations in order. The vector clock conveys
  that an item has been removed from the vector on another node. This
  variant provides rich support for serialization and convergence of
  deeply nested structures, at the cost that all modification
  operations take linear time instead of constant or log time."
  (:require [schism.impl.types.nesting-util :as nu]
            [schism.impl.protocols :as proto]
            [schism.impl.vector-clock :as vc]
            [schism.impl.core :as ic]
            [schism.node :as node]
            [clojure.set :as set]
            [clojure.data :refer [diff]]
            #?(:cljs [cljs.reader :as reader]))
  #?(:cljs (:require-macros [schism.impl.vector-clock :as vc]))
  #?(:clj (:import (clojure.lang IPersistentCollection IPersistentStack IPersistentVector Reversible IReduce IKVReduce Indexed Associative Counted IHashEq Seqable IObj IMeta IFn ILookup)
                   (java.io Writer)
                   (java.util Date Collection)
                   (java.lang Object Long))))

(declare nested-vector-conj nested-vector-pop nested-vector-empty nested-vector-assoc)

#?(:clj (deftype NestedVector [data vclock insertions]
          Counted
          (count [this] (.count ^Counted (.-data this)))

          IPersistentCollection
          (cons [this o] (nested-vector-conj this o))
          (empty [this] (nested-vector-empty this))
          (equiv [this other] (.equiv ^IPersistentCollection (.-data this) other))

          Object
          (equals [this o]
            (.equals (.-data this) o))
          (hashCode [this]
            (.hashCode (.-data this)))
          (toString [this]
            (.toString (.-data this)))

          IHashEq
          (hasheq [this]
            (.hasheq ^IHashEq (.-data this)))

          Seqable
          (seq [this]
            (seq ^Seqable (.-data this)))

          java.util.List
          (add [this o] (.add ^java.util.List (.-data this) o))
          (add [this index o] (.add ^java.util.List (.-data this) index o))
          (addAll [this c] (.addAll ^java.util.List (.-data this) c))
          (clear [this] (.clear ^java.util.List (.-data this)))
          (contains [this o] (.contains ^java.util.List (.-data this) o))
          (containsAll [this c] (.containsAll ^java.util.List (.-data this) c))
          (get [this i] (.get ^java.util.List (.-data this) i))
          (indexOf [this o] (.indexOf ^java.util.List (.-data this) o))
          (isEmpty [this] (.isEmpty ^java.util.List (.-data this)))
          (iterator [this] (.iterator ^java.util.List (.-data this)))
          (lastIndexOf [this o] (.lastIndexOf ^java.util.List (.-data this) o))
          (listIterator [this] (.listIterator ^java.util.List (.-data this)))
          (^Object remove [this ^int i] (.remove ^java.util.List (.-data this) i))
          (^boolean remove [this ^Object o] (.remove ^java.util.List (.-data this) o))
          (removeAll [this c] (.removeAll ^java.util.List (.-data this) c))
          (replaceAll [this op] (.replaceAll ^java.util.List (.-data this) op))
          (retainAll [this c] (.retainAll ^java.util.List (.-data this) c))
          (set [this i e] (.set ^java.util.List (.-data this) i e))
          (size [this] (.size ^java.util.List (.-data this)))
          (sort [this c] (.sort ^java.util.List (.-data this) c))
          (spliterator [this] (.spliterator ^java.util.List (.-data this)))
          (subList [this i j] (.subList ^java.util.List (.-data this) i j))
          (toArray [this] (.toArray ^java.util.List (.-data this)))
          (^"[Ljava.lang.Object;" toArray [this ^"[Ljava.lang.Object;" a]
           (.toArray ^java.util.List (.-data this) a))


          IObj
          (withMeta [this meta]
            (NestedVector. (with-meta ^IObj (.-data this)
                                 meta)
                               (.-vclock this)
                               (.-insertions this)))

          IMeta
          (meta [this]
            (.meta ^IMeta (.-data this)))

          IReduce
          (reduce [this f]
            (.reduce ^IReduce (.-data this) f))

          IKVReduce
          (kvreduce [this f init]
            (.kvreduce ^IKVReduce (.-data this) f init))

          IPersistentStack
          (peek [this] (.peek ^IPersistentStack (.-data this)))
          (pop [this]
            (nested-vector-pop this))

          IPersistentVector
          (assocN [this i v]
            (nested-vector-assoc this i v))

          ILookup
          (valAt [this k]
            (.valAt (.-data this) k))
          (valAt [this k not-found]
            (.valAt (.-data this) k not-found))

          Associative
          (containsKey [this k]
            (.containsKey ^Associative (.-data this) k))
          (entryAt [this k]
            (.entryAt ^Associative (.-data this) k))
          (assoc [this k v]
            (nested-vector-assoc this k v))

          Indexed
          (nth [this i]
            (.indexed ^Indexed (.-data this) i))
          (nth [this i not-found]
            (.indexed ^Indexed (.-data this) i not-found))

          IFn
          (invoke [this k]
            (.invoke ^IFn (.-data this) k))
          (invoke [this k not-found]
            (.invoke ^IFn (.-data this) k not-found)))
   :cljs (deftype NestedVector [data vclock insertions]
           ICounted
           (-count [this] (-count (.-data this)))

           IEmptyableCollection
           (-empty [this] (nested-vector-empty this))

           ICollection
           (-conj [this o] (nested-vector-conj this o))

           IEquiv
           (-equiv [this other]
             (-equiv (.-data this) other))

           IPrintWithWriter
           (-pr-writer [o writer opts]
             (-write writer "#schism/nested-vector [")
             (-write writer (pr-str (.-data o)))
             (-write writer ", ")
             (-write writer (pr-str (.-vclock o)))
             (-write writer ", ")
             (-write writer (pr-str (.-insertions o)))
             (-write writer "]"))

           IHash
           (-hash [this] (-hash (.-data this)))

           ISeqable
           (-seq [this] (-seq (.-data this)))

           Object
           (toString [this] (.toString (.-data this)))

           IMeta
           (-meta [this]
             (-meta (.-data this)))

           IWithMeta
           (-with-meta [this meta]
             (NestedVector. (-with-meta (.-data this)
                                            meta)
                                (.-vclock this)
                                (.-insertions this)))

           IFn
           (-invoke [this k]
             ((.-data this) k))
           (-invoke [this k not-found]
             ((.-data this) k not-found))

           IIndexed
           (-nth [this n]
             (-nth (.-data this) n))
           (-nth [this n not-found]
             (-nth (.-data this) n not-found))

           ILookup
           (-lookup [this k]
             (-lookup (.-data this) k))
           (-lookup [this k not-found]
             (-lookup (.-data this) k not-found))

           IAssociative
           (-contains-key? [this k]
             (-contains-key? (.-data this) k))
           (-assoc [this k v]
             (nested-vector-assoc this k v))

           IFind
           (-find [this k]
             (-find (.-data this) k))

           IStack
           (-peek [this]
             (-peek (.-data this)))
           (-pop [this]
             (nested-vector-pop this))

           IVector
           (-assoc-n [this n v]
             (nested-vector-assoc this n v))

           IReduce
           (-reduce [this f]
             (-reduce (.-data this) f))
           (-reduce [this f start]
             (-reduce (.-data this) f start))

           IKVReduce
           (-kv-reduce [this f init]
             (-kv-reduce (.-data this) f init))))

(defn nested-vector-conj [^NestedVector nvector o]
  (vc/update-clock now nvector
                   (let [[updated updated-dots] (nu/nested-update (.-data nvector)
                                                                  (.-insertions nvector)
                                                                  #(conj % o)
                                                                  node/*current-node*
                                                                  now)]
                     (NestedVector. updated
                                    (.-vclock nvector)
                                    updated-dots))))

(defn nested-vector-empty [^NestedVector nvector]
  (vc/update-clock _ nvector
                   (NestedVector. (vector)
                                  (hash-map)
                                  (vector))))

(defn nested-vector-pop [^NestedVector nvector]
  (vc/update-clock _ nvector
                   (NestedVector. (pop (.-data nvector))
                                  (.-vclock nvector)
                                  (pop (.-insertions nvector)))))

(defn nested-vector-assoc [^NestedVector nvector k v]
  (vc/update-clock now nvector
                   (let [[updated updated-dots] (nu/nested-update (.-data nvector)
                                                                  (.-insertions nvector)
                                                                  #(assoc % k v)
                                                                  node/*current-node*
                                                                  now)]
                    (NestedVector. updated
                                   (.-vclock nvector)
                                   updated-dots))))

(defn- elemental-data
  [^NestedVector v]
  (let [flat-data (nu/flat (.-data v))]
    {:vector-clock (.-vclock v)
     :elements (into []
                     (for [datum flat-data]
                       (let [dot (get-in (.-insertions v) (nu/access-path (key datum)))]
                        {:data {:entry datum
                                :insert-index (:i dot)}
                         :author-node (:a dot)
                         :record-time (:t dot)})))}))

(extend-type NestedVector
  proto/Vclocked
  (get-clock [this] (.-vclock this))
  (with-clock [this new-clock] (NestedVector. (.-data this)
                                                  new-clock
                                                  (.-insertions this)))

  proto/Convergent
  (synchronize [this ^NestedVector other]
    (let [own-meta (-> this .-data meta)
          own-data (elemental-data this)
          other-data (elemental-data other)
          retain (filter (ic/common-elements own-data other-data)
                         (:elements own-data))
          completed-elements (->> (apply ic/retain-elements
                                         (ic/distinct-data own-data other-data))
                                  (concat retain)
                                  (sort-by :record-time)
                                  (map nu/finalize-projection-key))
          completed-flat-data (map (comp :entry :data) completed-elements)
          completed-flat-insertions (map (fn [{:keys [author-node record-time]
                                          {:keys [insert-index entry] :as data} :data}]
                                      (let [dot {:a author-node :t record-time}]
                                        [(first entry) (if insert-index
                                                         (assoc dot :i insert-index)
                                                         dot)]))
                                       completed-elements)
          completed-vclock (ic/merged-clock completed-elements own-data other-data)]
      (vc/update-clock _ this
                       (NestedVector. (with-meta (nu/project completed-flat-data) own-meta)
                                      completed-vclock
                                      (nu/project completed-flat-insertions))))))

#?(:clj (defmethod print-method NestedVector
          [^NestedVector v ^Writer writer]
          (.write writer "#schism/nested-vector [")
          (.write writer (pr-str (.-data v)))
          (.write writer ", ")
          (.write writer (pr-str (.-vclock v)))
          (.write writer ", ")
          (.write writer (pr-str (.-insertions v)))
          (.write writer "]")))

(defn read-edn-vector
  [read-object]
  (let [[data vclock insertions] read-object]
    (NestedVector. data vclock insertions)))

#?(:cljs (cljs.reader/register-tag-parser! 'schism/nested-vector read-edn-vector))

(defn new-vector
  ([] (NestedVector. (vector)
                     (hash-map)
                     (vector)))
  ([& args] (vc/update-clock now nil
                             (let [[updated updated-dots] (nu/nested-update []
                                                                  []
                                                                  #(into % args)
                                                                  node/*current-node*
                                                                  now)]
                               (NestedVector. updated
                                              (hash-map)
                                              updated-dots)))))


================================================
FILE: src/schism/impl/types/nesting_util.cljc
================================================
(ns schism.impl.types.nesting-util
  "Utility functions for supporting nested collections."
  (:require [clojure.data :refer [diff]]
            [schism.impl.core :as ic]))


(defn compare-paths
  [[[a-type a-val :as first-a] & rest-a :as a] [[b-type b-val :as first-b] & rest-b :as b]]
  (cond
    (and (nil? a) (nil? b)) 0
    (nil? a) -1
    (nil? b) 1
    (= first-a first-b) (recur rest-a rest-b)
    (and (= a-type 's) (= b-type 'a)) -1
    (and (= b-type 's) (= a-type 'a)) 1
    (and (= a-type 's) (= b-type 's)) (compare a-val b-val)
    (or rest-a rest-b) (recur rest-a rest-b)
    :else (compare (hash a-val) (hash b-val))))

(defn flat
  "Flattens a structure of associatives and sequentials to an
  associative of paths to leaf values. Each step in a path will retain
  both the type and the edge value, maps will be flattened to
  associatives. Vectors, lists, and other seqs will be flattened to
  sequentials."
  [c]
  (if (or (not (coll? c))
          (empty? c))
    c
    (let [marker (if (map? c) 'a 's)
          m (if (map? c)
              c
              (map-indexed (fn [i e] [i e]) c))]
      (into {}
            (mapcat (fn [[k v]]
                      (let [child-flat (flat v)]
                        (if (or (not (coll? child-flat))
                                (empty? child-flat))
                          [[[[marker k]] child-flat]]
                          (for [[path element] child-flat]
                            [(apply vector [marker k] path) element])))))
            m))))

(defn access-path
  "`flat` returns a reconstitution path of both type and key. This is
  useful for reprojecting the flat version up into a nested structure,
  but is not amenable to `get-in`, `assoc-in`, et al. `access-path` will
  convert a reconstitution path into a more conventional access path."
  [reconstitution-path]
  (map second reconstitution-path))

(defn- assoc*
  [m [type key] v]
  (let [m (if m
            m
            (cond
              (= type 'a) {}
              (= type 's) []))
        assoc-fn (if (vector? m)
                   ic/assoc-n-with-tail-support
                   assoc)]
    (assoc-fn m key v)))

(defn- assoc-in*
  [m [[type key :as kspec] & kspecs] v]
  (if kspecs
    (assoc* m kspec (assoc-in* (get m key) kspecs v))
    (assoc* m kspec v)))

(defn project
  "Constitutes a structure as produced by `flat` up into a nested
  collection of maps and vectors. As all sequential items are coerced
  to vectors, this is not reflexive of `flat`."
  ([vals] (project vals nil))
  ([vals basis]
   (reduce (fn [m [k v]]
             (assoc-in* m k v))
           basis
           vals)))

(defn clean*
  "Remove the key at k and any empty parents above it."
  [m [k & ks]]
  (if ks
    (let [cleaned (clean* (get m k) ks)]
      (if (empty? cleaned)
        (cond (vector? m) (pop m)
              (map? m) (dissoc m k))
        (assoc m k cleaned)))
    (cond (vector? m) (pop m)
          (map? m) (dissoc m k))))

(defn nested-update
  "Does all of the book-keeping for nested map/vector combo data types.
  `original` is the original data structure, `provenance` is the
  original structure's provenance data, `update` is a update function
  to progress original.
  Returns positionally: the updated `original`, and, the updated `provenance`."
  [original provenance update author timestamp]
  (let [updated (update original)
        original-vals-flat (flat original)
        update-vals-flat (flat updated)
        [deletions additions common] (diff original-vals-flat update-vals-flat)
        provenance (reduce (fn [m [k v]]
                             (if (contains? additions k)
                               m
                               (clean* m (access-path k))))
                           provenance
                           deletions)
        addition-dots (for [[k v] (sort-by first compare-paths additions)]
                        (let [to-vector? (= 's (first (last k)))
                              distinct? (not (contains? deletions k))
                              basis {:a author
                                     :t timestamp}]
                          [k (if to-vector?
                               (merge basis {:i (if distinct? -1 (last (last k)))})
                               basis)]))]
    [updated (project addition-dots provenance)]))

(defn finalize-projection-key
  [m]
  (let [{:keys [entry insert-index]} (:data m)]
    (if insert-index
      (assoc-in m [:data :entry]
                [(conj (pop (key entry)) ['s insert-index]) (val entry)])
      m)))


================================================
FILE: src/schism/impl/types/set.cljc
================================================
(ns schism.impl.types.set
  "Definition and support for Schism's Convergent Set type, an ORSWOT
  implemented on top of Clojure's Persistent Set, Persistent Map and
  Schism's Vector Clock."
  (:require [schism.impl.protocols :as proto]
            [schism.impl.vector-clock :as vc]
            [schism.impl.core :as ic]
            [schism.node :as node]
            [clojure.set :as set]
            #?(:cljs [cljs.reader :as reader]))
  #?(:cljs (:require-macros [schism.impl.vector-clock :as vc]))
  #?(:clj (:import (clojure.lang IPersistentCollection IPersistentSet IHashEq Counted Seqable RT IFn IObj IMeta)
                   (java.io Writer)
                   (java.util Date Collection)
                   (java.lang Object))))

;; A CLJS and CLJ implementation of ORSWOT (Observed-Removed Set without Tombstones)

;; Each set maintains its own vector clock, and birth dots for each
;; member. A birth dot consists of the node adding the member, and the
;; date at which it was added. The vector clock determines if an
;; element has been removed: a compared set absent an element but with
;; a newer vector clock than the own birthdot on element indicates
;; that it was removed; a compared set absent an element with an older
;; vector clock indicates that the birthdot was never seen and should
;; be in the merged set.

;; Dot membership is always exactly the cardinality of the Set. Clock
;; membership is at most the cardinality of the set, but can correctly
;; synchronize with one more member than the total number of nodes in
;; the birth-dots; if a Vclock indicates an element was removed, the
;; node converging its changes can claim responsibility for removing
;; the element in the merged set.

;; TODO:

;; With vector clocks AND birthdots it is possible to disambiguate the
;; removal of an element from the post-replication addition of that
;; element. When merging other, examine it's vector clock for each
;; entry that is uniquely in own. If that entry's authoring node is
;; present in the vector clock, and the timestamp in other's vector
;; clock is less than the birth dot of own's entry, then retain the
;; entry, as other never saw it and could not have removed it.

(declare orswot-conj orswot-empty orswot-disj)

#?(:clj (deftype Set [data vclock birth-dots]
          Counted
          (count [this] (.count ^Counted (.-data this)))

          IPersistentCollection
          (cons [this o] (orswot-conj this o))
          (empty [this] (orswot-empty this))
          (equiv [this other]
            (.equiv ^IPersistentCollection (.-data this) other))

          IPersistentSet
          (disjoin [this o] (orswot-disj this o))
          (contains [this o] (.contains ^IPersistentSet (.-data this) o))
          (get [this o] (.get ^IPersistentSet (.-data this) o))

          Object
          (equals [this o]
            (.equals (.-data this) o))
          (hashCode [this]
            (.hashCode (.-data this)))
          (toString [this]
            (.toString data))

          IHashEq
          (hasheq [this]
            (.hasheq ^IHashEq (.-data this)))

          Seqable
          (seq [this] (.seq ^Seqable (.-data this)))

          java.util.Set
          (toArray [this] (.toArray (.-data this)))
          (^"[Ljava.lang.Object;" toArray [this ^"[Ljava.lang.Object;" a]
           (.toArray ^java.util.Set (.-data this) a))
          (add [this o] (throw (UnsupportedOperationException.)))
          (remove [this o] (throw (UnsupportedOperationException.)))
          (addAll [this c] (throw (UnsupportedOperationException.)))
          (clear [this] (throw (UnsupportedOperationException.)))
          (retainAll [this c] (throw (UnsupportedOperationException.)))
          (removeAll [this c] (throw (UnsupportedOperationException.)))
          (containsAll [this c] (.containsAll ^java.util.Set (.-data this) c))
          (size [this] (.size ^java.util.Set (.-data this)))
          (isEmpty [this] (.isEmpty ^java.util.Set (.-data this)))
          (iterator [this] (.iterator ^java.util.Set (.-data this)))

          IFn
          (invoke [this arg1]
            (.invoke ^IFn (.-data this) arg1))

          IObj
          (withMeta [this meta]
            (Set. (.withMeta ^IObj (.-data this) meta) (.-vclock this) (.-birth-dots this)))

          IMeta
          (meta [this]
            (.meta ^IMeta (.-data this))))
   :cljs (deftype Set [data vclock birth-dots]
           ICounted
           (-count [this] (-count (.-data this)))

           IEmptyableCollection
           (-empty [this] (orswot-empty this))

           ICollection
           (-conj [this o] (orswot-conj this o))

           ISet
           (-disjoin [this o] (orswot-disj this o))

           IEquiv
           (-equiv [this other]
             (-equiv (.-data this) other))

           ILookup
           (-lookup [this o]
             (-lookup (.-data this) o))
           (-lookup [this o not-found]
             (-lookup (.-data this) o not-found))

           IPrintWithWriter
           (-pr-writer [o writer opts]
             (-write writer "#schism/set [")
             (-write writer (pr-str (.-data o)))
             (-write writer ", ")
             (-write writer (pr-str (.-vclock o)))
             (-write writer ", ")
             (-write writer (pr-str (.-birth-dots o)))
             (-write writer "]"))

           IHash
           (-hash [this] (-hash (.-data this)))

           IFn
           (-invoke [this o] (-invoke (.-data this) o))

           ISeqable
           (-seq [this] (-seq (.-data this)))

           Object
           (toString [this] (.toString (.-data this)))

           IMeta
           (-meta [this]
             (-meta (.-data this)))

           IWithMeta
           (-with-meta [this meta]
             (Set. (-with-meta (.-data this)
                               meta)
                   (.-vclock this) (.-birth-dots this)))))

(defn orswot-conj
  [^Set orswot o]
  (vc/update-clock now orswot
                   (Set. (conj (.-data orswot) o)
                         (.-vclock orswot)
                         (assoc (.-birth-dots orswot) o [node/*current-node* now]))))

(defn orswot-empty
  [^Set orswot]
  (vc/update-clock _ orswot
                   (Set. (hash-set)
                         (hash-map)
                         (hash-map))))

(defn orswot-disj
  [^Set orswot o]
  (vc/update-clock _ orswot
                   (Set. (disj (.-data orswot) o)
                         (.-vclock orswot)
                         (dissoc (.-birth-dots orswot) o))))

(defn- elemental-data
  [^Set s]
  {:vector-clock (.-vclock s)
   :elements (into []
                   (for [datum (.-data s)]
                     (let [dot (get (.-birth-dots s) datum)]
                       {:data datum
                        :author-node (first dot)
                        :record-time (last dot)})))})

(extend-type Set
  proto/Vclocked
  (get-clock [this] (.-vclock this))
  (with-clock [this new-clock] (Set. (.-data this)
                                     new-clock
                                     (.-birth-dots this)))

  proto/Convergent
  (synchronize [this ^Set other]
    (let [own-meta (-> this .-data meta)
          own-data (elemental-data this)
          other-data (elemental-data other)
          retain (filter (ic/common-elements own-data other-data)
                         (:elements own-data))
          completed-elements (->> (apply ic/retain-elements
                                         (ic/distinct-data own-data other-data))
                                  (concat retain)
                                  (sort-by :record-time))
          completed-data (into #{} (map :data completed-elements))
          completed-birth-dots (->>  completed-elements
                                     (map (fn [{:keys [data author-node record-time]}]
                                            [data [author-node record-time]]))
                                     (into {}))
          completed-vclock (ic/merged-clock completed-elements own-data other-data)]
      (vc/update-clock _ this
                       (Set. (with-meta completed-data
                               own-meta)
                             completed-vclock
                             completed-birth-dots)))))

#?(:clj (defmethod print-method Set
          [^Set s ^Writer writer]
          (.write writer "#schism/set [")
          (.write writer (pr-str (.-data s)))
          (.write writer ", ")
          (.write writer (pr-str (.-vclock s)))
          (.write writer ", ")
          (.write writer (pr-str (.-birth-dots s)))
          (.write writer "]")))

(defn read-edn-set
  [read-object]
  (let [[data vclock birth-dots] read-object]
    (Set. data vclock birth-dots)))

#?(:cljs (cljs.reader/register-tag-parser! 'schism/set read-edn-set))

(defn new-set
  ([] (Set. (hash-set)
            (hash-map)
            (hash-map)))
  ([& args] (vc/update-clock now nil
                             (Set. (apply hash-set args)
                                   (hash-map)
                                   (apply hash-map
                                          (mapcat (fn [o]
                                                    [o [node/*current-node* now]])
                                                  args))))))


================================================
FILE: src/schism/impl/types/vector.cljc
================================================
(ns schism.impl.types.vector
  "Definition and support for Schism's Convergent vector type. The
  convergent vector is a timestamped log of entries with a vector
  clock & insertion index. Convergence places entries into the
  resultant vector in insertion order, with insertions occurring by
  replaying insertions operations in order. The vector clock conveys
  that an item has been removed from the vector on another node."
  (:require [schism.impl.protocols :as proto]
            [schism.impl.vector-clock :as vc]
            [schism.impl.core :as ic]
            [schism.node :as node]
            [clojure.set :as set]
            #?(:cljs [cljs.reader :as reader]))
  #?(:cljs (:require-macros [schism.impl.vector-clock :as vc]))
  #?(:clj (:import (clojure.lang IPersistentCollection IPersistentStack IPersistentVector Reversible IReduce IKVReduce Indexed Associative Counted IHashEq Seqable IObj IMeta IFn ILookup)
                   (java.io Writer)
                   (java.util Date Collection)
                   (java.lang Object Long))))

;; A CLJ & CLJS implementation of a convergent vector

;; Each vector maintains its own vector clock, and insertion times and
;; positions for each element of the vector. Vector entries and
;; insertions are correlated positionally (as the vector may contain
;; the same item multiple times.) Insertion times and indices dictate
;; ordering; elements inserted at the tail of the vector are recorded
;; as being inserted with index -1. The vector clock determines if an
;; entry has been removed.

(declare cvector-conj cvector-pop cvector-empty cvector-assoc)

#?(:clj (deftype ConvergentVector [data vclock insertions]
          Counted
          (count [this] (.count ^Counted (.-data this)))

          IPersistentCollection
          (cons [this o] (cvector-conj this o))
          (empty [this] (cvector-empty this))
          (equiv [this other] (.equiv ^IPersistentCollection (.-data this) other))

          Object
          (equals [this o]
            (.equals (.-data this) o))
          (hashCode [this]
            (.hashCode (.-data this)))
          (toString [this]
            (.toString (.-data this)))

          IHashEq
          (hasheq [this]
            (.hasheq ^IHashEq (.-data this)))

          Seqable
          (seq [this]
            (seq ^Seqable (.-data this)))

          java.util.List
          (add [this o] (.add ^java.util.List (.-data this) o))
          (add [this index o] (.add ^java.util.List (.-data this) index o))
          (addAll [this c] (.addAll ^java.util.List (.-data this) c))
          (clear [this] (.clear ^java.util.List (.-data this)))
          (contains [this o] (.contains ^java.util.List (.-data this) o))
          (containsAll [this c] (.containsAll ^java.util.List (.-data this) c))
          (get [this i] (.get ^java.util.List (.-data this) i))
          (indexOf [this o] (.indexOf ^java.util.List (.-data this) o))
          (isEmpty [this] (.isEmpty ^java.util.List (.-data this)))
          (iterator [this] (.iterator ^java.util.List (.-data this)))
          (lastIndexOf [this o] (.lastIndexOf ^java.util.List (.-data this) o))
          (listIterator [this] (.listIterator ^java.util.List (.-data this)))
          (^Object remove [this ^int i] (.remove ^java.util.List (.-data this) i))
          (^boolean remove [this ^Object o] (.remove ^java.util.List (.-data this) o))
          (removeAll [this c] (.removeAll ^java.util.List (.-data this) c))
          (replaceAll [this op] (.replaceAll ^java.util.List (.-data this) op))
          (retainAll [this c] (.retainAll ^java.util.List (.-data this) c))
          (set [this i e] (.set ^java.util.List (.-data this) i e))
          (size [this] (.size ^java.util.List (.-data this)))
          (sort [this c] (.sort ^java.util.List (.-data this) c))
          (spliterator [this] (.spliterator ^java.util.List (.-data this)))
          (subList [this i j] (.subList ^java.util.List (.-data this) i j))
          (toArray [this] (.toArray ^java.util.List (.-data this)))
          (^"[Ljava.lang.Object;" toArray [this ^"[Ljava.lang.Object;" a]
           (.toArray ^java.util.List (.-data this) a))


          IObj
          (withMeta [this meta]
            (ConvergentVector. (with-meta ^IObj (.-data this)
                                 meta)
                               (.-vclock this)
                               (.-insertions this)))

          IMeta
          (meta [this]
            (.meta ^IMeta (.-data this)))

          IReduce
          (reduce [this f]
            (.reduce ^IReduce (.-data this) f))

          IKVReduce
          (kvreduce [this f init]
            (.kvreduce ^IKVReduce (.-data this) f init))

          IPersistentStack
          (peek [this] (.peek ^IPersistentStack (.-data this)))
          (pop [this]
            (cvector-pop this))

          IPersistentVector
          (assocN [this i v]
            (cvector-assoc this i v))

          ILookup
          (valAt [this k]
            (.valAt (.-data this) k))
          (valAt [this k not-found]
            (.valAt (.-data this) k not-found))

          Associative
          (containsKey [this k]
            (.containsKey ^Associative (.-data this) k))
          (entryAt [this k]
            (.entryAt ^Associative (.-data this) k))
          (assoc [this k v]
            (cvector-assoc this k v))

          Indexed
          (nth [this i]
            (.indexed ^Indexed (.-data this) i))
          (nth [this i not-found]
            (.indexed ^Indexed (.-data this) i not-found))

          IFn
          (invoke [this k]
            (.invoke ^IFn (.-data this) k))
          (invoke [this k not-found]
            (.invoke ^IFn (.-data this) k not-found)))
   :cljs (deftype ConvergentVector [data vclock insertions]
           ICounted
           (-count [this] (-count (.-data this)))

           IEmptyableCollection
           (-empty [this] (cvector-empty this))

           ICollection
           (-conj [this o] (cvector-conj this o))

           IEquiv
           (-equiv [this other]
             (-equiv (.-data this) other))

           IPrintWithWriter
           (-pr-writer [o writer opts]
             (-write writer "#schism/vector [")
             (-write writer (pr-str (.-data o)))
             (-write writer ", ")
             (-write writer (pr-str (.-vclock o)))
             (-write writer ", ")
             (-write writer (pr-str (.-insertions o)))
             (-write writer "]"))

           IHash
           (-hash [this] (-hash (.-data this)))

           ISeqable
           (-seq [this] (-seq (.-data this)))

           Object
           (toString [this] (.toString (.-data this)))

           IMeta
           (-meta [this]
             (-meta (.-data this)))

           IWithMeta
           (-with-meta [this meta]
             (ConvergentVector. (-with-meta (.-data this)
                                            meta)
                                (.-vclock this)
                                (.-insertions this)))

           IFn
           (-invoke [this k]
             (-invoke (.-data this) k))
           (-invoke [this k not-found]
             (-invoke (.-data this) k not-found))

           IIndexed
           (-nth [this n]
             (-nth (.-data this) n))
           (-nth [this n not-found]
             (-nth (.-data this) n not-found))

           ILookup
           (-lookup [this k]
             (-lookup (.-data this) k))
           (-lookup [this k not-found]
             (-lookup (.-data this) k not-found))

           IAssociative
           (-contains-key? [this k]
             (-contains-key? (.-data this) k))
           (-assoc [this k v]
             (cvector-assoc this k v))

           IFind
           (-find [this k]
             (-find (.-data this) k))

           IStack
           (-peek [this]
             (-peek (.-data this)))
           (-pop [this]
             (cvector-pop this))

           IVector
           (-assoc-n [this n v]
             (cvector-assoc this n v))

           IReduce
           (-reduce [this f]
             (-reduce (.-data this) f))
           (-reduce [this f start]
             (-reduce (.-data this) f start))

           IKVReduce
           (-kv-reduce [this f init]
             (-kv-reduce (.-data this) f init))))

(defn cvector-conj [^ConvergentVector cvector o]
  (vc/update-clock now cvector
                   (ConvergentVector. (conj (.-data cvector) o)
                                      (.-vclock cvector)
                                      (conj (.-insertions cvector) [node/*current-node* -1 now]))))

(defn cvector-empty [^ConvergentVector cvector]
  (vc/update-clock _ cvector
                   (ConvergentVector. (vector)
                                      (hash-map)
                                      (vector))))

(defn cvector-pop [^ConvergentVector cvector]
  (vc/update-clock _ cvector
                   (ConvergentVector. (pop (.-data cvector))
                                      (.-vclock cvector)
                                      (pop (.-insertions cvector)))))

(defn cvector-assoc [^ConvergentVector cvector k v]
  (vc/update-clock now cvector
                   (ConvergentVector. (assoc (.-data cvector) k v)
                                      (.-vclock cvector)
                                      (assoc (.-insertions cvector) k [node/*current-node* k now]))))

(defn- elemental-data
  [^ConvergentVector v]
  {:vector-clock (.-vclock v)
   :elements (into []
                   (for [[element [author-node insert-index record-time]]
                         (map vector (.-data v) (.-insertions v))]
                     {:data {:element element
                             :insert-index insert-index}
                      :author-node author-node
                      :record-time record-time}))})

(extend-type ConvergentVector
  proto/Vclocked
  (get-clock [this] (.-vclock this))
  (with-clock [this new-clock] (ConvergentVector. (.-data this)
                                                  new-clock
                                                  (.-insertions this)))

  proto/Convergent
  (synchronize [this ^ConvergentVector other]
    (let [own-meta (-> this .-data meta)
          own-data (elemental-data this)
          other-data (elemental-data other)
          retain (filter (ic/common-elements own-data other-data)
                         (:elements own-data))
          completed-elements (->> retain
                                  (concat (apply ic/retain-elements
                                                 (ic/distinct-data own-data other-data)))
                                  (sort-by (fn [{:keys [author-node data record-time]}]
                                             (let [{:keys [insert-index]} data]
                                               [(if (= -1 insert-index)
                                                  ic/tail-insertion-sort-value
                                                  insert-index) record-time]))))
          ;; Given the potential for insertion at arbitrary indexes,
          ;; trying to find a common contiguous chunk is less fruitful
          ;; than taking our [element, node, index, timestamp] tuples
          ;; and treating them as discrete insertion
          ;; instructions. Removal is still indicated by vector clock,
          ;; AND it is reasonable to expect much of the vector to be
          ;; shared, so it's important to get to the right set of such
          ;; instructions. As time and index dictate the overall state
          ;; of convergence, it is not important to preserve ordering
          ;; through convergence, which affords using set logic to
          ;; find the common insertions.
          completed-data (reduce (fn [m element]
                                   (let [{:keys [element insert-index]} (:data element)]
                                     (ic/assoc-n-with-tail-support m insert-index element)))
                                 []
                                 completed-elements)
          completed-insertions (reduce (fn [m {:keys [author-node record-time]
                                               {:keys [insert-index]} :data}]
                                         (ic/assoc-n-with-tail-support m insert-index
                                                                       [author-node insert-index record-time]))
                                       []
                                       completed-elements)
          completed-vclock (ic/merged-clock completed-elements own-data other-data)]
      (vc/update-clock _ this
                       (ConvergentVector. (with-meta completed-data own-meta)
                                          completed-vclock
                                          completed-insertions)))))

#?(:clj (defmethod print-method ConvergentVector
          [^ConvergentVector v ^Writer writer]
          (.write writer "#schism/vector [")
          (.write writer (pr-str (.-data v)))
          (.write writer ", ")
          (.write writer (pr-str (.-vclock v)))
          (.write writer ", ")
          (.write writer (pr-str (.-insertions v)))
          (.write writer "]")))

(defn read-edn-vector
  [read-object]
  (let [[data vclock insertions] read-object]
    (ConvergentVector. data vclock insertions)))

#?(:cljs (cljs.reader/register-tag-parser! 'schism/vector read-edn-vector))

(defn new-vector
  ([] (ConvergentVector. (vector)
                         (hash-map)
                         (vector)))
  ([& args] (vc/update-clock now nil
                             (ConvergentVector. (apply vector args)
                                                (hash-map)
                                                (apply vector (for [i (range (count args))]
                                                                [node/*current-node* i now]))))))


================================================
FILE: src/schism/impl/vector_clock.cljc
================================================
(ns schism.impl.vector-clock
  "Utility functions for working with the vector clock of a value that
  participates in the Vclocked protocol."
  (:require [schism.impl.protocols :as sp]
            [schism.impl.core :as ic]
            [schism.node :as node])
  #?(:clj (:import (java.util Date))))

(defmacro update-clock
  "Binds the current time to `binding`, executes body, then updates
  the body's return value which participates in
  schism.impl.protocols/Vclocked, so that the vector clock contains the
  same time bound to `binding` for the current node."
  [binding clocked & body]
  `(let [now# (ic/to-date
               (max (ic/to-millis (ic/now))
                    (inc (apply max 0 (map ic/to-millis (vals (sp/get-clock ~clocked)))))))
         ~binding now#
         ret# (do ~@body)
         ret-clock# (sp/get-clock ret#)]
     (sp/with-clock ret# (assoc ret-clock# node/*current-node* now#))))


================================================
FILE: src/schism/node.cljc
================================================
(ns schism.node
  #?(:clj (:import (java.util UUID))))

(def ^:dynamic *current-node* nil)

(defn initialize-node!
  "Initialize `schism.node/*current-node*` to the passed in value,
  or a new random UUID. While any serializable value suffices as the
  current node, it should be unique within the cluster; a repeated
  node value may result in incorrect behaviors during convergence."
  ([] (initialize-node!
       #?(:clj (UUID/randomUUID)
          :cljs (random-uuid))))
  ([id] #?(:clj (alter-var-root #'*current-node* (constantly id))
           :cljs (set! *current-node* id))))

(defmacro with-node
  "Override the value of `schism.node/*current-node*` for the scope of
  `body`."
  [id & body]
  `(binding [*current-node* ~id]
     ~@body))


================================================
FILE: test/schism/core_test.cljc
================================================
(ns schism.core-test
  (:require [schism.core :as schism :include-macros true]
            #?(:clj [clojure.test :refer [deftest testing is]]
               :cljs [cljs.test :refer [deftest testing is]])
            [clojure.test.check.generators :as gen]
            [clojure.test.check.properties :as prop :include-macros true]
            [clojure.test.check.clojure-test #?(:clj :refer
                                                :cljs :refer-macros) [defspec]]))

#?(:cljs
   (deftest collections-with-NaN
     (is (not= (hash-map js/NaN [])
               (hash-map js/NaN []))
         "Because NaN cannot be compared for equality, two maps with NaN cannot be equal.")
     (is (not= (list js/NaN)
               (list js/NaN))
         "Because NaN cannot be compared for equality, two lists with NaN cannot be equal.")
     (is (not= (hash-set js/NaN)
               (hash-set js/NaN))
         "Because NaN cannot be compared for equality, two hash-sets with NaN cannot be equal.")
     (is (not= (vector js/NaN)
               (vector js/NaN))
         "Because NaN cannot be compared for equality, two vectors with NaN cannot be equal.")))

(def collection-any
  "CLJS any will sometimes return NaN, but property tests using
  equality of collections cannot pass with NaN as an element (see
  above), so explicitly filter out NaN"
  #?(:cljs (gen/such-that #(not (js/Number.isNaN %)) gen/any)
     :clj gen/any))



(defspec convergent-set-is-equivalent-to-hash-set
  50
  (prop/for-all [v (gen/vector collection-any)]
                (= (apply schism/convergent-set v)
                   (apply hash-set v))))

(defspec convergent-map-is-equivalent-to-hash-map
  50
  (prop/for-all [entries (gen/fmap (comp (partial mapcat identity) seq) (gen/map collection-any collection-any))]
                (= (apply schism/convergent-map entries)
                   (apply hash-map entries))))

(defspec nested-map-is-equivalent-to-hash-map
  50
  (prop/for-all [entries (gen/fmap (comp (partial mapcat identity) seq) (gen/map collection-any collection-any))]
                (= (apply schism/nested-map entries)
                   (apply hash-map entries))))

(defspec convergent-vector-is-equivalent-to-vector
  50
  (prop/for-all [v (gen/vector collection-any)]
                (= (apply schism/convergent-vector v)
                   (apply vector v))))

(defspec nested-vector-is-equivalent-to-vector
  50
  (prop/for-all [v (gen/vector collection-any)]
                (= (apply schism/nested-vector v)
                   (apply vector v))))

(defspec convergent-list-is-equivalent-to-list
  50
  (prop/for-all [v (gen/list collection-any)]
                (= (apply schism/convergent-list v)
                   (apply list v))))

(defspec conj-equivalence-for-sets
  50
  (prop/for-all [v (gen/vector collection-any)
                 e collection-any]
                (= (conj (apply schism/convergent-set v) e)
                   (conj (apply hash-set v) e))))

(defspec conj-equivalence-for-maps
  30
  (prop/for-all [entries (gen/fmap (comp (partial mapcat identity) seq) (gen/map collection-any collection-any))
                 e (gen/tuple collection-any collection-any)]
                (= (conj (apply schism/convergent-map entries) e)
                   (conj (apply hash-map entries) e))))

(defspec conj-equivalence-for-nested-maps
  30
  (prop/for-all [entries (gen/fmap (comp (partial mapcat identity) seq) (gen/map collection-any collection-any))
                 e (gen/tuple collection-any collection-any)]
                (= (conj (apply schism/nested-map entries) e)
                   (conj (apply hash-map entries) e))))


(defspec conj-equivalence-for-vectors
  30
  (prop/for-all [v (gen/vector collection-any)
                 e collection-any]
                (= (conj (apply schism/convergent-vector v) e)
                   (conj (apply vector v) e))))

(defspec conj-equivalence-for-nested-vectors
  30
  (prop/for-all [v (gen/vector collection-any)
                 e collection-any]
                (= (conj (apply schism/nested-vector v) e)
                   (conj (apply vector v) e))))

(defspec conj-equivalence-for-lists
  30
  (prop/for-all [v (gen/vector collection-any)
                 e collection-any]
                (= (conj (apply schism/convergent-list v) e)
                   (conj (apply list v) e))))

(defspec rest-equivalence-for-lists
  30
  (prop/for-all [v (gen/vector collection-any)]
                (= (rest (apply schism/convergent-list v))
                   (rest (apply list v)))))

(defspec assoc-equivalence-for-maps
  30
  (prop/for-all [entries (gen/fmap (comp (partial mapcat identity) seq) (gen/map collection-any collection-any))
                 k collection-any
                 v collection-any]
                (= (assoc (apply schism/convergent-map entries) k v)
                   (assoc (apply hash-map entries) k v))))

(defspec assoc-equivalence-for-nested-maps
  30
  (prop/for-all [entries (gen/fmap (comp (partial mapcat identity) seq) (gen/map collection-any collection-any))
                 k collection-any
                 v collection-any]
                (= (assoc (apply schism/nested-map entries) k v)
                   (assoc (apply hash-map entries) k v))))

(defspec assoc-equivalence-for-vectors
  30
  (prop/for-all [v (gen/such-that #(< 0 (count %))
                                  (gen/vector collection-any))
                 e collection-any]
                (gen/let [index (gen/choose 0 (dec (count v)))]
                 (= (assoc (apply schism/convergent-vector v) index e)
                    (assoc (apply vector v) index e)))))

(defspec assoc-equivalence-for-nested-vectors
  30
  (prop/for-all [v (gen/such-that #(< 0 (count %))
                                  (gen/vector collection-any))
                 e collection-any]
                (gen/let [index (gen/choose 0 (dec (count v)))]
                 (= (assoc (apply schism/nested-vector v) index e)
                    (assoc (apply vector v) index e)))))

(defspec pop-equivalence-for-vectors
  30
  (prop/for-all [v (gen/such-that #(< 0 (count %))
                                  (gen/vector collection-any))]
                (= (pop (apply schism/convergent-vector v))
                   (pop (apply vector v)))))

(defspec pop-equivalence-for-nested-vectors
  30
  (prop/for-all [v (gen/such-that #(< 0 (count %))
                                  (gen/vector collection-any))]
                (= (pop (apply schism/nested-vector v))
                   (pop (apply vector v)))))

(defspec dissoc-present-key-for-maps
  30
  (prop/for-all [entries (gen/fmap (comp (partial mapcat identity) seq)
                                   (gen/map collection-any collection-any {:min-elements 1}))]
                (gen/let [key (gen/elements (keys (apply hash-map entries)))]
                  (= (dissoc (apply schism/convergent-map entries) key)
                     (dissoc (apply hash-map entries) key)))))

(defspec dissoc-present-key-for-nested-maps
  30
  (prop/for-all [entries (gen/fmap (comp (partial mapcat identity) seq)
                                   (gen/map collection-any collection-any {:min-elements 1}))]
                (gen/let [key (gen/elements (keys (apply hash-map entries)))]
                  (= (dissoc (apply schism/nested-map entries) key)
                     (dissoc (apply hash-map entries) key)))))

(defspec dissoc-random-value-for-maps
  30
  (prop/for-all [entries (gen/fmap (comp (partial mapcat identity) seq)
                                   (gen/map collection-any collection-any {:min-elements 1}))
                 key gen/any]
                (= (dissoc (apply schism/convergent-map entries) key)
                   (dissoc (apply hash-map entries) key))))

(defspec dissoc-random-value-for-nested-maps
  30
  (prop/for-all [entries (gen/fmap (comp (partial mapcat identity) seq)
                                   (gen/map collection-any collection-any {:min-elements 1}))
                 key gen/any]
                (= (dissoc (apply schism/nested-map entries) key)
                   (dissoc (apply hash-map entries) key))))


(defspec disj-included-element-for-sets
  5
  (prop/for-all [s (gen/set collection-any {:min-elements 1})]
                (gen/let [e (gen/elements (apply hash-set s))]
                  (= (disj (apply schism/convergent-set s) e)
                     (disj (apply hash-set s) e)))))

(defspec disj-random-element-for-sets
  5
  (prop/for-all [s (gen/set collection-any {:min-elements 1})
                 e collection-any]
                (= (disj (apply schism/convergent-set s) e)
                   (disj (apply hash-set s) e))))

(defspec converge-after-ops-equivalent-for-sets
  50
  (prop/for-all [s (gen/set collection-any {:min-elements 3})
                 ops (gen/let [operants (gen/list collection-any)
                               operands (gen/vector (gen/elements [conj disj]) (count operants))]
                       (map vector operands operants))]
                (let [basis-cset (schism/with-node :start
                                   (apply schism/convergent-set s))
                      vanilla-result (reduce (fn [memo [f operant]]
                                               (f memo operant))
                                             s
                                             ops)
                      schism-result (schism/with-node :end
                                      (reduce (fn [memo [f operant]]
                                                (f memo operant))
                                              basis-cset
                                              ops))
                      converge-result (schism/with-node :start
                                        (schism/converge basis-cset schism-result))]
                  (= vanilla-result schism-result converge-result))))


================================================
FILE: test/schism/impl/types/list_test.cljc
================================================
(ns schism.impl.types.list-test
  (:require #?(:clj [clojure.test :refer [deftest testing is]]
               :cljs [cljs.test :refer [deftest testing is]])
            #?(:cljs [cljs.reader :as reader])
            [schism.impl.types.list :as slist]
            [schism.node :as node]
            [schism.impl.protocols :as proto])
  #?(:clj (:import schism.impl.types.list.ConvergentList)))


(deftest basic-IPC-ops
  (testing "Equiv for lists"
    (is (= (slist/new-list) '()))
    (is (= (slist/new-list :a true) '(:a true))))
  (testing "Empty for lists"
    (is (= (empty (slist/new-list :a true)) (empty '(:a true)))))
  (testing "Count for lists"
    (is (= (count (slist/new-list :a true)) (count '(:a true)))))
  (testing "Conj for lists"
    (is (= (conj (slist/new-list) [:a true]) (conj '() [:a true])))
    (is (= (conj (slist/new-list :a true) [:a false]) (conj '(:a true) [:a false])))))

(deftest basic-seq-ops
  (testing "conj"
    (is (= (conj (slist/new-list :a true) :a) (conj '(:a true) :a))))
  (testing "rest"
    (is (= (rest (slist/new-list :a true)) (rest '(:a true))))))

(deftest converge-test
  (testing "Converge after concurrent additions on another node."
    (node/initialize-node! :converge-test-origin)
    ;; Can only rely on millisecond time scales, so sleep 1 second
    ;; between ops so that there's some non-zero passage of time
    (let [transfer (-> (slist/new-list true :a)
                       (conj [:b 3]))
          other (node/with-node :converge-test-other-node
                  (conj transfer [:c :d]))
          result (proto/synchronize transfer other)]
      (is (= result '([:c :d] [:b 3] true :a)))))
  (testing "Rest on another node mirrored locally after converge."
    (node/initialize-node! :converge-test-origin)
    (let [transfer (slist/new-list :a true :b 3 :c :d)
          other (node/with-node :converge-test-other-node
                  (rest transfer))
          result (proto/synchronize transfer other)]
      (is (= other '(true :b 3 :c :d)))
      (is (= result '(true :b 3 :c :d))))))

(deftest seqable-test
  (testing "Can turn a CLIST into a seq"
    (is (= (seq (slist/new-list :a true :b 3 :c :d))
           (seq (list :a true :b 3 :c :d))))))

(deftest string-test
  (testing "Prints to console readably, even though edn is verbose"
    (is (= (str (slist/new-list :a true :b 3 :c :d))
           (str (list :a true :b 3 :c :d))))))

(deftest serialization-test
  (testing "Round trip serialization generates the same structure."
    (let [^ConvergentList origin (-> (slist/new-list :a true :b 3 :c :d)
                                     (conj [:d :quux])
                                     rest)
          ^ConvergentList round-tripped (-> origin
                                            pr-str
                                            #?(:clj read-string
                                               :cljs reader/read-string))]
      (is (= (.-data origin) (.-data round-tripped)))
      (is (= (.-vclock origin) (.-vclock round-tripped)))
      (is (= (.-insertions origin) (.-insertions round-tripped))))))

(deftest hashing-test
  (testing "Hashes to the same value as an equivalent list"
    (is (= (hash (into (slist/new-list) [[:a true] [:b 3] [:c :d]]))
           (hash (into '() [[:a true] [:b 3] [:c :d]]))))))

(deftest meta-test
  (testing "Metadata on CLISTs"
    (is (= (meta (with-meta (slist/new-list) {:test :data}))
           {:test :data}))))


================================================
FILE: test/schism/impl/types/map_test.cljc
================================================
(ns schism.impl.types.map-test
  (:require #?(:clj [clojure.test :refer [deftest testing is]]
               :cljs [cljs.test :refer [deftest testing is]])
            #?(:cljs [cljs.reader :as reader])
            [schism.impl.types.map :as smap]
            [schism.node :as node]
            [schism.impl.protocols :as proto])
  #?(:clj (:import schism.impl.types.map.Map)))

(deftest basic-IPC-ops
  (testing "Equiv for maps"
    (is (= (smap/new-map) {}))
    (is (= (smap/new-map :a true) {:a true})))
  (testing "Empty for maps"
    (is (= (empty (smap/new-map :a true)) (empty {:a true}))))
  (testing "Count for maps"
    (is (= (count (smap/new-map :a true)) (count {:a true}))))
  (testing "Conj for maps"
    (is (= (conj (smap/new-map) [:a true]) (conj {} [:a true])))
    (is (= (conj (smap/new-map :a true) [:a false]) (conj {:a true} [:a false])))))

(deftest basic-IPS-ops
  (testing "dissoc"
    (is (= (dissoc (smap/new-map :a true) :a) (dissoc {:a true} :a))))
  (testing "contains"
    (is (= (contains? (smap/new-map :a true) :a) (contains? {:a true} :a))))
  (testing "get"
    (is (= (get (smap/new-map :a true) :a) (get {:a true} :a)))))

(deftest converge-test
  (testing "Converge after concurrent additions on another node."
    (node/initialize-node! :converge-test-origin)
    ;; Can only rely on millisecond time scales, so sleep 1 second
    ;; between ops so that there's some non-zero passage of time
    (let [transfer (-> (smap/new-map :a true)
                       (conj [:b 3]))
          other (node/with-node :converge-test-other-node
                  (conj transfer [:c :d]))
          result (proto/synchronize transfer other)]
      (is (= result {:a true :b 3 :c :d}))
      (is (= {:a true :b 3 :c :d} (.-data result)))
      (doseq [[k v] (.-vclock result)]
        (is (#{:converge-test-origin :converge-test-other-node} k))
        (is (instance? #?(:clj java.util.Date
                          :cljs js/Date) v)))
      (is (= #{:converge-test-origin :converge-test-other-node} (set (keys (.-vclock result)))))
      (is (= (set (keys (.-data result))) (set (keys (.-birth-dots result)))))
      (doseq [[key [node time]] (.-birth-dots result)]
        (is (#{:a :b :c} key))
        (is (#{:converge-test-origin :converge-test-other-node} node))
        (is (instance? #?(:clj java.util.Date
                          :cljs js/Date) time)))))
  (testing "Dissoc on another node mirrored locally after converge."
    (node/initialize-node! :converge-test-origin)
    (let [transfer (smap/new-map :a true :b 3 :c :d)
          other (node/with-node :converge-test-other-node
                  (dissoc transfer :c))
          result (proto/synchronize transfer other)]
      (is (= other {:a true :b 3}))
      (is (= result {:a true :b 3})))))

(deftest seqable-test
  (testing "Can turn an ORMWOT into a seq"
    (is (= (seq (smap/new-map :a true :b 3 :c :d))
           (seq (hash-map :a true :b 3 :c :d))))))

(deftest ifn-test
  (testing "Can invoke an ORMWOT"
    (is (= ((smap/new-map :a true :b 3 :c :d) :c)
           ({:a true :b 3 :c :d} :c)))))

(deftest string-test
  (testing "Prints to console readably, even though edn is verbose"
    (is (= (str (smap/new-map :a true :b 3 :c :d))
           (str (hash-map :a true :b 3 :c :d))))))

(deftest serialization-test
  (testing "Round trip serialization generates the same structure."
    (let [^Map origin (-> (smap/new-map :a true :b 3 :c :d)
                          (conj [:d :quux])
                          (dissoc :c))
          ^Map round-tripped (-> origin
                                 pr-str
                                 #?(:clj read-string
                                    :cljs reader/read-string))]
      (is (= (.-data origin) (.-data round-tripped)))
      (is (= (.-vclock origin) (.-vclock round-tripped)))
      (is (= (.-birth-dots origin) (.-birth-dots round-tripped))))))

(deftest hashing-test
  (testing "Hashes to the same value as an equivalent hash-map"
    (is (= (hash (into (smap/new-map) [[:a true] [:b 3] [:c :d]]))
           (hash (into {} [[:a true] [:b 3] [:c :d]]))))))

(deftest meta-test
  (testing "Metadata on ORMWOTs"
    (is (= (meta (with-meta (smap/new-map) {:test :data}))
           {:test :data}))))


================================================
FILE: test/schism/impl/types/nested_map_test.cljc
================================================
(ns schism.impl.types.nested-map-test
  (:require #?(:clj [clojure.test :refer [deftest testing is]]
               :cljs [cljs.test :refer [deftest testing is]])
            #?(:cljs [cljs.reader :as reader])
            [schism.impl.types.nested-map :as nmap]
            [schism.node :as node]
            [schism.impl.protocols :as proto])
  #?(:clj (:import schism.impl.types.nested_map.NestedMap)))

(deftest basic-IPC-ops
  (testing "Equiv for maps"
    (is (= (nmap/new-map) {}))
    (is (= (nmap/new-map :a true) {:a true})))
  (testing "Empty for maps"
    (is (= (empty (nmap/new-map :a true)) (empty {:a true}))))
  (testing "Count for maps"
    (is (= (count (nmap/new-map :a true)) (count {:a true}))))
  (testing "Conj for maps"
    (is (= (conj (nmap/new-map) [:a true]) (conj {} [:a true])))
    (is (= (conj (nmap/new-map :a true) [:a false]) (conj {:a true} [:a false])))))

(deftest basic-IPS-ops
  (testing "dissoc"
    (is (= (dissoc (nmap/new-map :a true) :a) (dissoc {:a true} :a))))
  (testing "contains"
    (is (= (contains? (nmap/new-map :a true) :a) (contains? {:a true} :a))))
  (testing "get"
    (is (= (get (nmap/new-map :a true) :a) (get {:a true} :a)))))

(deftest converge-test
  (testing "Converge after concurrent additions on another node."
    (node/initialize-node! :converge-test-origin)
    ;; Can only rely on millisecond time scales, so sleep 1 second
    ;; between ops so that there's some non-zero passage of time
    (let [transfer (-> (nmap/new-map :a true)
                       (conj [:b 3]))
          other (node/with-node :converge-test-other-node
                  (conj transfer [:c :d]))
          result (proto/synchronize transfer other)]
      (is (= result {:a true :b 3 :c :d}))
      (is (= {:a true :b 3 :c :d} (.-data result)))
      (doseq [[k v] (.-vclock result)]
        (is (#{:converge-test-origin :converge-test-other-node} k))
        (is (instance? #?(:clj java.util.Date
                          :cljs js/Date) v)))
      (is (= #{:converge-test-origin :converge-test-other-node} (set (keys (.-vclock result)))))
      (is (= (set (keys (.-data result))) (set (keys (.-birth-dots result)))))
      (doseq [[key {node :a time :t} :as entry] (.-birth-dots result)]
        (is (#{:a :b :c} key))
        (is (#{:converge-test-origin :converge-test-other-node} node))
        (is (instance? #?(:clj java.util.Date
                          :cljs js/Date) time)))))
  (testing "Dissoc on another node mirrored locally after converge."
    (node/initialize-node! :converge-test-origin)
    (let [transfer (nmap/new-map :a true :b 3 :c :d)
          other (node/with-node :converge-test-other-node
                  (dissoc transfer :c))
          result (proto/synchronize transfer other)]
      (is (= other {:a true :b 3}))
      (is (= result {:a true :b 3}))
      (is (= (contains? (.-birth-dots other) :c) false))
      (is (= (contains? (.-birth-dots result) :c) false)))))

(deftest seqable-test
  (testing "Can turn an ORMWOT into a seq"
    (is (= (seq (nmap/new-map :a true :b 3 :c :d))
           (seq (hash-map :a true :b 3 :c :d))))))

(deftest ifn-test
  (testing "Can invoke an ORMWOT"
    (is (= ((nmap/new-map :a true :b 3 :c :d) :c)
           ({:a true :b 3 :c :d} :c)))))

(deftest string-test
  (testing "Prints to console readably, even though edn is verbose"
    (is (= (str (nmap/new-map :a true :b 3 :c :d))
           (str (hash-map :a true :b 3 :c :d))))))

(deftest serialization-test
  (testing "Round trip serialization generates the same structure."
    (let [^NestedMap origin (-> (nmap/new-map :a true :b 3 :c :d)
                          (conj [:d :quux])
                          (dissoc :c))
          ^NestedMap round-tripped (-> origin
                                 pr-str
                                 #?(:clj read-string
                                    :cljs reader/read-string))]
      (is (= (.-data origin) (.-data round-tripped)))
      (is (= (.-vclock origin) (.-vclock round-tripped)))
      (is (= (.-birth-dots origin) (.-birth-dots round-tripped))))))

(deftest hashing-test
  (testing "Hashes to the same value as an equivalent hash-map"
    (is (= (hash (into (nmap/new-map) [[:a true] [:b 3] [:c :d]]))
           (hash (into {} [[:a true] [:b 3] [:c :d]]))))))

(deftest meta-test
  (testing "Metadata on ORMWOTs"
    (is (= (meta (with-meta (nmap/new-map) {:test :data}))
           {:test :data}))))

(deftest path-atomicity-test
  (testing "Concurrent modification of a subtree by two nodes converges the atomic values"
    (node/initialize-node! :path-atomicity-origin)
    (let [original (-> (nmap/new-map :a true)
                       (assoc-in [:b :c] true)
                       (#(node/with-node :derivation-node-a
                           (assoc-in % [:b :d] {:e 3 :f "frog"}))))
          derivation-a (node/with-node :derivation-node-a
                         (assoc-in original [:b :d :f] "hog"))
          derivation-b (update-in original [:b :d :e] inc)
          result (proto/synchronize derivation-b derivation-a)]
      (is (= original {:a true
                       :b {:c true
                           :d {:e 3
                               :f "frog"}}}))
      (is (= result {:a true
                     :b {:c true
                         :d {:e 4
                             :f "hog"}}})))))

(deftest vector-conjs-compose
  (testing "Two conjs on different nodes yield both their conjs on convergence and do not overwrite"
    (node/initialize-node! :vector-conjs-origins)
    (let [original (-> (nmap/new-map :a [1])
                       (#(node/with-node :derivation-node-a
                           (assoc-in % [:b] [2]))))
          derivation-a (node/with-node :derivation-node-a
                         (update-in original [:a] conj 2))
          derivation-b (node/with-node :derivation-node-b
                         (update-in original [:a] conj 3))
          result (proto/synchronize derivation-b derivation-a)]
      (is (= original {:a [1]
                       :b [2]}))
      (is (= derivation-a {:a [1 2]
                           :b [2]}))
      (is (= derivation-b {:a [1 3]
                           :b [2]}))
      (is (= result {:a [1 2 3]
                     :b [2]})))))

(deftest interesting-map-inits
  (testing "{-1 [0]}"
    (node/initialize-node! :interesting-map-inits)
    (let [v (nmap/new-map -1 [0])]
      (is (= {-1 [0]} (.-data v)))
      (is (= :interesting-map-inits (get-in (.-birth-dots v) [-1 0 :a])))
      (is (= -1 (get-in (.-birth-dots v) [-1 0 :i])))
      (is (instance? #?(:clj java.util.Date
                        :cljs js/Date) (get-in (.-birth-dots v) [-1 0 :t])))))
  (testing "{0 [[0] 0]}"
    (node/initialize-node! :interesting-map-inits)
    (let [v (nmap/new-map 0 [[0] 0])]
      (is (= {0 [[0] 0]} (.-data v)))
      (is (= :interesting-map-inits (get-in (.-birth-dots v) [0 0 0 :a])))
      (is (= -1 (get-in (.-birth-dots v) [0 0 0 :i])))
      (is (instance? #?(:clj java.util.Date
                        :cljs js/Date) (get-in (.-birth-dots v) [0 0 0 :t])))))
  (testing "{0 [0 0] -1 [0 0 0 0 0 0] -2 0}"
    (node/initialize-node! :interesting-map-inits)
    (let [v (nmap/new-map 0 [0 0] -1 [0 0 0 0 0 0] -2 0)]
      (is (= {0 [0 0] -1 [0 0 0 0 0 0] -2 0} (.-data v)))
      (is (= :interesting-map-inits (get-in (.-birth-dots v) [0 0 :a])))
      (is (= -1 (get-in (.-birth-dots v) [0 0 :i])))
      (is (instance? #?(:clj java.util.Date
                        :cljs js/Date) (get-in (.-birth-dots v) [0 0 :t])))))
  (testing "{0 [0 0] -1 [0 0 0 0 0 0] -2 0}"
    (node/initialize-node! :interesting-map-inits)
    (let [v (nmap/new-map 0 [0 0] -1 [0 0 0 0 0 0] -2 0)]
      (is (= {0 [0 0] -1 [0 0 0 0 0 0] -2 0} (.-data v)))
      (is (= :interesting-map-inits (get-in (.-birth-dots v) [0 0 :a])))
      (is (= -1 (get-in (.-birth-dots v) [0 0 :i])))
      (is (instance? #?(:clj java.util.Date
                        :cljs js/Date) (get-in (.-birth-dots v) [0 0 :t])))))
  (testing "{{} [0 0 0 0 0 0 0 0] #{} 0}"
    (node/initialize-node! :interesting-map-inits)
    (let [v (nmap/new-map {} [0 0 0 0 0 0 0 0] #{} 0)]
      (is (= {{} [0 0 0 0 0 0 0 0] #{} 0} (.-data v)))
      (is (= :interesting-map-inits (get-in (.-birth-dots v) [{} 0 :a])))
      (is (= -1 (get-in (.-birth-dots v) [{} 0 :i])))
      (is (instance? #?(:clj java.util.Date
                        :cljs js/Date) (get-in (.-birth-dots v) [{} 0 :t]))))))


================================================
FILE: test/schism/impl/types/nested_vector_test.cljc
================================================
(ns schism.impl.types.nested-vector-test
  (:require #?(:clj [clojure.test :refer [deftest testing is]]
               :cljs [cljs.test :refer [deftest testing is]])
            #?(:cljs [cljs.reader :as reader])
            [schism.impl.types.nested-vector :as nvector]
            [schism.node :as node]
            [schism.impl.protocols :as proto])
  #?(:clj (:import schism.impl.types.nested_vector.NestedVector)))

(deftest basic-IPC-ops
  (testing "Equiv for maps"
    (is (= (nvector/new-vector) []))
    (is (= (nvector/new-vector :a true) [:a true])))
  (testing "Empty for maps"
    (is (= (empty (nvector/new-vector :a true)) (empty [:a true]))))
  (testing "Count for maps"
    (is (= (count (nvector/new-vector :a true)) (count [:a true]))))
  (testing "Conj for maps"
    (is (= (conj (nvector/new-vector) [:a true]) (conj [] [:a true])))
    (is (= (conj (nvector/new-vector :a true) [:a false]) (conj [:a true] [:a false])))))

(deftest basic-vector-ops
  (testing "peek"
    (is (= (peek (nvector/new-vector :a true)) (peek [:a true]))))
  (testing "pop"
    (is (= (pop (nvector/new-vector :a true)) (pop [:a true])))))

(deftest converge-test
  (testing "Converge after concurrent additions on another node."
    (node/initialize-node! :converge-test-origin)
    ;; Can only rely on millisecond time scales, so sleep 1 second
    ;; between ops so that there's some non-zero passage of time
    (let [transfer (-> (nvector/new-vector :a true)
                       (conj 3))
          other (node/with-node :converge-test-other-node
                  (conj transfer :d))
          result (proto/synchronize transfer other)]
      (is (= result [:a true 3 :d]))
      (is (= [:a true 3 :d] (.-data result)))
      (doseq [[k v] (.-vclock result)]
        (is (#{:converge-test-origin :converge-test-other-node} k))
        (is (instance? #?(:clj java.util.Date
                          :cljs js/Date) v)))
      (is (= #{:converge-test-origin :converge-test-other-node} (set (keys (.-vclock result)))))
      (is (= (count (.-data result)) (count (.-insertions result))))
      (doseq [{node :a time :t} (.-insertions result)]
        (is (#{:converge-test-origin :converge-test-other-node} node))
        (is (instance? #?(:clj java.util.Date
                          :cljs js/Date) time)))))
  (testing "Pop on another node mirrored locally after converge."
    (node/initialize-node! :converge-test-origin)
    (let [transfer (nvector/new-vector :a true :b 3 :c :d)
          other (node/with-node :converge-test-other-node
                  (pop transfer))
          result (proto/synchronize transfer other)]
      (is (= other [:a true :b 3 :c]))
      (is (= result [:a true :b 3 :c]))
      (is (= (count (.-insertions other)) 5))
      (is (= (count (.-insertions result)) 5)))))

(deftest seqable-test
  (testing "Can turn an nested vector into a seq"
    (is (= (seq (nvector/new-vector :a true :b 3 :c :d))
           (seq (vector :a true :b 3 :c :d))))))

(deftest ifn-test
  (testing "Can invoke a vector"
    (is (= ((nvector/new-vector :a true :b 3 :c :d) 0)
           ([:a true :b 3 :c :d] 0)
           :a))))

(deftest string-test
  (testing "Prints to console readably, even though edn is verbose"
    (is (= (str (nvector/new-vector :a true :b 3 :c :d))
           (str (vector :a true :b 3 :c :d))))))

(deftest serialization-test
  (testing "Round trip serialization generates the same structure."
    (let [^NestedVector origin (-> (nvector/new-vector :a true :b 3 :c :d)
                                   (conj [:d :quux])
                                   pop)
          ^NestedVector round-tripped (-> origin
                                          pr-str
                                          #?(:clj read-string
                                             :cljs reader/read-string))]
      (is (= (.-data origin) (.-data round-tripped)))
      (is (= (.-vclock origin) (.-vclock round-tripped)))
      (is (= (.-insertions origin) (.-insertions round-tripped))))))

(deftest hashing-test
  (testing "Hashes to the same value as an equivalent vector"
    (is (= (hash (into (nvector/new-vector) [[:a true] [:b 3] [:c :d]]))
           (hash (into [] [[:a true] [:b 3] [:c :d]]))))))

(deftest meta-test
  (testing "Metadata on nested vectors"
    (is (= (meta (with-meta (nvector/new-vector) {:test :data}))
           {:test :data}))))

(deftest path-atomicity-test
  (testing "Concurrent modification of a subtree by two nodes converges the atomic values"
    (node/initialize-node! :path-atomicity-origin)
    (let [original (-> (nvector/new-vector :a true)
                       (assoc-in [2 :b] true)
                       (#(node/with-node :derivation-node-a
                           (assoc-in % [3 :c] {:e 3 :f "frog"}))))
          derivation-a (node/with-node :derivation-node-a
                         (assoc-in original [3 :c :f] "hog"))
          derivation-b (update-in original [3 :c :e] inc)
          result (proto/synchronize derivation-b derivation-a)]
      (is (= original [:a true {:b true} {:c {:e 3 :f "frog"}}]))
      (is (= result [:a true {:b true} {:c {:e 4 :f "hog"}}])))))

(deftest vector-conjs-compose
  (testing "Two conjs on different nodes yield both their conjs on convergence and do not overwrite"
    (node/initialize-node! :vector-conjs-origins)
    (let [original (-> (nvector/new-vector :a [1])
                       (#(node/with-node :derivation-node-a
                           (assoc-in % [2] [2]))))
          derivation-a (node/with-node :derivation-node-a
                         (update-in original [1] conj 2))
          derivation-b (node/with-node :derivation-node-b
                         (update-in original [1] conj 3))
          result (proto/synchronize derivation-b derivation-a)]
      (is (= original [:a [1] [2]]))
      (is (= derivation-a [:a [1 2] [2]]))
      (is (= derivation-b [:a [1 3] [2]]))
      (is (= result [:a [1 2 3] [2]])))))

(deftest interesting-vector-inits
  (testing "Empty vector stacked 2 deep"
    (node/initialize-node! :interesting-vector-inits)
    (let [v (nvector/new-vector [[]])]
      (is (= [[[]]] (.-data v)))
      (is (= :interesting-vector-inits (get-in (.-insertions v) [0 0 :a])))
      (is (= -1 (get-in (.-insertions v) [0 0 :i])))
      (is (instance? #?(:clj java.util.Date
                        :cljs js/Date) (get-in (.-insertions v) [0 0 :t]))))))


================================================
FILE: test/schism/impl/types/set_test.cljc
================================================
(ns schism.impl.types.set-test
  (:require #?(:clj [clojure.test :refer [deftest testing is are]]
               :cljs [cljs.test :refer [deftest testing is are]])
            #?(:cljs [cljs.reader :as reader])
            [schism.impl.types.set :as sset]
            [schism.node :as node]
            [schism.impl.protocols :as proto])
  #?(:clj (:import schism.impl.types.set.Set)))

(deftest basic-IPC-ops
  (testing "Equiv for sets"
    (is (= (sset/new-set) #{}))
    (is (= (sset/new-set :a) #{:a})))
  (testing "Empty for sets"
    (is (= (empty (sset/new-set :a)) (empty #{:a}))))
  (testing "Count for sets"
    (is (= (count (sset/new-set :a)) (count #{:a}))))
  (testing "Conj for sets"
    (is (= (conj (sset/new-set) :a) (conj #{} :a)))
    (is (= (conj (sset/new-set :a) :a) (conj #{:a} :a)))))

(deftest basic-IPS-ops
  (testing "disjoin"
    (is (= (disj (sset/new-set :a) :a) (disj #{:a} :a))))
  (testing "contains"
    (is (= (contains? (sset/new-set :a) :a) (contains? #{:a} :a))))
  (testing "get"
    (is (= (get (sset/new-set :a) :a) (get #{:a} :a)))))

(deftest converge-test
  (testing "Converge after concurrent additions on another node."
    (node/initialize-node! :converge-test-origin)
    ;; Can only rely on millisecond time scales, so sleep 1 second
    ;; between ops so that there's some non-zero passage of time
    (let [transfer (-> (sset/new-set :a)
                       (conj :b))
          other (node/with-node :converge-test-other-node
                  (conj transfer :c))
          result (proto/synchronize transfer other)]
      (is (= result #{:a :b :c}))
      (is (= #{:a :b :c} (.-data result)))
      (doseq [[k v] (.-vclock result)]
        (is (#{:converge-test-origin :converge-test-other-node} k))
        (is (instance? #?(:clj java.util.Date
                          :cljs js/Date) v)))
      (is (= #{:converge-test-origin :converge-test-other-node} (set (keys (.-vclock result)))))
      (is (= (.-data result) (set (keys (.-birth-dots result)))))
      (doseq [[element [node time]] (.-birth-dots result)]
        (is (#{:a :b :c} element))
        (is (#{:converge-test-origin :converge-test-other-node} node))
        (is (instance? #?(:clj java.util.Date
                          :cljs js/Date) time)))))
  (testing "Disj on another node mirrored locally after converge."
    (node/initialize-node! :converge-test-origin)
    (let [transfer (sset/new-set :a :b :c)
          other (node/with-node :converge-test-other-node
                  (disj transfer :c))
          result (proto/synchronize transfer other)]
      (is (= other #{:a :b}))
      (is (= result #{:a :b}))))
  (testing "Concurrent converges will not resolve to a
  mutually-exclusive addition when vector clocks will support it."
    (node/initialize-node! :converge-test-origin)
    (let [transfer (-> (sset/new-set :a)
                       (conj :b))
          iterate (conj transfer :d)
          other (node/with-node :converge-test-other-node
                  (conj transfer :c))
          result (proto/synchronize iterate other)]
      (is (= result #{:a :b :c :d})))))

(deftest seqable-test
  (testing "Can turn an ORSWOT into a seq"
    (is (= (seq (sset/new-set :a :b :c))
           (seq (hash-set :a :b :c))))))

(deftest ifn-test
  (testing "Can invoke an ORSWOT"
    (is (= ((sset/new-set :a :b :c) :c)
           (#{:a :b :c} :c)))))

(deftest string-test
  (testing "Prints to console readably, even though edn is verbose"
    (is (= (str (sset/new-set :a :b :c))
           (str (hash-set :a :b :c))))))

(deftest serialization-test
  (testing "Round trip serialization generates the same structure."
    (let [^Set origin (-> (sset/new-set :a :b :c)
                          (conj :d)
                          (disj :c))
          ^Set round-tripped (-> origin
                                 pr-str
                                 #?(:clj read-string
                                    :cljs reader/read-string))]
      (is (= (.-data origin) (.-data round-tripped)))
      (is (= (.-vclock origin) (.-vclock round-tripped)))
      (is (= (.-birth-dots origin) (.-birth-dots round-tripped))))))

(deftest hashing-test
  (testing "Hashes to the same value as an equivalent hash-set"
    (is (= (hash (into (sset/new-set) [:a :b :c]))
           (hash (into #{} [:a :b :c]))))))

(deftest meta-test
  (testing "Metadata on ORSWOTs"
    (is (= (meta (with-meta (sset/new-set) {:test :data}))
           {:test :data}))))


================================================
FILE: test/schism/impl/types/vector_test.cljc
================================================
(ns schism.impl.types.vector-test
  (:require #?(:clj [clojure.test :refer [deftest testing is]]
               :cljs [cljs.test :refer [deftest testing is]])
            #?(:cljs [cljs.reader :as reader])
            [schism.impl.types.vector :as svector]
            [schism.node :as node]
            [schism.impl.protocols :as proto])
  #?(:clj (:import schism.impl.types.vector.ConvergentVector)))

(deftest basic-IPC-ops
  (testing "Equiv for vectors"
    (is (= (svector/new-vector) []))
    (is (= (svector/new-vector :a true) [:a true])))
  (testing "Empty for vectors"
    (is (= (empty (svector/new-vector :a true)) (empty [:a true]))))
  (testing "Count for vectors"
    (is (= (count (svector/new-vector :a true)) (count [:a true]))))
  (testing "Conj for vectors"
    (is (= (conj (svector/new-vector) [:a true]) (conj [] [:a true])))
    (is (= (conj (svector/new-vector :a true) [:a false]) (conj [:a true] [:a false])))))

(deftest basic-vector-ops
  (testing "conj"
    (is (= (conj (svector/new-vector :a true) :a) (conj [:a true] :a))))
  (testing "peek"
    (is (= (peek (svector/new-vector :a true)) (peek [:a true]))))
  (testing "pop"
    (is (= (pop (svector/new-vector :a true)) (pop [:a true])))))

(deftest converge-test
  (testing "Converge after concurrent additions on another node."
    (node/initialize-node! :converge-test-origin)
    ;; Can only rely on millisecond time scales, so sleep 1 second
    ;; between ops so that there's some non-zero passage of time
    (let [transfer (-> (svector/new-vector true :a)
                       (conj [:b 3]))
          other (node/with-node :converge-test-other-node
                  (conj transfer [:c :d]))
          result (proto/synchronize transfer other)]
      (is (= other [true :a [:b 3] [:c :d]]))
      (is (= result [true :a [:b 3] [:c :d]]))))
  (testing "Pop on another node mirrored locally after converge."
    (node/initialize-node! :converge-test-origin)
    (let [transfer (svector/new-vector :a true :b 3 :c :d)
          other (node/with-node :converge-test-other-node
                  (pop transfer))
          result (proto/synchronize transfer other)]
      (is (= other [:a true :b 3 :c]))
      (is (= result [:a true :b 3 :c])))))

(deftest seqable-test
  (testing "Can turn a CVECTOR into a seq"
    (is (= (seq (svector/new-vector :a true :b 3 :c :d))
           (seq (vector :a true :b 3 :c :d))))))

(deftest string-test
  (testing "Prints to console readably, even though edn is verbose"
    (is (= (str (svector/new-vector :a true :b 3 :c :d))
           (str (vector :a true :b 3 :c :d))))))

(deftest serialization-test
  (testing "Round trip serialization generates the same structure."
    (let [^ConvergentVector origin (-> (svector/new-vector :a true :b 3 :c :d)
                                       (conj [:d :quux])
                                       pop)
          ^ConvergentVector round-tripped (-> origin
                                              pr-str
                                              #?(:clj read-string
                                                 :cljs reader/read-string))]
      (is (= (.-data origin) (.-data round-tripped)))
      (is (= (.-vclock origin) (.-vclock round-tripped)))
      (is (= (.-insertions origin) (.-insertions round-tripped))))))

(deftest hashing-test
  (testing "Hashes to the same value as an equivalent vector"
    (is (= (hash (into (svector/new-vector) [[:a true] [:b 3] [:c :d]]))
           (hash (into [] [[:a true] [:b 3] [:c :d]]))))))

(deftest meta-test
  (testing "Metadata on CVECTORs"
    (is (= (meta (with-meta (svector/new-vector) {:test :data}))
           {:test :data}))))


================================================
FILE: test/schism/impl/vector_clock_test.cljc
================================================
(ns schism.impl.vector-clock-test
  (:require #?(:clj [clojure.test :refer [deftest testing is]]
               :cljs [cljs.test :refer [deftest testing is]])
            [schism.impl.vector-clock :as vc]
            [schism.impl.protocols :as proto]
            [schism.node :as node]))


(defrecord SimpleClocked [last-time vclock]
  proto/Vclocked
  (get-clock [_] vclock)
  (with-clock [this new-clock]
    (assoc this :vclock new-clock)))

(deftest update-clock-test
  (node/initialize-node! :clock-test-node)
  (let [test (->SimpleClocked nil {})
        updated (vc/update-clock time test
                                 (assoc test :last-time time))
        time (:last-time updated)]
    (is (= {:clock-test-node time} (proto/get-clock updated)))
    (is #?(:clj (instance? java.util.Date time)
           :cljs true))))


================================================
FILE: test/schism/node_test.cljc
================================================
(ns schism.node-test
  (:require #?(:clj [clojure.test :refer [deftest testing is]]
               :cljs [cljs.test :refer [deftest testing is]])
            [schism.node :as node])
  #?(:cljs (:require-macros [schism.node :as node])))

(deftest initialize-node!-test
  (testing "With no arg"
    (node/initialize-node!)
    (is (some? node/*current-node*)))
  (testing "with an arg"
    (node/initialize-node! "a string node id")
    (is (= "a string node id" node/*current-node*))))

(deftest with-node-test
  (testing "with-node clobbers other set values for current-node"
    (node/initialize-node! :with-node-id)
    (is (= :with-node-id node/*current-node*))
    (node/with-node :another-id
      (is (not= :with-node-id node/*current-node*))
      (is (= :another-id node/*current-node*)))))


================================================
FILE: test/schism/test.cljc
================================================
(ns schism.test
  (:require #?(:cljs [doo.runner :refer-macros [doo-tests]])
            schism.node-test
            schism.core-test
            schism.impl.vector-clock-test
            schism.impl.types.set-test
            schism.impl.types.map-test
            schism.impl.types.list-test
            schism.impl.types.vector-test
            schism.impl.types.nested-map-test
            schism.impl.types.nested-vector-test))

#?(:cljs (doo-tests 'schism.node-test
                    'schism.core-test
                    'schism.impl.vector-clock-test
                    'schism.impl.types.set-test
                    'schism.impl.types.map-test
                    'schism.impl.types.list-test
                    'schism.impl.types.vector-test
                    'schism.impl.types.nested-map-test
                    'schism.impl.types.nested-vector-test))
Download .txt
gitextract_v04t0_24/

├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── project.clj
├── resources/
│   └── data_readers.cljc
├── src/
│   └── schism/
│       ├── core.cljc
│       ├── impl/
│       │   ├── core.cljc
│       │   ├── protocols.cljc
│       │   ├── types/
│       │   │   ├── list.cljc
│       │   │   ├── map.cljc
│       │   │   ├── nested_map.cljc
│       │   │   ├── nested_vector.cljc
│       │   │   ├── nesting_util.cljc
│       │   │   ├── set.cljc
│       │   │   └── vector.cljc
│       │   └── vector_clock.cljc
│       └── node.cljc
└── test/
    └── schism/
        ├── core_test.cljc
        ├── impl/
        │   ├── types/
        │   │   ├── list_test.cljc
        │   │   ├── map_test.cljc
        │   │   ├── nested_map_test.cljc
        │   │   ├── nested_vector_test.cljc
        │   │   ├── set_test.cljc
        │   │   └── vector_test.cljc
        │   └── vector_clock_test.cljc
        ├── node_test.cljc
        └── test.cljc
Condensed preview — 28 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (144K chars).
[
  {
    "path": ".gitignore",
    "chars": 114,
    "preview": "/target\n/classes\n/checkouts\npom.xml\npom.xml.asc\n*.jar\n*.class\n/.lein-*\n/.nrepl-port\n.hgignore\n.hg/\nnode_modules/*\n"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 766,
    "preview": "# Change Log\nAll notable changes to this project will be documented in this file. This change log follows the convention"
  },
  {
    "path": "LICENSE",
    "chars": 1053,
    "preview": "Copyright 2021 Alex Redington\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this soft"
  },
  {
    "path": "README.md",
    "chars": 6360,
    "preview": "# schism\n\nA batteries included library of CRDT implementations of Clojure's core\ndata types: Sets, Maps, Vectors, and Li"
  },
  {
    "path": "project.clj",
    "chars": 1311,
    "preview": "(defproject com.holychao/schism \"0.1.2\"\n  :description \"First Class CRDTs for Clojure\"\n  :url \"https://github.com/aredin"
  },
  {
    "path": "resources/data_readers.cljc",
    "chars": 371,
    "preview": "{schism/set           schism.impl.types.set/read-edn-set\n schism/map           schism.impl.types.map/read-edn-map\n schis"
  },
  {
    "path": "src/schism/core.cljc",
    "chars": 3305,
    "preview": "(ns schism.core\n  (:require [schism.impl.types.set :as sset]\n            [schism.impl.types.map :as smap]\n            [s"
  },
  {
    "path": "src/schism/impl/core.cljc",
    "chars": 4199,
    "preview": "(ns schism.impl.core\n  (:require [schism.node :as node]\n            [clojure.set :as set])\n  #?(:clj (:import (java.util"
  },
  {
    "path": "src/schism/impl/protocols.cljc",
    "chars": 727,
    "preview": "(ns schism.impl.protocols)\n\n(defprotocol Convergent\n  (synchronize [convergent other]\n    \"Synchronizes `convergent` wit"
  },
  {
    "path": "src/schism/impl/types/list.cljc",
    "chars": 9698,
    "preview": "(ns schism.impl.types.list\n  \"Definition and support for Schism's Convergent List type. The\n  convergent list is a simpl"
  },
  {
    "path": "src/schism/impl/types/map.cljc",
    "chars": 11068,
    "preview": "(ns schism.impl.types.map\n  \"Definition and support for Schism's Convergent Map type, an ORMWOT\n  implemented on top of "
  },
  {
    "path": "src/schism/impl/types/nested_map.cljc",
    "chars": 12771,
    "preview": "(ns schism.impl.types.nested-map\n  \"Definition and support for Schism's Deeply Convergent Map type, an\n  ORMWOT implemen"
  },
  {
    "path": "src/schism/impl/types/nested_vector.cljc",
    "chars": 13586,
    "preview": "(ns schism.impl.types.nested-vector\n  \"Definition and support for Schism's Deeply Convergent Vector\n  type. The converge"
  },
  {
    "path": "src/schism/impl/types/nesting_util.cljc",
    "chars": 4578,
    "preview": "(ns schism.impl.types.nesting-util\n  \"Utility functions for supporting nested collections.\"\n  (:require [clojure.data :r"
  },
  {
    "path": "src/schism/impl/types/set.cljc",
    "chars": 9332,
    "preview": "(ns schism.impl.types.set\n  \"Definition and support for Schism's Convergent Set type, an ORSWOT\n  implemented on top of "
  },
  {
    "path": "src/schism/impl/types/vector.cljc",
    "chars": 13960,
    "preview": "(ns schism.impl.types.vector\n  \"Definition and support for Schism's Convergent vector type. The\n  convergent vector is a"
  },
  {
    "path": "src/schism/impl/vector_clock.cljc",
    "chars": 915,
    "preview": "(ns schism.impl.vector-clock\n  \"Utility functions for working with the vector clock of a value that\n  participates in th"
  },
  {
    "path": "src/schism/node.cljc",
    "chars": 751,
    "preview": "(ns schism.node\n  #?(:clj (:import (java.util UUID))))\n\n(def ^:dynamic *current-node* nil)\n\n(defn initialize-node!\n  \"In"
  },
  {
    "path": "test/schism/core_test.cljc",
    "chars": 9910,
    "preview": "(ns schism.core-test\n  (:require [schism.core :as schism :include-macros true]\n            #?(:clj [clojure.test :refer "
  },
  {
    "path": "test/schism/impl/types/list_test.cljc",
    "chars": 3461,
    "preview": "(ns schism.impl.types.list-test\n  (:require #?(:clj [clojure.test :refer [deftest testing is]]\n               :cljs [clj"
  },
  {
    "path": "test/schism/impl/types/map_test.cljc",
    "chars": 4272,
    "preview": "(ns schism.impl.types.map-test\n  (:require #?(:clj [clojure.test :refer [deftest testing is]]\n               :cljs [cljs"
  },
  {
    "path": "test/schism/impl/types/nested_map_test.cljc",
    "chars": 8488,
    "preview": "(ns schism.impl.types.nested-map-test\n  (:require #?(:clj [clojure.test :refer [deftest testing is]]\n               :clj"
  },
  {
    "path": "test/schism/impl/types/nested_vector_test.cljc",
    "chars": 6427,
    "preview": "(ns schism.impl.types.nested-vector-test\n  (:require #?(:clj [clojure.test :refer [deftest testing is]]\n               :"
  },
  {
    "path": "test/schism/impl/types/set_test.cljc",
    "chars": 4484,
    "preview": "(ns schism.impl.types.set-test\n  (:require #?(:clj [clojure.test :refer [deftest testing is are]]\n               :cljs ["
  },
  {
    "path": "test/schism/impl/types/vector_test.cljc",
    "chars": 3678,
    "preview": "(ns schism.impl.types.vector-test\n  (:require #?(:clj [clojure.test :refer [deftest testing is]]\n               :cljs [c"
  },
  {
    "path": "test/schism/impl/vector_clock_test.cljc",
    "chars": 831,
    "preview": "(ns schism.impl.vector-clock-test\n  (:require #?(:clj [clojure.test :refer [deftest testing is]]\n               :cljs [c"
  },
  {
    "path": "test/schism/node_test.cljc",
    "chars": 799,
    "preview": "(ns schism.node-test\n  (:require #?(:clj [clojure.test :refer [deftest testing is]]\n               :cljs [cljs.test :ref"
  },
  {
    "path": "test/schism/test.cljc",
    "chars": 873,
    "preview": "(ns schism.test\n  (:require #?(:cljs [doo.runner :refer-macros [doo-tests]])\n            schism.node-test\n            sc"
  }
]

About this extraction

This page contains the full source code of the aredington/schism GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 28 files (134.9 KB), approximately 35.1k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!